做微电网或综合能源方向的课题,大概率会遇到这样一个场景:手上有好几个微网的负荷、光伏、热负荷和储能数据,每个微网都有自己的运营方,彼此不能互相指挥,但物理上却硬生生连在同一条联络线或供热管道上。谁该多出力、谁该支援谁、电和热怎么分摊,算账越来越难。我复现《基于纳什博弈的多微网主体电热双层共享策略研究》时,就是为了解决这个问题——借助纳什博弈把多个利益不一的微网主体放进同一个框架里博弈,最后用Matlab把整套双层共享策略从论文符号翻译成可以运行的代码。这篇博文把完整的建模思路、代码实现路径和踩坑记录写下来,适合两类人:想快速复现论文算法做对比实验的研究生,以及面对一堆公式不知道如何落地的实战派从业者。
1. 多微网电热共享,到底在共享什么
1.1 单微网调度早已不够用
先把背景故事说清楚。微网是什么?简单讲,就是一块区域内把分布式光伏、风电、燃气轮机、储能、电锅炉、热泵以及本地负荷打包在一起的自洽供电系统。它既能并网运行,也能独立孤岛运行。单独一个微网做调度时,事情其实很单纯:在一个调度周期内,用最便宜的方式满足本地电负荷和热负荷。之前我做单微网能量管理系统时,这本质上就是一个确定性优化问题:光伏出力、负荷曲线给定,燃气轮机怎么开、储能什么时候充放电,把每天运行成本优化一遍就算完事。
但实际工程里,一个园区、一个区域往往不止一个微网。相邻微网之间有联络线,有的还共用热力管道。这时候单微网调度就不够用了——白天光伏大发而本地负荷低的微网,看着自家的电白白弃掉;另一个微网晚上负荷高企,却要从外部高价买电。如果各自为政,系统整体利用率低,成本也高;如果直接拉通统一调度,从物理层面看是最优的,从组织层面又行不通。原因在于每个微网都有自己的运营商,没有人愿意无条件服从全局调度。这种“物理上互联、管理上自治”的格局,正是后面所有复杂模型的根源。多微网共享调度要做的,就是在承认各主体自治的前提下,通过交易和策略实现比单干更好的结果。
1.2 电和热,是两套节奏完全不同的交易
“电热双层共享”这六个字里,最容易忽略的是电和热的本质差别。我自己复现早期就犯过想当然的错误,以为把电功率换成热功率,模型推导照葫芦画瓢就行,结果算出来的调度方案完全不合理。电和热虽然都是能量,物理属性差异却非常大,建议所有复现的人先把这张差异表建在心里。
| 属性 | 电力共享 | 热力共享 |
|---|---|---|
| 时间尺度 | 秒级到分钟级平衡,实时性极强 | 小时级响应,热惯性大,短时可不严格平衡 |
| 传输介质 | 联络线上的有功功率,损耗按线路电阻计算 | 热水或蒸汽,存在延时与散热损耗,沿程温度下降 |
| 主要约束 | 联络线容量、功率平衡、机组爬坡 | 热网传输容量、节点温度上下限、蓄热罐容量与充放速率 |
| 核心设备 | 燃气轮机、光伏、储能电池 | 热电联产、电锅炉、热泵、蓄热罐 |
| 计量结算 | 有功电能表计量,按kWh结算 | 按热量kJ/GJ结算,计量相对复杂 |
电的物理特性是“瞬间平衡”,发多少用多少,波动要靠储能和机组调节去扛;热的物理特性是“有惯性”,供热管道里的水本身就是储能介质,允许运行时段之间有热量盈余和亏欠,只要终端温度不越界就行。这也是模型里为什么要把电和热分两层处理的原因:电层优化需要精细的功率平衡约束,热层优化则要考虑热惯性、散热损耗和温度约束,两套逻辑不能用一个方程糊弄过去。
1.3 多个“老板”坐在同一张桌上
更关键的是,每个微网背后都是一个利益主体。A微网有余电,凭什么低价卖给B微网?C微网的燃气轮机余热可以供热,但D微网不想付钱,谁强制得了?在工程实际里,这种多主体利益冲突非常普遍,也正是纯物理优化模型难以解决的问题——因为物理优化默认“大家都听调度中心的”。然而真实的多微网系统里,只存在合作意向,不存在统一命令。因此在复现这篇论文时,模型做的最重要的一步,就是把每个微网当作一个独立决策者,让它以自身运行成本最小为目标,去决定共享多少电、共享多少热、接受什么价格。于是问题从“如何一起最优”变成了“如何各自最优且能共存”,这个转换一旦想通,后面看纳什博弈就顺理成章。
2. 纳什博弈为什么能治多主体协调的病
2.1 先分清两类问题:集中优化和分散博弈
很多第一次接触多微网课题的人,第一反应是用集中式优化:把所有微网的数据汇总到调度中心,以全局成本最小为目标,一步求出最优解。这种方法从数学上讲最漂亮,KKT条件一写,求解器一跑就有解。但它有一个致命前提:所有微网愿意把内部数据和决策权交给调度中心。我实际参与某多微网模拟项目时,各运营方的反馈很明确:“我们自己的运行数据可以报,但最终运行方案要我们自己定。”集中优化模型做得再准,落地时也走不通。
于是有了博弈的思路。如果用生活场景打比方,集中式优化像一家人坐在一起,由家长统一安排每个月怎么花钱,目标是全家总支出最小;博弈则更像一群朋友AA聚餐,每个人各自选自己想吃的菜,最后大家会根据别人的选择调整自己的点单,最终达到一个“没有谁愿意独自换菜”的状态。多微网电热共享本质上更像聚餐:大家共享餐桌(电网和热网),但谁都不愿意被指挥,于是只能用博弈来协调。
2.2 纳什均衡的通俗解读与限定条件
纳什均衡是博弈论里最核心的概念。它的定义表述很简洁:所有参与者都选定了自己的策略后,任何一个人单方面改变策略,都不能让自己过得更好,那么这个策略组合就是纳什均衡。注意,它不一定是所有人加起来最好的解,也不一定是公平的解,它只是稳定的解——谁也没有动机单独偏离。
我复现时一度困惑的问题是:纳什均衡和帕累托最优到底什么关系?后来想明白了。帕累托最优关注整体效率,存在一个方案让所有人都比现状更好;纳什均衡关注个体稳定性,每个人在现有牌局里都不愿意单独换牌。两者不一定重合,经典例子是“囚徒困境”:纳什均衡是两人都坦白,而整体最优是两人都沉默,却没人敢先沉默。多微网系统里同样可能出现“整体效益差一点,但个体稳定”的均衡结果,这正是多主体自治必须付出的代价。论文要做的事情,是通过设计共享价格机制和双层结构,把纳什均衡尽量推向更接近帕累托最优的结果。
在使用纳什均衡之前必须确认存在性。如果策略空间不连续、目标函数非凸,均衡可能根本不存在或者不唯一。好在多微网主体模型里,目标函数通常写成二次成本函数,约束是线性等式和不等式,每个主体的可行域是非空紧集,目标函数又是严格凸的,纯策略纳什均衡的存在性基本有保障。这也是整个Matlab复现能稳健跑通的数学基础——如果模型连均衡都不保证存在,代码里看到的只会是一堆不收敛的迭代日志。
2.3 双层结构里上层和下层分别管什么
标题里的“双层”是整个模型的骨架。我一开始把双层理解成两个优化问题直接串联,结果完全不对。认真推导迭代逻辑后才发现,这实际上是“一个博弈在上层、多个优化在下层”的嵌套结构。
上层处理的是共享策略:每个微网主体通过申报自己的共享电力、共享热力功率,或者通过协商形成的交易价格,和其他主体一起迭代,直到收敛到纳什均衡。下层处理的是主体内部的能量管理:在上层给定的共享功率或价格下,每个微网单独求解自己机组出力、储能充放电、蓄热罐充放热的优化问题,得出自身的最优响应。上层每个主体的策略变化会影响下层的优化结果,而下层的优化结果又会反过来影响上层的共享策略,循环往复,直到整个系统达到一个内外一致的稳定状态。
用代码来理解就是:外层while循环是博弈迭代,内层for循环依次求解每个主体的子优化问题。如果论文的数学公式里目标函数有两个优化层级相互嵌套,那基本就是这种结构。复现时要特别注意,上下层的决策变量维度不同、量级不同,迭代时的收敛判据要分别设置,不能用同一个绝对容差去卡,否则很容易因为某一层残差波动大导致外层始终无法收敛。
3. 拆解SCI复现中的数学模型
3.1 主体模型:运行成本怎么构成
把概念理清后,可以进入数学表达。先说明每个主体在下层优化问题里到底在最小化什么。一个典型微网主体的运行成本由几块组成:燃气轮机的燃料成本(通常写成出力的一次或二次函数)、从共享网络购电或购热的费用(或售电售热收入,符号需要注意)、储能的运行损耗成本、设备维护费用。目标函数可以简单归纳为:
主体运行成本 = 本地发电供热成本 + 向共享网络购买电热的费用 - 向共享网络出售电热的收益
我复现时最常犯的错误是正负号。购电费用要写进目标函数,售电收益要从成本里减掉;但如果该主体是多卖方,它的售电收益对于系统整体来讲其实是另一主体的成本,所以博弈的目标函数在不同文献里有不同写法。建议按“自身现金流出最小”来理解,这样正负号不容易写错。目标函数一般写成二次凸函数:
min f_i = Σ_t (a_i·P_i^2(t) + b_i·P_i(t) + c_i) + Σ_t (c_e_buy·P_buy(t) - c_e_sell·P_sell(t)) + 热力部分对应项
其中P_i是主体本地机组的电出力,P_buy和P_sell是主体与共享网络的电力交换量。热力部分的成本同理,可能包括燃气轮机余热回收成本、电锅炉消耗的电费、蓄热罐维护费用等。需要提醒的是,二次项系数a_i非常重要,它决定了目标函数的凸性和迭代收敛特性,复现时不要为了省事把它改成线性项。
3.2 电热耦合设备与共享网络约束
主体内部的约束才是复现工作的主要工作量,约束可以分为三类:设备约束、能量平衡约束、共享网络约束。
先看设备约束。燃气轮机有出力上下限和爬坡约束;储能电池有容量状态递推方程、充放电功率上限、SOC上下限;电锅炉有输出热功率上下限;蓄热罐有储热状态递推、充放热速率限制。其中储能和储热罐的时序耦合约束特别容易写错,尤其是状态递推方程的时间步索引,漏一项或者错一位,结果就会出现“能量凭空消失”的离奇现象。
电热耦合设备的约束是另一个重点。一台CHP机组,发电和产热不是独立的,电出力和热出力存在一个热电比范围,写成约束就是热出力等于电出力乘以热电比,或者热出力落在某个区间内。电锅炉则是把电能转化为热能,转化效率通常在0.9以上。这些设备的存在让电层和热层在物理上无法割裂,这也是为什么模型要强调“电热双层共享”而不是单纯的电共享或热共享。
共享网络约束包括联络线传输功率上限、热力管道传输容量、节点温度约束、共享功率方向限制等。复现时建议把共享功率设成有符号变量:正值表示输入,负值表示输出。传输容量约束写绝对值约束或者拆成两个线性不等式。热力网络往往还要考虑散热损耗,用固定比例系数对传输热量打折扣即可。
3.3 从模型到均衡的求解条件
有了目标和约束,每个主体的子问题就是一个凸二次规划,可以用KKT条件描述最优性。整体怎么形成纳什均衡?通常的做法是构造一个博弈迭代:每个主体在其他主体策略固定时求出自己的最优响应,所有主体同步更新策略,重复迭代。当所有主体的策略残差都小于容差时,认为到达纳什均衡。
在写代码之前,强烈建议先做一遍严格凸性检查。每个主体的目标函数必须是关于自己决策变量的严格凸函数,否则子问题可能有多个最优解,最优化响应是一个集合,迭代过程就会在两个甚至多个解之间来回抖动,永远达不到收敛。论文里往往会保证这一点,但复现时如果自己调整了参数,比如把成本系数改成线性甚至更简单,就很可能会破坏凸性。总之,数学上先确认存在性和凸性,代码才可能收敛;别一上来就埋头写while循环。
4. Matlab实现的关键路径与核心代码逻辑
4.1 复现前先做的事:参数整理与单位统一
很多复现失败不是算法问题,而是单位问题。论文里电力单位用MW,热量单位可能用GJ/h,储能容量用MWh,时间分辨率是1小时;到你自己造数据的时候,一个不留神就把热量和电力数值直接相加了,模型跑出的结果自然是错的。每次复现新模型,第一件事是把所有参数列一张表,统一单位,固定时间分辨率。下面是我复现这套模型时用过的参数清单,仅供参考。
| 参数类别 | 示例参数 | 单位 |
|---|---|---|
| 电源 | 光伏、燃气轮机出力上限 | kW |
| 负荷 | 电负荷、热负荷时序 | kW / kWth |
| 储能 | 电池容量、最大充放电功率 | kWh / kW |
| 储热 | 蓄热罐容量、充放热速率上限 | kWh / kW |
| 耦合设备 | CHP电出力上限、热电比、电锅炉电热效率 | kW / - / - |
| 共享网络 | 联络线容量、热网传输容量、热损失系数 | kW / kW / - |
| 价格 | 购电电价、售电电价、共享电价 | 元/kWh |
单位统一后,在Matlab里把所有已知量放在一个结构体里,比如params。我习惯写成params.pv、params.load_e、params.load_h这样清晰的名字,后面每个子问题都从params取数,避免在公式里硬编码数字。这是看起来琐碎但收益极大的习惯。
4.2 主体优化与博弈迭代的代码框架
模型主体部分的伪代码大致是这个结构:
% 初始化各主体的共享策略 strategy = zeros(n_agent, T); price = initial_price(T); % 初始共享价格 max_iter = 200; tol = 1e-4; for k = 1:max_iter % 下层:各主体独立求解自身最优响应 for i = 1:n_agent % 固定其他主体策略,构建第 i 个主体的约束和目标 x_i = solve_agent_problem(i, price, strategy); new_strategy(i, :) = x_i.share_e + x_i.share_h; end % 上层:根据所有主体的响应更新共享价格或策略 residual = max(abs(new_strategy - strategy), [], 'all'); strategy = alpha * new_strategy + (1 - alpha) * strategy; % 阻尼更新 price = update_price(strategy, price); % 价格调整 fprintf('iter=%d, residual=%.6f\n', k, residual); if residual < tol break; end end真实代码里还需要在solve_agent_problem内部处理储能约束、热网约束等,一般用Matlab优化工具箱的quadprog就能解。根据我的经验,求解工具够用就好,先跑通小规模算例,再考虑引入更复杂的建模层和外部求解器。内层每个主体问题只涉及一个微网的时间序列变量,规模不大,quadprog的耗时完全可以接受。
阻尼更新系数alpha是稳定性的关键。我实测alpha取0.5到0.8之间比较稳;alpha等于1时,如果初始解离均衡点远容易振荡,等于0.2时收敛很慢。迭代判据要分别看策略残差和价格残差,最好同时打印出来,肉眼观察两个量是在同步下降还是互相拉扯。
4.3 三个必须做的正确性验证
代码跑通不等于模型正确。论文复现最容易出现“算得出结果,但结果是错的”。我通常做三组验证,见下表。
| 验证场景 | 做法 | 预期现象 |
|---|---|---|
| 单主体退化 | 把所有微网数据合并成一个主体 | 博弈结果与集中式优化结果一致或误差在可接受范围 |
| 无互济场景 | 把联络线容量设为0、热网解列 | 各主体的调度结果应等于各自独立运行的本地优化结果 |
| 极端负荷场景 | 随机生成100组负荷曲线跑敏感性分析 | 均衡解落在合理范围,不出现功率越限或能量凭空消失 |
第一组验证尤其重要。如果多主体博弈代码在最简单的单主体情况下都不能和常规优化对齐,说明求解逻辑一定有bug;如果两组对齐了,至少说明代码的底层正确性有保障。第二组验证用来检查共享模块有没有引入不必要的耦合;第三组用来检查鲁棒性。这三组验证做完,我才会拿代码去复现论文里的图表,不然方向错了后边全是白费。
有一个小细节值得提:在单主体退化验证时,共享价格的初值并不会影响最终解,因为此时所有物理量都在同一个主体内部,价格只是内部记账;如果这个场景下价格还影响解,说明目标函数正负号或约束耦合写错了。
5. 复现途中踩过的坑与排除过程
5.1 迭代不收敛:罚参数不是越大越好
第一次跑博弈迭代时,我的收敛曲线像心电图,残差在第一轮下降后就开始来回跳。检查了约束和初值都没发现问题,后来逐行分析外层循环才发现,我在共享功率更新时引入了罚项,想让各主体的共享策略被“钉”在目标值附近。罚参数取得很大,本意是想让策略不敢跑偏,结果适得其反:罚项过大,主体每一步都过度反应,策略在下一次迭代时冲过头,形成持续振荡。这就像弹簧两端挂重物,刚挂上去时弹性系数太大,稍微动一下就来回弹。
解决办法是把罚参数调小,并配合阻尼更新。我的经验是先用无罚项的版本跑通,确认迭代本身能收敛,再加罚项;加罚项后,参数从一个较小值开始往上调,观察收敛速度和稳态残差,找到一个让迭代既快又不振荡的区间。这个“先裸跑,再加约束”的思路,对几乎所有分布式算法调试都适用。
5.2 热力约束的非凸性质如何绕开
热电联产设备和热网约束带进来之后,模型不再天然是凸的。典型麻烦来自两类约束:热网管道中的流量乘以温度形成的双线性项;以及某些CHP机组的热电比可变区间在x-y平面上并非凸集。如果直接把这类约束写进优化问题,quadprog会直接报错,被迫回退到穷举或启发式算法,收敛质量难以保证。
论文里通常有化简前提,比如假设热水流量恒定、只控制供回水温度,或把热网模型简化为带损耗系数的热量传输。复现时建议严格遵照论文的简化假设,不要另起炉灶追求更精细的热网模型。那些模型当然更准确,但会破坏原有的凸性,导致根本复现不出论文的数值结论。真正需要精细热网模型时,得换技术路线,比如分解成两个交替迭代的子问题,每轮固定一侧变量。
5.3 初值选择的经验法则与敏感性分析
初值对博弈迭代的影响比想象中大得多。同一个模型,初值不同,最终可能跑到不同的均衡点;遇到非严格凸的目标函数时,这种情况尤其常见。我第一次复现时直接在随机初始策略下跑,结果和论文里给出的均衡差异很大,一度怀疑代码写错。后来做敏感性分析才知道,问题出在多均衡上,不同初值落入了不同的均衡点。
大多数论文会明确或暗示地给出均衡唯一性条件,但复现时参数一改动,条件可能被破坏。我的建议是:先固定一组符合论文参数的初值,跑出稳定结果;然后把初值在可行域内随机扰动几百次,统计均衡策略分布的离散程度。如果结果很集中,说明均衡稳定;如果结果很分散,就要检查是否是目标函数凸性出了问题,或者模型存在多均衡。实际工程里也可以用“前一天的最优解作为今天迭代初值”这种思路,让算法在时间序列上有连续性,比每次从零开始随机初值有效得多。
还有一个经验是要把热层和电层的迭代初值分开设置。由于电层响应快、热层响应慢,合并设置会让热层拖慢电层收敛;单独给热层一个相对保守的初值,再让电层先跑几轮,整体收敛速度会明显提升。这个细节论文里不会写,只有自己跑迭代才会发现。
最后谈一点复现这套模型后的实际感触。论文里的双层博弈模型,看着公式复杂,落到代码上其实可以拆成很多个小而清晰的部分:数据结构、参数表、每个主体的quadprog、外层迭代循环、结果可视化。我复现之前习惯先把论文公式抄一遍再写代码,后来发现更高效的做法是先拿数据试算一个小例子,哪怕只用两个微网两个时段的简化版本,把模型的手算结果和代码输出对上,再扩展到全规模。这样能最快找出正负号、索引和单位的问题。如果你也在复现类似的多主体博弈论文,建议从最小可运行样例出发,逐步加约束、加主体、加时段,每一步都有验证点,最后再和论文图表对比。这个过程虽然慢,但每次报错都会有收获,等全套代码跑通,你对模型的理解深度远超过只看论文的层次。
还想再提醒一句:多微网博弈的初始条件、罚参数、迭代容差这些“工程味”很重的细节,往往是复现出与论文一致结果的关键。论文里的框架是地图,这些细节才是导航。