
风电、光伏与储能互补调度说白了就是让一群“看天吃饭”的设备和一群“随时能存能放”的设备在一条时间轴上互相搭把手。风大的时候多存电太阳猛的时候少买电天气不给力的时候让储能顶着上。这个项目把电池储能和废弃矿井小型抽水蓄能组合成一套混合储能系统用Python做建模、求解和结果可视化跑通完整的数据到调度曲线流程。文章给的是可以直接抄走的代码框架和我在实际调参中踩过的坑适合刚开始做风光储联合调度的研究生、电力系统工程师以及想用Python入门优化调度的同学。这类项目最容易被低估的地方不是“模型有多复杂”而是“把数据、约束、求解器粘在一起时坑到底有多少”。我一开始以为只要把目标函数写出来就算完事结果第一次跑出来的调度曲线惨不忍睹储能SOC直接越界抽蓄干脆全程躺着不动。后来我把模型拆开重新审视才发现问题出在约束的写法上。这篇就按从头到尾做一遍的顺序把完整思路、代码、参数实验和排错经验都记下来。1. 问题拆解风光储互补调度到底在解什么题1.1 三种设备的出力脾气和互补逻辑风电、光伏、储能这三类设备放在同一个调度模型里不是因为“配置齐全”而是因为它们各自的出力特性天然互补。风电出力在夜间和冬季往往更猛光伏只在白天有输出而负荷高峰一般出现在早晚两个时段。储能的任务就是在风光出力大于负荷时把多余电能存起来在出力不足时放出来补缺口。这个项目里储能不是单一的电池而是电池加废弃矿井小型抽水蓄能。电池响应快、能量密度高适合做小时级到分钟级的快速调节抽水蓄能容量大、寿命长适合做大容量、长时的能量搬移。但常规抽水蓄能需要上下水库配套设施选址非常受限制而废弃矿井恰好提供了现成的巷道和竖井空间改造成小型抽蓄既可以复用矿道水仓又能解决矿区转型后的闲置资源再利用问题。风电、光伏出力具有随机性和波动性这不是靠“把预测做准”就能解决的因为预测误差永远存在。调度模型要做的是在已知风光预测曲线的场景下决定每个时段该从电网买多少电、该让电池充多少放多少、该让抽蓄抽水还是发电最终让系统在满足负荷需求的前提下总运行成本尽可能低。1.2 为什么要把电池和废弃矿井抽蓄放在同一个调度模型里电池和抽蓄放在一起很多人会先入为主认为“这会让约束变多求解变难”。实际上把两类储能统一建模后计算量并不会明显增加因为它们在数学描述上高度相似都是用“状态变量加功率通道”描述。电池的数学模型是SOC递推方程抽蓄的能量状态可以用等效水库水位或等效储能电量来描述。两者的差异只在效率、容量上限和功率上限这几个参数上。我把它们抽象成同一个“储能节点”在功率平衡方程里电池放电功率和抽蓄发电功率都在电源侧相加电池充电功率和抽蓄抽水功率都在负荷侧相加调度模型不需要知道电是从电池里放出来的还是从蓄水池里流出来的。这种统一抽象的好处是后续想做扩展时加一个飞轮储能、加一个压缩空气储能只需再定义一组参数和递推约束目标函数和功率平衡的主体框架完全不用动。我在代码里特意把电池参数和抽蓄参数放在同一个参数区就是为这个目的。1.3 调度目标怎么定双惩罚系数的经济学含义目标函数不是越多越好也不是越复杂越好。这个项目的调度目标用一句话概括在满足功率平衡和储能运行约束的前提下最小化系统总运行成本。总运行成本包含三项购买外电费用、弃风惩罚、弃光惩罚。购买外电费用是主成本弃风弃光惩罚是对“浪费新能源”行为施加的代价。你可能想问为什么要给弃风弃光设惩罚而不是直接把“弃电量最小”写进目标函数因为惩罚系数代表的是“你愿意花多少钱来减少一度新能源弃电”。如果惩罚系数设得比购电价低模型会倾向于多买电多弃电如果设得比某些时段的售电收益还高模型就会宁可多储能也多消纳。这就是经济调度里惩罚系数的本质——它把“环保偏好”换算成成本信号。实际项目中惩罚系数可以按弃电成本估算也可以用敏感性分析来标定后面我会单独讲参数实验。2. 数据准备与模型搭建把物理问题翻译成数学问题2.1 风光出力、负荷与电价准备调度模型的第一步永远是数据而不是建模。这里的数据至少包括风电预测出力序列、光伏预测出力序列、负荷预测曲线和分时购电价。风电和光伏预测出力可以用历史统计数据、仿真软件生成或者直接使用公开数据集。这个项目为了把逻辑讲透用Python直接生成一组带有波动特征的模拟数据。实际操作时如果做论文研究建议使用某个风电场或光伏电站的典型日曲线如果做项目预研可以用数值天气预报配合功率预测模型生成。负荷曲线也一样网上有很多公开微电网负荷数据集也常见用冬季和夏季典型日曲线代替。价格数据尽量采用当地电网的分时电价目录峰谷价差会直接影响储能的调度策略。我模拟数据时给了一个阶梯电价峰时段电价比谷时段高出一倍以上这样储能才真正有“低充高放”的套利空间。2.2 电池储能和抽蓄的统一抽象能量状态与功率通道电池和抽蓄建模时我建议把“能量状态”和“功率通道”分开理解。能量状态就是电池SOC或蓄水池的等效储能电量功率通道是每个时段的充放电功率。调度决策直接控制功率通道功率经过效率和时间的累计影响能量状态。电池充电时实际进入电池的电量要乘以充电效率因为充电过程中有一部分能量变成了热损耗电池放电时放电功率是电池对外输出的功率内部消耗的能量要再除以放电效率才能折算回SOC变化量。抽蓄也一样抽水时从上水库到蓄水池的能量转换存在效率折减发电时水轮机组也有效率损失。我在代码里用两个递推方程分别描述电池和抽蓄的能量状态电池SOC_bat[t1] SOC_bat[t] eta_ch * P_ch[t] * dt - P_dis[t] / eta_dis * dt抽蓄SOC_phs[t1] SOC_phs[t] eta_pump * P_pump[t] * dt - P_gen[t] / eta_gen * dt这两个方程是整个调度模型的核心骨架。把握好这个统一抽象后面的约束就只是给变量加边界。2.3 废弃矿井抽蓄的简化建模要点废弃矿井小型抽水蓄能和常规抽水蓄能最大的区别在于它没有天然水库而是利用矿井巷道、井筒和采空区作为储水空间。巷道长度有限水头差往往不大但作为小型储能站布置在地面低洼处或井下反而有土建成本低、选址灵活的优势。在调度模型里不需要精细模拟巷道水力细节可以把等效储能容量近似为井下水仓可用水量与平均水头的乘积再折算成等效电量。最大抽水功率取决于水泵容量最大发电功率取决于水轮发电机容量。在允许范围内抽蓄机组可以连续调节这是小型机组比大型机组灵活的地方。我建摸时给抽蓄设置了独立的容量上下限和功率上下限并在递推方程中考虑抽水效率和发电效率。这里要特别提醒废弃矿井抽蓄的循环效率通常比电池低单程抽水发电综合效率可能在65%到75%而锂电池储能的综合效率可以做到85%以上。抽蓄的优势不在效率而在大容量和长寿命所以调度模型不应该强行让抽蓄频繁启停应该让它承担长时间尺度的能量搬移。2.4 约束条件清单哪些线不能碰调度模型的约束分为三类功率平衡约束、储能能量状态约束、变量边界约束。功率平衡约束是每时段的“记账式”约束。系统内所有电源出力加上购入电量必须等于负荷加上所有储能充电消耗。这里的电源包括风电消纳量、光伏消纳量、电池放电功率和抽蓄发电功率负荷侧包括实际负荷、电池充电功率和抽蓄抽水功率。储能能量状态约束包括递推方程、初始能量约束和末态能量约束。日调度场景中我会让末态能量等于初始能量也就是一天调度结束后储能状态恢复到起点这样模型才能在跨日场景中持续运行。如果做多日滚动调度可以把末态约束改成“不低于某个下限”给第二天留出调节空间。变量边界约束就是给每个决策变量设置上下限。购电量不能超过电网关口容量弃风弃光不能超过当前可用的风光出力充放电功率不能超过设备额定功率SOC不能超过容量上下限。这里有一个容易被忽略的问题充电和放电不能同时发生。理论上如果目标函数里没有损耗存在线性规划可能会给出“一边充电一边放电”的荒唐解。但因为充放电效率都小于1同时充放会造成能量损失在最小化成本的目标下模型天然不会选择这种操作。为了数值稳定我仍然额外加了一个约束同一储能设备同一时段的“充放功率之和不超过额定功率”这样即使求解器数值精度出现问题也不会出现“一边充满一边放空”的荒谬结果。3. Python完整实现从数据到调度指令3.1 环境准备与依赖库这个项目用到的Python库不多核心就四个numpy处理数组pandas管理时间序列PuLP建线性规划模型matplotlib画结果图。PuLP是这里的主角它自带CBC开源求解器安装后可以直接用不用额外配置商业求解器。后面如果想换Gurobi或CPLEX只需要把求解器接口换成对应的solver参数模型代码不用改。安装命令很简单pip install numpy pandas pulp matplotlib如果你是从零开始装的Python建议直接装Anaconda或Miniconda把科学计算库一次带齐。我在实际环境中用的是Python 3.10这些库目前都兼容没什么特殊要求。3.2 数据生成与参数区设置我习惯把参数单独放在代码开头这样改参数不需要动模型逻辑。下面这段代码生成了24小时的风光负荷数据并定义了电池和抽蓄的核心参数。import numpy as np import pulp as pl import matplotlib.pyplot as plt # 关闭画图警告设置中文字体后面讲乱码问题 plt.rcParams[font.sans-serif] [SimHei, Microsoft YaHei] plt.rcParams[axes.unicode_minus] False T 24 # 调度周期单位小时 dt 1.0 # 每个时段的时长单位小时 # 模拟风电功率曲线 t np.arange(T) wind_base 60 25 * np.sin(2 * np.pi * (t - 8) / 24) 10 * np.random.randn(T) wind_fc np.clip(wind_base, 0, 100) # 模拟光伏功率曲线只在白天出力 pv_fc np.clip(80 * np.sin(np.pi * (t - 6) / 12), 0, 80) pv_fc[t 6] 0 pv_fc[t 18] 0 # 模拟负荷曲线 load 120 30 * np.sin(2 * np.pi * (t - 9) / 24) 20 * np.random.randn(T) load np.clip(load, 80, 180) # 分时电价峰平谷三个段单位元/MWh price np.ones(T) * 300 price[(t 8) (t 11)] 500 price[(t 18) (t 21)] 550 price[(t 23) | (t 6)] 180这里的数据生成方式只是演示用途。如果做实际项目风电、光伏、负荷曲线都应该换成实测数据但数据结构保持一致后面的模型不需要做任何修改。下面定义储能参数# 电池参数 BAT_CAPACITY 100 # 电池容量 MWh BAT_SOC_MIN 20 # 最低SOC MWh BAT_SOC_MAX 100 # 最高SOC MWh BAT_INIT_SOC 50 # 初始SOC MWh BAT_MAX_CH 30 # 最大充电功率 MW BAT_MAX_DIS 30 # 最大放电功率 MW ETA_BAT_CH 0.95 # 充电效率 ETA_BAT_DIS 0.95 # 放电效率 # 废弃矿井抽蓄参数 PHS_CAPACITY 120 # 等效储能容量 MWh PHS_SOC_MIN 20 PHS_SOC_MAX 120 PHS_INIT_SOC 60 PHS_MAX_PUMP 20 # 最大抽水功率 MW PHS_MAX_GEN 20 # 最大发电功率 MW ETA_PUMP 0.85 # 抽水效率 ETA_GEN 0.85 # 发电效率 # 系统参数 BUY_MAX 200 # 关口购电上限 MW CURTAIL_WIND_PEN 350 # 弃风惩罚元/MWh CURTAIL_PV_PEN 400 # 弃光惩罚元/MWh MAINT_COST 0.1 # 储能的极小维护成本系数注意我这里用MWh和MW做单位调度时段是1小时所以功率乘时间就等于电量。如果时段粒度改成15分钟dt就改成0.25所有递推方程会自动调整不需要改其他逻辑。3.3 目标函数与约束条件的建模接下来用PuLP建立决策变量。这里每一类变量都对应调度模型中的一个维度。prob pl.LpProblem(Wind_PV_Storage_Scheduling, pl.LpMinimize) # 决策变量 P_buy pl.LpVariable.dicts(P_buy, range(T), 0, BUY_MAX) cut_w pl.LpVariable.dicts(cut_w, range(T), 0, None) cut_p pl.LpVariable.dicts(cut_p, range(T), 0, None) bat_ch pl.LpVariable.dicts(bat_ch, range(T), 0, BAT_MAX_CH) bat_dis pl.LpVariable.dicts(bat_dis, range(T), 0, BAT_MAX_DIS) pump pl.LpVariable.dicts(pump, range(T), 0, PHS_MAX_PUMP) phs_gen pl.LpVariable.dicts(phs_gen, range(T), 0, PHS_MAX_GEN) # SOC作为变量T1个时刻从0到T soc_bat pl.LpVariable.dicts(soc_bat, range(T 1), BAT_SOC_MIN, BAT_SOC_MAX) soc_phs pl.LpVariable.dicts(soc_phs, range(T 1), PHS_SOC_MIN, PHS_SOC_MAX)目标函数需要把每个变量的成本系数和变量本身相乘然后累加。# 目标函数购电成本 弃风惩罚 弃光惩罚 储能维护成本 prob ( pl.lpSum(price[t] * P_buy[t] for t in range(T)) pl.lpSum(CURTAIL_WIND_PEN * cut_w[t] for t in range(T)) pl.lpSum(CURTAIL_PV_PEN * cut_p[t] for t in range(T)) pl.lpSum(MAINT_COST * (bat_ch[t] bat_dis[t] pump[t] phs_gen[t]) for t in range(T)) )为什么最后要加一个极小的维护成本系数虽然充放电效率已经能阻止纯损耗行为但在线性规划中如果有多组解对应相同目标值求解器可能随机输出一些看似合理的解。加上这个成本项以后目标函数变成了严格凸的线性组合求解器会优先选择总操作量更小、更节省的方案。这个技巧在实际项目中很实用。功率平衡约束和储能递推约束如下# 功率平衡约束 for t in range(T): prob ( P_buy[t] (wind_fc[t] - cut_w[t]) (pv_fc[t] - cut_p[t]) bat_dis[t] phs_gen[t] load[t] bat_ch[t] pump[t] ) # 弃风弃光不能超过可消纳量 prob cut_w[t] wind_fc[t] prob cut_p[t] pv_fc[t] # 电池SOC递推 for t in range(T): prob ( soc_bat[t 1] soc_bat[t] ETA_BAT_CH * bat_ch[t] * dt - bat_dis[t] / ETA_BAT_DIS * dt ) # 抽蓄等效能量递推 for t in range(T): prob ( soc_phs[t 1] soc_phs[t] ETA_PUMP * pump[t] * dt - phs_gen[t] / ETA_GEN * dt ) # 初始和末态约束 prob soc_bat[0] BAT_INIT_SOC prob soc_phs[0] PHS_INIT_SOC prob soc_bat[T] BAT_INIT_SOC prob soc_phs[T] PHS_INIT_SOC写完这一部分模型基本就成型了。PuLP的约束写法和数学公式几乎一一对应读起来很直观这也是我推荐用PuLP而不是手搓矩阵的原因。3.4 求解与结果提取求解过程非常简单但结果提取有一些小坑要注意。# 求解 status prob.solve() print(求解状态:, pl.LpStatus[status]) # 提取结果到numpy数组 def get_value(vars_dict): return np.array([pl.value(vars_dict[t]) for t in range(T)]) P_buy_opt get_value(P_buy) cut_w_opt get_value(cut_w) cut_p_opt get_value(cut_p) bat_ch_opt get_value(bat_ch) bat_dis_opt get_value(bat_dis) pump_opt get_value(pump) gen_opt get_value(phs_gen) soc_bat_opt np.array([pl.value(soc_bat[t]) for t in range(T 1)]) soc_phs_opt np.array([pl.value(soc_phs[t]) for t in range(T 1)]) # 统计指标 total_wind wind_fc.sum() total_pv pv_fc.sum() wind_used total_wind - cut_w_opt.sum() pv_used total_pv - cut_p_opt.sum() total_cost pl.value(prob.objective) print(f风电消纳量: {wind_used:.2f} MWh, 弃风量: {cut_w_opt.sum():.2f} MWh) print(f光伏消纳量: {pv_used:.2f} MWh, 弃光量: {cut_p_opt.sum():.2f} MWh) print(f关口购电量: {P_buy_opt.sum():.2f} MWh) print(f系统总成本: {total_cost:.2f} 元)如果求解状态是Optimal说明模型有可行解且达到最优。如果你看到Infeasible通常意味着约束存在矛盾比如风电和光伏同时几乎为零负荷又很高购电上限不够。下文会专门说怎么排查。3.5 结果可视化与调度曲线图调度结果光看数字很难发现问题一定要画图。我一般画三张图一张是功率平衡图一张是储能SOC变化图一张是购电和弃电统计图。画图代码并不复杂关键是信息要齐全fig, ax plt.subplots(3, 1, figsize(12, 10)) ax[0].plot(t, P_buy_opt, label购电功率, markero) ax[0].plot(t, wind_fc - cut_w_opt, label风电消纳, markers) ax[0].plot(t, pv_fc - cut_p_opt, label光伏消纳, marker^) ax[0].plot(t, bat_dis_opt - bat_ch_opt, label电池净放电, markerd) ax[0].plot(t, gen_opt - pump_opt, label抽蓄净发电, markerx) ax[0].plot(t, load, label负荷, linestyle--, colorblack) ax[0].set_ylabel(功率/MW) ax[0].legend(locupper right) ax[0].grid(True) ax[1].plot(t, soc_bat_opt[:-1], label电池SOC, markero) ax[1].plot(t, soc_phs_opt[:-1], label抽蓄等效电量, markers) ax[1].set_ylabel(储能电量/MWh) ax[1].legend() ax[1].grid(True) ax[2].bar(t, bat_ch_opt, label电池充电, colorgreen, alpha0.6) ax[2].bar(t, -bat_dis_opt, label电池放电, colorred, alpha0.6) ax[2].bar(t, pump_opt, label抽水, colorblue, alpha0.3) ax[2].bar(t, -gen_opt, label发电, colororange, alpha0.3) ax[2].set_ylabel(储能操作功率/MW) ax[2].legend() ax[2].grid(True) plt.tight_layout() plt.show()我从实际经验里总结出一件事功率平衡图里只要出现电池净放电和抽蓄净发电同时为负就要引起警觉这可能意味着存在“充电后再放电”的无意义循环。虽然这种解概率不大但加了维护成本以后基本可以杜绝。4. 实验设计与参数调优用对照组找出调度规律4.1 不同储能配比的对照实验为了说清楚“电池和抽蓄到底谁在起作用”我建议做几个消融实验。所谓消融实验就是把某个储能设备关掉跑同样一组数据对比目标值和弃电情况。做法很简单把对应变量的功率上限设置为0或者在建模时不加对应的SOC递推约束。以下是我用模拟数据跑出来的一组代表性结果具体数字取决于随机种子但趋势明显。储能配置购电成本/元弃风弃光总量/MWh系统总成本/元无储能2658018635980仅电池229506729200仅抽蓄2412010532120电池抽蓄214303127480从这个表能清楚看到混合储能在总成本和弃电控制上都优于单一储能。原因是电池负责峰谷套利和短时响应抽蓄负责大容量的跨时段能量搬移两者分工不同组合后能覆盖更多场景。这个结论对工程配置也有参考意义不是储能加得越多越好而是要看风光占比和负荷曲线特征。如果系统以光伏为主电池的响应能力更重要如果系统里风电比例高且昼夜波动大抽蓄这种大容量储能更能在白天存电、夜间放电。4.2 惩罚系数的敏感性分析在目标函数里弃风惩罚350、弃光惩罚400是我拍脑袋定的。真正严谨的做法是跑一组敏感性分析把惩罚系数从100扫到1000看系统总成本和弃电量的变化。以我跑过的经验来说惩罚系数太小会让模型倾向于直接弃掉风光因为买电更便宜惩罚系数太大又会逼着储能拼命消纳新能源甚至可能出现“为消纳而消纳”的高成本调度方案。合理的惩罚系数应该落在“边际弃电成本”附近也就是让模型在弃电和购电之间保持经济中性。如果是在论文中使用建议把敏感性分析的结果画成二维曲线或热力图横轴是弃风惩罚纵轴是弃光惩罚颜色代表总成本。这样不仅能让结果更好看也能证明你的调度策略在全参数范围内都稳定。5. 常见问题与排查技巧实录5.1 求解无解该怎么排查我在这个项目里至少遇到过三次Infeasible。第一次是购电上限设得太低系统无解第二次是SOC末态约束设置为等于初始值但放电功率初始阶段上限太小导致后面无法恢复电量第三次是抽蓄初始SOC低于SOC下限递推方程一开始就不满足边界约束。排查Infeasible的通用思路是把约束逐个“松绑”再用二分法定位冲突约束。具体来说先把购电上限调到极大值如果解出来了说明是电量缺口过大再把SOC末态约束去掉如果解出来了说明是储能恢复不了初始值如果还无解就把递推方程中的效率都改成1排除效率导致的能量损耗问题。最笨也最有效的方法是给每个约束打印松弛量。PuLP的约束对象有.pi和.slack属性可以通过遍历模型约束来查看哪些约束被压缩到了极限。不过对小规模模型手动二分更快捷。5.2 SOC累计偏差问题电池SOC和抽蓄等效能量在递推过程中可能出现微小偏差原因有两个一是求解器数值精度有限二是效率四舍五入导致的累计误差。单日调度还好如果做365天连续调度哪怕每天只偏差0.01 MWh一年下来也可能积累到3.65 MWh影响后续调度结果。我的建议是在每个调度周期结束后做一次SOC修正把误差重新分配或直接重置为实际测量值。项目级的能量管理系统通常采用“状态估计周期重置”的方式调度模型里的SOC永远只是计划值不是真实值。5.3 数据对齐和时间粒度问题风电、光伏、负荷、电价四条曲线必须时刻对齐。刚拿到数据时最容易出现的问题是光照数据用东八区时间风电数据用UTC时间负荷数据又按自然日统计导致功率平衡约束在部分时段出现莫名其妙的缺口。我建议所有数据统一转换成项目本地时间并按调度时段重采样。如果做15分钟粒度调度就要把小时数据均匀插值到15分钟或者做分时累加不能简单填0。还有一个小坑时间是第二天的凌晨0点还是当天24点直接影响matplotlib横轴。5.4 matplotlib中文乱码与性能优化每次提到Python画图一定会有人遇到中文乱码。Windows下常见解决方案是设置“Microsoft YaHei”或“SimHei”Linux服务器上可能需要安装中文字体包。如果不想折腾字体也可以把所有图例和标签改成英文但这在中文报告里不太合适。另一个性能问题是当T很大、变量很多时PuLP构建约束时会变慢。如果优化周期超过24小时且粒度到了15分钟可以考虑把约束写成稀疏矩阵形式交给scipy.optimize.linprog求解。实测下来96个时段以内用PuLP完全够用超过200个时段建议换scipy矩阵化写法效率会差出一个数量级。5.5 求解结果里储能的“虚假活跃”有一次我跑出的调度方案里电池每小时都在充放看起来非常“勤奋”但系统总成本并没有明显改善。查了半天发现是因为维护成本系数设置得太小求解器在目标函数不敏感的情况下找了任意一个可行解。解决方式是把维护成本系数从0.1调整为0.5或者更大再跑一次储能的操作次数就明显减少了。这个问题的本质是线性规划存在多个等价最优解加入微小的正则项可以引导出更合理的调度方案。后续如果追求更好效果可以考虑在目标函数中增加储能启停次数的惩罚但这会让问题变成混合整数规划求解复杂度和耗时都会增加。6. 这个项目能怎么扩展从论文代码到工程落地6.1 时间尺度扩展日前调度、滚动修正与实时控制当前的模型是一个单日“全局最优”调度模型知道了全天数据直接一次性算完。实际运行中风光出力预测是会变化的不能拿早晨的结果直接命令下午的设备。工程上常用的做法是滚动优化。每隔15分钟或1小时滚动一次模型只求解未来4小时或12小时的调度计划执行第一个时段的指令然后丢掉旧窗口重新预测再次求解。这样调度系统永远在更新预测误差的影响就被控制在滚动窗口内。在代码里实现滚动优化并不难把当前这段求解代码包装成一个函数输入滚动窗口的数据输出第一个时段的指令然后循环调用就行。这个扩展是我最推荐先做的一步因为从论文模型到工程应用关键区别就在这。6.2 废弃矿井抽蓄的场地特殊性与仿真校准废弃矿井抽蓄建模有一个现实问题矿井巷道的水力阻力、水头损失、井筒深度的摩擦损耗都是等效效率模型没考虑的事情。在实际工程落地时建议用流体力学仿真或现场实测数据把抽蓄效率从常数改成水头或流量的函数。如果调度模型想更精确可以把效率不是常数而随流量变化的数据表引入模型。最简单的做法是在不同出力区间设置多个分段线性效率但那样会引入非线性或二进制变量求解复杂度上升。前期预研阶段用一个中等数值的效率常数完全足够说明调度策略的可行性。6.3 对“虚拟电厂”和园区微电网的迁移价值这套“风光多元储能联合调度”的框架不只适用于废弃矿井。园区微电网、增量配电网、虚拟电厂聚合商本质上都在做同一件事把一堆分布式的发用电设备打包成一个可调度对象在满足本地用电的同时降低总成本。如果把这个项目的电池换成电动车电池把抽蓄换成冷水机组或者UPS把风电光伏换成任意分布式电源模型结构基本不用变。所以我说这组代码真正的价值不是一个具体的风光储方案而是一套可以复用的“分布式能源优化调度骨架子”。我做完这个项目最大的体会是调度优化的门槛不在算法而在你愿不愿意把问题拆到“功率平衡能量递推”这么底层。把这层窗户纸捅破以后剩下的都是工程细节。照着这个思路去改你的具体场景你会发现杂七杂八的设备最后都能装进同一个模型里。