最近帮一位同行排查他手里的微电网优化调度代码,标题写的是“含可再生能源和储能的区域微电网最优运行(考虑鲁棒性和不确定性)——多阶段鲁棒调度模型”,代码框架看着挺全,但跑出来的结果一会儿不可行,一会儿解出来的成本曲线断崖式跳变。问题出在哪?其实出在“多阶段鲁棒”这个表述上——很多人把它当成一个黑盒,只想着把文献里的公式搬进MATLAB,却搞不清楚阶段之间到底在交换什么信息、不确定集要设多大、CCG迭代时上下界为什么会抖。
这篇博文我想解决的正是这件事:我会把“含可再生能源和储能的区域微电网最优运行”里的多阶段鲁棒调度模型,从数学骨架、MATLAB+YALMIP复现路径、CCG求解逻辑、到结果分析,完整拆开给你看。既适合刚接触鲁棒优化的研究生,也适合想把文献模型落地成可运行代码的工程师。标题里“完全复现”不是一句口号,而是要求你能自己动手把模型搭起来,并且清楚每一个约束、每一个参数为什么这么设。
1. 先想清楚一件事:多阶段鲁棒调度和两阶段鲁棒到底差在哪
1.1 阶段切分不是拍脑袋,是运行时间尺度的自然分层
微电网调度里最核心的时间层次就是“日前—日内—实时”。日前阶段在未知风光出力、负荷的情况下,先决定机组开停机、储能的充放电计划,以及从上级电网买电或者售电的意向电量;日内阶段获得更准确的风光预测后,重新调节各台可控机组的出力,修正储能的充放电速率;到了实时阶段,一般还要做一次反馈校正,用于消除预测误差引起的功率偏差。
在多阶段鲁棒模型里,这三个层级不是独立求解,而是一个决策-观测-再决策的嵌套结构。用行话说,第一阶段做“here-and-now”决策,后续阶段做“wait-and-see”决策。这样说可能还是有点抽象,我换个比喻:你可以把多阶段调度理解成开车去一个目的地,日前决策相当于出发前规划路线,确定大致几点到哪个路口;日内决策相当于在路上根据实时路况换道、调速;实时决策则相当于最后几公里的细节修正。如果把所有人都假设成永远按原计划走,那一旦路上堵车,整个计划就没法执行。微电网调度也一样,风光预测不可能百分之百准确,必须给后一阶段留调整空间。
很多人在复现时犯的第一个错误,是把“多阶段”简单理解成“多时段”,认为把24个时段的变量写进一个确定性优化里就是多阶段了。其实那只是大MILP,不确定性完全没有被建模进去,求解结果当然偏乐观。多阶段鲁棒模型要求的不只是变量带时间下标,而是每一阶段的可行域、目标函数和不确定性必须能够分离建模,让早期决策在后期不确定性实现后仍然可以找到稳妥的调整动作。
1.2 确定性模型和随机规划为什么在这个场景下不够用
先说确定性模型。它把光伏、风电、负荷都固定成预测值,目标函数是最小化总运行成本。这个模型跑得快,结果也漂亮,但实际运行中就出问题:某天下午光伏预测功率是800 kW,实际因为云层遮挡只有450 kW,如果日前计划里全部指望光伏给负荷供电,剩余功率缺口就得靠备用和联络线临时补,很可能补不上,导致切负荷。这不是参数调得不好,而是模型结构上就没考虑“预测会错”这件事。
随机规划的做法是对不确定性生成大量场景,例如用蒙特卡洛采样一千个风光场景,然后最小化所有场景下的平均成本。它的问题在于:第一,场景数量一多,模型规模指数级膨胀,24时段、三阶段、上千场景,求解时间很容易超过实际调度允许的时间窗口;第二,随机优化照顾的是“平均情况”,无法保证极端场景下系统不崩溃。比如一个50年不遇的连续阴天场景,如果场景库采样不到或者概率权重太低,解出来的计划在那种极端场景下依然可能失负荷。
鲁棒优化走的路线完全不同:我不需要知道不确定参数的概率分布,只需要知道它可能落在哪个区间内,然后找一个在所有可能实现里都能满足约束、且最坏情况下成本最小的决策。代价是解更“保守”——相当于为了防范那个极少发生的坏场景,多预留了一部分备用容量,多花点燃料费。多阶段鲁棒相比单阶段鲁棒的好处,是允许后期决策者根据已经观测到的信息做出适应性响应,从而把“过度保守”的程度降下来。这就是标题里“考虑鲁棒性和不确定性”的核心动机:既要能扛住最坏情况,又不想为最坏情况付出过高的成本。
1.3 最坏场景“藏”在哪,决定了模型怎么写
两阶段鲁棒模型的典型结构是最小化“当前成本+最坏场景下的未来成本”,数学上写成 min-max-min:第一阶段做决策,第二阶段在不确定集里找一个使运行成本最大的场景,然后在那个场景里做最小的调整。这是CCG(列与约束生成)算法能直接处理的结构。
但一到多阶段,问题就变得让人头疼:当有三个甚至更多决策层时,会出现多层嵌套的min-max-min,每一层都有决策变量,无法直接用标准CCG求解。所以工程复现里通常有两种务实做法。一种是“滚动两阶段鲁棒”:在日前层面求解两阶段模型,得到第一阶段计划;然后把第一个时段的结果固定下来,带入日内层面再求解一个带更精确预测的两阶段模型。这种做法的优点是逻辑简单、复用CCG代码,缺点是没有严格意义上一次性求解全局多阶段最优。
另一种是“仿射可调鲁棒优化”,把后续阶段的决策变量写成“基值+不确定性线性响应系数”的形式。例如实时出力表示为P_t = P_t^0 + Σ_k α_tk * Δξ_k,其中Δξ_k是第k类不确定参数的实现偏差,α_tk是待优化系数。这把内层的min优化变成对线性反馈策略的系数优化,模型变成三层线性的规划结构,可用任意对偶或者直接交给大MILP求解。我建议初学复现时先用滚动两阶段鲁棒,把结构跑通理解了,再改写仿射可调策略,这样排查问题会容易很多。
2. 模型骨架:目标函数、运行约束和不确定集怎么搭
2.1 目标函数里的每一项成本,都不是凭空来的
以我经常用的某园区微电网Demo为例,包含一台微型燃气轮机、一组锂电池储能、光伏阵列、风电机组和上级配电网联络线。调度周期取24小时,步长1小时。决策变量包括各时段燃机启停和出力、储能充放电功率、联络线购售电功率、负荷削减量。
目标函数需要覆盖这几个成本项:
第一,燃机的发电成本和启动成本。发电成本一般用二次函数 C = aP^2 + bP + c 表示,但直接二次函数在MILP里不好处理,通常做分段线性化。第二,储能的充放电损耗成本。虽然锂电池本身没有燃料成本,但循环次数会累计衰减,所以很多模型里会给一个很小的损耗代价,避免储能无意义地频繁充放。第三,从上级电网购电的费用和向电网售电的收益。第四,失负荷惩罚成本。第五,弃风弃光的惩罚成本。这个很重要,很多复现代码里不写弃风弃光约束,结果光伏出力超出负荷时只能强行消纳,模型自然找不到可行解。
我推荐的目标函数写法是:
min Σ_t [ C_g(P_g,t) + C_startup * u_start,t + C_buy * P_buy,t - C_sell * P_sell,t + C_curtail * (P_pv_forecast - P_pv_use) + C_penalty * P_cut,t ]
注意这里P_buy、P_sell是一对互斥变量,通常要加二进制标志位或者通过约束避免同时出现。我习惯直接加一个小整数变量,避免在KTT条件里引入讨厌的双线性项,这个问题后面讲CCG时还会碰到。
2.2 约束条件按“物理约束—耦合约束—边界约束”三层来理
最底层的物理约束是各设备自身的工作范围。燃机出力上下限、爬坡速率、最小开停机时间;储能的有功功率上下限、荷电状态SOC上下限、充放电效率以及SOC的时序递推关系;光伏和风电在任意时段的可用功率上限;联络线传输功率上限。
耦合约束主要是节点功率平衡方程。如果忽略网络拓扑,把园区当成单节点处理,那么约束就是电源总出力加上购电、减去售电、减去储能充电功率加上放电功率,要等于负荷减去失负荷量再加弃风弃光量。严格来说,如果节点电压和线路潮流也要建模,就得加DistFlow或交流潮流方程,但那样就不是“区域微电网最优运行”这篇复现文章的主线了,单节点模型已经足以讲清楚多阶段鲁棒调度的所有关键逻辑。
SOC递推是一个特别需要注意的约束。最简单的形式是 SOC_t = SOC_{t-1} + η_ch * P_ch,t - P_dis,t / η_dis。如果充放电效率都写成常数,这个约束是线性的。但如果你在文献里看到SOC递推里含二进制变量来表示“要么充电要么放电”,那就要小心了——这组二进制变量会让不确定子问题变成MILP,CCG对偶处理会非常麻烦。更稳健的做法是互补约束松弛,或者用第4节里我会讲的“先固定布尔变量、再对偶”的方法。
2.3 不确定集不是越大越好,预算约束才是控制保守度的关键旋钮
多阶段鲁棒模型里,光伏出力、风电出力、负荷预测误差都可以建模成盒式不确定集。以光伏为例,第t时段的不确定参数写作:
P_pv,t(u) = P_pv_for,t + ΔP_pv,t * u_pv,t
其中ΔP_pv,t是预测误差的上下界幅度,u_pv,t ∈ [-1, 1] 是归一化扰动。全部时段合在一起构成一个高维盒子。如果每个时段都允许u_t取到极端值,求解器永远会选择最坏的那个组合,结果会保守到离谱。
所以要引入预算约束,也就是所谓的Budget of Uncertainty。它的作用是限制“同时在最坏方向偏移的时段数”不超过一个上限。比如光伏24时段里,允许多达Γ个时段同时取到区间端点,其他时段扰动幅度线性递减。Γ设得越大,鲁棒解越保守,但成本越高;Γ=0时退化成确定性模型。实际操作中我会先跑Γ从0、2、4、6、8、12的一组实验,观察成本和失负荷量的临界点,再挑一个让失负荷概率可接受的最低成本值。这个敏感性分析基本是必做的,后面第5节我再展开说怎么看结果。
在YALMIP里的写法大致是这样:
% 已知光伏预测值 pv_for(24,1) 和扰动幅度 delta_pv(24,1) u_pv = sdpvar(24,1); % 归一化不确定性 U_pv = [-1 <= u_pv <= 1]; % 预算约束:各时段扰动绝对值之和不超过 gamma U_pv = [U_pv, sum(abs(u_pv)) <= gamma_pv]; % 实际光伏可用功率 pv_actual = pv_for + delta_pv .* u_pv;用abs在YALMIP里会增加二进制展开,导致模型复杂度上升。如果只是想快速测试,可以给正负扰动各设一个变量,把绝对值约束拆成两个线性约束。
2.4 多阶段之间的“状态变量衔接”才是最容易被忽略的细节
多阶段调度里,日前阶段决定的燃机开停机状态、储能日初SOC,必须作为固定参数传递给日内阶段;日内阶段在重新优化时,只能调整出力大小,不能推翻日前已经定下的启停计划。这个限制对线性模型很好加,就是给日内阶段的相应变量添加固定值约束。
但要注意储能SOC的衔接。日前阶段优化得到第12时段的SOC为0.65,日内阶段用更精确的风光预测重新优化时,SOC轨迹会偏离0.65。如果直接强制日内阶段第12时段SOC也等于0.65,就会把日内优化绑死;如果完全不约束,日内阶段可能在重优化时把SOC故意放空,导致后续时段无法支撑负荷。工程上的做法是给SOC保留一个小的松弛带,比如0.63到0.67,既能给日内重调度留余地,又不至于破坏边界安全。这种“软衔接”的办法,文献里一般不细讲,但实际复现时必须自己处理。
3. MATLAB+YALMIP完整复现路径:从参数定义到CCG求解循环
3.1 数据结构先设计好,别上来就写优化变量
在开始写YALMIP模型之前,我强烈建议先把数据表整理出来。我的习惯是分三个struct:data、param、result。data放预测曲线和成本系数,param放调度周期、预算值等控制参数,result放求解结果。
下面是我在模拟项目X里用的那份参数表,你可以直接参考:
| 参数 | 数值 | 说明 |
|---|---|---|
| 调度周期 | 24 h / 1 h | 单周期时段数 |
| 燃机出力上限 | 2000 kW | 微型燃气轮机 |
| 燃机爬坡上限 | 600 kW/h | 燃气轮机爬坡约束 |
| 储能容量 | 1500 kWh | 锂电池容量 |
| 储能充放电功率上限 | 400 kW | 双方向一致 |
| 储能效率 | 0.92 | 充放电统一折算 |
| 光伏预测峰值为 | 1200 kW | 中午时段出力峰值 |
| 风电预测峰值 | 800 kW | 夜间时段出力较高 |
| 联络线功率上限 | 1000 kW | 买卖电方向都是这个值 |
| 失负荷惩罚 | 5000 元/MWh | 高惩罚确保尽量不切负荷 |
| 弃风弃光惩罚 | 500 元/MWh | 优先保证消纳,但不算太高 |
这些数值不需要多精确,关键是让整个模型既有可行解又体现约束的紧张程度。如果储能容量设得太大,所有不确定性都能靠储能吸收,鲁棒优化和多阶段之间的差异就看不出来了。
3.2 两阶段CCG求解器的迭代逻辑:主问题切平面与子问题最坏场景
CCG的思路是把min-max-min拆成一个主问题MP和一个子问题SP,然后交替迭代。主问题是在一系列已知的“最坏场景”里共同优化第一阶段决策变量和虚拟变量;子问题固定第一阶段决策,在不确定集里寻找使系统成本最高的场景。子问题找到的新最坏场景会以“新变量+新约束”的形式加入主问题,因此叫“列与约束生成”。
迭代过程概括成伪代码:
初始化:选一个名义场景(预测值场景),加入主问题 for k = 1 : max_iter 求解主问题,得到第一阶段解 x^k 和下界 LB_k 固定 x^k,求解子问题,得到最坏场景 u^k 和上界 UB_k if (UB_k - LB_k) / LB_k < gap_tol break 将场景 u^k 对应的变量和约束加入主问题 end算法终止时,主问题的目标函数值始终不超过原问题的最优值,所以是下界;子问题求出来的最坏场景总成本由于给定第一阶段决策,是可行方案的成本,所以是上界。两者差值收敛到0时,说明找到了原问题的最优解。
在MATLAB里,YALMIP有个uncertain关键字可以直接声明不确定变量,会自动调用求解器内置的鲁棒优化能力。但如果是多阶段滚动鲁棒,我建议自己写CCG循环,不要依赖鲁棒求解器。原因是内置鲁棒模式不好控制CCG的上下界输出,不方便你看迭代行为,出了问题也难定位。
3.3 子问题的对偶转换是CCG里最容易写错的一段
两阶段子问题里面有一层min,也就是说固定第一阶段决策后,玩家“大自然”先选择一个最坏场景u,运行人员再在该场景下做最便宜的事后响应。这个内部min需要换成max才能得到SP的全局最优。
标准的做法是对内层做拉格朗日对偶,或者用强对偶定理把min目标替换成对偶变量的max目标。这里有一个关键细节:内层优化如果含有二进制变量(比如储能互斥标志、机组启停标志),就不能直接对偶,因为强对偶定理要求内层是连续的LP。如果内层的布尔变量不依赖场景,可以把它们固定为主问题已知值,再对剩下的连续LP对偶。如果二进制变量依赖场景,那问题会变成MILP套MILP,一般要么用大M法把互斥约束写成线性不等式,要么人工构造场景生成树来规避。
我在复现时更推荐一种变体做法:子问题直接保留内层min,把它和不确定集约束一起看成“以u为参数的分段线性函数”,然后用极值点枚举的方式采样预算约束的极端场景。具体做法是在不确定集顶点的有限组合里搜索最坏场景,等价于枚举u在预算约束下的多个端点。这样做会丢掉一部分连续变化的场景,但工程精度足够,而且避免了手工推导对偶方程组时出现的低级错误。
如果你坚持要对偶,我提两个可能踩到的坑。一个是等号约束的对偶变量是自由变量,在YALMIP里写的时候忘了定义成unbounded,导致等式功率平衡约束的对偶出不来。另一个是不等式约束多了或少了一项,对偶目标对不上,SP的UB值忽高忽低,CCG上下界不收敛。排错手段很简单,把SP单独拿出来,在几个固定的u场景下跟枚举法结果对一遍,先确认SP没问题,再去跑CCG主循环。
3.4 主问题代码骨架与求解器配置参考
主问题的YALMIP框架大概长这样:
% 主问题定义 x_mp = [sdpvar(24,1); binvar(24,1)]; % 连续变量 + 启停状态 cuts_scenarios = []; % 存储历史场景 % 每次迭代时,对历史场景k引入一组备用变量 z_k for k = 1 : num_iter if ~isempty(cuts_scenarios) zk = sdpvar(24,1); % 该场景下的调整成本代理 Constraints = [Constraints, ... % 场景k对应的变量与约束 PowerBalance_MP(:,k), ... zk >= cost_expression(:,k)]; Objective = Objective + zk; end end optimize(Constraints, Objective, ops);一个务实的建议是:主问题本质上是一个含二进制变量的MILP,求解器用支持混合整数规划的商用求解器(例如Cplex、Gurobi这类,习惯上我们直接用它们的名字指代)。在ops里要把MIPGap设小一点,比如0.01%;同时把TimeLimit设成单次求解不超过30秒。整个CCG循环限制最多10次迭代,绝大多数情况下5轮之内就能收敛。如果到第10轮还没收敛,大概率是子问题找出来的最坏场景不对,继续加迭代没有意义,应该回查SP。
4. 复现路上最容易翻车的几个点(附完整排查链路)
4.1 子问题对偶失败:第一步先确认内层是纯粹LP
一次典型的翻车经历是:某同学说他的CCG迭代到第3轮之后,UB不再下降反而上升,上下界拉开越来越大。我让他把SP单独抽出来跑,发现SP的目标值依赖一个二进制标志位,这个标志位会随输入场景改变,导致内层不是LP,强对偶不成立。他当时没意识到,问题的本质是储能充放电互斥约束引入了布尔变量。
排查链路是这样的:第一步,把SP内层所有变量导出来,检查是否存在binvar或整数变量;第二步,如果存在,看这些二进制变量是否随u变化,如果固定且不依赖场景,可以复制到主问题中;第三步,如果依赖场景,把互斥约束转化为连续约束,举例来说,储能P_ch和P_dis的互斥可以用 P_ch * P_dis <= 0 表示,但这个约束非线性,替代方案是加一个很小的充电损耗变量或者用SOS1约束;第四步,改完再跑SP,固定几个场景对比枚举法结果,看目标值和最优u是否一致。
这套排查链路看起来繁琐,但能避免你在错误模型上花掉一整个晚上。
4.2 大M取值问答题:M太小削可行域,M太大数值烂
多阶段鲁棒模型里大M法的出现频率很高:联络线购售电互斥、启停逻辑、惩罚上限截断都会用到。常见错误是拍脑袋取一个1000000,结果CPLEX/Gurobi在求解MILP时出现数值病态,LP松弛解一堆0.99999和0.00001,导致分支定界效率暴降。
更合理的做法是根据约束的实际量级计算M。比如燃机出力上限是2000 kW,启动逻辑约束可以把M取成2100或者2500,比上限大5%~20%即可。要是用来做“弃风量不超过光伏可用量”的截断,M取光伏最大预测功率的1.2倍就够了。M太大不仅慢,还可能在MILP求解过程中出现“看似满足约束实则违反几个单位”的数值假象,最终影响到CCG的UB。
如果调试时发现主问题LP松弛解有大量接近M的信号,我建议直接扫描一遍M参数,凡是超过对应物理量10倍的全部缩小。曾经有个案例,储能相关约束的M从100000降到3000之后,整个CCG迭代时间缩短了三分之二,结果精度还更高。
4.3 不可行问题的定位:从约束名到最坏场景的逐层追踪
多阶段鲁棒模型最常见的不可行来源是:不确定集太大,某个极端场景下无论怎么调整都满足不了功率平衡。如果你在CCG里遇到子问题求解返回infeasible,不要急着把不确定集调小,先做三件事。
第一,把当前第一阶段决策固定住,检查是不是储能的日末SOC约束在作怪。比如强迫一天结束SOC回到初值0.5,而早上为了让负荷不掉电已经把储能放到底了,晚上再怎么充都充不回0.5,那说明日前计划根本没有为晚间留充电时段。第二,把不确定集的预算值临时改成0,跑一次确定性版本,如果确定性版本可行而鲁棒版本不可行,说明问题大概率是坏场景下备用不够。第三,在SP的每个平衡约束或备用约束上加一个小松弛变量,松弛量乘上很大的惩罚系数,这样求解器即使不可行也会给你一个“接近可行”的解,告诉你哪条约束先被顶破。
用这个方法,我曾定位到某场景下是联络线功率上限顶不住:负荷高峰段光伏出力突然掉到预测值的一半,想从电网买电补缺口,但联络线已经满负荷,储能又已经放空,最后只能切负荷。定位之后,解决方案就是把储能日末SOC从0.5改成0.3,并调高燃机的爬坡上限,很简单。
4.4 收敛判据不是越严越好,迭代行为也要看趋势
CCG的收敛判断有两个门槛:一个是绝对gap,一个是相对gap,通常相对gap取0.5%就够用。但实际里有个陷阱:如果SP解出来的UB在大约0.1%的范围内上下抖动,而LB还在稳步上升,这时候gap会先减小再反弹,看起来好像不收敛。这可能是因为子问题MILP本身没解到全局最优,或者SP里存在数值误差引起的微小不可行。
我的习惯是记录每一轮的LB和UB,画一张迭代收敛曲线图。如果UB单调下降、LB单调上升,形成标准的“漏斗”收拢,那说明模型结构正确;如果UB来回震荡,先查SP的MIPGap是否设得太宽松;如果LB上升缓慢而UB一路走低,那多半是主问题的cut表达少了,某个约束没把坏场景正确传递到主问题上。调好之后,再跑一组不同的Γ值,确保收敛行为对参数变化不敏感,这样才能放心把代码交给别人用。
5. 拿到结果后怎么分析:成本对比、SOC轨迹和Γ敏感性
5.1 三种模型的成本对比:鲁棒解贵在哪,值不值
复现的真正目的是通过数字看清“鲁棒性买了什么”。我会在相同的算例参数下分别跑确定性模型、两阶段鲁棒模型和多阶段滚动鲁棒模型,输出总运行成本、预期失负荷量和弃风弃光量。
下面是一组典型结果,我在模拟项目X里跑出来的,不同算例数值会略有浮动,但趋势一致:
| 模型 | 总成本(万元) | 失负荷量(MWh) | 弃风弃光量(MWh) |
|---|---|---|---|
| 确定性模型 | 38.6 | 0.8 | 0.2 |
| 两阶段鲁棒(Γ=4) | 42.1 | 0.0 | 1.4 |
| 多阶段滚动鲁棒(Γ=4) | 40.3 | 0.0 | 0.6 |
这里有几个关键观察点。第一,确定性模型成本最低,但失负荷量不等于0,说明它在最坏场景下是真的会掉链子。第二,两阶段鲁棒把失负荷量压到0,代价是成本多了大约9%,弃风弃光也变多了,因为日前阶段为了应对最坏情况预留了大量备用,光伏和风电在某些时段被压低了出力。第三,多阶段滚动鲁棒在同样保证不失负荷的前提下,成本只比确定性模型多约4%,弃风弃光也远低于两阶段鲁棒。这个差异就是多阶段决策的价值——它允许日内根据实际情况精细调整,而不是让日前计划背一口大黑锅。
5.2 Γ到底取多少:成本与风险的平衡点怎么找
预算值Γ决定了最坏场景能“坏”到什么程度。如果Γ设为0,模型完全不考虑不确定性,成本最低,但风险最高;Γ设为24,意味着每一个时段都可以同时在最坏方向上偏移,系统为这个极端设定保留的备用会大得离谱,成本陡增。
实际操作中我会跑一组Γ等距取值,画成本和失负荷风险的双Y轴折线图。结论通常是三段式:Γ从0增到4,成本上升不多,但失负荷风险快速下降;Γ从4增到8,成本上升明显,但失负荷风险已经很低,边际改善极有限;Γ超过8以后,成本一路走高,风险却几乎不动,纯白花钱。对于我的这个园区案例,Γ取4到5之间是甜点区。你可以把自己的预测误差统计特性代入算一下,如果历史数据显示同时出现大规模预测偏差的概率本来就很低,那Γ就不要设得太大。
5.3 储能SOC轨迹里藏着的模型行为密码
最后一定要看储能SOC的时间曲线。我通常会把确定性模型和多阶段鲁棒模型的SOC画在同一张图上,对比之后会发现两个典型差异。
确定性模型的SOC曲线往往更“激进”:它敢让SOC在午间光伏大发时一口气充到接近上限,然后夜间持续放到接近下限,因为模型知道光伏预测不会错,可以精准地用空每一度电。多阶段鲁棒模型的SOC曲线则会更“克制”,充电速率更平滑,夜间保留的安全余额更高,因为模型不知道光伏实际出力会偏离多少,必须给后续时段留缓冲。
还有一个值得注意的现象:多阶段滚动鲁棒模型在两个相邻调度周期交界处的SOC会有轻微的“回摆”,这就是我在2.4节说的软衔接松弛带在起作用。如果你看到SOC在某小时出现尖刺或者突变,说明衔接约束或者爬坡约束写得太硬,需要调整松弛范围。SOC曲线正常了,整个模型基本也就过关了。
最后再分享一个我自己的习惯,复现任何鲁棒调度模型,都先把“确定性模型跑通、两阶段跑通、多阶段再跑通”这三步走完,每一步都留好对比结果。按这个顺序做下来,你对“完全复现”的理解会比单纯拿到一套代码深刻得多,以后无论是换算例规模、加网络拓扑,还是换求解器,都能举一反三。