简介:面向电-气-热综合能源系统(IES)研究的MATLAB代码包,围绕电、气、热三网耦合建模与优化调度的核心问题,完整实现了从电力、燃气、热力子系统独立建模到多能流耦合、供需平衡分析,再到基于遗传算法、粒子群等智能算法的优化调度求解。压缩包内共28个文件,以20个.m脚本为主体,覆盖各网络模型、耦合设备及调度策略;辅以TXT说明、Excel数据表、docx/xlsx技术报告及README.md,便于理解方法脉络、跟踪算法流程并复现算例。整包大小约5.13MB,内含完整的综合能源调度工程项目及配套数据,系统展示了多能源协同调度的典型场景。适合能源系统工程、电力系统优化方向的科研人员、研究生和工程师参考与二次开发。目前已有151人学习,代码结构清晰、可读性好,读者既能对照案例快速验证调度结果,也可修改目标函数与约束条件,进一步拓展至节能评估、碳排放分析等研究;以多能互补为主线,兼顾经济性与环保性,能够有效支撑综合能源系统调度领域的教学与科研工作。
1. 电-气-热综合能源系统耦合调度:为什么三张网要放进同一个优化模型
接手一个项目包,文件名是“023电-气-热综合能源系统耦合调度、优化调度.zip”,核心要解决的问题往往是同一个:电、气、热三张网络平时各算各的账——电网按电价调机组,气网按合同配气,热网按供热曲线烧锅炉,结果就是弃风时段电锅炉没开足,气价尖峰时段热电联产机组却在满发。电-气-热综合能源系统的耦合调度,就是把这三个子系统的运行约束写进同一个优化模型,让决策变量互相制约、互相补偿;优化调度则负责回答“未来24小时每台机组出多少力、气网购多少气、储热罐什么时候充放”。这篇笔记会拆开讲耦合建模、求解器实现和落地时最容易翻车的几个地方。适合正在做多能系统优化的研究生和园区能源调度工程师照着复现。
2. 耦合在哪:电-气-热三网的能量流与设备层建模
要把三张网拧成一个优化问题,第一步不是写目标函数,而是先把“能量从哪张网流进哪张网”画清楚。电-气-热综合能源系统里,真正让三张网发生耦合的设备就四类:热电联产机组(CHP)、燃气锅炉、电锅炉、电转气(P2G)。其中CHP和燃气锅炉是天然气网向电网和热网单向供能,电锅炉是电网向热网单向供能,P2G则反过来把电网的电变成天然气注入气网。理解这几条能量流,后面的约束建模才不会漏项。
2.1 燃气机组与CHP:电气耦合的核心设备
CHP是耦合调度的枢纽。燃气轮机的排烟余热通过余热锅炉回收,同时输出电和热,所以它的电出力与热出力天然绑定:背压式机组的热电比基本固定,抽凝式机组可以在一定区间内调节。建模时最常用的是固定热电比模型:
[ H_{\text{chp},t} = R_{\text{chp}} \cdot P_{\text{chp},t} ]
其中R为热电比,取值范围通常落在1.1到1.8之间。这个看似简单的关系式,实际上是电-气耦合调度里最容易“打架”的地方——电网希望CHP在电价高时多发电,热网却希望它稳定供热,当电出力和热出力按固定比例绑死时,优化器只能同时满足两个系统的约束,结果就是调度计划不再是“电网最优”或“热网最优”,而是折中后的系统最优。
除了热电比,CHP建模还有两个参数不能漏:最小技术出力和爬坡率。燃气轮机的最小电出力一般在30%到50%额定容量,低于这个值机组会进入燃烧不稳定区间,工程上不允许长时间运行;爬坡率则决定了机组在相邻调度时段之间的出力变化上限,忽略它的话,优化器会给出“上一小时零出力、下一小时满发”这种无法执行的方案。实际操作中,我一般会用下面的方式把CHP约束写进模型:
# CHP 电出力上下限 m.p_chp = pyo.Var(m.T, bounds=(20, 80)) # kW,最小技术出力20,额定80 # CHP 热出力与电出力绑定,热电比1.5 m.chp_couple = pyo.Constraint(m.T, rule=lambda m, t: m.h_chp[t] == 1.5 * m.p_chp[t]) # 爬坡约束:相邻时段电出力变化不超过15 kW m.chp_ramp = pyo.Constraint(m.T, rule=lambda m, t: m.p_chp[t] - m.p_chp[t-1] <= 15)这里有一个需要提醒的细节:抽凝式CHP的热电比并不是常数,而是一个运行区间。如果你的项目包用的是抽凝式机组,建议把固定热电比约束替换成两个线性不等式:(P_{\text{chp}} \geq a \cdot H_{\text{chp}} + b) 和 (P_{\text{chp}} \leq c \cdot H_{\text{chp}} + d),这样可行域会更接近机组真实工作区间,调度结果不会在热负荷高峰时给出“电出力很小但热出力很大”的造假方案。
2.2 P2G与电锅炉:电-气和电-热逆向耦合
P2G(Power to Gas)是电-气耦合里最有意思的逆向支路。电解槽把水电解成氢气和氧气,氢气再与二氧化碳反应生成甲烷,注入天然气网。整体效率通常在40%到60%之间,远低于其他转换设备,但它的价值在于把电网无法消纳的弃风弃光转化为气网燃料,变相实现了长时储能。模型上可以用一个线性关系近似:
[ G_{\text{p2g},t} = \frac{\eta_{\text{p2g}} \cdot P_{\text{p2g},t} \cdot 3.6}{H_{\text{gas}}} ]
其中(H_{\text{gas}})是天然气热值,取35.8 MJ/m³,(\eta_{\text{p2g}})取0.55,那么相当于每消耗1 kW电大约产出0.055 m³/h的天然气。你拿到项目包后,可以根据数据文件里的效率曲线替换这个常数,但要注意效率不是恒定的,电解槽在20%负荷以下效率会明显衰减,如果调度结果频繁出现P2G低负荷运行,需要加一个最低投运功率约束,比如小于30%额定功率时不允许运行。
电锅炉则是电-热耦合里响应最快的设备。电热效率可达95%以上,从零到满负荷只需几分钟,很适合用来平抑电网侧的短时波动。它的模型比P2G简单得多:(H_{\text{eb},t} = \eta_{\text{eb}} \cdot P_{\text{eb},t}),唯一要留意的是电锅炉的功率上限通常受配电容量限制,不是你模型里想取多大就取多大。
把P2G和电锅炉放在一起对比,参数差异很直观:
| 设备 | 转换方向 | 典型效率 | 响应时间 | 调度中的角色 |
|---|---|---|---|---|
| CHP | 气→电+热 | 总效率75%-90% | 分钟级 | 基荷与热源 |
| 燃气锅炉 | 气→热 | 85%-95% | 分钟级 | 补热源 |
| 电锅炉 | 电→热 | 90%-98% | 秒级 | 快速调峰 |
| P2G | 电→气 | 40%-60% | 分钟级 | 弃风消纳/储能 |
2.3 热力网络动态:为什么热负荷不能当普通电负荷处理
很多人在第一版模型里把热网当成“电一样的瞬时平衡网络”,这是耦合调度里最隐蔽的坑。热力网络与电网有一个本质差别:热水在管道里的流速只有1到3 m/s,热能从热源送到末端用户需要几十分钟甚至几小时,而且管道本身、建筑围护结构都有热惯性,相当于一个天然储热罐。如果用稳态模型忽略这些动态特性,优化器会认为“热源产热=用户用热必须瞬时平衡”,导致调度计划在冬季早晚高峰时段严重偏离实际。
我在工程上常用的处理方式是准动态建模:把热网的温度场简化成供回水温度两个状态量,管道延迟用时间常数(\tau)近似。每个时段的热负荷平衡写成:
[ H_{\text{source},t} = H_{\text{load},t-\tau} + C_{\text{pipe}} \cdot \frac{\mathrm{d} T}{\mathrm{d} t} ]
(\tau)根据管道长度和水流速估算,一般取2到5个调度时段;(C_{\text{pipe}})是管道热容。这样建模后,热源可以在用电高峰时段适当少产热、靠管道储热和建筑热惯性撑着,等电价低谷再补热。三张网的时间常数差异也决定了调度粒度不能统一拍脑袋:
| 网络 | 典型时间常数 | 调度粒度建议 |
|---|---|---|
| 电网 | 秒级 | 5-15分钟 |
| 气网 | 分钟级至小时级 | 15分钟-1小时 |
| 热网 | 30分钟-数小时 | 1小时 |
所以项目里如果只给了一套24时段、每时段1小时的数据,大概率是按热网粒度折中设计的。不要自作主张把时间粒度改成15分钟,除非你自己把气网和热网的动态参数重新标定一遍。
3. 优化调度模型怎么搭:目标函数、约束与决策变量
耦合建模的框架搭好之后,第二个重头戏是把调度问题写成数学优化模型。这一步决定了后面求解器能求出什么、求解快不快,也决定了调度结果能不能落地。常见做法是建立混合整数线性规划(MILP)模型:决策变量包括各设备的电出力、热出力、购电购气量、储热罐的充放功率和SOC(荷电状态),以及机组启停等0-1变量;约束覆盖网络平衡、设备运行区间、爬坡、储热动态等;目标函数则根据你要“经济最优”还是“碳-能协同”来定权重。
3.1 目标函数:从单一购电成本到碳-能-经济协同
很多入门版调度模型只写购电成本加购气成本,这在纯电力系统调度里够用,但在电-气-热综合能源系统里会得出一个偏颇的结论:为了省电费,优化器会不惜代价多用气,甚至让CHP在电价低谷也满发,完全不管碳排放和弃风惩罚。实际项目中更常用的目标函数是下面这个形式:
[ \min \sum_{t=1}^{T}\left( c_t^{\mathrm{e}} P_t^{\mathrm{buy}} + c^{\mathrm{g}} G_t^{\mathrm{buy}} + c^{\mathrm{CO}2} E_t + \lambda_t^{\mathrm{curt}} P_t^{\mathrm{curt}} + c^{\mathrm{om}} \sum{i} P_{i,t} \right) ]
其中各项分别是购电成本、购气成本、碳排放成本、弃风弃光惩罚成本和设备运行维护成本。权重怎么设,直接决定调度方案长什么样:
- 碳价取30到60元/吨时,P2G的消纳弃风价值开始体现,因为它把本要丢弃的绿电转成气、替代了部分天然气消耗;
- 弃风惩罚系数必须高于同期购电价,否则优化器宁可弃风也不让电锅炉或P2G去消纳;
- 运维成本按设备类型差异化设置,P2G的电解槽维护成本通常是电锅炉的3到5倍,不加这一项,优化器会让你在不需要的时候也开着P2G。
还有一个容易被忽略的点:热力舒适度损失成本。供热温度允许在一定范围波动时,可以把供热不足造成的舒适度损失按单位热负荷缺失折算成经济惩罚项写进目标函数。这样调度方案在极端寒潮时段会自动选择“少量减供”而不是“让CHP超负荷硬扛”。
3.2 约束体系:网络潮流、设备爬坡与热力管道储热
约束体系是调度模型里真正决定模型规模的“大头”。电网、气网、热网各自的网络约束加起来,最多能占到整个模型约束数量的80%。按工程上最常见的做法,它们被分成三层:
第一层是电网约束。配电网级别的调度模型不需要交流潮流,用直流潮流近似就够了:节点功率平衡、线路潮流上限、主网购电上限。关键是每个节点的功率平衡要同时包含所有耦合设备,比如某个节点上接了一台CHP和一个电锅炉,那该节点的净注入功率就得写成(P_{\text{chp}} - P_{\text{eb}} - P_{\text{load}})。
第二层是气网约束。气网的节点流量平衡跟电网类似,但管道流量和压力之间是非线性关系,严格来说是Weymouth方程:
[ f_{ij} \cdot |f_{ij}| = K_{ij}^2 (\pi_i^2 - \pi_j^2) ]
这个方程是非凸的,直接放进MILP模型没法求解,实际中通常做分段线性近似。气源井有最大日供气量约束,储气库有注入和采出速率约束,这些都需要单独建模。气网约束里最容易被漏掉的是管存(linepack)——管道里本身存有一定量的天然气,在高峰时段可以释放出来缓冲供需差,建模时可以把管存当成一个随时间变化的虚拟储气罐。
第三层是热网约束。热网平衡不像电网那么“硬”,因为热水的温度允许在一个舒适区间内波动。变量包括各换热站的供回水温度、管道热损失、储热罐的充放热功率。储热罐动态约束是热网建模的核心:
[ \text{SOC}{t+1} = \text{SOC}t + H{\text{chg},t} \cdot \eta{\text{chg}} - H_{\text{dis},t} / \eta_{\text{dis}} ]
注意充放热效率不对称,一般充热效率0.95左右,放热效率0.97左右。SOC初值要给定,末值一般要求不小于初值,否则调度方案会“寅吃卯粮”,把储热罐里的能量在调度周期内用光。
设备约束也值得单列一档。除了第2章提到的出力上下限和爬坡,还要补充最小启停时间约束。燃气机组从停机到启动需要几小时的暖机时间,热电机组更是如此,如果不加最小启停时间约束,优化器会给出“频繁启停”的省钱方案,实际根本执行不了。下表是约束与物理意义的对照,写模型时可以逐条核对有没有漏:
| 约束类型 | 物理意义 | 常见建模方式 |
|---|---|---|
| 电网节点功率平衡 | 电能不能凭空产生或消失 | 线性等式 |
| 气网节点流量平衡 | 气量进出守恒 | 线性等式 |
| 管道压力-流量关系 | 气网传输能力限制 | 分段线性近似 |
| 热网节点温度混合 | 供回水温度决定热功率 | 线性方程 |
| 设备出力上下限 | 设备物理边界 | 变量边界 |
| 爬坡与启停 | 运行连续性 | 线性不等式 + 0-1变量 |
| 储热罐SOC | 储能动态 | 差分方程 |
3.3 线性化与松弛:把非线性模型改造成MILP
前两节的内容凑齐后,模型大概率含有非线性项(Weymouth方程、热网温度乘积项),而Gurobi和CBC这类成熟求解器擅长处理的是MILP。所以建模阶段就要做线性化,这一步做得好不好,直接决定求解速度。
Weymouth方程线性化,我用得最多的是增量线性化(incremental linearization):先把压力平方(\pi^2)当作一个变量,再把管道流量与压力平方差的关系曲线用折线逼近。分段数取3到5段足够,再多只会增加变量数和求解时间,精度提升却很有限。气压节点压力约束两边都要加:压力低了气供不进去,压力高了管道可能爆管。
另一个常见非线性项是热网的温度与流量乘积。由于热功率等于比热容乘质量流量乘温差,当流量也是变量时,两个变量相乘就成了双线性项。大多数项目里热网流量变化不大,我会直接把它简化成固定流量的线性模型;实在需要变流量,就把温差当作决策变量,流量按典型日曲线固定。
Big-M法在耦合调度里也频繁用到,比如“P2G低于30%负荷不允许运行”这类条件约束就要引入0-1变量。M取值有个血泪经验:取物理上限的1.2到1.5倍,不要图省事直接写10000。M值过大会让线性规划松弛解严重偏离整数解,求解器要花大量时间在分支定界上,甚至出现数值奇异。一个合理的M值设置我一般这么写:
# 假设 p_p2g 的物理上限是60 kW,30%投运阈值为18 kW # M 取物理上限的1.3倍,约78 m.y_p2g = pyo.Var(m.T, within=pyo.Binary) # 1表示投运,0表示停机 m.p_p2g_lower_limit = pyo.Constraint(m.T, rule=lambda m, t: m.p_p2g[t] >= 18 - 78 * (1 - m.y_p2g[t])) m.p_p2g_upper_bind = pyo.Constraint(m.T, rule=lambda m, t: m.p_p2g[t] <= 78 * m.y_p2g[t])线性化之后,模型就是标准的MILP,求解器的选择就有谱了。商业求解器Gurobi在耦合调度这种中等规模问题上表现最好,几万到几十万变量都能在分钟级收敛;开源求解器SCIP也支持MILP但速度慢一个量级;CBC则适合教学和小规模案例验证。第4章会用Pyomo + Gurobi跑一个最小案例。
4. 求解器与实现:用Pyomo跑通一个24小时耦合调度小案例
理论模型说完,这一章给一套可以直接照抄的最小实现。我用的是Python + Pyomo,求解器用Gurobi;没有Gurobi license的话可以把SolverFactory换成glpk或cbc,模型不变,只是求解会慢一些。案例按“3节点电网 + 3节点气网 + 单节点热网”的小园设计,突出耦合设备,网络约束做最简化处理。拿到你的项目包后,把数据文件里的字段替换进来即可。
4.1 案例设置:小规模园区多能系统
对象是一个假设的工业园区:电网从上级网购电,气网从上级气源购气,园区内有一台1.5 MW级背压式CHP、一台燃气锅炉、一台电锅炉、一套P2G装置和一个储热罐。调度周期24小时,时间粒度1小时。设备参数如下表(示意值,实际以zip包内data.xlsx为准):
| 设备 | 出力下限(kW) | 出力上限(kW) | 效率/热电比 | 爬坡限值(kW/h) |
|---|---|---|---|---|
| CHP电出力 | 20 | 80 | 热电比1.5 | 15 |
| 燃气锅炉 | 0 | 50 | 效率0.88 | 20 |
| 电锅炉 | 0 | 120 | 效率0.95 | 40 |
| P2G耗电 | 0 | 60 | 效率0.55 | 15 |
| 储热罐 | - | 80 kWh | 充/放效率0.95/0.97 | 40 |
电负荷和热负荷取一组典型的冬日上午曲线:电负荷峰出现在晚间18:00,热负荷峰出现在早晨7:00。电价采用分时电价,谷段(23:00-6:00)0.38元/kWh,峰段(10:00-15:00、18:00-21:00)0.95元/kWh,气价固定2.8元/m³。这个参数组合下,CHP在峰电时段会优先满发,电锅炉和P2G会在谷电时段抢电,储热罐则是两者之间的“润滑剂”。
4.2 核心代码:Pyomo建模与Gurobi求解
下面这段代码是我在多个园区多能调度原型里反复用的一版骨架。先建模型和约束,再求解输出。代码里的数据我直接内联,方便你复制后立刻跑通;真实项目里从zip包内的CSV或Excel读取即可。
# 电-气-热耦合调度最小示例:24时段 MILP # 依赖:pyomo + gurobi(或 glpk / cbc) import pyomo.environ as pyo # ---------- 示意数据(实际项目从 zip 内数据文件读入) ---------- T = list(range(24)) elec_load = [110, 105, 100, 95, 90, 92, 98, 120, 135, 150, 165, 170, 175, 168, 160, 155, 165, 180, 190, 185, 172, 160, 145, 120] # kW heat_load = [180, 175, 170, 168, 165, 160, 155, 150, 145, 140, 138, 135, 140, 145, 150, 155, 160, 165, 170, 175, 180, 185, 190, 185] # kW elec_price = [0.45, 0.45, 0.40, 0.38, 0.35, 0.38, 0.50, 0.72, 0.85, 0.92, 0.95, 0.88, 0.80, 0.82, 0.86, 0.90, 0.95, 0.98, 0.92, 0.85, 0.72, 0.60, 0.50, 0.45] # 元/kWh gas_price = 2.8 # 元/m3 HV = 35.8 # 天然气热值 MJ/m3 model = pyo.ConcreteModel() model.T = pyo.Set(initialize=T) # 连续决策变量 model.p_chp = pyo.Var(model.T, bounds=(20, 80)) # CHP电功率 kW model.h_chp = pyo.Var(model.T, bounds=(30, 120)) # CHP热功率 kW model.g_chp = pyo.Var(model.T, bounds=(0, 30)) # CHP耗气 m3/h model.p_eb = pyo.Var(model.T, bounds=(0, 120)) # 电锅炉耗电 kW model.h_eb = pyo.Var(model.T, bounds=(0, 110)) # 电锅炉热功率 kW model.p_p2g = pyo.Var(model.T, bounds=(0, 60)) # P2G耗电 kW model.g_p2g = pyo.Var(model.T, bounds=(0, 20)) # P2G产气 m3/h model.g_buy = pyo.Var(model.T, bounds=(0, 50)) # 网购气 m3/h model.p_buy = pyo.Var(model.T, bounds=(0, 200)) # 网购电 kW model.soc = pyo.Var(model.T, bounds=(0, 80)) # 储热罐SOC kWh model.h_dis = pyo.Var(model.T, bounds=(0, 40)) # 储热放热 kW model.h_chg = pyo.Var(model.T, bounds=(0, 40)) # 储热充热 kW # ---------- 耦合与设备约束 ---------- # CHP热电比固定为1.5 model.chp_couple = pyo.Constraint(model.T, rule=lambda m, t: m.h_chp[t] == 1.5 * m.p_chp[t]) # CHP耗气量:输入能量 = (电+热)/0.85,再按热值折算为 m3/h def chp_gas(m, t): input_mj = (m.p_chp[t] + m.h_chp[t]) * 3.6 / 0.85 # MJ/h return m.g_chp[t] == input_mj / HV model.chp_gas_eq = pyo.Constraint(model.T, rule=chp_gas) # 电锅炉:电转热效率0.95 model.eb_couple = pyo.Constraint(model.T, rule=lambda m, t: m.h_eb[t] == 0.95 * m.p_eb[t]) # P2G:电转气,效率0.55 def p2g_gas(m, t): return m.g_p2g[t] == 0.55 * m.p_p2g[t] * 3.6 / HV model.p2g_gas_eq = pyo.Constraint(model.T, rule=p2g_gas) # ---------- 能量平衡约束 ---------- # 电网节点平衡:CHP出力+购电 = 电负荷+电锅炉耗电+P2G耗电 model.ele_balance = pyo.Constraint(model.T, rule=lambda m, t: m.p_chp[t] + m.p_buy[t] == elec_load[t] + m.p_eb[t] + m.p_p2g[t]) # 热网节点平衡:CHP热+电锅炉热+储热放热 = 热负荷+储热充热 model.heat_balance = pyo.Constraint(model.T, rule=lambda m, t: m.h_chp[t] + m.h_eb[t] + m.h_dis[t] == heat_load[t] + m.h_chg[t]) # 气网节点平衡:购气+P2G产气 = CHP耗气(燃气锅炉本例暂不投入) model.gas_balance = pyo.Constraint(model.T, rule=lambda m, t: m.g_buy[t] + m.g_p2g[t] == m.g_chp[t]) # 储热罐SOC动态 def soc_rule(m, t): if t == 0: return m.soc[t] == 20 + m.h_chg[t] * 0.95 - m.h_dis[t] / 0.97 return m.soc[t] == m.soc[t-1] + m.h_chg[t] * 0.95 - m.h_dis[t] / 0.97 model.soc_dyn = pyo.Constraint(model.T, rule=soc_rule) # ---------- 目标函数:购电成本 + 购气成本 ---------- model.obj = pyo.Objective( expr=sum(elec_price[t] * model.p_buy[t] for t in model.T) + sum(gas_price * model.g_buy[t] for t in model.T), sense=pyo.minimize) # ---------- 求解与输出 ---------- opt = pyo.SolverFactory('gurobi') # 无gurobi时改为'glpk'或'cbc' results = opt.solve(model, tee=False) for t in [0, 7, 12, 18, 23]: print(f"t={t}: CHP电 {model.p_chp[t]():.1f} kW | " f"CHP热 {model.h_chp[t]():.1f} kW | " f"电锅炉耗电 {model.p_eb[t]():.1f} kW | " f"P2G耗电 {model.p_p2g[t]():.1f} kW | " f"储热SOC {model.soc[t]():.1f} kWh")代码的逻辑说明分三层。第一层是设备耦合约束,CHP电出力和热出力被热电比绑死,耗气量通过总输入能量和热值反算;电锅炉和P2G同理。第二层是能量平衡约束,三个等式分别对应电网、热网、气网的节点守恒,这是“耦合”最核心的体现——同一个时段里,CHP多发的电直接影响电锅炉和P2G能分到多少电,而P2G产的气又反过来减少气网购气量。第三层是储热罐SOC差分方程,充热按95%计,放热按97%计。
几个参数要特别说明:CHP耗气量公式里除以0.85是假设机组综合效率85%,如果你用的机型效率不同,换掉这个常数即可;储热罐SOC初值20 kWh是人为给定的,实际项目里取前一天末时段的SOC;求解放到model.p_chp[t]()时,后面的括号是Pyomo取变量值的写法,漏了它打印出来会是变量对象而不是数值。这个小案例跑通后,目标值应该在1800到2400元之间,如果差得太多,先查单位。
4.3 参数调节:求解时长、相对间隙与时间粒度
模型能跑出结果只是第一步,结果可不可信、能不能在实际调度中落地,取决于几个关键参数有没有设对。第一个是MIP Gap。Gurobi默认的MIP Gap是1e-4,这个精度对小案例无所谓,但对几百个时段的真实项目会让求解时间成倍增加。我一般先设opt.options['mipgap'] = 0.01跑一版,确认目标值和方案合理性,再按需要收紧到0.001。第二个是时间粒度。第2章讲过,热网时间常数最大,如果项目里热网管道很长,1小时粒度可能失真,建议把管道分段细化或用准动态模型。第三个是求解时间上限。生产环境里调度计划必须在滚动前出结果,所以一般会设置opt.options['timelimit'] = 300,也就是5分钟没收敛就取当前最优可行解。
| 参数 | 影响 | 推荐设置 |
|---|---|---|
| MIP Gap | 求解时间 vs 最优性 | 调试0.01,生产0.001 |
| 时间粒度 | 热网动态精度 | 1小时起步,热网复杂时改30min |
| 求解时间上限 | 调度时效性 | 300s-600s |
| 储热SOC末值 | 防止“能耗尽” | soc[23] >= soc[0] |
第四行SOC末值约束是很多人会漏的。如果模型只跑24小时,不约束末值SOC,优化器会在最后几个时段拼命放热、让SOC掉到下限,把明天的热量提前用光。正确做法是把末值约束为不小于初值(即SOC[23] >= 20),或者把调度周期拉长到48小时、只取前24小时的结果作为正式方案,后面这种滚动方式在工程上更稳妥。
5. 耦合调度必踩的坑:5条血泪排查清单
模型从“能跑”到“结果可信”之间,隔着一条布满坑的路。下面这5条现象是我自己在电-气-热耦合调度项目里真实遇到过、并且反复被后来人问到的,按“现象→原因→解决”写清楚,排错时可以直接按图索骥。
5.1 模型一跑就报不可行,调哪个约束都没用
现象:求解器刚启动就输出Model is infeasible,连第一步迭代都走不下去。新手往往会去检查边界条件,把设备出力上下限放宽,但问题依然存在。
原因:最常见的是热网侧初值自相矛盾。比如储热罐SOC初值设了20 kWh,但第一个时段的充热功率下界默认是0、放热功率下界也是0,而热负荷平衡方程要求放热+CHP热出力必须等于热负荷,如果CHP热出力还被电负荷和热电比卡死,第一个时段的热平衡就无解。气网侧也常有类似情况,气源压力下限设成2.0 MPa,但管道流量方程算出来要2.4 MPa才能推动所需气量。这类问题本质是“初始状态+设备出力区间+平衡方程”三者交集为空。
解决:先把网络和储热约束全部注释掉,只保留设备和能量平衡约束跑一遍,确认设备模型本身有可行解;然后逐步加回储热动态、气网压力和电网潮流约束,每加一组就跑一次,用二分法定位是哪组约束把可行域“夹”没了。对储热罐初值,把SOC[0]从固定值改成可变变量并给定一个较小的上下界,让求解器自己找合适起点,通常能快速诊断。
5.2 求解器报告Optimal,但把结果代回气网校验时压力越限
现象:Gurobi输出Optimal solution found (gap 0.00%),结果很漂亮,但你把这组调度结果再喂给气网潮流计算程序时,发现某个节点压力逼近甚至超过管道上限,调度方案根本执行不了。
原因:这是耦合调度里最典型的“模型与物理脱节”。优化模型里的气网用的是稳态Weymouth方程,而实际气网在负荷快速变化时,管道内的气体压力存在动态缓冲(管存效应)和传输延迟,稳态方程忽略了这些过程。尤其是热负荷和CHP出力在早晚高峰跳变时,气网节点流量的瞬时变化比稳态计算剧烈得多,于是优化器认为“可行”的工作点,在动态过程中已经越限。
解决:工程上常用两种做法。一是给压力上下限留安全裕量,比如标准要求管压上限4.0 MPa,模型里按3.6到4.0 MPa作为硬边界,留出10%动态波动空间;二是在优化模型里增加管存变量,把管道当成一个虚拟储气罐建模,缓解瞬态压力冲击。如果你的项目包里有完整的气网拓扑数据,建议做一次离线动态仿真,把优化结果的时序流量喂进去,校验压力轨迹,这一步不能省。
5.3 MILP求解时间爆炸,Gap降到3%就跑不动了
现象:模型规模不大,几千个变量、几千条约束,但求解时间从几分钟膨胀到几小时,MIP Gap停在3%到5%再往下降不动。反复加求解器参数也没用。
原因:大概率是Big-M取值过大、或者引入了多余的整数变量。比如把M直接设成10000,哪怕变量物理上限只有80,也会让线性松弛的可行域严重偏离整数解,分支定界要遍历大量无意义的节点。另一种常见情况是热网时滞用了一堆0-1变量去建模“温度到达时间”,完全没有必要。
解决:把模型中所有M值统一检查一遍,原则上按对应变量物理上限的1.2到1.5倍取值。热网时滞用整数时间延迟(即把管道延迟固定为2个或3个时段)代替连续时间变量。还有一个经验:调试阶段把MIP Gap放宽到0.05,先用5%的牺牲换一个可行解,确认方案整体方向正确后,再收紧到0.001。这样比一开始就死磕0.1%省下大量时间。
5.4 储热罐整个调度周期里从没有充放动作,一直停在初值附近
现象:优化结果里储热罐SOC恒等于初值,充热功率和放热功率全为0。你明明在目标函数里没有给它设置任何使用障碍,但它就是不动作。
原因:这不是bug,而是建模不完备导致的经济信号缺失。如果电价不分时、热价恒定,储热罐“谷段充热、峰段放热”的套利空间为零;再叠加充放热效率损耗(充0.95、放0.97),储热罐每动作一次反而白白损失约8%的能量。追求成本最优的优化器当然选择完全不用它。另一个配合原因可能是热负荷弹性没有被货币化,储热罐削峰填谷带来的“保障供热安全”价值没有进入目标函数。
解决:把分时电价联动逻辑检查清楚——真实项目中储热罐的商业价值恰恰来自电价差;如果战略上就是要求储热罐参与调节,可以给它设最小日充放次数约束,或者把“储热罐SOC大小”作为约束加入目标函数,比如对SOC偏离目标区间进行惩罚。但在加上这些“人为意愿”之前,先想清楚:如果物理和经济上没有理由让它动,不动才是最优解。
5.5 总成本比手算大了一个数量级,结果明显不合理
现象:调度结果出来的总成本是手算估算值的8到10倍,决策变量看起来也没什么异常,但成本就是降不下来。
原因:单位换算翻车。这是多能系统联算特有的坑:天然气热值的单位是MJ/m³,电功率单位是kW,两者相乘前必须统一量纲。1 kW等于3.6 MJ/h,燃气锅炉产1 kW热需要的耗气量是(1 / (\eta \cdot 35.8 / 3.6)) m³/h,大约0.106 m³/h。如果代码里直接写g == (1 - eta) * h / HV,算出来的耗气量会大3.6倍。P2G的产气量换算同样容易犯这个错,电转气效率0.55,每kW电产气约0.055 m³/h,写成0.55就大了10倍。
解决:在数据加载模块里做一个量纲自检函数,打印所有关键变量的单位转换系数。每从数据文件读取一个参数,就按物理公式反推一遍单位:功率统一kW,气量统一m³/h,热量统一kW或MJ/h,能量统一kWh或MJ。把自检函数作为模型启动的第一道工序,任何项目的成本量级异常都能在几秒内暴露。量纲对了,成本数据才谈得上“可信”。
6. 调度结果的可信度验证:滚动回验、灵敏度与数据自检
模型跑完、坑也排完,最后一道工序是验证“这个优化结果换一天的数据还能不能成立”。耦合调度方案不能只在历史数据上好用,必须经得住边界条件变化。
6.1 结果可信度检查清单
拿到一组调度结果,我习惯先做几分钟人工检查,而不是直接看目标值。逐项核对能量平衡:每个时段电、热、气的进和出是否严格相等;设备出力是否全在上下限内且没有跨越爬坡约束;储热罐SOC是否满足末值要求。再把典型时段抽出来做物理直觉判断——比如电价尖峰时段CHP是否满发、谷电时段电锅炉是否全开,如果答案是否定的,要么数据有问题,要么约束里藏了隐含限制。
6.2 滚动时域回验:让调度方案对抗扰动
静态跑一次24小时优化,只能证明“在完美预测下可行”,而真实工况的负荷预测误差通常在5%到15%。工程上更可信的验证方式是滚动时域(MPC)回验:把第一天的最优方案固定执行,只滚动更新后几小时的预测数据并重新优化,然后看总成本和约束满足度与离线方案差多少。两者差距小于5%,说明方案鲁棒;差距超过15%,就要回头检查预测数据是否被模型“针对”了。
6.3 灵敏度分析:确认边界条件在哪
最后做一组灵敏度分析。把气价调高20%、电价调低20%、热负荷整体上移10%,分别记录目标函数值和各设备出力的变化。如果气价上涨10%导致P2G从停运变成满发,说明模型正处在经济拐点附近,这类边界信息要直接写进调度决策报告。我自己在这类项目上交过一笔学费:花了两周调模型,最后发现是热网入口温度的数据采集断点,导致热负荷曲线整体偏移5%,所有校验全部失效。从那以后,我拿到任何项目的zip包,第一件事永远是校验数据字段和量纲,而不是急着跑求解器。希望帮到你。
本文还有配套的精品资源,点击获取