
做多主体综合能源系统优化调度很多人拿到“计及需求响应和电能交互的主从博弈”这个题目时第一反应是“这不就是多个微网放在一起算经济调度吗”。我最初也这么干过把所有分布式电源、储能、负荷塞进一个全局优化器跑出总成本最低的方案然后按园区拆分结果。等我把方案发给项目组里的“园区运营方”角色时对方直接回了一句“这个方案里我们园区买电成本比不参与还高凭什么同意”这时候我才意识到多主体系统的核心难点根本不在设备建模而在“利益分配”和“价格机制”上。这篇文章就是围绕这个课题完整展开的记录需求响应怎么建模、电能交互怎么定价、主从博弈框架怎么搭、Matlab代码怎么一步步实现以及在调试过程中踩过的一堆坑。内容比较长适合正在做综合能源系统调度、需求响应策略、主从博弈/双层规划方向的研究生也适合想快速把这类模型跑起来、并用Matlab复现论文结果的工程师。读完你会对整套模型有一个从公式到代码的闭环认识。1. 多主体综合能源系统的“利益冲突”到底出在哪1.1 集中式优化的假设在现实里站不住脚集中式优化能成立的前提是存在一个全局调度中心能直接控制所有设备且所有参与主体无条件服从全局目标。这在单个园区内部还能勉强接受——比如同一个运营商的燃气轮机、储能、光伏我可以直接命令它们在某个时段多发电、多充电。但一旦把系统扩展到多个主体比如三个由不同公司运营的园区微网问题就来了每个主体都有自己独立的决策权追求的是自身运行成本最小化而不是全局总成本最小化。我第一次用集中式方法算三园区系统时全局最优方案里有一个园区因为负荷峰时段集中被分配到大量购电指标运行成本比它自己单独优化时高了将近18%。这个结果在数学上完全正确但现实里根本落不了地。这就是多主体系统和单主体系统最本质的区别系统最优不等于个体最优甚至可能严重损害某个个体的利益。如果你强行用集中式优化去给一群有独立决策权的主体做调度得到的方案大概率没有人愿意执行。1.2 电气耦合带来的“想互助但不知道以什么价格互助”多主体系统之所以有研究价值是因为不同主体的负荷曲线和资源配比往往存在互补性。举个我项目里的实例园区A的光伏装机很大中午有大量富余电力园区B正好是商业楼宇傍晚才到用电高峰中午负荷一般。如果园区A和园区B之间有一条交互线路理论上A中午可以卖电给BB晚上可以回售给A双方都能受益。但这里有一个关键问题交互价格怎么定如果交互电价定得太高买家不如直接向上级电网买电定得太低卖家不如把电卖给上级电网多赚钱。没有一个合理的中介价格机制内部交互就是一句空话。这个“价格机制”的制定者和响应者之间天然形成了一种层级博弈关系上层定电价下层根据电价决策用能这正是主从博弈能派上用场的地方。1.3 为什么是“主从博弈”而不是普通纳什博弈有些人会把主从博弈和多智能体纳什博弈混为一谈。两者最大的区别在于参与者地位是否对等。多智能体纳什博弈里每个参与者地位相同同时决策互相影响最终达到一个谁都不愿意单方面改变策略的均衡点。这种模型适合双寡头竞争、多个产消者自由交易等场景。但综合能源系统里的实际交易结构往往不是平等的。通常存在一个更上层的实体——比如综合能源服务商、园区运营商、或者微网调度中心——它有制定交互电价和交易规则的权力下面各园区运营主体是价格接受者只能根据上层给出的电价来调整自己的用能计划。这种“一个领导者多个跟随者”的层级决策结构用Stackelberg主从博弈来描述比用纳什博弈自然得多。上层的电价策略会影响下层的用电决策下层的决策反馈又会反过来影响上层定电价的收益最终收敛到一个双方都能接受的稳定策略点。2. 需求响应建模不是简单把负荷乘一个系数2.1 价格型需求响应的弹性矩阵模型需求响应里最常用也最好落地的是价格型需求响应PDRPrice-Based Demand Response。它的核心思想是电价高了用户就少用电电价低了用户就多用电。但这里有一个很多新手容易做错的地方——不能用当前时段电价直接套当前时段负荷变化因为用户对电价的响应存在跨时段转移。比如说上午10点电价升高用户不只是削减10点这一个时段的用电还可能会把原本安排在10点的洗衣机挪到下午2点电价低的时候。这就意味着10点的负荷变化不仅和10点的电价有关还和下午2点的电价有关。数学上要用一个弹性矩阵来描述Δq_i / q_i E_ii × (Δp_i / p_i) Σ_{j≠i} E_ij × (Δp_j / p_j)其中 Δq_i 是第i时段的用电变化量Δp_i 是第i时段的电价变化量E_ii 是自弹性系数本时段电价对本时段负荷的影响E_ij 是交叉弹性系数第j时段电价变化对第i时段负荷的影响。在Matlab代码里我习惯把所有时段的弹性系数整理成一个 N×N 的矩阵N是调度周期时段数比如24主对角线放自弹性非对角线放交叉弹性。然后在主程序里用矩阵乘法一步算出需求响应后的负荷向量。——这里有个实际经验弹性矩阵不要拍脑袋填最好用历史负荷和电价数据做最小二乘回归否则后续结果说服力不足。2.2 激励型需求响应的可中断负荷模型价格型DR的缺点是响应程度不可控因为它依赖用户“自发”调整用电行为运营商不能保证某个时段一定削减多少。所以在实际项目里我还会叠加一种激励型需求响应IDR最典型的形式就是可中断负荷ILInterruptible Load。可中断负荷的逻辑是运营商和用户提前签订协议当系统出现高峰负荷时运营商可以请求用户削减指定量的负荷作为回报运营商按削减量支付补贴。在优化模型里这就变成一个带成本项和约束的决策变量决策变量Q_IL(t)第t时段的可中断负荷削减量单位kW约束条件0 ≤ Q_IL(t) ≤ Q_IL_max(t)即削减量不超过用户可接受的最大中断量目标函数新增成本项Σ_t c_IL × Q_IL(t)其中c_IL是单位削减补贴单价。和价格型DR相比激励型DR对调度员来说“更听话”因为它是直接下发指令并支付补偿而不是靠价格信号间接引导。但代价是要付出真金白银的补贴成本所以在博弈框架里上层领导者需要在“节省的购电费用”和“支付的DR补贴”之间做权衡。2.3 综合能源系统里DR不能只盯电负荷综合能源系统的“综合”二字在需求响应环节尤其关键。园区里通常有燃气轮机或热电联产机组CHPCHP在发电的同时会产生余热这部分热量要供给建筑采暖。如果需求响应把电负荷削减了一大块CHP电出力就得降低那么热出力也跟着下降可能热负荷就不够了。换句话说电负荷的变化会通过CHP的“热电耦合”传导给热力系统如果模型里只考虑电而忽略热算法给的最优解在实际运行中可能完全无法满足供热需求。我在自己的模型里处理这个问题时是把“需求响应后的电负荷”和“需求响应后的热负荷”同时作为变量参与平衡约束。电负荷削减主要由用户关停部分电器实现热负荷削减则通过降低供暖温度设定值实现两者共享同一个弹性资源的预算约束。这样虽然问题规模变大但结果更贴近实际运行情况。3. 主从博弈框架设计上层定电价下层做响应3.1 博弈参与者的决策变量与目标函数我建的模型采用“1个领导者 3个跟随者”的结构。领导者是综合能源服务商负责制定各时段的内部交互电价 λ_inter(t)目标是让自己总运行成本最低跟随者是各个园区主体它们收到交互电价后各自调整发电、储能和购电策略目标是自身运行成本最低。这里有一个非常重要的设计细节上层给下层的不应该只是一个统一电价而是“购电价”和“售电价”有区别的双边价格机制。因为如果同一时段的买入价和卖出价完全一样下层参与者就可以在电价低的时段从上层大量买电、电价高的时段回售给上层产生套利行为破坏市场秩序。常见的做法是用一对价格 λ_buy(t) 和 λ_sell(t)且 λ_buy(t) λ_sell(t) 恒成立这个价差就是上层服务商的“服务费/网损补偿”。上层的目标函数大致是min Σ_t [ C_grid(t) × P_grid_buy(t) − R_sell_grid(t) Σ_i (λ_buy(t) × P_buy_i(t) − λ_sell(t) × P_sell_i(t)) Σ_i C_IL_i(t) ]其中 C_grid 是上级电网分时电价P_grid_buy 是上层从电网买电的功率P_buy_i 是第i个园区从上层购买的电量P_sell_i 是第i个园区向上层回售的电量C_IL_i 是第i个园区的需求响应补贴成本。下层的目标函数是各园区主体自己的运行成本最小化min Σ_t [ λ_buy(t) × P_buy_i(t) − λ_sell(t) × P_sell_i(t) C_fuel_i(t) C_bat_i(t) C_IL_i(t) ]C_fuel 是燃气轮机/CHP的燃料成本C_bat 是储能充放电的退化成本C_IL 是该园区响应需求响应指令的配合成本。可以看到下层主体最在意的运行成本和上层给定的电价直接相关而上层的收益又由下层购售电量决定博弈关系就是这样建立的。3.2 交互链路的物理约束和“禁止同时买和卖”约束在电能交互模型里各主体通过公共母线相连。数学建模上需要满足功率平衡Σ_i P_sell_i(t) P_grid_buy(t) Σ_i P_buy_i(t) P_grid_loss(t)简化处理时可以忽略网损直接用等式约束。但真正容易踩坑的地方在于一个园区在同一时段不能既向上层买电又向上层卖电否则就是“左手倒右手”物理上不成立数学上也会产生不合理的套利解。要约束这一点需要引入0-1变量 θ_i(t)构造如下约束0 ≤ P_buy_i(t) ≤ θ_i(t) × M 0 ≤ P_sell_i(t) ≤ (1 − θ_i(t)) × M其中M是一个足够大的正数Big-M。当 θ_i 1 时只允许买入当 θ_i 0 时只允许卖出。这是一个标准的互补约束建模技巧在Matlab里用 binvar 定义 θ然后配合 sdpvar 写进约束列表就行。3.3 双层规划求解迭代法还是KKT单层化从数学结构上说主从博弈等价于一个双层规划问题上层是领导者的优化问题下层是跟随者的优化问题且下层问题是上层问题的约束条件。求解双层规划有两条主流路线。第一条路线是数值迭代法上层给定电价 → 下层各自求解 → 返回购售电量 → 上层根据结果更新电价 → 循环直到收敛。这个思路直观但缺点非常明显——收敛性无法保证而且每一步下层优化都要调用一次求解器计算耗时随主体数量线性增长。我在早期版本里用过这个方法经常出现电价在两个值之间来回震荡、不收敛的情况。第二条路线是单层化也就是对下层问题取KKT最优性条件把“下层最优”这个条件转化为一组约束加到上层问题里从而把双层规划转化为单层的带均衡约束的数学规划问题也就是MPEC。然后再通过线性化手段把非线性的互补松弛条件转化为混合整数线性约束最终用MILP求解器直接求解。这是目前学术界和工业界处理主从博弈最主流的方式也是我最终采用的方式。4. Matlab代码实现从公式到可运行的调度程序4.1 代码结构与参数定义方式写这种规模较大的优化模型代码结构一定要模块化否则排错排到怀疑人生。我习惯按下面这种方式组织文件main_schedule.m主程序入口负责加载参数、定义变量、调用求解器、输出结果params_case.m参数定义文件所有设备参数、负荷曲线、电价、弹性系数都放这里build_lower_model.m构建下层主体优化子问题的函数build_kkt_constraints.m手动写出下层问题KKT条件并返回约束集合build_upper_model.m构建上层目标函数和约束plot_results.m结果可视化脚本。参数统一用Matlab的 struct 封装。比如p struct(); p.n 24; % 调度时段数24小时 p.nAgent 3; % 园区主体数量 p.pv [ ... ]; % 各园区光伏预测出力nAgent x n 矩阵 p.loadElec [ ... ]; % 各园区基础电负荷 p.loadHeat [ ... ]; % 各园区基础热负荷 p.priceGrid [ ... ]; % 上级电网分时电价1 x n 向量 p.ElastMatrix [ ... ]; % 需求响应弹性矩阵n x n p.bigM 1e5; % 大M值这样写的好处是做敏感性分析或换算例时只需要改params_case.m不需要动核心优化逻辑。4.2 用Yalmip搭模型的核心步骤Matlab里做优化建模我强烈建议用Yalmip工具箱。它把模型的定义和底层求解器解耦你只需要用sdpvar定义连续变量用binvar定义0-1变量然后在Constraints和Objective里把所有逻辑写进去最后统一调用外部求解器我用的Cplex也可以用Gurobi。下层各园区问题示意% 以下层主体i为例 x sdpvar(p.n, 1); % 购电功率 y sdpvar(p.n, 1); % 售电功率 z sdpvar(p.n, 1); % 储能放电功率 w sdpvar(p.n, 1); % 储能充电功率 q sdpvar(p.n, 1); % 可中断负荷削减量 theta binvar(p.n, 1); % 购/售互斥变量 Constraints []; Constraints [Constraints, 0 x theta * p.bigM]; Constraints [Constraints, 0 y (1 - theta) * p.bigM]; % 功率平衡约束 Constraints [Constraints, x - y z - w p.pv(i,:) p.loadDR(i,:)]; % 储能SOC递推约束 % 可中断负荷上下限约束 Objective sum(lambda_buy .* x) - sum(lambda_sell .* y) ... sum(c_fuel .* pchp) sum(c_bat .* (z w)) ... sum(c_IL .* q);把Yalmip的模型定义写完后求解就一行代码optimize(Constraints, Objective, sdpsettings(solver, cplex));如果你正在用的是旧版Yalmip和较新的Cplex/Gurobi容易遇到接口报错。解决办法是去Yalmip官网更新到最新版本确保yalmip文件夹在Matlab搜索路径里并且求解器路径也配置正确。不要在一个项目里混装多个版本的Yalmip很容易出现莫名其妙的“solver not found”。4.3 求解器配置和调试的关键技巧Cplex和Gurobi对MILP问题的支持都非常成熟但各自的参数差异会影响求解速度。我实际使用中会把MIP相对间隙设为较小的值而不是默认值opts sdpsettings(solver, cplex, cplex.mip.tolerances.mipgap, 0.0001); optimize(Constraints, Objective, opts);另外一定要检查求解结束状态。我会在代码里加一段判断if optimize(Constraints, Objective, opts) ~ 0 error(优化求解失败请检查约束和参数设置); end还有一种常见情况是Yalmip提示“Unable to prove infeasibility”意味着模型有效但求解器因为数值问题无法跨过可行域判定。这时候优先检查Big-M的取值看是不是过大导致数值条件恶化。我通常把Big-M设置成交互电价上限的10倍左右而不是简单给个1e10这个细节对求解稳定性影响非常大。5. 算例设计和结果分析怎么证明博弈策略有效5.1 算例参数设计的“讲究”这个博弈模型能不能跑出有意义的结果算例设计比算法本身更关键。如果各园区负荷曲线峰谷完全同步内部交互就没有空间需求响应也形同虚设。我设计的三个园区参数如下参数园区A园区B园区C负荷峰值时段中午11:00-14:00傍晚18:00-21:00全天均衡光伏装机容量(kW)800300500储能容量(kWh)600400300是否配置CHP机组是是否可中断负荷上限(kW)1008050可以看到园区A光伏富余但自己中午用不完园区B傍晚电负荷高但光伏中午又不能完全自用两者天然形成了互补交互的需求。上级电网分时电价设定为峰时1.2元/kWh平时0.8元/kWh谷时0.4元/kWh。交互电价的上下限设定为0.4~1.2元/kWh之间保证交互价格不会高过同级电网又不会低到没有利润空间。5.2 需求响应带来的调峰效果我对比了有/无需求响应两种情况下的系统总负荷曲线。无需求响应时系统在12:00和19:00有两个明显尖峰加入价格型和激励型DR之后峰值负荷分别下降了约9.6%和12.3%总购电成本下降约7.8%。需求响应的削峰填谷效果在这个算例里体现得很清晰。有意思的是需求响应不只是帮上层服务商降低了购电成本。对于下层园区主体来说响应电价信号本身也让它们的电费单变低了——因为它们在电价高的时段削减用电在电价低的时段增加用电等于把部分用电需求从高价时段转移到了低价时段。这正是需求响应“双赢”的本质用户省钱系统削峰运营商减少高额峰时购电。5.3 主从博弈 vs 集中式优化 vs 完全独立运行这是整个模型最有说服力的对比数据。我算了三种情况的结果策略模式系统总成本元个体成本最低值个体成本最高值方案可接受性集中式优化24,8606,1009,800差个体间成本分配不均完全独立运行28,3508,10010,400可行但没有利用交互主从博弈26,4207,2009,400良好每个个体都受益集中式优化虽然总成本最低但个别园区成本偏高利益分配不均衡导致方案不落地完全独立运行虽然大家没有异议但总成本最高浪费了系统内部的互动潜力主从博弈的总成本介于两者之间但每个参与者的成本都低于完全独立时的水平也就是说所有主体都有参与博弈的动机方案在现实中拥有可执行的基础。这组对比数据就是审稿人或者答辩老师最想看到的东西它直接说明了你用博弈而不是用集中优化建模的合理性。6. 这套代码实操中的坑与改进方向6.1 互补松弛条件线性化时的Big-M陷阱如果你走的是KKT单层化路线最头疼的就是互补松弛条件的线性化。下层问题的KKT条件里会出现 λ ≥ 0g(x) ≥ 0λ × g(x) 0 这种乘积形式这是强非线性项直接交给求解器大概率求不出来。标准做法是用Big-M法引入0-1变量拆分λ ≤ M × (1 − u) g(x) ≤ M × u其中u是0-1变量当u0时λ0当u1时g(x)0从而保证 λ × g(x) 0。这里的M选多大直接决定求解器能不能收敛。M取得太大比如1e8会导致Cplex的整数规划预处理阶段出现数值溢出表现为变量一直在微小数值上抖动、模型判定为infeasibleM取得太小比如100又会把真正的最优解排除在可行域外。我实践下来先估算约束中变量可能达到的最大量级再取Max的一个安全倍数通常在1e4到1e6之间求解器数值表现最稳定。6.2 迭代式博弈求解时的初值依赖问题如果你选择不用KKT单层化而用迭代法求解主从博弈那我必须提醒你初值对整个迭代过程的影响极大。我曾在初始交互电价随便给了个0.6元/kWh的情况下迭代25次才收敛而用上级电网分时电价作为初始值时第8次迭代就稳定了。更糟糕的是某些初值会导致均衡点在两个值之间来回震荡无法收敛。比如初始交互电价和下级电网峰时电价接近时园区在“内部买电”和“直接从电网买电”之间没有明显倾向决策在相邻迭代之间跳变系统就进入了振荡。解决这个问题的经验是从“无内部交互”的基准场景开始迭代——即初始交互电价设为上级电网电价让所有主体的最优决策都是不参与内部交互然后逐步调整电价观察需求变化再更新电价这个过程和真实电力市场的出清过程非常接近收敛性也更有保障。6.3 热负荷和电负荷的耦合约束不能省我在做这个课题时吃过一次亏初版模型里只约束了电功率平衡热负荷用固定值处理结果跑出来的调度方案中CHP机组的电出力受需求响应影响大幅下降导致热出力同步下降园区热负荷在早高峰时段出现了供给缺口。模型本身有可行解但放到实际场景里根本不能运行。解决这个问题需要对CHP机组的热电耦合特性建模。最常用的是定热电比模型Q_chp(t) α × P_chp(t)α为热电比。这样约束里加入热平衡方程Q_chp(t) Q_gb(t) Q_hs(t) Q_load_DR(t) Q_charge_hs(t) − Q_discharge_hs(t)其中Q_gb是燃气锅炉产热Q_hs是蓄热罐的充放热功率。这样电负荷需求响应导致的电出力变化会通过热电比传导到热平衡约束里模型才真正做到“综合能源”。6.4 从两主体系统开始调试再扩展到多主体最后分享一个非常实用的开发经验不要一开始就搭三主体博弈。先把模型简化到“1个领导者 1个跟随者”跑通整套代码和求解流程确认KKT条件推导无误、求解器收敛稳定之后再扩展到第二个、第三个主体。多主体系统调试时问题一旦出现很难判断是单个主体的建模错误还是主体之间的交互约束出了问题。从两主体开始你可以逐主体检查平衡约束和成本项排错效率高很多。另外我个人写这类代码的体会是把下层问题的目标函数和约束单独抽出来先当成一个独立优化问题测试用固定电价算一遍手算或者用Excel验证结果合理后再放进博弈框架里。如果一刀切直接写完整代码一旦KKT条件推导出错你连错误在模型逻辑里还是数学推导里都很难定位。这种“自底向上”的调试方式比硬啃完整代码要靠谱得多。