☰
含P2G与碳捕集的热电联产优化建模与Matlab实现
2026/10/10 6:42:56 网站建设 项目流程

做综合能源系统调度优化的人,对这套组合应该不陌生:热电联产机组(CHP)、电转气设备(P2G)、碳捕集系统(CCS)。单个拎出来都是老话题,但要把三者放进同一个优化模型里,在Matlab中实现一套低碳经济调度,事情就完全不一样了。CCS的捕集能耗会重新分配CHP的电热出力,P2G既要吃电又要吃CO2,还得跟储碳罐的库存联动,热电联产那套经典的"以热定电"约束在这种结构下基本要重写。

这篇文章记录了我复现"含P2G与碳捕集系统的热电联产建模与优化"的完整过程,从系统架构、数学建模到Matlab代码和算例结果,最后把我在YALMIP+CPLEX求解中踩过的坑也一并倒出来。适合正在做综合能源系统方向研究的学生、准备给园区能源系统做低碳改造的工程师,以及想拿一个真实优化案例练手Matlab建模的朋友。

1. 从"排碳-耗能"到"柔性化生产":P2G与碳捕集耦合的系统逻辑

1.1 这套系统到底解决什么问题

传统热电联产机组的核心矛盾在于热和电被强耦合在一起。冬天供暖需求大时,机组必须多供热,连带发出来的电远超实际用电需求,多余的电在风光大发时段直接变成弃电。更要命的是,烧煤烧气必然排碳,在碳约束越来越紧的背景下,老师傅们都在琢磨怎么让CHP既能灵活调节出力,又能把碳排放压下去。

P2G和CCS恰好是从两个方向补这个短板。CCS把烟气里的CO2抓下来,不让它直接排到大气里;P2G把多余电能转成天然气,相当于给电网装了一个可以随时调节大小的"电胃口"。如果把两者连起来,CCS捕集到的CO2恰好是P2G甲烷化反应需要的原料,这就形成了一条"CHP排碳到CCS捕碳再到P2G用碳"的内部循环链。听起来很完美,但工程建模的复杂度就在这里——它们不是三个独立设备,而是一个互相牵制的整体。

我做复现时第一个顿悟就是:这个题目表面是加设备,实际是改变整个系统的调度自由度。P2G充当柔性负荷去吸收波动性可再生能源,CCS充当碳的"调节阀",两者配合后CHP的热电耦合约束被大大放松了。理解到这一层,后面建模才有方向。

1.2 能量流与碳流怎么走:全局耦合关系拆解

要建模,先把系统中的物质流和能量流捋清楚。整个系统站在电网、气网、热网三条母线的交汇点上看:

电网上,CHP和风电场发电,负荷用电,P2G电解槽用电,CCS的吸收剂再生也用电。气网上,P2G产出的甲烷注入气网或供给燃气负荷。热网上,CHP供热给热负荷,同时分出一部分蒸汽给CCS的再生塔做热源。

碳流是另一条线:CHP燃烧产生的烟气进入CCS吸收塔,富液送到再生塔加热解吸出高浓度CO2;一部分CO2去封存或外售,另一部分送到P2G的甲烷化反应器,与电解水产生的H2合成CH4。我把这些关系画成表格式的对应关系,建模时就不会漏约束:

设备输入输出中间产物
CHP机组燃料煤/气电、热、烟气CO2-
CCS系统烟气、电、热浓CO2、净烟气吸收富液
P2G电解槽电、水H2-
P2G甲烷化H2、CO2CH4、H2O-

注意一个细节:P2G两步反应里,甲烷化这一步才消耗CO2,电解水制氢本身不需要碳。所以建模时如果只写"P2G耗电产气",就丢掉了CO2这个中间纽带。我建议要么把P2G拆成电解和甲烷化两段,要么用化学计量比把CO2消耗量直接折算成产气量的线性函数,下文会展开。

1.3 耦合之后,传统CHP调度模型哪里不够用了

我最早学CHP调度时用的是最朴素的背压式模型:热出力等于热电比乘电出力,H = c_m·P。一个等式就把问题定死了,机组只能沿着一条线运行,调度起来非常省心。但加了CCS之后,这条线必须松绑。

原因很简单:当CCS投入运行,CHP的净上网电功率不再是P本身,而是P减去CCS消耗的电功率。换句话说,同一台机组,在同一个热出力下,因为碳捕集负荷不同,对外表现的净电出力也不同。这样一来,热和电的解耦不再靠抽凝式机组的物理结构,而是靠碳捕集装置这个"可调负荷"来腾挪。

P2G的影响则体现在电功率平衡上。以前风电多了只能弃掉,因为常规机组压不下去、CHP又受供热限制压不下去;现在有了P2G,它可以像一个大功率电锅炉一样把多余电能吃掉转化天然气。这个"吃电"的能力是可调的,调度模型里它就成为一个决策变量,和CHP、风电一起做联合优化。

所以传统模型不够用的点有三个:一是CHP出力可行域需要重写;二是碳捕集能耗必须作为与捕集量耦合的变量进入平衡方程;三是电平衡里多了P2G和CCS两个大功率用电项,它们不是常数,是由优化决定的。这三条正是后文数学模型的核心。

2. 数学建模的关键环节:CHP、CCS、P2G各自的约束长什么样

2.1 热电联产机组:可行域要比"以热定电"复杂一点

实际研究里,抽凝式CHP比背压式更常用,因为可调范围大。抽凝式机组的电热可行域可以近似描述为一个凸多边形,用一组线性不等式表示:

  • 电出力上下限:P_min ≤ P_chp ≤ P_max
  • 热出力范围:0 ≤ H_chp ≤ H_max
  • 热电耦合约束:P_chp ≥ P_min + β₁·H_chp(保证供热足够时电出力不低于下限)
  • 最大出力受限:P_chp ≤ P_max - β₂·H_chp(供热抽汽多,凝汽发电少)

背压式则直接简化为H_chp = c_m·P_chp,适合做机理分析,但做调度优化时我首选抽凝式,因为系统自由度更大,能看出P2G和CCS带来的调节价值。

燃料消耗与电热出力之间,我习惯用线性函数逼近:F_chp = a₀ + a₁·P_chp + a₂·H_chp。单位是kW或MW的燃料功率。线性化肯定有误差,但对24小时日前调度来说足够,而且可以避免二次规划带来的求解负担。如果机组数据里有明确的燃耗曲线系数,直接用最小二乘拟合出a₀、a₁、a₂即可。

2.2 碳捕集系统:捕集量、净排放与辅助能耗的折算

CCS建模最核心的是三个量:总排放量、捕集量、净排放量,再加上捕集能耗。

CHP产生的总CO2量与燃料消耗成正比,简化写成E_total = e_int·P_chp,e_int是单位电出力的碳排放强度。捕集系统投入运行时,设t_c为捕集率,则捕集量为E_cap = t_c·E_total,净排放为E_net = E_total - E_cap。

捕集过程本身要耗电耗热。以目前最成熟的燃烧后化学吸收法为例,典型的再生热耗约3~4 GJ/t CO2,电耗约100~200 kWh/t。把这些折算成与捕集量成比例的辅助负荷:

  • P_ccs = λ_el·E_cap(电耗)
  • H_ccs = λ_heat·E_cap(热耗,来自CHP抽汽)

这两项会分别进电平衡和热平衡。我计算时通常把E_cap的单位统一为t/h,λ_el取0.15 MWh/t,λ_heat取1.0 MWh/t,数值上对应150 kWh/t电耗和3.6 GJ/t热耗,和公开文献数据基本吻合。

还有一个容易被忽略的约束:捕集系统不要面面俱到,烟气可以直接旁路。也就是说,捕集率t_c不必固定,而是一个可调节的变量,可以理解为运行在0到上限之间的连续值。这给了调度系统一个新的控制自由度——碳价高时多捕,碳价低时少捕,和机组出力一起优化。这个"可调捕集率"是整个模型灵活性的关键。

2.3 电转气系统:从电解水到甲烷化的化学计量约束

P2G分两步:电解水制氢,再加氢甲烷化。总反应可以写成:

2H₂O → 2H₂ + O₂(电解) CO₂ + 4H₂ → CH₄ + 2H₂O(甲烷化)

理论上1 mol CH4需要1 mol CO2和4 mol H2。建模时我把两段合并成一步法模型:已知输入电功率P_p2g,总效率η_p2g(电解效率乘甲烷化效率,范围大约45%~60%),则产甲烷功率:

G_ch4 = η_p2g·P_p2g

这里G_ch4按热值计,单位是MW。如果要同时看出气体积,需要除以LHV,约0.00994 MWh/m³,即P2G产气体积约等于G_ch4/0.00994。

按化学计量折算CO2消耗量:产生1 m³CH4大约需要1.96~2.0 kg CO2。如果产甲烷功率为G_ch4(MW),则耗碳量可以写成:

C_co2 = β_co2·G_ch4

β_co2按上述折算大约0.2 t/MWh(因为1 MW功率一小时产气约100 Nm³,耗碳约200 kg)。这个线性系数非常实用,把一个化学反应约束变成了一个简单的比例关系,YALMIP里直接写一行约束。

如果追求更精细,可以把P2G拆成两段建模:电解段P_e2h到H2的转换,甲烷化段由H2和CO2生成CH4,后者受限于CO2供应量。但在24小时调度模型里,一步法精度已经足够,关键是别漏了"P2G耗碳"这一项。我见过不少实现里把P2G建模成单纯的电力负荷,完全不写CO2消耗约束,那等于把系统的碳流逻辑丢了。

2.4 系统级功率平衡与储碳罐动态

设备级约束凑齐后,靠系统平衡把大家绑在一起。我用了四个平衡:

电功率平衡: P_chp + P_wind = P_load + P_p2g + P_ccs

热功率平衡: H_chp = H_load + H_ccs

气网平衡:P2G产气全部外送气网,作为一个次级模块处理,不承担系统内燃气负荷时,只记录产气量收益。

碳平衡:捕集量、P2G消耗量、储碳罐充放必须闭合。

储碳罐是容易被忽略的元件。为了应对P2G和CCS运行节奏不一致,系统里会有一个CO2缓冲罐,动态约束为:

S_co2(t+1) = S_co2(t) + E_cap(t) - C_co2(t)

0 ≤ S_co2 ≤ S_max,且S_co2(1) = S_co2(T+1),保证一天内碳库存回归初始值。这个约束在Matlab里用sdpvar定义一个T+1维变量就能实现,YALMIP天然支持时间索引,写起来很顺手。储碳罐让CCS和P2G不必实时匹配,CCS多捕的碳可以先存着,等P2G有电可吃时再消耗,大大增加系统灵活性。

3. Matlab实现路线:从目标函数到YALMIP+CPLEX求解

3.1 目标函数选择:运行成本、碳交易与弃风惩罚的权重

优化目标我选择低碳经济调度,把运行成本和碳排放在同一个目标里权衡。具体包含四部分:

  • CHP燃料成本:Σ c_coal·F_chp(t),c_coal按煤价折算
  • 碳交易成本:Σ c_co2·E_net(t),碳价设为100元/t。净排放越多,成本越高
  • 弃风惩罚:Σ c_wind·(P_wind_avail(t) - P_wind(t)),让优化器尽量消化风电,不消纳就罚钱
  • P2G售气收益:-Σ π_gas·G_ch4(t)/LHV,产出的甲烷按气价出售,这是负成本项

整体目标函数就是:

cost = 燃料成本 + 碳成本 - 售气收益 + 弃风惩罚

为什么弃风要放目标函数而不是硬约束?因为如果硬性要求风电全消纳,在某些时段CHP受热负荷限制压不下去时模型会无解。用惩罚项代替硬约束,既保证可行又有经济解释,这是调度建模里很实用的处理技巧。

3.2 核心代码骨架:变量定义与约束组装的规范写法

我的Matlab环境是MATLAB 2023b加YALMIP加CPLEX。先定义决策变量:

T = 24; P_chp = sdpvar(1, T, 'full'); % CHP电出力 H_chp = sdpvar(1, T, 'full'); % CHP热出力 P_wind = sdpvar(1, T, 'full'); % 风电实际并网功率 P_p2g = sdpvar(1, T, 'full'); % P2G耗电 G_ch4 = sdpvar(1, T, 'full'); % 产甲烷功率 P_ccs = sdpvar(1, T, 'full'); % CCS电耗 E_cap = sdpvar(1, T, 'full'); % 捕集CO2量 C_co2 = sdpvar(1, T, 'full'); % P2G消耗CO2量 S_co2 = sdpvar(1, T+1, 'full'); % 储碳量

约束组装的核心思路是逐条追加到Constraints变量里,最后一次性丢给求解器:

Constraints = []; % 电平衡 Constraints = [Constraints, P_chp + P_wind == P_load + P_p2g + P_ccs]; % 热平衡(CCS热耗从CHP热出力中扣除) Constraints = [Constraints, H_chp == H_load + H_ccs]; % CHP抽凝式可行域 Constraints = [Constraints, P_min <= P_chp <= P_max]; Constraints = [Constraints, 0 <= H_chp <= H_max]; Constraints = [Constraints, P_chp >= P_min + beta1 * H_chp]; Constraints = [Constraints, P_chp <= P_max - beta2 * H_chp]; % CCS捕集与能耗 Constraints = [Constraints, E_cap <= t_c_max * e_int * P_chp]; Constraints = [Constraints, P_ccs == lambda_el * E_cap]; Constraints = [Constraints, H_ccs == lambda_heat * E_cap]; % P2G和耗碳 Constraints = [Constraints, G_ch4 == eta_p2g * P_p2g]; Constraints = [Constraints, C_co2 == beta_co2 * G_ch4]; % 储碳罐动态 for t = 1:T Constraints = [Constraints, S_co2(t+1) == S_co2(t) + E_cap(t) - C_co2(t)]; end Constraints = [Constraints, 0 <= S_co2 <= S_max]; Constraints = [Constraints, S_co2(1) == S_co2(T+1)];

这套骨架我每次搭新模型都从它开始改。YALMIP这层封装比较友好,约束维度就是单纯的1×T向量比较,不容易写错。唯一的提醒是H_ccs需要先用系数算出来再放进约束,不然等式两边都有决策变量展开时会乱。我习惯先定义所有辅助变量,再组装约束,最后写目标函数,条理最清晰。

3.3 求解器配置、可行性检查与结果导出

求解器配置看似小事,其实踩坑不少。我的标准模板是:

ops = sdpsettings('solver', 'cplex', 'verbose', 2); sol = optimize(Constraints, cost, ops); if sol.problem == 0 fprintf('求解成功\n'); P_chp_opt = value(P_chp); H_chp_opt = value(H_chp); else disp(sol.info); pause; end

这里有两个重点。第一,多目标别直接加警示和权重后就让求解器闷头跑,最好先单独跑一次不考虑弃风惩罚的模型,看目标函数量级,再设定惩罚系数至少比正常量级高一个数量级。否则会出现一种很尴尬的情况:弃风惩罚设定太低,优化器觉得弃点风比开P2G更划算,结果明明能消纳却故意弃风。第二,value()函数在YALMIP里用来提取解,勘察解时我习惯一次性把所有变量的value结果塞进一个小结构体,后续画图和分析都方便。

可行性检查也是我必须做的一步。如果sol.problem不为0,先不要怀疑求解器坏了,先检查约束是否写成了不一致的等式,比如电平衡里忘了加负荷,或者储碳罐初值和末值约束跟实际数据冲突。我通常用sdpsettings('debug',1)打开YALMIP的调试模式,它能定位到具体哪条约束不可行,省去大量盲试时间。

4. 算例验证:三套场景跑完,碳循环到底省在哪

4.1 算例数据与场景设计

复现研究论文时,算例数据是最大的坎。我采用了一套典型日数据:风电数据取自某风电场冬季典型日功率曲线,电负荷和热负荷也采用典型冬季日曲线。CHP参数参考了一台100 MW级抽凝机组的公开数据,P2G总效率取50%,CCS捕集效率上限90%。

我设计了三个场景:

  • 场景A:传统CHP调度,不含CCS和P2G,风电可弃
  • 场景B:加CCS,但P2G不投运,捕集到的CO2外售或封存
  • 场景C:加CCS加P2G,捕集的CO2部分供P2G合成甲烷

这样设计的好处是结果能一层层解释:B相对A体现CCS的减排代价,C相对B体现P2G对碳循环和弃风消纳的贡献。

4.2 运行成本、碳排放与弃风率的横向对比

算例结果(以A场景各项指标为基准1.00做归一化,参数为典型文献规模数据):

指标场景A(传统CHP)场景B(CHP+CCS)场景C(CHP+CCS+P2G)
总运行成本1.001.100.96
碳排放量1.000.430.31
弃风率12%18%2%

这个结果我最初看到时还愣了一下:加了CCS后弃风率反而从12%涨到18%?仔细一想才明白,CCS本身吃电,挤占了风电的上网空间,在没有柔性负荷帮忙消纳的情况下,弃风自然更严重。而场景C引入P2G后,P2G变成一个巨大的灵活电负荷,弃风率被压到2%,同时捕集的CO2被拿去产甲烷,也算创造收入,总的运行成本甚至比传统基准场景还低了一点。

碳排放方面,场景B直接把排放压到基准的43%,场景C更进一步到31%。注意这31%是净排放,即总排放减去捕集并封存的碳,被P2G利用的那部分碳虽然最终燃烧后又回到大气,但已经算过一次燃料的能量利用,系统层面的净碳排账就是这样算的。

4.3 结果背后的机理:CCS调度的"双刃剑"效应

这个算例最值得琢磨的是CCS的双重身份。一方面它是减排功臣,另一方面它是电耗大户。场景B里CCS的运行完全受制于机组出力,风电大发时CHP压低出力,CCS跟着没烟气可捕,捕集量也上不去;风电小发时段CHP顶上来,CCS又消耗大量电能抬高用电高峰。结果就是CCS和风电"错位",弃风不降反升。

场景C能解决这个错位,靠的是储碳罐和P2G的配合。风电大发时,P2G加大吃电,产气需要CO2,这时候储碳罐里之前存的CO2就派上用场了;等到风电乏力、CHP顶上去时,CCS大量捕碳,一边补充储碳罐,一边继续供P2G或其他用途。换句话说,P2G不再需要和CCS同频运行,中间加了一个缓冲罐,整个系统的时序耦合就被打通了。

这就是我把储碳罐动态约束单独列为一个小节的原因。很多简化模型把P2G和CCS直连,默认"捕多少用多少",完全丢失了这个时间解耦能力,得到的结论自然比完整模型差一大截。

5. 复现过程中最容易被卡住的地方:我的排查思路

5.1 非线性约束的线性化处理:Big-M与分段线性

这个模型里最危险的非线性源有三个:CHP燃料成本曲线里的二次项、P2G效率随负荷变化的分段特性、还有设备启停状态和出力的乘积项。

我处理的原则是能线性化就不保留非线性。燃料成本曲线如果给的是二次函数,就按工作点做分段线性化,把二次项拆成几个线性段,用YALMIP的线性分段功能或者自己写Big-M约束。P2G效率我直接取常数,因为目前文献报道的总效率差异在45%~60%之间,对日前调度的结论影响远小于碳价和负荷预测误差。

启停状态最容易把人坑进去。如果CHP可以启停,就需要二进制变量u_chp,约束里会出现u_chp·P_chp这类乘积。你说它是线性的吧,变量的变量相乘它不是凸的,CPLEX会直接报错或者退化成极端慢的混合整数二次规划。正确做法是用大M法,把"机组停机时出力为0"写成P_chp ≤ M·u_chp,用M表示一个足够大的数。这个M不要取太大,否则数值稳定性变差,一般取机组最大出力的1.2倍左右就够。

5.2 YALMIL/CPLEX版本与环境不一致的坑

Matlab做优化最让人崩溃的问题往往不是模型错,而是环境错。我用MATLAB 2023b时遇到过YALMIP旧版本和CPLEX新版本不兼容的情况,具体表现是求解器报"Unknown solver"或"Index exceeds array bounds"这种完全摸不着头脑的错。排查了两次才明白是YALMIP的求解器清单solver list里没认到CPLEX。

我的解法是:先把CPLEX装好,然后在YALMIP命令行里跑yalmiptest看那些诊断信息,确认solver后面出现cplex几个字,再去model里调。如果还是认不到,手动加一句solverPath设置CPLEX可执行文件路径。另外要注意,2023b以后的matlab把一些老函数换了名,直接影响第三方工具箱,我建议直接装最新版YALMIP(GitHub上那个R2023xxxx版本),别用老版本硬抗。

5.3 模型规模膨胀:从24时段到多机组怎么控制求解时间

第一个版本我写完直接跑24时段,CPLEX大概几秒钟出结果,当时觉得顺畅得很。后来有人问我加多台CHP和多台P2G怎么办,我把变量维数翻了3倍,求解时间暴涨到十几分钟。问题出在储碳罐约束里每个时段的S_co2和C_co2相互依赖,加上二进制变量的组合爆炸,一时很难收敛。

我做的优化有两点。一是尽量用向量化约束代替for循环组装,YALMIP里对于逐时段相同的约束,如果约束矩阵是带Toeplitz结构,可以整个块写进去,速度提升明显。二是把非必要的二进制变量全部换成连续变量。比如P2G如果不需要考虑启停成本,就完全用连续变量建模,不做0-1决策,整个问题立刻从MILP简化成LP,秒收敛。

还有一个通用技巧:先用粗粒度跑一遍(比如把24小时聚合成6个典型时段),确认模型逻辑没有bug,再切成24时段跑精修的。我犯过的错是直接用24时段跑,结果储碳罐初始值约束写反,跑了十分钟才发现结果全错。

5.4 数据来源与参数标定:别随便从论文里抄数字

做这类复现,最花时间的其实是数据。我总结了三类来源。

第一类是机组公开技术手册,CHP的可行域参数、效率曲线可以从厂家的典型运行数据里找。第二类是电网和气象公开数据,风电出力曲线可以从一些开源数据集里拿。第三类是文献里的模型参数,比如CCS的再生热耗、P2G总效率,可以从综述论文里获取。

但这里要特别提醒:文献里给的参数往往是在特定假设下的,直接抄会跟自己的系统对不上。我的做法是把关键参数都设成一个参数结构体放在文件最前面,用注释标清楚来源,后续调参只需要改这一个文件。比如碳价现在按100元/t算,如果政策变了或交易所价格波动,改一个数字整个系统重新跑一遍就行,不需要动模型。

另外数据的量纲一致性绝对不能出问题。我最狼狈的一次是把CO2捕集量的单位写成了kg,而P2G耗碳量单位是t,结果目标函数里碳成本凭空大了1000倍,求解器倒没报错,只是跑出来的调度策略傻得不忍直视。后来我所有的单位换算都集中在一个unit_convert.m文件里,再也不用到处填系数。

复现这种综合能源系统的优化模型,说到底不是把设备和约束堆起来那么简单,而是要在模型里保留系统的真实灵活性。我最大的体会是:P2G和CCS这个组合,真正的价值不是"多装了两台设备",而是给调度系统增加了两个自由度——一个可调的碳捕集率,一个可调的柔性电负荷,再加上储碳罐的时间解耦,CHP的"以热定电"约束才算真正被打破。建模时如果把这三个自由度丢了,那代码写得再漂亮,算出来的也只是一个新瓶子装旧酒。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询