新闻详情

新闻详情

首页 / 资讯中心 / 详情

两阶段鲁棒微网调度实战:关键场景辨别算法与CCG求解全解析

发布时间:2026/9/30 19:26:13来源:尧图网络
两阶段鲁棒微网调度实战:关键场景辨别算法与CCG求解全解析
两阶段鲁棒微网调度这坑我踩过不少。说实话做微网优化调度的人大概率都遇到过这么一个问题风光出力、负荷需求都是不确定的你到底按哪个场景来排计划按最保守的吧成本高得没法看按预期值排吧真出个极端天气系统直接崩给你看。鲁棒优化上手之后又发现另一道坎两阶段问题里那个max-min嵌套结构用通用求解器根本没法直接解得靠分解算法一层层剥开。这个项目把关键场景辨别算法和两阶段鲁棒优化拧在一起直接解决了收敛慢、场景冗余两大痛点我拿到代码后反复测了几轮最终的收敛速度和结果保守性都在可接受范围里值得好好拆一拆。这个项目适合谁如果你正在做微网经济调度、综合能源系统鲁棒优化或者毕业论文正好卡在两阶段鲁棒这块那这篇笔记能帮你省掉不少翻文献、调代码的弯路。我会从模型思路开始讲再到关键场景辨别的实现逻辑然后是MatlabYALMIPCPLEX的具体落地方案最后把调试过程中遇到的坑和解决方案一并整理出来。看不懂公式的也不用慌我会用大白话把每个环节的为什么讲清楚保证你能拿着思路去读代码。1. 为什么微网调度必须用两阶段鲁棒1.1 确定性调度的局限计划赶不上变化先说说最简单的情况。如果风光功率、负荷曲线都是已知的那调度就是一个标准的线性规划问题在满足功率平衡和设备约束的前提下最小化总运行成本。目标函数大概是燃料成本、购电成本、储能损耗成本这些加起来约束就是各时段的功率平衡、机组出力上下限、储能SOC递推方程一次性解出来就完事。但真实系统不是这么运作的。光伏出力随云层变化能在一分钟内掉一半风电的波动更是让人头疼。如果只按预测值做日前计划到了实时运行阶段实际风光出力跟预测值差一截你的调度方案就废了——可能功率不平衡可能储能SOC超出限值严重的话还得切负荷。确定性模型的最大问题就是它把所有不确定性当成了已知值完全没有给意外留余地。1.2 鲁棒优化的核心思想保底思维鲁棒优化的思路跟保险有点像我不赌某个具体场景发生而是假设最坏情况会来然后在这个前提下做决策。代价是解决方案偏保守收益是不管实际来什么场景方案都可行。但这个最坏情况对微网调度来说没那么简单。你不能简单地只针对一个最坏场景做规划因为不同时段、不同设备的不确定性是耦合的——光伏出力低的时候风机可能出力高负荷峰值时刻可能刚好赶上风光双低。所以你需要一个不确定集合Uncertainty Set把所有这些可能的组合都装进去然后再去找最坏的那个。1.3 为什么是两阶段决策有先后这里就要说到两阶段结构了。用学术点的话说第一阶段是here and now决策第二阶段是wait and see决策。放到微网场景里第一阶段日前阶段给定预测信息和不确定集合决定机组的启停计划、与大电网的购售电合同量、储能的日前充放电计划基准值。这些决策一旦定了短时间内改不了。第二阶段实时调整当风光出力、负荷的实际值揭晓后在第一阶段决策不变的约束下通过调整储能出力、可控机组的小幅调节、弃风弃光等手段保证系统实时功率平衡并支付相应的调整成本。两阶段鲁棒优化的核心问题就是找到一个第一阶段决策使得在所有可能的不确定场景下第二阶段的最优调整成本最坏情况被控制在可接受范围同时总成本最小。数学形式写出来就是min_x c^T x max_{u∈U} min_y d^T y这个嵌套结构就是鲁棒优化的灵魂也是最难解的地方。外层的min找日前决策中间的max找最坏场景内层的min算实时调整。三个问题叠在一起直接求解是不可能的必须分解。1.4 两阶段结构在微网里的实际意义说了这么多理论落到工程上两阶段结构到底带来什么好处我认为有三点最明显决策分层清晰。日前阶段的机组启停、购售电合同这种动作慢、代价高的决策和实时阶段的储能微调、切负荷这种响应快、代价低的决策天然就是两个时间尺度的问题。放在一个模型里同时优化反而会掩盖不同决策的性质差异。不确定性定量可控。你可以通过调整不确定集合的大小比如风光出力波动幅度、预算系数来平滑鲁棒保守性和经济性之间的平衡。预算系数小方案经济性更好预算系数大防御能力更强。这个旋钮是确定性模型给不了的。求解框架成熟。两阶段鲁棒虽然难解但学术界已经形成了标准的分解框架尤其是CCGColumns and Constraints Generation列约束生成算法给工程实现提供了非常清晰的路线图。这个后面会详细展开。2. 关键场景辨别算法把复杂度打下来2.1 场景数量大是求解的第一大障碍做鲁棒优化的人都知道不确定集合理论上是连续的包含无穷多个场景。你没法真的去遍历每一个场景工程上常见做法是采样——比如用蒙特卡洛、拉丁超立方采样生成几千个离散场景来近似不确定集合。但采样数量一上去计算量就爆炸了。一个包含24时段、风光负荷三个不确定源的系统就算每个时段只采样20个离散水平组合出来的场景数也是天文数字。就算你只采样500个场景每个场景都要解一次二阶问题一次CCG迭代就是500个线性规划的求解整体算下来CPLEX再快也得等半天。更难受的是这500个场景里大部分是中性的、温和的场景对调度成本的影响很小白白浪费了算力。2.2 关键场景辨别的直觉别对所有场景一视同仁关键场景辨别算法的核心思想其实特别朴素如果你能用少数几个场景就刻画不确定集合中最恶劣的部分那你就不用管那些温和的场景了。先要明确什么是关键场景。在我的定义里一个场景如果它触发的最优调度成本高或者它导致系统运行约束接近临界值电压上限、储能SOC上限、联络线限值那它就是关键场景。反过来那些让系统运行很宽裕、成本很低的场景对鲁棒决策的影响微乎其微。基于这个思路算法大致分两步第一步场景预处理。用聚类算法K-means、DBSCAN或者更专业的谱聚类把原始场景池聚类成若干类然后每类中选取中心场景作为候选关键场景。这一步不是简单的随机抽样而是保留了场景的分布特征避免全部选中极端点而失去代表性。第二步关键度排序。对候选场景做一次快速评估——在某个参考解下计算每个场景对应的调整成本或者检查约束的违反程度。然后按成本或违约程度排序取前几个作为真正的关键场景。这一步等价于在做场景对决策的影响力度量。2.3 关键场景怎么用嵌入CCG框架光筛选出关键场景还不够它必须跟求解框架融合起来才有意义。在CCG迭代里关键场景的角色是主问题的初始场景集合。CCG的标准流程是主问题只带一个初始场景跑求出第一阶段决策然后把决策传给子问题子问题找到最恶劣场景如果这个最恶劣场景带来的成本超过主问题的目标函数说明主问题低估了风险就把这个新场景对应的约束和变量加入主问题再迭代。重复这个过程直到收敛。关键场景辨别算法在这里的作用就是提供一组质量更高的初始场景与其让主问题从一个温和场景起步经过很多次迭代才慢慢逼近最恶劣场景不如一开始就告诉主问题最坏的情况大概是这样的让第一轮主问题解出来就接近最优解迭代次数明显减少。2.4 从特征角度进一步压缩场景实际操作中我还做了两个额外处理来进一步压缩场景规模一是时段滑动窗口相关性特征。单独看某一时刻的风光出力波动意义不大真正影响调度的是前后时段的变化——比如储能能不能在下个时段腾出容量取决于前几个时段的充放情况。所以做聚类的时候我会把场景向量扩展成原始出力加一阶差分再送入聚类算法这样分出来的类在时序形态上更有辨识度。二是用CVaR式尾部风险思想。计算候选场景成本分布的分位数比如95%分位以上的场景才进入关键场景集这样既不会漏掉真正的极端情况也控制了关键场景的总数。3. 模型构建与Matlab代码实现3.1 目标函数怎么设计这个项目的目标函数分两部分。第一阶段成本主要是日前决策成本包括可控机组启停成本和日前购电成本如果第一阶段确定了购电功率的话。第二阶段成本是实时调整成本包括调整后的燃料成本增量、与电网交换功率的偏差惩罚、储能充放电损耗以及可能的可再生能源削减惩罚。用一个简洁的形式表达第二阶段的内层min问题本质上是在最坏不确定场景下通过调整可控变量让实时运行成本最小。注意两点启停变量留在第一阶段不做第二阶段优化因为机组启停在实时阶段根本来不及改变。这是建模时的关键简化也符合实际物理过程。第二阶段目标里要加惩罚项比如储能SOC越限惩罚、功率平衡松弛惩罚。加惩罚项不是偷懒而是为了让子问题在极端场景下依然可行避免因为约束过紧导致无解。数值上取个大数比如1e6就够了但要注意别大到导致数值病态。3.2 约束条件从物理到数学两阶段鲁棒模型的约束分三大类第一阶段约束包含机组出力上下限、最小启停时间约束如果考虑、日前购电合同容量限制、以及功率平衡约束按预测场景建立。功率平衡长这样P_g(t) P_w_fc(t) P_pv_fc(t) P_dis(t) - P_ch(t) P_buy(t) P_load_fc(t)下标fc表示预测值。注意这里用的是一组基础场景的平衡真正的实时调整要到第二阶段才体现。不确定集合约束是风光出力、负荷在集合范围内波动。常用盒式集合加上预算约束P_w(t) ∈ [P_w_low(t), P_w_up(t)] Σ |P_w(t) - P_w_fc(t)| ≤ Γ_w预算系数Γ控制保守程度。Γ0就是确定性模型Γ取到最大就是最保守的全区间盒式模型。我一般取Γ等于时段数的三分之一左右经济性和鲁棒性比较均衡。第二阶段约束在不确定场景给定后要求实时调整后的功率平衡、储能SOC递推、联络线功率限值全部满足。这里引入鲁棒可调变量——实时出力增量、储能充放电调整量、切负荷量等它们是不确定参数u的函数。第二阶段的最关键处理是消除max-min嵌套。对内层min问题求对偶将min转换成max再与外层max合并成max问题。这个过程会引入对偶变量与不确定参数相乘的双线性项——比如对偶变量乘风光出力偏差。处理这类非凸项标准做法是用大M法引入0-1辅助变量做线性化。这部分代码是子问题的核心也是初学者最容易卡住的地方。3.3 Matlab整体代码结构我的代码结构大致如下这个结构清晰也好扩展主程序 main_CCG_robust_microgrid.m ├── 参数设置段读取负荷、风光、电价数据设置不确定集合参数 ├── 场景生成段拉丁超立方采样生成场景池 ├── 关键场景辨别段K-means聚类 关键度排序 ├── CCG迭代段主问题求解、子问题求解、收敛判断 └── 结果输出段出图、成本分析核心函数是这几个function [x_opt, fval] solve_MP(KeyScenarioSet, params) % 主问题求解给定关键场景集构建并求解第一阶段辅助变量问题 function [obj_ub, u_star] solve_SP(x_opt, params) % 子问题求解固定一阶段决策找最恶劣场景含对偶变换和big-M线性化 function key_scenarios identify_key_scenarios(scenario_pool, params) % 关键场景识别聚类关键度排序建模工具用的是YALMIP求解器用CPLEX。YALMIP在学术圈用得最多建模语法接近数学表达式写完约束条件基本不用改就能调求解器尤其适合快速迭代。CPLEX的鲁棒性和大模型求解速度目前来看还是比开源求解器稳定得多——特别是MIP问题开源求解器在compact formulation上差距明显。3.4 第一阶段建模的关键卫生细节写主问题的时候有几处细节如果不加注意代码跑得通但结果却不受控不确定参数的缩放。风光出力、负荷量级差异大直接建模可能导致数值病态。我习惯把所有功率量折算成标幺值基准设为系统峰值负荷的80%左右求解器数值表现会好很多。辅助变量别乱加。CCG每次迭代都会给主问题新增一组场景对应的约束和变量迭代几十轮后模型规模会膨胀。YALMIP里一旦约束或变量对象被定义进模型后续迭代哪怕它不再被使用也还是算子。为了控制模型规模我在主问题构建时用循环变量结构每次迭代只保留新增场景的约束旧场景的约束保留但不再重复添加变量。这个优化对24时段微网模型很关键不控制的话内存和求解时间会指数上升。目标函数保持一致。主问题中加入辅助变量η用来逼近第二阶段最坏成本但η必须参与主问题的目标函数并作为约束η ≥ 各场景的二阶段成本。这个约束在YALMIP里写清楚否则主问题只是个确定性最小化问题收敛判断会失真。3.5 第二阶段对偶变换的完整流程子问题求解是整个项目的硬骨头。我一步步拆解一下实操时怎么落地第一步内层min线性化。把二阶段优化写成标准LP形式目标最小化 d^T y约束为 A y ≥ b(u)y 是连续变量。第二步取对偶。对偶问题变成最大化 b(u)^T π其中 π 是对偶变量约束 A^T π ≤ dπ ≥ 0。到这一步min已经转化为max。第三步合并max。内层max与外层max合并后子问题变成在(U, π)联合空间上的最大化问题。目标函数是 b(u)^T πU是盒式集合π是凸锥内的对偶变量。这时候目标中有双线性项 u和π相乘问题变成非凸。第四步big-M线性化。对u和π的乘积项做标准big-M线性化引入0-1变量和足够大的常数M。这一步是整个二阶段鲁棒求解最麻烦的一步。M取多大有讲究——太小会削掉可行域太大又导致数值不稳定。我的处理方式是先解一个无U变量的松弛问题从解中估算u的范围和π的量级再乘以安全系数确定M。实测下来这个方法能避免反复试M值。第五步求解混合整数问题MIP。完成线性化后子问题就变成一个标准MIP直接丢给CPLEX求解。解出来的u就是当前迭代下的最恶劣场景。这一步是CCG迭代的弹药每一轮的u方向都不同最终收敛时最新的u就是整个问题的最恶劣场景。4. 算例验证与调试心得4.1 怎么验证模型做对了从简单到复杂我特别不建议一开始就拿完整算例跑。正确的验证路径应该是第一步退化测试。把不确定集合预算系数Γ设成0即不确定集合缩成一个预测点。此时两阶段鲁棒退化成确定性优化。如果代码是对的CCG应该一步收敛结果应该和直接解确定性模型完全一致。如果这一步对不上说明主问题或子问题建模一定有bug。这一步通过率很低的时候我一般会拿一个规模极小3时段2个机组1个储能的手工可算的例子用Excel把结果核对一遍。第二步单调性测试。逐渐增大Γ最优成本应该单调不降。如果中间某点成本突然下降说明某个场景没被正确识别或者模型里存在违规松弛项。检查惩罚项是否过「软」了惩罚系数太小子问题就会倾向用惩罚代替真实成本导致结果异常。第三步场景对比测试。关键场景集从1个逐步增加到10个观察CCG迭代次数和最终成本的下降曲线。排布合理的话迭代次数应该显著减少成本曲线趋于平稳。如果关键场景数量增加但迭代次数没变说明场景辨别算法选出的场景缺乏多样性要检查聚类特征的选择。4.2 参数敏感性预算系数Γ怎么选预算系数Γ是鲁棒模型里最重要的旋钮。我跑了几个典型值Γ取值相对确定性的成本增加最恶劣场景的功率不平衡程度求解时间0确定性0%无法应对波动8秒1/3时段数8%-15%可控范围内45秒1/2时段数15%-25%显著缓解80秒全时段最大35%-50%极端安全210秒从表里能看出来Γ取1/3到1/2时段数是性价比最高的区间。工程上还要结合历史气象数据的波动水平去标定如果风资源丰富且波动大Γ要适当取大一些否则实时调整压力太大。4.3 常见报错与排查速查表我这次调试过程中遇到的典型问题整理成一张表直接抄作业症状可能原因解决方案子问题不可行第二阶段约束过紧添加松弛变量和惩罚项主问题目标值小于子问题值关键场景没传对检查场景集数据传递确保主问题包含所有已识别场景双线性项线性化后求解质量差M值过大用松弛解估算u范围缩小M值CCG长时间不收敛收敛公差太严将Gap容差从1e-6放宽到1e-3内存不足主问题场景变量膨胀使用结构体保存场景删除无用变量YALMIP报no suitable solver求解器路径未配置运行yalmiptest检查求解器是否识别重新添加CPLEX路径4.4 踩过的坑最典型的三个第一个坑是子问题对偶方向写反。max-min问题里inner min取对偶变成max后约束里的不等号方向必须反过来如果照抄原问题顺手写成≤整个子问题最优解就直接偏差。这个错误特别隐蔽因为模型还是能解出个数值但结果不对。我的排查手段是做一个固定场景的手工对偶推导标准三变量LP全流程手推一遍然后把代码得到的对偶变量值和手推答案比对。第二个坑是整数变量留在第二阶段。之前一个版本把储能充放电状态的0-1变量放进了二阶段导致子问题成为一个混合整数问题而对偶变换只能处理线性连续问题结果整个子问题变得不可解。费了很久才解决把状态变量挪到第一阶段第二阶段只做连续功率调整。第三个坑是场景辨别时直接对原始数据做聚类忽略了归一化。光伏出力最大2000kW负荷最大1800kW但储能容量才500kWh量级差异过大聚类结果基本按功率量级拉帮结派而不是按波动形态。归一化到0-1区间再聚类后识别的关键场景才真正有意义。5. 求解性能优化与工程落地的几个技巧5.1 用好求解器的MIP参数子问题线性化后是MIPCPLEX/Gurobi解MIP的策略直接影响性能。我实测下来最有效的方式是相对MIP Gap设为1%或0.5% 启用MIP强调——寻找可行解模式 限制单次MIP求解时间比如最大60秒限制求解时间看起来好像会影响精度但子问题在CCG里的角色是找到更恶劣的场景。稍微次优的场景解也可以驱动主问题改进。关键是最恶劣场景方向要大致对精度其实不需要太高。实践中子问题解的质量轻微下降就能显著加速整体迭代最终结果可能只差0.1%左右完全在工程接受范围内。5.2 热启动策略让主问题从优解附近起步CCG的主问题是一系列结构相似的MIP问题每次迭代只增加若干约束。求解器如果不主动利用上一轮解每次都从头开始分支定界时间浪费很大。我给主问题的0-1启停变量设置了热启动初值把上一轮主问题解出的启停变量值作为本轮MIP的初始可行解。实测这个改动让主问题求解时间下降约40%。具体做法是在CPLEX中设置cplex.MIP.Starts或者在YALMIP的optimize调用中用sdpsettings(solver,cplex,cplex.mip.start,...)传入。不太超纲但效果显著。5.3 并行计算处理大规模场景池场景辨别阶段如果场景池很大比如5000个场景每场景要解一个LP来评估关键度这个阶段天然适合并行。用MATLAB的parfor按场景分配任务每个worker独立求解评估最后汇总排序。需要注意的是parfor里YALMIP模型的构建要在worker内部独立完成不能共享模型句柄所以我把参数打包成结构体传入函数每个worker各自建模型求解。5.4 不确定性数据从哪里来场景生成方法关键场景辨别算法的起点是场景池没场景池一切都是空中楼阁。场景生成我推荐两种基于历史数据驱动的场景生成从历史实测数据中抽取风光、负荷样本用非参数核密度估计拟合分布再从中采样。这种方式对数据量的要求高但不依赖分布假设跟实际数据贴合度好。基于正态/威布尔分布的蒙特卡洛采样风电用威布尔分布光伏用Beta分布负荷用正态分布。生成速度快适合没有历史数据的场景。但分布参数的标定需要谨慎用历史数据去拟合参数后再采样。我个人偏好数据驱动加分布拟合的混合方案先用历史数据拟合分布参数再超大样本蒙特卡洛最后做场景缩减。这样生成的场景池在多样性和样本量上都够支撑关键场景辨识。6. 基于个人经验的扩展思路做完这个项目我最大的体会是鲁棒优化的难点从来不在数学公式的漂亮而在于把物理直觉和数据特征转换成能落地求解的代码结构。关键场景辨别算法本质上是给你提供了一个自由度——你可以根据不同时段、不同季节、不同负荷特性动态决定要关注哪些场景。比如夏天傍晚的尖峰负荷时段关键场景往往对应光伏出力低气温高导致制冷负荷上涨的组合而深夜时段关键场景又可能变成风电骤减叠加低谷负荷。这种动态辨识能力是固定场景集方法给不了的。最后再分享一个小技巧做结果分析时不要只看总成本和收敛曲线。把CCG每轮迭代识别出的最恶劣场景画在同一条时间轴上你会直观看到模型到底在防什么。我做完这个项目的第一张图就是不同迭代轮次的恶劣场景对比——能清楚地看到初始几轮场景都比较温和后期逐渐出现极端风光双低但负荷高企的组合到了收敛时最恶劣场景的结构相对稳定。这种可视化不仅对调试有价值放在论文里也能很直观地说明为什么需要关键场景辨别算法来加速鲁棒求解。如果你准备在这个框架上继续扩展我的建议是优先往两个方向走一是把储能寿命衰减模型加进去鲁棒框架下的储能充放电策略会明显更温和二是考虑电价的实时波动参与不确定集合也就是日前—实时两级电价联动这会显著影响购售电策略。主体结构不用动扩展第三阶段或加权不确定项即可。祝顺利。
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

LLM可观测性平台Hindsight:请求级追踪与异常诊断 2026/10/1 6:00:48

LLM可观测性平台Hindsight:请求级追踪与异常诊断

1. 项目概述:Hindsight 不是“事后诸葛亮”,而是一套面向 LLM 应用开发的可观测性基础设施Hindsight 这个名字乍看容易让人联想到“事后回顾”——英文里 hindsight 确实指“事后的洞察力”。但放在当前 LLM 工程实践语境下,它绝不是一句轻飘…

阅读更多 →
高斯贝叶斯图片分类器实战:从原理到批量分类与留一法验证 2026/10/1 6:00:48

高斯贝叶斯图片分类器实战:从原理到批量分类与留一法验证

简介:本资源是面向模式识别课程学习者与机器学习入门者的Python贝叶斯图像分类完整项目,对应课程大作业场景,帮助读者理解贝叶斯分类器在图片分类中的落地方式。项目同时提供控制台与GUI两种交互版本,涵盖图像预处理、特征提取、模…

阅读更多 →
Agent工程三层架构:Harness、Loop与Graph的生产级实践指南 2026/10/1 6:00:41

Agent工程三层架构:Harness、Loop与Graph的生产级实践指南

这话题我琢磨了很久,一直想找机会把 Agent 工程里那些看似玄乎、其实有章可循的东西整理出来。市面上聊 Agent 的文章不少,但大多数要么停在概念层,讲 ReAct、讲工具调用,要么一上来就甩一堆框架代码,让人看完更糊涂。…

阅读更多 →
LSTM图像描述源码实战:从环境搭建到推理生成全流程 2026/10/1 6:00:41

LSTM图像描述源码实战:从环境搭建到推理生成全流程

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
VS Code运行Vue项目指南:环境配置、依赖安装与报错排查 2026/10/1 6:00:34

VS Code运行Vue项目指南:环境配置、依赖安装与报错排查

1. 用VS Code跑Vue项目,卡住你的往往是环境而非代码先讲个真实场景。你从GitHub上拉下来一个Vue项目,或者照着教程敲完了代码,打开VS Code,终端里输入npm run dev,满怀期待等浏览器弹出来——结果要么报错刷屏&#xf…

阅读更多 →
RFdiffusion+ProteinMPNN抗体从头设计实战:参数调优与避坑指南 2026/10/1 6:00:33

RFdiffusion+ProteinMPNN抗体从头设计实战:参数调优与避坑指南

1. 为什么“从头抗体设计”值得单独开一课抗体药物的研发长期依赖动物免疫和噬菌体展示这两条路径,前者周期动辄数月,后者虽然快一些,但库容量和筛选通量始终是瓶颈。更关键的是,这两条路都建立在“自然界已经存在的抗体序列”这个…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉