ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

风光互补制氢合成氨容量-调度双层优化与Cplex求解

风光互补制氢合成氨容量-调度双层优化与Cplex求解 最近在复现一篇关于风光互补制氢合成氨系统的容量-调度优化论文用的求解器是Cplex代码环境是Matlab。断断续续啃了两周踩了好些坑也把整个系统的建模逻辑捋清楚了。这篇文章就把这次复现的完整思路、模型构建、Cplex接入方式和实际调试中遇到的问题都整理出来给准备做或者正在做类似方向的朋友做个参考。这套系统值得复现的原因在于它不是单纯的微电网容量配置也不是简单的制氢调度而是把风电光伏→电解制氢→储氢→合成氨这条完整的能源化工链条放进一个优化框架里同时考虑容量规划装多少风机、光伏、电解槽、储氢罐和运行调度每个小时风电出力怎么分配、电解槽开多大、氢怎么用。这类问题在综合能源系统研究和工程规划里非常有代表性用Cplex求解是因为模型最终可以转成混合整数线性规划MILP来精确求解相比启发式算法最优性有保证。无论你是做新能源消纳、绿氢化工、综合能源系统优化还是单纯想看看Cplex在Matlab里怎么解这类大规模调度问题这篇文章应该都能提供一些实打实的帮助。1. 看标题之前先搞懂这条能源链路的优化对象风光互补制氢合成氨系统说白了就是把不稳定的风光电转化为可储存、可运输的化学品。这个系统不是几个设备随便连起来而是涉及到一条多级能量传递链路每一级都有容量配置和运行约束的问题优化起来远比单一的风光电站或者单独的制氢厂复杂。1.1 从风光发电到合成氨的完整工艺流程整个系统的能量流动是单方向的风力发电机组和光伏阵列产生电能电能通过整流变流设备输送给电解槽电解槽将水电解为氢气和氧气氢气经过纯化、加压后进入储氢装置然后送入合成氨装置与氮气反应生成合成氨。最终产品是氨它的好处非常多——常温常压下是液体储存运输方便既可以直接用作肥料原料也可以作为一种氢的载体。这也是为什么很多风光资源丰富的地区都在布局风光制氢合成氨一体化项目本质上是解决风光电的消纳和氢的储运难题。这个系统里每一级设备都不是独立存在的上一级的输出直接决定下一级的可用资源。比如电解槽的耗电量决定了它需要风光出力的支撑储氢罐的容量决定了它能缓冲多少氢气的供需不匹配合成氨装置的年运行小时数又反过来约束了系统的有效产出。优化的时候任何一个环节的容量变化都会传导到整个系统这就是典型的牵一发而动全身。用一句话来概括这个优化问题的本质在满足制氢和合成氨需求的前提下用最小的年化总成本去配置各个设备的容量并且给出每一小时的最优运行策略。1.2 容量优化和调度优化分别回答什么问题这里要区分两个层次的优化很多第一次接触的人容易混。容量优化Capacity Optimization回答的是建多大规模的问题。风电装机多少兆瓦、光伏装机多少兆瓦、电解槽额定功率多大、储氢罐能装多少立方米的氢气、合成氨装置的年产能是多少。这些决策变量是设备规格层面的一旦定了项目初投资就基本确定了。容量优化通常在年尺度或典型日尺度上进行因为它关心的是长期规划方案。调度优化Scheduling/Dispatch Optimization回答的是每个时刻怎么运行的问题。给定设备容量之后在8760个小时中每个小时风电和光伏实际出力多少、电解槽输入功率多少、储氢罐充放氢多少、合成氨装置是满负荷跑还是降负荷跑。这些决策变量是运行层面的对应的是系统每天的运行成本、购电成本、收益等。容量优化和调度优化之间存在嵌套关系不同的容量配置下同一个调度策略产生的运行成本是不同的而运行成本又会反过来影响最优容量选择。这就是为什么说这是一个容量-调度双层耦合优化问题。在数学上常见的处理方式是用双层优化结构外层负责容量内层负责调度外层每给定一组容量方案内层就求解一次最优调度并返回运行成本。这里是典型的设置示例容量变量记作X_cap调度变量记作X_op外层目标函数是年化投资年运行成本的最小化内层目标函数是给定容量下的最小化运行成本外层容量优化 min (年化投资成本 年运行成本) s.t. 容量取值约束内层调度优化在外层给定容量后调用 min 年运行成本 s.t. 逐时功率平衡、设备运行约束、储氢动态约束容量和调度不是分开拍脑袋定的而是在同一个目标函数下互相制约、共同寻优。这也是这类系统分析比单层优化要麻烦的核心原因。1.3 为什么选Cplex而不是遗传算法这个问题在复现论文时经常被问到。很多文献里做容量优化用的是粒子群、遗传算法或者NSGA-II因为容量变量和调度变量混在一起形成非线性、非凸问题时数学规划工具不好直接处理。但这篇标题明确写的是Cplex求解说明它走的是精确求解路线——把问题建模成MILP然后用Cplex的分支定界算法去求全局最优解。用Cplex的优势非常明显它有全局最优性证明只要模型建得对收敛到的就是数学意义上的最优解不需要像启发式算法那样反复调种群大小、变异率、交叉率也不用担心收敛到局部最优。代价是建模时必须做线性化或者近似处理把非线性关系转成线性约束和整数变量模型规模上去之后对内存和求解时间的要求也高。以我的复现经验来看如果系统规模控制在单节点、数十个设备变量、8760个时段Cplex直接求解MILP是完全可行的如果再加设备数量或者把时间分辨率细化到15分钟就需要慎重考虑模型规模和求解性能了。后面具体说怎么控制规模。2. 容量-调度双层优化模型的建模逻辑与数学表达要做优化第一步就是把实际问题翻译成数学语言。这个翻译过程看起来简单实际上很多细节处理不好最后求解出来的结果根本不能用。下面按我的建模顺序拆开讲。2.1 外层容量规划的决策变量和目标函数先列出外层的决策变量集合。以我复现的模型为例容量变量一共5组风电装机容量 P_w_cap单位MW光伏装机容量 P_pv_cap单位MW电解槽额定功率 P_ele_cap单位MW储氢罐最大储氢量 V_h2_cap单位kg或Nm³合成氨装置产能 P_amm_cap单位t/h或kg/h外层目标函数是最小化年化总成本包含三个部分第一是年化投资成本。设备的一次性建设投资不能直接加进年成本里需要乘以资金回收系数CRF换算成年均等值支出。CRF的计算公式是CRF r × (1 r)^N / ((1 r)^N - 1)其中r是贴现率一般取6%到8%N是设备生命周期风电和光伏一般取20年电解槽取15年合成氨装置取20年。年化投资成本就等于每个设备的单位投资成本×容量×CRF加总之后得到总投资年均支出。假设风电单位投资成本k_w、光伏k_pv、电解槽k_ele、储氢罐k_h2s、合成氨装置k_amm那么年化投资成本表达式为C_inv_annual CRF_w × k_w × P_w_cap CRF_pv × k_pv × P_pv_cap CRF_ele × k_ele × P_ele_cap CRF_h2s × k_h2s × V_h2_cap CRF_amm × k_amm × P_amm_cap第二是年运行维护成本。一般按设备投资的一定比例估算比如风电运维费取总投资2%左右光伏取1.5%电解槽取3%到5%。第三是年运行成本这部分得靠内层调度优化返回包括从电网购电的费用、弃风弃光的惩罚成本如果有、设备启停损耗等再减去卖电收益并网场景。外层约束主要是容量的上下限范围比如风电装机不允许超过当地可开发资源的上限电解槽功率和风光总装机之间可以有粗略的比例关系约束储氢罐容量要覆盖合成氨装置连续运行的用氢需求等。2.2 内层调度优化的决策变量与逐时约束内层调度优化的核心是对每个时段tt1,...,T做运行决策决策变量包括风电上网出力 P_w_t光伏上网出力 P_pv_t电解槽输入功率 P_ele_t储氢罐充放氢流量 H_in_t、H_out_t一般统一成储氢量变化量合成氨装置耗氢量 H_amm_t并网场景下还有购电功率 P_buy_t、售电功率 P_sell_t内层的功率平衡约束是系统运行的核心任何时刻风电和光伏的实际出力加上购电功率如果并网必须等于电解槽耗电、合成氨装置辅助用电、再加上对外售电。写成线性约束是P_w_t P_pv_t P_buy_t P_ele_t P_aux_t P_sell_t所有的功率变量都含时间下标这一条约束在8760个小时里每个小时都要成立。这是整个模型里约束数量的大头。电解槽运行约束也比较关键。电解槽不是任何功率下都能运行它有最低运行负荷率一般需要在额定功率的10%到20%以上才能稳定运行同时不能超过额定功率。而且电解槽启动和停机的状态变化需要用二进制变量表示否则无法约束不能频繁启停或者最低运行时间这类逻辑。引入二进制变量u_ele_t代表电解槽在第t时段是否开机约束如下P_ele_min × u_ele_t ≤ P_ele_t ≤ P_ele_cap × u_ele_tP_ele_min是电解槽的最小运行功率P_ele_cap是额定功率。这组约束的作用是如果u_ele_t0则P_ele_t强制为0如果u_ele_t1则P_ele_t落在允许范围内。储氢罐的动态平衡约束需要特别小心它连接了制氢和用氢两侧。第t时段末的储氢量等于第t-1时段末的储氢量加上本时段电解槽的产氢量减去合成氨装置的耗氢量S_h2_t S_h2_{t-1} η_ele × P_ele_t / HHV_h2 - H_amm_t其中η_ele是电解槽效率HHV_h2是氢的高位热值用来把电功率折算成产氢量。S_h2_t必须始终在0到V_h2_cap之间0 ≤ S_h2_t ≤ V_h2_cap还有一个隐蔽的约束一个完整调度周期的初始和末尾储氢量要一致或者至少让末尾储氢量不小于初始值否则优化结果会利用把氢用光来占便宜导致结果在工程上不可行。这个问题我后面专门讲。2.3 双层问题的求解策略KKT转化还是迭代逼近这里需要解释一下双层模型的求解方法。外层容量变量给一组值内层调度问题就是一个带这些容量参数的MILP可以直接用Cplex求。但外层容量变量要搜索最优值不能随便枚举因为连续变量组合空间巨大。我复现时采用的是一种工程上非常实用的迭代求解策略初始化一组可行的容量配置将容量变量作为参数传入内层调度模型用Cplex求解得到该容量下的年运行成本把内层求解得到的运行成本作为外层容量优化目标函数的一部分等价于外层每评估一组容量就会调用一次内层求解外层用合适的搜索算法可以用Cplex的外层MILP建模也可以使用粒子群之类的元启发式算法驱动但考虑到标题明确说Cplex求解更稳妥的方式是把容量变量也建成MILP和内层约束做适当整合后一并求解检查前后两次迭代的最优目标函数值差值小于收敛阈值就停止。还有一种理论更严谨的方式是KKT条件转化当内层问题是线性规划时可以写出内层问题的KKT条件把内层问题整体等价为一组约束塞进外层的单层MILP中。这个方法的问题在于引入互补松弛条件后模型变成非线性还得用大M法线性化模型规模膨胀得很厉害实际求解效率并不理想。而且如果内层是MILP有整数变量KKT条件就不成立了。所以我的建议是如果内层没有设备启停整数变量KKT转化值得尝试但只要有电解槽启停这些二进制变量老老实实用迭代法或者直接构建单层MILP反而更快。3. Cplex接入Matlab的环境配置与常见坑既然标题明确了Cplex求解Matlab代码实现的前提就是把Cplex的Matlab接口调配好。这一步看起来简单实际是很多初次接触的朋友卡壳最多的地方。这里把完整的配置过程和报错处理写清楚。3.1 安装与路径配置的完整步骤第一步是安装CPLEX Optimization Studio。IBM官网上可以下载最新版本教育版用户可以申请免费学术许可证。社区版Community Edition也可以免费用但它有变量和约束数量的限制通常限制在1000个变量和1000个约束以内复现这个例子的话模型规模很可能超限建议直接用完整版或者学术版。安装完成后打开Matlab通过下面的命令把Cplex的Matlab API路径加进去。以CPLEX Studio 12.10在Windows下的默认安装路径为例addpath(C:\Program Files\IBM\ILOG\CPLEX_Studio1210\cplex\matlab\x64_win64);路径取决于具体的安装版本和目录。设置好之后验证一下cplex Cplex(test); cplex.solve();如果能够正常创建并求解一个空模型说明接口已经通了。为了避免每次重启Matlab都要重新设置路径建议用Matlab的预设路径功能把上面的目录永久保存或者在项目启动脚本startup.m里写上addpath语句。3.2 用Cplex类构建MILP模型的基本代码骨架Cplex在Matlab中的API比较简洁核心是用Cplex对象的方法添加目标函数、变量和约束。以一个小规模调度模型为例代码骨架如下% 创建模型对象 cplex Cplex(scheduling_opt); cplex.Model.sense minimize; % 求最小值 % 目标函数系数向量 f与变量顺序一一对应 cplex.Model.obj f; % 变量下界、上界和类型 cplex.Model.lb lb; cplex.Model.ub ub; cplex.Model.ctype ctype; % C 连续变量B 0-1变量I 整数变量 % 线性约束: lhs A*x rhs % 将等式约束拆成 lhs rhs 即可 cplex.Model.A A_sparse; % 注意要用稀疏矩阵 cplex.Model.lhs lhs; cplex.Model.rhs rhs; % 求解 cplex.solve(); % 提取结果 x_opt cplex.Solution.x; obj_value cplex.Solution.objval; cplex.Status对于8760个时段的调度模型变量数量轻松超过数万个约束矩阵用稀疏矩阵存储是必须的不然内存直接爆掉。Cplex自己的底层引擎支持大规模稀疏线性规划但Matlab端传入的稀疏矩阵格式要处理好每个变量和每条约束的顺序要和目标函数系数严格对应。3.3 配置过程中最常见的三类报错我在配置时遇到并且帮别人解决过的报错基本集中在三类。第一类是未定义变量cplex或类Cplex。这个报错九成是因为没加路径或者路径加错了层级。CPLEX安装目录下包含了cplex/matlab/x64_win64、cplex/matlab/demo等多个子目录要用x64_win64那个版本匹配Matlab位数。升级Matlab版本后可能还需要重新配置一次。第二类是许可证相关的报错比如No valid CPLEX license或者CPLEX Error 1016。这个通常是许可证服务没启动或者环境变量CPX_LICENSE_FILE没设置。如果用的是许可证服务器需要在系统环境变量里指定serverhost;如果是本地节点式许可证node-locked安装的时候会自动配置但也容易因为Matlab以管理员身份运行时权限路径不一致导致找不到。第三类是模型求解时Matlab卡死或者内存溢出这个不一定算配置错误更多是模型规模问题。解决办法是检查约束矩阵的稀疏性去掉多余的变量适当把时间精度从1小时扩展到2小时或4小时来减少规模。还有就是用分段线性化代替非线性约束以减少整数变量。4. 并网与离网场景的建模差异与结果对比标题里并_离网三个字意思是并网和离网两种运行场景都要分析。这两种场景虽然系统主体相同但数学建模差别非常大优化结果也会呈现出有意思的规律。4.1 并网模式的功率平衡与购售电建模并网模式下系统与大电网之间存在能量交换。每个时段允许从电网购电也允许向电网售电。这个看似简单的改动实际会显著改变优化结果。并网模式的功率平衡约束改为P_w_t P_pv_t P_buy_t P_ele_t P_aux_t P_sell_t新增的变量P_buy_t和P_sell_t都有边界约束购电功率不能超过与电网签订的协议容量上限售电功率也有限制购电和售电不能同时发生这个逻辑约束用二进制变量可以表达P_buy_t ≤ M_pbuy × u_buy_t P_sell_t ≤ M_psell × u_sell_t u_buy_t u_sell_t ≤ 1经济上讲购电电价一般按峰谷平三段或者分时电价来计算售电电价又分上网电价和市场化交易电价。引入购售电之后系统实际上多了一个电网储能的调节手段——风光出力大发时可以把多余电卖出去风光出力不足时可以从电网买电维持电解槽运行。这会直接影响最优容量配置离网时需要靠增加储能或者提高风光装机来保证供电并网时可以靠电网兜底所以往往并网方案的风光装机容量和储氢容量会比离网方案小但多出一项购电成本。目标函数里多了一组购售电费用项C_grid Σ_t (price_buy_t × P_buy_t - price_sell_t × P_sell_t)分时电价政策对调度策略影响很大。我实测的结果是在峰谷电价差足够大时优化模型会自动选择在低谷时段多买电制氢、高峰时段少用电甚至卖电电解槽的利用曲线明显跟着电价走。4.2 离网模式的供电可靠性约束离网模式完全不同。没有任何外部电网支援任意时刻所有负荷只能由风电和光伏承担。功率平衡约束直接简化为P_w_t P_pv_t P_ele_t P_aux_t这看上去只是把P_buy_t和P_sell_t去掉的问题但背后藏着极大的建模差异。离网模式下系统必须有足够的容量冗余应对风光出力的波动否则就会出现供电不足。为了保证系统可行需要引入可靠性约束常用的做法是指定最大允许的失负荷概率Loss of Power Supply ProbabilityLPSP或者设置系统最小备用容量系数。以LPSP约束为例需要引入一个表示切负荷量的变量P_cut_t当风光出力不足时允许部分负荷被切除约束设置为Σ_t P_cut_t / Σ_t (P_ele_t P_aux_t) ≤ LPSP_max同时功率平衡变成P_w_t P_pv_t P_cut_t P_ele_t P_aux_t这样模型在极端天气时段可以断电但是全年累计的缺电比例不能超过设定值。实际操作时这个约束还会导致模型中出现P_cut_t和P_ele_t的乘积项如果电解槽功率也要跟着削减需要线性化或者直接假定电解槽是可以灵活调节的负荷缺电时优先削减电解槽功率这在实际控制系统里也是合理的。4.3 两种场景下的容量配置对比我复现并网和离网两种场景后得到的结果规律性很强。以一套典型的风光资源和负荷参数为例此处参数来自常见工程案例区间具体数值根据当地资源重新标定两种场景下的最优配置差异大致如下优化结果离网模式并网模式风电装机容量高需要余量应对无光时段中低电网可兜底光伏装机容量高日照时段多出力中低电解槽额定功率偏高尽量多消纳风光电随电价策略波动储氢罐容量明显偏大长期储能缓冲相对较小系统年化总成本较高较低购电成本部分抵消弃风弃光率尽量低允许一定弃风弃光离网场景下储氢罐实际上承担了跨日甚至跨季的缓冲作用——新疆、内蒙这类地区冬季风光出力低连续几天阴天或者无风的时候只能靠储氢罐里攒下的氢维持合成氨装置运行。所以模型会自动把储氢罐容量配得比较大。并网场景下电网承担了部分缓冲作用储氢罐可以适当缩小。总成本往往比离网低但这时候要小心一个伪优化陷阱如果购电电价过低模型可能会大幅缩小风光装机导致系统名义上是风光互补制氢实际大部分电力来自电网这与项目初衷相悖。解决办法是给风光发电占比或者系统综合可再生能源利用系数加个下限约束比如要求风光发电量占总用电量的比例不低于80%。5. 复现过程中的关键细节与调试心得前四章把模型和求解环境都讲清楚了这一章专门写我在代码复现和调试过程中遇到的、网上资料比较少提及的细节问题和排错思路。5.1 时间尺度的选择全年8760小时还是典型日调度优化最理想的情况是直接把全年8760个小时全部建模进去这样风光出力的季节特性、连续多天的储氢动态过程都能如实反映。但8760个时段的MILP模型规模相当大尤其是还有电解槽启停二进制变量8760个二进制变量和储氢动态约束时Cplex求解时间可能会到几十分钟甚至数小时。我建议的折中方案是先用典型日法做快速验证再用全年数据做最终校核。典型日可以通过K-means聚类从全年风光出力数据里选出来比如春夏秋冬各选3到5个典型日或典型周然后按聚类占比加权计算年运行成本。这样模型规模能压缩一个数量级迭代调试效率高得多。要注意的一点如果用典型日代替全年储氢罐的跨日动态约束必须谨慎处理因为典型日拼接边界上储氢量可能不连续。最简单的办法是把边界条件设成储氢量在周期首尾等值并且把典型日之间的过渡视为瞬态。5.2 储氢罐动态平衡约束的边界处理这是我调试过程中掉坑最深的地方。储氢罐动态约束里如果处理不好末状态等于初状态这个条件优化器会给出一个看似成本很低、实际完全无法运行的方案。举个例子假如全年最后一个时段模型发现储氢罐里的氢没有用完它会想办法在最后一个时段加大合成氨耗氢量把氢清空从而减少储氢罐的容量需求。这个操作在数学上是合法的但实际操作中下一年还要继续运行你不能每年年末把罐清空。所以约束必须写成S_h2_T S_h2_0即周期末储氢量必须等于周期初储氢量这样系统才能循环运行。这个约束加上之后储氢罐容量会合理增大模型结果也真正可落地。另一个细节是储氢量的量纲。电解槽产氢量的物理量和储氢罐容量直接相加减必须统一单位。我用的单位是kg。电解槽输入功率P_ele_t单位kW乘以效率η_ele再除以单位电耗kW·h/kg就得到小时产氢量kg/h合成氨装置的耗氢量则按单位氨产品耗氢量折算成kg/h。千万不要同时用Nm³和kg混合建模量纲错乱排查起来极其痛苦。5.3 求解速度优化与可行性调试模型第一次求解时我遇到的情况是Cplex运行超过2小时还没收敛。排查下来主要是三个原因导致的。第一个是二进制变量过多。8760个时段的电解槽启停变量加上购售电状态变量加起来接近两万个。解决办法是用Cplex求解器的参数控制收敛性设置相对MIP gap比如0.5%或1%让求解器在满足精度要求时提前停止。实际工程应用中0.5%的gap对容量规划决策完全够用。cplex.Param.mip.tolerances.mipgap.Cur 0.005; cplex.Param.threads.Cur 8; % 开启多核第二个是约束矩阵条件数太差。有些约束里出现几万倍差距的系数比如投资成本是万元量级储氢量是公斤量级数值求解时容易出问题。解决办法是对模型做无量纲化或者量纲归一化处理把所有成本项统一成同一量级把功率、容量都折算到基准值上。第三个是模型不可行。刚建完模型求解时Cplex可能会报Model is infeasible。不要急着一行行查代码直接用Cplex的IISIrreducible Inconsistent Subsystem功能快速定位不可行约束集cplex.optimize(); if strcmp(cplex.Status,infeasible) cplex.refineConflict(); disp(cplex.Conflict); end冲突分析结果会直接告诉你哪几条约束同时满足不了比如储氢罐容量上限和初始储氢量约束冲突或者功率平衡约束与负荷上下限冲突问题一下就定位到了。5.4 复现论文时的参数来源与结果验证复现阶段最容易出现的问题是参数张冠李戴。不同论文里的风电单位投资成本、电解槽效率、电价曲线差异非常大直接用别人的参数会导致结果不可比。我的做法是建立一个参数清单表格每个参数标明来源论文、工程报告、假设值并记录敏感性分析范围。关键参数的常见参考范围具体值应以原文或实际项目为准参数典型范围风电单位投资成本4000~7000元/kW光伏单位投资成本3000~4500元/kW电解槽单位投资成本3000~6000元/kW随技术进步持续下降电解槽效率50%~80%合成氨装置耗氢量约176~180 kg H2/t NH3分时购电电价0.3~1.2元/kWh风光资源利用小时数风电2000~3500h光伏1200~1800h结果验证方面我会检查几个工程常识来判断模型输出是否合理电解槽年利用小时数是否落在1000到7000小时的合理区间储氢罐平均储氢水平是否处在20%到80%的正常波动带弃风弃光率是否与风光容量比例匹配。如果这些指标明显异常通常说明模型约束或者参数有问题得回头调试。6. 一套可直接修改运行的MatlabCplex求解骨架前面讲了大量理论和调试心得最后给出一份可以在自己机器上跑起来的最小实现骨架。这份代码不是论文复刻的完整版而是把容量-调度两层优化和Cplex接口的核心结构串起来你拿到之后改参数、加约束就能用。6.1 主程序结构与数据组织主程序分三段参数初始化、内层调度求解函数、外层容量优化驱动。为便于修改我把参数全部集中在一个结构体里。%% 参数初始化 params.T 24; % 调度周期时段数测试时先用24h params.dt 1; % 时段长度(h) params.P_w_cap 30; % 风电容量(MW) params.P_pv_cap 20; % 光伏容量(MW) params.P_ele_cap 25; % 电解槽额定功率(MW) params.P_ele_min 0.1 * params.P_ele_cap; % 最小运行功率 params.V_h2_cap 2e4; % 储氢罐容量(kg) params.eta_ele 0.62; % 电解槽效率 params.HHV_H2 33.3; % 氢气高位热值(kWh/kg) 约39.4kWh/kg这里按实际情况调整 params.Elec_per_kg 4.5; % 电解水制氢单位电耗(kWh/kg H2, 约54kWh/kg实际系统效率约70%) params.H2_per_NH3 0.178; % 合成氨单位耗氢(t H2/t NH3) params.NH3_rate 0.5; % 合成氨装置每小时产氨能力(t/h) params.price_buy [0.3, 0.6, 0.9, 0.3*ones(1,21)]; % 24小时购电电价(元/kWh) params.price_sell 0.25 * ones(1,24); % 售电电价(元/kWh)这里要说明一下具体电耗数值要根据你实际的电解槽效率和单位换算重新标定。模型运行正确与否的关键在量纲统一。6.2 内层调度模型的Cplex构建函数内层调度就是给定容量参数后用Cplex求解一个整数小时级的运行优化问题返回最小运行成本和最优调度结果。function [cost, x_opt] solve_dispatch(params) % 创建Cplex对象 cplex Cplex(dispatch); cplex.Model.sense minimize; % 变量索引组织以24时段为例 % 变量顺序P_ele(1:24), P_w(1:24), P_pv(1:24), P_buy(1:24), P_sell(1:24), % H2_sto(1:25), H2_amm(1:24), u_ele(1:24), u_buy(1:24), u_sell(1:24) n_T params.T; % 变量总数 n_var 8*n_T n_T 2*n_T; % 这里按实际变量个数填写 lb zeros(n_var, 1); ub inf(n_var, 1); ctype char(zeros(1, n_var)); f zeros(n_var, 1); % 目标函数购电费用-售电收益 % f中对应P_buy的系数 params.price_buy * 1000 * params.dt % 对应P_sell的系数 -params.price_sell * 1000 * params.dt % 按变量顺序填写目标系数、边界和类型 % 二进制变量用B连续变量用C % 约束矩阵 A []; lhs []; rhs []; % 逐时功率平衡约束 % P_w P_pv P_buy - P_ele - P_aux - P_sell 0 % 风力与光伏的最大可用出力按各自时序曲线给定写成约束 % 储氢动态约束 % S_h2(t1) S_h2(t) 产氢 - 耗氢 % 产氢 P_ele_t / params.Elec_per_kg % 耗氢 params.H2_per_NH3 * 1000 * params.NH3_rate % 装配进cplex cplex.Model.obj f; cplex.Model.lb lb; cplex.Model.ub ub; cplex.Model.ctype ctype; cplex.Model.A sparse(A); cplex.Model.lhs lhs; cplex.Model.rhs rhs; % 求解 cplex.solve(); cost cplex.Solution.objval; x_opt cplex.Solution.x; end核心逻辑就这些真正写的时候约束矩阵用循环逐时段添加但为了提高构建效率建议直接用块矩阵组装别一条一条addRows那样模型构建阶段会非常慢。6.3 外层容量优化的迭代驱动与结果输出外层驱动负责在给定的容量搜索空间内调用内层求解并汇总年化总成本。我测试时用过最简单的方式是网格搜索加内层Cplex求解把小规模问题跑一遍没什么问题。如果维度再高就该用前面说的单层MILP或者元启发式外层驱动。% 测试不同容量配置 cap_candidates [20, 25, 30, 35, 40]; % 风电候选容量 results []; for i 1:length(cap_candidates) params.P_w_cap cap_candidates(i); [op_cost, x_opt] solve_dispatch(params); inv_cost_annual CRF_w * k_w * params.P_w_cap CRF_pv * k_pv * params.P_pv_cap ... CRF_ele * k_ele * params.P_ele_cap CRF_h2s * k_h2s * params.V_h2_cap ... CRF_amm * k_amm * params.NH3_rate * 8760 * 0.001; total_annual_cost inv_cost_annual op_cost; results [results; cap_candidates(i), total_annual_cost]; end % 输出成本最低的配置 [min_cost, idx] min(results(:,2)); fprintf(最优风电容量: %.2f MW, 年化总成本: %.2f 万元\n, results(idx,1), min_cost);跑通这个骨架后就具备了继续扩展的基础加入全年8760小时数据、更细致的电解槽运行模型、合成氨装置的启停策略、以及并网离网对比模块。每一步扩展都是对原有模型的小幅修改不会伤筋动骨。说到我自己的体会做这类容量-调度耦合优化最容易犯的错误不是约束不够而是约束太多太死导致模型不可行或者结果违背工程直觉。好的建模习惯是从简单模型出发先跑通再逐步加细节每加一条约束都重新验证结果的变化是否在预期范围内。另外写代码时统一量纲、统一单位、参数集中管理能让你在后期排查时省下大量时间。这篇文章把这次复现过程中的关键内容都覆盖到了系统的工艺流程、双层优化建模逻辑、Cplex接入Matlab的方法、并网和离网建模差异、储氢动态约束的边界处理、求解性能调优以及一个可以直接跑通的最小代码骨架。如果你正在复现类似的论文照着这个思路走应该能绕开不少我踩过的坑。
返回列表