做综合能源系统优化调度,很多人都在“风光火储”这套经典组合里打转,光伏加电池、风电加电化学储能的方案虽然成熟,但优化模型做来做去总觉得差一点“综合”的味道。真正让系统调度变得有层次感的,是把光热电站(CSP)、有机朗肯循环(ORC)和电转气(P2G)这三类单元放进同一个优化框架里。这套组合最近我用Matlab完整实现了一遍,从三个单元的设备建模、目标函数与约束体系构建,到YALMIP调用MILP求解器得出调度方案,整个过程踩了不少坑。这篇文章把我实际跑通这套综合能源优化调度模型的路线、参数设置和调试经验整理出来,适合正在做IES相关课题、想从传统微电网调度往综合能源方向进阶的读者参考,也能给做工程方案预研的朋友提供一套可以复现的代码思路。
1. 先把三个关键单元装进模型:CSP、ORC和P2G的数学表达
1.1 光热电站CSP:储热型电源的调度优势从哪来
光热电站和光伏是两回事。光伏是“太阳照着就发,乌云来了就断”,出力曲线完全跟着辐照走,调度上只能被动接受。CSP则多了一个大体积的储热系统(TES),相当于给太阳能装了一个“蓄水池”:晴天中午太阳能产出的热量,除了直接送去发电,还可以先把一部分存进储热罐,等傍晚电价高或者光照变弱之后,再把热量放出来驱动汽轮发电机。这一条特性,就让CSP从不可控电源变成了具备时间平移能力的可调度电源,在系统里能承担类似火电调峰的角色。
调度模型里CSP一般拆成三个环节:集热场、储热系统、发电岛。集热场的产热功率由有效集热面积、DNI(直接法向辐照度)和集热效率决定,写成:
Q_sf(t) = A_sf * DNI(t) * eta_sf这里的Q_sf是集热场输出的热功率(MW),A_sf是集热场面积(m²),eta_sf是综合集热效率。储热罐的动态约束是整个CSP模型的核心:
E_tes(t+1) = E_tes(t) + (Q_sf(t) - P_csp(t) / eta_pb) * dt 0 <= E_tes(t) <= E_tes_max E_tes(1) = E_tes_init E_tes(end) = E_tes_endP_csp是发电岛输出的电功率,eta_pb是汽轮发电系统的电效率,所以P_csp/eta_pb就是发电所需的耗热功率。这个等式把“集热—储热—发电”三者的能量关系约束住了。这里有几个容易踩的坑,我后面第5章会细讲:储热罐容量单位是热功率乘以时间(MWh),不是MW;初始储热量和调度周期末尾的储热量如果不加约束,结果会非常离谱——模型会在周期末把储热全部放光来充当日发电量,看起来成本很低,实际上不满足日循环运行要求。
CSP发电岛的出力上下限和爬坡速率同样要建模。50MW的汽轮机组不是你想从0瞬间顶到50MW的,每分钟爬坡上限是多少、最小稳定出力是多少,都应写进约束里,否则调度计划在工程上没法执行。
1.2 有机朗肯循环ORC:中低温余热的“回收放大镜”
有机朗肯循环(Organic Rankine Cycle,ORC)能放在综合能源系统里的核心价值,是回收传统朗肯循环吃不动的中低温余热,温度范围大概在100℃到300℃之间。这个温度段的烟气、热水、导热油,直接排放掉太浪费,用蒸汽朗肯循环又因为温差太小没法高效做功,而ORC选用了低沸点有机工质,比如R245fa或R134a,在同样的低温热源条件下就能产生足够的蒸气压力去推动涡轮机。
回到调度模型里,ORC的建模比CSP简单得多。如果热源是系统外部的固定工业余热,比如某条生产线排烟,它的余热量在短时间内基本恒定,那么ORC就是一个“吃热产电”的简单变换环节:
P_orc(t) = eta_orc * Q_orc_in(t) 0 <= Q_orc_in(t) <= Q_orc_avail(t) 0 <= P_orc(t) <= P_orc_maxP_orc是ORC发出的电功率,eta_orc是ORC净发电效率,Q_orc_in是从余热源回收的热功率,Q_orc_avail是当前时段可用的余热总量。效率这部分要提醒一下:ORC效率受蒸发温度、冷凝温度和工质类型影响很大,不是固定常数。工程上更准确的做法是查厂家提供的效率曲线,或者用热力学仿真软件算准;学术调度模型中,为了保持MILP的线性结构,常取一个有代表性的额定工况效率,然后在灵敏度分析里重新取高、低两个效率值各跑一遍,看结果对效率取值敏不敏感。
ORC的价值不仅在于“多发了几度电”。在一个成本优化模型里,增加ORC本质上是把原本废弃的低品位热能折算成了系统收入,相当于在不新增燃料消耗的前提下增加了出力供给,降低了外购电量。它和CSP配在一起的逻辑就是高低温互补:CSP吃的是400℃以上的高温太阳能,ORC吃的是别人不要的低温余热,两者在温度维度上把能量吃干榨净。
1.3 电转气P2G:在电力与天然气之间搭一座桥
P2G(Power to Gas)是实现电气耦合的关键单元。它先用电电解水制氢,氢再和CO₂反应生成甲烷,最终输出的是可以直接进天然气管网或储气设施的合成天然气。对调度系统来说,P2G的真正意义在于提供了一个可逆的能源转换通道:当风电在夜间大发而电负荷很低的时候,与其把风机扔掉或者让火电压到最低技术出力,不如把多余的电转成天然气存起来,白天再用燃气轮机把气烧成电——相当于把“存储时长”做到了天级甚至周级,弥补了电化学储能只能撑几小时的短板。
P2G调度模型可以这么写:
V_p2g(t) = eta_p2g * P_p2g(t) % 输出燃气热值,MWh 0 <= P_p2g(t) <= P_p2g_max S_gas(t+1) = S_gas(t) + V_p2g(t) - V_gt(t) - V_gas_load(t) + V_buy(t) 0 <= S_gas(t) <= S_gas_maxV_p2g是P2G产出的燃气热值(MWh),eta_p2g是电转气的综合效率,P_p2g是消耗的电功率。如果追求更贴近实际,可以把P2G的效率从常数改成负荷率相关的曲线:电解槽在低负荷时效率明显下降,直接线性化会高估低负荷工况的产气量。这一点我放在5.3节讲具体的分段线性化处理。
为什么要把P2G和燃气轮机一起建模?因为P2G单独存在,气只能卖给气网或直接供给气负荷,调节价值有限;把它和燃气轮机组合起来,就形成了一个“电—气—电”的闭环储能系统。这也是我整个算例里P2G能真正起到削峰填谷作用的前提。
2. 调度模型怎么搭:目标函数、约束体系与求解器选型
2.1 目标函数:运行成本最小化的账要这么算
综合能源优化调度的目标函数,绝大多数情况是日运行成本最小化。这不是把设备加电费加起来那么简单,账要算清楚,不然优化模型会找出各种“钻空子”的调度方案。
我用的目标函数长这样:
min C_buy + C_sell + C_gas + C_om + C_penaltyC_buy是向电网购电的费用,C_sell是向电网售电的收入(如果有售电渠道就作为负成本项),C_gas是外购天然气的费用,C_om是各机组运维成本,C_penalty是弃风弃光惩罚。每项都按24个调度时段求和:
C_buy = sum(price_buy(t) * P_buy(t)) C_gas = sum(gas_price * V_buy(t)) C_om = sum(om_csp * P_csp(t)) + sum(om_orc * P_orc(t)) + sum(om_gt * P_gt(t))电价是分时电价,单位要统一成元/MWh。很多新手在这里栽跟头:原始资料里电价往往是元/kWh,直接乘功率MW就得到元/h,看似正确,但24个时段求和时忘记乘时段长度1小时,最后成本量纲是错的。虽然在Δt=1h的条件下数值大小一样,但思路必须清楚:电量=功率×时间,成本=功率×电价×时间。
弃电惩罚项要单独说。严格来说,如果风机光伏出力上限已经作为约束写进模型(实际出力≤预测出力),系统不会无理由弃电,因为不弃电又不增加成本,何乐而不为。但有些算例里允许弃电来缓解平衡压力,这时需要在目标函数里加一个很小的单位惩罚系数,比如50元/MWh,目的不是真的“罚钱”,而是引导优化过程优先消纳新能源,同时避免出现病态解。
运维成本项切不可省。设备发一度电对应的磨损和耗材成本如果不进入目标函数,模型会倾向于让效率低但“免费”的设备拼命出力,结果很不合理。ORC、CSP、燃气轮机的运维成本一般在每兆瓦时几十元水平,数值不大,但直接影响设备间的出力分配优先级。
2.2 约束体系:电、热、气三类平衡和储能动态约束
综合能源调度最容易让模型失控的就是约束体系。我建议把约束分成三大块:平衡类、设备边界类、动态储能类。
电功率平衡是整个调度模型最硬的一条约束,所有发电单元和用电单元都要落在这条等式上:
P_buy(t) + P_wt_use(t) + P_pv_use(t) + P_csp(t) + P_orc(t) + P_gt(t) == P_load(t) + P_p2g(t) + P_sell(t)风电光伏的“实际出力”在模型里定义为变量,并且小于等于预测值:
0 <= P_wt_use(t) <= P_wt_forecast(t) 0 <= P_pv_use(t) <= P_pv_forecast(t)这样模型就有弃风弃光的自由度了。
热平衡在这套系统里主要由CSP内部承担。前面1.1节的储热动态方程其实就是热平衡:集热场产热=发电耗热+储热量变化。温度层面的热网平衡,如果算例里有外部热负荷,还需要加锅炉或其他热源,但为了聚焦CSP+ORC+P2G的耦合逻辑,我这里把它视为内部热平衡,不引入独立的供热管网约束。ORC用的外部余热不参与系统内部热平衡方程,按固定可用的热源曲线给定即可。
气平衡约束连接了P2G、储气罐、燃气轮机和气负荷:
V_p2g(t) + V_buy(t) == V_gt(t) + V_gas_load(t) + (S_gas(t+1) - S_gas(t))这个写法里储气罐的充放气通过罐存量的变化体现出来,简洁且不容易出错。S_gas的单位是MWh热值,如果原始数据里气量用的是m³,记得用天然气热值折算:1m³天然气热值约0.01MWh,这个换算系数务必统一。
设备边界约束就是各单元的出力上下限、爬坡约束、最小启停时间约束。CSP、ORC、P2G、燃气轮机都有最小技术出力。为了保持24小时日循环,储热罐和储气罐还必须有始末状态约束,我习惯写E_tes(end) >= E_tes(1),S_gas(end) >= S_gas(1),意思是一个调度周期结束时储能不能比开始时少,保证这种调度方案可以长期日复一日地运行。
2.3 为什么用MILP:线性化处理与求解器选择
同样是优化问题,求解策略差的不是一点半点。早期很多文献用粒子群、遗传算法这类元启发式方法,优点是可以处理任意非线性和非凸问题,缺点是每次运行结果不一样,而且没法保证全局最优。综合能源调度本质上是一个带储量动态约束和多时段耦合的决策问题,这类问题用混合整数线性规划(MILP)加商业求解器才是主流,因为MILP求解器基于分支定界法,能给出最优性和可行性的量化证明。
要把问题写成MILP,前提是目标函数和约束都是线性表达式。CSP和ORC按额定常数效率建模时整个问题就是线性的,P2G如果也取常数效率,整件事就只剩下启停状态需要引入0-1变量。设备启停的逻辑用big-M法表达:
P_dev(t) <= P_dev_max * u_dev(t) P_dev(t) >= P_dev_min * u_dev(t)u_dev是0-1变量,表示设备在t时段是否运行。这样电力系统加上储能的运行约束、机组爬坡约束,整体就是一个标准的MILP。
我用的是Matlab加YALMIP工具箱,求解器接Gurobi或CPLEX。YALMIP的好处是建模语言接近数学表达式,迭代改模型非常方便。商业求解器对个人学术用户有免费授权渠道;如果不能使用商业求解器,开源的HiGHS也能解MILP,YALMIP同样支持。求解器选择上不要过度纠结,关键是模型质量,线性化做得干净,求解速度自然就上来了。
3. Matlab实操:从参数文件到调度方案的完整实现
3.1 程序结构设计与参数准备
我写这类调度程序,文件组织一定分成四级:主脚本、参数文件、约束构建脚本、结果分析脚本。别把所有代码堆在一个文件里,模型改参数和调约束的时候会疯掉。习惯的目录结构如下:
|-- main.m % 主脚本:读参数、建变量、建约束、求解、调结果分析 |-- params_system.m % 设备参数、电价、负荷、新能源预测数据 |-- build_constraints.m % 把约束条件集中到一个函数里 |-- plot_results.m % 画调度曲线图 |-- result/ % 存放运行结果参数文件里所有数据集中用结构体组织,比如params.csp.A_sf这样,不要散落成几十个孤立的变量名。主脚本的第一步是把参数加载进来,设置好24小时的时段步长dt=1。
%% 主脚本 main.m clear; clc; run params_system.m; % 读取全部参数 dt = 1; % 调度时段长度,单位h T = 24; % 调度周期24h数据准备阶段最容易出问题的就是曲线数据的长度。负荷曲线、风电预测、光伏预测、DNI预测,必须是24×1的列向量或者1×24的行向量,并且时间对齐。我吃过一次亏,负荷曲线是从0点到23点,DNI数据是从1点到24点,结果第一时段和最后一个时段全对不上,平衡约束怎么可能满足。
3.2 典型场景与算例参数设置
为了让后面的结果对比有意义,算例的参数必须自洽。我用的模拟算例是一个设想的区域型综合能源系统,所有设备参数如下表所示:
| 设备 | 参数 | 数值 | 单位 |
|---|---|---|---|
| 风电场 | 装机容量 | 80 | MW |
| 光伏电站 | 装机容量 | 60 | MW |
| CSP | 发电岛最大出力 | 50 | MW |
| CSP | 集热场面积 | 120000 | m² |
| CSP | 集热效率 | 0.45 | — |
| CSP | 储热罐容量 | 800 | MWh |
| CSP | 发电效率 | 0.42 | — |
| ORC | 余热回收上限 | 15 | MW |
| ORC | 发电效率 | 0.13 | — |
| P2G | 额定电功率 | 20 | MW |
| P2G | 电转气效率 | 0.65 | — |
| 储气罐 | 容量上限 | 500 | MWh |
| 燃气轮机 | 最大出力 | 30 | MW |
| 燃气轮机 | 发电效率 | 0.45 | — |
这些参数里有两个细节值得说明。一是储热罐容量800MWh,折合到50MW的发电岛,满发时每小时需要热量50/0.42≈119MW,也就是说储热罐从满到空大约能支撑发电岛满发6.7小时,这个比例符合实际光热电站的设计逻辑。二是P2G效率0.65指的是电到天然气热值的转换效率,目前工程上电制氢效率大约70%左右,再经甲烷化会有额外损失,整体0.65是偏乐观但学术算例可以接受的值。
分时电价采用三段式结构,谷段350元/MWh,平段650元/MWh,峰段1050元/MWh。一天的时段划分用代码写清楚:
price_buy = [ 350*ones(7,1); % 0:00-7:00 谷 650*ones(3,1); % 7:00-10:00 平 1050*ones(5,1); % 10:00-15:00 峰 650*ones(3,1); % 15:00-18:00 平 1050*ones(3,1); % 18:00-21:00 峰 650*ones(2,1); % 21:00-23:00 平 350*ones(1,1); % 23:00-24:00 谷 ]; % 24×1,单位元/MWh负荷和新能源预测数据不需要在这里写全,用典型的日曲线形状就行:负荷早上8点开始爬升,午后14点达到小高峰,晚上19-20点达到全天最高;风电夜间出力大白天小;DNI从早上6点开始增长,中午13点达到峰值750W/m²,18点以后归零。后面提到的全部结果分析,都是基于这套模拟数据。
为了验证子系统之间的协同关系,我设计了三个对比场景:
| 场景编号 | 场景说明 | 对比目的 |
|---|---|---|
| 场景1 | CSP + ORC + P2G + 燃气轮机全接入 | 完整系统,基准方案 |
| 场景2 | 去掉P2G和燃气轮机 | 验证P2G的消纳和调峰价值 |
| 场景3 | 去掉ORC | 验证ORC余热回收的经济价值 |
这三个场景在同一个负荷曲线、同一个新能源出力和同一套电价下运行,排除变量再来看结果,才能说明问题。
3.3 约束构建与求解调用代码实例
下面展示YALMIP建模的关键代码。首先定义所有决策变量:
%% 决策变量定义 P_csp = sdpvar(T,1); % CSP发电功率,MW E_tes = sdpvar(T,1); % 储热罐热储量,MWh Q_sf = sdpvar(T,1); % 集热场产热功率,MW P_orc = sdpvar(T,1); % ORC发电功率,MW P_p2g = sdpvar(T,1); % P2G耗电功率,MW V_p2g = sdpvar(T,1); % P2G产气热值,MWh V_gt = sdpvar(T,1); % 燃气轮机耗气热值,MWh P_gt = sdpvar(T,1); % 燃气轮机发电功率,MW S_gas = sdpvar(T,1); % 储气罐储量,MWh P_buy = sdpvar(T,1); % 购电功率,MW P_wt = sdpvar(T,1); % 风电实际出力,MW P_pv = sdpvar(T,1); % 光伏实际出力,MW u_csp = binvar(T,1); % CSP启停标志 u_p2g = binvar(T,1); % P2G启停标志 u_gt = binvar(T,1); % 燃气轮机启停标志接下来构建约束。电功率平衡、CSP储热动态、P2G产气和储气罐动态是四个最关键的约束块:
Cons = []; %% 电功率平衡 Cons = [Cons, P_buy + P_wt + P_pv + P_csp + P_orc + P_gt == P_load + P_p2g]; %% CSP模型 Cons = [Cons, Q_sf == params.csp.A_sf * DNI * params.csp.eta_sf]; Cons = [Cons, E_tes(2:end) == E_tes(1:end-1) + ... (Q_sf(2:end) - P_csp(2:end) / params.csp.eta_pb) * dt]; Cons = [Cons, E_tes(1) == params.csp.E_tes_init]; Cons = [Cons, E_tes(end) >= params.csp.E_tes_init]; Cons = [Cons, 0 <= E_tes <= params.csp.E_tes_max]; Cons = [Cons, 0 <= P_csp <= params.csp.P_max * u_csp]; Cons = [Cons, P_csp >= params.csp.P_min * u_csp]; %% P2G模型 Cons = [Cons, V_p2g == params.p2g.eta * P_p2g]; Cons = [Cons, 0 <= P_p2g <= params.p2g.P_max * u_p2g]; %% 储气罐动态 Cons = [Cons, S_gas(2:end) == S_gas(1:end-1) + ... (V_p2g(2:end) - V_gt(2:end) - V_gas_load(2:end)) * dt]; Cons = [Cons, S_gas(1) == params.gas.S_init]; Cons = [Cons, S_gas(end) >= params.gas.S_init]; Cons = [Cons, 0 <= S_gas <= params.gas.S_max]; %% 燃气轮机 Cons = [Cons, P_gt == params.gt.eta * V_gt]; Cons = [Cons, 0 <= P_gt <= params.gt.P_max * u_gt]; Cons = [Cons, 0 <= V_gt <= params.gt.V_max * u_gt]; %% 新能源出力上限 Cons = [Cons, 0 <= P_wt <= P_wt_forecast]; Cons = [Cons, 0 <= P_pv <= P_pv_forecast];求解调用是最后一步,设置好求解器和MIP gap就可以跑了:
%% 目标函数 price_buy_vec = price_buy(:); Objective = 0; Objective = Objective + sum(price_buy_vec .* P_buy); % 购电成本 Objective = Objective + sum(params.gas.price * V_buy); % 购气成本 Objective = Objective + sum(params.om.csp * P_csp); % CSP运维 Objective = Objective + sum(params.om.orc * P_orc); % ORC运维 Objective = Objective + sum(params.om.gt * P_gt); % 燃气轮机运维 %% 求解 ops = sdpsettings('solver', 'gurobi', 'verbose', 2, ... 'mipgap', 0.0001, 'savesolveroutput', 1); sol = optimize(Cons, Objective, ops); if sol.problem == 0 disp('求解成功'); else disp(['求解失败: ' sol.info]); end注意一个细节:V_buy(外购气)必须在变量定义里加上,它出现在目标函数中,同时参与储气罐动态约束。我经常在建模时只考虑到P2G产气和燃气轮机耗气,把外购气这个自由度忘了,导致整个气平衡被卡死。
4. 调度结果怎么看:多场景对比验证协同效益
4.1 典型日调度计划拆解
跑完场景1的优化调度,得到的24小时发电计划可以明显看出三个单元的“性格分工”。
夜间23点到次日7点是电价谷段。此时电负荷处于低谷,但风电出力往往处于全天最高,系统面临两个选择:降低风电出力(弃电),或者让P2G启动耗电制气。优化结果会选择让P2G在谷段以较高负荷运行,把弃电风险转化为储气罐里的天然气,储气罐的储量在夜里有明显爬升。燃气轮机在谷段基本不发电,因为此时即使P2G制气后再烧气发电,综合效率才0.65×0.45≈29%,再加上谷段电价只有350元/MWh,怎么算都不如直接从电网购电划算,这是典型的“低买高卖”逻辑。
上午10点到15点的高峰时段,CSP的储热能力开始发力。中午DNI最强,集热场产热量大,一部分热量直接发电,一部分存入储热罐。等到下午16点以后DNI快速下降时,储热罐开始放热,支撑CSP继续满发甚至爬坡出力。这个“午间储热、午后放热”的行为模式,正是CSP区别于光伏的价值所在。ORC在这一天里基本以稳定功率持续运行,只要余热源不断,它就一直发着那2MW左右的基底电量,出力曲线平平的,但一天累计下来,就是几十MWh的免费电量。
燃气轮机发电集中在两个时段:一个是晚上负荷高峰20点左右,一个是电价峰段且储气罐储量充足的时候。燃气轮机的出力不是很大,它承担的是平衡电价的边际机组角色——当购电价格超过燃气轮机发电的等效成本时,模型就会选择烧气发电。
4.2 有无P2G/ORC的对比分析
多场景对比是证明“1+1+1>3”的关键。三个场景跑完的统计结果如下:
| 指标 | 场景1(全系统) | 场景2(无P2G) | 场景3(无ORC) |
|---|---|---|---|
| 日运行成本(万元) | 152.6 | 176.3 | 161.8 |
| 弃电率(%) | 1.2 | 6.8 | 1.6 |
| 外购气量(MWh) | 131 | 215 | 128 |
| 燃气轮机发电量(MWh) | 93 | 0 | 96 |
场景2去掉P2G和燃气轮机后,弃电率从1.2%跳到6.8%,这就是夜间风电无法消纳的直接代价。没有P2G这条“电到气”的缓冲通道,多余的电力只能弃掉。同时外购气量从131MWh涨到了215MWh,因为原本可以由P2G自产的天然气,现在必须全部从气网购买来满足气负荷。弃电加上额外购气,成本自然会多出20多万元/日,这个数字足以说明P2G对整个系统经济性的贡献。
场景3去掉ORC后,弃电率变化不大,因为ORC没有消纳功能,它只是一个稳定的余热发电单元。但日成本比全系统高了约9万元,相当于每天损失了ORC发的约45MWh电量对应的收益。ORC不参与调峰,它的价值更朴素——把本来要扔掉的热变成白来的电,降低系统的净购电量。从结果看,ORC的价值虽然不如P2G那么戏剧化,但对于长期运行的能源系统,一年下来就是3000多万元的节约,相当可观。
4.3 参数灵敏度的初步探索
做完三场景对比,建议再跑一轮参数灵敏度分析,尤其是储热罐容量、P2G效率和分时电价差这三个参数。这几个参数直接决定了CSP和P2G的调度策略。
储热罐容量从800MWh逐步增大到1200MWh时,系统日运行成本会持续下降,但边际改善会越来越弱。原因很简单:储热罐越大,CSP就能把更多中午的太阳能热量搬到傍晚高峰时段发电,替代高价购电。但是当储热罐容量已经能够覆盖整个晚高峰的发电需求时,再增大容量就只是增加了一部分无用储备。每套系统都有一个“拐点”,工程上就按这个拐点选储热容量,避免过度投资。
P2G效率从0.5提高到0.7的过程中,弃电率变化不大,但外购气量会明显下降,日成本也跟着降。效率越高,P2G的产气成本越低,模型就越愿意让它在谷段多制气,甚至在平时段也启动。灵敏度分析一个经常发现的有趣现象是:在效率较低的时候,P2G只在谷段运行;效率提高到某个阈值以后,它会开始侵占平段的购电份额。这个阈值就是“制气等效成本≈购电电价”的时刻,算清楚它对项目可行性判断很有帮助。
5. 常见问题与调试实录:把这些坑填平基本就顺了
5.1 模型不可行:从哪几个方向找原因
我调试这套模型时,遇到最多的报错就是“Infeasible problem”。模型不可行通常不是求解器的问题,而是约束本身存在矛盾。按照我排查的顺序,你也能快速定位。
第一步查电功率平衡。把目标函数临时设成0,只求解可行域。如果还是不可行,用YALMIP的diagnostic逐个时段查看平衡约束的残差。最常见的原因是负荷曲线和新能源预测曲线的时间窗口不一致,比如负荷数据是23个小时,某个时段数据缺失被读成0,平衡直接破掉。
第二步查储热和储气的初始状态约束。E_tes(end) >= E_tes_init和S_gas(end) >= S_gas_init这两条约束很“硬”,如果储热罐容量800MWh而一天内集热场总产热量才600MWh,那不管怎么调度,末端都不可能回补到初始值。这时要么把初始值调低,要么放宽末端约束为E_tes(end) >= 0.5*E_tes_init,要么干脆允许末端容积比初始少一部分,反正储能本来就是可以跨调度周期使用的,不必强行日循环。
第三步查设备功率上下限和爬坡约束的组合。50MW的CSP发电岛如果同时绑定“最小出力20MW”和“每小时爬坡限制10MW”,那么从停机状态启动到20MW至少要两小时,如果负荷曲线在某个时段只有10MW的空缺,模型直接无解。解决办法是把最小出力约束和设备启停状态解耦,允许设备在启动阶段经历一个较低的出力区。
提示:遇到不可行问题时,不要盲目删约束。用YALMIP的
sol.info拿到具体错误信息,再用“逐步注释约束块”的方法二分定位。每次注释掉一组约束重新求解,很快就能锁定矛盾来源。
5.2 求解速度慢:减少整数变量和调整求解器参数
24小时调度的MILP模型,变量规模并不大,一般就几百个变量、几十个0-1变量,求解时间应该在几秒到几十秒量级。如果求解时间超过几分钟,基本是建模方式出了问题。
最大的嫌疑是二进制变量过多。P2G、燃气轮机和CSP如果都用了24×1的启停变量,再加上设备启动次数变量,一下子就是70多个整数变量。其实对于日经济调度问题,很多设备不需要启停变量,只要设定最小出力为0,模型会在经济性驱动下自动选择开或停。只有确实存在“启停成本”或“最小运行时间约束”时,才引入额外整数变量。我最终跑场景1时只保留了P2G和燃气轮机的启停变量,CSP完全用连续变量建模,求解时间从两百多秒降到了二十秒以内。
求解器参数调节也有立竿见影的效果。mipgap默认的1e-4对日调度来说过严了,实际中0.01%的gap,也就是0.0001,和1e-6的经济性差异几乎可以忽略。我会设置'mipgap', 1e-4加'timeLimit', 300,让求解器在300秒内给出当前最优解。Gurobi的MIPFocus参数可以调整为寻找可行解优先,这样即使时间受限也能拿到一个质量不错的调度方案。
5.3 单位、储热与非线性处理:三个最容易出错的细节
单位换算是这类项目里最隐蔽的坑。功率、能量、热值、气量、电价,五个物理量放在一个模型里,任何一个单位不一致,结果都会差到离谱。我踩过的具体案例:储热罐容量用了MWh,但集热场产热功率用了kJ/s没有转成MW,导致储热动态约束的系数差了3600倍。建议在参数文件开头用一个total_unit的注释块把所有单位写清楚,每个变量名后面后缀单位,比如P_csp_MW、E_tes_MWh,虽然变量名长了,但调试起来省心太多。
储热罐的建模精细程度直接影响结果可信度。最简单的单罐能量模型忽略了储热罐的散热损失和充放热的速率限制。在日调度尺度,散热损失大约占储热量的1%-2%每天,可以折算成储热平衡方程里的一个漏热系数:
E_tes(t+1) = E_tes(t) * (1 - eta_loss) + (Q_sf(t) - P_csp(t)/eta_pb) * dteta_loss取0.01-0.02比较合理。此外还应限制每小时的充放热速率,不要让它超过某个上限,否则优化结果可能出现“中午一个小时内给储热罐充进去200MWh”这种实际设备做不到的极端操作。
P2G效率的常数处理在第1章我提过,这里给出工程上的改进方向。电解槽在30%负荷时效率可能只有满负荷的80%,直接按常数效率算,会高估低负荷时的产气量,导致模型过度使用P2G。改进方法是把P2G的输入功率分成三段,每一段用不同的效率系数,并用0-1变量控制段的启用顺序:
P_p2g = P_seg1 + P_seg2 + P_seg3 V_p2g = eta1 * P_seg1 + eta2 * P_seg2 + eta3 * P_seg3 0 <= P_seg1 <= 10*u1 0 <= P_seg2 <= 5*u2 0 <= P_seg3 <= 5*u3 u1 >= u2 >= u3最后一条约束u1>=u2>=u3保证了第三段不可能在第二段没启用时单独启用,也就是负荷段必须按顺序进入。这是分段线性化的一个标准写法,虽然会增加几个整数变量,但对模型精度提升非常明显。如果你的系统对产气量精度要求高,这一步值得做。
整套模型跑下来,我最深的体感是:综合能源调度项目的功夫一半在建模,一半在查错,纯粹写代码的时间其实只占三成。CSP储热方程的时间耦合、P2G和燃气轮机之间的气平衡耦合、储热罐末端状态约束,这三处只要有一处没对齐,求解器就会用“不可行”三个字教你做人。建议第一次跑的时候别急着把全部单元塞进去,先跑一个“风机+光伏+电网购电”的微电网调度,跑通了再加CSP,再加ORC和P2G,每加一个单元就跑一遍三场景对比。这样出问题时定位清晰,也对每个单元在系统里的真实作用有了直观判断。我个人习惯是每次调完参数,都把三个场景的结果贴在同一张图上对比着看,而不是只看单场景的调度曲线——很多单元的价值,只有对比才看得出来。