做综合能源系统优化,最麻烦的不是设备建模,而是“不确定”三个字。风电、光伏出力说变就变,绿证价格和碳配额又给运行成本叠加了一层隐形成本。我最近把一个面向绿证-碳交易机制的综合能源系统两阶段鲁棒优化方法用Python完整实现了一遍,求解核心是C&CG(列与约束生成)算法。这套代码能处理风光出力的区间不确定性,在保证系统安全约束的前提下给出最经济的日前调度方案,同时把碳排放履约成本和绿证消纳权重都折算进优化目标。如果你正在做低碳园区、多能互补系统,或者想在电力市场环境下做鲁棒调度,这篇内容应该能帮你省掉不少折腾时间。
这类项目网上能搜到不少源码,但很多版本不是对偶变换符号写反,就是大M取值离谱导致结果完全不可信。所以这篇文章我会把建模思路、CCG迭代逻辑、Python实现细节和踩坑记录全部摊开来讲,尽量让拿到代码的人不仅能跑通,还能看懂为什么这么写。
1. 项目场景与建模对象拆解
1.1 综合能源系统到底在优化什么
我以一个典型的园区级综合能源系统(IES)作为算例。系统外部接入电网和天然气网,内部包含光伏、风电、热电联产机组(CHP)、燃气锅炉、电锅炉、电储能和热储能,同时供应电负荷和热负荷。优化目标很直白:在满足24小时冷热电平衡和设备运行约束的前提下,确定各设备的出力计划、购电购气量、储能充放策略,以及绿证购买量和碳配额履约量,使总运行成本最低。
这套结构是综合能源系统优化里最经典的配置。CHP同时产电和产热,能显著提高天然气利用效率,但它的电出力会受热出力约束,存在“以热定电”的耦合关系。燃气锅炉作为热力备用,响应速度快但效率不如CHP。电锅炉的价值在于消纳风电或光伏高峰时段的多余电力,把电转换成热,相当于给系统增加了一条“电转热”的灵活性通道。储能的作用更直白,低谷时段蓄能、高峰时段放能,平抑负荷波动和新能源的不确定性。
这里要注意,IES优化和普通电力系统优化的关键差异在于“多能互补”四个字。电能、热能、天然气在每个时段都要同时满足平衡方程,而且设备之间的出力还会互相制约。比如CHP多产电,则余热必然增加;燃气锅炉多产热,则购气量必然上升。这些耦合关系在建模时要全部写进约束矩阵,否则结果会出现“同时买高价电和烧气产热”这种不合常理的情况。
1.2 绿证和碳成本是怎么叠进目标函数的
绿证-碳交易机制是最近几年IES调度的热点约束,但如果只看论文里的目标函数,其实一点都不复杂。碳排放成本的核心是计算实际排放量与免费配额之间的差额,再乘上碳价。在日内调度模型中,我按常用做法把年度配额拆到当日,得到逐时段或整日的免费配额量。
外购电力对应的间接排放等于购电量乘电网排放因子,天然气燃烧对应的直接排放等于购气量乘天然气排放因子。两者相加得到总排放量,再减去免费配额,差额乘碳价就是碳履约成本。如果实际排放低于配额,差额为负,相当于卖出配额获得收益。这在线性规划里完全就是一条线性项,没有任何非线性困难。
绿证约束的建模有两种常见方式。第一种是独立约束:系统内可再生能源发电量加上购买的绿证量,必须达到总用电量的一定比例,这是消纳责任权重的直接体现。第二种是耦合抵扣:购买绿色电力证书后,其对应的减排量可以从碳排放中扣除,这样绿证价格和碳价就会通过优化变量产生联动。我带徒弟做项目时基本都用第二种,因为它能反映绿证和碳市场互认抵扣的政策趋势。
举个例子帮助理解。假设某时段外购电100MWh,电网排放因子0.7 t/MWh,则间接排放70吨。如果免费配额只有60吨,碳价60元/吨,则需要支付600元碳履约成本。此时购买5张绿证(对应5MWh可再生能源电量),按抵扣系数0.7计算可抵扣3.5吨碳排放,碳履约成本减少210元。但买绿证花了5张×50元/张=250元,合计反而多花40元。优化器就会自动判断:绿证价格、碳价、抵扣系数三者之间的数值关系,决定到底买几张绿证划算。这就是机制建模产生的成本权衡,也正是“绿证-碳交易”影响调度决策的核心路径。
1.3 风光一变天就出事:为什么确定性调度不够用
很多初学者先做确定性优化,就是假设风光出力等于预测值,直接解一个MILP。结果论文画出来的曲线很漂亮,但实际运行时会发现,光照突然被云遮住、风突然减小,之前求出的“最优解”可能直接导致失负荷或者设备越限。原因很简单:你把光伏预测值当成必然发生的事,系统没有给不确定性留出备用空间。
用生活类比来说明。确定性优化相当于你按今晚天气预报决定穿什么衣服出门,如果天气预报说晴天,你就只穿短袖,结果半夜降温就被冻着了。鲁棒优化则不同,它考虑的是“如果这一周天气变化范围是这样,最糟糕的情况会是什么样”,然后在这个最坏情况下做决策。决策结果可能不如晴天方案那么“省钱”,但无论天气怎么变,你都不会被冻病。这就是鲁棒优化的核心思想:用一点经济性损失换取全天候的可行性。
在IES调度里,不确定性主要来自风电、光伏发电的预测误差。实际出力通常落在预测值附近的区间内。两阶段鲁棒优化把问题分成“事前决策”和“事后调整”两层:第一阶段先确定一些不能等待的决策,第二阶段等不确定量实现后,再调整出力来满足平衡约束。目标函数里出现的那一坨max-min结构,就是在寻找“最坏的风光出力组合下,最小的运行成本能是多少”。这个数值其实就是系统为抵抗不确定性必须付出的代价,也就是鲁棒调度的“保费”。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 两阶段鲁棒优化的建模逻辑与求解算法
2.1 两阶段分工:哪些决策必须“现在定”,哪些可以“事后调”
两阶段鲁棒优化里,第一阶段和第二阶段的分工标准只有一个:这个决策能不能等不确定性实现后再做。如果必须提前签订合同、确认机组启停状态,放第一阶段;如果可以在实时运行时根据实际出力再调整,放第二阶段。
在IES调度中,机组启停状态、与外部电网或气网签订的合同购电量等通常是第一阶段决策。因为这些决策涉及物理启停或合同约束,无法在短时间内改变。而各设备的具体出力、储能的充放功率、需要调用的备用容量等,则放第二阶段。实际风光出力有一个具体数值后,第二阶段的调度员可以随时调整这些变量来保证系统平衡。
数学上写成标准形式是:
min_x c^T x + max_{u ∈ U} min_{y ∈ F(x,u)} d^T y
其中x是一阶段变量,u是不确定量,y是二阶段变量。外部max的部分在寻找最坏场景,内部min的部分在计算该场景下最优经济调度成本。第一阶段的成本c^T x加上最坏场景下的二阶段成本,就是鲁棒调度的总目标。
这里有个实际操作建议:第一阶段变量别塞太多0-1变量。主问题如果出现大量整数变量,每轮迭代都要重新求解MILP,计算时间会急剧上升。我做过对比,同样规模的问题,第一阶段加一组机组启停0-1变量,主问题求解时间可能翻3到5倍。所以如果运营模型里不关心启停状态,干脆把机组状态当作连续变量或固定为1,工程上完全够用。
2.2 不确定集怎么设计:区间、预算和保守度
鲁棒优化里没有概率分布的概念,只有不确定集U。相当于用集合来描述“不确定量可能落在哪些值”。最常见的是盒式不确定集:实测出力等于预测出力加减一个偏差量,即 u ∈ [u0 - Δu, u0 + Δu]。这个集合最直观,但直接用完整盒式会非常保守,因为所有不确定源同时达到最坏情况的概率极低。
为了控制保守度,引入预算约束。设标准化偏差为ξ,则ξ∈[-1,1],同时限制∑|ξ_i| ≤ Γ。Γ就是“预算”。Γ=0时退化为确定性模型;Γ=1表示所有不确定源最多只能有一个达到极限偏差;Γ增大,系统允许同时达到极限偏差的不确定源数量越多,结果越保守。
以风电和光伏两个不确定源为例,当预算Γ=1时,不确定集的极点是(1,0)、(-1,0)、(0,1)、(0,-1),分别对应“风大光小”“风小光大”等组合。这些极点正是最坏情况可能出现的候选点。在CCG算法的子问题求解中,我们通常只需要在这些极点上搜索即可。这也是为什么很多IES鲁棒优化论文采用盒式加预算的不确定集,因为极点枚举非常方便。
不确定集的参数设置要结合历史数据。光伏预测误差通常取预测值的15%到20%,风电取25%到35%。预测水平越差,波动范围就越大,鲁棒解也越保守。一开始建议只考虑风电和光伏两个不确定源,验证算法逻辑没问题后再扩展负荷或电价的不确定性。
2.3 CCG迭代流程:主问题-子问题怎么相互咬合
两阶段鲁棒优化直接用常规求解器是解不了的,因为max-min结构让问题变成了三层嵌套。C&CG(列与约束生成)算法是解决这类问题的标准方法,核心思想是通过迭代逐步逼近最坏场景。
算法流程如下:
- 初始化:设定初始最坏场景u^0,一般取预测值或一个极点。令迭代次数k=0,下界LB=-∞,上界UB=+∞。
- 求解主问题(MP):主问题包含第一阶段变量x、辅助变量θ,以及当前已经发现的所有场景u^0, u^1, ..., u^k对应的第二阶段变量y^0, y^1, ..., y^k。每个场景都要加入完整的运行约束,并要求θ≥每个场景的二阶段成本。求解MP得到x和θ,此时MP的目标值就是新的下界LB。
- 固定x*,求解子问题(SP):子问题形式为max_{u∈U} min_y d^T y,用来寻找当前x下最坏的场景u,并计算该场景下的最优运行成本Q(x*)。
- 更新上界UB:UB = min(UB, c^T x* + Q(x*))。因为只要给定x和u,我们就能构造出一个完整可行解,所以c^T x* + Q(x*)是原问题的一个可行解目标值。
- 收敛判断:计算相对间隙(UB-LB)/UB,如果小于设定阈值ε(比如1%),停止并输出结果;否则把u*作为新场景加入主问题场景集合,令k=k+1,回到步骤2。
CCG比Benders分解收敛快的原因就在于它是“全场景约束”切割。每轮迭代不仅加割平面,还加入完整的场景变量和约束,信息量远大于单独一条Benders割。我在实际算例中,通常10轮以内就能达到1%的相对间隙。
还需要注意子问题可行性问题。如果某些场景下第二阶段约束无解,说明x*不够健壮,主问题需要添加可行性割。工程上常用的办法是引入松弛变量,把不可行的约束“软化”掉,这样SP总是有解,然后通过主问题逐步消除松弛量。
2.4 为什么这套方案用Python+Gurobi最顺手
很多同类论文用的是MATLAB+YALMIP,写起来确实简洁。但YALMIP在复杂迭代循环里的灵活性不够,而Python有gurobipy这样贴近求解器的建模接口,动态增删约束和变量非常方便。特别是CCG每轮迭代都要往主问题里加一批新场景的变量和约束,这种“边迭代边建模型”的需求,gurobipy几乎是首选。
Gurobi求解MILP和LP的性能都很强,而且有学术免费许可。如果没有Gurobi,CPLEX和SCIP也可以作为替代,但代码需要相应调整。我个人不推荐用pulp来写这类模型,虽然它简单,但在大规模场景约束动态添加时性能和处理灵活性都比较吃力。
另外,Python生态对数据前处理和后处理都很友好。numpy用来组织参数数组,pandas处理负荷和价格数据,matplotlib画结果图,循环迭代时还有tqdm显示进度。整套工具链组合起来,开发效率比MATLAB高出不少。这也是我现在所有优化项目都用Python的原因。
3. Python代码实现:从模型到可运行程序
3.1 算例参数与数据准备
我采用的算例是24小时调度,时间步长1小时。系统包含外部电网、天然气网、光伏1MW、风电1.2MW、CHP额定电功率1.5MW、燃气锅炉0.8MW、电锅炉0.6MW、电储能0.5MWh/0.2MW,以及热储能0.6MWh/0.2MW。设备参数列在下面,方便对照代码。
| 设备 | 参数项 | 数值 |
|---|---|---|
| CHP | 额定电功率 / 电效率 / 热电比 | 1.5 MW / 0.35 / 1.2 |
| 燃气锅炉 | 额定热功率 / 效率 | 0.8 MW / 0.9 |
| 电锅炉 | 额定热功率 / 效率 | 0.6 MW / 0.95 |
| 电储能 | 容量 / 最大充放功率 / 效率 | 0.5 MWh / 0.2 MW / 0.95 |
| 热储能 | 容量 / 最大充放功率 / 效率 | 0.6 MWh / 0.2 MW / 0.9 |
| 光伏 | 预测峰值 / 波动范围 | 1.0 MW / ±20% |
| 风电 | 预测峰值 / 波动范围 | 1.2 MW / ±30% |
市场与机制参数方面,分时电价设置三个时段:峰段1.1元/kWh、平段0.7元/kWh、谷段0.35元/kWh,天然气价格取3.5元/m³。电网排放因子0.7 t/MWh,天然气排放因子2.0 t/千m³。免费碳配额按当日允许排放80吨计算,碳价60元/吨。绿证价格50元/张,每张对应1MWh可再生能源电量,抵扣系数按电网排放因子0.7设置,消纳责任权重设为15%。
在代码里,我习惯把所有参数放进字典或numpy数组,避免硬编码。负荷曲线、新能源预测曲线在算例中直接生成,实际项目中则从SCADA或气象预测系统导入。
python复制import numpy as np
T = 24
# 分时电价(元/kWh),转换为元/MWh
price_e = np.array([350, 350, 350, 350, 350, 700, 700, 1100, 1100, 1100,
700, 700, 1100, 1100, 1100, 700, 700, 1100, 1100,
1100, 700, 700, 350, 350]) / 1000 * 1000
# 负荷曲线:电负荷峰值为1.8MW,热负荷峰值为1.2MW
load_e = np.array([...]) # 24个时段的电负荷
load_h = np.array([...]) # 24个时段的热负荷
# 新能源预测出力
pv_pred = np.array([...]) # 24个时段光伏预测
wt_pred = np.array([...]) # 24个时段风电预测
# 机制参数
carbon_price = 60.
