风电不确定性下分布鲁棒机组组合:线性决策规则与Matlab实现

发布时间:2026/10/10 5:55:37
风电不确定性下分布鲁棒机组组合:线性决策规则与Matlab实现
调度中心的场景里机组组合Unit CommitmentUC从来不是一道简单的算术题。要在未来24小时里决定哪些机组开机、哪些停机、每台机发多少电同时还要满足负荷、留足备用、压住成本——这套优化模型本身已经很成熟了。但风电一接入问题就变了味风功率预测偏差可能在一天内波动几十万千瓦单靠一组固定出力计划根本兜不住。相比传统确定性UC我们真正要做的是“在不确定性之下做决策”而这也是近年来分布鲁棒优化机组组合受到关注的核心原因。这篇博文围绕“基于线性准则的考虑风力发电不确定性的分布鲁棒优化机组组合”展开讲的不是纯理论推导而是把模型思路、Matlab实现框架、求解器选型、实际调试经验一起梳理一遍。标题里的“线性准则”对应到优化术语上就是线性决策规则Linear Decision RuleLDR有时也叫仿射决策规则把第二阶段可调决策写成随机变量的仿射函数从而把两阶段分布鲁棒问题转化成可求解的混合整数规划或半定规划。下面我会把整条技术路线从头到尾拆开讲文末再附上我在实际跑算例时踩过的坑和排查方法。适合正在做电力调度、机组组合方向研究的同学以及想把分布鲁棒优化用在工程场景里的Matlab使用者。1. 项目背景为什么机组组合必须考虑风电不确定性1.1 传统机组组合到底在解决什么问题机组组合本质上是一个混合整数规划问题目标函数通常是燃料成本、启停成本之和约束包括功率平衡、机组出力上下限、爬坡约束、最小开停机时间、系统旋转备用等。调度员拿到负荷预测后把这些数据灌进模型解出一组“几点开哪台机、带多少负荷”的方案。这个模型在纯火电时代基本够用因为火电出力可以按计划执行负荷预测误差相对可控留几个百分点的备用就能应对。但问题也恰恰在这里传统UC把不确定性压缩成了一个固定备用需求而不是真正把不确定性“建模”进优化过程。一旦风电比例升高预测误差会同时影响功率平衡的左右两侧固定备用这种粗颗粒处理就不再可靠了。1.2 风电不确定性到底“不确定”在哪风电出力的不确定性主要体现在两个层面。一是预测误差风速的随机性导致功率预测存在较大偏差尤其在风电爬坡时段误差可以到装机容量的20%到40%。二是误差分布本身不稳定同一个风电场的出力误差在不同季节、不同天气过程下可能表现出完全不同的概率分布甚至一天的早中晚都会变化。这就带来一个很现实的问题我们在优化模型里到底该用哪个分布如果假设误差服从正态分布但实际分布明显偏态、重尾随机规划的优化结果就可能系统性低估风险。而如果只考虑最坏场景虽然安全但经济性会变得很差机组大量空开、备用过度冗余调度成本高到没法落地。1.3 三类不确定性建模思路的横向对比业内处理这类问题通常有三条技术路线方法对概率分布的要求优化目标计算复杂度保守性随机规划SP需要精确概率分布或场景集期望成本最小化高大量场景较低鲁棒优化RO只需不确定集合不看分布最坏场景成本最小化中高分布鲁棒优化DRO只需分布的部分矩/样本信息最坏分布下的期望成本最小化中高可调从表里能看出来分布鲁棒优化走的是中间路线不奢求精确分布也不完全放弃概率信息。它利用历史数据构造一个包含“可能真实分布”的模糊集然后在这个模糊集中寻找最坏的分布并优化该分布下的期望成本。这样既比随机规划稳健又比传统鲁棒优化经济而且通过调节模糊集大小可以控制保守程度。这也是本项目标题里“分布鲁棒优化”这个关键词背后的现实动机。2. 分布鲁棒优化与线性准则核心思路拆解2.1 分布鲁棒优化的“模糊集”思想要理解DRO先看它和最坏情况鲁棒优化之间的差别。传统RO假设不确定变量落在某个集合内优化的是这个集合中最坏情况下的成本DRO则认为不确定变量的概率分布本身是未知的只知道它属于某个集合——“模糊集”。在这个模糊集里我们找一个使目标期望最大的“坏分布”然后优化这个坏分布下的决策。模糊集的构造方式有很多常见的有两类。一类是基于矩信息已知不确定变量的均值、协方差或它们的上下界构建一个满足这些矩约束的分布集合。另一类是基于数据驱动用历史样本构造经验分布定义一个以经验分布为球心、分布距离如1-Wasserstein距离或KL散度不超过某个半径的“Wasserstein球”作为模糊集。两类构造方式在Matlab里都能落地区别在于对偶表达式的复杂度和最终的求解规模不同。用生活化的方式理解你不能确定未来一周到底下不下雨、下多少但你手头有过去几年的气象记录知道平均降水量范围。你会做两手准备——既不会只按最极端暴雨规划也不会因为“天气预报说晴天”就不做任何预案而是做一个在“最坏可能气候模式”下也不至于翻车的方案这就是DRO的逻辑。2.2 线性准则LDR解决的核心矛盾两阶段机组组合中第一阶段的启停和基点出力必须在不确定性实现之前确定第二阶段可以根据实际风电偏差调整机组出力、弃风、切负荷等。问题来了第二阶段的“调整策略”是一个无限维的决策函数理论上可以任意复杂这在实际求解中根本不可行。线性准则的做法很直接把可调决策限制成随机偏差的仿射函数。比如某台机组在t时段的微调出力写成δ(g,t) d0(g,t) d1(g,t) × (ξ(t) - μ(t))其中ξ(t)表示t时段的实际风电不确定性偏差μ(t)是偏差均值。于是“该时段怎么调整”这件事被压缩成了两个要优化的参数基准值d0和响应系数d1。全系统所有机组的可调策略最终变成一组有限的待优化系数矩阵。这个限制听起来是“牺牲了最优性”但工程上非常划算。风电偏差对机组出力调节的影响本身就有较强的线性特征机组在未改变启停状态的条件下出力调整接近仿射关系即便考虑爬坡限制线性决策规则也能给出质量足够好的近似解而计算复杂度从“无限维函数空间”降到了“有限维参数空间”这是它能在实际系统中用起来的根本原因。2.3 两阶段DRO-UC模型的数学骨架把上述思想写成标准的两阶段模型第一阶段决策开机状态z、基点出力p在不确定性实现前确定第二阶段根据不确定性偏差ξ通过线性决策规则计算各机组的调整量δ、弃风量、切负荷量。目标函数包含确定性的启停与燃料成本再加上分布鲁棒项——模糊集中最坏期望下的调节成本、弃风惩罚与切负荷惩罚。为了不让DRO在最坏期望项上退化成一个平凡问题目标里通常会加入与偏差相关的凸惩罚。比如调节成本用二次函数近似因为机组偏离计划出力会产生额外的AGC调节压力二次惩罚在物理上说得通数学上也足够方便。把线性决策规则代入二次惩罚后期望项会自然出现风电偏差协方差矩阵的迹这样模糊集里的二阶矩信息就真正参与了优化决策。约束层面功率平衡、机组出力上下限、爬坡约束等都需要对模糊集支撑集内的所有可能偏差成立。常见的处理是把支撑集设计成Box或椭球然后对线性约束求鲁棒对偶得到一个确定性的有限维约束。到这里一个无限维的两阶段DRO-UC问题就被“线性准则鲁棒对偶”转换成了可以交给求解器处理的形式。3. Matlab实现框架整体架构与关键模块3.1 代码整体架构与运行流程我实际在Matlab里实现这个模型时没有把代码堆在一个主脚本里而是按模块拆开这样调参和换算例都方便。整体目录大致是这样的project_root/ ├── data/ │ ├── gen_params.csv # 机组参数 │ ├── load_curve.csv # 负荷曲线 │ └── wind_data.csv # 风电历史功率/场景 ├── scripts/ │ ├── load_data.m # 数据读取 │ ├── gen_scenarios.m # 生成风电偏差场景 │ ├── build_fuzzy_set.m # 构建模糊集 │ ├── build_model.m # YALMIP建模 │ ├── solve_model.m # 求解器配置与求解 │ └── plot_results.m # 结果可视化 └── main.m # 主入口按顺序调用主入口main.m只做四件事加载数据、构造模糊集、建模求解、画图。每一步都有独立脚本方便单独调试。这个架构看起来没什么花哨但项目做大了之后你会发现模块化是控制调试痛苦的第一道防线。3.2 风电场景数据与模糊集参数准备无论采用矩模糊集还是Wasserstein球第一步都是准备风电偏差场景。一般做法是从历史预测误差中提取偏差样本或者用蒙特卡洛/拉丁超立方抽样生成一组足够大的离线场景矩阵。我建议场景数至少1000个否则模糊集的统计量估计会不太稳定。如果采用矩模糊集需要计算偏差样本的均值向量和协方差矩阵如果采用Wasserstein球则需要以全体样本的经验分布作为模糊集球心。除此之外支撑集一般设置为Box形式即每个时段的风电偏差处于预测误差的上下界之间这个上界从历史数据的最值或者预测置信区间得到。模糊集参数中Wasserstein半径的选择直接影响结果保守度。工程上常用交叉验证把历史数据分训练集和验证集交替实验选一个在验证集上表现稳定、又不至于成本过高的半径。这个方法听起来朴素但在没有更多先验信息的情况下比拍脑袋定半径靠谱得多。3.3 决策变量与YALMILP建模要点我的实现是基于YALMIPR2021b环境求解器用Gurobi或CPLEX。YALMIP的好处是可以直接用sdpvar和binvar定义连续变量与二进制变量写优化问题就像写数学公式一样省去手动构造大规模约束矩阵的大量工作。第一阶段的二进制启停变量和基点出力变量很简单z binvar(G, T, full); % 机组启停状态 p sdpvar(G, T, full); % 基点出力第二阶段的线性决策规则用一组基准系数和一组响应系数表示。假设每个时段的偏差变量是标量ξ(t)那么机组的调整量可以写成d0 sdpvar(G, T, full); % 基准调整量 d1 sdpvar(G, T, full); % 线性响应系数 xi sdpvar(T, 1, full); % 不确定偏差向量 % 线性决策规则delta_{g,t} d0(g,t) d1(g,t) * (xi(t) - mu(t)) delta d0 d1 .* (xi - mu); % 每行对应一台机组这里有一个特别容易踩的坑如果直接在YALMIP里把xi当作不确定变量参与约束求解器会把它当成普通决策变量整个模型就失去了“对所有不确定值成立”的意义。正确的做法是对约束做鲁棒对偶改造或者在YALMIP里使用uncertain和robust相关指令让框架自动处理不确定变量的量化约束。我的经验是对于机组组合这种规模的模型手写鲁棒对偶比依赖框架自动处理更可控尤其是在你希望精确控制模型规模的时候。3.4 求解器选择与混合整数规划处理完成建模后目标函数里启停成本是线性的燃料成本是线性的而调节成本由于线性决策规则会变成包含协方差矩阵的二次项属于二次规划叠加二进制变量后整体是一个混合整数二阶锥规划或者混合整数半定规划具体类型取决于模糊集的表达方式。我使用的求解器组合是Gurobi配YALMIP因为Gurobi对MISOCP的支持非常成熟求解速度和数值稳定性在中小规模算例上表现优秀。CPLEX也能求解但在处理Bilinear项时需要额外改写不太推荐新手直接用。如果遇到求解器提示“不支持的约束类型”优先检查二次项和绝对值项是否已经写成了支持的形式。4. 实操过程从风电场景到机组组合结果4.1 第一步生成风电偏差场景我以一个6机24时段的小型系统为例说明。假设系统中有2个风电场负荷曲线是典型的双峰形状风电预测曲线通过历史数据生成。风电偏差场景我用拉丁超立方抽样生成比简单蒙特卡洛抽样能更好地覆盖偏差空间的边缘区域这对后续构造支撑集Box是有帮助的。% 生成风电偏差场景示意 Nscen 1500; xi_raw lhsnorm(zeros(1, 2), Sigma_wind, Nscen); % 2个风电场 % xi_raw的每一行是一个场景每一列对应一个风电场偏差抽样后我会对场景做一次清洗剔除明显超出物理上下限的值重新计算均值mu和协方差Sigma。这里要提醒一句风电偏差往往不是严格正态的用lhsnorm只是因为它在相关性结构上方便控制如果你手头的历史数据足够多直接用bootstrap从历史误差样本里重采样往往更可靠。4.2 第二步构建模糊集和目标函数本例我采用矩模糊集因为它的对偶形式相对直观给定均值向量和协方差矩阵支撑集为Box范围。模糊集的参数在build_fuzzy_set.m里集中配置这样后面调保守程度只需要改一个文件不用满项目找参数。目标函数分为三块启停成本由二进制变量直接决定燃料成本由基点出力线性累加调节期望成本这是DRO的核心项把线性决策规则代入二次调节惩罚后最坏期望成本会写成均值项加协方差迹项。这个二次项在YALMIP里用sdpvar计算就可以。% 目标函数示意 objective sum(sum(start_cost .* z)) ... % 启停成本 sum(sum(fuel_cost .* p)) ... % 燃料成本 reg_cost; % DRO期望调节成本4.3 第三步加入线性准则约束与鲁棒对偶线性准则的关键约束是把可调量代入功率平衡和机组限值。以功率平衡为例某个时段所有机组出力加风电实际出力减弃风加上切负荷必须等于负荷。实际风电等于预测值加偏差因此约束里天然含有不确定项ξ。要让这个约束对所有ξ∈Ξ成立我会先把约束改写成“确定性部分 线性部分”的形式然后对Box支撑集取最坏值。最坏情况下的约束会包含绝对值项但绝对值在YALMIP里可以引入辅助变量转化为线性约束最终交给求解器处理。这一步最容易出问题的地方是符号弄反。取最坏值时要看ξ在不等式中的系数是正还是负正系数要取上界负系数要取下界。我早期调试时吃过亏把上下界取反导致模型结果离谱后来专门写了一个小的函数处理“随便给出A、b、Box支撑集自动返回鲁棒化后的约束”把这个坑堵住了。4.4 第四步求解与结果解读模型规模大概在几千个变量、上万个约束的量级包含24个时段、6台机组的启停二进制变量Gurobi通常几十秒到几分钟内能解到较好的间隙。求解完成后我主要看几类结果启停方案可以判断模型是否合理利用了低成本机组各时段出力注意观察机组出力距离上下限的裕度最坏期望成本这是分布鲁棒优化的核心指标它表示模糊集中最不利分布下的期望成本调节响应系数看机组对风电偏差的响应是否在合理范围。我发现一个很有价值的分析手段是“保守度扫描”把模糊集半径从0逐渐放大观察总成本的变化曲线。半径接近0时分布鲁棒优化退化成随机规划半径变大后成本逐步上升曲线斜率反映出系统面对分布不确定性的脆弱程度。如果某台机组或者某个时段的出力在半径小幅增大时就剧烈变化说明该处存在瓶颈这对调度员理解系统风险很有帮助。5. 常见问题与调试技巧实录5.1 模型无界或不可行这是我调试初期遇到最多的问题。无界多半是因为目标函数的二次惩罚项没有正确表达成正定形式或者可调决策没有被很好地限定在物理范围内。不可行则通常来自两个地方一是功率平衡约束的鲁棒化取错了最坏值方向二是最小启停时间约束与开机变量之间存在逻辑冲突。排查建议先把模糊集半径设成0让模型退化成确定性问题确认基础模型可解。然后把模糊集从很小的半径开始逐级放大定位不可行是从哪一步开始出现的。5.2 Wasserstein半径到底怎么定才合理不少论文喜欢给半径设一个相对较大的值但实际工程里半径过大会导致成本暴涨失去决策参考价值。我个人的经验是结合数据量和置信度来定再通过交叉验证微调。一个常用的经验公式是半径随样本量的增加按约1/√N的比例缩小样本越多经验分布越接近真实分布模糊集可以设得越小。如果你在第4.4节做的保守度扫描里发现成本曲线出现明显拐点拐点附近的半径通常是经济性和稳健性的良好平衡点。5.3 线性准则太保守怎么办线性决策规则把可调策略限制在仿射函数族内相对于完全自适应策略确实会带来一定的保守性。如果你发现结果比预期保守可以尝试两个优化方向一是把线性规则改成分段仿射规则让决策在不同偏差区间使用不同的响应系数二是在可调决策中加入更多随机变量信息例如把风电场各自偏差的相关性也纳入响应系数而不是只用一个总偏差变量。当然更复杂的规则会让模型规模快速膨胀。我通常在项目初期先用线性规则跑通全流程确认模型逻辑正确后再根据结果误差评估是否有必要升级规则。5.4 YALMIPGurobi求解性能优化当T从24扩展到96、机组数量增加到几十台时模型规模会成倍增长直接求解压力很大。几个常用的优化技巧场景削减对大量风电场景做快速前向选择保留少量有代表性的场景构建经验分布能显著降低模糊集建模的约束数量Benders分解把两阶段问题按第一阶段二进制决策和第二阶段连续调整拆开用Benders割迭代逼近冷启动先求解一个不考虑DRO项的确定性模型用得到的启停方案作为原问题的初始解能大幅减少分支定界时间。我测下来这些技巧合起来可以把一个原本要磨二十分钟的算例压缩到三分钟左右且最优性间隙变化很小。写在最后一点真实的项目体会这套模型我前后跑了两个版本从纯随机规划改到分布鲁棒优化最大的感受是“不确定性建模的精度值得用额外的复杂度去换”。固定备用方案诚然也能跑出安全结果但一旦风电渗透率上来成本代价非常直观。而分布鲁棒优化给了我们一个调节旋钮——通过模糊集半径控制风险偏好这在真实调度需求里特别实用。线性准则在其中扮演的角色很容易被低估。很多人把它当成一个“简化模型的不得已办法”但实际算下来机组调节对风电偏差的响应基本是近似线性的LDR引入的次优性远小于我们为了保守性而产生的成本代价。换句话说它的性价比非常高。最后再分享一个小技巧如果你第一次在Matlab里跑这个模型建议先用一个3机12时段的微型算例验证目标函数和约束逻辑确认结果在直觉上合理之后再去跑完整规模。不要一上来就放24小时系统否则一次模型错误会让你耗费一整晚去排查而问题范围从修一个符号错误变成全局搜索调试成本会大很多。先把架子搭对后面的优化和扩展才有意义。