ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

梯级水电调度期望最大化模型:光伏消纳与MILP求解实践

梯级水电调度期望最大化模型:光伏消纳与MILP求解实践 简介一份面向电力系统优化调度方向的毕业设计参考资源源于《梯级水光互补系统最大化可消纳电量期望短期优化调度模型》论文针对梯级水光互补短期调度问题提供了可复现论文模型的完整源程序。压缩包共4个文件含2个MATLAB脚本分别用于主程序运行与结果展示、1份代码说明PDF及1张结果示意图整体仅2.15MB结构紧凑、易于查阅。程序以可消纳电量期望最大化为目标以机组为最小调度单位精细建模电站、机组及电网约束并通过分段线性逼近、0-1整数变量、发电水头离散等方法将非线性模型转换为混合整数线性规划借助CPLEX求解算例参考西南某流域4个梯级水电站15台机组与2个光伏群可帮助读者理解不确定性建模与MILP求解思路。已有161人学习适合正在开展水光互补、新能源消纳或优化调度相关课题的高年级本科生和研究生通过代码说明与运行结果可快速掌握模型构建和求解流程。1. 梯级水电调度做「期望最大化」先解决光伏消纳账怎么算光伏出力不确定最常见做法是把预测坐实、把误差压小但预测再准也有爬坡和云团遮挡真正能用的是梯级水电的库容和时滞。这个模型把目标直接设为「可消纳电量期望最大」意思是调度方案不是追求某一条预测曲线下的最优而是对一组光伏出力场景做加权平均后的整体最优。这里有个反直觉结论未必每个场景都跑出最大消纳但期望值最大时梯级负荷的跨时段调配会变得异常灵活水电的电网支撑和光伏互补双重角色都会被激活。适合正在做水光互补调度、毕设方向是新能源消纳或梯级优化以及想把 MILP 建模落到 CPLEX 上的读者。下面按模型、线性化、求解、验证四个层面拆开讲。2. 可消纳电量期望目标与机组级约束体系2.1 目标函数为什么是「可消纳电量期望」并带场景概率常规调度模型常用「发电量最大」或「运行成本最小」但这个资源面向的是消纳口径目标函数要回答的是给定梯级水电和光伏的总送出能力在光伏波动的情况下系统最多能把多少电量真正送入电网。由于光伏出力是不确定的消纳电量就是随机量直接最大化随机量没有意义因此用期望值作为目标即对场景 s 的概率 p_s 和该场景下的可消纳电量 E_s 做加权求和max sum_s p_s * E_s其中 E_s 与该场景下水电出力、光伏出力、负荷需求和弃电惩罚项有关。常见做法是把弃电按惩罚系数放进目标这样模型会主动规避光伏高发时段的送出阻塞。注意场景概率 p_s 是外部输入可以由历史光伏出力聚类或者拉丁超立方抽样生成模型本身不负责生成场景。2.2 电站约束、机组约束和电网约束都做了什么原模型的最小调度单位是机组而不是电站这一点决定了建模粒度。电站级建模会把整个电站看成一个黑箱忽略机组间的水头差异、振动区差异和启停成本结果在低水头工况下容易给出不可行的机组组合。机组级建模则把每台机组的发电流量、出力上下限、振动区、最小开机时间和最小停机时间都显式表达出来约束体系按三层组织约束层级典型约束表达方式电站层水量平衡、库容上下限、出库流量限制、坝前水位-库容关系连续变量等式/不等式机组层出力上下限、发电流量上下限、振动区规避、启停逻辑、最小开停机时间混合整数约束电网层梯级总送出功率上下限、断面潮流限制、光伏消纳上限聚合功率不等式水位-库容关系和水头-耗水率关系是非线性的这部分留到第 3 章处理。电网层的约束不是简单把各电站出力相加而是要考虑梯级电站之间的输电通道容量以及光伏群接入点的注入功率限制。在西南流域的场景里光伏群通常接入梯级中某个电站的升压站因此电网约束要按接入点分别建而不是全流域统一一个断面。2.3 光伏不确定性场景集合怎么进入约束把光伏出力不确定引入模型不是简单地在目标函数里对 p_s 加权而是在每个场景下都要满足系统平衡约束和电网约束。也就是对每个场景 s有P_hydro(s,t) P_pv(s,t) P_dis(s,t) P_load(t) P_curtail(s,t)其中 P_dis 表示水电弃水对应少发的功率P_curtail 表示光伏弃电功率这两项在目标里是被惩罚的对象。换句话说模型在可行域内自动寻找「在哪些时段稍微少发水电、在哪些时段接受光伏弃电」的折中方案而不是把每个场景的等式约束都严格卡死。这个处理非常重要如果每个场景都要求等式严格成立且不允许弃电那模型会为了极端光伏场景而大幅牺牲正常场景的消纳量期望值反而下降。所以可消纳电量期望最大这个目标实际上容忍了部分场景下的合理弃电前提是它在所有场景的加权期望上不吃亏。2.3.1 场景数量的选择逻辑场景太少期望值估计偏差大优化结果在真实光伏波动下可能明显偏离预期场景太多MILP 规模成倍增长主机求解时间可能从分钟级变成小时级。实际案例里 10 到 20 个场景是比较常用的区间每个场景覆盖一天 96 个时段15 分钟分辨率或者 24 个时段1 小时分辨率取决于调度周期的时间颗粒度。3. 线性化降维分段逼近、0-1 变量与水头离散3.1 水电出力函数的分段线性逼近水电机组的出力并非发电流量和发电水头的线性函数典型表达式为 P 9.81 * η * Q * Hη 随工况变化H 随水库水位变化直接进 MILP 一定会破坏线性结构。工程上最常见的手法是分段线性逼近把发电流量 Q 的可行区间切成若干段每段内假设单位耗水率近似常数用 SOS2Special Ordered Sets of type 2或者一组连续变量和 0-1 变量来表达分段选择逻辑。构建逼近时需要确定分段点和对应的出力-流量折线。对每台机组给定最小技术出力对应的流量和满发流量中间插入 3 到 5 个分段点段数太少逼近误差大段数太多会增加 SOS2 约束的数量求解时间上升。生成约束的 MATLAB 代码逻辑如下% 对机组 i 在时段 t 建立分段线性出力-流量关系 % x: 发电流量, y: 出力, breakpoints: 单调递增的分段点 nSeg length(breakpoints) - 1; lambda sdpvar(1, nSeg); for k 1:nSeg % 每段对应一个权重变量 lambda(k) Q_expr Q_min sum(lambda(k) * (Q_break(k1) - Q_break(k))); P_expr P_min sum(lambda(k) * (P_break(k1) - P_break(k))); end SOS2(lambda);这段代码的核心思想是把连续流量变量拆成多个分段权重SOS2 约束保证最多只有相邻两个权重非零从而把流量点和出力点同时限定在同一条折线段上。这里用 sdpvar 只是示意实际接入 CPLEX 时需要把 lambda 声明成 IloNumVar 并显式添加 SOS2 约束。分段点的选取要考虑机组的运行特性水头高时同样的流量对应更高的出力因此分段点不能只设一组最好按水头区间分别标定。3.2 0-1 整数变量处理振动区和启停逻辑机组振动区是水电机组调度里避不开的坑。振动区内运行会造成机组剧烈振动和空蚀实际调度中要么避开要么快速穿越。建模上振动区约束是非凸的必须用 0-1 变量把允许运行区间拆成互斥的子区间。假设机组 i 在时段 t 的出力为 P(i,t)振动区为 [P_low, P_high]那么需要引入二进制变量 b(i,t,1)、b(i,t,2)P(i,t) P_min * b1 P_high * b2 P(i,t) P_low * b1 P_max * b2 b1 b2 z(i,t) % z(i,t) 为开机状态其中 b1 和 b2 不能同时为 1这样出力要么落在振动区以下的低负荷区间要么落在振动区以上的高负荷区间不会出现跨振动区取值的连续解。这是典型的「大 M 法 0-1 变量」拆非凸可行域的做法。如果机组有多个振动区就按同样的方式插多个二进制变量公式不复杂只是变量数量线性增加。启停逻辑的约束则以最小开停机时间为主常见表达是z(i,t) - z(i,t-1) z(i,tau) % 启动后至少运行 T_on 时段 z(i,t-1) - z(i,t) 1 - z(i,tau) % 停机后至少停 T_off 时段这类约束在 MILP 里是标准的但要特别注意如果目标里没有开停机成本项模型会倾向于频繁启停来钻约束空子因此目标函数中加入启停惩罚是很有必要的。3.3 发电水头离散把双线性项拆成选择逻辑出力函数中流量 Q 和水头 H 以乘积形式出现即双线性项直接线性化会引入大量辅助变量。作者在水头维度做离散把可能的水头范围分成若干个区间每个区间对应一个代表水头值再引入 0-1 变量表示机组当前处于哪个水头区间。具体做法是sum_k y(i,t,k) z(i,t) % 开机时只能选一个水头区间 H_hat(i,t) sum_k H_rep(k) * y(i,t,k) % 用区间代表水头近似实际水头 P(i,t) sum_k (a(k) * Q(i,t) b(k) * y(i,t,k)) % 各水头区间下出力-流量线性关系离散水头区间数量通常取 3 到 5 个。区间太粗出力-流量关系失真调度结果在真实水头下可能不可行区间太细y 变量数量增多分支定界树变大求解时间明显变长。这里有个工程技巧先用水头变化平缓的日期序列做预分析统计水头实际波动范围把区间边界设在概率密度较高的位置而不是均匀划分。4. MATLAB 数据流与 CPLEX 求解实现4.1 main.m 的调度流程与数据组织main.m 负责把电站参数、机组参数、光伏场景和负荷曲线组装成可供求解器消费的形式。由于 CPLEX 的 Java API 在 MATLAB 里调用需要额外配置 javaclasspath常见做法有两个一是直接在 MATLAB 里用 cplex 工具箱函数构建模型二是把数据写成 LP 格式或 MPS 格式在 Java 程序里读取并求解再把结果写回文本文件。后者的好处是模型构建和求解完全解耦出问题时可以单独用 CPLEX Studio 打开 LP 文件排查。main.m 的核心流程并不复杂% 按日调度周期96时段构建模型数据 spills []; % 弃水序列 p_pv load(pv_scenarios.mat); % 光伏场景集 for t 1:T for i 1:Nunit % 机组出力变量 P(i,t)0-1启停变量 z(i,t) % 振动区约束按 3.2 节的大M法生成 end % 汇总梯级总出力约束所有电站 光伏群的总送出功率限制 % 目标sum(p_s * E_s) - 惩罚项数据组织的关键是按「时段 × 机组」的二维结构给所有变量编号。CPLEX 的索引从 0 开始MATLAB 从 1 开始转换时很容易错位所以建议在 MATLAB 里预先分配好变量 id 矩阵把变量编号映射关系存入结构体后续无论写 LP 文件还是调 Java 接口都能直接引用。我在实际项目中会在构建约束矩阵的同时打印「变量名 → 列索引」的映射表到文本文件求解结果异常时对照检查比在黑盒里猜高效得多。4.2 CPLEX 求解参数与终止条件设置模型转成 MILP 后求解质量很大程度上取决于参数配置。剪枝策略、可行解提升polish和 MIP gap 容忍度三项最重要。穿靴戴帽的顺序建议是先用默认参数跑一次完整求解观察 gap 下降曲线和节点数然后针对性地收紧分支策略。常用参数表如下参数典型值说明MIP gap tolerance0.01 或 0.0051% 的 gap 在消纳电量评估中完全够用强求 0.1% 会让求解时间翻数倍node limit / time limit3600 秒大规模场景下必须限时否则分支定界树可能失控mip emphasis3隐藏可行解优先模式适合快速拿到可用调度方案threads8 或物理核数的一半线程数不是越多越好超过 16 线程加速比明显下降start algorithmbarrier根节点松弛用 barrier 往往比单纯形更快尤其模型含大量 SOS2 约束时求解代码在 Java 端的样子大致如下IloCplex cplex new IloCplex(); IloNumVar[] P new IloNumVar[nUnit * T]; // 添加目标sum_s p_s * E_sE_s 由 P_pv(s,t) P_hydro(s,t) 组成 cplex.addMaximize(objectiveExpr); cplex.setParam(IloCplex.Param.MIP.Tolerances.MIPGap, 0.01); cplex.setParam(IloCplex.Param.TimeLimit, 3600); cplex.setParam(IloCplex.Param.Threads, 8); if (cplex.solve()) { double[] result cplex.getValues(P); // 写出 P(i,t) 和 z(i,t) 到 solution.csv }这里把目标函数、约束构建隐去保留参数设置的结构因为每条约束的构建代码会非常长。关键点是IloCplex.solve() 返回布尔值只代表找到了可行解并达到 gap不代表模型已经最优。所以拿到解之后要额外读取 cplex.getMIPRelativeGap()如果超过 5% 就要回头检查场景数量或者约束是否过紧。另外 CPLEX 的 warning 输出里出现「No solution found」时优先检查变量上下界是否出现 Min Max 的矛盾这种问题通常来自水头离散区间和出力上下限的不一致。4.3 show_result.m 的结果回读与可视化show_result.m 主要负责读取求解结果文件并绘制几类关键图梯级各电站的出力过程线、库水位变化曲线、光伏消纳情况以及弃电功率曲线。读取结果的核心代码逻辑如下% 读取CPLEX或Java程序写出的 solution.csv % 文件格式: t,i,P_hydro,z,P_pv,P_curtail data readmatrix(solution.csv); t data(:,1); P_hydro data(:,3); P_curtail data(:,6); % 绘制某电站总出力与光伏出力的互补关系 figure; plot(t, P_hydro, LineWidth, 1.5); hold on; plot(t, P_pv, LineWidth, 1.5); xlabel(时段); ylabel(出力/MW); legend(梯级水电出力, 光伏出力);可视化更重要的是验证约束是否被满足而不只是看曲线的形状。我会额外绘制一个「弃电时段标注」的子图把 P_curtail 非零的时段用散点标出对比这些时段的光伏出力曲线确认模型是否在光伏高发且负荷低谷时正确地选择了弃电而不是随机弃电。另一个验证点每个时段各电站出力之和必须落在梯级总送出功率约束范围内这个检查写成断言超过容差就报 warning。5. 场景结果复盘四站十五机怎么看出调度效果案例系统是参考中国西南某流域的梯级水电站4 个电站共 15 台机组配 2 个光伏群光伏总装机远大于梯级水电的日调节能力所以调度方案中会看到明显的「光伏高发压低水电、光伏低谷水电顶上」的互补过程。拿到结果后先看三个指标总消纳电量期望值、弃电率、水电弃水量。如果弃电率集中在光伏高发的几个连续时段说明模型在电网约束下做了正确的取舍如果弃电时段零散分布在全天那大概率是机组振动区约束或者最小开停机时间约束在捣鬼。具体验证技巧上我习惯做一个「反事实对比」把光伏场景数量减半重新求解对比两组结果的弃电率差异。如果场景从 20 减到 10 弃电率变化超过 2 个百分点说明场景代表性不足需要重新做场景缩减如果变化很小则确认场景数量已经收敛。这个技巧不用改模型只改数据规模非常适合快速诊断不确定性建模是否到位。另外检查水头离散区间的边界如果某台机组长时间落在区间边界上说明离散太粗把区间边界调整到更贴近实际运行水头的位置通常能显著改善解的稳定性和可执行性。本文还有配套的精品资源点击获取
返回列表