1. 这个系统到底在优化什么:先理清“容量-调度”的双层逻辑
先把这个项目标题翻译成人话:风光互补发电,电量用来电解水制氢,氢气再去合成氨。系统可以并网运行,也可以离网孤岛运行。现在要回答两个问题——风电场、光伏电站、电解槽、储氢罐、氨合成回路各建多大容量(容量规划);在典型年或典型日的时间序列下,这些设备每小时该怎么启停、怎么出力(调度运行)。两个问题互相咬合,所以叫“容量-调度优化”。
我当初复现这个课题的时候,第一个感觉是:这不像一个单层优化问题,更像一个“先定规模、再跑运行、运行结果反过来修正规模”的循环。Cplex在这里扮演的角色是内层运行的求解器,而容量层往往要通过枚举网格、智能算法或者Benders分解等方式与内层嵌套。如果直接把所有容量变量和全年8760个小时的运行变量全部揉进一个混合整数线性规划(MILP)里,问题规模会迅速膨胀到Cplex很难在合理时间内收敛的程度。
这里需要明确一点:并网与离网是两种完全不同的优化语义。
- 并网模式:系统与外部电网存在功率交换,缺电时买电、富余时卖电(具体看是否允许反送,各论文假设差别很大)。电网实际上扮演了一个“无限大储能”,系统在极端天气下依然有功率兜底保障,所以容量配置可以相对紧凑。
- 离网模式:没有任何外部电源支撑,系统必须在任何时刻仅靠自身出力满足电-氢-氨全链条的功率需求。这要求容量配置冗余度显著提高,同时必须配置合理的储能(电储能和氢储能),否则风光波动会直接击穿整个系统的功率平衡。
复现时建议不要一上来就跑并网+离网+全年8760小时这种“大套餐”。我个人的做法是:先搭建离网模式下的典型周时间尺度模型,验证逻辑闭环;再切到并网模式,加上购售电变量;最后再扩展到全年或代表季。下面把每一步的技术细节拆开讲。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 设备的数学建模:从风光出力到氨合成塔,每个环节都是一个约束簇
整个系统的物料-能量流动主线是:风光发电 → 电解槽制氢 → 氢气缓冲储罐 → 合成氨回路 → 氨产品。中间还穿插着压缩机耗电、冷却水耗电等辅助负荷。
2.1 风光出力的时序建模
风电和光伏通常采用典型年逐时数据(风速、辐照度、温度)。实际复现时可以从MERRA-2或NASA POWER这类再分析数据中提取站点数据,也可以直接使用课题组已有的典型年气象数据。关键在于将气象数据转化为机组出力:
风电出力表达式一般写成分段函数,切入风速、额定风速、切出风速三个拐点。如果用风功率预测曲线近似,功率 (P_w) 与风速 (v) 的关系可表达为:
[
P_{w}(v) =
\begin{cases}
0 & v < v_{in} \text{ 或 } v \geq v_{out}\
P_{rated} \cdot \frac{v - v_{in}}{v_{rated} - v_{in}} & v_{in} \leq v < v_{rated}\
P_{rated} & v_{rated} \leq v < v_{out}
\end{cases}
]
光伏出力跟辐照度 (G) 和环境温度有关,常用公式是:
[
P_{pv} = P_{STC} \cdot \frac{G}{G_{STC}} \cdot [1 + \beta_T (T_{cell} - T_{STC})]
]
其中 (T_{cell}) 是电池板工作温度,(T_{STC}=25^\circ C),(G_{STC}=1000 W/m^2),(\beta_T) 是功率温度系数,取值大约在 -0.0035 到 -0.005 /°C 之间。不要小看温度修正项,在夏天高温强辐照条件下,光伏实际出力比额定折算低5%~8%,这会影响容量配置的冗余度。
2.2 电解槽的动态运行域
制氢电解槽是系统中最关键的“可控负荷”,它的数学模型直接影响优化结果的可行性。碱性电解槽(ALK)和质子交换膜电解槽(PEM)在项目中都很常见,两者在模型上主要有几个共同点:输入功率必须处于部分负荷范围内,一般取额定功率的20%~100%;氢气产出速率与输入功率近似线性;频繁启停会降低寿命,所以运行约束中可以引入最小连续运行时间或启停惩罚。
在Cplex的MILP模型中,电解槽的功率区间约束用二元变量表示:
[
y_{el,t} \cdot P_{el,min} \leq P_{el,t} \leq y_{el,t} \cdot P_{el,max}
]
其中 (y_{el,t} \in {0,1}) 表示电解槽在时刻 (t) 是否运行。电解槽输入功率经过整流器,本质上是一个恒电流负载特性,但在小时级调度的时间尺度上,采用上述线性可行域已经足够了,不需要考虑秒级的电压-电流耦合。
2.3 储氢罐与合成氨回路的耦合约束
氢储能与电储能的最大不同在于:氢气可以作为“跨天”甚至“跨周”的能量载体。储氢罐的约束与储电类似,包含容量上下限、充放能平衡、初始与终止储量的等式约束。这里有一个复现中的常见误区:制氢合成氨系统里,储氢罐不仅承担时间平移的作用,还承担“流量缓冲”的作用。合成氨回路对进气流量是稳定的,但风光制氢的产氢速率是波动的,两者之间的流量差全部靠储氢罐缓冲。
合成氨的氢气消耗速率可以用化学计量关系推算:合成1吨氨,理论需氢约0.176吨,实际工艺中按过量3%~5%计算。氨合成回路有一定的最低负荷率,通常为额定的40%~60%,取决于催化剂床层温度和回路压力控制能力。如果电解槽产氢速率频繁低于氨合成回路的最低稳定运行负荷,系统就必须考虑放空或加大储氢罐容量。这一条在容量优化中往往被低估,却是离网系统配置偏大的主要原因之一。
2.4 功率平衡与并网交互约束
整个系统在任意时刻 (t) 必须满足:
[
P_{w,t} + P_{pv,t} + P_{grid,t} + P_{dis,t} = P_{el,t} + P_{comp,t} + P_{aux,t} + P_{ch,t} + P_{load,t}
]
别忘了这个方程是“功率平衡方程”,它是优化模型的硬约束,必须被严格满足。如果没考虑功率平衡等式而只写了各设备自身的可行域,Cplex 可能会返回一个“功率供给不足”但目标函数却很小的解——那就白跑了。
并网模式下,(P_{grid,t}) 是带符号的变量,正值表示购电,负值表示售电。有些论文规定系统优先自用、余电上网,有些论文允许直接从电网购电来维持电解槽连续运行,不同假设会导致电解槽额定容量相差数倍。现实中,如果电网购电价足够低、电解槽高效运行带来的收益能覆盖电费成本,那么“连轴转”的电解槽方案反而经济性更好。这个结论听起来反直觉,但恰恰是容量规划的乐趣所在。
3. Cplex求解时的变量规模控制:为什么你的模型跑不动,我的模型三分钟出解
用Cplex求解这类问题的第一个坎往往是“规模爆炸”。假设时间粒度取1小时,全年8760个小时;每个时刻的决策变量包含风电、光伏、电解槽功率、储氢罐状态、电网购售电等至少10个连续变量和2~3个二元变量。直接建全年模型,连续变量接近9万个,二元变量近2.6万个,加上约束矩阵,Cplex在普通PC上未必无法求解,但内存消耗和求解时间会非常感人。
3.1 时间序列缩减:用典型日代替全年
实际复现中我强烈建议先采用“典型月/典型日”聚合法。先对全年风光出力数据做k-means聚类,把风光出力曲线聚成4~12个典型场景,并统计每个场景的天数权重。调度层只对典型日内的24小时做精细优化,容量层则按场景权重折算全年成本。这样变量规模直接降低两个数量级,Cplex求解速度可能从“数小时”缩短到“几十秒”。
聚类时需要注意:不能只看风速或辐照度单变量的分布,要把风光出力曲线当作多维向量来做聚类,否则会丢失风光互补性。我遇到过一种情况:单变量聚类结果把“大风小光”和“小风大光”聚到同一类,使得典型日场景里风光的互补性被平均掉了,优化出来的容量配置偏保守。
3.2 线性化处理:把非线性项转换成Cplex能吃的形态
这个模型里的非线性源主要有几类:
- 风功率曲线是分段线性的,可以通过分段McCormick或大M法变成线性约束;
- 储氢罐的储氢量与压力之间的关系,如果采用理想气体假设,压力与储量成正比,是可以直接线性表示的;
- 制氢、合成氨单位电耗如果是常数,则电耗与产量线性;
- 单位投资成本随容量变化产生规模经济效应时,会出现非线性项。此时要么采用固定容量档位和0-1变量选择,要么构造成分段线性成本曲线。
Cplex本身对二次约束和二次目标有支持(MIQP/QCP),但求解效率明显低于同规模的MILP。能线性化的绝不保留二次形式,这是我在实际复现中反复验证过的经验。Cplex里面所有变量默认上下界分别为1e20和-1e20,建议所有物理量都显式声明上下界。我遇到过由于风功率变量上界没有声明,解算器在并网模式下“凭空造”出巨大风电出力来满足负荷——数据直接变成垃圾。每个变量都必须根据风机数量、新能源装机的备选范围等给定有意义的上界。
3.3 Cplex参数调优经验
在Matlab中调用Cplex,常用方式是通过cplexmilp函数或Cplex类接口。如果在论文复现中你不想自己处理矩阵拼接,直接用MATLAB的optimproblem框架配合cplex求解器,代码可读性会好很多,但运算效率往往低一些。
分享一组我在复现时实际用到的参数配置:
| 参数 | 推荐取值 | 说明 |
|---|---|---|
cplex.Param.mip.tolerances.mipgap.Cur |
1e-4 |
提前终止条件,相对对偶间隙 |
cplex.Param.timelimit.Cur |
3600 |
最长求解时间,防止卡死 |
cplex.Param.threads.Cur |
4 |
多核并行,别贪多 |
cplex.Param.mip.strategy.file.Cur |
2 |
节点文件压缩策略,扩容减压 |
cplex.Param.mip.limits.nodes.Cur |
1e8 |
节点上限,防止无限分支 |
特别说一下mipgap。如果论文要求的结果精度是小数点后两位(比如容量结果单位是MW,通常MW精度并不需要特别高),那么设置1e-3甚至5e-3就够了。把mipgap设为1e-6会白白增加几十分钟求解时间,解出来的容量配置在工程上跟mipgap=1e-3完全没有本质差异。如果你想跑参数敏感性分析,需要跑几十上百个场景,mipgap一定要放宽,否则你会在电脑前等崩溃。
4. 双层优化的嵌套逻辑:先选容量,再调运行,用运行结果打脸初始容量
这个问题的内层是给定容量配置下的调度优化,外层是容量方案的搜索。外层每给出一组容量候选解,内层就要跑一次全时间序列的调度优化,返回年运行成本、弃风率、失负荷率等指标。外层根据这些指标调整容量,继续迭代。
4.1 为什么要用双层而不是单层
单层模型其实是可以写的:把容量变量和全时间序列的运行变量全部放进一个MILP里,目标函数包含投资成本和运行成本。但问题是决策变量的量纲和数值范围差异过大——投资成本动辄千万级,而某些运行变量的数值在0.0001级别,这在数值求解上会让Cplex内部的预处理机制花费大量精力在尺度平衡上。同时,容量变量(如电解槽台数、储氢罐体积)是整数变量,运行变量中又包含每天的开停机状态变量,两者耦合会把MILP的搜索空间撑得非常大。
双层结构的实际好处有两个:一是你可以把容量枚举的并行化做得很充分——每台机器跑一个容量方案,内层调度互不干扰;二是内层调度可以用更精细的时间尺度(比如15分钟),而外层容量评估不需要在每个候选方案上都用15分钟粒度重复跑,可以先用小时级快速筛选。
4.2 外层容量搜索的两种工程化实现
我见过并实际尝试过两种路径:
路径A:枚举网格法。 把风、光、电解槽、储氢罐的候选容量各自离散成若干档位,排列组合遍历。这种方式实现最简单,代码容易验证,但组合数随维度指数增长。如果每个维度取10档,5个维度就是10万次内层求解,每次需要30秒,总计超过800小时,明显不可行。
路径B:启发式迭代。 通常采用遗传算法或粒子群算法在外层搜索,内层保持Cplex精确调度。例如,种群规模30,迭代40代,需要跑1200次内层调度,每次调度2~3秒,总耗约1小时,比枚举法现实得多。用启发式算法的难点在于参数设置,种群大小、交叉变异的概率等都要反复调——但好处是你可以稍微加一些技巧,比如从初始风光的容量比1:1开始,这样收敛到最优解的速度会快很多。
4.3 内层调度中必须保留的工程约束细节
内层调度才是Cplex真正发挥威力的地方。这里有几个在论文中容易一笔带过、但实现时必须要写清楚的约束:
- 最小开停机时间约束:电解槽一旦启动,至少运行2~3小时才能停机(热应力考虑);氨合成回路一旦开工,尽量保持满负荷或最低稳定负荷连续运行,频繁开停会毁催化剂。
- 储氢罐初始–终止储量耦合约束:如果做全年连续仿真,罐储量的初始值和期末值要设定为同一个值,或者设定为高于某个比例,否则优化结果会把罐“白嫖”——在年初把罐排空、年末把罐充满,从而虚减装机需求。
- 弃风弃光惩罚项的建模:离网模式下,如果没有电储能承接多余电量,必须允许弃风弃光,但这部分对应能量成本为0,目标函数中不需要惩罚;但如果你论文里需要统计“弃风率”指标,就要在Cplex输出结果中额外计算,这是一定要写的。
这里我想特别强调一下并网模式的PCC容量约束:系统与电网连接点(PCC)的功率并不是无限大的,一般要设置一个最大注入/输出功率限制 (P_{pcc,max})。很多复现者容易漏掉这个约束,导致结果中并网功率频繁出现几百MW级别的剧烈波动——这在工程上不仅不现实,而且电网调度也绝不会接受。加一个PCC限值之后,系统就会主动把部分弃电转化为氢储存,容量配置结果更接近真实的示范工程。
5. 复现过程中我踩过的几个深坑与排查链路
这部分是我最想写的,因为文献里永远查不到这些细节。这些坑不会让你推导公式出错,但会让你的代码要么怎么都跑不出可行解,要么一旦跑出来数值就很离奇。
5.1 坑一:储氢罐平衡约束的“零漂移”问题
我最初在复现时,储氢罐的平衡约束写的是严格等式:(S_{t+1} = S_t + F_{in} - F_{out}),其中所有变量单位都是kg/h。这个式子本身没问题,但问题是电解槽产氢量与合成氨耗氢量之间的流量差乘以时间步长后,对应的储氢罐变化量是连续变量的微小差值。在Cplex的数值求解过程中,两三天的运行模拟后,储氢罐储量会出现微小的积累偏置,如果时间拉长到半年,这个偏置累积到了罐容量的±5%,直接导致年末出现“虚满”或“虚空”的伪可行解。
排查的过程很有趣:我发现某次输出中储氢罐在全年第200天附近出现了“负储量”,但Cplex却报告问题是可行的。后来检查原始约束矩阵才发现,问题出在初始化时给了一个隐性的缩放系数错误,导致等式中两侧单位不统一。排查方式是随机抽取10个连续时间断面,手动验算储氢罐平衡是否守恒,一下子就定位到了问题。
5.2 坑二:松弛变量把不可行问题变成“伪可行”
离网模式下,功率平衡约束非常紧,无风无光时段容易找不到可行解。很多人图省事,直接在功率平衡中加松弛变量并给一个大惩罚系数。这个做法本身在工程上可以理解——相当于允许切负荷。但惩罚系数如果没给够量级,Cplex会输出一个“每几小时切一点负荷”的方案,还声称是全局最优解。
我的排查经验是:连续三个晚上的失负荷功率超过24点,就要查这个惩罚系数的物理意义。把惩罚系数设为“单位失负荷价值”的3~5倍,这样只有在极端缺电时才会切负荷,而不是优化路径依赖。严谨一点的做法是,把失负荷变量单独统计并设置一个“年失负荷小时数上限”约束,比如全年不超过10小时,这样既让模型有退路,又保证结果在工程上可接受。
5.3 坑三:风光互补指标的“自我欺骗”
很多文章会说“风光互补可以平滑出力曲线”,但如果你把风、光容量比调到极端——比如风电占95%——优化结果依然会收敛“成功”,但它只是在用储能和电网硬扛波动,完全没发挥出互补优势。这里有一个值得说的决策链:
- 第一步,绘制全年风速和辐照度的互补系数热力图,看看该地区光照资源与风资源的日内/季度互补特征到底什么样;
- 第二步,设置一个装机比例的探索范围,例如风:光从1:4到4:1;
- 第三步,用“有效供电小时数”或“波动性指标”来辅助评估不同比例下的调度结果,而不只是盯着总成本看。
坐标放在西北某个风光资源都很丰富的站点上,我算出来的结果通常是风电占60%~70%左右最优。原因也简单:晚间无光但风速往往上升,白天的光伏正好压低电解槽的购电需求。这个规律在不同地区完全不同,所以别直接抄别人的装机比例,机制要放在自己数据里跑。
5.4 坑四:并行运行多方案时的许可证问题
如果做参数敏感性分析,一次要跑50个算例,你是老老实实排队,还是开MATLAB并行工具箱?并行当然好,但注意Cplex的许可证类型——如果是并发许可证,多开几个Worker没有问题;如果是单机单用户许可证,并行跑几十个cplexmilp进程可能直接把许可证占满,其他进程排队等待,一晚上的算例到第二天早上才跑了两三个。这个坑我花了一个通宵才明白。
6. 算例验证与结果判读:怎么确认你的优化结果是“真优”而不是“自嗨”
模型写好、Cplex能出解了,这只是第一步。你需要一个系统性的验证框架,否则你根本不知道每次运行结果中,究竟有哪些是物理规律,哪些是模型的人为设置导致的结果。
6.1 三类算例场景:极端边界是模型正确性的试金石
我一般建议设置三个算例:
- 无风无光连续72小时场景。离网模式下,如果储氢罐和电储能配置不足,优化结果应该显示“失负荷”或“不可行”。如果你的模型在离网模式下面对这种场景还能给出可行解且储能容量没有大幅增加,就要怀疑平衡约束里是不是有非物理的功率来源——比如售电功率变量写了负载侧方向不严谨,让系统“凭空卖电补贴负荷”。
- 全风光大发场景。连续5天强风强光,电储能和储氢罐应该被充到接近满容量,弃风弃光率会上升。如果此时弃风率很低,说明模型给电网或合成氨的受电能力可能设置得过宽松。
- 春夏秋冬四季典型日。分别跑4个典型日的调度结果,查看电解槽的等效满负荷小时数是否落在合理区间。如果冬季电解槽几乎全部时间满负荷,而夏季经常停机,说明你的容量配置带有明显的季节性偏置,可能需要在容量层加一个季节约束。
6.2 灵敏度分析的必要顺序
优先做三组单因素灵敏度:风光资源乘子(0.9~1.1)、设备投资成本乘子(0.7~1.3)、购电价乘子(0.5~2.0)。每组跑5~7个档位即可,21个场景足够画出三条光滑曲线。
复现这类问题,Cplex的内层求解稳定性基本不用怀疑,真正的建模功夫都在问题转换和外层算法与内层求解器的交互上。
如果在调试中反复出现不可行问题,可以用Cplex提供的cplexLazyConstraint机制或者等价的不可行性分析功能,打印出冲突约束集合,常见原因一般是储氢罐储量上下界写反、二元变量未把功率范围卡住等,逐条对比变量的上下界和它们出现的所有约束,就能快速定位。
