跑过EI论文复现的人都知道,最磨人的不是公式有多深,而是你照着公式把代码写完,结果和论文图表永远对不上。这次我想把风-水电联合优化运行这个方向从头到尾聊一遍,包括建模思路、不确定性场景处理、Matlab代码实现框架,以及复现过程中踩过的一串坑。这个课题在新能源调度领域算是典型题目,风电出力随机波动、水电快速调节,两者组合在一起就是一个标准的多时段电力系统优化问题,很适合用来理解随机优化、场景削减这些核心工具。
风-水电联合优化的本质,是把风电场的随机性和水电站的可调节性放进同一个优化框架。风电靠天吃饭,功率预测误差通常能到百分之十几;水电虽然受来水约束,但短时间尺度上调节比火电灵活得多。两者配合,水库的储能效应可以平滑风电波动,减少弃风,提高系统经济性,所以这个方向在EI期刊里出镜率一直很高。这篇内容适合三类读者:正在做EI复现或准备投小论文的研究生,需要把调度优化框架落到Matlab里的工程师,以及刚接触新能源调度、想理解建模套路的新手。我会用比较直白的语言把原理讲清楚,代码片段和参数设置也会直接给出来,方便你基于自己手里的论文去改。
1. 风-水电联合优化:这个课题为什么值得反复做
先从实际系统说起。风电大规模并网之后,弃风问题不会自动消失。电网吸收不了那么多波动功率,尤其夜间低谷时段风大负荷小,调度员面临的就是“要么限功率运行,要么想办法消纳”。水电是少数能在短时间尺度内快速调节出力的电源,来水多的时候可以把水蓄起来,风电多的时候水电站可以主动压出力、腾出空间给风电。这种互补特性,让风-水电联合优化成了既有物理基础又有经济价值的调度问题。
从发论文的角度看,这个方向有三大优势。第一,物理过程清晰,模型从水量平衡、功率平衡这些基本规律出发,审稿人不需要特殊背景就能看懂。第二,问题有复杂度,风电不确定性的引入让它不再是简单的确定性调度,需要随机优化或鲁棒优化等工具。第三,结果便于可视化,一张联合调度曲线图就能直观展示联合运行的优势。这些特点决定了它作为EI复现题目的热度。
复现这个课题,不需要一上来就翻几十篇论文。主流框架就是两阶段随机优化:第一阶段决定水电的调度计划,第二阶段根据风电场景计算系统运行费用和弃风惩罚,目标函数取所有场景的期望值。理解了这个框架,再看大多数风力相关的EI调度论文都会省力很多。另外这个建模思维可以迁移到风光储、水光蓄、多能互补等方向,核心思路一致,区别只在储能环节的约束形式不同,所以认真复现一次等于理解了一大类调度优化论文的底层逻辑。
2. 复现前必须明确的三个关键选择
2.1 不确定性处理:场景法还是鲁棒法
这是建模前最先要决定的事。场景法假设风电预测误差服从某个概率分布,通过蒙特卡洛采样生成一组可能的出力曲线,再用场景削减算法把它们压缩到少数典型场景,每个场景带一个权重。目标函数把这些场景下的成本期望值当作优化目标,得到的调度计划是“平均意义上最优”。
鲁棒法完全不同。它把预测误差描述成一个不确定集合,要求最坏情况发生时系统仍然可行,目标往往是最小化最坏场景下的成本或后悔值,所以结果偏保守,但安全性更高。
判断原始论文用哪一种方法,看几个词就行:如果论文描述里有“期望成本”“场景概率”“蒙特卡洛”,基本是场景法;如果出现“最恶劣场景”“min-max”“不确定集”,就是鲁棒法。我个人的建议是,入门复现先从场景法开始,因为它结果直观、更容易画对比图,Matlab代码量也相对少。下面这个表可以直接帮助你决策。
| 维度 | 场景法 | 鲁棒法 |
|---|---|---|
| 核心思路 | 采样有限场景,目标取概率加权期望 | 不确定集内最坏场景可行 |
| 保守程度 | 较低,经济性更好 | 较高,安全性更强 |
| 计算复杂度 | 随场景数线性增加 | 可能涉及对偶推导,复杂度更高 |
| 对复现友好度 | 高,结果容易解释 | 中,对偶转换有门槛 |
2.2 电源侧:常规水电还是抽水蓄能
标题写“风水联合”时,优先假设是常规水电站或梯级水电站。常规水电只能发电,变量是连续的,水量平衡、库容约束都比较简单,适合新手。抽水蓄能电站多了一个抽水工况,它既能发也能抽,建模时要引入0-1变量切换工况,一个0-1变量会让问题从连续优化变成混合整数优化,求解难度和调试成本明显上升。
如果论文里写的是“风-蓄联合”或“抽水蓄能电站”,才需要按混合整数来处理。普通“风水联合”复现,把水电当成连续可调电源,Q和P_h都设成连续变量就足够。判断标准其实很简单:看原论文的功率平衡式里有没有表示抽水用电的项,没有就是常规水电。
还有个容易踩坑的细节,梯级电站之间的水力联系。上游电站的出库流量,经过一个时滞会成为下游电站的入库流量。如果你复现的目标系统有多座电站,这个时滞必须写进约束,否则下游入库可能算错,负荷平衡对不上。
2.3 目标函数:经济性优先还是消纳优先
同一个物理系统,目标函数不同,最优解会差很多。常见有三种口径:最小化系统运行费用、最大化风电消纳量、最小化联合出力波动。EI论文里常把经济性和消纳量放进同一个目标里,弃风量会乘一个惩罚系数,代表弃风成本。
我复现时的一个习惯是,先把基准案例只做“功率平衡加水电物理约束”,定义清楚目标函数里每一项的含义,再去加场景、加惩罚。不要一上来就写带复杂惩罚的优化问题,不然模型出问题连查都查不动。目标函数到底是求最大还是最小也一定要确认,有的论文写“最大化社会总效益”,有的写“最小化总费用”,方向反了,结果一出就是负的弃风率,一眼假。
另外要提醒,有些论文里目标函数有多个求和项,比如购电成本是外购电量和电价的乘积,弃风惩罚是弃风量和惩罚系数的乘积。复现时要仔细对照求和下标,很多容易出错的地方都在下标上。后面第五部分我会专门说这个坑。
3. 从物理过程到数学表达式:完整建模思路
3.1 风电场景怎么生成、怎么削减
风电出力不确定性建模,第一步是场景生成。假设已知风电预测出力曲线 P_w^f(t),实际可用出力可以写为:
P_w^a(s,t) = P_w^f(t) + ξ(s,t)
其中ξ表示预测误差,通常假定服从均值为0的正态分布,标准差取预测值的10%~20%,用公式表示就是 ξ ~ N(0, (κ·P_w^f)^2),κ在0.1到0.2之间取值。对每个时段独立采样,一条采样曲线就是一个场景。
我一般先生成1000个原始场景,再削减到20个左右。场景削减的常用算法是同步回代削减(SBR),核心思路是反复合并距离最近的两个场景,把概率合并到保留场景上,直到场景数量达到目标值。核心逻辑可以用下面这段Matlab伪代码来表达:
function [P_scen, Prob] = scenario_reduction(P_raw, P_target) n = size(P_raw,1); Prob = ones(n,1) / n; while n > P_target % 计算两两场景间的欧氏距离 D = pdist2(P_raw, P_raw); D = D + eye(n)*inf; % 屏蔽自身距离 [i, j] = find(D == min(D(:)), 1); % 将场景j并入场景i,概率累加 Prob(i) = Prob(i) + Prob(j); P_raw(j,:) = []; Prob(j) = []; n = n - 1; end P_scen = P_raw; end这段代码是教学版本,效率不算高,但对于24小时、20个场景的规模足够用。如果你的数据量更大,建议直接用现成的场景削减工具箱,或者用k-means聚类替代。削减之后要检查一下削减结果,看看极端大风的场景有没有被丢掉,如果丢掉,优化结果会明显偏乐观。
3.2 水库水量平衡与水电出力约束
水电建模的核心是水库水量平衡。设 V(t) 为水库在时段 t 的库容,I(t) 为入库流量,Q(t) 为发电流量,Δt 为单个调度时段长度,水量平衡方程写为:
V(t+1) = V(t) + (I(t) - Q(t)) · Δt
库容有上下限约束:
V_min ≤ V(t) ≤ V_max
调度周期首末库容一般固定,比如 V(1)=V_0、V(T+1)=V_T,这代表一个调度周期内的循环约束。
水电出力 P_h(t) 通常是流量、水头、发电效率的函数。严格来说是 P_h=ρ·g·H·Q·η,ρ是水的密度,g是重力加速度,H是水头,η是发电效率。但在复现论文时这类非线性关系常常被简化处理,用线性关系代替:
P_h(t) = η_h · Q(t)
这里 η_h 是一个综合转换系数,直接把单位流量换算成电功率。如果论文里有损耗或非线性特性,可以在系数里做一定修正。还需要加上出力上下限和发电流量上下限:
P_h_min ≤ P_h(t) ≤ P_h_max Q_min ≤ Q(t) ≤ Q_max
这里必须提醒一个新手最容易漏的变量:弃水流量 S(t)。当来水过大、库容已满而发电空间又不够时,必须让多余的水溢流,也就是弃水。如果不加弃水变量,水量平衡方程会逼迫库容无限上升,最后求解器直接报无解。在水库模型中给弃水留一个非负变量 S(t) ≥ 0,这才是完整的水库调度模型。
3.3 功率平衡与两阶段优化目标
功率平衡是对每个时段、每个风电场景同时成立的。假设系统外部还可以向上级电网购电,功率平衡式为:
P_h(t) + P_buy(t) + P_w_use(s,t) = L(t)
其中 P_buy(t) 是外购电力,L(t) 是负荷需求。风电实际利用量和可用量之间的关系是:
P_w_use(s,t) + P_waste(s,t) = P_w^a(s,t)
P_waste(s,t) ≥ 0,也就是弃风变量。风电不能超出当前场景的可用功率出力,超出的部分只能被舍弃,这在约束里写清楚。
两阶段优化的目标函数可以写成所有场景下的期望成本:
Minimize Σ_s π_s Σ_t [ c_buy(t)·P_buy(t) + c_waste·P_waste(s,t) ]
π_s 是场景 s 的概率,c_buy(t) 是购电价,c_waste 是弃风惩罚系数。第一阶段的决策变量 V、Q、P_h 不依赖场景,第二阶段的决策变量 P_buy、P_w_use、P_waste 依赖场景。这个区别就是两阶段建模的关键,配合YALMIP实现时,只要把不依赖场景的变量留在循环外、把依赖场景的变量放进场景循环里,程序结构自然就对了。
4. Matlab代码实现:完整框架与易错细节
4.1 为什么选YALMIP+Gurobi而不是手写算法
复现EI论文的调度优化,我几乎不推荐自己写迭代算法。不少老论文用遗传算法、粒子群做求解,但启发式算法在确定性约束较多的调度问题上,求解精度和稳定性都成问题,你很难判断“结果不对”到底是模型错还是算法没收敛。更稳妥的做法是用数学规划求解器。
YALMIP是Matlab里的建模工具箱,它把变量声明、约束构建、求解器调用做成了一套很顺的接口。配Gurobi或CPLEX这类商业求解器,处理中小规模的混合整数线性规划非常快。如果你没有商业求解器授权,MATLAB自带的intlinprog也能解决一部分问题,但求解速度和大场景下的数值稳定性会比Gurobi差一些,所以预算允许的话还是建议配Gurobi。
4.2 代码骨架与两阶段变量的写法
完整的复现代码通常分五个模块:数据读取、场景生成、模型构建、求解、结果可视化。这里给一个YALMIP建模的核心片段,展示两阶段变量声明和约束构建:
%% 决策变量 V = sdpvar(T+1, 1, 'full'); % 库容 Q = sdpvar(T, 1, 'full'); % 发电流量 P_h = sdpvar(T, 1, 'full'); % 水电出力 P_buy = sdpvar(T, 1, 'full'); % 购电功率 P_w_use = sdpvar(Nscen, T, 'full'); % 场景s、时段t的风电利用 P_waste = sdpvar(Nscen, T, 'full'); % 场景s、时段t的弃风 Constraints = []; %% 水量平衡约束 for t = 1:T Constraints = [Constraints, V(t+1) == V(t) + (In(t) - Q(t))*dt]; end Constraints = [Constraints, V(1) == V0, V(T+1) == Vend]; Constraints = [Constraints, Vmin <= V <= Vmax]; Constraints = [Constraints, Qmin <= Q <= Qmax]; for t = 1:T Constraints = [Constraints, P_h(t) == eta*Q(t)]; Constraints = [Constraints, P_hmin <= P_h(t) <= P_hmax]; end %% 功率平衡与风电约束 for t = 1:T for s = 1:Nscen Constraints = [Constraints, P_h(t) + P_buy(t) + P_w_use(s,t) == L(t)]; Constraints = [Constraints, P_w_use(s,t) + P_waste(s,t) == P_w(s,t)]; Constraints = [Constraints, P_waste(s,t) >= 0]; end Constraints = [Constraints, P_buy(t) >= 0]; end %% 目标函数:期望购电成本 + 弃风惩罚 Objective = 0; for t = 1:T for s = 1:Nscen Objective = Objective + Prob(s) * (Price(t)*P_buy(t) + WastePenalty*P_waste(s,t)); end end %% 求解 ops = sdpsettings('solver', 'gurobi', 'verbose', 2); optimize(Constraints, Objective, ops);从这个片段可以看出两阶段结构如何落到代码里:P_h、Q、V 声明成普通的连续变量,不进入场景循环;P_w_use 和 P_waste 声明成 Nscen×T 矩阵,进入场景循环。YALMIP会自动处理这种结构,不需要手写拉格朗日乘子。
4.3 三个容易忽略的实现细节
第一个细节是维度匹配。把 P_w_use(s,t) 和 P_h(t) 这样不同维度的变量加在一起,YALMIP 表面上不会报错,但会给你自动广播成矩阵约束,结果和你期望的完全不一样。我建议在建模之前先把维度打印出来,用 size() 检查,或者设置 yalmip 的 warning 全开。
第二个细节是约束添加方式。用循环逐条拼接 Constraints 最直观,也最容易调试,但场景多时性能会下降。如果Nscen到100以上,建议改成矩阵约束一次性添加,或者用 repmat 展开。不过复现论文一般20个场景以内,循环拼接不会太慢。
第三个细节是库容初终值设置。论文里如果给出“调度周期末库容等于初始库容”,必须写成 V(T+1)==V0。如果论文要求“期末库容不固定但要满足某个下限”,则写成 V(T+1) ≥ Vend。这两个条件的目标解相差很大,别混。
5. 复现踩坑实录:这几个错误我都替你踩过
5.1 源论文公式符号不一致,一处错就全盘无解
我复现过一篇关于水风互补的论文,正文水量平衡式里入库量用的是 I(t),推导到下一节又变成 S(t),最后页面底部给了一个带负号的入库量。我照着公式写代码,求解器报infeasible,足足找了两天才定位到是符号问题。这个教训是:拿到论文后,先把所有公式按变量定义走一遍量纲推导,凡是出现“等式两边量纲不一致”的地方,先标记出来再决定用哪个版本。不要迷信印刷版公式。
5.2 求解器infeasible的定位流程
infeasible是复现时最常遇到的报错。我的排查流程是这样的:
- 先删掉所有约束,只看目标函数,确保模型本身能跑通。
- 一条一条加约束,每加一条都重新求解,哪条约束一加上就infeasible,问题就在那条。
- 重点检查库容上下限和水量平衡,尤其有没有漏掉弃水变量。
- 检查初终库容设置是否合理,如果 V0 和 Vend 与来水流量不匹配,几乎必然无解。
- 如果模型里有0-1变量,先把它改成连续变量试试,确定可行域本身没问题,再处理整数部分。
这套流程看起来很机械,但比你在代码里瞎改参数高效得多。遇到过很多次问题都出在水量平衡方程缺了一个非负变量,所以漏写弃水这件事值得反复强调。
5.3 m³/s和MW之间的单位换算
水电参数的单位是复现过程中最容易翻车的泥潭。流量常用m³/s,电量常用MWh,库容常用亿m³,调度时段是小时,这几个单位混在一起,按错一个数量级,结果差十万八千里。
明确一点:1 m³/s 的流量持续1小时,流过水量是3600 m³。如果水头是100米,理论功率大约是 P = ρ·g·H·Q = 1000×9.81×100×1 ≈ 981 kW,接近1 MW。这算出来的大概值就是功率。所以入库流量和发电流量在水量平衡式里必须乘上3600,把它换算成“每小时的立方米数”,再和库容单位对齐,否则库容变化会差3600倍。
你可以做一个最笨但最稳的自检:构造一个无风电的纯水电算例,只让水电机组带全部负荷,看看最终库容变化是不是等于总来水量减去总发电流量。如果数量级对不上,单位换算基本就是罪魁祸首。
5.4 复现结果对不上论文时的排查顺序
结果对不上论文时,别急着调场景数或惩罚系数。我先按这个顺序排查:
- 单位换算有没有问题。
- 目标函数方向对不对,是求最大还是求最小,惩罚项符号是不是正的。
- 变量维度有没有混用,比如P_h是不是被场景循环影响了。
- 场景削减后有没有丢掉极端大风场景,导致弃风被低估。
- 最后才考虑求解器数值精度或论文数据本身的问题。
很多论文其实没给全数据,负荷曲线、电价曲线、风电预测曲线都可能是示意图,直接数字化出来的数据和原作者用的不完全一致。这种情况能复现趋势就算成功,硬抠数值意义不大。
6. 实验设计:复现之后怎么让结果更有说服力
6.1 基准方案设计:跑三组对比才叫完整
只跑一组联合优化结果,没办法讲清楚模型价值。我习惯至少设三组方案:
方案A是水电单独调度、不接入风电,作为基础对照。方案B是风-水电联合调度但用确定性预测值,也就是忽略不确定性。方案C是风-水电联合调度加场景随机优化,也就是主结果。有条件的加方案D,用鲁棒优化做对比。
这四组方案跑完后,你能很自然地得到几个结论:方案B和C的区别体现不确定性建模的价值,方案A和C的区别体现风电接入的效益。用这个结构去组织结果图表,论文的前后逻辑会非常清晰。
6.2 评价指标怎么设计
除了系统总运行费用之外,我常用的评价指标有三个:
| 指标 | 计算方式 | 说明 |
|---|---|---|
| 弃风率 | Σ弃风功率 / Σ可用风电功率 × 100% | 越低说明风电消纳效果越好 |
| 联合出力波动率 | 联合出力序列的标准差 | 越低说明水电平抑效果越好 |
| 水电调节深度 | 调度周期内水电出力最大最小值之差 | 反映水电机组的调节负担 |
这三个指标加一个总费用指标,基本就能覆盖论文最关心的几个结论维度。在做方案对比时,指标口径必须统一,比如弃风率分母到底是风电可用量还是系统总负荷,论文里经常写得不清楚,你自己定好之后所有方案统一执行。
6.3 画图的具体建议
结果图尽量走“三张图”思路。第一张是联合调度出力图,用堆叠面积图表示水电、风电、购电三层,横轴是时段,纵轴是功率。第二张是库容或流量变化曲线,展示水库调度过程的合理性。第三张是方案对比柱状图,把弃风率和总费用放在同一个表里。这三张图基本就能撑起一篇复现报告的核心结论,再多反而分散注意力。
画图时注意几个细节:单位要统一并标注清楚,曲线颜色对比要明显,图例位置不要遮挡内容。Matlab默认的colormap配色有时不够清晰,建议直接手动指定几种常见的非冲突色,比如蓝色、橙色、绿色,网格线用淡灰,不要用深黑。
7. 复现完成后,这套代码还能往哪里延伸
复现出正确结果之后,这套代码可以直接作为扩展实验的底座。最常见的方向是把两阶段随机优化升级为分布鲁棒优化,不确定集从离散场景改为矩约束模糊集,在极端场景下更稳。想更实用一点,可以做多时间尺度协调,把日前调度和日内滚动修正结合起来,代码上增加一个滚动优化的循环即可。
另外一个很自然的方向是加储能。如果在风电场侧加一个电化学储能或者把水电站换成抽水蓄能,模型会从连续优化变成混合整数优化,但代码骨架不变,只需要在功率平衡等式里增加储能充放电项,在约束里增加储能SOC递推方程。这部分工作做出来之后,论文的创新点就从“方法复现”变成了“方法改进”,对科研用途来说价值更大。
如果目标是做工程实算,还可以把合成数据换成真实流域数据。用自己的径流数据和水电站参数替换模型里的 I(t)、V_min、V_max、P_h_max,再配合风电场的真实采集数据,整个复现就变成了一个实际系统的调度决策工具。这一步做完,代码就不只是复现论文,而是可以直接支撑项目报告和现场调度方案了。
最后说一点个人体会。复现EI论文这种事,难点往往不在算法本身,而在细节的耐心上。符号、单位、维度、约束的遗漏,任何一个地方出错都会让结果看起来不可理喻。但反过来,一旦你亲手把一个调度优化模型从公式变成可运行代码,并且跑通、画图、讲清楚结论,你对那篇论文的理解和记忆会比读十遍文献都深刻。这也是复现最大的价值。
如果你手头的论文刚好是这类新能源调度方向,我建议别急着去调大家关注的先进算法,先把确定性模型和场景法两条线跑通,再往上加复杂度,这套路线会稳很多。