
先说结论冷热电气多能互补的微能源网优化调度本质上是一个混合整数线性规划MILP问题而且是一个特别适合用MatlabYALMIPGurobi组合求解的问题。你可能看过不少论文用粒子群、遗传算法做这个但我在实际对比之后还是坚定地选了MILP这条路——原因后面展开讲。这篇内容适合正在做综合能源系统、微电网、园区级冷热电联供系统研究的同学也适合刚接触优化调度、想快速搭出一个能跑的Matlab模型的工程师。我会把设备建模、耦合约束、代码骨架、典型工况结果分析、还有我实际调试中踩过的坑都摊开讲尽量做到看完就能照着搭一版。1. 为什么我最终选择了YALMIPGurobi这条技术路线1.1 先说我走过的那条弯路智能算法不是万能的一开始我也跟风用过粒子群优化PSO和遗传算法GA来做冷热电联供系统的经济调度。当时觉得智能算法不需要求导、不需要凸性假设多灵活。实际跑起来才发现问题相当大。调度问题里大量存在设备启停、储能充放这样的离散决策——买不买电、启不启机、充不充能这些都是0-1变量。连续型的智能算法处理这类变量特别别扭最常见的做法是把0-1变量放松成连续变量再阈值化这一放松可行域就变了解得的最优性根本站不住脚。约束一多更是灾难我最多试过28个设备、96个时段PSO的罚函数法调惩罚系数调到怀疑人生罚轻了不可行罚重了目标被带偏。还有个更现实的问题智能算法每次运行结果都不一样。写论文的时候审稿人一句请提供全局最优解证明直接卡壳。不是说智能算法没用它在非线性强、模型不精确、可行域模糊的场合确实有优势。但微能源网调度模型本质上是线性的完全没有必要用启发式算法去碰运气。1.2 MILP建模的优势为什么它是微能源网调度的主流范式微能源网里最常见的设备——燃气轮机、燃气锅炉、电制冷机、吸收式制冷机、电锅炉、储电/储热/储冷——在稳态运行下能耗关系基本都能用线性或分段线性的效率模型表达。这意味着目标函数购电购气成本、设备运行成本、碳排放成本、能量平衡约束电热冷气四条母线、设备运行区间和爬坡约束、储能容量约束统统可以写成线性数学表达式设备启停决策由二进制变量表达。整个模型毫无悬念地落入标准MILP框架。对MILP问题Gurobi、CPLEX这类商业求解器可以在给定精度下证明最优性最优间隙可以收敛到0这是启发式算法做不到的。对科研写论文、做工程方案比选这个可复现、可验证最优的属性太重要了。而且MILP求解器内部的branch and cut、presolve这些技术已经成熟到可以处理几千个变量的模型求解速度远快于你手写任何启发式算法。1.3 为什么用Matlab而不是Python说实话用PythonGurobi也能做我身边不少同事就是这么干的。我之所以继续用Matlab主要是三个理由。一是数据前后处理和可视化方便。Matlab的plot家族画设备出力堆叠图、负荷曲线对比图代码量最小尤其area函数画堆叠面积图几乎是为调度结果展示量身定做的。二是YALMIP这个建模工具箱在Matlab里生态最成熟网上资料多遇到问题搜一下就有答案。三是我所在的团队前期潮流计算、设备建模代码都是Matlab写的整个项目在一套环境里跑通最省事。当然如果你所在的团队统一用Python那用Pyomo或直接Gurobi Python API也很顺畅。工具只是手段建模思想才是核心。下面所有代码示例我用的是MatlabYALMIP但约束表达方式换成Python只是语法差异。2. 微能源网设备建模每一个方程背后的物理含义2.1 系统的四条母线电、热、冷、气我做的微能源网结构是典型的园区级配置。理解这个结构是建模的第一步后面所有约束都是围绕母线平衡展开的。电母线连接上级电网、燃气轮机发电、光伏、电储能、电负荷、电制冷机、电锅炉气母线连接上级气网、燃气轮机、燃气锅炉热母线连接燃气轮机余热、燃气锅炉、电锅炉、储热罐、热负荷、吸收式制冷机消耗热来制冷冷母线连接电制冷机、吸收式制冷机、储冷罐、冷负荷。每个时段t通常取24个一小时时段或96个15分钟时段四条母线必须各自满足功率平衡。2.2 关键设备的线性化建模燃气轮机组CHP机组是整个系统的核心它同时产出电和热是多能互补的标志性设备。核心关系是热电联产方程发电功率等于天然气消耗量乘以发电效率再乘以低位热值回收余热功率等于天然气消耗量乘以热回收效率乘以低位热值。这里的天然气消耗量单位用kW按热值折算低位热值取9.78 kWh/Nm³也就是每立方米天然气完全燃烧产生的可用热量。实际建模时有几个容易漏掉的细节运行区间约束必须绑定启停变量即出力下限乘启停变量小于等于出力小于等于出力上限乘启停变量。这个约束的物理含义是机组没启动时出力必须是0启动后出力落在区间内。爬坡约束限制相邻时段出力变化速率即上一时段出力减去本时段出力小于等于向下爬坡速率本时段出力减去上一时段出力小于等于向上爬坡速率。最小连续运行/停机时间在简单日前调度研究里可以忽略但如果做日内滚动调度最好加上否则求解器会让机组做出每小时启停一次的极端策略物理上不可行。燃气锅炉就简单多了热出力等于耗气量乘以锅炉效率乘以低位热值再配上出力上下限。燃气锅炉一般不考虑启停二进制变量因为启停成本低、动作灵活这个假设在园区级规划里是被广泛接受的。电制冷机和吸收式制冷机是冷母线的两个互补来源。电制冷机的制冷功率等于耗电功率乘以能效比能效比一般在3到5之间含义是消耗1份电可以搬移3到5份热量。吸收式制冷机的制冷功率等于输入热功率乘以性能系数性能系数一般在0.7到1.2之间。这个数值差距很关键——电制冷的能效远高于吸收式制冷吸收式制冷的价值不在效率而在它消耗的是燃气轮机余热这种原本可能被浪费掉的热量。精细模型里吸收式制冷机的性能系数在低负荷时会明显下降简单模型保持恒定值即可需要更高精度时按分段线性函数处理。储能设备的数学模型在电、热、冷三种形式上是完全一样的区别只在自放率、充放效率和容量尺度。荷电状态等于上一时段荷电状态乘以自放率系数加上充电功率乘以充电效率减去放电功率除以放电效率。约束包括容量上下限、充电功率上限乘充电状态变量、放电功率上限乘放电状态变量、充电状态加放电状态小于等于1。最后这条互补约束极其关键如果不写求解器会在同一个时段同时充电和放电白白浪费能量还做出一个虚假的最优结果。我见过不少初学者栽在这里看起来成本很低实际是储能系统在左手倒右手。2.3 目标函数的设计经济成本还是碳排放我做过的两版目标函数差别挺大值得单独说一下。经济调度版是最常见的最小化各时段购电成本加购气成本加设备运维成本之和。购电成本用的是分时电价乘以购电功率购气成本是天然气单价乘以耗气量再除以低位热值把气量从kWh换算回Nm³运维成本按各设备出力乘以单位运维成本。这里有个关键细节如果允许向电网售电必须把电网交互功率拆成购电和售电两个非负变量并加上二者不同时为正的互补约束。这个约束用大M法线性化二者之和小于等于一个大数乘以一个0-1变量同时二者之和小于等于大数乘以一减这个变量。否则求解器会同时买入卖出赚差价得到一个完全虚假的最优解。低碳调度版是在目标里加入碳排放成本即碳配额价格乘以电网购电的碳排放因子加天然气消耗的碳排放因子。两个版本可以加权组合调节系数alpha和betaalpha加beta等于1实现经济性和低碳性的平衡。这个改动在代码层面只是目标函数多几行但对结果的影响非常大我在后面的工况分析里会具体说。3. 冷热电多能耦合的关键能源集线器与矩阵化约束表达3.1 能源集线器模型怎么用能源集线器是苏黎世联邦理工学院提出的经典概念核心思想是把多能源输入映射到多能源输出写成矩阵形式负荷向量等于耦合矩阵乘以输入向量。冷热电联供系统用这个框架表达特别清晰。电负荷的组成部分是燃气轮机发电加上电网购电减去售电减去电制冷机耗电减去电锅炉耗电加上储能放电减去储能充电热负荷的组成部分是燃气轮机余热加上燃气锅炉供热减去吸收式制冷机消耗的热量加上储热放热减去储热充热如果储热存在冷负荷的组成部分是电制冷机产冷加上吸收式制冷机产冷加上储冷放出减去储冷充入。在Matlab里直接用矩阵写本质就是把四条母线的平衡方程整理成线性等式组的形式。这样做的好处有两个。系统扩展方便后面加光伏、加储能、加氢能设备只需要在矩阵对应位置填系数不需要重写求解逻辑。与YALMIP的约束风格天然契合YALMIP本身就是面向等式不等式约束的建模环境直接传入矩阵约束就能高效处理。3.2 为什么耦合约束这么容易写错我调试时发现最容易出错的不是单个设备的模型而是设备之间的耦合链条。举两个我实际踩过的例子。第一个例子燃气轮机的余热不是想用多少就用多少。如果系统没有烟气旁通装置燃气轮机只要开机余热就必须被热负荷或吸收式制冷机消纳多出来的热量只能通过散热器排掉。这时热母线平衡方程里必须有一个弃热变量否则容易造成两种后果要么问题不可行因为热量无路可去要么求解器通过降低燃气轮机出力来规避余热结果完全偏离了以热定电的实际运行逻辑。第二个例子电制冷机和吸收式制冷机是典型的并联互补关系。夏季供冷时二者会争抢电和热两种不同来源的能量。单看冷母线平衡方程看不出这种竞争关系只有把目标函数里电价和天然气价的比值放进去才能解释为什么某个时段特定机组多出力。有一次我把气价从2.5元调到3.5元吸收式制冷机的出力占比直接从60%跌到20%电制冷机顶上来了。这就是多能互补系统整体最优而非单机最优的直观体现。3.3 代码层面的矩阵化做法我的实际做法是把每个时段t的所有决策变量排成一个列向量包含燃气轮机发电功率、耗气量、余热功率、燃气锅炉热出力、耗气量、电制冷机耗电、吸收式制冷机输入热功率、电锅炉耗电、购电、售电、储能充放电、荷电状态、弃热、各启停变量等。然后用YALMIP的变量函数定义全时段的变量矩阵用循环或向量化操作构建约束。这里有一个非常实际的性能经验不要用循环一条一条写96个时段的标量约束要基于向量化操作构建约束矩阵。我做过对比YALMIP处理一个96行向量的约束比处理96条独立标量约束快一个数量级以上。构建完成后用竖线连接所有约束形成一个约束集合交给优化函数求解。4. 调度代码的骨架从数据准备到结果导出4.1 环境配置里最容易忽略的细节我的环境是Matlab R2022a加YALMIP R20210430加Gurobi 10.0.1学术授权。Gurobi在Matlab里的路径设置其实很简单安装后添加Matlab接口路径到MATLAB搜索路径保存路径后重启Matlab再运行求解器设置函数即可。新手最容易被卡住的是license文件。Gurobi的license文件放在用户主目录下路径不能有中文必须和hostid匹配。我见过不少报license expired的最后发现是系统时间不对不是license真的过期。另外YALMIP的安装就简单多了把下载的文件夹添加到路径里就行没有编译过程。4.2 核心代码结构一个能跑通的主干下面给出一段简化但能跑通的主干代码。为了可读性我省略了部分储能约束和全部爬坡约束但骨架逻辑是完整的。实际运行前必须把每个变量的上下限、储能荷电状态递推式补全。%% 微能源网优化调度主程序骨架 % 数据准备 T 24; % 调度时段数 load_ele [...]; % 电负荷 1xT load_heat [...]; % 热负荷 1xT load_cool [...]; % 冷负荷 1xT price_ele [...]; % 分时电价 1xT price_gas 2.5; % 天然气单价 元/Nm3 LHV 9.78; % 天然气低位热值 kWh/Nm3 % 决策变量定义 u_gt binvar(1, T); % 燃气轮机启停 P_gt sdpvar(1, T); % 燃气轮机发电功率 F_gt sdpvar(1, T); % 燃气轮机耗气量 P_gb sdpvar(1, T); % 燃气锅炉热出力 F_gb sdpvar(1, T); % 燃气锅炉耗气量 P_ec sdpvar(1, T); % 电制冷机耗电功率 P_eb sdpvar(1, T); % 电锅炉耗电功率 P_buy sdpvar(1, T); % 电网购电功率 P_sell sdpvar(1, T); % 电网售电功率 SOC_ess sdpvar(1, T); % 储能荷电状态 P_ch sdpvar(1, T); % 储能充电功率 P_dis sdpvar(1, T); % 储能放电功率 Q_curt sdpvar(1, T); % 弃热功率 % 约束构建 C []; % 电母线平衡 C [C, 0.35*P_gt P_buy - P_sell - P_ec - P_eb P_dis - P_ch load_ele]; % 热母线平衡 C [C, 0.45*P_gt P_gb load_heat Q_curt]; % 冷母线平衡 C [C, 3.5*P_ec load_cool]; % 燃气轮机运行区间 C [C, 100*u_gt P_gt 1000*u_gt]; % 燃气轮机热电关系 C [C, F_gt P_gt/0.35]; % 储能荷电状态递推 C [C, SOC_ess(2:T) SOC_ess(1:T-1)*0.98 0.95*P_ch(2:T) - P_dis(2:T)/0.95]; C [C, SOC_ess 0.2, SOC_ess 0.9]; % 目标函数购电成本 购气成本 储能折旧成本 Objective sum(price_ele.*(P_buy - P_sell)) ... sum(price_gas*(F_gt F_gb)/LHV) ... 0.05*sum(P_ch P_dis); % 求解 ops sdpsettings(solver,gurobi,verbose,2); result optimize(C, Objective, ops); % 结果提取 P_gt_opt value(P_gt);这段代码故意省略了很多边界约束实际跑之前必须把每个变量的上下限、储能SOC的上下限都补全。YALMIP有个好处约束没写全时它会提示问题无界之类的警告提醒你先补约束再求解。4.3 数据前处理单位统一是第一位的我在做第一个案例时犯过一个特别低级的错误光伏出力给了kW电负荷给了MW天然气热值用了kJ/Nm³结果目标函数量纲完全乱了Gurobi直接报数值错误。后来统一成功率一律用kW、能量用kWh、气流量用Nm³/h、热值用kWh/Nm³这才顺利求解。单位统一之后建议用检查函数对每个变量做一轮合理性检查YALMIP的check命令可以逐条检查约束是否满足比你自己写断言方便得多。5. 调度结果怎么解读三种典型工况的对照5.1 冬季采暖工况下的运行特征冬季热负荷很大燃气轮机的余热基本全部被热负荷消纳。此时系统倾向于以热定电——燃气轮机为了供热而发电多余的电可以卖给电网如果允许售电或者存到电池或者干脆让电锅炉把电转成热。时段电价水平燃气轮机电锅炉储能低谷0.35元/kWh低出力高出力充电高峰1.2元/kWh全力运行低出力放电平段0.7元/kWh中等出力中等持平这个结果逻辑很清晰低谷期电价便宜电网购电划算燃气轮机少发电热负荷缺口由电锅炉补高峰期电价贵燃气轮机满发余热供采暖多余的电要么自用要么储能放电应对晚高峰。如果售电价很低多余的电宁可给电锅炉弃电转热也不愿意低价卖出。这个现象在结果里看得特别清楚。5.2 夏季供冷工况下的运行特征夏季冷负荷主导这里会出现一个反直觉的结论——从能效比看吸收式制冷机只有0.7到1.2远低于电制冷机的3到5按理说应该多用电动制冷但吸收式制冷机消耗的热量来自燃气轮机余热而燃气轮机发电本身能抵消一部分购电成本。所以在气价便宜、电价贵的组合下发电加吸收式制冷的组合策略反而更经济。气电价格比吸收式制冷占比电制冷占比系统总成本气价低/电价高60%40%以下最低气价高/电价低20%左右80%左右最低这就是多能互补价值最直观的体现——不是某个设备单独最优而是整体最优。我调整气价和电价做了几组敏感性分析这个规律相当稳定。5.3 过渡季节的综合指标过渡季节冷热负荷都不大系统会处于低出力多储能的状态。我一般会重点看三个指标24小时总购电成本、天然气总消耗量、弃热弃光总量。这三个指标放在一起就能快速判断调度结果的合理性。成本低了但弃热严重说明模型里可能缺少热负荷的灵活性或者储能容量配置偏小天然气消耗大了但电费省了很多说明气电比价策略下的选择还需要仔细核对。我还会把日前的调度结果和实际负荷曲线叠在一起看如果某些时段设备出力波动过于剧烈往往是爬坡约束设置不合理或者负荷预测数据本身有问题。6. 我踩过的坑求解器报错、数值尺度与调试技巧6.1 模型不可行到底是谁的锅这是所有初学者最容易崩溃的瞬间。我总结了一个排查顺序。先看优化返回的诊断信息如果显示问题不可行可以用赋值函数手动给变量赋一组预期合理值然后用检查函数逐条确认哪个约束超出范围。我遇到过的两种情况特别典型。第一种储能荷电状态递推关系的首末衔接问题。如果要求调度周期结束时荷电状态回到初始值而对初始值和末值的差范围限制得太紧很容易不可行。比如初始荷电状态是0.5要求结束时回到0.5但储能容量小、负荷大根本充不回去问题就无解了。解决方法是放宽末值范围或者允许一定的偏离并加惩罚项。第二种负荷数据本身超出了系统供给能力。比如夏季冷负荷峰值超过电制冷机和吸收式制冷机的总装机容量这就是设计容量选小了。这不是约束写错而是系统配置本身不合理。遇到这种情况要么加大设备容量参数要么允许切负荷——在目标函数里加一个高惩罚系数的切负荷变量。6.2 数值尺度问题大数和小数混在一起Gurobi对数值尺度非常敏感。举个例子电负荷最大5000 kW储能荷电状态是0到1的小数天然气热值是9.78目标函数里的成本系数跨度可以达到10的6次方以上。这种情况下求解器内部处理会变得很不稳定甚至出现误判不可行。解决办法有三个。把所有量纲统一到同一基准功率用kW、能量用kWh、成本用元不要混入MW和MWh的表述。对储能荷电状态约束把荷电状态乘容量作为一个整体变量来建模避免0到1的小数和MW级的功率直接混乘。对目标函数把所有成本统一除以一个常数比如除以10000不会影响最优解但能让求解器的数值条件好很多。我在一次仿真里把所有数值除以1000之后Gurobi的求解时间从47秒降到3秒这是实打实的收益。6.3 一个容易忽略的现实约束启动成本和阶梯电价很多教材里的简单模型不包含机组启动成本但实际调度里启停频繁会显著影响设备寿命而启动成本的存在会直接改变最优解的时序结构。加上启动成本很简单定义一个启动标志变量大于等于当前启停状态减去上一时段启停状态同时大于等于0再把启动标志变量乘启动成本加进目标函数。另外如果有阶梯电价或者容量电价购电成本就变成了分段线性函数。分段线性函数在MILP里可以用标准的分段线性建模方法处理也可以直接引入多个购电变量每个变量对应一个价格区间用二进制变量保证只落在其中一个区间。这些现实约束加进去之后模型复杂度上一个台阶但调度结果的可信度也会明显提升。6.4 两个亲测有效的调试习惯第一个先用少时段调试再放到全时段。6个时段的MILP几乎是秒出结果你可以在几分钟内反复调整约束定义等到逻辑全部正确再扩展到24或96时段。我见过太多人一上来就上96时段结果连约束手滑写错都发现不了求解器报个不可行就傻眼了排查难度直接翻好几倍。第二个把每个设备的出力和对应负荷曲线一起画出来。解除变量值之后直接用plot和area函数面积图画各设备的贡献叠加一眼就能看出某个时段总有设备出力异常。可视化不只是写论文时才用的工具调试阶段它是最直观的照妖镜比盯着数字矩阵高效得多。回到最初的问题。我踩过不少坑之后才意识到做微能源网优化调度最难的部分往往不是最后那个求解器调用而是建模时对物理过程的理解、对耦合约束的表达、对数据尺度的把控。先把一个能跑的MILP骨架搭起来再逐步加设备、加约束、加不确定性场景这套方法论让我从第一个案例到后来做园区级综合能源规划项目都走得很顺。如果你正准备开始做类似方向建议别在最开始追求模型有多复杂先把代码跑通再迭代升级这是效率最高的路径。