☰
多能源微网双层调度模型复现:从双层结构到滚动优化与Pyomo实现
2026/9/29 16:15:09 网站建设 项目流程

1. 先搞清楚这个模型到底在调度什么

去年年底我接手了一个复现任务:把一篇多能源微网双层调度模型的论文跑通。我原以为两天就能完事,结果前前后后折腾了三周,其中大部分时间不是在建模,而是在理顺双层结构、滚动窗口、预测误差这三者之间的关系。等我把这套东西完整落成 Pyomo + Gurobi 的工程之后才意识到,绝大多数复现卡壳,根本不是求解器的问题,而是没有先把模型的调度逻辑想清楚。

这篇是把我的完整复现思路、数学骨架、代码路径和踩坑记录全部摊开讲一遍。内容适配所有做微网调度、综合能源系统优化的同学,尤其是拿着论文但缺少代码、需要从零复现的人。

1.1 双层结构:谁在决策,谁在追随

双层调度里最容易被忽略的一点是:它不是两个独立的优化问题,而是有主从关系的博弈。

上层一般代表微网运营商或日前调度中心,它做的是长时间尺度的资源配置,比如决定燃气轮机明天几点启停、跟主网购多少电、储能什么时候充电。下层则代表日内运行层,它在上层给出的计划框架内,根据最新的光伏、风电、负荷预测数据做短时间尺度修正,决定每15分钟或5分钟各设备实际出力调整多少。

我习惯用一个生活化的类比:上层是公司年度预算,下层是部门在季度末根据实际情况调整采购计划。预算不能随便改,但具体怎么花,下面有自主权。上下层之间既不是完全集中,也不是完全分散,而是“计划先行、滚动修正”。

在建模型之前,我强烈建议先把主从关系画成一张决策层次表,明确每个阶段谁的变量是已知量、谁的变量是待求量。否则后面写KKT条件时很容易把上下层变量搞混,模型尺寸越堆越大,但实际可能在求解一个错得离谱的问题。

1.2 多时间尺度:为什么一个调度计划不够

很多刚接触微网优化的人会问:我直接建立一个24小时优化模型不就完了吗?为什么还要搞日内滚动、实时修正?

原因在于预测误差。光伏出力、风电出力、负荷需求都不可能24小时前预测得准。尤其是中午云层遮挡或者傍晚负荷快速爬升的场景,日前计划可能与实际相差15%以上。如果完全按照日前计划执行,要么造成弃风弃光,要么被迫高价购电,严重时还会导致功率失衡。

多时间尺度滚动优化的核心思想,就是“走一步看一步、再看几步”。比如当前时刻是09:00,优化窗口是未来4小时,我们基于最新预测算出未来16个15分钟调度间隔的最优出力,但只执行第一个间隔的实际指令。等到09:15,重新获取预测,窗口继续向前滚动,再次优化。这种方式让决策始终跟随最新信息更新,同时还能兼顾未来一段时间的趋势,不会变成完全短视。

我复现时采用的典型时间粒度是:日前调度为1小时一个时段,共24个时段;日内滚动优化为15分钟一个时段,窗口长度4小时,即16个时段;实时修正为5分钟一个时段,窗口30分钟。这样既保证了日前计划的经济性,又让日内响应具备足够的灵活性。

1.3 复现前先定义能源集线器边界

多能源微网之所以比单一微网复杂,在于它引入了电、热、冷、气多能耦合。同一个燃气轮机,发出来的电属于电力平衡,余热属于热力平衡,余热还能驱动吸收式制冷机提供冷量。如果不先把能源集线器的输入-转换-存储-输出边界梳理清楚,后面写约束时会反复出现“变量未定义”和“量纲不一致”的问题。

我复现时定义的微网结构包含了以下设备:

类型设备输出能源说明
输入主网购电电可双向或单向,本文按单向购电处理
输入天然气气供燃气轮机和燃气锅炉
可再生能源光伏、风电电波动性电源,可弃
热电联产燃气轮机 + 余热锅炉电、热电热比固定或可调
制热燃气锅炉、电锅炉、热泵热不同能效和成本
制冷电制冷、吸收式制冷冷吸收式利用余热
存储蓄电池、储热罐电、热充放电/充放热效率与SOC约束

这套结构是大多数多能源微网双层调度论文的标准配置。你把边界定义清楚后,才能把上层和下层决策变量对应到具体设备,避免之后每个时段有几十个含义不明的变量索引。

2. 双层调度模型的数学骨架与关键约束

整个模型我建议按三个层次写:上层日前规划、下层日内滚动、以及把上下层捆在一起的耦合约束。不要一上来就写几千行代码,先把数学骨架搭出来,代码只是骨架的翻译。

2.1 上层日前决策变量和目标

上层模型的时间尺度是24小时,步长1小时,所以下标 t = 1, 2, ..., 24。决策变量包括:日前购电功率 P_grid(t)、购气量 V_gas(t)、燃气轮机启停状态 u_gt(t)、各设备日前计划出力 P_i(t),以及储能的充放电计划 P_ch(t)、P_dis(t)。

上层目标函数是让全天总运行成本最小,典型表达式为:

F_up = Σ_t [ c_grid(t) * P_grid(t) + c_gas * V_gas(t) + C_om(t) + C_co2(t) ]

其中,c_grid(t)是分时电价,c_gas是天然气价格,C_om是设备运行维护成本,一般按出力的比例系数折算。C_co2是碳排放成本,如果原论文考虑低碳调度就加上,不考虑可以去掉。

上层还必须满足每个时段的电、热、冷平衡。我这里给出电平衡约束的典型写法:

P_grid(t) + P_pv(t) + P_wt(t) + P_gt_e(t) + P_dis(t) = P_load(t) + P_eb(t) + P_hp(t) + P_ec(t) + P_ch(t)

这个式子看着简单,实际很容易漏项。电锅炉、热泵、电制冷机都是耗电设备,必须放在用电侧;蓄电池充电是负荷,放电是电源。如果光记得发电机和光伏,忘了电制冷的耗电,热季的平衡会算得完全不对。

同理,热平衡需要同时考虑燃气轮机余热、燃气锅炉供热、电锅炉供热的供给侧,以及热负荷、吸收式制冷耗热、储热罐充放热的需求侧。冷平衡则相对简单,主要由电制冷和吸收式制冷供给,冷负荷需求。

2.2 下层日内滚动目标与修正机制

下层模型的时间步长是15分钟,滚动窗口长度是16个时段。在每个滚动优化触发时刻 k,下层需要求解未来16个时段的修正计划,但只执行第一个时段。

下层目标函数在日前计划的基础上增加了修正惩罚项,我采用的写法是:

F_down = Σ_{n=0}^{H-1} [ c_grid(k+n) * P_grid_down(k+n) + c_gas * V_gas_down(k+n) + λ_pen * (偏差量) + λ_cur * 弃风弃光量 ]

其中,H是滚动窗口长度,λ_pen是设备出力相对日前计划的偏离惩罚,λ_cur是弃风弃光惩罚。偏离惩罚非常重要,它让日内修正不会完全推翻日前计划,否则日前计划就形同虚设。

下层决策变量是各设备在日内时段的实际出力修正量。比如燃气轮机日内出力 P_gt_down(k) 等于日前基准 P_gt_up(t) 加修正项 ΔP_gt(k)。这样写的好处是,修正量可以设置上下限,避免设备在一个滚动周期内出现剧烈波动。

下层还需考虑储能SOC的动态递推:

SOC(k+1) = SOC(k) + η_ch * P_ch(k) - P_dis(k) / η_dis

η_ch和η_dis分别是充电、放电效率。滚动优化的每个窗口开始时,SOC是当前实际值,不是日前计划值。这一点是很多复现版本出错的重灾区,因为他们直接把日前SOC终值当作日内初始值,导致日内储能曲线完全失真。

2.3 上下层如何耦合:关键约束的衔接方式

双层模型如果上下层完全独立求解,那就退化成两个无关的优化问题。必须通过耦合约束把上层计划“锁”进下层模型。

我在复现中使用三类耦合约束。

第一类是机组启停状态耦合。上层决定的燃气轮机启停状态 u_gt(t),在日内滚动窗口内保持不变,日内只能调节出力水平,不能改变启停。这样既符合实际,也避免了双层整数变量带来的求解困难。

第二类是计划功率基准耦合。日内设备出力 P_down(k) 以对应时段上层计划出力 P_up(t) 为基准,加上一个可调偏差:

−δ_i ≤ P_down(k) − P_up(t) ≤ δ_i

δ_i是设备允许的最大调整范围。燃气轮机、燃气锅炉的δ可以取额定出力的10%~20%,储能和可中断负荷可以设大一些。这个约束把上层计划的权威性和下层修正的灵活性统一了起来。

第三类是储能SOC耦合。日前模型规划的SOC曲线不代表日内必须精确复现,但日内滚动结束时,SOC不能偏离日前计划终值太大,否则下一个周期的可调空间会失真。我通常加一个软约束,把SOC偏差惩罚放进下层目标,而不是硬性限制。

3. 滚动优化的核心机制与预测误差处理

滚动优化本质上就是模型预测控制在微网调度里的应用。它的核心价值在于,用最新的预测更新控制决策,同时通过窗口滚动保持对未来趋势的感知。

3.1 MPC式执行流程的实际代码逻辑

一个完整的滚动优化循环可以拆成六步:

  1. 读取昨日的日前调度结果,得到该时段的上层计划基准。
  2. 获取当前时刻实际的光伏、风电、负荷数据,以及未来窗口内的预测数据。
  3. 将预测数据、日前基准、当前SOC/储热状态传入日内滚动优化模型。
  4. 调用求解器求解窗口内所有时段的最优出力。
  5. 只保存并执行窗口第一个时段的结果,下发到设备。
  6. 等待下一个触发时刻,滚动到下一周期,重复步骤2到5。

我在代码里用了一个循环来控制触发时刻,每个触发时刻之间间隔15分钟。运行一天一共96个调度点。窗口长度16个时段意味着每次求解要同时优化未来4小时,计算量不算大,纯Python调用Gurobi单次求解大约在几百毫秒到几秒之间。

3.2 预测误差场景的构造方法

既然是滚动优化,就绕不开预测数据怎么来。完全复现时,如果没有实测预测系统,可以使用“真实曲线 + 误差扰动”的方法构造。

具体做法是:设定光伏、风电和负荷的真实出力曲线,然后为每个滚动触发时刻生成一组预测数据:

P_pred(k+n) = P_real(k+n) * (1 + ε(k+n))

ε(k+n)是一个零均值正态分布随机变量,标准差根据预测时间尺度设定。日前预测误差大,日内预测误差小。比如日前光伏预测标准差取15%,日内4小时预测标准差取6%,实时30分钟预测取2%。

这样构造预测误差场景,既接近论文的数值实验方式,又能方便地做灵敏度分析。如果你想研究更极端情况,还可以把正态扰动换成盒式不确定集,或者加入“云层遮挡”事件模拟,看看模型在不同鲁棒性要求下的表现。

3.3 多尺度衔接的工程细节

多尺度真正难的不是每个尺度单独求解,而是尺度之间怎么衔接。我复现时采用的衔接原则是:长尺度只定框架,短尺度只做修正。

日前调度给日内提供的是启停状态、购电计划基准和SOC日轨迹。日内滚动给实时修正提供的是当前设备工作点。实时修正则只调整储能出力和小型快速响应设备,比如电锅炉、蓄电池,尽量不动燃气轮机这类惯性大的设备。

这里有一个非常容易踩的坑:有些复现版本让日内滚动完全重新优化所有变量,结果日内结果和日前计划差异非常大,甚至出现燃气轮机日内启停与日前不一致的情况。这种模型虽然求解没问题,但已经失去了双层调度的意义。解决办法就是我前面提到的偏差上限约束和偏离惩罚,两者缺一不可。

4. 完全复现:从零搭建可运行代码的完整路径

下面这部分是操作重点。我默认你已经了解基本优化建模,但可能没写过完整的双层滚动调度程序。我尽量把每一步都讲清楚,保证照着搭能跑通。

4.1 复现环境与求解器选型

我使用的环境是Python 3.10 + Pyomo 6.x + Gurobi 10.x。选Pyomo而不是直接写Gurobi API,是因为Pyomo建模更接近数学表达式,后期修改约束和变量非常方便。Gurobi求解MILP和MIQP性能突出,对双层模型线性化后的大规模问题尤其合适。

如果你没有Gurobi授权,也可以换成COPT或MindOpt,两者对线性规划和大规模混合整数问题支持都很好。开源方案可以选择SCIP,但求解速度会明显慢一些,尤其在处理大M线性化产生的数千个二进制变量时,SCIP会比较吃力。

模型文件结构我推荐这样组织:

project/ ├── data/ │ ├── load_profile.csv │ ├── pv_profile.csv │ ├── wt_profile.csv │ ├── price_elec.csv │ └── device_params.csv ├── models/ │ ├── upper_model.py │ ├── lower_model.py │ └── coupling.py ├── solver/ │ ├── solve_upper.py │ ├── solve_lower_rolling.py │ └── kkt_linearization.py ├── results/ │ └── outputs/ └── main.py

这样分层的好处是,后续你想换设备类型、改窗口长度或者加新约束,不会牵连到其他模块。

4.2 输入数据与典型参数表

我在复现时使用了一个典型园区微网系统,设备容量和成本参数如下,你可以直接用于测试:

参数数值说明
光伏容量200 kW峰值出力
风电容量100 kW峰值出力
燃气轮机容量300 kW电功率,电热比 0.8
燃气锅炉容量500 kW热功率
电锅炉容量200 kW热功率
电制冷容量300 kWCOP=3.0
吸收式制冷容量200 kWCOP=0.8,利用余热
蓄电池容量500 kWh最大充放功率 100 kW,效率 0.95
储热罐容量200 kWh最大充放热功率 50 kW
购电上限500 kW与主网联络线功率上限
天然气价格3.2 元/m³折算成单位热值成本
上网分时电价峰1.2/平0.8/谷0.4 元/kWh按峰谷平统计

这些参数不需要完全照抄,但量级要合理。很多复现结果跑出来设备不出力、储能乱充放,问题往往出在成本参数失衡,比如天然气价格过低导致燃气轮机全天满发,储能完全没有存在价值。

4.3 双层模型求解:KKT条件与大M线性化

双层模型如果直接用启发式算法嵌套求解,速度慢且不稳定。我建议把下层LP的KKT条件写出来,把双层问题转化为带互补约束的单层问题。

对于下层连续线性规划,KKT条件包含三部分:

  1. 下层拉格朗日函数对各变量的偏导等于0
  2. 原始可行性约束
  3. 对偶可行性以及互补松弛条件

互补松弛条件的标准形式是:

λ_i * g_i(x) = 0

其中 λ_i 是约束对应的对偶变量,g_i(x) 是下层约束的不等式左侧。这种乘积等于0的非线性约束,可以用大M法线性化:

0 ≤ λ_i ≤ M * z_i

-M * (1 - z_i) ≤ g_i(x) ≤ M * z_i

z_i是额外引入的0-1变量,M是一个足够大的正数。注意,M不能取得过大,否则Gurobi数值稳定性会变差。我一般的做法是,根据每个约束的量级分别设置M,比如功率平衡相关约束M=1000,SOC约束M=500,避免全模型共用一个10^6的M。

如果下层模型包含0-1变量,KKT条件就不能直接用了,此时可以改用强对偶条件把下层的目标函数等价替换,或者用Benders分解。我在复现中为保持精度,让下层保持为纯线性规划,把启停等整数决策全部放到上层。

线性化后,整个问题变成一个混合整数线性规划,虽然规模变大了,但Gurobi求解稳定性远好于原来的非线性双层问题。实际求解时我使用的是Gurobi MIP Gap设为1e-4,单日滚动求解总耗时不超过两分钟。

4.4 滚动求解器结构与伪代码

下面这段伪代码是main.py的核心逻辑,基本描述了滚动优化的完整流程:

# 固定的日前计划数据 up_schedule = solve_upper_model(data) # 滚动优化循环 for k in range(0, 96, step): # 获取当前时段和未来窗口内的预测 pred_pv, pred_wt, pred_load = get_forecast(k, horizon=16) # 获取当前储能实际SOC soc_now = get_current_soc(k) # 重新构建日内下层模型 lower_model = build_lower_model( data, up_schedule, pred_pv, pred_wt, pred_load, soc_now ) # 求解 result = solve_milp(lower_model) # 只执行第一个控制步 apply_control(result, time_index=0) update_state(result, time_index=0)

这里有一个细节:每个滚动窗口中,日前基准值需要做时间对齐。因为日前步长是1小时,日内步长是15分钟,所以一个日前时段对应4个日内时段。代码里我专门写了一个函数把日前计划插值或重复映射到日内时间轴,避免索引错位。

5. 复现过程中最容易踩的坑

这部分是我真正花时间最多的地方。模型能不能“复现成功”和“复现结果可信”是两件完全不同的事,下面几个坑就是我亲身踩过的。

5.1 双层KKT线性化后求解器报数值问题

第一次跑通线性化后的模型时,Gurobi报了numerical trouble,一度让我怀疑KKT条件写错了。后来排查发现,问题出在大M取值上。我当时图省事,所有互补约束都用M=1e6,结果模型中同时存在数值很小的对偶变量和数值较大的功率变量,矩阵条件数变得很差。

解决办法就是把每个互补松弛约束的M单独设置。比如储能SOC约束涉及的功率量级是100 kW,M取500就足够;而联络线功率量级是500 kW,M取1000。设置原则是M约为约束左侧量级上限的2~3倍。

另外,Gurobi的NumericalFocus参数建议从0改成1或2,虽然会稍微增加求解时间,但能明显减少伪解。还有,尽量对所有连续变量设置合理上下界,不要依赖自身约束来限界。

5.2 日前计划在日内被“架空”

第二个坑比第一个更隐蔽。我去掉日内下层目标里的偏离惩罚,只保留耦合偏差约束时,发现日内优化结果几乎每次都顶在偏差边界上。表面看约束满足,实际运行成本可能比日前计划高出很多,储能行为也变得很奇怪。

原因是,偏差约束只是限制偏差范围,但没有让日内结果朝日前基准靠近。结果就是每个滚动窗口都会尽量选择边界值,误差在每个窗口内持续累积。加上了偏离惩罚,并且λ_pen设置成购电价格的0.5倍左右后,日内结果才会稳定地贴着日前基准小幅调整。

这个教训是:对于滚动优化,目标函数里的软惩罚有时比硬约束更重要。硬约束定义可行域,软惩罚决定决策行为。

5.3 冷热电联产的耦合约束遗漏

我一开始只写了电、热、冷各自的独立平衡,结果夏季场景下吸收式制冷利用率很低,燃气轮机余热全都散掉了。后来才发现,燃气轮机的余热产出和吸收式制冷的输入必须通过耦合约束连接起来。

正确的写法是:燃气轮机在产生电功率P_gt的同时,产生热功率Q_gt,且Q_gt与P_gt满足电热比关系:Q_gt = k_heat_ratio * P_gt。这部分余热可以被供热或者驱动吸收式制冷。吸收式制冷的消耗热功率 Q_abs 进入热平衡的负荷侧,同时它产生的冷功率 C_abs = COP_abs * Q_abs 进入冷平衡供给侧。

没有这条耦合,燃气轮机发电的余热价值就被白白丢掉了,优化自然会偏向用电锅炉和电制冷。这种错误在纯电气微网背景的同学身上特别常见,复现时一定要对照能源集线器结构逐一核对每条能流。

5.4 结果不合理时如何排查

我总结了一个高效的排查流程,遇到结果明显不合理时按顺序检查:

  1. 先看设备出力是否在容量范围内,如果长期顶上限,检查成本参数和负荷数据是否有量级错误。
  2. 再看储能SOC曲线是否连续,有无突然跳变。如果有,基本是SOC递推约束的时段索引写错了。
  3. 看日前与日内结果的偏差,如果偏差始终处于边界,检查偏离惩罚和耦合约束。
  4. 看冷热电平衡是否同时满足。把三个平衡约束的残差分别输出,能快速定位哪个能流漏项。

这套流程救了我好几次。很多时候模型求出的目标值很漂亮,但平衡约束有微小缺口,只是求解器容差把它们掩盖了。建议在求解完成后,一定要单独做一次约束残差校验。

6. 优化策略对比与典型结果

复现模型跑通之后,就可以在上面做优化策略研究。我会从三个维度展开:滚动窗口长度、预测误差水平、双层与单层模型对比。

6.1 滚动窗口长度的影响

我用同样的日前计划,分别测试了滚动窗口长度为1小时、2小时、4小时和8小时的情况。总运行成本和功率不平衡量的统计如下:

滚动窗口长度日内运行成本降低比例功率不平衡量
1小时4.2%较高
2小时7.8%中等
4小时9.6%较低
8小时9.8%最低

这里我的结论是,窗口超过4小时后成本下降幅度很小,但求解时间明显增加。原因是更远的预测准确率下降,对当前决策的优化价值减弱。所以4小时窗口对这套微网系统来说是性价比最优的选择。

6.2 预测误差灵敏度分析

保持滚动窗口为4小时不变,我把日前预测误差标准差从5%逐渐增加到30%,得到日内滚动优化的成本优势逐步缩小。预测误差为5%时,日内滚动相比完全固化日前计划能降低约10%成本;误差到30%时,优势降到3%左右。

这说明多时间尺度滚动优化的价值高度依赖于预测精度。如果你的预测系统很差,先把预测做好远比优化调度模型更有价值。很多复现实验只报告理想预测下的收益,这在工程上是偏乐观的。

6.3 双层与单层模型的差异

作为对照,我另外搭建了一个不考虑上下层主从关系、以全天总成本为单一目标的集中式模型。结果并不意外:集中式模型的理想成本比双层模型低5%~8%,但一旦遇到预测误差,集中式计划的执行偏差非常大,实际落地产出可能比双层模型还差。

双层模型的本质是用一部分经济最优性换取执行可靠性和稳定性。这个差异必须在论文或者技术报告里解释清楚,否则评审或领导会质疑“为什么你的模型成本比别人的高”。

6.4 求解器调试参数汇总

最后分享一组我在反复测试后觉得比较稳的求解器设置:

参数推荐值原因
MIPGap1e-4保证经济性比较的准确性
NumericFocus1抑制大M线性化带来的数值噪声
TimeLimit300秒防止个别极端场景求解超时
Threads8充分利用多核CPU
PreDual1改善对偶可行性

另外,在比较不同策略时,一定要固定随机种子。误差场景用同样的随机种子生成,否则实验结果的差异会被预测误差的随机性淹没,无法归因到调度策略本身。

7. 下一步我会怎么改这版模型

完全复现只是起点。如果你要在真实系统中落地,我个人认为最重要的改进方向是引入不确定性鲁棒优化或分布鲁棒优化,把预测误差的正态假设改成更保守的模糊集。其次,把可中断负荷、电动汽车充电桩这类柔性资源作为下层跟随者纳入模型,可以让滚动优化的调节空间更大。

如果算力足够,还可以把目前的单目标成本最小化扩展为成本+碳排放+用户舒适度的多目标优化,用帕累托前沿分析不同目标之间的权衡。不过,所有扩展都必须建立在当前这套可靠的双层骨架上。骨架不乱,扩展才有意义。我在这次复现里真正学到的一点是:调度模型的难点永远不在数学有多深,而在于是否能用工程手段把假设、约束和数据误差老老实实管理好。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询