北方供暖季的电力调度圈里,一直有个让人头疼的矛盾:那边风大的时候风电出力呼呼往上蹿,这边热电机组为了保供热只能硬顶着高负荷运行,电网消纳空间被挤占,最后只能眼睁睁看着风电场弃风。搞过调度优化的人都清楚,这背后是热电机组“以热定电”的刚性约束在作祟。今天想和你聊的,就是一套面向风电最大化消纳的热电联产机组联合优化控制方案,配套完整Matlab代码,把热电协调、优化建模、滚动求解这条链路整个走通。无论你是电力系统方向的研究生、做新能源消纳的工程师,还是刚接触优化调度的初学者,这篇内容都能给你一套可以直接抄作业的框架。
1. 问题本质:为什么热电机组会“吃掉”风电的消纳空间
1.1 “以热定电”约束到底是怎么来的
先看物理层面的原因。抽汽式热电联产机组和纯凝火电机组最大的区别在于,它除了发电之外还要对外供应采暖抽汽。中压缸做完功的蒸汽一部分被抽出去加热热网循环水,剩余部分才进入低压缸继续做功发电。抽汽量一旦增加,进入低压缸的蒸汽量就减少,发电功率上限跟着往下压;反过来,如果热负荷需求很高,机组为了供够热,电出力就被锁定在一个较高的水平附近动弹不得,这就是“以热定电”的由来。
在Matlab建模里,这个关系通常用热电耦合可行域来描述。最常见的是把机组的电出力上下限表示成抽汽量的分段线性函数,比如电出力下限不是常数,而是随着供热抽汽量增大而抬升的一条折线。换句话说,冬季热负荷上去之后,机组的调峰下限跟着抬高,整个系统的调峰能力被大幅削掉。风电大发的时候,电网需要火电让出空间,但热电机组让不出来,弃风就这样产生了。
1.2 联合优化解决的不是“单机问题”,而是“系统协调问题”
很多人一开始会问,为什么不直接让热电机组降电出力?因为在纯凝运行模式下,电出力下降是自由的,但在供热期,降低电出力意味着抽汽量结构要重新分配,而热力系统有一个实时平衡约束在里面——热网用户端的采暖需求必须被满足,热负荷不能像电负荷那样随心所欲地削减。所以问题的本质是一个多设备、多时段、热电耦合的系统级协调优化,而不是单台机组的局部调节。
这就要引入联合优化的思维。把风电场、热电机组、热负荷放在同一个优化框架里,用经济性目标(煤耗成本、弃风惩罚)驱动,在满足热平衡和电平衡的前提下,让风电尽量多上网。这里面自由度不止来自机组本身,还来自热网侧的惯性、蓄热特性以及多台机组之间的出力分配。一套设计良好的优化控制器,能在风电预测出力高的时段,提前调整热电机组的供热分配,为风电腾挪出空间。
1.3 几条技术路线的取舍:为什么选择联合优化控制
围绕热电联产促进风电消纳,业内主要有三条技术路线。第一条是加装储热罐,在热负荷低谷时把富裕热量存起来,等到风电大发时释放储热替代热电机组供热,从而压低机组电出力。第二条是配置电锅炉,直接用电加热替代部分热负荷,但这会额外增加耗电,经济性需要细算。第三条就是今天要展开的热电联产机组联合优化控制——不动硬件,靠优化调度算法挖掘系统本身闲置的调节能力。
三条路线各有适用场景。加装储热罐要额外投资,电锅炉有能效损耗,而联合优化控制只需要在控制层做文章,把已有的机组和热网资源重新组织一遍,边际成本低,见效快。实际工程里这三者往往还会组合使用,但作为起步和科研验证,先把联合优化控制这条主线跑通,再叠加储热或电锅炉,整个技术逻辑会顺畅很多。
2. 搭建优化模型:目标函数、约束条件与方案选型
2.1 目标函数怎么设才合理
优化模型的核心是目标函数,它直接决定了优化器朝哪个方向搜索。针对风电最大化消纳场景,我建议采用“煤耗成本 + 弃风惩罚”两段式的结构。煤耗成本部分通常拟合成关于电出力和热出力的二次函数,或者简化后采用分段线性函数。弃风惩罚部分设置一个单位电量的惩罚系数,这个系数要设得足够大,保证优化结果里不会出现无谓的弃风,但又不能太大到掩盖其他经济性信号。
一个常用的目标函数表达形式是:
objective = sum(sum(a .* P_chp.^2 + b .* P_chp + c)) ... % 煤耗成本 + penalty_wind * sum(sum(P_wind_avail - P_wind_use)); % 弃风惩罚其中 (P_chp) 是热电机组的电出力矩阵,维度是机组数乘以时段数;(P_wind_avail) 是风电预测可用出力,(P_wind_use) 是优化后实际消纳的风电功率。惩罚系数 (penalty_wind) 一经调大,优化器会自动把弃风量压到接近零,前提是约束条件允许。
2.2 约束条件梳理:一个都不能少
约束条件是整个模型里最见功力、也最容易出错的部分。我梳理了五个必须考虑的方面:
电功率平衡约束:每个时段内,所有机组电出力加上风电消纳功率,必须等于该时段的系统电负荷。这是硬约束,物理上必须守恒。
热功率平衡约束:每个时段内,热电机组的供热功率加上其他热源出力(如果有的话),必须等于热负荷。这里要注意热负荷的单位,工程上常用GJ/h或者MW,建模前统一换算。
机组运行可行域约束:电出力上下限、热出力上下限,以及热电耦合带来的限制。耦合部分可以通过定义一组顶点,用凸组合的方式描述可行域,或者直接写成分段线性不等式。
爬坡约束:机组出力在相邻时段间的变化速率限制。供热期热电机组受限于热网调节速率,爬坡率往往比纯凝工况更保守,设置时要留余量。
风电出力约束:实际消纳的风电功率必须在0和预测可用功率之间,这是决定弃风量的关键约束。
2.3 求解方法选型:为什么优先考虑混合整数线性规划
模型规模不算大的时候,求解方法的选择对结果影响不大,一旦机组数量和调度周期拉上去,方法选型就成了成败关键。我常用的做法是把问题转化成混合整数线性规划(MILP),用YALMIP在Matlab里建模,然后调用商业求解器求解。
之所以不直接上来就用遗传算法、粒子群这类智能算法,是因为它们没法保证找到全局最优,而且每次结果还有随机波动,不利于做工程复现和严谨的对比分析。MILP求解器在中小规模问题上能稳定给出全局最优解,而且求解时间完全可以接受。如果你手头没有商业求解器许可证,开源的求解器也能满足教学和科研的基本需求,只是求解速度会慢一些。
3. Matlab代码实现:从数据准备到求解的全流程解析
3.1 代码整体架构:模块化怎么拆
一个能稳定运行的优化程序,代码结构比你想的重要得多。我习惯把整个项目拆成五个模块:数据初始化模块、模型参数设置模块、优化模型构建模块、求解与结果输出模块、以及绘图可视化模块。每个模块单独一个脚本或函数,互不干扰,后期改参数、换数据都很方便。
数据初始化模块里,你需要定义时间步长(一般取1小时,精细调度可以取15分钟)、调度周期(典型取24小时或48小时)、机组台数、风电场容量等基础参数。模型参数设置模块里放机组的热电特性曲线系数、爬坡率、煤耗系数、热负荷和电负荷序列。优化模型构建模块是核心,用YALMIP定义决策变量、写约束、拼目标函数。求解模块调用求解器并读取结果。可视化模块把风电消纳、机组出力、热平衡这些关键量画出来,一眼看出优化效果。
3.2 关键代码解读:决策变量与约束的写法
决策变量定义是整个建模过程的第一步,也是最容易让人卡壳的地方。以 (N) 台机组、(T) 个时段为例,定义电出力矩阵 (P) 为 (N \times T) 的连续变量,热出力矩阵 (H) 同样为 (N \times T)。另外定义风电消纳向量 (P_wind),维度是 (1 \times T)。
P = sdpvar(N, T); % 热电机组电出力 H = sdpvar(N, T); % 热电机组热出力 P_wind = sdpvar(1, T); % 风电实际消纳功率约束加载时,电功率平衡约束这样写:
Constraints = []; Constraints = [Constraints, sum(P, 1) + P_wind == P_load];热功率平衡同理:
Constraints = [Constraints, sum(H, 1) == H_load];机组爬坡约束要特别注意首末时段衔接问题,一般是 (P(:, t) - P(:, t-1)) 在爬坡速率允许范围内,同时也要把最后一个时段和第一个时段连起来,因为这是一个周期性的调度问题。
3.3 数据准备:风电出力曲线和热负荷怎么定
数据准备阶段做得好,后面求解阶段就顺。风电出力曲线建议用典型日的实测数据或仿真数据,如果手头没有,可以用正弦叠加随机扰动来生成一条带有波动特征的出力序列,但要保证数据本身符合物理规律——比如夜间风速大、白天风速小的典型反调峰特性。热负荷曲线在北方供暖期基本跟室外温度走,呈缓慢波动的特性,不会像电负荷那样尖峰频现。
还有一个细节是热负荷单位与机组热出力单位必须统一。有的文献用GJ/h,有的用MW,两者其实只差一个常数换算关系,但不统一就会导致约束加载后量纲错乱,求解结果完全失真。我建议全工程统一采用MW作为功率单位,热负荷数值直接按热量换算关系折算。
3.4 求解设置与结果输出
调用求解器的代码非常简单:
ops = sdpsettings('solver', 'gurobi', 'verbose', 1); optimize(Constraints, objective, ops);求解完成后别急着收工,先查一下求解器的退出标志。如果返回的是infeasible,说明约束集合存在矛盾;如果是数值警告,可能要检查数据是否跨数量级过大。结果读取时把sdpvar的value()取出来,转成double存好,供后续画图和分析使用。一个完整的求解记录还应该包含总成本、弃风电量、机组启停状态这些指标,方便做方案对比。
4. 常见问题与排查技巧实录
4.1 求解器报infeasible问题,怎么定位
这是新手最容易崩溃的提示。infeasible意味着你写的约束集合里根本没有可行解,原因经常出在电平衡或热平衡被过度约束。排查思路很简单,先把除平衡约束以外的约束注释掉,看能不能求解;能求解就逐条加回来,定位出是哪组约束把可行域压成了空集。我碰到过最隐蔽的情况是热负荷数据里个别时段为负,而且机组热出力下限为正,这组约束自然永远无法满足。把数据画出来,肉眼扫一遍最高最低值,很多低级错误一眼就暴露了。
4.2 求解时间过长的优化手段
模型规模超过一定阈值后,求解速度会明显变慢。几个实用手段:一是对连续变量不要加多余的整数约束,整数变量是求解时间的大头,能避免就避免;二是分段线性逼近机组特性时,段数不要贪多,段数越多变量越多,精度提升却很有限;三是给求解器设置时间上限或MIP gap容忍度,工程场景下MIP gap在1%以内已经完全够用,不必强求绝对最优。
4.3 结果里风电消纳量很低,怎么排查
如果优化结果出来风电消纳量出乎意料地低,首先怀疑风电约束是否写对。检查 (P_wind \le P_wind_avail) 这组约束有没有落进去,其次检查电功率平衡约束等式右侧的电负荷数据是否包含了风电的消纳空间。还有一种情况是目标函数里弃风惩罚系数设得太小,优化器宁可弃风也不想调整机组出力,这时候把惩罚系数增大几个数量级再跑一次,结果通常会恢复正常。
4.4 维度不一致的隐藏地雷
Matlab对矩阵维度极其敏感,sdpvar变量维度写错,约束拼接时就会报维度不匹配。这属于低级错误,但特别耗时。我的习惯是每定义一个变量,紧接着用size()输出维度确认;约束写好之后,再用一条打印语句把每个约束的维度打出来做个最终检查。高频词失误主要集中在这几个位置:求和时漏掉维度参数、转置没加、时段索引从1开始但数据文件里第0行是表头。
5. 方案扩展:从基础模型走向工程应用
5.1 加入储热装置后的模型变化
跑通基础的热电联产联合优化模型之后,下一步可以加入储热罐。储热罐本质上是一个能量时移装置,它的核心变量是储热容量 (S),动态方程是相邻时段储热量的变化等于充放热功率。约束里多了储热容量上下限和充放热功率上下限,目标函数不用大变,但优化自由度明显增加。正如前面所说,风电大发时段,储热可以代替热电机组承担热负荷,机组电出力下限约束就松开了,风电消纳空间自然变大。
5.2 与电锅炉联动的协同优化
电锅炉的优势在于它的热出力可以直接由电力驱动,把一部分电负荷转成热负荷,从另一个方向缓解热电耦合矛盾。加入电锅炉之后,电功率平衡约束的左边多了一项电锅炉耗电,热功率平衡约束的右边多了一项电锅炉发热。注意电锅炉的效率不是100%,要考虑热损失。经济上的权衡也很有意思——电锅炉耗电的费用和它替代热电联产发电减少的煤耗成本之间,存在一个最优配比,这个配比正好交给优化器去找。
5.3 考虑风电预测误差的鲁棒优化框架
风电预测不可能完全准确,工程应用中必须考虑误差。一种务实的做法是采用场景法——抽出一组典型预测误差场景,把确定性模型扩展成多场景随机规划,目标函数变成各场景成本的期望值。这样模型规模会成倍增长,但得到的结果对误差的适应性会好很多。如果求解压力过大,也可以退一步用鲁棒优化,只考虑预测出力的置信区间边界,求得一个在极端场景下也不越限的调度方案,计算代价比随机规划小得多。
6. 实操心得与学习路径建议
我对这类项目的最大体会是:模型的物理合理性和数据的质量,远比算法的花哨程度重要。很多同学拿到题目就急着调算法,结果在错误的数据和错乱的单位上反复折腾,浪费大量时间。在动手写代码之前,先把热电机组的热电耦合特性在坐标系里画出来,把一天的热负荷曲线和风电曲线放在一张图上观察它们的峰谷关系——你对问题的理解深度,直接决定模型的表达高度。
学习路径上,建议先手工模拟一个2台机组、24时段的简化算例,把电平衡、热平衡、爬坡约束一条一条手写出来,理解每个约束存在的物理意义,然后再上Matlab+YALMIP实现。跑通之后再做灵敏度分析,逐个调整惩罚系数、负荷水平、风电渗透率,观察对结果的影响,这样才算真正吃透了这套模型。
最后再分享一个小技巧:所有数据文件、约束定义和结果指标,统一用一个带版本号的配置脚本管理,每次实验改了什么参数、出了什么结果、对应哪份曲线,都记录在案。优化控制类项目最怕的就是改来改去忘了基线在哪,有了这套管理习惯,复现结论和写论文的效率都会高出不少。