☰
风光互补制氢合成氨系统容量-调度优化:MILP建模与Cplex实现
2026/10/6 8:59:10 网站建设 项目流程

1. 先把复现对象拆开看:风光互补制氢合成氨系统的容量-调度优化

看到这个标题,我第一反应是:这又是一个把“容量规划”和“小时级运行调度”绑在一起做的混合整数线性规划问题。风光互补制氢合成氨,本质上是一条“可再生能源发电→电解水制氢→储氢→合成氨”的长链条,而“并/离网”则决定了这条链和外部电网之间到底能不能有功率交换。用 Cplex 求解,说明大概率是把它建模成了 MILP,需要通过整数变量处理启停、时段互斥等逻辑。

这类复现最容易出现的问题是:模型还没搭好就急着调求解器,结果 Cplex 报 infeasible,一头雾水。所以我想先把系统边界和模型逻辑讲清楚,再给出一条可以直接照着跑的 Matlab 实现路线。无论你是做论文复现、课题预研,还是刚接触能源系统优化,这篇文章的重点都是帮助你理解“为什么这么建模、Cplex 到底在解什么”,而不是只给你一串能跑的代码。

先说系统组成。风电和光伏提供主要电力,电解槽把水变成氢,储氢罐解决发电和用氢之间时间不匹配的问题,合成氨装置消耗氢气,和空分系统来的氮气一起生成氨。所谓容量优化,是决定风、光、电解槽、储氢罐、合成氨装置各装多大;所谓调度优化,是在给定容量下,决定每个时刻哪些设备出力、启停、充放氢、买卖电。标题里的“并_离网”,意思就是这套系统既可以接在电网上运行,也可以完全孤岛运行,两种模式下约束和成本结构完全不同。

1.1 并网与离网:差别不在“多一根线”,而在调度自由度

离网模式的硬约束非常直接:任意时刻,风电加光伏的出力,必须等于电解槽耗电、合成氨装置耗电、辅助系统耗电和弃电之和。电网不存在,所以不能买电也不能卖电,超出需求的电只能丢弃,缺电时氢气产量或合成氨产量就得往下压。这意味着离网模型里,容量配置的“冗余度”一定偏大,因为风光出力天然波动,你要么多装风机光伏保证低出力日的供氢量,要么多装储氢罐把富余时段的氢存下来。

并网模式的本质改变是增加了“购电”和“售电”两个自由度。缺电时可以买电,富余时可以卖电,甚至可以利用峰谷电价差,把电解槽和合成氨装置的运行时段挪到低电价时段。但并网也不是免费的:你要为变电容量、并网接口付出成本,而且大多数论文里会加“同一时段不能同时买电和卖电”的约束,否则模型会自动做低买高卖的无风险套利,产氢量全部变成零,只给电网发电,这显然不符合原意。

1.2 容量和调度为什么要一起优化,而不是分两步

很多入门者在复现时习惯先固定一组容量,再去做经济调度,跑完再手动调整容量。这样做在一两个典型日下能蒙对方向,但一旦加入储氢罐的动态约束,容量和调度就彻底耦合了。举个简单例子:储氢罐装小了,为什么会影响光伏装机?因为白天光伏大发时氢装不下,晚上没光时电解槽又没电可用,整个系统被“卡”在时序约束里,光伏再多也发挥不了作用。电解槽装大了,为什么可能反而推高弃电率?因为低出力时段电解槽吃不饱,但又不能频繁启停,强制最低负荷会让系统更僵硬。

所以容量的最优解,本质上是在“投资成本”和“运行约束是否满足”之间找平衡。只有把容量变量和小时级调度变量放进同一个模型一起优化,Cplex 才能通过求解器内部的边界、割平面和分支定界逻辑,同时判断“某个容量方案能不能跑得通”和“跑得通时运行成本是多少”。这也是为什么标题里叫“容量-调度优化分析”,而不是单纯的容量配置。

2. 模型主体拆解:目标函数、变量和约束

复现论文时,我会先把模型“翻译”成三张表:目标函数是什么,变量有哪些,约束有哪些。这三张表对了,代码只是机械装配。

2.1 目标函数先看口径:年费用还是净现值

常见的目标函数是年化总费用最小:

[ \min ; C_{\text{year}} = C_{\text{cap}} + C_{\text{om}} + C_{\text{buy}} - C_{\text{sell}} ]

其中 (C_{\text{cap}}) 是设备投资的年化成本,通常用资本回收系数 CRF 把一次性投资折算到每年:

[ CRF = \frac{r(1+r)^n}{(1+r)^n - 1} ]

这里 (r) 是折现率,(n) 是设备寿命。复现时必须先跟论文校对这个口径:有的论文用净现值,有的用等年值,有的把项目周期统一设成 20 年,有的分设备寿命单独折旧。这些细节直接影响容量结果,尤其是风电和光伏寿命长、电解槽寿命短,如果不分开折旧,电解槽会被低估成本,导致容量偏大。

(C_{\text{buy}}) 是并网模式下的购电费用,(C_{\text{sell}}) 是售电收入。离网模式下这两项直接置零。很多复现版本里还有弃电惩罚项,但原论文如果没写,就不要自己加,因为惩罚系数会改变容量方案的倾向。

2.2 变量设计:容量变量是“头”,时刻变量是“身子”

我习惯把变量分成两类。

第一类是容量变量,一般只有几个:风电装机 (P_{wt})、光伏装机 (P_{pv})、电解槽容量 (P_{el})、储氢罐容量 (H_{st}})、合成氨装置容量 (P_{am})。这些变量是连续非负变量,个别论文会要求离散装机台数,那就用整数变量表示,模型会难解很多。

第二类是调度变量,数量和典型日数、时间步长直接挂钩。常见的有:

  • 风电实际注入功率 (P_{wt}(t))
  • 光伏实际注入功率 (P_{pv}(t))
  • 电解槽耗电 (P_{el}(t))
  • 产氢流量 (F_{h2}(t))
  • 储氢罐状态 (S_{h2}(t))
  • 合成氨装置用氢流量 (F_{am}(t))
  • 合成氨装置耗电 (P_{am}(t))
  • 并网模式下购电 (P_{buy}(t))、售电 (P_{sell}(t))
  • 弃电量 (P_{curt}(t))
  • 设备启停状态 (z_{el}(t))、(z_{am}(t))

其中启停状态必须用二进制变量,这就是 Cplex 要处理整数变量的原因。如果原论文还考虑了电解槽爬坡、最小连续运行时间、储氢罐初始状态和终态相等,那么对应约束也会带着二进制变量出现,模型规模会明显增大。

2.3 关键约束:每一条都有物理含义

我复现时最重视的是下面这组约束,建议按顺序建模:

  1. 功率平衡。离网模式:

[ P_{wt}(t)+P_{pv}(t)=P_{el}(t)+P_{am}(t)+P_{other}(t)+P_{curt}(t) ]

并网模式加两项:

[ P_{wt}(t)+P_{pv}(t)+P_{buy}(t)=P_{el}(t)+P_{am}(t)+P_{sell}(t)+P_{other}(t)+P_{curt}(t) ]

(P_{other}) 是辅助设备负荷,比如空分制氮、冷却循环、照明控制等,很多人复现时会漏掉,导致模型结果偏乐观。

  1. 风光出力约束。风电和光伏实际注入功率不能超过当时的可用功率。可用功率等于归一化出力系数乘装机容量:

[ P_{wt}(t) \le CF_{wt}(t) \cdot P_{wt,cap} ]

[ P_{pv}(t) \le CF_{pv}(t) \cdot P_{pv,cap} ]

这两个约束看着简单,却决定了容量变量的可行性:如果装机很大但资源系数很低,模型会大量弃电,成本反而上升。

  1. 电解槽约束。电解槽有最小运行负荷,一般用二进制变量:

[ P_{el,min} \cdot z_{el}(t) \le P_{el}(t) \le P_{el,cap} \cdot z_{el}(t) ]

产氢量和耗电之间用效率系数关联。如果效率是常数,就是线性约束;如果论文里给的是部分负荷效率曲线,就需要线性化处理,通常取分段线性近似。

  1. 储氢罐动态约束。储氢罐是时间耦合的关键:

[ S_{h2}(t+1)=S_{h2}(t)+F_{h2}(t)-F_{am}(t) ]

同时有容量上下限,以及调度周期末状态等于初状态的循环约束。循环约束不能省,否则模型会把储氢罐里的氢“白送”给末端时段,导致结果失真。

  1. 合成氨装置约束。合成氨装置同样有最小负荷约束和启停二进制变量,同时耗电量和用氢量、产氨量之间存在转化系数。要注意氨本身也可以有储罐,如果原论文考虑氨储罐,还要再加一个储氨量动态约束,模型会复杂很多。

  2. 并网交互约束。一般要限制联络线功率上限,并加购电、售电互斥约束:

[ P_{buy}(t) \le P_{buy,max} \cdot z_{grid}(t) ]

[ P_{sell}(t) \le P_{sell,max} \cdot (1-z_{grid}(t)) ]

很多初版模型不写互斥约束,Cplex 虽然通常也不会傻到同时买和卖,但一旦出现同时买卖,目标函数里会莫名其妙多出成本,结果不可信。

3. 用 Matlab 和 Cplex 复现的完整路线

模型理清楚后,就是工程实现。Matlab 写优化模型的体验比 Python 好的一点是矩阵操作直观,但缺点是变量一多,索引容易乱。我的做法是把变量区块化,再用索引函数定位,而不是裸写 A 矩阵。

3.1 环境准备:把 Cplex 接到 Matlab

首先确认 Cplex 版本和 Matlab 版本兼容。IBM Cplex 安装目录下通常有一个matlab文件夹,里面是官方提供的接口函数。在 Matlab 里执行:

addpath(genpath('C:\Program Files\IBM\ILOG\CPLEX_Studio2211\matlab')); savepath; which cplexmilp

如果which能定位到cplexmilp,说明接口已经通。老版本接口一般用cplexmilp(f, Aineq, bineq, Aeq, beq, lb, ub, ctype, x0, options),其中ctype是一个字符向量,'C'表示连续变量,'B'表示二进制变量,'I'表示整数变量。新版本也有面向对象的接口,比如cplex = Cplex('model_name'); cplex.Model.obj = f;,两种方式都能用,我建议先学cplexmilp这种函数式接口,调试起来更快。

没有 Cplex 授权时,可以先用 Matlab 自带的intlinprog跑小算例,逻辑通了再换 Cplex。但要注意intlinprog和cplexmilp的输入参数不完全一样,换接口时不要直接复制。

3.2 数据准备:先搞清楚时间尺度

容量-调度联合优化的数据分两部分:资源数据和设备参数。风电、光伏要准备逐小时归一化出力系数,并网模型还要有分时电价。如果原论文用的是全年 8760 小时,那你的调度变量会非常庞大,Cplex 未必跑得动。复现时经常用典型日替代,比如按季节和天气类型用 k-means 聚成 12 类典型日,再乘以对应天数作为权重。这样做可以大幅压缩模型规模。

但是典型日数量不能拍脑袋。我踩过的坑是:只用 4 个典型日时,储能配置结果比论文低很多,因为极端天气被平均掉了;加到 12 个典型日后,储氢罐容量明显上升。如果你的复现结果和论文差距很大,第一步不是调 Cplex 参数,而是检查典型日数量和聚类方式。

3.3 矩阵装配:用索引函数避免 A 矩阵错位

这里给一个简化示例,变量区块划分如下:

% 变量区块 % x(1) 风电装机 % x(2) 光伏装机 % x(3) 电解槽装机 % x(4) 储氢罐容量 % x(5) 合成氨装置装机 % x(6:5+nT) 风电逐时注入 % x(...) 其他逐时变量按同样方式排列

在代码里,我会定义一个结构体保存索引位置:

nT = 24; idx.cap_wt = 1; idx.cap_pv = 2; idx.cap_el = 3; idx.cap_h2 = 4; idx.cap_am = 5; idx.Pwt = @(t) 5 + t; idx.Ppv = @(t) 5 + nT + t; idx.Pel = @(t) 5 + 2*nT + t; % 继续追加其他变量区

装配约束矩阵时,只通过索引函数访问变量位置,比如功率平衡约束:

Aeq = zeros(nT, nVar); beq = zeros(nT, 1); for t = 1:nT Aeq(t, idx.Pwt(t)) = 1; Aeq(t, idx.Ppv(t)) = 1; Aeq(t, idx.Pel(t)) = -1; Aeq(t, idx.Pam(t)) = -1; Aeq(t, idx.Pcurt(t)) = -1; beq(t) = 0; end

这样虽然代码看起来啰嗦,但每一行都能对应模型里的一个约束,排查问题时可以直接跳到对应循环,不用猜。

调用 Cplex 求解的示例:

ctype = char(ones(1, nVar) * 'C'); ctype(idx.z_el_start:idx.z_el_end) = 'B'; ctype(idx.z_am_start:idx.z_am_end) = 'B'; options = cplexoptimset('Display', 'iter', 'TolGap', 0.0001); [x, fval, exitflag, output] = cplexmilp(f, Aineq, bineq, Aeq, beq, lb, ub, ctype, [], options);

TolGap是 MIP 相对间隙。论文复现通常设到 0.1% 或 0.01% 就够,设成 0 会让求解时间成倍增加。工程上更聪明的做法是先设 1%,看大体容量结果,再收紧到 0.01% 做最终确认。

3.4 结果回读和后处理

Cplex 返回的x是一个长向量,里面按你定义的变量区块排列。回读时继续用索引函数拆包:

cap_wt = x(idx.cap_wt); cap_pv = x(idx.cap_pv); cap_el = x(idx.cap_el); cap_h2 = x(idx.cap_h2); Pwt = arrayfun(@(t) x(idx.Pwt(t)), 1:nT); Pel = arrayfun(@(t) x(idx.Pel(t)), 1:nT);

拿到容量结果后,不要急着画图。先做一个“可行性检查”:把最优解回代到所有约束中,看最大违反量是多少。如果违反量在 1e-6 量级,说明建模和求解都正常;如果出现 1e-2 量级的违反,基本可以确定是单位不一致或变量范围设置错误。

4. 复现时最容易踩的坑

这类题目的坑往往不在数学上,而在工程细节上。我把实际复现中遇到的典型问题整理成了一份排查清单。

4.1 模型不可行:先拆后合

Cplex 返回 infeasible 时,不要马上怀疑求解器。我一般按这个顺序排查:

  1. 先跑纯离网、去掉并网相关变量,看能否可行。
  2. 如果不可行,放宽储氢罐终态等于初态的约束,看模型是否变可行。如果变可行,说明储氢时序有问题。
  3. 如果仍不可行,把电解槽最小负荷约束去掉,看是否可行。这一步能判断是不是启停二进制变量和最小负荷把可行域压没了。
  4. 最后检查功率平衡约束的符号,尤其是P_wt和P_pv到底是“注入”还是“输入”,方向反了会让所有方程错位。

Cplex 本身也提供冲突集分析,新版接口可以输出不可行约束子集,但数据量大时不如自己拆模型直观。

4.2 求解时间太长:热启动和 MIP gap 是好朋友

容量-调度联合优化一旦把二进制变量加到全年 8760 小时,Cplex 可能几个小时出不来。这时候有两条路:一是缩小时间尺度,用典型日;二是给 Cplex 一个热启动初始解。

热启动在 Cplex 里可以用x0参数传一个可行解。比如先用较粗的模型跑一遍,把结果作为完整模型的初始解。Cplex 的 MIP 求解器会从这个解开始优化,分支定界树能剪掉大量无效区域。这个方法在复现论文时特别有效,尤其是你要反复微调参数,微调完之后模型结构没变,热启动能让求解时间从小时级降到分钟级。

4.3 数值尺度:系数差太多,Cplex 也会犯迷糊

我见过最隐蔽的坑是成本系数和功率变量量级差太大。比如装机成本用元/千瓦,电费却用元/兆瓦时,氢质量用千克,储能容量用立方米,一个目标函数里既有 1e8 的项又有 1e2 的项,Cplex 的数值容差会变得很难看,结果经常出现“微小违反约束但表面可行”的问题。

我的建议是统一用以下单位:

  • 功率:MW
  • 电量:MWh
  • 氢量:kg
  • 成本:万元
  • 电价:元/MWh 或元/kWh,但目标函数中所有金额单位一致

同时把所有变量上下限缩放到 0 到 1e4 左右,成本系数也换算到同一量级。这样 Cplex 的预处理效果更好,求解速度更快,结果也更稳。

4.4 典型日数量和权重错误

典型日缩减不是简单的“挑几天”。每个典型日必须带一个权重系数,表示这个代表日实际覆盖了多少天数。你在代码里把 8 个典型日的调度结果直接相加,然后说这是全年运行成本,那基本是错的。正确做法是在目标函数里按权重累加:

[ C_{\text{op}} = \sum_{d=1}^{N_d} w_d \sum_{t=1}^{nT} \left( c_{buy} P_{buy}(d,t) - c_{sell} P_{sell}(d,t) \right) ]

权重写错会让投资成本和运行成本的比例完全失调。复现论文时,我会先用一年的平均资源数据做一个粗校核,算出的年发电量、年制氢量和论文结果对比,如果偏差超过 5%,就先查典型日权重,而不是继续调模型。

4.5 论文结果对不上的常见隐藏假设

很多论文不会把每一个公式都写在正文里。比如合成氨装置可能只是一个大功率连续设备,也可能有明确的启动能耗;电解槽可能允许 0% 到 100% 宽范围运行,也可能有 30% 最小负荷;并网模型的购电电价可能分峰平谷,也可能是单一电价。这些细节都会改变容量结果。复现到一半发现对不上,先回头检查这些“小假设”,而不是怀疑 Cplex。

5. 一个可参考的简化算例

为了说明完整流程,我准备了一个简化的沙盘算例。参数不来自任何特定论文,只用于展示模型结构和结果解读方式,复现时请用论文数据替换。

5.1 基础参数

设备投资成本寿命年运维费率
风电6000 元/kW20 年2%
光伏3500 元/kW20 年1%
电解槽5500 元/kW15 年3%
储氢罐1200 元/kg15 年1%
合成氨装置4000 元/kW20 年2%

资源数据这样设置:风电年利用小时数约 2400 小时,光伏约 1500 小时,合成氨需求设定为每年固定吨位。并网模式电价采用峰平谷三段,峰段 1.2 元/kWh,平段 0.7 元/kWh,谷段 0.3 元/kWh。

5.2 离网模式结果

在离网模式下,模型会倾向于多装储氢罐来抵消风电和光伏的天然波动。一个典型结果是风电装机偏大,光伏装机适中,电解槽容量略小于风光的峰值功率,储氢罐容量明显大于单日氢气需求。为什么会这样?因为离网系统不能买电,缺电时只能靠储氢顶住,所以储氢成本虽然高,却是保证合成氨装置连续运行的唯一手段。

离网模式的弃电率通常会高于并网模式,这不是模型有问题,而是物理边界决定的。弃电率高意味着系统为“可靠性”多付出了装机成本,这在离网方案里是合理的。

5.3 并网模式结果

并网模式加入购电和售电后,容量结果会发生两种典型变化。如果峰谷电价差很大,电解槽容量可能增加,因为模型会在谷段大量买电制氢,等到峰段再把氨合成或储存下来,这样虽然买了电,但总成本和离网相比反而下降。如果谷段电价仍然很高,并网模式就只会作为“兜底”,容量配置和离网差别不大,购电量也很少。

这个算例提醒我们:并网不是简单地把离网方案砍小,而是根据电价曲线重排调度时序。复现论文时,如果并网结果和离网结果只差几个百分点,要怀疑电价曲线是不是给得太保守。

5.4 结果校验表

校验项方法合格线
功率平衡将最优解回代功率平衡约束最大偏差小于 1e-6
储氢终态检查调度期末储氢量等于初值偏差小于 0.1 kg
年制氢量用典型日权重折算与论文目标值偏差小于 5%
目标函数用同样的成本和效率参数重算与论文目标值偏差小于 5%
容量趋势对比各设备装机比例与论文主要结论一致

6. 我复现完之后的几点体会

这类项目做多了,最大的体会不是代码多熟练,而是“对模型物理含义的敬畏”。Cplex 再强,也只会按你给的约束找最优解,你给它单位错的系数,它照样给你一个精致的错误答案。

6.1 先跑通最小系统,再加大规模

我建议所有新手复现时都先做一个 24 小时、单典型日、去掉并网和储氢罐的版本,确认功率平衡和电解槽逻辑没问题后,再逐步加入储氢、并网、多典型日。每一步增加的东西越少,出错时越容易定位。我见过太多人一开始就上 8760 小时加 Cplex 热启动,结果 infeasible 后完全不知道问题在哪。

6.2 复现论文时,要把公式和代码对照成一个“表格”

每写一个约束,就在代码旁边注释对应论文公式编号,同时注明量纲和取值来源。这个方法很笨,但非常有效。因为复现的本质不是能跑出数字,而是你的模型假设和论文一致。只有把每个公式的物理含义、参数来源、边界条件都对照清楚,结果才能经得起推敲。

最后再分享一个实用小技巧:Cplex 跑完后,把目标函数拆成投资成本、运维成本、购电成本和售电收入四项分别输出,而不是只看总成本。多数情况下,论文的结果和你对不上的原因就藏在这几项里。投资成本偏高但运行成本偏低,说明容量偏大;购电成本很高,说明并网约束太松或电价数据没对齐。拆开看,一分钟就能定位问题,而不是对着总成本反复挠头。

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

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

立即咨询