“垃圾焚烧电厂、风电光伏、碳捕集装置、电转气设备,全部揉进同一个虚拟电厂里做日前优化调度”这件事,听起来像在拼一套复杂的乐高,实际上比大多数人想得更麻烦。我最近把“计及电转气协同的含碳捕集与垃圾焚烧虚拟电厂优化调度”这套模型用Matlab完整跑通了一遍,从目标函数搭建、约束条件梳理到用Yalmip调Cplex求解,中间踩了不少坑。今天不聊虚的,直接把这套代码的设计思路、数学建模细节、代码组织方式和调试经验一次说清楚,给正在做虚拟电厂优化调度、综合能源系统方向,或者准备用Matlab复现类似论文模型的朋友一个可以直接参考的版本。
这套东西的价值在哪里?说白了就是:当虚拟电厂里同时存在垃圾焚烧机组(碳排放源头)、碳捕集装置(减碳设备)、电转气单元(消纳新能源+消耗二氧化碳的设备)时,它们之间不是简单并联关系,而是通过电能流、天然气流、二氧化碳流三层网络深度耦合。如果用“各管各的”那种独立调度思路去做,弃风弃光、碳排放超标、燃气供应缺口这些问题全都会被放大。把P2G和碳捕集放在同一个优化框架里协同调度,才能真正把“碳”从负担变成资源。
1. 这套代码到底在解决什么问题
1.1 虚拟电厂里为什么要塞进垃圾焚烧机组
很多做虚拟电厂(VPP)模型的人,一上来就习惯把风电、光伏、储能、燃气轮机这些“干净”设备塞进去,几年都没碰过垃圾焚烧。但垃圾焚烧电厂在双碳背景下其实非常微妙:它一方面处理城市生活垃圾,社会效益在那儿摆着;另一方面,燃烧过程必然产生大量CO2,属于典型的“不得不排”。
可是从虚拟电厂的角度看,垃圾焚烧机组和普通火电有一个本质区别:它不只是发电,还在处理固废。你把它排除在VPP之外,就相当于放弃了一个稳定可控的出力单元。垃圾焚烧机组只要垃圾供应不断,就可以连续稳定发电,不像风电光伏看天吃饭。问题是它排放二氧化碳,如果直接把排放成本丢给碳市场,经济性会很难看。这时候碳捕集装置就该上场了——把垃圾焚烧产生的CO2尽量捕下来,减排的同时争取碳收益。
1.2 电转气与碳捕集不是拼盘,而是一条闭环链
很多论文把P2G(电转气)和CCS(碳捕集)当成两个独立设备处理,各写各的约束,最后塞进同一个目标函数里。这个做法没错,但远远不够。P2G里的甲烷化反应需要CO2作为原料,而碳捕集装置捕下来的CO2如果只拿去封存,成本高不说,还换不来多少产品价值。这两者天然就应该“手拉手”:
电力驱动电解槽制氢,氢气再去和捕集下来的CO2反应生成甲烷。这样,本来要花钱处理、花钱封存的CO2,变成了生产天然气的原料。垃圾焚烧电厂排放的碳,经过捕集、利用,最终变成甲烷重新回到燃气系统里,形成一条“发电→捕碳→制氢→产甲烷”的闭环链。
在优化调度里,这条闭环链意味着什么?意味着决策变量之间多了强耦合:P2G出力大的时候,它对CO2的需求也大,碳捕集装置就得加大捕集量;而碳捕集装置加大捕集量又需要消耗更多蒸汽和电能,反过来影响汽轮机的对外供电能力。这就是“计及电转气协同”这个题眼的核心逻辑。
1.3 一体化调度与独立调度的差距
如果把P2G和CCS建模成分离模块,我在实际调试中的体会是:结果经常出现“碳捕集量不足、P2G没碳可用”的尴尬局面,或者“P2G为了消纳弃风拼命产气,但碳捕集跟不上,最后只能外购CO2”的荒唐情况。一体化后,碳捕集速率、储碳罐状态、P2G负荷率被放进同一个优化问题里解,系统会自动在弃风时段加大P2G和CCS出力,在负荷高峰让CCS适当降载、优先保电。
这套代码解决的就是这个核心问题:如何让垃圾焚烧机组的排放特性、碳捕集的能耗特性、P2G的能量转换特性在同一个日前调度模型里达到最优平衡,使得综合运行成本最低、碳排放最少、弃风弃光率最小。
2. 系统架构与协同运行机制
2.1 设备构成与能源流、碳流走向
我搭建这个虚拟电厂时,设备清单大致如下:
- 垃圾焚烧发电机组(WtE):额定发电功率10 MW,日处理垃圾量300吨左右,是系统的主力稳定电源。
- 风电和光伏:作为清洁电源接入,场景设定为冬季典型日风电偏大、光伏偏小,便于验证消纳效果。
- 电转气系统(P2G):包含电解槽和甲烷化装置,最大输入电功率4 MW。
- 碳捕集装置(CCS):采用燃烧后化学吸收法建模,最大捕集CO2能力约5 t/h,捕集需消耗蒸汽和电。
- 储电、储气、储碳罐:分别平衡电、气、碳三网的时移量。
- 外部市场接口:可以向电网购电和售电,可以购买天然气补充气负荷。
这几类设备之间有三条网络通道,我习惯用“电-气-碳”三流来描述:
- 电能流:风电、光伏、垃圾焚烧、储能放电、购电一起满足电负荷、P2G耗电、碳捕集耗电、储能充电和售电。
- 天然气流:P2G产出的甲烷加上外购天然气,共同满足气负荷,储气罐可以缓冲。
- 二氧化碳流:垃圾焚烧烟气排放的CO2,一部分被捕集装置捕集,进入储碳罐;储碳罐里的CO2再输送给P2G的甲烷化反应器利用,多余部分可以出售或封存。
这三条流不是彼此独立的,P2G是电-气耦合的关键点,CCS是烟气-碳流的接入口,而P2G和CCS之间的CO2输送管道,则把碳流和气流再次绑定。
2.2 P2G与CCS的量化耦合关系
建模之前必须把P2G和CCS之间的数量关系算清楚,否则约束条件就是拍脑袋。
先看电解水制氢。工程上电解槽效率大约在65%~75%之间,取效率η_elec=0.7。1 MWh电能输入电解槽,能产出的氢气能量约为0.7 MWh,折合成质量大约0.021吨氢气(氢气低热值按120 MJ/kg算,即约33.3 kWh/kg)。
甲烷化反应CO2 + 4H2 → CH4 + 2H2O,按分子量算,1吨氢气大约需要消耗5.5吨CO2,产出4吨甲烷。所以我常用的简化公式是:
F_CO2(t) = β × P_P2G(t)
其中β就是单位P2G输入电功率对应的CO2消耗速率,综合算下来大概在0.12 t/MWh左右,具体看电解效率和甲烷化效率的取值。也就是说,如果P2G在某个时段运行4 MW,它一个小时内要消耗约0.48吨CO2。如果碳捕集跟不上,P2G甲烷化就只能降出力或者外购CO2。
CCS这边也有一个核心关系:捕集量增加会带来能耗增加。我简化建模为:
E_CCS(t) = λ × F_CCS(t)
其中λ是捕集单位CO2的等效电耗,典型值取0.3 MWh/t左右(包含再沸器热耗折算后的电当量)。捕一吨碳,等效消耗0.3 MWh的电,这个数不算大,但累加起来很可观。日捕集80吨就要24 MWh电能,这相当于一个10 MW机组满发2.4小时的电量。
2.3 场景设定与时间尺度
这套代码做的是日前优化调度,时间尺度选24小时,步长1小时,也就是T=24。这个粒度在园区级虚拟电厂里足够用了,既不会因为时间点太多导致MILP求解时间爆炸,又能看出一条典型日里峰谷电价、风光出力、气负荷变化对调度策略的全部影响。
场景数据我按冬季典型日设定夜间风电出力较高,白天光伏出力集中在10点到15点,气负荷在早高峰和晚高峰各有一个凸起,电负荷的峰谷差比较明显。这样的场景能同时检验P2G夜间消纳风电、CCS配合P2G供碳、以及高峰时段机组满发保供三个关键工况。
3. 数学模型:目标函数与约束条件这样搭
3.1 目标函数:钱从哪里省、碳怎么算
优化调度的目标是最小化虚拟电厂一个调度周期内的综合运行成本,包含六个部分:
- 购电成本与售电收益:按分时电价结算,购电为正、售电为负。
- 购气成本:外购天然气补足气负荷时的花费。
- 设备运维成本:垃圾焚烧、风电、光伏、P2G、CCS、储能各自按出力量乘以单位运维系数。
- 启停成本:垃圾焚烧机组启停带来的额外成本,用二进制变量表达。
- 碳交易成本:实际碳排放量与免费配额之差乘以碳价。碳排放量主要是垃圾焚烧烟气中未被捕集的CO2,捕集下来再利用的部分不计入排放。
- 弃风弃光惩罚:给新能源弃掉的部分加一个惩罚系数,这个是引导模型优先消纳风光的常见做法。
目标函数写出来长这样(伪形式):
min Σ [ 电价×购电量 - 售电价×售电量 + 气价×购气量 + 运维系数×各设备出力 + 启停成本 + 碳价×(实际排放-配额) + 弃能惩罚 ]
这里有个比较关键的设计:碳价机制不是简单加一个固定排放成本系数,而是引入了免费配额。配额不足的VPP要花钱买碳排放权,配额有盈余时相当于多了一笔收益。这个设计更贴近国内碳市场的运行逻辑,也让CCS装置的价值在目标函数里能被真正量化出来。
3.2 电、气、碳三网平衡约束
三网平衡约束是整个模型的地基,每一条都必须同时成立,缺一个模型就会飞掉。
电功率平衡约束:
P_Wte(t) + P_Wind(t) + P_PV(t) + P_Dis(t) + P_Buy(t) = P_Load(t) + P_Ch(t) + P_P2G(t) + P_CCS(t) + P_Sell(t)
这个式子很好理解,但注意P_P2G和P_CCS都出现在负荷侧——因为电转气和碳捕集本质上是“耗电设备”,它们消耗多少电、什么时候消耗,由优化模型自己决定。这正是协同调度的关键:把这两个设备的用电曲线也变成了优化变量,而不是固定参数。
气功率平衡约束:
P_P2G_Gas(t) + P_BuyGas(t) + P_GasDis(t) = P_GasLoad(t) + P_GasCh(t)
P2G产出的甲烷在这里等效成“热功率”单位(MW),和外购天然气、储气罐放气一起满足气负荷。P2G产气按热值折算成功率,单位统一后才能相加。
CO2平衡约束:
F_Store(t+1) = F_Store(t) + F_CCS(t) - F_P2G(t) - F_Sale(t)
储碳罐的状态递推式。F_CCS是碳捕集装置捕下来的CO2量,F_P2G是P2G甲烷化消耗的CO2量,F_Sale是外售或封存的量。这个约束保证了全系统碳流守恒,也是P2G和CCS协同关系的数学体现。
3.3 核心设备约束细节
垃圾焚烧机组的约束比普通火电机组多一点,主要是出力上下限、爬坡约束和最小启停时间:
P_Wte_min × u(t) ≤ P_Wte(t) ≤ P_Wte_max × u(t)
-Δ_Wte ≤ P_Wte(t) - P_Wte(t-1) ≤ Δ_Wte
其中u(t)是启停二进制变量。实际工程中垃圾焚烧机组一般是连续运行不轻易停机的,所以在代码里我通常把最小运行时间约束也加上,避免优化结果出现“每小时启停一次”这种不切实际的解。
碳捕集装置的约束包括捕集量上限、捕集能耗关系和储碳罐容量:
0 ≤ F_CCS(t) ≤ F_CCS_max
E_CCS(t) = λ × F_CCS(t) × Δt
0 ≤ F_Store(t) ≤ F_Store_max
P2G系统的约束包括输入电功率上下限、产气功率与耗电的关系、以及CO2消耗约束:
P_P2G_min ≤ P_P2G(t) ≤ P_P2G_max
P_P2G_Gas(t) = η_P2G × P_P2G(t)
F_P2G(t) = β × P_P2G(t) × Δt
这三个式子一摆,P2G的电输入、气输出、碳消耗三者就完全绑定了。
3.4 储能与新能源约束
储能部分我建模成通用的“充电-放电-容量”形式,电池储能和储气罐类似,只是效率和容量参数不同:
S(t+1) = S(t) + η_ch × P_Ch(t) × Δt - P_Dis(t) × Δt / η_dis
0 ≤ S(t) ≤ S_max
S(0) = S_initial
对于储碳罐,我在调试时强烈建议不要硬性要求S(T) = S(0),而是设置成S(T) ≥ 0.5 × S_max,或者干脆允许末状态自由,否则在边界场景下很容易变成不可行解。工程上储碳罐留一点余量是完全正常的。
新能源约束最简单的形式是:
0 ≤ P_Wind(t) ≤ P_Wind_forecast(t) 0 ≤ P_PV(t) ≤ P_PV_forecast(t)
这个写法允许预测曲线往下调整,也就是允许弃风弃光,但调整多少由优化模型根据成本决定。有了前面的弃能惩罚系数,模型只有在“弃能”比“使用这些电能”更划算时才会主动弃风,比如某些极端情况下P2G和储能都已经满了、又不能倒送电网。
4. Matlab代码实现:从参数到结果的全流程操作
4.1 代码结构怎么组织
一份能跑通且好改的Matlab代码,结构比内容更重要。我习惯按下面这四层组织:
- 主程序 main_VPP_schedule.m:负责清空环境、加载参数、调用建模和求解函数。
- 参数文件 load_parameters.m:集中存放所有基础数据。
- 模型构建 build_model.m:用Yalmip声明决策变量、目标函数和约束。
- 求解与后处理 solve_and_plot.m:调用求解器,提取结果并绘图。
这么拆的好处是,改电价、改碳价、改设备容量的时候不用动建模代码,只改参数文件就好。后面要做碳价敏感性分析,甚至可以直接在参数文件里套一层循环。
4.2 用Yalmip建模的关键写法
Yalmip是Matlab下非常好用的优化建模工具箱,不需要自己构造标准型矩阵,直接按数学公式声明变量和约束即可。代码骨架长这样:
% 决策变量 Pbuy = sdpvar(1, T); % 购电功率 Psell = sdpvar(1, T); % 售电功率 Pwte = sdpvar(1, T); % 垃圾焚烧出力 u_wte = binvar(1, T); % 垃圾焚烧启停 Pp2g = sdpvar(1, T); % P2G输入电功率 Qccs = sdpvar(1, T); % 碳捕集量 Sstore = sdpvar(1, T); % 储碳罐储量 ... % 目标函数 Objective = sum(price_buy .* Pbuy) - sum(price_sell .* Psell) ... + sum(price_gas .* PbuyGas) ... + sum(c_wte .* Pwte) + sum(c_p2g .* Pp2g) + ... + sum(carbon_price .* max(0, E_emit - E_quota)); % 约束条件 Constraints = []; % 电功率平衡 Constraints = [Constraints, Pwte + Pwind + Ppv + Pdis + Pbuy == ... Pload + Pch + Pp2g + Pccs + Psell]; % 机组上下限 Constraints = [Constraints, Pwte_min * u_wte <= Pwte <= Pwte_max * u_wte]; % 碳平衡 Constraints = [Constraints, Sstore(2:T) == Sstore(1:T-1) + Qccs(2:T) - Fp2g(2:T)];这里有几个我自己写代码时反复踩的经验:
第一,同类约束尽量写成一条向量表达式,不要在循环里逐时add约束。Yalmip处理向量约束的效率比几个for循环拼出来高很多,尤其是T=24时速度差距肉眼可见。
第二,二进制变量和连续变量混用时,注意给sdpvar和binvar区分开命名。命名乱了我调试过一晚上才查出来是哪条约束把模型搞成非线性的。
第三,功率等式约束用 == 而不是 =,这个几乎每个Yalmip新手都会犯一次错。
4.3 求解器配置与求解
模型构建好之后,求解配置是另一个关键环节。我用的组合是Yalmip + Cplex。Cplex处理中小规模MILP问题速度快、数值稳定,适合这种包含二进制启停变量的日前调度模型。
ops = sdpsettings('solver', 'cplex', ... 'verbose', 2, ... 'cplex.mip.tolerances.mipgap', 0.001, ... 'savesolveroutput', 1); result = optimize(Constraints, Objective, ops); if result.problem == 0 Pbuy_val = value(Pbuy); Pwte_val = value(Pwte); Pp2g_val = value(Pp2g); ... else disp('求解失败:'); disp(result.info); endmipgap设成0.001意味着允许0.1%的最优性间隙。对于这种工程调度模型,完全没必要追求严格0%的全局最优,0.1%的间隙已经能保证结果可信,但求解时间可能差好几倍。如果你没有Cplex授权,用Gurobi也行,只要把solver字段改成'gurobi',其他基本不用动。Yalmip对主流求解器的接口封装做得很统一。
4.4 结果输出与可视化
求解完第一件事不是画图,是检验结果合理性。我一般会把三张图一起画出来:电功率平衡堆叠图、气功率平衡图、碳流平衡图。电功率平衡图里把风电、光伏、垃圾焚烧、储能放电堆成正方向,把电负荷、P2G耗电、CCS耗电、储能充电堆成负方向,一眼就能看出每个时段谁在供、谁在用,尤其是夜间P2G有没有顶上来消纳风电,看堆叠图非常直观。
碳流图在协同调度里特别重要。把垃圾焚烧排放量、碳捕集量、CO2消耗量、储碳罐储量画到同一张图上,能直接验证碳平衡约束到底守没守住。我遇到过一次明明模型有解但结果看起来不合理的情况,最后就是看碳流图发现F_CCS数值超过了F_CCS_max,后来一查是约束里少了捕集量上限那几条。
5. 运行效果与关键参数影响分析
5.1 典型日调度结果拆解
我用一个算例试跑了完整模型,参数大致如下:WtE机组10 MW、P2G输入上限4 MW、碳捕集上限5 t/h、风电装机20 MW、光伏装机8 MW、分时电价谷段0.25元/kWh、峰段1.1元/kWh、碳价100元/t。
调度结果有几个明显规律:
- 夜间0点到5点风电出力大、电价便宜,P2G基本稳定在3.5 MW到4 MW满负荷运行,碳捕集装置同步在3 t/h到4 t/h的区间内运行,给P2G供碳。
- 上午10点到14点光伏大发,部分时段购电量接近于零,甚至出现短时倒送电,P2G降载,把更多电能留给电负荷。
- 傍晚18点到21点负荷高峰,垃圾焚烧机组满发,CCS捕集率主动降下来,优先保证对外供电能力。
这个结果符合物理直觉:协同调度模型知道什么时候该让P2G多吃风电,什么时候该让CCS缓一缓,而不是机械地按某个固定曲线运行。
5.2 P2G与CCS协同对弃风率的影响
我做了个对比实验:不接入P2G和CCS时,系统弃风率大约18%;只接入P2G不接碳捕集时,弃风率降到9%;P2G和CCS协同工作后,弃风率降到2%以下。核心原因就是CCS给P2G提供了本地碳源,让P2G不用外购CO2,运行成本降了,天然气的产量更有竞争力,模型就更愿意在弃风时段把P2G开大。
不过CCS不能无限提升P2G的低碳效益,原因在于CCS本身消耗能量。我试过把碳捕集能耗系数λ从0.2调到0.5,结果P2G的平均出力从3.8 MW一路降到2.1 MW。这说明模型对CCS能耗非常敏感。你以后调参如果发现P2G出力一直上不去,先查λ,别急着怀疑求解器。
5.3 碳价对调度策略的影响
碳价是另一个高敏感参数。我做了从0元/t到300元/t的敏感性扫描,规律非常清晰:
- 碳价低于40元/t时,CCS基本“意思一下”,捕集量很低,因为捕碳的能耗成本超过碳收益。
- 碳价在60~150元/t区间时,CCS捕集量快速上升,P2G因为有了本地碳源,出力也跟着增加。
- 碳价超过200元/t后,CCS达到上限,再加碳价对调度结果的影响就不大了。
这个敏感性曲线很有价值。做碳排放和经济效益分析时,完全可以拿它来说明“碳价在什么区间内CCS的投资运营最有吸引力”。很多论文里的CCS经济性分析其实就是这么画出来的。
6. 常见问题与调参排错实录
6.1 模型提示不可行,先这样排查
我最常遇到的报错就是infeasible(不可行解)。新手一看“problem=1”就懵了,其实排查顺序很固定:
第一,把周期约束(末状态等于初状态)删掉试试。很多不可行都是储碳罐、储气罐的边界和实际场景不匹配导致的,删掉后能定位问题。
第二,检查电功率平衡式里是不是少加了某个变量,比如储能充电、P2G耗电、CCS耗电这些负荷侧项经常被漏。
第三,检查上一个H2提到的储碳罐末状态约束。我前面强调过,不要用严格的等号周期约束,我实际工程经验就是S(T) ≥ 0.5×S_max,或者干脆S(T)自由,模型求解稳定性会好很多。
还有一个容易被忽略的点:所有功率单位必须是MW,所有能量单位必须是MWh,时间步长Δt=1h。如果你把某个参数写成了kW或者MW,等式两边的数量级差了1000倍,模型当然无解或者解出奇怪的结果。
6.2 求解太慢的调优方法
二进制变量多的时候,Cplex也可能要跑很久。我用的几个实用招数:
- 把mipgap从默认的1e-4放宽到1e-3,求解时间可能降一半以上。
- 给P2G和储能设“最大同时充电约束”,比如P_P2G(t) + P_Ch(t) ≤ 某个上限,把极端运行场景剪掉,能缩小搜索空间。
- 先不启用最小启停时间约束,跑通基础模型后再加上去。加约束是一层层来的,一上来全加上容易排查不动。
6.3 单位换算和守恒性检查
单位换算是这类多能流模型最容易出错的地方,尤其是P2G产气换算。电解槽输入是MWh,产气是Nm3,这两者之间必须经过氢气热值、甲烷化反应摩尔比折算。我在参数文件里写了一个换算函数,所有能量、质量、体积统一折算成标准SI衍生物理量后再建模,不在约束里做隐式换算。
碳平衡检查也有一个小技巧:跑完结果后单独算一算“总捕集量-总消耗量-外售量”等于多少,如果和储碳罐始末状态差对不上,说明约束写漏了或者单位错了。
6.4 一些实用的参数设置建议
最后给几个实战参数建议,都是我试过靠谱的:
- P2G总效率η_P2G取0.55~0.6,高于0.65就别用了,现实中很难做到。
- CCS捕集能耗λ取0.3 MWh/t左右比较合理,太乐观的λ会让模型高估捕碳收益。
- 弃能惩罚系数设在燃煤上网电价的1.5到2倍之间比较好。设太低,模型宁可弃风也不开P2G;设太高,又会出现P2G疯狂制气、储气罐始终满管的不真实结果。
- 储碳罐容量不要太大,按“最大捕集速率×2小时”的量级设置就够支撑日前调度了。
整套代码从搭模型到调参数,我前后跑了两周才算稳定。过程中最大的体会是:P2G和碳捕集之间的耦合关系,真的不能只看论文里那些公式,要把质量流、能量流、碳排放流的单位全部统一到同一套体系里,用可计算的方式把每个系数都落到具体数值上,模型才能从“看起来对”变成“跑起来也对”。
如果你打算在这个基础上扩展,建议下一步可以试试把随机场景加进来,比如用蒙特卡洛模拟风光预测误差,或者把P2G的电解槽做成分段线性化的变效率模型,都能让虚拟电厂的调度结果更贴近真实工程。先把手上的确定性模型跑熟,再往随机优化走,这条路是最稳的。