资讯详情

资讯详情

串行并行ADMM算法在配电网分布式优化中的Matlab实现

搞配电网分布式优化这行绕不开ADMM这三个字母。我最早接到基于串行并行ADMM算法的主从配电网分布式优化控制研究Matlab代码这个命题的时候第一反应是找一套能直接跑起来的模型把串行和并行两种架构都跑通再回头补理论。结果一上手就发现论文里轻飘飘的一句交替方向乘子法落到真实配电网算例上全是坑rho调不好残差震荡、parfor看起来并行实际不加速、边界功率符号写反了还给你一个看似收敛的伪解。这篇东西就是冲着这些坑来的适合那个凸优化学过、最优潮流会写、想转分布式却看论文看到头大的研究生或者工程师看。我会把主从配电网为什么非要拆开算、ADMM更新式在代码里长什么样、串行和并行两种实现的本质差异、以及我在标准测试系统上实测下来的收敛行为和参数敏感性一次性摊开讲清楚。1. 主从配电网为什么要绕开集中式优化1.1 主从结构到底把网络拆成了什么配电网一般是单电源辐射状结构一个变电站母线往下带若干条馈线每条馈线再分叉出许多分支。所谓主从不是变压器的主备逻辑而是把整张网络人为划分成一个主区域和若干个从区域。最常用的划分方式是在馈线的某个分段位置断开把下游一整块负荷连同它内部的分布式电源划成一个从区上游部分继续作为主区如果下游还有多个分支就照这个办法再切出多个从区。以33节点标准辐射状测试系统为例我习惯选在某条分段支路处断开切出一个相对独立的下游馈线段作为从区剩下部分作为主区再在另一条分支处断开就变成两个从区。切完之后主区和从区之间只保留两组物理耦合量边界节点电压以及从区从主区吸收的有功和无功功率。分布式优化的全部玩法就是只交换这两组边界量每个区域内部的潮流方程各自闭门求解。这种划分不是凭空设计的现实中它正好对应分段开关和联络开关的位置。地理上本来就存在的开关点天然就是划分自治范围的边界通信和采集设备也最容易布置在那些点上。明白这一点之后再去看ADMM里耦合变量这个词就不会觉得它是一个纯数学概念了。1.2 集中式SOCP模型在真实配网里的三个软肋集中式最优潮流非常成熟DistFlow方程加SOCP松弛之后交给YALMIP调求解器一个几十节点的配网几分钟就能出结果。但真实配电网落地的时候集中式有三个绕不开的软肋。第一个是数据汇聚。所有节点负荷、光伏出力的遥测信息必须完整送到控制中心任何一个远动终端通信中断或者延迟全局优化要么等、要么直接废。分布式光伏大量接入之后遥测点数量翻了几倍这个问题会越来越突出。第二个是隐私边界。配网里可能同时存在多个投资主体各自的储能、光伏、可调负荷运行数据都是商业信息没有哪个运营商会愿意把内部出力计划完整交给一个中心。第三个是滚动优化的实时性。系统规模从几十个节点涨到几百个再叠加大量柔性负荷和分布式电源集中式模型虽然能解但每一次重算都要把全网数据重新刷新一遍更新周期很难压到分钟级。我不是说集中式要淘汰而是说在主从结构下做分布式优化在通信可靠性、利益边界、扩展性上有天然优势。ADMM恰好能把主从分割这个物理现实变成一套可收敛的数学算法这就是为什么这个方向这么多年还有生命力。1.3 ADMM与对偶分解的对比为什么最后选了它配电网做分布式优化不止ADMM一条路。早期研究者常走对偶分解路线把全局约束的拉格朗日乘子当作价格信号广播下去各子问题收到价格后独立决策主问题再根据总量偏差更新价格。这条路思路非常清晰但致命问题在于潮流约束带来的目标函数并不强凸对偶函数一阶梯度更新非常容易抖经常出现边界量两边来回震荡、就是不收敛的局面。后来大家普遍转投增广拉格朗日方法靠二次罚项给子问题兜底ADMM就是这类方法里最适合在Matlab落地的。ADMM的核心优势可以这样理解每个子问题不是单纯优化自己的目标它还背着上一轮邻居给的边界参考值和一组罚项。罚项让子问题在每一轮保持一定的近似凸性同时它在同一轮里既更新原始变量、又更新对偶乘子相当于原始迭代和对偶上升同步进行。对非凸的潮流方程理论上它没有绝对收敛保证但大量文献和我的实测都表明耦合变量选得合理的情况下它总能落到和集中式几乎一致的运行点上。对做工程和写论文来说这就足够了。2. ADMM在配网潮流模型中的落地方式2.1 三步更新式x、z、乘子各司其职标准ADMM求解的问题是min f(x) g(z)约束条件是 Ax Bz c。写出来的增广拉格朗日先不列了代码里真正执行的只有三步。第一步更新x。固定z和乘子λ求解一个带罚项的局部问题相当于在主区域自己的成本和偏离邻居期望之间做权衡x_new argmin_x { f(x) (ρ/2) * || Ax Bz_old - c λ_old/ρ ||² }第二步对称地更新z。这次固定刚算出来的x_newz_new argmin_z { g(z) (ρ/2) * || Ax_new Bz - c λ_old/ρ ||² }第三步更新乘子λ_new λ_old ρ * (Ax_new Bz_new - c)放到配电网场景里x可以理解为主区变量z是从区节点的边界状态f是主区的网损和运行成本g是从区的目标函数而Ax Bz c就是我们切分网络时留下的边界一致性约束。每一轮迭代主从双方都在用对方上一轮给的边界值修正自己的内部解一来一回边界就慢慢对齐了。很多刚接触的人会把这三步当成普通的循环体实际运行起来才明白x更新那一步要调用完整的潮流子问题求解器z更新又要调用一次乘子更新只是加减法。整个ADMM的算力几乎全花在子问题上这也是后面并行化能不能提速的关键。2.2 耦合变量选哪些才不会让子问题退化确定边界耦合变量是整个建模里最讲究的一步。最自然也最常用的三组变量是边界节点电压幅值、主区流向从区的有功功率、无功功率。这三组量在主区那边是支路潮流的末端取值在从区那边是根节点的注入取值ADMM的一致性约束把它们拉齐。但这里有个非常容易翻车的地方如果直接把电压幅值本身作为边界变量潮流方程里电压和功率是相乘关系子问题很可能变成非凸常用求解器会直接抱怨甚至无解。实践经验是把电压平方V²作为一致性变量DistFlow支路方程在直角坐标下对电压平方的依赖友好得多子问题的凸性也更可控。另外不要贪心把主区和从区之间所有支路潮流都设成边界变量。边界变量太多每个子问题内部真正自由的可调量就太少解空间被压得厉害ADMM迭代会像两根弹簧顶着一块板一样反复震荡半天不收敛。2.3 非凸潮流子问题用松弛还是直接牛顿法有朋友问我是不是每个子问题都必须写成SOCP然后调用商业求解器。其实不一定。ADMM的每个子问题里边界变量被固定后区域内部就是一个经典潮流可行域问题。你可以用SOCP松弛也可以保留原始DistFlow方程用牛顿法直接迭代求解。SOCP的好处在于模型化程度高、变量多了不慌原始牛顿法精度高但子问题非凸混合到ADMM的外层迭代里两层非线性交互会让参数调试变得非常痛苦。我当时在串行版里用的是SOCP子问题加YALMIP求解因为实现简单、调试方便后来要做并行版发现YALMIP在parfor下面根本跑不快又花了一个星期把每个子问题重写成二次规划形式改用Matlab自带的quadprog求解。这样一来每个worker不需要启动解释器建模循环开销直接降了一个量级。这个选择对工程实现非常关键第三章我会专门展开。3. Matlab代码框架串行与并行的实现差异3.1 代码工程的文件划分方式搭建ADMM配网程序最忌讳把主循环、子问题、拓扑数据全塞进一个巨大的脚本里。我建议按职责拆成目录调起bug来会感激这个决定main_serial.m % 串行ADMM入口 main_parallel.m % 并行ADMM入口 data/ case_33bus.m % 节点编号、支路参数、负荷、分布式电源数据 funcs/ build_partition.m % 按指定断点生成主从分区表 solve_sub_master.m solve_sub_slave.m update_multiplier.m calc_residual.m adaptive_rho.m utils/ plot_profile.m % 画电压分布和残差曲线文件拆细之后每个子函数都能单独测。实际调试中至少有三分之一的bug出在矩阵维度和符号正负判断上拆开之后把每个函数的尺寸打出来看一眼就清楚了。我还习惯把节点数据统一放在一个文件里这样换33节点、69节点系统时只需要改数据文件主循环逻辑完全不动。3.2 串行ADMM的主循环骨架串行版主循环的框架我写给大家看lambda zeros(n_coupling, 1); x_master initial_master(...); y_slave initial_slave(...); for k 1:k_max x_master solve_sub_master(y_slave, lambda, rho, data); y_slave solve_sub_slave(x_master, lambda, rho, data); residue A * x_master B * y_slave - c; lambda lambda rho * residue; [r_pri, r_dual] calc_residual(x_master, y_slave, lambda, rho); if max(r_pri, r_dual) tol break; end end有三个细节要提醒。第一solve_sub_master里使用的是上一轮的y_slave也就是所谓旧值不要在同一个循环内把刚更新过的y_slave再传给master那就变成另一种迭代格式了。第二乘子更新的residue是原始残差检查一下符号是否和一致性约束定义一致。第三串行版的特点就是信息更新快x更新完马上影响到y所以它的迭代次数通常比并行版少三成到五成。3.3 并行ADMM的parfor分工与同步机制并行版和串行版的核心分歧在从区更新时机。并行版里所有从区在同一个循环轮次内使用相同的x_master旧值各自独立计算全部算完之后才汇总。这正好是Matlab parfor的舒适区因为parfor要求循环迭代互不依赖parfor s 1:n_slave y_par(:, s) solve_sub_slave_par(x_master, lambda_par(:, s), rho, data(s)); end % 同步点所有从区完成后统一汇总 y_slave aggregate(y_par);这里有三个必须注意的坑。第一个parfor体内不能依赖其他worker的结果所以每个从区的数据和参数必须提前组织成独立的struct或cell数组不能共享一个公共矩阵做读写。第二个如果子问题内部调用YALMIP或者CVX建模解释过程实际上是单线程跑parfor开了等于白开反而多了数据拷贝的额外开销。我把求解器换成了quadprog之后并行版才真正看到加速。第三个lambda和ρ是否需要按从区单独维护我的经验是要。不同从区大小不同、负荷不同收敛速度也不一样强制共享同一组乘子会拖慢所有区域。正确做法是lambda做成矩阵每一列对应一个从区。3.4 残差计算与停止条件的细节残差是ADMM的仪表盘。原始残差反映一致性约束被破坏的程度对偶残差反映乘子更新是否在来回摆动。只看原始残差、不看对偶残差你会在某些rho取值下看到原始残差已经不降了但对偶还在猛冲那种情况是最典型的伪收敛。常用的停止条件带绝对容差和相对容差eps_pri sqrt(p) * eps_abs eps_rel * max(||Ax||, ||Bz||) eps_dual sqrt(n) * eps_abs eps_rel * ||Aλ||实操时先设一组严格的eps_abs1e-5、eps_rel1e-4跑一遍看残差的台阶下降形状是否正常再放宽到研究需要的精度。不要一开始就追求高精度因为某些数值噪声在极小容差下会变成无限迭代。4. 33节点系统上的收敛结果与参数敏感性4.1 测试环境与基准解为了把串行和并行两个版本的差异说清楚我在一台普通的8核CPU、16GB内存笔记本上跑了33节点标准测试系统节点电压基准12.66kV总负荷约3.7MW加2.3Mvar无功。网络切分成一个主区和两个从区在一条主干支路处断开形成从区1在另一条分支处断开形成从区2耦合变量大约是三组边界量的组合。基准解用集中式SOCP最优潮流得到目标函数是系统网损加少量DG成本惩罚项所有分布式方案都拿这个解当标尺。4.2 迭代次数、墙钟时间与目标值对比稳定测试的一组参数是rho0.8、eps_abs1e-4、eps_rel1e-3结果如下版本完成迭代次数单次平均耗时总墙钟时间与集中式目标偏差串行ADMM260.42s10.9s0.06%并行ADMM480.18s8.6s0.12%集中式SOCP1—2.1s—单看迭代次数串行优势非常明显因为它每一轮都让从区立刻吃到主区最新信息但看总耗时并行反而更快原因是子问题经过quadprog重写后计算瓶颈全在矩阵运算多核分担那三个子问题能实打实把墙钟时间拉下来。如果从区数少于CPU核数或者子问题求解还是一层YALMIP解释器包着串行就会反超。这个结论直接决定你代码里该选哪个分支。4.3 rho对收敛性的影响与自适应策略rho这个参数对ADMM收敛速度的影响只能用极其敏感来形容。rho太小乘子更新像蜗牛爬原始残差倒是稳但迭代次数可能从20次飙升到90多次rho太大比如取到10前几轮原始残差确实飞速下降但之后再怎么迭代都进不了容忍度因为对偶残差开始高频震荡整个算法在边界附近来回弹。后来我在代码里加了自适应rho策略核心逻辑很简单每5轮比较一次原始残差和对偶残差的比值比值大于10就放大rho比值小于0.1就缩小rho其余情况保持不变。用了这个策略之后rho初值随便在0.01到10之间取都能在40轮以内收敛。这对参数扫描实验是救命级别的优化不然每组rho都要手动调半天。if mod(k, 5) 0 ratio r_pri / r_dual; if ratio 10 rho rho * 2; elseif ratio 0.1 rho rho / 2; end end5. 调参走过的弯路与可复用建议5.1 从残差不降到收敛的完整排查顺序跑出来残差不降第一个动作不是改rho而是按下面这个顺序查。第一步打印每个子问题解向量看边界变量的值是否在动。如果一动不动问题几乎一定出在子问题求解器本身的可行域定义上。第二步检查一致性约束矩阵的符号。主区流出功率的正方向在从区那边必须反号这是整个建模里最容易错的地方。我有一次就是符号写反ADMM迭代出的电压曲线整体往下平移了一段表面看残差嗡嗡往下掉实际是伪收敛。第三步把最大迭代次数设到300打印每50轮的残差值判断是发散还是单纯收敛慢。如果是发散先把rho降一个数量级如果是收敛慢优先查eps_rel是不是设置得太严。排查顺序的价值在于它让你用最少的时间确定是模型错了还是参数没调好。这两个问题处理方式完全不同一个是改代码一个是改配置。5.2 parfor加速的收益边界在哪里我在前文反复强调过YALMIP子问题在parfor下很难提速。为了让自己死心我用profile工具统计过单次子问题求解里YALMIP建模解释开销占了70%以上真正优化器求解只占不到30%。当瓶颈在建模解释这一层时开再多worker也只是反复触发解释器收益微乎其微。把子问题重写成quadprog之后建模开销几乎消失耗时集中在矩阵分解和回代多核并行的价值才真正释放。还有个偷时间的小技巧如果并行的几个从区拓扑结构完全相同、只是负荷场景不同那么每个从区的常数矩阵可以先做一次因数分解并缓存每次迭代只做回代。这个优化在串行和并行版里都好用单次子问题耗时能省一半以上。5.3 正确性验证的三板斧验证ADMM写没写对我固定做三件事。第一把收敛后的节点电压曲线和集中式解画在同一张图里两条线应几乎重叠。第二换两批不同的初始lambda跑两次看最终解是否基本不变。如果初始值不同结果就不同那说明算法很可能落在非凸问题的某个局部KKT点上需要换更小的rho或者重新考虑是否该把更多的非凸约束放进去。第三单独测试每个子问题自身的KKT条件随机给一组边界变量检查子问题内部的约束残差是否真的为0。这三项全过代码才能放心拿去跑实验和出图。5.4 把这套框架往三相不平衡和微网群扩展这套主从ADMM的可扩展性比想象中好。要改成三相不平衡配网变量从电压平方标量变成三相向量耦合变量从3个变成9个子问题的维度相应膨胀但框架不用推翻。要接入微网群把每个微网当成一个从区边界设在公共连接点加一组换流器控制变量就能描述功率互济。我做过的测试里改动都集中在子问题建模和一致性约束的维度上协调层的主循环几乎不用动。最后分享一个工作习惯每次跑完一组参数把残差曲线、目标值、迭代耗时、rho轨迹全部存成一个mat文件文件名带上场景和日期。写论文做敏感性分析的时候直接回头翻历史结果不用一切重跑。这种小事在项目后期能省出好几天时间。用一句话总结目前的体会串行ADMM赢在迭代次数少、逻辑直观适合当研究的第一版基线并行ADMM赢在墙钟时间短、扩展性强但要真正发挥优势必须绕开YALMIP那层解释器开销。两个版本都留在工程里比纠结谁替代谁有用得多——它们解决的是研究过程中不同阶段的问题。如果你正准备搭这套代码我的建议是先把串行跑顺子问题和残差逻辑全部验证干净再动手并行版。先吃到ADMM的稳再去追它的快这条路最顺。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →