风光互补制氢合成氨容量-调度优化:并网/离网MILP建模与Cplex求解
风光互补制氢合成氨听起来是一条非常完整的链条风机和光伏把电发出来电解槽把水拆成氢氢再和氮在合成塔里生成氨。但真正把它写成一个优化模型麻烦的地方其实不在于化学反应而在于“容量”和“调度”这两个词必须同时出现。设备买多大、每个小时怎么开、开多少彼此纠缠在一起拆开算就会失真。最近我完整复现了一份并网/离网模式下的风光互补制氢合成氨系统容量-调度优化代码求解器用的Cplex环境是Matlab。如果你也正在做新能源耦合化工的规划问题或者正打算把论文里的公式变成能跑出结果的程序这篇复盘应该能帮你少走不少弯路。我不打算把代码逐行贴出来那样又长又没用。真正值得分享的是模型边界怎么定、非线性项怎么线性化、Matlab里怎么把问题交给Cplex、并网和离网的结果差异从哪里来以及我调试时遇到的那几个“看起来像玄学、其实全是逻辑”的坑。1. 复现前先想清楚这个模型的决策边界到底在哪1.1 系统拓扑与可优化设备清单风光互补制氢合成氨系统的拓扑并不复杂。典型的场景是风电场、光伏阵列、电解槽、储氢罐、合成氨装置以及下游氨储存设施。并网模式下还有一条与电网连接的联络线。离网模式下没有联络线系统必须完全依靠自身出力维持全年的功率平衡。在建模之前首先要确认哪些设备容量是决策变量。常见的是风电装机容量、光伏装机容量、电解槽容量、储氢罐容量。有的论文还会把合成氨装置的处理能力也作为待优化变量因为合成氨装置本身价格不低若容量过大全年多数时间低负荷运行成本会很差。容量一旦确定整个系统的投资成本就基本确定了。真正让模型复杂起来的是运行侧的决策。风力和光伏出力是时序曲线电解槽要在每一时段决定电功率和产氢量氢气进入储氢罐后又要决定每个时段多少氢直接送合成氨、多少先储存合成氨装置本身有最小技术负荷和启停限制。除此之外并网模式里还有购电、售电两个决策离网模式里还有弃风弃光和切负荷两个松弛变量。1.2 容量优化与调度优化为何不能拆开做我第一次接触这类项目时直觉是先给定一个风光配比再把运行调度算出来然后调容量参数反复试。这个思路不是完全不能用但结果往往不是最优的甚至会得到明显违背常识的配置例如储氢罐容量很大电解槽却很小导致大部分时段储氢罐都在低位空转或者电解槽容量很大但没有足够的风光出力支撑它全年平均负荷率低得可怜。原因在于容量和调度是一对强耦合变量。储氢罐容量大小决定调度系统能否把中午的光伏富余电力转移到凌晨使用电解槽容量大小决定风光大发时能消化多少绿电合成氨装置的负荷曲线又反过来决定必须有连续的氢源。因此容量优化必须把一整年的运行调度纳入目标中让投资成本和运行成本在同一优化问题里权衡。这类问题在数学上通常建成一个混合整数线性规划MILP。容量变量是整年尺度上的决策像风机台数、电解槽模块数可以是整数运行变量是逐时段决策包含电解槽开关、储氢罐储量、购售电功率等。目标函数里的年化投资和全年运行成本加起来一次性交给求解器。1.3 并网与离网的建模差异不只是删掉一条线很多初学者误以为“离网”就是把并网约束里的电网买卖电变量去掉我没这么做以前也这么以为。实际操作之后才意识到两种模式在模型结构上的差异远不止少两个变量。并网模式下系统可以随时向电网购电或售电。于是每个时段都有一条松弛的功率平衡路径电解槽的运行区间可以更稳定地维持系统对极端天气的应对能力也更强。离网模式下风光为零且储氢不足的时段没有外援纯靠等式约束的话会出现不可行必须引入“允许切负荷”和“允许弃风弃光”的软约束否则在遇到连续阴天或长时间静风时段时模型根本无解。还有一个很实际的问题是氨合成塔的运行特性。并网系统若电解槽停掉还可以从电网买电继续产氢或者至少维持合成氨装置的保温与低负荷循环离网系统如果风光出力短期为零电解槽只能停机合成氨装置是否也要同步停机直接决定模型里要不要加入最小运行时间约束。我在复现时采用的方式是允许合成氨装置降负荷但限制每个调度日必须满足最低氨产量否则储氢罐会变成解决问题的“万能药”一直往里存氢整个配置结果就会失真。2. 数学模型怎么搭Cplex才愿意“好好解”2.1 目标函数年度化成本与收益项优化目标在复现里通常写成最小化年化总成本。投资成本要用资本回收系数把一次性建设费用折算成每年等额支出。如果项目寿命是20年折现率取8%资本回收系数大约是0.10185。这个系数做得对不对影响很大。哪怕容量结果相同年化成本错10%后面分析单位制氢成本或单位氨成本时都会跟着错。运行成本里包括设备维护成本、购电成本、切负荷惩罚成本。收益项包括售电收入和氨销售收入。如果题目是满足外部氨需求可以把氨销售收益省略只保留成本最小。如果允许自由决定产氨量就必须在目标函数中加入氨销售收益否则最优解会倾向于把所有设备容量都设成零毕竟什么都不建成本最低。我在复现时采用的框架是给定全年最小氨产量目标函数最小化投资、运维和运行费用的总和。代码里所有成本量纲统一成万元功率量纲统一成MW储能容量统一成MWh或吨氢时间按小时离散。2.2 关键约束体系建设从电平衡到物料平衡一个相对完整的约束体系大约分成四层。第一层是电功率平衡。并网模式下每个时段的风电出力加上光伏出力加上购电功率等于电解槽耗电、卖给电网的功率以及其他厂用电负荷。离网模式下购电项不存在整体等式变成风光出力等于电解槽耗电与弃电之和。若有切负荷再由变量把不平衡量吸收掉。第二层是电解槽模型。电解槽不是理想设备它有一个最小出力阈值比如额定功率的20%以下不能稳定运行。出于安全考虑某些方案还要求开停机次数受限。如果模型里只考虑功率和产氢线性关系实际上会高估在低功率工况下的设备灵活性。我复现时采用分段线性效率曲线用几个二进制变量表达运行区间问题仍然保持线性Cplex可解。第三层是储氢罐动态。储氢罐的库存方程是经典的状态递推当前时段库存等于上一时段库存加产氢量减送合成氨消耗量。储氢罐本身有上下限还要注意调度周期起点和终点的库存约束不能让模型把年初的氢“偷”出来用。我在部分算例里会把年初库存和年末库存设成相等避免损耗性结果这么做比较严谨。第四层是合成氨装置约束。合成氨负荷通常不能像电解槽一样频繁启停。因此要加入最小运行时间和最大运行时间限制。为简化计算复现代码常把合成氨装置简化成固定单位氢耗和连续可调的负荷区间技术上的波动细节不进入小时级调度模型。2.3 非线性项的处理让Cplex能认出这是MILP风光出力预测值是给定曲线购售电价格也是给定曲线所以电功率平衡是线性的。真正需要小心的是电解槽的耗电-产氢关系和储氢罐的压力-容量关系。很多工程公式包含了平方项或者效率随负荷率变化的非线性项。Cplex擅长的是LP、MILP和QP不能直接求解任意非线性模型。所以必须把非线性曲线分段线性化。以电解槽为例可以将负荷率从20%到100%切成3到5段每段上认为单位产氢耗电量线性变化。用分段法时最好使用SOS2变量或者含辅助二进制变量的Big-M方法。我在Matlab里优先用YALMIP建模因为YALMIP能比较自然地描述逻辑约束和分段函数然后指定Cplex作为求解器。它会在底层把分段线性展开成标准MILP。如果不想引入YALMIP直接调用Cplex的cplexmilp一样能解但需要自己把所有约束写成矩阵形式变量一多就很容易出错。大M参数是另一个常见隐患。如果M给得过大可能会破坏数值稳定性给得太小又可能错误地截断可行域。我的习惯是先找变量物理边界M只取比边界大20%到30%的值而不是随便填一个1e6。这一点在离网模式下尤其重要因为有些约束在极端时段的量级和常规时段差得很远。2.4 离网模式里的“软约束”设计离网系统如果完全不允许切负荷模型一定会把风电和光伏容量配得很大甚至出现全年很多时段都在弃电的结果经济上很浪费。现实中更合理的设计是允许少量切负荷但成本极高由模型在“增加一台风机”和“承受少量切负荷惩罚”之间做选择。我在复现代码里给离网模式增加了两个非负松弛变量弃风弃光电量和失负荷电量。失负荷电量乘以一个很高的惩罚系数进目标函数惩罚值一般设成系统所有方案单度电综合成本的若干倍确保正常情况下不会切负荷但在连续极端天气下也能给出可行解。这个做法让求解器从“报错无解”变成“给出一个经济上最优的可靠性折中方案”这是整个模型能否跑通的关键一步。3. Matlab应用里的实际工程化做法3.1 用什么方式调用Cplex我最终选了YALMIPMatlab调用Cplex常见有三条路线直接用Cplex的Matlab工具箱函数、通过YALMIP建模后指定Cplex、通过Tomlab等商业建模工具。对于这种规模的项目我最推荐第二种。YALMIP对变量的命名、约束的拼接和表达式的检查都很方便。尤其是涉及整数变量和分段约束时代码可读性会高很多。求解命令非常简单ops sdpsettings(solver, cplex, verbose, 2, showprogress, 1); result optimize(Constraints, Objective, ops);如果一定要用底层接口Cplex工具箱里最常用的是cplexmilp。它要求输入目标函数的系数向量、线性不等约束、等式约束、变量上下界以及整数变量的索引。用这种接口写全部约束维护成本比较高适合固定模型参数后反复求解如果你还需要继续改模型建议YALMIP。我复现时的经验是先用YALMIP写出一版带注释的清晰模型作为母版再为大规模算例生成一个优化的Cplex热启动版本这样调试和性能测试可以兼顾。3.2 变量定义和维度管理别让矩阵把自己绕晕容量层面风电光伏的额定容量可能是一维变量电解槽台数是整数变量运行时段的变量是二维数组。我建议先做好所有数据的维度命名再开始写约束。常见方法是全部用一行变量向量表示并维护边界索引避免三维数组进入矩阵转置的噩梦。下面是一段示意性的变量定义格式不源于任何特定项目仅供梳理思路% T: 调度时段数, 比如 8760 % 容量变量 P_wind sdpvar(1, 1, full); % 风电场额定容量 MW P_pv sdpvar(1, 1, full); % 光伏额定容量 MW E_h2 sdpvar(1, 1, full); % 储氢罐容量 MWh n_el intvar(1, 1); % 电解槽模块数 % 调度变量 p_wind_t sdpvar(T, 1, full); % 各时段风电出力 p_pv_t sdpvar(T, 1, full); % 各时段光伏出力 p_el_t sdpvar(T, 1, full); % 各时段电解槽输入电功率 q_h2_t sdpvar(T, 1, full); % 各时段产氢量 s_h2_t sdpvar(T1, 1, full); % 储氢罐库存 buy_t sdpvar(T, 1, full); % 购电功率 sell_t sdpvar(T, 1, full); % 售电功率用这种方式建模之后每写一条约束都可以先用Constraints [Constraints; ...]追加极大减少遗漏。别小看这件事我在第一次写的模型里就曾经把库存下标写错一位导致结果里出现储氢罐自产氢的假象这种问题光看逻辑表达式难以发现。3.3 典型日聚类与全年时序的取舍容量-调度问题的要害是全年运行成本。如果直接使用全年8760小时变量数量会瞬间上升到几十万对Cplex来说虽然能解但调试时每次等几小时不现实。常见的做法是用聚类算法从全年数据中挑出若干典型日然后按天数汇总。我在复现时先用kmeans对风电出力、光伏出力和负荷三类曲线做季节聚类筛出比如12个典型日每个典型日赋一个权重表示这个典型日代表多少真实天数。这样做可以把变量规模缩小一半以上求解速度显著提升。聚类的缺点是会丢掉极端场景。因此最终校核时我会把优化得到的容量配置重新放回全年8760小时时序里做固定容量下的可行性调度验证是否会有不满足的时段。这个校核步骤虽然增加工作量但能有效防止聚类带来的乐观偏差。3.4 求解参数配置容忍一个小gap速度提升很明显对于这种规模的大型MILP追求1e-6的严格最优解不现实也没必要。容量变量本身基于预测数据精度不需要到百分之一。我在代码里把Cplex的MIP相对gap设置为1%到2%也就是求解器在找到一个解后只要证明它与最优解的差距不超过该范围就停止。这样求解时间往往能从几小时降到十几分钟。Cplex还有一些很实用的参数值得试启停变量可以优先分支MIP节点内存设置上限防止内存爆炸算法选择上连续LP用barrier或dual simplex都可能带来性能差异。不过任何参数调整都必须基于实际算例测试不要照抄别人的设置。4. 算例观察并网和离网的调度结果藏着哪些规律4.1 先用一组示例参数把两种方案跑出来由于不同论文的系统参数差异很大我不单独给某一组数据背书。下面这张表是我在自测代码时用一组常见的成本假设跑出来的示例数字本身不代表某个特定工程但它可以很好地说明如何分析结果。指标并网模式离网模式风电装机容量MW4876光伏装机容量MW2635电解槽额定功率MW2228储氢罐可用容量MWh1842全年弃电率8%18%购电比例约35%0年化综合成本万元920012800从这张表里能看到两个很鲜明的结论。第一并网模式下系统可以利用电网作为“虚拟储能”光伏大发时多产氢晚间低电价时购电继续产氢所以储氢罐不需要太大。第二离网模式下一切都要靠自己风电和光伏装机必须大幅加码储氢罐容量也会翻倍增加否则全年出力曲线里的低谷无法跨越。年化成本因此高出约四成。4.2 并网调度策略电价与风光资源共同驱动看并网模式典型日调度曲线最直观的特征是电解槽功率几乎不会长时间为零。低电价时段哪怕风光出力很小模型也会从电网买电维持电解槽运行因为合成氨装置不能断氢电解槽停机再重启的成本要大于买电成本。高电价时段如果风光出力充足系统会选择把多余绿电卖给电网而不是全部用于制氢此时电解槽可能降负荷运行。有人会问这种模式下绿电比例会不会不足如果模型中没有加入“年购电量占比上限”或“绿氢比例下限”优化结果确实可能偏向多购电、少自建风光。论文里通常会把绿电比例作为约束写入并网场景如果代码里没有这个约束解读结果时要谨慎。我复现时加入了一个约束全年计入系统制氢消耗的购电量比例不超过某个设定值否则合成的“绿氨”名不副实。4.3 离网调度策略天气低谷要依靠储氢时间平移离网模式下比较典型的调度场景是连续两天风速低、光伏在午间才有一些出力。白天光伏出力驱动电解槽产氢一部分直接进入合成氨系统另一部分先存进储氢罐。夜间光伏归零如果此时现场风速也不够电解槽只能降功率或停机合成氨装置依靠储氢罐里的存量维持。随着时间推进储氢罐库存像蓄电池一样被持续释放直到下一个风光大发阶段。因此离网模式的最优容量往往由连续不利天气的持续时间决定而不是平均风速和平均辐照。只看年平均风光数据做容量设计一定会低估储氢需求。我在算例里专门截取了一段持续阴天的时间窗口做可行性检验发现有几次最优配置在普通典型日里表现很好一旦放到持续阴天场景就会因为储氢容量不足发生切负荷。5. 复现过程中最值得记录的坑和排查思路5.1 报infeasible不要慌先用Cplex的冲突分析第一次跑完整离网模型我遇到的是求解器直接返回infeasible。不成熟的排查方式是随机删减少数约束看能不能解出来。更系统的做法是启用Cplex的冲突分析器让它在约束集合里找一组造成矛盾的小型约束子集。在YALMIP环境下可以通过solvesdp的cplex反馈和Cplex诊断信息来定位但不够直观我是通过把模型存入LP文件再用Cplex交互命令的conflict指令来查的。检查下来最常见的不可行原因有三个。一是储氢罐初始库存设为零但最早几个时段的风光出力不足以同时满足合成氨需求和为电解槽供电模型必须从第一时刻就缺氢。解决方法是允许初始库存为一个待优化变量同时规定调度结束后的库存不得低于初始值。二是离网模式下把切负荷惩罚设得过低导致模型把切负荷当成一种廉价选项但外部需求又强约束氨产量必须全年满足两者产生冲突。三是年末库存等于年初库存的约束写错了方向等于把“必须存下多少氢”变成强制目标超出储氢罐容量。5.2 电解槽分段线性化导致整数变量爆炸把电解槽负荷范围分成5段再叠加启停变量零一变量数量会成倍增加。对于一个有几千个时段的算例二进制变量可能达到几万个Cplex求解时间会非常离谱。我发现一个有效的压缩方法是不对每个时段都引入完整的分段选择变量而是只在电解槽开机的时段引入。如果停机时段的负荷为0此时不需要分段变量可以用大M约束把产氢量强制为0。这样虽然增加了一点约束复杂度却可以大幅减少非零时段的整数变量。还有一类更“隐蔽”的整数变量来自合成氨装置的运行状态。如果允许合成氨装置每天启停模型可以省掉储氢罐但这在实际系统中不现实。我在代码里设置了一个最小连续运行时长约束比如“一旦启动至少连续运行若干天”用线性不等式表达累计状态。效果很好但要注意最小连续运行时间的约束数量不要随着时间窗口无限膨胀否则求解器又要卡住。5.3 单位不统一离网结果偏大偏小基本都是这种问题这是很多复现者最容易忽视的坑。风光出力常用MW电解槽耗电量常见单位是kWh/Nm3氢气储氢罐容量可能是kg或吨合成氨产量又是吨只要有一个换算系数出错整个容量配置就会同比例漂移。我调试时有个习惯把所有涉及物质流量的参数在处理阶段就统一换算成“等效能量”或“统一当量”。比如氢气按热值折算成MWh氨产量按单位氢耗折算成所需氢量那么储氢罐的上限就直接以MWh氢表示。所有参数进入模型前打印一遍单位变换结果寻找任何“数量级离谱”的中间量。Cplex并不会关心你的单位是否符合工程常识它只负责在给定约束下找最优值。一旦目标中某个系数大了一千倍求解结果就会悄悄偏向某类设备。5.4 结果看着反直觉时按固定顺序排查有一次并网模式的结果显示全年几乎不采购电系统却配置了很大的风电和光伏容量弃电率也很高。初看似乎是模型在刻意追求高自供率。仔细检查后发现是售电价格单位被我写错了目标函数中售电收益被放大成一个天文数字于是模型疯狂多装风光去卖电制氢反成了附属品。这提醒我结果反直觉时先查价格类参数再查容量年化系数最后才怀疑约束逻辑。顺序基本不要反。另一次离网模式下储氢罐永远是满的无论怎么调参数都不释放氢。后来发现储氢罐单位容积的投资成本太低几乎相当于免费储能模型当然会倾向把所有氢都存起来。给储氢罐一个更真实的成本系数后结果立刻恢复正常。这是典型的“参数决定行为”案例。6. 复现完成后还能向哪些方向延伸6.1 把确定性模型升级为多场景随机优化这类容量规划对风光资源的不确定性非常敏感。目前很多论文停留在“使用确定性典型年”的阶段但工程上至少要考察多风年、少风年、正常年三种场景。你可以在现有Cplex模型上改造成两阶段随机规划第一阶段决定容量第二阶段在每个场景中做运行调度目标是期望总成本最小。这样做变量和约束会随场景数量线性增加Cplex仍然能处理只是求解时间会更长。如果你觉得随机规划直接上太重还有一个折中方法在确定性模型里增加若干极端天气日并把这些日的24小时曲线硬塞进典型日聚类集合给它们一个适当的小权重。这样既不用重构模型也能让容量决策更稳妥地避开极端的连续无风光期。6.2 加入碳约束和绿色认证的量化逻辑绿氢和绿氨的价值很大一部分在于碳减排。现有模型如果要关联碳价格只需要在目标中增加一项碳排放成本并在并网模式中区分电网购电隐含碳和风光自产电的零碳属性。这样做之后并网优化会倾向于通过减少购电来降低碳排放惩罚。同理如果卖给外部市场的是“绿氨”还需要有严格的时间匹配或者电网绿电采购凭证约束。把碳成本从无到有、从低到高扫一遍你会看到容量配置会从“接近经济调度”逐渐变成“更大风光、更大储氢、更少购电”的演化路径。这条曲线很有价值直接服务于项目投资决策。如果你现在的代码还只有硬性绿电比例约束可以试试改成碳价格进入目标很多情况下更符合政策逻辑。6.3 从单厂规划走向园区综合能源管理随着代码跑通你会发现这套容量-调度联合优化的框架不只适用于合成氨。任何涉及可再生能源、电解水制氢、下游化工/交通负荷的系统都可以换成不同的物料流参数复用。如果再加入电池储能、热电联产或余热回收模型结构会更复杂但核心依旧是“容量投资小时级调度”的MILP骨架。这些扩展方向都建立在你现在把并网和离网两条路径都跑扎实的基础上。一个能解释清楚的模型边界比一个参数堆砌得满满、结果却解释不通的“大而全模型”要有用得多。复现代码的过程本质上也是在补足自己对系统运行逻辑的认知。把这套模型当作一个工具箱而不是一份交差的作业之后遇到任何新能源加化工的优化问题你都能很快找到切入点。