共享储能与综合能源微网主从博弈的MATLAB实现与优化调度实战
综合能源微网、共享储能、主从博弈这三个词放在一起很容易让人觉得这是某篇论文里的“高级概念”但真正动手把MATLAB代码跑起来之后我才发现这套模型并没有想象中那么遥不可及。它本质上解决的是一个非常实际的问题储能设备到底应该归谁用、怎么定价、微网怎么根据价格来安排自己的用电计划。我去年帮一个课题组搭过一套“共享储能综合能源微网”的仿真算例用的就是主从博弈框架MATLAB里面既有YALMIP建模也有迭代求解的逻辑。今天这篇想把整个代码架构和实现思路拆开聊一遍适合正在做微网优化、储能商业模式或者主从博弈仿真的研究生也适合刚接触能量管理系统的工程师至少能让你少走不少弯路。1. 先理清思路主从博弈为什么适合“共享储能微网”这类场景很多刚接触这个方向的人第一问题是“为什么非得用主从博弈直接用集中式优化不行吗”这个问题问得很关键因为只有把建模动机想明白后面看代码才不会晕。1.1 微网优化运行到底在优化什么综合能源微网通常包含风机、光伏、燃气轮机、电锅炉、储能、热负荷和电负荷等元素。所谓优化运行就是在一个调度周期内决定每一台设备每个时刻的出力使得总运行成本最低同时满足电、热、气等多种能源的平衡。常见的调度周期是24小时步长为1小时也可以用到15分钟。目标是经济性约束是能量平衡和设备物理限制。这个框架本身并不复杂核心难点在于“多能互补”让变量数量和耦合关系成倍增加。比如电转热会让电锅炉消耗电能产出热能燃气轮机同时产电和产热储能装置的充放电状态又会影响下一时刻的SOC这种时间耦合让优化问题变成一个动态决策问题。1.2 共享储能解决了谁的痛点如果每个微网都自建一套储能系统容量利用率通常不高。风电光伏大发的时段可能用不上晚上负荷高峰又未必充得够。储能成本摆在那里电池寿命、维护费用、闲置损耗都是实打实的资金压力。共享储能的概念就是把储能设施交给独立的运营商建设并运营微网按需购买充放电功率运营商通过服务费回收成本并盈利。这个模式有点像共享充电宝用户不用买充电宝按次付费平台通过调度充电宝在不同需求点之间的流转来提高利用率。储能运营商通过价格信号引导用户在不同时段充放电既能赚取峰谷价差又能帮助微网降低用电成本。1.3 主从博弈的上下层关系主从博弈也叫Stackelberg博弈是一种不对称决策模型。在这个场景里储能运营商是领导者先制定充放电价微网是跟随者在给定的价格下优化自己的用能和储能购买计划。为什么储能运营商是领导者因为它拥有储能资源的定价权处于更主动的位置。微网虽然也做决策但只能被动响应价格。换句话说上层先出价下层再根据价格选最优策略然后上层又根据下层的反应调整价格直到双方都没有动力改变自己的策略就达到了博弈均衡。1.4 为什么选“领导者-跟随者”而非集中式优化集中式优化的前提是整个系统的所有信息都集中在一个调度中心手里统一优化所有参与者的目标实现全局成本最低。但实际情况是微网和储能运营商属于不同主体各有各的私密信息比如微网的负荷预测、设备效率、内部成本运营商很难完全掌握反过来也一样。主从博弈的好处是只需要交换价格和功率计划不需要把各自的内部模型完全公开。这种分布式决策的方式更贴近电力市场的真实交易机制。代价是求解变难因为不是一个单层优化问题而是上下两层相互嵌套的决策问题。2. MATLAB代码架构与核心建模我写这套MATLAB代码的时候最初目标很明确用一个双层模型分别描述储能运营商的定价决策和微网的运行调度再用迭代或者KKT化简的方式去求解纳什均衡。整个代码要方便调试也要方便扩展。2.1 上层模型储能运营商收益最大储能运营商的决策变量是分时充电电价、放电电价以及服务费。它的收益来自多个方面向微网出售电能获得售电收入向微网收取充放电服务费从电网或上级能源市场低价购电用于给储能充电。目标函数就是总收益减去总成本。成本包括从电网购电的成本、储能充放电过程中的损耗成本、运维成本。运维成本通常简化成一个线性函数比如每次充放单位电量的成本为固定值不考虑电池老化或循环寿命的非线性影响。约束条件主要包括储能功率上下限同一时刻的充电功率和放电功率不能超过额定值储能容量约束SOC保持在安全范围通常设为0.1到0.9SOC动态方程SOC(t) SOC(t-1) 充电功率×充电效率/容量 - 放电功率/(放电效率×容量)不能同时充放电售电价格通常会有上下限约束防止恶意抬价。2.2 下层模型微网运行成本最小微网作为跟随者看到储能运营商给出的充放电价后需要安排自己内部的机组出力和从储能购买的功率使得当天总运行成本最低。成本主要包括从电网购电费用燃气轮机消耗天然气的成本从共享储能购买充电服务的费用如果储能允许微网充电或购买放电电能的费用如果微网有余电也可以反卖给储能或电网形成负成本。约束条件包括电功率平衡风机出力光伏出力燃气轮机发电储能放电电网购电 电负荷储能充电电锅炉耗电热功率平衡燃气轮机余热电锅炉产热储热设备放热 热负荷燃气轮机出力上下限和爬坡约束电锅炉出力上下限电网交互功率上下限。这里需要注意综合能源微网的下层模型本身就是一个多能源耦合的线性规划问题。如果储能价格是固定的那么下层模型可以直接用线性规划求解非常方便。2.3 博弈均衡求解的两种MATLAB思路主从博弈的求解目前主流有两种路径我两种都试过各有适用范围。第一种是“KKT单层化”。将下层微网优化问题的KKT最优性条件作为约束加到上层优化问题中。这样做的好处是理论上可以得到精确解但代价是引入了大量互补松弛条件和非线性项通常需要用大M法线性化变成一个混合整数线性规划问题对求解器要求比较高。YALMIP配合CPLEX或者Gurobi能处理但模型规模大时变量和约束数量会爆炸式增长。第二种是“迭代求解”。上层给定价格下层求最优功率计划并返回上层根据返回结果更新价格循环直到价格和收益不再变化。这种方法实现简单能直观看到博弈过程但收敛性不能保证需要加阻尼或调整步长。我实际演示时用迭代法更多因为它更直观也更容易让初学者理解博弈的交互过程。2.4 文件组织与数据流我习惯把代码拆成几个模块main.m主程序负责初始化参数、调用子模块、循环迭代、结果展示init_parameters.m设置所有参数包括设备容量、负荷数据、电价、效率等build_microgrid_model.m构建下层微网优化模型并求解输入是储能价格输出是各设备功率和微网购电/购储能计划build_storage_model.m计算上层储能运营商的收益输入是微网的购储能功率输出是收益值update_price.m根据上下层偏差更新价格check_convergence.m判断是否达到收敛条件。数据流是主程序初始化储能价格然后把价格传给微网模型微网求解放电功率和充电功率再把这部分功率返回给上层上层更新价格直到收敛。这个结构非常简单也方便后续把某个模块替换成更复杂的实例。3. 核心代码模块逐个拆解代码到底怎么写很多人喜欢直接上网找开源项目但开源项目往往为了通用性做了大量封装反而不容易看懂。我更推荐自己一步一步把模块搭起来每个模块都清楚它在算什么。3.1 数据准备模块的细节先在init_parameters.m里把基础数据设置好。类别有参数、单位、数值。可以用结构体封装方便后续调用。比如设置微网的原始负荷、风电出力、光伏出力每个都是一个24维的列向量。常见做法是读Excel或CSV也可以直接在脚本里用数组赋值。如果做测试我建议先用一个合成数据比如风电出力在夜间低谷大、白天小光照在中午大负荷早晚高峰这样方便验证模型行为是否合理。我常用的代码结构是这样的% 初始化参数 T 24; % 调度周期单位:小时 P_wind [0.4; 0.35; 0.3; ...]; % 风电预测出力 P_pv [0; 0; 0; ...]; % 光伏预测出力 P_load [0.8; 0.75; ...]; % 电负荷 H_load [0.6; 0.6; ...]; % 热负荷 price_grid [0.38; 0.38; ...]; % 分时电网电价每个数据都要保持相同的量纲。我在实际调参时把所有功率统一成MW所有电量统一成MWh价格统一成元/MWh虽然看着数字很大但换算关系比混着用千瓦时和兆瓦时省心得多。3.2 主循环与价格更新框架主程序的核心逻辑是一个循环里面反复调用两个模块。伪代码思路如下% 主循环迭代 price_c 0.3 * ones(T,1); % 初始充电价 price_d 0.5 * ones(T,1); % 初始放电价 lambda 0.5; % 迭代步长 max_iter 50; for iter 1:max_iter % 下层求解微网调度 [P_dis, P_char] solve_microgrid(price_c, price_d); % 上层根据微网返回的充放电功率更新价格 [new_price_c, new_price_d] update_price(price_c, price_d, P_dis, P_char); % 判断收敛 if max(abs(new_price_c - price_c)) 1e-4 max(abs(new_price_d - price_d)) 1e-4 break; end price_c new_price_c; price_d new_price_d; end这个过程里微网返回的充电功率和放电功率是关键的衔接变量。微网如果大量买放电功率说明当前放电价便宜上层下轮可以适当抬价如果放电功率很小说明微网不太买账上层就得降价。这种调整逻辑很像是市场里的价格试探。3.3 下层微网调度YALMIP实现微网模型我用YALMIP写因为YALMIP的语法更接近数学表达式容易检查模型对不对。下面是一个简化版的微网调度模型片段。% 变量定义 P_gt sdpvar(T,1); % 燃气轮机出力 P_eb sdpvar(T,1); % 电锅炉功率 P_grid sdpvar(T,1); % 电网购电功率 P_s_dis sdpvar(T,1); % 从共享储能放电获取的功率 P_s_char sdpvar(T,1); % 给共享储能充电的功率 SOC_s sdpvar(T,1); % 微网内部储能SOC如果有 % 目标函数 objective sum(price_grid .* P_grid) sum(c_gas * P_gt) ... sum(price_d .* P_s_dis) - sum(price_c .* P_s_char); % 约束 constraints []; constraints [constraints, P_load P_wind P_pv P_gt P_grid P_s_dis - P_eb - P_s_char]; constraints [constraints, 0 P_gt P_gt_max]; % ... 其他约束 options sdpsettings(solver, gurobi, verbose, 0); optimize(constraints, objective, options);这里有一个容易混淆的地方微网向共享储能买电时是从储能放电侧买所以价格用price_d如果微网自己的新能源发多了可以把多余的电存到共享储能里获得充电服务收入价格用price_c。因为price_c是储能运营商设定的充电价微网存进去越多运营商赚的服务费越多但微网获得的电价收入可能是负的即微网需要支付一定的充电服务费。3.4 上层储能运营商收益计算当微网返回它需要的充放电功率后上层就能计算储能运营商的收益。收益表达为% 储能运营商收益 income_sell sum(price_d .* P_s_dis); % 放电收入 income_char sum(price_c .* P_s_char); % 充电服务收入 cost_purch sum(price_grid_purchase .* (P_s_char P_s_dis)); % 从电网购电成本 cost_om sum(om_cost * (P_s_char P_s_dis)); % 运维成本 profit_storage income_sell income_char - cost_purch - cost_om;这里注意储能运营商的购电成本要覆盖两方面一是微网给储能充电的功率储能运营商相当于收了微网的电却要自己掏钱买电补充这个逻辑需要定义清楚。实际建模中储能运营商从电网购电用于给储能充电再放电卖给微网。如果微网自己有多余电存进来就是另一种交易模式。不同假设会导致收益公式略有不同我在仿真时会把交易协议写在注释里避免自己过两天忘记。3.5 收敛判断和结果输出收敛判据是用相邻两次迭代的价格差和收益差同时判断。因为价格更新时可能有小幅度震荡单看价格可能不够配合收益差更稳。结果输出建议画四张图微网电功率平衡图展示各个电源出力和负荷曲线储能充放电图展示SOC和充放电功率博弈迭代收敛图横轴迭代次数纵轴价格或收益微网购电费用和储能收益柱状图。画图用plot和stairs就行关键是图例和单位要做清楚。我经常犯的错是忘记把MWh转成kWh结果图上的数值和PPT里的对不上。4. 调参与踩坑记录把我的实战经验直接给你这部分是重点。模型跑通容易跑得合理很难我整理一些自己实际遇到过的坑。4.1 求解器选择与非线性项处理如果下层模型是线性的用YALMIP配Gurobi或CPLEX最稳。MATLAB自带的linprog也行但当双层迭代次数多时linprog每次重新求解的速度未必慢反而省去安装商业求解器的麻烦。麻烦的是迭代求解中价格乘以功率会形成双线性项这也是博弈问题的核心难点。如果采用KKT单层化这些双线性项会进入约束需要引入辅助变量和大M法线性化。用大M法时M的值不能取太大否则数值稳定性变差我一般取相关变量可能上限的10到100倍再根据结果微调。如果你只是想快速验证算法用迭代法就不需要处理非线性项因为价格固定后下层模型是线性的。双线性项只出现在上层收益计算里但上层并不需要求解优化问题只需要根据微网结果计算收益所以双线性没关系。4.2 迭代震荡与不收敛迭代法最典型的问题是价格来回跳。上层放电价抬高下层减少购买上层又降价下层增加购买循环往复无法收敛。我试过几种方法最有效的是“阻尼更新”new_price old_price alpha * (target_price - old_price);其中alpha是阻尼系数取值0.2到0.5。另外还可以用最近若干轮的价格平均值做平滑比如用过去三到五次的平均价格作为当前迭代的参考能明显抑制震荡。收敛阈值不是越小越好仿真数据精度有限设到1e-3或1e-4就够用。4.3 单位与量纲一致性这是新手最容易踩的大坑。有些算例里负荷单位是kWh电价单位是元/kWh储能容量用MWh如果没换算最后成本数值会差好几个数量级。我建议统一用国际单位制加一个基准值功率MW能量MWh电价元/MWh天然气价格元/MWh按热值折算这样所有目标函数的单位都是元横竖能对得上。我在代码开头写了一段单位换算注释把每个数据的原始单位转换成标准单位。4.4 常见报错速查表我整理了几个经常遇到的报错以及对应的解决方案直接列成表格方便对照。报错信息原因解决方案Nonlinear constraints require an options object用YALMIP建模时约束中包含非线性项但未指定适合非线性求解器的options用sdpsettings(solver,fmincon)或者将模型线性化No suitable solver found未安装支持的求解器或YALMIP找不到Gurobi/CPLEX路径先测试yalmiptest确认求解器路径配置正确Inf or NaN value in constraints数据中出现无穷大或NaN通常是负荷数据有缺失用isnan和isinf检查数据Index exceeds array bounds数组维度不匹配比如负荷数据是24×1价格用了24×2统一变量维度利用size检查Solver output is unbounded模型缺少关键约束比如没有给燃气轮机设上限检查约束条件是否完整尤其是功率平衡和上下限5. 从复现到扩展这套代码还能怎么改如果读者只是想交仿真作业跑到第四部分已经够了。但如果要做研究或者工程应用这套基础框架还有很多可以改进的方向。5.1 从确定性到随机优化目前模型假定风电、光伏、负荷都是已知的确定性曲线但实际预测总有误差。可以在下层微网模型中引入多个场景变成两阶段随机优化第一阶段决定储能价格第二阶段根据每个场景分别求微网调度再把场景期望收益作为上层目标。这样模型更真实但计算量会成倍增加。5.2 多微网与多储能扩展单个储能商对应单个微网只是最简单的“一主一从”。实际共享储能服务对象是多个微网价格可能一致也可能差异定价。多微网时储能运营商面对的是多个不同的跟随者每个跟随者需求不同博弈的均衡算法变得更加复杂。不过MATLAB的循环结构天然适合遍历多个微网只需要把下层模型封装成一个独立函数循环多次调用即可。5.3 碳交易机制接入环境目标现在是综合能源微网研究里绕不开的要素。碳交易机制相当于给碳排放量加了一个价格信号微网的燃气轮机、电网购电对应的间接排放都要计入碳成本。这部分扩展只需要在目标函数里加上一项碳价格乘以碳排放量不需要改变整体博弈框架是性价比很高的扩展方向。5.4 给新手的三个建议最后给想复现这个项目的朋友三个建议。第一不要一上来就追求完美模型先用一个简单的单时段或者两台设备的小算例手算或者笔算一遍最优调度结果再用MATLAB代码验证能帮你快速排查建模错误。第二先不用急着装CPLEX或Gurobi先用YALMIP默认求解器和一个小规模算例跑通整个迭代逻辑再去引入商业求解器提升效率。第三每次改完代码把运行结果和上一次对比如果某个变量突变优先检查单位换算和数据拼接而不是怀疑求解器坏了。我自己的体会是主从博弈的MATLAB实现最花时间的其实不是算法本身而是把“经济关系”转换成“数学约束”的那一步。充电价、放电价、服务费、购电成本、收益归属这些概念一旦用数值表达整个模型就立住了。只要你把交易逻辑梳理清楚代码实现就是水到渠成的事。等你把这一版跑通再回头去看论文里的KKT条件和强对偶理论会有一种豁然开朗的感觉。