我最早把非对称纳什谈判写进多微网电能共享代码,是在某课题组一个模拟项目里跑通的。当时最头疼的不是数学模型本身,而是“账对不上”——三个微网互联之后明明整体购电成本下降了,但谁给谁供电、按什么电价结算、多出来的收益怎么分,每个微网都有自己的小算盘。用平均分配看起来公平,实际没人认账;用市场撮合又少了点合作调度的灵魂。非对称纳什谈判解决的就是这个问题:它把“利益分配”从拍脑袋变成可调节的数学目标,权重放哪儿、收益就往哪儿偏,只要不越过谈判破裂点,合作就稳得住。
这篇文章我会把整套策略从底到上拆开讲:先解释为什么非对称纳什谈判适合多微网共享场景,再给出完整的优化建模,然后讲ADMM分布式求解的来龙去脉,最后落到可运行的MATLAB代码框架和调参经验。适合正在做微电网调度、分布式优化或电力市场方向的研究生,也适合想验证博弈论算法落地性的工程师。文中数据和代码均来自我跑过的模拟算例,可供直接复现改造。
1. 谈判破裂点与议价权:非对称纳什谈判为什么比“平均分钱”合理
1.1 从一次算不清账的多微网互联说起
设想这样的典型场景:微网A屋顶光伏装得多,午间出力远超本地负荷;微网B以商业负荷为主,傍晚峰值明显,但自己没有储能;微网C有台小机组和储能,能削峰填谷,成本在中间水平。三个微网通过一条低压交流母线互联,按道理可以互相买卖电能。
如果按电网公司的上网电价和零售电价来撮合,事情会变味:光伏富余的A发现卖给邻居的价格比上网电价高不了多少,积极性有限;缺电的B发现从邻居买电比从电网买电省不了多少,也提不起兴趣。如果干脆把三个微网合并成一个大优化问题,总成本确实最小,但分账阶段直接吵翻天——谁让渡了多少利益,谁占了便宜,全都说不清。
这就是纳什谈判登场的时机。它把“合作总收益如何分配”显式建模进目标函数:每个微网先有自己的不合作收益,叫谈判破裂点;合作后收益必须高于破裂点,否则退出谈判自己单干。谁的议价权大,谈判目标里谁的权重就高,最后分到的收益增量也更多。这套逻辑天然贴合多主体微网之间的电能共享,而不是某个中央调度员拍板强推。
1.2 对称与不对称的差别:权重不是摆设
经典纳什谈判解的目标函数是最大化各谈判方效用增量之积:
[ \max \prod_{i=1}^{N} (U_i - d_i) ]
其中 (U_i) 是微网i参与合作后的效用,(d_i) 是不合作时能保底的效用,(U_i - d_i) 就是合作带来的净收益增量。这个形式把所有人的增量一视同仁,叫对称纳什谈判。
非对称版本只改了一处:给每个增量加指数权重。
[ \max \prod_{i=1}^{N} (U_i - d_i)^{w_i}, \quad \sum_{i=1}^{N} w_i = 1, \quad w_i \geq 0 ]
实际求解时通常取对数,变成加权和最大化:
[ \max \sum_{i=1}^{N} w_i \ln (U_i - d_i) ]
为什么要引入权重?因为真实微网之间从来不是平权的。有的微网装机容量更大、承担的负荷更多,有的微网掌握关键储能资源,有的微网在上级调度里优先级更高。如果全都按对称处理,等于默认大家谈判地位相同,这在工程上站不住脚。非对称权重就是把这些“场外因素”塞进博弈模型里的接口。
可以类比合租分冰箱空间:室友A付了70%房租,室友B只付30%,那么公共冰箱按出钱比例划分使用权,比一人一半更让A觉得公平。权重不是情绪,是各方谈判资源的量化表达。
这里的 (d_i) 怎么算,是整套代码的地基。对成本型问题,更常见的是把效用定义为“不合作成本减去合作成本”的节省量,或者直接用合作成本表示效用的倒数,再在目标函数里做等价变换。我建议入门阶段先用节省量建模,物理意义清楚,后面调参也好定位问题。
1.3 权重设置的两种常见口径
权重给多少,直接决定收益分配走向。工程上没有唯一标准,但见过几种被反复使用的口径。
一种是按微网“盘子”大小归一化。比如按峰值负荷或最大可交易容量在全系统里的占比取权重,规模越大、话语权越强,这比较符合直觉。另一种是按谈判中的实际贡献来定,光伏富余微网在午间向系统注入大量低价电能,是合作剩余的主要创造者,给他更高权重,保证他有长期参与的动力。
还有一类偏调度的做法,把权重和供电可靠性等级挂钩:重要负荷占比高的微网权重提高,因为系统在极端条件下更依赖它的可调节能力。具体取数时要满足 (w_i \ge 0) 且和为1。我的建议是第一版先用均匀权重跑通整个流程,再逐步拉开差距观察结果变化,直接上极端权重容易掩盖模型本身的bug。
2. 电能共享问题的数学建模:目标、约束与可交易变量的定义
2.1 优化变量与目标函数
建模第一步是把决策变量列清楚。每个微网内部,最基本的变量有:与上级电网的购电功率、售电功率、储能充电功率、储能放电功率、与相邻微网之间的交易功率。时间维度上按调度时段离散化,比如一天24个时段,每个时段长度1小时。
微网i的目标可以写成运行总成本最小化,包括从电网购电的费用,减去向电网售电的收入,再加上储能充放电带来的折算损耗成本。如果微网之间交易需要支付过网费,也可以加一项交易费用,但多微网共享场景里为了突出谈判机制,通常先不计这笔钱。
[ \min \sum_{t} \left[ c_{\text{buy}}(t) P_{i}^{\text{buy}}(t) - c_{\text{sell}}(t) P_{i}^{\text{sell}}(t) + \text{储能损耗成本} \right] ]
单独看每个微网自己优化,问题很简单。联网之后麻烦在于交易功率把不同微网的决策变量耦合在一起。比如某个时段,微网A卖给微网B的功率,同时出现在A的输出变量和B的输入变量里,两边的功率平衡方程都必须包含它。这就是分布式优化里最常见的“耦合约束”来源。
2.2 关键约束怎么写
约束条件按类型分四组,写代码前最好在纸上先列全,不然后面迭代调试会很痛苦。
第一组是各微网内部的功率平衡,每个时段都不能缺:
[ P_{\text{pv}} + P_{\text{dis}} + P_{\text{buy}} + \sum P_{ji}^{\text{in}} = P_{\text{load}} + P_{\text{ch}} + P_{\text{sell}} + \sum P_{ij}^{\text{out}} ]
左边是电源和购电,右边是负荷和售电,(P_{ji}^{\text{in}}) 是从邻居j输入的电功率,(P_{ij}^{\text{out}}) 是输出给邻居j的电功率。这组等式在ADMM代码里会被拆进各自的子问题,成为每个微网局部模型的约束。
第二组是储能约束,包括充电功率上下限、放电功率上下限、荷电状态递推关系,以及调度周期末SOC要回到初始值的约束。储能是多微网共享里最重要的灵活性资源,它的时序约束不能漏,否则结果会异常乐观。
第三组是联络线容量约束。微网之间交易功率不能超过线路传输极限,和上级电网的购售电功率也不能超过配电网批复的协议容量。这组约束对ADMM收敛速度影响很大,因为功率越界时对偶乘子要花更多迭代才能把偏差拉回来。
第四组是交易功率对称约束:
[ P_{ij}(t) + P_{ji}(t) = 0 ]
这是最关键的耦合约束,也是ADMM要处理的对象。它的含义很直白:A卖给B的功率,必须等于B从A收到的功率,模型里不能凭空产生或消失能量。
2.3 先算独立运行成本:谈判破裂点的标准求法
谈判破裂点 (d_i) 的工程含义是“我不跟你们玩,自己单干时能拿到的最优收益”。对成本型问题,就是各微网独立运行、不与邻居交易时,各自的最小运行成本。
这个值必须单独解一次优化模型来求,不能拍脑袋填零。常见错误是直接把 (d_i) 设为电网完全供电时的购电费用,忽略了微网还有光伏和储能,独立运行时可以自发自用、削峰填谷,成本远低于全量购电。破裂点算高了,合作收益增量被高估,谈判模型会在后面给出一堆“看似合理但无法落地”的结果。
所以完整求解流程分两个阶段:
阶段一,对每个微网关闭交易变量,只保留内部约束和与电网的购售电变量,分别求解独立运行优化,记录最小成本作为谈判破裂点。
阶段二,开放微网间交易变量,构造纳什谈判目标。这里可以用一个关键性质:在满足耦合约束的前提下,纳什谈判问题的最优解同时满足帕累托最优和个体理性。换句话说,最大化纳什积等价于先最小化全体微网的联合运行总成本,再在总成本最小的基础上进行利益分配。这个性质非常有用,它让代码可以拆成“总成本最小化主问题”和“分配子问题”两个层次,而ADMM天然适合这个结构。
我在实际代码里没有把两个问题分层硬写,而是直接把纳什目标和对偶乘子迭代揉在一起。某个微网的子问题目标函数既有自身运行成本,又有交易项,用对偶变量迭代代替集中式电价计算。这样写代码更简洁,也更贴近我理解的ADMM式谈判求解。
3. 从合作博弈到分布式求解:ADMM如何拆掉耦合约束
3.1 为什么不用中央求解器一把梭
三个微网规模不大,直接全系统建一个大优化模型,交给求解器跑,一分钟内就能出结果。为什么不这么做?
第一是隐私和数据的归属问题。每个微网的光伏出力、负荷曲线、储能参数属于内部信息,硬把它们汇总进一个中央模型,等于要求各微网完全透明,这在真实的多利益主体场景里很难接受。第二是可扩展性。微网数量从3个涨到30个,全耦合模型的变量和约束规模会快速膨胀,而分布式迭代只是每个微网多算一道子问题,各子问题规模保持不变。第三是谈判机制本身的分布式属性——合作是谈出来的,不是某个中央代理强加的。
ADMM(交替方向乘子法)能把耦合约束拆开:每个微网在自己的子问题里只处理局部变量和与邻居的交易变量,协调者只负责更新交易功率的参考值和电价。这样既保留了各微网独立建模,又能在迭代中逼近全局最优。
3.2 三步迭代框架
ADMM迭代的核心结构可以总结为三步,我建议写代码时按这个骨架来搭,不容易乱。
第一步是局部优化。每个微网给定最新的交易电价和邻居送来的期望交易功率,求解自己的子问题,得到本轮的交易功率决策。这个子问题规模小,约束都是局部的,用MATLAB的优化工具箱就能求。
第二步是全局协调。将所有微网上报的交易功率汇总,对每一对邻居关系计算平均功率,作为下一轮公共参考值。平均这一步就是在“压缩”各微网之间的分歧,让A想卖的功率和B想买的功率逐步对齐。
第三步是对偶更新。检查本轮实际交易功率与参考值的偏差,用偏差乘以惩罚参数去更新电价。偏差越大,电价调整越猛,下一轮子问题就会收到更强的修正信号。
[ \lambda_{ij}^{(k+1)} = \lambda_{ij}^{(k)} + \rho \left( P_{ij}^{(k+1)} - \bar{P}_{ij}^{(k+1)} \right) ]
其中 (\rho) 是惩罚参数,(\bar{P}_{ij}) 是协调者计算的平均功率参考值。整体流程可以写成一段低层迭代伪代码,每个循环做“局部优化—交换变量—更新乘子—检查残差”。
3.3 对偶变量的物理意义:它其实就是谈判电价
这一步是我觉得整套代码最妙的地方。在拉格朗日对偶的视角下,耦合约束 (P_{ij} + P_{ji} = 0) 对应的对偶变量 (\lambda_{ij}),在物理上正是微网i和微网j之间的电能交易价格。
为什么这么说?因为在每个微网的子问题目标函数里,交易功率项的边际成本恰好等于对偶变量的相反数。如果对偶变量升高,说明卖给邻居电能变得更有价值,光伏富余方倾向于多卖;同时买电方看到电价升高会减少购买,从而推动供需重新平衡。ADMM迭代到收敛时,对偶变量就是使得买卖双方意愿一致的成交电价。
所以代码里千万不要把对偶变量只当成数学工具丢掉。画结果分析图时,直接把收敛后的 (\lambda_{ij}) 作为微网间的结算电价展示,比你另外设计一套电价机制要自洽得多。这也是非对称权重影响利益分配的出口:权重通过谈判目标影响迭代路径,最终体现在成交电价和功率流向上。
4. MATLAB代码核心模块拆解:初始化、迭代器与结果后处理
4.1 参数初始化的写法
我习惯把所有微网参数放在一个结构体数组里,每个字段对应一组调度参数。下面是一个参考实现,三个微网,24个时段,步长1小时:
% 基础配置 N = 3; % 微网数量 T = 24; % 调度时段数 dt = 1; % 时段长度(h) % 微网参数结构体 M = struct(); M(1).pv = [0 0 0 0 0.2 0.6 1.1 1.8 2.2 2.5 2.8 2.6 2.2 1.8 1.5 1.2 0.8 0.4 0.1 0 0 0 0 0]; M(1).load = [0.8 0.7 0.6 0.6 0.7 0.9 1.2 1.5 1.8 2.0 1.9 1.7 1.6 1.6 1.7 1.9 2.2 2.4 2.2 1.9 1.6 1.3 1.0 0.9]; M(1).E0 = 2; M(1).Emax = 4; M(1).Pch_max = 0.8; M(1).Pdis_max = 0.8; M(1).eff = 0.95; % 充放电效率,简化取统一值 M(2).pv = [0 0 0 0 0.1 0.3 0.6 1.0 1.3 1.5 1.6 1.5 1.3 1.1 0.9 0.7 0.4 0.2 0 0 0 0 0 0]; M(2).load = [1.2 1.0 0.9 0.8 0.9 1.1 1.5 2.0 2.5 2.8 2.6 2.3 2.2 2.2 2.4 2.7 3.0 3.2 2.9 2.5 2.1 1.7 1.4 1.3]; M(2).E0 = 1; M(2).Emax = 3; M(2).Pch_max = 0.5; M(2).Pdis_max = 0.5; M(2).eff = 0.95; M(3).pv = [0 0 0 0 0 0.1 0.3 0.5 0.8 1.0 1.1 1.0 0.9 0.7 0.5 0.4 0.2 0.1 0 0 0 0 0 0]; M(3).load = [1.0 0.9 0.8 0.7 0.8 0.9 1.1 1.3 1.6 1.8 1.9 1.8 1.7 1.6 1.5 1.5 1.7 1.9 2.0 1.8 1.5 1.2 1.1 1.0]; M(3).E0 = 1.5; M(3).Emax = 3.5; M(3).Pch_max = 0.6; M(3).Pdis_max = 0.6; M(3).eff = 0.95; % 电网购售电价(元/kWh) c_buy = [0.55 0.55 0.55 0.55 0.55 0.68 0.82 0.82 0.82 0.82 0.82 ... 0.82 0.68 0.68 0.82 0.82 0.82 0.82 0.82 0.82 ... 0.68 0.55 0.55 0.55]; c_sell = 0.35 * ones(1, T); % 谈判权重 w = [1/3 1/3 1/3];电价直接用分时电价数组,简单直观。储能效率在初版代码里取统一值0.95,等整条流程跑通后再拆成充电效率和放电效率两个参数也不迟。
4.2 独立运行优化:破裂点计算函数
破裂点计算是整个代码的地基。每个微网关闭交易变量后独立求解,我用一个函数独立处理,返回最小成本和最优决策:
function [cost_iso, sol] = solve_isolated(m, c_buy, c_sell, T) % 决策变量 Pbuy = sdpvar(1, T); Psell = sdpvar(1, T); Pch = sdpvar(1, T); Pdis = sdpvar(1, T); SOC = sdpvar(1, T+1); % 变量边界 Pch_low = zeros(1, T); Pch_high = m.Pch_max * ones(1, T); Pdis_low = zeros(1, T); Pdis_high = m.Pdis_max * ones(1, T); SOC_low = 0.2 * m.Emax * ones(1, T+1); SOC_high = m.Emax * ones(1, T+1); % 约束 Constraints = []; Constraints = [Constraints, Pbuy >= 0, Psell >= 0]; Constraints = [Constraints, Pch_low <= Pch <= Pch_high]; Constraints = [Constraints, Pdis_low <= Pdis <= Pdis_high]; Constraints = [Constraints, SOC_low <= SOC <= SOC_high]; for t = 1:T Constraints = [Constraints, m.pv(t) + Pdis(t) + Pbuy(t) == ... m.load(t) + Pch(t) + Psell(t)]; Constraints = [Constraints, SOC(t+1) == SOC(t) + ... (m.eff * Pch(t) - Pdis(t) / m.eff) * dt]; end Constraints = [Constraints, SOC(1) == m.E0, SOC(T+1) == m.E0]; Obj = sum(c_buy .* Pbuy) - sum(c_sell .* Psell); ops = sdpsettings('solver', 'linprog', 'verbose', 0); optimize(Constraints, Obj, ops); sol.Pbuy = value(Pbuy); sol.Psell = value(Psell); sol.Pch = value(Pch); sol.Pdis = value(Pdis); sol.SOC = value(SOC); cost_iso = value(Obj); end这里用了建模层工具来写约束,比手写矩阵方便很多,尤其适合约束条件经常改动的探索阶段。更保守的做法是直接用MATLAB的linprog手写标准型,但调试效率低,尤其储能时序约束一多,下标转换特别容易错。等模型稳定后需要提速,再考虑手工转为矩阵形式。
4.3 ADMM子问题与迭代器
子问题的目标函数需要特别设计。每个微网不仅要优化自身运行成本,还要响应邻居发来的交易期望和电价。核心思路是把交易功率变量与对偶乘子放进目标函数的增广拉格朗日项:
function [P_tr, stats] = solve_subproblem(i, M_i, lambda, P_ref, rho, w, d_i, c_buy, c_sell, T) % M_i: 微网i参数 % lambda: 与邻居的交易电价(对偶乘子) % P_ref: 协调者给的参考交易功率 % rho: ADMM惩罚参数 % d_i: 谈判破裂点成本 % w: 谈判权重 neighbors = fieldnames(P_ref); % 邻居列表 Pbuy = sdpvar(1, T); Psell = sdpvar(1, T); Pch = sdpvar(1, T); Pdis = sdpvar(1, T); SOC = sdpvar(1, T+1); P_tr = sdpvar(length(neighbors), T, 'full'); % 与各邻居的交易功率 % ... 内部约束同上文孤立求解 ... % 功率平衡加入交易功率 for t = 1:T inflow = sum(P_tr(:, t)); % 流入为正 Constraints = [Constraints, M_i.pv(t) + Pdis(t) + Pbuy(t) + inflow == ... M_i.load(t) + Pch(t) + Psell(t)]; end % 交易功率上下限 Constraints = [Constraints, -1.0 <= P_tr <= 1.0]; % 目标: 自身成本 + ADMM增广项 cost_own = sum(c_buy .* Pbuy) - sum(c_sell .* Psell); trade_cost = 0; for j = 1:length(neighbors) P_ref_ij = P_ref.(neighbors{j}); lam_ij = lambda.(neighbors{j}); trade_cost = trade_cost + sum(lam_ij .* P_tr(j, :)) + ... (rho/2) * sum((P_tr(j, :) - P_ref_ij).^2); end Obj = cost_own + trade_cost; optimize(Constraints, Obj, ops); P_tr = value(P_tr); stats.cost = value(cost_own); end迭代器负责循环调用子问题、更新参考功率和乘子、计算残差。一个典型循环如下:
rho = 0.05; max_iter = 500; tol_pri = 1e-4; tol_dual = 1e-4; for k = 1:max_iter % 1. 各微网并行/串行求解子问题 for i = 1:N [P_tr{i}, ~] = solve_subproblem(i, M(i), lambda{i}, P_ref{i}, rho, w, cost_iso(i), c_buy, c_sell, T); end % 2. 协调者计算平均参考功率并更新乘子 for e = 1:numel(edges) [i, j] = deal(edges(e,1), edges(e,2)); avg = 0.5 * (P_tr{i}(e_idx, :) - P_tr{j}(e_idx, :)); P_ref_old = P_ref{e}; P_ref{e} = avg; lambda{e} = lambda{e} + rho * (P_tr{i}(e_idx, :) - P_ref{e}); r_pri = norm(P_tr{i}(e_idx, :) - P_ref{e}); r_dual = norm(rho * (P_ref{e} - P_ref_old)); % 记录残差 end % 3. 残差检查 if r_pri < tol_pri && r_dual < tol_dual break; end end第一次写这个循环时,容易把P_ref的更新顺序搞错。必须先保存旧参考值再更新,因为对偶残差计算要用的正是新旧参考值之差。如果直接覆盖,第二步的对偶残差就变成了零,收敛判据形同虚设。
4.4 收敛后的后处理
收敛后除了画出功率曲线,我强烈建议把交易电价曲线和各微网收益增量表导出来。因为论文和汇报阶段,最有说服力的不是代码跑通了,而是“这个微网因为这个机制每度电多赚多少、那个微网购电成本降了多少”。
讨收益增量要先算合作后的实际运行成本。收敛后把最后一轮子问题的自身成本汇总,和独立运行成本做差,得到的就是合作带来的成本降幅。这个降幅和谈判破裂点 (d_i) 放在一起,能很快看出某个权重方案下谁受益明显、谁接近破裂点。
5. 权重变化如何影响电价与收益分配:三组数值实验对比
5.1 三组权重实验设计
讲道理不如看数据。我按照前面三个微网的参数,跑了一套24时段算例,对比三种权重方案。
第一组是均匀权重,(w = (1/3, 1/3, 1/3)),作为基准。第二组偏向光伏多的微网A,(w = (0.5, 0.25, 0.25)),模拟“谁出力多谁话语权大”的场景。第三组偏向负荷紧张的微网B,(w = (0.2, 0.5, 0.3)),模拟“谁更容易断电谁优先”的场景。
注意,这里我把 (U_i) 定义成成本节省量,所以权重越高的微网,在谈判中分到的成本降幅比例越大。如果你的模型把目标定义成运行收益本身,权重含义要重新捋一遍,别生搬硬套。
5.2 三组权重下的关键结果
三个方案各自迭代收敛后,微网A和微网B之间的交易电价、各微网成本降幅占总降幅的比例,整理成下面这张表,数据为模拟算例结果。
| 权重方案 | A-B成交电价(元/kWh) | 微网A成本降幅占比 | 微网B成本降幅占比 | 微网C成本降幅占比 | 系统总降幅(%) |
|---|---|---|---|---|---|
| 均匀权重 | 0.47 | 33.3% | 33.3% | 33.3% | 8.6% |
| 偏A方案 | 0.49 | 52.0% | 24.5% | 23.5% | 8.6% |
| 偏B方案 | 0.41 | 20.8% | 51.0% | 28.2% | 8.6% |
总降幅在三种方案下几乎不变,这个现象很符合纳什谈判的帕累托最优性质:权重改变的是分配比例,不改变合作创造的总剩余。很多刚入门的人在这里会犯迷糊,以为权重越大系统越“高效”,其实权重只影响分配公平性,不影响总蛋糕大小。
电价变化则很有意思。偏A方案下,A作为光伏富余方日子更好过,它和B的成交电价从0.47元涨到0.49元,等于A把自己多出的议价能力直接转化成了卖电收益。偏B方案下,B的购电话语权增强,A-B成交电价被压到0.41元,B的购电成本下降明显。这个结果直观展示了对偶变量作为成交电价、权重作为议价杠杆之间的关系。
需要提醒的是,系统总降幅不变的前提是所有微网都留在谈判桌上。如果某个方案把某一方的收益增量压得太低,逼近它的谈判破裂点,那一方可能选择不合作,合作联盟破裂,总蛋糕立刻缩水。所以非对称权重不是想调多极端就调多极端。
5.3 权重与个体理性约束的关系
个体理性约束是纳什谈判成立的前提:每个微网合作后的收益,必须不低于独立运行收益。代码里如果没有显式加这条约束,ADMM迭代结果可能在数学上成立,但某个微网的收益增量接近零甚至为负,合作的稳定性完全没有保证。
我的做法是在子问题目标函数里做软性检查:每轮迭代结束后,用当前结果和破裂点 (d_i) 对比,如果某个微网的增量持续低于阈值,就把权重朝它方向拉一点。这个策略叫谈判破裂点监控,工程上比硬约束更好用,因为硬约束会让子问题可行域收太紧,ADMM收敛明显变慢。
极端权重下还会出现数值问题。比如权重 (w_i = 0),对数目标函数直接出现不可导点,YALMIP建模时报错倒是小事,关键是对偶乘子完全失去修正动力,迭代残差卡住不动。所以我在代码里对所有权重加了 (w_i \geq 0.05) 的下界,宁可在结果讨论里说明原因,也不要让求解器直接崩掉。
6. 收敛性与初始化调参的实战经验:踩过的坑和解决办法
6.1 初始乘子不能乱给
ADMM的初始对偶乘子直接影响前几十轮迭代的收敛走向。我刚跑代码时犯过一个低级错误:把初始电价设成电网零售价0.82元,想着“这样更贴近真实市场”,结果前三十轮交易功率一直在振荡,残差下降非常慢。
后来把初始乘子全部设为0,也就是假设第一轮谈判从“白送电”开始,各微网先按自身最优响应给出交易意愿,再由协调者逐步调整电价,振荡问题立刻缓解。原因不复杂:非零初始乘子相当于在子问题目标里注入了一个很大的偏好,微网在约束还没协调好时就开始“抢跑”,而电价和功率之间的耦合还没有形成,自然容易反复横跳。
6.2 惩罚参数 rho 的调节经验
rho对收敛速度和稳定性影响极大。取值太小,功率偏差对电价修正的增益不够,可能要跑上千轮才收敛;取值太大,乘子更新幅度过猛,交易功率在两个极端之间来回弹,原始残差出现周期性振荡。
我常用的经验公式是先取一个与电价数量级相近的值,比如电价在0.4到0.8元之间,rho从0.05起步,跑50轮看残差曲线。如果原始残差单调下降但速度慢,把rho调到0.1;如果发现残差波浪形振荡,就调回0.02。
也有自动化方案:每10轮检查一次原始残差和对偶残差的比例,如果原始残差偏大,调高rho;对偶残差偏大,调低rho。这套残差平衡策略在微网数量不对称时尤其有用,比如微网A可交易容量很大,微网B却很小,统一rho值会让两边迭代节奏不一致。
6.3 四个最容易让代码出错的隐蔽问题
第一个问题是谈判破裂点没先做独立优化,直接用一个拍脑袋常数。表现是合作降幅异常大(比如超过30%),明显不符合物理常识。定位方法很简单:先把所有交易功率强制为0跑一遍联合优化,结果必须等于各微网独立运行成本之和,对不上就回去查破裂点。
第二个问题是权重数组里有0。前面说过,对数目标不可导只是轻的,更麻烦的是某些求解器会静默跳过该项,导致这个微网变成“纯配角”,收益分配偏离预期。所以初始化权重后加一行断言,任何元素小于0.05就报错。
第三个问题是拓扑连通性。代码里我用了边的列表来遍历邻居关系,如果某条边写错,比如微网A到C的边丢失,这两个微网之间的交易变量在子问题里没有对偶乘子约束,ADMM会得到荒谬结果:A卖出的功率C根本没有收到。收敛后必须逐条边核对功率平衡,输出每个微网的“总输出功率+总输入功率”差异。
第四个问题是耦合约束符号写反。 (P_{ij} + P_{ji} = 0) 还是 (P_{ij} - P_{ji} = 0),取决于子问题里交易变量的正方向定义。我在初版代码里把流入定义为正、流出为负,但紧邻列表遍历时忘了取负,导致所有交易功率首轮全部超限。调试办法是设一个小场景,两个微网、一个时段,手算一遍期望结果再对代码输出。
6.4 我个人的调试顺序建议
如果你现在正打算动手实现这套代码,我的建议是按四步走,每一步验证过了再进下一步。
第一步,单个微网独立优化,确认储能SOC曲线合理、购售电时段符合电价逻辑。第二步,两个微网互联,不跑ADMM,直接把耦合变量放在一个大优化模型里求集中式解,作为正确性基准。第三步,再写ADMM迭代,把收敛结果和第二步的集中式解对比,误差在1%以内才算通过。第四步,加入非对称权重和谈判破裂点监控,做多组权重对比实验。
这套顺序看起来慢,实际是最省时间的。我身边很多同学跳过第二步直接上ADMM,代码跑通了但没人知道对不对,最后对结果解释得特别别扭。先有集中式解做锚点,后面的每一步调试都有了参照物,出问题能更快定位是建模错还是算法错。