
最近一个月我一直在折腾一件事把碳交易机制写进微网优化模型再用Matlab做整套仿真分析。做之前我以为无非是目标函数里多一项“碳成本”真开始建模才发现配额基准怎么取、超额排放怎么算、储能启停和购售电互斥这些约束全部缠在一起每改一个参数都可能让结果翻盘。这篇博文是我完整跑通的一套微网优化模型框架覆盖从数学建模、Yalmip搭配商用求解器的Matlab实现到典型日仿真结果对比的全过程适合正在做微网经济调度、综合能源系统或者毕设卡在碳交易建模这一步的朋友直接参考。1. 碳交易机制进入微网优化从一个“多出来的成本项”说起1.1 为什么微网优化现在绕不开碳排放并网型微网的传统优化目标很简单电费最低。低谷多买电、光伏风电能发就发、储能在峰时放电完事。可一旦把碳排放当成有价格的东西这个简单的排序就被打破了。举个最直观的例子假设某时刻电网购电0.58元/kWh微型燃气轮机发电成本0.95元/kWh。传统优化会毫不犹豫买电网电因为便宜。但如果大电网的排放因子是0.581 kgCO2/kWh而微网拿到的免费配额基准只有0.4 kgCO2/kWh每从电网买1 kWh电就产生了0.181 kg的配额缺口按200元/吨的碳价折算相当于额外承担0.036元/kWh的碳成本。微燃机也有0.45 kg/kWh的直接排放折算碳成本约0.01元/kWh叠加发电成本后仍比电网贵所以单纯的“煤电vs燃气”对比里电网还是赢。但光伏风电一进来就不一样了。风光发电不产生任何配额消耗每发1 kWh电还帮整个微网腾出0.4 kg的配额盈余。碳价0.2元/kg时这意味着0.08元/kWh的额外收益。也就是说可再生能源不再是“省了燃料费”那么简单它还在偷偷印“碳配额钞票”。微网优化从单一的电价排序变成了“电费碳成本”的双重排序。很多朋友会问微网自己又不是控排企业为什么需要在乎碳价真实项目里微网确实未必直接参与碳市场履约但在园区综合能源、增量配电网这类场景中微网通常要面对上级碳配额指标分解或者作为虚拟电厂参与碳交易代理本质上都等价于给微网建一个“碳账本”。这层账算得越早项目做经济性论证时越有说服力。1.2 配额、实际排放、碳价碳交易模型的三块基石碳交易接入微网优化模型核心就三个量免费配额、实际排放、碳价。免费配额一般用基准线法确定这是国内项目里最常见的做法。基准线法的意思是微网每向负荷供应1 kWh电量就获得一定量的免费排放额度。公式为Q_allow k_allow * sum(P_load * dt)其中Q_allow是全天免费配额k_allow是配额基准典型值在0.4~0.6 kgCO2/kWh之间。这个基准值设置得松还是紧直接决定碳交易是“惩罚机制”还是“补贴机制”。实际排放则要分来源统计从大电网购电要折算间接排放微燃机/柴油发电机烧燃料算直接排放光伏风电为零。用公式表示E_total k_grid * sum(Pbuy * dt) k_mt * sum(Pmt * dt)k_grid是大电网排放因子不同区域差异很大华东电网可能只有0.5~0.6西北煤电占比高的地方能到0.8以上。k_mt是微燃机排放因子天然气机组通常按0.4~0.5 kgCO2/kWh取。这两个参数都是可以在Matlab里做成可配置项的因为后面敏感性分析总要调它们。碳价则决定了“配额缺口”值多少钱。实际排放高于配额超额部分要花钱买实际排放低于配额盈余部分可以卖出变成收益。于是碳交易成本写成F_carbon p_co2 * (E_total - Q_allow)如果括号里是正数代表净购买成本是负数代表净收益。我在初版模型里直接用这个线性形式跑通之后才升级成阶梯碳价。这里先提醒一句配额核算的基准对象到底是“负荷电量”还是“购电电量”不同论文口径完全不同建模前必须想清楚否则结果差一个数量级。2. 微网优化模型建模目标函数和约束的闭环设计2.1 目标函数运行成本、启停成本和碳交易成本怎么叠加建模最怕一上来就堆变量和约束。我的习惯是先写清楚要优化什么再倒推需要哪些决策变量。微网优化调度的目标函数可以拆成四块min Total F_energy F_fuel F_om F_carbonF_energy是纯电费项包括向电网购电的电费以及微网向电网卖电的收益后者是负成本。F_fuel是微燃机燃料费理论上燃料成本是发电功率的二次函数但在MILP框架里我会把二次曲线分段线性化。F_om是设备运行维护成本储能按充放电电量计费微燃机按发每度电计费。F_carbon就是上一节算出来的碳交易净成本。注意碳交易成本一定是放在目标函数里而不是做成二氧化碳排放上限这种硬约束。硬约束只能给出“排放不能超过某个值”的边界结果系统没有动力去比较“多排一吨碳付出的代价”和“少排一吨碳花掉的成本”之间到底哪个划算。把碳价写进目标函数调度策略才会自动在电费、燃料费、碳费之间做边际权衡这才是“碳交易优化”和“碳约束优化”最本质的区别。代码层面Yalmip里目标函数可以写成Price_buy ...; % 1x24 分时购电价 Price_sell ...; % 1x24 上网电价 Pbuy sdpvar(1, T); Psell sdpvar(1, T); Pmt sdpvar(1, T); Ch sdpvar(1, T); Dis sdpvar(1, T); F_energy sum(dt .* (Pbuy .* Price_buy - Psell .* Price_sell)); F_fuel sum(fuel_coef * Pmt); % 分段线性化后 F_om sum(om_mt * Pmt om_bat * (Ch Dis)); F_carbon p_co2 * (k_grid*sum(Pbuy)*dt k_mt*sum(Pmt)*dt - k_allow*sum(PLoad)*dt); objective F_energy F_fuel F_om F_carbon;这段代码有个非常容易错的地方单位。Pbuy单位是kWdt单位是h两者相乘才是kWh才可以乘以kg/kWh的排放因子得到kg。碳价用元/kg最后F_carbon单位才是元。我一开始把排放因子写成t/MWh碳价写成元/吨两个错单位叠加在一起结果目标函数的值看着像回事实际完全不对。2.2 母线功率平衡、储能SOC、购售电互斥约束怎么一次写对目标函数确定后模型的骨架就是约束。我把约束分成三类平衡约束、设备物理约束、逻辑互斥约束缺一不可。第一类是母线功率平衡这是整个模型的基石Ppv Pwt Pmt Dis Pbuy PLoad Ch Psell每个时刻光伏风电出力、微燃机出力、储能放电、电网购电加起来必须等于负荷加上储能充电和向电网卖电。这个公式很容易漏项尤其容易漏掉Psell那一路导致无解或者结果不合理。第二类是设备物理约束。光伏风电不能超过预测出力微燃机有出力上下限和爬坡率储能SOC要满足递推关系SOC(1) 0.5; % 初始荷电状态 for t 1:T Constraints [Constraints, SOC(t1) SOC(t) (Ch(t)*eta_ch - Dis(t)/eta_dis)*dt / E_bat]; end Constraints [Constraints, SOC_min SOC SOC_max]; Constraints [Constraints, SOC(T1) 0.5]; % 末态约束防止电池被榨干这里建议给SOC设末态约束否则优化器会把储能“用到最低点就完事”结果第24小时到第1天之间根本没考虑连续性。实际调度中储能是要循环使用的我习惯让第25个时刻的SOC不小于初始值也就是一天跑完不能把电耗尽。第三类是逻辑互斥约束。储能不能同时充电和放电联络线不能同时购电和卖电这两条如果不写死优化器会为了“吃差价”在同一条母线上既充电又放电得到荒谬结果。所以需要引入0-1变量u_ch binvar(1, T); u_dis binvar(1, T); u_buy binvar(1, T); u_sell binvar(1, T); Constraints [Constraints, Ch u_ch * Ch_max]; Constraints [Constraints, Dis u_dis * Dis_max]; Constraints [Constraints, u_ch u_dis 1]; Constraints [Constraints, Pbuy u_buy * P_line_max]; Constraints [Constraints, Psell u_sell * P_line_max]; Constraints [Constraints, u_buy u_sell 1];这一组约束是微网模型里最容易写错的地方。很多人只写了功率上下限忘记互斥约束然后发现仿真里储能一边充电一边放电购电一边进一边出目标函数还成负数。这个坑我踩过排查了整整半天。2.3 为什么我最终选择MILP求解而不是粒子群/非线性规划做这类优化总有人问为什么不用粒子群为什么不用遗传算法我的答案很直接这种模型天生适合混合整数线性规划。原因有二。第一储能充放状态、购售电状态、机组启停状态这几类0-1变量属于典型的整数决策粒子群和遗传算法处理整数约束很别扭通常要手动写罚函数罚函数系数一调就是好几个晚上。第二MILP能用分支定界法给出全局最优解和最优性间隙gap论文或者项目报告里可以交代“结果误差小于0.1%”。启发式算法跑十次有十次不同结果解释起来很被动。代价是要做线性化处理。微燃机燃料成本曲线、阶梯碳价曲线都是折线或二次曲线需要分段线性化这个我在下一节展开。实际测试下来24小时时段的微网调度模型用Cplex或者Gurobi求解时间基本都在几秒内完全满足工程研究需要。3. 碳交易成本在Matlab里的落地配额核算、阶梯碳价与Yalmip实现3.1 用Yalmip定义决策变量和碳交易约束Yalmip是被我用得最顺手的Matlab建模接口原因很简单你不需要手工把约束拆成A、b矩阵递给求解器sdpvar和binvar声明变量、写符号约束、最后调用求解器代码可读性高得多。碳交易约束在Yalmip里写起来很直接E_grid k_grid * sum(Pbuy) * dt; % 购电间接排放单位kg E_mt k_mt * sum(Pmt) * dt; % 微燃机直接排放 E_total E_grid E_mt; % 全天总排放 Q_allow k_allow * sum(PLoad) * dt; % 全天免费配额 F_carbon p_co2 * (E_total - Q_allow); % 碳交易净成本可为负这里需要注意Pbuy是一个1x24的向量sum(Pbuy)是全天购电总量再乘dt得到全天购电kWh。有些朋友习惯先写每时刻的排放再求和也可以但向量化写法更简洁求解时数值效率也更好。如果模型里默认不允许微网卖配额获利需要额外加一条约束Constraints [Constraints, E_total - Q_allow 0];但我在常用模型里不会加这条因为“配额盈余变现”本身就是碳交易给微网带来的一个新收益维度把它砍掉等于削掉了可再生能源的额外价值。具体要不要允许卖配额取决于你做的是配额约束型还是配额交易型项目这个业务逻辑一定要和用户或者导师确认清楚。3.2 阶梯碳价的分段线性化为什么要引入0-1变量固定碳价模型跑通后我决定升级成阶梯碳价。阶梯碳价的逻辑和阶梯电价一样超额排放量越多边际碳价越高。比如第一个100kg超额排放按0.2元/kg第二个100kg按0.4元/kg再往上按0.6元/kg。这种设计更贴合“减排压力递增”的政策意图——允许你超排但超排越多越肉疼。数学上这个问题就变成了一个分段线性函数的最小化。如果直接用max函数嵌套写模型会变成非光滑非线性的Yalmip里的sdpvar不好直接处理商用求解器也不保证收敛。标准做法是引入辅助变量和0-1变量把“超额量落在哪个区间”这件事变成线性约束。先设超额排放的正负分解x_pos sdpvar(1,1); x_neg sdpvar(1,1); Constraints [Constraints, E_total - Q_allow x_pos - x_neg]; Constraints [Constraints, x_pos 0, x_neg 0];然后对正超额部分分三段x1 sdpvar(1,1); x2 sdpvar(1,1); x3 sdpvar(1,1); z1 binvar(1,1); z2 binvar(1,1); z3 binvar(1,1); Constraints [Constraints, x_pos x1 x2 x3]; Constraints [Constraints, 0 x1 d1 * z1]; Constraints [Constraints, 0 x2 d2 * z2]; Constraints [Constraints, 0 x3 1e6 * z3]; Constraints [Constraints, z1 z2 z3 1]; F_carbon_ladder p1*x1 p2*x2 p3*x3 - p_sell*x_neg;其中d1、d2是第一段、第二段的区间长度z1、z2、z3是区间选择变量必须恰好激活一个区间。这里有个matlab规划细节第三段x3理论上无上限但优化模型里要给它一个足够大的常数上界比如1e6否则分支定界时数值会出问题。Cplex和Gurobi对1e6这种约束系数容忍度尚可别写1e12那是给求解器找麻烦。关于x_pos和x_neg会不会同时非零很多论文里会再加一组互斥约束。实际求解中不需要因为目标函数是线性最小化让两者同时非正非负只会增加成本或损失收益优化器不会那么傻。这个“自动互斥”的特性也是线性规划对偶理论里的经典结论你甚至可以省掉两个binvar。3.3 一个快速自检方法从碳价0到500扫一遍看趋势模型写完后第一件事不是看某个碳价下的具体结果而是做参数扫描自检。我会写一个外层循环把碳价从0按50元/吨的步长扫到600元/吨每次重新求解记录总成本、购电量、储能充放电量、碳排放量然后画四张趋势图。判断标准很简单随着碳价升高碳排放应该单调不增购电量应该总体下降光伏风电利用率应该上升。如果碳价升高反而刺激了购电那大概率是符号写反了——很可能在目标函数里把F_carbon写成了负项也就是卖配额赚钱变成了微网的核心收入调度策略会故意去买电制造排放再换配额收益结果完全失真。这个自检办法我每次换场景数据都会跑一遍。它不能证明模型最优但能快速过滤掉90%的建模低级错误。4. 典型日仿真场景构建与结果分析4.1 园区微网参数和典型日数据设定仿真总要有具体场景。我以一个园区微网为例构建典型日包含光伏300 kW、风电200 kW、微燃机100 kW、储能200 kWh/100 kW与外部电网的联络线功率上限150 kW。负荷曲线按工作日设定凌晨低谷约120 kW白天在180~220 kW之间波动晚高峰18点到22点冲到250 kW。风光出力按典型日设定光伏从6点开始爬升中午到峰值180 kW左右16点后快速衰减风电夜间出力较强最高160 kW白天较弱只有40~80 kW。分时电价取峰平谷三段峰时10:00-12:00、18:00-22:00为1.0元/kWh平时8:00-10:00、12:00-18:00、22:00-24:00为0.6元/kWh谷时0:00-8:00为0.3元/kWh。碳交易相关参数是重点我按表1设置参数符号取值说明电网排放因子k_grid0.581 kgCO2/kWh典型区域电网均值微燃机排放因子k_mt0.45 kgCO2/kWh天然气发电典型值免费配额基准k_allow0.4 kgCO2/kWh按负荷电量核算基础碳价p_co20.2元/kg200元/吨情景假设阶梯碳价三段0.2/0.4/0.6元/kg超额量递增惩罚这个配额基准设定值得多嘴一句k_allow取0.4而电网因子取0.581说明微网只要大量从电网买电就会产生配额缺口光伏风电发得越多缺口越小甚至变成盈余。两者相等时碳交易机制对微网整体是中性的低于电网因子才有真正的减排压力。4.2 无碳交易、固定碳价、阶梯碳价三组结果对比我用同一组负荷和风光出力数据跑三个场景只改碳交易部分无碳交易、固定碳价、阶梯碳价。仿真数值如表2。场景购电量占比光伏利用率总排放碳净收益综合总成本无碳交易56%96.2%1820 kg不计算4940元/日固定碳价51%98.8%1650 kg242元/日4815元/日阶梯碳价48%100%1515 kg173元/日4876元/日这几个数字是特定参数下的示例结果不代表所有场景都这样但趋势具有普遍性。无碳交易场景里系统只盯电费储能主要集中在峰时放电碳交易场景里储能充电时段开始向前半夜偏移因为谷时低价购电的同时配额消耗也更少——虽然在我的简化模型里电网排放因子是恒定的碳成本分摊到每个购电时刻也相同但低价谷电天然让“同样碳排放花的钱更少”所以储能更愿意低谷多充。固定碳价和阶梯碳价之间排放继续下降但综合总成本反而比固定碳价高了一点。这说明阶梯碳价的“边际惩罚”开始起作用系统想通过更多储放策略和光伏超发进一步减排但减排手段已经逼近物理极限继续减排的边际成本超过了碳价收益所以总成本小幅上翘。这个拐点就是碳价信号效率的体现。4.3 仿真结果里的三个关键现象现象一储能策略从“峰谷电价套利”升级为“峰谷电价碳排放双重套利”。谷时电价低、碳配额消耗单价低峰时放电替代的恰好是高价高碳的电网电量等于一次储能循环赚了两份钱。这也是我在报告里最想强调的一个结论碳交易机制下储能的经济价值比传统电价套利模型更高。现象二光伏风电的“配额盈余”变成了实实在在的收入。固定碳价场景里光伏利用率从96%上升到98.8%风电基本被全额消纳。每消纳1 kWh的可再生能源系统就多出0.4 kg的配额盈余碳价0.2元/kg时价值0.08元。对分布式光伏来说这相当于在度电收入上又叠加了一笔“绿色补贴”。这个结论如果做成敏感性分析图横轴是碳价纵轴是光伏最优装机容量会看到特别明显的正相关性。现象三碳价不是越高越好存在“减排收益饱和点”。从固定碳价到阶梯碳价排放继续降了135 kg但成本增加了61元/日相当于为减排这135 kg二氧化碳付出了61元成本折合约450元/吨。如果市场上碳配额价格低于这个数直接用钱买配额反而更划算。这个“边际减排成本”曲线才是碳交易优化最有价值的输出物。5. 求解与复现中我踩过的坑数值尺度、求解器选型、环境排错5.1 目标函数的数值病态把“元”换成“千元”后收敛速度天差地别我第一次跑通这个模型时Cplex报了一堆警告求解时间也奇慢中途状态怎么都不收敛。后来排查半天问题出在数值尺度上目标函数里电费是元级别储能运维成本是元级别但碳配额如果按吨/万kWh去算某些中间变量能出现10的9次方。MILP求解器内部用浮点运算对松弛和分支求解都会设定数值容差。约束系数和目标系数量级相差10的6次方以上时求解器经常把小数部分直接截断出现假不可行或者收敛极慢。解决办法是统一量纲功率一律kW时间一律h电量kWh排放kg成本元。如果目标函数总值达到百万级别整个目标函数除以1000变成千元求解数值更稳结果记得乘回来。这里也顺便回应一下很多人在Matlab里问的“1e100怎么表示”——Matlab当然能表示1e100但如果你把它当惩罚系数写进优化模型基本等于宣告求解器崩溃。我趟过的另一个坑就是把“禁止违背某约束”写成加一个1e9的巨大惩罚项结果求解器陷入数值震荡后来全部改成严格的逻辑约束或二进制变量一次通过。5.2 Yalmip商用求解器的接线问题license、mex和并行池Yalmip只是建模接口真正求解靠后端求解器。我推荐先装Gurobi或者Cplex再装Yalmip然后把Yalmip的路径和求解器的路径都加到Matlab搜索路径里。这时候有一个常见问题在Matlab里跑Yalmip提示求解器找不到或者报“Invalid MEX file”。原因通常是求解器的mex接口没有编译对应Matlab版本的接口文件重新运行求解器自带的mex安装脚本就行。如果遇到license相关错误比如license manager error -8这通常在Matlab层面和求解器层面都可能出现。Matlab层面的-8错误大多和正版授权服务的连接状态有关检查服务是否启动、主机名是否改变、授权文件路径是否丢失求解器层面的license错误则要检查环境变量或者license文件是否指向正确的服务器地址。这些都是环境配置问题不是代码问题不要一上来就重装系统。另外用parfor批量扫碳价参数时经常会碰到“no parallel pool”的报错。如果机器上没有Parallel Computing Toolbox或者并行池没启动parfor不会自动降级成普通for循环而是直接报错。最简单的方式是直接用普通for循环因为24时段的MILP单次求解通常不到一秒扫50个碳价参数也就是半分钟的事完全没有必要开并行池。真要跑8760小时的大规模模型再考虑并行和滚动优化那是另一套玩法了。5.3 无解、乱报和“碳交易毫无影响”的自查清单最后给一份我自己反复使用的自查清单排查顺序从“模型能不能解”到“结果合不合理”问题可能原因排查方法求解器报无解功率平衡方程漏了Psell或Ch项逐项检查等式的每一项是否都有对应变量SOC越界SOC索引对错了T和T1画SOC曲线看是否在某个点突跳购电和售电同时非零漏写u_buy u_sell 1查看u_buy和u_sell序列找出同刻为1的点碳价从0扫到600结果完全不变配额基准高于调用的排放因子微网永远盈余对比k_allow和k_grid、k_mt的大小把基准调严碳排放随碳价升高反而变大F_carbon符号写反卖配额成了主要赚钱手段检查目标函数中F_carbon是加项还是减项储能一天结束后SOC严重下降缺SOC(T1) SOC(1)约束加末态约束后重跑这里面最坑的是“碳交易毫无影响”的情况。我一开始用了一个特别宽松的配额基准结果全场景无论怎么调碳价调度策略纹丝不动因为没有一次运行到配额边界之外碳交易成本恒为零。这其实也反过来印证了一件事碳交易模型的边际效果完全取决于配额基准是否“碰到了约束边界”。如果碰不到碳价就是个摆设。最后再分享一点调参过程的实际体会整个模型里最敏感的参数不是碳价而是免费配额基准。k_allow从0.4改成0.45购售电策略、储能调度、综合成本全会变。所以拿到任何碳交易优化模型第一件事就是把配额基准、排放因子、碳价这三个参数做成可配置变量先跑一遍组合扫描看清楚系统在什么边际条件下会切换电源策略、储能策略再谈更复杂的多目标优化或者随机规划。我踩过最重的一个坑是拿别人论文里的代码直接换数据跑结果因为对“配额按负荷电量还是购电电量核算”理解不一致跑出来的结论和别人完全相反。碳交易的数学形式非常简单但业务口径的不同会导致结果南辕北辙。做仿真之前先花一小时把口径对齐比多写一百行代码都值。这套模型框架后续能扩展的方向很多加入碳捕集设备、把风光出力改成不确定性场景、做多微网之间的碳配额共享调度本质都是在同一个MILP骨架上增加约束和成本项。希望这篇记录能让你少踩几个我踩过的坑。