做微电网优化调度这几年,我发现自己最常被问到的不是某个目标函数怎么写,而是“多个微网放到一起,各自又有各自的小算盘,到底怎么协调才靠谱”。单独一个园区微网做电热联供,调度模型随手就能写;但要扩展到多个微网,让它们既共享电能又共享热能,还要让每个微网都从自身利益出发做决策,问题一下子就变得立体了。这也就是你看到“基于纳什博弈的多微网主体电热双层共享策略Matlab代码”这类题目时,真正要去啃的东西。这篇博客我打算把整个项目从模型到代码走一遍,涵盖为什么用纳什博弈来搭建共享框架、双层策略是怎么拆出来的、每个微网内部的电热联供优化怎么建模,以及Matlab里从数据结构、yalmip建模到博弈迭代循环的完整实现。适合正在做多微网协同调度、综合能源系统仿真、或者准备写博弈类电力论文的读者,照着抄一部分代码也能省不少力气。1. 项目概述与核心问题拆解1.1 标题逐层拆解:先搞清楚这个项目在做什么先把“基于纳什博弈的多微网主体电热双层共享策略Matlab代码”这句话拆开看,里面其实藏着四层信息。第一层是“多微网主体”。这六个字说明场景不是一个独立微网,而是至少两三个、甚至十几个微网同时挂在一张配电网上,彼此之间可能还有热力管网连接。每个微网都是一个独立决策主体,有自己的电负荷、热负荷、发电设备和储能设备,也有自己的运行目标和成本约束。第二层是“电热双层共享”。电可以共享,热也可以共享,但是电网络和热网络的物理特性、传输损耗、交易方式都不一样,所以不能把“共享”简单处理成一个统一的能量池。现实中有很多论文把电和热放在一个模型里,但实际计算时还是要分成本质不同的两个通道来对待。这里的“双层”通常有两层含义:一是上层做共享交互层的决策(也就是微网之间的买卖量、交易价),二是下层做各微网内部的电热联合调度;也有一种理解是,电共享和热共享本身构成两个决策层级,先确定电的交互再迭代热的交互。两种理解都能在代码里落地,后面我会重点讲我采用的双层处理方式。第三层是“纳什博弈”。这是方法论的核心。多微网之间的利益关系,天然就是一群理性参与者各自追求自身成本最小化、同时又被其他参与者的决策所影响的过程。纳什博弈解决的就是这种“谁也压不住谁,最后大家谁都不想再单方面改变自己的策略”的均衡状态。第四层是“Matlab代码”。这说明题目要求不是一篇理论推导型论文,而是实实在在能在Matlab里跑出结果的东西。所以我在下文中会直接把代码结构、函数写法、迭代循环都贴出来,而不是停留在公式和概念表面。1.2 这个项目到底解决了什么问题说一个我实际接触过的场景:某产业园区里规划了三个微网,A微网有燃气热电联产机组和小型光伏,B微网有电锅炉和储能,C微网光伏装得很多但几乎没有供热能力。三个微网之间的电力线路是互通的,热力管道也有一段管网相连,大家离得很近,完全有条件互相买卖电和热。问题来了:如果用一个集中控制器统一调度所有设备,那算起来简单,但现实里A、B、C可能是三个不同的投资运营主体,谁也不愿意把自己的设备控制权交给别人。A想的是“我CHP多发电,自己用不完就高价卖给B和C”,B想的是“我晚上电价低谷时用电锅炉蓄热,白天热价高的时候卖给C”,C想的则是“我白天光伏充到储能,晚上自己的储能不够用了再从A手里买”。这时候就需要一个博弈机制,让每个微网在给定共享电价和热价的前提下,独立做自己的内部调度决策,同时它报出来的买卖量又会反过来影响价格,最终形成一个稳定的均衡。这种模式放在Matlab里,最直接的好处是:你可以把每个微网的内部优化模型写成同一个函数,然后反复调用这个函数来模拟各个微网的决策过程,最后得到一个纳什均衡解。整个过程其实不难,难点在于如何把“博弈迭代”和“双层决策”完整地塞进一套逻辑里。2. 博弈模型架构与双层策略的设计思路2.1 为什么不能用单目标集中式优化很多人第一反应是:既然要让系统总成本最小,为什么不能把所有微网的设备都统一写进一个目标函数,用求解器一把梭?确实能,而且如果所有微网属于同一个运营主体,集中式优化就是最优解,计算效率也高。但一旦每个微网是独立的利益主体,情况就变了——每个微网核心关心的是自己的成本,而不是系统总成本。用一个生活化类比来解释:几个邻居共同出资打一口井,如果由一个设计院统一规划每个人用多少水,理论上总抽水成本最低。但实际是每家都想自己用水时不受限制,同时别人多用水导致水位下降又会让自家抽水成本升高。这时候必须有一套水价机制,让每家基于水价自己决定用多少水。每个家庭根据水价做对自己最有利的决策,最终形成一种“我说不出更好的选择”的状态。在微网调度里也是这样。各微网之间通过共享交易价格耦合,价格又受总供需影响。每个微网在选择自己的购售电量/热量时,会同时影响价格,进而影响其他微网的成本函数。这不再是一个单层的最优化问题,而是一个博弈问题。纳什均衡之所以被广泛使用,是因为它描述了一个稳定点:给定其他微网的策略,每个微网都达到了自身成本最小的策略,没有微网愿意单方面改变自己的购售计划。2.2 纳什博弈的三要素和双层结构定位纳什博弈有三个基本要素:参与者、策略集、效用函数。放在这个项目里,我习惯把它们映射成下面的关系:博弈要素项目中的对应对象说明参与者每个微网作为独立决策主体,不向下级服从策略集各时段的购电/售电量、购热/售热量也就是微网与其他微网之间的交互计划效用函数该微网的综合运行成本(取负值)成本越低,收益越高,所有微网都在最小化自身总成本但这里有个细节:如果只是“多个微网同时决策并互相影响”,在数学上通常被描述为一个非合作博弈,直接求解并不容易,因为每个微网的目标函数里都隐含着其他微网的决策变量。实际实现中比较常见的手段是把共享价格设定为外部变量,并采用迭代出清的方式逼近均衡。这就是“双层”要发挥作用的地方。我采用的方案是:上层是一个共享能源平台,负责制定微网之间的电能交易价格和热能交易价格;下层是各微网在已知共享价格的情况下,独立求解自己内部的电热联合优化问题,产出一组购售计划。上层会根据当前的供需差额调整共享价格,下层在上层给定的价格下再做一轮优化,如此循环直到电热两个共享通道都达到供需平衡。这个结构里,每个微网之间是在进行一场纳什博弈,而每个微网与共享平台之间则是跟随者与价格制定者的关系。2.3 双层共享策略的优势与风险双层结构最大的优势是解耦。如果没有这个中间价格层,每个微网的目标函数里都要直接带入其他微网的决策变量,那么这个问题的规模会随着微网数量增加而爆炸式增长,并且约束矩阵极度稠密。有了价格这个中间变量,微网之间的耦合就被压缩成了一条很细的通道:每轮迭代中,各微网看到的只是一个标量价格,内部决策完全独立计算。但也有风险。最典型的风险是价格更新规则如果不合适,迭代很难收敛到均衡,而是会出现震荡甚至混沌。这一点我放到后面关于代码与调参的部分详述。另一个风险是热网的计算复杂度比电网更高,热能传输有延时、有损耗,很多初版模型会先忽略热网管道的传输延时,只用静态的节点热功率平衡近似。作为入门版本,这个简化是可以接受的。3. 数学模型与关键公式拆解3.1 单个微网内部的电热联合优化模型先看内层模型的数学结构,这是整个代码的基础。内层模型描述的是第 i 个微网在给定共享电价和共享热价后,如何决策自己所有可控设备的出力,使得一天内总运行成本最小。为便于说明,先列基本变量。设调度周期为 24 小时,时间间隔为 1 小时。对于第 i 个微网,t 时段的决策变量包括:燃气轮机/热电联产机组的电出力p_chp(i,t)热电联产机组伴随产生的热出力,近似用固定热电比r_chp计算:h_chp(i,t) = r_chp * p_chp(i,t)电锅炉消耗的电功率p_eb(i,t)电锅炉产生的热功率h_eb(i,t) = eta_eb * p_eb(i,t)向共享平台购电/售电功率pbuy(i,t)和psell(i,t)向共享热网购热/售热功率hbuy(i,t)和hsell(i,t)与上级电网的购电功率pgrid(i,t)储能电池的充/放电功率(可选)目标函数是典型的最小化日运行总成本,公式写出来是:min sum_t [ achp * p_chp(i,t)^2 + bchp * p_chp(i,t) + cchp + cgrid(t) * pgrid(i,t) + cbuy_e(t) * pbuy(i,t) - csell_e(t) * psell(i,t) + cbuy_h(t) * hbuy(i,t) - csell_h(t) * hsell(i,t) ]第一项是CHP的燃料成本,我用二次函数拟合,实际工程中也可以用分段线性函数。第二项是向上级电网购电的成本,cgrid(t)是分时电价。第三项和第四项是与其他微网通过共享平台交互电能的成本或收益,注意售电价格不一定等于购电价格,两者之间存在一个价差,这个价差就是共享平台或线路损耗的体现。第五项和第六项对应热能交易。内层的约束条件包括:电功率平衡:光伏出力 + CHP电出力 + 储能放电 + 购电 + 购自共享电 = 电负荷 + 电锅炉耗电 + 储能充电 + 售电 + 卖给共享电热功率平衡:CHP热出力 + 电锅炉热出力 + 购自共享热 = 热负荷 + 售热给共享热CHP出力上下限约束,以及爬坡约束电锅炉容量约束储能SOC动态方程,充放电功率上下限,以及SOC初末值约束购售电/购售热不能同时发生的约束这些约束用yalmip写起来并不复杂,关键是要把“购售不同时”的互补约束处理好,具体代码我后面会展示。3.2 共享价格更新规则现在关键一步来了:共享平台如何更新价格?这是把内层模型变成纳什博弈的关键。我在代码里用了一个非常朴素但有效的供需差额反馈规则。以电共享通道为例,定义某轮迭代中所有微网的总购电量为Qbuy_e,总售电量为Qsell_e,则供需差额Delta_e = Qbuy_e - Qsell_e。如果Delta_e大于零,说明整体上购电需求大于售电供给,当前电价偏低,应该上调共享电价。反之,如果Delta_e小于零,说明供大于求,进一步下调电价。价格更新公式写为:cbuy_e_next = cbuy_e_cur + kp_e * Delta_e csell_e_next = cbuy_e_next - margin_e其中kp_e是调节步长系数,margin_e是设定的价格差。热力共享通道完全同理,用一个独立的Delta_h去调节热价。这个更新规则在文献里经常被称为“价格反馈出清法”,它的好处是实现简单、不依赖太多的导数信息,和纳什博弈的正向迭代法也能很好匹配。3.3 纳什均衡迭代与双层决策的嵌套关系整个迭代过程是双层嵌套的,可以概括为:外循环负责更新共享交易价格。每一轮外循环中,平台根据上一轮得到的供需差额,用上面的公式调整电、热两个价格通道。内循环则是在给定的当前价格下,让各微网独立完成内部优化,并且重复多次。为什么要重复多次?因为虽然给定价格后各微网内部优化是独立的,但所有微网的新购售计划加在一起,会改变市场总供需,而市场总供需又会通过下一轮价格更新反过来影响各微网的决策。这里我把内循环看作是“微网群对当前价格的最优响应”,用多次迭代直到各微网购售量收敛到不动点,就得到了当前价格下的近似纳什均衡。外循环再更新价格,回到内循环重新求解,直到电、热两个共享通道的供需差额都小于设定阈值。从博弈论角度讲,内循环收敛的解,就是给定价格下多微网之间的纳什均衡。外层价格继续调整,本质上是在主从博弈中寻找上层策略,因此这个模型同时包含了微网之间的非合作纳什博弈,以及共享平台与微网群之间的主从递进关系。这也是标题里“纳什博弈”和“双层”两个关键词的准确对应。4. Matlab代码实现全流程4.1 数据准备与整体代码结构先说整体文件结构。我习惯把一个大项目拆成几个函数文件,而不是把所有代码堆在一个脚本里。这样既可以复用,也方便排查bug。main.m % 主程序,控制双层迭代和结果输出 init_case.m % 初始化微网、设备参数与负荷数据 micro_model_yalmip.m % 单个微网内部优化函数,返回成本与购售量 nash_inner_loop.m % 内循环:固定价格下迭代求解纳什均衡 update_shared_price.m % 外循环:根据供需差额更新共享电价与热价 plot_results.m % 结果可视化初始化部分重点工作是定义所有微网共享的一套基础数据结构。我用结构体数组来表示多个微网,每个元素是一个微网的全部参数。下面给一段极简的初始化代码示例,说明风格:%% 初始化基础时间设置 T = 24; % 调度时段数 dt = 1; % 1小时为单位 %% 微网数量 NM = 3; %% 微网公共价格初值 price.cbuy_e = 0.6; % 共享购电价初值,元/kWh price.csell_e = 0.5; % 共享售电价初值 price.cbuy_h = 0.3; % 共享购热价初值,元/kWh price.csell_h = 0.25; % 共享售热价初值 %% 逐个构建微网对象 network(NM) = struct(); % 预分配 for i = 1:NM network(i).name = sprintf('Microgrid_%d', i); %% 电负荷与热负荷曲线,24x1 network(i).load_e = [ ... ]; % 这里填入实际数据 network(i).load_h = [ ... ]; %% 光伏出力,24x1 network(i).pv = [ ... ]; %% CHP参数 network(i).chp_pmax = 2.0; % MW network(i).chp_pmin = 0.2; % MW network(i).r_chp = 1.2; % 热电比 network(i).a_chp = 0.0005; % 成本二次项 network(i).b_chp = 0.08; % 成本一次项 network(i).c_chp = 0.1; % 常数项 %% 电锅炉 network(i).eb_pmax = 1.2; network(i).eta_eb = 0.9; %% 储能电池 network(i).bat_cap = 2.0; network(i).bat_pmax = 0.8; network(i).battery_soc0 = 0.5; network(i).battery_soc_end = 0.5; end这里需要说明,初值的选取挺讲究的。共享电价初值最好取在上级电网购电价附近,因为如果初值设置过高,所有微网一开始都会选择大量售电而不购电,内循环会直接进入“供远大于求”的极端状态;初值太低则反过来。我在实际调试时,一般先把微网在没有共享通道、只从上级电网购电的情况下跑一遍,算出每个微网在各时段的边际成本,再以这个成本区间作为共享价格初值的参考范围。这个方法看起来笨,但是非常稳。4.2 单微网内部优化函数:用yalmip建模核心函数是micro_model_yalmip.m,它接收某一个微网的结构体m、全时段的价格结构体price、上级分时电价cgrid,返回该微网的最优成本和购售量结果。下面的代码是一个可以跑通框架的简化版本:function [cost_i, plan] = micro_model_yalmip(m, price, cgrid) T = numel(m.load_e); % 决策变量 p_chp = sdpvar(T,1); % CHP电出力 p_eb = sdpvar(T,1); % 电锅炉电输入 pbuy_e = sdpvar(T,1); % 从共享平台购电 psell_e = sdpvar(T,1); % 向共享平台售电 hbuy_h = sdpvar(T,1); % 购热 hsell_h = sdpvar(T,1); % 售热 pgrid = sdpvar(T,1); % 上级购电 pch = sdpvar(T,1); % 储能充电 pdch = sdpvar(T,1); % 储能放电 soc = sdpvar(T+1,1); % 储能SOC % 辅助变量,避免购售同时发生 u_buy_e = binvar(T,1); u_sell_e = binvar(T,1); u_buy_h = binvar(T,1); u_sell_h = binvar(T,1); % 目标函数 obj = 0; for t = 1:T fuel_cost = m.a_chp * p_chp(t)^2 + m.b_chp * p_chp(t) + m.c_chp; obj = obj + fuel_cost ... + cgrid(t) * pgrid(t) ... + price.cbuy_e * pbuy_e(t) - price.csell_e * psell_e(t) ... + price.cbuy_h * hbuy_h(t) - price.csell_h * hsell_h(t); end % 约束集合 constraints = []; for t = 1:T % 电平衡:光伏+CHP+放电+购电+上级购电 = 负荷+电锅炉+充电+售电 constraints = [constraints, ... m.pv(t) + p_chp(t) + pdch(t) + pbuy_e(t) + pgrid(t) ... == m.load_e(t) + p_eb(t) + pch(t) + psell_e(t)]; % 热平衡:热电比供热+电锅炉供热+购热 = 热负荷+售热 constraints = [constraints, ... m.r_chp * p_chp(t) + m.eta_eb * p_eb(t) + hbuy_h(t) ... == m.load_h(t) + hsell_h(t)]; % CHP约束 constraints = [constraints, ... m.chp_pmin <= p_chp(t) <= m.chp_pmax]; constraints = [constraints, ... p_chp(t) - p_chp(max(1,t-1)) >= -0.5]; % 简化爬坡 constraints = [constraints, ... p_chp(t) - p_chp(max(1,t-1)) <= 0.5]; % 电锅炉 constraints = [constraints, 0 <= p_eb(t) <= m.eb_pmax]; % 储能 constraints = [constraints, soc(t+1) == soc(t) + (0.95*pch(t) - pdch(t)/0.95)/m.bat_cap]; constraints = [constraints, 0 <= pch(t) <= m.bat_pmax*u_buy_e(t)]; % 这里仅示例,更稳妥用binvar constraints = [constraints, 0 <= pdch(t) <= m.bat_pmax*u_sell_e(t)]; constraints = [constraints, 0 <= pbuy_e(t) <= 10*u_buy_e(t)]; constraints = [constraints, 0 <= psell_e(t) <= 10*u_sell_e(t)]; constraints = [constraints, 0 <= hbuy_h(t) <= 10*u_buy_h(t)]; constraints = [constraints, 0 <= hsell_h(t) <= 10*u_sell_h(t)]; constraints = [constraints, u_buy_e(t)+u_sell_e(t) <= 1]; constraints = [constraints, u_buy_h(t)+u_sell_h(t) <= 1]; end constraints = [constraints, soc(1) == m.battery_soc0, soc(T+1) == m.battery_soc_end]; constraints = [constraints, 0 <= soc <= 1]; % 求解 ops = sdpsettings('solver', 'cplex', 'verbose', 0); optimize(constraints, obj, ops); % 输出 cost_i = value(obj); plan.p_chp = value(p_chp); plan.p_eb = value(p_eb); plan.pbuy_e = value(pbuy_e); plan.psell_e = value(psell_e); plan.hbuy_h = value(hbuy_h); plan.hsell_h = value(hsell_h); plan.pgrid = value(pgrid); plan.pgrid = value(pgrid); end这段代码展示的是核心骨架,里面第44行以后我用了4个二元变量来禁止购售同时发生,这是一种简单直接的办法。实际工程中要考虑计算负担,因为混合整数变量的引入会让求解时间明显变长。如果微网数量很多,或者调度周期是96点、甚至1440点,二进制变量会让内循环的求解时间无法接受。常见的替代办法是放宽购售同时发生的限制,用商业求解器求解连续二次规划,速度会快很多。不过从严格物理模型角度看,购售同时发生确实不合理,需要根据你的计算资源来权衡。4.3 内循环:纳什均衡迭代有了单微网优化函数,内循环就好办了。内循环的思路是:在当前共享价格下,所有微网独立求解自己的优化问题,得到一批购售计划;然后把这批计划加总,看总购售差是否在可接受范围内;如果不满足,就按价格反馈规则更新共享价格;然后再让所有微网重新求解,不断迭代。这里和前面说的“外循环更新价格”又有一点交互。实际代码我不会把一个循环里做两层价格更新搞得太复杂,最常见的是统一走一条循环:给定一个初始共享价格,所有微网求解得到各自计划,加总求供需差额,如果差额小于阈值则停止,否则更新共享价格再继续。这个价格更新本身就是在逼近市场均衡,当供需差额趋近于零时,对应的结果本质上就是一个近似纳什均衡解。这正好和双层结构匹配:外层平台调整价格,内层微网最优响应,两者交替进行。下面给出内循环代码:function [plan_all, price_final, state] = solve_multi_microgrid(network, price_init, cgrid) NM = length(network); T = numel(network(1).load_e); price = price_init; max_outer_iter = 100; tol = 0.001; state.converged = false; kp_e = 0.02; % 电价格调节步长 kp_h = 0.01; % 热价格调节步长 margin_e = 0.02; % 购售电价差 margin_h = 0.01; for outer = 1:max_outer_iter % 各微网独立优化 plan_all = []; total_buy_e = zeros(T,1); total_sell_e = zeros(T,1); total_buy_h = zeros(T,1); total_sell_h = zeros(T,1); for i = 1:NM [~, plan] = micro_model_yalmip(network(i), price, cgrid); plan_all(i).plan = plan; total_buy_e = total_buy_e + plan.pbuy_e; total_sell_e = total_sell_e + plan.psell_e; total_buy_h = total_buy_h + plan.hbuy_h; total_sell_h = total_sell_h + plan.hsell_h; end del_e = sum(total_buy_e - total_sell_e) / T; del_h = sum(total_buy_h - total_sell_h) / T; if abs(del_e) < tol && abs(del_h) < tol state.converged = true; break; end % 更新价格 price.cbuy_e = price.cbuy_e + kp_e * del_e; price.csell_e = price.cbuy_e - margin_e; price.cbuy_h = price.cbuy_h + kp_h * del_h; price.csell_h = price.cbuy_h - margin_h; % 保底:价格不能低于下限 price.cbuy_e = max(price.cbuy_e, 0.1); price.cbuy_h = max(price.cbuy_h, 0.05); end price_final = price; end代码里用了所有时段总供需差额的平均值作为反馈信号,好处是平滑,不容易因为单个时段偏差过大导致价格剧烈震荡。实际运行时我会在调试过程中把每一次迭代的del_e和del_h打出来,观察数值往哪个方向走——这一步比任何公式都更能帮助你理解博弈是否收敛。4.4 外层双层调度:平台目标与价格优化上面solve_multi_microgrid实现的是微网之间非合作博弈的均衡出清。但“双层”还没有完全体现出上层平台的主动优化作用。在很多项目里,上层共享平台往往不只是被动调价,它也有自己的目标,比如让整个区域从上级电网购电的总成本最小,或者让可再生能源消纳量最大。我把上层逻辑设计成:外层除了用供需差额调整价格之外,还要同时评估平台自己的运行收益。假设平台以“综合运行成本最小 + 可再生能源消纳最高”为目标,那么每一轮外层迭代时,都要根据当前价格下全部微网的购售量,计算平台的总目标值。如果目标值比上一轮好,就保留当前价格;否则反向调整价格。用一个简单的启发式搜索就可以实现:function [best_price, best_cost, history] = outer_principal_loop(network, price_init, cgrid) NM = length(network); T = numel(network(1).load_e); max_outer_iter = 50; price = price_init; best_cost = inf; best_price = price; history = []; for k = 1:max_outer_iter [plan_all, price_final, state] = solve_multi_microgrid(network, price, cgrid); % 上层目标:系统从上级电网购电成本 + 共享交易网损成本 obj_upper = 0; for i = 1:NM obj_upper = obj_upper + sum(cgrid .* plan_all(i).plan.pgrid); end % 加上共享交易过网费,这里以价差乘交易量近似 for i = 1:NM obj_upper = obj_upper ... + (price_final.cbuy_e - price_final.csell_e) * sum(plan_all(i).plan.pbuy_e) * 0.1 ... + (price_final.cbuy_h - price_final.csell_h) * sum(plan_all(i).plan.hbuy_h) * 0.1; end history(end+1) = obj_upper; if obj_upper < best_cost && state.converged best_cost = obj_upper; best_price = price_final; end % 如果已经收敛且结果稳定,退出 if state.converged && k > 5 break; end % 扰动价格,作为上层寻优手段;实际项目中可用粒子群代替 if mod(k,2) == 0 price.cbuy_e = price.cbuy_e * 0.98; else price.cbuy_e = price.cbuy_e * 1.02; end price.csell_e = price.cbuy_e - 0.02; end end这段代码的思想更接近“启发式外层寻优”,它不像数学规划那样保证找到全局最优,但在微网规模小、价格维度低的情况下,配合低价差、低压调步长,可以得到稳定的近似最优解。如果你的项目要求更严谨,可以把外层替换成粒子群、模拟退火等群体智能算法,替换入口我已经留好了。4.5 结果可视化与关键指标跑完整个双层博弈,至少要看三组结果。第一组是各微网的购售电计划曲线,看每个微网在哪些时段买、哪些时段卖,这能直观反映博弈行为。第二组是电热共享价格随迭代的收敛曲线,看价格是否稳定下来。第三组是系统负荷平衡情况,确保总购电加总售电基本平衡。绘制图表时我贴出的代码通常类似:figure; subplot(2,1,1); stairs(1:T, price_final.cbuy_e, 'r-'); hold on; stairs(1:T, price_final.csell_e, 'b--'); legend('共享购电价','共享售电价'); xlabel('时段/h'); ylabel('价格/(元/kWh)'); subplot(2,1,2); for i = 1:NM stairs(1:T, plan_all(i).plan.pbuy_e - plan_all(i).plan.psell_e); hold on; end legend('微网1','微网2','微网3'); xlabel('时段/h'); ylabel('购售电量/MW');看这些曲线的时候有个小技巧:如果某个时段出现一个微网很短的时间窗内同时出现大量买入和大量卖出,大概率是模型里购售互斥约束的二元变量在相邻时段切换时引起的震荡。可以把这个时段的设备出力曲线也画出来,多半能在电锅炉的启停或者储能充放电逻辑上找到原因。5. 常见问题与排查技巧实录5.1 迭代不收敛,价格和购售量一直抖动这是多微网博弈代码里最常见的问题。我个人调试时遇到过几次,现在归纳一下最典型的三种原因。第一种原因是价格调节步长kp_e取得太大。价格反馈的步长过大时,每一轮价格调整都会过冲,上一轮还是供大于求压低了价格,下一轮立刻变成供不应求把价格推上去,形成一个周期震荡。这种震荡的图像通常是两条线在某个平衡点附近来回摆动,幅度不衰减。解决方法是把步长缩小到原值的五分之一甚至十分之一,然后观察波形是否收敛。缺点是收敛速度变慢,实际项目中可以用自适应步长,即供需差额大时步长取大些,差额接近阈值时逐步缩小步长。第二种原因是目标函数非凸。CHP燃料成本用了二次函数,这个本身是凸的,问题不大。但如果加了储能、电锅炉爬坡、购售切换二元变量,整个内层问题就变成了混合整数二次规划。多个带二进制变量的优化问题串行迭代时,很容易出现类似“囚徒困境”的切换行为。为了降低复杂性,可以在内层迭代时暂时固定储能的充放电状态,等大方向收敛后再放开。第三种原因是个别微网对价格非常不敏感,不是因为价格低就不卖,而是受到设备容量限制,价格怎么变都没用。调试时可以先看每个微网自身的容量约束是否过紧,比如某微网CHP容量只有0.2MW,而它的电负荷有1MW,那它大概率每一轮都会大量购电,价格波动对它几乎没影响。这个现象本身不一定是问题,但会让系统整体价格长时间朝一个方向漂移,导致总迭代次数暴涨。5.2 内层优化返回不可行,模型直接崩了不可行问题的根源通常是约束之间互相冲突。我遇到过几次特别典型的。一个是储能SOC约束里,初值SOC和末值SOC设成了0.5,但整个周期内负荷峰值前后相差太大,导致储能必须在一个时段内既大量充电又大量放电,而容量约束不允许,于是模型直接不可行。解决方法是不要同时卡初值SOC和末值SOC,先放开末值约束,看结果落在什么位置,再根据实际需要调整电池初始容量。另一个典型冲突是热平衡约束里只考虑了电锅炉供热和CHP热回收,没有加辅助热源,一旦热负荷峰值超过了这两个设备的供热能力上限,模型同样会报不可行。在多微网共享场景中,某个微网自己热源不足时正常应该通过热网向其他微网购热,但要确保该微网的热力管道容量上限约束没有写得过紧,否则购热也被限制了,问题就无解。检查时先把所有容量上限约束放大十倍,如果模型立刻变得可行,基本就是容量值写得不合理。用yalmip排查不可行问题有一个很好用的手段:把optimize的第二个返回值拿出来看,diagnostic里会提示inf或NaNs。还可以把约束拆成几组逐一测试,比如先单独测电平衡约束是不是能跑通,再单独测热平衡约束。这样定位比空想快得多。5.3 求解时间过长,内循环成为瓶颈这个项目如果用yalmip加Cplex求解,最耗时的部分是每一轮外层迭代里,每个微网都要重新调用一次optimize,而这些微网问题里又都带二进制变量。假设3个微网、100次外层迭代,每次内循环要反复调用多次,总求解次数会到几百上千次。就算单次求解只有一两秒,整个实验跑下来也要几十分钟。我的优化策略是三步走。第一步,把购售互斥问题尽量简化为约束松弛,不要用二元变量,而是直接加入一个很小的惩罚项,这样问题从MILP退化为连续二次规划,速度提升非常明显。第二步,提前把光伏、负荷曲线、上级电价做向量化存储,尽量避免在循环里反复访问结构体字段。第三步,把同一轮的3个微网优化变成并行计算,用Parfor替代for,多核机器上可以快三倍。如果项目非常大,还有个思路是引入经济学里的“价格响应函数”,先把每个微网在不同价格下的响应曲线离线计算出来,再用插值或者神经网络拟合出价格到购售量的映射,在线运行时直接用映射求解均衡。这样内循环就变成了解一组方程组,速度可以快几个数量级。5.4 纳什均衡解和集中式最优解差太多:这是正常现象第一次跑多微网博弈的程序,很多人会拿结果和集中式单目标优化对比,然后发现系统总成本高了不少。这不一定是代码错了,而是博弈本来就会产生“个体最优之和劣于总体最优”的结果,这正是非合作博弈的基本特征。共享平台通过价格来缩小组网的外部性,价差里就包含了平台收益和网损成本。只要每个微网之间没有强制契约,这种效率损失就是天然存在的。如果你的目标是让系统总成本尽可能接近集中式最优,可以尝试调整共享价格的价差margin_e和margin_h。价差越大,微网之间交易的积极性越差,每个微网会更依赖上级电网购电,总成本反而高;价差越小,共享交易越充分,系统总成本越接近理想情况,但平台的过网费收益就越少。这个参数实际上是整个博弈策略里最核心的调节旋钮。6. 一些比较私人的实操体会整套框架跑下来,我最想说的是:博弈类项目在Matlab里真正难的地方,不在于公式推导,也不在于yalmip建模,而在于把所有决策层之间的“反馈回路”理清楚,并且用一个稳定的数值迭代方式把它跑起来。我第一次写这个项目时,花了大量时间在理论上纠结到底什么是纳什均衡解,后来发现直接上手写循环反而更容易理解。你先让所有微网在当前价格下各自算一遍,看供需缺多少,调个价格,再算一遍,只要这个循环满足一定的条件,它就是博弈论里所说的最优响应动态,收敛点就是纳什均衡。所谓“两层”,也不过就是在这个循环外面再加一层价格寻优。如果让我给新手一个建议,我会说先不要追求模型庞大。用两个微网、8个时段的缩小算例,把整个双层循环跑通,画出供需差额和价格收敛曲线,确定逻辑没有问题,再慢慢扩展到24点、更多微网。因为微网数量一多,价格迭代的不稳定性和参数敏感性都会成倍上升,到时候很难分清是模型问题还是代码bug。缩微算例里你可能几分钟就找到问题,扩展到完整场景可能要折腾一整天。另外有两点小经验一直让我的项目少走了弯路:一是所有外部输入数据都要单独放一个初始化文件,不要在微网优化函数里硬编码,否则后续换算例时你根本不知道该改哪里;二是每一轮外层迭代的结果一定要保存成结构化数据或者表格,方便画收敛曲线。调试时远远看画出来的曲线,跟盯着命令行里刷出来的数字相比,完全是两种效率。如果后续想把这个模型往更深的学术方向扩展,我建议把内层循环里的价格反馈迭代换成基于演化博弈的复制者动态,或者把储能设备和热网管道延时加入模型。那时你面对的就不再是一个简单的共享能源调度问题,而是一个有真实动态特性的多主体博弈系统,思路和代码都会上升到新台阶。但至少现在,先用这篇文章里的框架把基础版本跑通,已经是很大的第一步了。
做微电网优化调度这几年,我发现自己最常被问到的不是某个目标函数怎么写,而是“多个微网放到一起,各自又有各自的小算盘,到底怎么协调才靠谱”。单独一个园区微网做电热联供,调度模型随手就能写;但要扩展到多个微网,让它们既共享电能又共享热能,还要让每个微网都从自身利益出发做决策,问题一下子就变得立体了。这也就是你看到“基于纳什博弈的多微网主体电热双层共享策略Matlab代码”这类题目时,真正要去啃的东西。这篇博客我打算把整个项目从模型到代码走一遍,涵盖为什么用纳什博弈来搭建共享框架、双层策略是怎么拆出来的、每个微网内部的电热联供优化怎么建模,以及Matlab里从数据结构、yalmip建模到博弈迭代循环的完整实现。适合正在做多微网协同调度、综合能源系统仿真、或者准备写博弈类电力论文的读者,照着抄一部分代码也能省不少力气。1. 项目概述与核心问题拆解1.1 标题逐层拆解:先搞清楚这个项目在做什么先把“基于纳什博弈的多微网主体电热双层共享策略Matlab代码”这句话拆开看,里面其实藏着四层信息。第一层是“多微网主体”。这六个字说明场景不是一个独立微网,而是至少两三个、甚至十几个微网同时挂在一张配电网上,彼此之间可能还有热力管网连接。每个微网都是一个独立决策主体,有自己的电负荷、热负荷、发电设备和储能设备,也有自己的运行目标和成本约束。第二层是“电热双层共享”。电可以共享,热也可以共享,但是电网络和热网络的物理特性、传输损耗、交易方式都不一样,所以不能把“共享”简单处理成一个统一的能量池。现实中有很多论文把电和热放在一个模型里,但实际计算时还是要分成本质不同的两个通道来对待。这里的“双层”通常有两层含义:一是上层做共享交互层的决策(也就是微网之间的买卖量、交易价),二是下层做各微网内部的电热联合调度;也有一种理解是,电共享和热共享本身构成两个决策层级,先确定电的交互再迭代热的交互。两种理解都能在代码里落地,后面我会重点讲我采用的双层处理方式。第三层是“纳什博弈”。这是方法论的核心。多微网之间的利益关系,天然就是一群理性参与者各自追求自身成本最小化、同时又被其他参与者的决策所影响的过程。纳什博弈解决的就是这种“谁也压不住谁,最后大家谁都不想再单方面改变自己的策略”的均衡状态。第四层是“Matlab代码”。这说明题目要求不是一篇理论推导型论文,而是实实在在能在Matlab里跑出结果的东西。所以我在下文中会直接把代码结构、函数写法、迭代循环都贴出来,而不是停留在公式和概念表面。1.2 这个项目到底解决了什么问题说一个我实际接触过的场景:某产业园区里规划了三个微网,A微网有燃气热电联产机组和小型光伏,B微网有电锅炉和储能,C微网光伏装得很多但几乎没有供热能力。三个微网之间的电力线路是互通的,热力管道也有一段管网相连,大家离得很近,完全有条件互相买卖电和热。问题来了:如果用一个集中控制器统一调度所有设备,那算起来简单,但现实里A、B、C可能是三个不同的投资运营主体,谁也不愿意把自己的设备控制权交给别人。A想的是“我CHP多发电,自己用不完就高价卖给B和C”,B想的是“我晚上电价低谷时用电锅炉蓄热,白天热价高的时候卖给C”,C想的则是“我白天光伏充到储能,晚上自己的储能不够用了再从A手里买”。这时候就需要一个博弈机制,让每个微网在给定共享电价和热价的前提下,独立做自己的内部调度决策,同时它报出来的买卖量又会反过来影响价格,最终形成一个稳定的均衡。这种模式放在Matlab里,最直接的好处是:你可以把每个微网的内部优化模型写成同一个函数,然后反复调用这个函数来模拟各个微网的决策过程,最后得到一个纳什均衡解。整个过程其实不难,难点在于如何把“博弈迭代”和“双层决策”完整地塞进一套逻辑里。2. 博弈模型架构与双层策略的设计思路2.1 为什么不能用单目标集中式优化很多人第一反应是:既然要让系统总成本最小,为什么不能把所有微网的设备都统一写进一个目标函数,用求解器一把梭?确实能,而且如果所有微网属于同一个运营主体,集中式优化就是最优解,计算效率也高。但一旦每个微网是独立的利益主体,情况就变了——每个微网核心关心的是自己的成本,而不是系统总成本。用一个生活化类比来解释:几个邻居共同出资打一口井,如果由一个设计院统一规划每个人用多少水,理论上总抽水成本最低。但实际是每家都想自己用水时不受限制,同时别人多用水导致水位下降又会让自家抽水成本升高。这时候必须有一套水价机制,让每家基于水价自己决定用多少水。每个家庭根据水价做对自己最有利的决策,最终形成一种“我说不出更好的选择”的状态。在微网调度里也是这样。各微网之间通过共享交易价格耦合,价格又受总供需影响。每个微网在选择自己的购售电量/热量时,会同时影响价格,进而影响其他微网的成本函数。这不再是一个单层的最优化问题,而是一个博弈问题。纳什均衡之所以被广泛使用,是因为它描述了一个稳定点:给定其他微网的策略,每个微网都达到了自身成本最小的策略,没有微网愿意单方面改变自己的购售计划。2.2 纳什博弈的三要素和双层结构定位纳什博弈有三个基本要素:参与者、策略集、效用函数。放在这个项目里,我习惯把它们映射成下面的关系:博弈要素项目中的对应对象说明参与者每个微网作为独立决策主体,不向下级服从策略集各时段的购电/售电量、购热/售热量也就是微网与其他微网之间的交互计划效用函数该微网的综合运行成本(取负值)成本越低,收益越高,所有微网都在最小化自身总成本但这里有个细节:如果只是“多个微网同时决策并互相影响”,在数学上通常被描述为一个非合作博弈,直接求解并不容易,因为每个微网的目标函数里都隐含着其他微网的决策变量。实际实现中比较常见的手段是把共享价格设定为外部变量,并采用迭代出清的方式逼近均衡。这就是“双层”要发挥作用的地方。我采用的方案是:上层是一个共享能源平台,负责制定微网之间的电能交易价格和热能交易价格;下层是各微网在已知共享价格的情况下,独立求解自己内部的电热联合优化问题,产出一组购售计划。上层会根据当前的供需差额调整共享价格,下层在上层给定的价格下再做一轮优化,如此循环直到电热两个共享通道都达到供需平衡。这个结构里,每个微网之间是在进行一场纳什博弈,而每个微网与共享平台之间则是跟随者与价格制定者的关系。2.3 双层共享策略的优势与风险双层结构最大的优势是解耦。如果没有这个中间价格层,每个微网的目标函数里都要直接带入其他微网的决策变量,那么这个问题的规模会随着微网数量增加而爆炸式增长,并且约束矩阵极度稠密。有了价格这个中间变量,微网之间的耦合就被压缩成了一条很细的通道:每轮迭代中,各微网看到的只是一个标量价格,内部决策完全独立计算。但也有风险。最典型的风险是价格更新规则如果不合适,迭代很难收敛到均衡,而是会出现震荡甚至混沌。这一点我放到后面关于代码与调参的部分详述。另一个风险是热网的计算复杂度比电网更高,热能传输有延时、有损耗,很多初版模型会先忽略热网管道的传输延时,只用静态的节点热功率平衡近似。作为入门版本,这个简化是可以接受的。3. 数学模型与关键公式拆解3.1 单个微网内部的电热联合优化模型先看内层模型的数学结构,这是整个代码的基础。内层模型描述的是第 i 个微网在给定共享电价和共享热价后,如何决策自己所有可控设备的出力,使得一天内总运行成本最小。为便于说明,先列基本变量。设调度周期为 24 小时,时间间隔为 1 小时。对于第 i 个微网,t 时段的决策变量包括:燃气轮机/热电联产机组的电出力p_chp(i,t)热电联产机组伴随产生的热出力,近似用固定热电比r_chp计算:h_chp(i,t) = r_chp * p_chp(i,t)电锅炉消耗的电功率p_eb(i,t)电锅炉产生的热功率h_eb(i,t) = eta_eb * p_eb(i,t)向共享平台购电/售电功率pbuy(i,t)和psell(i,t)向共享热网购热/售热功率hbuy(i,t)和hsell(i,t)与上级电网的购电功率pgrid(i,t)储能电池的充/放电功率(可选)目标函数是典型的最小化日运行总成本,公式写出来是:min sum_t [ achp * p_chp(i,t)^2 + bchp * p_chp(i,t) + cchp + cgrid(t) * pgrid(i,t) + cbuy_e(t) * pbuy(i,t) - csell_e(t) * psell(i,t) + cbuy_h(t) * hbuy(i,t) - csell_h(t) * hsell(i,t) ]第一项是CHP的燃料成本,我用二次函数拟合,实际工程中也可以用分段线性函数。第二项是向上级电网购电的成本,cgrid(t)是分时电价。第三项和第四项是与其他微网通过共享平台交互电能的成本或收益,注意售电价格不一定等于购电价格,两者之间存在一个价差,这个价差就是共享平台或线路损耗的体现。第五项和第六项对应热能交易。内层的约束条件包括:电功率平衡:光伏出力 + CHP电出力 + 储能放电 + 购电 + 购自共享电 = 电负荷 + 电锅炉耗电 + 储能充电 + 售电 + 卖给共享电热功率平衡:CHP热出力 + 电锅炉热出力 + 购自共享热 = 热负荷 + 售热给共享热CHP出力上下限约束,以及爬坡约束电锅炉容量约束储能SOC动态方程,充放电功率上下限,以及SOC初末值约束购售电/购售热不能同时发生的约束这些约束用yalmip写起来并不复杂,关键是要把“购售不同时”的互补约束处理好,具体代码我后面会展示。3.2 共享价格更新规则现在关键一步来了:共享平台如何更新价格?这是把内层模型变成纳什博弈的关键。我在代码里用了一个非常朴素但有效的供需差额反馈规则。以电共享通道为例,定义某轮迭代中所有微网的总购电量为Qbuy_e,总售电量为Qsell_e,则供需差额Delta_e = Qbuy_e - Qsell_e。如果Delta_e大于零,说明整体上购电需求大于售电供给,当前电价偏低,应该上调共享电价。反之,如果Delta_e小于零,说明供大于求,进一步下调电价。价格更新公式写为:cbuy_e_next = cbuy_e_cur + kp_e * Delta_e csell_e_next = cbuy_e_next - margin_e其中kp_e是调节步长系数,margin_e是设定的价格差。热力共享通道完全同理,用一个独立的Delta_h去调节热价。这个更新规则在文献里经常被称为“价格反馈出清法”,它的好处是实现简单、不依赖太多的导数信息,和纳什博弈的正向迭代法也能很好匹配。3.3 纳什均衡迭代与双层决策的嵌套关系整个迭代过程是双层嵌套的,可以概括为:外循环负责更新共享交易价格。每一轮外循环中,平台根据上一轮得到的供需差额,用上面的公式调整电、热两个价格通道。内循环则是在给定的当前价格下,让各微网独立完成内部优化,并且重复多次。为什么要重复多次?因为虽然给定价格后各微网内部优化是独立的,但所有微网的新购售计划加在一起,会改变市场总供需,而市场总供需又会通过下一轮价格更新反过来影响各微网的决策。这里我把内循环看作是“微网群对当前价格的最优响应”,用多次迭代直到各微网购售量收敛到不动点,就得到了当前价格下的近似纳什均衡。外循环再更新价格,回到内循环重新求解,直到电、热两个共享通道的供需差额都小于设定阈值。从博弈论角度讲,内循环收敛的解,就是给定价格下多微网之间的纳什均衡。外层价格继续调整,本质上是在主从博弈中寻找上层策略,因此这个模型同时包含了微网之间的非合作纳什博弈,以及共享平台与微网群之间的主从递进关系。这也是标题里“纳什博弈”和“双层”两个关键词的准确对应。4. Matlab代码实现全流程4.1 数据准备与整体代码结构先说整体文件结构。我习惯把一个大项目拆成几个函数文件,而不是把所有代码堆在一个脚本里。这样既可以复用,也方便排查bug。main.m % 主程序,控制双层迭代和结果输出 init_case.m % 初始化微网、设备参数与负荷数据 micro_model_yalmip.m % 单个微网内部优化函数,返回成本与购售量 nash_inner_loop.m % 内循环:固定价格下迭代求解纳什均衡 update_shared_price.m % 外循环:根据供需差额更新共享电价与热价 plot_results.m % 结果可视化初始化部分重点工作是定义所有微网共享的一套基础数据结构。我用结构体数组来表示多个微网,每个元素是一个微网的全部参数。下面给一段极简的初始化代码示例,说明风格:%% 初始化基础时间设置 T = 24; % 调度时段数 dt = 1; % 1小时为单位 %% 微网数量 NM = 3; %% 微网公共价格初值 price.cbuy_e = 0.6; % 共享购电价初值,元/kWh price.csell_e = 0.5; % 共享售电价初值 price.cbuy_h = 0.3; % 共享购热价初值,元/kWh price.csell_h = 0.25; % 共享售热价初值 %% 逐个构建微网对象 network(NM) = struct(); % 预分配 for i = 1:NM network(i).name = sprintf('Microgrid_%d', i); %% 电负荷与热负荷曲线,24x1 network(i).load_e = [ ... ]; % 这里填入实际数据 network(i).load_h = [ ... ]; %% 光伏出力,24x1 network(i).pv = [ ... ]; %% CHP参数 network(i).chp_pmax = 2.0; % MW network(i).chp_pmin = 0.2; % MW network(i).r_chp = 1.2; % 热电比 network(i).a_chp = 0.0005; % 成本二次项 network(i).b_chp = 0.08; % 成本一次项 network(i).c_chp = 0.1; % 常数项 %% 电锅炉 network(i).eb_pmax = 1.2; network(i).eta_eb = 0.9; %% 储能电池 network(i).bat_cap = 2.0; network(i).bat_pmax = 0.8; network(i).battery_soc0 = 0.5; network(i).battery_soc_end = 0.5; end这里需要说明,初值的选取挺讲究的。共享电价初值最好取在上级电网购电价附近,因为如果初值设置过高,所有微网一开始都会选择大量售电而不购电,内循环会直接进入“供远大于求”的极端状态;初值太低则反过来。我在实际调试时,一般先把微网在没有共享通道、只从上级电网购电的情况下跑一遍,算出每个微网在各时段的边际成本,再以这个成本区间作为共享价格初值的参考范围。这个方法看起来笨,但是非常稳。4.2 单微网内部优化函数:用yalmip建模核心函数是micro_model_yalmip.m,它接收某一个微网的结构体m、全时段的价格结构体price、上级分时电价cgrid,返回该微网的最优成本和购售量结果。下面的代码是一个可以跑通框架的简化版本:function [cost_i, plan] = micro_model_yalmip(m, price, cgrid) T = numel(m.load_e); % 决策变量 p_chp = sdpvar(T,1); % CHP电出力 p_eb = sdpvar(T,1); % 电锅炉电输入 pbuy_e = sdpvar(T,1); % 从共享平台购电 psell_e = sdpvar(T,1); % 向共享平台售电 hbuy_h = sdpvar(T,1); % 购热 hsell_h = sdpvar(T,1); % 售热 pgrid = sdpvar(T,1); % 上级购电 pch = sdpvar(T,1); % 储能充电 pdch = sdpvar(T,1); % 储能放电 soc = sdpvar(T+1,1); % 储能SOC % 辅助变量,避免购售同时发生 u_buy_e = binvar(T,1); u_sell_e = binvar(T,1); u_buy_h = binvar(T,1); u_sell_h = binvar(T,1); % 目标函数 obj = 0; for t = 1:T fuel_cost = m.a_chp * p_chp(t)^2 + m.b_chp * p_chp(t) + m.c_chp; obj = obj + fuel_cost ... + cgrid(t) * pgrid(t) ... + price.cbuy_e * pbuy_e(t) - price.csell_e * psell_e(t) ... + price.cbuy_h * hbuy_h(t) - price.csell_h * hsell_h(t); end % 约束集合 constraints = []; for t = 1:T % 电平衡:光伏+CHP+放电+购电+上级购电 = 负荷+电锅炉+充电+售电 constraints = [constraints, ... m.pv(t) + p_chp(t) + pdch(t) + pbuy_e(t) + pgrid(t) ... == m.load_e(t) + p_eb(t) + pch(t) + psell_e(t)]; % 热平衡:热电比供热+电锅炉供热+购热 = 热负荷+售热 constraints = [constraints, ... m.r_chp * p_chp(t) + m.eta_eb * p_eb(t) + hbuy_h(t) ... == m.load_h(t) + hsell_h(t)]; % CHP约束 constraints = [constraints, ... m.chp_pmin <= p_chp(t) <= m.chp_pmax]; constraints = [constraints, ... p_chp(t) - p_chp(max(1,t-1)) >= -0.5]; % 简化爬坡 constraints = [constraints, ... p_chp(t) - p_chp(max(1,t-1)) <= 0.5]; % 电锅炉 constraints = [constraints, 0 <= p_eb(t) <= m.eb_pmax]; % 储能 constraints = [constraints, soc(t+1) == soc(t) + (0.95*pch(t) - pdch(t)/0.95)/m.bat_cap]; constraints = [constraints, 0 <= pch(t) <= m.bat_pmax*u_buy_e(t)]; % 这里仅示例,更稳妥用binvar constraints = [constraints, 0 <= pdch(t) <= m.bat_pmax*u_sell_e(t)]; constraints = [constraints, 0 <= pbuy_e(t) <= 10*u_buy_e(t)]; constraints = [constraints, 0 <= psell_e(t) <= 10*u_sell_e(t)]; constraints = [constraints, 0 <= hbuy_h(t) <= 10*u_buy_h(t)]; constraints = [constraints, 0 <= hsell_h(t) <= 10*u_sell_h(t)]; constraints = [constraints, u_buy_e(t)+u_sell_e(t) <= 1]; constraints = [constraints, u_buy_h(t)+u_sell_h(t) <= 1]; end constraints = [constraints, soc(1) == m.battery_soc0, soc(T+1) == m.battery_soc_end]; constraints = [constraints, 0 <= soc <= 1]; % 求解 ops = sdpsettings('solver', 'cplex', 'verbose', 0); optimize(constraints, obj, ops); % 输出 cost_i = value(obj); plan.p_chp = value(p_chp); plan.p_eb = value(p_eb); plan.pbuy_e = value(pbuy_e); plan.psell_e = value(psell_e); plan.hbuy_h = value(hbuy_h); plan.hsell_h = value(hsell_h); plan.pgrid = value(pgrid); plan.pgrid = value(pgrid); end这段代码展示的是核心骨架,里面第44行以后我用了4个二元变量来禁止购售同时发生,这是一种简单直接的办法。实际工程中要考虑计算负担,因为混合整数变量的引入会让求解时间明显变长。如果微网数量很多,或者调度周期是96点、甚至1440点,二进制变量会让内循环的求解时间无法接受。常见的替代办法是放宽购售同时发生的限制,用商业求解器求解连续二次规划,速度会快很多。不过从严格物理模型角度看,购售同时发生确实不合理,需要根据你的计算资源来权衡。4.3 内循环:纳什均衡迭代有了单微网优化函数,内循环就好办了。内循环的思路是:在当前共享价格下,所有微网独立求解自己的优化问题,得到一批购售计划;然后把这批计划加总,看总购售差是否在可接受范围内;如果不满足,就按价格反馈规则更新共享价格;然后再让所有微网重新求解,不断迭代。这里和前面说的“外循环更新价格”又有一点交互。实际代码我不会把一个循环里做两层价格更新搞得太复杂,最常见的是统一走一条循环:给定一个初始共享价格,所有微网求解得到各自计划,加总求供需差额,如果差额小于阈值则停止,否则更新共享价格再继续。这个价格更新本身就是在逼近市场均衡,当供需差额趋近于零时,对应的结果本质上就是一个近似纳什均衡解。这正好和双层结构匹配:外层平台调整价格,内层微网最优响应,两者交替进行。下面给出内循环代码:function [plan_all, price_final, state] = solve_multi_microgrid(network, price_init, cgrid) NM = length(network); T = numel(network(1).load_e); price = price_init; max_outer_iter = 100; tol = 0.001; state.converged = false; kp_e = 0.02; % 电价格调节步长 kp_h = 0.01; % 热价格调节步长 margin_e = 0.02; % 购售电价差 margin_h = 0.01; for outer = 1:max_outer_iter % 各微网独立优化 plan_all = []; total_buy_e = zeros(T,1); total_sell_e = zeros(T,1); total_buy_h = zeros(T,1); total_sell_h = zeros(T,1); for i = 1:NM [~, plan] = micro_model_yalmip(network(i), price, cgrid); plan_all(i).plan = plan; total_buy_e = total_buy_e + plan.pbuy_e; total_sell_e = total_sell_e + plan.psell_e; total_buy_h = total_buy_h + plan.hbuy_h; total_sell_h = total_sell_h + plan.hsell_h; end del_e = sum(total_buy_e - total_sell_e) / T; del_h = sum(total_buy_h - total_sell_h) / T; if abs(del_e) < tol && abs(del_h) < tol state.converged = true; break; end % 更新价格 price.cbuy_e = price.cbuy_e + kp_e * del_e; price.csell_e = price.cbuy_e - margin_e; price.cbuy_h = price.cbuy_h + kp_h * del_h; price.csell_h = price.cbuy_h - margin_h; % 保底:价格不能低于下限 price.cbuy_e = max(price.cbuy_e, 0.1); price.cbuy_h = max(price.cbuy_h, 0.05); end price_final = price; end代码里用了所有时段总供需差额的平均值作为反馈信号,好处是平滑,不容易因为单个时段偏差过大导致价格剧烈震荡。实际运行时我会在调试过程中把每一次迭代的del_e和del_h打出来,观察数值往哪个方向走——这一步比任何公式都更能帮助你理解博弈是否收敛。4.4 外层双层调度:平台目标与价格优化上面solve_multi_microgrid实现的是微网之间非合作博弈的均衡出清。但“双层”还没有完全体现出上层平台的主动优化作用。在很多项目里,上层共享平台往往不只是被动调价,它也有自己的目标,比如让整个区域从上级电网购电的总成本最小,或者让可再生能源消纳量最大。我把上层逻辑设计成:外层除了用供需差额调整价格之外,还要同时评估平台自己的运行收益。假设平台以“综合运行成本最小 + 可再生能源消纳最高”为目标,那么每一轮外层迭代时,都要根据当前价格下全部微网的购售量,计算平台的总目标值。如果目标值比上一轮好,就保留当前价格;否则反向调整价格。用一个简单的启发式搜索就可以实现:function [best_price, best_cost, history] = outer_principal_loop(network, price_init, cgrid) NM = length(network); T = numel(network(1).load_e); max_outer_iter = 50; price = price_init; best_cost = inf; best_price = price; history = []; for k = 1:max_outer_iter [plan_all, price_final, state] = solve_multi_microgrid(network, price, cgrid); % 上层目标:系统从上级电网购电成本 + 共享交易网损成本 obj_upper = 0; for i = 1:NM obj_upper = obj_upper + sum(cgrid .* plan_all(i).plan.pgrid); end % 加上共享交易过网费,这里以价差乘交易量近似 for i = 1:NM obj_upper = obj_upper ... + (price_final.cbuy_e - price_final.csell_e) * sum(plan_all(i).plan.pbuy_e) * 0.1 ... + (price_final.cbuy_h - price_final.csell_h) * sum(plan_all(i).plan.hbuy_h) * 0.1; end history(end+1) = obj_upper; if obj_upper < best_cost && state.converged best_cost = obj_upper; best_price = price_final; end % 如果已经收敛且结果稳定,退出 if state.converged && k > 5 break; end % 扰动价格,作为上层寻优手段;实际项目中可用粒子群代替 if mod(k,2) == 0 price.cbuy_e = price.cbuy_e * 0.98; else price.cbuy_e = price.cbuy_e * 1.02; end price.csell_e = price.cbuy_e - 0.02; end end这段代码的思想更接近“启发式外层寻优”,它不像数学规划那样保证找到全局最优,但在微网规模小、价格维度低的情况下,配合低价差、低压调步长,可以得到稳定的近似最优解。如果你的项目要求更严谨,可以把外层替换成粒子群、模拟退火等群体智能算法,替换入口我已经留好了。4.5 结果可视化与关键指标跑完整个双层博弈,至少要看三组结果。第一组是各微网的购售电计划曲线,看每个微网在哪些时段买、哪些时段卖,这能直观反映博弈行为。第二组是电热共享价格随迭代的收敛曲线,看价格是否稳定下来。第三组是系统负荷平衡情况,确保总购电加总售电基本平衡。绘制图表时我贴出的代码通常类似:figure; subplot(2,1,1); stairs(1:T, price_final.cbuy_e, 'r-'); hold on; stairs(1:T, price_final.csell_e, 'b--'); legend('共享购电价','共享售电价'); xlabel('时段/h'); ylabel('价格/(元/kWh)'); subplot(2,1,2); for i = 1:NM stairs(1:T, plan_all(i).plan.pbuy_e - plan_all(i).plan.psell_e); hold on; end legend('微网1','微网2','微网3'); xlabel('时段/h'); ylabel('购售电量/MW');看这些曲线的时候有个小技巧:如果某个时段出现一个微网很短的时间窗内同时出现大量买入和大量卖出,大概率是模型里购售互斥约束的二元变量在相邻时段切换时引起的震荡。可以把这个时段的设备出力曲线也画出来,多半能在电锅炉的启停或者储能充放电逻辑上找到原因。5. 常见问题与排查技巧实录5.1 迭代不收敛,价格和购售量一直抖动这是多微网博弈代码里最常见的问题。我个人调试时遇到过几次,现在归纳一下最典型的三种原因。第一种原因是价格调节步长kp_e取得太大。价格反馈的步长过大时,每一轮价格调整都会过冲,上一轮还是供大于求压低了价格,下一轮立刻变成供不应求把价格推上去,形成一个周期震荡。这种震荡的图像通常是两条线在某个平衡点附近来回摆动,幅度不衰减。解决方法是把步长缩小到原值的五分之一甚至十分之一,然后观察波形是否收敛。缺点是收敛速度变慢,实际项目中可以用自适应步长,即供需差额大时步长取大些,差额接近阈值时逐步缩小步长。第二种原因是目标函数非凸。CHP燃料成本用了二次函数,这个本身是凸的,问题不大。但如果加了储能、电锅炉爬坡、购售切换二元变量,整个内层问题就变成了混合整数二次规划。多个带二进制变量的优化问题串行迭代时,很容易出现类似“囚徒困境”的切换行为。为了降低复杂性,可以在内层迭代时暂时固定储能的充放电状态,等大方向收敛后再放开。第三种原因是个别微网对价格非常不敏感,不是因为价格低就不卖,而是受到设备容量限制,价格怎么变都没用。调试时可以先看每个微网自身的容量约束是否过紧,比如某微网CHP容量只有0.2MW,而它的电负荷有1MW,那它大概率每一轮都会大量购电,价格波动对它几乎没影响。这个现象本身不一定是问题,但会让系统整体价格长时间朝一个方向漂移,导致总迭代次数暴涨。5.2 内层优化返回不可行,模型直接崩了不可行问题的根源通常是约束之间互相冲突。我遇到过几次特别典型的。一个是储能SOC约束里,初值SOC和末值SOC设成了0.5,但整个周期内负荷峰值前后相差太大,导致储能必须在一个时段内既大量充电又大量放电,而容量约束不允许,于是模型直接不可行。解决方法是不要同时卡初值SOC和末值SOC,先放开末值约束,看结果落在什么位置,再根据实际需要调整电池初始容量。另一个典型冲突是热平衡约束里只考虑了电锅炉供热和CHP热回收,没有加辅助热源,一旦热负荷峰值超过了这两个设备的供热能力上限,模型同样会报不可行。在多微网共享场景中,某个微网自己热源不足时正常应该通过热网向其他微网购热,但要确保该微网的热力管道容量上限约束没有写得过紧,否则购热也被限制了,问题就无解。检查时先把所有容量上限约束放大十倍,如果模型立刻变得可行,基本就是容量值写得不合理。用yalmip排查不可行问题有一个很好用的手段:把optimize的第二个返回值拿出来看,diagnostic里会提示inf或NaNs。还可以把约束拆成几组逐一测试,比如先单独测电平衡约束是不是能跑通,再单独测热平衡约束。这样定位比空想快得多。5.3 求解时间过长,内循环成为瓶颈这个项目如果用yalmip加Cplex求解,最耗时的部分是每一轮外层迭代里,每个微网都要重新调用一次optimize,而这些微网问题里又都带二进制变量。假设3个微网、100次外层迭代,每次内循环要反复调用多次,总求解次数会到几百上千次。就算单次求解只有一两秒,整个实验跑下来也要几十分钟。我的优化策略是三步走。第一步,把购售互斥问题尽量简化为约束松弛,不要用二元变量,而是直接加入一个很小的惩罚项,这样问题从MILP退化为连续二次规划,速度提升非常明显。第二步,提前把光伏、负荷曲线、上级电价做向量化存储,尽量避免在循环里反复访问结构体字段。第三步,把同一轮的3个微网优化变成并行计算,用Parfor替代for,多核机器上可以快三倍。如果项目非常大,还有个思路是引入经济学里的“价格响应函数”,先把每个微网在不同价格下的响应曲线离线计算出来,再用插值或者神经网络拟合出价格到购售量的映射,在线运行时直接用映射求解均衡。这样内循环就变成了解一组方程组,速度可以快几个数量级。5.4 纳什均衡解和集中式最优解差太多:这是正常现象第一次跑多微网博弈的程序,很多人会拿结果和集中式单目标优化对比,然后发现系统总成本高了不少。这不一定是代码错了,而是博弈本来就会产生“个体最优之和劣于总体最优”的结果,这正是非合作博弈的基本特征。共享平台通过价格来缩小组网的外部性,价差里就包含了平台收益和网损成本。只要每个微网之间没有强制契约,这种效率损失就是天然存在的。如果你的目标是让系统总成本尽可能接近集中式最优,可以尝试调整共享价格的价差margin_e和margin_h。价差越大,微网之间交易的积极性越差,每个微网会更依赖上级电网购电,总成本反而高;价差越小,共享交易越充分,系统总成本越接近理想情况,但平台的过网费收益就越少。这个参数实际上是整个博弈策略里最核心的调节旋钮。6. 一些比较私人的实操体会整套框架跑下来,我最想说的是:博弈类项目在Matlab里真正难的地方,不在于公式推导,也不在于yalmip建模,而在于把所有决策层之间的“反馈回路”理清楚,并且用一个稳定的数值迭代方式把它跑起来。我第一次写这个项目时,花了大量时间在理论上纠结到底什么是纳什均衡解,后来发现直接上手写循环反而更容易理解。你先让所有微网在当前价格下各自算一遍,看供需缺多少,调个价格,再算一遍,只要这个循环满足一定的条件,它就是博弈论里所说的最优响应动态,收敛点就是纳什均衡。所谓“两层”,也不过就是在这个循环外面再加一层价格寻优。如果让我给新手一个建议,我会说先不要追求模型庞大。用两个微网、8个时段的缩小算例,把整个双层循环跑通,画出供需差额和价格收敛曲线,确定逻辑没有问题,再慢慢扩展到24点、更多微网。因为微网数量一多,价格迭代的不稳定性和参数敏感性都会成倍上升,到时候很难分清是模型问题还是代码bug。缩微算例里你可能几分钟就找到问题,扩展到完整场景可能要折腾一整天。另外有两点小经验一直让我的项目少走了弯路:一是所有外部输入数据都要单独放一个初始化文件,不要在微网优化函数里硬编码,否则后续换算例时你根本不知道该改哪里;二是每一轮外层迭代的结果一定要保存成结构化数据或者表格,方便画收敛曲线。调试时远远看画出来的曲线,跟盯着命令行里刷出来的数字相比,完全是两种效率。如果后续想把这个模型往更深的学术方向扩展,我建议把内层循环里的价格反馈迭代换成基于演化博弈的复制者动态,或者把储能设备和热网管道延时加入模型。那时你面对的就不再是一个简单的共享能源调度问题,而是一个有真实动态特性的多主体博弈系统,思路和代码都会上升到新台阶。但至少现在,先用这篇文章里的框架把基础版本跑通,已经是很大的第一步了。