☰
两阶段鲁棒优化与关键场景辨别算法:微网调度实战解析
2026/10/20 23:28:16 网站建设 项目流程

去年帮某工业园区微网调度项目做算法改造时,遇到过挺典型的一个场景:清晨调度系统基于预测数据给出当天的机组出力和储能充放电计划,结果上午一场异常的云层让光伏出力突然掉了将近四成。原本规划从光伏取电的时段,实际只能用价格更高的市电来补。这种“计划赶不上变化”的情况,在微网调度里几乎天天都在发生。后来我给这套系统换成了两阶段鲁棒优化模型,并在求解过程中嵌入了关键场景辨别算法,情况才有明显改观。

这篇文章就把这套方法里里外外拆开讲清楚。我默认读者有一定优化调度的基础,但即使你只是刚接触Matlab和线性规划,按着里面的建模思路和代码框架走一遍,也能在中小规模微网上搭出一套能跑通的两阶段鲁棒调度程序。文章会重点讲四件事:为什么确定性优化在微网场景下不够用、两阶段鲁棒模型的结构是什么、关键场景辨别算法到底在辨什么,以及配套Matlab代码的实现细节和实际调试中会踩的坑。

1. 为什么微网调度需要“考虑最坏情况”的鲁棒思想,而不是指望预测更准

1.1 确定性调度在不确定性面前的脆弱

传统的微网日前调度,流程一般是先拿到光伏、负荷的预测曲线,然后把它们当作确定参数,写成一个混合整数线性规划(MILP),求出各机组启停、储能充放电、向大电网购售电的计划。这个方法本身没有问题,问题出在“把预测当作真实”这个前提上。

光伏出力预测再好,也躲不开云层遮挡、温度变化这些随机因素。负荷侧也一样,一个园区午休时间突然多开几台大功率设备,负荷曲线立刻和预测值错开一截。实际项目中,光伏日前预测误差经常到二到三成,遇到突变天气甚至更高。把这些误差带进确定性模型,结果往往是:某几个时段实际功率不平衡、储能被充到上限无法再吸收多余光伏、需要临时高价购电,甚至被迫切除部分负荷。调度计划看起来最优,实际运行时却处处被动。

所以我不太建议在微网调度项目里把全部精力押在“把预测精度再提高几个百分点”上。预测精度当然要追,但更根本的问题在于,调度方案必须对“预测出错”这件事本身有准备。

1.2 从追求预测准确转向容忍不确定性

鲁棒优化的思路正好相反:我不需要知道未来具体是哪条曲线,我只要求自己做的决策,在未来任何一种“可能的曲线”下都能满足约束、不至于亏得离谱。你可以想象成出门前看天气预报有30%概率下雨。确定性思维是“大概率不下雨,我不带伞”;鲁棒思维则是“万一真下了呢?我宁可多背一把伞”。

把这个想法落到微网调度里,就是给光伏出力、负荷这些不确定参数划定一个变化范围,然后在最坏组合下优化调度方案。代价是运行成本通常比确定性方案高一些,换来的是方案在任何可能场景下都“兜得住”。对必须保障供电可靠性、或者并网协议对功率交换有严格限制的微网来说,这个代价是值得的。

1.3 不确定集设计:鲁棒程度的调节旋钮

不确定集是不确定参数的取值范围,它直接决定了鲁棒方案的保守程度。用得最多的是箱式不确定集,即每个时段的功率围绕预测值在一定偏差内浮动。箱式集最大的缺点是容易过度保守——它隐含了“所有时段同时取到最坏值”的假设,而现实中这种情况几乎不会发生。

工程中更常见的做法是在箱式集基础上加入预算约束,限制整个调度周期内最多允许多少个时段同时偏离预测值。这类预算参数通常记作Γ。这个参数就是保守程度的调节旋钮:Γ=0时退化成确定性优化,Γ取满时段数时就变成完全最坏情况。实际调试中一般取时段总数的10%~30%,既保留鲁棒性,又不至于成本涨得无法接受。

2. 两阶段鲁棒框架下的调度模型拆解:第一阶段拍板与第二阶段调整

2.1 第一阶段决策:在不确定性暴露之前就要定的事

两阶段一词,来源于实际决策的时间顺序。第一阶段发生在不确定性实现之前,也就是“还没看到真实光伏出力那一天”就要定下来的事,常见的有机组启停状态、与大电网签订的日前购电协议、储能的日前充放电基线等。这些决策的共同特点是:一旦定下来,当天很难修改。机组启动需要时间,购电合同不能随意撕毁,储能状态则受物理动态制约。

从数学形式上看,第一阶段决策变量里通常含有大量0-1整数变量,这决定了主问题是一个MILP。也是因为它含整数变量,求解复杂度明显高于纯线性问题,后面CCG算法设计时都要围绕这一点来考虑。

2.2 第二阶段决策:看到真实场景后的补救动作

到了日内,真实的光伏、负荷数据逐渐揭晓,运行人员就可以根据实际情况做调整。这些调整包括柴油/燃气机组的实时出力、储能充放电功率的修正、可控负荷的削减等。第二阶段的核心含义是“亡羊补牢”,把不确定性带来的额外成本压到最低。

值得注意的是,第二阶段决策变量通常以连续变量为主。如果机组启停也放到第二阶段去自由变化,那就相当于日内可以随便启停机组,物理上不现实,计算负担也会陡增。常规的做法是第一阶段固定启停,第二阶段只做连续功率调整。这点在建模和后面写代码时都要保持一致,否则上下界对不上,算法会振荡。

2.3 min-max-min结构到底在说什么

两阶段鲁棒优化的目标函数,惯用写法是 min-max-min 三重结构。外层min对应第一阶段决策,内层max对应不确定性取到的最坏情况,最内层min则是在这个最坏情况下第二阶段调整成本的最小值。整体翻译成人话就是:我先定一个方案,然后大自然故意给我出最难的题,我还得保证在难题下能有办法用最小成本应对。

用考试类比可能更直观。第一阶段是“我决定今天复习什么”,第二阶段是“卷子发下来后发现题目特别难,我还能不能及格”。鲁棒优化要求的不只是平均分,而是最低分也不低于某个门槛。

2.4 需要满足的核心约束

无论哪个阶段,微网调度模型的物理约束基本是固定的:节点功率平衡约束、机组出力上下限与爬坡约束、储能SOC递推公式与容量约束、与上级电网的联络线功率限制,以及考虑配网潮流时的支路容量约束。代码实现里,功率平衡是最容易出bug的地方,正负号约定稍微不一致,结果就完全跑飞。

3. 关键场景辨别算法:从海量不确定场景里抓出真正危险的少数派

3.1 枚举所有场景在计算上不现实

如果只有一两个不确定源,把不确定集里的极端组合全部列出来,再用确定性优化求解,倒也不是不行。但微网调度通常同时有光伏、风电、负荷三类不确定源,每个不确定源还有96个时段。哪怕每个时段只取“高/中/低”三档,组合数也是天文数字,根本没法逐个求解。更麻烦的是,很多表面上看很极端的场景,比如“所有光伏出力最低、所有负荷最高”,在真实系统里未必真正构成约束——因为可能刚好碰上储能容量充足、机组余量很大。

所以问题的关键在于:能不能只挑出那些真正会把调度方案逼到墙角的关键场景,然后用它们来构建鲁棒模型?答案是可以,这就是关键场景辨别算法的用途。

3.2 关键场景的判断标准

给定一个调度方案,我们可以把任一不确定场景代进去,求解一个以第二阶段成本最小化为目标、检查是否越限的子问题。如果某个场景下,系统需要支付极高的调整成本,或者出现切负荷、功率越限,那这个场景就构成了当前方案的“压力测试”——它才是真正关键的场景。

关键场景不一定是某个单一极端值组合。它往往由系统当前状态决定:某个时段光伏骤降、负荷正处于晚高峰,而储能恰恰在前一时段放空了,这时候约束才是最紧的。这也是为什么我总提醒团队,别拍脑袋挑场景,要让数据说话,靠辨别算法去筛。

3.3 工程化的辨别流程:场景池加排序

我在实际项目里用的关键场景辨别算法,核心步骤可以概括成四步:

  1. 生成候选场景池。用蒙特卡洛或拉丁超立方采样,从不确定集里生成成百上千个场景;如果手头有历史数据,也可以直接抽取典型日场景混进去。
  2. 对当前调度方案做快速评估。把每个候选场景代入一个简化的第二阶段模型,计算它的调整成本和越限情况。
  3. 按得分排序。核心指标是场景总成本,必要时叠加越限惩罚项;排序靠前的K个场景被标记为关键场景。
  4. 迭代更新。关键场景集合交给主问题重新优化调度方案,得到新方案后再回到第2步。

这个流程的优点非常明显:不需要推导复杂的对偶子问题,每个候选场景的评估都是独立的LP或MILP,天然适合并行计算。工程上几行Matlab就能实现。代价是精度受采样密度影响,如果场景池没有覆盖到真正的坏场景,可能会漏掉关键信息。所以迭代轮数不能省,场景池规模也不能太小。

3.4 场景辨别和经典CCG子问题的区别

严格的两阶段鲁棒模型,子问题通常通过强对偶转成单层max问题,然后精确求解最恶劣场景。经典CCG就是走这条路线。关键场景辨别算法则可以看作CCG子问题的一种工程化近似实现,二者并不矛盾。表格对比如下:

对比项经典CCG子问题关键场景辨别算法
最恶劣场景来源对偶推导后精确求解候选场景池中筛选排序
对偶/KKT推导必须不需要
每次迭代计算量求解一个max-min问题并行评估N个LP/MILP
最恶劣场景精度线性条件下精确受采样密度影响
工程实现难度较高较低
适用场景有理论洁癖、规模不大微网规模、工程快速落地

实际代码里建议先跑场景辨别算法,用它的结果作为CCG的初始场景集合,之后再偶尔用严格子问题校验一次,确认没有遗漏更恶劣的场景。这样既稳又省事。

4. CCG求解流程与关键场景迭代的联动逻辑

4.1 主问题的形式:把所有关键场景拉进来一起约束

CCG的主问题承担两件事:决定第一阶段变量,同时保证对所有已识别出的关键场景,系统都能找到一个可行的第二阶段调整方案。数学上,就是让每个关键场景共享同一组第一阶段决策,但各自拥有独立的第二阶段决策变量。

为了表达“最坏场景下的总成本”,通常引入一个辅助变量η,约束η不小于每个关键场景的总成本,目标函数里只保留第一阶段成本加η。这样主问题仍然是一个单目标MILP,可以直接扔给求解器。

4.2 子问题的角色:产出一个新关键场景

子问题的输入是当前主问题解得的第一阶段方案,输出是让第二阶段成本最大的那个不确定场景,以及对应的最大成本。在严格CCG中,子问题通过强对偶转成单层问题求解;在关键场景辨别实现中,子问题角色由场景池评估模块扮演,它把排序第一的场景返回给主循环。

这里有个容易绕晕的点:子问题算出来的最恶劣场景,往往和上一轮迭代找到的场景不一样。每次主问题改变第一阶段决策,系统的“最怕”场景也随之改变,所以必须反复迭代,直到两者稳定。

4.3 CCG主循环的通用步骤

整个两阶段鲁棒调度的外层主循环,我用伪代码概括一下:

  1. 初始化:给关键场景集合放一个“预测场景”和若干极端场景;令上界UB=inf,下界LB=-inf。
  2. 求解主问题,得到当前第一阶段方案和目标值LB。
  3. 固定这个方案,运行关键场景辨别算法,找出最恶劣场景及其成本,加上第一阶段成本后更新UB。
  4. 若(UB-LB)/LB小于设定阈值,停止迭代。
  5. 把新发现的关键场景加入集合,回到第2步。

工程经验是:CCG前几轮收敛非常快,LB、UB差距能在三四轮里从百分之十几压到百分之一二。但越往后越慢,甚至出现小幅振荡。振荡不一定说明算法坏了,更多时候是因为场景池采样没覆盖到让约束变紧的那个区域,或者两个阶段模型假设不一致。

4.4 加速收敛的几个实际技巧

第一,初始场景集合别只放预测场景。我会额外放两个极端场景:光伏最低且负荷最高、光伏最高且负荷最低。这样主问题从一开始就考虑极端边界,迭代轮数明显减少。第二,主问题不要每次只加一个新场景,可以把历史出现过的关键场景全部保留在集合中。虽然会增大MILP规模,但能防止收敛过程中的反复摇摆。第三,如果第二阶段完全是连续变量,评估场景时直接用LP即可,几十毫秒就能算完一个,几百个场景并行也就是一两秒的事。

5. Matlab代码实现细节:从数据结构到核心求解模块

5.1 整体程序架构

一套可复用的两阶段鲁棒微网调度Matlab代码,我习惯按模块拆分:数据输入、场景生成、主问题构建、场景评估、CCG主循环、结果输出。这样每个模块可以单独测试,也方便替换不同的求解器。

数据部分建议放在一个结构体或脚本里,包含系统参数(机组、储能参数)、预测曲线、不确定偏差、预算参数Γ、场景池规模N、关键场景数K、收敛阈值等。把所有参数集中管理,后面调参会轻松很多。

5.2 不确定集与场景生成模块

场景生成的第一步是构造不确定参数。以光伏为例,假设已知预测序列pv_base和各时段最大偏差err,箱式不确定集就是让pv在[pv_base-err, pv_base+err]内浮动。样品生成用拉丁超立方比纯蒙特卡洛更均匀,Matlab自带的lhsdesign就能用。

下面是一段简化的场景生成代码:

% 参数:n_horizon=96, N=2000, pv_base为96x1预测序列, err为96x1偏差 u_vals = lhsdesign(N, n_horizon); % 生成[0,1]均匀拉丁超立方样本 pv_scenarios = zeros(n_horizon, N); for k = 1:N % 将样本映射到[-1,1]并与偏差相乘,得到场景偏移 offset = (2*u_vals(k,:)' - 1) .* err; pv_scenarios(:,k) = pv_base + offset; end

注意,这只是最基础的采样。如果希望场景更贴近真实,可以直接用历史预测误差的经验分布来采样,效果会更好。

5.3 关键场景辨别模块的实现

关键场景辨别模块是所有工程化代码的核心。它的输入是当前第一阶段方案x_current、候选场景池、系统参数;输出是关键场景序号集合。

核心逻辑是先对每个场景做快速评估。第二阶段评估函数里,要调用一个局部的优化求解:固定x_current,把场景s的光伏、负荷代入,求解第二阶段最小成本。这步我建议用linprog或quadprog完成,因为第二阶段变量是连续的,速度很快。

function critical_idx = identify_critical(x_current, scenario_pool, sys, K) num_scen = size(scenario_pool.pv, 2); scores = zeros(num_scen, 1); parfor s = 1:num_scen % 每个场景独立评估,并行加速 [cost_s, viol_s] = eval_scenario(x_current, scenario_pool(:, s), sys); % 越限加惩罚,确保不可行场景也排到前面 scores(s) = cost_s + sys.penalty * viol_s; end [~, sorted_idx] = sort(scores, 'descend'); critical_idx = sorted_idx(1:min(K, num_scen)); end

这段代码里我对不可行场景做了惩罚加权处理。实际中可能会出现某些场景无论如何调整都无法满足约束,那就需要在前两轮迭代中优先把它们排进关键场景集合,逼着主问题去修正第一阶段方案。这是保证鲁棒性的关键机制。

5.4 主问题构建与求解

主问题是一个MILP,推荐用YALMIP建模,后面接Gurobi或CPLEX求解,比Matlab自带的intlinprog快很多。建模的思路是:

  • 定义第一阶段决策变量x,比如机组启停、购电合同。
  • 对每个关键场景s,定义第二阶段决策变量y_s,比如机组出力、储能充放电。
  • 加入各场景对应的功率平衡和物理约束。
  • 引入η,加约束η≥每个场景的总成本。
  • 目标为第一阶段成本+η。

YALMIP代码框架大致如下:

% 假设关键场景集合存放在cell数组critical_set中 ops = sdpsettings('solver', 'gurobi', 'verbose', 0); x = binvar(n_x, 1); y = cell(length(critical_set), 1); constr = []; for s = 1:length(critical_set) y{s} = sdpvar(n_y, 1); constr = [constr, scenario_constraints(y{s}, x, critical_set{s}, sys)]; constr = [constr, scenario_cost(y{s}, critical_set{s}, sys) <= eta]; end optimize(constr, first_stage_cost(x) + eta, ops);

需要特别强调一点:所有关键场景共享同一个第一阶段决策x,但y_s是独立的。如果你在循环里不小心把y也定义成全局共享变量,那所有场景的调整动作就被绑死了,问题会退化成求一个“平均场景”,鲁棒性完全丧失。

5.5 参数设置与调试建议

一套能跑的初始参数可以从这个配置起步,再根据算例调整:

参数初始取值说明
调度时段数9615分钟一个点
候选场景池规模N1000~5000越大覆盖越好,但评估耗时增加
关键场景数K5~10K越大主问题越重
预算参数Γ8~16时段总数的10%~20%
收敛阈值0.5%~1%工程上足够
最大迭代轮数15~20防止死循环

6. 算例验证与落地过程中的坑:收敛性、保守度与常见问题

6.1 一个典型算例的收敛表现

为了讲清楚实际效果,我描述一个测试算例:光伏装机200kW,负荷峰值250kW,储能容量100kWh、最大充放电功率50kW,一台50kW柴油机,接入上级配网的联络线限功率80kW。预测曲线按夏天典型日设计,光伏偏差设为±20%,负荷偏差±8%。

确定性优化在这个系统上得到的方案,最优成本假设是日均4200元。但把预测场景换成若干个真实可能出现的坏场景,直接测这个确定性方案的场景成本,经常出现5000元以上的情况,个别场景甚至需要切负荷。换成带关键场景辨别的两阶段鲁棒调度后,方案成本上升到约4600元,但所有候选场景下的成本都被压在4800元以内,越限案件为零。多花不到10%的钱,换来“最坏也有底”的保障——这正是鲁棒调度的价值。

收敛方面,这个算例从预测场景出发,CCG迭代了6轮,UB-LB gap从初始的11.4%降到0.6%。其中第二轮贡献最大,直接把gap压到2%附近,后面几轮都是小幅修正。

6.2 不肯收敛的排查思路

两阶段鲁棒代码最难调的就是“上下界gap振荡”和“一直收敛不下去”。遇到这种情况,我一般按顺序检查三件事。

第一,两个阶段模型是否一致。最常见的问题是第一阶段目标里算了某部分成本,子问题评估时又用不同的价格系数,导致UB和LB各说各话。第二,场景池是否太小或采样不均匀。如果候选场景全是围绕均值附近,极端场景根本没出现过,那主问题再怎么迭代也碰不到最恶劣场景,UB会虚低。第三,检查不确定集偏差是否设得过大。偏差太大意味着系统要同时应对极其极端的情况,很可能无解,求解器会返回一个很差的UB,导致gap收不下去。

6.3 保守度控制的关键经验

设计不确定集时,偏差参数最好来自历史预测误差的分位数,而不是拍脑袋定一个百分比。比如取过去30天光伏预测误差的90分位数作为最大偏差,这样既覆盖了大多数现实情况,又不会太离谱。

预算参数Γ的调节也很有讲究。Γ太小,鲁棒性和确定性方案几乎没有差别;Γ太大,成本膨胀得厉害。我常用一个两步法:先跑几个Γ值(比如4、8、12、16),画出“最坏场景成本-调度成本”的帕累托曲线,再让运行人员结合安全要求挑一个折中值。这一步沟通做好,方案落地时阻力会小很多。

6.4 求解规模失控的处理

关键场景数K一多,主问题里的场景约束会成倍增加,求解时间直线上升。这时可以先去掉那些“辨识度低”的场景,也就是只比常规场景贵一点点、约束也没绷紧的场景。另外,如果同一批关键场景反复出现在好几十个候选场景中,说明它们是同一类,可以只保留一个代表。场景去重这一步处理得好,MILP规模往往能缩减一半以上。

我自己的体会是,这套方法真正难的地方其实不在算法推导,而在数据准备和参数标定。开始动手写代码之前,我建议先把你手头微网的光伏、负荷历史数据和预测误差统计整理一遍,确定合理的不确定集参数,否则后面所有实验结论都是空中楼阁。最后再分享一个小技巧:初始关键场景集合里放一个预测场景加两个边界极端场景,能显著降低迭代轮数,你可以直接抄作业。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询