
搞微电网调度的人几乎没有谁没被“明天风多大、光多强、负荷涨多少”这件事折磨过。不是算不出来而是你今天算好的开停机方案明天可能因为一片云盖住光伏或者一阵风全停就变成一个亏钱甚至失负荷的方案。这类问题在专业上叫不确定环境下的动态经济调度工程上最常用的落地手段就是场景法先根据历史数据生成一大批未来可能出现的曲线再用削减算法挑出少数有代表性的曲线最后丢进优化模型里求期望成本最优。今天就把场景生成与场景削减这条链路的逻辑、算法和踩过的坑一次讲透。1. 微网调度里不确定性到底“捣了哪些乱”1.1 为什么动态经济调度非要扯上场景先理清一个概念。所谓动态经济调度不是把每个时段单独算一遍而是要考虑跨时段耦合。比如燃气机组从20%负荷爬到80%需要时间储能电池这一小时充多少直接影响下一小时的荷电状态甚至是冷热电联供机组启停之后不能立刻关停。每个时段的最优解都不是独立的所以调度问题要放在一个时间窗口里整体求解。但问题来了模型里的风电、光伏、负荷这些输入在调度时刻并不确定。传统做法是取预测值作为确定性参数算出来一个“点解”。缺点是预测误差一旦变大点解就完全失真。我也见过直接用最坏情况做鲁棒优化的方案所有不确定量按边界取结果可行域被缩得极小经济性差到没法看。场景法走的是中间路线把未来可能的运行状态生成出一批样本每个样本都是一条完整的多时段曲线然后优化模型针对这些样本求期望成本最优。这样既不要求你对分布做特别强的假设又能让求解器在合理的计算时间内跑出来是工程上接受度最高的做法。1.2 场景生成到底在生成什么这里我打个比方。最近流行上传一段视频生成对应的三维场景本质是从一段包含时间、视角变化的二维图像序列里重建出一个三维空间。场景生成干的事其实是类似的根据有限的历史观测去“重建”未来可能出现的多维时间序列集合。视频转三维场景补的是空间维度场景生成补的是概率维度。你手头只有过去几十天的风电出力、光伏出力、负荷曲线但你需要知道明天可能出现哪些形态的曲线它们的概率有多大这就得靠生成模型把这些看不见的分布“外推”出来。从数学上讲场景就是一个随机向量的实现。典型场景是一段长度为T的时序每个时间步包含风功率、光伏功率、负荷功率等多个分量所以一个场景就是T乘以变量数的矩阵。生成的意义不是算出某个确定值而是让后续调度模型能覆盖足够多的可能性。很多新手直接拿历史曲线当天场景用也行但历史只有那么几条覆盖不了太多情况尤其在极端天气下容易出偏差。2. 场景生成从“乱抽”到“像样”的三个关键动作2.1 先把随机变量的“脾气”摸清楚场景生成的第一步往往不是采样而是参数估计。你得先回答三个问题风功率的分布长什么样负荷预测误差是偏大还是偏小不同时段之间有没有强相关性最常见的做法是假设风速服从Weibull分布再通过风机功率曲线转换成风电出力。但注意功率曲线是有上下限的风速超过切出风速后出力直接归零所以生成的风电出力分布是混合型的有大块概率堆积在0和额定值附近。如果你硬用正态分布去拟合出来的场景会高估中间段出力低估极端情况。负荷曲线相对温和预测误差通常可以用正态分布描述但也要看季节和用户类型。工业用户负荷波动小商业用户午高峰明显居民用户晚高峰突出这些结构性特征都要从历史样本里抽取。我在项目里一般先对历史数据做季节和星期分类再分别估计每个类别的均值和协方差矩阵比一股脑全放一起准得多。这里推荐一个实操习惯把历史数据按“同一小时”分组估计每个时段的均值和标准差再估计相邻时段之间的相关系数。只估计边际分布、不考虑时间相关性是你后面做场景削减时最可能翻车的隐患。2.2 抽样方法蒙特卡洛不是唯一选项参数定下来之后就是生成样本了。最直观的是蒙特卡洛抽样从分布里随机抽。好处是实现简单坏处是如果分布是重尾的你需要抽大量样本才能覆盖尾部区域。比如你抽2000个风功率场景可能只有两三个落在“大风导致出力满发”的区域后面削减的时候一不小心就把它们全削没了调度方案对极端情况的预判就缺了一块。所以我做工程时更喜欢用拉丁超立方采样。它的核心思想是分层把概率空间均匀切成若干区间在每个区间里至少抽一个样本保证抽出来的点能均匀覆盖整个分布空间。简单说蒙特卡洛是“随机撒网”拉丁超立方是“网格布点”后者在小样本量下覆盖率更高。先对单一随机变量做分层采样然后再处理变量间的相关性。常用手段是对协方差矩阵做Cholesky分解。设L是协方差矩阵C的下三角矩阵先采样一组独立的标准正态变量u经过mu L*u变换后得到的变量就带有指定的相关性。这个方法很成熟几乎所有科学计算库都有现成实现。2.3 时间相关性、厚尾与边角情况处理场景生成里最容易忽视的一个点是时间相关性。很多新手给每个时段单独抽样结果生成出来的风电曲线像锯齿一样跳来跳去前一小时还是0.8下一小时突然变成0.1再下一小时又跳到0.9。这种场景在物理上根本不可能出现因为风功率有惯性波动是连续的。处理办法很简单在生成模型里加入时序依赖。可以用一阶自回归模型让当前时段的值与上一时段的值相关。对风电来说平滑系数通常取0.7到0.95取值越大曲线越平滑。光伏因为昼夜规律明显还要叠加一个“白天平滑、早晚陡变”的形态约束。厚尾和边界也是重灾区。风功率出力不能低于0也不能高过额定值场景生成后要做截断处理。但是直接用np.clip截断会让概率质量堆在边界上导致优化模型认为“满发”或“零出力”的概率比真实情况高很多。更合理的做法是在采样前就引入边界感知的分布比如用截断正态分布或者在采样后对边界样本做局部微调。检查生成质量时我习惯把生成场景的统计特征与历史数据对一遍看均值、标准差、90%分位数差多少。差值超过5%就回去检查参数估计而不是只看几张曲线好不好看。3. 场景削减把人留多了模型跑不动留少了结果没底3.1 聚类思路用“代表”代替“全部”场景多不是坏事但优化模型扛不住。一个微网动态经济调度问题如果有20个时段、上百个0/1变量再叠加3000个场景求解器很可能一晚上都跑不完。所以必须削减。最简单、也最常用的方案是聚类。把每个场景当成高维空间里的一个点用K-means或谱聚类把它们分成若干簇然后用簇中心代替整个簇。K-means的好处是快实现到处都有坏处是对初始值敏感容易陷入局部最优而且它对形状复杂的簇效果一般。我建议在聚类前先对场景做降维比如用主成分分析保留90%以上的方差再在低维空间里聚类。这样计算更快也能过滤掉一些噪声维度。聚成多少簇合适没有固定答案我一般先看轮廓系数找拐点再结合硬件能力削减后场景数量控制在10到20个之间。但K-means有个天然缺陷它是“等权”聚类对每个簇只保留一个中心簇内概率信息就丢了。尤其是那些出现概率低但代价很高的极端场景很可能被并入一个大簇里直接“平均掉”。所以我更推荐用基于距离的概率削减方法。3.2 用Kantorovich距离做场景筛选工程实践中效果比较好的是基于Kantorovich距离的快速前向削减。Kantorovich距离又叫Wasserstein距离衡量两个概率分布之间“搬石头”的最小代价。对离散场景来说就是把一个场景集的概率质量搬到另一个场景集上需要移动的最小加权距离。快速前向削减的流程不复杂初始时所有场景都在保留集合里每轮尝试删掉一个场景看它和剩余场景的加权距离会增加多少选择增加最小的那个场景删除同时把它携带的概率质量转移到最近的保留场景上。重复直到场景数量达到目标值。这样做有几个好处保留下来的一定是“离群但重要”的场景概率权重会自动调整为更合理的分配极端场景如果与所有场景距离都很远删除代价太大反而会被留下来。前向削减的Python实现量很小迭代逻辑也清晰实测下来稳定性比K-means好很多。用Kantorovich削减时要留意它的计算复杂度是O(n^2)量级。初始场景是3000个时还能接受但如果上到1万个两两距离矩阵的存储就有点吃紧了。解决办法是先做一轮粗聚类到500个再精细削减到目标数量误差增加可以忽略。3.3 削减之后的概率权重怎么定削减完不是直接把场景丢进模型还要给每个场景重新分配概率权重而且所有场景的权重之和必须等于1。如果是用聚类权重就是簇内样本数除以总样本数如果是用前向削减权重就是一路累积下来的概率质量。别小看这一步。我见过有人削减后直接给每个场景平均权重算出来的期望成本比真实值偏了一大截方案也明显偏保守。因为高频场景被平均权重低估低频高风险场景又被平均权重高估最优解自然就偏了。一个稳妥的校验方式削减前后分别求所有场景下的平均目标函数值两个值的差控制在几个百分点以内说明削减质量基本过关。如果差距过大大概率是概率权重分配有问题或者场景数量K选得太少了。4. 动态经济调度模型怎么把场景真正用起来4.1 目标函数与多场景约束场景生成好了削减完了接下来就是往调度模型里嵌。动态经济调度的目标函数一般是总运行成本最小化。放到多场景框架下就是每个场景按其概率权重计算的成本之和最小。成本包括机组燃料成本、启停成本、运行维护成本、从电网购电成本还可以加一个惩罚项用来约束失负荷或弃风弃光。写成数学形式就是min Σ_s p_s * C_s(x_s)。注意这里下标s代表场景。很多变量是场景相关的比如每个场景下的机组出力、储能充放电功率但也有一些变量是所有场景共享的比如机组的启停状态。启停机决策必须在看到真实场景之前定下来这就叫“这里-现在”决策学术上叫非预期性约束。经验提示场景一旦增多约束数量也会等比例放大模型规模很容易失控。一个好习惯是先用10个场景跑通模型确认逻辑正确后再增加到20个、30个。直接拿100个场景调试反馈周期太长会浪费很多时间。4.2 日前计划和日内滚动怎么配场景场景法在日前计划和日内滚动两个阶段的应用风格不一样。日前计划看重的是全天形态所以需要覆盖多发可能的风电曲线、光伏曲线、负荷曲线削减后的场景大多在10个上下就够用。日内滚动更关注最近几个小时的预测精度场景数量可以更少但要频繁更新比如每15分钟重新生成一次场景只看未来4小时。我自己做日内滚动调度时更喜欢用“分时段场景集”而不是全天场景。未来1小时用精细场景后面3小时用粗略场景这样算得准又算得快。如果一刀切用全天场景局部预测误差会污染整个窗口的调度策略。另外储能和爬坡约束天然会把场景耦合起来。比如储能最优策略是“低谷充电、高峰放电”但如果场景集里没有包含低谷和高峰的边界情况储能策略就会偏保守。所以生成场景时不要太平均要保证典型“尖峰日”“低谷日”都有代表削减后的场景集才有辨识度。5. 一套现成的Python实现路线含代码5.1 数据准备和参数估计这一步别偷懒这里我直接给一套我常用的实现路线。第一步永远是从历史数据里估参数别上来就跑模型。假设你的历史数据是hourly形状为(天数, 24小时)每一行是一天24个时点的出力或负荷。我先按星期几和季节分组然后估计每个分组的均值向量mu(24维)和协方差矩阵cov(24×24)。这段代码很简单但它是整个场景生成的骨架。import numpy as np def estimate_param(history): # history: (n_days, 24) 按天分行的历史样本 mu history.mean(axis0) cov np.cov(history.T) return mu, cov这里有一个细节协方差的自由度需要足够大。如果你只有30天历史数据却要估24×24的协方差矩阵那么矩阵几乎是奇异的生成出来的场景会非常病态。至少要有100天以上的样本或者用Ledoit-Wolf收缩法对协方差矩阵做正则化实测效果明显稳定。5.2 场景生成与削减的参考代码生成场景我用的是“先独立采样再Cholesky相关化”的老套路。先对每个时段独立做拉丁超立方采样然后用协方差矩阵的Cholesky分解把相关性加进去。单位向量形状为(24, n_scenarios)转置后得到(n_scenarios, 24)。from scipy.linalg import cholesky from scipy.stats import norm def gen_scenes(mu, cov, n_scenes2000, seed0): rng np.random.default_rng(seed) T len(mu) u rng.random((T, n_scenes)) # (T, n_samples) 均匀样本 z norm.ppf(u) # 分位数变换到标准正态 L cholesky(cov, lowerTrue) scenes mu[:, None] L z # 加相关结构 return scenes.T # (n_scenes, T)注意如果原始协方差矩阵不正定Cholesky会直接报错。遇到这种情况先给协方差矩阵的对角线加一个小量比如1e-6再用。我在实际项目里通常加1e-4效果更好。削减部分我用快速前向削减。核心逻辑看我注释里的顺序就行每次删一个场景把它的概率加到最近邻场景上直到数量达标。from scipy.spatial.distance import cdist def reduce_scenes(scenes, probs, num_keep): scenes np.asarray(scenes) probs np.asarray(probs, dtypefloat) idx np.arange(len(scenes)) while len(idx) num_keep: D cdist(scenes[idx], scenes[idx], metriceuclidean) cost np.sum(D * np.outer(probs[idx], probs[idx]), axis1) cost - np.diag(D) * probs[idx] ** 2 # 去掉自身配对 rm np.argmin(cost) # 把被删场景概率合并到最近邻 dist_to_rm D[rm].copy() dist_to_rm[rm] np.inf nbr np.argmin(dist_to_rm) probs[idx[nbr]] probs[idx[rm]] idx np.delete(idx, rm) new_probs probs[idx] new_probs new_probs / new_probs.sum() return scenes[idx], new_probs这段代码绘图方便、验证也直观唯一需要注意的是每轮都重新算两两距离矩阵n_scenes大时会比较慢。工程化的时候可以先粗聚类再精细削减也可以在每轮只更新受影响的行列不做全量重算。5.3 削减完了一定要做质量校验我的习惯是削减后立刻做三件事看削减前后边际分布直方图是否接近、看两两场景之间的最小距离是否合理、看削减前后调度目标函数值差异是否在允许范围内。其中目标函数值校验最直接。你先把生成的所有场景喂给调度模型求一次期望成本再把削减后的场景喂进去再求一次期望成本。如果两个结果相差超过5%说明削减丢掉的东西影响到了决策需要增多保留场景数量或改用后向削减。这一步是很多算法文章的盲区。论文里可能只给你看一条曲线“削减前1000个 vs 削减后10个”却不告诉你差距多大。放到实际项目中这5%的差距直接决定你用10个场景还是25个场景。6. 常见问题排查与实操心得6.1 一张表对照排查场景问题我用一张表总结一下自己在项目里碰到的典型问题和解法方便你排查时快速定位。现象可能原因处理思路削减后期望成本比削减前低很多概率权重重新分配不准或极端高成本场景被削掉了检查概率归一化增大保留场景数改用后向削减生成的风电场景像锯齿一样跳未加入时间相关性用AR(1)模型或带协方差结构的相关采样光伏场景夜间不为0没有处理零值结构对夜间时段强制置0再加平滑过渡削减后某一两个场景权重特别大聚类中心初始化不合理多次运行取最优改用快速前向削减调度模型求解时间爆炸场景多、整数变量多先削减到10个跑通再逐步增加优先做线性化优化结果第二天执行偏差大场景未覆盖真实出现的运行形态检查历史数据是否包含当日天气类型调整场景生成参数你会发现大多数问题根源不在算法太复杂而在生成和削减时的分布假设不合适。做工程千万不要迷信单一路径多准备两套方案至少一个聚类、一个距离削减互相校验。6.2 我踩过的坑和现在习惯性保留的做法我最早做微网调度时犯过一个特别典型的错误盲目追求场景数量多生成了5000个场景觉得“越多越接近真实”。结果就是模型求解到天亮都出不来最后只能中途放弃改成确定性模型凑合。后来把场景削减到15个求解时间从几小时降到十几分钟期望成本和5000个场景的结果只差了2%左右。这个对比让我明白一个道理场景数量不是精度来源场景代表性和概率权重才是。另一个让我印象深刻的是相关性处理。有一次我生成的风电和光伏场景明明是各自独立生成的看起来都正常但放进调度模型后发现“风光同高”“风光双低”的情况严重偏少。原因就是我只考虑了各自的时间相关性忽略了风、光、负荷之间的空间相关性。后来我在生成阶段把三者放进同一个协方差矩阵里做联合采样问题立刻消失。现在我保留了一个习惯每次做完削减都会把削减前后的场景集可视化对比一次再用一小段调度模型做校验。如果目标函数差异在3%以内才把结果正式作为输入。这个习惯帮我挡掉过不少麻烦很多论文里轻描淡写的“场景削减”实际操作起来都是在跟概率分布、求解性能和工程误差三方博弈。把它当一条流水线来做每一步留校验口比最后出了问题再去翻算法靠谱得多。最后分享一个小技巧调度模型和场景生成尽量解耦场景模块独立成一个函数只要保证输出格式一致无论是换历史数据、换置信水平、换季节调度模型都不用大改。我自己的项目结构里场景模块、削减模块、优化模块各占一层测试哪一层出问题心里有数改起来也不会牵一发动全身。这套思路沿用到现在微网、园区、区域综合能源系统都在用核心就是那条“生成-削减-校验-调度”的链路。