2. 系统架构与调度逻辑设计2.1 为什么是“风光储废弃矿井抽蓄”的组合先把这个组合拆开看。风力和光伏的出力天然具有波动性和间歇性风电往往夜间出力大、白天出力小光伏恰好相反白天中午达到峰值、傍晚归零。这种天然的日内互补特性让“风光”成为第一层互补但光靠这两者还远远不够因为天气过程性变化会导致连续多天阴雨、无风风光同时“躺平”。这时候储能就要上场了。电化学储能响应速度快、控制灵活适合做秒级到分钟级的功率平抑但短板也明显——成本高、寿命有限不适合做大规模、长时间的能量搬移。抽水蓄能虽然响应速度稍慢但胜在容量大、寿命长、度电成本低适合做小时级到日级的能量调度。废弃矿井抽水蓄能是这里面最有意思的一环。常规抽蓄需要建上下两个水库选址受限极大。而废弃矿井已经有现成的井筒和巷道把上层巷道或地表水池作为上库、更深层的采空区作为下库利用井筒落差就能实现抽水蓄能相当于把一份废弃资源盘活了。国内很多资源枯竭型城市都有大量这样的矿井改造成本远低于新建抽蓄电站而且能解决矿井关停后的土地利用问题。所以这个组合的逻辑是风电光伏提供清洁电量电化学储能负责快速响应和短时平抑废弃矿井抽蓄负责大容量、长时间的削峰填谷和跨日调度三层配合形成时间尺度上的互补。2.2 调度模型的分层思路实际工程里调度系统一般不会用一个大而全的模型把所有问题包进去而是分层处理。我在这套Python实现里也是同样的思路分三层第一层是日前计划层Day-ahead。基于未来24小时的风光功率预测曲线和负荷预测曲线以系统运行成本最低或新能源消纳最大为目标求解未来一天的机组出力和储能充放电计划。这一层用的是混合整数线性规划MILP决策变量包括各时段风电机组出力、光伏出力、电化学储能充放电功率、抽蓄机组抽水/发电功率。第二层是日内滚动修正层Intra-day。日前预测不可能完全准确所以每15分钟或1小时滚动更新一次用最新的实测数据和超短期预测结果修正出力计划。这一层解决的是预测误差带来的偏差。第三层是实时控制层Real-time。AGC自动发电控制级别的控制由电化学储能来响应秒级波动抽蓄不参与这一层因为它的响应速度跟不上。这三层之间通过数据接口传递结果日前计划给日内层提供基准日内层修正后的计划给实时层提供参考值。Python在这种多层模型里最大的优势就是可以方便地把不同类型的求解器、数据源和优化模型粘合在一起不像传统调度软件那么封闭。3. 核心模块与Python实现要点3.1 数据预处理风电、光伏出力的“清洗”流程调度模型再漂亮进了脏数据也白搭。我在这套代码里花了不少时间处理数据主要做四件事一是异常值剔除。风机和光伏的实际运行数据里经常出现负值厂用电倒送、毛刺通信干扰、长时间零值停机检修这类问题。我的处理方法是设定物理阈值——风速在切入风速和切出风速之间且机组健康时出力必须在额定功率的0到1.05倍之间光伏出力必须在0到装机容量×当前理论辐照度对应功率之间。超出这个范围的直接标为异常用前后时刻的插值替换。二是缺测数据填补。风光场站的数据采集经常断档短时间缺测我用线性插值超过2小时的缺测就得用相似日替换或者用预测模型补全简单线性插值会导致长时间段数据失真。三是时间分辨率统一。风电数据可能是5分钟一条光伏可能是15分钟一条储能系统的数据又可能是秒级的全部重采样到统一的15分钟间隔。重采样时要注意功率数据用平均值电量数据用累计值混淆了会把调度结果算错。四是气象数据对齐。风速、辐照度、温度等气象数据要按场站位置和时间戳与出力数据对齐这是做功率预测的基础。Python的pandas库做这类处理和重采样非常顺手核心代码大致是这样import pandas as pd import numpy as np # 读取风电场出力数据时间列解析为datetime df_wind pd.read_csv(wind_power.csv, parse_dates[time]) df_wind.set_index(time, inplaceTrue) # 处理异常值物理阈值之外的替换为NaN capacity_wind 100 # MW风电场装机容量 df_wind[power] df_wind[power].apply( lambda x: x if 0 x capacity_wind * 1.05 else np.nan ) # 线性插值填补短时间缺测 df_wind[power] df_wind[power].interpolate(limit8) # 最多插值8个点即2小时 # 重采样到15分钟间隔 df_wind_15min df_wind.resample(15min).mean() # 与气象数据按时间戳对齐 df_weather pd.read_csv(weather_data.csv, parse_dates[time]) df_weather.set_index(time, inplaceTrue) df_merged df_wind_15min.join(df_weather, howinner)这里limit8是经验值——超过2小时的连续缺测插值出来的数据已经不可信了宁可标记为不可用让调度模型去做保守决策也不能硬塞一个假数据进去。3.2 风光出力的数学模型化风电出力建模我用的是分段函数。风速低于切入风速一般3m/s左右或者高于切出风速一般25m/s时出力为零切入和额定风速之间出力近似按三次方关系增长额定风速以上出力恒定在额定功率。写成Python函数很直接def wind_output(v_wind, v_in3, v_out25, v_rated12, P_rated100): if v_wind v_in or v_wind v_out: return 0 elif v_in v_wind v_rated: return P_rated * (v_wind - v_in) / (v_rated - v_in) ** 3 * (v_rated**2 - v_in**2) else: return P_rated光伏出力则要用到辐照度和温度。标准公式是P_pv G / G_ref × P_stc × [1 - β × (T_cell - T_ref)]其中G是实际辐照度G_ref是标准测试条件STC下的辐照度1000W/m²P_stc是组件额定功率β是温度系数通常取0.0035~0.005/°CT_cell是电池板温度T_ref是25°C。电池板温度可以用环境温度和辐照度近似计算T_cell T_amb 0.03 × G这些模型虽然简化了一些物理过程但用于调度层面的研究和工程评估是够用的不需要做到组件级别的精细建模那是光伏设计软件的事。3.3 储能系统建模电化学储能建模核心是两个约束功率上下限约束和电量状态SOC转移方程。SOC的递推关系是SOC(t1) SOC(t) η_ch × P_ch(t) × Δt / E_cap - P_dis(t) × Δt / (η_dis × E_cap)η_ch和η_dis分别是充电和放电效率电化学储能一般取90%~95%。P_ch和P_dis是充放电功率Δt是时间步长E_cap是电池容量。SOC要保持在安全范围内通常10%~90%避免过充过放。抽水蓄能建模比电化学储能多了两个细节一是抽水和发电的效率不同抽水效率一般75%~85%发电效率85%~90%综合效率在70%左右二是水量平衡约束上库的水量变化等于抽水进水量减去发电用水量减去蒸发损失。废弃矿井抽蓄还有一个额外的约束——矿井巷道的水位不能超过安全限值否则会有淹井风险。这套代码里储能建模的核心约束长这样# 电化学储能SOC递推 soc[t1] soc[t] eta_ch * p_ch[t] * dt / E_bat - p_dis[t] * dt / (eta_dis * E_bat) # 抽蓄水库水位递推 V_up[t1] V_up[t] eta_pump * p_pump[t] * dt - p_turb[t] * dt / eta_turb - V_evap[t] # 约束发电和抽水不同时进行 p_pump[t] p_turb[t] 1 # 标准化为0-1变量 # 约束矿井巷道水位安全限值 V_up_min V_up[t] V_up_max注意最后一条约束这是废弃矿井抽蓄区别于常规抽蓄的关键点。矿井改造的上库容量有限水位波动太快会带来安全隐患所以调度模型里必须把这个约束显式写进去。4. 互补调度优化模型的数学表达4.1 目标函数与约束条件的取舍这套模型的目标函数我选了系统运行成本最小化。运行成本包括几个部分从主网购电的费用、弃风弃光惩罚成本、储能充放电的损耗成本电池老化折算。目标函数的数学形式是min Σ_t [ C_grid(t) × P_grid(t) C_wind_curtail × P_curtail_wind(t) C_pv_curtail × P_curtail_pv(t) C_bat_degrad × (P_ch(t) P_dis(t)) ]C_grid是分时电价日内峰谷价差大的时候这个目标函数的调度效果非常明显。弃风弃光惩罚成本的设置很重要——它决定优化器是倾向于多储能来消纳新能源还是宁可买网电、少动作。惩罚成本设太高结果会偏乐观全部新能源都被“消纳”了设太低储能几乎不动模型失去意义。我调试时试过一组参数弃电惩罚从0.1元/kWh逐步提到0.6元/kWh调度结果从“储能基本闲置”过渡到“储能满充满放”。最终定在0.3~0.4元/kWh比较合理既能体现新能源消纳的优先级又不会让储能过度激进。约束条件除了上节提到的储能SOC、抽蓄水量、充放电互斥之外还有功率平衡约束这是所有调度模型的灵魂系统在每个时刻必须满足负荷供需平衡。P_wind(t) P_pv(t) P_bat_dis(t) P_hydro_gen(t) P_grid(t) P_load(t) P_bat_ch(t) P_hydro_pump(t)这个等式约束把所有电源、负荷和储能元件联系在了一起。4.2 为什么选择混合整数线性规划MILP本模型中最关键的数学难点是充放电互斥约束。电化学储能不能同时充电和放电抽蓄机组不能同时抽水和发电。如果用连续变量建模优化器可能会给出“既充又放”的荒谬结果因为同时充放可以把损耗成本做大在某些约束下确实会出现这种解。解决办法是把充放电状态设为0/1整数变量加上互斥约束。这样就引入了整数变量模型变成MILP。Python生态里求解MILP的库很成熟我用的比较多的是ortools和PuLP两个都支持开源求解器。ortools对大规模问题的性能更好PuLP的建模语法更贴近数学表达、易读性好。from ortools.linear_solver import pywraplp solver pywraplp.Solver.CreateSolver(CBC) # CBC是开源MILP求解器 T 96 # 24小时15分钟一个时段 # 变量定义 p_wind [solver.NumVar(0, 100, fwind_{t}) for t in range(T)] p_pv [solver.NumVar(0, 80, fpv_{t}) for t in range(T)] p_bat_ch [solver.NumVar(0, 40, fbat_ch_{t}) for t in range(T)] p_bat_dis [solver.NumVar(0, 40, fbat_dis_{t}) for t in range(T)] # 0/1状态变量 u_bat_ch [solver.IntVar(0, 1, fu_ch_{t}) for t in range(T)] u_bat_dis [solver.IntVar(0, 1, fu_dis_{t}) for t in range(T)] # 互斥约束不能同时充放电 for t in range(T): solver.Add(p_bat_ch[t] 40 * u_bat_ch[t]) # 充电状态为0时充电功率必为0 solver.Add(p_bat_dis[t] 40 * u_bat_dis[t]) solver.Add(u_bat_ch[t] u_bat_dis[t] 1) # 互斥这个p capacity * u的写法是MILP建模的基本套路本质上是用一个大常数big-M把连续变量和整数状态变量关联起来。实际求解时CBC求解器在这个规模下几乎秒解即使把时间步长细化到5分钟、T288求解时间也在可接受范围内。5. 实操案例含废弃矿井抽蓄的综合调度场景5.1 场景参数设定下面用一个实际规模的风光储互补系统做完整调度仿真。先说清楚参数方便你复现对比组件参数数值风电场装机容量100 MW光伏电站装机容量80 MW电化学储能容量/功率40 MWh / 20 MW废弃矿井抽蓄上库容量5万m³抽蓄机组抽水/发电功率15 MW / 12 MW抽蓄效率抽水/发电0.82 / 0.88负荷峰值典型工业负荷150 MW调度周期时长/间隔24h / 15min矿井抽蓄的关键参数是上下库落差。按照废弃矿井的典型条件取落差150米可以估算储能容量E ρ × g × H × V / 3.6e6代入ρ1000 kg/m³g9.8 m/s²H150 mV5万m³E 1000 × 9.8 × 150 × 50000 / 3.6e6 ≈ 204 MWh也就是说五万方水在这个落差下能储存约204MWh的能量——这是电化学储能的5倍。当然这只是理论储能容量实际每天还要留一部分水量给蒸发和巷道渗漏不过量级差异已经说明问题了。5.2 典型日调度结果的三个时段分析用真实日期的风光和负荷数据跑了一天仿真结果可以分三个时段来看。凌晨0点~6点风电机组趁夜间大风满发光伏出力为零负荷处于低谷。此时系统功率过剩调度策略是电化学储能以20MW功率充电抽蓄以15MW功率抽水把多余的绿色电力转化为水和电池里的能量。到早上6点电化学储能SOC从初始的50%充到78%抽蓄上库水位从50%升到71%为白天高峰做好准备。上午9点~下午4点光伏出力爬升到峰值附近负荷也进入白天的平峰期。风电出力有所下降系统整体基本平衡储能和抽蓄不动作或者间歇小功率运行。傍晚6点~晚上10点这是全天的黄金时段——光伏出力归零负荷到达晚高峰同时电网分时电价也到了峰值。调度策略变成电化学储能以20MW满放抽蓄以12MW满发两者合计提供32MW的支撑功率。对比同等规模的电化学储能系统20MW抽蓄在这个时段多提供了12MW持续4小时相当于多搬了48MWh电量。5.3 与传统单储能方案的对比数据仿真结束后我把抽蓄方案和“只用电化学储能”的方案做了对比结果很有意思。抽蓄方案日运行成本比单电池方案低约7.3%——抽蓄虽然单位投资不低但运维成本低、寿命长而且这207MWh的水库容量避免了电化学储能扩容的巨大投资。新能源消纳率方面抽蓄方案达到96.8%比单电池方案的91.2%高出5.6个百分点——多出的这5.6%基本都是连续阴天时段靠抽蓄的大容量硬扛下来的电池在那种场景下早就满了、无法继续充电。6. 常见问题与排查技巧实录6.1 求解器报“不可行”Infeasible怎么办这是跑调度模型最常遇到的问题原因通常是约束自相矛盾。我遇到过最典型的一次某台抽蓄机组的发电功率上限是12MW但我把水量约束算错了初始水位不够支撑晚高峰连续发电4小时不到第三个时段水量就低于下限约束无法满足求解器直接报不可行。排查方法可以分为三步第一步用model.ComputeConstraintActivities()看哪条约束的活动量异常第二步把约束逐条注释掉二分定位矛盾源头第三步检查初始条件特别是SOC初值和水库初始水位。这些条件看似不起眼但直接决定了前几个时段有没有可行解。6.2 求解时间过长或者不收敛怎么优化当时间粒度细化到5分钟、T288变量数量增多后CBC求解器可能要好几分钟才出结果。我常用的优化手段给连续变量一个合理的初始可行解比如直接把“不调度”方案作为热启动传入这样求解器能更快找到可行域把不重要的约束放宽比如蒸发损失按固定比例算不用做一个额外变量。6.3 风光预测不准导致计划执行偏差现实场景中预测误差不可避免。我的处理思路是日前计划里加入10%的旋转备用约束——也就是说调度结果至少要保证在任何时刻都有10%的调节裕量。这个百分比可以根据季节微调大风季光伏预测误差大备用电还要再留高一点。日内滚动修正层每4小时重新求解一次用最新预测数据刷新未来12小时计划把误差持续“滚”掉。6.4 一个虚拟变量的坑建抽蓄模型时我一度把上下库的水量关系简化成了单一变量结果做出一个“凭空多出水”的模型——发电放水后抽水功率算出来是负的。后来把所有水量变量都显式建模并且每条水量平衡约束都写了收支两边的完整项这个问题才彻底解决。7. 扩展方向与我的实践体会这套模型做完之后我觉得有三条值得深入的方向一是加入电池寿命衰减模型因为电池循环次数是有限的每次充放电都在消耗寿命把老化成本实时纳入目标函数之后电池的使用策略会更保守、更符合实际运维需求二是把废弃矿井的热储能也利用起来——矿井巷道里的水温恒定冬天可以供热夏天可以供冷配合热泵系统整个废弃矿井的能源潜力能进一步挖出来三是把电力市场环境下的现货交易和辅助服务收益纳入优化目标当前模型只考虑了系统自身成本最小没考虑电价的实时变化和备用容量市场收益。最后分享一个我做这套代码时踩过的大坑——别把所有逻辑堆在一个.py文件里。我一开始把数据读取、模型构建、求解、画图全写在一起结果参数一调整个文件到处都要改。后来按功能拆成四个模块data_loader.py数据读取和预处理、model_builder.py约束和目标函数构建、solver.py求解器封装、plotter.py结果可视化每个模块配一个配置文件config.yaml存所有参数。改参数只需要改配置文件代码一行不动。这么做之后调试效率至少提升了一倍。废弃矿井抽水蓄能这个方向我一直觉得是被低估的。国内数量庞大的废弃矿井分布在很多资源枯竭地区这些地方往往也是新能源资源丰富但电网末端薄弱的地方。把它们改造成抽蓄电站既解决了新能源消纳问题又盘活了废弃资产还能带动当地就业可以说是“一石三鸟”。我这套Python代码只是把这个思路落成了一个可以跑、可以调、可以继续扩展的仿真工具真正的价值还在工程落地那一步——但那一步需要的就不再只是代码了。