这几年读过不少虚拟电厂调度方向的论文和代码,大多数模型的骨架是“火电+风电+储能+负荷”,把需求响应和电动汽车塞进去就算进阶。但如果题目里出现了“碳捕集”“电转气”“垃圾焚烧”这三样,事情的性质就变了——它们不是简单在机组清单里多加三台设备,而是会改变整个VPP的能量流和碳流走向。这篇博文就针对“计及电转气协同的含碳捕集与垃圾焚烧虚拟电厂优化调度”这个方向,把建模仿真时真正需要想清楚的事情按我的复现顺序讲一遍,从数学模型拆解、目标函数设计,到Matlab代码实现和算例结果分析,最后是踩坑记录。适合正在做综合能源系统优化、虚拟电厂调度,或者准备落地这类论文作为毕业设计、期刊论文复现的同学参考。
1. 从“各自为战”到“VPP协同”:这套模型到底在解决什么问题
1.1 垃圾焚烧发电:出力稳定但排放敏感的“主力热源”
垃圾焚烧发电这几年在园区级能源系统里存在感很强,核心原因是它的出力特性比风电光伏稳定太多。只要垃圾来料量确定,焚烧炉的热功率基本可控,机组可以承担基荷甚至部分腰荷,不像光伏晚上归零、风电一言不合就反调峰。这也是虚拟电厂愿意把它收编进来的原因——一个聚合了垃圾焚烧的新能源虚拟电厂,在参与电网调度时首先就有“出力可信度”的优势。
但垃圾焚烧的优点和麻烦是绑在一起的。麻烦在于烟气排放,尤其是CO2。虽然垃圾中的生物质部分焚烧时可以被视为碳中和来源,但在一个以“含碳捕集”为卖点的研究框架里,垃圾焚烧机组仍然是园区内最大的集中排放源,它的烟气CO2浓度高、排量稳定,恰恰是碳捕集最喜欢的处理对象。也是因为这个原因,很多含垃圾焚烧的VPP研究都要叠加碳捕集系统,先在烟气出口把CO2拿掉一部分,园区级的碳排放强度才压得下来。
另一个更现实的理由是电碳耦合。垃圾焚烧机组出力越高,烟气量越大,可捕集的CO2就越多;而捕集CO2需要消耗大量电能和热能用于溶剂再生和气体压缩。这意味着碳捕集装置不能简单当成一个“恒定功率的负设备”建模,它的运行状态必须跟焚烧机组的出力联动。这个联动关系,是整套模型里第一个容易写错的地方,也是决定模型是否“物理自洽”的关键分水岭。
1.2 碳捕集与电转气:两个耗能设备如何形成闭环
电转气在VPP里通常扮演“绿电消纳兜底”的角色:光伏大发、负荷又起不来的时候,与其花钱弃电,不如把电变成氢气,甚至进一步甲烷化变成天然气存起来。电变气的过程本身要耗电,看起来像是“赔本买卖”,但在高弃电率场景下,这部分电能的机会成本很低,P2G反而能帮系统降低总成本。它的定位不是常规电源,而是一个“可调节的储能型负荷”。
这里有一个经常被忽略的协同点:P2G的甲烷化环节需要CO2原料,而碳捕集系统恰好能从烟气里提供高浓度CO2。于是CCS和P2G就形成一个能量流和碳流上的闭环——CCS从垃圾焚烧烟气中捕集碳并输送给P2G,P2G用多余的新能源电力制氢,再与CO2合成甲烷,最终注入燃气管网或直接供给燃气轮机。一次处理,既消纳了电力,又固定了碳。这种“电-气-碳”三联供的架构,让两个耗能设备从互相争电变成互相成就,这也是标题里“电转气协同”最核心的物理内涵。
在数学建模层面,这个闭环意味着不能把CCS的CO2捕集量和P2G的CO2消耗量分开建模。需要引入明确的碳结转关系:被P2G消耗的那部分CO2,可以从系统净排放中扣除;剩余捕集的CO2则作为产品出售或封存。如果忽略这个耦合,模型的碳平衡方程永远对不上,求出的“碳成本”自然也是错的。
1.3 虚拟电厂聚合视角下的协同边界
单看某个设备,调度问题很简单;一旦把这些设备放进同一个虚拟电厂,约束就开始打架。比如碳捕集要耗电,P2G也要耗电,两者叠加之后,原本够用的新能源出力和电网购电可能就不够用了。再比如燃气轮机烧的是P2G提供的甲烷,而P2G的甲烷产量又取决于可再生能源有功出力和当前碳捕集系统供给的CO2量,三者形成时序上的强耦合,任何一个环节出现偏差,整个调度链都会被拖住。
所以整个模型的核心矛盾是:如何在24小时的调度周期内,协调垃圾焚烧、新能源、储能、燃气轮机、CCS、P2G这六个环节的出力或耗能曲线,使得在满足功率平衡和碳约束的前提下,系统运行总成本最低、碳排放尽可能低、新能源弃电率尽可能可控。这就是标题里“协同”二字的真实含义——不是把设备简单拼在一起,而是要让它们的运行曲线互相咬合,最终呈现出一个“既会省电,又会省碳”的调度策略。
2. 系统能流与碳流建模:先画清楚物质流再写约束
2.1 电-气-碳三类耦合流的走向
在写任何Matlab代码之前,我强烈建议先画一张能流/碳流图,把每条物理连接标清楚。因为优化模型里的每一个变量、每一条约束,本质上都是在描述这张图上的某一个箭头。我曾经跳过这一步直接建模,结果约束写了一半发现漏了一条气路,整个模型重做,算是白踩的坑。
电的走向:垃圾焚烧机组、风电、光伏、燃气轮机是电源;负荷、碳捕集装置、P2G电解槽、储能充电是“电的汇”;外部电网购电从电网侧补足差额。
气的走向:天然气从外网购入,也可以由P2G(甲烷化)生产,共同汇入储气装置或直接供应燃气轮机。这里要注意,储气罐的充放状态也属于气网侧的“功率平衡”。
碳的走向:垃圾焚烧和燃气轮机燃烧产生CO2;CCS捕集系统从烟气中分离CO2;“捕获的碳”一部分供给P2G做甲烷化原料,剩下的可视为碳产品出售或者封存;在碳市场配额约束下,系统最终净排放量决定碳交易成本。
这三张流图叠在一起,就是完整的物理模型。缺了任何一条边,优化结果都会失真。
2.2 核心单元数学模型
以典型的一小时为步长、24小时为调度周期为例,我列出各个单元的建模要点,这些都是可以直接换算成Matlab数组和约束的。
垃圾焚烧机组方面,出力和来料量直接挂钩。模型里常用上下限加爬坡率约束,同时需要一条“烟气CO2产量—发电量”的线性关系。如果垃圾热值变化不大,这个比例系数可以当作常数处理;如果来料热值波动大,就需要把垃圾破碎后的入炉量也作为一个变量,模型复杂度会明显上升。
碳捕集装置方面,捕集到的CO2量等于烟气CO2产生量乘以捕集率,捕集率根据装置类型和运行模式可取0.85到0.95。捕集能耗通常分成固定能耗和随捕集量变化的能耗两部分。为了简化MILP模型,能耗与捕集量之间用分段线性关系逼近是最实用的做法。
P2G单元方面,电解槽效率、甲烷化反应耗CO2量、产气量以及额外产热,理论上都是非线性的。工程上可以取典型效率常数,比如1 kWh电能对应约0.6立方米左右的氢热值,再把甲烷化过程的CO2消耗按化学反应计量比折算。更精细的做法是分段线性化处理电解槽变负荷效率曲线,但这对初学者来说,容易把模型求解时间拖得很长。
燃气轮机方面,主要关心气电转换关系、发电效率、碳排放系数和爬坡约束。若P2G产甲烷,燃气轮机消耗的气量和产生的CO2要在碳平衡中体现。
储能设备方面,连续变量是soc和功率,0/1状态变量是充放标志位。储能不能同时充放,这类互斥约束用两个二进制变量实现。
2.3 时间尺度与数据粒度选择
很多人在建模时纠结步长取15分钟还是1小时。我的经验是,如果只做仿真研究和论文复现,1小时步长、24小时周期最合适——算例数据好找、模型规模适中、结果可视化方便。只有在需要精细刻画调峰和快速爬坡的情况下,才考虑15分钟步长,但那样P2G电解槽和储气罐的动态方程都会跟着复杂化,前后端工作量倍增。
此外,典型日的选取也要小心。选一个光伏大发、负荷低谷、风电高渗透的日子,P2G才有发挥空间;选一个负荷高峰、垃圾焚烧满发的日子,CCS才有足够的烟气量潜力。为了对比“有协同/无协同”的效果,我通常设置两到三个不同典型日做敏感性分析,这样结论才不会只被某一个天气场景绑死。
3. 目标函数和约束体系的数学化表达
3.1 经济性目标:运行成本+碳交易成本+惩罚项
在虚拟电厂优化调度里,最常见的目标函数是最小化总运行成本。它的构成可以分为几块,每一块都对应一个真实的资金流出。
第一块是购电费用和购气费用,股价按功率乘以时间累加,气费用乘以消耗量。注意购电费用要用分段电价或实时电价序列表示,不能用一个均值。
第二块是新能源和机组的运维成本,按单位电量运维费率计算。垃圾焚烧机组的运维费率比普通火电略高,因为烟气处理系统维护成本占比大,这个细节会影响机组出力的经济排序。
第三块是碳交易成本,等于净排放量与免费配额的差值乘碳价,净排放高于配额就需要买碳,低于配额卖碳获利。碳价参数对结果影响很大,后面算例里会专门说明。
第四块是弃风弃光惩罚费用,这是为了让优化结果主动消纳新能源而设置的一项成本。如果不加这一项,优化算法很可能为了省设备磨损,选择直接弃掉新能源。
更完整的模型还可以加上机组启停成本。垃圾焚烧机组运行模式相对固定,启停成本通常不高;燃气轮机的启停成本需要单独建模。储能不直接进入目标函数,它的收益已经通过系统购电成本和碳排放成本两个维度间接体现出来了。
3.2 约束清单与线性化处理
为了防止模型无解或出现“虚假节省”,约束条件要成体系地写。我的习惯是分四组:
功率平衡约束,任意时刻发电与购电之和等于负荷与所有耗能设备之和。这一个等式约束就包含了CCS和P2G耗电的耦合影响。
机组与设备运行约束,包括出力上下限、爬坡约束、最小运行/停机时间、储能soc与功率限制、储气罐容量约束等。
碳排放约束,根据系统净排放量限制或碳交易配额约束,同时定义CCS捕集量以及P2G消耗CO2量的结转逻辑。
耦合与逻辑约束,比如P2G产甲烷的量不超过储气/用气需求;燃气轮机的耗气量与产电量的耦合系数;CCS捕集能耗与垃圾焚烧出力的关联关系。
值得提醒的是,很多非线性约束在YALMIP里可以直接写成逻辑表达式让求解器处理,但如果不打算引入非线性求解器,最好在建模阶段就改成分段线性约束,否则求解时间和收敛稳定性会很差。
3.3 碳平衡方程:谁产生碳、谁消耗碳
这部分是整个模型里“含碳捕集”“电转气”两个关键词的落脚点。碳平衡方程要回答三个问题:系统里产生了多少碳、捕集了多少碳、净排放到底是多少。
定义系统内主要碳排放源为垃圾焚烧和燃气轮机的燃烧排放,排放量分别与燃料量或发电量成正比。CCS捕集量等于总产生量乘以捕集率;P2G甲烷化消耗CO2的量等于其甲烷产量折算的CO2需求量;未被捕集的和捕集后未利用的部分,就是最终排放到大气中的量。若模型考虑碳市场,还需要用“最终净排放减免费配额”乘以碳价。
这里的物理含义是:P2G消耗了系统的碳,实际上是把一部分CO2转化成了燃料,燃料再次燃烧时又回到系统,形成碳循环。所以在长期能量平衡中,甲烷化并不是“消除碳”,而是“延迟碳的排放”。但如果用P2G产出的甲烷替代了原本需要外购的化石天然气,那它的减排价值主要体现在替代效应上——这个逻辑必须在结果解读时说清楚,否则很容易被人质疑是在“刷指标”。
4. Matlab+YALMIP求解流程与代码骨架
4.1 环境准备和Matlab建模路线
先把技术栈说清楚:这个问题本质上是混合整数线性规划,Matlab不是求解器,而是建模和数据处理平台。我通常用YALMIP作为建模语言,用CPLEX或Gurobi作为底层求解器。YALMIP的优势是语法直观,写约束就像写数学公式,变量定义、约束拼接、目标函数的三段式结构非常清晰。如果你没有商业求解器,也可以先用Matlab自带的intlinprog跑小规模问题验证逻辑,再换CPLEX跑完整算例。
这里顺带提一句自己踩过的环境问题:YALMIP和求解器之间的版本兼容性容易出问题,常见的报错是“solver not found”“license error”,很多情况下是XML License文件里的HostID和当前机器网卡硬件地址不一致导致的。遇到这类问题,先跑一下YALMIP自带的yalmiptest,看测试项里CPLEX是否显示可用;再检查环境变量路径,确保求解器目录确实加进去了。另外,在较新的Matlab版本上安装老版本YALMIP,有时会出现函数库冲突,建议优先从GitHub仓库拉最新版,而不是用几年前的打包版。
4.2 优化建模代码骨架
下面是简化后的YALMIP建模骨架,完整的算例还包括数据读取和结果导出。这段代码展示的是变量定义、约束拼接和目标函数的基本组织方式,我用注释说明了每一块在物理上对应什么:
%% 定义时段与设备参数 T = 24; % 调度周期,小时数 % 假设已有输入序列: % P_wte 垃圾焚烧预测出力(kW) % P_wind 风电预测出力(kW) % P_pv 光伏预测出力(kW) % P_load 负荷预测曲线(kW) %% 决策变量 P_gt = sdpvar(1, T); % 燃气轮机出力 P_bat = sdpvar(1, T); % 储能放电为正,充电为负 soc = sdpvar(1, T); % 储能荷电状态 u_bat = binvar(1, T); % 1表示放电,0表示充电 P_p2g = sdpvar(1, T); % 电转气耗电功率 P_ccs = sdpvar(1, T); % 碳捕集耗电功率 M_co2 = sdpvar(1, T); % CCS捕集到的CO2量 M_co2_p2g = sdpvar(1, T); % 供给P2G甲烷化的CO2量 P_grid = sdpvar(1, T); % 从外部电网购电 %% 约束 Constraints = []; % 功率平衡:电源之和 = 负荷 + 各耗能设备 Constraints = [Constraints, P_wte + P_wind + P_pv + P_gt + P_bat + P_grid ... == P_load + P_p2g + P_ccs]; % 储能soc递推与充放互斥 Constraints = [Constraints, soc(2:T) == soc(1:T-1) + 充电功率 - 放电功率]; Constraints = [Constraints, P_bat <= M * u_bat]; % 大M法限制放电 % CCS捕集量与耗电耦合 Constraints = [Constraints, P_ccs == k_ccs * M_co2]; % P2G耗电与产气耦合,以及消耗的CO2 Constraints = [Constraints, P_gas_out == k_p2g * P_p2g]; Constraints = [Constraints, M_co2_p2g == k_co2_per_gas * P_gas_out]; % 碳量平衡:捕集量 = 供给P2G的量 + 出售/封存量 Constraints = [Constraints, M_co2 == M_co2_p2g + M_co2_sell]; % ...其他机组爬坡、储气罐容量、碳配额等约束 %% 目标函数 Objective = sum(购电电价 .* P_grid) ... + sum(气价 .* 外购天然气量) ... + sum(运维成本项) ... + sum(碳价 .* (净排放 - 免费配额)) ... + sum(弃电惩罚系数 .* P_abandon); %% 求解 ops = sdpsettings('solver', 'cplex', 'verbose', 2, 'mip.tolerances.mipgap', 0.005); optimize(Constraints, Objective, ops);这只是一个骨架,真正的难点在把CCS和P2G的耦合约束写对。代码里我习惯用“分离的物理量”而不是“合成量”,例如把捕集到的CO2分成“给P2G”和“出售/封存”两个变量,这样碳平衡检查时能直接从数值上看出每个路径的碳去了哪里。
4.3 求解结果解析与边界校验
优化完成不等于模型正确。拿到结果后,我通常做三件事来校验:
第一,把最优解代回功率平衡方程,检查每个时段的电力、气量、碳量是否都闭合,任何偏差超过1%都说明约束写错了。这一步看着笨,却是发现隐性bug最有效的手段。
第二,对比各时段CCS的捕集量和垃圾焚烧出力的比例关系,看是否落在设计捕集率范围内。如果出现“垃圾焚烧停机但CCS满负荷运行”的情况,说明约束里漏掉了烟气来源限制。
第三,检查二进制变量对应的结果是否符合物理常识,比如储能是否在电价高的时段放电,在电价低的时段充电,P2G是否在弃电时段运行,燃气轮机是否只在气价低或需要快速爬坡时才启动。
全套校验跑过之后,再谈算例分析才有实际意义。
5. 典型日仿真算例:从结果反推模型是否合理
5.1 场景设置与输入参数设计
为了验证CCS和P2G对虚拟电厂调度的影响,我通常会设置三组对照场景:不装碳捕集、只装碳捕集但不装P2G、碳捕集与P2G协同运行。三组场景都用同一个典型日数据,确保新能源出力曲线、负荷曲线、垃圾焚烧来料曲线完全一致,变化的只是设备集合,这样对比出来的差异才能归因到设备组合本身。
输入参数方面,最重要的是新能源出力曲线、电量电价、天然气价格和碳交易价格。碳价对含CCS的模型影响极大:碳价只有30元/吨左右时,CCS捕集一吨碳的电耗和运维成本可能比配额价格还高,这时候最优解很可能选择不捕集;碳价涨到120元/吨甚至更高,碳捕集的减排收益才开始真正体现。所以在做结果分析时,至少要针对碳价做一个敏感性分析,否则“加了CCS所以成本更低”这个结论在低碳价格下根本不成立。
5.2 多场景对比:CCS和P2G各自贡献了什么
下面这个相对结果表格是典型参数配置下的示例,不要把它当普适结论,但它很有代表性。数值以基准场景的总成本作为1.00计算,目的是展示各设备组合之间的相对关系:
| 场景 | 总成本(相对值) | 弃电率 | 净碳排放量(相对值) |
|---|---|---|---|
| 基准VPP(无CCS、无P2G) | 1.00 | 12.5% | 1.00 |
| 有CCS、无P2G | 0.97 | 13.2% | 0.62 |
| 有CCS、有P2G协同 | 0.91 | 5.8% | 0.48 |
从结果里能读出几个关键信号。单独加CCS时,总成本和弃电率变化不大,但净碳排放量大幅下降——这是因为CCS本身只处理碳排放问题,对新能源消纳没有直接帮助,甚至因为增加了额外耗电,系统需要更多外购电力,弃电率反而略微上升。加上P2G之后,弃电率显著下降,碳排放又进一步降低,这就印证了“协同”的价值:P2G吃掉了原本会被弃掉的新能源电力,而它产气时又需要CCS提供的CO2,两个系统在运行上实现了互相支撑。
5.3 模型有效性检验方法
算例结果合理并不代表模型没有bug。我常用的检验方法是“极端化检查”:把碳价设为0,看碳捕集量是否迅速降到技术允许的下限;把新能源出力曲线整体抬高50%,看弃电量变化是否符合直觉;把气价定得很高,看P2G是否更倾向于全时段产气来替代购气。这种小技巧能快速定位约束写错的环节,比只盯着总成本指标靠谱得多。另一个实用做法是逐时段画堆叠图,现在很多Matlab脚本推荐用stackedplot可视化各类电源出力与负荷曲线,一旦出现某条曲线在负荷高峰时段突变为0,基本就能锁定到对应设备的约束漏洞。
6. 复现过程中最常踩的坑与解决清单
6.1 碳捕集能耗归属不清导致功率失衡
这是新手最容易犯的错误。很多人把CCS的耗电量当作“系统固定的额外负荷”,与垃圾焚烧机组的出力完全脱钩。实际中CCS的能耗主要来自吸收剂再生和压缩机,能耗量直接取决于捕集到的CO2量,也就是和焚烧炉的产烟量有关。如果模型里P_ccs是一个和焚烧出力无关的固定值,那么当焚烧机组出力下调时,模型仍会按满负荷捕集量算能耗,结果会把所有电源出力往上抬,功率平衡看着能过,实际上完全违背物理。
正确做法是把CCS耗电设置为捕集量的线性函数,再把这个函数纳入整体功率平衡等式。同等重要的一步是允许优化器决定“捕集量”本身,而不是把捕集率固定在额定值。这样系统在电价高、新能源出力不足的时段,可以选择适度降低捕集量来让出功率,给调度器增加一层灵活性。
6.2 P2G产物形态与效率曲线失真
P2G的产物区分很关键。电解水直接产氢,单位电耗较低,但下游燃气轮机如果只吃天然气,氢气就需要额外的掺混设施;如果直接产甲烷,需要额外的CO2原料和甲烷化反应器,单位综合效率会有损耗。很多模型把所有P2G都简化为“直接产甲烷”,又忽略了CO2原料约束,导致碳平衡中CO2来源始终是空的,结果里看似“零碳”,一查物理机制全是漏洞。
另一个问题是拿固定效率代表全工况。电解槽在低负荷区间的效率会明显下降,用一个常数会高估低谷时段P2G的产气量。如果想要更贴近实际情况,又不想让模型非线性化太重,首选分段线性效率,或者干脆给P2G设置一个最低运行功率,把严重偏离设计工况的低效率区间直接禁掉。
6.3 单位、求解器参数和启停变量带来的隐形错误
最后一个坑就是单位不一致。发电功率用MW、电量却用kWh计算,气价用元/立方米、燃气热值却用MJ/kg,这样的模型解不收敛还算小事,算出来结果偏差几十个百分点才是真正恼人的。我的习惯是把所有物理量统一折算到“每小时”为基准,气体流量的单位全部按热值折算成MW,而不是直接写体积流量。这样整个模型的单位体系才闭合。
求解器参数也有讲究。MILP模型如果默认的MIP gap设成0.01%,在T=24、多个二进制变量的情况下会异常慢。我一般把MIP gap放宽到0.5%或1%,计算精度损失很小,计算时间却能从几小时降到几分钟。启停变量里面,如果已经写入了“最小启停时间”约束,记得不要把启停成本重复计入目标函数两次,否则模型会为了省启停成本而输出锯齿形的启停序列,看起来很不自然。
最后分享一个调试技巧:在YALMIP里给二进制变量赋初值也是调试MILP的好办法,先用一个整数可行解作为起点,求解器收敛速度会快很多。如果你看到“infeasible problem”报错,优先检查互斥约束的0-1组合是否覆盖了所有状态空间,充电与放电、启动与停机、进气与排气这三类互斥标志,是最常见的无解根源。找到并修正这组约束后,几乎所有模型都能顺利求解。