ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

主从博弈框架下的区域综合能源低碳经济调度及Matlab实现

主从博弈框架下的区域综合能源低碳经济调度及Matlab实现 1. 为什么区域综合能源调度必须引入主从博弈传统综合能源系统的优化调度通常站在上帝视角假设一个全能调度中心把所有设备、所有用户都当成听指挥的棋子直接算一个全局最优解。这个思路在单主体场景下没问题可一旦把范围扩到区域问题就来了——园区里有多个独立的利益主体比如综合能源服务商、楼宇用户、电动汽车聚合商、储能运营商它们各有各的目标谁也不会甘愿牺牲自己的利益去成全全局最优。我最早接触这类项目时拿经典的混合整数线性规划直接建模把多个主体统一成一个目标函数结果仿真结果漂亮得不行但一放到实际场景就被业主否了。为啥因为里面对用户的假设太理想真实用户会为了省电费改变用能时段运营商会为了多赚服务费调整售热价这些行为反过来又影响源侧机组的出力计划。这种双向互动关系本质上就是博弈不是单纯的最优化。这就是多主体主从博弈Stackelberg game登场的原因。它的核心思想很简单博弈里分领导者和跟随者领导者先出牌比如定电价、热价跟随者根据这个价格调整自己的用能策略领导者再根据跟随者的反应调整定价如此循环直到双方都不愿意单方面改变策略也就是达到Stackelberg均衡。放到区域综合能源系统低碳经济优化调度的场景里典型的分层架构就是上层是综合能源运营商做低碳经济调度决定各机组出力和售能价格下层是用户侧主体根据价格信号优化自己的用能行为。上下层各自求解通过迭代收敛到均衡。用Matlab实现这套逻辑难点其实不在求解器而在怎么把博弈结构、分层迭代、低碳约束这些工程细节组织成可读、可改、可复现的代码。这篇文章我会从建模逻辑讲到代码结构再讲到实际跑通时那些文档里不会写的坑适合正在做综合能源调度方向的研究生以及刚接触主从博弈想拿Matlab练手的工程师。2. 分层模型的核心架构谁先出牌谁跟牌2.1 上层领导者的决策空间在区域综合能源系统里上层领导者通常是区域能源运营商它管理着光伏、风电、燃气轮机、电锅炉、储能等设备。这一层的目标函数一般写成系统运行成本最小加碳排放惩罚最小或者干脆把碳交易成本直接并入经济目标。传统经济调度的目标函数长这样% 目标函数示意伪代码风格实际为Matlab的fmincon/fminunc目标函数 total_cost sum(C_gt * P_gt C_grid * P_grid C_gb * H_gb) ... lambda_c * (E_emission - E_quota);其中C_gt是燃气轮机发电成本系数C_grid是购电电价C_gb是燃气锅炉产热成本lambda_c是碳交易价格E_emission是实际碳排放量E_quota是免费配额。这里的关键点是上层不只是做设备出力决策它还要制定销售给用户的能源价格也就是决策变量里既有物理量各机组出力又有经济量售电售热价。这就引出了分层模型的第一特征上层的决策直接影响下层的优化问题。比如运营商把电价从0.6元/kWh提到0.8元/kWh用户侧的可平移负荷就会往后挪原本高峰时段的大功率用电设备会被改到谷段。用户行为一变运营商预测的负荷曲线就失准了它必须重新调度机组。所以这两个问题不能分开独立求解必须耦合迭代。2.2 下层跟随者的行为建模下层主体的类型很多可以是居民用户、商业楼宇、工业用户也可以是电动汽车聚合商。在建模时我不会把每个用户单独建模——那会导致下层问题规模爆炸。更合理的方式是按负荷类型聚合把区域内的用户分成几个典型主体每个主体内部再细分柔性负荷、刚性负荷、可平移负荷。以楼宇用户为例它的优化目标是在满足室内温度舒适度、设备用电需求的前提下最小化用能成本。决策变量包括各时段从电网购电的功率、从热网购热的功率、储能充放电功率等。这里有个典型的博弈味道用户把上层给定的能源价格当作参数求解自己的优化问题但用户的优化结果用能曲线又反过来成为上层调度时的负荷约束。2.3 上下层之间的耦合变量分层模型最关键的是找到耦合变量。我在实际建模中主要用两类价格变量上层制定的售电价格、售热价格直接进入下层目标函数的成本项。负荷曲线变量下层优化后的用电用热需求作为上层调度模型中的负荷需求约束。这两个变量一来一回就构成了博弈迭代的主循环。收敛判据一般设为相邻两轮迭代中上层目标函数值的变化小于预设阈值或者价格与负荷曲线的偏差足够小。我自己调试时习惯把误差阈值设为1e-4迭代上限设为50次。实测下来多数场景15~25轮就能收敛如果超过40轮还没稳定基本可以断定是参数设置有问题而不是算法本身慢。3. 低碳经济调度的数学模型目标函数与约束条件怎么落笔3.1 经济目标与低碳目标的融合方式很多刚入门的朋友第一反应是低碳目标就是加一个碳排放约束把总排放量卡在某个上限以内。这个思路大方向对但容易把模型搞死——如果碳配额给得太紧可行域直接空了求解器报错。比较务实的做法是用碳交易机制把碳排放变成一种可交易的成本实际碳排放低于配额时剩余的配额可以在碳市场卖出获得收益实际碳排放高于配额时需要购买额外的配额形成成本。这样低碳目标就不是硬约束而是进入了经济目标函数变成了惩罚项或交易项。我在Matlab里就把它写成一个随排放量线性变化的成本项碳交易价格设为外生参数方便做敏感性分析。这个设计有一个实际好处调度结果不会因为追求低碳而出现不合理的弃风弃光因为过高的碳价会让燃气轮机自发减少出力系统自然转向前期投入已成沉没成本的光伏和风电。3.2 功率平衡约束与设备出力约束区域综合能源系统的平衡约束比纯电力系统复杂在多能互补。电、热、气三种能量流都要平衡而且中间还有耦合设备——燃气轮机同时产电产热电锅炉把电转成热储能既能存电也能存热。电功率平衡约束% 电功率平衡发电 购电 储能放电 新能源 负荷 电锅炉耗电 储能充电 P_gt P_grid P_pv P_wind P_dis P_load P_eb P_chg;热功率平衡约束H_gb H_chp H_dis_heat H_load H_chg_heat;其中H_chp是燃气轮机的余热回收供热H_gb是燃气锅炉产热储能的热端同样有充放过程。这些约束在Matlab里用线性等式表达直接放进fmincon的Aeq和beq参数即可。设备出力约束相对直接无非是上下限约束和爬坡约束。燃气轮机开机后有个最小技术出力光伏和风电出力受预测上限限制储能SOC要在安全范围内。这里我要特别提醒储能SOC的时序耦合经常被初学者忽略。SOC不是独立变量它会沿着时间轴累加上一时段的SOC直接影响下一时段的可充可放空间。所以约束里必须包含SOC的状态转移方程SOC(t1) SOC(t) eta_chg * P_chg(t) - P_dis(t) / eta_dis;如果你用的求解器不支持这种时序耦合变量比如某些简化版本的线性规划工具调度结果会出现储能白天充一点、晚上充一点的抖振现象非常影响方案落地。3.3 下层用户的最优用能模型下层用户的优化目标相对小巧以最小化当日用能费用为主。我把用户负荷分成三类刚性负荷必须满足不可调整比如照明、电梯柔性负荷可以在一定时段范围内平移比如洗衣机、消毒柜可削减负荷允许削减一部分但有舒适度惩罚成本。这样下层模型就是一个带有时段耦合的线性规划。用户的决策变量是各时段从上级购电购热的功率以及柔性负荷的启停时段。因为下层模型规模不大我用linprog就能在毫秒级求解完全不影响迭代效率。这里有个建模口径问题必须想清楚下层用户的反应函数写好了如实地描述了用户会怎样根据电价改变用能行为。但这个模型必须是线性或凸的。为什么因为在数学上只有下层问题是凸优化Stackelberg博弈才有良好的收敛性质而且可以用KKT条件把双层问题转化为单层问题。如果你的下层模型里加入了非凸项比如阶梯电价的非线性项求解就会变成混合整数非线性规划非常头大。4. Matlab代码实现的关键模块与分层迭代流程4.1 整体代码架构怎么安排代码实现里最忌讳的就是把所有逻辑塞进一个巨大的脚本文件。我强烈建议按功能模块拆分至少分成四个文件文件/函数职责核心代码入口main_stackelberg.m主迭代流程上下层循环调用上层和下层函数upper_leader.m上层目标函数与约束fminconlower_follower.m下层用户响应函数linproginput_data.m参数初始化负荷/新能源预测曲线数据导入主循环的逻辑其实就三句话上层给价格下层响应收敛判断。伪代码写出来让人一目了然% 初始化 price_e price_e_init; % 初始售电价 price_h price_h_init; % 初始售热价 iter 1; while iter max_iter % 下层给定价格用户做出最优用能决策 [load_use, cost_user] lower_follower(price_e, price_h); % 上层根据用户负荷重新优化机组出力与价格 [P_result, price_new] upper_leader(load_use); % 判断收敛价格变化量 delta_price norm(price_new - [price_e; price_h]); if delta_price tol break; end price_e price_new(1:24); price_h price_new(25:48); iter iter 1; end4.2 上层求解器选择fmincon还是linprog上层模型如果做严格建模包含燃气轮机启停状态这种0-1变量就得用intlinprog求解混合整数线性规划或者fmincon的SQP算法做非线性规划。但如果做工程简化把机组启停状态预先定好比如全天开机就能用linprog线性求解速度飞快。我的经验是分两步走先用linprog做一个不考虑启停的快速求解得到机组出力的基线和价格信号如果后续要做精细化分析再引入0-1变量切换到intlinprog。这样做的好处是调试期排错容易模块化程度高不至于一开始就被MILP的计算时间拖垮。上层购买电量与售电价格之间的约束关系要注意运营商从配网购电的价格一般固定但向用户售电的价格是它可以调整的博弈变量。在低碳经济调度的框架下售电价格的下限通常要高于购电成本否则运营商亏本上限受监管约束不能无限提价。这两个边界在初始化时就要写清楚。4.3 下层响应函数的两种写法下层响应函数有两种实现方式。第一种是求解式直接调用linprog求用户最优代码直观但每次迭代都要重新求解第二种是解析式如果下层模型简单到可以推导出闭式解就可以把用户响应写成价格的显式函数省掉每次调用求解器的开销。区域综合能源系统场景里下层往往带有储能或蓄热装置闭式解存在的前提条件不成立几乎没有能推导出干净解析式的场景。所以我推荐老老实实用linprog。实测一个24时段的用户优化问题linprog单次求解在0.05~0.1秒级别50次迭代也就几秒钟完全能接受。下层模型里最容易改错的地方是时段耦合约束——储能SOC的跨时段递推、柔性负荷一旦启动就必须连续运行N小时的约束。这些在Matlab里都要用矩阵形式一次性写入Aineq和bineq。调试这类约束时我的办法是先去掉所有时段耦合约束跑通基线再逐个加回来这样一旦结果异常能迅速锁定是哪类约束出的问题。4.4 迭代求解的收敛判据与加速技巧收敛判据我刚才提到用价格变化的二范数这里补充一个工程细节不要只看上层目标函数的变化因为目标函数在迭代后期可能已经非常平缓但价格和负荷曲线还在小幅震荡。用价格和负荷双指标同时判断更稳妥converge (delta_price tol_price) (delta_load tol_load);实际调参中我发现初始价格的选择对收敛速度影响相当大。如果初值给得太离谱比如电价设为0.1元/kWh下层用户的响应会极不理性——用户恨不得全天满负荷用电上层再根据这个负荷曲线调度机组得到的新价格可能又涨到天价往复震荡好几轮才能回到理性区间。更稳的做法是拿前几天的历史平均价格做初值或者先跑一次不带价格优化的传统调度把边际成本当作初始价格。这样迭代路径平滑很多也能少踩几个超调量的坑。5. 算例测试把代码丢进Matlab之前必看的输入输出细节5.1 典型算例的参数表我搭建了一个包含一台燃气轮机、光伏电站、风电场、电锅炉、燃气锅炉、电储能和热储能的区域综合能源系统作为测试算例。设备参数如下方便你直接抄作业设备容量效率/成本系数备注燃气轮机1000 kW电效率0.35气价2.5元/m³热电联产光伏电站400 kW弃光允许率5%出力曲线来自实测数据风电场300 kW弃风允许率10%同上电锅炉200 kW电转热效率0.9受电网购电上限约束燃气锅炉600 kW热效率0.9作为备用热源电储能500 kWh/250 kW充放效率0.95SOC范围0.1~0.9热储能300 kWh/150 kW充放效率0.9SOC范围0.1~0.9负荷侧我接了三个典型用户聚合体一个以写字楼为主的纯电热负荷一个带充电桩的电动汽车聚合商一个对热价敏感的居民社区。这三个主体的参数不同对价格的反应灵敏度也不同算出来的博弈结果才丰富。5.2 从结果中你能看到什么跑完代码Matlab的figure窗口会输出几类关键图。第一个是上层的设备出力曲线图你能看到燃气轮机在峰段全出力、谷段减出力光伏和风电以优先消纳的方式被安排。第二个是储能SOC曲线这个要看SOC是不是在一天内爬坡到满再放空如果整天都在0.5附近纹丝不动说明储能完全没有被调度起来多半是峰谷价差不够大或者约束写错了。第三个非常关键对比有博弈和无博弈的调度结果。无博弈模型直接用预测负荷做输入算出来的系统总成本可能会偏低但这是一种虚假的最优有博弈模型里用户对价格做出反应后负荷曲线变得平坦峰段燃料消耗降低算出来的成本虽然在某些时段有所上升但全天的综合表现是更真实的。低碳指标上观察碳排放量和碳交易成本的对比。我实测在碳价设为50元/t时与传统不考虑碳约束的调度比系统碳排放降低约8%~12%同时总成本上升控制在3%以内。这说明碳交易机制确实能在低碳和经济之间找到平衡点而不是把成本压力全部转嫁给用户。6. 跑代码过程中最容易翻车的几个坑6.1 求解器报Infeasible先查约束再查数据fmincon或linprog报infeasible是新手最常遇到的噩梦。前一刻代码还在跑加了某个约束后突然无法找到可行解。我的排查顺序是先把所有不等式约束逐条注释掉直到模型恢复可行找到引发不可行的约束后检查系数方向不等式方向很容易写反检查约束矩阵的行列维度是否匹配Matlab对维度检查相对宽松但有时自动广播会掩盖真正的错误检查数据量纲kW和MW混用会让约束系数差上千倍求解器很容易落入数值陷阱。一个非常典型的问题储能SOC初始值设成了0.5但充放电效率不是1导致一天运行结束后的SOC比如0.62和初始SOC对不上。如果你加了末端SOC等于初始SOC的约束那就会在24时段模型里藏一个顺序耦合约束处理不好直接infeasible。我一般把末端SOC约束放宽为一个范围0.45~0.55既保证储能运营的周期性也给求解器留出喘息空间。6.2 迭代不收敛八成是价格初值或步长设置有问题我遇到迭代不收敛时把锅甩给算法本身的概率很低绝大多数都是价格初值或步长设置的问题。价格更新步长的设置需要仔细斟酌——步长太大会出现震荡步长太小会让收敛变得极慢。工程实践中我会写一个自适应步长当连续两轮价格变化方向一致时适当放大步长以加速一旦方向反转立刻减半步长。这个策略能有效缓解价格震荡的问题代价是牺牲一点点理论上的简洁性但工程效果非常好。6.3 下层模型不响应价格检查价格是否进入了目标函数还有一个隐蔽的问题下层模型参数传入了但结果看起来像没反应。比如你从0.5元/kWh调价到0.7元/kWh用户用电曲线纹丝不动。这时候别急着怀疑博弈逻辑先在命令行打印一下下层的目标函数值确认价格变量确实被读进去了。很常见的情况是脚本里定义了一个price变量但下层函数内部又定义了一个同名局部变量把外部传进来的值覆盖了或者用了全局变量但忘记在函数内部声明global price_e。这类低级错误靠看代码很难发现加打印输出才是硬道理。6.4 矩阵维度不匹配的快速定位小技巧Matlab处理约束矩阵时如果Aineq的行数和bineq的列数不匹配会报维度错误。但当你使用了循环生成约束时循环次数稍有偏差就会导致矩阵维度出错而且报错信息只指向调用linprog的那一行。我的定位技巧是把生成约束矩阵的部分单独写成一个子函数在函数末尾加断言语句assert(size(Aineq,1) length(bineq), 不等式约束矩阵行数与b不匹配); assert(size(Aineq,2) n_vars, 不等式约束矩阵列数与决策变量数不匹配);这样报错信息会直接给出是行还是列出了问题省下无数脑细胞。7. 向真实项目扩展的几个方向分层博弈模型跑通了不等于工作结束了。真实项目里还有几个扩展方向很值得做我这里挑三个最常被问到的说一下。第一个是加入风光出力的不确定性。博弈模型里风光出力是确定的预测曲线但实际情况偏差很大。常见做法是引入场景法把风光出力做成多个典型场景每个场景对应一个博弈迭代最后按场景概率加权得到期望调度方案。这个扩展在Matlab里改动不算大把input_data里的预测曲线替换成场景集合即可但迭代次数会成倍增长。第二个是考虑碳捕集与封存CCS设备。如果你的系统里燃气轮机的碳排放量占比很高光靠碳交易机制调峰还不够那就得引入碳捕集装置。这会让上层模型增加一个新的耦合变量——捕集到的CO2量和封存成本下层用户的博弈关系保持不变。我在一个扩展版本里试过碳捕集设备的引入让系统碳排放再降15%左右但设备投资成本回收周期较长经济性要看碳价脸色。第三个是把储能容量本身也作为优化变量。前文的模型把储能容量看成了固定参数但实际规划阶段要回答配多大储能划算这个问题。这时可以做一个外层容量优化的循环外层变化储能容量内层跑24时段主从博弈调度算总成本迭代得到最优容量。这个双层嵌套计算量暴涨建议配合并行计算工具箱用。我个人的体会是这套分层博弈框架的适用范围远比想象中广。不管上层是区域能源运营商还是微网聚合商下层是居民用户还是工业用户只要你能把双方的目标函数写清楚、把耦合变量找对整个代码架构不用推翻重建。这大概也是Stackelberg模型在能源领域经久不衰的根本原因——它承认了每个主体都有自己的利益诉求而调度方案的落地恰恰要建立在各方利益都能被纳入考量的基础上。
返回列表