资讯动态

电池+矿井抽蓄:风光储互补调度的Python建模与求解

发布时间:2026/10/9 3:55:07 来源:尧图企业网站定制
做新能源多能互补调度这个方向最容易被卡住的不是优化算法本身而是你把现实世界里的风电、光伏、储能揉成一个能让求解器理解的数学模型。去年我参与了一个风-光-储互补调度研究项目储能侧同时接了常规电池和一座利用废弃矿井改造的小型抽水蓄能电站全部调度逻辑用 Python 实现。这篇文章把出力模型、储能建模、目标函数、求解器选型和代码骨架完整过一遍适合正在做风光储优化调度课题的研究生以及需要在 Python 里搭建滚动调度原型的新能源工程师。先说结论这个项目让我最大的收获不是把某个求解器用熟了而是真正理解了“短时储能”和“长时储能”在同一个调度模型里该怎么分工。电池响应快、循环寿命成本高适合做分钟级到小时级的功率平滑抽水蓄能容量大、寿命长但机组启停慢适合做跨时段的大幅能量搬移。两种储能放进同一个优化模型约束条件完全不同如果只是简单粗暴地等效成一个储能块结果会偏离实际很远。1. 为什么多能互补调度值得用 Python 重做一遍先说背景。新能源场站并网之后最头疼的就是弃风弃光和系统调峰压力。风光的出力曲线和负荷曲线在时间上经常错位后半夜风速高、负荷低白天光伏大发但负荷还没到晚高峰。这个时候就需要储能来“挪”能量把低谷时段用不完的电存下来等到高峰时段放出去。单靠电池做这件事问题很明显。电池的功率密度高、响应速度以毫秒计但容量成本高而且频繁深度充放会加速寿命衰减做跨 4 到 6 小时以上的大能量搬移很不经济。单靠常规抽水蓄能也不现实抽蓄电站对地形和水源要求苛刻选址基本被天然条件锁死不是想建就能建。所以就有了一个很有意思的思路把废弃矿井改造为小型抽水蓄能电站。矿山的竖井本身就有几百米落差井下采空区和巷道可以被改造成下水库地面塌陷坑或者堆场改建上水库。相比新建大坝这种改造只是利用已有的井筒和空间土建成本低许多而且矿区往往电网配套成熟——毕竟过去采矿本身就是用电大户。目前国外已经有煤矿退役后改造成抽蓄电站的落地项目国内也在做前期论证。这种电站单机容量不大通常几个兆瓦到几十兆瓦但胜在建设门槛低、分布广很适合做分布式长时储能。这个场景天然适合用 Python 做研究和原型验证原因有三个数据处理顺手。风电光伏调度的大量工作在做时序数据清洗、合并、重采样pandas 处理这类问题很舒服。优化建模生态完整。从 scipy.optimize.linprog 到 PuLP、OR-Tools再到 DEAP 遗传算法线性规划、混合整数规划、进化算法都有的选同一个问题可以用多种方法交叉验证。结果分析和论文出图方便。matplotlib 画功率平衡图、SOC 曲线、水量变化曲线都很成熟改参数重跑也快。我见过不少人用 MATLAB 做这类研究但如果目标是快速迭代模型、随时换求解器、把结果嵌入更大的数据分析流程Python 的灵活度明显更好。2. 风电和光伏出力模型时序数据怎么变成调度输入调度的前提是先把风光出力变成一条有时间戳的功率序列。这一步做不好后面所有优化都是空中楼阁。2.1 风电功率序列从风速到并网功率风电出力不是风速的简单乘除而是经过功率特性曲线转换的。简化模型用的是三段式函数风速低于切入风速一般 3m/s 左右或高于切出风速一般 25m/s时出力为 0风速从切入风速升到额定风速一般在 11 到 13m/s之间功率近似线性上升达到额定风速后功率保持额定值直到切出。用 numpy 向量化写这个函数很简单几行代码就能对整条风速序列做批量转换import numpy as np def wind_power_from_speed(v, v_in3.0, v_out25.0, v_rated12.0, p_rated50000): p np.zeros_like(v, dtypefloat) # 上升段 ramp (v - v_in) / (v_rated - v_in) valid (v v_in) (v v_rated) p[valid] p_rated * ramp[valid] # 额定段 p[(v v_rated) (v v_out)] p_rated return p实际做项目时更推荐直接使用场站的历史功率数据或气象预测数据。如果你手头只有风速数据可以用上面的函数生成功率序列但要同时给出力加上爬坡限制——风电机组的功率变化率不是无限的通常每分钟不超过额定功率的 10% 左右这个约束后期要写进调度模型里否则求解器会给出瞬跳的不合理结果。风电出力还有一个容易被忽略的细节是尾流效应和场站内各机组的不完全一致性。我做这个项目时没有精细建每台机组的尾流模型而是直接把整个风电场的功率上限当作调度变量这种做法在日内调度的尺度上已经足够没必要引入过多非线性。2.2 光伏出力辐照、温度与效率折算光伏出力的核心转换公式可以写成P_pv P_rated * (G / G_ref) * [1 - β * (T_c - 25)]其中 G 是倾斜面辐照度G_ref 是标准测试辐照度1000 W/m²β 是温度系数T_c 是电池板工作温度。夜间 G 接近 0出力自然为 0阴天云层遮挡可以通过给 G 乘一个随机系数来模拟。这里要和关注 MPPT 的朋友多说一句调度模型里光伏功率上限应当取“经过 MPPT 跟踪后的最大功率点功率”而不是光伏组件的理论峰值功率。MPPT 电路的作用是让组件始终工作在最大功率点附近所以调度层不需要再考虑电压电流的搜索过程直接用 MPFT 输出后的功率曲线作为可调度上限即可。如果你看到某篇文献里光伏出力超过额定装机多半是把辐照和组件效率的参数设得太理想了真实场站有逆变器限功率、组件衰减、灰尘遮挡等损耗实际可用出力通常要乘以 0.9 左右的经验系数。2.3 构建统一的时序数据集风电、光伏、负荷三条曲线最终要合并成一个以时间为索引的 DataFrame。我在项目里用的是 96 点/天也就是 15 分钟一个调度时段这个分辨率能保留清晨和傍晚出力陡变的细节又不会让优化模型膨胀到难以求解。import pandas as pd df pd.DataFrame({ wind_avail: wind_power_from_speed(speed_data), pv_avail: pv_power_from_irradiance(irr_data, temp_data), load: load_data, }, indexpd.date_range(start2024-06-01, periods96, freq15min))数据合并时有一个坑不同来源的数据时间戳可能不一致。有些气象数据是整点整点给的负荷数据可能刻到 15 分钟风速数据可能还是 10 分钟采样。我习惯统一先 resample 成 15 分钟间隔用前向填充处理缺失值然后再进建模流程这样能少踩很多时间索引错位的坑。3. 电池储能和废弃矿井抽水蓄能的差异化建模两种储能虽然最终都表现为“某时刻可以充电也可以放发”但内部动态约束差别非常大必须分开建模。3.1 电池储能SOC、充放效率与寿命折损电池储能的经典递推公式是SOC(t1) SOC(t) [P_ch(t) * η_ch - P_dis(t) / η_dis] * Δt / E其中 P_ch 是充电功率P_dis 是放电功率η_ch 和 η_dis 分别是充放电效率E 是电池额定容量Δt 是时段长度。SOC 始终要保持在 [SOC_min, SOC_max] 区间内一般取 10% 到 90%这样既保护电池也为系统留有备用。建模时必须显式防止同一个时段既充电又放电否则求解器会利用“充电升 SOC、放电满足负荷、两者同时发生”的漏洞产生伪最优解。通常用一对互斥的 0-1 变量来约束P_ch M * y_chP_dis M * y_disy_ch y_dis 1M 取一个足够大的数比如电池最大功率的 1.5 倍。寿命折损这个点容易被忽略但对实际运行影响很大。调度模型如果完全不考虑电池衰减结果会倾向让电池频繁满充满放这在纸面上漂亮实际运行中电池寿命会迅速恶化。折损可以简化成两种做法一是限制日等效循环次数二是给每次充放电加一个边际成本。我在项目里用的是一天循环次数的硬约束加上一个很小的充放电成本项既保持模型线性又能有效抑制过激的电池使用策略。3.2 废弃矿井小型抽蓄水量守恒与电力约束废弃矿井抽蓄的结构其实不复杂竖井本身就是压力管道地面端是上水库可以是一个坑塘或者改造的储水池井下巷道和采空区是下水库。水从上部放到下部时推动水轮机发电发电后再用多余电能抽回上部循环往复。所谓水头大致就是矿井深度几百米很常见。它的核心约束是水量守恒而不是 SOC 那种能量守恒。上水库的水量递推关系是V_up(t1) V_up(t) - Q_gen(t) * Δt Q_pump(t) * Δt发电功率和流量之间的关系是P_gen ρ * g * H * Q_gen * η_turbine抽水功率和流量的关系是P_pump ρ * g * H * Q_pump / η_pump实际建模时通常直接把每个时段的发电功率和抽水功率当作决策变量再用线性系数反推流量变化。上水库和下水库的库容都有上下限防止放干或者溢流。此外还要设置发电和抽水的功率上下限抽蓄机组的单机容量有限不能像电池那样在很大范围内连续调节。我选用 300m 水头、5MW 装机作为示例参数时一个时段15 分钟抽满 50MWh 的蓄能大约需要连续抽水 10 个小时所以这种储能天然就是把能量存储从小时级扩展到日级适合配合风光周期间的错峰。3.3 两种储能的互补定位放进同一个模型前先用表格理清它们的角色差异特性电池储能废弃矿井小型抽蓄典型规模10MW / 20MWh5MW / 50MWh响应速度毫秒级适合快速调节分钟级启动适合提前排程能量密度高占地小低但库容可以做得很大日循环损耗充放效率约 90%循环寿命有限往返效率约 70%-75%但寿命极长运维成本电池衰减需要更换机械设备维护为主结构简单典型角色平滑短时波动跨时段大能量搬移两种储能的互补关系很像厨房里的冰箱和冰柜电池是冰箱随取随用但空间有限抽蓄是冰柜存取一次动静大但能囤很多货。调度模型同时协调这两者才能既照顾到分钟级的功率平衡又实现日级乃至周级的能量调配。4. 互补调度模型的数学表达与求解策略模型的目标不是“光伏发多少就消纳多少”这么简单而是要综合权衡购电成本、弃电惩罚和储能损耗。4.1 目标函数怎么定义“最优”我用的目标函数是系统运行总成本最小化由四项组成从电网购电成本Σ C_buy(t) * P_buy(t) * Δt向电网售电收入目标函数里为负- Σ C_sell(t) * P_sell(t) * Δt弃风弃光惩罚成本Σ C_curtail * (P_wind_curtail(t) P_pv_curtail(t)) * Δt电池充放电老化成本Σ C_bat * (P_ch(t) P_dis(t)) * Δt弃电惩罚项非常关键。如果这一项的系数为零求解器为了降低购电成本可能会选择在低谷时段刻意弃掉便宜的风电从系统角度看并不合理。给弃电设一个较高的惩罚系数相当于强制模型尽可能把低谷富余电量送进储能或卖给电网。在写论文时很多人喜欢用“新能源利用率最大”作为目标。实际操作中我在利用率目标里面加了一个购电成本项做双目标权衡或者直接转成带权重的单目标不然模型会不计代价地消纳新能源导致储能被滥用。4.2 约束条件的完整清单调度约束可以拆成四组每一组都有对应的 Python 变量和约束写法功率平衡每个时段风电出力 光伏出力 电池放电 抽蓄发电 购电 负荷 电池充电 抽水用电 售电。这是整条牛顿线上的唯一硬等式任何方案都必须满足。电源侧约束风电、光伏实际出力不超过可用出力弃风弃光量是非负变量外购和外售电功率在传输极限以内。电池约束SOC 递推、SOC 上下限、充放电功率上下限、互斥充电放电标志。抽蓄约束上水库水量递推、上下限、发电功率上下限、抽水功率上下限、发电与抽水互斥机组不可能同时干两个活。抽蓄的启动时间也是一个隐藏约束。机组从静止到满发需要几分钟在 15 分钟调度时段内可以近似处理为启动后直接进入目标功率但如果你要精细建模就需要给机组加最小开停机时间和爬坡约束。我在项目和大多数论文一致用“机组运行状态的最小持续时间约束”来描述这一点避免求解器让抽蓄机组频繁启停。4.3 线性规划、MILP 还是进化算法这个问题取决于模型是否包含 0-1 整数变量。如果电池充放电互斥、抽蓄发电抽水互斥模型就是混合整数线性规划MILP。如果去掉整数变量、把互斥关系近似为可同时发生退化成线性规划LP求解直接一个linprog就搞定。我的建议是分步走第一步先用 LP 快速验证模型逻辑是否正确、结果是否合理。LP 版本允许充放同时存在虽然现实中不合理但能跑出全局基线。第二步再加 0-1 变量升级成 MILP用 OR-Tools 默认的 CBC 求解器求解。如果模型规模大求解时间超了才考虑换成商业求解器或者进化算法。进化算法在这个场景里不是最优选择。MILP 求解器效率高、能保证全局最优而遗传算法每个个体都要跑一次仿真计算成本高且最优性没有保证。只有当目标函数出现非线性、约束不可线性化时才考虑用 DEAP 这类库。5. Python 代码骨架从数据到调度结果的实现细节下面给一套我在项目里实际使用的代码骨架结构可以直接套用你自己的模型。5.1 项目文件怎么组织project/ ├── data/ # 原始气象、负荷、电价数据 ├── models/ # 风电、光伏、储能类定义 │ ├── wind.py │ ├── pv.py │ ├── battery.py │ └── pumped_storage.py ├── optimization/ # 调度优化建模 │ ├── build_model.py │ └── solve.py ├── results/ # 输出结果和图表 └── main.py # 主入口把风、光、储各自封装成类主流程只负责读取数据、组装模型、调用求解器。这样换参数、换场景很方便也方便做灵敏度分析。5.2 建模区用 OR-Tools 搭决策变量与约束调度核心代码可以用 OR-Tools 的pywraplp搭建。简要代码逻辑如下from ortools.linear_solver import pywraplp T 96 # 15分钟一个点一天96点 solver pywraplp.Solver.CreateSolver(CBC_MIXED_INTEGER_PROGRAMMING) # 决策变量 p_wind [solver.NumVar(0, wind_cap, fwind_{t}) for t in range(T)] p_pv [solver.NumVar(0, pv_cap, fpv_{t}) for t in range(T)] p_buy [solver.NumVar(0, grid_limit, fbuy_{t}) for t in range(T)] p_ch [solver.NumVar(0, bat_pmax, fch_{t}) for t in range(T)] p_dis [solver.NumVar(0, bat_pmax, fdis_{t}) for t in range(T)] y_ch [solver.IntVar(0, 1, fy_ch_{t}) for t in range(T)] y_dis [solver.IntVar(0, 1, fy_dis_{t}) for t in range(T)] # 互斥约束 for t in range(T): solver.Add(p_ch[t] bat_pmax * y_ch[t]) solver.Add(p_dis[t] bat_pmax * y_dis[t]) solver.Add(y_ch[t] y_dis[t] 1)抽蓄部分加一组水量变量和流量变量核心是水量平衡约束v_up [solver.NumVar(0, v_max, fv_up_{t}) for t in range(T)] p_gen [solver.NumVar(0, ps_pmax, fgen_{t}) for t in range(T)] p_pump [solver.NumVar(0, ps_pmax, fpump_{t}) for t in range(T)] for t in range(1, T): energy_gen p_gen[t] / ps_eff_gen * 0.25 # 15分钟流量换算 energy_pump p_pump[t] * 0.25 * ps_eff_pump solver.Add(v_up[t] v_up[t-1] - energy_gen energy_pump)这里的换算系数要提前按水头和机组效率算好单位统一成 MWh 或者万立方米都可以只要一致就行。5.3 求解与结果回写求解前建议设置求解时间上限防止大模型卡死solver.set_time_limit(120000) # 120秒 status solver.Solve() if status pywraplp.Solver.OPTIMAL: print(最优目标值, solver.Objective().Value()) results[wind] [p_wind[t].solution_value() for t in range(T)]求解完成后把变量值写回 DataFrame一方面核对功率平衡是否闭合另一方面直接用于画图。5.4 可视化结果可视化我用三张图第一张是功率平衡堆叠图把风电、光伏、储能充电、购电、负荷叠在一起第二张是电池 SOC 和抽蓄水量随时间的变化第三张是弃风弃光率柱状图。画图时有个小提示时间轴刻度如果直接显示 96 个点会挤成一团可以先设置主刻度为每 6 小时一个标记再用mdates.DateFormatter格式化时间标签这样横轴看起来清爽得多。6. 典型日仿真三种配置下的运营指标对比代码跑通之后我做了三组对比实验来验证模型的价值。6.1 算例参数设定参数取值风电场装机50 MW光伏装机30 MW电池储能10 MW / 20 MWh废弃矿井抽蓄5 MW / 50 MWh峰值负荷40 MW电网购电价峰谷比峰时 1.0 元/kWh谷时 0.3 元/kWh弃电惩罚系数0.8 元/kWh负荷曲线取典型的夏季日曲线风光出力用模拟的一日 96 点数据。对比三种配置无储能、仅电池储能、电池抽蓄联合储能。6.2 三种配置的调度结果对比指标无储能仅电池电池抽蓄弃风弃光率18.6%8.2%3.1%外购电量MWh856801742电池日等效循环次数-1.40.7抽蓄日启动次数--2以上数据是本算例的仿真输出具体数值取决于风光资源、负荷曲线和参数设置但这个趋势非常典型电池加入后弃电率大幅下降再加抽蓄后电池循环次数减半系统对电网的依赖进一步降低。这说明两种储能在时间尺度上的互补确实有效。6.3 从调度曲线上看两种储能如何分工看最优调度曲线时能明显读到两条规律。第一条是电池主要承担 1 到 2 小时尺度的削峰填谷比如傍晚光伏退出时负荷快速上升电池立刻以最大功率放电填补缺口。第二条是抽蓄负责更长时间的搬移夜间 1 点到 5 点风电大发、电价最低抽蓄开机抽水 4 小时上午 9 点到 11 点负荷爬升抽蓄再放水发电 2 小时。这种跨 6 小时以上的能量搬移如果全用电池做等效循环深度和衰减都会很大而对抽蓄来说只是一次常规工作循环。另一个值得关注的现象是加入抽蓄之后电池的放电深度明显变浅SOC 大多维持在 30% 到 80% 区间内运行而不是贴着上下限跑。这说明长时储能把“底座”托住了电池不用再被迫做深度充放这对实际运行中的寿命表现非常有利。7. 这个项目踩过的坑与我个人的实操建议最后分享几个我实际做这个项目时踩过的坑每一个都让我多花了一两天时间排查。7.1 数据侧预测功率太干净会骗人我一开始用的是模拟的平滑风光出力数据模型跑出来非常漂亮弃电率低、SOC 曲线顺滑。换成实测数据后立刻崩溃风电实际出力带有很多高频抖动和陡峭爬坡光伏在清晨到上午会出现云层导致的快速跌落。后来我养成了一个习惯任何调度模型在提交结论之前必须把实测时序数据过一遍并且给风电、光伏都加上爬坡约束或可用出力波动限制。7.2 模型侧忘记互斥约束会得到“伪最优”这是我印象最深的坑。第一次跑 LP 版本时某几个时段出现充电功率和放电功率同时很大、SOC 基本不动的情况目标值还特别好看。原因就是模型可以把买来的电充进电池同时电池放电卖给电网靠峰谷电价差“空转套利”。加上互斥约束后这种伪最优立刻消失。如果你觉得自己的模型结果好得不可思议先检查一下是不是出现了同时充放。7.3 时间分辨率与求解速度我试过用 5 分钟一个时段跑一周的调度模型变量数量暴涨CBC 求解器直接卡在几十分钟不出结果。后来我把时间分辨率改成 15 分钟、把优化周期切成“日前 24 小时 日内滚动 4 小时”求解时间降到 10 秒以内效果损失很小。对于小型抽蓄这种分钟级启动的设备15 分钟精度完全够用没必要为了理论上的精度把计算时间拖垮。另外还有一个工程细节MILP 求解器如果长时间解不出来不要急着换求解器先检查约束中是否出现了过大或过小的数值。比如水量单位用立方米、功率单位用瓦数字量级差十几个数量级会让求解器数值稳定崩溃。我统一把功率写成 MW、能量写成 MWh、水量折算成等效能量 MWh模型数值稳定了求解时间也大幅缩短。这个项目做下来我个人最深的体会是调度优化的价值不在算法本身有多么花哨而在于你对每个储能设备的物理特性和时间尺度理解得有多准确。电池和废弃矿井抽蓄在同一个模型里不是替代关系而是接力关系——一个管分钟级一个管小时级配合好了弃风弃光率、外购电量和储能寿命三者才能一起改善。如果你刚开始搭这类模型建议先拿一组小规模数据把 LP 版本跑通确认功率平衡闭合再逐步加入整数变量和更复杂的约束一步步把模型做实。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑