
做风电、光伏与储能互补调度运行研究这个课题最开始我以为是数学建模加优化求解的常规组合真正动手才发现从风光出力数据到储能系统建模再到最后的Python代码实现每一步都藏着不少隐性坑。这篇文章就是做完整个项目之后的完整复盘我会把从问题定义、模型搭建、算法选择到代码落地的全过程都拆开讲一遍包括那些踩过坑的地方希望能帮到正在做新能源调度方向研究的学生以及想入门电力系统优化调度算法开发的工程师朋友。整体来看这个课题最核心的价值在于它同时处理了两个维度的问题一个是风光出力的随机性和波动性怎么通过调度来平抑另一个是两种特性完全不同的储能设备电池和废弃矿井小型抽水蓄能怎么协同配合。后者是这个题目最有意思的地方也是很多论文里没有展开讲的部分。1. 项目背景与整体思路拆解1.1 风光储互补调度到底在解决什么问题风电和光伏的出力都是看天吃饭风电受风速影响白天风速往往偏小夜间才可能起来光伏只在有光照的时段出力夜间直接归零。两个电源单独看都不可控叠加在一起虽然能缓解一部分波动——比如白天光伏出力大、夜间风电出力大的天然时间互补——但远达不到稳定供电的水平。储能在这个系统里扮演的角色简单说就是时间搬运工把出力过剩时段的多余电能存起来放到出力不足或负荷高峰的时段释放。但储能本身也有讲究电池储能响应快、效率高但容量相对有限而且频繁深度充放会加速寿命衰减抽水蓄能容量大、寿命长可一旦启停响应速度就差了不少。把这两种储能放到同一个调度模型里让它们各干各的擅长部分才是这个课题的真正难点。这里要特别说下废弃矿井小型抽水蓄能。常规抽水蓄能需要天然的地形落差和足够的水源站址选择极其受限。废弃矿井则提供了一个现成的地下空间——矿道和巷道经过改造后可以作为地下水库与地面水池或者塌陷区形成落差小的落差也能跑低水头机组。这种做法把废弃矿山的空间资源利用起来算是储能领域一个很有潜力的应用方向。1.2 系统组成与整体技术路线我搭的仿真系统包含五个部分风电场、光伏电站、电池储能、废弃矿井小型抽水蓄能以及一个典型工业园区的日负荷曲线。调度采用日前计划模式时间分辨率取1小时一共24个时段。这个粒度对验证算法和跑通流程足够了工程上如果要做到15分钟级别的滚动调度逻辑是一样的只是变量数量翻几倍。整个技术路线可以拆成五个环节数据准备风速、辐照度、负荷的序列生成→ 单元出力建模风机功率曲线、光伏出力公式→ 储能系统建模电池SOC递推、抽蓄水库能量状态→ 优化模型构建目标函数加约束条件→ Python求解与结果分析。这样拆的好处是每个环节可以独立调试哪块报错立刻能定位到。项目里我用的系统基本参数如下组成单元额定容量/关键参数说明风电场60 MW切入风速3 m/s额定风速12 m/s切出25 m/s光伏电站40 MW标准工况功率温度系数-0.004/℃电池储能20 MW / 40 MWhSOC范围0.1~0.9充放电效率0.95废弃矿井抽蓄15 MW / 120 MWh发电效率0.85抽水效率0.9日水量平衡负荷峰值约75 MW典型工业负荷早晚双高峰这套参数规模不算大但足够说明调度逻辑。实际项目中所有数值都可以按真实场站的容量替换。2. 风光出力建模与数据处理2.1 风电出力模型与功率曲线拟合风电建模的第一步是生成风速序列。风速在统计上一般服从威布尔分布我习惯用numpy.random.weibull生成形状参数为2的基序列再乘以尺度参数来模拟一天内风速的均值水平。如果要做更贴近实际的研究可以通过AR(1)自回归模型给风速加上时序相关性这样风速不会在相邻时段突变看起来更真实。风速序列生成后通过功率曲线换算成风电出力。风机功率曲线可以简化成三段线性模型风速低于切入风速不出力切入风速到额定风速之间出力随风速近似线性上升额定风速到切出风速之间维持额定出力超过切出风速风机切出保护出力归零。公式化就是P 0v v_ci 或 v ≥ v_co P P_r × (v - v_ci) / (v_r - v_ci)v_ci ≤ v v_r P P_rv_r ≤ v v_co下面这段是我在模型里实际用的模拟代码import numpy as np np.random.seed(42) T 24 # 24小时 # 模拟一天风速威布尔分布 白噪声平滑 shape, scale 2.0, 6.0 wind_speed np.random.weibull(shape, sizeT) * scale # 简单滑动平均让风速曲线更平滑 wind_speed np.convolve(wind_speed, np.ones(3)/3, modesame) def wind_power_curve(v, v_ci3.0, v_r12.0, v_co25.0, p_r60.0): p np.zeros_like(v, dtypefloat) for i in range(len(v)): if v[i] v_ci or v[i] v_co: p[i] 0.0 elif v[i] v_r: p[i] p_r * (v[i] - v_ci) / (v_r - v_ci) else: p[i] p_r return p P_w_avail wind_power_curve(wind_speed)这里要注意功率曲线的线性段只是简化处理真实风机厂商给的功率曲线是非线性的精度要求高的时候可以用多项式插值或者直接从曲线表里查值。但对于调度层的模型来说线性近似已经能反映风电出力的基本变化规律。2.2 光伏出力模型与辐照度模拟光伏出力比风电更好建模核心影响因素就是辐照度和组件温度。标准测试条件STC下组件出力为 P_stc实际出力按辐照度比例折算再乘一个温度修正系数P_pv P_stc × (G / G_stc) × [1 k × (T_cell - T_stc)]其中 G_stc 取1000 W/m²T_stc 取25℃k 是温度系数一般取 -0.004/℃。组件温度可以用环境温度加辐照度影响的简化经验公式来估算练手阶段直接用固定环境温度加一个温度波动序列就够。辐照度曲线我习惯用余弦包络 云遮挡噪声的方式模拟白天从早晨开始上升中午最大傍晚归零然后叠加随机云层导致的短时波动。云遮挡在时序上一般持续20到60分钟所以我会用高斯噪声做平滑而不是完全的白噪声。# 模拟辐照度余弦日变化 云层噪声 t np.arange(T) hour (t 6) % 24 # 假设6点日出 G_max 800.0 G np.array([G_max * np.sin(np.pi * (h - 6) / 12) if 6 h 18 else 0.0 for h in hour]) noise np.random.normal(0, 60, sizeT) noise np.convolve(noise, np.ones(3)/3, modesame) G np.clip(G noise, 0, 1000) P_stc 40.0 T_cell 28.0 0.03 * G # 组件温度近似 P_pv_avail P_stc * (G / 1000.0) * (1 (-0.004) * (T_cell - 25.0)) P_pv_avail np.clip(P_pv_avail, 0, P_stc)这段代码做出来的光伏出力曲线带有明显的中午高峰和随机波动比纯正弦曲线真实不少。需要注意光伏模型里温度修正的系数是负数高温会让组件出力下降夏天中午反而会出现温度型拐点这个细节在调度分析里很容易被忽略。2.3 数据清洗与典型日场景生成模拟数据相对干净但如果你用的是真实场站的历史数据清洗这步就不能省。我的做法是三步走先做物理阈值截断风速超过40 m/s或者辐照度超过1100 W/m²的异常点直接剔除或限制再做滑动窗口平滑消除遥测设备造成的毛刺最后做时间一致性检查相邻时段出力突变幅度超过设定阈值比如额定功率的50%的视为坏数据并插值替换。如果手里有的一年数据都要用来做日前调度计算量会很大。在前期验证阶段可以用K-means对日出力曲线聚类挑出几个典型日场景来代表晴朗大风阴天小风多云这类典型工况。聚类特征就用24点出力向量距离度量用欧氏距离聚类数选3到5个就够。用sklearn的KMeans就能实现几行代码的事情。3. 储能系统建模电池与废弃矿井抽水蓄能3.1 电池储能BESS建模要点电池储能的建模核心是荷电状态SOC的递推关系。每个时段充进去的电量经过充电效率折算后增加SOC放出来的电量要除以放电效率才是实际消耗的SOCSOC_t SOC_{t-1} η_ch × P_ch,t × Δt / E_rated - P_dis,t × Δt / (η_dis × E_rated)实际建模时有几个容易被坑的细节。第一SOC不能设成0到1的完整区间锂电通常限制在0.1~0.9之间这既是为了保护电池也是为了防止模型在边界处出现退化解。第二充放电功率上限和容量之间要匹配比如20 MW的功率配40 MWh的容量满功率充电2小时才能充满这就决定了这套储能主要适合做短时调节而不是长时搬移。第三同一时段同时充放电在物理上是禁止的但线性规划如果不用0-1变量很可能会出现边充边放的无意义解解决办法是在目标函数里给充放电都加上一个很小的运行成本系数模型就会自动避免这种浪费行为。电池寿命衰减是个复杂问题。短期调度的仿真里可以不建寿命模型但如果要做长时间尺度评估建议加一个简化约束每天累计充放电量不超过额定循环次数对应的能量吞吐比如一天最多等效0.8次满充满放。3.2 废弃矿井小型抽水蓄能建模废弃矿井抽水蓄能的原理不复杂利用地下巷道或采空区作为下水库地表水池或塌陷区作为上水库竖井当作压力管道。抽水时把水从巷道抽到地面把风光过剩的电能转化为水的势能发电时水从地面经竖井流回巷道推动水轮机发电。储能容量的理论估算公式是E ρ × g × H × V × η_roundρ取1000 kg/m³g取9.8 N/kgH是上下库的高差V是有效储水容积η_round是循环效率。举个例子如果废弃矿井深度150米巷道有效容积4万立方米循环效率0.75理论储能大约是1000×9.8×150×40000×0.75 4.41×10^9 J换算成电量就是1225 kWh左右。如果巷道容积更大或者用多水平巷道并联容量还能进一步提升。但有一类容量约束特别重要抽蓄并不是想充多少就充多少下库巷道的可用容积决定了一次循环能存多少水同时为了防止巷道完全排空或者漫顶水位需要保持在安全上下限之间。所以在调度模型里抽蓄不能简单按功率上下限来约束还要引入能量状态的递推和容量边界。我在建模时采用了等效能量水库的方法把水量直接折算成水库储能状态E_res单位MWhE_res,t E_res,t-1 η_pump × P_pump,t × Δt - P_turb,t × Δt / η_turb这个公式和电池SOC递推在形式上完全一致只是把充放电效率的挂载位置对调了一下充电抽水要乘效率放电发电要除效率这是因为抽水消耗的是电网电能发电输出的是电能量两者方向相反。这种统一形式的好处是之后在优化求解部分可以把电池和抽蓄放在同一套框架里处理。对于日调度还要加一个关键约束调度周期结束时水库能量状态要回到初始值。如果不加这个约束线性规划会贪心地把最后一小时的蓄水全部放空来满足功率平衡结果看似完美实际上第二天就没水可用了。3.3 两种储能特性对比与协同价值特性电池储能废弃矿井小型抽蓄响应速度毫秒~秒级分钟级循环效率85%~95%70%~80%功率/能量密度高低但容量可做大循环寿命数千次后衰减数十年无显著衰减站址约束无特殊要求依赖废弃矿井存在单位储能成本高低于电池改造成本摊薄后适用场景调频、短时削峰、平抑快速波动日循环调峰、长时能量搬移两种储能放在同一个调度系统里天然形成互补电池负责冲锋快速响应光伏云层遮挡这种分钟级波动抽蓄负责长跑承担小时级的能量搬移和日调峰任务。在优化模型里由于两种储能的技术参数差异巨大求解器会自然地分配角色——成本低的抽蓄承担时间跨度长的能量转移电池则承担高功率短时调节。这也是多元储能协调的价值所在。4. 优化调度模型构建4.1 目标函数设计这个项目用了单目标加惩罚项的处理方式把弃风弃光、供电缺口、储能运行成本都折算成综合运行成本。目标函数写成min Σ_t ( λ_curt × CUR_t λ_shed × SHED_t λ_bat × (P_ch,t P_dis,t) λ_pump × (P_pump,t P_turb,t) )各项的含义和取值逻辑是CUR_t 是第t时段的弃风弃光电量取单位惩罚成本0.3元/kWh让模型尽量消纳可再生能源SHED_t 是供电缺额惩罚成本取10元/kWh比弃电惩罚高一个量级保证可靠性优先于经济性电池和抽蓄的运行成本系数取0.005元/kWh只是为了防止同时充放电的退化解不代表真实度电成本。这个目标函数的本质是尽量少弃电、尽量不缺电、储能别做无用功。如果你研究的场景是并网友好型而不是孤网也可以把目标改成最大化可再生能源渗透率或者最小化联络线功率波动但惩罚系数的相对大小关系要不变——缺电的代价始终应该是最大的。4.2 约束条件梳理约束条件是整个模型最需要耐心的部分我梳理下来一共六类功率平衡约束是最基础的等式每个时段都必须满足 P_w,t P_pv,t P_dis,t P_turb,t SHED_t Load_t P_ch,t P_pump,t这个式子的物理含义很清晰电源侧出力加上缺电松弛变量等于负荷加上储能吸收的功率。左边是供给右边是需求缺口用 SHED 来松弛这样约束不会因为数据不合理而直接不可行。然后是各类设备的技术约束。风电和光伏上网功率不能超过可用出力电池充放电功率有上限抽蓄发电和抽水功率有上限电池SOC在0.1到0.9之间抽蓄能量状态E_res在最小安全储量和最大库容之间。这些约束在LP里都是一堆上下界写起来累但逻辑直接。最后是动态递推约束也就是上一节里写的电池SOC递推方程和抽蓄能量递推方程加上日末状态等于日初状态的水量平衡约束。这组约束是模型的核心少了它们储能就成了空中楼阁。4.3 求解方法选择这个模型是典型的线性规划问题决策变量是各时段的功率值目标函数和约束全部线性。我用的是SciPy的linprogmethod选highs它在稀疏矩阵处理上比默认的内点法更适合这种中大规模的调度问题。如果你的模型需要考虑抽蓄机组启停次数电池充放电状态切换这类逻辑就需要引入0-1变量问题会从LP变成混合整数线性规划MILP。这时候scipy的linprog就不够用了建议上Pyomo加开源的CBC求解器或者直接用商业求解器比如Gurobi的学术版。我个人在练手阶段用scipy就够了因为纯LP模型已经把核心调度逻辑跑通了0-1变量只是让模型更精细。当然也可以用遗传算法、粒子群这类启发式算法来解但我不推荐把它们作为唯一求解手段。启发式算法对非线性、非凸问题有效用在这个线性模型上既慢又不稳定同样的代码跑两次结果可能不一样。把它们留作和线性规划结果的对比验证或者处理非线性约束时再用才是正确姿势。5. Python代码实现核心环节5.1 项目目录结构与环境依赖我的项目文件组织成下面这样每个模块职责单一后期改参数、换数据都不用翻整个工程wind_pv_storage_scheduling/ ├── data/ │ ├── wind_speed.npy │ ├── irradiance.npy │ └── load.npy ├── src/ │ ├── models.py # 风机、光伏、储能模型 │ ├── optimization_lp.py # 线性规划调度求解 │ ├── rule_based.py # 规则基准调度 │ └── plot_results.py # 结果可视化 ├── main.py └── requirements.txt依赖库就四个numpy、pandas、matplotlib、scipy。pandas主要用于处理从CSV读取的风速、辐照度、负荷历史数据数据量不大时也可以只用numpy。环境安装直接一行命令pip install numpy pandas matplotlib scipy如果你用的是Anacondanumpy和pandas大概率已经装好了补一个scipy和matplotlib就行。Python版本建议3.9或以上linprog的highs求解器在老版本里可能不可用。5.2 数据模拟与单元模型代码主程序里先准备好一天的负荷序列和风光可用出力。负荷我模拟的是典型工业园区曲线早上8点和下午17点各有一个高峰夜间基础负荷在40 MW左右# 负荷序列生成 base_load 45.0 load base_load 12.0 * np.exp(-((hour - 8) / 3) ** 2) 15.0 * np.exp(-((hour - 17) / 4) ** 2) load np.random.normal(0, 2.0, sizeT)然后将第二节生成的风光出力序列和这里的负荷合并成一个输入数据集。到这里模型的前半部分电源出力和负荷需求就准备好了。5.3 基于线性规划的调度求解代码这是整个项目的核心。我用一个8变量每时段的线性规划来实现风电上网、光伏上网、电池充电、电池放电、抽蓄抽水、抽蓄发电、缺电、弃电。变量拼接方式是把不同属性的变量按时间顺序排成一个向量索引计算需要特别小心否则拼错一个位置就会得出完全错误的调度结果。from scipy.optimize import linprog T 24 # 定义变量索引每个时段8个变量 # 0:PW 1:PV 2:CH 3:DIS 4:PM 5:TU 6:SHED 7:CUR n_vars T * 8 # 目标函数系数 c np.zeros(n_vars) for t in range(T): base t * 8 c[base 2] 0.005 # 电池充电成本 c[base 3] 0.005 # 电池放电成本 c[base 4] 0.005 # 抽水成本 c[base 5] 0.005 # 发电成本 c[base 6] 10.0 # 缺电惩罚 c[base 7] 0.3 # 弃电惩罚 # 不等式约束A_ub x b_ub A_ub [] b_ub [] for t in range(T): base t * 8 # 风电/光伏上网功率不超过可用出力 row np.zeros(n_vars) row[base] 1 A_ub.append(row); b_ub.append(P_w_avail[t]) row np.zeros(n_vars) row[base 1] 1 A_ub.append(row); b_ub.append(P_pv_avail[t]) # 电池充放电功率上限 row np.zeros(n_vars) row[base 2] 1 A_ub.append(row); b_ub.append(20.0) row np.zeros(n_vars) row[base 3] 1 A_ub.append(row); b_ub.append(20.0) # 抽蓄抽水/发电功率上限 row np.zeros(n_vars) row[base 4] 1 A_ub.append(row); b_ub.append(15.0) row np.zeros(n_vars) row[base 5] 1 A_ub.append(row); b_ub.append(15.0) # 缺电和弃电不能超过合理范围 row np.zeros(n_vars) row[base 6] 1 A_ub.append(row); b_ub.append(load[t]) row np.zeros(n_vars) row[base 7] 1 A_ub.append(row); b_ub.append(P_w_avail[t] P_pv_avail[t]) # 等式约束A_eq x b_eq A_eq [] b_eq [] # 功率平衡约束 for t in range(T): base t * 8 row np.zeros(n_vars) row[base] 1 # PW row[base 1] 1 # PV row[base 3] 1 # DIS row[base 5] 1 # TU row[base 6] 1 # SHED row[base 2] -1 # CH row[base 4] -1 # PM A_eq.append(row) b_eq.append(load[t]) # 电池SOC递推等式SOC_t SOC_{t-1} 0.95*CH_t - DIS_t/0.95 soc np.zeros((T, 1)) E_bat 40.0 soc_init 0.5 for t in range(T): base t * 8 row np.zeros(n_vars) row[base 2] -0.95 / E_bat # 充电使SOC增加移到等式左边 row[base 3] 1.0 / (0.95 * E_bat) # 放电使SOC减少 if t 0: A_eq.append(row) b_eq.append(soc_init) else: prev_base (t - 1) * 8 row[prev_base 2] 0.95 / E_bat row[prev_base 3] -1.0 / (0.95 * E_bat) # 上面两行难以直接表示为A x下面用逐时段拼接方式更清晰这里我遇到过一个典型问题SOC递推是当前时段状态等于上一时段状态加充放电影响严格来说它应该写成SOC_t - 0.95/E_bat × CH_t 1/(0.95×E_bat) × DIS_t - SOC_{t-1} 0但SOC并不是LP的决策变量而是通过递推约束隐含定义的。更干净的做法是把每个时段的SOC也设为决策变量然后约束联结。我在实际项目里的处理方式是给每个时段同时加入SOC变量第8个之后的扩展变量然后写递推等式。为了文章篇幅这里给出的是简化版本核心逻辑已经能说明白——真正在本地跑的时候建议把所有变量包括SOC和水库能量状态统一作决策变量这样约束搭建会更直观、也更好排查问题。最后求解并解析结果res linprog(c, A_ubnp.array(A_ub), b_ubnp.array(b_ub), A_eqnp.array(A_eq), b_eqnp.array(b_eq), bounds[(0, None)] * n_vars, methodhighs) if res.success: x res.x P_w x[0::8] P_pv x[1::8] P_ch x[2::8] P_dis x[3::8] P_pm x[4::8] P_tu x[5::8] shed x[6::8] curt x[7::8]说实话linprog跑这个规模的问题完全没压力求解耗时在毫秒级。真正费时间的反而是矩阵搭建过程索引一不留神就会错位我在调试阶段用了一个土办法先求出解后手动验证几个典型时段的结果比如夜间光伏为零时PV变量必须严格为0否则说明索引拼错了。5.4 调度结果可视化与指标评估调度结果光看数字不够直观我习惯画一张堆叠图把一天24小时的功率分配展示出来横轴是时间纵轴是功率风电、光伏、电池放电、抽蓄发电依次堆叠负荷用折线叠加在图上。电池SOC和抽蓄能量状态单独画在下方子图。下面是可视化代码的核心部分import matplotlib.pyplot as plt fig, axes plt.subplots(2, 1, figsize(12, 8), sharexTrue) ax axes[0] ax.stackplot(range(T), P_w, P_pv, P_dis, P_tu, labels[Wind, PV, Battery Disch., PHS Gen.], alpha0.7) ax.plot(range(T), load, labelLoad, colorblack, linewidth2) ax.plot(range(T), P_ch P_pm, labelCharging/Pumping, linestyle--, colorred) ax.set_ylabel(Power (MW)) ax.legend(locupper left) ax.set_title(Optimal Dispatch Result) ax axes[1] ax.plot(range(T), soc, labelBattery SOC, markero) ax2 ax.twinx() ax2.plot(range(T), e_res, labelPHS Energy State, colorgreen, markers) ax.set_ylabel(SOC) ax2.set_ylabel(E_res (MWh)) ax.legend(locupper left) ax2.legend(locupper right) plt.tight_layout() plt.show()画完图先别急着下结论对着图看一眼这几个应该出现的特征光伏出力高的中午时段电池和抽蓄应该在充电下午负荷高峰时段抽蓄或者电池应该在放电夜间如果风大且负荷低应该出现弃电或者抽蓄充电而不是强行让风电场降到很低的出力水平。评估指标方面我最关注三个数字可再生能源利用率 (总可用风光 - 弃电量) / 总可用风光供电可靠性用失负荷率衡量即缺电总量除以负荷总量储能利用情况看两个储能单元的日循环次数和总吞吐量。这几个指标算完了项目的结论基本也就成型了。6. 常见问题与排查技巧实录6.1 高频疑难与解决方案速查表调试这个项目的过程中我前后遇到过不少问题挑几个高频的列成速查表都是实测过的解决方案常见问题可能原因排查与解决办法求解结果提示不可行等式或不等式约束之间存在矛盾给功率平衡等式加缺电松弛变量SHED先跑通再加严格约束电池出现同时充放电目标函数里没有充放电成本项给充放电变量加极小的正系数比如0.005抽蓄日末能量归零缺少水量平衡约束强制E_res日末等于日初可以放宽到不小于初始值SOC曲线一直贴在下限电池容量或功率参数设置不合理检查E_rated与P_max的匹配关系看是否2小时就充满/放完弃风弃光量异常高消纳能力不足或储能容量过小看储能是否在风光高峰时段满充考虑增大抽蓄容量结果对初始SOC极其敏感没有让SOC有恢复空间初始SOC设0.5并观察日末是否回到合理区间这里特别想提醒的是不可行问题百分之八十是因为约束太紧而不自知。比如你同时要求功率平衡严格相等和电池容量很小在负荷高峰又强制风电全额上网那肯定无解。遇到这种情况先加松弛变量把模型跑通再逐步收紧比盯着报错信息干想高效得多。6.2 踩坑经验与调参心得有几个经验是做了三四个版本之后才总结出来的。第一时间分辨率真的会影响结论。我用1小时粒度算出来的抽蓄调度是一个时段要么抽水要么发电的方波型模式改成15分钟粒度后因为风光出力波动更精细抽蓄的出水曲线就平滑很多也更接近真实运行。所以我建议前期验证用1小时粒度写论文或者出工程结论时尽量跑15分钟粒度。第二目标函数的量纲一定要统一。我一开始把所有成本都写成了万元结果惩罚系数设成0.0001求解器直接把这些项忽略了导致模型疯狂弃电。后来把所有单位统一成元/kWh或者元/MWh量纲一致后再调整惩罚系数之间的比例关系缺电弃电运行损耗模型行为才正常。第三废弃矿井抽蓄的水头变化不能完全忽略。我最早用的固定水头模型把E_res当作线性能量库算出来的结果偏乐观。因为巷道水位变化会影响实际发电水头进而影响出力功率。后来我在模型里加了水头修正系数发电效率随能量状态线性变化虽然多了非线性约束但结果更可信。如果不想引入非线性可以按平均水头打折处理至少留个安全裕度。另外对于想把这个项目往论文方向扩展的朋友有几个方向值得考虑加入风电功率预测和光伏功率预测的不确定性用场景法或者鲁棒优化处理在目标函数中引入碳排放成本来体现绿色价值把调度周期从日前扩展到日前日内滚动的两阶段框架让模型更贴近实际运行。这些扩展在代码层面不需要推翻重来只要在现有LP框架上增加变量和约束就能实现这也是用线性规划建模的优势所在。最后再分享一个我自己的习惯每次跑完调度我第一件事不是看目标函数值而是先把SOC和E_res曲线拉出来看一遍。如果这两个储能状态曲线没有有涨有落的合理循环说明模型大概率在偷懒——要么在借松弛变量逃避约束要么根本没在合理利用储能。模型可以复杂但物理直觉永远是最先信得过的那个判断标准。