资讯动态

梯级水光互补调度模型复现:MILP建模与光伏不确定性场景生成实战

发布时间:2026/9/30 3:50:04 来源:尧图企业网站定制
最近一直在做“梯级水光互补系统最大化可消纳电量期望短期优化调度模型”的复现工作项目标题带着“[EI复现]”四个字拿到手的时候我就知道这事不简单——里面既有梯级水电复杂的上下游水力耦合又有光伏出力的随机性还要在期望意义下最大化可消纳电量三者叠在一起建模和代码都不是随便套个优化工具箱就能糊弄过去的。这篇文章把我从读原文、搭模型、写Matlab代码到调试跑通的完整过程整理成文。内容包括模型的核心数学结构、光伏不确定性场景怎么生成、为什么把问题写成混合整数线性规划而不是启发式算法、关键约束的建模细节、完整可参考的Matlab代码骨架以及复现过程中踩过的坑和排查方法。不管你是准备做EI论文复现的研究生还是想在水光互补调度方向起步的工程师这篇文章应该能帮你少走不少弯路。1. 项目到底在做什么问题拆解与价值判断1.1 梯级水光互补系统的物理背景先弄清楚被调度对象长什么样。所谓梯级水电站就是一条河上串联了多个坝址上游电站的出水会作为下游电站的入流形成上下游之间严格的水量延时耦合。在这条流域范围内再叠加集中式光伏电站就构成了“水光互补”系统光伏出力高时水电机组少发或者多蓄水光伏出力低时水电机组加大出力顶上。梯级的意义在于上游放水不只是为了满足自身发电还要为下游创造发电条件所以调度上不能只看单站必须全局统筹。这类工程在西南地区非常常见——大江大河上修一串梯级电站周围山头上铺满光伏板。电网给这种综合能源基地留的消纳通道是有限的尤其在汛期和午间光伏大发时段重叠的时候通道容量就成为瓶颈。我们的模型要回答的核心问题就变成了在未来一天光伏出力不确定的前提下梯级水电站的开停机计划、出力曲线和库容蓄放策略怎么安排才能让整个系统“期望上”可消纳的上网电量最大。1.2 最大化可消纳电量期望意味着什么注意标题里三个关键词“最大化”“可消纳电量”“期望”。很多人第一眼看到这个题目容易把它当成普通的发电量最大化其实不是。“可消纳”三个字说明存在一个外部约束通常是外送通道能力、电网接纳上限或负荷需求曲线。系统能不能把电卖掉、送出去取决于通道容量。所以模型不是单纯让水电站发出多少电而是让发出的电里能被网架接纳的部分最大化多余部分要么弃水要么弃光。“期望”两个字则是因为光伏出力是随机变量。短期预报不可能是完美的我们不能只拿一条确定性光伏预测曲线去优化而要考虑各种可能场景下的期望效果。在数学上就要把目标函数写成对随机变量的期望通常通过场景集来近似生成N个光伏出力场景每个场景下系统作相应调整目标函数取各场景的概率加权平均。这就把随机优化问题转化成了可计算的确定性等价问题。1.3 EI复现的正确打开方式EI复现这件事很多同学把它理解为“把论文的公式抄一遍把程序跑通”。作为做过几轮这种复现的过来人我的建议是真正有价值的复现至少要包含三层。第一层是复现数值结果也就是论文里的算例曲线、指标数据能用代码重新生成出来偏差在合理范围内第二层是复现模型结构包括决策变量是什么、目标函数权重怎么取、约束哪些是硬约束哪些是软约束、场景集怎么构造第三层是复现工程可移植性也就是把模型抽象成一套输入输出清晰、可以换数据、换参数、换求解器的代码框架而不是写死一套只对该论文有效的脚本。这个项目我做的目标就是第三层。代码没有绑定某一个求解器自带的建模语法而是用通用的YALMIP建模再调用商业求解器求解这样既方便EI复现结果对比也方便后续扩展成别的调度模型。而且整个程序分模块写数据、场景、约束、求解、绘图彼此解耦这是我认为最值得保留的习惯。2. 核心建模思路与原理细节2.1 系统拓扑与拓扑等效建模第一步是确定系统等值拓扑。我用的方案是流域内共I级梯级水电站记为i1,2,…,I每个站配置若干台可调机组光伏电站作为另一个并网电源和水电站一起接入同一升压站再通过一条外送通道送往主网。在这个拓扑下关键物理关系有三个。第一个是水量平衡上一级电站的出库流量经过一定时间延时短期日内调度通常简化为一小时内的部分延时或忽略延时成为下一级电站的入库流量。第二个是出力关系水电站出力由发电流量和水头决定严格来说是一个双线性关系P η × ρ × g × Q × H。双线性会让模型非凸工程上常用分段线性化或固定水头近似处理。短期调度模型一般假设水位-库容曲线、尾水位-出库流量曲线均为分段线性再用McCormick松弛或二元展开来线性化。第三个是通道约束所有电源出力之和不大于通道容量这个约束本质上是“可消纳”的数学表达。2.2 光伏出力的不确定性场景建模光伏出力是随机量原始的确定性预测曲线只是一个基准场景。为了表达“期望”需要一套场景生成技术。我在代码中采用了拉丁超立方采样(LHS)加Cholesky分解两步法首先基于预测误差分布通常假设服从截断正态分布或Beta分布对各时段偏差进行抽样然后用LHS保证样本在概率空间上均匀覆盖如果光伏电站之间有历史相关性再用Cholesky分解把独立抽样转换成具有指定相关系数的样本。场景数N不能太大。N100时MILP问题规模会显著增大N500时求解时间可能完全无法接受。实际复现中我用N50作为平衡点——期望值已经收敛到稳定性较好的范围并且求解器能在十几分钟内给出全局最优解。计算出每个场景的发生概率时一种简单做法是等概率即p_s1/N更精细的做法是用场景削减技术如快进选择法把500个原始场景削减成50个代表场景并重新分配概率代码里两种方式都保留方便对比。2.3 梯级水电运行约束梯级水电是约束最密集的部分逐条梳理如下库容动态平衡约束。每个水库每个时段遵循蓄水量变化等于入库流量减去发电流量与弃水流量之差。对梯级上游i而言入库流量来自天然径流对下游i1而言入库流量还包含上一级的发电流量和弃水流量。这个约束把“梯级”的核心耦合关系表达得清清楚楚。库容和流量上下限约束。水位/蓄水量上、下限对应防洪和生态要求发电流量上下限对应机组技术特性弃水流量是决策变量允许模型在必要时弃水。这类约束看似简单但常常是模型无解的重要来源——比如初始库容给得过高而后续天然来水又很大库容上限和强制出力的下限组合导致没有可行解。机组运行约束包括启停逻辑、最小运行/停机时间、出力爬坡速率。如果要精细到机组级就需要引入机组组合变量问题变成混合整数问题。短期调度尺度如96时段下机组组合是必需的因为梯级水电站夜间光伏为零为了给白天留库容夜间可能需要关停部分机组。外送通道约束和电力平衡。任意时段所有电源出力之和不超过通道容量C_max同时不低于某个技术出力下限。如果系统还配有抽水蓄能那抽水负荷相当于额外可调负荷但本项目重点在梯级水电光伏所以负荷侧约束简化为固定消纳上限。2.4 目标函数与惩罚机制目标函数写为最大化可消纳电量的期望。在一段时间范围T内定义每个时段的系统上网功率为水电机组总出力加光伏出力减弃光量再乘时段长度即为该时段电量。取期望时对每个场景求和并按概率加权。为了避免目标函数中出现“弃电越少或者发电越多”的含糊博弈我推荐在目标函数中增加一个很小的惩罚项例如弃水流量和弃光量的二次惩罚系数数量级为10^-4到10^-3。这样一个微小惩罚不会改变最优解的主要方向却能让求解器在多个近似最优解之间稳定选择避免解在连续几天调度里的抖动。这是我在复现过程中自己补进去的trick原文未必写但对数值稳定性非常有用。之所以用“最大化期望”而不是“最大化最坏场景”是因为两者对应的鲁棒优化模型难度完全不同。期望模型是MILP可以直接调求解器最坏场景模型需要引入鲁棒对等转换会大幅增加变量和约束数量。EI论文里通常做期望模型复现时不要自己给自己加难度先保证期望模型跑通再做扩展。3. 算法选型与求解器配置3.1 为什么选MILP框架梯级水光互补短期优化调度本质上是一个大规模混合整数线性规划。决策变量包括各时段机组开停机状态0-1整数变量、水电出力、发电流量、弃水量、库容变量连续变量目标函数和约束在做了线性化处理后全部是线性表达式。为什么不用动态规划因为梯级水库的联合状态空间会爆炸存在著名的“维数灾”问题三级以上梯级的联合状态就非常夸张离散水位一多内存和时间都无法接受。为什么不用遗传算法这类启发式因为它们难以保证全局最优而且EI复现要求结果与原文对比非确定性算法跑出的数值不具备严格的可重复性。MILP配上商业求解器每次运行得到的都是全局最优解具备严格的重复性这也是论文复现场景里最稳妥的路线。3.2 求解器选型与参数调节我在Matlab环境里用YALMIP作为建模层求解器选了Gurobi和CPLEX两个都试过。对典型的48时段或96时段模型场景数50水电站5级机组状态变量约5×96480个0-1变量约束约上万条。Gurobi在默认参数下就能在几分钟内求解CPLEX通常也不差。几个经验性的求解器参数给你参考设置MIPGap为0.01%也就是要求解落在全局最优的0.01%范围内保证复现结果与论文一致开启求解器多线程一般8到16线程对MIP加速非常明显设置TimeLimit为1800秒防止个别数据组合导致卡死对于对称性强的模型开启symmetry breaking通常有帮助。这些参数在YALMIP里通过sdpsettings传入即可。例如ops sdpsettings(solver,gurobi,verbose,2, ... gurobi.MIPGap,0.0001, ... gurobi.TimeLimit,1800);3.3 变量规模与计算复杂度估算写程序前先算一下问题规模很关键。假设T96时段、I5个水电站、每站1台可调机组、N50个光伏场景。由于场景只影响光伏出力取值不会扩增决策变量数量这里有个重要区别如果采用“期望值替代法”决策变量不与场景耦合问题规模小但精度差如果采用“场景耦合法”也就是每个场景下决策变量都不同那么变量数要乘N规模直接到50倍一般不可解。实际上大多数论文用的是第一种思路或者做一个很不错的折衷水电调度决策是日前固定的不随日内光伏场景变化而光伏弃光的决策是随场景变化的。这样主线决策变量保持一套只有辅助决策变量随场景变化。用数学语言说这是两阶段随机规划的处理方式第一阶段决定水电开机、出力基准和库容策略第二阶段根据光伏场景决定弃光量以平衡偏差。这种结构是复现中最容易写错的点。代码里如果有地方把水电出力变量也按场景展开那模型规模瞬间会爆炸。我实际测试下来96时段×5电站×50场景的水电出力全展开模型变量会到50万量级求解会非常吃力而采用两阶段结构后主线变量只有大约几千个求解难度低了一个数量级。4. Matlab代码实现与实操记录4.1 代码整体目录结构我建议的代码目录如下字段用英文或拼音均可main.m % 主程序入口 data_load.m % 读取基础数据径流、光伏、通道容量 scenario_gen.m % 生成光伏出力场景 model_setup.m % 构建YALMIP变量与目标函数 build_constraints.m % 添加约束 solve_model.m % 调用求解器 result_plot.m % 绘图与数值输出main.m的逻辑初始化参数调用data_load加载数据调用scenario_gen生成场景调用model_setup/build_constraints建模调用solve_model求解最后用result_plot输出图表。这种结构的好处是每个环节独立可测改一个部分不会牵连其它部分调试的时候能快速定位是哪一类约束出了问题。4.2 数据准备与处理数据是复现的第一道关卡。需要准备如下基础数据表数据项符号示例规模天然径流Q_natT×I 矩阵预测光伏归一化出力P_pv_forecastT×1光伏预测误差标准差sigma_pvT×1库容上下限V_min, V_maxI×1发电流量上下限Q_min, Q_maxI×1通道容量C_max标量初始库容V_initI×1特别提醒数据单位必须统一。我见过太多复现报错是因为电量用了MWh流量用了m³/s而库容用了亿m³几个单位一混水量平衡约束根本对不上。我的做法是流量统一转成m³/h库容统一用m³时段长度按秒计算最后电量由功率×时段长度得到天然一致。数据读入后直接在Matlab里做一轮assert检查把量纲错误挡在建模之前。4.3 场景生成模块核心代码下面给出scenario_gen.m的核心片段这段代码用了LHS生成误差可控的场景方便直接迁移function [pv_scen, prob] scenario_gen(pv_forecast, sigma, N, T) % 输入预测曲线、标准差向量、场景数、时段数 % 输出N×T矩阵的光伏场景和等概率权向量 prob repmat(1/N, N, 1); % 等概率场景 pv_scen zeros(N, T); for t 1:T % 每个时段独立进行拉丁超立方采样 u lhsdesign(N, 1); % N个[0,1]均匀样本 % 用正态反函数转换为误差截断在[-3σ,3σ] err norminv(u, 0, sigma(t)); err max(-3*sigma(t), min(3*sigma(t), err)); pv_scen(:,t) max(0, pv_forecast(t) err); end % 规范化到光伏装机容量以内 pv_scen min(pv_scen, 1); end需要说明的是如果论文里考虑了光伏时段间自相关性上面“逐时段独立采样”是不够的。严格做法是对整个T时段误差向量按协方差矩阵做Cholesky分解后耦合采样。我这里先给出简单的独立版本跑通流程相关性版本代码量会再增加几十行思路是一致的先生成独立标准正态矩阵再左乘协方差矩阵分解得到的下三角矩阵即可。4.4 约束建模与MILP求解代码model_setup.m的核心是定义决策变量。决策变量定义参考% 水电连续变量 P_hyd sdpvar(I, T); % 水电出力 Q_flow sdpvar(I, T); % 发电流量 Q_spill sdpvar(I, T); % 弃水流量 V sdpvar(I, T1); % 库容变量 % 机组状态0-1变量 u binvar(I, T); % 机组运行状态 % 弃光电量变量每个场景下不同 curtail_pv sdpvar(N, T);构建约束时重点看这几个% 水量平衡逐级耦合 for i2:I V(i,t1) V(i,t) dt*Q_nat(i,t) dt*(Q_flow(i-1,t)Q_spill(i-1,t)) ... - dt*(Q_flow(i,t)Q_spill(i,t)); end % 通道消纳约束场景耦合 for s1:N sum_hyd sum(P_hyd(:,t)); Constraints [Constraints, sum_hyd pv_scen(s,t) - curtail_pv(s,t) C_max]; Constraints [Constraints, curtail_pv(s,t) 0]; Constraints [Constraints, curtail_pv(s,t) pv_scen(s,t)]; end % 水电出力与发电流量关系线性化系数K近似 P_hyd(i,t) k(i) * Q_flow(i,t); % 机组启停约束与出力限制 for i1:I for t2:T Constraints [Constraints, ... P_hyd(i,t) P_max(i) * u(i,t), ... P_hyd(i,t) P_min(i) * u(i,t)]; % 这里还可以继续加最小启停时间约束代码略示意为主 end end目标函数写成Objective -sum(sum(P_hyd)) * dt ... - sum(sum(curtail_pv .* repmat(prob,1,T))) * dt ... - 1e-4 * sum(sum(Q_spill)) * dt; % 注意YALMIP默认最小化所以对最大化目标取负号这行代码是很多人容易搞混的地方YALMIP的optimize默认是求最小值所以我们要么用objective -原始目标要么在optimize参数里指定目标方向。我习惯直接取负号逻辑简单明确。此外弃光电量目标要乘场景概率再汇总代码里用repmat把概率向量扩展成与场景矩阵相同尺寸相乘后求sum就得到期望。4.5 结果输出与约定result_plot.m里我通常输出三类图日前计划瀑布图每时段显示水电出力、光伏出力、弃光电量的堆叠情况能直观看到通道是否被充分利用库容过程线每个水库的库容变化曲线检查是否安全落在上下限内多场景消纳结果箱线图统计50个场景下可消纳电量的分布展示期望值和中位数的关系。数值指标方面建议输出总可消纳电量期望值、弃光电量期望值、弃水电量期望值、通道利用率可消纳电量/通道容量×时段数。这几个指标是EI论文里最常见的对比依据也是复现结果和原文对表的重点。如果通道利用率长期接近100%说明模型的瓶颈约束已经完全激活验证逻辑是自洽的。5. 常见问题与排查技巧实录5.1 “无解”怎么排查MILP模型报Infeasible是复现期最糟心的事。我的排查顺序是先检查量纲和符号。库容初始值在区间内吗发电流量正向吗场景里的光伏出力有没有超过装机上限我建议建模过程中每添加一类约束就用一个小规模数值测试一次而不是全部写完再一起试。比如跑一个只有两步时段、单个水电站的mini模型无解问题立刻缩小范围。再检查水位库容曲线和水头处理。如果水头和库容的关系是非线性的而线性化区间覆盖不到初始库容对应的范围约束可能会在一个“空档”里变得矛盾。把库容分区数从5段涨到20段通常能解决这类问题。最后检查通道约束和弃电变量的冗余性。有一个常见错误通道容量约束里把光伏直接等效成了P_pv而没考虑弃光变量导致光伏大发时段约束硬性违反模型直接报无解。弃光变量存在的意义就是“软性”调整给它留足够的范围0到光伏出力再配合惩罚项无解概率会大幅下降。5.2 求解时间过长怎么办如果模型变量上了1万求解时间超过半小时优先考虑三件事减少0-1变量数。把96时段机组状态合并为几个典型分段例如每4小时一个状态段机组变量数直接除以4减少场景数。把50场景削减到20场景做预求解看目标函数值变化是否超过2%如果不大就用20场景正式跑给MIPGap松一点。EI复现不需要0.0000%的绝对最优0.1%以内一般不影响结论。还有一个非常实用的加速trick先求解一个连续松弛问题把得到的解作为MIP的初始可行解warm start喂给求解器可以让整数变量的分支定界过程大幅收敛。具体就是先去掉u的0-1约束求解一次把u的连续值round成整数解再作为启动点传入。由于水电调度问题机组状态数量少这个trick非常高效。5.3 复现EI论文的避坑清单最后给一张避坑速查表坑表现解决办法目标方向反了结果全是0或离谱小值检查YALMIP默认最小化电量算错数值差几个数量级统一时间单位确认dt用秒还是小时梯级水力延时被忽略但论文明确写了库容过程线严重畸变加延时矩阵典型1到2小时场景概率没有归一化目标函数值异常大确认prob总和为1机组启停约束缺失出力曲线频繁震荡添加最小启停时间约束这张表是我在复现周期里被现实毒打后的积累每一条都对应过具体半夜调bug的经历。尤其是“目标方向反了”这条我第一次写YALMIP时吃过亏后来直接在main里打印一个随机可行解的目标函数符号确认无误后再提交正式求解。6. 一些个人体会与可以玩的方向做完这个复现我的一个很深的体感是梯级水光互补调度模型的难点不在某个单一约束而在于“梯级”和“随机”两个词叠在一起带来的建模惯性问题。千万不要一上来就把所有东西都精细化先跑通一个简化的确定性模型再逐步加入随机场景最后才加机组组合整数变量。这种“由简到繁”的路径最适合复现类项目也最适合带学生上手。另外一个可以继续玩的方向把期望模型扩展成分布鲁棒优化也就是认为光伏出力的真实分布属于一个以经验分布为中心的模糊集目标变为在最坏分布下最大化可消纳电量期望。这种模型在近年的EI/SCI文献里比较热门而且它的约束形式和当前MILP差别不大只是在目标函数里多了一个对偶变量维度。如果你已经把现在的确定性等价MILP代码调通了那么扩展成分布鲁棒优化只需要在目标函数和约束里增加与模糊集半径相关的项代码基础完全可以复用。最后再分享一个小经验所有边界条件的数值检查不要靠肉眼盯着Excel表去看把数据读入Matlab后直接写一行assert语句检查上下限比如assert(all(V_init(:) V_min(:)) all(V_init(:) V_max(:)))。这行assert能在模型跑飞之前帮你拦截掉90%的低级错误。如果你正准备复现类似的论文别嫌前期数据检查繁琐——把基础打牢后面跑通整条模型是水到渠成的事。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑