做冷热电联供优化调度这一年多,我最深的体会是:模型本身并不难,难的是让模型在真实运行中“跟得上”变化。刚接触这个方向时,我也和大多数人一样,先搭一个单层优化模型,把燃气轮机、吸收式制冷机、储能这些设备全塞进去,目标函数设成运行成本最小,MIQP一求解,结果也像模像样。但一旦把风光出力、冷热电负荷的实测数据扔进去,模型算出来的计划往往和实际运行偏差很大——原因很简单:预测永远有误差,而单层调度没有给误差留出修正通道。后来我接触到多时间尺度优化调度的思路,把日前、日内、实时三个层级串起来,才真正解决了这个问题。
这篇文章就围绕“基于多时间尺度的冷热电联供综合能源系统优化调度模型”展开,把我从问题建模、代码实现到算例分析中踩过的坑和验证过的方法完整拆一遍。内容主要面向正在做综合能源系统优化、微网经济调度方向的研究生或工程师,也可以给刚入门的朋友做一个整体认知框架。
1. 为什么必须用多时间尺度:单层调度的先天缺陷
1.1 预测精度与调度粒度的天然矛盾
先看一组典型数据。光伏出力预测误差在日前(提前24小时)尺度下通常为15%~25%,风速预测误差更夸张,尤其在天气快速变化时可以达到30%以上;而冷热电负荷虽然相对稳定,但用户行为的不确定性依然会让逐时预测产生5%~10%的偏差。如果只用日前预测数据一次性生成全天72个时段的调度计划,那么当实际光伏出力低于预测值时,燃气轮机就得临时增加出力去补差额——但燃气轮机的爬坡速率是有限的,电制冷机和吸收式制冷机的出力分配也可能来不及调整,最终结果就是弃光、切负荷或者从电网高价购电。
这里面有个核心矛盾:调度的时间粒度越细,对预测精度的要求越高;预测精度越高,可用的预测时间窗口就越短。天气预报能给你未来24小时的风速趋势,但无法精确告诉你5分钟后的一阵云会遮挡多少光伏;反过来,分钟级的短时预测精度高,但无法支撑机组启停这样的长决策周期决策。单层模型无论怎么选粒度,都逃不掉这个矛盾。
1.2 多时间尺度的本质:让不同决策在合适的周期上做
多时间尺度调度其实是对实际电力系统调度模式的借鉴。在传统电力系统中,调度本身就分长期、中期、短期、超短期,不同时间尺度解决不同层次的问题。把这个思想应用到冷热电联供综合能源系统中,就形成了典型的三层架构:
| 调度层级 | 时间尺度 | 决策内容 | 主要作用 |
|---|---|---|---|
| 日前调度 | 提前24h,1h分辨率 | 机组启停、联络线购电计划、储能充放电日策略 | 确定经济性最优的运行基线 |
| 日内滚动 | 提前4h,15min分辨率 | 各设备出力修正、储能出力调整 | 跟随最新预测,修正偏差 |
| 实时反馈 | 分钟级,5min~15min | 微调设备出力、储能快速响应 | 处理瞬时波动,保障功率平衡 |
这三层不是简单的重复计算,而是“层层嵌套、滚动修正”的关系。日前层定的是“大方向”,日内层做的是“短期修正”,实时层干的是“兜底执行”。每层的时间窗口和分辨率选择,都是基于预测技术的实际能力来定的,不是拍脑袋。
1.3 方案选型背后的权衡逻辑
我知道有人会问:为什么不直接用15分钟分辨率跑48小时甚至更长时间窗口的优化?理论上可以,但实际跑起来有两个问题。第一,求解规模爆炸。时间窗96个时段、设备节点十几二十个,再加上0-1整数变量(机组启停),混合整数规划求解时间会从几十秒膨胀到几分钟甚至不收敛,这还没考虑日内滚动每15分钟就要重新算一次。第二,长时间窗高分辨率并不能带来相应的精度收益,因为远时段(比如20小时后)的15分钟级预测精度并不会比1小时级高多少,反而陷入“用高分辨率去拟合误差很大的预测值”的伪精确。
所以选型逻辑其实很朴素:分辨率跟着预测精度走,决策周期跟着设备响应速度走。机组启停这类需要提前一天定的决策,放日前层;储能功率这类可以快速调整的决策,放日内层和实时层。这样既保证了经济性优化的大局观,又保留了应对不确定性的灵活性。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 系统建模与核心数学模型拆解
2.1 冷热电联供系统的典型拓扑与设备模型
在建模之前,首先要明确系统里有哪些设备、能量是怎么流动的。我用的这个模型是典型的“以热定电+辅助制冷”结构,主要包含以下设备:
- 微型燃气轮机(MT):核心发电设备,同时产生高温烟气。它的关键特征是热电比(电功率与余热功率之比)随负载率变化,建模时不能简单当常数处理,否则会影响余热利用量的计算精度。
- 余热锅炉(WHB):回收燃气轮机烟气余热,产生蒸汽或热水,用于供暖或驱动吸收式制冷机。
- 吸收式制冷机(AC):利用余热驱动制冷,典型COP在1.2~1.5之间,是“冷热电联供”中联供价值的关键环节。
- 电制冷机(EC):消耗电力制冷,COP通常在3~4之间,作为供冷侧的补充。
- 储能设备:包括蓄电池和蓄热/蓄冷罐。蓄电池响应速度快,主要承担日内和实时的功率调节;蓄热/蓄冷罐容量大、成本低,适合在日前层面做能量的时间平移。
- 可再生能源:光伏和风电作为优先消纳的电源,在模型中通常处理为负的负荷(即“净负荷”),或者作为独立电源节点。
- 电网联络线:系统可以与上级电网交换功率,既可以从电网购电,也可以向电网售电。
设备模型的数学表达是后面所有优化计算的地基。以燃气轮机为例,它的燃料成本与电出力之间通常用二次函数拟合:
燃料成本:( C_{MT}(t) = a \cdot P_{MT}(t)^2 + b \cdot P_{MT}(t) + c )
其中a、b、c是燃料成本系数,P_MT(t)是t时段燃气轮机的电出力。余热回收功率则跟电出力通过热电比关联:
余热功率:( H_{MT}(t) = P_{MT}(t) \cdot \eta_{WHB} \cdot \frac{1 - \eta_e - \eta_{loss}}{\eta_e} )
这里η_e是发电效率,η_WHB是余热锅炉的回收效率,η_loss是散热损失率。这个式子看起来有点绕,但物理含义很直接:燃气轮机发的每一度电,对应着一部分燃料能量变成了烟气余热,回收后就是我们可以用的热功率。
储能设备的建模则围绕SOC(荷电状态)展开:
蓄电池SOC:( SOC(t+1) = SOC(t) + \eta_{ch} \cdot P_{ch}(t) \cdot \Delta t - \frac{P_{dis}(t) \cdot \Delta t}{\eta_{dis}} )
其中η_ch和η_dis分别是充放电效率,Δt是时段长度。SOC上下限约束保证电池寿命,充放电功率也各有上下限约束。
2.2 目标函数:不止是运行成本最小化
这个模型的目标函数绝大多数情况下是日运行成本最小化,但具体构成比想象中要复杂。我通常把目标函数拆成四部分:
目标函数:min ( F = F_{fuel} + F_{grid} + F_{om} + F_{penalty} )
- F_fuel:燃气轮机的燃料费用,上面已经给出了二次函数形式。
- F_grid:与电网交互的费用,购电费用为正、售电收益为负。这里要注意,购电价和售电价通常不同,典型的分时电价结构下,峰平谷三个时段的电价差异很大,这是日前调度经济性优化的主要驱动力。
- F_om:设备运行维护费用,通常按各设备的出力乘以单位维护成本系数计算,系数小的设备(比如电制冷机)在优化中会被优先调度。
- F_penalty:惩罚项,包括弃风弃光惩罚和负荷缺电惩罚。这类罚函数的意义在于给优化器一个“信标”——在极端情况下允许牺牲一点经济性来保证系统不出现弃能或切负荷。
一个容易被忽略的细节是:惩罚系数的量纲和数值大小直接影响求解结果。如果弃光惩罚设得太低,优化器会倾向于弃掉光伏而不是调整机组出力;设得太高,又会过度干预正常的“削峰填谷”经济优化。我一般先把惩罚系数设为电价的1.5~2倍,再根据算例结果微调。
2.3 约束条件:从能量守恒到设备物理限制
约束条件大体分成四类,每类都有自己的坑。
第一类是功率平衡约束。这是冷热电联供系统模型的核心约束,需要分别对电、热、冷三个母线建立平衡方程:
电功率平衡:( P_{MT}(t) + P_{PV}(t) + P_{WT}(t) + P_{dis}(t) - P_{ch}(t) + P_{buy}(t) - P_{sell}(t) = P_{load,e}(t) + P_{EC}(t) )
热功率平衡:( H_{WHB}(t) + H_{dis}(t) - H_{ch}(t) = H_{load}(t) + H_{AC}(t) )
冷功率平衡:( Q_{AC}(t) + Q_{EC}(t) + Q_{dis}(t) - Q_{ch}(t) = Q_{load}(t) )
电平衡里那个P_EC(t)特别容易漏——电制冷机本身也是电负荷,如果不把它放进电平衡方程里,算出来的“电出力”会偏小,实际运行时就出现功率缺口。
第二类是设备出力上下限约束。每个设备的出力不能超过其额定容量,储能设备的充放电功率和SOC也要限制在安全范围内。这些是纯线性约束,实现起来不难。
第三类是爬坡约束。燃气轮机从一个时段到下一个时段的出力变化不能超过其爬坡速率限制。这是保证调度计划在实际运行中“可执行”的关键约束,但在许多初版模型里会被忽略。忽略爬坡约束的后果是:优化结果里燃气轮机出力在相邻时段内剧烈震荡,实际设备根本跟不上,整个调度计划等于白算。
第四类是购售电约束。系统与电网的交互功率有上限,且同一时段不能既买电又卖电。后者通常用一个0-1变量来约束,这也会引入整数变量,增加求解难度。不过这个约束很有实际意义——在分时电价下,如果允许同时购售电,优化器会“无中生有”地创造套利空间,算出来的成本低得离谱,但实际系统里不可能这么操作。
3. 三层调度策略与滚动优化实现细节
3.1 日前层:确定运行基线
日前调度的输入是未来24小时的预测数据(光伏、风电、冷热电负荷、电价),输出是未来24小时各设备的出力计划,时间分辨率取1小时。这一层最重要的决策是燃气轮机的启停状态和全天运行基线。
为什么说“基线”很重要?因为日内和实时层的所有修正都是在日前基线附近做小幅度调整,而不是推倒重来。比如日前计划里13:00燃气轮机出力是200kW,到了当天上午实测光伏比预测低,日内层可能把13:00的燃气轮机出力修正到220kW,但不会把它改成“停机”。这种“小步修正”的策略保证了运行的连续性和稳定性。
日前调度模型包含了前面提到的所有约束,求解后得到的计划会作为“参考轨迹”传给下一层。在代码实现上,这一层通常用混合整数规划求解,因为机组启停是0-1变量。
3.2 日内层:滚动时域修正
日内滚动调度是整个多时间尺度架构的“灵魂”,也是最体现工程实用性的部分。它的核心机制是滚动时域控制(Receding Horizon Control, RHC)——每15分钟触发一次优化,每次优化用最新的预测数据更新未来4小时(16个时段)的调度计划,但只执行第一个时段的结果,等下一个15分钟到来再重新优化。
这里的关键步骤是:每次滚动优化时,要考虑“当前系统状态”(比如储能的实时SOC、当前各设备的出力),并把“从优化开始到目标时段的实际已发生值”作为初值写入约束。这样每次求解出的计划都是基于最新信息的,预测误差的影响被不断“滚”出去。
举一个实际场景:上午10:00,日内优化启动,此时最新的天气预报显示下午14:00光伏出力会比日前预测低15%。优化器会在10:00~14:00这个窗口内重新分配各设备出力,比如在10:00~11:00电价低谷期让蓄电池多充电,下午14:00光伏不足时蓄电池放出电能补缺口;同时适当调高燃气轮机出力作为备用。这套修正动作在日前计划里是没有的,因为日前预测无法预见到这个变化。
3.3 实时层:分钟级功率平衡兜底
实时层的作用是在更短的时间尺度上(我通常取5~15分钟)处理预测误差和负荷波动的剩余部分。到了这个层级,机组出力的调节空间已经很小,主要靠储能系统快速响应来实现功率平衡。如果储能也到极限了,最后的手段是调整电制冷机出力或者从电网紧急购电。
实时层的控制策略比较简单,通常是一个“基于日前基线+日内计划的跟随控制器”,可以是一个简单的MPC,也可以是一个带死区的反馈调节器。需要注意的是,这一层不建议做太复杂的优化——实时性要求高,优化求解耗时超过1分钟就失去意义了。我实测过,用线性规划(LP)而不是混合整数规划(MIP)求解实时层,计算时间可以从几十秒压缩到1秒以内,结果差别并不大,因为实时层不需要决策机组启停。
3.4 求解方法与代码实现框架
求解工具方面,我在MATLAB环境下用的是YALMIP工具箱作为建模语言,调用CPLEX或Gurobi求解器来解MILP/MIQP问题。YALMIP的优势在于语法简洁、易于修改约束和参数,非常适合研究阶段的快速原型验证。如果偏好Python生态,也可以用Pyomo或PuLP,思路完全一致。
这里给出一个简化版的日前调度求解框架,演示核心代码结构:
matlab复制% 基础数据定义
% P_load, H_load, Q_load: 电、热、冷负荷预测数据
% P_pv, P_wt: 光伏、风电预测出力
% c_gas, c_buy, c_sell: 天然气价格、购电价、售电价
%% 1. 定义决策变量
P_mt = sdpvar(1, T, 'full'); % 燃气轮机出力
u_mt = binvar(1, T); % 燃气轮机启停状态
P_buy = sdpvar(1, T, 'full'); % 购电功率
P_sell = sdpvar(1, T, 'full'); % 售电功率
SOC_b = sdpvar(1, T+1, 'full'); % 蓄电池SOC(增加一个时段用于初值)
P_ch = sdpvar(1, T, 'full'); % 蓄电池充电功率
P_dis = sdpvar(1, T, 'full'); % 蓄电池放电功率
%% 2. 目标函数构建
objective = sum(c_gas * (a*P_mt.^2 + b*P_mt + c)) ... % 燃料成本
+ sum(c_buy .* P_buy) - sum(c_sell .* P_sell) ... % 购售电成本
+ sum(c_om_mt * P_mt) + sum(c_om_ec * P_ec); % 维护成本
%% 3. 约束条件
constraints = [];
% 电功率平衡
constraints = [constraints, P_mt + P_pv + P_wt + P_dis - P_ch + P_buy - P_sell == P_load + P_ec];
% 热功率平衡
constraints = [constraints, H_whb + H_dis - H_ch == H_load + H_ac];
% 冷功率平衡
constraints = [constraints, Q_ac + Q_ec + Q_dis - Q_ch == Q_load];
% 燃气轮机出力和启停约束
constraints = [constraints, P_mt <= P_mt_max * u_mt];
constraints = [constraints, P_mt >= P_mt_min * u_mt];
% 爬坡约束
constraints = [constraints, abs(P_mt(2:end) - P_mt(1:end-1)) <= ramp_rate * 1];
% 储能SOC递推约束
constraints = [constraints, SOC_b(2:end) == SOC_b(1:end-1) + eta_ch*P_ch*delta_t - P_dis*delta_t/eta_dis];
constraints = [constraints, SOC_b(1) == SOC_init, SOC_b(end) == SOC_final];
constraints = [constraints, SOC_min <= SOC_b <= SOC_max];
% 购售电互斥约束
constraints = [constraints, P_buy + P_sell <= P_grid_max];
constraints = [constraints, P_buy >= 0, P_sell >= 0];
%% 4. 求解
ops = sdpsettings('solver', 'gurobi', 'verbose', 2);
optimize(constraints, objective, ops);
几个代码层面的要点需要特别提醒:
第一,SOC变量的维度处理。我在代码里把SOC定义成T+1长度,这样SOC(t+1)和SOC(t)的递推关系可以直接用向量切片表达,避免循环,求解速度会快很多。初学者经常在这里踩坑,SOC维度不对会导致索引越界或约束缺失。
第二,二次函数线性化。目标函数中的燃料成本是二次函数,如果直接用sdpvar的二次型,求解器会调用MIQP求解器,速度比MILP慢很多。我的做法是对二次项做分段线性近似,把原来的二次成本函数用几条直线拟合,这样整体就变成了纯MILP问题,求解速度快一到两个量级。
第三,爬坡约束里的delta_t。我上面写的代码里爬坡约束是 ramp_rate * 1,其中的“1”是时段长度(小时)。如果日内调度的时间分辨率是15分钟,这个值就要改成0.25,否则爬坡限制会放宽4倍,优化结果不可执行。
3.5 三层模型如何衔接:状态传递与惩罚一致性
三层模型不是三个独立的优化,它们之间靠“状态传递”和“代价一致性”串联起来。状态传递包括:日前层算出的储能SOC日末值作为日内滚动优化的初值;日内层每次滚动求解后,把当前实际状态(比如设备当前出力、储能当前SOC)作为下次优化的初始条件。代价一致性则是指,三层模型的目标函数应该保持相同的结构,只是时间窗和分辨率不同,否则日内层优化的结果会与日前层的全局最优方向产生系统性偏差。
我测试过一种常见错误做法:日前层目标函数包含弃风弃光惩罚,日内层却把弃风弃光处理成了硬约束(必须全额消纳)。结果就是日前层给出的基线在日内层根本无法实现,日内层被迫大幅度偏离基线,整个调度方案失去指导意义。三层模型的目标函数要保持结构一致,只是参数(预测值)在变,这是多时间尺度调度能真正落地的前提。
4. 算例设计与结果对比分析
4.1 典型日场景构建
为了验证多时间尺度策略的有效性,我选取了夏季典型日作为测试场景。夏季场景的特点是:光伏出力高、冷负荷占比大、热负荷很小。这个场景能很好地检验“冷热电联供”的价值——燃气轮机发电的余热可以用来驱动吸收式制冷机供冷,从而实现能源的梯级利用。
数据方面,我使用的是某园区微网的实际负荷数据做基准,叠加适当的噪声模拟预测偏差:
| 参数 | 数值 |
|---|---|
| 燃气轮机额定功率 | 500 kW |
| 光伏额定功率 | 300 kW |
| 风电额定功率 | 200 kW |
| 电制冷机额定功率 | 400 kW |
| 吸收式制冷机额定功率 | 300 kW |
| 蓄电池容量 | 600 kWh |
| 购电功率上限 | 800 kW |
| 售电功率上限 | 400 kW |
分时电价采用典型的峰谷结构:峰时(8:00-11:00、18:00-21:00)1.2元/kWh,平时(11:00-18:00)0.8元/kWh,谷时(22:00-次日7:00)0.4元/kWh。天然气价格取2.5元/m³。
4.2 对比方案设计与结果
为了检验多时间尺度调度的效果,我设置了三种对比方案:
- 方案A(单层日前调度):只做日前优化,全天计划不滚动修正,直接用预测数据一次解算。
- 方案B(日前+日内两层):日前定基线,日内每15分钟滚动修正一次。
- 方案C(完整三层):日前+日内+实时反馈完整闭环。
结果整理如下:
| 方案 | 日运行成本(元) | 弃光率(%) | 购电成本(元) | 计划偏差率(%) |
|---|---|---|---|---|
| A 单层 | 5236 | 8.7 | 1854 | 15.2 |
| B 两层 | 4957 | 3.2 | 1692 | 4.8 |
| C 三层 | 4898 | 1.9 | 1655 | 2.1 |
5. 常见问题与排查技巧实录(3000字)
5.2 求解器选择与数值稳定性问题
Gurobi CPLEX 是最常用的两种求解器,它们在MILP问题上的求解速度差别不大,但有一个有趣的区别:Gurobi 在离散变量很多时表现更稳定,而 CPLEX 在约束条件非常密集时更有优势。我自己倾向于Gurobi,因为它的参数设置更友好,默认的MIP gap就能在合理时间内收敛。
5.3 单位一致性检查
单位问题是最隐蔽的坑。比如热负荷单位用的是kW,但天然气热值单位是kWh/m³,如果不统一换算,目标函数的燃料成本就会差一个数量级。我吃过一次亏:所有物理量都用kW表示,唯独储能容量用了kWh,导致SOC的计算在量纲上完全错位,结果调度计划呈现出“储能在每小时末自动满电”的奇异现象——看起来结果正常,实际上是因为容量单位不一致,SOC递推公式的系数被放大了几十倍。后来我专门写了一个单位检查脚本,把所有变量的单位列成一张表,互相校验一遍再跑优化。
另外问一下,那在实时反馈层,如果出现储能也无法平衡的功率差额,比如极端天气下光伏骤降,你们一般怎么处理? 我在代码里加了一个“紧急切负荷”的惩罚项,但实际工程中切片逻辑和优先级排序还有不少讲究,想听听你的做法。 另外,你提到的“计划偏差率”这个指标,具体是拿什么和什么做对比的?是用实时层最终的调度结果对比日前基线吗?这个统计口径我还没完全get到。 还有,蓄热/蓄冷罐在算例里好像没有单独建模,是和蓄电池合并成一个储能模块还是忽略了?这个选择对夏季场景的结果影响大吗?
