并/离网风光互补制氢合成氨容量-调度优化:Matlab+Cplex完整复现

📅 发布时间:2026/10/6 9:31:06
并/离网风光互补制氢合成氨容量-调度优化:Matlab+Cplex完整复现
这个并/离网风光互补制氢合成氨系统容量-调度优化分析的复现项目核心是用Matlab调用Cplex求解一个混合整数线性规划问题既要决定风机、光伏、电解槽、储氢罐、氨合成装置装多大容量又要在并网和离网两种模式下安排每个小时的电力和氢气流向。我把它从头到尾完整复现过一遍这篇就记录我建模、求解、调参和踩坑的整个过程包含可以直接参考的模型表达式、YalmipCplex代码框架和一批实测下来很管用的调试技巧适合正在做绿氨、制氢系统优化或者打算用Cplex求解器做园区能量调度的朋友。1. 项目在做什么并/离网风光制氢合成氨的优化需求1.1 系统架构与能流逻辑风光互补制氢合成氨本质上是把不稳定的可再生能源电力转成可储存的氢再进一步合成氨利用氨的储运优势把能量固化下来。系统里的能流方向很清楚风机和光伏发出的电一部分直接进入电解槽制氢另一部分可能卖给电网并网模式或者被储能设备吸收电解槽产出的氢气存入储氢罐储氢罐再向氨合成装置供氢配合空分得到的氮气在高温高压下生成氨。这个流程听起来简单但做优化的时候就麻烦了。风电和光伏出力天然波动电网购电电价又分峰谷储氢罐的容量会同时约束制氢和合成氨两个环节的节奏合成氨装置还有最小运行负荷和启停代价。所有这些因素耦合在一起单纯拍脑袋定容量一定会出问题。并网和离网是两种完全不同的运行逻辑。并网模式下系统可以把电网当成一个大容量缓冲池电价低的时候多买电制氢电价高的时候卖电赚差价电网交互功率有上下限约束。离网模式下没有电网兜底系统必须靠储氢罐或者储能电池来扛波动容量配置上通常要求风机和光伏装得更大电解槽和储氢罐也要留裕量否则晚上风小或者阴天连续几天合成氨装置就只能停摆。1.2 为什么容量和调度必须放在一起优化很多刚接触这类问题的人会犯一个错误先把风机、光伏、电解槽的容量定死再去算运行调度。这个思路问题很大。容量决策决定了全年8760小时里系统的运行边界而调度结果又决定了设备投资的收益能不能收回来两者是相互决定的。举个例子储氢罐容量如果只够缓冲12小时那么调度策略就倾向白天光伏多的时段制氢、夜间合成氨继续消耗全年氨产量被储氢容量卡住但如果储氢罐放大到48小时系统就能在连续大风天大量制氢囤积调度曲线完全不一样氨产量可能翻倍储氢罐的投资也能算得过来账。这就是容量和调度必须耦合优化的根本原因。从数学上看这类问题常常被描述成双层优化上层决定容量下层在上层给定的容量下做运行调度。但在这个具体项目里只要所有约束和成本项都是线性的容量变量和运行变量可以直接放进同一个混合整数线性规划里一次性求解我在后面第3章会详细展开。1.3 适用场景与预期输出这类复现项目的成果一般会落在三个层面规划设计层面给出一份设备容量配置表包括风机台数、光伏装机、电解槽额定功率、储氢罐容量、氨合成装置规模。运行调度层面给出一组典型日的24小时功率分配曲线显示出什么时候制氢、什么时候卖电、储氢罐怎么充放。经济分析层面算出年化总投资、年运行成本、售氢/售氨收入以及用平准化成本衡量的单位制氢成本和单位氨成本。你要是正在做绿氨项目的前期可研、园区多能互补规划或者单纯想复现论文里的算例这整个流程都能直接拿去改参数用。2. 优化模型设计从工程问题到数学表达2.1 设备建模与年化成本先看设备侧。风电和光伏的出力序列是输入数据由历史风速和光照数据经过功率曲线模型得到这部分直接给成一个逐小时序列不参与决策变量。实际建模时要注意风电出力序列要区分理论可发功率和实际使用功率因为高风时段可能用不完会产生弃风弃风量本身就是调度变量。电解槽的建模稍微复杂一点。它的输入功率可以在0到额定容量之间连续调节但一般建议加一个最小运行负荷约束比如不低于额定功率的10%因为电解槽频繁深度降负荷会损伤催化剂实际工程里也很少让它低于某个下限运行。电解效率可以近似为常数但如果你想更精细也可以把电耗率设成分段线性函数这会增加一些0-1变量不过Cplex处理起来完全没问题。储氢罐用能量状态变量建模数学上就是一个动态方程当前时刻储氢量等于上一时刻储氢量加上本时段制氢产氢量减去送往合成氨装置的耗氢量再考虑储氢罐自身的充放损失。储氢容量在调度问题里是给定参数但在容量优化问题里是决策变量。氨合成装置也有两个关键参数单位氨产品的耗氢量、电耗量。合成氨是连续化工过程启停代价巨大所以模型里通常引入一个0-1变量表示装置是否运行并加最小连续运行时间约束或者至少加最小运行负荷约束避免求解器给出每两个小时开关一次这种不现实的方案。投资成本的处理是很多复现稿翻车的地方。一次性投资必须按寿命期折算成年值用的是资金回收系数CRFCapital 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(T1,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(t1) 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_grid0, P_buy0)]; 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×2472个0-1变量Cplex很快能解。但如果你把周期拉到8760小时0-1变量数量直接到3×876026280个求解难度是几何级数上升。这就是为什么前面强调用典型日法而不是全年直接建模。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(t1) 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依然是求解这类问题的第一选择。希望这篇能帮你把这个项目跑通少走点我走过的弯路。