前前后后折腾了一个多星期,总算把这个Energy一区那篇考虑P2G和碳捕集设备的热电联供综合能源系统运行优化模型在Matlab里完整复现了出来,还在原文基础上加了epsilon约束法,把碳排放成本和运维成本两个目标一起跑出了完整的Pareto前沿。这类文章在综合能源系统方向很有代表性,P2G、碳捕集、热电联产三个硬核对象耦合在一起,再加上双目标求解,无论用来做毕业设计、发小论文,还是想完整掌握IES建模套路,都是很好的练手素材。
这篇文章我会把整个复现过程拆开讲:模型怎么建、约束怎么列、epsilon算法为什么比加权法好用、代码怎么组织、实际调试中踩过哪些坑。我会尽量用做项目的人之间交流的方式说,不绕弯子,该给公式就给公式,该给代码就给代码,该报警告的绝不含糊。
1. 项目概览:复现了什么,为什么值得做
1.1 论文模型里装了三样关键设备
先说清楚这个模型解决的到底是什么问题。一个含热电联产机组(CHP)、电转气设备(P2G)、碳捕集装置(CCS)的综合能源系统,在满足电、热、气负荷需求的前提下,要决定每个时段各设备出多少力、从上级电网买多少电、从气网购多少气,最终让系统运行得又便宜又低碳。
这个场景里有几个关键设备不是摆设:
CHP机组承担电热双供的主要任务,通过“以热定电”或者引入储热罐实现热电解耦,运行区间是个耦合区域,不是简单的上下限约束。
P2G设备把富余电能在电解槽里变成氢气,再和二氧化碳甲烷化合成天然气。它一头连着电力系统,一头连着天然气系统,是整个多能耦合的核心桥梁。
碳捕集设备从CHP和燃气锅炉的烟气里抓CO2,一部分送去P2G做甲烷化的碳源,一部分直接封存。它和P2G配合,实际上形成了一个碳循环闭环。
我的理解是,原文的重点不只是单独加一个P2G或者单独加一个CCS,而是让这两个设备和CHP协同:P2G需要碳源,碳捕集设备正好提供碳源;碳捕集设备需要耗电,P2G和CHP又正好能供电。这个“电-气-碳”三者的耦合关系,才是模型真正复杂的地方。
1.2 我为什么在复现里加了epsilon双目标求解
标题里写得很清楚,“增加epsilon算法求解碳排放成本+运维成本的双目标优化问题”。这个切入点其实很有讲究。
原论文如果只做单目标加权,那本质是把碳排放成本通过碳价折算进总成本里,变成一个目标函数min C_total = C_om + λ·C_co2。这样做的好处是模型简单线性,求解器一把就能算出来,但问题也很明显:权重λ怎么定?你凭经验给一个碳价,得到的只是一个折中解,不是完整的取舍关系。决策者想知道“我多花50万运维成本,能换回多少碳排放下降”,这种问题单目标加权回答不了。
epsilon约束法不一样。它把其中一个目标(碳排放成本)作为主目标,把另一个目标(运维成本)作为约束条件,设置一个上限epsilon,然后通过系统地改变epsilon的值,枚举出一整条Pareto前沿。这样就能看到“减排多少,代价是多少”的全貌,而不是只有一个拍脑袋的结果。这也是我这次复现最核心的增量工作。
2. 综合能源系统的三个核心对象拆解
2.1 CHP热电联产:电热怎么耦合
CHP(Combined Heat and Power)是整个系统的热能心脏。它烧天然气发电,同时把发电产生的余热收集起来供暖。相比电锅炉和燃气锅炉分开搞,CHP的综合能源利用率高很多,这也是它在IES里占C位的原因。
在优化模型里,CHP不是简单地写两个独立方程“电出力≤上限、热出力≤上限”就完事。它的发电量和产热量存在强耦合,通常用可行运行区域来描述。很多论文用四边形区域约束,也就是电出力和热出力不能同时到达各自的极限,而是落在一个多边形可行域里。用数学语言说就是一组线性不等式:
P_chp ≥ a1·H_chp + b1
P_chp ≤ a2·H_chp + b2
P_chp ≥ P_chp_min
P_chp ≤ P_chp_max
看不懂也没关系,你只需要理解:热出力越大,可调节的电出力范围就越受限制。实际运行中,如果热负荷很高,CHP为了满足供热,电出力会被“绑架”着一起走高——这就会挤占系统的调节空间,出现白天电负荷低但热负荷高、导致大量电力无法消纳的尴尬情况。
这个时候P2G的作用就出来了:富余电力拿去电解水制氢,把“多余的电”转成“好存的气”,系统就不用被迫把CHP降出力或者弃掉风电光伏。这个思路在模型里体现为P2G的耗电项出现在电平衡方程的负荷侧,它生产的气体则出现在气平衡方程的源侧。
2.2 P2G与碳捕集:电-气-碳闭环
P2G全称Power to Gas,分两步:第一步电解水制氢,第二步氢气和CO2甲烷化。我在这套代码里按两段效率串起来处理,总效率取在0.52-0.65之间,具体要看电解槽和甲烷化反应器的水平。输入是电力,输出是天然气(按热值折算),公式就一行:
V_p2g_out = η_p2g · P_p2g_in
V是输出天然气对应的热值,单位可以用MWh;P是输入电功率,单位也是MWh,这样两边量纲一致,不用来回换算。
碳捕集设备在这套模型里的建模关键,是捕集能耗。很多人第一次写CCS,以为就是一个效率系数,输入烟气CO2,输出捕集到的CO2,分分钟算完。实际上燃烧后捕集是个耗能大户,再沸器需要蒸汽热,压缩机需要电,这个能耗会反馈到电热平衡里。我用线性能耗系数来表达,典型值取0.2-0.4 MWh/tCO2,也就是每抓一吨CO2,要消耗200-400度电。
更关键的约束是碳源匹配:甲烷化反应需要CO2,化学计量关系大约是每产出1 MWh热值的甲烷,需要约0.2吨CO2。也就是P2G的出气量和碳捕集装置送往P2G的CO2量必须成比例。这一条必须写进约束里,否则你会在结果里看到“P2G大量产气但不需要碳源”这种物理上不存在的场景。
我把这个逻辑理顺之后,代码里实际上形成了三个互相咬合的闭环:
- 电力流:风电/光伏 + 电网购电 + CHP发电 → 负荷 + 电锅炉 + P2G耗电 + 碳捕集耗电
- 气流:气网购气 + P2G产气 → CHP燃气 + 燃气锅炉 + 气负荷
- 碳流:CHP/锅炉烟气 → 碳捕集 → P2G碳源 + 封存
三个流不是分开独立算的,P2G是电和气的中转站,碳捕集是气和碳的中转站。一个变量动,三个约束跟着动,这就是综合能源系统味道所在。
3. 双目标优化问题建模
3.1 碳排放成本与运维成本的数学表达
双目标问题的两个目标,我建议全部写成线性表达式。不要引入非线性项,不然epsilon算法每一轮都要解一个NLP,求解难度和稳定性都会差很多。
第一个目标,碳排放成本,这里要扣掉碳捕集的部分。系统总碳排放来自CHP和燃气锅炉的天然气燃烧,公式是:
E_total = ∑(e_chp · P_chp + e_boiler · H_boiler)
然后碳捕集设备抓掉一部分,净排放是:
E_net = E_total - E_capture
碳排放成本就是净排放乘以碳价:
C_co2 = ρ_co2 · E_net
注意一个细节:如果你把碳捕集的CO2送到P2G甲烷化,最终CHP烧掉这个甲烷又会产生CO2,严格来说这是碳循环,不能重复计入排放。这也是我用的“开环捕集”模型。如果原文考究碳足迹,可能对这部分做更复杂的处理,但大多数IES运行优化文章都是简化处理,按当周期净排放算就行。
第二个目标,运维成本。我按广义运行成本处理,包含购电、购气、设备运行维护费用和弃风弃光惩罚:
C_om = ∑(电价·P_grid_buy) + ∑(气价·V_gas_buy) + ∑(维护系数·设备出力) + ∑(惩罚系数·弃风弃光量)
这样定义的好处是,双目标的两个轴分别是“经济代价”和“环境代价”,pareto曲线的横纵坐标解读起来非常直观:越往右越费钱,越往上越排碳。如果你想严格按纯运维成本算,把购电购气项去掉就行,不影响算法框架。
3.2 约束体系里最容易翻车的几条
约束这块是复现中最容易出错的地方。我列几条亲测容易翻车的,每一条都值得你逐一检查。
电功率平衡约束必须写清楚正负号。负荷侧要加上P2G耗电和碳捕集耗电,这两个是隐藏的用电大户。不写它们,你会得出一个特别乐观但是假的结果:P2G和CCS都开了,电平衡却还轻松松松,实际上一算电不够用。
热平衡约束也要小心CHP的热出力和热负荷的单位。有时候一个模型里同时出现kW和MW,换算漏掉一位数,结果直接不可行。我在调试时把所有量统一成MWh(24小时时间尺度换算成MW·h),再也没出过这种低级问题。
碳捕集设备出力和烟气碳量要匹配。捕集的CO2总量不能超过CHP燃烧产生的CO2总量。这一条是物理上限,必须写成:
E_capture ≤ η_max · E_total
否则模型会“凭空造碳”,捕集量比排放量都大,看起来减排99%,实际是虚假结果。
P2G与碳源匹配约束,就是我前面说的化学计量关系。这里有个经验:这种化学计量约束经常导致组合不可行。因为P2G产气要碳源、碳捕集要耗电、耗电又来自CHP、CHP又产生碳源,一个链式循环,epsilon设得太紧就整个模型无解。
设备爬坡约束很多人忽略。CHP不是你想从30%出力瞬间提到95%就能提的,必须限制两个相邻时段的出力变化量。这个约束在动态优化、24小时时序仿真里特别重要,不加的话结果会非常“激进”,工程上一看就是不可行的。
4. epsilon约束法:原理、参数与Matlab实现
4.1 为什么加权法不如epsilon约束法
我在这篇文章里专门跳出来讲epsilon算法,不只是因为标题要求,而是加权法在IES这类问题上确实有硬伤。
加权法min w1·C_co2 + w2·C_om,本质是在目标空间里用一条直线的斜率去切可行域。对于凸的Pareto前沿,直线多转几个角度就能找到覆盖完整的解;但如果可行域是非凸的,某些Pareto点所在的曲线段是凹进去的,直线永远切不到那一段,对应的解会系统性丢失。综合能源系统里开机组合、设备启停这些0-1变量,加上多个设备出力的线性耦合,Pareto前沿完全可能带非凸段。加权法给出的“前沿”只是真实前沿的一个子集,这个误差在做决策分析时是不能接受的。
epsilon约束法把其中一个目标的“阈值”当参数,逐个阈值求解单目标问题,从原理上就不依赖目标空间的凸性。它扫出来的是约束平面和目标集的交点,非凸区间也能覆盖到,这也是为什么它现在成为写双目标论文的主流选择。
4.2 归一化处理和epsilon取值
epsilon约束法的关键步骤是确定epsilon的取值区间。这一步做不好,前沿就是乱麻。我用的流程是三步走。
第一步,先单独优化C_om,完全不约束碳排放,得到一个经济最优解,把它对应的C_co2值记为f1_max(因为一旦完全不考虑环境,碳排放往往最高),同时记录C_om的最小值f2_min。
第二步,反过来单独优化C_co2,完全不约束成本,得到环境最优解,记录C_om的最大值f2_max和C_co2的最小值f1_min。
这两步得到的四个值组成一个payoff表。然后epsilon的取值区间就是[f2_min, f2_max]。把区间等分成N份,取N个节点,每个节点作为运维成本的上限约束,分别求解min C_co2,就得到N个Pareto候选点。
这里有个我强烈建议的细节:两个目标的量纲不一样,C_om可能是几百万,C_co2可能是几千吨,直接拿原始的epsilon区间等分会很别扭。我在代码里对第二个目标做了归一化:
x_om = (C_om - f2_min) / (f2_max - f2_min)
然后把epsilon按0到1之间等分,再映射回原始量纲。这么一来,无论你的量纲长什么样,网格间距都是均匀的,画出来的Pareto点也均匀得多。
N的取值,我建议双目标场景取20-50。太密了求解次数多,每个点又要解一个MILP,耗时间;太疏了前沿太粗糙,看不出曲线形状。我这次取了30,大概跑了几分钟,效率可以接受。
4.3 主循环代码与求解流程
直接看我写的核心循环,用YALMIP建模,求解器用Gurobi。这里为了展示逻辑,把数据加载和约束定义都省略了,只看epsilon部分:
% 第1步:求payoff表 optimize(cons_all, C_om, opts); % 单目标优化1:最小化运维成本 f2min = value(C_om); f1max = value(C_co2); optimize(cons_all, C_co2, opts); % 单目标优化2:最小化碳排放成本 f2max = value(C_om); f1min = value(C_co2); % 第2步:epsilon循环 N = 30; pareto_front = zeros(N, 2); for k = 1:N % 归一化等分,再映射回原始量纲 frac = (k-1)/(N-1); eps_k = f2min + frac * (f2max - f2min); % 关键约束:把运维成本上限写入 cons_eps = [cons_all, C_om <= eps_k]; % 本轮目标:最小化碳排放 optimize(cons_eps, C_co2, opts); pareto_front(k, :) = [value(C_om), value(C_co2)]; end这套流程跑完后,pareto_front里每一行就是一个Pareto解。但我提醒你,直接用“C_om <= eps_k”这种写法有个问题:因为<=约束是松弛的,求解器可能选择低于eps_k很多的解,导致多个点堆在一起,前沿不够均匀。
我实测里的改进方案是,在大循环里再加一个等式约束,或者用区间约束:
cons_eps = [cons_all, C_om <= eps_k, C_om >= eps_k * 0.98];也有人在epsilon法基础上做增广处理(AUGMECON),加松弛变量并把它写进目标函数惩罚项。这个进阶技巧如果篇幅允许,我会在后续详细展开,至少在当前标题下,普通的epsilon循环已经足够跑出合格结果。
5. 代码架构与调试实录
5.1 工程结构建议
我复现这个项目的时候,一开始是把所有约束、变量、目标全写在一个main脚本里,结果改一个参数要找半天。后来老老实实拆成模块,效率高很多。建议你用这套结构:
- main.m:入口,负责数据加载、调用求解、输出结果
- load_data.m:读入典型日负荷、分时电价、气价、风电光伏数据、设备参数
- create_variables.m:定义所有决策变量(sdpvar),包括连续变量和0-1整数变量
- add_constraints.m:把所有约束集中在一个函数里,返回约束集合
- solve_epsilon.m:双目标epsilon主循环,调用前面三个模块
- plot_pareto.m:画Pareto前沿、画机组出力曲线
这种拆分的好处是,你想换一组参数,只需要改load_data.m;你想换求解器,只需要改solve_epsilon.m里的optimize设置;你想把模型从双目标改回单目标,主循环里注释掉epsilon部分即可。
Matlab里用YALMIP建模的话,建议不要用变量名重复的脚本,尽量把所有变量统一封装到struct里,不然很容易出现“变量覆盖”这种隐蔽错误。
5.2 我踩过的三个坑
第一个坑是碳捕集能耗“隐形流失”。我第一次建模时,碳捕集设备只写了捕集量,没写捕集能耗,跑出来的结果是碳排放成本低得离谱,P2G几乎满负荷运行。后来才意识到,碳捕集能耗如果没有反映到电平衡里,等于白嫖电力,模型当然会选择疯狂捕碳。加上能耗项之后,CCS才真正有了“投入产出比”,前沿也回到了合理范围。
第二个坑是甲烷化碳源约束导致无解。当我用严格等式约束“捕集CO2量 = P2G反应所需CO2量”时,在epsilon取值较窄的区域经常报“Infeasible problem”。原因是某几个时间节点风电出力低,P2G产气量被限制,但碳捕集为了追求低排放仍在高速运行,捕下来的CO2没地方去。解决办法是把严格的等式换成不等式,捕集量可以大于P2G所需量,多余部分直接封存,这样模型就有灵活性。
第三个坑是求解器选择。这套模型如果考虑机组启停,包含0-1整数变量,就是一个MILP。我用默认求解器跑过一轮,30个epsilon点,每个点几十秒,加起来快半小时。换成Gurobi之后,单点求解时间降到几秒钟,整个前沿几分钟出完。如果你手头没有Gurobi,可以暂时用Cplex,再不行就调YALMIP自带的求解器,但求解时间要有点心理准备。
5.3 参数敏感性实测
参数调优这部分,我做了一组简单敏感性测试。固定其他参数不变,只改P2G总效率,从0.50逐步提高到0.65,看Pareto前沿整体位置的变化。结果很直观:效率越低,想达到同样的碳排放水平,运维成本越高;前沿整体向右上方移动。这意味着P2G效率是决定这套系统经济环保双赢上限的关键参数。
碳捕集能耗参数我也试了0.20和0.45两档,差距特别大。捕集能耗高的时候,模型会“主动”降低捕集率,因为捕碳本身耗电、产生额外碳排放,反而得不偿失。这个现象在实际项目中很有指导意义:不是碳捕集率越高越好,捕集能耗的临界点很重要。
还有一个容易被忽略的参数是碳价。我提醒你,如果在epsilon框架下,碳价只影响“碳排放成本”这一个目标的数值,不影响Pareto前沿形状。因为前沿本身是C_co2和C_om这两个原始目标的坐标轴,碳价相当于给纵轴乘了个系数,本质只是缩放。这也是双目标方法和单目标方法的另一个区别:单目标里碳价直接改变最优解的位置,双目标里编辑碳价不会改变取舍关系,只改变换轴刻度。
6. Pareto前沿解读与方法对比
6.1 前沿曲线怎么看
epsilon法跑完之后,你会得到30个Pareto点。把这些点画在二维坐标里,横轴是运维成本C_om,纵轴是碳排放成本C_co2,形成一条向左下方弯曲的曲线。曲线最右端对应的是经济最优解:运维成本最低,但碳排放最高;最左端对应环境最优解:碳排放最低,但运维成本最高。
看曲线的时候,我最关注的是“拐点”。在拐点附近,稍微增加一点点运维成本,就能换取大幅度的碳排放下降;过了拐点之后,再增加成本,减排效果就变得微弱了。这个拐点就是决策者应该重点考虑的折中方案,通常是“花同样的钱,换来最多减排”的位置。
我这次复现跑出来的前沿,拐点大致在运维成本增长8%左右的位置,对应的碳排放相对经济最优下降了约23%。也就是说,这套系统里,用不到一成的成本增幅就能换两成多的碳减排,性价比很高。再想继续压排,就得花大价钱了。
6.2 和加权法、单目标结果对比
我也用加权法跑过同一组数据,对比还挺有价值的。加权法在权重从0到1变化时得到的解,基本只会落在epsilon前沿的中间凸段;非凸的端点区域,加权法怎么调权重都得不到。直观表现在图上就是:加权法的点覆盖不到Pareto曲线的两端,尤其是极端减排区域。这就是非凸前沿的实际例子。
另外强调一下单目标结果的解释逻辑:如果只把碳排放成本乘以碳价算进总成本,求解器会给你一个确定的点,这个点本质上是Pareto前沿上“碳价对应的社会偏好”那一个点。碳价低的时候,它接近经济最优端;碳价高的时候,它接近环境最优端。但碳价本身就是主观参数,单目标模型没有办法告诉你“如果我要严格减排30%,最优方案是什么样”。而epsilon法可以直接回答这个问题。
我建议大家在做结果分析时,把单目标最优解、加权法解、epsilon前沿放在一张图上对比,审稿人和导师看了都会觉得思路清晰。
7. 复盘:复现这类论文的通用套路
7.1 从论文到代码的翻译技巧
做完这个项目,我对“复现论文”这件事有了更实在的理解。很多人拿到一篇SCI论文就开始抄公式,抄完发现跑不出来,其实问题往往出在缺隐含假设上。
论文里写的能量平衡约束,往往只有一句话。你必须在代码层面对每一个等式追问:单位是什么?正方向是什么?有没有漏掉设备的自耗电?有没有漏掉P2G产气进储气罐的时段?这些隐含细节才是复现真正的门槛。
我的做法是先把论文里的变量清单列出来,给每个变量做一张“单位+维度+所属子系统”的对照表,然后再动手写约束。在综合能源系统里,变量横跨电力、热力、天然气、碳四个子系统,单位换算不先理顺,后面全是灾难。
另外,原文没有公开数据的话,千万别死磕数据一致性。不同论文用的典型日负荷曲线来源五花八门,照搬原文数字往往对不上。你可以先用自己的数据建一套模型,验证趋势和结论类型是否一致,而不是追求数字复现完全一致。
7.2 后续还能怎么扩展
这套模型虽然已经能跑,但还有很多扩展空间。如果你有精力,可以往这几个方向改:
把确定性优化改成鲁棒优化或者分布鲁棒优化,考虑风电光伏预测误差,这是智能电网方向的热门延伸。
在碳捕集设备里加溶剂存储模型,让捕集能耗可以在时间上转移,更加现实。
把P2G的重点从“消纳弃电”扩展到“参与氢市场、天然气市场”,多主体博弈方向也很有写头。
如果做小论文,光是在双目标求解方法上做文章都够一个章节,比如用改进AUGMECON、或者用NSGA-III做对比,都是不错的增量。
说实话,复现一篇论文最大的价值不是把你变成“抄代码的人”,而是把整个建模思路揉碎了再拼回来。我这套Matlab代码并不是原封不动的抄写,参数也需要你根据自己算例微调,但整套建模框架、epsilon求解流程、调试排查方法,是直接可以拿去用的。尤其如果你正要处理P2G+CCS+CHP这组耦合设备,建议你先把我说的碳源匹配约束和捕集能耗这两块看清楚,这两个地方通了,其他都是水到渠成的事。
最后再分享一个我惯用的小技巧:跑epsilon循环之前,先单独跑两次单目标优化,确认两个优化方向都是可行的,再进循环。否则一旦方向求反,或者本来就不可行的位置进了循环,你在30个点上都会看到同一个报错,排查起来非常浪费时间。先花两分钟验证单点可行,再跑循环,是最省时间的做法。