资讯动态

产消者能量共享的非合作博弈分布式优化:基于ADMM的Matlab完整实现

发布时间:2026/9/26 17:21:16 来源:尧图企业网站定制
开门见山说结论这个项目解决的是社区微网里一群屋顶光伏、储能、柔性负荷用户的电能共享问题每个用户都是独立的“产消者”既产电又用电大家在没有统一调度者的情况下自行达成交易最后用Matlab把整套博弈过程和分布式迭代算法跑通得到收敛均衡并验证经济性。这个方向做的人不少但大多数文章只讲模型和理论真正能把非合作博弈分布式求解落到Matlab代码层面、并且能复现结果的公开资料其实不多。这篇博文就把我当时从建模到代码、再到调参与排错的全过程拆开写一遍适合正在做微网能量共享、电力市场经济方向的研究生也适合想在自己项目里引入分布式优化求解思路的工程师参考。1. 需求拆解与方案选型逻辑1.1 产消者能量共享到底解决什么问题先还原一下实际场景。假设一个小型工业园区或者高层住宅小区屋顶铺满了光伏板白天日照充足时有几家用户发了电用不完晚上下班回到家另外几家用户负荷上来了家里却没装储能。传统模式下光伏余电只能低价卖给电网晚上再按零售电价从电网买回来中间这个价差就是实实在在的损失。能量共享想做的一件事就是让产消者之间直接撮合交易中午A家的余电送给B家价格介于上网电价和零售电价之间A家多赚一点B家少花一点双方都划算。事情听起来很简单但撮合交易不是拍脑袋定个价就行。每家有光伏、有储能、有自己的负荷曲线谁该买、谁该卖、卖多少、价格怎么定这些互相耦合在一起而且每家都想让自己利益最大化。这个问题天然就不是一个“统一调配”的任务而是一场多方博弈。把博弈论引入进来不是为了显得高深而是因为参与者的关系决定了它必须要用这个框架去描述。这个思路也直接决定了后续所有工作产消者之间的耦合关系用共享功率平衡约束描述价格通过博弈均衡内生决定而不是由外部给定求解过程采用分布式优化逐轮逼近均衡。整个项目实质上是在回答一个问题一群自私但理性的产消者怎么在不暴露自己全部隐私的前提下通过局部信息交互收敛到各方都能接受的交易结果。1.2 为什么选非合作博弈而不是合作博弈或集中调度在实际选型的时候我首先排除的是集中调度。集中调度假设有一个可信的中心节点掌握所有产消者的光伏出力、负荷、储能SOC等私有数据然后统一做全局最优。这在理论上能拿到最低系统总成本但现实里有几个坎过不去一个是隐私用户不想把自己家几点用电、冰箱功率多少告诉别人另一个是可扩展性产消者数量一多中心求解的变量规模涨得非常快而且单点故障会导致整个系统瘫痪还有一个隐藏问题——就算中心能算出全局最优它怎么让每个自私的产消者心甘情愿按照全局方案执行没有激励机制约束的最优方案在落地时大概率会偏离。合作博弈也考虑过经典做法是计算Shapley值或者核仁来分配合作收益。合作博弈的效率更高因为它在追求系统总成本最小但它的软肋同样明显收益分配机制复杂参与人一旦觉得分配不公就会退出而且计算成本随人数上升非常快需要排列组合计算产消者数量稍大就难以处理。最后定下来用非合作博弈老实说一个最核心的原因是它最贴近真实的交易场景。每个产消者只关心自己的收益最大化在市场上报自己的交易意愿其他人也是这个想法大家最后达成的状态就是谁也找不到单方面改方案能让自己更好的状态这就是纳什均衡。这种“各自为战但整体收敛”的模式天然适合分布式求解也天然适合当前电力市场“去中心化交易”的趋势。博弈论的严谨性保证了至少存在均衡解分布式算法保证了我们能算出来这两点正是选型的关键。1.3 分布式优化在这里的具体优势分布式优化听起来很玄实际解决的是三个非常具体的问题。第一个是隐私保护产消者本地求解子问题时只需要上传交易意向比如想买的电量给协调器不用暴露负荷曲线、光伏出力和储能状态。第二个是并行计算每个子问题彼此独立可以在Matlab里用parfor循环或者多线程并行求解Primal decomposition、ADMM这类算法天然支持这种并行结构产消者数量从5个扩展到100个计算时间的增长远低于集中式优化。第三个是可落地性分布式框架下整个系统没有一个“无所不知”的中心只需要一个轻量级的协调实体做信息转发和价格更新这更容易被实际工程接受。我当时还做了一组简化测试来验证必要性把产消者数量从5个逐步增加到50个集中式IPSolver的求解时间大约按三次方速度增长而分布式ADMM加上并行求解时间增长接近线性。这个实验直接让我确定了最终方案用非合作博弈建模交易关系用ADMM做分布式求解每个子问题交给Matlab的quadprog求解器处理。2. 数学模型与核心算法原理2.1 产消者个体运行模型的建立方式写代码之前先把数学模型理清楚。单个产消者i在一个调度周期内有若干个时段我这里用的是24个时段、1小时步长。它的目标是最大化整个周期的效用用数学表达就是最小化运行成本从电网购电的费用加上储能充放电的折旧成本再减去向电网卖电的收入和在共享市场卖电的收入。每个产消者的可调资源有三类分布式光伏预测出力曲线调度周期内不可控、储能系统充放电功率上下限、SOC连续性约束、容量上下限和柔性负荷这里我暂时简化成固定负荷扩展的时候可以把可平移负荷加进去。需要重点强调的是储能建模很多初学者在这个地方翻车SOC的递推关系是SOC(t1) SOC(t) - P_ch(t)*eta_ch*dt P_dis(t)/eta_dis*dt注意充放电效率方向不能写反否则储能会在迭代中偷偷“凭空产电”整个博弈结果全乱掉。目标函数里的二项式惩罚项也值得说一句。储能充放电成本我用了a * P^2的二次项加线性项一方面模拟了电池退化成本的边际增加另一方面二次项让子问题的目标函数变成严格凸函数这对收敛性分析非常关键。非严格凸问题在分布式求解中最常见的问题就是震荡不收敛加一个很小的二次正则项是最便宜有效的稳定手段。2.2 共享博弈模型玩家、策略和收益函数把每个产消者抽象成博弈参与者那么一个完整的博弈模型需要定义清楚三个要素。玩家集合就是N个产消者每个玩家的策略就是它与公共母线交换的功率向量实际实现里我用买入功率和卖出功率两个非负变量来拆分避免符号问题收益函数就是前面说的效用函数它由本地运行成本、与电网交互费用以及与共享市场交互费用三部分组成。这里有个让很多论文读者困惑的点共享市场电价是怎么来的答案是不需要预先给定。共享电价由市场供需关系的平衡条件内生决定数学上表现为所有人共享功率之和必须恒等于零的耦合约束。这个约束保证了整个共享市场内部的功率守恒把这个问题称为广义纳什均衡问题Generalized Nash Equilibrium ProblemGNE之所以是“广义”就是因为玩家的策略集受到耦合约束的限制不能独立分开求解。纳什均衡的定义用大白话说就是给定其他所有产消者的策略任何单个产消者单独改变自己的买电策略都不可能再提高自己的收益。此时没有任何人有动机单方面偏离这个状态就是博弈的解。数学上我们需要求解的是一组变分不等式的最优性条件而ADMM恰好是一种公认有效的GNE求解方法这也是我选用它作为核心算法的根本原因。2.3 ADMM求解原理对偶变量就是共享电价ADMMAlternating Direction Method of Multipliers交替方向乘子法求解GNE的机制很多资料写得抽象我换个生活化的方式讲。想象几家邻居凑在一起拼着买电每家在算自己买多少但大家最后必须保证“你买的就是我卖的”总账要对平。ADMM干的事情就是反复走三个步骤第一步每户人家根据当前电价自己盘算最佳购电量第二步把所有人的意向汇总算一个“集体平均意见”第三步根据大家的偏离程度调整电价让大家下一次报价更接近。重复这三步直到大家对某个交易电量形成共识电价也稳定下来。放到数学层面耦合约束sum_i P_share_i 0的对偶乘子lambda迭代更新公式是lambda lambda rho * sum(P_share)这个lambda的物理含义就是共享电力市场的清算价格。这样一个设计非常优雅价格不是人为设定的外部参数而是算法收敛过程中内生迭代出来的均衡价格它天然介于上网电价和零售电价之间反映共享市场的供需紧张程度。ADMM的收敛性对有理论保证前提是子问题目标函数是闭凸函数且整体问题有解。实际迭代中需要监控两条指标原始残差当前共享功率总和不等于0的程度和对偶残差价格更新的波动幅度两者同时小于阈值才认为收敛。代码中对应两条norm()判断当时我在这一点上理解得不够深只盯原始残差导致早期实验出现“表面收敛但其实价格还在飘”的情况后来把两个残差都加上了才稳定下来。3. Matlab代码实现与核心模块解析3.1 代码目录架构与模块职责Matlab代码整体的组织逻辑是“一个主脚本、五个功能模块、一个参数文件”。我实际跑通的目录结构如下EnergySharing_Game/ ├── main_ADMM.m % 主程序初始化、迭代、收敛判断、结果汇总 ├── config_parameters.m % 参数文件产消者数量、负荷、光伏、储能、电价 ├── generate_data.m % 生成负荷曲线与光伏出力曲线含随机性 ├── solve_prosumer_subproblem.m % 单个产消者子问题求解核心 ├── update_global_variable.m % 全局共享功率更新 ├── update_dual_variable.m % 对偶乘子电价更新 ├── compute_nash_gap.m % 计算纳什缺口判断是否到达均衡 ├── plot_results.m % 画图共享功率、电价、SOC、费用对比 └── metrics_economics.m % 统计各产消者的收益成本指标这个结构对新手最友好的地方在于“参数”和“逻辑”分离。config_parameters.m里改几个人数、容量、电价参数整个系统行为就立刻改变非常适合做参数敏感性分析。generate_data.m里用正弦叠加随机噪声的方式生成负荷和光伏曲线好处是无需外部数据文件就能复现缺点是如果你做论文实验最终还是要换真实数据这部分后面排错小节里会专门提一句。主迭代部分不要写成一坨几千行的脚本否则调参的时候想死。每个功能函数的输入输出定义清楚尤其是solve_prosumer_subproblem它接收当前共享电价lambda、惩罚参数rho、共享功率参考值P_ref和本地产消者参数返回本地最优购售功率、储能动作和本地目标函数值。这样设计后后面想换求解器、换模型只需要改这一个函数。3.2 核心ADMM迭代框架与子问题求解配置先看主迭代循环的核心代码这里是整个程序的心脏% 初始化 P_share zeros(N, T); % 各产消者共享功率买入为正 P_mean zeros(1, T); % 共享功率平均值即全局参考值 lambda zeros(1, T); % 共享电价对偶变量 residual_prim inf; residual_dual inf; tol 1e-4; maxIter 300; for k 1:maxIter P_share_old P_share; lambda_old lambda; % 第一步并行求解各产消者子问题 for i 1:N [P_share(i,:), P_sto(i,:), P_grid(i,:), ~] ... solve_prosumer_subproblem(i, lambda, rho, P_mean, params); end % 第二步更新全局参考值投影到平衡超平面 P_mean_new mean(P_share, 1); P_mean P_mean_new; % 第三步更新对偶变量共享电价 lambda lambda rho * (P_mean - P_share_target); % 收敛判断两个残差同时达标 residual_prim norm(P_mean - 0, 2); % 共享功率总量平衡残差 residual_dual rho * norm(P_mean - P_mean_old, 2); if residual_prim tol residual_dual tol break; end end这段代码里有几个细节必须留意。首先P_share_target在无外部交易时是0向量它表示共享市场内部总交换功率必须为0如果你想让这个社区还能和外部电网交易只需要把P_share_target改成预设的外部购电协议曲线整体框架不用动。其次P_mean_old要在每次迭代开始时保存否则对偶残差就算不对——这个错误我踩过一次现象是明明共享账目已经平衡但价格还在持续缓慢漂移容易误判不收敛。子问题求解本身我用Matlab自带的quadprog完成因为目标函数是二次的、约束是线性的标准凸二次规划问题。变量排列方式是每个时段买入功率、卖出功率、储能放电功率、储能充电功率、向电网购电功率、向电网售电功率顺序一旦固定下来就不要轻易改后面写约束矩阵的时候变量索引排错是高频bug。我当时就是用变量索引表注释在函数头排错时节省了大量时间。quadprog的解算器参数设置为interior-point-convex并在目标函数里加了一个极小的高斯扰动比如1e-6倍的单位阵避免退化问题。3.3 参数配置一个可以直接跑通的仿真场景为了让代码能直接跑起来我设计了一套六产消者场景数据如下产消者编号光伏容量(kW)储能容量(kWh)日峰值负荷(kW)净特性P115208光伏盈余型P212010光伏盈余型P36014负荷主导型P481016负荷主导型P5103012带储能平衡型P60018纯负荷型各产消者的负荷曲线在早晚高峰段做差异化设置光伏出力按照夏季典型日的钟形曲线乘以各自的装机容量比例生成。电价方面分时电价设置为峰时1.2、平时0.8、谷时0.4元/kWh上网电价0.42元/kWh。一个关键参数是ADMM的惩罚参数rho我分别试过0.01、0.1、0.5、1.0、5.0实际发现0.1到0.5这个区间收敛速度和稳定性最均衡。rho太小会收敛极慢rho太大容易引发对偶变量的剧烈振荡极端情况下直接发散。这个结论不一定对所有场景适用但可以作为你调参的起点范围。仿真输出部分我建议至少画四张图共享功率时间曲线、共享电价收敛曲线、储能SOC曲线、各产消者成本堆叠柱状图。其中共享电价曲线非常重要——收敛之后的平稳电价曲线可以直接说明整个市场的清算价格走势也能验证均衡的价格是否落在上网电价与零售电价之间的合理区间。4. 仿真结果分析与经济性验证4.1 收敛性验证残差曲线与迭代次数跑通代码后的第一件事不是看结果经济性而是验证算法是否收敛。我当时设置了两个监控量原始残差sum(P_share)的对数曲线和对偶残差的对数曲线分别以横向迭代次数为坐标画出来。六产消者场景下rho取0.3大约迭代40次后原始残差降到1e-3以下80次左右两个残差都降到1e-4以下总计时不到十秒子问题全部串行求解的情况下。如果换成并行parfor大概能压缩到三秒以内。这里有个容易误导人的现象需要警惕ADMM的残差下降不是单调的前几次迭代甚至可能出现残差上升、价格剧烈波动的情况。这是算法正常的表现不是bug因为初始点在可行域之外算法在“震荡调整”阶段。判断收敛性的正确方式是看残差是否进入一个单调下降通道而不是要求它从第一步开始就一路向下。偶尔会出现前二三十步完全发散、然后突然收敛的奇特情况这通常和rho选择偏大有关把rho调到0.1附近能明显改善。4.2 均衡共享结果与内生价格分析收敛后各产消者的共享功率曲线就能反映整个社区的“能源流”中午时段P1和P2的光伏盈余量最大大部分卖给P3和P6傍晚P3和P6负荷达到高峰此时储能P5放电参与共享共享电价随之抬升。最有意思的是P6这个纯负荷型产消者它没有光伏也没有储能但在共享机制下依然受益——白天从邻居那里买的共享电比电网零售电价低晚上光伏没出力时再回归电网购电。共享电价曲线落在0.54到1.05元/kWh之间全天平均约0.71元/kWh。这个结果完全符合经济直觉它高于上网电价0.42元/kWh所以光伏盈余型产消者愿意参与共享而不是全部上网它低于零售电价平段0.8元/kWh和负荷高峰期电价所以负荷主导型产消者愿意从共享市场买电而不是全部依赖电网。这是检验模型正确性的一个非常重要的sanity check如果计算出来的共享电价低于上网电价或者高于零售电价说明方向搞反了。4.3 与无共享、集中式最优方案的量化对比经济性验证是博弈能量共享项目最核心的说服力来源。我在同样数据下跑了三种模式做对比模式一是无共享模式各产消者完全独立运行光伏余电上网、缺电买网电模式二是集中式全局最优把所有产消者的变量放到一起联合优化总成本作为全局下界模式三是本文的分布式博弈方案。结果汇总如下表模式社区总日成本(元)相对无共享节省说明无共享独立运行1218.6-基线方案集中式全局最优1068.212.3%理论最优下界分布式非合作博弈1075.911.7%与集中式差距0.7%这个结果的价值在于两点。第一分布式方案相比无共享模式节省了11.7%的成本证明了能量共享本身有显著经济效益而且这个效益不是靠强制的集中调度下发的而是每个产消者自己理性决策达到的均衡状态自然涌现的结果。第二分布式方案与集中式最优只差0.7%的效率损失这0.7%可以理解为“隐私和自治的代价”相比于强制调度带来的隐私问题和可扩展性问题这个成本完全值得付出。再细看各产消者的个体收益光伏盈余型P1和P2收益提升最明显因为它们卖出的共享电价远高于上网电价负荷主导型P3和P6的成本降低来自共享电价低于零售电价P5作为带储能平衡型不但通过峰谷套利获利还在傍晚高价时段参与共享放电收益提升幅度最大。所有参与者的收益都严格改善这是博弈均衡在现实中能持续存在的先决条件——如果某一家比不参与共享时还亏它就有动机退出交易均衡就会瓦解。5. Matlab实现中的高频问题与排错实录5.1 符号约定混乱导致的总量不平衡问题共享功率的符号是排错重灾区。我在代码里明确规定“买入为正、卖出为负”并在config_parameters.m文件头注释所有函数都要遵循这个约定。很多人会出现这个问题子问题里为了优化方便定义买入功率变量为B(i,t)、卖出功率变量为S(i,t)然后在汇总共享功率时用了B(i,t) - S(i,t)但在储能建模中又用了相反的符号结果共享总量永远平衡不了。这个问题的根源是储能功率、共享功率、电网交互功率三个量用了不统一的参考方向。我的建议是统一在配置文件的注释里画一张“功率方向图”储能充电为正、放电为负共享市场买入为正、卖出为负电网购电为正、售电为负。每次新增一个函数前都回头看一眼这张图基本能杜绝方向混乱。检查方向有没有搞反有一个快速技巧单独设一组纯光伏盈余用户运行共享模式如果发现盈余用户在共享市场上显示为买入方而不是卖出方说明方向反了。5.2 rho参数与初始点导致的收敛问题不收敛和高频振荡是ADMM类算法最常见的翻车点。我已经强调过rho的影响这里说两个更深层的问题。一个是初始点的选择如果lambda初始化为全零同时共享功率也初始化为零算法通常会从“所有产消者完全自给自足”的点开始这个点离均衡点可能会比较远导致前期迭代比较激烈。一个更稳妥的做法是先用无共享模式各自求解一次用结果中的余电和缺电量作为P_share的初始估计这样可以大幅缩短前期的震荡阶段。这个方法我试过之后收敛速度大约能提升30%。另一个要注意的是当你把外部电网交互变量也放进共享约束里问题会变得非常复杂。我的建议是初始版本先固定电网购销电价为给定参数让共享市场作为贴近电网的“内部走廊”这个基础版本跑通了再考虑电网电价作为变量、共享和电网耦合的综合场景。渐进式建模能减少很多“想一次解决所有问题然后一夜调不通”的挫败感。5.3 求解器选择与Matlab性能优化细节子问题求解器兼容性是新手最容易忽略的坑。如果直接用Matlab自带的quadprog优点是无须额外安装任何工具箱缺点是二次项如果写得不小心容易被警告“改用interior-point-convex”。正确的写法是在options里显式指定optimoptions(quadprog,Algorithm,interior-point-convex,Display,off)。如果你有Yalmip和Gurobi/Cplex授权也可以把子问题写成Yalmip的optimize格式代码可读性更好求解速度在问题规模上来后远超quadprog但要注意Yalmip在循环里反复调用会有变量重建的开销每次迭代新建优化对象会导致额外耗时建议使用一个固定的约束模板只更新参数不要整个重建。性能方面Matlab循环是出了名的慢但这个项目里子问题本身内部有优化求解器瓶颈不在循环而在优化求解过程。我实测下来六个产消者串行跑三百轮迭代大概需要十几秒瓶颈在quadprog调用次数太多。优化手段主要是两个一是用parfor并行化子问题求解六个子问题分配到六个worker上耗时能压到串行的四分之一左右二是缩小无效迭代空间把变量按时段分块处理quadprog本身的稀疏求解器在求解时段维度的线性方程时效率更高尤其当你把H矩阵显式构造成稀疏矩阵并用sparse()构建时速度提升非常可观。5.4 结果异常时的排查顺序表实际操作中结果异常通常有规律可循。我整理了一个按优先级排查的速查方法按这个顺序查基本上能覆盖80%的异常情况。异常现象首选排查方向次选排查方向共享功率总量长期不为零检查买入/卖出符号检查P_share_target是否设非零电价收敛到电网电价之外检查rho和正则项系数检查约束中电价是否被固定储能SOC越界或突变检查SOC递推是否用了前后不一致的公式检查时段步长dt是否错写为1小时全局收敛但某个产消者收益为负检查该产消者的光伏/负荷数据生成是否异常检查该产消者是否被错误分配了储能参数不同初始点收敛到不同结果问题非严格凸加二次正则项验证均衡唯一性关于均衡不唯一这一点我想多说一句。严格凸目标函数保证均衡解唯一但当你把储能成本设成纯线性或忽略惩罚项时子问题可能退化导致多个均衡点共同存在。处理方式是在目标函数中加极小量二次正则项eps_penalty * norm(x,2)^2其中eps_penalty取1e-4量级即可。这个微小的改动既不会影响经济性结果精度又能让算法稳定收敛到唯一均衡。这也是我在实际调试中觉得性价比最高的一处修改。6. 进一步扩展方向与实际使用体会代码跑通后我后来在这个框架基础上做过几处扩展成本都不高。第一处是给共享市场引入阶梯价格或碳配额约束把每个产消者的碳排放转换为成本项均衡的结果开始偏向低碳出力实现经济效益和环境效益的兼顾第二处是加入电动汽车充电负荷把EV的充电时段作为可调策略产消者之间的博弈维度会大幅增加收敛速度明显变慢但框架本身无需调整第三处是把确定性光伏曲线换成蒙特卡洛随机场景配合鲁棒优化里的不确定性集合让均衡解具备更强的抗扰动能力。我个人实际运行下来最大的体会是这类项目最花时间的往往不是算法原理理解而是Matlab实现中对矩阵维度和变量索引的反复校对。分布式优化算法的本质是“简单重复”每轮迭代都是在执行同一套子问题求解和变量更新一旦某处维度写错错误会像滚雪球一样在几十轮迭代里反复累积。一个值得养成的习惯是每轮迭代里加一条断言语句检查共享功率向量维度、SOC是否在允许范围、总账是否平衡三到五个断言能拦住九成以上的低级错误。此外迭代过程中把原始残差、对偶残差和价格信息实时打印出来会让你对整个算法的运行状态形成直觉性的感知这在调参时非常重要。如果后续有人想做工程化部署只需把Matlab的子问题求解换成C或者Python的凸优化接口整个分布式框架直接平移这就是这套建模思路的真正价值所在——模型和算法是对的语言只是载体。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑