我在做一个园区级综合能源系统的优化调度时,客户最初的要求非常朴素:全年的购电费用最小。于是我按分时电价做了个精细的谷充峰放策略,储能谷段充满、峰段放电,燃气轮机配合峰段顶峰,算下来电费确实压下去不少。可到月底一算总账,发现碳排放配额缺口比预想大得多,缺口部分按碳市场价格买回来之后,原先省下的电费反而被抵消了一大块。
这就是“双碳目标下综合能源系统优化”最典型的尴尬:分时电价和碳交易是两套不同逻辑的价格信号,分开算计的时候各自都合理,合在一起看却互相打架。我们真正要做的,不是分别找最低点,而是让一套调度策略在这两个信号之间取得平衡。
这篇文章就把这件事拆开讲透。我会从两类价格信号的结构差异入手,说说优化模型怎么搭、目标函数和碳约束怎么写、求解器怎么选,最后给一组模拟数据的测算对比,让大家直观看到碳价加入之后,机组启停和储能策略到底发生了什么变化。适合正在做综合能源调度、微电网经济优化,或者刚接触碳配额成本化的同学参考。
1. 只看电价调度的“省钱错觉”:我被一个峰值碳排放卡了脖子
1.1 分时电价驱动下的常规调度长什么样
典型的综合能源系统里,电负荷高峰往往和电价高峰重合。常规调度逻辑是:低谷时段从电网多买点便宜电,填进储能;高峰时段储能放出来,同时燃气轮机顶上,尽量减少高价购电。这个思路本身没错,经济上完全站得住。
问题出在“只盯着电价”这件事上。电价是日内波动的,但碳排放是按月、按年度累计核算的。你谷段买电是便宜了,但电网侧的间接排放照样发生;你峰段让燃气轮机满发,天然气的直接排放也在同步增长。调度策略把购电成本压得越低,越可能把便宜时段的高电耗和高碳排一起放大。
我当时也犯了同样的错:目标函数里只有购电费用、燃料费用和设备运维费用,碳排放只是最后一层“事后统计”,根本没有参与优化决策。等运行一个月把账单拉出来,碳排放超了配额约7%,这部分缺口直接在碳市场买了配额,多花的钱比谷段省下的电费还多。
1.2 碳配额缺口才是真正没有写进模型的成本
很多团队做综合能源优化时,把碳配额当成一个“合规要求”,感觉只要不超过配额就行。但超过配额其实是有价格的,而且这个价格会随着市场交易波动,不是固定常量。更重要的是,配额本身也是稀缺资源,你的系统如果恰好处于配额基准线边缘,那每多排一吨碳,都要真金白银去买缺口。
这就意味着,碳排放不应该是优化跑完之后的一个校验指标,而应该以“碳成本”的形式进入目标函数,和购电费用、燃料费用并列参与寻优。否则你算出来的所谓“经济最优解”,只是电价视角下的最优,不是用户实际掏钱视角下的最优。
1.3 两种信号的时间尺度差异决定了模型复杂度
分时电价以小时为单位波动,碳配额以年度或月度为单位核算,碳价以交易日为单位变化。这三者放在同一个优化周期里,就产生了数据对齐问题:日内调度要响应小时级电价,碳排放约束却要盯住月度总量。
实操中比较合理的做法是“日调度嵌套月校核”:用典型日做时序优化,日内逐小时决策;月度层面统计碳排放总量和配额缺口,计算碳成本后回填到日优化参数里,迭代修正。这样既保留了日内电价优化对设备启停的精细刻画,又把碳成本的长期信号沉淀到了每一次调度决策中。
2. 分时电价与碳价如何走进同一张目标函数
2.1 分时电价的结构拆解
分时电价的本质是引导用户转移用电行为。国内工商业用户的电价通常划分尖峰、高峰、平段、低谷几个档位,夏季和冬季还会因为负荷特征不同调整时段。典型数值大概在谷段0.4元/千瓦时到尖峰1.3元/千瓦时之间浮动,峰谷价差可能达到3倍以上。
这个结构对储能很友好:谷段充、峰段放,一充一放就能赚取价差。但对燃气轮机来说,意味着“何时开机”比“是否开机”更关键。峰段自发电比购电便宜,谷段则反之,所以机组往往需要每天启停一次,甚至两次。
分时电价本身不反映环境成本。电价里如果没包含碳排放的价格信号,用户看到的购电成本就是“不完整”的。
2.2 碳交易的核算逻辑与配额处理
碳市场的运行逻辑可以简化成三条线:配额、实际排放、缺口盈余。配额是政府或其他授权机构按基准发放的排放上限,实际排放是系统边界内各种耗能活动折算出的二氧化碳当量,两者之差就是需要到市场买卖的额度。
综合能源系统涉及两类排放:一类是天然气、煤等燃料燃烧产生的直接排放,一类是外购电力在上游电厂侧产生的间接排放。前者好算,按燃料消耗量和排放因子折算;后者要用区域电网的平均排放因子乘以购电量。很多系统做优化时只盯直接排放,把电网购电对应的间接排放漏掉了,算出来的“低碳策略”往往有偏差。
配额基准的确定方式对优化结果影响非常大。如果按历史强度发放配额,那系统越节能越吃亏,因为基准会被拉低;如果按行业先进值发放,那优化就有明确追赶目标。模型里需要把配额作为一个外部给定的参数输入,而不是让模型自己优化出配额。
2.3 碳成本的表达式:把排放约束“货币化”
要让碳成为优化中的价格信号,最简单的方式是在目标函数里增加一项碳交易成本,表达式很直白:
[ C_{carbon} = \lambda_{co2} \times (E_{total} - E_{quota}) ]
其中 (\lambda_{co2}) 是碳价,(E_{total}) 是系统总排放,(E_{quota}) 是配额。当排放低于配额时,这一项是负收益,系统可以在碳市场出售盈余配额;高于配额时就是额外成本。
这样处理的好处是把“限排”这种硬约束变成了“衡量代价”的软约束。系统不再需要人为规定一个绝对的排放上限,而是让模型在边际成本上自行决策:如果减排一吨碳的成本低于碳价,模型会选择减排;如果高于碳价,模型会选择买配额。这个平衡点在优化中自然涌现,比拍脑袋定一个排放上限要合理得多。
3. 优化模型搭建:从设备方程式到碳配额不等式
3.1 决策变量与目标函数
综合能源系统优化的核心是一套时序决策。以小时为步长,以典型日为单位,决策变量包括各时段外购电功率、燃气轮机发电功率与启停状态、储能充放电功率、电锅炉产热功率等。
目标函数写成所有成本的累加:
[ \min \sum_{t} \left( C_{buy,t} + C_{fuel,t} + C_{om,t} + \lambda_{co2} \cdot (E_{total,t} - E_{quota,t} / T) - C_{sell,t} \right) ]
其中 (C_{buy,t}) 是分时购电费用,(C_{fuel,t}) 是天然气燃料费用,(C_{om,t}) 是设备运行维护费用,(C_{sell,t}) 是余电或余热出售的收益。配额每天按比例分配后参与逐时优化,月度总量再做校核。
3.2 关键约束条件怎么落笔
约束条件里的核心是能量平衡。电功率平衡要求每个时段外购电、光伏出力、燃气轮机发电、储能放电之和,等于电负荷、储能充电、电锅炉耗电之和;热功率平衡要求燃气轮机余热回收、电锅炉产热之和,等于热负荷。
设备运行约束方面,燃气轮机有出力上下限、爬坡速率和最小连续运行时间;储能电池有充放电功率限制、荷电量递推关系,以及为防止循环损耗而设的日充放次数限制。需要特别注意的是,储能充电和放电通常不能同时进行,这个行为要用0-1变量约束,否则模型会出现无意义的“充放抵消”。
碳排放约束写成累计不等式:
[ \sum_{t} \left( E_{direct,t} + E_{indirect,t} \right) \leq E_{quota} + E_{buy} ]
(E_{buy}) 是配额缺口变量,代表允许在碳市场购买的量。配合目标函数里的碳价,这个变量会自己权衡到最优值。
3.3 硬约束、软约束与影子价格
有些项目会把碳排放上限作为硬约束直接写进模型,这种做法简单但容易导致无解:如果配额下得太紧,模型找不到可行调度策略,只能把负荷切除或者强制停机,这在工程上是不允许的。
我习惯采用“配额缺口松弛法”,给缺口变量配一个比较大的惩罚系数,或者直接用碳价格作为系数放进目标函数。这样模型在极端情况下可以超排,但会付出代价,优化结果会平滑很多。影子价格还能告诉你边界条件变化时成本的敏感度,这个信息在项目汇报时非常有用。
3.4 一个简化的模型代码骨架
下面是用Pyomo搭建的一个最小示例骨架,省略了具体的设备参数,只展示目标函数和主要约束的结构,方便理解整体写法。
import pyomo.environ as pyo model = pyo.ConcreteModel() # 时段集合,24小时 model.T = pyo.Set(initialize=range(24)) # 决策变量 model.p_buy = pyo.Var(model.T, domain=pyo.NonNegativeReals) # 外购电 model.p_gas = pyo.Var(model.T, domain=pyo.NonNegativeReals) # 燃气轮机出力 model.u_gas = pyo.Var(model.T, domain=pyo.Binary) # 燃气轮机启停 model.p_ch = pyo.Var(model.T, domain=pyo.NonNegativeReals) # 储能充电 model.p_dis = pyo.Var(model.T, domain=pyo.NonNegativeReals) # 储能放电 model.e_soc = pyo.Var(model.T, domain=pyo.NonNegativeReals) # 储能电量 # 目标函数:购电 + 燃料 + 碳成本 def objective_rule(m): total = 0 for t in m.T: total += price_buy[t] * m.p_buy[t] total += price_gas * m.p_gas[t] / eta_gas total += co2_price * (e_direct(t, m) + e_indirect(m.p_buy[t]) - e_quota_avg) return total model.obj = pyo.Objective(rule=objective_rule, sense=pyo.minimize) # 电功率平衡约束 def power_balance(m, t): return (m.p_buy[t] + pv[t] + m.p_gas[t] + m.p_dis[t] == load_elec[t] + m.p_ch[t] + p_eb[t]) model.power_bal = pyo.Constraint(model.T, rule=power_balance) # 储能递推约束 def soc_evolution(m, t): prev = m.e_soc[t-1] if t > 0 else soc_init return m.e_soc[t] == prev + eta_ch * m.p_ch[t] - m.p_dis[t] / eta_dis model.soc_eq = pyo.Constraint(model.T, rule=soc_evolution) # 储能容量上下限 def soc_limit(m, t): return soc_min <= m.e_soc[t] <= soc_max model.soc_bounds = pyo.Constraint(model.T, rule=soc_limit) # 防止同时充放 def no_simul_ch_dis(m, t): return m.p_ch[t] + m.p_dis[t] <= ess_power_max model.ess_exclusive = pyo.Constraint(model.T, rule=no_simul_ch_dis)这段代码不是能直接跑的生产级模型,但结构已经足够表达核心逻辑。工程化时还要补上热平衡、天然气流量上限、设备启动能耗、爬坡约束等细节。
4. 求解器与算法选择的实战判断
4.1 为什么首选混合整数线性规划
综合能源系统优化里既然有燃气轮机启停、储能运行状态这类开关逻辑,就有0-1变量。只要整数变量参与,问题就从线性规划变成了混合整数线性规划。
MILP的优点是能给出全局最优解。对设备数量在几十台、变量在几千个的园区级问题,商业求解器通常在几十秒内就能收敛。相比之下,遗传算法、粒子群这类启发式算法虽然也常被引用,但它们给的是“可行解”,未必是“最优解”,而且调参成本高,复现性差。除非模型的非线性实在无法线性化,否则我不会轻易上启发式算法。
4.2 非线性环节的线性化处理
综合能源系统里天然存在非线性因素,比如燃气轮机效率和负载率之间的曲线关系、储能电池充放电效率和电流的关系、变频设备的功率-流量特性。把这些非线性直接丢给MILP求解器是不行的,需要做线性化近似。
最常用的处理是分段线性化。把燃气轮机负载率分成几段,每段用不同的线性效率公式,用0-1变量锁定当前负载率所在段。分段越多越接近真实曲线,求解时间也会增加。工程上取4到6段往往就能保证误差在可接受范围内。
效率曲线以外的非线性,比如碳排放在不同负荷下的非线性系数,也可以采取同样的分段策略。关键原则是:曲线越陡峭的地方分段要越密,平坦区间可以拉大段距。
4.3 求解器选型与实际配置经验
商业求解器和开源求解器我都有使用经验。开源求解器在中小规模问题上表现越来越稳定,比如CBC、HiGHS这类,几千个约束的项目完全可以处理,主要限制在于整数变量数量增大后求解时间会快速上升。商业求解器在分支定界的剪枝策略上更成熟,适合万级以上变量、需要长时间序列滚动优化的场景。
另外值得留意的是求解时间预算问题。工程上不用追求每次对话都跑到最优,设定一个“最大求解时间”和“相对最优间隙”的组合更实际。比如允许2%的次优偏差,可能把求解时间从十分钟压缩到几十秒。对日内滚动调度来说,这个偏差对成本的影响微乎其微,但对工程部署的响应速度影响很大。
5. 一次带数据的模拟测算:碳价如何改写机组启停表
5.1 算例场景与边界设定
为了把前面几节的内容落到数字上,我做了一次模拟测算。模型对象是一个虚构的中型园区,包含300千瓦光伏、1兆瓦燃气轮机、500千瓦电锅炉、300千瓦时/150千瓦储能,电负荷峰值约500千瓦,热负荷峰值约300千瓦。
分时电价采用典型工商业分段设置,全天划分尖峰、峰、平、谷四个档位,具体数值参考下表。
| 时段 | 电价(元/kWh) |
|---|---|
| 0:00-8:00(谷) | 0.35 |
| 8:00-11:00(平) | 0.70 |
| 11:00-15:00(峰) | 1.00 |
| 15:00-18:00(平) | 0.70 |
| 18:00-19:00(尖峰) | 1.20 |
| 19:00-21:00(峰) | 1.00 |
| 21:00-24:00(平) | 0.70 |
天然气价格按3.2元/立方米折算为燃料成本,燃气轮机发电效率取40%,热电联产综合效率80%。电网排放因子取0.58 kgCO2/kWh,天然气直接排放因子按0.20 kgCO2/kWh发电量折算。配额设置为每日7.5吨,碳价假设50元/吨。
5.2 两种场景的优化结果对比
我设置了两个场景:场景A的目标函数不包含碳成本,只包含购电、燃料和运维费用,这个对应很多项目初期的做法;场景B的目标函数叠加碳成本,采用碳价50元/吨。两个场景的约束条件完全相同,都允许配额缺口存在。
优化结果汇总如下。
| 指标 | 场景A(只看电价) | 场景B(电价+碳价) |
|---|---|---|
| 日购电成本(元) | 3160 | 3390 |
| 日燃料成本(元) | 2230 | 1960 |
| 日碳成本(元) | 85 | 40 |
| 综合成本(元) | 5475 | 5390 |
| 碳排放总量(吨) | 9.2 | 8.3 |
| 配额缺口(吨) | 1.7 | 0.8 |
| 燃气轮机发电量(kWh) | 8600 | 7400 |
| 储能谷段充电量(kWh) | 920 | 1040 |
5.3 两组数据背后的调度逻辑
从数值上看,场景A的购电成本虽然低,但燃料成本高企,燃气轮机在谷段和平段都在出力。场景B则不同,燃气轮机明显向峰段和尖峰段集中,谷段和平段的出力压了下来,换成低价网购电加储能放出来。这个转移带来的结果是碳排放下降约9.8%,综合成本反而降低了约85元。
储能行为的变化更值得注意:场景B中谷段充电量从920千瓦时升到1040千瓦时,说明碳价让峰段购电的“隐性成本”变高了,储能通过多充多放来对冲这部分成本。即便储能的循环效率不是100%,多出来的峰段放电收益仍然覆盖了谷段充电的损耗。
这个案例说明一个道理:当碳价进入目标函数,它不仅仅是给排放“加了一笔账”,而是改变了所有边际成本之间的相对关系。机组的启停顺序、储能的充放深度、外购电和自发电的比例,都会因此重排。
5.4 碳价高低对策略的敏感性
我把碳价从零一直扫到150元/吨,观察燃气轮机发电量和碳排放总量的变化。结果显示:碳价低于30元/吨时,调度策略基本不受影响,因为碳成本相对于峰谷电价差来说太小,还不足以改变边际决策;碳价超过50元/吨后,燃气轮机在平段和谷段的出力会明显减少;碳价到120元/吨以上,系统会主动压减部分可转移负荷,甚至考虑增加储能容量。
这个敏感性区间的意义在于:碳价不是拍脑袋定一个数字就能完事的,做方案时需要把碳价的可能波动区间都纳入,观察策略在不同碳价下的稳健性。如果某个方案在碳价40元和80元时的策略差异巨大,说明系统的经济性过度依赖碳价假设,这个风险需要重点提示。
6. 把模型推向落地时反复遇到的五个坑
6.1 配额基准数据的口径混乱
模型里对配额参数的依赖很高,但实际拿到的配额数据往往口径不统一。有的按企业总排放量分配,有的按单位产品产量分配,有的按供电量和供热量分别分配。综合能源系统涉及电、热两种产品,热电分摊的比例会直接影响配额量,进而影响优化结果。
建议在项目一开始就和客户明确配额核算边界:外购电力对应的间接排放算不算进来?外售热力是否单独核算?这两个问题不确定,模型跑出来的碳成本就没有意义。
6.2 碳价输入用的挂牌价还是实际成交价
碳市场的挂牌价和实际成交价往往有差距,大额配额的协议价格也可能低于公开市场价格。如果直接把挂牌价当模型输入,优化所得的碳成本可能与财务实际记账有出入。
给模型输入碳价之前,最好先统计最近一个季度的成交均价,而不是某个时点的即时价格。如果交易量小、价格波动大,就要做敏感性分析,给出碳价上下浮动20%时的成本变化区间。
6.3 燃气轮机恒定效率导致调度失真
很多初版模型把燃气轮机效率设成恒定值,这个假设短期内可以用,但遇到低负载运行就容易出问题。燃气轮机在30%以下负载时效率可能陡降10个百分点以上,如果模型仍然假设高效运行,优化结果会让机组在低负载段长时间运行,实际运行却很不经济。
解决办法是分段线性化效率曲线,并且在模型校验阶段用历史运行数据复核。复核时重点看“模型计算出的燃料消耗”和“实际燃料账单”的偏差,偏差超过5%就需要回头查效率模型。
6.4 时间颗粒度太粗吞掉了分时电价细节
有些团队为了降低求解规模,用两小时甚至更粗的步长建模。分时电价是按小时划分的,如果步长跨了两个电价段,模型会采用加权平均电价,结果就是储能充放电的边际收益被平均化,峰谷套利空间被严重高估或低估。
我的建议是核心优化至少按1小时建模,如果项目对控制策略的实时性有更高要求,那就按15分钟建模,同时叠加一个运行约束:同一个电价段内储能充放功率变化幅度不宜过大,防止“理论最优策略”在实际下发时剧烈抖振。
6.5 从优化结果到实际执行还有一层偏差
优化模型算出来的是理想调度曲线,实际执行时还要面对设备响应延迟、天气预报偏差、负荷预测误差等问题。光伏出力预测晴天和雨天差一大截,如果按理想化的光伏曲线做调度,储能和燃气轮机的策略都会跑偏。
比较稳妥的做法是采用滚动优化:每15分钟或每小时更新一次预测数据,重新求解未来4到6小时的调度策略,只执行当前时段的控制指令。这样做虽然牺牲了一部分“全时段最优”,但对预测误差的鲁棒性提升很大。在工程现场,高效率的鲁棒方案永远比纸面上的最优方案更有价值。
我从这个项目里最深的体会是:综合能源系统优化到后面根本不是求解器比赛,而是对“价格信号理解深度”的比拼。分时电价教会我们何时用电,碳交易教会我们用什么电。把这两件事放进同一个模型,虽然模型的复杂度上去了,但客户看到的账单才真正对得上他付出的成本。如果你也开始做类似的系统,建议先别急着上重型求解器,把分时电价的分段、配额核算的边界、电网排放因子的选取这三件事和客户彻底对齐,再开始建模。这几件事不搞清楚,后边算得多漂亮,现场都落不了地。