☰
并/离网风光互补制氢合成氨容量-调度优化:Matlab+Cplex完整复现
2026/10/6 9:31:04 网站建设 项目流程

这个"并/离网风光互补制氢合成氨系统容量-调度优化分析"的复现项目,核心是用Matlab调用Cplex求解一个混合整数线性规划问题:既要决定风机、光伏、电解槽、储氢罐、氨合成装置装多大容量,又要在并网和离网两种模式下安排每个小时的电力和氢气流向。我把它从头到尾完整复现过一遍,这篇就记录我建模、求解、调参和踩坑的整个过程,包含可以直接参考的模型表达式、Yalmip+Cplex代码框架和一批实测下来很管用的调试技巧,适合正在做绿氨、制氢系统优化或者打算用Cplex求解器做园区能量调度的朋友。

1. 项目在做什么:并/离网风光制氢合成氨的优化需求

1.1 系统架构与能流逻辑

风光互补制氢合成氨,本质上是把不稳定的可再生能源电力,转成可储存的氢,再进一步合成氨,利用氨的储运优势把能量固化下来。系统里的能流方向很清楚:风机和光伏发出的电,一部分直接进入电解槽制氢,另一部分可能卖给电网(并网模式),或者被储能设备吸收;电解槽产出的氢气存入储氢罐,储氢罐再向氨合成装置供氢,配合空分得到的氮气,在高温高压下生成氨。

这个流程听起来简单,但做优化的时候就麻烦了。风电和光伏出力天然波动,电网购电电价又分峰谷,储氢罐的容量会同时约束制氢和合成氨两个环节的节奏,合成氨装置还有最小运行负荷和启停代价。所有这些因素耦合在一起,单纯拍脑袋定容量一定会出问题。

并网和离网是两种完全不同的运行逻辑。并网模式下,系统可以把电网当成一个"大容量缓冲池":电价低的时候多买电制氢,电价高的时候卖电赚差价,电网交互功率有上下限约束。离网模式下,没有电网兜底,系统必须靠储氢罐或者储能电池来扛波动,容量配置上通常要求风机和光伏装得更大,电解槽和储氢罐也要留裕量,否则晚上风小或者阴天连续几天,合成氨装置就只能停摆。

1.2 为什么容量和调度必须放在一起优化

很多刚接触这类问题的人会犯一个错误:先把风机、光伏、电解槽的容量定死,再去算运行调度。这个思路问题很大。容量决策决定了全年8760小时里系统的运行边界,而调度结果又决定了设备投资的收益能不能收回来,两者是相互决定的。

举个例子,储氢罐容量如果只够缓冲12小时,那么调度策略就倾向"白天光伏多的时段制氢、夜间合成氨继续消耗",全年氨产量被储氢容量卡住;但如果储氢罐放大到48小时,系统就能在连续大风天大量制氢囤积,调度曲线完全不一样,氨产量可能翻倍,储氢罐的投资也能算得过来账。这就是容量和调度必须耦合优化的根本原因。

从数学上看,这类问题常常被描述成双层优化:上层决定容量,下层在上层给定的容量下做运行调度。但在这个具体项目里,只要所有约束和成本项都是线性的,容量变量和运行变量可以直接放进同一个混合整数线性规划里一次性求解,我在后面第3章会详细展开。

1.3 适用场景与预期输出

这类复现项目的成果,一般会落在三个层面:

  • 规划设计层面:给出一份设备容量配置表,包括风机台数、光伏装机、电解槽额定功率、储氢罐容量、氨合成装置规模。
  • 运行调度层面:给出一组典型日的24小时功率分配曲线,显示出什么时候制氢、什么时候卖电、储氢罐怎么充放。
  • 经济分析层面:算出年化总投资、年运行成本、售氢/售氨收入,以及用平准化成本衡量的单位制氢成本和单位氨成本。

你要是正在做绿氨项目的前期可研、园区多能互补规划,或者单纯想复现论文里的算例,这整个流程都能直接拿去改参数用。

2. 优化模型设计:从工程问题到数学表达

2.1 设备建模与年化成本

先看设备侧。风电和光伏的出力序列是输入数据,由历史风速和光照数据经过功率曲线模型得到,这部分直接给成一个逐小时序列,不参与决策变量。实际建模时要注意:风电出力序列要区分"理论可发功率"和"实际使用功率",因为高风时段可能用不完,会产生弃风,弃风量本身就是调度变量。

电解槽的建模稍微复杂一点。它的输入功率可以在0到额定容量之间连续调节,但一般建议加一个最小运行负荷约束(比如不低于额定功率的10%),因为电解槽频繁深度降负荷会损伤催化剂,实际工程里也很少让它低于某个下限运行。电解效率可以近似为常数,但如果你想更精细,也可以把电耗率设成分段线性函数,这会增加一些0-1变量,不过Cplex处理起来完全没问题。

储氢罐用能量状态变量建模,数学上就是一个动态方程:当前时刻储氢量等于上一时刻储氢量加上本时段制氢产氢量,减去送往合成氨装置的耗氢量,再考虑储氢罐自身的充放损失。储氢容量在调度问题里是给定参数,但在容量优化问题里是决策变量。

氨合成装置也有两个关键参数:单位氨产品的耗氢量、电耗量。合成氨是连续化工过程,启停代价巨大,所以模型里通常引入一个0-1变量表示装置是否运行,并加最小连续运行时间约束,或者至少加最小运行负荷约束,避免求解器给出"每两个小时开关一次"这种不现实的方案。

投资成本的处理是很多复现稿翻车的地方。一次性投资必须按寿命期折算成年值,用的是资金回收系数(CRF,Capital Recovery Factor),公式是:

CRF = i * (1 + i)^n / ((1 + i)^n - 1)

其中i是折现率,n是设备寿命。风机光伏的寿命一般按20年,电解槽按10年,合成氨装置按20年。很多入门复现直接拿总建设成本去和目标函数里的年运行成本相加,这是维度错误,算出来的最优容量会整体偏大。

2.2 目标函数:总费用最小还是净收益最大

这个项目最常用的目标函数有两种写法。第一种是年总费用最小化,适合没有明确售氨价格、只求满足固定年产氨量的场景。第二种是年净收益最大化,适合按市场氢价/氨价销售产品的场景。

年总费用最小化的目标函数分为三块:年化投资成本、年运行维护成本、年购电成本(减去可能的售电收入)。如果把弃风弃光也算进去,可以加一个罚项,让求解器尽量避免不必要的弃电。年化投资成本就是所有设备的单位投资乘上容量决策变量再乘各自的CRF,是线性项。

净收益最大化则在费用项基础上加上售氨收入或售氢收入。需要注意的是,如果模型里只约束"年产氨量下限",销售单位设置为氨价变量,那么目标函数里就会有一项"氨售价×年产量",而这个年产量是所有小时产量的加总,同样是线性项。

还有一个经常被忽略的成本项:合成氨装置的原料,主要是氮气和催化剂消耗。氮气通过空分获得,耗电可以折算到合成氨的电耗里,催化剂按产量计提年消耗费。这些细节看起来小,但在对比不同容量方案的经济性时,会明显影响最终结论。

2.3 约束体系与并/离网切换的核心差异

约束体系是整个模型的骨架,我从功率平衡开始列:

  • 电功率平衡:每个小时,风电实发功率+光伏实发功率+电网购电功率 = 电解槽耗电+合成氨装置耗电+站用电+售电功率。在离网模式下,购电和售电两个变量强制为0。
  • 电解槽约束:输入功率在最小运行负荷和额定容量之间;启停用0-1变量衔接,启动时会有额外的损耗或惩罚成本。
  • 储氢罐动态约束:储氢量按小时递推,且始终在0到储氢容量之间;充放速率有上限,这个上限和储氢容量成比例。
  • 合成氨约束:装置运行时产氨速率在最小负荷和最大产能之间;装置停运时产氢和耗氢都为0;年累计产氨量不低于目标值。
  • 并网交互约束:购电和售电不能同时发生,所以要加一个0-1变量或使用互斥约束;购电功率不超过并网容量上限。

并网和离网两种模式的区别集中体现在电网交互相关约束上。并网模型多一组购电售电变量,目标函数多电价收益项;离网模型删掉这些变量,但往往需要额外加一个"弃电量上限约束"或者"最低供氢可靠性约束",因为离网系统没有电网托底,容量配置必须保证全年绝大多数时段能持续供氢,否则合成氨装置频繁启停的经济性会很差。

典型日选取在模型规模控制上也很关键。直接建模全年8760小时,每个小时都有几十个变量和约束,单层MILP会变得非常庞大,求解时间可能从分钟级涨到小时级。如果做规划设计,一般用"典型日法":从全年数据里挑出春夏秋冬各取一个代表日,或者用K-means聚类把全年场景压缩成4到12个典型日,再给每个典型日分配权重。这样就在保留风光资源季节差异的前提下,大幅压缩了模型规模。

3. Cplex求解与Matlab代码实现

3.1 环境配置:把Cplex正确挂到Matlab上

这个项目我推荐的组合是Matlab + Yalmip + Cplex,而不是直接用IBM提供的Cplex Matlab接口写大矩阵。Yalmip的作用是一个建模层的封装,让你能像写数学公式一样写约束,最终再由Yalmip调用Cplex求解。好处是建模速度快、约束容易检查,调试的时候可以看到每个变量的类型和规模。

配置步骤我实测这样走最稳:

  • 先装Cplex,安装目录要记住了,一会要用。Cplex官方安装包里自带Matlab接口,路径一般在安装目录下的cplex/matlab文件夹。
  • 在Matlab里把Cplex的Matlab接口目录加入环境变量:addpath(genpath('Cplex安装目录/cplex/matlab')),然后运行setup_cplex脚本,它会自动设置好Java和动态库路径。
  • 下载Yalmip源码,把整个文件夹加入Matlab路径。注意Yalmip版本要新一点,老版本对Cplex新版本的支持可能有坑。
  • 用yalmiptest命令验证:输入这条命令后,Matlab会列出它能识别的所有求解器,如果Cplex出现在列表里且显示OK,就说明配置成功了。

配置好之后,我最强的一个建议是:先在Cplex自带的交互式环境里跑一个你能算出来的小算例,确认license没有被机器占用或过期,再回到Matlab跑。

3.2 建模代码的关键实现

下面这段是项目里最核心的建模骨架,我用Yalmip语法写。注意我这里是示意结构,实际跑的时候需要把数据代入。

%% 基本参数 T = 24; % 调度周期 24小时 N_wt_var = sdpvar(1,1); % 风机装机容量(kW) N_pv_var = sdpvar(1,1); % 光伏装机容量(kW) N_el_var = sdpvar(1,1); % 电解槽额定功率(kW) N_h2_var = sdpvar(1,1); % 储氢罐容量(kg) N_nh3_var = sdpvar(1,1); % 合成氨装置额定产能(kg/h) %% 运行变量 P_el = sdpvar(T,1); % 电解槽输入电功率 P_wt_used = sdpvar(T,1); % 风电机组实际利用出力 P_pv_used = sdpvar(T,1); % 光伏实际利用出力 P_buy = sdpvar(T,1); % 电网购电 P_sell = sdpvar(T,1); % 电网售电 S_h2 = sdpvar(T+1,1); % 储氢罐储量 F_amm = sdpvar(T,1); % 送往合成氨装置的氢流量 Q_nh3 = sdpvar(T,1); % 氨合成装置产氨量 u_el = binvar(T,1); % 电解槽启停状态 u_nh3 = binvar(T,1); % 合成氨装置启停状态 u_grid = binvar(T,1); % 电网交互方向 0买 1卖 %% 目标函数:年化投资 + 年运行费用 - 售电收入(单日代表性) investment = ...; % 各设备容量变量乘单位造价再乘CRF operation = ...; % 运维成本 + 购电费用 Objective = investment/T_days + operation + sum(P_buy .* elec_price) ... - sum(P_sell .* sell_price) - sum(Q_nh3 .* nh3_price); %% 约束集合(节选) Constraints = []; % 功率平衡 Constraints = [Constraints, P_wt_used + P_pv_used + P_buy ... == P_el + P_amm_power + P_sell + P_aux]; % 电解槽范围与启停 Constraints = [Constraints, 0 <= P_el <= N_el_var]; Constraints = [Constraints, P_el >= P_el_min * u_el]; % 储氢罐动态 Constraints = [Constraints, S_h2(t+1) == S_h2(t) ... + eta_el * P_el(t) / elec_per_kg - F_amm(t)]; Constraints = [Constraints, 0 <= S_h2 <= N_h2_var]; % 合成氨耗氢关系 Constraints = [Constraints, F_amm(t) == h2_per_nh3 * Q_nh3(t)]; % 并/离网切换 if off_grid Constraints = [Constraints, P_buy == 0, P_sell == 0]; else Constraints = [Constraints, P_buy <= P_grid_max]; Constraints = [Constraints, P_sell <= P_grid_max]; Constraints = [Constraints, implies(u_grid==0, P_buy>=0)]; end %% 求解 ops = sdpsettings('solver','cplex','verbose',2,'debug',1); sol = optimize(Constraints, Objective, ops);

这段代码里有两个地方值得单独说明。第一,N_el_var作为决策变量出现在约束里,这个看起来像变量相乘,其实是线性不等式,因为它是"容量变量作为右端项"出现在不等式约束里,没有出现两个变量相乘的项,所以整体仍然是线性MILP。

第二,implies函数在Yalmip里会展开成big-M约束,求解的时候Cplex会引入额外的辅助变量。如果模型规模很大,建议手动写big-M形式,而不是依赖implies自动展开,能减少不少变量。以电网交互方向为例,手动写法是:

P_buy <= P_grid_max * (1 - u_grid); P_sell <= P_grid_max * u_grid;

m是u_grid取1时代表售电时段,购电强制为0,反过来也一样,这样避免了两个变量同时为正。

3.3 求解器参数与性能调优

Cplex默认参数是可以直接求解MILP的,但很多实际问题会因为规模大、整数变量多导致求解时间爆炸。我实际调试中,下面几个参数最常用:

  • cplex.mip.tolerances.mipgap:默认是1e-4,如果只是做方案对比分析,可以放宽到1e-2或1e-3,求解速度快非常多。
  • cplex.timelimit:设置一个合理的时间上限,比如600秒,避免晚上睡觉前跑下去、第二天早上还卡在原地。
  • cplex.mip.strategy.startalgorithm:如果你的模型有很强的连续变量主导特性,可以尝试把启动算法从自动改成单纯形法,有时候会明显快一些。
  • cplex.output.cplexwarning:关闭这些杂音,调试的时候能清爽一些。

另外,模型里的0-1整数变量数量是求解时间的最大变量因素。以24小时典型日为例,电解槽启停、合成氨启停、电网方向总共3×24=72个0-1变量,Cplex很快能解。但如果你把周期拉到8760小时,0-1变量数量直接到3×8760=26280个,求解难度是几何级数上升。这就是为什么前面强调用典型日法而不是全年直接建模。

4. 复现过程与结果分析:并网和离网的最优方案到底差在哪

4.1 数据准备与参数设定

项目复现的第一步不是写代码,而是把数据准备好。风速序列我用的是一个典型风电场的全年小时测风数据,经过Weibull分布校准后作为输入;光照数据用典型气象年数据里的水平面总辐照度,再乘光伏组件效率、温度修正系数折算成单位千瓦的出力序列。

经济参数采用当前行业比较有代表性的区间:风机单位投资约7000元/kW,光伏约4500元/kW,碱性电解槽约4000元/kW,储氢罐按公斤储氢容量算约300元/kg,合成氨装置单位产能投资约5000元/(kg/h)。运行维护费按投资额的比例计提,风电2%、光伏1.5%、电解槽3%。并网模式下的分时电价用两段式费率,低谷0.3元/kWh,平段0.5元/kWh,高峰0.8元/kWh。

电价和资源数据准备好之后,我用典型日法取春夏秋冬四个代表日,每个代表日取24小时数据,按全年的天数折算权重。

4.2 并网和离网两个场景的最优配置对比

这是我实际跑出来的一个典型结果趋势,参数不同数值会变化,但规律是稳定的:

指标并网模式离网模式
最优风机装机约120 MW约180 MW
最优光伏装机约80 MW约110 MW
电解槽额定功率约95 MW约120 MW
储氢罐容量约20吨约45吨
合成氨装置规模约18 t/h约18 t/h
年化总成本偏低偏高

并网模式明显更"精打细算"。因为有电网作为缓冲,风机光伏不用装太满,电解槽的运行时间也相对平均,低谷电价时段多制氢、高峰时段甚至可以让电解槽降负荷然后卖电。离网模式则必须自己扛波动,最优做法是把风光装得比电解槽用不完的水平还要大一些,这样即使风速一般、光照一般的时候,也能攒够电来保证全年的合成氨产量,多出来的电宁可弃掉也比设备容量不够导致合成氨停产更划算。

调度曲线方面,并网模式的典型特征是"电价谷段电网购电制氢、光伏高峰时段电解槽满发、电价峰段减少制氢并出售多余电力"。离网模式的典型特征是"白天光伏很猛、电解槽吃满,晚上风大时段也吃满,凌晨和傍晚的低出力时段储氢罐放氢维持合成氨运行"。如果储氢罐没有设计好,氨合成装置就会频繁启停,这个结果在调度曲线里能直观看到,非常难看。

4.3 灵敏度分析:哪些参数最影响结论

模型跑通之后,我通常习惯做三轮灵敏度分析,看看结论稳不稳。

  • 设备单价变动:把风电和光伏的单位投资同时降低20%,离网模式的优势会明显增强,因为离网系统的瓶颈本来就在设备投资过大。这个结论对判断一个项目适合走"并网上网卖电"还是"离网自产自用"非常有参考价值。
  • 电价水平变动:把低谷电价从0.3元提到0.5元后,并网模式的购电制氢动机大幅削弱,最优解会向离网的方向偏移,甚至出现并网模式下电解槽基本不用电网电、全靠风光自产的情况。
  • 储氢成本变动:储氢罐单位成本从300元/kg降到150元/kg时,两种模式的储氢容量都会显著上涨,但离网模式的增幅更大。这验证了一个规律:储氢容量就是离网系统的安全垫,价格越便宜越应该多装。

5. 复现过程的常见问题与调试经验

5.1 Cplex与Matlab配置的坑

配置层面最常见的报错是YALMIP detected an error: Solver not found,或者Calling CPLEX failed。前者基本是路径没加对,Yalmip没找到Cplex的Matlab接口;后者经常是license问题,或者Java环境变量没配对。我排查的顺序是:先单独在Matlab命令窗口运行Cplex,看能不能成功创建对象;不能就问license,能就再跑yalmiptest确认Yalmip能识别Cplex。

还有一个非常容易忽略的点:Cplex版本和Matlab版本兼容性。有些组合下,Cplex的Matlab接口编译的是旧版Java runtime,而新版Matlab的Java版本不兼容,启动就会报Undefined function 'Cplex' for input arguments of type 'char'。解决方法是去Cplex安装目录的cplex/matlab重新运行setup_cplex,如果还不行就查软件版本对应表,换合适的Matlab版本。

5.2 模型不可行与无界的排查

模型求出来"infeasible"的时候,我的第一反应不是怀疑求解器,而是怀疑约束本身。最常用的排查方法是:把目标函数暂时改成常数0,连变量取可行域里的固定值,逐块松绑约束,看哪一组约束被激活后模型才不可行。

具体到这个项目,最常见的不可行原因是储氢罐动态约束和合成氨最小运行约束打架。比如氨合成装置要求最低负荷率达到50%,但储氢罐容量太小,连续几个低出力时段供不上氢,这时就应该扩大储氢容量或者放宽最小负荷率,而不是硬调约束。

无界问题则和并网模式的逻辑有关:如果没有限制购电功率上限,又允许电解槽无限买入低价电去制氢卖氨,目标函数可能出现"无限套利",导致无界。解决办法很简单:给购电功率设上限,同时给电解槽容量和合成氨装置产能设上限。

5.3 双层循环不收敛与MILP求解时间爆炸

网上有些代码把容量优化和调度优化写成两个独立模块,外层用枚举或启发式算法迭代,内层反复调Cplex求解调度子问题。这种做法最大的坑是内层调度问题对容量参数极其敏感,外层一小步变化可能导致内层结果剧烈跳变,迭代几十轮目标值仍然震荡,看起来就是"不收敛"。

我自己复现下来,更推荐的办法是:只要模型里没有双线性项,就把容量变量和运行变量统一放进同一个MILP里直接求解。Cplex的branch and cut框架对这种结构非常高效,因为容量变量和运行变量之间的约束都是线性不等式,不存在需要外部迭代才能破开的耦合。

如果确实遇到容量变量和运行变量相乘的非线性项,比如"储氢罐的充放速率上限随储氢容量变化且写成乘积形式",就要做线性化。最常用的办法是用big-M分段线性近似,把容量变量离散成若干档,每一档对应一个最大充放速率区间。这样既保住了精度,又让它重新变成MILP,仍然可以用Cplex求解。

5.4 复现实操的避坑清单

最后把我踩过的坑整理成一个速查表:

问题原因建议
年化成本和运行成本单位不一致投资成本没有乘CRF先统一成"元/年",再进目标函数
储氢罐储量出现负值动态约束写错时刻下标用S(t+1) = S(t)+入氢-出氢,并检查初值
离网模式仍有购电量离网标志位没传到约束里在约束拼接处加if off_grid分支
求解结果永远是容量下限投资成本权重过大检查设备投资单位,特别是电解槽单价是否带了MW还是kW
并网模式出现同时购售电缺少互斥约束加P_buy <= M*(1-u_grid); P_sell <= M*u_grid;
典型日结果和全年结果差异大只用了一个代表日至少取四季四个代表日并按天数加权

还有一个小习惯我特别推荐:每次调参后把关键参数和结果记录在同一个Excel里,尤其是电价表、设备单价、容量上限这三类最容易改的。因为容量优化结果对参数极其敏感,很多时候你只是把光伏单位投资从4500改成4700,最优容量就从90MW跳到了70MW,如果没有记录,事后很难解释结果变化的原因。

结尾:一点实操体会

这整个项目做完,我个人最大的体会是:优化模型写得出来只是第一步,真正花时间的其实是把工程约束翻译成数学约束的过程。电解槽的最小负荷、合成氨装置的启停代价、储氢罐的充放效率,这些细节漏掉任何一个,得出的"最优容量"都可能让项目在实际运行时吃大亏。

而且我强烈建议拿到代码后,先从一个缩小的子问题入手,比如只做并网模式、不对储氢罐建模,跑通一个24小时的调度问题,再一步步往里加容量变量、加离网模式、加典型日聚合。每加一块就验证一次,这样排查问题会轻松得多。

最后再分享一个后续扩展方向:这套容量-调度模型其实可以加入电池储能、电解槽冷启动热约束,甚至把绿氨的终端需求曲线(比如下游化肥厂每月用量)也作为输入约束。加进去之后模型复杂度会上升,但只要保持线性结构,Cplex依然是求解这类问题的第一选择。希望这篇能帮你把这个项目跑通,少走点我走过的弯路。

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

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

立即咨询