做了那么多年电力系统优化调度方向的仿真我越来越觉得“多微电网共享储能配电网博弈”是个特别典型、又特别容易讲不清楚的题目。很多刚接触这个方向的同学论文里把“Stackelberg博弈”“KKT条件”“双层优化”这几个词堆得挺漂亮打开Matlab就不知道怎么下手。我也见过不少把Yalmip代码写出来但解出来全是NaN或者迭代一万次都不收敛的情况。这篇就围绕这个题目把我实际做仿真时的思路、建模过程、代码模块划分以及最常踩的几个坑一次性说清楚。如果你正在做多微电网租赁共享储能的配电网优化调度希望在Matlab里跑出能用的仿真结果或者你是刚接触双层博弈优化、想搞明白这到底是怎么落实到代码上的这篇应该能帮你省不少时间。1. 场景与矛盾租来的共享储能博弈从哪来1.1 多微电网共享储能的典型场景先把场景放在脑子里。假设配电网下面挂了三个微电网每个微电网里都有分布式光伏、常规负荷也可能有一点小风机。白天光照好的时候光伏大发微电网自己用不完余电想卖给电网到了傍晚和晚上光伏出力掉下去负荷还坚挺微电网又得从配电网买电。峰谷电价差越来越大这个“午间卖电、晚峰买电”的时段矛盾就非常突出。每个微电网如果自己建一套储能投资成本高而且每一个站的容量利用率往往很低。拿我算过的一个三微电网模型来说如果各自配置储能不考虑建设成本时运行费用能压下来但一旦把折算到日期的投资成本、运维成本算进去单个微电网的储能经济性就很差。于是就有了一种更灵活的模式配电网侧或者第三方建一个共享储能电站微电网不自己持储而是按需“租用”容量和充放电次数。这就是“多微电网租赁共享储能”的基本盘储能产权不归微电网但使用权按租赁机制分配。1.2 为什么集中式优化在这里走不通很多同学的第一反应是既然要配电网整体最优那把几个微电网的数据全部收集上来做一个集中式优化不就行了目标函数统一约束统一全局最优解也能求到。学术上当然可以这么干但实际运营中几乎走不通原因有两个最现实。第一个是信息壁垒。多微电网往往属于不同的运营主体每个微电网内部的光伏预测、负荷曲线、储能剩余电量这些都是私有信息。你让它们把全部数据交给配电网做统一调度没有激励也不现实。第二个是利益不一致。配电网想压低运行成本、保证电压安全各微电网想最小化自己的购电费用和储能租金。目标不一致还存在明显的先后决策关系——配电网定租金/电价微电网再响应底层就构成了Stackelberg博弈。用博弈论处理这类问题是更合理的路径。配电网是领导者它通过设定租赁单价、交易电价来引导微电网的用电和充放电行为微电网是跟随者它在给定的价格下优化自己的购电、光伏消纳和储能使用策略。清楚了这一点后面所有建模都围绕“价格引导下的主从决策”展开。2. 数学建模上层配电网与下层微电网到底在优化什么2.1 配电网层的决策变量与目标函数配电网层的角色是系统运行者和价格制定者。它决策的是向微电网售电的电价也就是微网购电电价微网向配电网反送电的上网电价共享储能租赁给各微电网的单位容量租金和单位充放电量使用费。目标函数常见有两种设计方式取决于你的论文主线。一种是配电网运行成本最小化包括向上级电网购电的成本、网络损耗成本减去向微电网售电的收益、储能租赁服务收入另一种是整体社会效益最大化把微电网收益也算进来但实际做仿真时更常用前者因为它更能体现“配电网作为独立利益主体”的地位。这里最容易犯的一个错误是把所有变量塞到同一个目标函数里直接求全局最优这其实是集中式优化不是博弈。配电网层只能优化它自己能控制的变量微电网的决策变量是它响应后的结果应该作为下层问题的函数而不是作为配电网的自由变量。2.2 微电网层每个主体在租储能时的运行约束每个微电网层是一个单层优化问题目标函数是自己在调度周期内的净成本最小。一般来说包括向配电网购电的费用光伏剩余电量上网的收入如果是卖电给配电网共享储能的租赁成本和充放电服务费弃光惩罚如果允许弃光储能运行维护成本。功率平衡约束是微电网内部最基本的等式约束光伏出力 购电功率 储能放电功率 负荷 售电功率 储能充电功率。共享储能这块要单独说。微电网不拥有储能所以每个微电网有一个“租赁容量上限”它实际能用的储能变成了一个额外决策变量。储能荷电状态SOC按小时递推充电时增加放电时减少。这里要注意一个细节如果储能是共享的多个微电网共同使用同一个储能电站那么任意时刻所有微电网充放电功率之和不能超过储能电站的充放电功率上限SOC也是全局统一的只是分配给不同微电网的容量不同。2.3 共享储能租赁定价与结算机制的数学表达共享储能的结算方式一般分成两部制容量费和使用费。容量费按租赁容量计算不管你用不用只要占用了资源就得付使用费按实际的充放电量计算。这个设计对只租不用、或只用不租的微电网都形成约束能让共享储能资源被更合理地分配。从配电网角度看租赁价格是博弈的领导者策略。价格定得过高微电网宁愿多从配电网买电也不用储能储能设备闲置价格定得过低所有微电网都抢着用储能可能出现容量超卖配电网自身收益也受损。所以最优价格一定存在一个平衡点这也是优化调度要解决的核心问题。刻画这个问题时常用的是不等式约束和互补条件。建模时微电网会在租赁容量和实际使用量之间做权衡配电网则在价格和服务量之间做权衡。两层目标相互嵌套就形成了双层优化结构。3. 核心求解链路从双层问题到单层可解模型3.1 两种常见求解路径及其适用场景双层优化问题不能直接用现成的单层求解器一股脑解。仿真中最常用的就是两条路。路径一迭代求解法。初始化一组电价和租金依次求解各微电网的下层问题得到购电量和储能使用量配电网根据响应结果更新价格再重新求解微电网问题循环往复直到价格和功率不再变化。这种思路直观也符合Stackelberg博弈“领导者宣布策略跟随者响应的动态过程”很多做分布式算法的论文用的就是这个框架。但它的缺点是收敛性无法严格保证价格振荡的情况我实际遇到过很多次。路径二KKT条件单层化。把每个微电网的下层优化问题用它的KKT条件替换下层问题就从“优化模型”变成了“约束条件”整个双层问题被改写成配电网的单层优化问题。如果下层问题是线性规划或凸二次规划KKT条件是原下层问题最优解的充要条件这样改写是严格的求出来就是双层模型的最优解。缺点也很明显互补松弛条件会引入整数变量求解规模成倍增长。实操时我的做法是先用KKT单层化求解小规模系统得到基准解再用迭代法重跑一遍做对比。这样既能验证迭代法的结果是否偏离最优解又能给论文提供一个“分布式求解与集中KKT结果一致性”的算例分析。3.2 下层问题的KKT条件推导要点以每个微电网的下层线性规划为例。它的目标函数是最小化购电成本和储能租赁成本约束包括功率平衡等式、储能SOC递推等式、购电功率上下限、储能充放电上下限、SOC上下限等。写KKT条件时按下面的步骤来基本不会乱写出拉格朗日函数把等式约束乘上拉格朗日乘子λ不等式约束乘上非负乘子μ对各决策变量购电功率、储能充电功率、放电功率、租赁容量等求偏导令其等于0得到平稳性条件保持原约束不变得到原可行性条件拉格朗日乘子加上非负约束得到对偶可行性条件每条不等式约束对应一个互补松弛条件μ×(约束上下限–实际值) 0。这里面最麻烦的就是互补松弛条件。它是一个“乘积等于零”的非线性约束不能直接丢给Gurobi这类求解器。通常做法是用大M法把互补条件线性化引入0-1整数变量把形如“μ≥0, g(x)≤0, μ·g(x)0”改写成“g(x) ≥ –M·(1–z)μ ≤ M·z”这样一组线性不等式。注意M的取值非常讲究。取得太大数值稳定性差求解器容易陷入病态取得太小可能把可行域削掉一块求出来的解不是真最优。我的经验是先解一次松弛问题从原始和对偶变量的数量级反推M的取值通常设置成变量数量级的10到100倍就够了。3.3 单层化后交给什么求解器如果下层KKT线性化之后整个模型是混合整数线性规划MILP那么YalmipGurobi或者YalmipCplex都是Matlab环境下的标准配置。如果配电网潮流用了二阶锥约束那么模型会变成混合整数二阶锥规划MISOCPGurobi和Mosek都支持。在实际代码里先用sdpvar定义变量然后用optimize求解。但双层问题单层化后往往规模不小一定要设置好求解器的参数比如MIPGap、时间限制。我通常会设置MIPGapAbs或者相对MIPGap在1e-4到1e-3之间否则一个几十个小时的多时段模型很可能会在整数变量上卡几个小时不动。4. Matlab工程实现代码结构、数据流与关键细节4.1 代码模块划分与数据准备这个项目我在Matlab里一般按下面的结构组织结构虽然简单但工程上非常好维护main.m % 主程序入口负责数据加载、求解和结果汇总 data_load.m % 读取配电网参数、负荷曲线、光伏曲线、储能参数 upper_model.m % 配电网层目标函数与约束 lower_mg_model.m % 单个微电网的下层优化函数 kkt_linearization.m % 下层问题的KKT条件以及互补约束的线性化 plot_result.m % 可视化各微电网功率曲线和储能SOC数据准备非常关键。配电网可以用IEEE 33节点这种经典系统把几个微电网分别接在节点上光伏和负荷数据用典型日曲线。时间尺度我习惯按24小时时段建模每个时段1小时。共享储能参数包括额定容量、最大充放电功率、初始SOC、SOC上下限、充放电效率、租赁容量上限。微电网参数包括光伏装机容量、负荷曲线、购电功率上限、弃光成本、微电网与配电网的连接节点。4.2 关键变量定义与约束映射用Yalmip定义变量时要特别注意变量的维度。微电网数量、时段数、节点数这三层维度刚开始很容易写串。我的编码习惯是每个微电网单独定义一个结构体变量例如mg{i}.pbuy、mg{i}.pcharge这样约束和目标都更清晰。下面是共享储能SOC递推约束的一个典型写法for t 2:T mg_soc(t) mg_soc(t-1) etac * mg_charge(t) - (1/etad) * mg_discharge(t); end其中mg_charge、mg_discharge分别是该微电网在共享储能中分到的充电和放电功率etac是充电效率etad是放电效率。注意这里充放电功率的含义是“微电网从储能吸收/注入的侧功率”还是“储能侧的实际功率”两种定义方式效率公式写法不同。我建议全程统一用微电网侧功率这样租赁结算、容量约束、电量约束能对得上减少很多混乱。4.3 双层KKT代码的落地细节KKT单层化在代码层面最繁琐的部分是把下层问题的所有约束转化为KKT条件再合并进上层。这个模块我建议用函数封装输入是下层问题的变量句柄和参数输出是KKT条件约束。写下层问题目标函数的时候要选择“不含二次项”的表达。因为互补松弛要求下层问题是线性的最省事。比如微电网购电成本写成Price_buy * pbuy不要写成pbuy^2这种带二次项的形式。如果要考虑购电价格的线性函数那就引入辅助变量线性化否则下层变成二次规划KKT里会多出很多复杂的交叉项。互补条件线性化时要为一组约束定义一个二元变量。环境变量多的时候用大M会自动引入一堆整数变量模型求解速度会显著变慢。所以实际编码时我会先分析哪些不等式约束在实际问题中大概率不起作用比如购电功率通常不会触碰上下限这种约束可以保守地丢掉互补条件只保留原始约束和对应的对偶可行性以此降低求解规模。你可能觉得这样处理“不严谨”但从工程仿真角度如果最优解处本来就不会触碰这些边界丢掉互补条件并不会改变解。我在算例里验证过多次结果和完整KKT几乎一致求解时间却能快一个量级以上。4.4 迭代法求解的Matlab主循环如果按迭代法实现主循环长这样price_rent ones(T,1) * init_rent; for iter 1:iter_max for i 1:n_mg [mg_result{i}, charge_plan{i}] lower_mg_model(mg_data{i}, price_rent); end new_rent update_rent(mg_result, charge_plan); if norm(new_rent - price_rent, 2) tol break; end price_rent alpha * new_rent (1 - alpha) * price_rent; % 阻尼更新 end这个alpha阻尼系数值我在实际跑的时候经常给到0.3到0.5之间尤其是当迭代出现振荡的时候。不要天真地每次都全部使用新价格那样非常容易在两组价格之间跳来跳去我最早跑这个题目就是在这里卡了很久。5. 结果分析与踩坑实记收敛性、对比实验和三个坑5.1 收敛性怎么看迭代振荡怎么处理算例跑完之后先不要急着看成本数字第一步必须是验证收敛性。画两条曲线配电网租赁价格随迭代次数的变化曲线、微电网购电量随迭代次数的变化曲线。有经验的话一眼就能看出是平滑收敛还是有周期性振荡。如果价格在几个值之间来回跳排在前面的原因一般是两个一是阻尼系数设得太大没有起到稳定作用二是下层微电网的响应函数存在多解价格变化后微电网的最优解边界换了一个方向。处理振荡一个小技巧是对连续多次迭代的电价做指数滑动平均替代直接用上一轮的结果作为下一轮的初始值。这个方法虽然简单但我在多个算例中都很管用。如果最终判断迭代法收敛到了某个均衡点一定要和KKT单层化的基准解对比。如果差距在1%以内基本可以确认博弈最优解是合理的如果有明显偏差先检查初始价格和迭代停止条件通常能解决。5.2 对比实验怎么设计才有说服力论文里的对比实验我建议至少做三组。第一组是“无共享储能”模式。每个微电网必须自建储能或者完全依赖配电网购电看缺储时候的成本。第二组是“共享储能固定价格”模式租赁价格固定不做博弈优化。第三组是“共享储能博弈优化定价”模式也就是本篇模型。三组跑完之后对比总运行成本、光伏消纳率、储能利用率三个指标基本就能把博弈优化的价值讲清楚。再做一个灵敏度分析扫描租赁价格上限或光伏渗透率。你很快会发现当光伏渗透率很低时微电网对储能的需求不大博弈定价的优化空间也很小渗透率提高之后午间弃光严重共享储能的价值才会凸显出来。这个结论放在论文里非常加分。5.3 三个实战中踩得最深的坑第一个坑是SOC初值陷阱。做24小时调度时储能SOC是一个跨时段耦合的状态量很多初学者把初始SOC设成50%终止SOC却没有任何约束结果求解器为了省成本会在最后一个时段把SOC全部放掉导致末状态异常。我的建议是加一个终止SOC范围约束或者做循环调度时直接用“末状态回到初状态”的周期约束这样结果才符合实际运行逻辑。第二个坑是大M选择不当导致的结果错误。这个前面已经说过。最容易翻车的地方是互补条件线性化中的大M如果给的M太小会把原本可行的方案误排除M太大数值病态可能让求解器报无界或不可行。解决思路是跑几组预试验观察对偶乘子的量级再据此设置。不要所有互补条件全都用同一个M。第三个坑是共享储能的容量约束漏写。很多人在多微电网模型里每个微电网单独列SOC约束以为这就够了结果忽略了“多个微电网共用一个储能电站”这个前提。真正的共享储能必须加约束同一时段所有微电网的充放电功率之和不得超过共享储能的总功率上限所有微电网的租赁容量之和不得超过储能总额定容量。这两条约束漏掉之后算出来的结果已经不是共享储能的运行场景而是每个微电网各有一台无损虚拟储能的场景。最后说点个人体会这套模型和代码我前前后后改过三轮。第一轮用纯迭代法跑起来虽然快但论文评审最容易被问“你怎么保证收敛到最优解”第二轮完整做KKT单层化结果更可靠但模型规模一大Gurobi也经常跑得很慢第三轮才找到平衡主结果用KKT单层化灵敏度分析用迭代法。这个组合方式我建议你也试试。另外给刚开始做这个方向的同学一个建议先别急着直接上多微电网把一个微电网的下层模型和上层配电网的交互跑通画出一条SOC曲线确认储能和电价方向是对的再扩展到三微电网甚至更多。多微电网带来的复杂度是指数上升的一上来就跑多主体版本出了问题往往分不清是模型问题还是代码问题。仿真代码不是写完就完事的用“减少一个微电网、对比结果是否合理”的方式去测试能帮你找到很多隐藏的bug。这个项目的坑我已经替你踩了一部分照着上面的思路去搭应该能少走很多弯路。
