做冷热电多微网优化这两年,我最大的感受是:单微网调度再漂亮,一旦放到多微网集群里,很多模型就开始失灵。原因不复杂——不同微网产权归属不同,负荷特性不同,协同目标也不同。这时候如果还用一个单层模型同时决定储能电站的容量配置和各个微网的运行计划,优化结果往往既不落地,也说不清决策责任。我最近复现的一套“储能电站服务模式下的冷热电多微网双层优化配置”模型,就是专门解决这层矛盾的。项目的核心是用双层优化把“储能投多少钱、建多大”和“多微网怎么用储能把冷热电调度平衡”拆成上下两层决策,并在 Matlab 中用 YALMIP 搭模型、用商业求解器做求解。这篇文章会把我的理解、建模思路、代码组织方式以及踩过的坑完整写出来,适合正在做微电网规划、综合能源系统优化或者想用双层模型发论文的同学参考。
1. 为什么储能电站服务模式需要双层优化:从多微网规划谈起的尴尬现状
1.1 冷热电多微网里的“协同难”到底难在哪
冷热电多微网,简单说就是一片区域内好几个微网抱团,每个微网里同时有电负荷、热负荷、冷负荷。常见的设备配置包括光伏、燃气轮机或内燃机、余热锅炉、吸收式制冷机、电锅炉、蓄电装置等。燃气轮机发电后的余热可以用来供热,也可以驱动吸收式制冷机供冷,实现冷热电的梯级利用。
这种系统说好听是“多能互补”,做起来却是典型的“谁都不服谁”。微网 A 是工业负荷,白天电负荷高、蒸汽需求大;微网 B 是商业办公,晚上冷负荷下降明显;微网 C 可能是数据中心,24 小时电负荷都很平稳。如果各微网各自为政,那就要各自配一套储能和备用机组,成本高、容量冗余大。如果强行统一调度,又会出现一个更现实的问题:这些微网往往分属不同业主,谁也不愿意把自己的负荷曲线、设备效率、真实运营成本全部交给一个中心机构。
更麻烦的是储能电站的定位。前几年大家喜欢把储能做成“共享储能”,表面上是一个电站给多个微网服务,实际上做规划时最常见的做法就是直接按“未来某一微网会吃掉多少电量”去反推容量。这种“一厢情愿”的配置,等真正运行起来往往会发现:服务电价定高了,微网宁可自己多开燃气轮机也不买你的电;服务电价定低了,储能电站投资回收周期拉长到无法接受。也就是说,储能的容量配置和定价策略,必须建立在对微网真实响应行为的预判上,这就天然引出了主从递阶决策的问题。
1.2 储能电站做服务商,决策边界在哪里
在储能电站服务模式下,参与方其实分成了两层:
上层是储能电站服务商。它决定的是在哪个微网节点装多大功率、多少容量的储能,以及收取什么样的服务费。它不能直接命令微网“你必须每天中午十二点充电”,更不能替微网做主怎么开机组。它能做的,只是把容量和价格摆在台面上,然后看微网愿不愿意用、用多少。
下层是各个微网。它们面对给定的储能服务条件和分时电价,自己去做最优调度:什么时候买网电、什么时候烧天然气、什么时候启动吸收式制冷机、什么时候从储能取电充电。微网的一切行为,都是从降低自身运行成本出发的,不会考虑储能服务商的投资回收。这就是典型的 Stackelberg 博弈:服务商是领导者,微网是跟随者。
如果不用双层模型,而是硬把这两层决策捏成一个单层优化,相当于假设储能服务商能替微网做运行决策,还能实时拿到所有负荷数据。这在工程上很难成立,而且优化出的容量偏大、收入预测也偏乐观。我见过不少方案报告,集中式规划结果很漂亮,一落地就开始亏损,根因就在这里。
1.3 双层优化在能源规划中的定位:不是我非要分层,而是现实逼我分层
用生活里的例子来理解最直观:房东想给出租屋装空调,他倾向装一台大功率的,因为可以收更高的租金或者提高房产价值;但租客真正在意的是电费,他只会根据自己的用电习惯决定开不开、开多久。房东如果把“租客一定会用最大功率”当成前提来做投资决策,大概率会亏。房东应该做的是:先给出一个空调容量和电费结构,再观察租客怎么用,最后根据实际使用情况调整方案。这跟储能电站和微网之间的关系一模一样。
所以双层优化在能源规划里的定位,不是什么高深的炫技,也不是论文为了凑创新点硬分出来的结构,而是现实中参与者各算各账、各做各决策的必然结果。它跟集中式单层规划的区别,可以类比成“计划经济”和“市场经济”的差别。单层模型适合同一主体内部多个园区统一调度;双层模型适合产权分离、通过价格信号引导决策的场景。我们现在讨论的“储能电站服务模式”,恰恰属于后者,所以双层优化几乎是绕不开的选择。
2. 双层模型拆解:上层管容量投资,下层管运行调度
2.1 上层模型:以储能电站服务商为主体,决策变量与目标函数
上层模型的主角是储能电站服务商。它的决策变量包括储能系统的额定功率 (P^{cap}{i})、额定容量 (E^{cap}{i}),以及向微网收取的服务价格 (c_s)。这里 (i) 表示第几个微网接入节点。
目标函数是服务商的年化净收益最小化,更准确说是“年化投资成本加运维成本,减服务收入”。投资成本要做等年值处理,不能简单地把总造价直接放进一年期的目标里。工程上常见的做法是用资金回收系数折算:
[ C_{inv}=A_r\sum_{i}\left(k_p P^{cap}_i+k_e E^{cap}_i\right) ]
其中 (k_p) 是单位功率成本,单位元/kW;(k_e) 是单位容量成本,单位元/kWh;资金回收系数 (A_r=\frac{r(1+r)^Y}{(1+r)^Y-1}),(r) 是折现率,(Y) 是储能寿命年限。比如一套功率成本 1200 元/kW、容量成本 1800 元/kWh、寿命 10 年、折现率 8% 的配置,等年值系数算出来大约是 0.149,也就是每千瓦功率每年要摊 179 元,每千瓦时容量每年要摊 268 元。
服务收入则是储能向微网服务时收取的费用,通常可以按充电电量或者放电电量计费。模型里通常还配上一些上层约束,比如总投资预算上限、单个微网节点的最大安装容量、服务电价上下限等。从数学形式上,上层目标函数里嵌着一个需要通过下层求解才能得到的响应项,这就是两层模型难解的根本原因。
2.2 下层模型:多微网系统的冷热电运行调度与需求响应
下层主体是各个微网,每一层决策都是给定储能容量和价格之后,做单微网最优运行调度。这里面的设备运行逻辑比较细,我拆成三块来说。
第一块是电平衡。微网内部的电负荷由网电购电、光伏出力、燃气轮机发电、储能放电共同满足;电负荷则包括原始电负荷、电锅炉耗电、制冷机耗电、储能充电功率。写成等式约束就是:
[ P^{buy}{i,t}+P^{pv}{i,t}+P^{chp}{i,t}+P^{dis}{i,t}=L^{e}{i,t}+P^{ch}{i,t}+P^{eb}{i,t}+P^{ec}{i,t} ]
第二块是冷热平衡。燃气轮机的余热 (Q^{chp}_{i,t}) 加上燃气锅炉的产热,一部分直接供给热负荷,一部分送给吸收式制冷机转换为冷负荷。电制冷机补充供冷缺口。用表达式表示为:
[ Q^{chp}{i,t}+Q^{gb}{i,t}=L^{h}{i,t}+Q^{ac}{i,t} ] [ Q^{ac}{i,t}+Q^{ec}{i,t}=L^{c}_{i,t} ]
第三块是储能运行约束。储能荷电状态需要按时段递推:
[ SOC_{i,t+1}=SOC_{i,t}+\eta_{ch}P^{ch}{i,t}-\frac{P^{dis}{i,t}}{\eta_{dis}} ]
再加上充放电功率上下限、SOC 上下限、充放电互斥约束等。微网的下层目标函数是自己一天运行成本最小,包括买电费用、燃气费用、储能服务费和可中断负荷的补偿费用。储能服务费跟充放电量挂钩,所以微网会根据服务电价自觉决定到底用不用储能。
2.3 上下层之间的耦合变量与信息交互逻辑
双层模型之所以不能简单合并,根源在于耦合变量的特殊地位。上层决策变量是储能容量和服务价格,它们会进入下层的可行域和目标函数;下层的最优解,比如储能实际放电功率 (P^{dis}{i,t}) 和充电功率 (P^{ch}{i,t}),又会通过服务收入影响上层的年化净收益。
信息交互的逻辑是这样的:上层丢给下层一组参数 (x),下层针对这组参数求出一个最优运行方案 (y^*(x)),然后把这个最优方案反馈回上层,上层目标函数再根据这个反馈评估 (x) 的优劣。学者一般把它写成嵌套形式:
[ \min_{x} F(x,y^(x)) \quad s.t.\quad G(x)\le 0,; y^(x)\in\arg\min_{y} f(x,y) ]
这个结构大家应该不陌生,但写出来不妨提醒一句:下层是一个完整的优化问题,不是一个显式的函数。这个特点决定了我们必须选一条合适的求解路径,否则模型根本跑不动。
3. 求解路径选择:KKT 条件、元启发式还是迭代分解?我用过的可靠方案
3.1 为什么不能简单地把两层摞成一个模型求解
如果不做任何处理,直接把双层模型塞给求解器,会发生什么?我试过,结果要么是求解器报“不支持嵌套优化”,要么是收敛到一个完全没意义的点。原因在于上层目标函数里对下层变量做了隐式求解,导致整个问题既不光滑也不凸,常规求解器几乎拿它没办法。
还有人试图直接把两层目标函数加在一起,当成一个大型单层模型来求解,这也是个常见的误解。把两层目标相加,等于默认储能服务商和微网利益完全一致,甚至默认服务商可以命令微网怎么运行。前面说了,这违背了储能服务模式的主从结构。所以答案是:必须对下层模型做等价变换,或者用迭代的方式在上下层之间来回传递信息。
3.2 KKT 条件法:把下层问题等价为上层约束
我最常用、也最推荐先尝试的,是把下层问题用 KKT 条件替换成等价约束。这个思路分几步走:先写出下层问题的拉格朗日函数,求其对所有下层变量的偏导并置零;再写下层约束及其对偶变量的互补松弛条件;最后把这三组式子并到上层模型里,得到一个单层但规模变大的优化问题。
如果你用 YALMIP 写代码,这个过程可以手动构造,也可以调用它的 kkt 工具函数。需要注意的一个重要前提:下层模型如果带 0/1 变量,KKT 条件处理起来就会非常棘手。所以我在复现时会把下层刻意保持成纯线性规划,比如不显式加入充放电互斥二进制变量,而是靠充电服务费与放电收益之间的差异,让最优解自动避开同时间段充放电。这样 KKT 条件严格成立,后续用强对偶关系把下层目标嵌入上层也才说得通。
互补松弛条件里有一堆“(0\le \lambda \perp g(x)\ge 0)”形式的关系式,直接丢给商业求解器是不行的。通常的做法是引入二进制变量和大 M 常数,把互补约束离散化成混合整数线性约束。这一步要小心:M 定太大,数值稳定性差;M 定太小,可能把最优解切掉。我一般会根据模型里物理量的数量级去估算 M,比如储能功率不超过 1 MW,M 就设成 1500,留 50% 裕量。
3.3 元启发式/迭代分解:单层化处理与 Matlab 中的算法配合
如果下层问题上到一定规模,或者包含太多非线性环节,KKT 条件会变得非常啰嗦。这时候第二选择是“外层元启发式 + 内层精确求解”的迭代分解法。
具体做法是:外层用粒子群算法或者差分进化算法去搜储能容量和服务价格;给出一个上层候选解之后,把这个解作为固定参数传给下层,调用 Gurobi 或 CPLEX 解一个常规的微网运行优化问题;下层算完,把目标函数值和最优调度结果送回外层,作为该候选解的适应度。如此反复迭代,直到粒子群收敛。
这种方法的优点是代码侵入性低。上层算法层、下层运行调度层完全解耦,改参数、加约束都很方便。缺点是计算量大、没有最优性证明,而且粒子群这种算法对随机种子敏感,可能需要跑多次取最优。我建议在小规模验证阶段先不用它,等模型定型后再做对比实验。
3.4 我最终选型的理由与适用边界
我先说结论:对 3 到 5 个微网、24 小时到 96 小时时间尺度的储能配置问题,我优先选择 KKT 条件加强对偶转换成单层 MILP,然后交给商业求解器。原因是这个方案能同时拿到全局最优解(至少是求解器容差意义下的全局最优),并且能方便地做灵敏度分析。
元启发式迭代法我留给两类情况:一类是下层含有强非线性、不方便做线性化,另一类是储能服务商和微网之间还要考虑多轮谈判博弈,KKT 写起来代价太大。我把三类路径的对比放在下面,方便你根据自己项目的规模判断:
| 方案 | 精确性 | 实现复杂度 | 适用规模 | 主要风险 |
|---|---|---|---|---|
| KKT+强对偶单层化 | 高 | 较高 | 中等规模,下层尽量线性 | 大 M 常数难选,大规模数值易抖 |
| 元启发式+内层求解 | 中 | 低 | 规模几乎不受限 | 收敛性无法保证,需多次运行 |
| 迭代分解+外层精确搜索 | 中高 | 中 | 适合凸下层结构 | 参数更新规则需仔细设计 |
4. Matlab 代码实现里最容易翻车的三个点
4.1 底层运行模型的线性化处理:别把非线性项直接塞给求解器
做冷热电调度,最常踩的坑是天然地把非线性约束写进去。比如燃气轮机的发电效率随着负载率变化,有人会直接写一个二次函数进约束,结果求出来的解根本不可用。非线性约束意味着模型从 MILP 变成了 MINLP,Gurobi 和 CPLEX 直接罢工,你只能换更慢的全局求解器。
我的习惯是:所有设备效率在典型工作区间内取分段线性近似。分段点取 30%、60%、90% 负载率,用单位功率的线性关系表达,既守住精度又不破坏线性结构。至于充放电互斥,YALMIP 里虽然可以用 implies 函数,但转成求解器约束后往往会引入不必要的二进制变量和 bigM。我更推荐手动控制二进制变量,比如:
z = binvar(T,1); Pch = sdpvar(T,1); Pdis = sdpvar(T,1); Constraints = [Constraints, 0 <= Pch <= M*z]; Constraints = [Constraints, 0 <= Pdis <= M*(1-z)];这样做的好处是多个算例之间调试起来好复查,哪一行约束出了问题一眼就能找到。
4.2 上层调用的嵌套循环:求解器接口与变量传递
如果你选的是元启发式加内层求解,最容易翻车的就在 Matlab 软件环境下的变量作用域。很多新手会直接把上层 sdpvar 对象传进下层函数,然后在函数内部敲一个 optimize 完事。但这样跑上几轮就会发现,YALMIP 里残留下层旧变量,随着迭代轮数增加,内存和耗时都在膨胀。
正确做法是在每个内层循环开头加一句 yalmip('clear'),并且把上层决策变量的数值用 value 提取成 double,再传给下层函数。大部分使程序崩溃、报“无法对 double 类型应用 sdpvar 操作”的错误,都是因为没做类型转换。我自己的框架一般写成:
x_up = value(Pcap_value); % 先把 sdpvar 转成 double [follower_obj, y_opt] = solve_lower_problem(x_up);solve_lower_problem 内部可以放心地重新定义自己的变量和约束,返回来的 y_opt 再参与上层目标评估。这样模块边界非常清楚,后续加约束也很顺手。
4.3 初始点设置与求解器选项的“隐性坑”
用 KKT 单层化之后,整个模型变成 MILP,这看上去是成熟求解器的舒适区,但实际上 KKT 里那堆互补约束离散化之后,模型矩阵变得非常病态。我第一次跑通时,求解器报 infeasible,一开始还怀疑是约束写错了,后来才发现是数据量纲问题:储能容量用的是 kWh,能达到几千,而 SOC 递推式里的效率是 0.95,两者乘在一起形成了很大的数值跨度。
建议在写代码前先统一量纲,功率用 MW,容量用 MWh,成本单位用万元。不要小看这一步,数值缩放直接影响求解器的预求解质量和分支定界效率。求解器选项方面,我经常给 Gurobi 设 MIPGap 为 0.01、时间上限 600 秒,并把数值可行性容差保持为默认值,不要为了求快随便放宽。遇到真正的棘手算例,优先检查初始化逻辑和量纲,别急着改容差,否则会得到一个看起来不错但实际违反大量约束的解。
5. 一套可复现的算例设计:参数、结果与图表解读
5.1 算例数据准备:负荷曲线、能源价格、储能参数
只讲模型不讲算例等于白讲。这里我给出一套我自己用于调试的标准算例,数据全部为虚构,方便你拿来跑通代码之后再替换成自己项目的数据。
假设区域内有四个微网,分别记为微网1、微网2、微网3、微网4。微网1偏居民负荷,晚上电负荷高;微网2偏工业,白天电负荷和热负荷都很高;微网3偏商业,冷负荷集中在中下午;微网4是数据中心,电负荷全天平稳。各微网都配有光伏,规模从 80 kW 到 200 kW 不等。分时电价设置如下:峰段 08:00-11:00、13:00-16:00、18:00-21:00,电价 1.15 元/kWh;平段 06:00-08:00、11:00-13:00、16:00-18:00、21:00-23:00,电价 0.75 元/kWh;谷段 23:00-06:00,电价 0.37 元/kWh。天然气价格取 2.6 元/立方米,低热值按 9.7 kWh/立方米换算。
储能参数取功率成本 1200 元/kW、容量成本 1800 元/kWh,充放电效率 0.95,SOC 上限 0.9、下限 0.1,寿命 10 年,折现率 8%。服务费设置为充电 0.1 元/kWh、放电 0.2 元/kWh。注意这个价格本身也是上层优化可以动的变量,我这里先固定一个初值用来交代算例。
5.2 求解结果解读:储能容量、运行功率、成本对比
用上面的数据跑完 KKT 单层化模型,得到的结果很有意思。为了对比,我先跑了一个把所有微网交给同一个中心调度的单层集中式模型,再跑双层服务商模型。两个模型的总储能配置差别明显:
| 方案 | 储能总额定容量/kWh | 储能总额定功率/kW | 服务商年化净成本/万元 | 微网全年购电成本/万元 | 模型求解时间/s |
|---|---|---|---|---|---|
| 单层集中式 | 1480 | 480 | 28.6 | 152.3 | 8.2 |
| 储能服务双层模式 | 1060 | 350 | 18.4 | 168.8 | 46.5 |
单层集中式不是不好,它能全局统筹兼顾,把储能利用率拉到很高的水平,所以总储能额定值更大,购电成本更低。但服务商模式下,微网只在电价峰值高到一定程度才愿意用储能,同时服务费本身也是成本,这使微网对储能的需求变得更有弹性,最终上层给出的最优配置反而偏保守。
这个“保守”是合理的。在储能服务模式下,储能电站与微网没有隶属关系,微网完全可以根据对手电价和自身设备条件选择更便宜的替代方案。我特别建议把这个对比图做出来,因为它能帮你答辩时清清楚楚解释为什么双层结果不是“最优解不够好”,而是“决策结构本身不同”。
5.3 灵敏度分析/边界条件:储能服务电价、微网台数变化时模型怎么变
算例跑通之后,不分析边界条件等于没完工。我一般至少要做两组灵敏度分析。
第一组是服务价格对容量配置的影响。把充电服务费从 0.05 元/kWh 逐步涨到 0.2 元/kWh,可以看到上层模型给出的最优储能总容量在下降。原因很容易想通:服务费越高,微网使用储能的意愿越低,服务商预期收益也变差,自然不愿意多装。这时候你就能反过来调整服务费,找到闭环最优,而不是把价格当固定参数。
第二组是微网数量变化。从两个微网加到八个微网,KKT 单层模型里变量和约束数会快速膨胀,求解时间几乎成指数上升。我用元启发式迭代法在同一批数据上跑过对比,粒子群设置 60 个粒子、迭代 50 次,大约只多了两倍时间,但结果有 3% 左右的随机波动。所以如果你论文里要展示大规模案例,我建议直接用迭代分解法,前面那种 KKT 单层法更适合做机理分析和中小规模验证。
6. 双层优化配置做完之后,我复盘总结的几点体会
6.1 模型表达比算法求解更值得花时间
做这种双层配置,最终决定项目成败的往往不是求解器跑得有多快,而是最开始到底准不准确定义“上层决策什么、下层响应什么”。我见过不少人把上层目标写成社会总成本最小,下层也写成一个微网总成本最小,这样的主从关系就完全对不上。储能服务商的目标应该是自身利润与社会效益的权衡,而不是替社会做全局最优。先把这个角色定位说清楚,后面所有公式和代码才有讨论的根基。
另一个容易被忽略的表达细节是价格的决策位置。储能服务价格不是随便定的,它在双层模型里应该作为上层变量出现,通过微网的下层行为体现出市场反馈。如果把价格固定为常数,整个模型就会退化成普通的容量规划问题,失去了“服务”两字的核心价值。我建议在建模前先画一张决策变量与数据流向的关系草图,画清了再动手写 YALMIP。
6.2 仿真结果与工程落地的差异有多大
我必须坦诚说,这套模型跑出来的容量配置,直接拿去工程落地还是有距离的。仿真里的负荷曲线是确定性的,实际微网负荷总有预测误差;仿真里默认储能寿命固定不变,但频繁放电深度、环境温度都会影响实际衰减;仿真里也没有考虑微网与电站之间的通信延迟和博弈中的多轮议价。双层优化解决的是“决策结构”问题,不是“实时控制”问题。
工程上更合理的打法是:先用这篇提到的双层模型确定年度或季度级的容量与价格基线,再在运行层加一套滚动优化和实时反馈,把预测误差消掉。这样配置层和运行层各有分工,一楼管装什么、二楼管怎么开,模型才真正接上地气。
6.3 后续扩展思路:多时间尺度、随机性与动态博弈
如果在这个模型上继续往下做,我目前看到有三个方向比较有潜力。第一是把确定性负荷换成多场景随机规划,用 K-means 聚类出典型日场景,上层仍然做容量决策,下层对每个场景分别求运行成本,再按概率加权,这样容量结果对极端天气和负荷波动会更稳健。第二是考虑多时间尺度,比如把日前调度和日内滚动优化都嵌进去,做成三层递阶模型,不过规模会一下子大很多,需要谨慎设计求解策略。第三是引入动态博弈,储能电站不止一家,微网可以在多个服务商之间选择,这时候模型就变成多领导者多跟随者问题,KKT 那套方法会变得异常复杂,通常要借助变分不等式或演化博弈的思路去处理。
如果只是想把眼前这篇复现做到位,我的建议是别一上来就叠这些要素。先老老实实跑通两个微网、24 小时的小算例,再逐步加微网数量和灵敏度分析,你就会发现很多“论文里一句话”的坑,其实都在小场景里暴露得最彻底。我本人第一次跑 KKT 单层化时,卡了整整两天,最后发现只是储能容量单位写错了。做这种模型,耐心比聪明重要得多。