这几年读过不少虚拟电厂调度方向的论文和代码大多数模型的骨架是“火电风电储能负荷”把需求响应和电动汽车塞进去就算进阶。但如果题目里出现了“碳捕集”“电转气”“垃圾焚烧”这三样事情的性质就变了——它们不是简单在机组清单里多加三台设备而是会改变整个VPP的能量流和碳流走向。这篇博文就针对“计及电转气协同的含碳捕集与垃圾焚烧虚拟电厂优化调度”这个方向把建模仿真时真正需要想清楚的事情按我的复现顺序讲一遍从数学模型拆解、目标函数设计到Matlab代码实现和算例结果分析最后是踩坑记录。适合正在做综合能源系统优化、虚拟电厂调度或者准备落地这类论文作为毕业设计、期刊论文复现的同学参考。1. 从“各自为战”到“VPP协同”这套模型到底在解决什么问题1.1 垃圾焚烧发电出力稳定但排放敏感的“主力热源”垃圾焚烧发电这几年在园区级能源系统里存在感很强核心原因是它的出力特性比风电光伏稳定太多。只要垃圾来料量确定焚烧炉的热功率基本可控机组可以承担基荷甚至部分腰荷不像光伏晚上归零、风电一言不合就反调峰。这也是虚拟电厂愿意把它收编进来的原因——一个聚合了垃圾焚烧的新能源虚拟电厂在参与电网调度时首先就有“出力可信度”的优势。但垃圾焚烧的优点和麻烦是绑在一起的。麻烦在于烟气排放尤其是CO2。虽然垃圾中的生物质部分焚烧时可以被视为碳中和来源但在一个以“含碳捕集”为卖点的研究框架里垃圾焚烧机组仍然是园区内最大的集中排放源它的烟气CO2浓度高、排量稳定恰恰是碳捕集最喜欢的处理对象。也是因为这个原因很多含垃圾焚烧的VPP研究都要叠加碳捕集系统先在烟气出口把CO2拿掉一部分园区级的碳排放强度才压得下来。另一个更现实的理由是电碳耦合。垃圾焚烧机组出力越高烟气量越大可捕集的CO2就越多而捕集CO2需要消耗大量电能和热能用于溶剂再生和气体压缩。这意味着碳捕集装置不能简单当成一个“恒定功率的负设备”建模它的运行状态必须跟焚烧机组的出力联动。这个联动关系是整套模型里第一个容易写错的地方也是决定模型是否“物理自洽”的关键分水岭。1.2 碳捕集与电转气两个耗能设备如何形成闭环电转气在VPP里通常扮演“绿电消纳兜底”的角色光伏大发、负荷又起不来的时候与其花钱弃电不如把电变成氢气甚至进一步甲烷化变成天然气存起来。电变气的过程本身要耗电看起来像是“赔本买卖”但在高弃电率场景下这部分电能的机会成本很低P2G反而能帮系统降低总成本。它的定位不是常规电源而是一个“可调节的储能型负荷”。这里有一个经常被忽略的协同点P2G的甲烷化环节需要CO2原料而碳捕集系统恰好能从烟气里提供高浓度CO2。于是CCS和P2G就形成一个能量流和碳流上的闭环——CCS从垃圾焚烧烟气中捕集碳并输送给P2GP2G用多余的新能源电力制氢再与CO2合成甲烷最终注入燃气管网或直接供给燃气轮机。一次处理既消纳了电力又固定了碳。这种“电-气-碳”三联供的架构让两个耗能设备从互相争电变成互相成就这也是标题里“电转气协同”最核心的物理内涵。在数学建模层面这个闭环意味着不能把CCS的CO2捕集量和P2G的CO2消耗量分开建模。需要引入明确的碳结转关系被P2G消耗的那部分CO2可以从系统净排放中扣除剩余捕集的CO2则作为产品出售或封存。如果忽略这个耦合模型的碳平衡方程永远对不上求出的“碳成本”自然也是错的。1.3 虚拟电厂聚合视角下的协同边界单看某个设备调度问题很简单一旦把这些设备放进同一个虚拟电厂约束就开始打架。比如碳捕集要耗电P2G也要耗电两者叠加之后原本够用的新能源出力和电网购电可能就不够用了。再比如燃气轮机烧的是P2G提供的甲烷而P2G的甲烷产量又取决于可再生能源有功出力和当前碳捕集系统供给的CO2量三者形成时序上的强耦合任何一个环节出现偏差整个调度链都会被拖住。所以整个模型的核心矛盾是如何在24小时的调度周期内协调垃圾焚烧、新能源、储能、燃气轮机、CCS、P2G这六个环节的出力或耗能曲线使得在满足功率平衡和碳约束的前提下系统运行总成本最低、碳排放尽可能低、新能源弃电率尽可能可控。这就是标题里“协同”二字的真实含义——不是把设备简单拼在一起而是要让它们的运行曲线互相咬合最终呈现出一个“既会省电又会省碳”的调度策略。2. 系统能流与碳流建模先画清楚物质流再写约束2.1 电-气-碳三类耦合流的走向在写任何Matlab代码之前我强烈建议先画一张能流/碳流图把每条物理连接标清楚。因为优化模型里的每一个变量、每一条约束本质上都是在描述这张图上的某一个箭头。我曾经跳过这一步直接建模结果约束写了一半发现漏了一条气路整个模型重做算是白踩的坑。电的走向垃圾焚烧机组、风电、光伏、燃气轮机是电源负荷、碳捕集装置、P2G电解槽、储能充电是“电的汇”外部电网购电从电网侧补足差额。气的走向天然气从外网购入也可以由P2G甲烷化生产共同汇入储气装置或直接供应燃气轮机。这里要注意储气罐的充放状态也属于气网侧的“功率平衡”。碳的走向垃圾焚烧和燃气轮机燃烧产生CO2CCS捕集系统从烟气中分离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. MatlabYALMIP求解流程与代码骨架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、无P2G1.0012.5%1.00有CCS、无P2G0.9713.2%0.62有CCS、有P2G协同0.915.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%在T24、多个二进制变量的情况下会异常慢。我一般把MIP gap放宽到0.5%或1%计算精度损失很小计算时间却能从几小时降到几分钟。启停变量里面如果已经写入了“最小启停时间”约束记得不要把启停成本重复计入目标函数两次否则模型会为了省启停成本而输出锯齿形的启停序列看起来很不自然。最后分享一个调试技巧在YALMIP里给二进制变量赋初值也是调试MILP的好办法先用一个整数可行解作为起点求解器收敛速度会快很多。如果你看到“infeasible problem”报错优先检查互斥约束的0-1组合是否覆盖了所有状态空间充电与放电、启动与停机、进气与排气这三类互斥标志是最常见的无解根源。找到并修正这组约束后几乎所有模型都能顺利求解。