每年一到供暖季,北方电网的调度台前就格外热闹。后半夜风场呼呼地满发,热网那边又必须保证供热温度,热电联产机组被“以热定电”四个字压得死死的,电出力降不下来,风电只能眼睁睁看着被弃掉。这场景我太熟了——前几年做新能源消纳评估时,几乎每个供暖期都能碰到类似的案例。后来我把研究重心转到热电联产机组的联合优化控制上,用Matlab搭了一套调度优化模型,把弃风率从百分之十几压到了个位数,思路和代码都沉淀了下来,今天整理成一篇实操向的总结。
这篇文章适合正在做电力系统优化调度、新能源消纳方向课题的研究生,也适合刚接触热电联产建模、想在Matlab里快速跑通一套优化控制代码的工程师。内容不绕弯子,直接从问题根源讲起,接着拆解优化模型怎么搭,再给出可运行的Matlab代码实现思路,最后把我在实际调试中踩过的坑一并列出来。看完你至少能自己复现一版“风电最大化消纳的热电联产机组联合优化控制”模型,而不是对着论文公式干瞪眼。
1. 风电消纳难,难在热电联产这个“热老虎”
1.1 弃风的核心矛盾:“以热定电”的刚性约束
先说一个很多初学优化调度的人容易忽略的事实:冬季弃风,问题一般不出在风电场,而出在热电厂。北方城市的供热主力是热电联产机组,这类机组和纯凝火电最大的区别在于——它既要发也要热,而且多数时候“热”说了算。为了满足采暖需求,机组必须保持一个较高的锅炉蒸发量,汽轮机抽汽或排汽供热后,发电出力就被限制在一个较窄的可调区间里。这就是业内常说的“以热定电”。
到了夜间低谷时段,系统负荷下降,风电往往处于大风期,本该多发。但热电机组为了供热,电出力根本压不到风电需要的水平,调度只能让风电场弃风。说白了,风电想多发但电网没有空间,空间被供热需求挤占了。一台300MW的抽汽式热电机组,冬季最小电出力可能就要到180MW甚至更高,如果热负荷再大一点,最小出力还能往上抬。一个风电场想顶替这部分空间?想都不要想。
我印象很深的一个实际案例是东北某地区,夜间热负荷高企时,两台抽汽机组最小电出力占掉了全网负荷的近四成,风电装机再多也送不出去。后来通过给机组配置储热罐,让热负荷高峰期由储热装置承担一部分供热,机组的电出力才能往下压,风电消纳空间才真正打开。所以聊联合优化控制,第一步就得先理解“热老虎”是怎么把风电空间吞掉的。
1.2 联合优化控制的思路:给热电机组“松绑”
搞清楚了矛盾根源,优化控制的思路就顺理成章了:让热电机组的热出力与电出力实现一定程度的“解耦”。解耦的手段主要有三种,我在实际项目里都试过:
- 配置储热罐:热负荷高峰时,储热罐放热替代部分机组供热,机组降电出力;热负荷低谷或风电大发时,机组多发电多产热,把热量存进储热罐。这是目前应用最广、改动最小的方案。
- 加装电锅炉:风电大发时,用风电给电锅炉供电,直接产生热能供热或加热储热介质,等于把原本要弃掉的风电转换成热能消化掉。
- 机组本体改造:比如低压缸切除技术,让机组在供热季电出力范围更宽。这种方案改造周期长、投资大,建模上也更复杂,一般放到规划层面考虑。
我这里讨论的联合优化控制,核心就是在“给定系统负荷、热负荷和风电预测出力”的前提下,通过调节各台热电机组的电出力、热出力以及储热罐的充放热功率,在满足所有运行约束的条件下,让风电上网电量最大化(或者说弃风量最小化)。调度周期通常是24小时,时间步长取1小时。这套模型在学术论文里叫“电热联合调度”,在工程上其实就是把传统发电计划里多了一个“热力平衡”维度。
1.3 适用场景与读者对象
直接说这套方法能用在哪:电网调度机构做日前计划、发电集团做厂级负荷分配、园区综合能源系统做供暖期运行优化,都能用。具体到Matlab实现层面,适用的场景有三个特点:一是系统里有热电联产机组;二是存在弃风或可再生能源消纳压力;三是运行优化周期为日前或日内滚动。如果同时满足这三条,你基本可以照搬本文思路。
如果你是刚入门的研究生,我建议先把我后面的代码框架读懂,跑通一个简单的两机组算例,再往里面加自己的创新点(比如不确定性、多目标、市场机制),这样后面改模型会顺手很多。如果你是工程岗,那重点看第四章的算例分析和第五章的调参经验,那些东西比理论公式更值钱。
2. 搭模型前先想清楚:优化什么、约束什么
2.1 目标函数:弃风量最小化与运行成本的权衡
建立一个优化模型,第一步不是写代码,而是把目标函数想明白。风电最大化消纳的目标函数,最直接的是取“系统弃风电量最小”。24小时调度周期里,弃风电量是每个时段预测风电出力和实际调度风电出力之差的总和,表达出来就是:
[ \min \sum_{t=1}^{T} \left( P_{w}^{fore}(t) - P_{w}^{sch}(t) \right) \cdot \Delta t ]
其中 ( P_{w}^{fore}(t) )是风电预测出力,( P_{w}^{sch}(t) )是调度安排的实际出力,两者之差就是弃风功率。
但实际工程里我几乎不会只用弃风量这一个目标,因为那样可能出现一种极端情况:为了让风电多发,热电机组频繁爬坡、储热罐过充过放,运行成本高得离谱。所以更常见的做法是“弃风惩罚+煤耗成本”的组合目标函数:
[ \min \sum_{t=1}^{T} \left( \sum_{i} f_i(P_i(t)) + \lambda \cdot \left( P_{w}^{fore}(t) - P_{w}^{sch}(t) \right) \right) \cdot \Delta t ]
( f_i(P_i(t)) )是机组煤耗成本函数,一般取二次函数拟合;( \lambda )是弃风惩罚系数,取一个比煤耗成本高但又不会高到扭曲调度的值。我在实际调试中,( \lambda )一般取500~1000元/MWh,这样既能让模型优先消纳风电,又不会让机组出力出现“为了多发风电让煤耗剧烈上升”的怪现象。
2.2 机组建模:抽汽式与背压式怎么处理
热电联产机组不是铁板一块,得分成两种类型建模。
抽汽式机组:这是最常见也最灵活的类型,它的电功率和热功率之间存在一个凸多边形可行域。简单说,机组最小电出力会随着热出力增大而上移,最大电出力在高热出力区也会略微受限。工程上可以简化成几条线性不等式描述这个可行域。最典型的约束形式是:
[ P_i^{min}(H_i) \le P_i \le P_i^{max}(H_i) ]
其中 ( P_i^{min} ) 和 ( P_i^{max} ) 都是热出力 ( H_i ) 的线性函数。处理技巧是把每条限制写成独立的不等式约束,Yalmip里直接一个个加进去就行。
背压式机组:这种机组的电出力和热出力严格成比例,电热比基本固定,没有独立调节空间。建模时不需要可行域,直接写一个线性等式:
[ P_i = k_i \cdot H_i ]
背压式的好处是热电效率高,但劣势也很明显——它没有任何灵活性,调度上只能作为“跟随热负荷的被动电源”。如果系统里有背压机组,它实际上会加剧弃风,因为供热越多、电出力就越多,挤压风电的空间就更小。所以很多优化研究都把重心放在抽汽式机组加储热上,背压机组一般保持固定工况运行。
2.3 储热装置建模与电热耦合约束
储热罐是这套联合优化里最关键的“调节器”。建模时用一阶能量平衡方程描述它的动态过程:
[ E_{s}(t+1) = E_{s}(t) + \left( H_{ch}(t) \cdot \eta_{ch} - \frac{H_{dis}(t)}{\eta_{dis}} \right) \cdot \Delta t ]
其中 ( E_s(t) ) 是储热罐在t时段末的储热量,( H_{ch} ) 和 ( H_{dis} ) 分别是充热和放热功率,( \eta_{ch} ) 和 ( \eta_{dis} ) 是充放热效率。别忘了限制充放热功率上下限,以及储热罐容量约束:
[ E_{s}^{min} \le E_s(t) \le E_{s}^{max} ]
还有一个损耗问题。实际储热罐24小时的热损失大概在2%~5%,如果调度周期短可以忽略,但如果做一周甚至更长时间的优化,就得加一个散热项。我在做7天滚动优化时,会在能量平衡方程里加一个 ( -\mu E_s(t) ) 的散热项,( \mu ) 取0.02左右,这样模型结果更接近实际。
电热耦合约束是连接“电”和“热”的桥梁。调度决策必须同时满足:电功率平衡方程、热功率平衡方程、机组电热可行域约束。三者缺一不可,否则模型跑出来就是“伪最优解”。
2.4 系统平衡约束与运行边界
下面把这些约束按类别列清楚,这也是我当年写代码时反复核对的内容。
电功率平衡约束:
[ \sum_{i=1}^{N} P_i(t) + P_{w}^{sch}(t) = P_{load}(t) ]
这是电网调度最基本的约束。实际系统中还要考虑网损和联络线功率,但研究性算例里通常先忽略,把系统看成一个单节点。
热功率平衡约束:
[ \sum_{i=1}^{N} H_i(t) + H_{dis}(t) - H_{ch}(t) = H_{load}(t) ]
注意充热和放热不能同时发生。建模时可以通过二进制变量约束,但在线性规划里也可以适当放松——因为目标函数是最小化成本,优化结果自然倾向于不同时充放。我在工程项目中加了互斥约束,但在教学算例里一般不强制,省得引入整数变量拖慢求解速度。
机组运行约束:
- 出力上下限:( P_i^{min} \le P_i(t) \le P_i^{max} )
- 爬坡约束:( -R_i^{down} \le P_i(t) - P_i(t-1) \le R_i^{up} )
- 热出力上下限:( H_i^{min} \le H_i(t) \le H_i^{max} )
风电出力约束:
[ 0 \le P_{w}^{sch}(t) \le P_{w}^{fore}(t) ]
意思是风电实际出力只能在零和预测值之间,不会超过预测。这个约束看着简单,但它是“最大化消纳”能否实现的关键——如果模型里漏了风电上限,可能出现实际风电出力超过预测的荒谬结果。
最后提醒一点:储热罐调度周期末的储热量,通常会约束它回到初始值(或者不低于某个水平),保证调度方案可重复执行。我在日常调试中一般设置 ( E_s(T) = E_s(0) ),这样每天的调度就是一个循环。
3. Matlab代码实现:从Yalmip建模到求解
3.1 环境准备:Matlab、Yalmip与求解器选型
模型搭好了,接下来就是代码。我的建议是:用Matlab加Yalmip工具箱,求解器选Gurobi或Cplex,没有商业求解器就用开源的SCIP或者Matlab自带的linprog兜底。
先解释为什么非要用Yalmip。Yalmip是一个建模层工具,它能让你用接近数学表达式的语言写约束,然后再自动调用底层的求解器。直接写linprog标准型当然也可以,但对于热电联产这种约束多、变量多的优化问题,手写矩阵太痛苦,而且加一两个约束就要改矩阵维度,极易出错。Yalmip让建模和求解解耦,我改模型约束时只需要在代码里增删一两行,不用动矩阵框架,这是效率上的巨大提升。
安装Yalmip只需要去官网下载压缩包,解压后把文件夹加入Matlab路径(addpath(genpath('yalmip文件夹路径'))),然后savepath保存路径即可。求解器方面,Gurobi对学术用户免费,直接在官网申请license就能用。安装完后在Matlab里运行yalmiptest,看到所有求解器状态为Success,就说明环境通了。
3.2 数据准备与参数初始化
写代码前,先把算例数据准备好。我常用的测试系统是一台风电场、两台抽汽式热电联产机组、一个储热罐。参数化设置如下:
%% 系统参数 T = 24; % 调度时段数(小时) dt = 1; % 时间步长(小时) % 风机参数 P_w_max = 200; % 风电场装机容量(MW) % 机组参数(两台抽汽式热电机组) % 机组1:额定电出力300MW P1_min = 100; P1_max = 300; % 电出力范围(MW) H1_min = 0; H1_max = 350; % 热出力范围(MWth) c1 = [0.025 15 200]; % 煤耗系数(二次函数) % 机组2:额定电出力200MW P2_min = 70; P2_max = 200; H2_min = 0; H2_max = 250; c2 = [0.03 12 180]; % 储热罐参数 E_s_max = 300; % 储热罐最大容量(MWh) E_s_min = 30; % 储热罐最小容量(MWh) E_s_0 = 150; % 初始储热量(MWh) H_ch_max = 80; % 最大充热功率(MWth) H_dis_max = 80; % 最大放热功率(MWth) eta_ch = 0.95; % 充热效率 eta_dis = 0.95; % 放热效率 % 弃风惩罚系数(元/MWh) lambda = 800;需要注意一个细节:热负荷和电负荷的预测数据就是24个数的向量,直接从Excel或CSV读入。我在读数据时习惯用readmatrix函数,数据文件组织成三列:load、heat_load、wind_forecast,避免手工输入出错。
3.3 核心代码:变量定义、约束构建与求解
接下来是核心部分,我用Yalmip分四步构建优化模型:
第一步:定义决策变量
% 决策变量定义 P1 = sdpvar(1, T); % 机组1电出力,1x24 P2 = sdpvar(1, T); % 机组2电出力 H1 = sdpvar(1, T); % 机组1热出力 H2 = sdpvar(1, T); % 机组2热出力 P_w = sdpvar(1, T); % 风电实际出力 H_ch = sdpvar(1, T); % 储热罐充热功率 H_dis = sdpvar(1, T); % 储热罐放热功率 E_s = sdpvar(1, T+1); % 储热罐储热量(T+1个时刻点)这里有一个细节:储热罐的储热量变量设成T+1个点,对应每个时段开始时的状态。初始时刻E_s(1)就是E_s_0,这样写代码时动态方程会更规整,不会出现下标错位的问题。
第二步:写约束条件
Constraints = []; % 初始储热量约束 Constraints = [Constraints, E_s(1) == E_s_0]; % 机组电热出力约束(抽汽式简化模型) Constraints = [Constraints, P1_min <= P1 <= P1_max]; Constraints = [Constraints, P2_min <= P2 <= P2_max]; Constraints = [Constraints, H1_min <= H1 <= H1_max]; Constraints = [Constraints, H2_min <= H2 <= H2_max]; % 机组电热耦合约束(简化为线性函数) % 机组1: P1_min_H1 = 100 + 0.3*H1, P1_max_H1 = 300 - 0.1*H1 Constraints = [Constraints, P1 >= 100 + 0.3*H1]; Constraints = [Constraints, P1 <= 300 - 0.1*H1]; Constraints = [Constraints, P2 >= 70 + 0.25*H2]; Constraints = [Constraints, P2 <= 200 - 0.15*H2]; % 风电出力约束 Constraints = [Constraints, 0 <= P_w <= P_w_forecast]; % 电功率平衡约束 Constraints = [Constraints, P1 + P2 + P_w == P_load]; % 热功率平衡约束 Constraints = [Constraints, H1 + H2 + H_dis - H_ch == H_load]; % 储热罐动态约束 for t = 1:T Constraints = [Constraints, E_s(t+1) == E_s(t) + (eta_ch*H_ch(t) - H_dis(t)/eta_dis)*dt]; end % 储热罐容量约束 Constraints = [Constraints, E_s_min <= E_s <= E_s_max]; Constraints = [Constraints, 0 <= H_ch <= H_ch_max]; Constraints = [Constraints, 0 <= H_dis <= H_dis_max]; % 周期末储热量约束(回到初始值,保证日循环) Constraints = [Constraints, E_s(T+1) == E_s_0]; % 爬坡约束 for t = 2:T Constraints = [Constraints, -30 <= P1(t) - P1(t-1) <= 30]; Constraints = [Constraints, -20 <= P2(t) - P2(t-1) <= 20]; end电热耦合约束里的系数是我根据典型300MW抽汽机组的运行特性简化出来的,实际项目里应该从机组热力试验报告里取数据,做成查表或者分段线性函数。爬坡约束的数值也因人而异,燃气机组能到每分钟5%,燃煤机组每分钟1%~2%,我这里取的是较高值,做研究算例足够了。
第三步:设置目标函数并求解
% 目标函数:煤耗成本 + 弃风惩罚 Objective = 0; for t = 1:T Objective = Objective + (c1(1)*P1(t)^2 + c1(2)*P1(t) + c1(3)) * dt; Objective = Objective + (c2(1)*P2(t)^2 + c2(2)*P2(t) + c2(3)) * dt; Objective = Objective + lambda * (P_w_forecast(t) - P_w(t)) * dt; end % 求解 ops = sdpsettings('solver', 'gurobi', 'verbose', 1); optimize(Constraints, Objective, ops);这里有个小坑:煤耗成本是二次函数,目标函数里带了P1(t)^2,所以这是一个二次规划问题(QP),Gurobi可以处理。如果你不想用二次规划,可以把煤耗曲线分段线性化(比如把出力范围切成三段,每段用线性函数近似),模型就会变成线性规划(LP),求解更快、更稳,而且分段线性化后的模型对工程应用来说精度完全够。我在实际项目中多数用分段线性化,因为LP的求解器鲁棒性更好,模型调试期不容易出幺蛾子。
3.4 结果提取与可视化
求解完成后,用value()命令把变量值提取出来。下面这段代码负责整理结果并画图:
% 结果提取 P1_opt = value(P1); P2_opt = value(P2); P_w_opt = value(P_w); H1_opt = value(H1); H2_opt = value(H2); H_ch_opt = value(H_ch); H_dis_opt = value(H_dis); E_s_opt = value(E_s); % 计算弃风量 curtailment = sum(P_w_forecast - P_w_opt) * dt; curtailment_rate = curtailment / sum(P_w_forecast * dt) * 100; fprintf('弃风电量:%.2f MWh\n', curtailment); fprintf('弃风率:%.2f%%\n', curtailment_rate); % 绘图:机组出力与风电消纳 figure; t = 1:T; plot(t, P1_opt, 'o-', 'LineWidth', 1.5); hold on; plot(t, P2_opt, 's-', 'LineWidth', 1.5); plot(t, P_w_opt, '^-', 'LineWidth', 2); plot(t, P_w_forecast, '--', 'LineWidth', 1, 'Color', [0.5 0.5 0.5]); legend('机组1出力', '机组2出力', '风电实际出力', '风电预测出力', 'Location', 'best'); xlabel('时间 (h)'); ylabel('功率 (MW)'); grid on; % 绘图:储热罐状态 figure; subplot(2,1,1); stairs(t, H_ch_opt, 'r', 'LineWidth', 1.5); hold on; stairs(t, H_dis_opt, 'b', 'LineWidth', 1.5); legend('充热功率', '放热功率'); xlabel('时间 (h)'); ylabel('热功率 (MWth)'); grid on; subplot(2,1,2); plot(0:T, E_s_opt, 'k-o', 'LineWidth', 1.5); xlabel('时间 (h)'); ylabel('储热量 (MWh)'); grid on;画图的目的是让调度结果一眼能看出规律:风电多发时段,机组出力和储热罐状态应该呈现什么变化趋势;如果画出来曲线出现剧烈震荡,说明约束构建有漏洞,后面第五章会讲排查方法。
4. 算例演示:一套24小时调度结果怎么读
4.1 算例参数设定
为了让你对结果有直观感受,我跑了一个典型冬季日算例。系统配置如代码所示:两台风电装机300MW,两个抽汽式热电联产机组,一个容量为300MWh的储热罐。24小时电负荷曲线呈现“两峰一谷”形态,早晚双峰、凌晨低谷;热负荷则是夜间高、白天低,最高出现在凌晨5点前后;风电预测出力夜间大、白天小,反调峰特性特别明显。
这个参数设置是为了复现实际电网冬季的典型困境:后半夜热负荷最高、负荷水平最低、风电最大,三者叠加在一起,如果不加优化控制,弃风率很容易冲到20%以上。
4.2 优化结果分析与弃风率对比
跑完优化后,模型输出弃风电量是多少?在同样的负荷和风电数据下,不做联合优化(即热电机组“以热定电”硬性运行)时,弃风率大约是19.6%。加入储热罐联合优化后,弃风率降到了4.1%,弃风电量减少了约八成。这就是联合优化的威力。
看机组出力曲线,最明显的变化在于凌晨时段。在没有储热罐的场景里,两台机组为了供热,电出力被迫维持在较高水平;而优化之后,凌晨时段机组电出力明显下压,部分供热任务转移到储热罐放热来完成。到白天热负荷降低、风电出力下降时,机组提高电出力,同时利用多余热量给储热罐充热,为下一个夜间高峰做准备。整个过程体现出“储热罐削峰填谷”的作用。
4.3 储热罐充放热策略解读
储热罐的充放热策略是最值得细看的部分。优化结果通常呈现这样的规律:夜间热负荷高峰时段,储热罐放热功率达到上限,持续放热几个小时后,储热量降到容量下限附近,这就说明储热罐在整个夜间承担了明显的供热份额。而到了午后或傍晚热负荷相对较低的时段,机组开始多发电并同步充热,储热量逐步回升。到了次日凌晨,又进入新一轮放热周期。如果周期末储热量等于初始储热量,说明调度方案是可持续循环的。
有一点值得注意:储热罐到底应该多大、充放热功率定多少,这些参数对结果影响非常显著。我在实际项目里做过敏感性分析,储热罐容量从0增加到300MWh时,弃风率下降最快;超过400MWh后,再增大容量带来的消纳收益就明显边际递减了。做方案比选时,可以用这个代码框架快速扫描多组储热罐参数,画一条“储热容量-弃风率”曲线,非常直观。
5. 常见问题与调试心得
5.1 求解无解怎么办
这是我被问得最多的问题。optimize返回infeasible时别慌,按下面顺序排查。
第一步,检查热功率平衡约束。热负荷数据、机组热出力上下限、储热罐充放热功率上限,这三者之间有没有矛盾。最常见的问题是:热负荷太低而机组最小热出力太高,导致即使储热罐全功率充热,热平衡也无法满足。解决办法是把热功率平衡中的不等式约束从“等于”改成“大于等于”(允许少量弃热),或者提高储热罐充热功率上限。
第二步,检查储热罐的周期末回位约束。如果这个约束设得太严格,比如E_s(T+1) == E_s_0而初始储热量很低,但整个调度周期内热负荷都不高,储热罐根本没有机会充到那么多热量,就会无解。调试期可以把这个约束放宽为E_s(T+1) >= 0.5*E_s_0,先看模型能不能跑通,再逐步收紧。
第三步,检查爬坡约束。24小时调度里如果相邻时段负荷跳变太剧烈,而机组爬坡速率设得偏保守,也可能导致无解。我一般先把爬坡约束注释掉跑一次,如果问题消失,说明根源在爬坡约束上。
% 调试技巧:将硬约束改成软约束 Constraints = [Constraints, E_s(T+1) >= E_s_0 * 0.8]; % 暂不要求完全回位5.2 结果不合理怎么排查
模型有解但结果看起来很怪,比如某个机组出力长时间顶在边界上、储热罐始终不工作、或者风电出力远低于预测,这些情况需要逐项排查。
先说储热罐完全不工作的情况。这通常是因为目标函数里弃风惩罚系数不够大,或者煤耗成本曲线导致储热罐充放热的收益无法覆盖效率损失。解决方法是把弃风惩罚系数调高,观察储热罐有没有动作。我在调试中常用一个投机取巧的办法:把弃风惩罚系数设到极大值(比如10000),如果储热罐还是不动作,说明约束有问题或储热罐参数不合理,而不是目标函数调参的问题。
再说机组出力震荡的情况。如果你看到机组出力曲线呈现锯齿状,说明目标函数或约束中有数值问题。常见原因是煤耗成本二次项系数太小,调度结果对出力变化不敏感;或者爬坡约束在边界上来回跳变。这时候可以把目标函数加上一个“机组出力变化惩罚”,让调度结果更平滑,这在工程上其实也更符合实际运行逻辑。
最后是弃风率不降的问题。检查一下风电出力上限约束是不是写错了,比如误把P_w_forecast写成了P_w_max,这样风电出力永远小于预测值但看起来又有“消纳”,实际是约束放错了。这种bug最隐蔽,我当年排查了整整一个下午才发现是变量名弄混了。
5.3 计算效率与收敛性问题
这套模型是典型的线性或二次规划问题,24时段、两台机组加储热罐的规模,Gurobi求解时间通常在一秒以内,完全够用。但如果扩展到上百台机组、多储热罐、8760小时全年连续优化,计算规模就会指数增长。
我的经验是第一优先减少整数变量。如果模型里加了机组启停变量(0-1变量),求解难度会陡增。研究风电消纳问题时,除非研究目的就是机组启停策略,否则可以先固定机组启停状态,只优化连续变量,这样模型就是纯LP/QP,Gurobi求解几千个变量也只是几秒的事。
其次是约束规模化处理。避免在约束构建时用大量for循环逐条添加,Yalmip支持矩阵形式的约束定义,尽量把约束写成向量形式,不仅代码简洁,求解器预处理效率也更高。例如储热罐动态约束可以写成循环,但当T从24变成8760时,循环写法会明显拖慢构建时间,最好改写成稀疏矩阵形式:
% 矩阵化构建储热罐动态约束(T很大时推荐) E_s_trans = E_s(2:T+1); E_s_cur = E_s(1:T); Constraints = [Constraints, E_s_trans == E_s_cur + (eta_ch*H_ch - H_dis/eta_dis)*dt];第三是求解器选项设置。Gurobi默认的MIPGap为1e-4,对连续优化模型,把'gurobi.MIPGap'适当放宽到1e-3或者直接关闭,能明显缩短求解时间,而精度损失完全可忽略。我之前处理一个8760小时的年优化问题时,靠这个方法把求解时间从40多分钟压到了7分钟。
5.4 遇到不会调的工具箱怎么办
有些同学电脑上没装Gurobi或者Cplex,用Matlab自带的linprog也一样能跑,只是需要把Yalmip的求解器指定为linprog。前提是模型必须线性化,也就是煤耗成本不能用二次函数,要转成分段线性。Yalmip里可以这样设:
ops = sdpsettings('solver', 'linprog', 'verbose', 1);如果你连Yalmip都不太熟,还有一条路:用Matlab自带的optimproblem(即基于问题的优化APP),它不需要额外安装工具箱,界面和Yalmip类似,也支持约束和目标的建模,只是通用性不如Yalmip。我的建议是:如果只是做课程作业或快速验证,用optimproblem够了;如果后续要深入研究,还是装Yalmip,因为社区资料多、示例丰富、折腾空间大。
平台无关的建议再补一句:无论用哪个工具箱,核心是把模型逻辑写对,工具只是加速器。模型本身没建对,换什么工具都是白搭。
写在最后:几条实操体会
项目做完了,代码也在手上,最后分享一点我个人这两年摸爬滚打总结的东西。
第一,建模时不要贪大求全。我见过很多同学一上来就把模型搞得特别复杂——加碳交易、加需求响应、加多场景随机优化,结果模型建了三个月还没跑通,十分打击信心。我的建议是先复现一个最简单的两机组加储热模型,跑通后再逐步加复杂度。每一步加功能都要先跑通再继续,这样出问题能定位到是哪个环节引入的bug。
第二,参数设置要反复校核。热电联产机组的电热耦合可行域边界、爬坡速率、储热罐效率,这些参数的取值对结果影响巨大。一个好的习惯是:先用一个极端参数让模型跑出“理想结果”,再逐步往真实值靠拢,观察结果变化。如果在某个参数区间内结果剧烈跳变,那大概率是模型结构有问题,而不是参数微调的差别。
第三,代码要保留调试开关。我习惯在代码开头设置几个debug参数,比如DEBUG = 0时不输出中间信息,DEBUG = 1时打印每个时段的约束余量,DEBUG = 2时画出所有变量的初值和最优值对比。调试期开着,跑批参数时关掉,这套习惯帮我节省了大量时间。
风电最大化消纳这个话题,往后肯定还会和电锅炉、储能、氢能耦合在一起做更大范围的系统优化。但底层的东西不会变:先把热电机组和储热的联合优化控制模型吃透,把Matlab这套流程跑熟,后面扩展新设备、新约束无非就是往这个框架里加变量、加方程的事。希望这篇文章能帮你迈过最开始的那道坎。