
搞虚拟电厂优化调度这几年我最大的感触是以前算成本煤耗、购电、启停基本就覆盖了现在不一样了碳配额、碳交易价格、弃电惩罚、制氢掺氢、碳捕集能耗全都挤进同一个目标函数。最近我用Matlab完整跑了一个项目基于阶梯碳交易的含P2G-CCS耦合和燃气掺氢的虚拟电厂优化调度求解端用的是YalmipGurobi。整个模型把电、气、碳三条链统一扔进一个混合整数线性规划里兼顾新能源消纳和碳成本控制。这篇文章把这个项目的建模思路、公式转化、关键代码和踩坑记录完整还原出来。适合正在做电力系统优化调度、综合能源系统仿真、碳交易机制建模的研究生和工程师参考尤其是被“阶梯碳价怎么线性化”“P2G和CCS怎么耦合”“掺氢比例怎么建模不非线性”这几个问题卡住的朋友。1. 整体设计与思路拆解1.1 为什么要把“碳”写进调度目标这个项目标题里三个关键词并不是三个独立功能它们其实是一条闭环。P2G是“电转气”电解水制氢必要时再通过甲烷化把氢和二氧化碳合成天然气CCS是“碳捕集”把燃气机组尾气里的二氧化碳抓回来燃气掺氢则是把P2G直接产出的氢气按比例掺进燃气轮机燃料。三者放在一起逻辑就顺了新能源富余电力 → 电解制氢 → 氢气一部分用于掺氢发电一部分与CCS捕集来的CO2合成甲烷 → 甲烷再作为天然气供燃机使用。这样既平抑了风光波动又把二氧化碳从“末端排放物”变成了“可循环的碳原料”。阶梯碳交易在这个闭环里起的是经济杠杆作用。配额用不完可以卖超出越多买配额的单位成本越高。当碳价足够高时运营商才愿意为电解槽、碳捕集装置多付电费和运维费。换句话说P2G和CCS不是单纯的技术展示它们的投资和运行决策是由碳市场“逼”出来的。这正好回答了很多人问我的一个问题为什么放着便宜的天然气不用非要花那么多电去制氢、捕碳因为在阶梯碳价下直接烧天然气的隐性成本可能比“制氢掺氢捕碳”的联合成本更高。1.2 阶梯碳交易和线性碳价模型的区别传统文献经常把碳成本简化为线性碳税超出配额部分乘以一个固定单价。这种模型实现简单但不符合实际碳市场的特征。现实碳市场中配额缺口越大买方越集中价格往往呈阶梯式上升。你超排1吨和超排1000吨承担的边际成本完全不同。所以在项目里我采用了阶梯碳交易机制把超额排放量分成多段每段对应一个递增的单价。这样做的好处是模型能更真实地刻画减排压力但它给建模带来一个直接问题——碳交易成本变成了超排量的分段线性函数。分段线性函数写进以成本最小化为目标的模型时本身可以保持凸性但如果直接用if判断或者min/max函数去处理Yalmip往往无法自动线性化传到Gurobi要么报错要么结果不理想。这里有一个很重要的技巧如果碳价逐段递增那么超排量可以拆成多个非负变量比如Δ d1 d2 d3每段设上下界碳交易成本写成c1d1 c2d2 c3*d3。因为目标是最小化总成本求解器会自动优先填满低价档位不需要额外引入二进制变量模型依然是一个干净高效MILP。只有当你要同时处理“超排买碳”和“富余卖碳”两种方向时函数会变成非凸才需要引入二进制变量。建议第一次做阶梯碳交易的同学先只做“超排买碳”模型跑通以后再扩展卖碳收益。1.3 为什么选MatlabYalmip而不是Python这个问题几乎每一次做报告都会被人问。我做优化调度首选MatlabYalmipGurobi不是因为Python不行而是Yalmip的语法非常贴近数学表达式。你在纸上写出min f(x) s.t. g(x)≤0Yalmip里基本就是同一个样子不用像通用编程语言那样绕来绕去。尤其电力系统调度里变量往往是24维向量Yalmip对向量的支持非常顺手约束批量写、批量改调试效率高很多。至于求解器Gurobi对MILP的求解性能确实强学术许可也相对好拿。如果机器上没有GurobiCplex或者开源的HIGHS也能用Yalmip里一行切换这对学生党很友好。整个项目的代码框架可以拆成定义时间轴和预测曲线 → 初始化决策变量 → 写目标函数 → 写约束 → optimize求解 → 提取结果绘图。后面第3节我会把核心代码片段直接放出来照着改参数就能跑。2. 核心细节解析与实操要点2.1 功率平衡与碳平衡的主骨架建模第一步永远是物理平衡。电功率平衡是所有调度的地基本项目的平衡式大致是风电实际出力 光伏实际出力 燃气轮机出力 储能放电 电网购电 负荷 储能充电 电解槽耗电 CCS系统耗电 电网售电注意电解槽和CCS都是耗电设备必须放在等式右侧。很多新手建模型时容易漏掉CCS的能耗导致碳捕集凭空多出一大块不计成本的耗电结果自然失真。CCS捕集单位CO2的电耗一般在0.3~0.5 MWh/t别小看这个值一天下来对系统购电量的影响非常大。碳平衡方面要分清楚几个概念。燃气轮机的CO2总排放来自天然气燃烧氢气燃烧不产CO2所以掺氢比例越高天然气用量越少总排放越低。然后CCS捕集其中一部分剩下的才是净排放。项目里还把P2G甲烷化消耗的CO2视为从排放中脱离因为捕集回来的碳进入了甲烷再循环没有再排进大气。算碳配额时不能用“燃机总排放减去CCS捕集量”再减一遍“P2G消耗量”必须统一口径否则碳账会重复计算目标函数就失真了。2.2 P2G-CCS耦合约束怎么建P2G-CCS耦合是标题里的重点也是很多人第一步就卡住的地方。具体到模型里它由四条约束组成电解槽模型H2总产出 电解效率 × 电解功率氢气分配H2总产出 直接掺氢消耗的H2 进入甲烷化反应的H2甲烷化反应P2G产CH4功率 甲烷化效率 × 进入反应器的H2功率CO2需求甲烷化过程消耗CO2量 单位CH4对应的CO2消耗系数 × P2G产CH4功率CCS侧的约束相对简单捕集CO2量 捕集率 × 燃气机组总排放。这里捕集率一般取0.85~0.9工程上这个指标比较典型不能设成1.0因为CCS装置没法做到100%捕集。耦合关系体现在CO2平衡上CCS捕集量 P2G甲烷化消耗量 封存量。如果捕集量不够P2G用要么从外部购买CO2要么降低P2G出力。我在项目里设成优先满足P2G需求剩余CO2再送往封存这样“绿色合成天然气”的效果最明显。2.3 燃气掺氢的线性化技巧掺氢比例建模算是这个项目里最容易被非线性卡死的环节。如果直接把“掺氢比例ρ”作为决策变量那么氢气提供的热功率H_burn和混合燃料总热功率F_mix之间会出现ρ×F_mix这种双线性项MILP求解器处理不了。我的处理方式是换一套变量把燃烧侧拆成两个独立决策变量——天然气热功率F_ng和氢气热功率H_burn混合燃料总热功率F_mix F_ng H_burn。掺氢上限就用一个线性约束表达H_burn ≤ ρ_max × F_mix这里的ρ_max是给定的能量比例上限不是决策变量所以整个模型保持线性。如果后续想研究不同掺氢比例的影响直接改ρ_max做敏感性分析就行不必把比例塞进优化变量里。这个技巧把这个项目90%的非线性问题都消掉了非常实用。燃气轮机出力和燃料的关系简化为P_gt η_gt × F_mix。其中η_gt是燃气轮机发电效率我项目中取0.45左右忽略掺氢对效率的微小影响。如果要做更精细的建模可以按掺氢比例查表分段插值但那会让模型复杂不少对整体调度结论影响有限。2.4 储能与机组运行的细节约束储能部分最容易犯的低级错误是同充同放。如果不在模型里加充放电互斥约束优化器在目标函数允许的情况下可能让电池一边充电一边放电刷数据最后SOC曲线看着正常实际结果毫无意义。互斥约束要写P_ch ≤ 20 × u_stP_dis ≤ 20 × (1 - u_st)这里u_st是0-1变量20是储能最大充/放功率。SOC递推式也别忘常规写法是SOC(t1) SOC(t) η_ch × P_ch(t) × Δt / 容量 - P_dis(t) × Δt / (η_dis × 容量)燃气轮机约束就是常规的上下限、爬坡和启停逻辑这些在教材里很常见不赘述。还有个容易忽略的是与上级电网的交换功率限制。如果不设购电功率上限优化器可能会不计代价地从电网买电来支撑电解槽和CCS虽然购电成本会拦一下但这种“使劲购电”在工程上并不合理通常也要加联络线容量约束。3. Matlab代码实现的核心片段3.1 环境准备与变量初始化先说环境。我用的Matlab R2023b装了Yalmip和Gurobi 11。安装完成后在Matlab里执行yalmiptest看到画面里Gurobi一项显示OK就可以开始干活了。需要提醒的是每次新开Matlab要先把求解器路径加进去避免optimize时报找不到求解器。变量初始化阶段先把调度周期设为24小时T24时间尺度为主。核心决策变量包括燃气轮机出力、启停二进制变量、电解槽功率、CCS耗电功率、储能充放电和SOC、P2G产氢电量、氢分配变量、天然气燃料热功率、掺氢热功率、混合燃料热功率、CO2捕集量、CO2消耗量、碳交易超排分段变量等。代码片段如下T 24; % 读入风光出力预测、负荷预测 P_wf [...]; % 1x24 风电预测MW P_pvf [...]; % 1x24 光伏预测MW P_load [...];% 1x24 负荷预测MW % 主要决策变量 P_gt sdpvar(1, T); % 燃气轮机出力 u_gt binvar(1, T); % 燃气轮机启停 P_el sdpvar(1, T); % 电解槽输入功率 P_ccs sdpvar(1, T); % CCS系统耗电功率 P_buy sdpvar(1, T); % 电网购电 P_sell sdpvar(1, T); % 电网售电 P_ch sdpvar(1, T); % 储能充电 P_dis sdpvar(1, T); % 储能放电 soc sdpvar(1, T1); % SOC含初始值 u_st binvar(1, T); % 储能充放电互斥 H2_total sdpvar(1, T); % 电解槽总产氢 H2_direct sdpvar(1, T); % 氢气直接用于掺氢 H2_meta sdpvar(1, T); % 氢气进入甲烷化 F_ch4 sdpvar(1, T); % P2G甲烷化产CH4 F_ng sdpvar(1, T); % 燃机消耗天然气热功率 H_burn sdpvar(1, T); % 燃机掺氢热功率 F_mix sdpvar(1, T); % 燃机混合燃料总热功率 Q_cap sdpvar(1, T); % CCS捕集CO2量 Q_p2g sdpvar(1, T); % P2G甲烷化消耗CO2量 E_net sdpvar(1, T); % 每时段净碳排放 % 阶梯碳交易分段变量 DeltaE sdpvar(1,1); d1 sdpvar(1,1); d2 sdpvar(1,1); d3 sdpvar(1,1);这种命名方式看起来变量很多但每个变量对应模型里的一个物理量调试时出问题能一眼定位。3.2 目标函数与关键约束代码目标函数是综合成本最小化包含购电成本、外购天然气成本、设备运维成本、碳交易成本、弃风光惩罚。这里外购天然气只针对F_ng氢气和P2G产甲烷的成本已经体现在电解耗电和运维成本里不要再重复计一遍燃料费这是一个容易算重的地方。% 成本参数 c_buy 0.6; % 购电价格元/kWh实际需要按分段电价写 c_gas 2.8; % 天然气价格元/m3需换算为元/MWh热值 c_om [0.02, 0.015, 0.03, 0.005]; % 燃机、电解槽、CCS、储能的单位运维成本 cu [80, 120, 180]; % 阶梯碳价元/t B1 500; B2 1000; % 阶梯分界点t objective sum(P_buy * c_buy * 1000) ... % 购电 sum(F_ng * c_gas) ... % 购气注意单位统一 sum(P_gt * c_om(1) P_el * c_om(2) P_ccs * c_om(3)) ... (cu(1)*d1 cu(2)*d2 cu(3)*d3) ... % 碳交易成本 0.3 * sum((P_wf P_pvf) - (P_w P_pv)); % 弃风弃光惩罚约束部分我挑核心几条放出来重点是电功率平衡、P2G-CCS耦合、掺氢线性约束Constraints []; % 电功率平衡 Constraints [Constraints, ... P_w P_pv P_gt P_dis P_buy ... P_load P_ch P_el P_ccs P_sell]; % 燃气轮机与掺氢 Constraints [Constraints, 30*u_gt P_gt 120*u_gt]; Constraints [Constraints, P_gt 0.45 * F_mix]; Constraints [Constraints, F_mix F_ng H_burn]; Constraints [Constraints, H_burn 0.2 * F_mix]; % 掺氢能量比例上限20% % P2G-CCS耦合 Constraints [Constraints, H2_total 0.65 * P_el]; Constraints [Constraints, H2_total H2_direct H2_meta]; Constraints [Constraints, F_ch4 0.8 * H2_meta]; Constraints [Constraints, Q_p2g 0.2 * F_ch4]; % 产1MWh甲烷对应CO2需求系数 Constraints [Constraints, Q_cap 0.85 * 0.2 * F_ng]; % 燃烧排放因子*捕集率 Constraints [Constraints, Q_p2g Q_cap]; % CO2优先自产自用 Constraints [Constraints, H_burn H2_direct]; % 掺氢直接来自P2G % 储能 for t 1:T Constraints [Constraints, ... soc(t1) soc(t) 0.95*P_ch(t) - P_dis(t)/0.95]; Constraints [Constraints, 0 soc(t1) 100]; Constraints [Constraints, 0 P_ch(t) 20]; Constraints [Constraints, 0 P_dis(t) 20]; Constraints [Constraints, P_ch(t) 20*u_st(t)]; Constraints [Constraints, P_dis(t) 20*(1-u_st(t))]; end % 阶梯碳交易只买不卖版本 E_netValue sum(E_net); Constraints [Constraints, E_netValue - E_free d1 d2 d3]; Constraints [Constraints, DeltaE d1d2d3]; Constraints [Constraints, 0 d1 B1, 0 d2 B2-B1, 0 d3 1e4];注意这里E_net的计算要在约束里显式给出比如取E_net 0.2*F_ng - Q_cap表示天然气燃烧总排放扣掉捕集量。不同项目的碳口径会有差异但核心思想是“系统对外净排放”才参与碳交易。单位上我全程按能量功率MW和小时累计处理碳排放量单位是吨约束里乘以调度周期持续时间后与阶梯分界点对齐。3.3 求解设置与结果提取求解设置很简单但有几个参数经常会救大忙ops sdpsettings(solver, gurobi, verbose, 1, ... gurobi.MIPGap, 0.001); sol optimize(Constraints, objective, ops); if sol.problem 0 % 求解成功提取结果 P_gt_v value(P_gt); P_el_v value(P_el); P_ccs_v value(P_ccs); % 绘制功率平衡图、碳排放图 else % 输出错误信息 disp(sol.info); endsol.problem 0表示找到可行解。不等于0时先别慌看sol.info提示是infeasible还是unbounded再回头查约束。MIPGap设到0.001通常足够如果模型规模大、求解慢可以放宽到0.005结果差别不会太明显但求解时间可能成倍下降。3.4 场景对比结果怎么看为了验证模型有效性我设置了几个场景基准场景不带任何低碳技术和碳交易第二个场景只加线性碳税第三个场景加入P2G-CCS第四个场景在第三个基础上再加燃气掺氢并改用阶梯碳价。四个场景对比下来趋势非常明显场景总成本(万元)系统碳排放(t)弃电率(%)场景1无碳交易、常规燃气1820512012.5场景2线性碳税、无P2G-CCS1765486012.5场景3阶梯碳价 P2G-CCS167539807.8场景4阶梯碳价 P2G-CCS 掺氢163035205.2这里要说明一下表中数据是我为了演示趋势调整过的示意结果不代表某个真实系统的绝对数值。但“阶梯碳价耦合技术能同时压降总成本和碳排放、并提升新能源消纳”这个结论是稳定的。具体到你自己的算例参数不同趋势可能不完全一样但模型框架和求解逻辑是通用的。4. 常见问题与排查技巧实录项目调试过程中踩过不少坑汇总成速查表基本覆盖90%的MatlabYalmip调度模型问题现象常见原因解决办法Unbounded objective电平衡或碳平衡约束缺失目标函数里的成本项没闭合逐条检查约束先去掉碳交易分段变量试跑Infeasible氢平衡互相矛盾比如掺氢需求超过P2G产氢能力临时调大产氢上限、放宽掺氢比例上限或增加外部购氢变量结果出现同充同放储能没有加充放电互斥约束引入u_st二进制变量写P_ch≤Mu_stP_dis≤M(1-u_st)碳交易成本一直是0免费配额设置过高或分段变量没有接入目标函数检查目标函数里是否含cu(1)*d1cu(2)*d2cu(3)*d3以及配额基准是否太小求解器报非线性错误代码里用了max、min、if等条件表达式用分段线性化和二进制变量替换或者把非线性部分参数化求解特别慢BigM设过大、整数变量对称性强、MIPGap设太低BigM尽量取紧约束边界MIPGap放宽到0.005减少对称二进制变量写代码时的几个实操体会再展开说说。第一个坑碳交易成本函数千万别直接用max。我第一次写阶梯碳价时图省事写C_carbon max(0, E_net - E_free) * cu结果Yalmip直接报错。这类非光滑函数在MILP里必须显式线性化。用分段变量拆完后还要检查分段的上下界是否和实际排放量级匹配。比如B1设为500吨、B2设为1000吨如果系统一天超排5000吨那d3会被拉很高碳价最高档主导此时再对比中低档的影响就没什么意义了。第二个坑单位不统一结果离谱。天然气热值单位、CO2排放因子的单位、P2G产气量单位三者但凡有一个换算错模型能跑通但结果完全不对。我在初版代码里把天然气的“立方米/小时”直接当成“MW”用结果P2G产出的甲烷量凭空放大了十几倍。后来把所有参数汇总成一张单位表统一按“MW、MWh、吨、元/MWh”四个维度核验问题当场消失。建议你在代码注释里把每个参数的物理单位标清楚尤其排放因子不同文献用的单位五花八门不换算好后面全错。第三个坑P2G和CCS耦合容易算重碳账。燃机排放CO2CCS捕集一部分P2G又消耗捕集后的CO2。如果你既在“碳交易排放量”里减掉全部CCS捕集量又在“碳排放总量”里减去P2G消耗量那就是负负得正凭空多出减排量。我在模型里只用净排放量参与碳交易即总排放减去捕集量P2G消耗的CO2不额外算减排优惠。这样既简单也符合大部分文献的口径。第四个坑氢平衡约束别漏。电解槽产氢的总量要等于直接掺氢量和甲烷化用氢量之和。很多复现者只写了掺氢约束忘了甲烷化也要消耗氢导致P2G-CCS耦合链路不完整结果产出的甲烷凭空冒出来约束不合理求解器直接报Infeasible。排查时把氢平衡约束打开看每个时段的H2_total、H2_direct、H2_meta是否同时为正问题一下就清楚了。第五个坑储能SOC的初始值。虚拟电厂调度通常是滚动周期SOC(1)要设置为实际状态不能随便填0。如果初始SOC和最终SOC差距过大储能会被优化器用来做套利造成一天末尾SOC异常。常规做法是加一个SOC(T1) SOC(1)约束或者让SOC(T1)在一个合理区间内比如40%~60%模拟电池的持续性运行。5. 这个模型还能怎么扩展5.1 碳价和掺氢比例的影响范围项目跑通之后我特别喜欢做敏感性分析因为这是论文和工程报告里最能说明问题的部分。比如把阶梯碳价的最高档从180元/t提到260元/t系统的最优P2G运行功率会明显上升弃电率和碳排放会进一步下降。这说明碳价是驱动低碳技术投入的关键参数而不是纯粹的“罚款项”。掺氢比例上限也很有意思。ρ_max从10%提到30%燃气轮机的天然气用量减少碳排放因子下降但同时电解槽需要消耗更多电力系统购电成本上升。在风光资源丰富的场景下30%掺氢可能更优在风光资源一般、依赖电网购电的场景下反而可能推高总成本。这个拐点就是做配置方案时要关注的焦点。轮机和天然气管道对掺氢比例有工程安全限制模型里可以设上限但不能设太高实际项目一般不超过20%~30%。5.2 从确定性调度走向不确定性优化这个项目当前做的是确定性调度也就是风光出力和负荷预测是给定的。工程上风光预测误差不可避免下一步可以扩展为两阶段鲁棒优化或随机优化。用现有的确定性MILP作为第二阶段模型第一阶段加场景生成和鲁棒调节子Yalmip依然支持只是问题规模和求解时间会明显增加。另外碳价本身也有不确定性考虑阶梯碳价在不同市场阶段可能平移或斜率变化时可以引入信息间隙决策理论或者条件风险价值作为风险约束。目前已经有文献在往这个方向走框架和这个基本一致只是把部分参数随机化、增加风险度量项。5.3 多目标优化与碳流追踪目前模型直接把碳排放折算成了碳交易成本放进目标函数。如果想看“成本和排放”的双目标Pareto前沿可以把碳交易成本拆出来作为第二目标用加权和或ε-约束法处理。Yalmip对多目标单次求解不直接支持但循环改动目标权重就能画出完整的Pareto曲线。还有一个思路是做碳流追踪。传统电力系统讲潮流现在园区型虚拟电厂更关注“碳排放从哪个设备来、到哪里去”。利用本模型的CO2平衡约束可以统计每个时段FCAS捕集量、P2G消耗量、系统净排放量的分布配合桑基图展示碳流这在报告里是非常加分的可视化。最后说一点实际体会。这类多能耦合调度模型最怕的不是公式难写而是单位混乱和碳口径不清。我最初一版代码因为把天然气热值和电功率混用P2G产气量整体高了十倍排查时看着数据百思不得其解。后来把所有参数都先画在一张单位表里每条约束按“功率MW、能量MWh、碳t”三个物理量自查模型就稳定多了。如果你也准备复现这套基于阶梯碳交易的虚拟电厂优化调度建议先把电平衡、碳平衡、氢平衡三条主线写全再往里面加阶梯碳价和掺氢细节整个MILP基本上一次就能跑通。