做能源系统优化的朋友应该都有体会,“冷热电联供型微网”这个概念听起来很清晰——燃气轮机发电、余热回收制热制冷、多能互补——但真正要把一套含冰蓄冷空调的调度策略跑起来,坑是一个接一个。这个项目的核心是两件事:让电、热、冷三种能量在微网内部高效协同,同时引入冰蓄冷空调作为既有冷负荷又带储能属性的柔性资源,再用多时间尺度优化调度去对冲光伏出力和负荷预测的不确定性,最后用Matlab代码把整个模型和求解过程落地。这篇文章我会把建模思路、多时间尺度的衔接逻辑、Yalmip求解的关键代码、以及实际调试中踩过的那些坑一次性拆开说清楚。
这个项目适合这几类人看:做微网和综合能源系统优化调度研究的学生和工程师、刚入手Yalmip工具箱想找完整练手案例的Matlab用户、以及正在做空调负荷管理或需求侧响应的从业者。看完你至少能明白一套完整的含冰蓄冷CCHP微网调度代码是怎么从零搭起来的,哪些地方容易出错,以及怎么用最少的时间把结果跑出来。
1. 项目整体设计与思路拆解
1.1 冷热电联供微网的能量流本质
冷热电联供型微网本质上是一套小型的分布式能源系统。燃气轮机燃烧天然气发电,产生的高温烟气经过余热回收装置,一部分供给吸收式制冷机产生冷量,一部分通过换热器供给热负荷。跟传统“电网买电+电空调制冷+燃气锅炉供热”的分供模式相比,联供最大的优势是能源梯级利用:高品位的热能先发电,中低品位的余热再用来制冷和制热,综合能源利用率能到80%以上。
但联供微网也带来了一个天然的麻烦:电、热、冷三条能量流是强耦合的。燃气轮机一旦发电,烟气余热就跟着产生,冷和热没法像电那样按需独立启停。尤其是冷负荷波动大的夏季,如果只盯着电力平衡做调度,很容易出现“电够了但冷不够”或者“为了供冷白白浪费了余热”的尴尬局面。这就是为什么冷热电联供微网的调度不能简单套用普通微网的模型。
从数学角度看,这个系统是一个多输入多输出的能量转换网络。输入侧是天然气、电网购电、光伏出力和风速,输出侧是电负荷、热负荷和冷负荷,中间经过燃气轮机、余热锅炉、吸收式制冷机、电制冷机、蓄冰槽、蓄电池等设备完成能量的时空转移。优化调度的任务,就是在这套能量网络中找出一组设备出力组合,让总运行成本最低,同时满足所有供需平衡和设备物理约束。
1.2 冰蓄冷空调给调度带来了什么
冰蓄冷空调放在这个系统里,本质上是给冷负荷装了一块“电池”。
常规电制冷空调是即发即用:制冷机耗电产生冷量,冷负荷消耗冷量,二者实时平衡。但冰蓄冷系统多了一个蓄冰槽,夜间电价低谷时电制冷机满负荷制冰,把冷量以冰的形式储存起来;白天电价高峰时制冷机可以减少甚至停止运行,靠融冰释放冷量来满足部分冷负荷。
从电网角度看,这是经典的削峰填谷;从微网调度角度看,这等于把刚性冷负荷变成了柔性可调资源。调度员可以在电价低谷、光伏大发、余热充足的时候多制冰存起来,在电价高峰、冷负荷尖峰的时候多融冰释放,灵活度一下提升了一个档次。
不过,这个“电池”跟蓄电池有一个本质区别:蓄电池的充电功率和放电功率可以独立控制,而蓄冰槽的传热特性、冰水温度、融冰速率都受物理条件限制,建模的时候必须处理好制冰工况和融冰工况的切换。这也是这个项目相比普通CCHP调度最特殊的地方——冷侧多了一个带时间耦合的状态量,蓄冰槽什么时候制冰、什么时候融冰、融多少,都要跟电侧和热侧的调度一起协同优化。
1.3 多时间尺度调度到底在解决什么问题
只做一套日前24小时计划够不够?不够。
原因很现实:光伏出力预测、冷热电负荷预测都不可能做到完全准确。尤其是中午光伏大发时段,如果日前计划是固定不变的,实际运行大概率会偏离最优状态,甚至出现供需失衡。多时间尺度调度的核心思路,就是把调度决策拆成两层:
- 日前调度:提前24小时制定,时间尺度为1小时,确定燃气轮机启停、冰蓄冷蓄放冰策略等需要提前安排的大决策。这类决策调整代价高,比如燃气轮机启停不能频繁变化,蓄冰槽的冰也不能白天临时决定“今天不蓄了”。
- 日内调度:在运行当天滚动执行,时间尺度缩短到15分钟或1小时,基于最新的光伏和负荷预测信息,修正各可控设备的出力计划。当预测跟实际偏差较大时,只需要在日前计划基础上做局部调整,不需要推倒重来。
这种“全局粗调+局部精调”的框架,是应对不确定性的主流方案。这个项目把日前和日内两级调度同时建出来,用同一套Matlab模型跑通,这也是我觉得最值得复现的地方。
2. 系统建模:设备模型与约束拆解
2.1 供能设备模型
建模的第一步,是把每一个设备的输入输出关系转化成数学约束。这里以最常见的CCHP系统配置为例,包含燃气轮机、余热回收装置、吸收式制冷机、电制冷机、蓄冰槽和蓄电池。
燃气轮机模型。核心变量是发电功率 (P_{GT}(t)) 和消耗的天然气燃料成本。燃气轮机的发电效率随负载率变化,精确建模用非线性曲线,但调度模型为了可解性,通常采用分段线性化。简化模型可以写成:
[ F_{GT}(t) = \frac{P_{GT}(t)}{\eta_{GT}} \cdot \frac{1}{LHV_{gas}} ]
其中 (F_{GT}(t)) 是天然气消耗量,(\eta_{GT}) 是发电效率,(LHV_{gas}) 是天然气低位热值。烟气余热回收量 (Q_{rec}(t)) 近似为:
[ Q_{rec}(t) = P_{GT}(t) \cdot \frac{1-\eta_{GT}}{\eta_{GT}} \cdot \eta_{rec} ]
这里注意,余热回收量不是独立的决策变量,它跟发电功率一一绑定。这意味着燃气轮机一旦多发电,余热就多,制冷制热侧也跟着变。建模时这个耦合关系必须写进约束,不能把电功率和热功率当独立变量分别优化。
吸收式制冷机。利用余热产生冷量,模型最简单,输入余热、输出冷量,中间乘一个制冷系数 (COP_{ac}):
[ Q_{ac,out}(t) = Q_{ac,in}(t) \cdot COP_{ac} ]
但要注意吸收式制冷机的出力有上下限,而且如果余热回收量不够,它没法凭空制冷。
电制冷机。耗电产冷,同样有COP:
[ Q_{ec,out}(t) = P_{ec}(t) \cdot COP_{ec} ]
在含冰蓄冷的系统里,电制冷机通常有双工况:常规制冷工况和制冰工况。两种工况的COP不一样,建模时要分开处理。制冰工况下电制冷机的输出不再直接进入冷负荷,而是转化为蓄冰槽的蓄冰量。
2.2 冰蓄冷空调:最容易写错的地方
冰蓄冷系统建模的难点在于蓄冰槽的状态约束。蓄冰槽引入了一个状态变量 (SOC(t)),表示t时刻蓄冰槽的蓄冰量占比,取值范围[0,1]。状态转移方程是:
[ SOC(t+1) = SOC(t) - \frac{Q_{ice,out}(t)}{CAP_{ice}} + \frac{P_{ice,in}(t) \cdot COP_{ice}}{CAP_{ice}} ]
这里面有几个容易出错的细节:
制冰和融冰不能同时进行。这在数学上必须用0-1整数变量控制,写成: [ u_{ice}(t) + v_{ice}(t) \leq 1 ] 其中 (u_{ice}(t)=1) 表示制冰,(v_{ice}(t)=1) 表示融冰。如果不加这个约束,优化器极有可能在同一个时刻既制冰又融冰——净效果相同但白白耗电,这种错误很隐蔽,不仔细看结果根本发现不了。
制冷机在制冰工况下工作,冷量不直接供给冷负荷。所以冷功率平衡方程里,电制冷机的制冷出力要区分“供冷模式”和“制冰模式”。
融冰放冷速率有上限,这个上限通常正比于当前蓄冰量: [ Q_{ice,out}(t) \leq k_{melt} \cdot SOC(t) \cdot CAP_{ice} ] 意思是冰越少,能融出来的速率越低,物理上很好理解。如果不加这个约束,优化器会在需要冷量时一次性把所有冰全放出来,这在工程上做不到。
蓄冰槽的跨日清零约束。调度周期结束时,蓄冰槽SOC必须回到初始值(通常是0或某个设定值),否则第二天的计划就失真了。
2.3 目标函数与三条能量平衡约束
目标函数设定为系统总运行成本最小:
[ \min \sum_{t=1}^{T} \left[ C_{gas}(t) + C_{buy}(t) \cdot P_{grid}(t) + C_{om}(t) + C_{curtail}(t) \right] ]
其中 (C_{gas}) 是燃气轮机燃料成本,(C_{buy}) 是购电价,(C_{om}) 是设备运行维护成本,(C_{curtail}) 是弃风弃光惩罚项。光伏和风电的边际成本为零,所以如果约束里没有惩罚项,优化器不会主动弃掉它们,但实际中受线路容量和功率平衡限制,弃风弃光还是会发生,惩罚项能让模型在“必要的弃”和“不必要的弃”之间做出合理取舍。
三条能量平衡方程是约束的核心:
电功率平衡: [ P_{GT}(t) + P_{grid}(t) + P_{pv}(t) + P_{wt}(t) = P_{load}(t) + P_{ec}(t) + P_{ice,in}(t) + P_{bat,ch}(t) ] 注意左边是四个电源,右边除了电负荷,还要加上电制冷机耗电、制冰耗电和蓄电池充电。
热功率平衡: [ Q_{rec,heat}(t) + Q_{boiler}(t) = Q_{heat,load}(t) ] 如果余热回收的热量不够,燃气锅炉补充;如果有多余,也可以在模型里加入热储能消纳。
冷功率平衡: [ Q_{ac,out}(t) + Q_{ec,cool}(t) + Q_{ice,out}(t) = Q_{cool,load}(t) ] 这是含冰蓄冷系统的核心平衡式,冷负荷由吸收式制冷、电制冷机供冷和融冰供冷三者共同承担。
这三条平衡方程看起来简单,但每条都牵扯到多个设备的耦合变量。实际建模时,我最常犯的错误就是写电平衡时忘记把制冰耗电加上,结果系统在夜间显示“功率平衡”,实际上电量对不上。
3. 多时间尺度调度机制与Matlab实现
3.1 日前与日内的衔接逻辑
这个项目用的多时间尺度框架,具体衔接方式如下:
日前调度:以1小时为间隔,共24个时段。已知量是光伏、风电和冷热电负荷的日前预测曲线,决策量是燃气轮机各时段出力、电网购电计划、电制冷机和吸收式制冷机的出力分配、蓄冰槽各时段的蓄放冰计划。求解完成后,得到一组日前计划,其中最关键的是燃气轮机的出力基线和蓄冰槽的SOC变化轨迹。
日内调度:以15分钟为间隔,采用滚动优化窗口。假设预测时域为4小时(即16个15分钟时段),每15分钟滚动一次。日内调度的已知量是更新后的超短期预测,决策量是各可控设备的出力修正量。重点来了——日内调度不会推翻日前决策,而是在日前计划基础上做局部修正。比如日前计划里燃气轮机10点出力500kW,日内调度发现光伏实际出力比预期多了100kW,那么就在约束中加入:
[ P_{GT}(t) = P_{GT}^{DA}(t) + \Delta P_{GT}(t) ]
其中 (\Delta P_{GT}(t)) 是日内修正量,范围比日前出力范围小得多。这个修正量通常还要加一个调整速率限制,防止燃气轮机出力在短时间内剧烈波动。
在Matlab里实现两级调度,最简单的方式是把求解函数封装成一个带参数的函数:
function result = solve_schedule(pred_data, type, prev_plan) % type = 'DA' 日前调度 % type = 'ID' 日内调度,prev_plan 是日前计划基线 ... end日前调度调用一次,拿到全天的计划;日内调度在一个循环里反复调用,每次输入最新的预测数据和上次的计划,然后把第一个时段的决策结果作为实际执行值,滚动向前。
3.2 Yalmip建模的核心代码段
Matlab里的优化建模我推荐使用Yalmip工具箱,它最大的好处是语法接近数学表达式,模型改起来非常直观。配合CPLEX或Gurobi求解混合整数线性规划(MILP),速度和稳定性都有保障。第一次跑这个项目的人,我建议先装Yalmip,再装一个求解器,这两个装好,剩下就是写代码的事。
变量定义部分的代码大致长这样:
% 决策变量 P_GT = sdpvar(1, 24); % 燃气轮机发电功率 P_grid = sdpvar(1, 24); % 电网购电功率 P_ec = sdpvar(1, 24); % 电制冷机功率 Q_ac = sdpvar(1, 24); % 吸收式制冷输出 Q_ice_out = sdpvar(1, 24); % 融冰供冷功率 SOC = sdpvar(1, 25); % 蓄冰槽蓄冰状态,多一个末端时段 u_ice = binvar(1, 24); % 制冰状态标志 v_ice = binvar(1, 24); % 融冰状态标志约束构建的核心代码段:
Constraints = []; % 电功率平衡 Constraints = [Constraints, P_GT + P_grid + P_pv_pred + P_wt_pred ... == P_load + P_ec + P_ice_rate * u_ice]; % 冷功率平衡 Constraints = [Constraints, Q_ac + P_ec * COP_ec + Q_ice_out ... == Q_cool_load]; % 冰蓄冷SOC状态转移 Constraints = [Constraints, SOC(t+1) == SOC(t) ... - Q_ice_out(t) / CAP_ice ... + P_ice_rate * COP_ice * u_ice(t) / CAP_ice]; % 制冰与融冰互斥 Constraints = [Constraints, u_ice(t) + v_ice(t) <= 1]; % 融冰速率受当前蓄冰量限制 Constraints = [Constraints, Q_ice_out(t) <= k_melt * SOC(t) * CAP_ice]; % 周期末SOC回到初始值 Constraints = [Constraints, SOC(25) == SOC(1)];这里有一个我自己踩过的坑:SOC变量的维度是1×25,但蓄放冰约束是按t=1到24写的,如果索引不一致,Matlab会报维度错误。解决办法是在循环里统一用t做索引,并且把SOC(t+1)关联到SOC(t),这样逻辑最清晰。
目标函数代码:
Objective = sum(C_gas * F_GT) + sum(C_buy * P_grid) ... + sum(C_om_ec * P_ec) + sum(C_curtail * P_curtail); Ops = sdpsettings('solver', 'cplex', 'verbose', 1); optimize(Constraints, Objective, Ops);3.3 结果分析与可视化看什么
模型跑完之后,最容易忽略的是结果分析这一环。代码能跑通不等于模型正确,我拿到结果的第一反应永远是先画出四张图:
- 电功率平衡图:看四条电源曲线和负荷曲线的加和是否吻合,重点检查制冰耗电有没有占掉一块空白。
- 热功率平衡图:看余热回收和锅炉供热的分配比例。
- 冷功率平衡图:看电制冷、吸收式制冷和融冰供冷三条曲线如何叠加覆盖冷负荷。
- 蓄冰槽SOC曲线:看蓄冰时段是不是落在电价谷段,融冰时段是不是集中在电价峰段。如果SOC曲线在峰段反而上升、谷段反而下降,那说明目标函数或约束有方向性错误。
Matlab里画图的代码很简单:
figure; stairs(1:24, P_GT, 'LineWidth', 1.5); hold on; stairs(1:24, P_grid, 'LineWidth', 1.5); stairs(1:24, P_pv_pred + P_wt_pred, 'LineWidth', 1.5); stairs(1:24, P_load, '--k', 'LineWidth', 1.2); legend('燃气轮机', '电网购电', '可再生电源', '电负荷'); xlabel('时间/h'); ylabel('功率/kW');跑通第一个版本后,建议做一个“对照实验”:把冰蓄冷空调的蓄放冰约束去掉(即电制冷机只能实时供冷),重新求解,对比两组结果的经济性和冷负荷供给方式。这个对照实验能非常直观地说明冰蓄冷的价值——你会看到蓄冰工况下总成本下降、峰段电制冷出力明显降低。这也是论文里最常用的灵敏度分析思路。
4. 调试经验盘点:常见问题与解决实录
4.1 求解器报无解,第一步排查哪里
模型写好后第一次跑,最容易碰到的问题就是infeasible。这时候不要慌,按顺序排查:
先查功率平衡约束。我碰到过最多次的情况是电平衡里漏加了制冰耗电项,导致夜间时段系统“凭空”多了一部分电量。排查方法很简单,把约束逐个注释掉,看到哪条约束被注释后模型变可行,问题就出在那条约束上。
再查蓄冰槽边界。SOC初始值设为0.5(半槽冰),但结束约束又要求SOC回到0.5,这没问题;但如果初始值设置为0,某天冷负荷特别大导致夜间根本蓄不满冰,SOC末值达不到0,就会无解。解决办法是把跨日SOC约束改成不等式,允许少量偏差并加上惩罚,或者给蓄冰槽容量留出足够裕量。
最后查负荷数据的单位。这个问题非常基础但经常发生——热负荷习惯用kW,冷负荷习惯用kW或RT(冷吨),如果不统一转换系数,功率平衡完全错位。1冷吨约等于3.517kW,这个换算在代码里一定要统一。
4.2 SOC曲线不合理的背后原因
有时候模型能求解,但结果里的SOC曲线不对劲——比如白天电价高峰期SOC还在上升。
如果白天电价高峰期SOC还在上升,第一反应是检查电价序列,看是不是数据录入时把谷时段和峰时段标反了。排除了电价问题,就要查蓄冰槽的功率上限约束,看看是不是给得太宽,导致即使电价不是最低的时候,制冷机制冰也依然经济。还有一种情况是热侧和冷侧的耦合出了问题:吸收式制冷机出力可能受余热约束不足,电制冷机被迫在白天出力,如果此时电价已经很高,优化器宁愿用更便宜的谷电制冰,结果SOC在白天继续上升,这说明冷负荷平衡其实已经失衡。这时候要回头检查冷负荷的数据曲线是否合理。
4.3 求解耗时过长与线性化技巧
MILP问题一旦时段数变多、整数变量变多,求解耗时就会指数增长。这个项目里,整数变量主要来自制冰融冰状态0-1变量和蓄电池充放电状态变量。解决思路有三个:
第一,减少整数变量。如果工程上允许,蓄冰槽的制冰功率可以按固定速率处理,用一个功率档位描述,这样整数变量只有“制冰/不制冰”两个状态,而不是连续的多个功率档位。
第二,如果只需要粗略估算,可以先把SOC的连续模型跑一遍,用最优解作为整数规划的初始可行解,再传给MILP求解器。Gurobi和CPLEX都支持通过MIP start提供初始解,能显著加速求解。
第三,把求解时间限制放开的同时,设置一个合理的MIP gap。工程上MIP gap控制在1%以内就已经很可靠了,不必追求到0.01%。设太大结果不可信,设太小耗时指数上升,1%是经验和学术之间比较平衡的点。
4.4 常见问题速查表
| 现象 | 最可能的原因 | 排查与处理办法 |
|---|---|---|
| 求解器报infeasible | 功率平衡漏项、SOC边界设定不合理 | 逐条注释约束定位,检查跨日SOC约束 |
| SOC曲线白天还在上升 | 电价序列错位、冷负荷数据异常 | 核对峰谷电价时段,检查吸收式制冷余热输入是否受限 |
| 求解耗时超过10分钟 | 整数变量过多、约束过于密集 | 合并制冰功率档位,设置MIP gap,提供初始解 |
| 结果出现制冰融冰同时进行 | 缺少互斥约束 | 增加 u+v<=1 约束 |
| 冰蓄冷系统不产生任何经济效益 | 峰谷电价差太小、蓄冰槽容量设置过大 | 检查电价差,调整蓄冰槽容量或冷负荷峰值 |
| 日内调度频繁触发启停 | 日前计划约束太紧,日内修正范围太小 | 放宽日内修正量的上下限,增加修正速率限制 |
5. 项目可以怎么进一步扩展
这个模型跑通之后,往上扩展的方向其实很多。第一个直接的想法是加入蓄电池,跟冰蓄冷形成“电储能+冷储能”的混合储能系统,此时电平衡方程里多一项充放电功率,蓄电池的SOC约束跟蓄冰槽SOC约束结构几乎一模一样,代码复用的成本很低。
第二个方向是把碳交易机制考虑进去。碳排放配额、碳价、燃气轮机和电网购电的碳排放系数相乘,目标函数里加一项碳成本项,这样调度结果会明显偏向低碳运行,也更有政策研究价值。
第三个方向是引入需求响应。跟冰蓄冷同源的思路,冷负荷中有一部分是柔性负荷(比如空调温度设定可以上下浮动),通过价格激励让用户调整负荷曲线,本质上跟冰蓄冷是互补的——冰蓄冷是供给侧平移冷量,需求响应是主动调整冷负荷。把这两个放一起,冷侧的灵活性就更完整了。
另外,如果要追求更贴近实际,可以考虑把日前调度里的燃气轮机启停状态也建模成整数变量,这样日前决策里多了一组更贴合工程实际的0-1变量。代价是模型规模和求解时间都会上一个台阶,但这也正是多时间尺度调度研究里最常遇到的取舍问题。
6. 最后再说几句代码之外的体会
我在实际跑这个项目的过程中,最深的一个感受是:模型的技术细节固然重要,但真正让项目走通的关键环节其实是“数据的可信度”和“结果的解释性”。
虚拟数据建模时,一切都很完美,光伏曲线光滑得像学校教科书,冷负荷峰值整整齐齐。但只要换成实际抄表的负荷数据,各种问题就暴露出来了:数据的采样间隔不一致、个别时段有缺失值或噪声尖峰、单位在不同来源的表格里不统一。这些数据问题如果不在预处理阶段解决,后面调再多的约束也没有意义。所以如果你要用这个项目,第一周先别急着写代码,把数据集对齐、清洗、插值好,这一步做扎实了,后面所有工作都会顺很多。
另一方面,结果算出来后不要只看总成本数字。我习惯了每次跑完先追问几个问题:蓄冰槽在什么时候蓄的冰,融冰供冷占了冷负荷多大比例,燃气轮机的出力曲线跟电价的波峰波谷对应关系是否合理。如果这些问题的答案都能用一句话解释清楚,那说明你的模型是“有灵魂”的;如果解释不通,哪怕最优解数值没问题,也一定有哪些约束或参数需要重新审视。
这套代码跑通之后,回过头来再看“冷热电联供型微网”这个标题,你会发现它其实是一个容器,里面装的不只是蓄冰槽的方程和优化器的求解记录,更重要的是你从物理理解到数学建模再到代码落地的完整链路。把这个链路走完,遇到其他类型微网项目时,你大概率也能很快上手。