多微电网、共享储能、博弈调度这几个词放在一起,乍一看像三个独立方向的拼盘,但真正做过配电网优化的人应该能立刻get到,这是一个非常典型的“多主体利益博弈”场景。这几年分布式光伏、风电在配电网层面大规模接入,负荷的峰谷差也在拉大,单一微电网自己配储能,容量小了不够用,容量大了闲置严重,投资回报率很难看。而共享储能这种模式——相当于把储能站从“私有财产”变成“公共资源”,谁缺电谁租、谁有余电谁存——就成了一个很自然的解决思路。
但问题也随之而来:多个微电网都要用同一个储能站,谁优先充、谁优先放、租金怎么定、配电网的网损由谁承担?这些矛盾靠单独优化某一个主体是解决不了的,必须用博弈的思想来做调度。这篇总结我把这套建模和Matlab实现思路完整梳理一遍,从博弈模型设计、KKT条件转换,到YALMIP+求解器的代码框架,再到算例结果分析和常见的坑,尽量写得能直接照着复现,而不是给一个花架子流程图。
1. 问题背景与整体设计思路
1.1 为什么单个微电网“自建储能”不划算
先看一个典型场景。某园区配电网下挂了3个微电网,每个微电网都装了分布式光伏,容量大概在500kW到800kW之间。白天光照好的时候,各微电网内部光伏出力可能超过自身负荷,产生倒送功率;到了傍晚负荷高峰,光伏出力又快速跌到零,微电网不得不从上级配电网大量购电。
如果每个微电网各自装一套储能,会面临两个尴尬数字:按各自峰值负荷的20%配置储能,3个微电网加起来要装大概900kW/1800kWh的储能,初始投资接近500万;但实际运行时,由于各微电网的负荷曲线和光伏出力曲线不完全重合,很多储能容量一天里根本用不满,等效利用小时数极低。加上储能电池的循环寿命是有限的,每多充放一次都是成本,利用率越低,折算到每度电上的成本就越高。
共享储能的意义就在这里:用一个集中式储能站(比如600kW/1200kWh)替代三个分散的小储能,总容量降下来了,但通过错峰调度,实际效果能覆盖住三个微电网的调峰需求。储能利用率上去了,单位成本自然下来。不过,这个“错峰调度”不是拍脑袋排出来的,而是要通过博弈让每个微电网主动调整自己的充放电计划。
1.2 多主体博弈到底在博什么
在这个系统里,至少有三类利益主体:
配电网运营商(DSO):关注的是整个配电网的网损、电压质量、以及联络线功率峰值。它手里有电网拓扑信息和跟上级电网的购电电价,可以通过调整内部电价信号、或者收取网损费用来影响微电网的行为。
多个微电网(MG):每个微电网有自己的负荷和分布式电源,运行目标是让自身一天的总运行成本最小,包括向上级购电费用、向共享储能租赁的费用、以及自己分布式电源的运维成本。
共享储能运营商(SESO):建了一个集中储能站,通过向微电网出租充放电容量来赚钱。它的目标是在满足自己储能安全约束的前提下,最大化租赁收益。
这三个主体的目标函数是互相冲突的:微电网希望租金越低越好,储能运营商希望租金越高越好;微电网希望多从配电网买便宜电,配电网希望联络线功率不要太冲。这种多主体冲突没法用一个统一的全局优化目标来表达,所以需要用博弈论来建模。
标题里的“博弈优化调度”,最常用、也最适合配电网场景的是主从博弈(Stackelberg game):配电网运营商或共享储能运营商作为领导者,先发布价格信号;多个微电网作为跟随者,在这个价格下各自独立优化自己的用电计划。领导者根据跟随者的响应再调整价格,迭代到双方都不愿意单方面改变策略,就得到了Stackelberg均衡。
1.3 为什么选择这种“先定价格、再定功率”的主从结构
我在最初设计这个问题时,也纠结过是直接用纳什均衡(多个微电网同时博弈、地位对等)还是用主从博弈。实际做下来,在配电网这种分层结构里,主从博弈更符合物理本质。
原因是:微电网之间通常不存在直接的电力交易通道,它们都要通过公共配电网连接。真正掌握网络信息和定价权的是上级配电网或储能运营商,它天然处于“领导者”的位置。如果硬把微电网设计成对等的纳什博弈,网络约束(比如节点电压、馈线容量)就不好处理,因为网络约束是全局性的,不属于任何一个微电网。而主从博弈天然分层,领导者可以把网络约束放在自己的上层问题里,下层微电网只需要响应价格信号,结构非常清晰。
从Matlab实现的角度,主从博弈也比纳什均衡好求得多。纳什均衡往往需要求解一个变分不等式,通用解法不强;而主从博弈可以通过KKT条件把下层问题转成上层的约束,最终变成一个单层优化问题,直接用现成的混合整数线性规划求解器就能解。
2. 博弈优化模型的数学建模
2.1 上下层决策变量的设计
先明确各主体手里有哪些“牌”可以打。
领导者(共享储能运营商或配电网)的决策变量:
- 售电/购电价格:储能向微电网放电时的售电价格、从微电网充电时的购电价格;
- 或者可分配的储能容量:每时段给每个微电网分配的充放电功率上限。
跟随者(每个微电网MG_i)的决策变量:
- 自身各种分布式电源的出力;
- 与配电网的交换功率(买电/卖电);
- 向共享储能申请的充电功率和放电功率。
这里有一个常见的设计选择:如果领导者的决策变量是价格,那么下层微电网是一个价格接受者,求得的解是均衡价格下的最优用电计划;如果领导者直接决策容量分配,那更像是一个资源分配问题,博弈色彩会弱一些。实际文献里常见的是“储能运营商决策租赁价格,微电网决策租赁电量”的组合,所以我也按这个思路来建模。
2.2 微电网下层优化模型
每个微电网MG_i的目标函数是自身总成本最小化:
min ∑(购电费用 + 储能租赁费用 + 分布式电源运行成本 - 向电网卖电收益)
约束条件包括:
- 功率平衡约束:光伏出力 + 储能放电 + 上级购电 = 负荷 + 储能充电 + 上级卖电;
- 联络线功率上下限:微电网与配电网的交换功率不能超过线路容量;
- 储能充放电功率约束:充放电不能同时进行,且有最大功率限制;
- 储能SOC约束:每个时段SOC在安全范围内,且一天结束时SOC要恢复初值(防止把储能当“无底洞”用)。
这个下层问题本身是一个线性规划(LP),因为目标函数和约束都是线性的。要做成博弈的话,还需要往目标函数里加入对价格信号的响应项,也就是“被领导者定价引导”的部分。
2.3 共享储能上层优化模型
储能运营商的目标是收益最大化:
max ∑(售电收入 + 充电服务费 - 购电成本 - 储能折旧成本)
它的约束包括储能本身的物理约束:充放电功率上限、SOC动态方程、SOC上下限、日始日终SOC平衡。同时,它的价格决策会影响下层微电网的用电行为,所以它不是简单的单层优化,而是“带约束的下层问题”的领导者问题,数学上是一个MPEC(Mathematical Program with Equilibrium Constraints,带均衡约束的数学规划)问题。
这里要特别说一下,MPEC是不满足常规凸优化条件的,因为下层问题的最优性条件引入了互补松弛约束,直接扔给Gurobi是解不了的。标准的处理办法是用KKT条件把下层问题“提升”到上层,然后对互补松弛约束做线性化。
2.4 KKT条件替换下层问题
对于一个线性规划的下层问题,它的KKT条件包含三类:
- 稳定性条件(stationarity):拉格朗日函数对决策变量求导等于零;
- 原始可行性:原问题约束成立;
- 对偶可行性 + 互补松弛:对偶变量非负,且约束和对偶变量的乘积等于零。
把这三类条件全部写成上层问题的约束,原来的双层问题就变成了单层问题。步骤很清楚:
- 写出下层问题的拉格朗日函数;
- 对所有决策变量求偏导,得到稳定性条件;
- 保留原约束作为原始可行性约束;
- 把互补松弛条件用大M法或强对偶定理线性化。
互补松弛约束的形式是“λ ⊥ (Ax - b)”,也就是λ≥0、Ax-b≥0、λ·(Ax-b)=0这三者同时成立。最常用的线性化方法是引入二进制变量和大M参数:
- λ ≤ M·z
- Ax - b ≤ M·(1 - z)
其中z是0/1变量,M是一个足够大的正数。这个M的取值有讲究:取得太小可能切掉可行解,取得太大可能导致数值病态,一般取该约束物理量最大可能值的10到100倍。
2.5 从MPEC到MILP:最终交给求解器的形态
下层问题如果只是LP,经过KKT替换后,上层问题变成带互补线性化约束的MILP。整条技术路线总结成一句话:上层领导者决策价格,下层微电网响应功率,用KKT把下层卷进上层,用大M把互补松弛摊平,最后得到一个可以用商业求解器解的混合整数线性规划。
不过,这里还有一个备选方案:如果下层问题数量多、规模大,整体转成MILP之后整数变量会爆掉(每个互补约束需要1个0/1变量,下层约束一多就成百上千个整数变量),求解速度会很慢。这种情况下,用分布式的迭代求解更实用:上层先给定价格,下层并行优化各自问题,把结果返回上层,上层根据响应更新价格,迭代到收敛。这种方法虽然不能保证收敛到全局最优,但工程上完全够用,而且每一步只需要解一个LP,速度优势巨大。后面Matlab实现部分我会讲这两种方式的代码结构。
3. Matlab代码整体结构与核心函数设计
3.1 工程化文件组织
我建议把代码按功能拆成几个文件,不要全都堆在一个脚本里。一方面是好调试,另一方面是后面换算例场景时不用大改。
main_stackelberg.m % 主程序,负责数据加载、循环调用、结果汇总 init_system_data.m % 初始化微电网、配电网、储能参数 build_upper_model.m % 构建储能运营商/配电网的上层优化模型 build_lower_model.m % 构建单个微电网的下层优化模型 solve_mpec.m % 用KKT转换求解单层MILP(方式一) solve_iterative.m % 迭代式主从博弈求解(方式二) plot_results.m % 绘图:功率曲线、SOC曲线、收敛曲线、成本对比这种结构下,换场景只需要改init_system_data.m里的数据,逻辑代码不用动。算例规模大的时候,也方便把求解部分改成parfor并行。
3.2 数据准备:别让数据成为复现的拦路虎
算例数据是整个复现过程中最容易翻车的地方。很多论文的代码下载下来跑不通,80%的原因是数据没给全,比如负荷曲线和光伏出力曲线不匹配、储能参数缺失、电价曲线单位不对。
我用的这组典型参数如下:
| 参数 | 数值 |
|---|---|
| 微电网数量 | 3个 |
| 调度周期 | 24h,单位时段1h |
| 光伏装机容量(各MG) | 800kW / 600kW / 500kW |
| 微电网峰值负荷 | 1200kW / 900kW / 700kW |
| 共享储能额定容量 | 1200kWh |
| 储能额定功率 | 600kW |
| 储能SOC下限/上限 | 0.1 / 0.9 |
| 储能充放电效率 | 0.95 / 0.95 |
| 分时电价(峰/平/谷) | 1.2 / 0.8 / 0.4 元/kWh |
| 微电网购电价(上级电网) | 分时电价 * 1.05 |
| 微电网售电价(上级电网) | 分时电价 * 0.9 |
负荷曲线我一般用实际采集的典型日数据,如果没有,可以按“早晚高峰、中午低谷”的形态用正弦函数叠加随机噪声生成。光伏出力用“晴天日”曲线,从早上6点开始上升,中午12点到1点达到峰值,下午5点降到零。注意要让光伏峰值出现在中午低谷电价时段,这样才有“午间存电、晚峰放电”的经济动机。
3.3 主程序循环逻辑
迭代式主从博弈的流程是这样的:
- 初始化价格(储能租赁电价取分时电价的某个倍数作为初值);
- 每个微电网以当前价格独立求解自己的LP,得到充电/放电功率需求;
- 储能运营商汇总所有微电网的申报功率,加上网络约束,求解自己的优化问题,更新价格;
- 检查前后两轮微电网申报功率的偏差是否小于阈值(比如1e-4),如果满足就停止,否则回到步骤2。
这个流程非常像拍卖:微电网报需求,储能运营商调整报价,来来回回几次就收敛了。关键点在于步骤3里储能运营商的模型必须包含“微电网的响应函数”,也就是前面说的KKT条件。用迭代的方式做,就不需要把KKT显式写出来,而是靠外部循环来逼近均衡点,代码简单很多,代价是收敛速度会受初值影响。
3.4 YALMIP建模核心代码片段
下面给一段建立下层微电网优化模型的YALMIP代码,变量声明和约束写法都以可直接运行为目标:
function [model] = build_lower_model(mg_data, price, storage) % mg_data: 微电网负荷、光伏、联络线限值 % price: 储能租赁电价(领导者给定) % storage: 储能参数 % 决策变量 Pbuy = sdpvar(24, 1); % 向上级电网购电 Psell = sdpvar(24, 1); % 向上级电网售电 Pch = sdpvar(24, 1); % 储能充电功率 Pdis = sdpvar(24, 1); % 储能放电功率 SOC = sdpvar(24, 1); % 储能荷电状态 % 目标函数 Obj = sum(Pbuy .* mg_data.grid_buy_price) ... - sum(Psell .* mg_data.grid_sell_price) ... + sum((Pch - Pdis) .* price) ... % 租赁费用 + sum(Pdis .* storage.degradation_price); % 储能磨损折算 % 约束条件 Constraints = []; % 功率平衡:光伏+购电+放电 = 负荷+售电+充电 Constraints = [Constraints, ... mg_data.pv + Pbuy + Pdis == mg_data.load + Psell + Pch]; % 联络线约束 Constraints = [Constraints, 0 <= Pbuy <= mg_data.Plink_max]; Constraints = [Constraints, 0 <= Psell <= mg_data.Plink_max]; % 储能充放电约束(同一时段不能同时充放) Constraints = [Constraints, 0 <= Pch <= storage.Pmax * (1 - is_charging)]; Constraints = [Constraints, 0 <= Pdis <= storage.Pmax * is_charging]; % 储能SOC动态 Constraints = [Constraints, SOC(1) == storage.SOC_init + Pch(1)*storage.eta_ch - Pdis(1)/storage.eta_dis]; for t = 2:24 Constraints = [Constraints, SOC(t) == SOC(t-1) + Pch(t)*storage.eta_ch - Pdis(t)/storage.eta_dis]; end Constraints = [Constraints, storage.SOC_min <= SOC <= storage.SOC_max]; Constraints = [Constraints, SOC(24) == storage.SOC_init]; % 日始日终一致 % 求解 options = sdpsettings('solver', 'gurobi', 'verbose', 0); optimize(Constraints, Obj, options); model.Pch = value(Pch); model.Pdis = value(Pdis); model.SOC = value(SOC); model.Obj = value(Obj);这里面有个容易写错的点:充放电同时性的约束。初学者经常写成Pch * Pdis == 0,这是一个非线性约束,求解器会报错或者求解极慢。正确做法是引入二进制变量is_charging,把约束线性化。上面的代码里is_charging是一个24维的二进制变量,当时段t处于充电状态时等于1,放电状态时等于0。
3.5 上层储能运营商的MPEC目标
上层模型在YALMIP里本质上和下层很像,只不过多了一大堆KKT条件。我的建议是先不要手写所有的KKT,太容易出错。可以直接用YALMIP的kkt函数来生成下层问题的KKT条件,然后append到上层问题的约束里。
% 下层微电网问题(用优化模型对象表示) lower_opt = optimizer(Constraints, Obj, options, {u}, {Pch, Pdis, SOC}); % 生成KKT条件 [KKT_system, details] = kkt(lower_opt, {'Pch', 'Pdis'}); % 上层问题约束拼接 Constraints_upper = [Constraints_upper, KKT_system];虽然YALMIP的kkt函数在旧版本里有一些坑(比如有些非线性算子识别不了),但在我们这种纯线性模型里是没问题的。生成完KKT之后,还需要手动处理互补松弛约束,通常是用binvar声明二进制变量辅助大M法,这部分建议封装成单独的函数,方便复用。
3.6 求解器的选择与参数配置
这个问题用到的求解器主要是Gurobi或CPLEX,因为转成MILP之后整数变量规模不小。如果没有商业求解器,也可以先用intlinprog顶着,但性能会差不少。我的配置建议:
options = sdpsettings('solver', 'gurobi', ... 'gurobi.MIPGap', 0.001, ... 'gurobi.TimeLimit', 600, ... 'verbose', 0); % 如果用CPLEX % options = sdpsettings('solver', 'cplex', ... % 'cplex.mip.tolerances.mipgap', 0.001, ... % 'verbose', 0);Gurobi求解MILP的默认MIPGap是1e-4,对于这种调度问题其实可以放宽到1e-3,求解速度快很多,而目标函数值差异几乎可以忽略。TimeLimit设置很重要——尤其是在调试阶段,如果不加时间限制,遇到一个病态模型可能几分钟都算不完,严重影响调试效率。我习惯先把TimeLimit设成120秒,能算出来再逐步加大。
4. 两种求解路线的对比与代码实现说明
4.1 单层MILP路线:一次求解,理论最优
把KKT条件代入上层,整个问题变成一个单层的MILP,好处很明显:一次求解就能得到博弈均衡的全局最优解,不需要关心初值、收敛速度这些麻烦事。缺点是整数变量多,规模大了求解时间呈指数增长。
我做过一个3微电网、24时段、每时段300个约束左右的模型,转成MILP后大概有800多个二进制变量,Gurobi求解时间在3到10分钟之间。这个速度拿来发论文是没问题的,但要反复调参时就很痛苦。
4.2 迭代路线:先求可行,再求最优
迭代路线代码量少很多,逻辑直观,每一步只解线性规划(LP),速度快到飞起。3个微电网并行求解,每一步耗时基本在0.5秒以内,迭代30次左右就能收敛,总共不到20秒。
缺点是需要手动设置价格更新规则和收敛判据,而且价格更新策略直接影响收敛质量。常用的更新方式是次梯度法:
price_new = price_old + alpha * (申报功率总需求 - 储能可提供功率)步长alpha的取值是门手艺活,取大了来回震荡,取小了收敛太慢。我的经验是先取一个较大的步长快速逼近,等目标函数值变化开始变慢时再缩小步长。
4.3 两种路线的代码对比例子
这是迭代法的核心循环:
for iter = 1:max_iter % 步骤1: 给定价格,求解所有微电网下层问题 for k = 1:num_mg mg_model(k) = build_lower_model(mg_data(k), price, storage); end % 步骤2: 汇总申报功率,判断收敛 demand_charge = sum([mg_model.Pch], 2); demand_discharge = sum([mg_model.Pdis], 2); gap = max(abs([demand_charge - demand_charge_prev; ... demand_discharge - demand_discharge_prev])); if gap < tol break; end % 步骤3: 更新价格(次梯度法) price = price + alpha * (demand_charge - storage.Pmax * ones(24,1)); price = max(price, price_min); price = min(price, price_max); demand_charge_prev = demand_charge; demand_discharge_prev = demand_discharge; end其中price_min和price_max是价格的上下限,根据分时电价来确定,不能放开了让价格乱跑,否则很容易发散。
我在实际项目中更推荐一个混合做法:先用迭代法快速得到一个近优解,作为MILP的初值热启动,再切到单层MILP做精细求解。这样既保证了求解质量,也避免了MILP冷启动时前几百秒的探索过程。Gurobi的Start参数可以直接传入这个热启动解。
5. 算例设计、结果分析与出图技巧
5.1 典型算例场景设计
为了把博弈效果展示清楚,我设置了3个对比场景:
- 场景A:无储能。每个微电网只从上级电网购电,光伏多余电量直接上网卖电。
- 场景B:各微电网自建储能。储能容量按各自峰值负荷的20%配置,不参与共享。
- 场景C:共享储能。3个微电网共同租赁一个集中储能站。
从运行结果看,场景C的总运行成本最低。关键在于储能利用率这个指标,自建储能场景里,每个微电网储能的日循环次数只有0.6到0.9次,而共享储能站全天循环次数可以达到1.6次以上。电池的度电成本跟循环次数直接挂钩,循环次数上去了,单位储能成本就降下来了。
5.2 结果图表要怎么画才说明问题
Matlab绘图的几个关键图件:
储能SOC曲线:3个微电网共享一个储能站时,多时段内不同微电网在交替充放电,SOC曲线呈现明显的“锯齿形”,说明储能被高频利用。而自建储能场景下,每条SOC曲线都比较平,放电深度也不深。
各微电网购电功率曲线:共享储能场景下,中午光伏大发时微电网会大量充电,晚高峰时放电,从配电网购电的峰值会被削掉一截。这个削减幅度是论文里最有说服力的指标。
博弈收敛曲线:画出每一轮迭代后微电网申报总功率的偏差值,横轴是迭代次数,纵轴是gap,可以看到偏差指数级下降。这张图能证明你用的是博弈收敛,而不是随便迭代了几次就停了。
成本对比柱状图:横轴是3个场景,纵轴是总运行成本。重点标注出共享储能相比自建储能的成本下降百分比。
5.3 从结果中能读出什么:不只是“省了多少钱”
算例做完之后,还需要留意一个更深层的现象:博弈均衡点下,微电网的成本未必比“只优化自己而不考虑别人”时更低。这跟囚徒困境有点类似——每个微电网个体理性地追求成本最小,但所有微电网同时这么做,反而可能造成储能资源挤兑,大家都不好过。共享储能的博弈调度,本质上是用一个价格信号来协调多个理性主体的行为,让个体最优和全局最优尽量接近。
我在调试时多次观察到,如果把租赁价格定得过高,微电网宁可多从配电网买电也不用储能,储能收益率反而下降;价格过低,又会出现多个微电网同时抢着租用储能,形成新的峰谷矛盾。找到那个微电网愿意用、储能运营商又有利可图的“中间价格区间”,是调参时最有意思的部分,也是这类问题的核心难点。
6. 常见问题与排查技巧实录
6.1 算不出来、求解器报错怎么办
我自己踩坑最多的是YALMIP的kkt函数报错。最常见的情况是最新版YALMIP对某些变量类型的约束生成KKT条件时,会缺少某几行对偶变量,导致上层模型约束不完整,求解器返回infeasible。排查方法是把KKT条件逐一打印出来,跟手写的理论推导对照一遍,看看哪条稳定性条件缺失了。
另外,大M的取值也是个高频坑。我见过有人设M=1e6,结果Gurobi直接报数值问题。大M的正确取值原则是:“刚刚大于该约束在物理上可能的最大取值即可”。比如联络线功率上限是500kW,那么M取5000就足够了,完全没必要取1e6。M太大,会把MILP的LP松弛边界推离真实可行域,求解器内部的branch-and-cut算法效率暴跌。
6.2 迭代不收敛或结果震荡
用次梯度法更新价格,最常见的现象是价格在上限和下限之间来回震荡。解决手段有三个方向:
- 减小步长alpha,这最简单有效;
- 对价格做“惯性”处理,即新价格 = 0.7 * 新价格 + 0.3 * 旧价格,能显著抑制震荡;
- 不用次梯度,改用“最佳响应”更新:每次把所有微电网的申报需求汇总后,直接求解一个不考虑互补约束的储能优化问题,用它的对偶变量作为新价格信号。
第三个方法收敛性更好,但实现复杂度稍高。如果只是做课程作业或验证方法可行性,前两个就够用了。
6.3 MATLAB版本和工具箱的坑
kkt函数依赖YALMIP的优化模型接口,有些旧版本(2020年以前)对sdpvar和optimizer的交互支持不佳,建议直接把YALMIP升级到最新版。另外,YALMIP本身是第三方工具箱,不是Matlab官方自带的,安装时要把路径添加到MATLAB的搜索路径里,很多报错“Undefined function or variable 'sdpvar'”就是路径没设置好。
求解器方面,Gurobi和CPLEX都需要单独安装并配置license。安装完一定记得在MATLAB里执行gurobi_setup或者cplex_setup,然后输入yalmiptest验证YALMIP能正常调用求解器。这一步不做好,后面所有代码都会卡在“No solver found”这一行上。
6.4 常见问题速查表
| 现象 | 可能原因 | 解决办法 |
|---|---|---|
| sdpvar未定义 | YALMIP路径未添加 | 检查setpath,重启MATLAB |
| 求解器提示无输出 | 许可证未激活 | 运行yalmiptest诊断 |
| 模型infeasible | 大M太小或约束冲突 | 核查KKT条件、放宽SOC约束 |
| 求解时间过长 | 整数变量过多 | 增加MIPGap到1e-3,设TimeLimit |
| 迭代结果震荡 | 步长设置不合理 | 缩小alpha,加惯性系数 |
| SOC曲线不闭合 | 日末SOC约束缺失 | 添加SOC(24)==SOC_init |
| 功率平衡不满足 | 光伏/负荷曲线时段错位 | 检查单位,确认都是24维向量 |
6.5 调试技巧:先跑通最小用例再上规模
这个过程我踩了很多次坑。一开始直接在24时段、3个微电网的规模上调试,出了问题根本不知道是模型逻辑错还是数据错,排查效率极低。后来学聪明了,先用2个微电网、6个时段的最小用例跑通,确认博弈收敛、成本计算结果符合直觉,再逐步扩展到24时段。
具体做法是:把光伏和负荷曲线截取前6个点,储能SOC约束也按6个时段设置。跑通之后检查一个关键指标——储能只有在这6个时段里有充放行为,且充放电量完全对得上SOC变化。这个验证一旦通过,基本可以确定模型逻辑没问题,后面的扩展只是数据规模变化。
还要强调一个容易被忽略的细节:每个微电网的日末SOC约束最好都设为初值。如果不加这个约束,优化结果会出现储能一天结束时电量被放空的“偷能量”现象——它在利用日与日之间的边界漏洞,虽然单日成本好看,但第二天的运行没法继续。我们做调度时,边界条件必须闭环。
7. 一些个人实操心得
这套“多微电网+共享储能+博弈调度”的Matlab实现,这两年从理论到落地我已经跑了不下十来组算例,最大的两个感悟是:博弈建模的成败不在于算法多复杂,而在于下层模型是否真正反映了每个微电网“个体理性”的行为逻辑;以及价格信号的设计能不能同时引导个体和整体的利益趋同。只改KKT推导不动价格更新规则,模型很容易变成一个花瓶。
再说一个实用建议——如果只是做单时段的博弈分析,不需要24时段这么重,可以用3个典型时段(峰、平、谷代表时段)来搭建模型,计算量小,出图也更直观。等把博弈过程彻底跑通了,再换24时段精算。
最后分享一个小技巧:在跑大规模算例前,先用profile查看一下代码的耗时分布。很多时候90%的时间都花在求解器内部,而不是建模过程本身。如果是迭代法,可以试试把各微电网下层的LP放到parfor循环里并行求解,在3微电网时效率提升有限,但如果算例扩展到10个以上微电网,并行求解几乎是必须的。共享储能博弈的魅力就在于,微电网越多、负荷互补性越强,这套方案的收益就越明显——不过要是你只想跑个demo,3个微电网、单台储能站就已经能讲清楚整个故事了。