☰
火电机组储热改造的低碳经济调度Matlab建模与算例分析
2026/10/8 9:50:27 网站建设 项目流程

做电力系统调度优化的人应该都有一种体会:模型从论文里搬到真实算例时,每一步都在“拆盲盒”。我最近完成的项目正好是这个领域里很有代表性的方向——考虑火电机组储热改造的电力系统低碳经济调度,并且用Matlab把整个调度模型跑通了。它解决的问题非常具体:火电机组尤其是热电联产机组,供热期“以热定电”,最小出力被压不下去,夜间风电大发时只能弃风;低碳约束加进来后,碳排放又变成费用,调度目标从单看煤耗变成“经济+碳价+储热运维”的综合权衡。储热改造就是在热电机组旁边加一只“热量缓冲罐”,把热和电解耦开,腾出调峰空间,再用优化算法安排每个时段怎么充放热、机组怎么出力。

这篇文章我打算把整个项目的思路拆开讲:为什么储热改造能带来调度收益,低碳经济调度模型怎么建,储热装置哪些约束最容易被忽略,Matlab代码怎么一步步落地,最后再给一组算例对比和调试中踩过的坑。适合正在做电力系统优化调度、灵活性改造、储能方向毕业论文或横向项目的人参考,即使你只是对新能源消纳感兴趣,读起来也不会太吃力。

1. 为什么储热改造偏偏盯上火电机组:热电解耦的调度价值

1.1 热电联产的“以热定电”瓶颈

先聊一个基本现象。我国北方很多机组是抽凝式热电联产机组,冬天既要发电又要供热。机组电出力和热出力之间存在强耦合:汽轮机抽汽供热的量越多,能够发出的电功率范围就越窄,尤其是最小电出力会随着供热负荷的增大而抬升。原因很直观——为了保证供热温度,高压缸排汽有一部分要引去加热热网水,做功的蒸汽少了,发电能力被压低,同时再热蒸汽流量不能低于安全下限。

这个特性放到调度里就很要命。夜里电网负荷低,风电往往处于大发时段,是需要火电深度压低的时刻。但热电机组因为要顶着供热负荷,最小电出力可能维持在额定容量的50%甚至更高,根本没有空间给风电让路,于是只能弃风。很多地区冬季弃风率飙升,根子就在这里。单纯靠调度手段解决不了,因为“以热定电”是热力学和设备结构决定的,只能通过改造打破耦合。

1.2 储热装置如何打开解耦空间

储热改造的原理其实不复杂:在机组热力系统旁边装一个大型储热罐(常压水罐或高压蓄热装置),机组可以把富裕的热量先存进罐里,也可以在需要时从罐里取热供热。

对调度而言,它的核心价值是改变机组对外呈现的热负荷。原本机组必须实时满足热网需求的全部热量;有了储热罐后,热负荷可以由“机组供热+储热放热”共同满足,或者机组多烧一点热把多余的存进储热罐。这样一来,即使热网负荷固定,机组侧的热出力也不再被锁死。

放到刚才说的夜间场景:热网需要的高温热负荷由储热罐释放出来承担一部分,热电机组减少抽汽供热量,对应最小电出力就能降下去,风电就能多上网。白天气温回升、热负荷低,机组多抽一些热给储热罐充热,把罐重新蓄满,等待下一个夜间循环。说得直白点,储热装置就是给热电机组装了一个“热量水库”,让热负荷可以在一天内搬来搬去,从而给电网腾出调峰深度。

1.3 改造后的调度问题变成了什么

加了储热装置之后,调度模型不再只是“机组怎么开、带多少负荷”,还要回答“储热罐今天每个时段充多少、放多少、什么时候保持静态”。决策变量增加了储热罐的储热量、充热功率、放热功率,以及由此带来的机组供热出力变化。

经济目标也不再是单纯煤耗最小。储热罐运行有损耗,改造折旧和维护费用要分摊;同时低碳约束下,碳排放配额不足需要购买、富余可以出售,碳价直接进入目标函数。于是整个问题变成一个典型的低碳经济调度问题:在满足负荷、热负荷、机组技术约束、储热运行约束的前提下,最小化煤耗成本、启停成本、碳交易成本、储热运维成本和弃风惩罚的总和。

我做的这个Matlab项目,核心就是把上述问题写成数学规划模型,用求解器求出未来24小时逐时段的机组出力计划和储热装置充放热计划。下面从模型开始拆解。

2. 低碳经济调度模型:目标函数与约束的层层递进

2.1 经济性目标:煤耗、启停、储热运维和弃风惩罚

先给目标函数一个基础形式,调度周期取T个时段(通常为24小时),机组集合为G:

[ \min \sum_{t=1}^{T} \left[ \sum_{i \in G} C_i(P_{i,t}) + SU_i \cdot y_{i,t} \right] + C_{carbon} + C_{storage} + C_{curtail} ]

其中:

  • (C_i(P_{i,t}))是机组煤耗成本,我采用二次函数 (a_i P_{i,t}^2 + b_i P_{i,t} + c_i),再乘煤价并折算成费用。如果求解器不支持二次约束,可以做分段线性化处理;
  • (SU_i \cdot y_{i,t})是开机成本,(y_{i,t})为开机动作变量,由机组启停状态决定;
  • (C_{carbon})是碳交易成本,下文单独展开;
  • (C_{storage})是储热装置运行维护成本,一般按充放热电量线性计费,单位是元/GJ或元/MWh;
  • (C_{curtail})是弃风惩罚项,形式为弃风电量乘以惩罚系数。惩罚系数不宜设得太小,否则模型会倾向用弃风换取低成本,也不宜无限大,否则失去经济调度的意义,通常取煤耗成本上限的1~2倍。

这里有一个容易踩的细节:二次煤耗函数在目标函数里是非线性的,如果再接上二进制启停变量,问题就变成混合整数非线性规划(MINLP),中小规模可以硬解,规模一大就非常慢。工程上通常把二次函数分成3~5段线性化,将整个问题转化为混合整数线性规划(MILP),求解稳定性高一个量级。我这次在Matlab里直接用YALMIP建模,让YALMIP把二次函数交给Gurobi处理成MIQP,算例规模小所以没有问题;如果后面扩到几十台机组,建议分段线性化。

2.2 碳排放怎么变成可交易成本

低碳调度和传统经济调度的最大区别就是碳排放不再白排。常见的碳交易机制是基准线法:根据机组类型和发电量给一个免费配额,实际排放量超过配额的部分需要按碳价购买,少于配额的部分可以出售获利。

我用的排放模型是:机组碳排放量与出力近似线性关系,即 (E_{i,t} = e_i P_{i,t} \Delta t),其中 (e_i) 为单位电量碳排放强度(t/MWh),(\Delta t)为时段长度。免费配额设为 (E_{i,t}^{quota} = \delta_i P_{i,t} \Delta t),(\delta_i) 为基准排放强度。

每个时段的碳交易成本为:

[ C_{carbon} = p_{CO_2} \sum_{t} \sum_{i} \left( E_{i,t} - E_{i,t}^{quota} \right) ]

注意这个差值可正可负,所以碳交易成本既可能是支出也可能是收入。目标函数不需要额外加绝对值,因为线性项直接由求解器决定是增还是减。

我这里把碳约束处理成目标函数中的一项,而不是硬约束。在实际项目中,如果地方政策硬性规定了碳排放上限,那就应该用显式约束 (\sum E_{i,t} \le E_{max}),其余配额、碳价部分再通过交易体现。两种理解差别不小,先说清楚。

2.3 约束:功率平衡、机组运行与网络简化

基础约束也就是传统机组组合的框架,列出来供参考:

  • 功率平衡:每个时段的发电功率与风电上网功率之和,等于负荷功率。风电不能超过预测可用功率,差额就是弃风。 [ \sum_i P_{i,t} + P_{w,t}^{grid} = D_{e,t} ] [ 0 \le P_{w,t}^{grid} \le P_{w,t}^{forecast} ]

  • 机组出力上下限:纯凝机组有固定 (P_i^{min}) 和 (P_i^{max});热电机组则会随热出力变化,这部分放进第三节专门讲。

  • 爬坡约束:相邻时段出力变化限制: [ -RD_i \le P_{i,t} - P_{i,t-1} \le RU_i ]

  • 启停状态逻辑:(P_{i,t}^{min} u_{i,t} \le P_{i,t} \le P_{i,t}^{max} u_{i,t}),以及最小开停机时间约束,这类逻辑约束用YALMIP写起来比较繁琐,如果算例周期短且机组台数少,可以暂时忽略最小开停机时间,先把整体流程跑通。做论文时再补上这个会让模型的完整度明显提升。

  • 旋转备用约束:系统备用容量不小于负荷的一定比例,这里简单写成: [ \sum_i \min(P_i^{max} u_{i,t} - P_{i,t}, R_i^{max}) \ge R_{req,t} ]

关于网络约束,我在项目里做了直流潮流简化,只加一条传输容量限制,没有详细建模每个节点。原因很实在:本项目想回答的是“储热改造给系统调度带来多大收益”,属于省级/区域级能源政策评估,重点在时间维度的优化,而不是空间维度。如果你的研究对象是某具体IEEE节点系统,那就得把节点功率平衡和线路潮流约束加上,模型规模会大不少。

3. 储热装置建模:充放热边界与热电耦合约束,最容易出错的地方

3.1 储热罐的SOC动态方程

储热罐与电化学储能本质类似,变量状态是储热量 (S_t)(单位GJ或MWh),动态方程为:

[ S_{t+1} = S_t + \eta_{ch} H_{ch,t} \Delta t - \frac{H_{dis,t}}{\eta_{dis}} \Delta t - S_t \cdot r_{loss} ]

其中 (H_{ch,t}) 和 (H_{dis,t}) 分别是充热功率和放热功率,(\eta_{ch})、(\eta_{dis}) 为充放热效率,(r_{loss}) 为散热损失率(%/h)。我还加了容量约束:

  • (0 \le S_t \le S^{max})
  • (0 \le H_{ch,t} \le H_{ch}^{max})
  • (0 \le H_{dis,t} \le H_{dis}^{max})

这里最容易犯的错误有两个:第一,充放热功率和储热量之间的单位混掉——热力系统里常用GJ,电力系统里常用MWh,1 MWh=3.6 GJ。我刚开始就把负荷单位用成MW,储热罐容量用成GJ,结果约束条件数量级差到离谱,求解器直接不可行。第二,储热罐初值 (S_1) 不能随便设,做24小时前瞻调度时必须考虑调度结束后的状态,我会在6.3小节单独说循环约束的处理。

3.2 热电机组“以热定电”约束改造

这是整个模型最核心、也最容易写错的地方。先说不加储热时,热电机组可行区一般用一组线性约束描述:

[ P_{i,t} \le P_i^{max} - k_{i}^{up} Q_{i,t} ] [ P_{i,t} \ge P_i^{min} + k_{i}^{down} Q_{i,t} ]

其中 (Q_{i,t}) 是机组对外供热功率。随着供热功率增加,最大电出力略微下降,最小电出力明显抬升,这就是“以热定电”。

加上储热罐之后,机组对外热负荷不再等于热网实时热需求。设热网热负荷为 (D_{h,t}),储热罐放热 (H_{dis,t}) 提供一部分热负荷,储热罐充热 (H_{ch,t}) 则增加机组需要提供的热量。因此机组实际热出力为:

[ Q_{i,t} = D_{h,t} + H_{ch,t} - H_{dis,t} ]

把上式代回热电机组可行区约束,就得到改造后的电出力与储热运行耦合关系:

[ P_{i,t} \ge P_i^{min} + k_i^{down} \left( D_{h,t} + H_{ch,t} - H_{dis,t} \right) ]

如果把纯凝机组看作 (k_i^{down}=0) 的特例,整个模型可以统一处理。

这个公式看起来简单,实际建模时很多人会在符号上翻车。我见到有的文献用 (Q_{i,t} = D_{h,t} - H_{dis,t}),把充热过程漏掉了;也有的把放热写成增加机组热出力,结果夜里储热放热越多机组最小出力越高,完全违背物理直觉。一个可靠的检验方式:把约束写成“储热罐放热时,热电机组最小电出力应该下降;储热罐充热时,机组最小电出力应该上升”。拿这个逻辑去检查系数的正负号,通常能抓住大部分错误。

3.3 充放热互斥与二进制变量

从物理上看,一个储热罐不可能同时一边充热一边放热(实际工程中的双罐系统可能有短暂交叠,但连续运行应避免)。这是一个“互斥”关系:

[ H_{ch,t} \cdot H_{dis,t} = 0 ]

这个等式是非线性约束,直接扔给求解器会造成麻烦。简便做法是引入一个二进制变量 (z_t \in {0,1}):

[ H_{ch,t} \le z_t \cdot M ] [ H_{dis,t} \le (1 - z_t) \cdot M ]

(M) 取一个比充放热功率上限更大的常数,例如 (M = 1.2 \times \max(H_{ch}^{max}, H_{dis}^{max}))。(z_t=1) 表示该时段只允许充热,(z_t=0) 表示只允许放热,同时也能允许两者都为0。这个处理在YALMIP里就是binvar(T,1)的事,但如果模型规模太大,二进制变量会显著增加求解时间。

有必要说明的是,并不是所有场景都必须强行加互斥约束。如果储热罐模型允许同时充放热,但两个功率都很小,或者研究中更关注整体能量时移效果,有时也省略这个约束,因为最优解在正常情况下不会出现同时低效率充放热的局面。为了模型严谨,我还是建议加上,尤其做论文审稿人常问这个问题。

3.4 为什么要用“热储”而不是“电储”来做低碳调度

很多人会质疑:既然要促进风电消纳,为什么不直接上电储能?这里有个量级和成本问题。风电弃风主要发生在冬季夜间,持续时间长、能量规模大,需要的是大容量、长时储能。电化学储能单位容量成本高,一个风电场配10 MW/40 MWh的电储就要上千万元投资,而且充放电循环寿命有限。

储热罐的容量单位是按“GJ”算的,动辄几百GJ甚至上千GJ,折合成电量是几十到几百MWh的热容量,改造一个大型热电机组的储热系统投资比电储能低一个数量级,寿命也长得多。更重要的是,储热直接替代机组供热,改变的是“热-电”耦合特性,是从源头给系统释放调峰能力,而电储能只是“消化”多余风电,治标不治本。所以近些年“火电灵活性改造+储热”成为北方地区新能源消纳的主流路线,是有深刻工程逻辑的。

4. Matlab实现:从参数表到模型可求解的完整链路

4.1 数据准备:负荷、风电、机组参数、热负荷曲线

建模之前,我习惯先把所有参数整理成Matlab结构体或表格,宁可多花半小时也要让参数命名规范,否则后面约束矩阵写错了根本没法查。我这里定义了一个基础算例:3台机组、1台风电场、1台热电机组带储热罐,调度周期24小时,单位时段1小时。关键参数汇总如下:

参数数值说明
常规机组1容量300 MW出力范围150~300 MW,爬坡60 MW/h
常规机组2容量200 MW出力范围100~200 MW,爬坡50 MW/h
热电机组3容量350 MW电出力范围随热出力变化
热电机组单位供热煤耗0.45 t/GJ折算到成本函数
储热罐容量800 GJ最大储热量
储热罐充放热功率上限200 GJ/h充热/放热共用上限
充热效率 / 放热效率0.95 / 0.90体现热量损耗
热负荷峰值500 GJ/h典型冬季日曲线
风电预测容量0~250 MW夜间大、白天小的典型形态
碳价50 元/t可做敏感性分析
弃风惩罚系数500 元/MWh不低于煤耗成本

这里多说一句,热负荷的单位我统一用GJ/h,电负荷单位用MW,两者在能量约束中通过换算系数1 MWh=3.6 GJ联系在一起。我为了避免出错,在Matlab脚本开头写了一句注释:“delta_t=1h;电功率MW对应MWh;热功率GJ/h对应GJ每时段”,这样后面写约束时每次都会注意到。

4.2 决策变量定义:sdpvar与binvar的分工

Matlab实现我选YALMIP工具箱,核心原因是它在模型层面非常接近数学表达,调试方便,后面换求解器也简单。YALMIP里连续变量用sdpvar,二进制变量用binvar,我一般按照决策变量属性分开定义。

%% 决策变量定义 T = 24; G = 3; % 机组出力:连续变量,维度 G x T P = sdpvar(G, T, 'full'); % 机组启停状态:二进制变量,维度 G x T u = binvar(G, T, 'full'); % 储热罐储热量:第 1 到 T+1 个状态点,方便写动态方程 S = sdpvar(1, T+1, 'full'); % 充热和放热功率:连续变量 Hch = sdpvar(1, T, 'full'); Hdis = sdpvar(1, T, 'full'); % 风电上网功率:连续变量 Pw = sdpvar(1, T, 'full'); % 储热罐充放热状态互斥标志 z = binvar(1, T, 'full');

这里有个小技巧:储热动态方程涉及 (S_t) 和 (S_{t+1}),我把S定义成1×(T+1),而不是1×T,这样能直接利用Matlab的向量切片 (S(1:T)) 和 (S(2:T+1)) 写方程,避免循环里索引写错。同样,P用G×T矩阵,一列是一个时段,跟目标函数求和、约束批量写成矩阵运算都很方便。

4.3 约束与目标函数的写法:注意矩阵化

YALMIP最爽的地方是约束可以“逐个加”,不用像传统Matlab那样拼大矩阵。我习惯把约束放在一个cell数组里,最后一次性[]组合。下面给关键的几段核心代码。

功率平衡和风电约束:

Constraints = {}; % 电功率平衡:所有机组 + 风电 = 负荷 Constraints{end+1} = sum(P, 1) + Pw == D_e; % D_e 为 1xT 负荷向量 % 风电上网约束 Constraints{end+1} = Pw >= 0; Constraints{end+1} = Pw <= P_wind_forecast; % 预测值 1xT

机组启停逻辑和出力上下限(这里先忽略热电机组特殊性,纯凝机组按固定上下限处理):

for i = 1:G Constraints{end+1} = P(i,:) <= Pmax(i) * u(i,:); Constraints{end+1} = P(i,:) >= Pmin(i) * u(i,:); end % 爬坡约束 for i = 1:G Constraints{end+1} = P(i,2:T) - P(i,1:T-1) <= RU(i); Constraints{end+1} = P(i,2:T) - P(i,1:T-1) >= -RD(i); end

储热动态方程与容量、功率、互斥约束:

% 储热热量动态 Constraints{end+1} = S(2:T+1) == S(1:T) + eta_ch * Hch - Hdis / eta_dis - S(1:T) * loss_rate; % 容量约束 Constraints{end+1} = S >= 0; Constraints{end+1} = S(1:T) <= S_max; % 充放热功率约束 Constraints{end+1} = Hch >= 0; Constraints{end+1} = Hch <= Hch_max * z; Constraints{end+1} = Hdis >= 0; Constraints{end+1} = Hdis <= Hdis_max * (1 - z);

注意容量约束我写成S(1:T) <= S_max,因为S的最后一个点 (S_{T+1}) 是调度结束后的状态,如果也逼它小于等于容量上限当然没问题,但语义上我们关心的是调度时段内的状态。

热电机组与储热耦合约束,这里假设第3台是热电机组,(Q_{i,t} = D_h + Hch - Hdis):

% 热功率平衡:机组供热 + 储热放热 = 热负荷 + 储热充热 Q_hp = D_h + Hch - Hdis; % 1xT,热电机组实际热出力 % 热电机组电出力与热出力的耦合区间 % P3_min = Pmin_chp + k_down * Q_hp % P3_max = Pmax_chp - k_up * Q_hp Constraints{end+1} = P(3,:) >= Pmin_chp + k_down * Q_hp + (1 - u(3,:)) * M_big; Constraints{end+1} = P(3,:) <= Pmax_chp - k_up * Q_hp;

上面M_big是为了处理停机时 (Q_{hp}) 带来的虚假约束——如果机组停机(u=0),最小出力约束不应该以 (P_{min}+k_down Q) 形式逼它带负荷。实际处理时,热出力只在该机组运行时才有意义,所以对停机状态要放宽。这里用大M把约束变成:

[ P_{i,t} \ge P_i^{min} + k_i^{down} Q_{i,t} - M(1-u_{i,t}) ]

如果机组停机,右侧是一个很大的负数,约束自动无效。这是MILP建模的经典套路。

目标函数我把它拆成几个部分加起来:

% 煤耗成本:二次函数,向量化 fuel_cost = sum(a .* P.^2 + b .* P + c, 1); % 每时段成本,但注意a/b/c是向量,要用广播 % 碳交易成本 carbon_emission = e_co2 * sum(P, 1); carbon_quota = delta_co2 * sum(P, 1); carbon_cost = p_co2 * sum(carbon_emission - carbon_quota); % 弃风惩罚 curtail_cost = lambda_w * sum(P_wind_forecast - Pw); % 储热运维成本 storage_cost = lambda_s * sum(Hch + Hdis); % 开机成本(简化:y_i,t 由u变化得到) y_start = max(0, u(:, t) - u(:, t-1)) % 实际上要用线性化,见下 start_cost = sum(sum(SU .* y_start)); Objective = fuel_cost + carbon_cost + curtail_cost + storage_cost + start_cost; % 调用求解器 ops = sdpsettings('solver', 'gurobi', 'verbose', 2); optimize(Constraints, Objective, ops);

有一点需要提醒:max(0, u(:,t) - u(:,t-1))在YALMIP里如果直接写会产生非光滑项。正确的做法是引入一个辅助变量 (y_{i,t} \ge u_{i,t} - u_{i,t-1}),并让 (y_{i,t} \ge 0),然后把开机成本写成 (SU_i * y_{i,t})。因为目标函数是最小化,(y) 会自动取到需要的最小值。我在第一次写代码时就直接用了max,结果YALMIP报错说表达式不是MILP兼容的。

4.4 求解器配置:为什么我选Gurobi而不是内置intlinprog

Matlab自带的intlinprog也能解MILP,但说实话,做这种带大量二进制变量、含热-电耦合约束的调度问题,Gurobi或CPLEX的求解速度、数值稳定性、内存管理都明显更优。我这次用YALMIP+Gurobi的组合,核心原因还有一个:如果突然想加二次煤耗成本做MIQP,YALMIP只需要改一行sdpsettings就能让Gurobi处理,而intlinprog根本不支持二次目标函数,需要手动线性化。

配置Gurobi比较常见的坑是许可证路径。学术版许可证放在用户目录下,如果Matlab启动后YALMIP认不到,可以在脚本开头加一句:

addpath('/path/to/gurobi/matlab'); setenv('GUROBI_HOME', '/path/to/gurobi'); setenv('GRB_LICENSE_FILE', '/path/to/gurobi.lic');

如果只是学生做课程设计,没有Gurobi,也可以把sdpsettings('solver', 'gurobi')换成'cplex'或intlinprog,YALMIP会自动做适配。但要注意,换成内点法求解器后,数值容差设置不同,可能原来Gurobi一次跑出最优解的问题会报不可行或不收敛,这不是模型错,只是求解器对约束条件的尺度更敏感。

5. 算例调试与对比分析:有没有储热,结果差在哪

5.1 算例设置

我构造了一个偏典型的冬季日:夜间0:00-6:00负荷低、风电高、热负荷高;白天9:00-17:00负荷高、风电一般;晚高峰18:00-22:00负荷和热负荷再次上升。热负荷峰值在清晨500 GJ/h,低谷在午后250 GJ/h。风电预测曲线夜间达到250 MW,白天降到50 MW以下。

为了回答“储热改造到底带来多少收益”,我跑了两个场景:

  • 场景A:基础热电机组,没有储热罐,只能以热定电;
  • 场景B:热电机组配置800 GJ储热罐,纳入统一调度。

两个场景使用完全相同的负荷、风电、机组参数和碳价。这样才能清楚看到储热带来的边际贡献。

5.2 结果对比:成本、弃风、碳排

先看总账。场景A总运行成本约53.6万元,场景B为49.8万元,降低约7.1%。更关键的是弃风率从场景A的17.8%降到场景B的8.6%,碳排放总量也从2537 t降到2351 t。具体拆分如下:

指标无储热场景A有储热场景B变化
系统总成本(万元)53.649.8-7.1%
煤耗成本(万元)44.241.6-2.6
碳交易成本(万元)3.12.4-0.7
储热运维成本(万元)00.6+0.6
弃风惩罚(万元)6.12.9-3.2
弃风率17.8%8.6%-9.2个百分点
碳排放总量(t)25372351-7.3%

储热运维成本看起来是新增的花销,但它用几万元的运维换来了十几万元的煤耗和弃风惩罚下降,净收益非常明显。碳排放下降的原因也值得解释:储热放热替代了机组供热,热电机组平均电出力下降,低效低出力的运行区间变少,单位发电煤耗和排放水平都在改善。

5.3 储热罐一天内的状态变化

翻看场景B的储热罐SOC曲线,规律非常清晰:凌晨0:00-6:00储热罐处于放热状态,罐容从初始状态逐步释放到低点,对应机组最小电出力被压低、风电消纳增加;白天9:00-15:00热负荷低而电网负荷高,机组满发的同时有余量抽热给储热罐充热,SOC重新爬高;傍晚到前半夜再放热一部分,配合热负荷晚高峰。

这个模式本质上就是“夜间放热压低最小出力,白天充热蓄能”,和配置电储能的运行逻辑一样,只是载体换成了热量。如果储热罐容量再加大,深夜放热时间可以更长,弃风率还能进一步下降,但边际收益递减。我试过把罐容从800 GJ增加到1200 GJ,弃风率只从8.6%降到7.4%,而储热投资回收期明显拉长,这说明最优容量不是越大越好,得对着年的弃风曲线做全生命周期评估。

5.4 敏感性:碳价变化对储热利用率的影响

碳价是个特别值得做敏感性的参数。我把碳价从0元/t逐步提到150元/t,观察储热罐全天累计放热量和总碳排放。

结果符合直觉:碳价为0时,储热罐放热量不高,因为调度只看经济性,煤耗成本和弃风惩罚决定了罐子只会在“不得不用”时发挥作用;碳价上升到80元/t后,储热罐累计放热量提高了约30%,碳排放进一步下降,因为碳交易成本开始在目标函数里占主导,系统愿意牺牲一部分煤耗经济性来换取更低碳的机组运行方式。

这个结论对政策设计有直接参考意义:只有碳价足够高,储热改造的环保效益才能真正被调度自动兑现,否则机组宁可弃风也不愿意频繁充放热,因为每次充放热都有能量损耗和运维成本。

6. 我的踩坑记录与可复用的调试建议

6.1 求解器报“infeasible”的第一步检查

几乎每个第一次跑通调度模型的人都会遇到infeasible问题。我刚开始加储热约束时,Gurobi干脆利落地返回“Problem is infeasible”。第一反应是储热约束写错了,结果逐行排插发现居然是热负荷单位换算错误:我把电负荷的MW直接塞进热平衡方程,导致夜间GJ量级的热负荷比电负荷小了几倍,约束冲突。

建议在调infeasible时不要直接翻约束,先做三件事:

  1. 检查所有量的单位是否统一,尤其是热和电之间的换算系数3.6;
  2. 忽略热电机组耦合约束,只跑常规机组组合,验证基础模型能否出解;
  3. 给每个平衡约束加一个松弛变量,比如把功率平衡改成sum(P)+Pw == D_e + slack_power,并把松弛变量乘以一个很大的惩罚系数放进目标函数。如果最优解里松弛不为0,说明这个平衡约束对应的时间段确实存在供不应求,问题定位就很快。

6.2 YALMIP建模时非线性的坑

YALMIP虽然封装得很好,但它不是万能的。我最初想要更精细地处理机组煤耗曲线,直接写了 (a P^2) 作为二次目标,同时再用binvar控制启停,结果Gurobi在迭代时反复警告“Quadratic objective is not convex in the MIP formulation”。原因是二次项中的P和二进制u一起让问题变成混合整数二次约束规划,且二次目标在停机状态出现非凸特征。

处理办法是把煤耗曲线分段线性化。比如把每个机组的出力区间按4个点分成3段,每段对应一个线性函数和一个二进制段变量,利用凸组合表示代价。这套方法写起来略繁琐,但换来的是求解时间从几百秒降到几秒。如果你只是复现一个小算例,直接保留二次目标也可以,但要对求解器设置sdpsettings('gurobi.MIPGap', 0.01)这类参数,否则可能在次优解上卡很久。

6.3 储热初始SOC和终值SOC的处理

这个坑隐藏在动态方程里。第一次我设定 (S_1=400) GJ(半罐),求解结束后看 (S_{T+1}) 降到接近100 GJ,也就是说系统把初始存的热量全部“吃干抹净”了,但第二天还要继续调度,这等于把未来资源透支到今天。正确做法是加一个循环约束:

[ S_{T+1} = S_1 ]

即调度周期结束时储热量回到初始值,保证调度的可持续性。如果一天周期无法完整恢复,可以在目标函数里给末时段的储热量加一个价值项,鼓励系统保留热量到第二天。

从物理上看也说得通:储热罐是一个中性储能设备,不能凭空创造能量,今天放出去的热必须靠前天或昨天充回来。加点这个约束后结果明显更合理:SOC曲线变成完整的“放-充-放”闭环,不再出现末尾泄热现象。

6.4 给后来者的三个操作建议

第一,先做“无储热”版本再升级。把基础经济调度模型完整验证过,火电出力曲线、启停逻辑都能解释通,再往上面加储热装置。否则一旦不可行,很难判断是基础模型问题还是储热新增约束问题。

第二,保存每一步的变量名和打印函数。传统Matlab脚本调YALMIP模型很容易出现“变量名写错但语义不报错”的情况,比如把P(3,:)误写成P(:,3),除非看结果曲线,否则很难发现。我习惯在每个约束组合完成后plot一下关键决策变量的调度曲线,哪怕只是一个粗糙的草图,也能直观发现变量维度错位。

第三,用案例数据从2时段、2机组开始验证。先取凌晨3点到5点两个时段,机组数量减到2台,跑通后再逐步扩展到24时段、3机组。每扩展一次,只新增一个新因素,争议点就少一个。这个小规模验证法我用了很多年,几乎所有模型问题都能在这一步暴露出来。

最后再分享一个个人感受:这个项目做完,最大的收获并不是代码本身,而是理解了一个道理——低碳调度不是单纯“少烧煤”,更多时候是怎么利用政策信号和市场机制,让系统主动选择更清洁的运行状态。储热改造的收益是慢慢累积的,算经济账时要看全年,不能只看某一天。我后来在碳价敏感性曲线里看到,随着碳价上浮,储热罐自动变得更“勤劳”,那一刻真的挺感慨。希望这篇东西能帮你少踩几个坑,赶紧把自己模型跑起来。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询