ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

基于鲁棒优化的风光并网备用容量配置Matlab实现

基于鲁棒优化的风光并网备用容量配置Matlab实现 都说风光发电不好调度难就难在“看天吃饭”这四个字上。你早上预测的出力曲线可能中午就被一片云打乱下午风一停整个运行计划就得推翻重来。这篇要聊的项目就是用鲁棒优化把这笔“看天吃饭”的账算清楚在给定风光出力不确定性的前提下系统的上、下备用容量到底该留多少留多了成本涨多少留少了风险又有多大。整个研究工作全部用Matlab代码实现既包含完整的建模推导也包含可复现的求解流程尤其适合正在做电力系统经济调度、机组组合、新能源消纳方向毕业设计或论文复现的同学。这项研究的核心答案其实只有一个鲁棒性水平这个旋钮拧得越紧系统越不怕极端天气但总成本也会跟着往上走。你需要的不是“最安全”的方案而是“性价比最高”的安全。下面我直接把思路、模型、代码和踩过的坑全部摊开讲。1. 问题背景与整体设计思路1.1 风光不确定性在调度问题里到底意味着什么传统火电调度是一个确定性优化问题负荷曲线给你了机组参数给你了直接求解机组组合和经济调度就行。但加入风光之后原本的等式约束变成了带不确定参数的等式约束——风电场和光伏电站的出力不是固定值而是一个区间甚至是一个概率分布。很多初学者一开始不重视这个区别直接把风光预测曲线当成确定值代入模型。这样做出来的调度方案有两个毛病一是系统没有预留足够的向上备用万一风电突然降出力火电来不及顶上去只能切负荷二是系统也没有预留足够的向下备用万一光伏中午大发火电压不下去只能弃风弃光。这两种情况在现实中都会造成经济损失而你的模型却完全看不到这些损失算出来的“最优成本”就是一张空头支票。所以在风光并网的调度模型里“不确定性来源”本身就是模型的一部分。你需要明确回答三个问题风光出力在什么范围内波动系统靠什么手段应对这种波动应对波动的代价怎么折算成成本1.2 为什么上下备用容量是这道题的题眼备用容量分两种向上备用和向下备用。向上备用指的是当风光实际出力低于预测值时火电机组还能往上加出力的空间或者储能还能再多放出的功率向下备用则相反指风光实际出力高于预测值时火电机组能往下压出力的空间。这两个指标直接决定了系统的鲁棒性。如果向上备用留得足够多那就算风光出力掉到区间下限系统也能通过火电加出力来平衡功率如果向下备用留得足够多那就算风光出力冲到区间上限系统也能通过火电减出力来消化多余电量。但备用容量不是免费的。多留一份向上备用可能意味着多开一台机组或者让已在运行的机组偏离经济工况点运行这些都会反映在总成本上。本项目把上下备用容量明确地建模进约束条件里和鲁棒性参数耦合在一起观察它们对总成本的影响这正是整个研究的核心张力安全性和经济性怎么平衡。1.3 整体方案选型为什么走鲁棒优化而不是随机规划处理不确定性有两大类经典方法随机规划stochastic programming和鲁棒优化robust optimization。随机规划需要假设风光预测误差的概率分布然后生成大量场景用蒙特卡洛或者场景缩减方法把这些场景塞进优化模型里。好处是结果比较精细能给出期望成本坏处是计算量大而且概率分布这个东西本身也是估计出来的可能跟实际情况对不上产生“Garbage in, garbage out”的麻烦。鲁棒优化的思路更直接我不需要知道误差的精确分布只需要知道误差的边界然后保证在这个边界范围内的所有可能情况下系统都不会出问题。这种方法不需要场景枚举也规避了概率分布估计不准的问题代价是结果偏保守——它保护的是“最坏情况”而最坏情况未必真的会发生。对于这个项目来说鲁棒优化显然是更合适的选择。一方面我们想研究的是“不同鲁棒性水平”对成本的影响这正是鲁棒优化框架里的参数化讨论另一方面Matlab环境下用YALMIP工具箱搭建鲁棒优化模型配合Cplex或Gurobi求解流程非常成熟实现起来也不折腾。2. 数学模型拆解与公式推导2.1 目标函数总成本都包括哪些钱模型的目标函数是整个调度周期通常取24小时内的系统总成本最小。总成本不是简单的一个数它由好几块拼起来每一块都有明确的物理含义。第一部分是火电机组的煤耗成本通常表示成出力P的二次函数形如a * P^2 b * P c。目标函数里直接放二次函数的话模型会变成二次约束二次规划QCQP或者混合整数二次规划MIQP求解器处理起来稍慢。实操中我会把二次函数分段线性化切成四五段精度够用求解速度却快很多。这个细节在后面代码里会体现。第二部分是机组的启停成本。启动一台火电机组有冷启动成本和热启动成本之分停机也要付出代价。这部分由0-1变量的变化状态来建模是机组组合问题的核心也是让模型从纯线性规划变成混合整数线性规划MILP的原因。第三部分是备用容量的配置成本。向上备用的单位成本比向下备用略高因为要机组保持一部分出力裕度相当于让机组长期偏离最经济的出力点运行。这部分成本直接和鲁棒性参数正相关是我们观察总成本变化的核心来源。第四部分是惩罚成本比如失负荷惩罚和弃风弃光惩罚。在鲁棒优化框架下如果备用容量约束写得足够严格理论上不应该出现失负荷或弃风弃光但为了防止模型在某些极端场景下无解我还是会在约束里加上松弛变量并配一个很大的惩罚系数。这是一种典型的工程化处理不能让模型因为一根筋的约束直接无解你得给它一个“用钱解决问题”的出口。2.2 约束条件常规约束加备用容量约束常规约束包括功率平衡约束、火电机组出力上下限约束、爬坡约束、最小启停时间约束这些在标准的机组组合模型里都有不再赘述。真正让我多花了很多时间的是下面几个跟风光鲁棒性直接相关的约束。第一个是系统功率平衡约束注意这里的风光出力不是预测值而是带不确定性的区间值。也就是说这个等式约束不再是一个确定等式而是一族等式对应着风光出力区间内每一个可能的实现。鲁棒优化要保证这一族等式全部可满足。第二个是正旋转备用约束要求系统在任意时刻的向上备用容量总和不小于风光出力可能低于预测值的最大幅度。第三个是负旋转备用约束要求向下备用容量总和不小于风光出力可能高于预测值的最大幅度。这两个约束就是上下备用容量在数学上的具体落地也是把“鲁棒性水平”和“系统总成本”联系起来的桥梁。2.3 鲁棒性参数与不确定性集合的构建要研究“不同鲁棒性”对成本的影响首先要定义一个可以连续调节的鲁棒性参数。我用的是经典的盒式不确定集合box uncertainty set配合预算参数的计算方式。具体来说假设风电预测出力为P_wind_pred_t光伏预测出力为P_pv_pred_t那么它们在t时刻的实际出力可以写成P_wind_t P_wind_pred_t ΔP_wind_t其中ΔP_wind_t为预测误差满足|ΔP_wind_t| ≤ ε_w * P_wind_pred_t。ε_w就是风电的最大相对预测误差典型取值为0.15到0.3。为了让模型可以在“完全不考虑不确定性”和“考虑最坏情况”之间连续过渡我给不确定集合加了一个预算参数Γ取值范围[0, 1]。当Γ 0时ΔP_wind_t只能取0模型退化为确定性模型当Γ 1时ΔP_wind_t可以在整个区间内任意取值模型保护的是最坏情况当Γ介于0和1之间时模型只保护一部分不确定性比如Γ 0.5时实际相当于要求系统应对一半预测误差上限的风光波动。这个设计非常直观也很容易通过循环扫描Γ值来观察成本变化趋势。这里需要补充一个实操经验有些文献把Γ定义成不确定时段的数量比如24个时段里最多有多少个时段同时发生最大偏差。我个人觉得这种方式物理直觉更强但实现起来要引入额外的0-1变量把问题变成两阶段鲁棒优化计算复杂度明显上升。而用[0,1]连续参数的方式不需要引入额外的二进制变量完美的兼容单阶段鲁棒优化的求解框架实现起来简单很多。如果你的核心目标是研究成本趋势而不是追求学术上的严谨性这个简化完全值得。2.4 最坏场景与对偶转化单阶段鲁棒优化的核心处理手段是对偶转化。原始问题是min-max结构外层最小化成本内层在给定调度方案下找到使约束最不利的风光出力实现。由于内层的风光不确定集合是简单盒式集合而且约束关于ΔP是线性的内层最大化问题可以被替换成它的对偶最小化问题从而把整个双层模型转化成一个等价的单层MILP。关于对偶转化我踩过一次很深的坑。最开始我把不确定参数直接暴力枚举成一堆场景塞进模型想着“多几个场景不就等于考虑不确定性了吗”。结果模型规模爆炸16台机组、24个时段、每个时段5个场景求解器跑了两个小时都没出结果。后来老老实实用对偶转化同一个问题Cplex十几秒就解完了。如果你不想手动推导对偶问题也可以用YALMIP的robustoptimize命令直接声明不确定变量让工具箱帮你做转化。但我还是建议自己至少手推一次因为理解了转化过程你才能真正判断模型里的变量阶次和约束是否满足强对偶条件出了问题也好排查。3. Matlab代码实现与核心环节3.1 数据准备与参数设置搞研究的第一步不是写代码是先把数据准备好。这个项目我用的数据集包括火电机组参数出力上下限、爬坡率、煤耗系数、启停成本、24小时负荷预测曲线、24小时风电预测出力曲线、24小时光伏预测出力曲线以及各个时段的风光相对预测误差系数ε。这些数据从哪里来给你几个实际可用的渠道如果是做论文复现IEEE标准测试系统的数据是最稳妥的网上搜“IEEE 30-bus system data”或者“IEEE 118-bus system data”能拿到一整套带火电机组参数的基准数据。风光出力曲线可以用某地区实际的历史出力数据归一化之后叠加到测试系统里。实在找不到用正弦曲线加随机扰动生成一组“看起来合理”的数据也没问题但要在论文里明确说明这是人造数据并注明生成方式。代码里我会把这些参数放到一个结构体里统一定义避免零散的全局变量到处飞。这里有一个小建议所有涉及单位的地方都统一成MW和$或元不要在代码中途换算单位团队协作和后期检查都会省很多力气。3.2 用YALMIP搭建优化模型的框架YALMIP是Matlab环境下的建模工具箱它对用户非常友好你用sdpvar声明变量、用binvar声明0-1变量然后把约束和目标函数一条一条写出来最后直接调optimize命令交给求解器。关键代码框架大概是这样的思路% 定义变量 P sdpvar(n_gen, T, full); % 火电出力 u binvar(n_gen, T, full); % 开机状态 startup binvar(n_gen, T, full); % 启动动作 shutdown binvar(n_gen, T, full); % 停机动作 R_up sdpvar(n_gen, T, full); % 向上备用 R_dn sdpvar(n_gen, T, full); % 向下备用变量声明是第一步也是最容易被忽视的一步。我在这个阶段犯过的错是忘了加full参数导致P变成了对称方阵后面所有约束的维度全部对不上报错信息还特别迷惑。后来我养成了一个习惯每次创建变量之前先在草稿纸上把变量的维度写清楚行是什么、列是什么再敲代码。约束定义用方括号拼装比如constraints []; % 功率平衡约束确定性基准工况 for t 1:T constraints [constraints, sum(P(:,t)) P_wind_pred(t) P_pv_pred(t) L_load(t)]; end3.3 备用约束怎么具体写成代码备用约束是模型的灵魂。我实际的实现方式是先让系统承诺一个基准出力点在这个基准点上考虑风光波动备用容量必须覆盖波动区间。具体到代码向上备用约束要覆盖风光出力向下波动的最坏情况for t 1:T total_up_reserve sum(R_up(:,t)); % 风光出力可能低于预测值的最大幅度 worst_down_deviation gamma * (eps_wind(t) * P_wind_pred(t) eps_pv(t) * P_pv_pred(t)); constraints [constraints, total_up_reserve worst_down_deviation]; end向下备用约束要覆盖风光出力向上波动的最坏情况for t 1:T total_dn_reserve sum(R_dn(:,t)); worst_up_deviation gamma * (eps_wind(t) * P_wind_pred(t) eps_pv(t) * P_pv_pred(t)); constraints [constraints, total_dn_reserve worst_up_deviation]; end注意这里的逻辑Γ越大需要覆盖的波动幅度越大系统需要配置的备用容量就越多成本自然上升。当Γ 1时系统需要应对风光的完全最大偏差当Γ 0时备用约束退化为只要求系统具备技术上的最小备用容量比如负荷的5%这时候成本最低但一旦实际风光波动稍微大一点系统就扛不住了。另一个关键约束是备用容量的物理可行性。火电机组预留向上的备用容量意味着它的实际出力要留出足够的调节空间for i 1:n_gen for t 1:T constraints [constraints, P(i,t) R_up(i,t) u(i,t) * P_max(i)]; constraints [constraints, P(i,t) - R_dn(i,t) u(i,t) * P_min(i)]; end end最后一个容易被忽略的约束是爬坡约束与备用的耦合。机组在t时段预留的备用容量在t1时段可能要真正兑现所以爬坡约束必须把备用容量考虑进去否则你会得到一套“理论上优雅、实际上根本无法执行”的调度方案。这个细节我一开始漏掉了后来用仿真去校验火电实际出力轨迹发现有些机组从t到t1的出力变化超过了物理爬坡极限整个方案等于白算了。3.4 不同鲁棒性参数的批量扫描核心研究目标是对比不同鲁棒性水平下的总成本所以在模型主函数之外我还写了一个扫描循环脚本gamma_list 0:0.1:1; cost_total zeros(length(gamma_list), 1); cost_reserve zeros(length(gamma_list), 1); cost_fuel zeros(length(gamma_list), 1); for k 1:length(gamma_list) gamma gamma_list(k); [cost_total(k), cost_reserve(k), cost_fuel(k), details] run_robust_dispatch(gamma); end这里我建议不要一上来就扫0到1步长0.05这样的高精度网格。先用0.1的步长跑一遍观察成本曲线的大致形状确认没有明显突变后再在拐点附近加密。这是做仿真实验的基本功先粗后细、先整体后局部。每次运行完还要检查求解器的退出标志。YALMIP返回的problem字段0代表求解成功1代表求解器遇到数值问题2代表问题无可行解。千万不要只在控制台看一眼结果就往下走一定要写代码主动检查problem值并保存日志否则你可能拿着一个根本没收敛的解去画论文里的趋势图白白浪费时间。3.5 结果输出与可视化结果可视化的核心是三条曲线总成本随Γ变化的曲线、各部分成本分解随Γ变化的曲线、机组组合方案随Γ变化的对比图。总成本曲线是最直接的研究结论横轴是鲁棒性参数Γ纵轴是系统总成本。理论上你会看到一条单调不减的曲线Γ越接近1成本越高。更有意思的是各部分成本的分解煤耗成本可能变化不大因为总出力水平基本由负荷决定真正变化明显的是备用容量配置成本它随着Γ近乎线性上升。机组出力曲线用堆叠面积图来画可以直观看出哪些机组在Γ增大时被强行拉高或压低出力。另外一个很实用的图是“机组开停机状态图”横轴是时段纵轴是机组编号用色块表示开机状态一眼就能看出不同Γ下的机组组合模式差异。我还习惯把结果导出一份Excel存档包含每个Γ下的所有决策变量值。这样后面写报告或者做敏感性分析就不用重新跑一遍模型了。4. 仿真结果分析与鲁棒性-成本权衡4.1 总成本随鲁棒性参数的变化曲线我先说结论趋势总成本曲线不是一条简单的直线而是一条先缓后陡的曲线。在Γ从0增加到0.3左右时总成本上升并不明显在Γ超过0.5之后成本上升速度明显加快。原因是备用容量的边际成本不是恒定的——系统先把成本最低的机组出力调整空间用掉代价很小当需要更多备用时就得让更多机组偏离经济工况点甚至额外启动一台机组边际成本就上去了。这个趋势本身就是一个很重要的研究结论盲目追求高鲁棒性性价比是递减的。如果研究环境的典型预测误差水平不超过20%那取Γ 0.5左右可能已经覆盖了大部分实际风险而成本只增加了10%出头如果硬要把Γ推到1.0成本可能飙升30%以上换来的保护却只是应对一个大概率不会发生的极限场景。4.2 各部分成本的分项拆解把总成本拆开看能发现很多有趣的现象。煤耗成本的变化有很强的非线性特征。Γ增大初期由于系统需要预留更多向上备用火电机组略微上调出力点煤耗成本小幅增加但到Γ足够大的时候系统可能直接多开一台小机组来分摊备用压力煤耗成本反而可能出现一个小的下降跳跃因为多开机组后单台机组的负载率下来了总煤耗未必上升。备用容量成本是一条清晰的上升曲线这是模型结构决定的没什么悬念。启停成本则往往呈现阶梯状变化因为开停机决策是0-1整数变量一次性跳变。我在分析结果时发现Γ从0.8到0.9时启停成本突然增加了一大笔点开机组状态图才发现为了满足更高的备用需求系统额外启动了一台之前一直处于停机状态的机组这比让在线机组继续承担备用的成本更低。惩罚成本在所有Γ取值下应该都是零这是鲁棒优化模型的性质决定的——只要模型有解理论上不应该出现失负荷和弃风的“违规”。如果你的结果里惩罚成本非零说明约束建模有bug或者惩罚系数设得太小被优化器利用了优先排查这两个方向。4.3 机组出力模式的变化分析我们来看机组侧的响应。随着Γ增大最直接的变化是机组群的出力分布变得更加“分散”。Γ较小时系统倾向于把大部分出力压在效率最高的大机组上小机组尽量少开这样煤耗最低但备用需求上来之后大机组的出力必须让出空间否则没有向上调节的余地于是小机组被启动分担一部分出力。还有一个微妙的变化发生在边界时段也就是负荷爬坡最快的那几个时段。Γ增大后机组组合方案会在边界时段多开一台机组而不是依赖在线机组的爬坡能力来应对负荷变化因为爬坡能力已经被备用容量占用了。这是机组组合分析里一个很经典的现象备用约束和爬坡约束之间存在资源竞争鲁棒性水平通过备用需求间接影响了系统应对负荷变化的灵活性。4.4 实际调度决策建议从调度运行角度这个模型给出的建议可以概括为三条一是设置一个可接受的失负荷风险概率阈值反查对应的Γ值用风险偏好来指导鲁棒性参数的选择二是标准化预测误差的统计计算用历史预测误差的分布特征来确定ε和Γ的合理范围而不是拍脑袋定三是把“备用容量成本曲线”纳入电力市场的辅助服务定价参考为备用容量的补偿标准提供理论依据。需要强调的是鲁棒优化给出的结果是一个“下限意义上的保证”它保证在这个鲁棒性水平下系统不会出现任何违规但这不代表更高的鲁棒性一定带来更高的实际收益因为现实中的不确定事件不一定落在最坏情况上。所以论文里要把这条逻辑界限写清楚审稿人和导师都特别看重这一点。5. 常见问题与排查技巧实录5.1 YALMIP建模报错与求解器配置如果你的模型在optimize阶段报错“No suitable solver for this problem class”多半是求解器没装好或者YALMIP没有正确识别到求解器。在跑代码之前先运行一句yalmiptest这个命令会列出YALMIP识别到的所有求解器。对MILP问题你需要保证Cplex或Gurobi在行列之中如果只有linprog和intlinprogMatlab自带的虽然也能跑小规模问题但求解速度慢得让人抓狂。另外提一个很多人不知道的细节Cplex和Gurobi都需要单独的许可证学术版用学校邮箱申请一般当天就下来了。别去搞什么破解版求解器厂商对学术用户很友好也可能引发版权风险走正规渠道才是最省心的。5.2 强对偶条件不满足导致的对偶转化错误最隐蔽的坑内层不确定参数和决策变量相乘导致约束非线性化没法直接对偶。比如你写了类似R_up * ΔP这种项就成了双线性项对偶转化直接失效。排查方法把所有约束里同时包含决策变量和不确定变量的项全部列出来逐一检查。鲁棒优化建模有一条铁律不确定变量必须和常数相乘不能和决策变量相乘。否则就退化成多阶段鲁棒优化问题需要改用CCG算法或Benders分解不是单阶段能搞定的。5.3 计算时间爆炸如果Γ扫描一次就要跑一个多小时问题基本出在目标函数里的二次项上。用分段线性化替换二次煤耗成本函数后MILP的求解速度会快一个数量级以上。具体做法是对机组出力区间分成若干段每段用线性函数逼近再引入SOS2约束或者用0-1变量做选择。分段数取4到5段成本函数拟合误差通常控制在0.5%以内。另外一个加速技巧是设置求解器的相对最优性间隙MIP gap tolerance比如设成1%而不是默认的0.01%。对研究趋势分析来说1%的间隙完全够用求解时间可能下降70%。5.4 不确定性参数设置的边界问题风光预测误差系数ε取多少直接决定了结果的可信度。不要拍脑袋取0.2就完事至少要做一次敏感性分析把ε从0.1扫到0.4看总成本曲线的形状变化。如果你的结论在ε变化时方向一致成本随Γ单调上升那这个结论就是鲁棒的如果趋势出现反转说明你的结论对数据太敏感需要重新审视模型的合理性。还有一个细节光伏和风电的ε要分开设置光伏的正向误差实际出力高于预测在中午可能很大负向误差实际出力低于预测在早晨和傍晚更常见。用同一个ε会掩盖两者时间分布上的差异。5.5 从单时段扩展到多时段/多区域的思考如果你后面想把模型扩展到更大规模的系统比如多区域互联电网每个区域有自己的风光接入点那么不确定性集合就需要写成“多个盒式集合的笛卡尔积”。这时候对偶转化的复杂性会显著上升我会建议切换到两阶段鲁棒优化列与约束生成CCG的框架这才是目前学术界的标准做法。单阶段鲁棒优化适合验证思路、跑通流程但做深入研究和投稿还是得上CCG。我自己是在单阶段跑通之后又花了大概两周时间把代码重构到CCG框架里。重构的过程很痛苦但收益也很大主要体现在计算效率和对不确定性集合的表达能力上。如果你目前还在课程设计或者毕业设计阶段先把单阶段版本吃透就够了。结尾这套Matlab代码我从搭模型到跑出完整的Γ扫描曲线前前后后折腾了将近一个月。回头看最花时间的不是建模也不是代码本身而是参数之间的耦合关系没理顺备用容量约束和爬坡约束打架、目标函数线性化和求解精度冲突、Γ的物理含义和模型形式不匹配。好在这些坑都一个个填平了现在跑一遍全流程只需要十几分钟改几组参数就能迁移到别的测试系统上。如果你在复现过程中卡在某一步尤其是对偶转化或者备用约束的定义上别硬扛把你的报错信息和约束定义截下来对照第2节和第5节的内容反复检查绝大多数问题都能解决。希望这份分享能帮你少走一点弯路把精力花在真正有意义的结果分析上。
返回列表