做微网动态经济调度的人,几乎都要和“场景”这两个字打交道。注意,这里说的场景,跟最近很火的那种“上传一段视频生成对应的三维场景”完全不是一回事。在电力系统的语境里,场景是指对风电、光伏、负荷这些不确定量未来可能呈现的“典型样子”的采样描述——说白了,就是把“未来可能发生什么”这件事,用一组带概率的时间序列表达出来。而场景生成与削减,就是这套流程里最核心、也最容易被忽视的两个环节。
这篇文章面向正在做微网调度建模的研究生、工程师,或者刚接手随机优化项目、被一堆分布函数和聚类算法搞得头疼的同行。我会把这套东西从原理到实操拆开讲清楚:为什么要用场景,场景怎么生成,生成完为什么还要削减,削减到什么程度才合适,以及我在实际项目中踩过的坑。全程不堆公式,但关键的流程和参数逻辑都会给到,方便你回去直接改造成自己的方案。
1. 先想明白:动态经济调度为什么非要“场景”不可
1.1 动态调度和静态调度的本质差异
传统经济调度,很多时候只关心“某一个时段”怎么把发电成本压到最低。但微网里的动态经济调度是另外一回事,它要在一个调度周期内(常见的是一天24小时、96个时段,每个时段15分钟)做出连贯的决策。这里的关键是“连贯”:上一时段的储能充放电决定了当前时段的荷电状态,当前时段的机组出力又受到爬坡速率的限制,而负荷和新能源出力时刻在波动。任何一个时段的决策错了,后面的时段都会被拖累。
这就带来一个很头疼的问题:你没法用一个“确定性的未来”来做决策。光伏出力受云层遮挡影响,风电场出力跟着风速波动,负荷更是随着人们的生产生活节奏起起伏伏。如果调度模型把这些都当成确定值处理,那算出来的方案在真实运行中往往会出问题——要么备用容量不足,关键时刻顶不上;要么过于保守,白白弃掉不少清洁能源。场景生成的意义就在于此:它把“未来有很多种可能”这件事显式地放进优化模型里,让决策在统计意义上更稳健。
1.2 不确定性到底长什么样
搞场景之前,得先知道自己面对的不确定性是怎么分布的。不同源荷特性的差别非常大,这也是许多新手上来就套正态分布然后翻车的原因。
光伏出力在时间上高度集中在白天,而且受云层影响呈现出“多云天出力大幅波动”的厚尾特性,很多文献用Beta分布来描述光照强度的随机性。风速则更经典,双参数Weibull分布是行业标配,它能够很好地刻画“大多数时候风速不高、偶尔来一场大风”的偏态特性。负荷的随机性相对温和,但一天内早晚高峰的结构性波动很强,常用的建模方式是在预测曲线附近叠加正态分布或t分布的扰动项。
除了单变量分布,还得注意时间相关性。相邻时段的天气不是独立的——如果下午三点起了云,这团云大概率四点钟还在。用独立采样生成场景,会得到一条剧烈抖动的伪风速曲线,这种场景送入调度模型,只会让优化器疲于奔命,算出来的储能充放电策略完全没有参考价值。所以,场景生成不是说“抽样就行”,而是要尽量保留时序自相关性。
1.3 场景在动态经济调度中的“生态位”
如果你接触过两阶段随机优化,应该对场景在其中的位置有印象。随机动态经济调度一般写成这样的形式:外层决策是“今天开机哪些机组、储能初始状态怎么设定”,这些决策在一开始就要定下来;内层决策是“在每个可能出现的场景下,各时段怎么调出力、怎么充放电”,这些决策要等到场景观测到之后再调整。场景集就是这个模型的核心输入:它决定了期望成本怎么算,也决定了约束条件要对哪些情况满足。
这里有个两难。场景数太少,不足以覆盖真实情况,优化的结果对不确定性不敏感,跟确定性调度区别不大;场景数太多,比如一次性生成5000个场景、每个场景96个时段,优化模型的变量规模直接飙升到几十万甚至上百万,普通商用求解器跑起来非常吃力,项目迭代效率也受不了。所以就有了“场景削减”这个环节:在尽量不损失分布信息的前提下,把场景集压缩到可计算的规模。这本质上是精度和算力之间的博弈,而削减算法的好坏,直接决定这场博弈的平衡点落在哪里。
2. 场景生成:从概率分布到可计算的“未来切片”
2.1 蒙特卡洛采样的底层逻辑与适用边界
场景生成最常见的人门方法就是蒙特卡洛采样。思路很简单:先根据历史数据估计出不确定量的概率分布,然后从分布里反复抽样,每一组抽样结果就构成一个场景。以风速为例,如果假设它服从双参数Weibull分布,那么只需要从历史风速里用极大似然法估计出形状参数和尺度参数,就能用随机数发生器生成大量风速序列。
但这里有个细节:直接独立抽样得到的是“一堆没有时间记忆的点”,不是一条合理的风速曲线。我在实际项目中处理这个问题时,常用的做法是先估计相邻时段风速的自相关系数,然后用一阶自回归模型来生成时序序列,也就是在上一时段风速的基础上加上一个服从条件分布的随机扰动项。这样生成的场景曲线,既有统计分布上的正确性,又保留了“下午的风和上午的风有关联”这个基本物理常识。同样地,辐照度、负荷也可以按这个思路改造。
蒙特卡洛方法的优点就是灵活、实现简单,不管分布多奇怪,只要采样次数足够多,统计特征总能逼近真实分布。缺点也很明显:如果想要覆盖小概率但高影响的事件(比如极端寒潮导致的负荷尖峰),纯随机采样得抽很多次才可能碰到一次。如果只是做常规调度分析,这个缺点一般还可以接受;但如果你要做极端工况下的校核,就得上重要性采样或者后面会提到的针对性场景生成方法了。
2.2 时间序列模型:让场景带“记忆”
前面提到自回归思想,再往前走一步,就是ARIMA这类经典时间序列模型。ARIMA的核心是把一个时间序列看成“过去值的线性组合+白噪声”,差分项负责处理趋势和非平稳性。在微网场景生成的场景里,ARIMA通常用来给负荷或风速的“波动序列”建模,而不是直接对出力本身建模。
我自己更常用的做法,是先用历史数据训练ARIMA模型,得到一个预测值和残差的标准差估计,然后蒙特卡洛采样残差序列来生成备选场景。这样生成的场景天然具备正确的时序相关性,并且因为残差一般近似服从正态分布,采样和实现都比较顺手。不过ARIMA也有局限:它对非线性特征和突变事件的刻画能力一般,遇到天气过程性突变(比如冷锋过境)时,模型的预测区间会明显偏窄——换句话说,它给出的场景集可能会系统性低估极端事件的概率。
用ARIMA生成场景时,有几个参数需要反复调:阶数p和q的选择,一般用AIC/BIC准则帮忙筛选,但也不能全信信息准则,还得结合业务需求做人工校验;差分的阶数d要谨慎,差分过多会把有用的长期信息也差掉,导致生成的场景虽然平稳但失去了本来应有的季节特征。我遇到过最典型的问题就是把光伏出力序列强行做了二阶差分,结果生成出来的场景全是围绕0波动的白噪声,完全看不出一天之内日出日落的形状,后来改成按日类型分别建模才解决问题。
2.3 相关性问题:用Copula表达变量间的“默契”
微网里很少只有一个随机量,往往是光伏、风电、负荷同时存在,而且它们之间彼此相关。比如同一个微网内的光伏和负荷,在夏季往往有正相关性——太阳越大,空调负荷越高;风电和负荷之间的相关性,又在不同季节呈现完全不同的模式。如果只对每个变量单独建模、独立采样,生成的场景集就丢失了变量之间的“默契”,调度结果自然失真。
处理多维相关性,业界最成熟的方法就是Copula。Copula的通俗理解是:把每个变量的边缘分布(各自长什么样)和变量之间的连接结构(彼此怎么联动)拆开建模。边缘分布可以用前面提到的Beta、Weibull或者非参数法来拟合,连接结构则用Copula函数来描述,常见的有Gaussian Copula和t-Copula。把这个思路翻译到具体操作里,大致是三步:第一步,把每个变量的历史观测值转化为对应的均匀分布分位数;第二步,用这些分位数估计Copula函数的参数;第三步,从Copula中联合采样,再反变换回各变量原始分布。
这套方法我实测下来,最关键的收益不是相关系数更精确,而是生成场景时能保持变量间的尾部相关性——尤其是极端天气下“大风和低负荷同时出现”这类情况。这种场景看起来冷门,但对微网是否需要在夜里保留备用容量、储能要不要留底电,影响非常大。要是用独立采样,这类场景出现的概率会明显偏低,导致调度策略在真正的恶劣天气面前缺乏抵抗力。
2.4 场景生成阶段最容易翻车的三个细节
场景生成看起来就是“抽样”,但实际操作中翻车率极高。我梳理一下最常踩的坑,基本都是项目里真实遇到过的。
第一个是数据泄漏。有人直接把全部历史数据拿去拟合分布,又用同一段数据的均值曲线来生成场景,结果评估出来误差极低,到了线上立刻“现原形”。正确做法是把历史数据严格切成训练段和测试段,生成场景时只用训练段的信息,测试段只用来做评估。第二个是样本外极端事件被平滑掉。概率分布建模天然会把低频极端事件当成“离群点”处理,拟合时它们的贡献很小,导致生成场景集里几乎没有极端天气的影子。这种情况下,我一般会单独构造一个“极端场景库”,把历史上有记录的新能源停机、负荷尖峰等现象做成固定场景,按一个小概率附加到场景集里,而不是指望纯随机采样能自然覆盖。第三个是季节混淆。冬天的负荷形态和夏天的完全不一样,把一年的数据混在一起拟合一个分布,生成出来的场景一定是四不像。我的习惯是按季节(甚至在过渡季节按月)分别建立模型,宁可多维护几套参数,也要保证场景的物理意义是清晰的。
3. 场景削减:在“精度”和“算力”之间找平衡
3.1 削减的本质不是“删数据”,而是概率测度的近似
不少人第一次接触场景削减时会下意识觉得:这就是从一堆场景里挑出几个“代表”,把相似的删掉。这个理解方向没错,但不够准确。站在数学角度看,原始场景集定义了一个经验概率分布,削减的目的,是找到另一个规模更小的分布让二者的“距离”尽量小。这个“距离”在文献里常用Kantorovich距离或者Wasserstein距离来度量——听起来高大上,你可以把它理解为两个分布之间“搬运概率质量”的最小代价。
所以场景削减的结果不只是一组更少的曲线,它同时还必须给每个保留场景分配一个发生概率。这个细节非常重要:如果削减之后仍然用等概率来对待每个场景,本质上等于刻意扭曲了分布信息。实际操作中,很多人拿着聚类算法把场景聚类完、取个簇心就走,完全不考虑簇内场景原始概率的累加,导致削减后的期望值偏移很大。后面做优化求解的时候,目标函数里每个场景的权重一旦错配,成本误差就很容易被放大到不可接受的程度。
3.2 快速前向削减法:原理与手算直觉
快速前向削减法(Fast Forward Selection,FFS)是场景削减领域最经典的方法之一,它和另一种叫同步回代削减(Simultaneous Backward Reduction)的方法是互补的关系。FFS的核心策略是“贪心”式地、从原始场景集中逐个挑出最能代表整体分布的场景,直到凑够目标数量。
具体流程可以这样描述:每一步迭代,它从剩余未入选的场景中挑一个出来,使得把它加入已选集之后,两个场景集之间的Kantorovich距离增量最小。这个做法兼顾了两件事——已选场景彼此之间不能太像,不然冗余;同时已选场景又得尽量覆盖原始场景集中的“高密度区域”。等迭代到目标数量之后,再把原始场景按与哪个已选场景距离最近进行分配,并把概率累加给对应的代表场景。这个过程听起来不复杂,但在实操中用纯Python循环实现时,要小心计算复杂度。原始场景几千个、每个场景几十上百个时段时,场景间距离矩阵就是百万级规模,每一步迭代还要做贪心搜索,不优化一下矩阵运算效率,跑起来会非常痛苦。我习惯先把所有场景间的成对距离向量化计算并缓存下来,后续的贪心搜索直接对距离矩阵做索引操作,速度能提升一个数量级。
3.3 聚类方法:从工程便利性出发的替代方案
如果说FFS是“精雕细琢”,K-means聚类就是“大刀阔斧”。工程上我接触到的微网项目里,用K-means做场景削减的反而更多,原因无他:实现快,效果也不差。做法很简单,把每个场景当成多维空间里的一个点(维度数等于时段数),然后用K-means算法聚成预先设定好的K类,取每个类的中心向量为代表场景,类的权重由类内场景数量(或者考虑原始概率后的加权和)决定。
这里有几个工程细节值得注意。一是要归一化。光伏出力的数值范围是0到装机容量,负荷可能是几千千瓦,风速又是完全不同的量纲,如果不做归一化直接扔进聚类算法,聚类结果基本由数值大的变量主导。二是距离度量。K-means默认用欧氏距离,它在场景削减场景下通常够用,但如果场景的形态更重要(比如“先涨后跌”和“先跌后涨”虽然均值相同,调度意义却不同),可以考虑用动态时间规整距离或基于形状的距离,不过计算成本会高不少,需要权衡。三是K值的选择。这里没有一劳永逸的办法,我一般会同时算3~5个K值下削减前后概率分布的Wasserstein距离,画一条误差随K值变化的曲线,找到“误差开始变平缓”的拐点,那个K值就是性价比最高的选择。
用K-means做削减时还有一个容易出问题的地方:聚类中心是类内场景的均值,这种做法天然会把类内的极端波动“平均”掉。如果原始场景集中包含特意构造的极端场景,聚类之后它们可能被稀释在一个大簇里,等你想在校验恶劣工况时去找对应的极端场景,已经找不到了。针对这种情况,我通常的补救方案是:在K-means削减之后,把原始场景集中距离最终代表场景最远的那几个极端场景单独保留下来,再按一个小概率附加进削减后的场景集。
3.4 削减效果评估:三条硬指标
场景削减做完,到底行不行,不能靠感觉,得有量化标准。我每次做项目评审时都会给出三组数字,比任何解释都管用。
第一个是分布层面的指标,也就是削减前后两个概率分布的Wasserstein距离。这个值越小,说明削减后的场景集在“概率空间”上越接近原始场景集。第二个是统计特征层面的指标,比如新能源出力和负荷的期望值、标准差,以及各时段分位数。削减后的场景集应该在这些统计量上与原场景集保持接近,一般期望值误差能控制在2%以内就算很好了,分位数误差尤其要关注尾部——如果削减后的5%分位数偏差过大,说明极端场景信息丢失了。第三个是经济层面的指标,把两套场景集分别代入同一个动态经济调度模型,比较优化出来的期望运行成本、储能充放电次数和弃风弃光率。这个指标最直观也最有说服力。我见过不少场景集在统计指标上完美,但代入模型后成本突变好几万块的情况,原因就是细节形态(比如出力曲线在第13时段附近的尖峰)被破坏了。
三条指标之间需要平衡,不必强求每一项都最优。一个经验性的做法是:做削减时以Wasserstein距离为主目标,削减完成后用另外两个指标做复核,如果统计指标和经济指标偏差过大,再回头调整削减数量或换一种削减算法。
4. 一套能落地的随机动态经济调度实操流程
4.1 数据准备和参数设置
光讲原理不过瘾,我把一套实际跑通过的流程骨架整理出来,给你做个参考。调度周期设为一天,分辨率15分钟,一共96个时段。微网内主要包含光伏1000kW、储能300kW/600kWh,允许从外部电网购电,但购电价格分峰谷平时段。负荷数据取某工业园区夏季节典型日,光伏出力用Beta分布描述,负荷用ARIMA扰动生成。
下面是基础参数表:
| 参数 | 数值 | 备注 |
|---|---|---|
| 调度周期 | 24h / 96时段 | 每时段15分钟 |
| 光伏容量 | 1000 kW | 场景生成主要对象 |
| 储能容量 | 300 kW / 600 kWh | SOC范围0.1~0.9 |
| 储能充/放电效率 | 0.95 | 简化对称效率 |
| 购电峰值/平时/谷值电价 | 1.2 / 0.75 / 0.4 元/kWh | 跟当地电价政策有关 |
| 弃光惩罚 | 0.3 元/kWh | 仿真中对未消纳光伏的惩罚 |
| 旋转备用需求 | 负荷的5% + 光伏出力的10% | 应对不确定性的保守策略 |
这套参数不算复杂,但足以体现动态经济调度里“动态”二字的含义:储能SOC的时序递推、机组爬坡约束、每个时段购电决策对后续时段可用容量的影响,这些全都耦合在一起。
4.2 从原始数据到调度方案的四步走
完整流程可以拆成四步,我每个项目都会按这个顺序推进:
第一步,数据预处理。把历史光伏、负荷数据的异常值清掉,缺失片段用前后时段插值补齐,然后按季节切片。这个步骤看似普通但最影响后面所有环节,数据不干净,生成场景的分布参数就是错的,后面一切努力白费。
第二步,场景生成。用蒙特卡洛方法生成2000个原始场景,每个场景包含96时段的光伏出力和负荷曲线。注意这里要用带时序相关的采样,至少加一阶自回归项,否则场景曲线看起来“电锯式”抖动。如果项目里同时有风速和光伏,就引入Copula把相关性做进去。
第三步,场景削减。我们在这套流程里用FFS,把2000个场景削减到30个。削减完成之后,把30个场景的概率归一化,确保概率总和为1,同时打印削减前后光伏出力的期望值曲线来直观判断质量。原本用一个5000维的场景集做优化,现在压缩到30个场景,模型规模缩小了两个数量级,求解时间能控制在几分钟以内。
第四步,构建并求解随机动态经济调度模型。目标函数是期望运行成本最小化:购电费用 + 弃光惩罚 + 储能运维成本。约束条件包括每个时段的功率平衡、储能SOC递推约束、充放电功率上下限、购电功率上限,以及一个跨场景耦合的备用约束。求解器我一般用商业求解器搭配YALMIP或直接写Python接口,模型规模不大,求解很稳。
4.3 小型算例的结果长什么样
我把这套流程跑过一遍,拿到的结果规律性很强:削减后的30个场景,光伏出力期望值曲线和原始2000个场景的曲线几乎重合,波动幅度的分位数带也保持得不错。调度方案的表现是,储能基本按照“光伏大发时充电、晚高峰时放电”的模式运行,购电尽量集中在谷价时段——这符合直觉,也验证了模型逻辑没有跑偏。
最让我关注的一个数字是削减前后调度成本的偏差。在这个算例里,用30个场景求出的期望运行成本,和2000个场景求出的值相差不到1.5%。这说明FFS把关键分布信息保留住了。之前试过用等概率取聚类中心的做法,成本偏差直接到6%以上,原因就是场景削减后的概率权重没处理好。这个对比充分说明了为什么场景削减不能只砍数量、不管概率。
还有一个细节,削减后场景里保留了1~2个“光伏出力明显偏低”的尾巴场景。这两个场景概率不高,但它们负责撑起备用约束,让储能低电量运行时段不会出现备用缺口。这正好呼应前面说的:削减场景时,不能把极端场景一刀切掉。
5. 常见问题与实战排查
5.1 削减后概率分布“失真”怎么判断
有一次我在项目中削减完场景,画出来负荷的期望曲线跟原始曲线偏差很小,但带入优化模型后,购电成本的P95分位值明显偏低。排查之后发现问题出在负荷尖峰场景:原始场景集里有一批负荷尖峰出现在晚高峰的场景,在FFS迭代过程中,它们没有被单独选中,而是被分配给了相近的低负荷代表场景,概率被摊薄,尖峰信息被抹掉了。
解决思路有两个方向。一个是把负荷、光伏分开做削减而不是拼在一起做联合削减,因为联合削减时不同变量的削峰会互相掩盖,分开削减、再把结果组合起来,可以针对性保护关键形态。另一个是在削减目标函数里对极值时段加权,也就是给晚高峰时段的负荷偏差更大的权重,这样削减算法会优先保住高峰场景。
5.2 生成场景波动太“平缓”或太“剧烈”的调参方法
场景生成的波动幅度直接取决于采样时扰动项的标准差。如果生成场景的曲线都贴着历史均值走,看不到应有的波动,那大概率是扰动标准差设小了。反过来,如果场景曲线抖得像噪声,完全没有时序特征,多半是时序相关参数设得太低,甚至忘了加自回归项。
我常用的校准办法是:拿生成场景集算一遍各时段的标准差,跟历史数据的时段标准差比较。如果各时段的标准差在历史值的80%~120%范围内,我就认为波动幅度是合理的。差得多了,就去调节扰动项和自回归系数。这个方法简单有效,比跑到优化阶段再回来排查快得多。
5.3 调度结果对场景数目“不敏感”的隐情
有时候你会发现场景数从500减到20,期望运行成本几乎不变,看起来很美好。但别高兴太早,这往往不是削减算法厉害,而是模型本身对不确定性不敏感——比如约束里根本没有备用容量,没有储能SOC边界条件,或者目标函数里没有弃光惩罚。模型感知不到不确定性的影响,自然也不在乎场景数量。
这种情况需要回头审视模型设计。一个最直接的验证方法是评估松弛变量:检查随机调度结果中,有多少时段处于备用约束的临界状态。如果几乎不触碰临界,说明不确定性约束太松;如果频繁触碰,说明模型对场景敏感,削减的质量才会真正影响结果。
5.4 削减结果不可复现的排查思路
很多同行做场景生成时图省事,随机种子不固定,每次运行生成不同的场景、削出不同的结果,优化结论也随之漂移,写报告时才发现结果对不上。这个问题解决起来很简单:在采样和聚类/削减环节里显式固定随机数生成器的种子,最好在代码里把随机种子和关键参数写成一个配置文件,每次运行都留档。
如果是同一份数据、同一个种子还出现结果不一致,那就得检查代码里是否有并行计算的竞态条件,或者是求解器线程数设置不一致导致优化结果有细微差异。这种情况在Matlab和Python混合编程的工程里不少见,排查起来比较费时间,但一旦规范了随机种子、求解器参数的统一入口,问题基本能根除。
我玩这套流程几年的体会是:场景生成和削减看起来是数学问题,真正难的是工程细节。分布参数差一点、概率权重错一点、极端场景丢一个,最后都会体现在调度方案的经济性和可靠性上。所以每做一个新项目,我都会先跑一遍“生成→削减→调度→评估”的全流程闭环,确认统计指标和成本指标都能对齐,再扩展规模或者改场景模型。别嫌这一步麻烦,它帮我省掉的返工时间,比写一整段代码多得多。下次你遇到调度结果不合理又查不出模型问题时,不妨先回头看看场景集本身是否靠谱,多半会有意外发现。