两阶段鲁棒优化在微电网调度中的Matlab实现与CCG算法解析

发布时间:2026/9/9 2:18:10
两阶段鲁棒优化在微电网调度中的Matlab实现与CCG算法解析 搞微电网优化调度的人应该都有这种经历——风光预测曲线画得再漂亮到了实际运行那天照样给你表演一个预测偏差自由落体。我当时把确定性优化跑出来的日前调度方案拿到实时运行环境里一比对燃气轮机的出力偏差动不动超过15%遇到阴天或者逆温层光伏出力能比预测值掉下去三分之一。调度成本超支还是小事供电可靠性出问题才是真正要命的事。从那时起我就把研究重点转到了两阶段鲁棒优化上用大M法处理模型中的非线性项用CCG列与约束生成算法迭代求解整套流程在Matlab里跑通效果比预想中扎实得多。这篇内容是我从数学建模到代码落地的完整复盘重点讲为什么这么建模、每步数学变换的动机、CCG算法怎么一步步收敛以及Matlab实现里那些最容易卡住的细节。适合正在做鲁棒优化入门、或者想把两阶段模型真正跑出结果的工程师和研究生参考。1. 为什么确定性优化在风光接入后越来越不好用1.1 确定性优化的本质是在赌预测值传统确定性优化的思路很简单把风、光、负荷当作已知常量输进去求解一个数学规划得到一套调度方案。这在负荷相对平稳、电源以火电为主的时代问题不大因为预测误差只有3%到5%即便预测不准系统自身的热备用和调节能力也能兜住。但新能源大规模接入后这套逻辑的根基动摇了。风电出力在日内可能从满发跌到零光伏在云层经过时几分钟内波动就能超过50%。这时候如果还按确定性模型给出的单一方案运行一旦真实的风光出力偏离预测值轻则经济性变差重则直接违反网络安全约束甚至触发切负荷。这里有个反直觉的点并不是预测模型做得越准确定性优化就越可靠。因为无论预测精度怎么提高极端场景始终存在——对鲁棒优化来说我们需要的是一个无论不确定性如何实现都能保证安全的方案而不是一个在期望场景下表现最好、遇到极端场景就崩溃的方案。1.2 随机优化与鲁棒优化的理念差异在鲁棒优化之前很多人先接触的是随机规划。随机规划要求给出不确定变量的概率分布然后用蒙特卡洛抽样生成大量场景把期望值作为目标函数。这在分布已知且维度不高的情况下效果不错但有两道坎一是实际工程中很难获得精确的风光出力或负荷预测误差分布很多时候只能靠历史数据拟合而历史数据恰恰缺乏极端情况二是场景数量一多模型规模膨胀求解时间以小时计工程上根本等不起。鲁棒优化走的是另一个极端不需要概率分布只需要知道不确定量的波动范围然后做最坏情况下的决策。它的数学风格和结论都很硬只要不确定量落在给定集合内解就一定可行。缺点是天然保守——如果直接把所有不确定量放到盒式边界算出来的方案可能贵得离谱。所以后来发展出了预算不确定集用来控制保守程度把最坏限定在一个合理范围内。而两阶段鲁棒优化加预算不确定集加CCG算法正好是这一思路下最适合落地到Matlab的组合。1.3 两阶段决策的时间结构为什么是两阶段因为电力调度的决策链条天然就是分层的。拿微电网日前调度举例第一阶段是日前决策需要在不知道明天风光实际出力的情况下先定下机组启停、与大电网的购售电计划这类现在必须拍板、事后难以快速调整的决策第二阶段是实时调整等风光出力逐渐明确以后在给定第一阶段决策的基础上调整燃气轮机出力、储能充放电功率用最经济的方式实现功率平衡。从数学上这是一个典型的min-max-min结构$$\min_{x} ; c^T x \max_{u \in U} ; \min_{y \in \Omega(x,u)} ; d^T y$$其中$x$代表第一阶段决策变量机组启停等$u$是不确定向量风光出力、负荷偏差$y$是第二阶段决策变量实时出力调整。这套结构的核心思想是第一阶段做决策时就要为最坏情况预留调整余地第二阶段则在不确定性实现后做最优响应。一开始接触这套模型时我也觉得抽象后来把它类比成出门前决定带不带伞——你没法预知会不会下雨但可以根据天气预报的波动范围提前决定带伞第一阶段决策等真下雨了再决定是打车还是走路第二阶段决策。鲁棒优化关心的是如果雨下得特别大最坏场景你的方案还能不能保证自己不淋透。2. 不确定性集合把波动铸成数学边界2.1 三种主流不确定集合的取舍不确定性集合的选取直接决定了模型的保守度和计算复杂度。实际项目里主流的集合有三种。盒式不确定集最简单对每个不确定参数给定上下界形式是$u \in [\hat{u} - \Delta u, \hat{u} \Delta u]$。它的优点是建模直观缺点是默认所有不确定量同时取到极端值这在工程上几乎不会发生导致结果过度保守。如果四个不确定源同时冲到最差边界算出来的成本高得没法看调度员看了直接摇头。椭球不确定集能刻画变量间的相关性形式更贴近真实但会在模型中引入二阶锥约束求解复杂度和耗时都会上升在CCG的迭代框架里如果每轮都求解SOCP累积时间非常可观。预算不确定集是折中也是我推荐的主力选择。它在盒式集合的基础上加一个预算约束限制不确定量同时达到极端偏差的数量或总偏差幅度。这样既能抵抗极端场景又不至于把所有不确定量全部推向最坏情况。2.2 预算不确定集的数学形式对于每个不确定参数$i$先将实际值表示为预测值加偏差$$u_i \hat{u}_i \Delta u_i z_i$$其中$z_i$是标准化的偏差变量$z_i \in [0, 1]$$\Delta u_i$是最大允许偏差。预算约束写为$$\sum_{i} z_i \le \Gamma$$这里的$\Gamma$就是不确定性预算。当$\Gamma 0$时所有偏差为0模型退化为确定性优化当$\Gamma$等于不确定参数的总个数时允许所有参数同时达到最大偏差退化为盒式集合取中间值时只允许部分参数一起恶化从而在鲁棒性和经济性之间取得平衡。我在实际案例里会同时处理风电、光伏、负荷三类不确定量预算约束就把它们放在一个统一的框架里来限制。比如有6个不确定节点取$\Gamma 3$物理含义就是同一时刻最多允许3个节点的偏差同时拉满其余节点只能部分偏差。这是一种很务实的保守度控制手段工程上也容易跟业务方解释。2.3 波动范围的取值经验不确定集合参数的选取很多人第一反应是翻文献找一个数但实际项目里最靠谱的还是从历史预测数据和实际运行数据的统计偏差中来。以日前调度尺度为例我常用的经验范围是负荷预测误差取预测值的5%到15%风电出力预测误差取20%到30%光伏预测误差取15%到25%。这个差异主要来自物理特性——风电受大气环流影响大日内波动剧烈误差天然更大负荷有较稳定的日用电规律预测模型相对成熟误差较小。如果是做实时调度5分钟或15分钟尺度这些误差范围可以压缩到原来的三分之一左右。注意不同地区、不同季节的波动特性差异很大。内陆光伏在冬季晴天的出力规律性很强误差可能只有10%而梅雨季节、山区风电的误差可能超过40%。建立模型前先用几个月的历史数据做一次误差分布统计比盲目套文献参数可靠得多。2.4 不确定集合结构对求解的影响不确定集合的形状直接决定了子问题的求解难度。盒式集合和预算集合都是多面体子问题对偶后是线性规划CCG框架下每轮迭代都比较轻量。如果换成椭球集合问题会变成二阶锥规划虽然CCG在理论上依然适用但每轮迭代的时间和数值敏感性都会明显上升。我之前尝试过把光伏和负荷的相关性用椭球集合刻画结果收敛轮数虽然差不多但单轮求解时间翻了近三倍。综合考虑求解效率和保守度控制预算集合是性价比最高的选择。除非业务上明确要求考虑变量相关性否则我不建议轻易上椭球。3. 两阶段鲁棒优化的数学模型一场接力赛3.1 紧凑形式与决策变量映射在正式建模前建议先把变量按阶段和性质分类这会直接影响后面Yalmip代码的结构。以我常用的一个微电网算例为例系统包含燃气轮机、储能、光伏、风电和负荷。变量类型阶段含义$x_{gt}$0-1第一阶段燃气轮机启停状态$P_{gt}^{DA}$连续第一阶段日前计划燃气轮机出力$P_{grid}^{DA}$连续第一阶段日前计划购售电功率$P_{gt}^{RT}$连续第二阶段实时燃气轮机出力调整$P_{grid}^{RT}$连续第二阶段实时购售电调整$P_{ess}^{ch/dis}$连续第二阶段储能充放电功率$P_{curt}$连续第二阶段弃风弃光量$u^{wt}, u^{pv}, u^{load}$连续/0-1不确定风光负荷偏差变量第一阶段的0-1变量是让模型变复杂的主因也是CCG算法选择主问题-子问题结构的核心原因——如果全是连续变量整个问题直接变成一个非线性规划处理方式完全不同。3.2 第一阶段约束第一阶段对应日前调度不确定量还没有实现所以系统的功率平衡只能基于预测值建立$$\sum_g P_{gt}^{DA} P_{grid}^{DA} P_{pv}^{fore} P_{wt}^{fore} P_{load}^{fore}$$同时要满足机组出力上下限、启停逻辑约束$$x_{gt} P_{gt}^{min} \le P_{gt}^{DA} \le x_{gt} P_{gt}^{max}$$购售电功率限制$$-P_{grid}^{max} \le P_{grid}^{DA} \le P_{grid}^{max}$$第一阶段的目标是开机成本和日前购电成本之和。注意第一阶段模型中已经把第二阶段成本用变量$\theta$替代$\theta$在迭代中会逐渐逼近最坏场景下的第二阶段运行成本。3.3 第二阶段约束第二阶段是实时调整问题不确定量$u$已经实现要在这个前提下以最小成本修正功率偏差。功率平衡约束是关键$$\sum_g P_{gt}^{RT} P_{grid}^{RT} (P_{pv}^{fore}u^{pv}\Delta P_{pv}) (P_{wt}^{fore}u^{wt}\Delta P_{wt}) P_{ess}^{dis} - P_{ess}^{ch} P_{load}^{fore} u^{load}\Delta P_{load} - P_{curt}$$这里$P_{curt}$是弃风弃光变量它是保证第二阶段总有可行解的关键——如果没有任何可切负荷或弃光的手段最坏场景下功率平衡可能根本无解。储能约束包括SOC递推$$SOC_{t1} SOC_t \eta_{ch} P_{ess}^{ch} \Delta t - P_{ess}^{dis} \Delta t / \eta_{dis}$$以及充放电功率上下限$$0 \le P_{ess}^{ch} \le P_{ess}^{max}, \quad 0 \le P_{ess}^{dis} \le P_{ess}^{max}$$第二阶段的目标函数包含实时调整成本和弃风弃光惩罚。这里把弃风弃光设置成大惩罚系数的目的是让算法优先用机组和储能去平衡实在平衡不了才允许弃。3.4 子问题的整体结构子问题本身就是一个完整的max-min问题外层是寻找最坏的不确定实现$u$内层是该场景下最优的第二阶段调度。这两层要一起处理方法就是下面要讲的大M法和对偶变换。这里先记住一个结论子问题在经过变换后会变成一个单层的混合整数线性规划可以直接交给Gurobi求解。4. 大M法把先看后定的优化问题变成可求解的数学规划4.1 子问题为什么不能直接求解整个两阶段鲁棒优化里子问题$\max_{u \in U} \min_{y \in \Omega(x,u)} d^T y$没法直接丢给求解器因为max和min套在一起求解器不会处理这种多层结构。处理方法是把内层的min问题用对偶或KKT条件替换掉使整个子问题变成一个单层优化。4.2 对偶变换与双线性项的出现假设第二阶段模型是线性规划在不含整数变量的前提下可以把内层min写成对偶形式。由于内层是min问题对偶后变成max问题就能与外部max合并成一个max问题。但这里会出现一个问题对偶问题的目标函数中不确定变量$u$和对偶变量$\pi$会以乘积形式出现即$u \cdot \pi$这样的双线性项。Gurobi和Cplex这类求解器虽然能处理一部分非凸二次约束但效率和数值稳定性都很差。这里就需要大M法把这个乘积项线性化。具体做法是在预算不确定集中偏差变量$z_i$本质上可以看作0-1变量因为线性规划的最优解在多面体极点取得极点上$z_i$必然取0或1于是$z_i \cdot \pi_i$是一个0-1变量乘以连续变量的乘积项。引入辅助变量$w_i$表示这个乘积用一组大M约束来建立$w_i$和$z_i, \pi_i$的关系$$w_i \le M z_i$$$$0 \le \pi_i - w_i \le M(1 - z_i)$$当$z_i1$时第一个约束使得$w_i \le M$第二个约束的右侧化为$0 \le \pi_i - w_i \le 0$推出$w_i \pi_i$当$z_i0$时第一个约束强制$w_i 0$第二个约束变成$0 \le \pi_i \le M$。这样乘积项就被完全线性化了。4.3 用KKT条件的替代路线除了对偶变换还可以用KKT条件替换内层优化问题。KKT条件包含原始可行、对偶可行和互补松弛三部分其中互补松弛条件形式为$\lambda \cdot g(x, u, y) 0$同样是双线性等式也靠大M法线性化。引入0-1变量$\xi$把一个乘积约束拆成两组不等式$$g(x,u,y) \le M(1-\xi)$$$$\lambda \le M \xi$$这样当$\xi0$时强制$g0$当$\xi1$时强制$\lambda0$就等价于两者至少一个为0也就是互补松弛。对偶变换和KKT条件两条路线最终殊途同归。我个人习惯用对偶变换因为它的推导更紧凑且能直接利用强对偶定理保证最优性。KKT条件的优点是当模型中含有一些不方便对偶的约束形式时更通用但引入的0-1变量会多一些求解稍慢。4.4 大M取值的实操经验大M值的选取是个典型的看着简单、做起来全是坑的环节。M太小会切掉真实最优解M太大数值病态、求解器警告频出严重时直接报infeasible。我踩过最典型的一次把M统一设成1e6结果Gurobi在子问题求解时报Constraint matrix is singular而且MIP gap卡在5%纹丝不动。后来把M逐项检查发现功率平衡约束对应的M只需要1e3量级就够了M设定过大导致预求解阶段把大量有效约束降级了。经验方法是针对每个约束单独配M值取该项物理量上限的10到100倍。比如功率平衡约束中负荷的M取最大负荷的20倍即可对偶变量的M取相应成本系数的100倍。总之不要图省事用一个统一大M数值稳定性会差很多。更讲究的做法是求解前做一轮M的敏感性分析——从一个较小的值开始逐步放大找到结果不再变化的最小M。注意如果子问题里同时有多个双线性项每个项对应的M最好都单独设置。虽然这会多花一点建模时间但对求解器的数值健康度影响非常大。4.5 强对偶等式的替代方案除了大M法还有一种思路是利用强对偶定理若内层LP有可行解且满足强对偶条件则原始目标函数等于对偶目标函数。把这个等式直接加入约束消除max-min嵌套中min的存在从而把双线性项限制在目标函数中再配合线性化处理。这个做法看似更简洁但对偶变量与原始变量的乘积项处理并不比大M法容易而且要求约束矩阵满足较严格的正则条件。实际项目里我基本用大M法因为它对模型结构适应性强改起来也方便。5. CCG算法从最坏场景到足够好的迭代5.1 为什么选CCG而不是Benders两阶段鲁棒优化的标准求解路线有两条Benders分解和CCG。Benders分解通过添加对偶割平面来逼近原问题每次迭代在主问题里加一个关于$\theta$的约束不增加新变量。它的缺点是当主问题含0-1变量时割平面收敛速度偏慢往往需要很多轮迭代。CCG的做法不一样它在每轮迭代中把子问题发现的最坏场景$u^*_k$直接写进主问题同时为这个场景添加一组完整的第二阶段变量和对应约束。也就是说CCG不只是加一个割而是把一整套新变量和新约束都加进去。因为这个特点CCG在含整数变量的两阶段鲁棒优化里通常比Benders收敛快得多往往十几轮以内就能达到1e-3的精度。5.2 主问题和子问题的结构主问题MP的初始形式是$$\min_{x, \theta} ; c^T x \theta$$s.t. 第一阶段约束机组启停、购售电限制等以及与每个已发现场景$u^*_k$相关的约束$$\theta \ge d^T y_k$$$$y_k \in \Omega(x, u^*_k)$$这里的$k$是迭代轮数索引。每轮CCG都会往主问题中添加一个下标为$k$的新变量组$y_k$以及对应场景$u^*_k$下的约束。主问题给的是目标函数的一个下界。子问题SP则是给定第一阶段解$x^*$求解$$Q(x^) \max_{u \in U} ; \min_{y \in \Omega(x^, u)} ; d^T y$$子问题的目标函数值代表当前第一阶段方案在最坏场景下的第二阶段运行成本它给的是目标函数的一个上界。5.3 CCG的完整迭代流程CCG的完整流程如下第一步初始化。设下界$LB -\infty$上界$UB \infty$迭代次数$k 1$已发现场景集合为空。第二步求解主问题MP得到第一阶段最优解$x^*_k$和最优目标值$\theta_k$。更新下界$$LB \max{LB, ; c^T x^*_k \theta_k}$$第三步把$x^_k$代入子问题SP求解最坏场景$u^_k$和子问题目标值$Q(x^*_k)$。更新上界$$UB \min{UB, ; c^T x^_k Q(x^_k)}$$第四步检查收敛条件如果$UB - LB \varepsilon$停止迭代当前方案即鲁棒最优方案否则把最坏场景$u^*k$加入主问题为它添加一组新变量$y{k1}$和对应约束然后$k k1$回到第二步。这个流程最初看起来会担心主问题规模越来越大——没错主问题的变量和约束数量确实会随迭代轮数线性增长。但实际经验是CCG收敛很快通常5到15轮就能达到1e-3的间隙主问题规模完全可控。5.4 收敛阈值设定与加速技巧收敛阈值$\varepsilon$我一般设在1e-3到1e-4之间。设到1e-5不是不行但主问题会多迭代好几轮求解时间翻倍对工程结果没有任何肉眼可见的改善。如果追求快可以先设一个较大的阈值比如1e-2跑一轮用结果检查模型是否合理再正式设1e-4跑最终结果。还有一个实用加速技巧在CCG迭代过程中可以把历史轮次中出现过的$\theta$割平面信息保留下来而不是每次从零开始。有些场景下上一轮的最优第一阶段解可以作为下一轮主问题的初始可行解能明显减少MIP求解时间。在Matlab里实现方法是每轮结束把$x^*$和$\theta$的初值传给下一轮Yalmip支持通过assign和optimize的usex0选项实现。6. Matlab代码实现从数学到能跑的工程细节6.1 工具箱与求解器选择Matlab下实现这套模型我的组合是Yalmip做建模语言Gurobi做底层求解器。Yalmip对这类带0-1变量和上下界约束的混合整数规划支持得很好语法简洁调试方便。Gurobi的MIP求解速度在同类商业求解器里属于第一梯队对CCG这种需要反复求解MIP的场景非常关键。没有Gurobi授权的话Cplex是备选SCIP虽然免费但性能差距明显尤其在处理规模稍大的两阶段问题时。如果经费紧至少保证用Cplex或Gurobi不要用Matlab内置的intlinprog硬扛——MIP性能和稳定性差距太大很多情况下直接卡死。6.2 代码总体结构整个代码的主循环逻辑可以用文字描述如下外层循环负责CCG迭代每轮先求解主问题MP然后把第一阶段结果传给子问题SPSP返回最坏场景再更新上下界并判断是否收敛。6.3 主问题MP的建模代码主问题的核心变量是阶段一的0-1变量$x$和代表第二阶段成本的变量$\theta$。每轮迭代要新增一组阶段二变量$y_k$。这里必须注意Yalmip的变量对象是增量式的每次迭代新增的变量和约束都要通过[..., ...]拼接保留下来不能在循环里覆盖旧变量。% 初始化 X binvar(n_gt, n_t, full); % 阶段一机组启停 theta sdpvar(1, 1); % 第二阶段成本近似 Constraints []; % 约束集合 Objective 0; Y {}; % 阶段二变量元胞数组 % 阶段一目标 Objective Objective c_start * X(:) ...; % 阶段一约束 for t 1:n_t Constraints [Constraints, sum(X(:, t)) 1]; % 备用约束示例 end % 每轮迭代添加到主问题 for k 1:n_iter % 新增一组阶段二变量与场景 u{k} 对应 Y{k} sdpvar(n_y, 1); % 阶段二连续变量 % 阶段二约束u_star{k} 是上一轮子问题返回的最坏场景 Constraints [Constraints, Y{k} 0]; Constraints [Constraints, A_2 * Y{k} B_2 * [X(:); u_star{k}]]; Objective Objective theta; % theta 放在目标中 Constraints [Constraints, theta d * Y{k}]; end ops sdpsettings(solver, gurobi, verbose, 0); optimize(Constraints, Objective, ops); x_star value(X);6.4 子问题SP的建模与大M法线性化子问题的建模是整套代码里最考验耐心的部分。以一个包含双线性项$z_i \cdot \pi_i$的子问题为例Yalmip中的处理方式是引入辅助变量$w_i$把大M约束逐条写进去。% 子问题给定 x_star求最坏场景 u 和第二阶段成本 u sdpvar(n_u, 1); % 不确定变量偏差 z binvar(n_u, 1); % 标准化0-1偏差变量 pi sdpvar(n_pi, 1); % 对偶变量 w sdpvar(n_u, 1); % 双线性项替代变量 % 预算不确定集约束 Constraints [u u_hat delta_u .* z]; % 不确定量表达式 Constraints [Constraints, sum(z) Gamma]; Constraints [Constraints, 0 z 1]; % 对偶可行约束 Constraints [Constraints, A_dual * pi d]; % 大M线性化w z .* pi M 1e4; % 根据需要单独调整 for i 1:n_u Constraints [Constraints, w(i) M * z(i)]; Constraints [Constraints, 0 pi(i) - w(i) M * (1 - z(i))]; end % 目标对偶目标 线性化后的双线性项 Objective_sub b_dual * pi w * coeff_u; optimize(Constraints, -Objective_sub, ops); % 子问题是max问题取负号给min求解器 u_star value(u); Q_value value(Objective_sub);这里的关键点在于优化表达式中目标函数必须是线性的所以双线性项必须全部通过大M辅助变量替换不能直接把z .* pi留在目标函数里否则Yalmip会默认调用非凸求解器整个流程直接报废。6.5 主循环的数据保存与更新CCG实现中最容易出bug的地方是主问题历史信息被覆盖。每轮迭代新增的$u^*_k$、变量组$Y_k$和约束都必须以元胞数组或增量拼接的形式保存绝对不能在一个sdpvar变量上反复重新赋值否则前几轮的场景信息全部丢失算法退化成一个单场景近似结果完全不对。我习惯用一个结构体保存每轮的场景和成本Scenarios{k}.u u_star; Scenarios{k}.cost Q_value;主问题建模时遍历这个结构体把每个历史场景的约束加进去。这样代码逻辑清晰也方便出问题时逐轮检查。6.6 求解器参数配置Gurobi在MIP求解中有几个参数值得调。MIPGap通常设默认即可但可以在收敛慢时放宽到1e-3以加快速度TimeLimit建议设一个上限防止某轮子问题卡死Threads可以根据机器核心数调整CCG主问题规模较大时多线程收益明显。在Yalmip中的设置方式ops sdpsettings(solver, gurobi, verbose, 0, ... gurobi.MIPGap, 1e-3, ... gurobi.TimeLimit, 600, ... gurobi.Threads, 8);7. 算例结果与参数敏感性分析7.1 算例参数设计为了验证模型和算法的有效性我构造了一个小型微电网系统一个燃气轮机最大出力100 kW、一组储能容量200 kWh最大充放电功率50 kW、一个光伏电站预测出力60 kW、一个风电场预测出力80 kW、峰值负荷150 kW购电价格按分时电价设置。预测误差参数为负荷10%、光伏20%、风电30%。不确定预算$\Gamma$分别取0、2、4、6进行测试。7.2 CCG收敛过程实录第一轮迭代主问题中还没有任何历史场景解出来的是一个较松弛的下界同时给出一个乐观的第一阶段方案。子问题在这个方案下寻找最坏场景返回一个较大的上界。第二步迭代后上下界开始靠近。以$\Gamma4$为例收敛数据大致如下迭代轮数下界LB上界UB间隙差距11852.42416.8564.432123.62298.1174.552210.32267.256.982236.52253.817.3122244.12248.94.8到第12轮上下界间隙已经压到5元以内相对间隙约0.2%已经远好于工程上通常要求的1%。CCG的收敛速度在这个算例中表现很好主问题规模也没有变得不可控。7.3 不同不确定性预算下的结果对比改变$\Gamma$的值观察总成本和调度方案的变化预算Γ总成本元/日与确定性方案的差距01825.3基准21988.68.9%42244.823.0%62472.135.4%$\Gamma0$就是确定性优化成本最低但完全没考虑不确定性一旦风光实际出力偏离预测值方案可能直接不可行。随着$\Gamma$增大系统预留的调节能力增加成本随之上升。$\Gamma4$时成本增加约23%换来的是在最多4个不确定点同时拉到极端偏差的情况下依然保证安全。这是典型的用经济性换取鲁棒性。实际操作中我建议把$\Gamma$当作一个可调旋钮跟业务方反复沟通确定平衡点——不是越大越好而是要结合电网供电可靠性要求、弃风弃光惩罚成本、以及他们对风险的容忍度来选。7.4 鲁棒方案和确定性方案在极端场景下的对比把确定性方案和鲁棒方案分别拿到同一个极端场景下做后评估光伏出力比预测值低40%风电出力比预测值低30%负荷比预测值高10%。确定性方案在这个场景下功率平衡被打破需要切负荷25 kW对应惩罚成本约1200元鲁棒方案$\Gamma4$由于提前预留了机组和储能调节空间通过增加燃气轮机出力和储能放电即可平衡无需切负荷额外运行成本约420元。从总成本角度确定性方案日成本加切负荷惩罚约3025元鲁棒方案加上预留成本约2665元鲁棒方案综合反而更经济。这验证了鲁棒优化的核心逻辑看似多花钱买保险长期看更省钱。8. 实操中踩过的坑与解决记录8.1 大M值过大导致的数值病态这是我踩过最深的坑。刚开始做子问题线性化时图省事把所有大M统一设为1e6结果Gurobi在求解子问题时警告矩阵数值条件数过大MIP gap卡在5%无法收敛。排查后发现问题出在功率平衡约束对应的大M上——这个约束的物理量级是负荷千瓦级1e6的大M比实际范围高出三个数量级直接让预求解器把有效约束降级成冗余约束。解决办法是逐个约束分析量级把大M降到物理上限的10到100倍。调整后子问题求解时间下降了一个数量级收敛立刻恢复正常。8.2 子问题不可行的问题第一阶段方案在某个不确定场景下可能不满足功率平衡或机组爬坡约束导致子问题无解。这个问题的根源在于第二阶段模型里没有给无法平衡留余地。解决方法是引入虚拟的弃风弃光变量和切负荷变量并赋予极高的惩罚系数。这样在最坏场景下子问题总是有解只是惩罚成本会很高从而在CCG迭代中反向引导主问题调整第一阶段决策避免出现这种场景。这也是很多文献里提到的可行性再调度思想。8.3 主问题迭代变量被覆盖的bug早期版本代码里我把阶段二变量直接命名为y而不是Y{k}每轮迭代都用y sdpvar(...)重新定义。结果主问题的约束虽然每轮都加进去了但变量对象是同一个导致所有历史场景共享同一组变量算法彻底失效迭代不收敛。后来改成元胞数组Y{k}每个场景对应独立变量组问题立刻解决。这是CCG实现中最典型的代码逻辑错误排查难度不小因为Yalmip不会报错只会默默给出一个错误的结果。建议代码里对每一轮的新变量显式命名并在约束中用[Constraints, ...]增量拼接不要覆盖。8.4 求解器与Yalmip的版本兼容问题Yalmip对不同求解器的接口更新较快有时会碰到版本不兼容导致optimize报错或求解器参数不生效的情况。比如旧版Yalmip对Gurobi 10的支持就有问题gurobi.MIPGap参数会被静默忽略。升级Yalmip到最新版本通常能解决这类问题。如果发现求解器调用了但结果明显异常先检查sdpsettings里的参数是否生效再看optimize返回的info字段是否为00代表求解成功这两步能过滤掉绝大多数环境配置问题。8.5 收敛缓慢时的处理建议如果CCG在特定算例上超过30轮仍不收敛优先怀疑三个方向一是大M取值不合理导致的数值问题二是子问题存在多组对称解导致最坏场景来回跳变三是主问题初始下界过松。前两种问题通过调整M的值和添加对称性割约束解决第三种情况可以给主问题加一个初始可行解加快收敛。9. 我自己总结的一条落地经验整套模型跑通后回头看最有价值的不只是算法和代码而是对鲁棒优化到底在优化什么这个问题的理解。很多人一上来就陷入大M取值、CCG迭代、Yalmip语法的细节里反而忽略了模型设计中最重要的问题不确定性集合怎么建才符合实际第一阶段哪些决策真正需要提前拍板第二阶段哪些调整手段真正可用这些问题的答案直接决定模型能不能落地。我现在的做法是先在Excel里把物理问题完整梳理一遍——列出所有决策变量、约束、不确定源画清楚变量间的时序关系然后再动手写代码。这一步看着费时间但能避免百分之八十以上的返工。另外建议先跑一个很小的算例验证算法正确性再上规模。我在一个两机三节点的微型系统上把CCG的每一步收敛数据都手工验算过一遍确定无误后才把算例扩大到实际系统的规模。这样做虽然前期多花了两天但后期排查问题的成本低得多。

相关新闻