
前阵子我在做一个区域综合能源的优化调度项目核心设备是光热电站、有机朗肯循环ORC和P2G电转气整套模型和调度代码用Matlab实现。这类系统现在已经不是单纯的概念了光热电站自带储热能让可再生能源出力变得可控ORC负责把中低温余热捡回来继续发电P2G则把富余电力变成天然气把电网和气网连在一起。把这几个模块揉进同一个优化模型瓶颈往往不在理论本身而在怎么把它们老老实实翻译成可求解的数学问题。这篇文章就把我当时建模、写码、踩坑的完整过程复盘一遍打算做综合能源调度、写论文或者做工程预研的同学可以参考。我的场景是一个并网型综合能源系统包含风电、光伏、带熔盐储热的光热电站、ORC余热发电、P2G装置、燃气锅炉、电锅炉以及电、热、气三类负荷。整个系统有两个核心痛点第一风电光伏出力波动大容易出现弃电第二光热电站虽然自带储热但发电和储热本来就争夺同一个热源要是不做统一调度热量分配会很浪费。加一个ORC进去是为了把中低温余热二次发电加一个P2G进去是为了给弃电找一条转存成天然气的出路。这三条线最后都会汇聚到同一个优化模型里由调度中心统一决定每个时刻该发出多少电、充多少热、制多少气。1. 项目背景为什么把光热、ORC和P2G放进同一个模型1.1 光热电站不是“不能调”的新能源大家一提太阳能第一反应就是光伏。光伏的出力跟光照强度直接挂钩中午高、早晚低、阴天几乎归零而且没有惯性电网只能被动消纳。光热电站完全是另一套逻辑它用定日镜或槽式集热器把太阳光转换成高温热能存在熔盐罐或者导热油罐里需要发电的时候再放热驱动汽轮机。储热罐相当于一个“能量仓库”所以光热电站的发电功率可以在一定范围内平移甚至夜间也能顶上几个小时的负荷。但这里有个容易忽略的约束储热罐里的热量不是从天上掉下来的它只能靠白天的太阳辐照慢慢充。如果调度不合理可能出现两种情况——要么白天把热全拿去发电导致晚上储热罐放空要么白天拼命储热结果发电量不够反而买了高价电。更麻烦的是光热发电的汽轮机组有一个最小技术出力不是想发多少就发多少这个非凸特性在建模时必须用0-1变量处理。我在做这个项目时最早把光热电站当成一个“可调电源”简单处理结果出来的方案白天疯狂停机、晚上疯狂发电完全没法工程落地。1.2 ORC在综合能源里的真实作用有机朗肯循环ORC是这整套系统里“捡漏”的角色。传统朗肯循环用水做工质要在高温下才能高效运行但很多系统里的余热温度只有100℃上下水根本没法用。ORC用的是低沸点有机工质比如R245fa、R134a80℃到300℃的热源都可以拿来发电只是效率不高通常7%到13%左右。在咱们这个模型里ORC接在光热电站的“尾巴”上。光热汽轮机做功后排出的乏汽或者冷却余热温度已经不高直接扔掉太浪费把这部分低品位热量引给ORC就能额外挤出一部分电。这个思路在工程上叫能源梯级利用虽然单看ORC效率不高但它用的是一份“原本要丢弃的热”多出来的电几乎是纯收益。不过调度模型里必须写清楚ORC的热量输入同样来自储热系统它跟光热发电、对外供热是在同一个热源池里抢热量不能既想储热多又想发电多还想ORC一直满发。1.3 P2G的价值体现在跨界P2GPower to Gas是电转气核心设备是电解槽有时候后面还加一套甲烷化装置。它的作用是把电力变成氢或者合成天然气从而把电力系统和天然气系统连通起来。为什么要让模型里带P2G我的核心考虑是“弃电转气”。比如某个大风天的凌晨风电大发负荷很低电又不方便存储这时候与其弃掉不如把电力注入电解槽制出氢气再通过甲烷化变成天然气进入管网或者供给园区气负荷。整个过程综合效率并不高大约0.6到0.7制出来的天然气在市场上也没有巨大的价格优势但它的意义在于给系统增加了一条柔性通道电价低、弃电多的时候就转气气价高、气负荷吃紧的时候就用气。这在优化模型里表现为一种可平移的灵活负荷能显著改善新能源消纳率。但P2G千万不能模型化得太理想。我第一次建模时直接把P2G输出当成“纯收益”放在目标函数里没有搭配输入电功率和运行范围约束结果优化器把P2G当成印钞机电价高的时候也让它运行白白亏钱。后来加上了效率约束和上下限又发现需要与购气成本放在同一个口径里对比才能避免“自产气比买气贵还非要用自家气”的逻辑矛盾。1.4 三个设备合起来的调度价值把光热、ORC、P2G放在同一个优化框架里核心价值不是单个设备的利润而是时间维度和能源形态维度的互补。光热储热解决“时间平移”ORC解决“品位提升”P2G解决“跨网转化”。三者放在同一个模型里联合优化得到的是一个全局最优的调度策略而不是每个设备各管各的。实际上我在项目推进中对比过“分步调度”和“联合调度”的结果分步调度往往会出现白天光热拼命发电晚上储热放空ORC只能跟着停机而P2G因为买不到低谷电也形同虚设。联合调度后白天光热会适当少发一点储热充到高位ORC在白天用余热发电晚上或者高负荷时段再让光热和储热联合出力P2G则只在凌晨风电过剩时段启动。整体运行成本能降不少弃电率也有明显改善。这个对比说明综合能源优化调度的意义就在“综合”两个字上。2. 系统建模把每个设备翻译成数学约束2.1 光热电站的数学表达光热电站的建模我分成三个模块集热场、储热罐、动力发电单元。集热场的输出热功率主要由三样东西决定法向直接辐射DNI、集热场面积A_sf和集热效率η_sf。[ Q_{sf}(t) \eta_{sf} \cdot A_{sf} \cdot DNI(t) ]η_sf严格说不是常数它跟太阳高度角、环境温度、镜场清洁度都有关我做项目时取了时段平均效率满足调度粒度的要求。DNI用的是典型日逐时数据这个数据在模型里属于外生输入类似风电的预测功率。储热罐的动态是系统建模最关键的部分[ S_{tes}(t1) S_{tes}(t) \eta_{ch} \cdot Q_{ch}(t) - \frac{Q_{dis}(t)}{\eta_{dis}} ]其中S_tes是储热罐当前储热量Q_ch(t)是充热功率Q_dis(t)是放热功率η_ch和η_dis分别是充放热效率。我一般设置储热罐容量上限S_max以及周期调度约束初始储热量S(1)等于末端储热量S(T1)不然优化器会把初始的那一罐热“白嫖”掉算出来的费用虚假偏低。光热发电模块要表达成——输入热功率Q_pb(t)乘上热电转换效率η_pb得到电功率P_csp(t)。但这个转换不是任意比例的汽轮机有最小运行负荷我加了二进制变量u_csp(t)来描述启停P_csp(t)大于等于P_csp_min乘u_csp(t)小于等于P_csp_max乘u_csp(t)。这种约束在优化里叫“0-1约束”也是MILP模型里最让人头疼的那部分。2.2 ORC的简化与边界ORC模块相对简单因为它的本质是一个热转电过程核心变量是输入热功率Q_orc_in(t)和输出电功率P_orc(t)。我在项目初期用了恒定效率模型[ P_{orc}(t) \eta_{orc} \cdot Q_{orc_in}(t) ]η_orc取多少这要看你针对的热源温度。我用的R245fa工质热源温度在120℃左右η_orc取10%。如果你要写论文比较严谨的做法是分段线性效率曲线低负荷率时效率低一点中高负荷率时效率高一点用几个区间描述。这样模型会复杂一点但结果更接近实际。ORC模型里还有一个容易被忽略的点最小启动热功率。ORC设备不是有一点余热就能转的热源太小时工质都无法气化完全所以我设置了Q_orc_min和Q_orc_max对应二进制变量u_orc(t)。如果你不想用额外二进制变量也可以直接让ORC的最低输入热功率等于0但这样会让模型在某些时段“制造”出很小的输出功率实际工程里很难看。我自己实际做的时候为了控制整数变量数量把ORC处理成“跟随模式”只要光热汽轮机运行ORC就有一个最低出力区储热罐放电或集热场有多余热时ORC参与分配热量。这样调度变量少求解速度快结果也够用。2.3 P2G环节的电转气链条P2G链路我建了两段电解槽和甲烷化。为了不把模型搞得太复杂我在常规项目里只关心输入电功率P_p2g_in(t)和输出的天然气功率F_p2g_out(t)中间用一个总效率η_p2g串联[ F_{p2g_out}(t) \eta_{p2g} \cdot P_{p2g_in}(t) ]这里的单位要统一P_p2g_in是电功率MWeF_p2g_out是天然气热功率MWthη_p2g通常是0.6左右。如果输出的是氢气则效率按0.7算如果后续甲烷化成SNG则按0.6算这个口径影响很大一定要保持一致。P2G的运行范围同样需要上下限不能允许它在0到额定容量之间任意连续变化尤其电解槽部分负荷太低时效率会显著下降所以我设了最小运行功率P_p2g_min。如果园区配了储氢罐或储气罐再额外加一条动态方程[ V_{gas}(t1) V_{gas}(t) F_{p2g_out}(t) - F_{gas_use}(t) ]这样P2G就有能力“错峰生产”夜里制气存起来白天用气高峰放出来。不过本项目我为了聚焦光热电站在热平衡中的作用暂时没加储气罐P2G生产的天然气直接流向气负荷或外售。2.4 热网与负荷之间的连接关系综合能源系统的有趣之处在于电、热、气三条平衡不是独立的。我们系统里有燃气锅炉、电锅炉、光热集热场、储热罐、ORC、热负荷每一个设备都连着热这一条主线。热平衡写成[ Q_{csp_heat}(t) Q_{gb}(t) Q_{eb}(t) Q_{tes_dis}(t) Q_{load}(t) Q_{tes_ch}(t) Q_{orc_in}(t) ]等式左边是热源供出的热量右边是热负荷、储热罐充热和ORC输入热的总和。这个方程是整个模型里最容易写错的地方因为同一股热既要去发电、又要去供暖、还要去充罐分布式地多通路分配时不能只写某一个设备的平衡。电平衡相对直白[ P_{buy}(t) P_{wt}(t) P_{pv}(t) P_{csp}(t) P_{orc}(t) P_{load}(t) P_{p2g}(t) P_{sell}(t) P_{eb}(t) ]电锅炉在这里相当于一个电-热耦合负荷P2G相当于一个可控电负荷气平衡则包含购气、P2G产气和气负荷。三条平衡方程都建好这个系统才算“耦合”起来后面的优化才有意义。3. 优化调度模型目标函数与约束条件设计3.1 目标函数算总账而不是算单台设备优化调度的目标函数我用的是“系统总运行成本最小”包括购电成本、购气成本、设备运维成本、弃电惩罚同时把P2G产气的外售收益或自用价值抵消掉。[ \min \sum_{t1}^{T} \left[ c_{buy}(t)P_{buy}(t) - c_{sell}(t)P_{sell}(t) c_{gas}F_{buy}(t) \sum_i c_{om,i}P_i(t) c_{curt}P_{curt}(t) \right]\Delta t ]这里有几个细节要说明。第一购电价不是常数采用的是分时电价白天尖峰电价高凌晨谷段电价低P2G就是靠这个价差才有存在意义。第二弃电惩罚项不是真实成本而是为了让模型尽量少弃风弃光而设的“软约束”一般取一个比各种供电成本都要高的数比如几百块钱一兆瓦时。第三设备运维成本按出力比例算体现设备损耗。如果你想兼顾低碳调度还可以在目标函数里加一个碳排放成本项比如购电折碳排放乘以碳价购气也折碳排放。我这次没加碳税单纯做经济调度但代码结构里已经预留了参数位置后续想扩展很容易。3.2 三大平衡约束的细节处理优化模型最核心的一环是三个平衡约束前面建模部分已经写了基本形式这里展开几个工程细节。电平衡里购电和售电不能同时发生。如果不加约束模型可能一边高价买电一边低价卖电利用价差无中生有降低成本这在数学上就是纯套利漏洞。线性化的办法是引入二进制变量u_buy(t)购电功率不能超过购电上限乘以u_buy(t)售电功率不能超过售电上限乘以1-u_buy(t)。热平衡里储热罐的充热和放热也不能同时发生否则效率系数会造成“充放同时”的虚假能量循环。做法跟购售电一样用另一个二进制变量控制。不过有些文献为了减少整数变量会允许充放同时为0但不允许同时为正这属于效率约束的一个分支要小心写对。气平衡稍微简单一点[ F_{buy}(t) F_{p2g_out}(t) F_{load}(t) ]如果园区对外售气再加一项售气变量。天然气的购气量有最大限制这个别漏了气网不是无穷大。3.3 设备约束与爬坡限制设备出力上下限不用多说每个机组都有额定容量。需要注意光热发电、ORC这类机组还带最小出力所以要用二进制变量配合区间约束。爬坡约束也很重要尤其是光热汽轮机的升降负荷速率远没有燃机那么快如果忽略爬坡调度结果会严重偏乐观。我设置的爬坡约束形式是[ -P_{ramp_down} \cdot \Delta t \le P_i(t1) - P_i(t) \le P_{ramp_up} \cdot \Delta t ]在Matlab里用YALMIP写这条很不费劲一个for循环就能加进去。但要注意如果t1超出调度周期循环要截止到T-1不然索引越界。3.4 为什么非要用MILP而不是启发式算法很多做调度的同学一上来就想用粒子群或者遗传算法觉得不需要求导、实现简单。但实际问题里带大量二进制变量机组启停、充放热互斥、购售电互斥这些离散决策用启发式算法很难稳定处理。粒子群跑10次可能给你10种结果有时候偏差还大写论文凑结果倒是有人这么干但要用于工程复现就很麻烦。所以我选的是混合整数线性规划MILP求解器用Gurobi或者CPLEXMatlab里通过YALMIP建模非常方便。只要约束和变量都是线性形式MILP求解器能保证全局最优而且24小时或48小时尺度的调度问题变量数量通常在几百到几千个求解时间也就几秒到几十秒完全在可接受范围内。4. Matlab代码实现从数据准备到结果出图4.1 整体代码结构与数据准备代码我拆成了四个脚本主入口main.m、参数与数据加载data_load.m、模型构建model_build.m、结果绘图result_plot.m。这样改参数和换算例都比较方便。数据类型主要是DNI逐时数据光热集热场输入风电、光伏预测出力作为可调度上限电负荷、热负荷、气负荷三类负荷曲线分时购电价、售电价、购气价光热汽轮机、ORC、P2G、燃气锅炉、电锅炉的容量与效率参数数据单位统一套用MW和MWh。缺省情况下典型日数据可以用24个点表示。如果要跑全年8760小时数据量会大很多但模型结构不用变只是求解时间会上升。4.2 用YALMIP定义决策变量和约束先在Matlab里安装YALMIP并确保Gurobi或CPLEX求解器能被识别。变量定义用sdpvar和binvar比如光热汽轮机启停变量是二进制储热罐充放热变量是非负连续。T 24; P_csp sdpvar(1, T, full); P_orc sdpvar(1, T, full); P_p2g sdpvar(1, T, full); P_buy sdpvar(1, T, full); P_sell sdpvar(1, T, full); Q_ch sdpvar(1, T, full); Q_dis sdpvar(1, T, full); S_tes sdpvar(1, T1, full); u_csp binvar(1, T, full); u_sto binvar(1, T, full);然后开始加约束。二元变量带来的非线性项比如u_csp乘以P_csp_min直接写成线性形式Constraints []; % 光热出力上下限 for t 1:T Constraints [Constraints, P_csp_min*u_csp(t) P_csp(t) P_csp_max*u_csp(t)]; end电平衡约束直接写成等式Constraints [Constraints, P_buy P_wt P_pv P_csp P_orc P_load P_p2g P_sell P_eb];储热罐动态写成循环for t 1:T Constraints [Constraints, S_tes(t1) S_tes(t) eta_ch*Q_ch(t) - Q_dis(t)/eta_dis]; Constraints [Constraints, 0 S_tes(t1) S_tes_max]; Constraints [Constraints, 0 Q_ch(t) u_sto(t)*Q_sto_max]; Constraints [Constraints, 0 Q_dis(t) (1-u_sto(t))*Q_sto_max]; end Constraints [Constraints, S_tes(1) S_tes(T1)];最后设置目标函数并调用求解器Objective sum(c_buy.*P_buy - c_sell.*P_sell c_gas.*F_buy c_om_csp.*P_csp c_om_orc.*P_orc c_curt.*P_curt) * dt; ops sdpsettings(solver, gurobi, verbose, 2); optimize(Constraints, Objective, ops);跑完之后用value()函数提取结果再画图。4.3 结果分析与曲线绘制结果曲线我一般画三张图。第一张是电功率平衡堆叠图把购电、风电、光伏、光热、ORC几块功率叠起来能直观看到是谁在哪个时段承担了负荷。第二张是热功率平衡图显示储热罐在白天充电、晚上放电的过程以及ORC从热源里分走了多少热。第三张是储热罐SOC和P2G功率曲线用来验证弃电消纳逻辑。最有意思的是P2G曲线和弃风曲线的对比在优化结果里P2G基本只在凌晨风电高发、电价最低的时段启动白天价格高它反而老老实实停机。这说明目标函数和约束设置是对的系统确实会“寻找”最优运行区间。如果画出来的P2G曲线正好跟你想的相反那大概率是效率或者收益参数设置出了问题。5. 调试过程中的常见坑与排查心得5.1 ORC效率建模失真我最开始偷懒把ORC效率设成固定12%但没加最小输入热功率约束结果优化器出奇招热负荷低谷时段把所有多余热都塞给ORC明明热量只有几兆瓦ORC还要强行启动产出的电还不够补偿购电成本。后来给ORC加了0-1启停变量和最低输入热功率结果才正常。这类问题排查时可以看两个指标一是ORC全年利用小时数二是单位发电成本。如果ORC频繁启停但每段运行时间都很短基本就是约束漏了。5.2 P2G在电价高峰运行的反常现象有一次优化结果里P2G在电价最高的下午还在运行我一开始以为是目标函数写错了后来发现是天然气外售价格设得过高使得P2G制气拿去卖比直接买电卖电更划算。这提醒我P2G的经济性好坏完全取决于气价和电价的关系建模时要算清等号两边的口径。以1MWh电输入P2G为例产气量约0.6MWh热功率。只有在这0.6MWh气的外售价格高于1MWh购电电价时P2G才可能盈利。我把这个盈亏平衡点直接预计算出来写进项目文档里作为检查模型是否失真的第一道标尺。5.3 储热罐初始储热量的陷阱这是一个特别隐蔽的坑。如果S_tes(1)设置为0但S_tes(T1)允许自由那么优化器会在最后一个时段把储热罐里面剩余的热量全部放掉用来减少购电成本表面上看总能降了实际上是在“透支未来热量”。所以储热罐必须加末端约束要么S_tes(T1)等于初始值S_tes(1)要么至少设置一个最低末端储热量否则结果没有工程意义。5.4 求解缓慢的常规处理手段模型加上二进制变量后求解速度往往会明显下降尤其是做8760小时全年调度时。我的一般处理手段是先跑24小时典型日验证逻辑再扩展成多个典型日如果一定要跑全年就采用聚类方法选出几个代表日而不是把8760个小时全塞进去。另外大M系数要尽量取紧。比如购电量的上限就应该取联络线容量不要取一个非常大的数否则整数规划的松弛问题会很难求解。还有YALMIP默认的一些设置未必适合大规模问题可以把solver的MIPFocus参数调成1或2让Gurobi更积极地寻找可行解实测对求解时间有明显改善。5.5 结果合理性检查的三个关键步骤我每跑完一组算例都会做三件事。第一检查所有平衡约束的残差是否在允许范围内。第二检查储热罐SOC曲线是否出现突变储热量的变化率应该等于充放热功率的差值如果SOC曲线光滑度异常说明可能漏了某个热流分支。第三手动算一笔简化账比如某个夜里P2G启用了2小时根据效率回算产气量再对照气负荷曲线看这笔账能不能对上。只要这三个检查都过结果基本能用。综合能源优化调度这类模型说实话门槛不在求解器也不在那几个设备公式而在你能不能把能量流之间的耦合关系梳理清楚。我后面再做类似的系统第一步永远是画能量流图把所有输入、输出、存储、转换关系标成一张大图再回头看数学建模就会清晰很多。Matlab的好处是建模和验证都在同一套环境里画完结果直接能看到调度行为是否合理迭代效率比传统C环境高了不少。