
虚拟电厂日前调度最典型的痛点就是“早上拍板的计划下午就变了”。分布式光伏出力被云层一挡实际出力比预测值低一大截负荷预测在傍晚高峰又经常低估储能和可调机组不得不临时顶着调。上个月我调一个园区级虚拟电厂的日前鲁棒优化调度方案时把源侧和荷侧的随机波动同时纳入模型用两阶段鲁棒优化做决策才真正把偏差考核费压下来。这篇就聊聊这个题目背后完整的建模、求解和工程落地思路包括我踩过的坑和调参经验。适合正在做能源管理系统、虚拟电厂聚合商日前申报或者研究鲁棒调度的同行。1. 问题拆解为什么日前调度必须同时盯住源侧和荷侧的不确定性1.1 虚拟电厂日前调度的业务背景虚拟电厂本身不建电厂它是把分散在园区、楼宇、厂房里的分布式光伏、风电、储能、充电桩和可调负荷“聚”成一个整体对外像一个可控电源参与电力市场或需求响应。日前调度的核心任务是在提前一天甚至提前24小时为所有聚合资源排出第二天的出力曲线和申报功率同时给出储能充放电计划、可调负荷的削减量、备用容量等。这个任务和传统电网调度最大的区别在于虚拟电厂里的分布式资源数量多、单体容量小、运行约束却非常细碎。比如储能不能同时充放充电桩有功率区间可调负荷削减不能超过某个时长光伏逆变器有功无功耦合。变量一多决策模型很快变成一个大规模混合整数规划。而所有这些约束还要在一个“第二天实际运行环境不确定”的前提下成立问题就变得更复杂。1.2 “源-荷双重不确定性”具体指什么所谓“双重不确定性”说的是不确定性同时出现在电源侧和负荷侧而这两个方向还经常互相叠加。源侧的不确定性主要来自可再生能源。光伏出力取决于辐照度一片云飘过来就可能造成十兆瓦级的短时出力跌落风电则随风速变化几分钟内波动就能超过爬坡能力。尽管预测模型越来越准但在日前尺度上光伏出力预测误差普遍在10%~20%极端天气下可能更大。负荷侧的不确定性来自用户行为。车间用电受订单影响办公楼空调受天气影响电动汽车充电更是随插随充。最头疼的是很多虚拟电厂聚合的是灵活性资源比如空调负荷参与需求响应用户既可能提前响应也可能临时退出。这样负荷预测误差常常和源侧误差同时出现。如果处理不好最直接的结果是虚拟电厂日前申报的曲线和实际执行曲线不一致产生偏差电量。现在偏差考核是按正负偏差的绝对值罚款误差越大考核费越高。做过交易的人都知道偏差考核吃掉的利润往往比设备运行成本还多。1.3 处理不确定性主流思路并不只是鲁棒优化在确定用鲁棒优化之前我也比较过其他几种方案。这里简单说下我的判断。确定性调度比较简单把预测值当成真实值模型规模小、求解快调度员也容易理解。但为了安全通常会在外面加固定比例的旋转备用。这种做法一旦碰上预测偏差较大的日子备用可能不够用只能靠实时市场高价买电如果备用设得太大经济性又很差等于每天为极端场景买单。随机优化可以同时考虑概率信息把不确定性建模成多个场景。理论上最贴合实际但难点在于概率分布很难准确拿到而且场景一多模型大到没法求解。裁剪场景又要损失代表性工程落地成本不低。模型预测控制适合滚动修正但虚拟电厂必须在日前节点提交报价曲线不能等到第二天再修正。所以它更适合做日内调度不适合做日前决策的核心引擎。鲁棒优化的思路则完全不同我不去想每种场景出现的概率只定义一个“不确定集合”只要实际光伏出力和负荷落在这个集合里我做出的调度计划就保证不违约、不越限。它不需要精确概率分布只需要预测误差的大致范围这个数据从历史运行记录里就能统计出来。在生产环境中这个特点是很有吸引力的。当然鲁棒优化不是没有代价它天然会比确定性方案保守一些因为要照顾到集合里的所有情况包括那些概率很低的最坏情况。怎么控制保守程度就需要通过不确定集合的预算参数来调节。这也是后面要重点展开的部分。2. 鲁棒优化建模从确定型模型到两阶段鲁棒模型的完整推导2.1 先把确定型日前调度模型写清楚任何鲁棒模型都是从确定型模型扩展来的所以第一步要把基础模型写扎实。我这里用一套简化但不失一般性的模型来说明。决策变量分三类第一阶段决策机组启停状态、储能是否充放电、可调负荷的削减状态、日前申报功率等。连续运行决策各时段机组出力、储能充放电功率、弃光/弃风量、切负荷量。辅助变量状态变量、爬坡辅助变量等。目标函数一般是总运行成本最小化包括火电/燃气机组的发电成本和启停成本储能充放电的退化成本从上级电网购电的成本弃可再生能源的惩罚费用切负荷的惩罚费用偏差考核费用如果用线性化方式处理。约束包括功率平衡约束所有电源出力 储能放电 上网购电 负荷 储能充电 可调负荷新增用电机组出力上下限、爬坡约束、最小启停时间约束储能的SOC递推、充放电功率上下限、充放电状态互斥可调负荷削减量区间和削减时长约束联络线功率约束备用容量约束。写成紧凑形式就是min f(x) g(p) s.t. A x ≤ b B x C p ≤ d p ≥ 0这里 x 表示二进制整数决策p 表示连续功率决策。注意因为虚拟电厂里大量资源都是离散的机组开停、储能状态所以确定型基础模型通常是一个混合整数线性规划MILP。MILP 本身已经不好解加入不确定性后更不能直接用枚举法。2.2 不确定集合的构造盒式、预算约束和多面体鲁棒优化的核心不是“目标函数怎么变”而是“不确定性用什么集合描述”。工程中最常用的是盒式不确定集加预算约束。以光伏出力 P_pv 为例它的真实值可以写成P_pv(t) P_pv_forecast(t) ΔP_pv(t)预测误差 ΔP_pv(t) 在一个区间内变化|ΔP_pv(t)| ≤ err_pv(t)这里的 err_pv(t) 就是该时段预测误差的边界通常取历史误差分布的分位数。如果只限制单个时段的范围集合就是矩形盒子U_box { ΔP_pv(t) : |ΔP_pv(t)| ≤ err_pv(t), ∀t }但这个盒子集合有一个很大的问题它假设所有时段的最大误差同时出现而实际上一天里不可能每个时段的光伏预测都同时偏到极限值。如果直接用盒式集合模型会非常保守成本暴涨。所以需要引入预算约束Budget of Uncertainty。最简单的方式是限制一天内所有时段误差偏离的总幅度Σ_t |ΔP_pv(t)| / err_pv(t) ≤ Γ_pv或者更常用的离散形式引入0-1变量指示该时段是否发生极端误差Σ_t z_pv(t) ≤ Γ_pv -Large·z_pv(t) ≤ ΔP_pv(t) ≤ Large·z_pv(t) -err_pv(t) ≤ ΔP_pv(t) ≤ err_pv(t)Γ_pv 就是鲁棒调节参数业内也习惯叫“预算系数”。它表示“我允许最多多少个时段同时出现极端偏差”。Γ0 时退化为确定性模型随着 Γ 增大不确定集合越来越“胖”解的鲁棒性越来越强但成本也相应上升。负荷侧不确定性同样处理记 ΔP_load(t)。在实际系统中源和荷的误差边界要分开统计不能直接用同一个经验值。我遇到过有同事图省事把光伏误差边界和负荷误差边界设成同一个比例结果调度方案在正午和傍晚两个峰谷转折点格外别扭后来才知道是因为两边误差的时变特征完全不同。更精细一点的多面体不确定集还会加上相关性约束比如限制光伏误差和负荷误差之间的联动关系。这类联合集合能显著降低保守度但求解难度也上来了。建议先做盒式预算约束的版本跑通后再考虑扩展。2.3 两阶段鲁棒模型为什么必须用两层决策两阶段的思想和虚拟电厂实际运行节奏完全对应。第一阶段是“日前承诺决策”在第二天到来之前确定机组启停、储能充放电状态、可调负荷是否参与、申报功率曲线。这些决策一旦定下来第二天不能再改所以又叫“here-and-now”决策。第二阶段是“实时调整决策”等到第二天实际光伏出力和负荷已经显现出来调度系统在满足所有运行约束的前提下调整机组的出力、储能的实际充放电功率尽量以最低成本消除偏差又叫“wait-and-see”决策。用数学语言写出来就是min_x { a^T x max_{u∈U} min_{y∈F(x,u)} b^T y }x 是第一阶段的整数和连续变量u 是源-荷不确定参数光伏出力、负荷等y 是第二阶段调整变量F(x,u) 表示给定 x 和 u 后的可行域。外面一层的 min 求日前成本中间一层 max 寻找最坏情况的不确定参数最内层的 min 是在这种最坏情况下做实时最优调整。三层嵌套体现了“先决策后揭晓再调整”的过程最终求出的 x 在面对集合 U 内任何可能的 u 时都能找到可行的 y并且总成本不超过目标值。这里需要特别注意第二阶段的 y 通常只包含连续变量比如机组实际出力、储能放电功率、弃光量。如果把第二阶段的调节动作也强行加上整数决策比如“实时要不要切负荷”强对偶条件就会被破坏CCG 子问题就不好处理。所以建模时尽量把设备状态放在第一阶段第二阶段只留下连续的功率修正量。2.4 目标函数和约束写成标准两阶段形式在实际代码里我习惯把目标函数拆成两部分第一阶段成本机组启停成本储能充电/放电状态相关固定成本可调负荷削减的补偿成本日前申报的联络线购电成本第二阶段成本各机组出力的边际成本储能放电退化成本弃可再生能源惩罚切负荷惩罚实时偏差购电/售电价格差第二阶段成本和不确定参数有关所以要去寻找最坏情况。约束也要拆成两层第一阶段约束只涉及 x比如机组最小启停时间、储能不能同时充放、联络线最大申报功率等。第二阶段约束必须对任意 u∈U 都成立写成“鲁棒约束”。例如实时功率平衡Σ_i p_i(t) p_dis(t) p_buy(t) - p_ch(t) - P_load(u,t) P_pv(u,t) Σ_j p_cut(t)这里 P_load 和 P_pv 都是不确定参数 u 的函数。这个约束必须对集合内的所有 u 都可行所以本质上是一个半无限约束。CCG 算法就是通过不断生成最坏场景来逼近这个无限约束集。一句话总结建模的要点第一阶段拍板状态类决策第二阶段修正连续量所有涉及不确定参数的约束都放到第二阶段或者作为鲁棒约束来处理这样既符合实际运行逻辑又方便后续用分解算法求解。3. 模型求解CCG 算法到底怎么迭代代码实现怎么落地3.1 为什么不能用简单的鲁棒对等转换有的教程会把单阶段鲁棒优化直接转换成一个大线性规划或二阶锥规划。但两阶段鲁棒问题不行因为第一阶段有整数变量第二阶段的可行域又依赖不确定参数和第一阶段的耦合直接对偶化会导致混合整数双线性项常规求解器没法直接处理。工程上最常采用的是两类分解算法Benders 分解和列与约束生成Column-and-Constraint GenerationCCG。CCG 的核心思想是先对一个不确定场景子集求解主问题然后找到这个子集之外最坏的不确定参数子问题再把最坏场景对应的第二阶段变量和约束加入主问题。每次迭代相当于“给模型增加一个约束和一组新变量”所以叫列与约束生成。实际应用中CCG 比传统 Benders 分解遇到整数决策时更灵活收敛也更快。我在同一套算例上对比过两种算法CCG 通常只需要 5~10 次迭代Benders 可能要走 30 轮以上。对于日前调度这种需要每天滚动的场景CCG 的收敛速度优势非常明显。3.2 主问题给定场景子集下的原模型主问题维护一个最坏场景的集合 K第 k 次迭代时我们手头已经有了 k 个不确定性参数取值 u_1, u_2, ..., u_k。主问题就是在这些场景下最小化总成本同时要求第二阶段的调整变量 y_k 在每个场景下都可行。主问题形式min a^T x θ s.t. A x ≤ b θ ≥ b^T y_k, ∀k B x C y_k ≤ d - E u_k, ∀k x ∈ X, y_k ∈ Yθ 是一个辅助变量它用来逼近“最坏场景下的第二阶段成本”。注意每轮迭代会加入一组新的变量 y_k 和对应的约束。主问题是 MILP在场景数不多时求解是比较快的。实际代码里我第一次迭代的主问题往往只包含几个预置的典型场景比如“光伏偏小、负荷偏大”“光伏偏大、负荷偏小”“光伏负荷都偏小”等。这能让上下界起始就很接近减少迭代次数。这个预置场景技巧很实用强烈建议保留。3.3 子问题寻找最坏场景子问题的任务是在给定第一阶段决策 x* 后寻找一个不确定参数 u 和相应的第二阶段调整 y使得第二阶段成本最大化。形式上是Q(x*) max_{u∈U} min_{y∈F(x*,u)} b^T y内层 min 关于 y 是线性规划。因为线性规划满足强对偶条件可以把内层对偶化把 min 问题转成 max 问题这样外层已经是 max两层 max 合并成一个 max 问题。对偶后的子问题大约长这样max_{u, λ} λ^T (d - E u - B x*) s.t. C^T λ ≤ b λ ≥ 0 u ∈ U这里 λ 是第二阶段约束的对偶乘子。由于 u 和 λ 以乘积形式出现在目标函数里这就是一个双线性规划。但不确定集合 U 是离散的盒式预算约束常用方法是把 u 替换成它的边界变量再引入大M变量转换成 MILP 求解。如果 U 是连续多面体且目标函数是线性也可以直接保留双线性项后用空间分支定界处理但工程上我更愿意用离散化 大M简单可靠。还有一种做法是固定 x* 后把子问题直接还原成原始问题形式用求解器对 u 做枚举式搜索。因为 u 的维度是 T时段数预算约束限制了二进制变量个数实际枚举空间没有想象中大。当 Γ 比较小的时候这种搜索比解一个大 MILP 还快。我在原型里先用了通用的 MILP 求解后来做了自定义枚举分支单次子问题求解时间从 30 秒降到 5 秒。如果整个项目对求解时间敏感这部分值得专门优化。3.4 迭代流程与收敛判据一个完整 CCG 迭代流程是这样的初始化选取一组初始最坏场景令下界 LB-∞上界 UB∞迭代次数 k1。求解主问题得到最优值 obj_MP 和第一阶段解 x*。因为主问题只考虑了部分场景它的最优值是原问题的下界所以更新 LB obj_MP。将 x* 代入子问题求解最坏场景 u* 和第二阶段成本 Q(x*)。如果 Q(x*) 是有限值那么 x* 在原问题中对应的总成本是 a^T x* Q(x*)它是原问题的上界候选。更新 UB min(UB, a^T x* Q(x*))。判断 UB - LB ≤ ε如果满足就停止返回 x*。否则将新找到的最坏场景 u* 对应的第二阶段变量 y* 和约束加入主问题kk1回到步骤 2。伪代码不用太复杂关键是记录每一轮的 LB 和 UB观察它们收敛的趋势。初始化场景集 S0 LB -∞, UB ∞, k 0 while (UB - LB) / max(1, UB) eps: 求解主问题 - obj_MP, x* LB max(LB, obj_MP) 求解子问题(x*) - Q*, u* UB min(UB, cx* Q*) 给主问题添加场景 u* 对应的约束和变量 k k 1我在调包过程中发现收敛阈值设成相对间隙 1% 左右就好再小只会增加迭代次数对实际调度收益没有明显改善。因为日前调度本身对很小的成本差异不敏感但过高的精度要求会导致储能状态在连续几天调度结果里来回翻转反而影响设备寿命。4. 算例设计与参数调优经验用数据说话而不是拍脑袋4.1 测试系统的数据准备为了验证鲁棒模型我搭了一个模拟园区虚拟电厂算例不涉及任何地理信息仅用于说明方法。系统包含光伏装机 50 MW用历史出力数据生成预测误差分布一台 10 MW 燃气机组作为快速调节电源储能 10 MW / 20 MWh充放电效率 95%SOC 最小 10%、最大 90%可调负荷 5 MW允许削减时段不超过 4 小时联络线容量 30 MW向主网购电价格为分时电价。负荷侧使用典型工业园区负荷曲线光伏预测通过“预测值误差”方式构造其中误差边界按历史数据的一定分位数确定。我建议误差边界不要用固定比例而是分时段取历史误差的 90% 分位数。比如午间光伏预测误差标准差很大就给它更大的边界夜间负荷行为平稳边界反而可以缩小。这样建的模型才有实际意义。数据处理上还需要对功率平衡方程做统一的单位换算所有约束里功率用 MW能量用 MWh。储能 SOC 递推时特别注意时间步长一小时一个时段就是最简单的情况。4.2 鲁棒调节参数 Γ 的选择成本与风险的权衡Γ 是这个模型里最重要的旋钮。它是“允许多少个时段出现极端偏差”的上限。在我这个算例里把 Γ 从 0 逐步调到 12观察总成本变化Γ预算日前调度成本万元/日成本增幅事后场景模拟违约率0确定型38.20%11.4%339.63.7%2.8%641.58.6%0.9%943.112.8%0.4%1244.817.3%0.1%可以看到Γ 从 0 增加到 6 时成本只增加 8.6%但违约率从 11.4% 降到了 0.9%。这是性价比最高的区间。再往后Γ 继续增加成本增速明显快于风险改善这就是超保守的体现。工程上怎么选 Γ一个实用的做法是用历史数据回测多个 Γ 取值画出“成本-鲁棒性”Pareto前沿再结合本地区偏差考核尺度选择拐点。不要直接套用论文里的默认值不同系统的最优区间差别很大。我最终选的 Γ6对应大约 0.9% 的违约率。对虚拟电厂来说这个违约率完全可接受而且成本增幅不大。如果所在地区偏差考核很严格可以适当再调大到 8~9但一般不建议超过 10。4.3 收敛性表现与迭代次数实际运行中CCG 在测试算例上通常收敛得很快。从 Γ6 开始初始场景预置后第一次迭代就找到了比较接近上界的方案整个迭代过程 6 轮收敛到 1% 相对间隙。最常见的影响收敛速度的因素有三个初始场景集合是不是覆盖典型极端情况。初始场景太温和前几次迭代来回震荡迭代次数会明显增加。子问题求解精度。子问题没有精确解到全局最优会给上界一个偏小的值收敛判据可能虚高导致提前退出或振荡。我建议子问题的求解容差设为主问题的 1/10。大M取值。第二阶段储能相关约束如果用了大M取值过大容易造成对偶乘子病态直接拖慢收敛。这个在前面的避坑部分细说。用同样的数据跑随机优化场景法取 500 个场景需要 3 万多秒而鲁棒CCG 从建模到解完只需要 10 分钟内。这个差距在需要每天滚动调度的系统里是决定性的。4.4 与确定型方案和蒙特卡洛回测的对比仅仅看模型自身迭代还不够我把最终鲁棒调度方案和原始的确定型方案放到 1000 组蒙特卡洛模拟场景中做了回测模拟数据由历史误差分布抽样生成。对比结果挺直观确定型方案平均运行成本 39.7 万元/日其中包含 1.2 万元/日的偏差考核费用有约 9% 的场景出现弃光或切负荷越限。鲁棒方案Γ6平均运行成本 40.3 万元/日其中偏差考核费用降到 0.3 万元/日几乎全部场景都不越限。名义上的日前成本鲁棒方案更高但算上实时调整后的偏差考核费鲁棒方案的平均总成本反而比确定型方案低了约 1.5%。这正是“名义成本”和“实际总成本”之间的差别。鲁棒优化的目标不是追求名义上的最低而是减少运行中的意外开销。我还特别检查了一下极端场景光伏出力连续 4 小时低于预测下限、晚间负荷比预测高 6%。确定型方案在这种场景下被迫切负荷切负荷惩罚高达近 10 万元鲁棒方案通过储能在午间多充电、燃气机组提前启动把切负荷量控制为零。这种极端场景虽然一年不一定碰上几次但只要碰上鲁棒方案少的钱远超平时多花的成本。5. 常见问题与排查技巧实录都是实践里踩出来的坑5.1 源荷双重不确定性不能简单“拆开算”刚开始做的时候我把光伏和负荷各自用独立的盒式不确定集然后叠加。模型解出来特别保守而且储能策略看起来很“怪”白天拼命多充电晚上又拼命少放电。后来查了一下历史数据发现园区光伏出力高峰和负荷高峰之间存在明显的负相关关系——光伏出力大的时候往往是午间阳光强、空调负荷还没起来的时候两者同时偏离极端值的概率很低。解决办法是把源荷差额作为联合不确定量构造一个联合预算约束。比如定义净负荷偏差ΔP_net(t) ΔP_load(t) - ΔP_pv(t)然后用联合集合限制Σ_t |ΔP_net(t)| / err_net(t) ≤ Γ_net这样做的效果立竿见影同样的违约率水平下联合不确定集的日前成本比两个独立集合的成本低约 5%因为模型不再为“不可能同时发生的坏情况”白白预留容量。5.2 第二阶段的整数变量是最大的雷我在给储能加“是否充电”状态变量时最开始把状态变量放在了第二阶段认为反正储能可以在实时调整时切换充放电状态。结果子问题内层 min 问题变成了一个 MILP强对偶条件不成立CCG 的上下界一直对不上迭代了 15 轮都不收敛。排查之后才意识到储能充放电状态必须在第一阶段确定下来第二阶段的实时调整只能在这组状态给定的前提下修正功率大小。如果模型要求更高的灵活性可以在第一阶段设置多个储能状态场景让它近似决策。总之保持第二阶段为纯 LP 是一个工程上稳妥的做法。5.3 大M取值太大会拖垮一切CCG 子问题线性化双线性项时经常要引入大M。我在一个版本里为了图省事把大M统一取成 1e6。结果求解器得出的对偶乘子小到失去参考价值主问题新增的约束也出现严重数值病态收敛速度慢得出奇。排查方法很直接检查主问题约束的系数矩阵条件数然后逐条调整M值。比如储能功率上限是 10M 取 50 就好联络线容量是 30M 取 120 足够。关键是 M 必须比该约束所有可能取值都大一点但又不能大到离谱。调整后CCG 的迭代次数从 15 次降到了 6 次。另外求解器的数值容差参数也值得留意。有些求解器默认的 Markowitz 容差在大模型里会引入额外数值问题可以适当调紧或调松具体要看报错和日志没有固定答案。5.4 不确定性边界要用历史数据滚动更新不能用固定值模型上线初期我直接把历史一年预测误差的 95% 分位数拿来做固定误差边界。运行到第二个月发现雨季和晴天的预测误差分布差很多固定边界既让模型在晴天过于保守又让其在雨天不够鲁棒。后来改成滚动更新的机制每周根据最近 30 天的预测误差数据重新计算各时段误差边界和预算参数。这样鲁棒模型能跟随季节变化成本一直保持在合理水平。具体更新频率可以是一个月一次但至少不要只在项目验收时更新一次。5.5 在速度上“作弊”初始场景和热启动CCG 的实际计算时间主要被主问题里的 MILP 占用。一个有效的优化方法是用前一天的调度结果作为当天主问题的热启动初值因为相邻两天的运行状态变化不大这样通常能省掉 30%~40% 的求解时间。再一个技巧是初始场景不仅仅包括“光伏低、负荷高”这些人工构造的极端组合还要把之前若干天实际运行中最不利的时刻加进去。这样第一天迭代时主问题就已经包含历史真实坏场景上下界往往能在第一次或第二次迭代就收敛到 1% 以内。6. 一点个人经验谈做这个项目最深的体会是鲁棒优化不是把误差范围调大就算完成。它真正的价值是提供了一个可以量化“保守程度”的旋钮让我们在成本和风险之间找到符合业务需求的平衡点。调参和建模的功夫一大半不在求解器而在对实际运行数据的理解上。建议接手类似项目时先花时间做预测误差分布的分析画出各时段的误差棒再决定不确定集合的结构。模型上线后也不要撒手不管要定期用实际运行数据回测验证边界参数是否还合理。虚拟电厂调度是一个连续演进的问题不确定性集合也应该是滚动的不是一次定死。希望这篇操作记录能帮你少走点弯路。