ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

两阶段鲁棒微网调度+关键场景辨别算法的Matlab实现全解析

2026/10/6 9:57:11 拓冰建站 浏览量
两阶段鲁棒微网调度+关键场景辨别算法的Matlab实现全解析 做微网调度的人大多有过这种经历预测数据出来时觉得方案很完美实际运行起来却被现实狠狠教育。光伏出力突然塌到预测值的一半负荷尖峰恰好出现在储能快要放空的时段——精心设计的确定性调度方案到了现场可能一天都撑不住。这也是我花了几周时间完整复现“两阶段鲁棒微网优化调度关键场景辨别算法”这套Matlab代码的原因。它解决的问题非常明确在光伏、风电、负荷都存在不确定性的前提下得到一个无论如何都不会让微网越限、成本又可接受的调度方案。这篇文章会把模型结构、算法原理、代码实现以及我调试中踩过的坑全部拆开讲适合正在研究微网优化、鲁棒优化、或者正在复现相关论文的电力方向研究生和工程师参考。1. 微网调度为什么绕不开不确定性从确定性模型的失灵说起1.1 确定性调度模型的基本框架标准微网日前调度模型长这样决策变量包含燃气轮机等可控机组的启停状态和出力、储能充放电功率、与大电网的交换功率以及各类负荷的切负荷量。约束条件主要是功率平衡、机组出力上下限、爬坡约束、储能SOC范围、交换功率限制。目标函数是最小化总运行成本包括燃料费、启停费、设备折旧费、购电费以及弃光弃风或切负荷带来的惩罚成本。这套框架本身没有太多新意绝大多数微网调度研究都是在它上面做扩展。问题出在一个被很多人默认接受的前提上模型里的光伏出力、风电出力和负荷都是预测得到的点值。光伏功率预测误差动不动就是20%到30%负荷在极端天气下的突变同样不可小视。拿一个点值去做优化本质上就是在假设“预测一定准”。对大电网来说单点误差可以被系统级备用抹平但微网是小系统装机容量小、调节手段有限误差对结果的影响会被显著放大。1.2 预测误差叠加后会发生什么我在测试里见过不止一次这种场景预测光伏出力180kW实际只有100kW预测负荷300kW实际因为天气原因飙到370kW。如果调度方案是在确定性模型下求出来的它给出的储能放电计划很可能刚好不够用最后只能切负荷或者高价购电。更麻烦的是有些动作在确定性模型里因为容量限制根本做不了比如储能已经放到SOC下限模型却还在计划继续放电。这种“组合最坏情况”看起来概率不高但对微网这种小系统来说一旦发生就是实打实的越限事故。确定性模型给出的解就像一个在刀锋上跳舞的方案预测偏差稍微大一点不仅经济性立刻崩溃可行性也会崩。所谓可行性崩掉就是模型里有约束实际上已经无法满足只是因为在求解时没有把这些场景考虑进去所以你根本看不到。1.3 三类不确定性处理路线的取舍面对不确定性主流做法有三类。随机规划假设不确定量服从已知分布用大量随机场景去求期望成本最小的方案经济性通常最好但分布假设一旦不准结果就会偏差。鲁棒优化不给分布做假设只给定不确定量的变化范围即不确定集并保证在这个集合内的所有实现下方案都可行抗风险能力强代价是相对保守。分布鲁棒优化介于两者之间假设真实分布落在一个模糊集内取最坏分布下的期望近些年在理论上很火但建模和求解复杂度确实高。标题里说的两阶段鲁棒优化走的是第二条路线。但它不是把鲁棒思想硬塞进单阶段模型而是把决策拆成前后两个阶段既保留鲁棒性又给运行调整留了空间。2. 两阶段鲁棒模型先定计划、再见招拆招2.1 “两阶段”到底在拆什么两阶段鲁棒优化的核心是把调度问题拆成“现在就做”和“见到再做”两部分。第一阶段在这里称为here-and-now必须在不确定量实现之前敲定典型的是机组的启停这类慢速决策定了之后短时间内不能反悔。第二阶段叫wait-and-see是在不确定量可以被实际观测到之后做的快速调整包括机组出力微调、储能充放电、与大电网交换功率等。为什么要这么拆因为真实运行就是这样电网调度员不可能等恶劣天气来了才临时启动一台机组但出力指令和储能功率却可以在15分钟或者1小时级别的间隔内反复调整。如果所有决策都放进单阶段鲁棒模型模型就必须要求所有变量对任意不确定量同时可行结果会保守得很离谱。两阶段结构其实是准确描述“计划慢、调整快”这种运行逻辑的自然选择。2.2 模型的min-max-min三层结构带不确定量的两阶段鲁棒模型标准形式是min cx max min dy x∈X ξ∈Ξ y∈Ω(x,ξ)外层min是第一阶段的启停成本加第二阶段的最坏运行成本内层max是不确定性变量在不确定集里寻找最恶劣的实现最内层min则是在给定最坏场景下做经济运行调整。这是一个嵌套的三层优化问题没法直接交给求解器。标准解法无非两类Benders分解割平面类以及CCGColumn-and-Constraint Generation列和约束生成。CCG因为收敛快、对混合整数线性规划支持友好在近年文献中更常见。而关键场景辨别算法可以理解成在CCG框架下对子问题做的一种加速替代方案。2.3 不确定集怎么搭不确定集是鲁棒优化的灵魂。常用三种盒式集直接取上下界四个角全满足最稳健但也最容易过度保守椭球式集引入参数间的相关性比盒式温和但模型会变成二阶锥或二次规划求解困难一些多面体式集用线性不等式来定义不确定范围可以纳入部分相关性同时保持线性结构工程上最常用。我在模型里选的是多面体盒式光伏、风电、负荷各自有预测区间再额外加一条总偏差约束体现“这些不确定量不会同时到达极端”的物理事实。这样既不会像纯盒式那样死守所有极端角落计算量也完全可控。需要提醒的是不确定集的尺寸直接决定解有多保守别凭感觉拍脑袋定最好统计历史预测误差的分位数来校准。3. 关键场景辨别算法的机制不遍历无穷场景只揪住危险的那几个3.1 传统方法的痛点如果严格按照上一节的定义去求解内层的max理论上是遍历不确定集里每个点这是连续集上的无穷问题不可能直接做。CCG的传统路子是主问题和子问题来回迭代求解主问题得到第一阶段决策把它代入子问题对给定决策寻找最坏场景然后把这个最坏场景生成的新约束加回主问题继续迭代。这个方法非常可靠但有个让人头疼的地方每一轮子问题都要做一个max-min优化通常要借助强对偶转化或KKT条件涉及到双线性项或混合整数项求解负担很重。尤其不确定集维度升高、系统规模变大以后子问题占掉整个求解时间的绝大多数。关键场景辨别算法换个思路不在每一轮迭代里精确寻找最坏场景而是在迭代中通过场景分析提前识别出一小组“关键场景”把连续的不确定集近似成有限离散点集合。这样一来原来的max-min问题退化成有限场景集合上的离散优化难度直接下降。3.2 关键场景是怎么被“揪”出来的完整流程分几步走。第一步用拉丁超立方采样在这个不确定集内部生成N个候选场景比如500到1000个保证覆盖各种组合。第二步拿当前的第一阶段解对每个候选场景求一次第二阶段成本。注意这一步是普通线性规划求值不是max-min嵌套优化计算量小很多。第三步按第二阶段成本从高到低排序提取成本靠前的K个场景作为关键场景候选。第四步做场景辨识校验因为主问题每轮更新后第一阶段解会变化场景的排名也会跟着变所以需要在迭代过程中周期性地重新排序、更新关键场景集合。第五步用更新后的关键场景集合重构主问题得到新的第一阶段解再回到第二步继续循环直到关键场景集合不再变化。这样做的优势很明显第二阶段子问题从双线性规划变成了普通LP求解器压力小很多关键场景候选集合可以在迭代中复用前几轮的场景结果能作为后面几轮的初始可行解而最终得到的解等于在关键场景覆盖下达到最优和连续不确定集上的严格鲁棒解之间存在一层受控的近似差距这个差距可以在收尾阶段通过新增挑战场景来缩小。需要强调一点用有限关键场景替换连续集严格来讲会轻微低估最坏情形的成本。所以算法最后必须加一个验证阶段用最终的第一阶段解在全空间重新做随机场景扫描比如再抽1000个全新的随机场景检查有没有哪个场景的第二阶段成本超过已有关键场景的成本最大值。如果发现了把这个“漏网”场景补充进集合重新求解。这一步绝不能省。3.3 场景辨别不是简单的“取前K个”很多人第一次接触这个算法容易把它理解成“采样一批场景然后取成本最高的前K个”。这是一个非常普遍的误解。如果只是取头K个那你大概率选出来的都是光伏为零、风电为零、负荷最大这种绝对极端组合。但实际算一算就会发现有的场景虽然看起来很凶储能一充一放、电网购电一补就消化掉了有的场景看起来温和却因为正好触发机组的爬坡极限第二阶段成本反而极高。所以衡量标准必须是第二阶段成本而不是不确定量的绝对值。我在实现中还不只用一个指标。除了第二阶段成本我会额外检查每个场景的对偶乘子也就是影子价格。如果某个场景同时触发了好几个紧缺约束比如机组爬坡到顶、储能SOC贴到边界、电网购电也满了那它大概率是全局层面的危险场景就算当前排序不在头部也要提前加进候选集合。这个改进是我在基础算法上自己加的实测下来对收敛速度帮助非常明显。提示场景辨识不要做成一次性工作。主问题每一轮更新后第一阶段解在变场景的危险度排名也会变。如果固定场景集合不更新往往会把真正的关键场景漏掉最终结果在鲁棒性上是失真的。4. Matlab实现拆解从建模到迭代求解的代码骨架4.1 工具链与数据准备环境方面我用的是Matlab R2021b、YALMIP工具箱求解器配的是Gurobi 9.5学术许可就能申请。CPLEX也完全能跑但在对偶转化和混合整数处理上Gurobi在YALMIP下的兼容性更省心。算例系统配的是两台燃气轮机、一组储能、光伏加风电以及常规负荷和可切负荷。数据方面需要24小时预测负荷曲线、光伏和风电预测出力曲线、不确定量的偏差范围我取了±15%到±25%、机组参数表上下限、爬坡率、成本系数以及储能参数容量、充放电效率、SOC上下限、初始SOC。这些数据建议统一放到Excel或MAT文件里单独写一个读取函数方便后面换算例。4.2 第一阶段模型先定启停和基础计划在YALMIP里用binvar定义机组启停sdpvar定义连续变量。核心代码长这样% 机组变量 u binvar(n_gen, T, full); % 启停状态 p sdpvar(n_gen, T, full); % 机组出力 % 储能变量 p_ch sdpvar(n_bess, T, full); % 充电功率 p_dis sdpvar(n_bess, T, full); % 放电功率 E sdpvar(n_bess, T1, full); % SOC轨迹 % 购售电变量 p_net sdpvar(1, T, full);第一阶段约束包含启停逻辑约束比如u(t)-u(t-1) ≤ 1表示开机动作u(t-1)-u(t) ≤ 1表示停机动作出力上下限写成p_min.*u(t) ≤ p(t) ≤ p_max.u(t)爬坡约束写成p(t)-p(t-1) ≤ ramp_up这种形式储能SOC递推写成E(t1) E(t) η_chp_ch - p_dis/η_dis。功率平衡约束不写在这里它属于第二阶段子问题因为在实际运行里功率平衡要等不确定量实现后才能最终闭合。4.3 第二阶段子问题单阶段LP求值第二阶段子问题接收第一阶段的启停解和当前不确定量实现然后最优化地调整出力、储能和购电。它只是普通的线性规划直接交给YALMIP求即可function [cost, P_adj] second_stage(u_fixed, xi, param) % u_fixed: 第一阶段确定的启停 % xi: 当前不确定量实现 (光伏、风电、负荷的预测误差) P_pv_real param.P_pv_fc xi(1); P_wt_real param.P_wt_fc xi(2); P_load_real param.P_load_fc xi(3); % 定义第二阶段变量出力、储能、购电、切负荷 ... Constraints [功率平衡机组出力限制储能SOC限制交互功率限制]; ops sdpsettings(solver,gurobi,verbose,0); Optimize(Constraints, cost, ops); end这里有个细节必须注意第一阶段里已经停机的机组在第二阶段子问题里要把出力锁死为0不能让它通过调整变量偷偷启动。如果你忘了加这个锁死约束两阶段逻辑就名存实亡了整个模型退化成同时决策结果失去鲁棒意义。4.4 场景辨别循环的主代码核心循环逻辑我整理成下面的框架% 主循环场景集合更新 第一阶段求解 while iter max_iter % 1. 用拉丁超立方采样生成候选场景 xi_cand lhsdesign(N_samples, n_unc) .* (ub - lb) lb; % 2. 对每个候选场景求第二阶段成本 costs zeros(1, N_samples); parfor k 1:N_samples [costs(k), ~] second_stage(x_fixed, xi_cand(k, :), param); end % 3. 按成本排序选取关键场景 [~, idx] sort(costs, descend); key_idx idx(1:key_num); key_scenarios xi_cand(key_idx, :); % 4. 用关键场景集合构造主问题 [x_fixed, LB, MP_flag] master_problem(key_scenarios, param); % 5. 收敛判断关键场景是否稳定 if isequal(sort(key_idx), sort(prev_key_idx)) break; end iter iter 1; end第二步的parfor很有价值我在8核机器上把N1000个场景的求值阶段加速了4到5倍。如果你的机器核数多这一步一定要用并行。第四步里面主问题需要为每个关键场景建立一组对应的第二阶段变量拷贝同时它们共享第一阶段变量这样才合得上鲁棒化的语义。提示key_num的大小很关键。取得太小鲁棒性不足取得太大主问题里的混合整数线性规划规模会爆炸。我的实测经验是在3个不确定量的系统上K取10左右就能很好地平衡计算时间和鲁棒性如果维度提高到5到6个建议把K放到20到30之间。4.5 收尾验证别让抽样骗了你算法收敛之后我一定会跑一轮全新的场景验证。具体做法是从不确定集里重新采1000个随机场景用最终解去求这些场景的第二阶段成本记录最大值再和保存下来的关键场景最大成本对比。如果最大值高出超过5%左右就把触发最高成本的那个场景加入关键场景集合回到主问题重新求解一轮。这一步本质上是在给场景近似买保险。我测试时遇到过两次漏网场景一次是光伏中低水平配合负荷小尖峰的组合另一次是风电和光伏同时偏低但负荷中等的场景。这两个场景都不是绝对极端但恰好对机组爬坡约束冲击特别明显。要不是收尾验证逮住它们最终解在真实运行时就会越限。5. 算例结果与对比鲁棒解到底贵了多少5.1 算例配置测试系统的关键参数如下两套燃气轮机额定功率分别为300kW和200kW爬坡速率分别180kW/h和120kW/h储能容量600kWh最大充放电功率150kW充放电效率0.92光伏额定120kW风电额定80kW峰值负荷380kW与大电网交换功率上限150kW。不确定量取光伏±20%、风电±25%、负荷±10%相对预测值。这个区间是基于我手头数据的预测误差分位数取的不是随便拍的。5.2 三种方案的对比结果我对比了确定性调度、严格盒式鲁棒调度、关键场景鲁棒调度三套方案结果整理成表格指标确定性方案盒式鲁棒方案关键场景鲁棒方案调度总成本基准值15.6%7.8%最坏场景下的运行成本严重越限、成本失真28.4%11.3%最大切负荷量发生多次越限00求解耗时12秒187秒43秒这个结果说明两个事实。一是确定性方案在成本数字上确实最优但在最坏场景下面临越限和切负荷那个成本根本兜不住二是盒式鲁棒虽然理论上无懈可击但代价是成本整体抬高15.6%而且求解时间是关键场景方案的4倍以上。关键场景鲁棒方案在只增加7.8%成本的前提下就把最坏场景下的成本增幅限制在了11.3%并且全程没有切负荷从工程角度看是最经济又安全的折中。5.3 不确定集尺寸的敏感性我还做了不确定集尺寸的敏感性测试把不确定集从±10%逐步扩大到±30%观察三套方案的调度成本变化。结果很直观确定性方案在扩大不确定集时成本几乎不变但切负荷风险急剧上升严格盒式鲁棒方案的成本随不确定集扩大几乎线性上升增长速度最快关键场景鲁棒方案的成本上升相对平缓说明它在不确定性强的时候更有优势——你只需要付出的成本就能买下关键危险场景的保险而不用为了所有极端角落都付高价。另一件值得做的是可视化把最终选出的关键场景的时序曲线和调度指令画在一张图上可以看到调度方案在哪些时段专门为最坏情况预留了备用空间。答辩或者写论文的时候这张图比任何文字都有说服力。6. 调试中踩过的坑与收敛性调优经验6.1 不可行问题从哪下手查两阶段工程最容易炸的地方是第二阶段可行性。我的排查步骤是固定的。第一步固定第一阶段解和中位场景单独跑第二阶段线性规划如果连中位场景都不可行那基本是原始数据问题不是算法问题——要么功率平衡写错了要么储能SOC初值对不上。第二步中位场景可行但极端场景不可行那就回头检查不确定集尺寸是不是定得过大或者第二阶段调整变量的范围是不是太小。典型例子是切负荷上限设得太高等于给了模型一个无底洞约束形同虚设。第三步如果子问题本身可行但主问题加上场景约束后反而不可行那大概率是割约束构造出逻辑错误——每个场景的变量拷贝范围没写对或者某个变量没有在场景间保持一致性。调试工具方面建议把YALMIP的verbose参数设为1输出每个线性规划的求解状态遇到定位不了的不可行约束直接调yalmip(debug)它会帮你把冲突约束组找出来。这两个操作几乎能解决九成以上的不可行问题。6.2 收敛慢的三个常见原因和处理手段收敛慢我总结出三个高频原因和对应解法。第一个是场景数量或K值太小导致关键场景集合在迭代中反复震荡不收敛。解法是增大K同时把更新策略从“整体替换”改成“加权合并”——保留上一轮集合里排名靠前的场景再并入本轮新增的危险场景避免一次换血把信息全丢光。第二个是第一阶段混合整数变量太多主问题求解太慢。解法是给求解器放宽mipgap从默认的1e-4放宽到0.005代码是ops sdpsettings(solver,gurobi,mipgap,0.005);工程上这种精度完全够用但求解速度能快一个数量级。第三个是子问题重复计算太狠。同一批场景在主问题更新前后经常被反复求值建议把每个场景对应场景索引和目标值缓存起来如果场景没变就之间沿用上一次的结果。这一步看似简单实际省下来的求解时间非常可观。6.3 怎么判断你的解是真的鲁棒还是只是看起来鲁棒判断鲁棒性有个标准动作事后模拟。我的做法是把最终调度指令固定下来生成大量历史预测误差样本逐小时检查功率平衡、SOC边界、机组爬坡是否违反统计违反次数和最小裕量。如果鲁棒方案在全程样本里零越限而确定性方案超过25%的样本触发切负荷那这个鲁棒模型才算真正有效果。很多人只看目标值不看约束违反率这是个典型误区。鲁棒优化的价值排序应该是可行性优先、成本其次。一个成本数字漂亮但约束会崩的方案在真实运行里没有意义。提示保存日志文件很重要。两阶段模型每次迭代的上下界、场景集合变化和求解耗时都要记录到文本或Excel出问题的时候可以直接回查是哪一轮迭代引入的故障省去大量重复排查时间。6.4 一个让我排查两天的坑储能SOC轨迹在不同阶段里的盗梦空间这个坑值得单独拿出来讲。第一阶段模型里写了SOC递推约束第二阶段子问题里也写了同样的递推但因为两阶段变量是分开定义、分开求解的实际运行时两个阶段可能各自使用完全不同的储能功率序列。第一阶段的优化器以为它在充电第二阶段的优化器却在放电然后整个系统还在目标函数里把储能收益算得漂漂亮亮的。等你把真实的SOC轨迹画出来才发现它早就飞出边界了。解决办法不算复杂但很容易漏在第二阶段求解结束后把SOC轨迹回填到第一阶段并显式添加两条一致性约束强制两阶段使用相同的储能交互功率。写完这两条约束之后我才真正理解为什么论文里总强调“耦合变量要在所有场景间保持一致”这句话。两阶段模型不是两个独立优化任务的拼接它是一个存在内部耦合的完整问题任何破坏耦合细节的实现解出来都是海市蜃楼。这套代码最终跑通之后我的实际感受是两阶段鲁棒优化本身已经是一个成熟的框架了真正决定一个实现质量高低的是那些容易被忽略的细节。先进算法的价值恰恰要落在扎实的工程实现上。如果后续想继续扩展可以在这个框架上加动态时间窗口做实时滚动调度或者把关键场景辨别的逻辑移植到分布鲁棒优化的模糊集构造上都比另起炉灶更高效也更容易出成果。