梯级水光互补短期优化调度模型解析与Matlab代码实现
发布时间:2026/9/26 23:29:55来源:尧图网络
看到这个标题我第一反应就是这又是哪个课题组在啃EI论文做代码复现的活儿。梯级水光互补、最大化可消纳电量期望、短期优化调度这三个词凑在一起基本就是电力系统随机优化领域一篇典型论文的标准配置。我前两年带过一个小团队专门做新能源并网调度算法类似的复现项目做了不少从零搭Matlab代码再跑出收敛结果最短也花了两周多。这篇博文就从这个标题出发把模型原理、代码实现、常见坑一次性讲透给正在复现论文的同行省点踩坑时间。1. 先搞清楚这套模型到底在算什么1.1 梯级水光互补系统的基本运行逻辑很多刚接触这个方向的同学看到“梯级水光互补”五个字就有点蒙。拆开来看其实不复杂。什么叫梯级水电站同一条河流上从上游到下游依次建了多个水库电站上游电站发完电的水不会消失而是顺着河道流到下游电站变成下游的入库流量。这就形成了一条“串联”的水力链条。我习惯用一个类比一条传送带上的多个关卡上游关卡放多少水下游关卡才有多少水可用上游不放水下游就算想发满也发不满。光伏这边也好理解。光伏出力有三个明显特征白天中午出力最大、傍晚归零、阴雨天剧烈波动。水电刚好相反只要水库里有水发电机组的启停和出力调整可以做到分钟级响应。一个波动大但“零成本”一个调节快但“水资源有限”两者放在一个系统里联合调度就是水光互补的核心含义——用可控的水电去平抑不可控的光伏波动同时尽量少弃水、少弃光。但这里有个工程细节很多人忽略梯级水电站之间不光有水力联系还有时间延迟。上游电站开闸放水水不是瞬间就流到下游电站的可能要经过1到2个小时的水流传播时间。这个延迟在短期调度里必须考虑不然下游电站的来水过程会被算错调度计划在现场根本执行不了。这也是梯级调度比单库调度麻烦的一个主要原因。1.2 “最大化可消纳电量期望”这句标题该怎么读我拿到这个标题的时候先做了个拆句最大化 / 可消纳电量 / 期望。三个词各有各的门道。先说“可消纳电量”。发电不是越多越好电网能接住的电是有限的受输电断面容量、系统负荷水平、调峰能力等因素约束。水电和光伏发出的电如果超出电网接纳能力多余的部分只能被切掉——光伏那边叫“弃光”水电那边叫“弃水”。所以模型优化的对象不是“发电量”而是“能被电网消纳的电量”。这个区别很关键目标函数里如果写成了最大化总发电量那你就是在让系统超发算出来的调度方案根本不满足实际约束。再说“期望”。光伏出力是随机量受天气预报、云层遮挡、气温光照影响在调度决策时还没法精确知道明天中午光伏到底发多少。期望的意思就是在多种可能的光伏出力场景下计算一个概率加权平均的消纳电量把这个值作为优化目标。这比简单的确定性优化比如只用光伏预测的均值要更稳妥因为它考虑了多种未来情景。把三个词拼起来模型的角色就清楚了在电网接纳能力的硬约束下通过优化各时段的水电出力计划使得梯级水电和光伏的总消纳电量在各种光伏场景下的期望值最大。一句话概括就是“把随机波动的水光组合出力尽量填满电网能接住的空间”。1.3 短期优化调度的时间坐标“短期”这个词在电力调度里有明确含义一般指日前调度或日内滚动调度。时间范围是未来24小时到72小时分辨率从15分钟到1小时不等。这篇论文的典型设置是24小时、1小时间隔也就是24个时段。为什么用1小时不用15分钟粒度越细模型规模越大求解时间越长。1小时间隔在日前计划阶段是工程上的主流选择因为光伏预测在日前时间尺度上本身就有较大误差再细化时段对精度的提升有限反而拖慢求解。15分钟粒度通常是日内滚动修正阶段才用的。短期调度要解决的决策问题包括每个时段每台水电机组发多少电、发电流量怎么分配、要不要弃水、光伏能消纳多少、库容水位怎么变化。这些决策不是孤立的上一个时段的水放多了本时段库容就低了下游来水就变了未来时段的可发电力就少了。所有时段联合求解才算一个“调度计划”。2. 模型数学化目标函数与约束体系的完整拆解2.1 目标函数期望电量的数学表达把上面的言语翻译成数学语言模型的目标函数可以写成max Σ_s π_s · Σ_t [ Σ_i P_h(i,t) P_use(s,t) ]这里有三个求和号一层层解读i是水电站编号t是时段编号s是光伏场景编号。P_h(i,t)是第i个水电站在时段t的出力单位一般是MW。P_use(s,t)是场景s下时段t实际消纳的光伏功率。π_s是场景s的发生概率所有场景概率加起来等于1。外层Σ_s是对场景做概率加权得到的就是“期望”二字。细心读者会问为什么P_h不带场景下标sP_use却带这是模型做了两阶段决策的假设——水电出力计划在日前就要定下来看到光伏实现值之前所以不随场景变化光伏消纳量在日内可以根据实际来水情况调整所以随场景变化。这个假设符合实际水电站日前需要报发电计划不可能等光伏出力确定了再决定发多少。如果你在代码里把所有决策变量都加了场景下标那就变成了“完全自适应调度”这相当于假设调度员有上帝视角每个场景下都能重新优化水电。这样做虽然目标函数数值会更好看但工程上不符合日前计划流程。复现论文时要仔细看原文到底是哪种假设这直接决定变量维度和代码结构。2.2 梯级水电约束水量平衡、库容与出力梯级水电的约束是整个模型最硬核的部分也是最容易算错的地方。核心约束是水量平衡方程V(i,t1) V(i,t) [ Q_in(i,t) Q_up(i,t-τ_i) - Q_turb(i,t) - Q_spill(i,t) ] · Δt其中V(i,t)是第i个水库在时段t初的蓄水量单位万m³。Q_in(i,t)是天然区间来水即第i个水库控制流域内的降雨产流。Q_up(i,t-τ_i)是上游电站经过τ_i个时段的传播延迟后到达本电站的出库流量。Q_turb(i,t)是发电流量Q_spill(i,t)是弃水流量。Δt是时段长度1小时就用1小时。这个方程本质上讲的是这个时段水库多了多少水来水少了多少水出库剩下的就变成库容变化。除了水量平衡还有一组上下限约束。库容不能超过防洪限制水位对应的库容也不能低于死水位库容发电流量不能超过机组最大过流能力弃水流量下限为0上限受泄洪设施能力限制。水电站出力不能超过装机容量同时要满足出力与流量的转换关系。功率-流量关系是这里最容易出问题的地方。严格来说水电机组出力等于ρgQHη取决于水头H而水头又随库容水位变化是非线性关系。短期调度模型里最常用的处理方式是用线性化系数k近似P_h(i,t) k_i · Q_turb(i,t)k_i表示单位发电流量对应的出力单位比如MW·h/万m³。很多EI论文会进一步做分段线性化把水头变化的影响近似进去。复现的时候要注意看清楚原文用的哪种处理。最后还有末库容约束比如要求调度周期结束时水库蓄水量不低于某个目标值或者等于初始库容日循环运行。没有这个约束模型会倾向于把水全部放光发电。2.3 光伏侧约束与系统消纳能力约束光伏侧的约束相对简单就是一个上限限制每个场景下每个时段的实际消纳功率不能超过该场景的预测出力。写成数学式就是0 ≤ P_use(s,t) ≤ P_pv_pred(s,t)。P_use是个非负决策变量P_pv_pred是已知参数。这里有个容易被忽略的物理含义光伏消纳量小于预测值时差值就是弃光。模型不会主动弃光除非系统消纳能力不够或者水电需要保库容所以P_use会尽量顶到预测值上限。你观察最终结果里P_use和P_pv_pred的差就能直观判断系统在哪些时段弃光了。系统消纳能力约束是水光耦合的关键。在每个时段梯级水电总出力加上光伏消纳功率不能超过电网对该区域的接纳能力C(t)Σ_i P_h(i,t) P_use(s,t) ≤ C(t)C(t)可以理解成外送断面容量、当地负荷水平、系统调峰能力综合折算出来的上限一天24小时可以是变化的。比如夜间负荷低C(t)就小白天负荷高C(t)就大。这个约束迫使模型在光伏大发时段压低水电出力把消纳空间让给光伏——这就是水光互补在数学上的直观体现。2.4 为什么这套模型比“确定性等值”更好你可能想问直接用光伏预测均值当参数做一个确定性优化不是更简单吗答案是可以但结果不可靠。光伏预测均值只代表最可能的一条出力曲线而真实的出力可能在均值上下大幅波动。如果一个确定性模型说“明天中午消纳100MW没问题”实际光伏可能因为一片云飘过来变成80MW那水电计划就偏保守了白白损失20MW的发电空间反过来光伏实际发了120MW水电计划却已经把消纳空间占了那20MW只能弃掉。场景法通过生成多个可能的光伏出力场景并加权平均相当于让调度计划对多种未来情况“平均意义上最优”。这就是标题里“期望”二字的数学价值所在。当然期望优化不是万能的它不保证最坏情况下一定不弃电真要追求极端场景下的安全性得用鲁棒优化这个后面第6章再说。3. Matlab代码实现从公式到可运行程序的转化3.1 建模工具链Yalmip 求解器做优化调度复现我强烈建议直接用Yalmip工具箱别自己手写线性规划矩阵。Yalmip把Matlab变成了一个高度抽象的建模语言你只需要按数学式子抄成代码它自动帮你把优化问题转成求解器能认的标准形式。这个好处的价值等你调试过几十条约束就体会到了。求解器方面CPLEX和Gurobi二选一学术许可证都是免费的。我倾向Gurobi因为它的线性规划求解速度在中小规模问题上更快而且MIPGap设置更友好。Yalmip在调用时只需在sdpsettings里指定solver参数不用管底层的接口细节。环境配置有个小坑要先提醒装好Yalmip之后第一次运行optimize之前先跑一下yalmiptest命令确认你的Matlab能找到Gurobi或CPLEX。我遇到过很多次模型写得好好的结果一求解就报“No suitable solver”最后发现是求解器的路径没加到Matlab搜索路径里。这个排查步骤5分钟能解决别慌。3.2 程序文件组织与决策变量定义复现一个优化模型我习惯把程序拆成4到5个文件单独管理千万别全挤在一个脚本里。推荐结构是这样main.m主入口串联整个流程。load_params.m定义系统参数返回一个结构体params。gen_scenarios.m生成光伏出力场景和概率返回矩阵pv_pred和概率向量probs。build_model.m调用Yalmip建模接收参数和场景返回优化问题和解。plot_results.m画调度曲线、库容曲线、弃电情况。决策变量的定义是建模第一件事。用sdpvar创建连续变量维度要心里有数本模型里水电决策变量不随场景变化光伏消纳变量随场景变化。% T时段数n_h水电站数n_s光伏场景数 n_h 2; % 梯级水电站数量 T 24; % 时段数 n_s 5; % 光伏场景数 % 水电决策变量不随场景变化日前计划 V sdpvar(n_h, T1, full); % 库容万m3有T1个时刻点 Q_turb sdpvar(n_h, T, full); % 发电流量万m3/h Q_spill sdpvar(n_h, T, full); % 弃水流量万m3/h P_h sdpvar(n_h, T, full); % 水电出力MW % 光伏消纳功率随场景变化日内可调 P_use sdpvar(n_s, T, full); % 场景s下时段t的消纳光伏功率库容变量设成T1列是因为水量平衡方程需要从t时刻推到t1时刻最后一天的末库容也要参与约束。这个细节容易漏漏了的话末库容约束就没法写。3.3 目标函数与约束的Yalmip写法目标函数直接按2.1节的数学式抄。核心是期望的计算每个场景的消纳电量求和后乘以该场景概率再期望叠加。%% 目标函数最大化可消纳电量期望 % 水电计划电量 所有电站所有时段的出力之和 hydro_energy sum(sum(P_h)); % 光伏期望消纳电量 各场景消纳电量按概率加权 pv_energy_expected sum(probs .* sum(P_use, 2)); % Yalmip默认最小化所以目标取负 objective - (hydro_energy pv_energy_expected);probs是n_s×1的列向量sum(P_use,2)得到n_s×1的列向量每个场景的总消纳量逐元素相乘再求和就是期望值。约束的写法最考验细心程度。梯级水量平衡是主菜我建议用双重循环逐站逐时段添加Constraints []; %% 水量平衡约束 for i 1:n_h for t 1:T % 该电站该时段的天然来水 inflow Q_natural(i, t); % 如果存在上游电站叠加梯级传递流量考虑水流延迟 if i 1 tau flow_delay(i); % 水流从上游电站到本站的延迟时段数 if t - tau 1 inflow inflow Q_turb(i-1, t-tau) Q_spill(i-1, t-tau); end end % 水量平衡库容变化 来水 - 出库 Constraints [Constraints, ... V(i, t1) V(i, t) (inflow - Q_turb(i, t) - Q_spill(i, t)) * dt]; end end这里有个建模细节值得多说一句当t - tau小于1时表示上游出库还在调度周期外不予考虑。初始时段循环里没有上游来水叠加是合理的因为边界条件在参数里已经通过初始库容体现了。接下来的约束组我通常写成连续块逻辑上更清晰%% 库容上下限约束 V_lo params.V_lo; % 各水库死库容万m3 V_hi params.V_hi; % 各水库防洪限制库容万m3 for i 1:n_h for t 1:T1 Constraints [Constraints, V_lo(i) V(i,t) V_hi(i)]; end end %% 发电流量与弃水流量上下限 for i 1:n_h for t 1:T Constraints [Constraints, 0 Q_turb(i,t) Q_turb_max(i)]; Constraints [Constraints, 0 Q_spill(i,t) Q_spill_max(i)]; % 出力-流量线性关系k为综合出力系数 Constraints [Constraints, P_h(i,t) k(i) * Q_turb(i,t)]; Constraints [Constraints, 0 P_h(i,t) P_h_cap(i)]; end end %% 末库容约束退出调度周期时不低于目标库容 for i 1:n_h Constraints [Constraints, V(i, T1) V_end_target(i)]; end %% 光伏消纳约束 for s 1:n_s for t 1:T Constraints [Constraints, 0 P_use(s,t) pv_pred(s,t)]; end end %% 系统消纳能力约束水光耦合约束 for s 1:n_s for t 1:T Constraints [Constraints, sum(P_h(:,t)) P_use(s,t) C_limit(t)]; end end如果你仔细看会发现这些约束全是线性的这设计不是随意的。线性约束保证整个问题是一个线性规划LPGurobi求解LP的速度非常快几千个变量几万个约束也就秒级到十秒级的事。假如你把水头非线性不加处理直接塞进来模型就变成非线性规划求解时间可能从秒级变成几分钟甚至压根不收敛。所以复现论文时看到非线性一定要想尽办法线性化或分段线性化这是工程可解性的关键。3.4 求解设置与结果提取模型建好之后求解和结果提取就很简单了%% 求解 options sdpsettings(solver, gurobi, verbose, 2); sol optimize(Constraints, objective, options); if sol.problem 0 fprintf(求解成功目标函数值期望消纳电量: %.2f MWh\n, -value(objective)); else disp(求解失败请检查约束和数据); disp(sol.info); end %% 提取结果 V_opt value(V); P_h_opt value(P_h); P_use_opt value(P_use); Q_turb_opt value(Q_turb); Q_spill_opt value(Q_spill);sol.problem等于0表示求解成功这个标记字段是Yalmip的标准输出我每次求解完都会第一时间检查比单纯看命令行输出可靠。提取结果后energy总消纳量需要把水电和光伏期望分开统计方便后面画图时展示互补关系。4. 实操过程完整跑通一个双站光伏算例4.1 测试算例参数为了把这个模型真正跑起来我设计了一个最小测试算例2个梯级水电站加1个集中式光伏电站调度周期24小时。这个规模既能反映梯级耦合和光伏随机性的核心特征又不至于让初学者陷入数据海洋。下面这个表格是我实际设置的参数你可以直接拿去当模板改参数项电站1上游电站2下游单位装机容量10080MW死库容15001200万m³防洪限制库容50004000万m³初始库容35003000万m³目标末库容35003000万m³综合出力系数k0.850.72MW·h/万m³最大发电流量180150万m³/h最大弃水流量200180万m³/h电站2的入库流量由区间来水和电站1出库流量延迟1小时共同构成。区间来水我取了典型的枯水期数据24小时总量大约2.8万m³时间分布还算平稳。光伏预测出力按晴天场景生成中午12点达到峰值80MW。4.2 光伏场景生成与概率设定光伏场景生成这一步我用的方法是以预测出力曲线为基准叠加一个乘性随机扰动因子。扰动因子服从正态分布均值1标准差0.1但限制在0.8到1.2之间防止出现负值或超出装机容量的明显不合理的场景。抽取10个原始场景再用K-means聚类缩减到3个典型场景概率取各聚类中场景的归一化数量。这样得到的3个场景典型特征是一个接近晴天的“高发场景”概率约0.4一个略低于预测的“中发场景”概率约0.35一个明显偏低、模拟多云天气的“低发场景”概率约0.25。场景概率向量直接参与目标函数的期望计算这在Yalmip里实现起来没有任何额外成本。4.3 运行结果与调度曲线解读求解完成之后我最喜欢看三张图水电出力曲线、光伏消纳曲线、库容变化曲线。这三张图放在一起水光互补的内在逻辑就全出来了。在我的算例结果里一个典型特征是中午11点到14点光伏出力冲上高位系统消纳能力此时只有150MW左右。如果水电跟光伏抢空间必然造成弃光所以模型把水电出力压到了很低的水平两个电站加起来在中午时段只发了40MW左右。光伏出力下降的傍晚时段水电立刻爬坡顶上保持系统总出力尽量贴近消纳能力上限。库容曲线的走势也很有意思。上游电站夜晚蓄水白天稍微放水发电下游电站因为接收了上游出库流量库容变化更平缓。整个调度周期结束时库容正好回到目标值验证了水量平衡约束和末库容约束都正确。你如果复现出来发现末库容对不上目标值那一定是水量平衡方程里少了上游出库项或者单位换算出了问题。4.4 期望目标值怎么解读目标函数返回值是一个具体数字比如“期望可消纳电量18.7GWh”。这个数字怎么解读呢它表示在3个光伏场景的概率分布下该调度方案期望能被电网消纳的总电量。注意它不等于任何一个具体场景下的实际消纳电量而是一个概率加权平均值。更实用的解读是拿它跟基准方案比。比如固定水电按常规调度不配合光伏再看期望消纳电量是多少。我实测下来的对比水光联合调度比水电单独常规调度能多消纳5%到10%的电量具体数值取决于系统消纳瓶颈的紧张程度。瓶颈越紧互补调度的价值越大。5. 复现路上踩过的坑与排查实录5.1 水量平衡不闭合末库容对不上这个是我复现梯级调度踩过的第一个坑。现象是明明设置了末库容等于初始库容求解结果却告诉你无解或者强行求解后库容曲线明显不对。排查后发现问题出在单位换算。原始数据里天然来水用的是m³/s发电流量用的是万m³/h两个单位直接相加水量自然不守恒。m³/s乘以3600才是m³/h再除以10000才是万m³/h。建议在load_params.m里把所有流量统一换算成万m³/h并在代码里写清楚单位注释。别信肉眼写个断言函数检查每个时段的水量平衡残差这一步能帮你抓住90%的建模错误。5.2 模型报Infeasible怎么快速定位Yalmip求解回报“infeasible problem”的时候别慌也不要瞎猜着改数据。我常用的排查手法是二分法先把系统消纳能力约束C(t)放得很大比如取所有时段都等于500MW看看模型是否变可行。如果可行说明问题出在消纳约束跟其他约束打架如果还是不可行再把末库容约束删掉试试以此类推每轮删一组约束直到找到哪组约束组引起了矛盾。还有一种常见情况光伏预测峰值大于系统消纳能力而水电又有最小出力的隐含约束比如为了维持库容或流量要求。两个约束同时收紧可能直接导致某个时段无解。这时候要审查时段内的可行空间而不是全局乱调。5.3 水头变化被忽略出力系数取值太随意有些论文里给出的综合出力系数k其实是把水头变化平均化的结果。如果你直接拿单点水头系数去套调度方案可能在特定库容状态下高估或低估实际出力。更稳妥的做法是给每个电站按水位区间设置2到3个分段系数P_h与Q_turb之间的关系变成分段线性函数。Yalmip里可以用ismember或者引入辅助0-1变量实现分段线性化。代价是模型从LP变成混合整数线性规划MILP求解时间会明显上升。对于只有两三个电站的小规模系统这个代价是可以接受的。5.4 场景太多求解时间失控我一直强调场景数不宜贪多。曾有一次我把场景加到50个模型变量和约束数直接爆炸Gurobi求解花了将近20分钟这在实际项目里根本没法迭代调试。推荐做法是调试阶段用2到3个场景跑通全流程等代码逻辑完全正确后再逐步增到10到20个场景检验结果稳定性。如果最终要跟论文里的结果对齐再看原文用了多少场景。一般来说EI论文里短期调度场景数10到30个比较常见再大通常都做了场景缩减。5.5 Yalmip和求解器的常见报错速查报错现象常见原因解决办法No suitable solver for problemYalmip没找到Gurobi/CPLEX检查求解器安装路径运行yalmiptest验证Solver not applicable问题类型和求解器不匹配检查模型是否引入了0-1变量但没配MILP求解器NaN or Inf in objective参数里有空值或无穷值检查pv_pred、Q_natural等数据矩阵是否存在未赋值元素Infeasible problem约束过紧或数据矛盾用5.2节的二分法逐组约束排查Failed to allocate memory场景数或变量维度过大缩减场景检查是否有变量维度定义错误导致爆炸6. 复现之后模型的扩展与工程落地6.1 从期望优化走向鲁棒优化期望优化保的是平均水平不保最坏情况。如果你的项目关心极端天气下的运行安全可以把目标改成最大化最坏场景下的可消纳电量也就是max-min问题。这种鲁棒优化模型在Matlab里可以用Yalmip配合鲁棒工具箱实现也可以手动引入辅助变量把max-min问题转成线性规划。代价是目标值通常比期望优化低因为系统为了保最坏场景会牺牲部分正常场景下的发电量。6.2 从电量目标到经济目标标题里的目标是电量期望复现完你可以顺手把它改成经济目标给水电和光伏设置上网电价给弃电设置惩罚系数目标函数就变成最大化期望收益。这个改动很小只需要在目标函数里把电量乘上价格向量。但有一个重要区别要注意经济目标下系统可能主动选择高价时段多发、低价时段少发调度结果会和纯电量目标有明显差异。这是从“技术调度”向“市场调度”迈出的一步很多论文的后续工作都在做这个扩展。6.3 从日前计划到日内滚动修正最后说说工程落地。日前模型算出来的计划是开卷答案实际运行时天气变了、负荷变了计划必须跟着调。常见的做法是模型预测控制MPC框架每4小时滚动一次用最新预测数据重新求解未来12小时或24小时的调度计划但只执行前4小时的决策。我实际做过一版这样的MPC滚动调度跟纯日前计划比弃光率能下降3到5个百分点因为每次滚动优化都吃进了最新的光伏预测信息。这套思路可以无缝搭在本文的模型代码上把main.m包进一个for循环每次循环更新光伏场景和初始库容求解后只取前几个时段的决策值然后推进时间窗口。复现论文这件事我个人的体会是代码能跑通只是第一步真正的收获在于你能在代码里找到每一个约束对应的物理含义以及每一步简化假设带来的误差有多大。梯级水光互补调度这个题目模型框架是相对标准的难点全在细节——水流延迟、单位换算、场景取舍、非线性处理任何一个地方疏忽结果就可能偏得离谱。希望这篇博文能帮你少走几段弯路。
网站建设高端定制企业官网