ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

风电、光伏与电池及废弃矿井抽蓄互补调度Matlab实现解析

风电、光伏与电池及废弃矿井抽蓄互补调度Matlab实现解析 风电、光伏这种新能源出力靠天吃饭波动性和随机性几乎是刻在骨子里的。单独并网时候电网调度的压力还能靠火电硬扛可再生能源渗透率一上来光靠预测已经不够了必须引入储能这个缓冲池。而储能的选型又不该只盯着一类电池更要考虑工程条件、响应速度、容量成本这些维度组合起来的整体价值。我最近做的这个风电、光伏与储能(含电池和废弃矿井小型抽水蓄能)互补调度运行研究的Matlab项目底层逻辑就是围绕怎么把这些出力特性差异巨大的电源和两类储能拧成一股绳在保证供需平衡的前提下把新能源利用率提到最高、系统运行成本压到最低来展开的。这篇我打算把整个项目的建模思路、调度策略、求解逻辑以及Matlab代码实现中的关键环节和坑点完整拆开聊一聊适合有电力系统分析基础、正在做新能源消纳或者储能调度方向研究的朋友参考也适合刚接触YALMIP这类优化建模工具的读者跟做复现。1. 项目整体设计与调度架构——先把互补这层关系想明白1.1 为什么是风电光伏电池废弃矿井抽蓄这四类组合做互补调度之前先得把各台机组的性格脾气摸清楚。风电的出力集中在夜间和后半夜冬季风大、夏季相对弱出力波动以分钟到小时级为主光伏恰好相反白天午间出力峰值夜间零出力受云层遮蔽影响时出力会出现锯齿状跳变。电池储能的特点是响应速度快(毫秒到秒级就能完成功率转换)、能量密度高但成本也高更适合做短时功率平滑和日内峰谷套利废弃矿井小型抽水蓄能则是把退役矿井的巷道、竖井、采空区改造成具有上下水库条件的抽蓄电站启停速度虽然比不上电池但胜在容量大、寿命长、度电成本低适合做小时级以上的电量搬移。这四类组合放在同一套调度框架里本质是在做快慢搭配风电、光伏在满足负荷和各类约束的前提下尽量多发作为能量的主要供给源电池储能承担高频波动分量和短时功率支撑充当秒级到分钟级的缓冲矿井抽蓄承担长时段的电量吞吐白天光伏出力大时抽水蓄能、晚上负荷峰段放水发电充当小时级到日级的储能池系统还允许适度的弃风弃光作为灵活性措施兜底避免储能容量不足时强行调峰导致系统崩溃。这个架构里调度决策的核心难点在于电池和抽蓄的时间尺度不同、功率响应特性不同、运行约束形态也不同不能简单地用一个统一的储能模型替代。我在Matlab里最终是把两类储能拆开建模再用功率平衡方程和互补协调逻辑把它们共同的系统级约束串起来。这套思路比单独使用电池储能或者单独使用抽水蓄能的方案在平抑波动和降低运行成本上的优势非常明显。1.2 调度体系的整体运行逻辑项目采用的是日前调度为主、日内滚动修正为辅的框架。日前调度基于风电、光伏出力和负荷的预测曲线以24小时为优化周期、1小时为调度间隔安排次日各机组的出力计划、两类储能的充放电计划以及弃风弃光决策日内滚动修正则每15分钟到1小时重新优化剩余时段用实时数据替换预测值消除预测误差的影响。在Matlab代码实现里我通常把这两层决策写成两个相对独立又共享参数的模块日前层输出各时段的机组参考出力值和储能充放电参考值日内层以日前计划为基准在可调范围内做偏差修正目标函数里加入计划偏离罚项避免大面积重新调度造成机组频繁爬坡。这套两层递进的架构在工程上非常成熟既能保证经济性最优又能兼顾实时运行的可靠性。代码层面不需要特别复杂的技巧关键是数据接口要设计清楚。我的做法是日前模块的结果保存成一个struct日内模块直接读取两个模块共用一套约束构建函数避免同日和日内代码逻辑不一致导致结果对不上。1.3 调度目标的多维度权衡调度运行研究里目标函数设计直接决定优化结果的方向。这个项目中我设置了三个维度并用加权方式整合成一个综合目标经济效益维度购电成本(这里把系统视为一个微电网/独立供电区域与主网存在电量交互时计入购售电价差)、储能运行维护成本、弃风弃光的惩罚成本运行安全维度馈线功率波动惩罚反映在满足负荷的前提下希望系统净出力曲线尽量平稳减少对电网的冲击资源利用维度新能源利用率最大化也就是弃风弃光总量最小化。权重系数的取值我用的是数量级归一化层次分析法辅助确定的方式。具体来说先把三个目标的量纲统一到折算成本上——新能源弃电惩罚系数取为对应时段上网电价的0.8~1.2倍运行安全惩罚则按馈线功率标准差折算成虚拟成本。通过这种设计优化结果自然而然地优先保证高收益、低弃电的系统运行方式而不会因为某个目标量纲过大扭曲整体调度方向。2. 风电与光伏出力建模——预测数据和场景生成的细节处理2.1 风电出力模型与实测数据的预处理风电出力建模在调度研究里一般有两种路径一种是基于实际风速数据建立风速-功率转换关系另一种是直接采用已有的出力时序数据。由于Matlab里做调度优化更关注的是出力场景本身我在项目中采用了后一种方式但加入了数据清洗和归一化的步骤。具体做法是读入原始的风电功率时序数据后先检查是否存在负值(测量异常)、空值或者超过装机容量的记录对异常点采用前后时刻线性插值修正然后按装机容量归一化到0到1之间得到单位功率出力序列。考虑到风电的昼夜特性和季节性我会把全年数据按典型日类型聚类选出大风日、中风日、小风日三类场景作为调度输入。聚类用Matlab内置的kmeans函数就能完成简单高效。风电出力建模还有一个关键点时间分辨率的匹配。如果调度间隔是1小时那么风电数据必须按小时平均或者取代表值不能直接把15分钟数据塞进小时级模型。我在代码里统一用reshape和mean组合的方式做数据聚合确保后续跟负荷曲线、光伏曲线的时间轴完全对齐。2.2 光伏出力模型与辐照度转换光伏出力的理论模型基于辐照度、环境温度和光伏板转换效率三项因素。经典公式为P_pv(t) eta_pv * A_pv * G(t) * (1 - beta * (T_c(t) - T_ref))其中eta是光电转换效率A是光伏板面积G是倾斜面上的辐照度beta是温度系数(通常取0.003~0.005每摄氏度)T_c是电池板工作温度T_ref是标准测试温度(25摄氏度)。电池板工作温度可以用环境温度加上辐照度引起的温升近似估算。不过实际调度代码里我更推荐直接使用归一化的光伏出力曲线作为输入原因有两点一是安装地的实测辐照度数据往往有限二是调度优化不关心光伏阵列内部的电气细节只需要知道该时段光伏最大可发功率是多少。项目代码中我把标准光伏出力曲线(晴天条件下近似余弦形状)乘上随机天气系数模拟多云天气下的出力波动再用Matlab的randn加扰动生成多组预测误差场景。这种方式做场景生成速度快而且能自然体现光伏出力中午高、早晚低的典型形态。2.3 预测误差处理与典型场景生成调度模型能否贴近实际运行很大程度取决于预测误差建模是否合理。我在项目里采用了正态分布误差叠加法假设风电出力预测误差服从均值为0、标准差为预测值比例的正态分布光伏预测误差的分布则带一定的偏斜特征(阴天时段误差更大)。基于这样的假设利用Matlab的mvnrnd函数生成500组误差向量叠加在确定性预测曲线上再用K-means聚类缩减为10组典型场景每组场景附带发生概率。这些场景在优化模型里以机会约束或者鲁棒区间的形式参与调度决策。为了避免模型复杂度过高我在基础版本中采用了确定性等效方式用典型场景的期望值作为约束输入只在日内滚动层加入场景集的宽松边界。这种期望区间的组合既能反映预测不确定性又不会让求解时间失控。3. 双储能系统建模——电池和废弃矿井抽蓄怎么差异化配合3.1 电池储能建模的四个核心约束电池储能在调度模型里不是一个能充能放的黑盒必须准确刻画它的运行边界。我在Matlab代码中为电池建立的状态包括充放电功率、荷电状态(SOC)、以及是否处于充电/放电/闲置状态的三组0-1变量。核心约束分四类功率边界约束充电功率在0到最大充电功率之间放电功率在0到最大放电功率之间SOC动态更新方程SOC(t1) SOC(t) eta_ch * P_ch(t) * dt / Cap - P_dis(t) * dt / (eta_dis * Cap)其中eta_ch和eta_dis分别表示充放电效率Cap是电池额定容量SOC上下限约束一般限制在0.1到0.9之间避免过充过放损伤电池寿命充放电互斥约束同一时段不能同时充电和放电用二进制变量实现。电池的充放电效率我取了两个不同数值充电效率0.95放电效率0.92。这个细节很多初写调度代码的人容易忽略但恰恰是它决定了实际储能吞吐的端到端效率直接反映在系统经济性上。3.2 废弃矿井小型抽水蓄能的建模要点废弃矿井改造的抽水蓄能项目跟传统抽蓄站在运行特性上最大的差别在于水头高度受矿井巷道结构限制上水库容量可能很小因此不能简单套用大型抽蓄的模型。我把这个小抽蓄抽象成四个关键参数最大发电功率P_g_max和最大抽水功率P_p_max由水轮机和水泵机组决定上水库有效库容V_up_max决定最大储存水量对应的等效电量水头H和容量转换系数k关系为E mppt rho * g * H * V * eta其中eta是综合效率(发电状态约0.75抽水状态约0.85)水头变化对功率的影响由于巷道式上水库水位浮动可能明显我在模型中用平均水头近似处理并设置功率修正系数。对应的约束形式跟电池的SOC方程类似但物理量不同。我定义变量V_sto(t)表示t时段末上水库的储水量动态方程为V_sto(t1) V_sto(t) eta_p * Q_p(t) * dt - Q_g(t) * dt其中Q_p(t)是抽水流量(对应功率)Q_g(t)是发电流量。V_sto限制在最小死水位库容和最大库容之间。同时单一时段内抽水和发电也是互斥的。3.3 两类储能互补的协调逻辑把电池和抽蓄放下同一个系统里不代表它们各干各的就好关键在于协调策略。我在代码里引入了一条响应分层原则电池优先承担负荷跟踪和波动平抑任务功率指令变化快但总量小抽蓄承担电价套利和削峰填谷任务功率指令变化慢但单次持续时长长。具体到数学实现是在目标函数中加入两类储能的功率变化量惩罚项电池储能惩罚系数取小值允许它频繁调整抽蓄惩罚系数取较大值抑制它频繁启停。这个做法在优化结果中会自然形成电池高频小幅度吞吐、抽蓄低频大幅度吞吐的功率分配格局与实际工程经验一致。项目实施后我对两类储能的利用率做了统计电池日均有效吞吐次数约为6~8次抽蓄日均启停次数控制在2次以内。这个分配比例在确保系统灵活性的同时也大大减少了抽蓄机组的机械磨损风险。4. 互补调度优化模型——目标函数、约束体系与求解方法4.1 目标函数的具体数学形式以系统日运行总成本最小为基本目标考虑微电网与主网的电量交互成本时目标函数可以写成min sum_t( c_grid(t) * P_grid(t) ) sum_t( c_om_wt * P_wt(t) c_om_pv * P_pv(t) ) sum_t( c_om_bat * (P_ch(t) P_dis(t)) ) sum_t( c_om_pv2p * P_p2g(t) ) sum_t( c_cur * (P_wt_avail(t) - P_wt(t) P_pv_avail(t) - P_pv(t)) )各项含义分别是从主网购电成本、风电机组和光伏阵列的运行维护成本、电池充放电运行成本、抽蓄机组运行成本、弃风弃光惩罚成本。其中P_wt_avail和P_pv_avail是该时段最大可发功率如果实际调度值小于可发值就产生了弃电惩罚随之产生。我还加入了馈线功率平稳性目标作为次级目标通过在目标函数里加入馈线功率变化平方项促使优化结果主动平抑波动而不是把所有波动都甩给储能。实际运行中这个目标对于减少电池过度充放有效也明显降低了并网点的功率冲击。4.2 约束体系的完整搭建模型约束分五组功率平衡约束风、光、电池放电、抽蓄发电、主网购电之和等于负荷加上电池充电、抽蓄抽水、主网售电之和。这一条是整个模型的脊梁必须保证每个时刻严格成立机组出力边界风、光出力在0到最大可发功率之间主网交互功率在限值以内电池运行约束SOC动态、容量上下限、充放电功率边界、充放互斥抽蓄运行约束库容动态、库容上下限、发电/抽水功率边界、启停互斥爬坡约束风电和光伏不设爬坡限制(出力由资源决定)但主网交互功率设置爬坡限制防止并网点功率突变。爬坡约束的引入很关键。我在前几版模型里遗漏了它导致优化结果中出现电网交互功率从0跳变到3MW的瀑布曲线明显不符合工程实际。加上爬坡限制后结果曲线平滑得多系统运行方案的可执行性大幅提升。4.3 求解方法与Matlab工具链选型这个模型是混合整数线性规划(MILP)问题整数变量来自电池和抽蓄的启停互斥约束。我在Matlab中的求解框架优先选择YALMIPR2025b内置求解器打底先用YALMIP建模调用intlinprog或者商业求解器求解。为什么用YALMIP而不是直接写矩阵因为调度模型涉及的约束和变量数量多手写约束矩阵非常容易出错而且一旦规模扩大(比如场景数增加到20个、调度间隔改为15分钟)代码可维护性会变得极差。YALMIP的符号建模方式让我能像写数学公式一样写约束调试效率高很多。求解器的选择也有讲究如果模型规模小(几百个变量)直接用intlinprog就够速度快且不依赖外部工具箱如果模型规模大(上千个变量)建议配置Gurobi或者CPLEX尤其在处理场景数较多的随机优化时商业求解器在整数变量处理和并行计算上的优势非常明显。我在项目中两套方案都跑过对比的结果是Gurobi在2000个变量、10个场景的情况下比intlinprog快约3倍。5. Matlab代码实现的核心环节拆解——从数据到结果的完整链路5.1 数据准备与参数初始化这部分看起来没技术含量实际是最容易出bug的地方。我习惯把系统参数集中放在一个初始化脚本里用结构体统一管理比如% 基础参数设置 Para.horizon 24; % 调度周期(h) Para.wt_cap 80; % 风电装机容量(MW) Para.pv_cap 60; % 光伏装机容量(MW) Para.bat_cap 20; % 电池容量(MWh) Para.bat_pmax 5; % 电池最大充放电功率(MW) Para.phs_pmax 4; % 抽蓄最大发电/抽水功率(MW) Para.phs_vmax 30; % 上水库有效库容(千立方米) Para.grid_pmax 30; % 主网交互功率上限(MW) Para.eta_ch 0.95; % 电池充电效率 Para.eta_dis 0.92; % 电池放电效率 Para.eta_pump 0.85; % 抽蓄抽水效率 Para.eta_gen 0.75; % 抽蓄发电效率 c_grid_day 0.55; % 主网购电价格(元/kWh) c_cur 0.30; % 弃风弃光惩罚系数(元/kWh)所有原始数据(负荷曲线、风、光出力数据)保存在Excel或mat文件中由一个单独的load_data.m脚本统一读取输出Load_series、Pwt_avail、Ppv_avail三个数组。这样设计的好处是换一组数据时只需替换数据文件不用改动任何主程序代码。参数初始化时要特别注意单位统一。我在项目初期就吃过这个亏——功率用MW、容量用MWh、价格用元/kWh但抽蓄的库容又是按水量(立方米)来算的如果不换算成等效能量约束方程直接乱套。建议把所有物理量统一折算成功率(p.u.)和能量(p.u.)或者统一折算成MWh体系再写进模型。5.2 YALMIP建模与约束构建实现决策变量定义我放在一个独立的build_variables.m文件中核心片段如下% 定义决策变量 P_wt sdpvar(1, 24, full); % 风电实际出力 P_pv sdpvar(1, 24, full); % 光伏实际出力 P_ch sdpvar(1, 24, full); % 电池充电功率 P_dis sdpvar(1, 24, full); % 电池放电功率 u_ch binvar(1, 24); % 电池充电状态 u_dis binvar(1, 24); % 电池放电状态 SOC sdpvar(1, 25, full); % 电池SOC轨迹 % 抽蓄变量 P_pump sdpvar(1, 24, full); % 抽水功率 P_gen sdpvar(1, 24, full); % 发电功率 u_pump binvar(1, 24); % 抽水状态 u_gen binvar(1, 24); % 发电状态 V_sto sdpvar(1, 25, full); % 上水库库容轨迹 % 主网交互变量 P_grid sdpvar(1, 24, full); % 购电为正售电为负这里我把SOC的索引范围设为1到25比调度时段多一位是为了方便处理初始和末状态约束——SOC(1)是初始荷电状态SOC(25)是周期结束荷电状态中间的SOC(i)与第i-1时段的充放电关联这个细节让方程的索引更清晰不会出现分母为零或者下标越界的隐性错误。约束构建部分的关键逻辑是电池充放电互斥约束。YALMIP中可以直接写Constraints []; Constraints [Constraints, 0 P_ch Para.bat_pmax * u_ch]; Constraints [Constraints, 0 P_dis Para.bat_pmax * u_dis]; Constraints [Constraints, u_ch u_dis 1]; % SOC动态 for t 1:24 Constraints [Constraints, SOC(t1) SOC(t) Para.eta_ch * P_ch(t)/Para.bat_cap - P_dis(t)/(Para.eta_dis * Para.bat_cap)]; end Constraints [Constraints, 0.1 SOC 0.9];抽蓄的约束写法类似只是把SOC替换成库容动态而且库容的等效电量换算要单独处理。我的做法是先计算抽蓄能量转换常数发电1MW·h需要的水量由V_per_MWh 1000 / (9.8 * H * eta_gen)确定然后再写库容与功率的关系。5.3 求解、结果整理与指标评估求解部分非常直白ops sdpsettings(solver, gurobi, verbose, 0, showprog, 0); optimize(Constraints, Objective, ops);但求解结束后如何整理结果却是决定后续分析效率的关键。我建议把优化结果集中存放到一个struct中Result.P_wt value(P_wt); Result.P_pv value(P_pv); Result.P_ch value(P_ch); Result.P_dis value(P_dis); Result.SOC value(SOC); Result.P_pump value(P_pump); Result.P_gen value(P_gen); Result.V_sto value(V_sto); Result.P_grid value(P_grid); Result.Obj value(Objective);指标评估我重点算这几个新能源利用率实际消纳电量 / 最大可发电量弃风弃光率弃电量 / 最大可发电量储能吞吐量各时段充放电量之和系统综合运行成本目标函数值馈线功率标准差反映并网波动程度。绘制结果图用plot函数分组展示为了可视化的美观性我通常把风电、光伏、负荷曲线画在第一张图储能功率和SOC画在第二张图抽蓄功率与库容画在第三张图主网交互功率单独画在第四张图。图与图之间的时间轴统一方便对比分析。6. 常见问题与排查技巧实录——这些坑我帮你踩过了6.1 YALMIP求解时出现infeasible problem怎么办无解是调度模型里最让人头疼的问题。遇到无解我的排查顺序是先看功率平衡方程是否自洽把负荷曲线加起来跟风电光伏最大可发量储能最大放电力主网最大购电力做总量对比如果总供给上限小于总负荷系统性无解这种只能改参数再查SOC初末状态约束如果要求SOC(25)SOC(1)且电池初始电量很低可能导致整个周期内可放电量不足这种情况通常需要放宽末状态范围或者提高初始SOC检查爬坡约束主网交互功率爬坡限值设得过小也会在负荷快速变化时段造成无解。我还在代码里为每个约束打了标签(tag参数)一旦YALMIP返回不可行直接查看约束池的具体信息快速定位到是哪一条约束爆掉了。6.2 求解结果出现锯齿状充放电曲线怎么办打开结果图发现电池充放电在相邻时段来回切换这不是bug而是目标函数里缺少对储能动作次数的惩罚。解决方式有两个方向在目标函数中增加充放电切换惩罚项例如0.01 * sum(abs(u_ch(2:24) - u_ch(1:23)) abs(u_dis(2:24) - u_dis(1:23)))或者在求解后加滤波平滑处理但我强烈建议用前者因为后者会破坏优化结果的可行性和物理一致性。加了切换惩罚后电池的调度曲线会明显变懒也就是更贴近工程上尽量减少不必要的动作次数的要求。6.3 抽蓄库容经常在边界处顶格怎么办抽蓄电站库容满或者空的边界状态说明当前调度时段安排不合理或者是库容参数相对于系统规模偏小。我排查过几次后发现最常见的原因是抽蓄的能量转换效率损耗被低估抽水时消耗的功率等于发电功率除以效率平方如果效率取错库容的等效周转能力会被高估调度策略就会出现充不进去、放不干净的假象。解决方式是核对实际水头和机组效率曲线把库容折算时的效率参数调整为该工况下的实测值。还有一个经验技巧给库容约束设置软边界即在目标函数中加入库容越限惩罚项让优化器宁愿调整运行策略也不要把库容顶到物理极限。6.4 代码规模变大后求解时间暴涨怎么办一次性把约束全塞进optimize里求解时间会随着变量和约束数量非线性增长。当我从24时段扩展到96时段、场景数从1增加到20时求解时间从几秒钟涨到了几分钟。我采用的优化措施有三个减少整数变量把抽蓄的启停状态用连续变量松弛只在功率接近零时强制互斥大幅减少分支定界压力采用场景聚合不是所有场景都需要独立变量有些相似场景可以共享一组决策变量只在目标函数中按概率加权冷启动技巧用上一轮的结果作为下一轮迭代的初始解assign可以明显加快收敛尤其是在日内滚动调度中效果显著。Matlab代码要写成模块化风格避免一套脚本跑到底。我最终把项目拆成了setup_parameters.m、build_model.m、solve_model.m、post_process.m四个模块每个模块负责一件事调参、换数据、加约束都很方便。6.5 一个额外提醒结果合理性要人工过一遍数值优化结果再漂亮也要拿工程直觉做检验。曾经有一次优化给出的方案是靠主网购电满足全部负荷干脆把风光全弃了——目标函数值确实最小但显然违背了研究初衷。原因是弃风弃光惩罚系数设得太低低于主网购电成本。这种情况下只要把惩罚系数调高到比购电价更高模型就会优先消纳新能源。这一条也是做调度优化的通用经验惩罚系数的设置逻辑永远要跟实际工程经济激励方向一致否则模型会找到钻空子的极端解。另外我习惯在输出报告里附上一张每类设备日运行统计表包含风电实际利用小时数、光伏利用小时数、电池等效循环次数、抽蓄启停次数、弃电量占比、平均购电价等指标。这张表是判断调度策略是否合理的体检报告比单纯看目标函数值直观得多。我个人在实际操作中还有一个体会这套风光储互补调度的研究边界并不止于24小时日前优化。项目代码稍微扩展一下把时间分辨率改成15分钟、把扇区模型改成多微网互联拓扑就自然能延伸到日内实时调度和远期规划的问题。很多同学一开始犯的错误是使劲堆约束——模型越修越复杂但求解结果却越来越难解释。倒不如像我这样先把基础场景跑通再逐步加复杂度每一步都盯着指标变化看这样既能验证模型正确性也容易产出真正有说服力的结论。最后分享一个小技巧Matlab里的optimize返回的诊断信息一定要充分利用。调试模型时设置verbose 1仔细看求解器输出的每一次迭代信息包括松紧约束、分支次数、整数间隙当你调完一系列参数后这些诊断记录就是判断模型健康状况的最好依据。如果你也在做类似的调度研究建议先把代码跑通一个基准算例再去改储能配置、换预测场景一步步向实际工程场景靠拢这条路走下来整个调度系统的研究框架也就彻底掌握在手里了。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进