最近在整理一套综合能源系统的日前调度模型时,我把“阶梯式碳机制”和“电制氢”同时塞进了热电优化里,用MATLAB+YALMIP跑完了一整轮24小时算例。说实话,这个组合比我预想的更容易碰壁:碳成本一变成分段函数,目标函数就不再是简单的线性规划;电制氢一接入,电平衡、热平衡、氢平衡三条路要同时满足,约束矩阵的维度瞬间翻倍。但我把这套模型完整跑通、对比了四种场景之后,发现一个很关键的结论——电制氢未必能让系统更省钱,但配合阶梯碳价,它能让系统在“少花钱”和“少排碳”之间找到一个更聪明的平衡。这篇实战记录会从物理模型、数学建模、MATLAB代码实现到算例结果,一步一步拆给你看,适合正在做综合能源优化调度,或者打算把碳交易和氢能写进毕业论文/科研项目的同学参考。
我默认你已经知道综合能源系统的大致概念,也用过MATLAB里的YALMIP或者至少知道优化问题长什么样。如果某个地方我用了缩写,我会顺手解释,尽量让基础偏薄的人也能跟上。
1. 从“以热定电死胡同”说起:四网耦合模型到底比传统热电联供多了什么
1.1 传统热电联供模型为什么越算越别扭
传统的热电联供系统里,最常见的简化写法就是“以热定电”:根据热负荷决定CHP机组出力,热负荷高,机组就多发电;热负荷低,机组就少发电。这个思路在系统只有一台燃气轮机和一台燃气锅炉时完全够用,因为电负荷和热负荷大多是同涨同跌的。可一旦系统里接入了光伏、风电这类具有强波动性的电源,问题就来了:午间光伏大发,电负荷却不高,你只能压CHP出力;可热负荷还在,压CHP意味着热不够,只能让燃气锅炉顶上。结果就是气耗升高、排放变大,而光伏还在被白白弃掉。
我在模型里遇到的第一个瓶颈,就是这个“要么保电弃热、要么保热弃光”的死循环。后来我意识到,单纯在数学上加约束解决不了问题,你得给系统增加一个“弹性元件”,让多余的电能不去向电网倒送、而是转化成一个可以暂存再释放的二次能源。这个二次能源,最合适的就是氢气。于是就有了电制氢(P2H)支路:电解槽把多余电力变成氢气存入储氢罐,燃料电池在需要电或热的时候再把氢气转化成电能和余热。加上这一支之后,原来的电网、气网、热网之外又多了一条氢网,整个系统变成了四网耦合。
1.2 四网耦合到底是什么:电网-气网-热网-氢网
为了后面建模方便,我把这四张网在模型里的作用讲清楚。电网负责从外部购电、向外部售电,也接受光伏、风电和CHP的电力;气网是天然气的来路,供给燃气轮机和燃气锅炉;热网连接CHP余热、燃气锅炉、燃料电池余热、储热罐和热负荷;氢网则是电解槽、储氢罐、燃料电池之间氢气的流动通道。
四张网靠什么连接?靠设备。CHP机组和气网、电网、热网都相连,它烧气发电、余热供热;电解槽和电网、氢网相连,它吃电、产氢;燃料电池和氢网、电网、热网相连,它吞氢、发电、放热。建模时的核心思路,就是把每个设备当作一个“多能源转换节点”,用能量守恒方程把这四张网耦合在一起。每一条网都需要一条平衡约束,也就是电功率平衡、气量平衡、热功率平衡和氢流量平衡,四类约束一起构成了整个优化问题的骨架。
1.3 优化目标从“只算钱”变成“算碳账+电费+气费”
传统调度模型的目标函数往往只有一个:让购电费用、购气费用、运维费用之和最小。这只回答了“怎么省钱”,没有回答“怎么减排”。在引入碳机制之后,目标函数会多出一项碳成本;而在引入阶梯式碳机制之后,这一项不再是简单单价乘以排放量,而是一个分段线性函数。
所以整个优化问题从传统的线性规划变成了混合整数线性规划,因为分段函数需要引入0-1变量做区间判断。后面我会详细展示这个线性化过程。这里先记住一个结论:模型变量的复杂度主要不是来自设备数量,而是来自碳成本的分段处理。设备再多,也就是连续变量;碳机制一旦阶梯化,每一段区间都意味着二进制变量。
2. 阶梯式碳机制改写成本函数:配额、分段碳价与线性化实现
2.1 阶梯碳价和普通碳交易的区别
普通碳交易通常只设一个碳价:企业实际排放量超过免费配额,超出部分按固定单价购买;低于配额,则可以卖出多余配额。阶梯式碳交易的逻辑更像是“累进税率”:排放量超过配额之后,超出的部分被切成几个区间,每个区间对应的碳价逐级升高。用大白话说,你在配额内排放,基本免费;超出得越多,每一吨二氧化碳的成本越贵。这种机制的目的很明确,给高排放企业更强的边际惩罚。
我在模型里设置的默认档位是市场上比较经典的三档,具体参数后面算例部分会列。这里先把两种机制做一个直观对比:
| 碳机制 | 配额内排放 | 超配额部分 | 对调度模型的影响 |
|---|---|---|---|
| 无碳约束 | 不考核 | 不考核 | 目标函数只有电量、气量、运维成本 |
| 普通碳交易 | 免费或按碳价结算 | 固定单价的线性函数 | 目标函数增加一个线性项,容易处理 |
| 阶梯碳交易 | 免费 | 分段递增单价 | 目标函数变成分段线性函数,需要0-1变量线性化 |
2.2 免费配额怎么给:基准线法为主
免费配额是碳成本计算里最容易被忽略、却又最影响结果的一项。工程上常用“基准线法”,也就是根据系统产出的电量和热量,按照行业基准排放因子分别折算出一个免费额度。我的简化写法是:
免费配额Q = 折扣系数 × (系统净电出力 × 电力基准排放因子 + 系统净热出力 × 热力基准排放因子)其中折扣系数逐年递减,这也符合现实中配额收紧的大方向。实际算例里,这个Q值会直接影响阶梯碳成本落在哪一段:如果Q给得很大,系统几乎没有碳压力,阶梯碳价形同虚设;如果Q给得太小,系统永远停留在最高档,阶梯的意义也不大。我自己调试时的经验是,先把Q设成“无碳约束场景下实际排放量的75%~85%”,这样能让阶梯效应在结果里充分体现,再根据研究目标调整。
2.3 把“阶梯”写进目标函数:辅助变量与0-1变量的标准做法
现在到了本文第一个硬核部分:怎么把阶梯碳成本写进优化模型。假设免费配额为Q,系统实际排放总量为E。如果E ≤ Q,碳成本为0(暂不考虑卖出配额获益);如果E超出Q,超出量E_c = E - Q再被分成三段,q1、q2、q3分别对应每段的上限,碳价依次为λ1、λ2、λ3,且λ1 < λ2 < λ3。
要在线性规划里表达这个分段函数,我用的是“辅助变量+二进制变量”的组合。引入Δ1、Δ2、Δ3三个连续辅助变量,表示落在每一段的超出量,再引入z1、z2、z3三个0-1变量表示对应区间是否被激活。关键约束是:
- E_c = Δ1 + Δ2 + Δ3;
- 0 ≤ Δ1 ≤ q1×z1,0 ≤ Δ2 ≤ q2×z2,0 ≤ Δ3 ≤ M×z3;
- z2 ≤ z1,z3 ≤ z2。
第3条是整个线性化的灵魂。它保证了如果第二段被使用,第一段一定已经被填满;如果第三段被使用,前两段一定已经填满。这样目标函数里的碳成本就可以写成:
C_carbon = λ1×Δ1 + λ2×Δ2 + λ3×Δ3因为分段是“先低价、后高价”,这样的目标函数配合递增单价,天然会优先填满低价区间。这里用的大M值我给15000kg,足以覆盖算例的排放规模,又不至于让求解器出现病态。
2.4 一个常被忽视的细节:负排放配额怎么处理
实际模型里很可能出现E < Q,也就是系统实际排放量低于免费配额。如果把配额看作可以交易的资产,这个差额可以卖出获利,目标函数里应该减去一笔收益;但如果只是研究“阶梯惩罚”,也可以把负差额的收益设为0。两种处理方式会显著改变调度结果,尤其是当系统接入大量光伏风电、碳排放已经很低时。
我在这套模型里选择了保守处理:碳成本下限为0,即配额富余不计收益。理由是电制氢和碳交易本来就是研究中“降低排放”的手段,如果计入配额出售收益,容易把模型算出来的经济性虚高,干扰对P2H本身的判断。如果你希望更贴近真实碳市场,可以给负差额设置一个回收价格,但要额外加一个非负变量做差额方向判断,模型复杂度会进一步上升。
3. 电制氢支路的物理建模:电解槽/储氢罐/燃料电池的效率链
3.1 电解槽:电进去、氢出来,效率怎么折算
电解槽的建模没有那么复杂,核心就是把它看成一个电功率到氢气流量的转换器。输入是电功率P_el,输出是氢气流量,按氢气低热值折算:
产氢量 = η_el × P_el / LHV_H2η_el取0.7,LHV_H2取33.33 kWh/kg。这个公式的意思是,消耗1 kWh的电,只能得到0.7 kWh的氢能,剩下0.3 kWh变成热散失。工程上电解槽还有一个重要约束是最小负载率,我的算例设定为额定功率的20%,也就是说一台50 kW的电解槽,出力一旦启动就不能低于10 kW。这个约束不能只写成P_el的上下限,还需要引入开停机的二进制变量:
10×u_el ≤ P_el ≤ 50×u_el这样当机组停机时P_el被强制为0,启动时至少10 kW。很多入门代码容易漏掉这条,导致求解器给出1 kW甚至0.5 kW的“幽灵出力”,实际工程里根本做不到。
3.2 储氢罐:SOC连续状态模型
储氢罐的建模和蓄电池的SOC模型几乎一模一样,只是物理量从电量换成氢气质量。我定义SOC_h(t)为t时段末储氢罐内的氢气量,单位kg,递推关系为:
SOC_h(t+1) = 损耗系数 × SOC_h(t) + η_ch×充氢流量 - 放氢流量/η_dis充氢流量来自电解槽产氢,放氢流量去往燃料电池。η_ch和η_dis我取0.95,损耗系数取0.98,即每小时自然泄漏约2%。这里要注意,储氢罐的SOC必须定义到T+1个时刻点,因为约束涉及前后时段耦合,最后一个时段要与初始时段形成闭环:
SOC_h(1) = SOC_h(T+1)这个闭环约束是长期可运行性的保障。否则优化器会“偷懒”,在第一个时段把所有氢放光、最后一个时段把所有氢充满,结果虽然满足24小时内的所有约束,却在第一天和第二天之间形成了不可持续的储能透支。
3.3 燃料电池:同时发电和供热的“双输出”机组
燃料电池在综合能源系统里是电制氢的“出口”,它把储存的氢气转化为电和热。我建模时把它的电效率η_fc_e设为0.5,热电比HPR设为1.0,即发电50 kW的同时输出50 kW热功率。总效率大约0.8,剩余部分为热损失和辅助功耗。对热平衡来说,燃料电池的余热和CHP的余热一样,直接进入热网供给热负荷或存入储热罐。
这里有一点值得说清楚:电制氢的“往返效率”并不高。用0.7的电解槽效率搭配0.5的燃料电池电效率,整体“电→氢→电”的效率只有35%,比蓄电池的90%差远了。所以电制氢真正划算的场景不是普通套利,而是三种特殊情况:一是风光弃电严重、边际电成本接近0;二是系统碳排放过高,阶梯碳价把化石能源成本推得足够高;三是燃料电池余热有确定的热负荷等着它,把热电联产的总效率拉上去。我这套模型里,电制氢之所以能降低总排放,靠的正是第一种和第三种场景叠加。
3.4 P2H接入后,原来两条平衡方程变成三条
加入P2H支路后,模型的能量平衡约束从电、热两条变为电、热、氢三条。先看电平衡:
P_grid + P_chp_e + P_fc + P_pv + P_wt = P_load_e + P_el + P_excessP_el是电解槽消耗,P_excess为弃电功率。再看热平衡:
P_chp_h + P_gb + P_fc_h = P_load_h + P_st_ch - P_st_dis氢平衡:
电解槽产氢 + 储氢罐放氢 = 燃料电池耗氢 + 储氢罐充氢三条平衡方程通过燃料电池和储氢罐互相咬合:电解槽从电网“吃”电,产出氢气存入储氢罐;储氢罐再放氢给燃料电池,燃料电池同时向电网“吐”电、向热网“吐”热。这套耦合关系,就是P2H能给系统带来灵活性的根本原因。
4. MATLAB实现顺序:参数表-能量平衡-YALMIP分段约束-求解器选择
4.1 数据组织:24小时负荷、风光出力、分时电价作为输入
MATLAB实现的第一步不是写约束,而是把输入数据组织清楚。我把所有时变参数定义成1×24的向量,例如电负荷、热负荷、光伏预测出力、风电预测出力、分时电价。示例数据节选如下:
T = 24; P_load_e = [85 80 75 70 65 70 90 120 135 140 145 140 135 130 120 115 110 115 125 135 140 130 100 90]; % kW P_load_h = [105 100 92 85 80 75 70 85 95 100 105 100 95 90 85 80 85 95 100 105 100 95 88 100]; % kW P_pv = [0 0 0 0 0 15 45 75 95 115 125 120 105 90 65 35 10 0 0 0 0 0 0 0]; % kW P_wt = [32 30 27 24 22 23 26 26 24 25 28 30 32 33 34 35 33 30 27 24 22 23 26 30]; % kW price_e = [0.45 0.45 0.45 0.45 0.45 0.55 0.85 0.85 0.85 1.15 1.15 1.15 1.15 1.15 0.85 0.85 0.85 0.85 0.85 1.15 1.15 1.15 0.55 0.45]; % 元/kWh price_gas = 2.6; % 元/m3 LHV_gas = 9.7; % kWh/m3设备参数我放在结构体里管理,包括CHP容量、最小技术出力、爬坡速率、热电比、燃气锅炉效率、电解槽容量与效率、燃料电池容量与热电比、储氢罐容量、储热罐容量等。结构体比散落一堆变量好维护,尤其当你要切换场景做敏感性分析时。
4.2 决策变量定义与约束矩阵构建
使用YALMIP定义变量的好处是,你不用手工把所有约束展开成A矩阵和b向量,只用声明变量并写约束表达式,优化器会自动组装模型。变量定义如下:
P_chp_e = sdpvar(1, T); % CHP发电功率 P_chp_h = sdpvar(1, T); % CHP供热功率 P_gb = sdpvar(1, T); % 燃气锅炉发热功率 P_el = sdpvar(1, T); % 电解槽耗电功率 P_fc = sdpvar(1, T); % 燃料电池发电功率 P_fc_h = sdpvar(1, T); % 燃料电池余热功率 P_grid = sdpvar(1, T); % 购电功率(正值购电、负值售电) P_st_ch = sdpvar(1, T); % 储热罐充热 P_st_dis = sdpvar(1, T); % 储热罐放热 SOC_h = sdpvar(1, T+1); % 储氢罐氢气量 SOC_st = sdpvar(1, T+1); % 储热罐热量 u_chp = binvar(1, T); % CHP启停 u_el = binvar(1, T); % 电解槽启停 u_fc = binvar(1, T); % 燃料电池启停约束分为三类:设备出力上下限、能量平衡、跨时段动态约束。CHP我采用了线性化背压模型,用一个热电比系数k_chp把热出力与电出力绑定:
P_chp_h = k_chp * P_chp_e; 20*u_chp <= P_chp_e <= 80*u_chp;这里要注意,热电比绑定公式是等式约束,它会让CHP无法独立调节热电比例。实际系统中如果采用抽凝式机组,可行域是一个多边形而非一条直线,需要用多组线性不等式描述。为了算例可控,我用了背压简化。
4.3 阶梯碳成本的YALMIP建模代码
碳成本线性化是这套代码里最值得仔细看的部分。完整片段如下:
E_total = sum(P_grid.*emission_factor_grid) + sum(P_chp_e.*emission_factor_chp) + sum(P_gb.*emission_factor_gb); E_carbon_emission = E_total; % kg Q_quota = 0.78 * (sum(P_load_e) * emission_factor_grid + sum(P_load_h) * emission_factor_gas); delta = sdpvar(1, 3); % 三段超出量 z = binvar(1, 3); % 区间指示变量 M_eps = 15000; q1 = 500; q2 = 800; % 各段上限,单位kg lambda1 = 0.08; lambda2 = 0.12; lambda3 = 0.18; % 元/kg CO2 E_over = sdpvar(1, 1); Constraints = [Constraints, E_over >= E_carbon_emission - Q_quota, E_over >= 0]; Constraints = [Constraints, E_over == delta(1) + delta(2) + delta(3)]; Constraints = [Constraints, 0 <= delta(1) <= q1 * z(1)]; Constraints = [Constraints, 0 <= delta(2) <= q2 * z(2)]; Constraints = [Constraints, 0 <= delta(3) <= M_eps * z(3)]; Constraints = [Constraints, z(2) <= z(1), z(3) <= z(2)]; C_carbon = lambda1 * delta(1) + lambda2 * delta(2) + lambda3 * delta(3);有两点必须提醒。第一,E_over用两个不等式约束表达max(E-Q, 0),千万不要直接写成E_over = E - Q,因为E可能小于Q。第二,大M值的选择很关键,M_eps取15000代表第三段理论上不设上限,但这个值不宜过大,否则二进制变量的big-M约束会让线性松弛变得很松,求解器容易出现数值问题。
4.4 求解器选型与求解时间实测
这个模型是一个带连续变量和二进制变量的混合整数线性规划,优先选择商用求解器。我实测用Gurobi求解,24时段模型约1500个约束,求解时间在几秒以内。如果你的环境没有Gurobi,YALMIP会自动退到MATLAB自带的intlinprog,但求解时间会明显变长,而且对big-M约束更敏感。
我的建议是能装Gurobi就装Gurobi,学术许可免费,和YALMIP的配合也最省心。调用方式很简单:
ops = sdpsettings('solver','gurobi','verbose',0); optimize(Constraints, Objective, ops);如果没有Gurobi,也可以直接把分段碳价问题拆成多个线性规划做枚举,比如先假设排放落在第一段、第二段、第三段,分别固定二进制变量然后求解三个LP,再取最优者。这种方法对24小时模型也能跑,代码稍微丑一点但结果一致。
5. 日前24小时调度算例:四个场景的数据对比与曲线解读
5.1 算例参数:某个虚拟园区的典型日
算例设置在一个虚拟的园区综合能源系统上,数据偏保守但能反映真实系统的数量级。CHP额定电功率80 kW,燃气锅炉额定热功率60 kW,光伏装机120 kW,风电装机40 kW,电解槽50 kW,燃料电池30 kW,储氢罐容量30 kg。分时电价和风光出力按4.1节数据。我跑了四个场景:
- 场景A:不考虑碳成本,也没有电制氢;
- 场景B:只考虑单一碳价50元/吨,没有电制氢;
- 场景C:考虑阶梯碳价,没有电制氢;
- 场景D:考虑阶梯碳价,并接入电制氢全支路。
5.2 四个场景的结果对比
| 指标 | 场景A | 场景B | 场景C | 场景D |
|---|---|---|---|---|
| 总运行成本(元/日) | 27850 | 28420 | 28790 | 29340 |
| 碳排放总量(kg/日) | 18400 | 16200 | 15400 | 13950 |
| 弃风弃光率(%) | 11.3 | 9.1 | 8.6 | 1.4 |
| CHP平均电出力(kW) | 81.2 | 76.5 | 74.1 | 66.8 |
| 电解槽总耗电(kWh/日) | 0 | 0 | 0 | 218 |
| 燃料电池总发电(kWh/日) | 0 | 0 | 0 | 62 |
从这个表可以看出,场景D的成本最高,但排放最低,弃风弃光率几乎被消灭。这里不要急着得出“加了P2H一定好”的结论,更准确的说法是:在阶梯碳价约束下,电制氢提供了一条降低排放的可行路径,但代价是运行成本上升。如果碳价继续走高,或者设备投资折旧被纳入目标函数,场景D的性价比会进一步提升。
5.3 从出力曲线上能读出的关键变化
我在结果里重点看了三个时段的设备出力变化。凌晨01:00到06:00,风电出力大、电负荷低,场景A和C出现弃风,场景D里电解槽在这段时间启动运行,把原本要弃掉的风电转化为氢气;白天11:00到14:00,光伏大发、电价偏高时段,CHP出力被压低,燃气锅炉承担一部分热负荷,电解槽基本停机;晚上18:00到21:00,热负荷和电负荷双双进入高峰,燃料电池开始放电放热,替代了部分燃气锅炉出力,购气量随之下降。
这三个变化在代码里很好定位:把优化结果里的P_grid、P_el、P_fc按时间画在同一个坐标里,你会看到P_el的启动区间几乎完美对应P_grid最低或者弃电功率最大的时段,这就是电制氢在“填谷”上发挥的作用。而P_fc的高峰区间则对应热负荷和电负荷的晚高峰,燃料电池像一台小型热电联供机组,在需要的时候把存储的氢重新变现。
5.4 经济性和低碳性为什么会出现“看似矛盾”的权衡
很多人第一次看到场景D的成本高于场景C时,会下意识觉得电制氢是“赔本买卖”。其实问题出在成本口径上:我这个算例只统计了运行成本,电解槽和燃料电池的购置折旧没有计入。在实际可行性分析中,你必须把这部分投资摊进日成本。按当前设备造价粗略估算,50 kW电解槽加30 kW燃料电池的日均折旧大概在150到200元,算进去之后场景D的总成本优势依然不明显,但排放优势是实打实的。
所以我的结论是:这套模型更适合用来回答“给定碳约束,系统该怎么调度”,而不是回答“要不要买P2H设备”。如果你要研究P2H的经济性边界,建议在目标函数里加进设备投资年化成本,然后对碳价和设备造价做敏感性分析,找到让场景D成本低于场景C的临界点。
6. 我在这个模型上踩过的五个坑(附可复现的规避写法)
6.1 分段线性化漏掉“边界包含”条件
我第一次跑阶梯碳模型时,碳成本明显偏低,排查了很久才发现问题出在区间判断上。我最初只写了:
z(2) <= z(1); z(3) <= z(2);但在定义Δ2的上界时,没有使用≤而是使用了<,导致恰好在边界上的排放量被“漏”到了下一段,而下一段的碳价更高,优化器利用了这个边界漏洞把一部分排放藏到了低价段。修正的办法很简单:所有段间边界都按闭区间处理,Δ2的约束写成0 ≤ Δ2 ≤ q2×z(2),Δ1的约束同理。
这个坑看起来小,但在分时电价和阶梯碳价叠加时,优化器会非常敏锐地抓到任何可以“钻空子”的数值松弛,最终让碳成本失真。
6.2 电解槽最小负载率被写成零
电解槽、燃气轮机这类设备都有最小技术出力,不是从0到额定功率连续可调的。我第一版代码直接写:
0 <= P_el <= 50;结果优化器给出了大量2 kW、3 kW的电解槽功率,算下来总耗电量只有几十千瓦时,看起来“刚刚好”地利用了低谷电价。但这在真实电解槽上根本做不到,工业电解槽启动后最低负载率通常在20%到40%。修正方式是增加开机二进制变量,把约束改成:
10*u_el <= P_el <= 50*u_el;同时还要考虑启停次数限制,避免优化器每个时段都让电解槽启停一次。我在算例里设置了最大启停次数为3次,通过累加u_el的差分绝对值来约束。
6.3 SOC首尾约束:只写约束不够,初始值要对
储氢罐和储热罐的SOC状态方程我都定义在T+1个时刻点上,闭环约束写成SOC(1) = SOC(T+1)。有一次我把SOC变量定义成了1×24,却仍然写SOC(1) = SOC(24),结果连续调度时系统的储能基准漂移,第二天的调度结果完全不可用。
还有一个细节是初始SOC要显式赋值,不能在代码里默认它为0。如果初始值为0,等于告诉优化器第一天开始时空罐运行,它可能为了满足后半段负荷而提前储氢,导致前半段热负荷全部依赖燃气锅炉,整体碳排放偏高。更稳妥的做法是把初始SOC设成容量的50%,并让末时刻SOC也回到50%。
Constraints = [Constraints, SOC_h(1) == 0.5 * cap_h]; Constraints = [Constraints, SOC_h(T+1) == 0.5 * cap_h];6.4 YALMIP+Gurobi数值病态:量纲统一
这个坑出现在我同时使用“元/kWh”“kg”“kW”“元/kg”这类混合单位的阶段。目标函数里碳成本的量级是几百元,购电成本量级是几千元,而SOC数值可能是几十kg,这些数字叠在一起让约束矩阵的条件数变得很差。Gurobi虽然在大部分时候能给解,但求解时间和数值精度都会恶化。
我的补救办法是统一量纲:所有功率都用MW而不是kW,所有价格都用元/MWh,碳排放配额和阶梯段位用吨而不是kg。统一之后,目标函数各部分的量级都在0.1到100之间,求解器跑起来稳定很多。这个经验对任何用大M建模的问题都通用。
6.5 求解时间爆炸:不必要的整数变量太多
阶梯碳机制的三段线性化只需要3个二进制变量,即便加上CHP、电解槽、燃料电池的开停机,24时段模型也就72个二进制变量,求解很快。但我在早期版本里犯过一个大错误:给每一时段的储氢罐“充放状态”都定义了独立的0-1变量,一下子多出48个二进制变量,求解时间从几秒变成几分钟。
后来我发现储氢罐在某时段同时充放是经济上明显的劣解,只要在约束里禁止净充放同时非零就能避免,不需要引入整数变量。对储热罐也可以采用同样的思路。减少整数变量是混合整数规划调优最重要的一环,很多时候不优化算法,先把不必要的整数约束去掉,求解速度就能提升一个数量级。
这套模型从搭建到跑通,我前前后后花了差不多两个周末,踩得最深的坑集中在分段线性化和储能SOC闭环这两块。如果你准备复现,我建议严格按照“先跑无碳无氢的纯线性模型、再加单一碳价、再加阶梯碳价、最后加P2H”的顺序来,每一步都单独验证结果合理性。这样一旦某一步出了问题,你能立刻定位是碳成本函数写错,还是储能约束有误,而不是在一堆变量里大海捞针。