做过综合能源优化调度的人都知道,冷热电联供(CCHP)微网程序最大的坑不是数学求解,而是模型写得太“完美”,离实际设备一对比就崩。我最近把一套基于冷热电联供的综合能源耦合微网优化程序在MATLAB里完整跑通了,从设备级建模到日前调度求解,再到结果后处理,整个过程踩了不少雷,也是真正理解了“耦合”二字在这些模型里意味着什么。这篇就把整套建模思路、MATLAB实现框架、求解器选型和一些排查技巧完整复盘一遍,适合正在做能源互联网、微电网优化方向的研究生和刚入门的设计人员参考。
1. 综合能源微网优化到底在优化什么
拿到一个“综合能源耦合微网优化程序”的需求,第一件事不是急着写代码,而是把物理对象和优化边界捋清楚。所谓CCHP,即Combined Cooling, Heating and Power,冷热电联供,本质是把燃气轮机或内燃机发电后的余热回收,供给吸收式制冷机做供冷、通过换热装置供热,实现能源的梯级利用。传统分产系统里,发电靠电网、供热靠锅炉、供冷靠电制冷,三套系统互相独立,能源利用效率通常在40%到60%之间。而CCHP系统把燃气的一次能源先发电,再把排烟和缸套水的余热“变废为宝”,综合效率能做到75%到85%。这一点是整套优化模型的经济性来源,也是所有约束条件和目标函数设计的起点。
1.1 冷热电联供系统的核心组成
我做的这套仿真里,微网侧的核心设备包括:燃气轮机(或燃气内燃机)作为原动机、余热回收装置(烟气换热器与缸套水换热器)、吸收式溴化锂制冷机、电压缩式电制冷机、燃气锅炉备用的补热模块、电储能电池以及蓄热/蓄冷罐。电网作为外部电源接入,可以和燃气轮机一起向电负荷供电,同时允许在峰时段向电网反送电,视项目所在地区的上网政策决定是否开启。
从能量流来看,这个系统可以看成一个多输入多输出的能源枢纽模型:输入端是天然气和电网购电,输出端是电、热、冷三种终端能量。关键在“耦合”二字——冷负荷不一定非得用电制冷机扛,可以从燃气轮机的余热里走吸收式制冷路径;热负荷既可以从余热回收换热站取,也可以用燃气锅炉补燃。这种不同能源载体之间的可替换性,给了调度优化很大的灵活性空间,也因此形成了一个带互补关系的非线性混合优化问题。
1.2 优化目标的数学表达
优化的目标函数一般有两种写法。第一种是运行成本最小化,把所有设备的燃料成本、购电成本、运行维护成本加总,再减去可能的售电收益。第二种是碳排放最小化,对天然气消耗量和电网购电电量分别乘上碳排放因子再求和。我日常做得最多的还是以运行成本为目标,碳排放作为约束条件加上限,因为这样和实际项目的经济考核最贴近,也方便对决策者解释方案的价值。
决策变量分为连续型和离散型两类。连续型包括:燃气轮机输出功率、电制冷机功率、吸收式制冷机供冷功率、锅炉供热量、储能充放电功率、蓄热罐储放热功率等;离散型主要是燃气轮机的启停状态(0/1变量),以及可能存在的多机组组合选择。整个优化问题离散周期一般取24小时,步长为1小时,形成24个时段的混合整数线性规划(MILP)或混合整数二次规划(MIQP),目标函数写成:
[ \min \sum_{t=1}^{24} \left[ c_{\text{gas}} \frac{P_{\text{GT},t}}{\eta_{\text{GT}}} \Delta t + c_{e,t} P_{\text{grid},t} \Delta t + \sum_{i} c_{\text{OM},i} |P_{i,t}| \Delta t - c_{\text{sell},t} P_{\text{sell},t} \Delta t \right] ]
其中 (\eta_{\text{GT}}) 是燃气轮机发电效率,这里有个细节:效率不是常数,会随负载率变化,为了保持线性化,通常的做法是把效率曲线做分段线性近似,而不是直接写成分式约束,这一点后面细说。
2. 建模细节与参数标定
2.1 能量平衡方程怎么写得严谨
模型的核心是每一时段的电、热、冷三种能量平衡约束。电功率平衡是最容易出问题的地方,因为Charging、Discharging、Grid import/export的方向很多,符号稍微搞错就可能导致调度结果出现“既买电又卖电”的荒谬情况。
电平衡方程:
[ P_{\text{GT},t} + P_{\text{grid},t} + P_{\text{PV},t} + P_{\text{dis},t} = P_{\text{load},t} + P_{\text{EC},t} + P_{\text{ch},t} + P_{\text{sell},t} ]
其中 (P_{\text{dis},t}) 是电池放电功率,(P_{\text{ch},t}) 是充电功率。我在程序里规定 (P_{\text{ch},t})、(P_{\text{dis},t}) 都是非负变量,同时用二进制变量 (u_{\text{ch},t}) 防止同一时段既充又放,这是工程上常见的互补约束写法。如果嫌二进制变量太多拖慢求解速度,也可以用下面的线性约束来限制:
[ 0 \le P_{\text{ch},t} \le M \cdot (1 - u_{\text{dis},t}) ] [ 0 \le P_{\text{dis},t} \le M \cdot u_{\text{dis},t} ]
M的选取不能太大,否则会破坏MILP的数值稳定性,我一般取电池额定功率的1.1倍。
热平衡同样要小心:吸收式制冷机的驱动热源来自余热回收,它既是“热负荷”又是“冷供给”,耦合关系体现在这里。热平衡方程:
[ Q_{\text{HR},t} + Q_{\text{boiler},t} + Q_{\text{dis,heat},t} = Q_{\text{load},t} + Q_{\text{ac},t} + Q_{\text{ch,heat},t} ]
其中 (Q_{\text{HR},t}) 是余热回收量,它与燃气轮机发电量之间满足 (Q_{\text{HR},t} = (1 - \eta_{\text{GT}}) \cdot \eta_{\text{HR}} \cdot P_{\text{fuel},t}),这个公式把热和电绑在了一起。冷平衡则相对独立:
[ Q_{\text{ec},t} \cdot \text{COP}{\text{ec}} + Q{\text{ac},t} \cdot \text{COP}{\text{ac}} = C{\text{load},t} ]
注意:工程上冷负荷习惯用“制冷量”而不是“制冷功率”描述,建模时我没有纠结单位问题,统一用kW,但COP的取值必须和设备的冷量单位保持一致,否则一个数量级的错误会让整个结果差到十万八千里。
2.2 关键设备模型与典型参数
燃气轮机模型是整套模型中最需要狠抠细节的部分。发电效率和输出功率不是线性关系,我实测下来,低负载率时效率掉得非常快,满负荷时能到33%左右,30%负载时可能只有25%。为了不让模型过度复杂,程序里做了三段式分段线性效率曲线,每条线段对应一个区间约束,YALMIP里可以用define()和implies()的组合实现,或者直接枚举区间用big-M做线性化。
吸收式制冷机用的溴化锂机组,COP一般在1.2到1.4之间,它的驱动热来自余热,在夏季冷负荷大的时段性价比极高。对比之下,电压缩式制冷机COP在3.5到5之间,虽然制冷效率高,但用的是高品质电能,边际成本取决于当时的购电电价。这两条供冷路径的博弈是调度程序最精彩的部分,通常结果是:白天电价峰时,程序会偏向让燃气轮机多发点电,余热给吸收式制冷机供冷;夜间电价谷时,直接买电网的电开电制冷机更划算。我在参数标定阶段整理了这样一张典型参数表:
| 设备名称 | 参数 | 典型取值 |
|---|---|---|
| 燃气轮机 | 发电效率 | 0.33(满负荷)/ 0.25(30%负荷) |
| 余热回收效率 | 烟气+缸套水综合 | 0.65 |
| 吸收式制冷机 | COP | 1.3 |
| 电压缩式制冷机 | COP | 4.0 |
| 燃气锅炉 | 热效率 | 0.90 |
| 电储能电池 | 充放电效率 | 0.95 / 0.95 |
| 蓄热罐 | 储/放热效率 | 0.97 / 0.97 |
储能设备的建模需要引入跨时段的状态变量,SOC递推公式为:
[ \text{SOC}{t+1} = \text{SOC}t + \frac{P{\text{ch},t} \cdot \eta{\text{ch}} - P_{\text{dis},t} / \eta_{\text{dis}}}{E_{\text{cap}}} \cdot \Delta t ]
为了防止程序钻漏洞“免费搬运能量”,初始SOC和终止SOC要约束为相等,或至少保证终止SOC不低于初始SOC的90%。我在实际运行里把这个约束加了硬限制,避免程序为了凹成本把电池在最后时刻放空,导致下一轮调度无电可用。
2.3 运行策略与调度周期选择
优化的时间尺度直接决定模型复杂度。我做的是日前调度,未来24小时的分辨率为1小时,已知负荷预测曲线和分时电价曲线,一次性求解全时段最优调度方案。这个方案的好处是与绝大多数园区的实际运行方式一致——前一天下午做次日的调度计划,操作员按计划表执行,遇到实际偏差再做实时调整。如果要做到15分钟级滚动优化,模型规模会扩大四倍,求解时间指数级增长,一般只在有实时控制系统时才会做。
调度策略里还需要明确“以电定热”还是“以热定电”。传统CHP设计中二者只能选一个,前者适合电负荷为主导的场景,后者适合热负荷刚性强的场景。但在综合能源微网中,有了电制冷机、燃气锅炉和储能罐,就不再需要刚性绑定,系统可以自由地在“发电+余热供冷/热”和“购电+电制冷+锅炉补热”两条路径之间找最优。这种自由度的提升正是“耦合”带来的红利,但也让程序调试的难度上升了一个台阶。
3. MATLAB程序实现与求解器选型
3.1 建模工具选择:YALMIP还是手写求解器接口
MATLAB里做优化调度,主流两条路。第一条是全手写约束矩阵,然后调用intlinprog或商业求解器,思路直白但对人的建模能力要求极高,改一个约束条件可能要把矩阵维度全部重推导一遍。第二条是YALMIP这样的建模工具,把变量、约束、目标函数用符号表达式写出来,求解器选择只改一个参数。我强烈建议从YALMIP入门,开发效率至少提升一倍,而且能让你把精力放在物理模型而不是矩阵拼接上。
YALMIP配合的底层求解器首选商业求解器,CPLEX或Gurobi,对中小规模的MILP问题性能碾压内置intlinprog。如果实验室没有授权,可以用SCIP或者默认内置的求解器凑合,但遇到有200个以上二进制变量的模型,求解时间很可能从秒级恶化为小时级。就我的经验,一个24时段的CCHP调度模型,二进制变量主要来自燃气轮机启停和储能充放电状态指示,总共约72到96个变量,用Gurobi求解耗时一般在2到5秒内,完全够工程迭代使用。
3.2 核心代码框架与调度求解实现
下面给出一段伪代码骨架,展示整个程序的主干结构,核心思路是把数据准备、变量定义、约束生成、求解、结果提取严格分层,方便后续换设备参数和改目标函数。
%% 综合能源微网日前优化调度骨架 % 数据准备 T = 24; load_profile = xlsread('load.xlsx','电负荷'); heat_profile = xlsread('load.xlsx','热负荷'); cool_profile = xlsread('load.xlsx','冷负荷'); price_buy = xlsread('tariff.xlsx','购电价'); price_sell = xlsread('tariff.xlsx','售电价'); %% 定义优化变量 P_GT = sdpvar(1,T,'full'); % 燃气轮机输出电功率 P_buy = sdpvar(1,T,'full'); % 电网购电 P_sell = sdpvar(1,T,'full'); % 电网售电 P_ch = sdpvar(1,T,'full'); % 电池充电 P_dis = sdpvar(1,T,'full'); % 电池放电 SOC = sdpvar(1,T+1,'full'); % 电池荷电状态 u_GT = binvar(1,T); % 燃气轮机启停状态 u_ch = binvar(1,T); % 充电状态指示 u_dis = binvar(1,T); % 放电状态指示 %% 约束条件 Constraints = []; % 电功率平衡 Constraints = [Constraints, ... P_GT + P_buy - P_sell + P_dis - P_ch == load_profile]; % 电池SOC递推 for t = 1:T Constraints = [Constraints, ... SOC(t+1) == SOC(t) + (eta_ch*P_ch(t)/E_cap - P_dis(t)/(eta_dis*E_cap))*dt]; end % 初始与终止SOC约束 Constraints = [Constraints, SOC(1) == 0.3, SOC(T+1) == 0.3]; % 燃气轮机输出范围与启停约束 Constraints = [Constraints, ... P_min*u_GT <= P_GT <= P_max*u_GT]; %% 目标函数 Objective = sum(price_buy.*P_buy - price_sell.*P_sell) ... + gas_price*sum(P_GT./(eta_GT_linear(P_GT))) ... + sum(om_cost.*P_GT); %% 求解 ops = sdpsettings('solver','gurobi','verbose',2,'showprogress',1); optimize(Constraints, Objective, ops); %% 结果提取与绘图 P_GT_opt = value(P_GT); P_buy_opt = value(P_buy);代码里的eta_GT_linear是分段线性效率近似函数,这里需要单独实现。我的做法是预先算好三个典型负载率点的燃料量,然后用线性插值生成燃料成本与发电量的线性约束,这样可以很自然地接入MILP框架。另一种更严谨的做法是把发电量拆成多个变量表示分段段位,但写起来复杂,对于一般园区级仿真,效率近似带来的误差完全可以接受。
3.3 结果分析与方案对比
跑完优化后,第一步不是看目标函数值,而是检查每个时段的能量平衡是否严格闭合。我会画一张24小时电功率堆叠图,把电网购电、燃气轮机出力、电池放电从上到下堆叠起来,看是否与负荷曲线完全贴合。热和冷也各画一张,确保没有出现“有冷无热”或者“用锅炉烧热同时又把吸收式制冷机的热白排掉”的矛盾现象。
对比纯分供方案(电网购电+电制冷+燃气锅炉)和CCHP耦合方案的运行成本,在气价2.5元每立方米、峰谷电价差超过0.8元每千瓦时的情况下,我实测一个夏季典型日的成本节约幅度在12%到18%之间。节能率并没有广告中的30%那么多,主要原因是冷负荷占比高的时段吸收式制冷机COP只有1.3,直接拉低了整体经济性。这个结论提醒我,做方案论证时一定要用自己项目的负荷数据跑一遍,别拿别人论文的结果直接引用。
4. 常见问题与排查技巧实录
4.1 求解不可行或结果异常发散的排查
程序第一次跑出来infeasible,几乎是每个入坑的人都会遇到的事。我的排查顺序固定不变:第一,把目标函数设成常数,先只求解约束系统,判断是约束本身冲突还是求解器数值问题;第二,逐段放宽储能约束,尤其先禁掉SOC初始等于终止这个硬条件;第三,检查是否存在变量维度与负荷数据维度不匹配的问题,比如T定义成了24却有一个约束用的数组长度是25,这类低级错误造成的不可行占比极高。
还有一种“假装可行”的脏情况:模型返回了结果,但某个设备出力长时间卡在边界上,或者储能的SOC曲线呈现锯齿状高频波动。这是目标函数中存在线性项导致的“贪心”现象,蓄电池会在每个电价低谷都充满、高峰都放空,即使当天第二时段根本不需要充放。解决思路是加入一个很小的SOC平滑惩罚项,比如对每次充放电循环额外征收折算损耗,或者限制每个时段的充放电倍率不超过0.5C,这样调度结果会贴近实际运行习惯。
4.2 求解器选型与数值陷阱
求解器之间的性能差异在MILP问题上极其显著。我用过intlinprog、CPLEX和Gurobi分别跑同一套模型,intlinprog在96个二进制变量的模型上经常要跑5分钟以上,Gurobi把求解时间压到2秒以内,CPLEX居中在10秒左右。如果你的模型有连续变量被加到了二次项,比如储能电池的充放电损耗写成 (P^2) 形式,记得检查求解器是否支持MIQP,不支持的话要退回线性化或者改用分段线性逼近。
数值尺度问题也值得单独说。我曾经把设备功率用W为单位建模,储能容量用kWh为单位,结果目标函数各项数量级差了三个以上数量级,Gurobi的预处理阶段直接报警告,求解精度崩溃。统一所有变量到MW或者kW,目标函数系数保持在0.01到100之间,求解器的数值稳定性会提升一大截。
4.3 调度结果的可解释性检查
程序算完不是终点,能被现场运行人员接受才是终点。每出一版调度结果,我都会做几项“反常识”检查:夏季高峰电价时段,燃气轮机是否在满发;夜间谷电时段,是否还在烧天然气发电(正常应该停机、买电供给);冷负荷很小而热负荷很大的过渡季节,吸收式制冷机是否还在运行(正常应该关闭,走锅炉供热)。任何违反物理直觉的结果,都意味着要么效率曲线参数标定错了,要么约束方向写反了。
我还在程序里内置了一个“纯约束校验模式”,在不优化目标函数的情况下,让求解器任意寻找一个可行解,验证所有设备启停状态和出力是否满足上下限。这个模式对于批量参数扫描、算例复现特别有用,别人给的数据跑不动的时候,我先跑一遍该校验模式,立刻能定位是模型问题还是数据问题。
最后再分享一个实际体会
这一套程序从模型搭建到稳定运行,我前后迭代了十几个版本,最大的感受是:综合能源优化的瓶颈不在MATLAB代码本身,而在于对物理设备特性的把握程度。效率曲线、COP取值、储能损耗这些参数,任何一个标定偏差都能让优化结果“差之毫厘,谬以千里”。我的习惯是把每个设备模型单独抽出来做单元测试,固定边界条件下验证能量守恒,再拼装到系统层面。以我个人的实操经验,这套工作流虽然前期慢一点,但后期排查问题的效率高得多,强烈建议无论是写课程作业还是做实际项目,都按这个思路来搭建你的微网优化程序。