简介:面向电力系统经济调度场景的MATLAB代码包,聚焦热电联产机组与火电、热电机组、风电机组的多能源协同调度,引入变相储热技术平抑供需波动,可在满足机组启停、负荷需求、排放等约束下求解低成本运行策略,兼顾经济性与环保性,适合研究人员、调度工程师及高年级学生深入实践。压缩包共8个文件,以4个m程序为主体,覆盖不同储热方案下的调度建模分支,另有2份Excel初始数据表、1份程序说明txt和1个嵌套rar程序,整体仅62KB,结构紧凑。已有573人学习下载。通过初始数据、说明文档和多个调度计算程序,可完整了解从参数准备、约束构建、优化求解到结果验证的流程,同时体会算法选择、代码优化与并行计算处理技巧,便于复现实验、改造算法,或作为课程设计、毕业设计的参考基础。
1. 热电联产调度:为什么“多发电”反而可能亏钱?
电网低谷时段,别的机组都在降出力,唯独热电联产机组不能跟着降——因为采暖热负荷是刚性的。机组为了供热,电出力被“绑架”在半功率以上,多余的电在峰谷价差巨大的市场上几乎卖不出价,甚至要倒贴弃电费用。这不是偶发故障,而是采暖季每天都在发生的常态。热电联产调度优化的核心命题,就是在这种“以热定电”的约束下,找出让整个系统成本最低的机组组合与出力计划。这套工程把背压/抽凝机组、相变储热、风电不确定性全部装进了MATLAB调度模型,理解它,等于理解现代电力系统“热-电-储”联动调度的一条完整主线。
2. 热电强耦合的数学本质:从“以热定电”到机组可行域建模
2.1 背压式与抽凝式:两种机组,两种自由度
先分清最容易被忽略的底层区别。背压式机组的热出力与电出力保持固定比例,数学上写为 P_e = c_m × H,c_m 由汽轮机结构决定,运行时不可调。这意味着给这种机组下“降电出力”指令根本做不到,除非同步降热出力。而抽凝式机组多了一个可调抽气阀门,电、热可以在一个二维区域内自由组合,但区域边界依然随热出力变化——热出力越高,电出力可调范围越窄。
这个差别决定了调度模型的可行域结构。背压机组的约束是一条直线,而抽凝机组是凸多边形。在 MATLAB 工程里,凸多边形的顶点通常存成两个列向量,程序运行时把顶点序列传给约束生成函数,逐时段构造线性不等式:
% 抽凝式机组热-电可行域,简化为四条边界 % H_t 为热出力,P_t 为电出力,上下界都随 H 线性移动 % P_min + k1*H <= P <= P_max - k2*H A_chp = [1, -k1; -1, -k2]; b_chp = [0; 0]; % 示意:实际按边界常数展开这里 k1、k2 的意义很直接:热出力每增加 1 MW,电出力下限被迫抬高 k1 MW,上限被压低 k2 MW。这就是“以热定电”的数学来源——热出力直接压缩了电出力的可调区间,而不是通过某个惩罚项软约束来实现。很多新手把“以热定电”理解成目标函数里加一项热负荷惩罚项,方向就错了:它首先是可行域形状问题,不是目标函数权重问题。调度代码里如果只写了煤耗目标而没写可行域边界,求解器走出来的“最优解”在物理上根本开不出来。
从代码优化的角度看,两个 k 值是调试重点。它们来自热力试验或厂家资料,一旦填错,所有电出力限值都会跟着错,而且错误是线性的——用一组已知工况校验即可定位,不需要排查非线性问题。
2.2 目标函数与约束矩阵:为什么直接写“总煤耗最小”会翻车
调度模型最常见的翻车点,是把目标函数写成 min Σ coal_cost,然后发现求解器给出的“最优解”是全部机组停机——因为没加负荷平衡约束,什么都不发当然最省。一个合法的调度目标必须同时满足三类约束:功率平衡等式约束、机组爬坡与出力限值约束、储热与备用约束。
工程里典型的目标函数长这样:
% 目标:煤耗二次项 + 启停成本 + 弃风惩罚 % 决策变量 x 按 [P; H; P_w; P_w_curt; Q_ch; Q_dis; SOC] 堆叠 % 煤耗是二次函数,直接求解会变成 QP,且带整数启停变量时更难收敛 % 常见做法:把二次项分段线性化,转成 MILP 用 intlinprog 求解 sections = 3; % 分段数 [slope, intercept] = piecewise_linear(coal_curve, sections); f_cost = slope * P_seg + intercept * u_on; % u_on 为二元启停变量 f_penalty = lambda_wind * P_w_curt; % 弃风惩罚系数 f = [f_cost; f_penalty];这里有几个设计选择值得展开。煤耗二次项不该留给非线性求解器:分段线性化后模型变成混合整数线性规划(MILP),求解速度快一到两个数量级,而且解是全局最优而非局部最优。这是“代码优化”在这份工程里最实质的体现——优化的不是某个函数写法,而是整个问题类别。
弃风惩罚系数 lambda_wind 的选择也藏玄机。它如果低于煤耗成本,求解器会理性地选择弃风而不是让热电机组降价出力;如果过高,结果又会对预测误差过度敏感。经验上把它设在峰谷电价差的 1.5 倍左右,能逼出合理的热电解耦行为。注意这个系数不是物理量,是调度策略的体现,跑敏感性分析时值得专门扫一遍。
2.3 风电不确定性:调度为什么需要“旋备”而不是精确预测
风电预测误差是调度模型必须面对的不确定性来源。数小时前的预测与实际出力偏差可达 10%~20%,在夜间大风时段,偏差绝对值足以顶掉一台中型火电机组的出力。调度模型不可能依赖精确预测,只能给系统预留旋转备用:
% 旋转备用约束:开机机组可加出力之和 >= 负荷备用 + 风电备用 % sum(P_i_max * u_on - P_i) >= R_load + alpha * P_w_forecast % alpha 是风电备用系数,常取 0.15~0.2 A_res = [A_units, zeros, -alpha * P_w_forecast_series]; b_res = R_load_series;备用约束的实质是“嘲讽”预测曲线最坏的那一段。alpha 越大,系统越稳健,但经济性越差——每多留 1 MW 备用,就有一台机组被迫在非经济区运行。这个系数如果取 0,模型会拿着预测曲线当真值来做确定性优化,闭环上一遇到偏差就翻车。
在我看过的调度工程源码里,最影响求解质量的反而不是目标函数和备用约束本身,而是约束矩阵的拼接方式。调度模型按时间步堆叠变量,时段间的爬坡约束会引入跨行耦合,矩阵列数动辄上千,行数随约束类型累加。约束写错位了报错信息不一定明显,常见症状是“单一时段约束正确,整体模型病态”。所以成熟的代码会在求解前打印 Aeq 和 beq 的行数、检查每一行对应哪个时段哪类约束——这是后面避坑章节里最值得养成的一个习惯。
3. 相变储热:如何用“跨时段搬运”给热电机组松绑
3.1 为什么是相变储热,而不是热水罐
储热想解决的问题,是把“供热必须即时满足”变成“供热可以跨时段搬运”。热水罐是一种显热储热方案,工程简单、成本低,但它有两个调度层面的硬伤:一是单位体积储热密度低,兆瓦时级容量需要巨大罐体;二是放热过程温度持续滑移,出口温度不稳定,供热质量不好保证。相变储热利用材料熔化/凝固过程的潜热,在温度几乎不变的条件下吸收或释放大量热量,储热密度通常是显热的 3~5 倍。
| 对比项 | 显热储热(热水罐) | 相变储热 |
|---|---|---|
| 储热密度 | 低,体积大 | 高,约为显热的 3~5 倍 |
| 放热温度 | 持续下降,不稳定 | 相变平台期近似恒定 |
| 调度建模 | 需考虑温度分层 | 能量平衡 + 恒定温度近似 |
| 自放电损失 | 散热损失随温度升高增大 | 保温良好时损失可控 |
| 适用场景 | 短时调峰 | 跨时段搬移、深度调峰 |
从调度模型视角看,相变储热最友好的特性是“温度近似恒定” —— 这意味着充放热功率模型里不需要引入温度变量,只需维护一个能量状态量 SOC。项目文件里的jiaxiangbianchure.m正是加装了相变储热模块的调度脚本,对比基准脚本看,它多出的核心就是一组状态方程和充放热互斥约束。
3.2 储热状态方程:SOC 的累积、边界与周期约束
相变储热罐在调度模型里被抽象成一个能量容器,状态量是储热量 SOC(单位 MWh),控制量是充热功率 Q_ch 和放热功率 Q_dis(单位 MW)。离散时间步 dt(单位 h)下的状态递推:
% 储热罐能量递推,dt 统一为 1 小时 % SOC(t+1) = SOC(t) + eta_ch * Q_ch(t) * dt - Q_dis(t) * dt / eta_dis % 充放热不能同时进行;SOC 必须位于 [SOC_min, SOC_max] % 周期约束:SOC(0) = SOC(T),否则求解器会把储热罐当免费能源 A_soc = build_soc_transition_matrix(n_horizon, dt, eta_ch, eta_dis); b_soc = zeros(n_horizon, 1); A_mutex = [eye(T), eye(T)]; % Q_ch + Q_dis <= Q_max 的紧凑写法 b_mutex = Q_max * ones(T, 1);两个细节直接决定这个模块对不对。第一,SOC 初末相等约束(SOC(0) = SOC(T))必须有,否则优化器会在首个时段拼命充热、最后一个时段拼命放热,把储热罐当免费能源薅,目标函数数值会异常好看但完全违背调度语义。第二,充放热互斥约束必须写成硬约束。如果只靠目标函数里“充放热同时发生时效率惩罚”这种软约束,线性规划求解器大概率会在某些电价时段同时给出 Q_ch 和 Q_dis 都大于 0 的解——物理上这就是一台永动机。
相变材料在相变平台期充放热功率基本恒定,这让 Q_ch_max 和 Q_dis_max 可以取定值,上述线性约束严格成立。如果换成热水罐,最大充放热功率会随温差变化,约束变成非线性,求解难度就不是一个量级了。
3.3 风-热-储联动:低谷充热、高峰放热的收益逻辑
储热模块放进调度模型后,真正改变的是热平衡方程的形态。原来“机组热出力 = 热负荷”被改写成“机组热出力 + 储热放热 - 储热充热 = 热负荷”。这样在夜间风电大发、电价低谷时段,可以让机组降低电出力(同时热出力下降),用风电给储热罐充热;白天电价高峰或热负荷尖峰时,再由储热罐放热补足供热缺口,机组得以保持高电出力。
这套联动的收益来源有三层。第一层是弃风减少:原本因“以热定电”被迫压掉的风电,现在可以被储热罐“吃掉”;第二层是燃料节约:新能源代替了燃煤供热,煤耗下降直接反映在目标函数上;第三层是调峰收益:储热让热电机组在需要深度调峰的时段可以把热负荷转给储热罐承担,机组电出力下限进一步降低,整个系统的调节能力变宽。
这些收益不会在所有时段同时出现,但调度模型的最优解会自动在三个收益来源之间做权衡,前提是目标函数里三者的价格信号都正确——煤价、电价、弃风惩罚缺一不可。这也解释了为什么这份工程里weijiachure.m(外加储热版本)、jiayibanchure.m(一般储热版本)和jiaxiangbianchure.m(相变储热版本)要分开写:三种储热方式的成本参数、效率参数、最大功率完全不同,共用一套模型逻辑可以,但参数化做得不干净就会互相污染。熟悉这个套路的人会直接把储热模块做成一个结构体参数,用solver_options切换——这是代码优化在工程组织上更高级的一步。
4. 代码实战与避坑:从 bolier.m 到三个储热变体脚本的真实工程细节
4.1 一份工程里到底装了什么
解开压缩包,能看到一套麻雀虽小但五脏俱全的调度程序骨架。bolier.m是锅炉模型,负责把煤耗曲线和热出力限值包装成目标函数系数与约束矩阵;jiayibanchure.m是基础储热版本的主调度脚本;jiaxiangbianchure.m是对应相变储热的变体;weijiachure.m则是外加储热模块的版本。三套主程序共用同一套数据读入逻辑,区别集中在储热模型参数和约束组装方式上。
| 文件/脚本 | 在工程中的角色 |
|---|---|
bolier.m | 锅炉/煤耗模型,生成目标函数系数与热出力约束 |
jiayibanchure.m | 一般储热版本的调度主程序 |
jiaxiangbianchure.m | 相变储热版本的调度主程序 |
weijiachure.m | 外加储热模块的调度主程序 |
初始数据一般储热.xlsx | 一般储热算例的负荷与风功率序列 |
初始数据.xlsx | 基准算例数据 |
相变储热程序.rar | 相变储热子模块的独立打包 |
程序说明.txt | 变量定义、运行顺序与已知问题说明 |
有个容易被当段子看的细节:bolier.m的拼写不标准(正确是 boiler),但这类工程里见怪不怪。更值得留意的反而是这种命名暴露的工程习惯——主程序按储热方式划分成三个变体,意味着某开发者一开始就没打算做通用模型,而是先跑通一个场景再复制改参数。这种“复制-修改”模式在快速验证期效率极高,但副作用是修 bug 时要同时改三个文件。等你看完四个避坑记录,会对这种取舍有更具体的体感。
4.2 常见问题:四个让结果“看起来对但实际全错”的细节
坑一:SOC 曲线出现诡异尖峰,储热效率超过 100%。
现象:诊断图里 SOC 在几十个时段内冲到上限然后暴跌,储热罐“凭空多出”能量。原因:数据表里充放热功率单位是 MW,但时间步 dt 在一个模块里写的是 15 分钟、另一个模块写的是 1 小时,导致状态方程里 dt 不一致,SOC 累积量失真。解决:程序开头统一规定 dt = 1 小时,所有数据读入后按 MW 和 MWh 换算,状态递推只用全局 dt 变量。排查时先打印SOC(2:end) - SOC(1:end-1)看有没有异常跳变,比追着约束矩阵看快得多。
坑二:求解成功,但热平衡残差不为零。
现象:intlinprog 返回退出标志 1,可结果里机组热出力加储热放热不等于热负荷加储热充热。原因:热平衡被写成了目标函数里的软惩罚项,权重不够大,求解器为了降低煤耗故意牺牲热平衡“一点点”。解决:把热平衡立为硬等式约束,放进 Aeq/beq 而不是目标函数;同时在求解后强制打印最大热平衡残差,超过 1e-3 就直接报警。这也是我后面每次改约束都条件反射要做的一件事。
坑三:MILP 求解时间从几秒变成几十分钟。
现象:把相变储热模块加进去后,问题规模没大多少,求解时间却爆炸。原因:相变储热版本的充放热互斥约束引入了二元变量,且变量顺序排列不当,分支定界树严重退化。解决:重新编排决策变量顺序,把二元变量集中放在变量向量尾部,同时给互斥约束一个紧的松弛:Q_ch + Q_dis <= Q_max而不是两条独立上限Q_ch <= Q_max, Q_dis <= Q_max。前者的线性松弛紧得多,分支定界效率提升非常明显。
坑四:弃风惩罚系数设置过低,优化结果出现大面积“自愿弃风”。
现象:风资源充足时段,结果里风电出力明显低于预测值,而煤耗机组还开着。原因:lambda_wind 只有 20,而煤耗成本折算到单位电量是 150 上下,求解器理性地选择了弃风。解决:把弃风惩罚调到峰谷价差的 1.5 倍以上重跑;顺便做一次敏感性扫描,绘制“弃风惩罚系数 vs 弃风电量”曲线,找到拐点。这一步不是调代码,是调策略——系数本身代表决策者对新能源消纳的重视程度。
5. 设计取舍:经济最优不等于运行最优,调度代码该怎么迭代
方案带来的最深层问题是:经济最优解在真实系统里可能是脆弱的。MILP 给出的“全局最优”建立在预测曲线、机组参数全准确的假设上,而实际运行中热负荷预测偏差、风电波动、机组爬坡速率受限,都可能让这个最优解在闭环上偏离预期。遇到这种情况,常见的应对思路是放弃单点最优而改成“多场景鲁棒调度”——用悲观、乐观、基准三套风功率场景各跑一遍,然后取最保守的调度点,再叠加储热罐的调节余量。这等价于用少量计算量换稳定性,工程上比执着追求那一两个百分点的成本节约要回报更高。
代码层面我一般会这么理解三个变体脚本的取舍:jiayibanchure.m、jiaxiangbianchure.m和weijiachure.m本质上是同一套模型的三个参数配置。当前工程的分文件方式验证速度快,但等你要做敏感性分析时就会感受到它的代价——每个参数都得改三个文件,改漏一个结果就漂了。更稳妥的演进是抽出统一的build_gen_model()函数,把储热类型作为结构体字段传入,数据表里多存一列storage_type用来区分。这样“代码优化”就从改脚本迈进到改模型架构,调参、测试、回溯都省力得多。
从那以后,我每次改调度约束都强制自己先跑一遍残差诊断和 SOC 边界检查,确认热平衡硬约束和储热状态方程都没被破坏,再谈目标函数的数值漂移。调度代码的翻车往往不在优化算法本身,而在约束矩阵拼接和数据单位这种“看起来不可能错”的地方——多留一个诊断入口,比多信一次求解器的退出标志可靠得多。希望帮到你。
本文还有配套的精品资源,点击获取