1. 项目概述与核心问题解析
1.1 标题背后到底在解决什么问题
这个题目信息量很大。先说结论:它解决的核心问题,是在电价波动的基础上,让一个包含电、气、热等多种能源形式的系统,在保证安全稳定运行的前提下,通过两阶段优化调度把运行成本降到最低,同时怎么动态响应电网的实时需求。这类研究在当前能源领域非常主流,因为综合能源系统已经不再是概念了,在实际规划、运行、改造或做科研课题时,都需要涉及类似的调度策略。
我拆解这个标题的几个关键词时,马上想到了以下几个核心点。
第一个是“分时电价需求响应”。现在电网侧发用电平衡压力越来越大,峰谷电价差拉得非常开,很多地区已经有分时电价政策,有些地方甚至还有尖峰电价。对一个综合能源系统来说,用户的负荷结构里如果有柔性负荷,比如说可平移的工业流程负荷、可调整的空调负荷、可充放电的蓄电蓄热装置,那电价差就是一个可以直接利用的可调节资源。需求响应的本质,就是引导这些负荷在电价低的时候用能,在电价高的时候减用或改用其他能源,比如用气锅炉替代电锅炉、用蓄电替代网购电。
第二个是“两阶段日前日内滚动优化”。这点的意思是,一天之前先根据预测数据制定一个全天范围内的计划,然后在当天根据实际运行情况不断修正这个计划。为什么这么做?因为可再生能源比如光伏、风电出力预测准确度有限,负荷预测也同样存在偏差,如果只做一次优化然后一条道走到黑,运行结果大概率不经济甚至可能越限。日前计划管全局,日内滚动管修正,这是一个标准的递进优化思路,也符合电力系统调度从计划走向实时执行的工程逻辑。
第三个是“综合能源系统”。这个词在近年的学术论文和工程示范项目里出镜率极高。电、气、热、冷多种能源在同一个系统里耦合运行,设备包括热电联产机组、燃气锅炉、电锅炉、吸收式制冷机、蓄电储能、蓄热罐、光伏、风机等。每一种设备都有自己独特的容量限制和爬坡约束,能源之间通过某些设备互相转换,整个系统就是一个物理上高度耦合、时域上动态响应的复杂对象。从优化数学上看,它是一个混合整数规划问题,处理不了连续和离散变量同时存在的模型,很多初学的人会卡在这一步。
1.2 适合谁来读,能解决什么痛点
从我的实际接触来看,这个题目最适合三类人。
第一类是正在做综合能源系统、微电网、能源互联网相关课题的研究生和工程师。他们往往已经有一些数据或实验平台,但不知道怎么把需求响应、分时电价这些概念转化成一个可求解的数学模型。这篇文章里讲的两阶段框架和约束建模方式,可以直接借鉴到自己的项目里。
第二类是做建筑能源系统、工业园区多能互补项目的从业者。这类项目的关键就是怎么把燃气、电、热几种能源用好、搭配好,具体到每天怎么调度设备,这篇文章会提供一个直观的参考方案。
第三类是想在自己工作中加入电价响应策略的技术人员。很多人会把需求响应理解成简单的“低电价多用电”,但实际上真正落地需要考虑设备爬坡、储能状态、系统平衡、响应约束等一堆限制条件,这些细节不是看看政策文件就能学会的。
这篇文章要讲的核心内容,就是完整拆解以下几个问题:分时电价模型怎么搭、需求响应约束怎么加、两阶段调度框架怎么设计、MATLAB代码里的优化求解配置怎么处理和工程实际中的常见坑。这些内容都是我这个方向实际跨过一遍流程总结出的经验。
1.3 为什么用MATLAB而不是其他语言
这个问题估计有不少刚接触的人有过疑问。优化调度领域里,Python也有很成熟的生态,Pyomo、Gurobi的Python接口、Pandas做数据处理都很方便。但我个人经验是,MATLAB在这类研究里使用频率依然很高,且合理。
原因有几个。第一,MATLAB的Optimization Toolbox和YALMIP工具箱让建模和求解器之间衔接非常直接。你不必自己写求解器接口,拿到的是一个符号化、矩阵化的建模表达形式,这有利于快速验证算法逻辑。第二,MATLAB里做矩阵操作非常顺手,而两阶段优化里大量需要用到矩阵拼接、维度对齐、结果重排这类工作,尤其是日前计划做96点、日内滚动每15分钟做一次优化时,矩阵维度和时间轴的处理就是日常操作。第三,很多在能源领域做了多年的工程师,最早就是从MATLAB起步的,整个团队的技术栈、代码库、数据处理模式都基于MATLAB形成,换语言意味着沉没成本很高。
所以,如果你能接受MATLAB的license费用和高内存占用这个小缺点,它在综合能源系统优化调度这个场景下确实是性价比很高的选择。
2. 分时电价与需求响应模型详解
2.1 首先要把电价这个变量建模成什么样子
分时电价模型是整个优化问题的基础工程量。在设置分时电价之前,系统里各个设备没有体现出明确的时间维度偏好,所有能耗都会堆在同一个时间点上;加了分时电价,系统才会自动把用能安排到便宜时段。所以这个模型看起来很简单,但它直接决定了后面所有优化的经济性导向。
在MATLAB代码里,我通常把一天划分为24个时段或96个时段,并设置三个或四个电价档位。下面是一个典型的输入数据形式,实际应用时根据当地政策直接修改具体数值即可:
| 时段类型 | 时段范围 | 电价(元/kWh) |
|---|---|---|
| 谷段 | 23:00—次日7:00 | 0.38 |
| 平段 | 7:00—10:00,15:00—18:00,21:00—23:00 | 0.68 |
| 峰段 | 10:00—15:00,18:00—21:00 | 1.12 |
这里有一个细节值得注意:有些地方在尖峰时段还有额外的尖峰电价,通常比峰段贵20%到40%。尖峰时段往往出现在夏冬两季的某个较短的时间窗口内。如果系统里有可以蓄能的设备,比如蓄电、蓄热、蓄冷,我建议在建模时单独把尖峰时段设一个更高的电价,而不是笼统地合并到峰段里,这样优化结果能更精准地体现出储能价值的发挥空间。
2.2 需求响应的核心:用户负荷是怎么被“响应”的
需求响应本质是让负荷侧具备可调节性,而不是固定被视为刚性需求。理解这一点非常关键。传统调度里负荷是外部条件,优化算法只能被动满足负荷;而在需求响应模式下,一部分负荷可以主动参与到优化中,优化算法可以调度它。
在建模时,我通常把用户负荷拆成三类:
刚性负荷:比如医院手术室的用电、数据中心核心设备的用电,这些负荷不能参与调节,必须无条件满足。
可平移负荷:比如厂区的某些工序、
可削减负荷:比如正常空调温度设为26摄氏度时的用电,在电价过高時可以降低到28度,用减少供冷量的方式减少用电。
三类负荷里,刚性负荷建模最简单,就是一个等式约束;可平移负荷需要引入整数变量,表示是否平移以及平移到哪个时段;可削减负荷引入的是削减比例上下限约束和削减成本系数。
举个实际例子来说明可平移负荷是怎么建模的。假设某工厂有一个持续6小时的工序,每天必须在上午9点到下午5点之间的某个时间段内完成。那这个工序在优化模型里就是一个典型的可平移负荷。电网在中午12点到下午2点的电价如果特别贵,那么优化算法可能会把这个工序往后推到下午3点开始,推到下午5点结束,刚好避开电价最贵的时段,电解费用就降下来了。
但这会引发一个很现实的约束:平移负荷往往不能拖到太晚,一是生产节拍要求,二是如果所有负荷都挤到谷段开始运行,可能引起新的负荷尖峰。所以需要设置“平移时间窗”和“同时段平移负荷上限”这两层约束,确保响应之后的负荷曲线不会太难看。
2.3 需求响应弹性系数的确定方法
需求响应模型里第三个关键量是需求响应弹性系数,因为不同用户对电价响应程度差异很大,而这种差异会直接影响优化结果。
需求弹性系数定义是负荷变化百分比除以电价变化百分比。比如电价上涨20%、负荷下降4%,则需求弹性是-0.2。在基础建模中,通常把弹性设置为-0.1到-0.3之间。这个数值在理论分析中可以用历史数据回归拟合,也可以用调查问卷法,但在工程实践里常用“先按经验值、再按实际效果调整”的方法。
在代码实现里,我会把负荷削减比例直接与原弹性系数挂钩,表达式如下:
[ \Delta P_t = P_{base,t}^{flex} \cdot \varepsilon \cdot \frac{C_{price,t} - C_{price,avg}}{C_{price,avg}} ]
其中(\Delta P_t)表示t时段柔性负荷的削减量,(P_{base,t}^{flex})为t时段可削减负荷基准值,(C_{price,avg})为日均电价。建立关系后,在优化模型里直接把它作为约束条件而非固定值参与优化,这样处理其实更能体现需求响应的动态性。
不过这里要提醒一点:需求响应需要给用户补偿,所以削减负荷并不会100%降低系统总费用。因为削减负荷意味着放弃了一部分用电收益,在优化过程中需要给削减行为设置一个边际成本系数。这个系数的物理意义是用户每少用1千瓦时电需要获得的经济补偿。如果这个系数设置得太高,优化算法就会选择不去削减负荷;设置得太低,优化算法会不惜代价地削减刚性负荷。所以需要根据实际情况仔细调整。
2.4 分时电价与需求响应关联时的常见误区
这个模型看起来简单,但实际用的时候几个坑是普遍存在的。
第一,为了引导用户避峰,电价差设计得过于极端。电动汽车集中充电叠加谷段蓄电设备同时开启,会在凌晨形成一个比晚高峰还高的峰谷腰荷。这种情况下,优化模型的经济性指标确实下降了,但系统的实际运行安全性却摊上了新风险。所以在需求响应的约束里必须加上“平移后的负荷峰值不超过原峰值”这一项。
第二,把弹性系数设为常数太粗糙了。不同季节、不同天气、不同用户类型下,弹性差异往往可以差出一个数量级。比如刚进入冬季第一次寒潮时,居民用户对采暖负荷几乎没有弹性,你把电价调高他们也不会减少用电,只能靠系统自身设备调节。所以实际建模时建议把弹性系数按“工作日/休息日”和“用户类型”进行多维拆分。
第三,缺少对响应后负荷曲线的量化评估。这就回到了我上面说的那一点:需求响应做完之后,不能只看成本降了多少,还得看负荷曲线的峰值、峰谷差、波动率等指标。有的文章摘要里只写“成本降低8%”,但你看它负荷曲线,发现峰谷差反而变大了,这种情况在实际工程里很容易被电网考核,需要注意。
3. 综合能源系统设备建模与关键约束
3.1 核心设备的能量转换关系
综合能源系统的独特点在于多能互补,因为它可以通过转换设备实现不同能源之间的替代。如果一个系统里只有电而没有气,那就叫电气系统;只有热而没有电,那就叫热力系统。之所以称之为“综合”,是因为电、气、热、冷在设备中不断转化、存储、释放,整条链条是联动的。
在一个典型的园区级综合能源系统里,我一般会包含以下设备:光伏机组、风电机组、热电联产机组(CHP)、燃气锅炉、电锅炉、吸收式制冷机、电制冷机、蓄电池、蓄热罐。其中有些设备输出单一能源,有些设备同时输出多种能源。
举个例子,热电联产机组是综合能源系统的核心设备,它同时产生电和热。它的模型通常包含以下约束:
[ P_{chp,t} = \eta_{chp}^{e} \cdot G_{chp,t} ]
[ H_{chp,t} = \eta_{chp}^{h} \cdot G_{chp,t} ]
其中(G_{chp,t})是t时段天然气输入功率,(\eta_{chp}^{e})和(\eta_{chp}^{h})分别为发电和产热效率。这里存在一个关键参数叫“热电比”:
[ r_{chp} = \frac{\eta_{chp}^{h}}{\eta_{chp}^{e}} ]
实际上CHP机组的热电比并非固定值。在现代分布式能源系统里,不少CHP机组支持一定范围内的热电比可调,也就是说在同一发电出力下可以调节产热量。这让优化模型更灵活,但调度决策前必须确保生成的热能在供热系统内可以被完全消耗,否则系统就会面临弃热问题。
再说电锅炉。电锅炉是一个典型的“电转热”设备,它的效率通常在0.9到0.98之间。电锅炉建模型相对简单,但有一个关键的问题是它把电能直接转换成热能,如果电价处于峰段,运行电锅炉几乎一定是不经济的。但在风、光出力过剩导致电网出现负电价或极低电价的时段,电锅炉就有发挥空间了。这也是为什么分时电价模型对综合能源系统调度如此重要。
3.2 蓄电池和蓄热罐的动态约束
储能设备是需求响应的重要工具。把便宜的电存起来留到贵的时候用,这是储能学中“套利”的核心逻辑。但它的约束条件比较复杂,通常包含以下几个维度:
荷电状态(SOC)约束:蓄电池的SOC在任意时刻位于[SOC_min, SOC_max]区间内,比如0.2到0.9。容量设太小没有调节空间,设太大对电池寿命影响较大。
充放电功率约束:充放电功率不能超过额定值,且从同一个设备维度来看,充电和放电不能同时进行。这一点在数学上需要引入二进制变量来限制:
[ 0 \le P_{ch,t} \le P_{ch}^{max} \cdot u_t ]
[ 0 \le P_{dis,t} \le P_{dis}^{max} \cdot (1 - u_t) ]
其中(u_t)是0-1变量,表示t时段充电状态。
- 能量转移关系:
[ SOC_{t+1} = SOC_t + \eta_{ch} \cdot P_{ch,t} \cdot \Delta t - \frac{P_{dis,t}}{\eta_{dis}} \cdot \Delta t ]
- 周期约束:调度周期结束时SOC要与起始值保持一致,否则第二天无法按计划运行。
蓄热罐与蓄电池的建模逻辑类似,但有几个区别。蓄热罐散热损失不可忽略,尤其是保温不太好的罐体,一天散失5%到10%的热量都是正常的。所以在蓄热罐的能量转移方程中,会在等式右侧加一个散热系数项。蓄热罐的另一个区别是它往往与CHP机组、电锅炉联动,这就引入了运行约束:蓄热罐在t时段蓄入的热量不能超过CHP机组和电锅炉的总产热量,取出的热量则要满足用户热负荷的需求。
我遇到很多人在初始建模时忽略蓄热罐的散热损失,结果就是最优解异常乐观。比如一个动辄几千千瓦的蓄热系统,被全天候存热再放热,实际收益却被散热消耗抵消大半。所以我的经验是无论数据多难看,都建议把散热损失纳入模型,否则调度结果落地后经济性大打折扣。
3.3 网络约束与系统平衡约束
综合能源系统的约束条件中最容易被忽略但最影响可解性的,是网络约束。
电力网络约束包括节点电压约束、线路潮流约束和变压器容量约束。在输电网里潮流约束用交流潮流模型描述复杂度很高,但对园区级微电网通常采用直流潮流或简单功率平衡加线路极限约束的方式简化。以辐射状配电网示例,需要把每条线路的功率传输不超过其热稳定极限,并保持电压在合格范围内。有的教材里写0.95到1.05倍额定电压可以作为安全范围。如果优化的调度计划违反电压约束,实际运行中可能导致设备跳闸。
热力网络的建模相对简单一些。供热管道通常具备热惯性,也就是说管道里的热水温度不会瞬间变化,这在小时级调度中一般可以简化处理。但如果你做的是分钟级日内滚动优化,就需要把管道蓄热效应纳入考虑,因为它会影响热负荷供应的即时响应能力。
系统平衡约束是每类综合能源系统都要有的最基本约束,表达形式是电、热、气各自在每个时段都满足“供给侧出力+储能释放=负荷需求+储能充入+网络损耗”这样的关系。这组约束也是优化模型里矩阵规模最大的约束组之一,当调度时段取96个点时,平衡约束和相关行数会迅速膨胀,矩阵构建效率就会明显影响求解性能。
3.4 设备模型参数选择的工程经验
关于设备参数,国内常见机组的技术参数可以供初建模型的参考:
| 设备 | 容量范围 | 效率/COP | 爬坡率限制 |
|---|---|---|---|
| 燃气轮机CHP | 0.5—10 MWe | 电效率33%—40%,热效率40%—50% | 每分钟2%—5% |
| 燃气锅炉 | 1—20 MWth | 85%—92% | 每分钟3%—6% |
| 电锅炉 | 0.5—5 MWth | 95%—98% | 每分钟5%—10% |
| 吸收式制冷机 | 0.5—5 MWth | COP 0.7—1.3 | 较慢 |
| 电制冷机 | 0.5—5 MWth | COP 3.0—5.5 | 较快 |
这些参数主要来自公开的技术手册。实际操作时,不同厂家的设备参数差异会比较大,尤其是变频设备的爬坡率可以做到很高,而大型吸收式制冷机的启停响应则明显迟缓。在建模初期没有必要追求极致的参数精度,重要的是把设备之间的耦合关系和基本约束框架梳理清楚。参数精调是在后续运行调试阶段逐步完成的。
4. 两阶段日前日内滚动优化调度框架详解
4.1 为什么要用两阶段而不是单阶段
这是整篇博文里我认为最关键的一个设计决策。直接原因很简单:日前预测信息不准确,只依赖日前做出的决策就跑不下去。
在一个实际系统中,光伏和风电出力预测在日前阶段可能偏差在10%到20%甚至更大,负荷预测偏差也不小,而且越大越靠近实时越准。如果完全按照日前计划运行,当实际光伏出力远低于预测值时,系统可能无法满足负荷需求;当实际光伏出力远高于预测值时,会因为无法消纳而弃光。这两种情况都会破坏调度经济性。
但如果完全不做日前计划,只靠日内实时优化,系统就等于短视的。因为储能设备的充放电策略、可平移负荷的时段安排、CHP机组的启停计划都需要提前安排,无法根据未来数小时的信息做全局统筹。
两阶段方案正好解决了这些问题:日前阶段有全局视角但没有精度优势,日内阶段有精度优势但缺少全局长远视角,两者放在一起恰好形成互补。
两阶段优化与“模型预测控制”思想是相通的。两者都是滚动时域控制下的常见工程策略。但在综合能源系统的优化里,两阶段更强调日前计划与日内修正的明确分工,尤其是通过“计划跟踪”来实现两个阶段衔接,而不是完全抛弃日前计划重新优化,我认为这是更符合实际电力调度运行习惯的做法。
4.2 日前阶段怎么建模
日前阶段的任务是:根据次日的光伏、风电、负荷的预测值,以及次日的电价曲线,在满足所有设备约束的前提下,制定一个24小时内每小时一决策的优化计划。这个计划要确定每台设备的启停状态、出力大小、储能充放电功率和可平移负荷的安排方案。
优化目标通常设置如下:
日前目标函数:
[ \min \sum_{t=1}^{T} [C_{grid,t}^{buy} \cdot P_{grid,t}^{buy} - C_{grid,t}^{sell} \cdot P_{grid,t}^{sell} + C_{gas} \cdot G_{total,t} + \sum_{i} C_{om,i} \cdot P_{i,t} + \sum_{j} C_{DR,j} \cdot \Delta P_{j,t}] ]
其中:
- (P_{grid,t}^{buy})、(P_{grid,t}^{sell})分别为t时段从电网购电与向电网售电功率
- (C_{gas})为天然气单位热值价格
- (C_{om,i})为第i台设备的单位运行维护成本
- (C_{DR,j})为第j类柔性负荷的需求响应补偿成本
- (T)为调度总时段数
设备组合启停优化、储能调度与可平移负荷安排,这三个决策变量乃是日前阶段分析与求解的核心部分。包含0-1变量的指令被称为混合整数线性规划,或混合整数二次规划,而决策变量数量也影响着求解难度。
在MATLAB代码实现中,我通常用矩阵拼接的方式把决策变量顺序定义为:每个时段的购售电、CHP电出力、燃气锅炉热出力、电锅炉热出力、蓄电池充放电、蓄热罐充放热、可平移负荷的启停标志等。这种按“变量块+时间轴”组织的顺序在建立约束矩阵时能省不少事。
4.3 日内阶段是怎么修正日前计划的
日内阶段在当天内以固定的时间间隔滚动运行,常见配置为每1小时启动一次优化,每次优化覆盖未来4至8小时范围,只执行第一时段的决策。等到下一时刻到来时,系统获取最新的实际运行数据与预测数据,再次滚动优化,实现对计划的闭环修正。
这里有几个关键点需要详细说。
第一,怎么处理“日前计划”与“日内优化”的关系?我看到很多文献里的做法是在日内优化目标中加入一个“日前计划跟踪项”:
[ \min \left[ \sum_{t=1}^{H} C_{operation}(t) + \lambda \sum_{t=1}^{H} \left( P_{i,t}^{intra} - P_{i,t}^{day-ahead} \right)^2 \right] ]
这个跟踪项的物理意义是:如果日内修正偏离日前计划太多,会付出额外的惩罚代价。(\lambda)的大小决定了日内优化的自由度。(\lambda)设得过大,日内滚动会盲目追求和日前计划的一致性,失去了修正的价值;(\lambda)设得过小,日内优化几乎完全自由,容易频繁调整设备出力,增加运行磨损和实际操作的困难。这个参数一般是一个需要根据仿真实验反复调整的经验值。
也有一些论文采用“开环反馈校正”策略,即日内只对净负荷的偏差部分进行补偿,不动原来设备的基准出力计划。这种做法求解速度快,运行稳定性好,但同时也丢掉了一部分通过重新分配功率获得最优收益的机会。到底采用哪种方式,要结合实际系统的调节能力和设备特性来定,没有绝对的最优解。
第二,日内优化是不是要把所有的日前决策变量都重新求解一遍?我的经验是不是。日内阶段主要是对连续型变量做修正,也就是设备出力、储能充放电功率、购售电功率这些变量。而0-1变量在日内阶段尽量保持不变,原因是设备启停不能频繁改变,否则会导致设备磨损加剧,运行过程也会变得极不稳定。按照这个思路,日内阶段可以直接基于日前计划中已确定的启停状态,只优化连续变量。这样做不仅计算量大幅下降,也更贴近工程实际的可操作性。
第三,日内滚动窗口怎么选择?通常滑动窗口有4小时、6小时、8小时这三种。窗口太短看不到远处的负荷趋势,储能优化的长远性就差;窗口太长又削弱了日内的精度优势。我个人建议在初始搭建时先取6小时窗口,96点模式就是24个时段。在实际测试时再比较不同窗口长度对成本和运行平稳性的影响。如果有条件,还可以做不同窗口长度下的对比仿真实验。
4.4 阶段之间如何衔接
两个阶段衔接是整个框架最需要仔细考虑的地方。衔接好的策略运行起来平滑稳定,衔接不好的策略往往出现运行计划大幅跳变、储能状态错乱等问题。
我采用的衔接策略是:日内阶段每滚动优化一次,都反馈更新一次设备的实时状态数据作为下一次优化的初始状态,尤其是蓄电池SOC、蓄热罐蓄热量、设备当前出力水平等关键状态量。日前计划中已经固定下来的0-1变量保持边界取值,连续变量的初值则设为日前计划对应的值或当前实际值。在此基础上进行局部优化,能保证两阶段问题的一致性。
此外,衔接时还需要特别注意“日前计划中未决变量”的处理。比如某台CHP机组在日前计划里8点启动、16点停机,但日内由于实际热负荷变化,可能需要在15点就停机。这类情况需要在日内阶段增加一个“允许停机时间窗口”约束,防止设备刚停机又启动。
5. MATLAB代码实现与核心逻辑拆解
5.1 代码整体架构与函数设计
用MATLAB实现两阶段优化调度,代码结构设计得清晰与否直接决定你后面调试效率。良好的代码结构能省去大量调试与修改的时间。我自己常用的代码架构包含以下几个层次:
数据输入层:读入负荷预测、光伏预测、风电预测、电价数据、设备参数,统一存储在一个结构体数组里。MATLAB里的数据结构体的字段命名清晰,对读代码的人非常友好。在这一层我会把所有的时间序列数据都转换为列向量,以方便矩阵运算要求的一致性。
模型构建层:这个层面调用YALMIP或直接使用Optimization Toolbox构造目标函数、约束条件和决策变量。通常我会把每个设备的约束封装成独立的函数,例如一个函数只负责构建CHP的约束矩阵,另一个函数只负责构建储能充放电的变量关系,各函数的输出是约束表达式或系数矩阵。
求解层:调用求解器。MATLAB环境里我常用内置的intlinprog求解MILP问题。安装了Gurobi或Cplex的也可以通过YALMIP指定调用。求解时间上,24时段模型通常在几秒到几十秒之间,96时段模型通常需要几分钟,属于正常范围。
结果整理层:将求解得到的变量结果转换为结构体,输出每个时段的各设备出力、储能SOC、购电功率等数据,同时计算总成本和成本组成。
结果可视化层:绘制电功率平衡图、热功率平衡图、储能状态曲线、电价与负荷响应对比图等,为结果分析提供直接辅助。
这种模块化代码的好处是,如果某台设备坏了、换了一台新设备或者计划增加一套新的储能系统,只需修改对应设备的参数文件和对应的约束函数,其他部分的代码逻辑基本不需要改动。
5.2 核心变量定义与初始化示例
我在这里展示一下MATLAB代码里初始化变量和约束矩阵的方式。以下代码基于YALMIP工具箱的建模风格,这是目前综合能源系统优化领域使用比较普遍的方式,它让模型表达式跟数学公式之间的对应关系非常直观:
% 基本参数设置 T = 24; % 调度时段数,高手可扩展为96 buyPrice = [谷段电价向量; 平段电价向量; 峰段电价向量]; % 按时段顺序排列 sellPrice = buyPrice * 0.85; % 上网电价通常低于购电价 gasPrice = 2.8; % 天然气价 元/Nm3 eta_chp_e = 0.35; eta_chp_h = 0.45; % CHP电效率和热效率 eta_gb = 0.9; % 燃气锅炉效率 eta_eb = 0.95; % 电锅炉效率 % 定义决策变量 Pbuy = sdpvar(T, 1); % 购电功率 Psell = sdpvar(T, 1); % 售电功率 Pchp = sdpvar(T, 1); % CHP电出力 Hchp = sdpvar(T, 1); % CHP热出力 Hgb = sdpvar(T, 1); % 燃气锅炉热出力 Heb = sdpvar(T, 1); % 电锅炉热出力 Pch = sdpvar(T, 1); % 蓄电池充电功率 Pdis = sdpvar(T, 1); % 蓄电池放电功率 SOC = sdpvar(T+1, 1); % SOC状态,含初始状态 Hch = sdpvar(T, 1); % 蓄热罐蓄热功率 Hdis = sdpvar(T, 1); % 蓄热罐放热功率 HS = sdpvar(T+1, 1); % 蓄热罐蓄热量 uchp_on = binvar(T, 1); % CHP启停状态 uch_bat = binvar(T, 1); % 蓄电池充电标志 uch_heat = binvar(T, 1); % 蓄热罐蓄热标志初步定义变量后,设置约束条件。YALMIP的方式是把所有约束放进一个元胞数组Constraints中,最后一次性求解。
Constraints = []; % 电功率平衡约束 for t = 1:T Constraints = [Constraints, Pbuy(t) + Pchp(t) + Pdis(t) + Ppv(t) + Pwt(t) == ... Pload(t) + Pch(t) + Heb(t) / 0.95 + Psell(t)]; end % 热功率平衡约束 for t = 1:T Constraints = [Constraints, Hchp(t) + Hgb(t) + Heb(t) + Hdis(t) == ... Hload(t) + Hch(t)]; end % CHP出力上下限与爬坡约束 for t = 1:T Constraints = [Constraints, Pchp_min * uchp_on(t) <= Pchp(t) <= Pchp_max * uchp_on(t)]; if t > 1 Constraints = [Constraints, -Ramp_chp <= Pchp(t) - Pchp(t-1) <= Ramp_chp]; end end这种方式写出来的代码非常接近数学模型的原始表达式。对读过几篇文献的人来说,能一眼看懂每个约束对应哪个物理关系。
5.3 两阶段滚动优化的循环控制逻辑
日前优化和日内滚动优化的代码差异体现在循环结构和数据更新方式上。我总结了一个标准化的日内滚动优化循环框架,核心是外层循环控制滚动次数,内层执行“读数据—构建模型—求解—取首步—更新时间序列—进入下一轮”的循环逻辑。
% 设定日内滚动相关参数 intra_Horizon = 6; % 日内优化窗口长度 6小时 startTime = 7; % 假设从早上7点开始滚动 numSteps = (24 + intra_Horizon - 1); % 总滚动次数 for k = 1:numSteps % 读取最新实测与预测数据,变时间窗 curTime = startTime + (k-1); % 当前时刻 window = [curTime : curTime + intra_Horizon - 1]; % 第k次优化窗口 % 判断窗口是否跨日 window = mod(window - 1, 24) + 1; % 构建当前优化窗口的约束条件 [Constraints, Objective, varsStruct] = buildIntraModel(data, window); % 求解 ops = sdpsettings('solver', 'gurobi', 'verbose', 0, 'showprogress', 0); optimize(Constraints, Objective, ops); % 获取当前窗口首时段的决策值下发给系统 dispatchSolution(k) = value(varsStruct.Pbuy(1)); % 实际执行并更新状态 data.SOC_init = value(varsStruct.SOC(2)); data.HS_init = value(varsStruct.HS(2)); % 更新预测数据 data = updateForecast(data, curTime + 1); end这个循环逻辑看起来不长,但有几个容易出错的地方必须留意。
第一个坑是窗口跨越凌晨时刻时的索引处理。直接取curTime + i,当i足够大时索引会超过24,这时候必须用mod函数做取模运算,否则程序会报索引越界错误。看似很小的问题,实际运行中却很容易让人卡住。
第二个坑是滚动频率与预测更新周期的匹配问题。如果系统上午8点的一次日内优化只用早上6点做出的预测,那么滚动优化意义就打了折扣。必须让日内优化在每次执行前都获取最新一轮的预测数据,这样才能体现出“滚动”与“更新”的意义。
第三个坑是SOC的初值传递。如果忘了把上一轮优化结果的SOC(2)传为下一轮的初始SOC,优化模型会以为蓄电池可以凭空在任意SOC状态下开始运行,导致结果严重偏差且不具备实际可行性。
5.4 求解器选择与求解效率优化
关于求解器,我的建议是:小规模模型,比如24时段、单园区、无网络约束的情况,直接用MATLAB内置的intlinprog就够了,不需要额外安装商业求解器。但如果你要跑96时段模型,或者加入网络潮流约束和大量0-1变量,内置求解器可能会遇到“机器不可行”或长时间无解的情况,这时候就需要考虑Gurobi或Cplex了。
我对比过不同求解器的实际表现。在同一个96时段、含8台设备和网络约束的MILP模型上,MATLAB内置求解器可能需要十几分钟,但Gurobi通常一两分钟就能给出较好的整数解,差距非常明显。
求解效率提升方面,有几个实用经验值得分享:
报告显示早上7个小时的优化任务,前两小时发生的爬坡约束会造成严重的求解延误;把对称性约束加进模型,例如充电同时不得超过功率上限的互补条件,求解时间可减半。另一个重要技巧是将储能SOC的上下限约束在优化前就用参数替换掉,减少不必要的线性行数,这样能显著缩小问题规模。
还要特别注意YALMIP建模方式的效率影响。引入binvar的标准形式在建模里比较自然,但当变量数量达到数百上千个时,用binvar定义0-1变量往往会带来额外的内部变换开销。若遇到求解过慢的情况,建议改用optimvar数组构造优化问题,速度可能提升巨大。
6. 常见问题与排查实录分享
6.1 常见问题速查表
下面我把实际运行MATLAB两阶段优化调度时踩过的坑整理成速查表。这些问题都是真实项目中反复被新人问起的经典问题,按出现的频率排序:
| 问题现象 | 可能的根因 | 排查步骤与处理方法 |
|---|---|---|
| 求解结果出现“Infeasible Problem” | 约束冲突,比如负荷太高设备容量不够 | 先移除储能约束看问题上是否解决,若移除后可行说明储能参数设置不当;再检查SOC初值与终值约束是否冲突 |
| 求解时间过长或“超过时间限制” | 模型规模过大 | 减少0-1变量数量,日内阶段固定启停变量只优化连续变量;增加求解器的MIPGap参数值,以接受次优解换取速度 |
| 结果出现充电和放电同时为正 | 缺少互补约束 | 在约束中增加u_ch和u_dis互斥约束,确保两者之和不超过1 |
| 购电功率为负 | 售电与购电逻辑混淆 | 检查购售电功率的非负约束或互补约束,通常购售不同时发生 |
| 日前与日内结果差异太大 | 日前与日内模型参数不一致 | 核对两个阶段使用的设备参数、预测数据口径与电价数据是否一致 |
| 结果曲线波动过于剧烈 | 爬坡约束过松 | 增加设备的爬坡率约束,或对调度结果做平滑后处理 |
| 储能SOC曲线跳变 | 时间步长与功率单位不一致 | 检查Δt在能量转移方程中是否使用了统一的单位(小时) |
| 需求响应补偿费用为负数 | 弹性系数符号错误 | 需求弹性系数应为负数,补偿费用应为正数 |
6.2 一个现场排查案例:日前优化可行但日内处处矛盾
有一次我调试一套仿真模型时,遇到一个特别隐蔽的问题:日前优化一切正常,成本也很合理,但一进入日内滚动阶段,第一个时段就报不可行。检查了代码里各种参数和等式约束,似乎都没有明显毛病。
后来我把模型约束一条条拆开做可行性测试,找到了问题所在。问题出在CHP机组的最小技术出力约束上。日前优化时,CHP在某几个时段被优化算法设置了停机,也就是启停变量(u_{chp} = 0)。而在日内阶段,我的代码逻辑虽然继续固定了启停变量,但热负荷预测数据做了一个小幅修正后,热负荷在某一个时段小幅增加了。按照日前计划的CHP停机安排,该时段只有燃气锅炉和蓄热罐放热来供热量,但燃气锅炉的容量和蓄热罐的最大放热速率加起来刚好差一点点不够用,于是触发不可行。
解决的思路是:在日内阶段把CHP的启动状态从固定值改为“可延迟启动”,即允许CHP在日内提前启动,但必须设置最小运行时间约束,防止设备频繁启停。这样既保留了日前计划的总体结构,又给了日内调整的必要灵活性。这个问题提醒了我一点:日内阶段虽然以修正为主,但一旦发现不可行,不要只看约束数值,而是要检查日前固定的0-1变量在新的预测数据下是否仍然可行,必要时必须打破日前决策的僵化约束。
6.3 参数调试与灵敏度分析的一些经验
两阶段优化调度模型建好之后,还有一项非常关键且容易被跳过的工作:参数灵敏度分析。它不仅能帮你理解模型行为,还能帮你避免参数设置不当导致的调度结果失真。
我常用的分析维度包括:
电价峰谷差对储能利用率和系统成本的影响:增大峰谷差,储能套利空间变大,系统成本会下降。但如果峰谷差已经足够大,再继续拉大边际效益会递减,因为储能的容量限制截断了套利空间。
需求响应弹性系数对负荷调节量的影响:弹性系数从0.1提高到0.3,系统成本的变化幅度能直观反应需求响应资源的价值。
储能容量对系统成本的影响:这个其实很有意思。储能容量增大的边际效益在规模达到一定程度后会明显放缓,甚至出现投资回收期拉长的现象。优化模型里如果不考虑储能的全生命周期成本,只看日运行成本的话,结果往往是“储能容量越大越好”,这与工程投资判断是矛盾的。所以在做规划类分析时,需要把容量成本折算到日成本中,而不是只看运行成本。
光伏渗透率对调度策略的影响:光伏比例变化会显著改变日前阶段与日内阶段的需求响应策略走向。光伏多的时候,日内修正的重要性凸显,因为光伏预测误差对净负荷的影响特别大。
6.4 MATLAB编码过程中的几个实用技巧
最后分享几个MATLAB实操层面的小技巧,每一个都在调试过程中帮我节省了大量时间。
第一点是全用结构化数据传递参数。别用一堆全局变量传递参数,那会让多人协作的项目变得非常难维护。把所有参数放在data结构体内,函数之间只传递data和结果结构体。要修改某一参数时,只需找到初始化data的那一行进行修改,调试和排查时不用在代码各处翻找赋值语句。
第二点是增加约束采用“元胞数组延迟拼接”的方式。不要在一个for循环里反复做数组拼接,这样MATLAB会很慢。一个常见的优化做法是先用元胞数组把所有约束存放起来,循环结束后一次性做中括号拼接:
Constraints_cell = cell(T, 1); for t = 1:T Constraints_cell{t} = [Pbuy(t) + Pchp(t) == Pload(t), ...]; end Constraints = [Constraints_cell{:}];这段代码处理96时段的模型时速度提升非常明显。
第三点是注释要认真写清楚每个约束的物理意义。综合能源系统优化模型一不小心就会写出一大堆看不懂的矩阵表达式,你写完当时知道是什么,三个月后再打开根本看不懂当时的逻辑。我在每个约束的表达式中都会注明对应设备的类型和对应的物理含义,这样后续维护和向上汇报时都能省掉大量解释时间。
第四点是数值量纲的一致性。这是最容易被忽视的坑。如果电价用的是元/kWh,功率是kW,那你计算能量时要把功率乘以时间得到kWh。但在方程里如果没有显式写上Δt,功率和能量就会混在一起,整体结果数值就会完全失控。我在代码的固定位置统一设置了dt变量,每个涉及到能量交换的约束都显式乘上dt。这个方法配合代码审查流程使用,效果很好,可以大幅减少因量纲问题导致的调试时间。
写在最后的几点经验
做综合能源系统两阶段优化调度这个方向,最大的感受是它并不只是把数学模型建出来、解出来那么简单。模型能跑通只是起点,真正有价值的是让模型在实际情况中可落地、可解释、可调试。
我自己走过一段弯路,一开始花了很多时间在追求模型复杂度上,试图把每一种设备、每一个物理过程都精确描述,结果模型规模爆炸,求解时间长得根本没法做日内滚动。后来才意识到,工程上追求的不是模型有多精细,而是计算结果在合理的计算时间内足够可靠、足够贴近实际运行边界。现在我做模型时先跑通简单版本,再按需求逐步增加约束维度,这个思路在多个项目里都验证过,很有效。
另外,两阶段方案中日内阶段的边界处理和衔接问题最需要花心思处理,因为它涉及两个优化模型的耦合关系,稍有粗心就会出现各种不好排查的隐性错误。建议在写代码时对衔接部分做特别注释,甚至单独写一个衔接函数来组织数据传递,避免埋坑。
如果你正准备在自己的场景里搭建类似系统,我的建议是先选一个简单的单园区案例,用公开的数据或历史运行数据把基础框架跑通,再逐步增加设备类型和约束维度。所有优化调度模型的复杂度和调试难度会随着约束规模的增加而指数级上升,所以别急着一步到位,从简到繁推进最为稳妥。