
风电、光伏与抽水蓄能电站互补调度运行研究一份可复现的Matlab实现笔记搞新能源调度的人应该都有同感风电和光伏的出力曲线全是“看天吃饭”的锯齿状波动。白天光照强光伏功率一路飙升傍晚又断崖式下跌风场更是毫无规律半夜可能满发白天反而趴窝。这种波动性扔进电网里如果不加调节弃风弃光率和系统调峰压力都会非常难看。把抽水蓄能电站加进来做多能源互补调度是目前工程上最成熟的调节手段之一。这篇博文我就把风-光-蓄互补调度运行研究这个课题的建模思路、Matlab实现细节和踩坑记录完整地梳理一遍适合刚入门电力系统优化调度的研究生也适合想做新能源消纳方案的技术人员参考。课题本身不算特别复杂核心就三件事把风电、光伏、抽水蓄能的出力特性用数学模型描述清楚把调度目标和约束条件写成可求解的优化问题然后选一个合适的算法用Matlab把整个流程跑通。难的地方也在这三件事里——模型简化到什么程度、约束怎么处理、算法参数怎么调每一步都直接影响结果的可靠性。下面我把完整思路拆开来讲。1. 为什么非要做风-光-蓄互补调度1.1 风、光出力“看天吃饭”三个绕不开的痛点先说风电。风速的随机性和间歇性不用多解释更麻烦的是风电往往有反调峰特性——夜间负荷低谷时段风速反而偏高风电出力大结果就是谷底被进一步下压火电机组被迫深度调峰甚至停机。到了白天负荷高峰风速又可能降下来风电帮不上忙系统只能靠其他电源硬扛。光伏的特性大家更熟悉出力曲线基本跟着太阳走午间形成“鸭子曲线”的肚子傍晚快速跌落形成陡峭的爬坡需求。这种短时大幅波动对系统的爬坡速率要求极高常规火电机组根本跟不上需要更灵活的资源去填补。第三个痛点是预测误差。风电功率预测误差在10%-20%很正常光伏受云层影响更是经常“说翻脸就翻脸”。预测偏差意味着调度计划不能完全执行需要留有足够的调节裕度来消化实际出力和计划出力的差值。这三个痛点叠加起来单靠火电调峰不仅成本高响应速度也跟不上必须引入储能性质的调节资源。1.2 抽水蓄能凭什么当“调节器”抽水蓄能是目前最成熟的大规模储能方式核心原理就是能量时移——负荷低谷时把水从下水库抽到上水库储存势能负荷高峰时放水发电。它最大的优势在于响应速度快工况转换灵活机组从静止到满发一般只需要几分钟调节速率可以达到每分钟几十兆瓦这是火电机组完全比不上的。从调度角度看抽蓄相当于给系统提供了一个“缓冲池”。光伏大发时把多余的电能通过抽水方式存起来避免弃光光伏出力骤降或晚高峰时再放水发电顶上。风-光-蓄联合起来就可以把原来“不可控的波动出力”改造成“可控程度更高的组合出力”这正是当前能源基地建设中多能互补模式的工程基础。需要说明的是抽蓄本身也有约束不是想怎么调就怎么调。抽水工况和发电工况不能同时进行水库库容有上下限机组启停有最小间隔时间一天内抽水量和发电量还要满足水量平衡。这些约束正是调度模型里最需要细致处理的部分。1.3 这个调度模型到底在优化什么把风、光、蓄放在一起做互补调度本质上是在解一个带约束的优化问题。目标函数按照研究侧重点不同可以是最小化系统运行成本、最大化新能源消纳量也可以是最小化等效负荷波动或碳排放量。实际课题中最多的是以系统运行成本最小为目标同时兼顾弃风弃光率。决策变量包括各时段抽蓄机组的发电功率和抽水功率、火电机组的出力分配以及需要时还可以把联络线交换功率纳入进来。约束条件包括电力平衡约束、机组出力上下限、爬坡约束、水库水量与库容约束、系统备用约束等。听起来和普通的经济调度没有本质区别但加入抽蓄之后就多了一个“时间耦合”的特性决定当前时段抽多少水会影响未来时段的发电能力所以不能逐时段独立求解必须把整个调度周期内各时段的决策统筹起来一起优化。这个特性也决定了求解算法的选择——需要能够处理非线性、高维度、含约束的优化问题粒子群算法这类智能算法在这种场景下是常用的选择后面细讲。2. 从物理特性到数学模型2.1 风电出力特性建模风电出力建模在调度研究中一般不做微观空气动力学计算工程上最常用的思路是先给定预测功率曲线再叠加不确定性场景。基础模型是风功率转换关系式[ P_w \begin{cases} 0, v v_{ci} \text{ 或 } v \geq v_{co} \ \frac{v^3 - v_{ci}^3}{v_r^3 - v_{ci}^3} P_r, v_{ci} \leq v v_r \ P_r, v_r \leq v v_{co} \end{cases} ]其中(v_{ci})、(v_r)、(v_{co})分别是切入风速、额定风速和切出风速(P_r)是额定功率。实际写代码时直接把预测出力序列读入就行风电的时序功率数据可以从项目资料或公开数据集里获取。2.2 光伏出力特性建模光伏出力同样建议直接用预测曲线如果要从光照强度推算标准公式是[ P_{pv} \eta_{pv} \cdot A \cdot G \cdot [1 - \beta(T_c - T_{ref})] ]其中(G)是太阳辐照度W/m²(A)是光伏板总面积(\eta_{pv})是光电转换效率(\beta)是温度系数(T_c)是电池板温度。这个公式的价值在于可以做场景敏感性分析比如你想要考察温度升高时光伏出力怎么变化改参数重新跑一遍就行。在Matlab里我习惯把风、光数据统一组织成矩阵行是时段比如一天24个时段列是不同场景。这样后续写约束条件时向量化操作会非常方便不需要写一堆for循环。2.3 抽水蓄能机组运行模型抽蓄的建模是整套模型的核心。单台机组的运行工况可以简化成水泵工况、发电工况和停机工况三种状态。数学描述上发电功率和抽水功率各有上下限并且同一时刻只能处于一种工况。抽发之间的能量转换关系是发电时[ E_{gen} \eta_g \cdot V_{dis} \cdot \rho \cdot g \cdot H ]抽水时[ E_{pump} \frac{V_{pump} \cdot \rho \cdot g \cdot H}{\eta_p} ]其中(\eta_g)、(\eta_p)分别是发电效率和抽水效率一般取0.85-0.9(H)是水头差(V)是水量。调度研究中更常用的是能量层面的简化抽水1度电发电时大约只能收回0.75度电左右。这个能量损耗是抽蓄调度的关键因素模型必须体现出来否则优化结果会倾向于频繁抽发而失去经济性。水量平衡约束要写清楚上下水库的联动关系。以上水库为例[ V_{t1} V_t \eta_p \cdot P_{pump,t} \cdot \Delta t \cdot k_1 - P_{gen,t} \cdot \Delta t / k_2 ]这里的(k_1)、(k_2)是功率-水量换算系数具体数值根据水头和库容曲线确定。简化处理时也可以直接用能量作为状态变量建模即上水库的“储能电量”等于之前累计的抽水电量乘以效率再减去累计发电量。两种方式效果等价但能量建模写代码更直观推荐初学者使用% 上水库储能量动态方程24时段 E_store(1) E_store_0; for t 1:24 E_store(t1) E_store(t) pump_power(t) * eta_p * dt - gen_power(t) * dt / eta_g; end这个方程是整个调度模型里最核心的约束之一。它把“当前时段的决策”和“未来时段的可用能量”联系了起来实现了时间维度上的解耦——每时刻的决策不再独立而是受制于此前积累的储能状态。3. 互补调度优化模型与约束体系3.1 目标函数经济性与环保性怎么权衡我做的这个模型目标函数选的是系统综合运行成本最小包含三部分火电煤耗成本、抽蓄运行维护费用、弃风弃光惩罚成本。写出来就是[ \min ; C \sum_{t1}^{T} \left( \sum_{i1}^{N} (a_i P_{i,t}^2 b_i P_{i,t} c_i) c_{om} (P_{pump,t} P_{gen,t}) c_{curt} (P_{wind,t}^{pred} - P_{wind,t}^{sch} P_{pv,t}^{pred} - P_{pv,t}^{sch}) \right) ](a_i)、(b_i)、(c_i)是火电机组的煤耗系数(c_{om})是抽蓄单位电量运维成本(c_{curt})是弃风弃光惩罚系数。惩罚项的存在非常关键它把“多消纳新能源”转化成了目标函数内的经济信号。只要惩罚系数设置合理优化结果就会自动倾向于优先利用风电和光伏出力而不是一味压低成本而牺牲清洁能源消纳。实际调试时你会发现惩罚系数的大小直接影响结果走向。设得太大优化算法会为了消纳最后1%的新能源而不计成本地调用抽蓄导致抽蓄频繁启停设得太小又会出现明显的弃风弃光现象。我一般先设一个基准值然后通过敏感性分析调整观察弃风弃光率和总成本之间的平衡点。3.2 约束条件电力平衡、机组出力、库容水位约束条件分四类全部要转换成一堆矩阵或者函数句柄才能在优化算法里处理第一是功率平衡约束。任一时刻所有电源出力之和要等于负荷加上抽蓄的抽水功率[ \sum_{i1}^{N} P_{i,t} P_{w,t} P_{s,t} P_{gen,t} P_{load,t} P_{pump,t} ]注意抽水功率在这里是“负荷”放水发电是“电源”符号方向不要搞错。第二是机组出力上下限约束。火电有最小技术出力和最大出力限制抽蓄在发电工况和抽水工况下也各有功率范围。爬坡约束也要加火电机组相邻时段出力变化不能超过爬坡速率。第三是上水库库容约束。储能量不能超过水库最大容量也不能低于死水位对应的最小储能量。同时调度周期结束时上水库的储能量要恢复到初始值这样才能保证调度方案可以循环执行。第四是逻辑约束。同一时段不能同时抽水和发电。建模时通常引入二进制变量(\delta)来处理[ \delta_t \cdot P_{gen}^{min} \leq P_{gen,t} \leq \delta_t \cdot P_{gen}^{max} ] [ (1-\delta_t) \cdot P_{pump}^{min} \leq P_{pump,t} \leq (1-\delta_t) \cdot P_{pump}^{max} ]这个约束处理起来比较麻烦涉及混合整数问题后面讲算法时会讨论怎么处理。3.3 为什么选粒子群算法而不是线性规划看到这里你可能会问目标函数里有二次项约束里有混合整数直接用商业求解器或者Matlab自带的优化工具箱不香吗确实如果问题规模不大可以直接用intlinprog处理混合整数线性规划或者fmincon处理非线性规划来求解。我自己的实践体会是问题规模小、约束条件线性化程度高的时候求解器效率远高于智能算法结果也更稳定。但当约束条件复杂、目标函数非线性强、变量维度升到几十上百时传统求解器容易陷入局部最优或者干脆不收敛这时候粒子群算法这类启发式算法反而更灵活——它不要求目标函数和约束有特定的数学形式改模型时不需要把约束重新线性化。粒子群算法的思路很直观一群粒子在可行解空间里飞行每个粒子记住自己找到的最好位置个体最优同时知道整个群体目前找到的最好位置全局最优然后根据这两个信息更新速度和位置。迭代若干代之后粒子群收敛到最优解附近。代码量小易于实现做课题研究时快速改模型、跑对比方案很合适。对于抽蓄的“不能同时抽发”逻辑约束粒子群的一个取巧处理是把发电功率和抽水功率都作为决策变量但施加一个惩罚项当二者同时大于0时给目标函数加一个很大的惩罚值。这样粒子在搜索过程中会自然避开“既抽又发”的不可行区域避免引入整数变量大大简化了编程难度。4. 粒子群算法Matlab实现附可跑通的代码框架4.1 调度周期与数据准备我以一天24个时段为例每个时段1小时。输入数据包括24时段的负荷预测值、风电预测出力、光伏预测出力、火电机组参数、抽蓄参数。数据从CSV读入是我测试下来最稳定的方式用readmatrix或readtable都可以。这里提一个热知识readmatrix在读取带有时间列的CSV时会自动处理格式但如果你只需要数值部分直接用readmatrix(file, Range, B2:Y25)指定范围更省心。% 读取数据示例 data readmatrix(schedule_input.csv, Range, B2:Y25); load_profile data(1, :); % 负荷 wind_forecast data(2, :); % 风电预测 pv_forecast data(3, :); % 光伏预测数据准备好后把决策变量定义成向量形式。假设火电有2台机组加上抽蓄发电功率和抽水功率每个粒子的决策变量维度就是 (24 \times 4)拉平成一维向量就是96维。粒子群处理高维问题时维度越高收敛越慢这个维度规模还在可接受范围内。4.2 粒子群主程序写粒子群主程序时我习惯把“算法主循环”和“适应度函数”分开这样调试时只改一个文件就行。主循环代码如下% 粒子群主程序 nVar 96; % 决策变量数量2台火电*24 抽发*24 抽水*24 nPop 80; % 种群数量 maxIter 300; % 最大迭代次数 % 初始化粒子位置和速度在边界内随机 X repmat(xmin, nPop, 1) rand(nPop, nVar) .* repmat((xmax - xmin), nPop, 1); V zeros(nPop, nVar); % 计算初始适应度 fitness zeros(nPop, 1); for i 1:nPop fitness(i) myCostFunction(X(i, :)); end % 初始化个体最优和全局最优 pbestX X; pbestF fitness; [gbestF, gbestIdx] min(fitness); gbestX X(gbestIdx, :); % 迭代 w 0.9; c1 2.0; c2 2.0; % 惯性权重和学习因子 for iter 1:maxIter w 0.9 - 0.5 * iter / maxIter; % 惯性权重线性递减 for i 1:nPop % 更新速度速度边界裁剪 V(i, :) w * V(i, :) c1 * rand(1, nVar) .* (pbestX(i, :) - X(i, :)) ... c2 * rand(1, nVar) .* (gbestX - X(i, :)); V(i, :) max(min(V(i, :), vmax), -vmax); % 更新位置 X(i, :) X(i, :) V(i, :); % 边界处理越界粒子拉回边界 随机扰动 outLow X(i, :) xmin; outHigh X(i, :) xmax; X(i, outLow) xmin(outLow) 0.1 * rand(size(xmin(outLow))) .* (xmax(outLow) - xmin(outLow)); X(i, outHigh) xmax(outHigh) - 0.1 * rand(size(xmax(outHigh))) .* (xmax(outHigh) - xmin(outHigh)); % 重新计算适应度 fitness(i) myCostFunction(X(i, :)); % 更新个体最优 if fitness(i) pbestF(i) pbestF(i) fitness(i); pbestX(i, :) X(i, :); end end % 更新全局最优 [bestF, bestIdx] min(pbestF); if bestF gbestF gbestF bestF; gbestX pbestX(bestIdx, :); end fprintf(迭代%d: 最优成本%.4f\n, iter, gbestF); end代码里的速度边界vmax我通常设成变量范围的10%-20%太小容易收敛慢太大会导致粒子在可行域边缘反复振荡。4.3 适应度函数怎么写适应度函数是整个模型的核心它把决策变量翻译成目标函数值和约束违反量。我的写法是先计算目标函数值再计算各约束的违反程度最后把违反程度乘以一个大惩罚系数加进适应度里。function f myCostFunction(x) % 解码决策变量 P1 x(1:24); % 火电1出力 P2 x(25:48); % 火电2出力 Pgen x(49:72); % 抽蓄发电功率 Ppump x(73:96); % 抽蓄抽水功率 % 基础成本计算 fuelCost1 sum(a1 * P1.^2 b1 * P1 c1); fuelCost2 sum(a2 * P2.^2 b2 * P2 c2); omCost c_om * sum(Pgen Ppump); % 弃风弃光量计算 curt sum(max(0, wind_forecast - wind_schedule) max(0, pv_forecast - pv_schedule)); curtCost c_curt * curt; % 功率平衡约束检查 balanceViolation sum(abs(P1 P2 Pgen wind_schedule pv_schedule - load_profile - Ppump)); % 水库库容约束 E_store zeros(25, 1); E_store(1) E_init; for t 1:24 E_store(t1) E_store(t) Ppump(t) * eta_p - Pgen(t) / eta_g; end storeViolation sum(max(0, E_store - E_max)) sum(max(0, E_min - E_store)); % 最终适应度 目标函数 约束惩罚 penalty 1e5; % 惩罚系数 f fuelCost1 fuelCost2 omCost curtCost penalty * (balanceViolation storeViolation); end注意max(0, ...)这种写法能自动把约束违反量线性化处理配合惩罚系数让粒子快速逃离不可行区域。实际调参时如果发现粒子经常越界就把惩罚系数调大如果结果精度不够稳定就减小惩罚系数并增大种群规模。4.4 算法参数调试心得粒子群参数说多不多但每个都影响结果。惯性权重(w)我采用线性递减策略从0.9降到0.4。初始(w)大粒子全局搜索能力强不容易漏掉好的区域迭代后期(w)小局部搜索能力强结果更精细。学习因子(c_1)、(c_2)通常都取2.0但如果你发现结果振荡厉害可以把(c_1)调小一些比如1.5减少粒子“过度自信”导致的速度振荡。种群规模和迭代次数的选择我建议宁多勿少。96维问题种群80-100、迭代300-500次是一个相对稳妥的配置。如果跑了500次迭代成本还在明显下降那就继续加大迭代次数直到连续50代成本变化小于0.1%再停止。调试时可以先跑一个较小的种群快速验证模型逻辑Model调通后再加大规模进行正式实验。5. 常见问题与排查技巧实录5.1 运行Matlab时的典型问题速查表这个项目在Matlab环境下跑的过程中我自己遇到过的和帮别人排查过的问题不少整理成表格方便查阅。问题现象根本原因解决办法intlinprog或优化工具箱函数未定义缺少Optimization Toolbox工具箱在Matlab的“附加功能”里安装Optimization Toolbox也可以用粒子群等自编算法绕过粒子群迭代发散适应度出现NaN决策变量超出物理允许范围比如功率为负值或过大检查边界设置检查代码中sqrt或log函数是否对负数求值导致NaN在适应度函数开头加数值有效性判断结果中抽蓄同时抽水和发电逻辑约束未处理或惩罚系数太小在适应度函数中增加同时抽发的惩罚项系数设为无法接受的量级水库储能量不守恒末时刻偏离初始值水量平衡方程写错或效率系数应用位置反了单独写一段代码验证储能量动态方程输入一个固定抽发序列人工核算一遍收敛速度极慢1000代还在波动决策变量范围和速度范围设置不合理检查变量初始化和vmax设置尝试归一化决策变量到[0,1]区间结果随机性大每次运行不一致粒子群算法本质是随机优化算法未设置随机种子统一用rng(固定值)设置随机种子便于对比实验复现5.2 数据处理与导入的常见坑Matlab做课题时数据处理往往是隐藏的时间杀手。CSV导入这块readmatrix是实测最稳的函数但有两个注意点文件路径不能有中文或特殊符号否则部分老版本Matlab会报错如果CSV第一行是表头readmatrix默认会跳过表头但如果你手动指定了Range表头跳过的行为会变化最好用readtable先看一眼数据长什么样再决定怎么读。还有一个小坑是时间列的处理。很多新能源数据文件第一列是时间戳比如2025-01-01 00:00如果你只需要数值列直接readmatrix会自动把时间列忽略但有时候数据文件里的时间是日期序列值Excel导出的常见坑就会把时间列当成普通数值读进来导致你的数据矩阵整体错位。我的习惯是每次读入后先size()检查矩阵维度再plot()一眼扫一遍曲线确认数据没串位再往下走。单列数据转置、多列提取这些基础操作就不展开了。有一点值得提Matlab R2016b之后推荐用datetime类型处理时间索引做预测数据对齐时非常方便。如果你需要把datetime转成字符串用于文件名直接用char(datetime)或datestr都能实现但需要注意datetime在R2022a之后显示格式有调整建议显式指定Format属性。5.3 结果不收敛、振荡怎么排查粒子群的结果如果呈现明显的振荡优先检查三件事第一目标函数的量级是否合理。如果你的成本值是百万级别的而约束惩罚只有十万级别粒子就会宁愿违反约束也要降低成本结果里会出现功率平衡明显被破坏的情况。解决方法是把惩罚系数抬高到目标函数正常量级的10-100倍。第二速度更新是否越界。没有速度裁剪的粒子群会越飞越远位置越来越离谱。务必加上V max(min(V, vmax), -vmax)这行代。实际测试中加了速度裁剪后同样的模型迭代300次的效果比不裁剪迭代1000次还稳定。第三初始化是否覆盖了整个可行域。如果repmat(xmin)和repmat(xmax)写反了或者初始位置全部集中在一个角落粒子群前几百代都在赶路收敛自然慢。建议初始化后做个直方图确认粒子分布均匀。6. 结果分析、扩展方向与我的几点体会6.1 调度结果怎么分析算完出结果之后除了看最终成本值我建议至少画三张图第一张是各电源出力时序堆叠图可以直接看出抽蓄在负荷低谷时抽水、高峰时发电的“削峰填谷”行为是否符合预期第二张是上水库储能量变化曲线正常情况应该是一个先下降后回升或者先上升后下降的平滑曲线如果储能量曲线剧烈震荡说明约束或惩罚项的位置有问题第三张是弃风弃光功率曲线用来验证多能互补的消纳效果。很多同学喜欢只看一个总成本和收敛曲线就收工这远远不够。抽蓄调度方案的合理性必须通过时序图来检验——比如夜间抽水功率恰好等于风电富余功率说明消纳逻辑是对的如果抽蓄在白天光伏大发时反而发电那模型大概率出错了。6.2 从简化算例到实际工程的升级路线课题做完了如果想往工程应用方向延伸这个模型有不少升级空间。一是把新能源出力的不确定性显式建模比如用场景法或者机会约束规划来处理风电光伏预测误差让调度方案具备鲁棒性。二是加入多时间尺度协调日前计划、日内滚动修正、实时调整三层架构是实际调度系统的标准配置。三是把火电的启停费用和最小启停时间加进去模型复杂度提高一个档次但更贴近真实电网运行需求。如果性能吃紧可以试试把粒子群和局部搜索算法结合做混合算法——用粒子群做全局搜索再用fmincon做局部精修。这两个方法在Matlab里都有现成接口组合使用效果往往比单一算法更好。6.3 个人体会先跑通后优化再回归物理这个课题做了三轮之后我最深的体会是调度优化项目的核心难点不在算法而在模型物理含义的准确性。粒子群、差分进化、遗传算法这些算法代码在网上都能找到成熟版本真正决定结果可信度的是你对抽蓄运行规律、电力平衡约束、弃能惩罚机制的理解深度。很多同学找到一个标准的Particle Swarm代码就拿自己数据开跑结果曲线很漂亮成本也降了但仔细一看抽蓄在白天高峰时段还在抽水这明显不符合物理逻辑整个结论就站不住脚了。所以我强烈建议在写完模型代码之后先构造一个简单的两时段算例比如低谷时段和高峰时段各1小时手工计算一遍最优分配再用完整模型去复现。这一步能帮你验证所有约束方程都写对了再上大算例就会少走很多弯路。这种“先跑通、再优化、最后回归物理意义检验”的习惯是我做这个课题积累的最重要的经验。最后分享一个小技巧调度模型代码版本管理一定要做好。从最简单的模型开始每加一个约束就保存一个版本加完跑一次测试。这样出现问题时可以快速知道是哪个改动引入了错误不用对着几百行代码从头揪到尾。Matlab对文件夹路径要求严格建议每个课题建立一个独立项目目录把数据、主程序、适应度函数、结果图分开存放运行环境保持一致避免不同版本Matlab或工具箱差异带来的麻烦。