
做多微网协调控制的人应该都有同感单微网能量管理已经够琐碎一旦扩展到多微网麻烦直接从“一个优化问题”变成“一堆优化问题加一堆交互约束”。而这个课题里最麻烦的地方在于每个微网都有自己的运营主体没有人愿意把光伏出力、负荷曲线、储能SOC这些“底牌”交给一个中心调度者可如果完全不协作又会出现在同一时刻你有富余电卖不出去、我缺电只能高价从大电网买的尴尬情况。计及碳排放之后问题又多了一层燃气轮机便宜但排碳高光伏清洁但出力不确定协调策略必须让每个微网在成本与排放之间自己权衡。这篇文章就把我当时做的一套完整方案拆开讲——基于交替方向乘子法ADMM的多微网电能交互分布式运行策略含Matlab代码实现思路和所有关键细节适合正在做微电网优化、分布式调度、碳约束运行相关课题的读者参考。1. 先搞清楚为什么非得用ADMM多微网电能交互的集中式困局1.1 多微网系统的真实运行场景多微网并不是把几个微网简单堆在一起。我在建模时习惯把系统想成“一个园区里有好几栋独立运营的楼宇微网”每个微网内都有光伏、负荷、储能条件好一点的还装了微型燃气轮机。它们通过一条公共母线与外部大电网相连彼此之间也允许交换电能。这种结构带来的直接好处是互补性。白天光伏多的微网可以把富余电卖给晚间负荷重的微网而不是压低出力负荷峰谷错开的微网之间可以通过交互减少对大电网的依赖。但问题也随之而来交互电量是多少、往哪个方向流、电价怎么定这些都不是拍脑袋定的需要在一个优化框架里统一决策。更关键的是每个微网是独立主体。我调研阶段跟不少做园区能源管理的人聊过大家最担心的一件事就是自己的光伏装机、负荷细节被别的运营方知道。这不是不信任而是商业上的正常边界。所以整个问题天然就指向分布式求解——只交换必要的边界信息不暴露内部数据。1.2 集中式优化为什么在工程里行不通如果忽略隐私问题集中式优化在数学上其实很简单把所有微网的变量收集到一个大模型里目标函数是所有微网成本之和约束条件包含所有微网内部的功率平衡、设备限制以及微网之间的交互功率关系丢给求解器一次算完。问题是出在工程可行性上。先说隐私集中式要求每个微网把光伏预测曲线、负荷曲线、储能容量、机组参数全部上报给中心节点这在多主体场景里几乎不可能被接受。再说计算微网数量一旦上去决策变量和约束数量显著膨胀尤其是每个微网都配有储能和燃气轮机时时间耦合约束会让整体模型的规模非常可观求解时间不一定能控制在滚动调度的窗口内。还有单点故障问题。中心调度器一旦出故障整个系统的协调就瘫痪了。而分布式方法在理论上允许各个微网在失去通信时退回到孤岛运行状态这种降级特性在工程里非常值钱。ADMM恰恰是这类问题里最成熟的分布式算法之一既能保证收敛到全局最优在凸问题前提下又不需要微网之间交换太多信息这使它成为多微网电能交互问题的首选算法框架。1.3 碳排放不只是一条约束更是一个成本信号我在最初看这个问题时以为计及碳排放就是在原模型尾部加一条“排放量不能超过某上限”的约束。后来实际建模才发现碳排放更像一个成本信号它会真正改变每个微网的调度偏好。常见的做法是把碳排放折算成成本放进目标函数。燃气轮机发电时单位出力对应一个碳排放强度从大电网购电也对应一个电网平均排放因子这两项乘以碳价就得到碳成本。碳价给得越高微网就越倾向于多买隔壁光伏微网的电少用自己的燃气轮机甚至愿意多充一些储能来替代晚间的火电购电。如果只把碳排放做成硬约束模型会变得非常僵硬上限设太松约束起不到作用设太紧可能出现无解。把它做成目标函数里的成本项之后就可以做碳价敏感性分析观察碳价从低到高时交互策略如何平滑变化这个结果对方案汇报和论文讨论都很有价值。我在后面的仿真里也重点看了这一块。2. 把碳排放和电能交互写成数学模型2.1 目标函数每个微网要算的四笔账多微网优化模型的起点是每个微网自己的目标函数。以微网 \(i\) 为例在调度周期 \(T\) 内它的总成本由四部分构成。第一部分是燃气轮机发电成本采用常规二次函数\[ F_{g,i} \sum_{t1}^{T}\left( a_i P_{g,i,t}^2 b_i P_{g,i,t} c_i \right) \]第二部分是与大电网交易的成本购电计正、售电计负\[ F_{grid,i} \sum_{t1}^{T}\left( \pi_t^{\rm buy} P_{i,t}^{\rm buy} - \pi_t^{\rm sell} P_{i,t}^{\rm sell} \right) \]第三部分是碳排放成本这里把燃气轮机发电和从大电网购电都折算成排放\[ F_{CO2,i} \pi^{\rm CO2} \sum_{t1}^{T}\left( \varepsilon_g P_{g,i,t} \varepsilon_{grid} P_{i,t}^{\rm buy} \right) \]第四部分是设备运维成本光伏、储能、风机各有单位运维费用通常在功率或能量上线性计费。四个部分加在一起就是每个微网在分布式中要独立最小化的局部目标。这里的关键点在于碳排放成本是内部化的微网在决策时就会主动权衡燃气轮机出力和购电策略而不是等全局模型告诉它“你的排碳配额超了必须降低出力”。2.2 约束条件不能只盯功率平衡目标函数定完之后约束条件要分三类来看。第一类是微网内部的功率平衡约束。任意时刻 \(t\)微网 \(i\) 内部必须满足\[ P_{i,t}^{pv} P_{i,t}^{w} P_{g,i,t} P_{i,t}^{dis} P_{i,t}^{buy} \sum_{j \in \Omega_i} P_{ij,t} P_{i,t}^{load} P_{i,t}^{ch} P_{i,t}^{sell} \]这个公式看着长但物理含义很直接所有进来的功率光伏、风机、燃气轮机、储能放电、购电、从邻居微网流入必须等于所有出去的功率负荷、储能充电、向大电网售电、向邻居微网流出。这里的 \(P_{ij,t}\) 表示微网 \(i\) 到微网 \(j\) 的交互功率是后面分布式分解的重点。第二类是设备自身的约束。燃气轮机出力上下限和爬坡约束储能SOC动态方程、充放电功率限制、SOC上下限光伏和风机的出力上限购售电功率上限。这些约束属于微网内部隐私完全在子问题内部处理。第三类是微网之间的耦合约束。对于任意两个相连的微网 \(i\)、\(j\)在任意时刻 \(t\)交互功率必须满足\[ P_{ij,t} P_{ji,t} 0 \]这条约束的意思是我从你这里买的电就是你向我卖出的电数值相等、方向相反。正是这条约束把所有微网的子问题耦合在了一起导致问题不能直接拆开必须用分布式优化算法来处理。2.3 耦合约束与ADMM分解的钥匙直接对耦合约束做拉格朗日乘子松弛也能拆问题但ADMM更强的地方在于它还加了一个二次惩罚项这让子问题在迭代中拥有更强的“黏性”不容易在边界条件附近振荡。ADMM的标准做法是引入全局一致性变量 \(z_{ij,t}\)把原耦合约束拆成两个软约束\[ P_{ij,t} - z_{ij,t} 0, \qquad P_{ji,t} z_{ij,t} 0 \]也就是让微网 \(i\) 的交互功率 \(P_{ij,t}\) 去追踪 \(z_{ij,t}\)让微网 \(j\) 的交互功率 \(P_{ji,t}\) 去追踪 \(-z_{ij,t}\)。这样每个微网的子问题里只出现自己的交互功率变量和全局变量 \(z\)完全可以独立求解。对每个微网 \(i\)增广拉格朗日函数写成\[ L_i F_i \sum_{j \in \Omega_i}\sum_{t} \lambda_{ij,t}\left( P_{ij,t} - z_{ij,t} \right)\frac{\rho}{2} \sum_{j \in \Omega_i}\sum_{t} \left( P_{ij,t} - z_{ij,t} \right)^2 \]这样设计的好处是子问题中增加的拉格朗日乘子项和二次项都是关于 \(P_{ij,t}\) 的简单凸项不会破坏子问题的凸性也不会显著增加求解难度。后面的所有Matlab代码都是在这个形式上进行迭代的。3. ADMM求解流程三步迭代与收敛判据3.1 ADMM标准形式与三步更新ADMM的目标是把一个带等式约束的优化问题分解成多个更容易求解的子问题然后通过一个固定套路反复迭代。它的标准形式是\[ \min ; f(x) g(z), \quad s.t. ; Ax Bz c \]对应到多微网场景\(x\) 就是所有微网内部运行变量和交互功率的集合\(z\) 是全局一致性变量。每一步迭代都分为三步第一步固定 \(z\) 和拉格朗日乘子 \(\lambda\)求解所有 \(x\) 相关子问题第二步固定最新的 \(x\)更新 \(z\)第三步更新拉格朗日乘子。如果不太熟悉优化理论可以把这三个步骤理解成一种“先各自做计划再对账再调整”的机制。每个微网先按当前的对账参考值优化自己的计划然后所有微网把计划摆到桌面上对账如果有的微网想卖多了、有的想买少了就通过乘子更新来修正参考值下一轮再各自调整计划。如此循环直到大家的计划基本对得上为止。3.2 多微网场景下的子问题怎么解在多微网问题里每个微网在每一轮ADMM迭代中需要求解的子问题是这样的在给定一致性变量 \(z_{ij,t}\) 和乘子 \(\lambda_{ij,t}\) 之后最小化包含自身成本、碳成本、交互惩罚项的目标函数同时满足设备约束。子问题的目标函数可以写成\[ \min ; F_i(x_i) \sum_{j \in \Omega_i}\sum_{t} \lambda_{ij,t}\left( P_{ij,t} - \hat z_{ij,t} \right)\frac{\rho}{2} \sum_{j \in \Omega_i}\sum_{t} \left( P_{ij,t} - \hat z_{ij,t} \right)^2 \]这里的 \(\hat z\) 是上一轮更新后的一致性变量。这个子问题本身还是一个小型的最优潮流/经济调度问题可以使用YALMIP加求解器如quadprog、cplex、gurobi来求解也可以写纯Matlab二次规划形式。我在实际项目里用的是YALMIP定义子问题模型整体代码写起来非常快。当然如果目标是追求极致速度可以考虑将子问题整理为标准二次规划然后用quadprog直接调用。子问题内部的光伏出力和负荷都是固定参数决策变量主要是燃气轮机出力、储能充放电功率、购售电功率和交互功率。3.3 收敛判据不能只看目标函数不动了ADMM迭代不能只靠“目标函数变化很小”来判断收敛这是很多初学者容易踩的坑。标准做法是计算两个残差原始残差和对偶残差。原始残差反映的是各微网交互计划与一致性变量之间的偏差定义大致为\[ r^k \sqrt{\sum_{i}\sum_{j}\sum_{t} \left( P_{ij,t}^k - z_{ij,t}^k \right)^2} \]对偶残差反映的是一致性变量自身的变化幅度定义大致为\[ s^k \rho \cdot \sqrt{\sum_{i}\sum_{j}\sum_{t} \left( z_{ij,t}^k - z_{ij,t}^{k-1} \right)^2} \]停止条件是原始残差和对偶残差同时小于各自阈值且一般还会设置最大迭代次数。如果只盯着目标函数看会出现目标函数已经平稳但交互功率还没对上的虚假收敛。这一点在后面代码调试部分还会具体展开。4. Matlab代码实现模块划分与关键迭代片段4.1 代码结构与数据组织一个完整的多微网ADMM仿真程序我不建议把所有东西塞进一个脚本里。我当时的代码分成五个文件结构非常清晰case_data.m定义微网数量、拓扑连接关系、设备参数、碳价、分时电价等基础数据solve_subproblem.m求解单个微网当前迭代轮次的子问题输入是当前微网索引、一致性变量、乘子和公共数据输出是该微网的调度结果update_z.m根据各微网返回的交互功率更新所有一致性变量update_lambda.m更新拉格朗日乘子run_admm.m主循环负责调用以上所有模块并输出收敛曲线和调度结果。数据组织上我倾向于把每个微网的数据放进一个结构体数组例如mg(1).pv、mg(1).load、mg(1).battery。交互拓扑用邻接矩阵表达哪两个微网之间存在交互通道一目了然。整个程序要支持多时段滚动最常见的是24小时调度时间步长1小时。把时间维度加进来之后一致性变量 \(z\) 和乘子 \(\lambda\) 都要带上时间索引这一点在初始化时要特别注意否则容易在更新时出现维度错误。4.2 主循环与子问题求解的关键代码主循环的骨架大概是这样的我用简化代码来说明逻辑实际使用时要根据自己定义的变量名和数据格式调整。% run_admm.m 主循环核心逻辑示意 T 24; % 调度时段数 N 4; % 微网数量 rho 1e-3; % 惩罚参数 maxIter 200; epsP 1e-4; % 原始残差阈值 epsD 1e-4; % 对偶残差阈值 % 初始化一致性变量 z 和乘子 lambda Z zeros(N, N, T); Lambda zeros(N, N, T); % 迭代主循环 for iter 1:maxIter P_old P_result; % 保存上一轮交互功率用于对偶残差 % 第一步依次求解每个微网子问题 for i 1:N [P_result(i,:,:), ~] solve_subproblem(i, Z, Lambda, rho, mg); end % 第二步更新一致性变量 Z update_z(P_result, N, T); % 第三步更新乘子 Lambda update_lambda(Lambda, P_result, Z, rho, N, T); % 计算残差 primal_res compute_primal_residual(P_result, Z); dual_res compute_dual_residual(P_result, P_old, rho); % 判断是否收敛 if primal_res epsP dual_res epsD fprintf(收敛于第 %d 次迭代\n, iter); break; end end这里面的P_result(i,j,t)表示微网 \(i\) 在当前迭代轮次给出的对微网 \(j\) 的交互功率。solve_subproblem内部用YALMIP构造目标函数和约束我贴一下目标函数构建的核心片段% solve_subproblem.m 内部目标函数构建示意 x sdpvar(size(decision_vars, 1), 1); % 决策变量 objective x * H * x f * x; % 自身成本二次型 % 加上ADMM惩罚项iAdj为与微网i相连的邻居集合 for idx 1:length(iAdj) j iAdj(idx); for t 1:T Pij x(index_Pij(idx, t)); % 交互功率变量 z_ij Z(i, j, t); lambda_ij Lambda(i, j, t); objective objective ... lambda_ij * (Pij - z_ij) ... rho/2 * (Pij - z_ij)^2; end endupdate_z的更新逻辑也比较直接。对于相连微网 \(i\) 和 \(j\)理想情况是希望 \(P_{ij}\) 等于 \(-P_{ji}\)所以最简单的一致性更新方式是取两者的平均% update_z.m 一致性变量更新示意 Z(i,j,t) 0.5 * (P_result(i,j,t) - P_result(j,i,t));这样微网 \(i\) 到 \(j\) 的参考交互功率正好是微网 \(j\) 到 \(i\) 参考交互功率的相反数精确满足方向相反的要求。4.3 参数配置从惩罚参数到收敛阈值ADMM的收敛速度和最终精度对参数相当敏感最核心的就是惩罚参数 \(\rho\)。我一般先看一眼目标函数里二次成本项的系数量级然后让 \(\rho\) 跟它维持在同一数量级附近。我在多个算例里发现\(\rho\) 选得太小时原始残差下降非常慢交互功率在相邻迭代轮次里来回震荡常常要跑上百轮才勉强收敛\(\rho\) 选得太大残差下降倒是快但子问题中二次惩罚项会“压过”真实成本项导致最终解与真实最优解存在偏差。实践中 \(\rho\) 从 \(10^{-3}\) 到 \(10^{-2}\) 之间试看残差下降曲线最平稳的那个值就是当前场景下的好选择。收敛阈值方面原始残差和对偶残差的阈值设成 \(10^{-4}\) 量级足够精确再往下收敛轮数会显著增加但对结果精度的提升非常有限。最大迭代次数设200到300轮就够了如果300轮还没收敛多半是模型或参数有问题而不是迭代轮数不够。5. 仿真结果能告诉我们什么5.1 收敛性与计算时间整个程序跑通之后我先验证的是收敛性。以4个微网、24小时调度为例在合适参数下原始残差随迭代次数大致呈现“前期快速下降、后期缓慢贴近零”的形态。前20轮残差能下降一到两个数量级40到80轮之后降到阈值以下。作为参照我会把各微网的目标函数之和跟同一场景下集中式求解的目标函数值做对比。凸问题前提下ADMM迭代收敛后两者应当非常接近差个0.1%左右都算正常。如果发现偏差超过1%就要回头检查一致性变量更新逻辑或子问题建模是否出了差错。计算时间方面串行求解4个子问题每个子问题用quadprog大概零点几秒一轮迭代总耗时就在1秒附近200轮下来大约两三分钟。把子问题循环改成parfor之后一轮迭代时间能压到原来的四分之一左右。对于科研验证来说这个速度完全够用。5.2 计及碳排放前后各微网的调度行为差异这是我认为整个课题里最有观察价值的部分。把碳价从0逐步抬高调度结果会发生明显变化。碳价为0时燃气轮机出力只受自身成本影响光伏较多但自身负荷不足的微网会倾向于多卖给邻居或大电网整体策略完全是成本驱动。引入碳价之后燃气轮机的实际使用成本提高了微网会主动减少燃气轮机出力想办法从隔壁光伏充足的微网买入更多电能。一个很有意思的现象是碳价较高时原本在午后光伏大发、不需要购电的某个微网会愿意在上午额外充一些电目的不是为了自己晚高峰用而是为了在傍晚替代一次燃气轮机发电。也就是说碳排放成本不仅影响了当下时段的决策还通过储能的时间平移特性改变了整个日内的能量分配。交互功率曲线因此变得更加平滑微网间的互济电量上升向大电网的总购电量和全系统碳排放同步下降。当然碳排放下降是有代价的系统总运行成本会略有上升。这属于典型的“减碳成本”通过调整碳价可以看到一个很平滑的减碳成本曲线这比单纯报一个“减少碳排放多少”的结论有说服力得多。5.3 交互功率契约与集中式最优解的对比ADMM迭代收敛之后每个时段、每条交互通道上的 \(z_{ij,t}\) 就是微网之间实际执行的“购售电契约”。我在结果分析里习惯把每对微网之间的交互功率画成一张热力图横轴是24小时纵轴是交互通道颜色表示交互功率大小和方向一眼就能看出哪些时段、哪些通道的交互最频繁。和集中式最优解对比时分布式解在交互功率上几乎完全一致少数时段可能相差0.5%到1%这主要是收敛阈值造成的精度损失。需要提醒的是ADMM只能保证收敛到全局最优解的前提是原问题为凸问题。如果内部加入了0-1变量比如储能启停变量或者购售电状态变量问题变成混合整数规划ADMM就没有全局最优保证这时候对比集中式解时发现差异偏大要能解释得清楚。6. 跑了几个月代码后的填坑经验6.1 惩罚参数无法“一套通吃”我最开始天真地以为调好一个 \(\rho\) 就可以应付所有场景事实很快打脸。改变碳价、改变负荷曲线、改变拓扑连接最优 \(\rho\) 都会轻微移动。虽然大部分场景下 \(\rho5\times10^{-3}\) 左右都能跑但要追求又快又准每换一组案例还是得重新瞄一下残差曲线。一个实用技巧是把残差下降速度打印出来看如果原始残差和对偶残差下降速度严重不均衡就调整 \(\rho\) 来拉平原始残差下降慢、对偶残差下降快就增大 \(\rho\)反之就减小 \(\rho\)。这个方法比纯靠试参数高效得多。6.2 一致性变量初值与热启动一致性变量 \(z\) 初始化成全零通常可以正常工作因为ADMM对 \(z\) 的初值不算特别敏感。但拉格朗日乘子 \(\lambda\) 如果初始值给得离谱前二三十轮迭代会明显震荡。稳妥起见\(\lambda\) 从全零开始最安全。热启动技巧在滚动调度场景里非常管用。如果要跑24小时滚动优化上一时段的收敛结果可以直接作为下一时段 \(z\) 和 \(\lambda\) 的初值迭代次数经常能减少一半以上。代价是代码需要多维护一份状态缓存但对计算时间的提升是实打实的。6.3 子问题不可行的排查链路ADMM过程中最常遇到的报错是“子问题无解”或者求解器返回infeasible。遇到这种情况别急着怀疑算法先按这个顺序排查。第一步检查交互功率变量的上下限是否在子问题里正确添加。如果 \(P_{ij,t}\) 的边界条件只在全局模型里设置子问题里忘了加就可能出现子问题为了满足自身功率平衡而让交互功率跑到不合理范围进而导致无解。第二步检查一致性变量更新后是否可能出现与交互变量方向不一致的情况。更新 \(z\) 时用平均法一般比较稳健但如果自定义了加权方式权重设置不合理会导致子问题之间出现矛盾性参考值求解器自然报错。第三步检查储能SOC约束。储能SOC在24小时内是时间耦合的子问题里如果初始SOC和末段SOC约束过紧加上其他约束极小概率会出现可行域为空。这时可以把SOC末端约束放宽成不等式约束或者加入松弛变量。6.4 收敛了但结果不对的排查思路比不收敛更让人头疼的是“看起来收敛了但结果跟期望不符”。我曾遇到过一次情况残差已经降到阈值以下但某个微网的交互功率始终对不上另一个微网的反向功率差了一小截。后来查出来是更新Z时我只更新了部分通道的一致性变量漏掉了几条交互通道的更新代码。这种问题从残差公式里很难直接看出来因为残差统计的是所有通道的总和个别通道的小偏差会被整体掩盖。调试这类问题的有效做法是把每对交互通道的残差单独打印出来画成一张按通道拆分的残差热力图。哪条通道颜色总是深问题就在哪条通道直接定位到对应代码段比整体看残差曲线快得多。6.5 一个关于出图的实用建议最后分享一个小习惯。我在写仿真结果时会同时保存ADMM迭代的中间结果和最终调度结果后者单独存成结构体文件。这样后续做敏感性分析、换参数重跑时不需要重新执行一遍整套迭代流程直接读取保存的结果就能画图。画交互功率图时建议用蓝色和橙色表示正反向颜色映射范围对称设置这样买卖方向一目了然。另外在论文里展示收敛曲线时纵轴建议用对数坐标否则前期残差下降速度快后期缓慢逼近零的过程在普通坐标下根本看不出细节。跑完这套ADMM多微网代码我最深的体会是分布式优化的难点从来不在算法公式本身而在于把公式变成工程上可维护、可调试、可复现的代码同时还要能解释清楚算法每一步的行为。希望这篇拆解能帮你少走一些弯路。