这个题目,我在实验室里泡了大半个月才真正理顺。含冰蓄冷空调的冷热电联供型微网多时间尺度优化调度,初看是一个标准的综合能源优化问题,实际做起来涉及的坑非常多——冷热电三种能量流的耦合关系、蓄冰槽跨时段的状态衔接、日前日内实时三个尺度之间的变量传递,任何一个环节断了,结果都会出现明显的物理不守恒。
这篇博文不打算从综述开始讲,直接聊我在用Matlab复现这个调度框架过程中的设计思路、关键模型、代码组织方式和踩过的坑。内容面向正在做微网优化调度方向的研究生、需要搭综合能源算例的工程师,以及所有被YALMIP加商业求解器这个组合折磨过又离不开它的朋友。看完之后,你至少能把一条"能跑通、结果合理"的复现路径带回去,而不是在茫茫多的公式和报错之间继续摸索。
1. 项目整体设计与思路拆解
1.1 微网系统结构:冷热电三条能量流怎么串起来
先把这个系统的"家底"理清楚。冷热电联供型微网,英文缩写是CCHP(Combined Cooling, Heating and Power),核心思想是让一套能源系统同时供应电、热、冷三种负荷。典型结构包含这几块:
- 供电侧:燃气轮机(或燃气内燃机)、光伏、上级电网购电、蓄电池。
- 供热侧:余热锅炉或热交换器(回收燃气轮机余热)、燃气锅炉(补燃调峰)。
- 供冷侧:溴化锂吸收式制冷机(用热能制冷)、电制冷机(用电制冷)、冰蓄冷空调(蓄冰加融冰)。
- 负荷侧:电负荷、热负荷、冷负荷。
能量流的关系是:燃气轮机烧天然气,同时发电和产出高温烟气;烟气余热经过余热锅炉回收后,一部分直接变成热功率供应热负荷,另一部分进入吸收式制冷机变成冷功率。电制冷机和冰蓄冷空调则用电来制冷。这样一来,原本"电是电、热是热、冷是冷"的割裂系统,就变成了"燃气进、三能出"的联供系统。综合能源利用率比单纯的分布式光伏加电空调高不少,这也是这个方向一直有人研究的原因。
从调度的视角看,三条能量流的平衡必须同时满足:任意时刻,电负荷等于各电源出力之和减去用电设备的耗电;热负荷等于余热供热加锅炉补燃;冷负荷等于直供冷量加融冰冷量加吸收式制冷量。三条平衡通过燃气轮机的"热电比"耦联在一起——你多发电,余热就多,热和冷就多;反过来,如果冷负荷很大,热却用不完,就需要想办法把多余的热散掉或加大制冷。这种耦合关系是单做电力的优化调度完全不会遇到的。
1.2 为什么偏偏要加冰蓄冷空调
做这个课题之前,我对冰蓄冷空调的认知停留在"商场夜里制冰、白天化冰省钱"的粗浅印象。真正建模之后才发现,它在微网里的价值远比"省钱"两个字大。
先说痛点。夏季典型的工业园区或商业综合体,制冷负荷峰值和电负荷峰值高度重叠——中午最热的时候,空调拼命制冷,电负荷也冲到最高。这时候燃气轮机可能已经满发,光伏出力再高也顶不住,只能靠高价购电。如果单纯扩大设备容量,投资成本和运行成本都会水涨船高,显然不经济。
冰蓄冷空调的思路就是"移峰填谷":夜间电价低谷时段,电价便宜,系统用电驱动制冷主机把水制成冰存起来;白天电价高峰,系统停掉部分制冷主机,靠融冰释放冷量来满足冷负荷。这样就把白天的制冷耗电"平移"到了夜间,缓解了电负荷峰值,也减少了高价购电。
在建模层面,冰蓄冷空调相当于一个"冷量储能装置",但比蓄电池的模型简单得多。蓄电池要考虑充放电效率、SOC-功率耦合曲线、循环寿命衰减这些复杂约束,而蓄冰槽只需遵守能量守恒和容量限制。它跟冷热电联供结合之后,等于给整个微网增加了一个"时间搬移"的自由度:白天冷负荷高但电价贵,就可以多用融冰供冷;夜间电价便宜,就多制冰把冷量存起来。这个自由度对优化结果的影响非常明显,我后面会详细展开。
1.3 多时间尺度优化到底解决了什么问题
为什么不能只做一次"全天优化"?因为实际运行中,光伏出力预测误差、负荷预测误差、电价波动,都会让"算好的计划"在执行时失效。单层优化模型的假设是"未来24小时参数全部已知",现实显然不是这样。
多时间尺度优化的思路,有点像天气预报的体系:长期预报看趋势,短期预报盯细节,实况监测做校验。我的复现框架分三层:
- 日前调度(小时级,一天24个时段或96个时段):基于短期预测的负荷、光伏、电价数据,决定燃气轮机启停、蓄冰槽蓄冰计划、电网购电计划这些"粗决策"。
- 日内滚动(15分钟级,滚动时域优化):基于超短期预测,每15分钟滚动一次,只优化未来几小时的出力细节,修正日前计划与实际的偏差。
- 实时调整(分钟级):处理秒级到分钟级的波动,通过蓄电池和蓄冰槽的快响应特性,保证功率实时平衡。
三层结构各有各的职责:日前层管"大体格局",日内层管"偏差修正",实时层管"兜底平衡"。每一层只修正上一层留下的偏差,而不是把整个问题从头算一遍。这种分层处理的好处是计算速度快、鲁棒性强,代价是层与层之间需要一套合理的变量传递规则,这正是复现中最容易出问题的地方。
1.4 方案选型:为什么用Matlab加YALMIP
复现这个课题,我一开始也考虑过用Python的Pyomo或者直接用商业求解器接口。最后选了Matlab加YALMIP组合,原因很实际:
YALMIP的建模语法非常接近数学公式。论文里的一大堆求和式、不等式写进YALMIP,基本是一一对应,不容易在"公式翻译成代码"这一步出错。相比之下,用Pyomo虽然也不算难,但很多习惯Matlab的同行在初期切换时总会有点别扭。
另一个原因是我需要频繁画逐时的电平衡图、冷平衡图、SOC曲线图。Matlab的绘图生态在这一类科研可视化里确实方便,批处理循环跑算例也不慢。至于求解器,我用的组合是YALMIP调用CPLEX,备选Gurobi。两者都是成熟的商业MILP求解器,处理几千个二进制变量的混合整数线性规划问题没有太大压力。
如果追求极端求解速度,可以考虑手写约束矩阵并直接调用求解器API,但那是工程落地阶段的事。学术验证和算法研究阶段,用YALMIP这种建模语言快速迭代,性价比高得多。
2. 核心细节解析与实操要点
2.1 冰蓄冷空调建模的三个核心环节
冰蓄冷空调的建模是整个题目的灵魂。我第一次复现的时候,直接把它当成"一台普通电制冷机加一个蓄电池",结果冷量平衡怎么都对不上。后来老老实实拆成三个子模型:
第一是制冷主机模型。冰蓄冷空调里的制冷主机有双工况能力:制冷工况下直接提供冷水给末端;制冰工况下产生的冷量用于制冰。两个工况的能效比(COP)不同,通常制冰工况COP比制冷工况低一些,因为制冰需要的蒸发温度更低。数学上写成:
制冷工况冷量 = COP_c × 耗电功率 制冰工况冷量 = COP_ice × 耗电功率
这两个功率之间存在模式互斥——同一时刻主机要么直供要么制冰,不能又供冷又制冰。
第二是蓄冰槽模型。蓄冰槽的状态用蓄冰量S_ice来表示,单位是kWh冷量。能量平衡方程是:
S_ice(t+1) = S_ice(t) + (制冰功率 - 融冰供冷功率) × 时段长度
也就是说,这一时段存进去的冰量减去融出去的冰量,等于蓄冰槽冷量的变化量。融冰功率有上限约束,不能无限制地快速融冰,否则供水温度波动会很大;蓄冰量也有上、下限约束,槽内不可能空也不可能满到溢出来。
第三是末端联合供冷模型。用户侧冷负荷需要由制冷主机直供冷量、融冰冷量和吸收式制冷机冷量共同满足:
冷负荷 = 直供冷量 + 融冰冷量 + 吸收式制冷量
这看起来像简单加法,实际运行中却是三种冷源"比拼成本"的过程。夜间电价低,制冰便宜,蓄冰槽把冷量存起来;白天电价高,融冰就成了最经济的冷源;吸收式制冷机用的是燃气轮机余热,边际成本几乎为零,应该优先满发。
实操中有三个关键注意点:
- 蓄冰槽的末态约束必须加。调度周期结束时蓄冰量要回到初始水平,或允许一个小的偏差范围。如果不加,求解器会在一天结束时把槽里的冰全部耗尽,第二天早上蓄冰槽就是空的,跨天运行完全不成立。
- 融冰速率上限比蓄冰速率上限更值得花心思。蓄冰在夜间慢慢蓄没问题,白天融冰供冷如果速率上限设太小,会出现"有冰却供不出冷"的尴尬情况,冷负荷平衡被约束卡死。
- 制冰模式和直供模式的互斥约束一定要建模。我试过不加互斥约束时,模型会给出"同一台主机同时制冰和直供"的结果,数值上满足等式,物理上却是胡闹。后面在4.2节会讲具体怎么处理。
2.2 冷热电联供设备建模的核心参数
燃气轮机是整个系统的"发动机",它的模型决定了冷热电三条能量流的耦合强度。建模只需要三个关键参数:发电效率η_e、热电比r、余热回收利用率η_h。
发电效率把天然气输入能量转化为电功率:发电功率 = η_e × 天然气输入功率,其中天然气输入功率等于天然气耗量乘以低位热值。热电比描述的是"发一度电的同时产生多少余热":余热回收功率 = 发电功率 × 热电比。余热回收利用率回答"回收的余热里有多少能被实际利用"——因为烟气温度、换热损失等因素,不可能100%利用。
回收的余热进入两个下游设备:
一路走热交换器直接供热,满足热负荷。热交换器模型简单,输入热功率乘以效率就是输出热功率。
另一路走溴化锂吸收式制冷机,把热能变成冷能。吸收式制冷机没有大功率压缩机,主要耗电是溶液泵和冷却塔风机,相比电制冷机省电得多,但它的COP(热制冷系数)不高,通常在0.7到1.4之间,也就是说1kW热量只能换0.7到1.4kW冷量。而电制冷机的COP普遍在3到5,1kW电能可以换3到5kW冷量。这两者相互配合,形成了"电制冷效率高但耗电贵,吸收式制冷耗电少但要烧余热"的经典取舍。
还要补一台燃气锅炉作为热源补充,用来覆盖热负荷峰值或燃气轮机停机时的供热缺口。没有这个补充热源,系统在热负荷高峰时很可能出现热平衡无解的困境。
2.3 多时间尺度调度框架的搭建思路
三层调度框架,我推荐"分层建模加变量传递"的思路,而不是在一个模型里堆所有时间尺度。
第一层,日前优化。时间分辨率取1小时,一个调度周期24个点(也可以取96个点,即15分钟分辨率,但计算量会大不少)。决策变量是燃气轮机启停、各机组启停、蓄冰槽全天蓄冰融冰计划、蓄电池充放电计划、购电售电计划。目标函数是全天总运行成本最小。
第二层,日内滚动。时间分辨率取15分钟,滚动窗口通常取4小时或6小时。每15分钟触发一次优化,重新预测未来窗口内的负荷和光伏出力,调整连续变量的出力。日内层不再碰启停变量——机组开没开、蓄冰槽能不能用,都继承日前层的决策结果,最多做小幅修正。
第三层,实时调整。时间分辨率1到5分钟,我通常用一个简化的模型预测控制来做,或者干脆写一个反馈校正脚本:实时检查功率越限情况,如果越限,按优先级依次调节蓄电池出力、融冰速率、购电功率。实时层不需要求解大规模优化问题,否则计算延迟无法接受。
在Matlab里复现时,我强烈建议先把单时间尺度下的所有模型跑通,再复制到日内层,最后加实时层。直接一次写成三层,变量传递逻辑一旦出错,排查起来非常痛苦。
2.4 目标函数与约束条件的完整梳理
目标函数我选择最小化系统总运行成本,具体展开是四项:
- 购电成本:由上级电网购电的电量乘以实时电价,售电收入则是负成本。
- 天然气成本:燃气轮机和燃气锅炉消耗的天然气量乘以天然气价格。
- 设备运维成本:各设备出力乘以对应的单位运维成本系数。
- 启停成本:机组启停一次产生的附加费用,用二进制变量乘一个大系数来实现。
目标函数写成数学形式是:
min Σ_t [ 购电价格×购电功率×Δt - 售电价格×售电功率×Δt + 天然气价格×天然气耗量×Δt + Σ_i 运维成本系数×设备出力×Δt + 启停成本×启停标志 ]
约束条件要覆盖五个方面:
- 电平衡约束:各电源出力加购电加蓄电池放电,等于电负荷加各用电设备耗电加购电加蓄电池充电。最容易漏的是电制冷机和制冰机的耗电,这两台都是用电大户,漏一个等式直接不守恒。
- 热平衡约束:余热供热量加锅炉补燃热量,等于热负荷。
- 冷平衡约束:直供冷量加融冰冷量加吸收式制冷量,等于冷负荷。
- 设备出力约束:每台设备的出力有上下限,且上下限与二进制启停变量相乘——停机时出力必须是0。
- 储能约束:蓄电池和蓄冰槽都要满足SOC动态方程、容量上下限、充放电功率上限,以及调度末期的SOC回位约束。
电网交互约束也很重要。从上级电网购电不能无上限,实际工程中受变压器容量限制,需要设一个购电功率上限,否则优化器会在电价便宜时无限购电、大量蓄能,结果严重脱离实际。
3. 实操过程与核心环节实现
3.1 代码架构设计:让每一层都能单独调试
代码组织上,我采用模块化文件夹结构:
CCHP_Microgrid/ ├── data/ 负荷、风光、电价等输入数据 ├── models/ 设备参数定义 ├── schedules/ 日前、日内、实时三个调度层 ├── utils/ 数据读取、结果输出、画图工具 └── main.m 主程序main.m里放几个开关变量:run_day_ahead、run_intraday、run_realtime。调试时只开其中一层,跑通之后再层层叠加。每个调度层文件夹下再拆成build_model.m、solve_optimization.m、save_results.m三个脚本。
这个结构的最大好处是:改参数不用四处找;换算例只需要换data文件夹里的数据文件;各层级能独立验证正确性。我见过很多同学的复现代码是"一个巨型脚本跑到底",改一个参数要滚动半个小时才能看到结果,那种体验我是不推荐的。
3.2 模型实现:从数学公式到YALMIP约束
下面直接看蓄冰槽相关的YALMIP实现片段,这是一个简化版但足够说明核心逻辑:
% 决策变量 P_ice = sdpvar(1, T); % 制冰电功率,kW Q_melt = sdpvar(1, T); % 融冰供冷功率,kW S_ice = sdpvar(1, T+1); % 蓄冰量,kWh % 二进制变量 u_ice = binvar(1, T); % 制冰模式指示 % 蓄冰槽SOC动态方程 for t = 1:T Constraints = [Constraints, S_ice(t+1) == S_ice(t) + (P_ice(t) * COP_ice - Q_melt(t)) * dt]; end % 边界约束 Constraints = [Constraints, S_ice(1) == S_ice_initial]; Constraints = [Constraints, S_ice(T+1) >= S_ice_initial]; Constraints = [Constraints, 0 <= S_ice <= S_ice_max]; Constraints = [Constraints, 0 <= Q_melt <= Q_melt_max]; Constraints = [Constraints, 0 <= P_ice <= P_ice_max * u_ice];这里有一个我踩过的坑:如果不把制冰模式和直供模式做互斥,模型会在同一时段既制冰又直供大量冷量,看似满足冷平衡,实际物理上不成立。解决方法是把主机的直供模式也建模成一个二元变量,加一条"直供状态加制冰状态小于等于1"的互斥约束。代价是多了一倍的二进制变量,求解时间会上升,但在可接受范围内。
3.3 求解器配置与性能调优
求解器设置上,我推荐:
options = sdpsettings('solver', 'cplex', 'verbose', 2); options.cplex.mip.tolerances.mipgap = 0.001; % MIP gap设为0.1% options.cplex.mip.limits.timelimit = 300; % 求解时限300秒 options.cplex.mip.strategy.search = 4; % 深度优先搜索MIP gap这个参数值得多说两句。很多新手为了追求极致精度,把gap设成0,结果模型稍复杂一点就几小时跑不完。我实测下来,日前调度这类场景,0.1%的gap完全够用——输入数据本身就有预测误差,再高的求解精度也是白费。同样的算例,gap从0.001放宽到0.01,求解时间从20分钟降到3分钟,目标函数值差异不到0.3%。
对于更复杂的算例,试试"热启动":先求解一个不考虑整数变量的松弛问题,把连续解作为整数求解的初始点喂给求解器,能显著加速求解。再不行就考虑把同类型多台机组聚合成一台等效机组,整数变量数量直接减半。
3.4 算例设计:参数从哪来、结果怎么看
复现过程中最头疼的就是参数散落在各篇文献的表格里,很容易抄错。我整理了一份常用参数速查表,数值可以根据实际场景调整:
| 设备 | 参数 | 数值 |
|---|---|---|
| 燃气轮机 | 额定发电功率 | 1000 kW |
| 燃气轮机 | 发电效率 | 0.35 |
| 燃气轮机 | 热电比 | 1.2 |
| 吸收式制冷机 | 热制冷系数 | 1.1 |
| 电制冷机 | COP | 4.0 |
| 冰蓄冷主机 | 制冷COP | 3.5 |
| 冰蓄冷主机 | 制冰COP | 3.0 |
| 蓄冰槽 | 最大蓄冰量 | 1200 kWh |
| 蓄冰槽 | 最大融冰速率 | 300 kW |
| 蓄电池 | 容量 | 500 kWh |
| 蓄电池 | 最大充放电功率 | 200 kW |
设计算例时,冷热电负荷的比例要和设备容量匹配。燃气轮机热电比1.2意味着发1000kW电的同时产生约1200kW余热,如果热负荷没有那么高,多出来的余热只能靠吸收式制冷机制冷消化,或者直接散掉。负荷曲线设计不合理,会出现"热过剩、冷不足"或者反过来,模型结果一看就是病态的。
结果验证一定靠画图。我固定的三张图是:电功率平衡堆叠图、冷功率平衡堆叠图、蓄冰槽和蓄电池SOC曲线。如果SOC曲线在白天出现莫名的剧烈波动,大概率是约束或参数有问题,不是求解器发疯。
4. 常见问题与排查技巧实录
4.1 无可行解:先用松弛变量定位问题
无可行解是遇到最多的报错。我的经验是:不要盯着报错干瞪眼,直接在约束里加松弛变量来"探测"问题出在哪。
做法是:在每个关键等式约束右端加一个非负松弛变量,目标函数里加一个很大的惩罚项。求解之后,看哪些时段、哪条约束的松弛变量非零,问题就定位了。常见的无可行解原因有四个:
- 蓄冰槽末态约束太紧。一天的冷量被用得太狠,最后回不到初始状态。解决方法是放宽末态约束为允许一定偏差,或者扩大蓄冰槽容量。
- 电平衡约束漏了用电设备。常见的是漏掉电制冷机或制冰机耗电,导致电量凭空消失。
- 燃气轮机最小出力约束与负荷不匹配。比如夜间热负荷很小时,燃气轮机的最低出力对应的余热却无处消纳,需要加"余热排放"变量来模拟实际运行中的散热。
- 购电上限设得太小。系统在高峰期想多买电却买不到,导致负荷无法满足。
用松弛变量定位后,通常只需要调整几个参数就能跑通。
4.2 蓄冰槽SOC"对不上账"
多时间尺度复现中最隐蔽的问题,是日前层和日内层对蓄冰槽SOC的计算对不上。日前层用1小时步长,日内层用15分钟步长,两层对"同一个物理时刻"的S_ice值定义必须严格对齐。
我建议在变量传递时单独写一个convert_time_index函数,把日前层的时段索引映射到日内层的时段索引。不要直接拿变量赋值,因为Matlab数组下标从1开始,而文献公式里常常从0开始,一个偏移就能让所有SOC错位一个时段,排查起来极其痛苦。
另外,日内层执行完优化之后,应该把实际运行的蓄冰量回代到日前层的下一轮优化中,形成一个闭环反馈,而不是每轮都从日前层的预测值出发。
4.3 日前日内变量衔接错位
日前层决策了机组启停和蓄冰计划,日内层应该怎么处理这些决策?我踩过的坑是:把日前层结果当成硬约束,日内层完全不能改变,结果预测误差稍大一点,日内优化就无解。
正确的做法是用软约束:启停状态必须继承,这是硬约束,因为机组开关机有物理时间门槛;但连续出力可以允许在小范围内调整,代价是目标函数里加一个"偏离日前计划"的惩罚项。
具体实现:
% 日内层启停强约束 Constraints = [Constraints, u_gt_ID == u_gt_DA]; % 日内层出力软约束 Constraints = [Constraints, abs(P_gt_ID - P_gt_DA) <= delta_gt];delta_gt不建议设太小,理想数值是日前预测误差的2到3倍。留足调整空间,日内层才有实际意义。
4.4 整数变量太多导致求解缓慢
多时间尺度模型最大的性能瓶颈就是整数变量数量。日前层96个时段、每时段十几个设备,如果每个设备有两个模式,整数变量数量轻松破几千。CPLEX处理3000个二进制变量没问题,但再往上增长,求解时间会指数级上涨。
我的排查思路是:
- 先缩减时段做模型验证。把96时段缩到24时段,模型跑通且结果合理后再恢复。
- 设置合理的MIP gap和时限,不要死磕最优解。
- 考虑机组聚合。多台同类型机组聚合成一台等效机组,整数变量数量直接减半。
- 检查是否有冗余变量。比如某个设备在某个时段根本不需要启停决策,可以用固定值替代。
4.5 调试三板斧:能量守恒对账、单设备单场景测试、基准场景对照
整个复现过程中,最后沉淀出来一套"调试三板斧":
第一板斧,能量守恒对账。把逐时的电平衡、热平衡、冷平衡等式两侧分别求和,任何一个差值超过0.01kWh,说明约束有问题。这个方法能快速定位平衡约束写错方向的低级错误。
第二板斧,单设备单场景测试。把系统简化为"一台燃气轮机加一个固定负荷",目标函数简化成只统计这台设备的发电量,验证设备模型的输入输出关系是否正确。设备模型错了,后面所有优化结果都是空中楼阁。
第三板斧,基准场景对照。设置一个零光伏、零蓄冰、固定电价的基准算例。这种情况下优化结果应该非常直观——购电量等于负荷减去燃气轮机发电量。如果结果不符合直觉,要么模型有bug,要么参数有抄错。
这三板斧配合起来,我解决过的问题包括:制冰机的COP写反、冷负荷平衡符号写错、蓄冰槽SOC的dt没有乘上时段长度、日前和日内层时间索引移位……每一个都是"看起来没毛病、结果就是不对"的坑。
写在最后
我个人在实际操作中的体会是:复现这类多时间尺度优化调度,真正的价值不在"跑出一个漂亮的目标函数数值",而在于把能量流、信息流、时间流三层关系彻底理顺。做完这个项目之后,我对"储能是系统灵活性核心"这句话的理解深了一层——无论是蓄冰槽还是蓄电池,它们真正的作用是把能量从时间维度上搬移,把"刚性系统"变成"柔性系统"。
最后再分享一个小建议:写代码之前,先把冷、热、电三条平衡方程手写在纸上,标清楚每一个变量的单位、取值范围和所属层级,再动键盘。我前两周返工的大多数问题,都是因为模型草图没画清楚就急着敲YALMIP。先把模型"伺候"明白了,代码只是一个翻译过程。