前阵子做完了一个含氢气氨气综合能源系统优化调度的Matlab项目,简单说就是把风电/光伏、电解槽制氢、储氢罐、合成氨装置、储氨罐、氢燃料电池和氨燃料发电这几大块,全部收进同一个优化框架里,用Matlab建模求解,算出系统在典型日内的最优运行策略。这种“电-氢-氨”联动的综合能源系统,这两年确实是热点方向,核心价值在于氢和氨都是优秀的长时储能介质,能扛住几天甚至跨季节的能量转移,正好补上锂电池储能在长时间尺度上的短板。如果你正在做综合能源系统调度研究,或者刚接到一个类似的题目,这篇文章从建模思路、约束处理、Matlab代码框架、求解器配置,到算例结果怎么分析,会一次性讲透。
1. 先想清楚:氢氨综合能源系统到底在“优化”什么
很多人拿到这种题目第一反应是去敲代码,我建议先冷静地画一张能量流图。你要是连能量从哪来、到哪去、中间在哪一步损耗最大都没想清楚,后面建模肯定会被约束条件虐得体无完肤。
1.1 系统里的设备链路与能量流转
一个典型的含氢氨综合能源系统,设备链路大致是这样的:新能源机组(风电、光伏)发出的电,一部分直接供给负荷,剩下的余电有两种去处,一是进电化学储能,二是驱动电解槽制氢。电解槽出来的氢气,一部分进储氢罐直接当氢燃料用,一部分送进合成氨装置,和从空分装置来的氮气反应生成氨,氨可以存进储氨罐,后续既可以直接作为燃料卖给外部用户,也可以在负荷高峰时通过氨燃料发动机或者固体氧化物燃料电池(SOFC)重新发电回馈电网。
我画过一张比较完整的能量流图,里面还有余热回收链条:电解槽、合成氨反应器和燃料电池都有大量余热,如果是热电联产场景,余热还能进热网,但这样模型维度会急剧膨胀。最开始我没加这部分,先把主能量流理顺,结果证明这个决策是对的。主线捋清之后,热的部分后面想加其实难度不大,反而是把氢氨耦合的非线性关系处理明白比较费劲。
1.2 优化目标不是只有成本,碳排放同样关键
另一个需要提前想清楚的问题是:优化目标是什么?单目标还是多目标?
纯成本最小化是最常见的目标函数,涵盖向外部电网购电费用、燃料费用、设备启停费用、运行维护费用,以及弃风弃光惩罚。但很多项目会加上碳排放约束甚至直接以碳排放最小化为目标。这是因为在“双碳”背景下,氢氨系统的重要卖点就是减排,你不把碳成本纳入模型,最后算出来的“最优”方案可能并不符合实际政策导向。
我在自己的模型里选了“经济成本+碳交易成本”的加权单目标形式,把碳排放量按碳价折算成费用放进目标函数,这样既能保持线性规划的求解效率,又能体现不同碳价水平对调度策略的影响。如果你要做双目标帕累托前沿,用Matlab的mpt3或者YALMIP配合多目标求解器也不是不行,但调试量会大不少,建议先把单目标跑通再说。
1.3 为什么偏偏把氢和氨放进同一个调度框架
说实话,单独做氢储能调度或者单独做天然气-氢掺混的文章已经很多了,含氨系统最近才被广泛关注,原因在于氨作为氢载体的独特优势。氨的储运技术非常成熟,液氨的体积能量密度比液氢还要高一些,而且氨在常压下只需冷却到-33度就能液化,储运成本远低于氢。
把氨放进调度模型后,系统等于多了一个“跨介质储能”维度:氢可以自己存,也可以转化成氨存,还可以通过氨发电用掉。三个环节之间是强耦合的,什么时候制氢、什么时候合成氨、什么时候氨发电,本质上是一个时序协调问题。这也就是为什么这个题目必须用优化调度的手段来解决,而不是靠简单规则判断。
2. 数学模型搭建:从目标函数到约束条件的细节处理
做优化调度的核心工作,百分之七十的时间都花在建模上。这一步做好了,后面Matlab代码就是“翻译”而已。我建议你不要一上来就堆公式,而是先用自然语言把问题和时间步长、决策变量定义清楚。
2.1 目标函数设计:运行成本、购能和碳排放怎么加权
我的目标函数长这样,给它拆开看就很好懂:总成本等于向电网购电的成本,加上系统内设备(电解槽、合成氨单元、发电单元)的运维成本和启停成本,再减去外售氢、外售氨和向电网反送电的收入,最后再加上弃风弃光惩罚和碳排放成本。
这里的几个细节值得单独说一下。运维成本我用的是线性模型,即单位出力乘以运维系数,没有考虑过载时效率下降的非线性折旧,这对于日前调度尺度来说精度够用,求解速度还快。碳排放成本我按外购电的网侧排放因子折算,也就是系统购了多少电,这部分上游发电碳排放也算到系统头上,这样能防止模型为了省钱无限制地购电而忽略减排责任。
关于弃风弃光惩罚系数,这个参数很敏感。设得太低,模型会选择弃新能源来省设备运维费;设得太高,可能逼得系统把氢储罐灌满还在电解,也不合理。我建议惩罚系数至少高于系统边际发电成本,或者用风电光伏出力的单位价值作为基准来标定。
2.2 设备模型与约束条件:电功率、物料平衡、储能递推
再来看约束,这是整个模型最容易出问题的地方。最基本的电功率平衡约束是:风电出力加光伏出力加燃料电池出力加氨发电出力加外购电,等于负荷加电解槽耗电,这是每一时刻都必须严格满足的等式约束。
氢平衡约束需要特别小心。在我的模型里,电解槽产氢量等于直供氢量、去往合成氨装置的氢气量、进入储氢罐的充氢量以及外售氢量之和。这里有一个单位换算的坑:合成氨装置消耗的是氢气质量流量,电解槽产氢是按标准立方米或者千克计,做Matlab代码的时候一定要统一单位,比如全部换算成kg/h再进入模型,否则数值会差出数量级。
合成氨装置的物料平衡按化学反应方程N2+3H2→2NH3来算,生产1kg氨大约需要0.176kg氢。这个系数可以根据项目精度要求调整,如果是简化模型,甚至可以按0.2kg氢/kg氨来估算,但要心里清楚这是个近似。
储能约束是另一个重灾区。储氢罐和储氨罐的状态,我采用的是标准的荷电状态(SOC)递推公式,即当前时段的储量等于上一时段储量加上充入量乘以充入效率,减去放出量除以放出效率。还记得两个边界条件:储能初值要与末值相等,这是为了让调度结果能循环执行;SOC不能低于最小储量也不能高于最大储量,防止模型把储能当无限垃圾桶。
设备出力上下限和爬坡约束不能忘。电解槽和燃料电池都有最大最小出力比例,合成氨装置由于工艺特性,往往有最小技术出力要求,甚至要考虑启停时间约束。如果完全不设爬坡约束,MiLP解出来可能非常激进,上一小时电解槽满载运行,下一小时直接停机,这种结果工程上是没法用的。
2.3 非线性项的线性化处理
氨系统的物料关系其实存在一些非线性,最典型的是氢氨转换效率和合成氨反应器的负荷率相关。严谨来说,合成氨装置在部分负荷下效率会下降,但如果直接用非线性效率,模型就变成NLP或者MINLP,Gurobi这类MILP求解器就用不了了。
我用的处理方式是用分段线性化近似,效率曲线取几个典型负荷率点,然后用分段线性函数表示,配合0-1变量选中当前段。这在YALMIP里可以用implies,或者直接手写big-M约束实现。对于步长1小时的日前调度,分段数取4到5段精度已经足够,再多的段数只会拖慢求解速度,对结果几乎没影响。
还有一类非线性来自目标函数里的“misfit”惩罚,比如我们希望储能容量在调度期末尽量回到初始值,直接加等式约束太严格,就可以用正负偏差变量相加的方式线性软化。这个技巧在综合能源系统调度中很实用,建议记下来。
2.4 调度周期的选择:24小时 vs 168小时
调度周期怎么选,直接影响模型规模和解的质量。氢氨储能的调节周期本身就跨小时,只优化24小时,储能首末状态强制相等,可能看不出跨日调节的价值。我建议如果你的算例数据支持,优先做168小时(一周)的滚动优化或者直接优化整个典型周,再按天提取调度结果。
当然,调度周期拉长后变量数量成倍增长,168小时模型配合6个以上设备,变量轻松突破几万个,对求解器内存和求解时间都是考验。我实测下来,Gurobi求解线性化后的MILP,168小时算例一般几分钟到几十分钟能出结果,还在可接受范围。如果实在算不动,可以考虑把步长从1小时改成2小时,或者用滚动时域优化,把长周期问题切成若干重叠短窗口迭代求解。
3. Matlab代码实现全流程:从工程结构到求解器配置
模型建好了,剩下的就是把公式搬进Matlab。这个环节看似简单,但代码组织得好不好,直接决定后面改参数、加约束、出图表的效率。我自己的工程结构可以给你直接参考。
3.1 工程结构怎么组织最清爽
我的Matlab工程一般分成四个目录加一个主脚本:数据目录放风/光/负荷曲线和系统参数;模型目录放所有约束和目标函数的构建文件;求解目录放求解器调用代码;结果目录放图表导出脚本。主脚本只有几十行,依次调用数据的加载、变量的声明、约束的组装、目标函数的定义、求解和结果可视化。
这样组织的好处是维护性极强。我后来加敏感性分析的时候,只改了数据文件和主脚本里的循环,模型文件一行没动。你要是把所有代码堆在一个脚本里,前半夜写模型没问题,后半夜想找某个爬坡约束在哪一行能改到心态崩溃。
3.2 用YALMIP建模的关键写法
变量声明我推荐用YALMIP的sdpvar定义连续变量,用binvar定义0-1变量,然后用Constraints = [Constraints, ...]的方式一步步追加约束。最后用optimize(Constraints, Objective, options)直接调用Gurobi求解。
举个关键变量的例子,电解槽输入电功率是连续变量,合成氨装置启停状态是0-1变量,两者之间有互锁关系:当装置停机时输入功率必须为0,当装置运行时输入功率必须在上下限之间,我用约束来表示这种逻辑。在YALMIP里可以写成约束等式和不等式,注意下标循环要用matlab的向量化写法,避免t=1:24的循环套n个变量导致模型构建速度慢几个数量级。
这里有一个经验:如果你从没装过YALMIP,直接用matlab的optimproblem框架也可以实现线性规划,但0-1变量和约束拼接的灵活度会差很多。我强烈建议花半小时装一下YALMIP,它不加密、免费开源,学术场景完全够用。
3.3 Gurobi求解器配置与MILP求解要点
求解器我选的是Gurobi。学术界可以直接申请免费license,安装好之后在Matlab里运行gurobi_setup完成路径配置,之后YALMIP会把它识别为默认MILP求解器。
有几个求解参数比较值得调。我通常会把MIPGap上限设置为1%或0.5%,这样能显著缩短求解时间,因为综合能源调度这种大规模整数规划问题,要证明到0.1%的最优性往往需要额外消耗大量时间,而工程上1%的次优解已经完全可用。另外,设置TimeLimit参数控制最大求解时间,防止某些极端工况下求解器死磕不退。
还要注意一个trick,就是给变量提供好的初始可行解。Gurobi支持通过YALMIP的assign函数给一些关键0-1变量赋初值,之后再调用optimize,求解器会从热启动点开始搜索,速度提升非常明显。尤其是氨发电单元启停这种二值决策多的问题,一个好初值能省一大半求解时间。
3.4 结果可视化怎么画才有效
求解完成以后,别急着复制数据到Excel,先用Matlab把结果图画出来,很多问题一眼就能发现。我通常用四个子图:第一个是电功率平衡堆叠图,风电、光伏、购电、负荷和电解槽消耗一起展示,检查每个时刻功率是否真的平衡;第二个是氢/氨储罐SOC曲线,看储能有没有越界或者异常跳变;第三个是设备出力时序图,重点盯电解槽和合成氨装置的启停情况;第四个是系统单位成本构成柱状图,直观看出钱到底花在哪些地方。
画图的时候我习惯用Matlab的tiledlayout,把四个子图排成2x2,然后统一配置坐标轴字体和LineWidth,导出成emf或者pdf格式放进论文里基本不用再调。这里提醒一句,Matlab的legend如果曲线多了会自动遮图,记得手动设置位置,或者干脆不画legend,只在线旁标注文字。
4. 算例分析与结果解读:怎么从一堆数字里看出问题
算例分析是最能体现项目价值的部分,但也是很多人最后敷衍了事的部分。模型再复杂、代码再漂亮,如果结果分析讲不清“为什么是这个策略”,这项目在同行眼里就是半成品。我通常按三个层次做分析。
4.1 典型日调度结果长什么样
首先看一个典型日的调度结果。比如某天风电出力较大、光伏午间出力峰值明显,模型会倾向于在风电大发时段优先电解制氢,把多余电能转化为氢存储起来,在傍晚负荷高且风电下降时段,氢燃料电池启动发电回补负荷。
这个结果最值得分析的亮点是系统内部的能量时空转移:白天光伏和风电的盈余变成了晚间高峰的供电,这一过程没有任何碳排放增加。同时合成氨装置可能保持较稳定的基荷运行,即使他用电,但耗电量相对可控,这样氨储罐能持续累积“跨日能量储备”。我在论文里会专门截取这段时间窗口的功率平衡图,把电解槽削峰填谷的作用讲清楚。
如果结果出现一些反直觉现象,比如风速很小、光伏很大的时刻反而没让电解槽满负荷运行,先别急着怀疑模型,去查是否有爬坡约束或者合成氨装置最小出力限制在作怪。很多时候,看起来“不合理”的结果反而暴露了约束条件在起作用。
4.2 不含氢氨路径的对比实验有什么意义
为了说明氢氨系统的价值,我建议至少跑一个对比场景:把氢/氨储能路径关掉,系统只保留电化学储能和购电,其他条件一模一样。两个算例对比下来,最常见的结论是含氢氨系统的弃风弃光率显著下降,购电成本也明显降低,但设备投资和运维成本上升。
这种对比能回答一个非常关键的问题:氢氨系统到底比纯电化学储能好在哪?你会从对比数据中发现,锂电池储能适合日内调峰,但遇到连续几天阴天或小风天就无能为力;而氢氨储能可以通过合成氨预先囤积大量能量,实现跨天甚至跨周的调节。这种时间尺度的补位优势是含氢氨系统最核心的卖点,在对比图里会让审稿人或导师一眼就看出价值。
4.3 敏感性分析:氢价、碳价对运行策略的影响
第三步就是做敏感性分析。我一般会选氢价、氨售价、碳交易价格、新能源渗透率这几个参数,扫描变化范围从基准值上下浮动50%,画出关键指标(总成本、购电比例、弃风率、制氢量)随参数变化的曲线。
以碳价为例,碳价从0上升到较高水平时,系统会主动减少外购电,增加本地氢氨发电比例,同时提高电解槽的运行小时数,因为清洁燃料发电替代高碳购电带来的减排收益大于额外运维成本。如果碳价继续升高到一定程度,合成氨装置的氨产量甚至会提高,因为氨作为外售清洁燃料的经济价值进一步抬升。
敏感性分析做完,你手里就有足够的数据支撑最后的研究结论了。也是在这个时候,你会对“为什么优先用氢氨长时储能”这个问题有了从量化角度的完整理解,而不是停留在定性描述层面。
5. 常见问题与排查技巧实录
最后这部分全是实际调试中踩过的坑,每一条都是我或者同行朋友用真金白银的时间换来的。遇到同样的问题,按下面的方式处理,能少走很多弯路。
5.1 模型跑出来结果“不符合物理直觉”怎么办
最常见的问题:风电大发时段系统反而不制氢,反而去购电。第一反应别改代码,先查负荷曲线和电价曲线。如果购电电价有峰谷分段,模型宁可峰时买电也不谷时制氢,只能说明你电价设置不合理,正常应该是谷时买电制氢、峰时用氢发电回补才对。
另一种反直觉情况是储能末值不等于初值导致调度失配。我一开始用365天连续优化,跑了一天一夜还不出结果,后来发现储能初始和终止条件没有闭合,模型在疯狂利用边界套利。加上首末储能相等约束后,问题迎刃而解。
5.2 求解器报“infeasible”怎么定位
如果Gurobi告诉你模型不可行,先不要慌。最实用的方法是用YALMIP的check功能逐条查看约束残差,或者干脆把所有约束先注释掉,逐类加回去,哪一类约束加上以后模型不可行了,问题就出在那类约束上。
我遇到的不可行原因中,比例最高的是电功率平衡约束写死了需求必须等于供给,但新能源出力上限固化了,某时刻所有电源加在一起也满足不了负荷。解决办法是给电平衡加一个松弛变量,比如可中断负荷或者失负荷惩罚,这样既保证模型有解,又能在结果里看出系统真实缺电时段。第二个高频原因是储能SOC递推里充放效率和功率方向搞反了,导致能量越算越多,最终突破储量上限。这一类问题,用上述“逐类排除法”定位非常快。
5.3 Matlab运行环境与工具箱的兼容坑
最后说两个Matlab环境的坑。YALMIP对新版Matlab的支持有时会滞后一点,如果你用的是很新的Matlab版本,装完YALMIP后跑起来报“Undefined function”,先去YALMIP官方GitHub页看更新记录,多半需要升级YALMIP版本。另一个是Gurobi和Matlab版本的接口兼容问题,建议安装Gurobi官方提供的Matlab接口时,用系统默认的编译器重新编译一遍mex文件,我遇到过接口文件版本不匹配导致求解器无法加载的问题,重新编译后就好了。
还有一个小建议,跑大规模算例之前,先清理一下Matlab工作区变量并且关闭所有图窗,不然内存碎片多了,求解过程中很容易出现out of memory。我这边的服务器配置不算差,但第一次跑168小时模型时没注意这些,直接卡死了三次,后来每次跑之前都会打包运行一遍clear all,就再也没出现过内存问题。
最后再分享一个小经验:做这种含氢氨的综合能源系统优化调度,最忌一上来就追求模型面面俱到。先把主线打通,用单日算例验证模型行为是否符合物理直觉,再逐步把氨转化效率、爬坡约束、碳成本这些细节叠加上去。很多同学第一步就倒在过度复杂的模型上,连一个可行解都跑不出来。我自己是在单日模型反复验证了三十多组参数之后,才敢放开跑168小时和全年算例,整个过程虽慢,但翻车概率极低。这个节奏,也建议你试一下。