1. 这套系统到底在做什么:CSP+ORC+P2G的互补逻辑先说结论:光热电站、有机朗肯循环、P2G这三样东西放一起,核心解决的不是“多发电”,而是“让每个能量品种都在最该出现的时刻出现”。我做过的某个模拟园区项目里,光伏和风电装了不少,但下午光照最猛的时候电价反而最低,上网就是亏;晚上电价拉满,本地却没有出力手段。与此同时,园区里有一堆工业余热,常年排掉没人管。后来我们把这三套设备加进调度模型,场景才真正变得有意思起来。**光热电站(CSP)**和光伏最大的区别在于它自带储热。白天集热场吸收太阳能后,可以先把热量存进熔盐罐,等到电价高的晚高峰再放热发电。说白了,光热电站相当于一个“太阳能的蓄电池”,只不过存进去的是卡路里而不是库仑。调度模型里最需要小心的就是这个储热罐的充放热逻辑——它决定了光热能不能把白天便宜的太阳能挪到晚上卖。**有机朗肯循环(ORC)**在这里是专门啃“低品位热量”的。常规蒸汽朗肯循环对热源温度要求高,工业余热、光热发电放出来的低品位热量根本带不动蒸汽轮机。而ORC用的是低沸点工质(比如R245fa),60℃以上的热水就能驱动膨胀机发电。我常跟人打比方:蒸汽朗肯循环是“只吃大餐”,ORC是“给啥剩饭都能打包”——温度不够高的热量,ORC照样能榨出电来。在综合能源系统里,ORC往往是打通“热”和“电”两个能源网络的关键接口。**P2G(电转气)**解决的是最头痛的“多余电量往哪塞”的问题。深夜风电大发、电网调度不出去的时候,与其弃风弃光,不如把电变成氢气和天然气存起来。P2G一般分两步:电解水制氢,然后有条件的话再把氢和二氧化碳甲烷化合成天然气。因为天然气管道和储气设施非常成熟,P2G产出的甲烷可以直接进天然气管网,相当于给电力系统装了一个超大容量的“气体电池”。三个设备串起来之后,系统就有了三条能量时移路径:热路(光热储能)、电-气路(P2G)、热-电路(ORC)。优化调度的本质,就是在这三条路径之间找最优分配。2. 三个核心设备的数学模型:先算清楚再谈优化优化调度这件事,90%的工作量不在于求解器跑得有多快,而在于设备模型建得对不对。模型粗了,调度结果看着漂亮但实际没法落地;模型细了,非线性约束一堆,求解器直接卡死。我下面给出的建模方式是工程实践里平衡精度和可解性的一个常用版本。2.1 光热电站:集热、储热、发电三段式模型光热电站我习惯拆成三个模块来建模:集热场、储热罐、发电岛。集热场的产热功率取决于太阳直射辐射DNI和集热面积,还要乘以集热效率。实际代码里我会简化为:P_abs(t) = eta_field * A_field * DNI(t)这里eta_field是集热效率,A_field是集热场面积。如果有人做过实际光热项目会知道,集热效率本身跟环境温度和风速有关,但调度级模型通常取一个分段常数就够了。储热罐的模型是调度里最容易出错的地方。它的核心是一个能量守恒方程:SOC_st(t) = SOC_st(t-1) + eta_ch * P_ch(t) * dt - P_dis(t) * dt / eta_dis注意几个细节:充热和放热分别有损耗系数,eta_ch和eta_dis一般不相等,而且不能在同一时刻既充又放——这是我见过很多初学者最容易忽略的逻辑约束。另外SOC要限制在[SOC_min, SOC_max],防止储热罐过热或者放空。发电岛就是把储热罐放出来的热量经过换热、推动汽轮机发电:P_ele_csp(t) = eta_pb * P_dis(t)发电功率有上下限约束、爬坡约束,还有最小运行时间约束(汽轮机不能频繁启停)。2.2 ORC机组:负载率影响效率,不能当作恒效率设备很多论文里把ORC当成一个固定效率的“黑箱”:输入热功率乘以0.13就完事。但这个假设在调度级会带来很大误差,因为ORC在低负载率的时候效率会明显下降,而且它有一个最小稳定出力,低到一定程度就得停机。我建议用下面的分段线性化模型来表示变效率特性。先定义负载率:r_orc(t) = P_ele_orc(t) / P_rate_orc净输出电功率等于膨胀机发电减去工质泵消耗:P_ele_orc(t) = eta_gen * P_exp(t) - P_pump(t)而发电效率跟负载率之间的关系,实测曲线上是一条凹曲线。工程上可以把负载率分成三段,每一段用线性方程逼近,避免直接做二次约束。ORC的启停约束是调度里真正的难点。停机之后的短时间内不能立刻重启,因为工质需要重新预热循环。在数学上这需要一个最小停运时间的0-1变量约束:y_on(t) + y_on(t+1) + ... <= 1 (在最小停运时间内最多启动一次)这个约束写起来麻烦,但跳过了它,算出来的调度方案在实操里根本执行不了。2.3 P2G电解槽与甲烷化:惰性大,必须考虑启停代价P2G里反应速度慢的设备是电解槽,它从冷态到满负荷运行往往需要几十分钟甚至更久。所以调度模型里P2G不能像储能电池那样想充就充。我用的是两阶段模型。第一阶段是电解槽,输入电功率,输出氢:V_h2(t) = eta_ele * P_p2g_ele(t) / LHV_h2第二阶段是甲烷化,消耗氢和二氧化碳生成甲烷:V_ch4(t) = eta_meth * V_h2(t) * LHV_h2 / LHV_ch4甲烷化后的天然气可以直接卖给气网或者给燃气轮机用。有的园区也会在中间加一个储氢罐,氢气既能供给甲烷化,也能直接卖给附近的加氢站——这个灵活度会直接影响调度结果。电解槽的功率调节范围通常是[20%, 100%]额定功率,低于20%就直接停机。这里必须设置一个决策变量y_p2g(t)表示启停状态,同时配合最小连续运行/停运时间约束。另外一个工程细节是:电解槽的启停是有寿命代价的,所以目标函数里最好放一个启动成本项,否则求解器会为了省几块钱电费让电解槽频繁启停。3. 综合优化调度模型:目标函数、约束条件、线性化处理三段设备模型各自成立之后,开始搭整个园区的优化调度框架。这一步的难点不在于单个设备的表达,而在于把电、热、气三条能源网络的平衡方程耦合到一起。3.1 目标函数:是一天运行的经济账,不是最小化用电成本我设计的目标函数是系统日运行总成本最小化,包括五个部分:min sum_t [ C_ele_buy(t) + C_gas_buy(t) - R_ele_sell(t) + C_om(t) + C_start(t) + C_co2(t) ]各项含义分别是:C_ele_buy:从电网购电的费用,按分时电价计算。价格曲线对光热和P2G的调度决策影响非常大,一定要拿到真实的峰谷电价数据去做。C_gas_buy:从气网购气的费用。R_ele_sell:系统向外售电的收入。注意这里卖电价格通常远低于买电价格,所以模型不会出现“低买高卖”套利漏洞,但储能的充放电决策会因此更保守。C_om:运维成本,按各设备的出力线性折算。C_start:启停成本,主要是电解槽和ORC的启动代价。系统避免了“频繁启停换调度收益”的虚假优化。C_co2:碳排放成本,给P2G的减排效益一个货币化标尺。有一个容易被忽略的点:P2G产出的甲烷如果输入管网,在目标函数里应该体现为“燃气销售收入”或者“减少购气成本”,二选一,绝不能两边都算。3.2 能量平衡约束:电、热、气三条母线园区内部会设三个虚拟“母线”——电母线、热母线、气母线,每个时刻的能量进出必须守恒。电平衡:P_load(t) + P_p2g_ele(t) = P_csp_ele(t) + P_orc_ele(t) + P_pv(t) + P_wind(t) + P_grid_buy(t) - P_grid_sell(t)热平衡:H_load(t) + H_ch_st(t) = H_solar(t) + H_boiler(t) + H_dis_st(t)注意系统里可能还有燃气锅炉作为备用热源,这里我把它放在了等式右侧。气平衡:G_load(t) = G_grid_buy(t) + G_p2g_ch4(t) - G_ch4_consumed(t)气平衡里如果没有燃气轮机或者燃气锅炉的用气项,P2G的产气就只能外售或者存到储气罐里,这会大幅改变调度行为,建模的时候要想清楚自己的园区到底给气网留了多大吞吐空间。3.3 非线性约束的线性化:这是Matlab代码实现中最容易翻车的地方非线性来源主要有两个:ORC变效率与负载率的乘积耦合,P2G功率与产氢量的非线性曲线。处理手法是分段线性化。以ORC为例。热功率与电功率的关系P_ele = f(P_thermal)如果是一条凸曲线,可以把热功率区间分成K段,每段用一个线性系数近似:P_thermal(t) = sum_k [ P_thermal_k(t) ] P_ele(t) = sum_k [ eta_k * P_thermal_k(t) ]再给每段加上0-1变量做约束,确保分段顺序正确。这个逻辑在Matlab里用Yalmip表达的话,就是implies和binvar的组合。这里有一个我必须提醒的事情:如果你直接把eta * P_thermal写成两个连续变量相乘的形式,求解器会报二次约束(QCQP),虽然Gurobi也能解,但求解速度和稳定性比纯MILP差不少。实践上我建议无论如何优先转成MILP,20个时间步的设备联合调度,MILP往往几十秒就出结果,而同样规模的二次约束可能要跑几分钟还未必收敛。4. Matlab代码实现的核心逻辑:按“声明变量—建约束—求解—回读”的顺序来整个代码框架我推荐用Yalmip + Gurobi的组合。Yalmip让声明优化变量和写约束变得非常直观,Gurobi负责底层的分支定界求解。如果你是学生或者评估用途,也可以用Cplex或开源的Cbc替代,步骤完全一致。4.1 数据准备:把设备参数和时序数据组织成结构体我习惯把所有静态参数放在一个params结构体里,把时序数据放在data结构体里。比如:dt = 1; % 时间步长,单位小时,24小时调度 T = 24; params.csp.eta_field = 0.45; % 集热效率 params.csp.A_field = 200000; % 集热场面积m2 params.csp.eta_pb = 0.38; % 发电岛热电转换效率 params.csp.SOC_min = 0.1; % 储热罐最小容量标幺 params.csp.SOC_max = 0.95; % 储热罐最大容量标幺 params.csp.P_rate = 30; % 光热发电额定功率MW params.orc.P_rate = 5; % ORC额定电功率MW params.orc.f_min = 0.3; % ORC最小负载率 params.orc.eta_seg = [0.08 0.11 0.13]; % 各分段效率 params.p2g.P_rate = 15; % 电解槽额定输入电功率MW params.p2g.eta_ele = 0.7; % 电解效率 params.p2g.eta_meth = 0.85; % 甲烷化效率 params.p2g.up_time = 2; % 最小连续运行时间h params.p2g.down_time = 1; % 最小连续停运时间h时序数据包括DNI、光伏出力、风电出力、电负荷、热负荷、分时电价等,全部弄成T×1的列向量。4.2 变量声明:连续变量、0-1变量各就各位% 决策变量 P_csp = sdpvar(T, 1); % 光热发电功率 SOC_st = sdpvar(T, 1); % 储热罐SOC标幺 P_ch = sdpvar(T, 1); % 储热罐充热功率 P_dis = sdpvar(T, 1); % 储热罐放热功率 P_orc = sdpvar(T, 1); % ORC发电功率 P_p2g = sdpvar(T, 1); % P2G输入电功率 V_ch4 = sdpvar(T, 1); % P2G产甲烷速率 % 0-1变量 y_csp = binvar(T, 1); % 光热发电机组启停 y_orc = binvar(T, 1); % ORC启停 y_p2g = binvar(T, 1); % P2G启停 y_charge = binvar(T, 1); % 储热罐充热状态 y_discharge = binvar(T, 1); % 储热罐放热状态这里引入y_charge和y_discharge是为了避免储热罐同时充放。约束就是y_charge + y_discharge <= 1,同时P_ch和P_dis分别用y_charge和y_discharge做上限约束。4.3 约束构建:用for循环逐时刻建立Yalmip里最常见的就是用for t = 1:T循环然后逐条加约束。以储热罐SOC衔接为例:Constraints = []; for t = 2:T Constraints = [Constraints, SOC_st(t) == SOC_st(t-1) ... + eta_ch * P_ch(t) * dt / 3600 ... - P_dis(t) * dt / 3600 / eta_dis]; end Constraints = [Constraints, SOC_st(1) == SOC_init]; Constraints = [Constraints, SOC_st >= params.csp.SOC_min]; Constraints = [Constraints, SOC_st <= params.csp.SOC_max];这里有个无数人踩过的坑:SOC的单位统一问题。储热罐容量如果是MWh,那充放热功率单位是MW、时间单位是h,直接乘dt就能得到MWh,但如果你用的是3600秒换算,就会把数量级搞错。我的建议是全部用电功率的单位体系来算,储热罐容量定为MWh,功率定为MW,时间步长定为h,全程不需要除以3600。上面代码里我写了/3600是因为很多教材用了kW和秒,导致代码里多有混乘,实际你自己写的时候务必统一。4.4 求解与结果回读求解配置上,Gurobi的gap控制很关键。对于24小时调度,设options.gap = 0.01就够了;如果是168小时的周调度,可以放宽到0.05。options = sdpsettings('solver', 'gurobi', 'verbose', 1); options.gurobi.MIPGap = 0.01; optimize(Constraints, Objective, options);求解完成后,把变量值返回给工作区:P_csp_val = value(P_csp); P_orc_val = value(P_orc); P_p2g_val = value(P_p2g); SOC_val = value(SOC_st);然后画出力曲线、SOC曲线、电平衡曲线。很多人在这一步就停了,但我会多说一句:任何优化结果都必须做“解可行性检查”——把求出来的设备出力代回原始模型(未线性化版本)验证一遍,确保不是线性化误差导致的边界外解。5. 典型日的调度结果与运行曲线:能看出什么门道我说一套典型的模拟结果,帮助没有实际跑过模型的朋友建立直觉。场景是某工业园区的夏季典型日,安装了30MW光热电站、5MW ORC机组、15MW P2G电解槽,光伏装机20MW,风电装机10MW,最大电负荷约50MW。典型日出力曲线大致长这样:06:00-10:00:光照逐渐增加,但负荷也在爬升。系统从电网少量购电,光热电站接收热量但发电机不急于并网,因为上午电价平段不高。储热罐开始蓄热,P2G维持最低运行状态。10:00-15:00:光伏大发、电价走低。这个时候系统会压低光热发电出力,甚至让光热完全停机,纯粹靠光伏满足负荷。多余电量全塞给P2G制氢,再甲烷化进气网。如果气网购气价比售电电价高,系统就有动力多产气。15:00-18:00:光伏开始衰减,但电价还未进入高峰,储热罐积攒的热量开始少量释放给ORC发电,弥补光伏出力下滑。18:00-21:00:晚高峰电价最高。储热罐全速放热,光热发电满发;ORC也从工业余热里榨出全部可发电量;P2G这时候会基本停机,因为把电拿去产气的收益远赶不上直接卖电。系统可能还会从电网多购一部分高价电,但相比无调度方案已经少购很多。21:00-24:00:电价回落,光热和ORC逐步减出力,储热罐进入低SOC状态,准备迎接第二天。这套曲线的核心思想简单说就是:**光热和储热负责“迁移”白天的太阳能,P2G负责“吸收”过剩电能,ORC负责“捡漏”低品位余热。**三者配合,让一天里每个时刻的边际发电成本尽量贴着负荷曲线走。6. 我踩过的坑和给后来者的排查建议最后这部分是花的时间最多、也最希望有人早点告诉我的东西。6.1 “不可行解”大概率不是数据问题,而是约束自相矛盾我第一次跑通模型时,Gurobi报出infeasible,第一反应是怀疑电价数据不对。后来用gurobi的IIS(不可行性分析)才查出来,问题出在储热罐SOC约束和最小运行时间约束的冲突上:储热罐在半夜被迫放热以满足某个最小放热时长约束,但放出来的热又没有负荷接盘,导致热量平衡等式无解。解决办法是给储热罐的充放电加入一个“允许零功率运行”的弹性约束,同时在热平衡方程里加上一个“弃热”变量表示可以排掉多余热量。工程现实就是这样——园区里会有冷却塔处理多余的无法利用的热量,模型里也得给人留一个出口。6.2 二进制变量别一上来就放大时间尺度如果你把调度周期设为168小时,每个设备还带着最小启停时间约束,二进制变量数量会爆炸。我举个例子:光热、ORC、P2G三个设备,每个含3-5个0-1变量,168步就是2000多个二进制变量,加上连续变量、约束数量上万条,Gurobi能解但很慢。实践策略是分阶段调度:先以24小时为周期做日前调度,把每个小时的设备启停状态定下来;再以15分钟为粒度做日内滚动修正,只调整连续变量(功率水平),不碰0-1变量。这既是计算效率的取舍,也更符合实际电网调度流程。6.3 热负荷缺口的处理最容易被低估综合能源系统和普通电力系统的最大区别在于:热负荷是有惯性、有延迟的。热水管网里的工质流动需要时间,热负荷对供热功率的响应不是即时的。我在第一版模型里把热负荷当成刚性约束,结果调度结果异常激进,储热罐在高价时段拼命放热,但在实际管网上热水还没送到末端用户,调度的“最优解”其实无效。改进方式是给热负荷加一个温度滞后环节,不做成一阶动态,而是做一个“允许短时略微失稳,但全天累计供热量平衡”的松弛约束。这在调度级模型里是很多人不会提但实际非常重要的一个修正。6.4 关于P2G产物去向,先想清楚气网能不能反送部分项目在书面上设计了P2G的甲烷返送气网,但实际情况是当地燃气管网的进气压力和气质指标未必允许。如果你的项目也面临这个问题,在优化模型里建议把“售气收入”改成“自用燃气锅炉燃料成本节约”,并加上“气网买入量不小于零”的下限约束,否则模型会算出一个根本没法执行的高产气量。我后来给某园区做的优化模型里,P2G的甲烷化模块是可选旁路——氢气既可以进甲烷化进管网,也可以用储氢罐存着卖给周边加氢站。这样系统的灵活性大很多,电价低时电解槽制氢,氢价高时卖氢,甲烷化只在天然气价格高于氢价折算价时才开启。这个开关本身又是一个0-1变量,但对经济性的提升非常显著。6.5 别忘了给设备“休息时间”,否则运维方不会接受做优化调度的人往往只盯着经济最优,忽略了设备实际运行约束。比如连续高温天气下,光热电站的熔盐泵不能长时间满负荷运行,需要检修窗口;ORC的膨胀机组连续运行几天后要降低负载“透一口气”。这些约束如果不在模型里体现,调度系统可能在第三天安排光热和ORC连续满发,现场根本执行不了。我的做法是在目标函数里加一个“疲劳惩罚项”——运行时间越长,微增运维成本系数越高。这虽然让日前调度的总成本略微上升,但换来了设备的可执行性。跟运维团队打交道多了就明白,一套“算得出但开不了机”的调度程序,装了也是白装。7. 几点个人体会光热电站、有机朗肯循环、P2G这三者的组合,初看是三个设备放在一个园区里,实际是三套时间常数完全不同的能量转换装置被硬拧在一个优化框架里。光热的储热罐是小时级调节,ORC的启停是分钟级响应,P2G的电解槽是秒级但效率与负载强相关——把这三类动态特性塞进同一个MILP模型里,本身就是一件需要反复打磨的事。我做这套系统的最大感受:别指望单靠一个求解器解决所有工程问题。优化调度模型只是给决策层提供一个基准方案,真正的落地还需要气象预测、设备健康状态、市场电价预测这些外围模块的支撑。模型越精致,对输入数据的敏感度就越高,预测有一点偏差,调度方案就可能会走样。给计划入坑的朋友一个建议:先从24小时静态调度跑通,再逐步加入P2G的启停约束、光热储热罐的SOC联动、最后考虑ORC的变效率线性化。每加一类约束,就回去检查一次解的可行性,千万别全部堆完一次性调试——那时候报错根本不知道从哪查起。如果你正准备用Matlab搭这套模型,我希望这篇分享能帮你少走我走过的弯路。代码不是最难的部分,最难的是对每个设备物理特性的理解有没有体现到约束里。模型里的每一行约束,最终对应的都是现场一台真实的阀门、一条真实的管道。想清楚这一点,很多取舍就自然有答案了。
1. 这套系统到底在做什么:CSP+ORC+P2G的互补逻辑先说结论:光热电站、有机朗肯循环、P2G这三样东西放一起,核心解决的不是“多发电”,而是“让每个能量品种都在最该出现的时刻出现”。我做过的某个模拟园区项目里,光伏和风电装了不少,但下午光照最猛的时候电价反而最低,上网就是亏;晚上电价拉满,本地却没有出力手段。与此同时,园区里有一堆工业余热,常年排掉没人管。后来我们把这三套设备加进调度模型,场景才真正变得有意思起来。**光热电站(CSP)**和光伏最大的区别在于它自带储热。白天集热场吸收太阳能后,可以先把热量存进熔盐罐,等到电价高的晚高峰再放热发电。说白了,光热电站相当于一个“太阳能的蓄电池”,只不过存进去的是卡路里而不是库仑。调度模型里最需要小心的就是这个储热罐的充放热逻辑——它决定了光热能不能把白天便宜的太阳能挪到晚上卖。**有机朗肯循环(ORC)**在这里是专门啃“低品位热量”的。常规蒸汽朗肯循环对热源温度要求高,工业余热、光热发电放出来的低品位热量根本带不动蒸汽轮机。而ORC用的是低沸点工质(比如R245fa),60℃以上的热水就能驱动膨胀机发电。我常跟人打比方:蒸汽朗肯循环是“只吃大餐”,ORC是“给啥剩饭都能打包”——温度不够高的热量,ORC照样能榨出电来。在综合能源系统里,ORC往往是打通“热”和“电”两个能源网络的关键接口。**P2G(电转气)**解决的是最头痛的“多余电量往哪塞”的问题。深夜风电大发、电网调度不出去的时候,与其弃风弃光,不如把电变成氢气和天然气存起来。P2G一般分两步:电解水制氢,然后有条件的话再把氢和二氧化碳甲烷化合成天然气。因为天然气管道和储气设施非常成熟,P2G产出的甲烷可以直接进天然气管网,相当于给电力系统装了一个超大容量的“气体电池”。三个设备串起来之后,系统就有了三条能量时移路径:热路(光热储能)、电-气路(P2G)、热-电路(ORC)。优化调度的本质,就是在这三条路径之间找最优分配。2. 三个核心设备的数学模型:先算清楚再谈优化优化调度这件事,90%的工作量不在于求解器跑得有多快,而在于设备模型建得对不对。模型粗了,调度结果看着漂亮但实际没法落地;模型细了,非线性约束一堆,求解器直接卡死。我下面给出的建模方式是工程实践里平衡精度和可解性的一个常用版本。2.1 光热电站:集热、储热、发电三段式模型光热电站我习惯拆成三个模块来建模:集热场、储热罐、发电岛。集热场的产热功率取决于太阳直射辐射DNI和集热面积,还要乘以集热效率。实际代码里我会简化为:P_abs(t) = eta_field * A_field * DNI(t)这里eta_field是集热效率,A_field是集热场面积。如果有人做过实际光热项目会知道,集热效率本身跟环境温度和风速有关,但调度级模型通常取一个分段常数就够了。储热罐的模型是调度里最容易出错的地方。它的核心是一个能量守恒方程:SOC_st(t) = SOC_st(t-1) + eta_ch * P_ch(t) * dt - P_dis(t) * dt / eta_dis注意几个细节:充热和放热分别有损耗系数,eta_ch和eta_dis一般不相等,而且不能在同一时刻既充又放——这是我见过很多初学者最容易忽略的逻辑约束。另外SOC要限制在[SOC_min, SOC_max],防止储热罐过热或者放空。发电岛就是把储热罐放出来的热量经过换热、推动汽轮机发电:P_ele_csp(t) = eta_pb * P_dis(t)发电功率有上下限约束、爬坡约束,还有最小运行时间约束(汽轮机不能频繁启停)。2.2 ORC机组:负载率影响效率,不能当作恒效率设备很多论文里把ORC当成一个固定效率的“黑箱”:输入热功率乘以0.13就完事。但这个假设在调度级会带来很大误差,因为ORC在低负载率的时候效率会明显下降,而且它有一个最小稳定出力,低到一定程度就得停机。我建议用下面的分段线性化模型来表示变效率特性。先定义负载率:r_orc(t) = P_ele_orc(t) / P_rate_orc净输出电功率等于膨胀机发电减去工质泵消耗:P_ele_orc(t) = eta_gen * P_exp(t) - P_pump(t)而发电效率跟负载率之间的关系,实测曲线上是一条凹曲线。工程上可以把负载率分成三段,每一段用线性方程逼近,避免直接做二次约束。ORC的启停约束是调度里真正的难点。停机之后的短时间内不能立刻重启,因为工质需要重新预热循环。在数学上这需要一个最小停运时间的0-1变量约束:y_on(t) + y_on(t+1) + ... <= 1 (在最小停运时间内最多启动一次)这个约束写起来麻烦,但跳过了它,算出来的调度方案在实操里根本执行不了。2.3 P2G电解槽与甲烷化:惰性大,必须考虑启停代价P2G里反应速度慢的设备是电解槽,它从冷态到满负荷运行往往需要几十分钟甚至更久。所以调度模型里P2G不能像储能电池那样想充就充。我用的是两阶段模型。第一阶段是电解槽,输入电功率,输出氢:V_h2(t) = eta_ele * P_p2g_ele(t) / LHV_h2第二阶段是甲烷化,消耗氢和二氧化碳生成甲烷:V_ch4(t) = eta_meth * V_h2(t) * LHV_h2 / LHV_ch4甲烷化后的天然气可以直接卖给气网或者给燃气轮机用。有的园区也会在中间加一个储氢罐,氢气既能供给甲烷化,也能直接卖给附近的加氢站——这个灵活度会直接影响调度结果。电解槽的功率调节范围通常是[20%, 100%]额定功率,低于20%就直接停机。这里必须设置一个决策变量y_p2g(t)表示启停状态,同时配合最小连续运行/停运时间约束。另外一个工程细节是:电解槽的启停是有寿命代价的,所以目标函数里最好放一个启动成本项,否则求解器会为了省几块钱电费让电解槽频繁启停。3. 综合优化调度模型:目标函数、约束条件、线性化处理三段设备模型各自成立之后,开始搭整个园区的优化调度框架。这一步的难点不在于单个设备的表达,而在于把电、热、气三条能源网络的平衡方程耦合到一起。3.1 目标函数:是一天运行的经济账,不是最小化用电成本我设计的目标函数是系统日运行总成本最小化,包括五个部分:min sum_t [ C_ele_buy(t) + C_gas_buy(t) - R_ele_sell(t) + C_om(t) + C_start(t) + C_co2(t) ]各项含义分别是:C_ele_buy:从电网购电的费用,按分时电价计算。价格曲线对光热和P2G的调度决策影响非常大,一定要拿到真实的峰谷电价数据去做。C_gas_buy:从气网购气的费用。R_ele_sell:系统向外售电的收入。注意这里卖电价格通常远低于买电价格,所以模型不会出现“低买高卖”套利漏洞,但储能的充放电决策会因此更保守。C_om:运维成本,按各设备的出力线性折算。C_start:启停成本,主要是电解槽和ORC的启动代价。系统避免了“频繁启停换调度收益”的虚假优化。C_co2:碳排放成本,给P2G的减排效益一个货币化标尺。有一个容易被忽略的点:P2G产出的甲烷如果输入管网,在目标函数里应该体现为“燃气销售收入”或者“减少购气成本”,二选一,绝不能两边都算。3.2 能量平衡约束:电、热、气三条母线园区内部会设三个虚拟“母线”——电母线、热母线、气母线,每个时刻的能量进出必须守恒。电平衡:P_load(t) + P_p2g_ele(t) = P_csp_ele(t) + P_orc_ele(t) + P_pv(t) + P_wind(t) + P_grid_buy(t) - P_grid_sell(t)热平衡:H_load(t) + H_ch_st(t) = H_solar(t) + H_boiler(t) + H_dis_st(t)注意系统里可能还有燃气锅炉作为备用热源,这里我把它放在了等式右侧。气平衡:G_load(t) = G_grid_buy(t) + G_p2g_ch4(t) - G_ch4_consumed(t)气平衡里如果没有燃气轮机或者燃气锅炉的用气项,P2G的产气就只能外售或者存到储气罐里,这会大幅改变调度行为,建模的时候要想清楚自己的园区到底给气网留了多大吞吐空间。3.3 非线性约束的线性化:这是Matlab代码实现中最容易翻车的地方非线性来源主要有两个:ORC变效率与负载率的乘积耦合,P2G功率与产氢量的非线性曲线。处理手法是分段线性化。以ORC为例。热功率与电功率的关系P_ele = f(P_thermal)如果是一条凸曲线,可以把热功率区间分成K段,每段用一个线性系数近似:P_thermal(t) = sum_k [ P_thermal_k(t) ] P_ele(t) = sum_k [ eta_k * P_thermal_k(t) ]再给每段加上0-1变量做约束,确保分段顺序正确。这个逻辑在Matlab里用Yalmip表达的话,就是implies和binvar的组合。这里有一个我必须提醒的事情:如果你直接把eta * P_thermal写成两个连续变量相乘的形式,求解器会报二次约束(QCQP),虽然Gurobi也能解,但求解速度和稳定性比纯MILP差不少。实践上我建议无论如何优先转成MILP,20个时间步的设备联合调度,MILP往往几十秒就出结果,而同样规模的二次约束可能要跑几分钟还未必收敛。4. Matlab代码实现的核心逻辑:按“声明变量—建约束—求解—回读”的顺序来整个代码框架我推荐用Yalmip + Gurobi的组合。Yalmip让声明优化变量和写约束变得非常直观,Gurobi负责底层的分支定界求解。如果你是学生或者评估用途,也可以用Cplex或开源的Cbc替代,步骤完全一致。4.1 数据准备:把设备参数和时序数据组织成结构体我习惯把所有静态参数放在一个params结构体里,把时序数据放在data结构体里。比如:dt = 1; % 时间步长,单位小时,24小时调度 T = 24; params.csp.eta_field = 0.45; % 集热效率 params.csp.A_field = 200000; % 集热场面积m2 params.csp.eta_pb = 0.38; % 发电岛热电转换效率 params.csp.SOC_min = 0.1; % 储热罐最小容量标幺 params.csp.SOC_max = 0.95; % 储热罐最大容量标幺 params.csp.P_rate = 30; % 光热发电额定功率MW params.orc.P_rate = 5; % ORC额定电功率MW params.orc.f_min = 0.3; % ORC最小负载率 params.orc.eta_seg = [0.08 0.11 0.13]; % 各分段效率 params.p2g.P_rate = 15; % 电解槽额定输入电功率MW params.p2g.eta_ele = 0.7; % 电解效率 params.p2g.eta_meth = 0.85; % 甲烷化效率 params.p2g.up_time = 2; % 最小连续运行时间h params.p2g.down_time = 1; % 最小连续停运时间h时序数据包括DNI、光伏出力、风电出力、电负荷、热负荷、分时电价等,全部弄成T×1的列向量。4.2 变量声明:连续变量、0-1变量各就各位% 决策变量 P_csp = sdpvar(T, 1); % 光热发电功率 SOC_st = sdpvar(T, 1); % 储热罐SOC标幺 P_ch = sdpvar(T, 1); % 储热罐充热功率 P_dis = sdpvar(T, 1); % 储热罐放热功率 P_orc = sdpvar(T, 1); % ORC发电功率 P_p2g = sdpvar(T, 1); % P2G输入电功率 V_ch4 = sdpvar(T, 1); % P2G产甲烷速率 % 0-1变量 y_csp = binvar(T, 1); % 光热发电机组启停 y_orc = binvar(T, 1); % ORC启停 y_p2g = binvar(T, 1); % P2G启停 y_charge = binvar(T, 1); % 储热罐充热状态 y_discharge = binvar(T, 1); % 储热罐放热状态这里引入y_charge和y_discharge是为了避免储热罐同时充放。约束就是y_charge + y_discharge <= 1,同时P_ch和P_dis分别用y_charge和y_discharge做上限约束。4.3 约束构建:用for循环逐时刻建立Yalmip里最常见的就是用for t = 1:T循环然后逐条加约束。以储热罐SOC衔接为例:Constraints = []; for t = 2:T Constraints = [Constraints, SOC_st(t) == SOC_st(t-1) ... + eta_ch * P_ch(t) * dt / 3600 ... - P_dis(t) * dt / 3600 / eta_dis]; end Constraints = [Constraints, SOC_st(1) == SOC_init]; Constraints = [Constraints, SOC_st >= params.csp.SOC_min]; Constraints = [Constraints, SOC_st <= params.csp.SOC_max];这里有个无数人踩过的坑:SOC的单位统一问题。储热罐容量如果是MWh,那充放热功率单位是MW、时间单位是h,直接乘dt就能得到MWh,但如果你用的是3600秒换算,就会把数量级搞错。我的建议是全部用电功率的单位体系来算,储热罐容量定为MWh,功率定为MW,时间步长定为h,全程不需要除以3600。上面代码里我写了/3600是因为很多教材用了kW和秒,导致代码里多有混乘,实际你自己写的时候务必统一。4.4 求解与结果回读求解配置上,Gurobi的gap控制很关键。对于24小时调度,设options.gap = 0.01就够了;如果是168小时的周调度,可以放宽到0.05。options = sdpsettings('solver', 'gurobi', 'verbose', 1); options.gurobi.MIPGap = 0.01; optimize(Constraints, Objective, options);求解完成后,把变量值返回给工作区:P_csp_val = value(P_csp); P_orc_val = value(P_orc); P_p2g_val = value(P_p2g); SOC_val = value(SOC_st);然后画出力曲线、SOC曲线、电平衡曲线。很多人在这一步就停了,但我会多说一句:任何优化结果都必须做“解可行性检查”——把求出来的设备出力代回原始模型(未线性化版本)验证一遍,确保不是线性化误差导致的边界外解。5. 典型日的调度结果与运行曲线:能看出什么门道我说一套典型的模拟结果,帮助没有实际跑过模型的朋友建立直觉。场景是某工业园区的夏季典型日,安装了30MW光热电站、5MW ORC机组、15MW P2G电解槽,光伏装机20MW,风电装机10MW,最大电负荷约50MW。典型日出力曲线大致长这样:06:00-10:00:光照逐渐增加,但负荷也在爬升。系统从电网少量购电,光热电站接收热量但发电机不急于并网,因为上午电价平段不高。储热罐开始蓄热,P2G维持最低运行状态。10:00-15:00:光伏大发、电价走低。这个时候系统会压低光热发电出力,甚至让光热完全停机,纯粹靠光伏满足负荷。多余电量全塞给P2G制氢,再甲烷化进气网。如果气网购气价比售电电价高,系统就有动力多产气。15:00-18:00:光伏开始衰减,但电价还未进入高峰,储热罐积攒的热量开始少量释放给ORC发电,弥补光伏出力下滑。18:00-21:00:晚高峰电价最高。储热罐全速放热,光热发电满发;ORC也从工业余热里榨出全部可发电量;P2G这时候会基本停机,因为把电拿去产气的收益远赶不上直接卖电。系统可能还会从电网多购一部分高价电,但相比无调度方案已经少购很多。21:00-24:00:电价回落,光热和ORC逐步减出力,储热罐进入低SOC状态,准备迎接第二天。这套曲线的核心思想简单说就是:**光热和储热负责“迁移”白天的太阳能,P2G负责“吸收”过剩电能,ORC负责“捡漏”低品位余热。**三者配合,让一天里每个时刻的边际发电成本尽量贴着负荷曲线走。6. 我踩过的坑和给后来者的排查建议最后这部分是花的时间最多、也最希望有人早点告诉我的东西。6.1 “不可行解”大概率不是数据问题,而是约束自相矛盾我第一次跑通模型时,Gurobi报出infeasible,第一反应是怀疑电价数据不对。后来用gurobi的IIS(不可行性分析)才查出来,问题出在储热罐SOC约束和最小运行时间约束的冲突上:储热罐在半夜被迫放热以满足某个最小放热时长约束,但放出来的热又没有负荷接盘,导致热量平衡等式无解。解决办法是给储热罐的充放电加入一个“允许零功率运行”的弹性约束,同时在热平衡方程里加上一个“弃热”变量表示可以排掉多余热量。工程现实就是这样——园区里会有冷却塔处理多余的无法利用的热量,模型里也得给人留一个出口。6.2 二进制变量别一上来就放大时间尺度如果你把调度周期设为168小时,每个设备还带着最小启停时间约束,二进制变量数量会爆炸。我举个例子:光热、ORC、P2G三个设备,每个含3-5个0-1变量,168步就是2000多个二进制变量,加上连续变量、约束数量上万条,Gurobi能解但很慢。实践策略是分阶段调度:先以24小时为周期做日前调度,把每个小时的设备启停状态定下来;再以15分钟为粒度做日内滚动修正,只调整连续变量(功率水平),不碰0-1变量。这既是计算效率的取舍,也更符合实际电网调度流程。6.3 热负荷缺口的处理最容易被低估综合能源系统和普通电力系统的最大区别在于:热负荷是有惯性、有延迟的。热水管网里的工质流动需要时间,热负荷对供热功率的响应不是即时的。我在第一版模型里把热负荷当成刚性约束,结果调度结果异常激进,储热罐在高价时段拼命放热,但在实际管网上热水还没送到末端用户,调度的“最优解”其实无效。改进方式是给热负荷加一个温度滞后环节,不做成一阶动态,而是做一个“允许短时略微失稳,但全天累计供热量平衡”的松弛约束。这在调度级模型里是很多人不会提但实际非常重要的一个修正。6.4 关于P2G产物去向,先想清楚气网能不能反送部分项目在书面上设计了P2G的甲烷返送气网,但实际情况是当地燃气管网的进气压力和气质指标未必允许。如果你的项目也面临这个问题,在优化模型里建议把“售气收入”改成“自用燃气锅炉燃料成本节约”,并加上“气网买入量不小于零”的下限约束,否则模型会算出一个根本没法执行的高产气量。我后来给某园区做的优化模型里,P2G的甲烷化模块是可选旁路——氢气既可以进甲烷化进管网,也可以用储氢罐存着卖给周边加氢站。这样系统的灵活性大很多,电价低时电解槽制氢,氢价高时卖氢,甲烷化只在天然气价格高于氢价折算价时才开启。这个开关本身又是一个0-1变量,但对经济性的提升非常显著。6.5 别忘了给设备“休息时间”,否则运维方不会接受做优化调度的人往往只盯着经济最优,忽略了设备实际运行约束。比如连续高温天气下,光热电站的熔盐泵不能长时间满负荷运行,需要检修窗口;ORC的膨胀机组连续运行几天后要降低负载“透一口气”。这些约束如果不在模型里体现,调度系统可能在第三天安排光热和ORC连续满发,现场根本执行不了。我的做法是在目标函数里加一个“疲劳惩罚项”——运行时间越长,微增运维成本系数越高。这虽然让日前调度的总成本略微上升,但换来了设备的可执行性。跟运维团队打交道多了就明白,一套“算得出但开不了机”的调度程序,装了也是白装。7. 几点个人体会光热电站、有机朗肯循环、P2G这三者的组合,初看是三个设备放在一个园区里,实际是三套时间常数完全不同的能量转换装置被硬拧在一个优化框架里。光热的储热罐是小时级调节,ORC的启停是分钟级响应,P2G的电解槽是秒级但效率与负载强相关——把这三类动态特性塞进同一个MILP模型里,本身就是一件需要反复打磨的事。我做这套系统的最大感受:别指望单靠一个求解器解决所有工程问题。优化调度模型只是给决策层提供一个基准方案,真正的落地还需要气象预测、设备健康状态、市场电价预测这些外围模块的支撑。模型越精致,对输入数据的敏感度就越高,预测有一点偏差,调度方案就可能会走样。给计划入坑的朋友一个建议:先从24小时静态调度跑通,再逐步加入P2G的启停约束、光热储热罐的SOC联动、最后考虑ORC的变效率线性化。每加一类约束,就回去检查一次解的可行性,千万别全部堆完一次性调试——那时候报错根本不知道从哪查起。如果你正准备用Matlab搭这套模型,我希望这篇分享能帮你少走我走过的弯路。代码不是最难的部分,最难的是对每个设备物理特性的理解有没有体现到约束里。模型里的每一行约束,最终对应的都是现场一台真实的阀门、一条真实的管道。想清楚这一点,很多取舍就自然有答案了。