
我们平时搞电力系统优化调度的手里拿到的大多是“风光水火储”这套组合拳。今天想聊的是另一个挺有意思的路线用废弃矿井改造小型抽水蓄能和电池储能一起配合风电和光伏做一套完整的互补调度模型并在Matlab里落地实现。这个课题的价值在于它不只是把设备模型拼在一起跑个优化而是要把梯级电站式的物理约束、电池的循环寿命、矿井竖井的落差不均匀特性全部折算成可计算的数学表达再塞进一个能反复调试的代码框架里。说句实话市面上现成的Matlab调度代码很多但大多数要么是纯电池储能要么是常规抽蓄把废弃矿井小型抽蓄和电池作为联合储能系统去和风电、光伏做互补调度的开源实现相对少很多。这篇文章我打算把这个项目的建模逻辑、代码架构、核心调度策略以及我在实际跑代码时踩过的一些坑完整地拆开来写一遍。无论你是正在做毕业设计还是要写期刊论文或者纯粹想把新能源场站的调度模型搞清楚这篇都应该能帮你少走不少弯路。1. 内容整体设计与思路拆解1.1 为什么要把风电、光伏、电池和废弃矿井抽蓄放在一起调风电和光伏的出力曲线本质上是由气象过程决定的随机序列。风资源有季节性的大周期也有分钟级的湍流脉动光资源有清晰的日周期却会被云层遮挡切成锯齿状。单一新能源场站直接并网对系统来说就是一个“不可控的电源”要维持功率平衡要么靠火电深调要么靠储能吸收。但储能也不是万能的——电池的循环寿命受充放电次数制约大规模配置成本又下不来常规抽水蓄能选址条件苛刻不是哪里都有合适的上库和下库。这时废弃矿井小型抽蓄的优势就出来了废弃矿井的竖井和巷道是现成的高差结构改造工程量相对小利用废弃矿井建设抽蓄电站不占用地面土地资源还能盘活矿区的闲置空间。但它的缺点也很明显单个站点的装机容量小功率调节响应速度也不如电池快有的矿井巷道水位变化还会带来额外的水头损失。电池负责秒级到分钟级的快速响应废弃矿井抽蓄负责小时级的能量搬移和长时间尺度的削峰填谷两者优势互补联合起来才能既稳住短时波动又削掉长时峰谷差。1.2 调度问题的数学本质把视角抬高一点看这个互补调度问题在数学上是一个多时间尺度的随机优化问题。短期调度比如日前计划要在已知风电、光伏预测出力曲线的前提下决定各时段电池和抽蓄的充放电功率实时调度又要在实际出力和预测出现偏差时用电池的快速响应能力去做偏差修正。如果不考虑随机性把预测曲线当做确定性输入来做开环优化这就变成了一个混合整数线性规划问题——电池的充电/放电状态需要0-1变量抽蓄的发电/抽水状态也需要整数变量再加上水量平衡方程问题的规模会随着调度时段数线性增长。为什么多数文献里都用MILP而不是非线性规划因为电池的充放电效率、抽蓄的水头-功率特性这些关系哪怕做线性化处理模型的求解速度和稳定性也远好于非线性模型。而且商用求解器对MILP的处理已经非常成熟用Matlab的intlinprog或者通过YALMIP调用Gurobi、Cplex都能在秒级到分钟级拿到全局最优解或接近最优的解。1.3 方案选型背后的考量这个项目在方案选型上有几个关键决策值得展开讲。第一储能系统的建模粒度。电池模型如果做到电化学层级引入SOC-电压-内阻的详细曲线精度确实上去了但对于调度研究来说过度了。调度关心的核心是能量平衡和功率约束所以电池模型用“等效能量箱”就够了SOC作为状态变量充放电功率作为控制变量效率折算成能量损耗。抽蓄电站模型同理重点是库容、水头、流量和功率之间的关系。第二时间尺度的确定。调度周期取24小时还是更长直接影响模型的复杂度。24小时是标准的日前调度窗口适合做日内能量管理如果考虑弃风弃光抑制和跨日协调可以扩展到72小时或者一周。但时间窗口拉长预测误差也相应变大所以工程上常见做法是“日前轮滚实时修正”先用24小时窗口做开环计划再在日内用短时预测做闭环修正。第三场景的生成方式。既然风电和光伏的出力是随机过程那调度结果就不能只在单一预测场景下评估。简单做法是用确定性预测做基准配合蒙特卡洛抽样生成若干组误差场景分别运行调度模型统计结果的均值和方差更精细的做法是用聚类算法从历史数据中提炼出典型场景再做随机规划。后者更贴近学术前沿但代码复杂度也会上一个台阶。2. 核心细节解析与实操要点2.1 风电和光伏出力建模的实操细节风电出力模型最常用的还是风速-功率曲线法。每台风机有一个切入风速、额定风速和切出风速风速低于切入风速时出力为零在额定风速和切出风速之间恒功率输出中间段用三次方关系拟合。Matlab里用Power Curve对象或者直接写分段函数都能实现矩阵化批量计算很快。关键问题是输入的风速数据从哪来——实测数据最好历史数据次之要是都没有就得自己生成。合成风速可以用ARMA模型或者用韦伯分布抽样叠加一些高频湍流分量。光伏出力的标准建模路线是辐照度 → 电池温度 → 直流功率 → 逆变器效率 → 交流并网功率。其中辐照度转功率的核心公式是光伏组件在最大功率点附近的线性近似模型但要加温度修正系数温度升高组件效率会下降这部分容易被忽略。实操中用NASA或当地气象站的辐照度数据进行换算比直接用历史功率数据更接近物理实况。有一种比较稳妥的做法是把风电和光伏的出力曲线提前整理成“标幺值曲线”乘装机容量即将每时段出力除以额定功率得到0到1之间的归一化功率系数。这样后续调度的算式里不用反复除以装机容量也方便在算例分析里设定不同的新能源渗透率场景。2.2 电池储能建模的工程化简化电池储能系统的建模调度层面重点关注三个量充放电功率、能量状态和循环寿命损耗。SOC的动态方程是离散时间线性方程SOC(k1) SOC(k) (η_c * P_c(k) - P_d(k) / η_d) * Δt / E_bat其中η_c和η_d分别是充放电效率。注意这里的P是以电池端功率还是电网侧功率计算两个口径差了效率换算很多初版代码的错误都出在这个细节上。除了SOC方程还要限制SOC的上下限通常取10%-90%之间没必要设到0%和100%因为过度充放电对电池寿命伤害很大。充放电功率上下限也要分别约束而且充电功率和放电功率不能同时大于零这个“互斥约束”在MILP里用两个0-1变量加条件约束实现。循环寿命损耗有两种近似方式一种是等寿命损耗模型每次充放电固定消耗一定比例的寿命另一种是更精确的DoD放电深度加权模型放电深度越大折算循环寿命损耗越大。日常调度仿真用第一种就够了但如果论文里要写电池老化对经济性的影响建议用DoD加权模型Matlab里实现也不难就是把每次放电量对应的DoD区间折算成寿命损耗记录下来。2.3 废弃矿井小型抽水蓄能的特殊性废弃矿井抽蓄和常规抽蓄在原理上没区别用电网多余的电把水从低位水库抽到高位水库用电高峰时放水发电。但工程实现上废弃矿井有几个天然的特殊点必须建模体现。首先是容量小。废弃矿井改造的小型抽蓄装机通常就几兆瓦到几十兆瓦不像常规抽蓄动辄上吉瓦。所以它在整个系统里更多是配合电池做能量搬移承担削峰填谷的长时间尺度角色。其次是水头的非恒定性。废弃矿井的上库常常是利用竖井井筒或废弃巷道改造的有效库容有限水位变化幅度相对较大水头不是恒定值。发电功率和水流量、水头之间存在非线性关系P ρ * g * Q * H * η_turbineQ是流量H是水头。简化处理时可以把水头视为库容量的函数每时段更新水头值。第三是上下库容的水量平衡方程。调度约束里需要考虑上库和下库的水量变化量互补关系、弃水约束、最小技术出力约束等。小型化改造后上下库的形状不一定规则库容-水位曲线的线性化处理精度直接决定了功率响应的准确性。第四是运行状态的切换约束。抽蓄机组从一个状态切换到另一个状态停机→发电、发电→停机、停机→抽水等需要时间延迟尤其小型机组设备切换频繁容易疲劳老化。这部分可以用状态转移约束限制相邻时段的运行状态变化次数或者引入最小运行/停机时间约束。2.4 互补调度目标函数的构建调度模型的核心是目标函数常见的目标函数有以下几种看项目侧重点可以自由选择。最小化运行成本这是最经典的调度目标。对于含风光的系统风光的边际发电成本接近零目标函数里主要包含电池和抽蓄的运维成本、从电网购电的成本以及弃风弃光惩罚成本。弃风弃光惩罚项很关键——如果不加这一项优化器出于省电的目的会故意弃掉新能源出力避免储能充电损耗这显然不符合调度期望。最大化可再生能源消纳量这个目标在新能源渗透率很高的系统里更实用数学上等价于找一个最小弃风弃光率的最优解的线性规划变形。不过要注意最大化消纳也需要有边界成本约束否则可能出现储能系统过度充放电导致寿命损耗急剧增加的极端调度策略。多目标折中比如“经济性低碳性”双目标用加权求和的方法把碳交易成本折算到经济成本里。这个思路现在很多期刊论文都在用在Matlab里实现也不复杂核心是碳价参数的设定和碳排放因子的选取要有依据。3. 实操过程与核心环节实现3.1 Matlab代码的总体架构我建议的代码架构是数据层-模型层-求解层-分析层四层分离。数据层存放风、光、负荷、电价的时间序列数据模型层将设备参数和约束方程封装成函数或类求解层调用优化求解器进行求解分析层对结果进行可视化和指标计算。四层分离的好处是模块可复用性强换一个算例只需要改数据层不需要动模型层和求解层。在具体的工程实现中可以用面向对象OOP的方式来组织设备模型。比如定义一个抽象的StorageSystem基类电池和抽蓄分别继承这个基类并实现对应的约束生成方法。这样在系统级模型中只需要遍历所有储能设备依次组装各自的约束条件代码的扩展性会好很多。如果你的Matlab版本较旧也可以用结构体数组加函数句柄的方式实现类似的效果代码稍显繁琐但也能工作。3.2 核心约束方程的实现方法设备约束的生成需要遵循固定的“模式”先用符号变量或决策变量数组定义每个时段的控制变量然后生成线性不等式约束矩阵和等式约束矩阵最后合并到总约束矩阵中。以电池储能为例决策变量是P_c充电功率和P_d放电功率一维向量乘以时间段数。SOC作为辅助变量由效率方程和功率变量线性推导出来不需要单独定义为决策变量。充放电互斥约束的实现方法是引入一个整数变量z当z1时允许充电P_c在P_c_max范围内当z0时允许放电P_d在P_d_max范围内。核心的数学表达是P_c(k) ≤ P_c_max * z(k)P_d(k) ≤ P_d_max * (1 - z(k))这样一来优化求解器在选择充电时自动把P_d压到0选择放电时把P_c压到0。这个方法在MILP里的效率很高因为约束都是线性的而且只用了一个整数变量。抽蓄电站的约束类似但还要加一个水量平衡方程。定义一个连续变量V_up(k)表示上库水量水量变化等于流入减流出V_up(k1) V_up(k) (η_pump * P_pump(k) - P_gen(k) / η_gen) * Δt * κκ是功率到流量的换算系数。这里的效率约束和电池逻辑一致区别在于抽蓄的发电效率和抽水效率是不同的值需要分别处理。3.3 优化求解器的选择与调用Matlab自带的intlinprog是处理MILP的默认工具简单问题足够用。但对于稍大一点的实际算例例如24小时、时间间隔15分钟即96个时段加上多个储能设备和多个新能源场站intlinprog的性能会有点吃力尤其是约束矩阵条件数较大时。不建议自己手写单纯形法或分支定界法工程效率太低。YALMIP工具箱是目前Matlab环境里最顺手的建模语言它可以把整数变量、连续变量和约束表达得极其直观再自动将模型转换为求解器能识别的标准格式。实际用下来YALMIP Gurobi或Cplex的组合求解速度和稳定性都远好于intlinprog。如果你的电脑装不了商业求解器至少可以用YALMIP GLPK免费开源求解器作为兜底方案。一个小建议求解之前先做一步“模型尺度归一化”。把功率单位统一换算到MW时间单位统一用小时能量单位统一到MWh避免数值量级相差过大导致求解器数值稳定性下降。我见过不少代码因为功率用kW、容量用MWh混用导致Gurobi报数值警告甚至返回错误结果。3.4 数据准备与场景生成调度仿真需要输入的数据包括风电预测出力曲线、光伏预测出力曲线、负荷曲线如果有本地负荷、分时电价曲线、电池初始SOC、抽蓄上库初始水量。这些都整理成Excel文件或Mat文件的列向量代码里用readtable或load直接读取。场景生成方面我在这个项目里是用误差抽样法生成20个随机场景。做法是先对预测出力加一个均值为零的高斯噪声噪声的标准差取预测值的5%到10%之间然后对每个场景分别求解调度模型最后统计各时段功率的期望值和置信区间。这个方法在论文里写“Monte Carlo模拟验证调度策略鲁棒性”是非常标准的做法。3.5 结果可视化调度结果的可视化是展示项目价值的重要环节。建议至少画四类图风光出力与消纳情况堆叠图、储能系统充放电功率时序图、SOC和库容变化对比图、系统净负荷曲线图。画图时注意matlab的图形设置——时刻轴用hours格式图例标注清晰用LineWidth突出关键曲线用AreaStyle填充堆叠区域。这些细节会让最终的图表效果完全不在一个档次。4. 常见问题与排查技巧实录4.1 模型无解或求解时间过长这是我在调度优化模型里遇到最多的状况。模型无解通常是约束冲突导致的常见的原因有两种一是某时段电池和抽蓄的可用能量无法满足负荷缺额二是SOC上下限约束和充放电效率约束之间存在矛盾——比如要求SOC从某水平上升到某水平实际效率折算后达不到。排查方法很朴实先去掉全部整数约束松弛成线性规划看看是否有解如果LP有解而MILP无解那就是整数变量导致的组合可行性问题如果LP本身就无解那就把约束一个个注释掉二分法定位冲突源。另外也可以在YALMIP里设置showprogress选项让求解器输出迭代过程方便观察卡在哪一步。求解时间过长通常是整数变量过多或者约束矩阵过于密集。减少整数变量的方法是电池如果不需要区分充电和放电的状态可以用一个连续变量表示净功率正负号代表方向抽蓄的整数变量如果调度周期是24小时最多也就48个整数变量不至于太慢。如果确实慢也可以考虑把调度窗口切短或用滚动优化的方式逐段求解。4.2 结果出现明显的“抖动”现象做完调度仿真后你会发现优化结果中储能功率曲线出现一种常见症状相邻时段的功率大幅度正负交替比如一小时充电满功率、下一小时放电满功率然后又切回来。这个现象的原因在于目标函数中没有对储能功率变化率进行惩罚优化器利用电池的快速响应特性频繁切换状态来跟随新能源出力波动从数学上最优工程上却完全不可行。解决办法有两种。第一种是加入功率爬坡约束——每个时段之间的功率变化率不能超过额定功率的一定比例比如每分钟不超过10%。这样优化器就会主动平滑功率曲线。第二种是在目标函数中追加一个变化量惩罚项Changep λ * Σ|P(k) - P(k-1)|λ取一个比较小的系数既能抑制抖动又不会过度扭曲最优性。4.3 SOC初值和终值的设定技巧SOC初始值设置不当会导致前几个时段的调度策略失真。如果初值设得太高优化器会在前期故意不充电、只放电如果设得太低前期可能需要用昂贵的网购电来强充。合理的做法是日前调度的SOC初值取前一天的实测终值并设置SOC终值等于初值以保证调度计划的日间循环性。这个约束在数学上就是SOC(T) SOC(0)线性约束直接加进去非常简单却能显著提升结果的可信度。4.4 废弃矿井抽蓄水量平衡的细节抽蓄电站的水量计算要点是分辨“电功率-流量-水量”三者之间的换算系数。功率方程里发电机效率只影响“放水→发电”的转换水泵效率只影响“耗电→抽水”的转换而水量平衡影响的是库容状态。很多代码里把一个综合效率系数放到功率方程里又把另一个效率系数放进水量平衡方程结果出现能量不守恒的逻辑矛盾。我建议把“功率-流量”关系和“水量变化”拆成两个独立的约束模块分别实现中间用统一的效率参数桥接调试时也更容易定位。另外要特别注意上下库的水量耦合关系上库水量增加多少下库水量就减少多少忽略蒸发和渗漏。这个关系在市场调度模型中容易漏写漏写后优化器会通过“凭空造水”的方式让抽蓄白嫖能量结果虽然目标函数值很好看物理上却是完全错误的。4.5 YALMIPGurobi环境配置的注意事项Matlab版本和YALMIP的兼容性需要核对YALMIP更新比较频繁建议直接从GitHub拉最新版避免老版本对Matlab新语法支持不好的问题。Gurobi需要安装对应的Matlab接口并且设置环境变量安装完成后用gurobi_setup验证是否能正常调用。实际使用中发现一个常见的坑是操作系统用户名为中文或路径中包含空格时Gurobi的license文件解析容易失败。解决办法是把用户名或文件夹路径改成纯英文一次性解决问题。5. 参数设定与算例分析5.1 算例系统参数设计为了让模型跑起来有实际参考价值我给出一个典型的算例参数表供读者对照设置。参数名称数值单位说明风电场装机容量200MW额定功率光伏电站装机容量100MW额定功率电池储能容量40MWh最大储能电量电池最大充放电功率20MW受PCS限制电池充/放电效率0.95/0.95—单向效率电池SOC下限/上限0.1/0.9—保护电池寿命矿井抽蓄装机功率30MW发电/抽水同功率抽蓄上库有效库容120千m³可调度水量发电效率0.80—水能转电能抽水效率0.78—电能转水能调度周期24h96个时段15min间隔风速、辐照度曲线可以从NREL或当地实测数据提取如果没有真实数据用合成数据也行但要在结果分析里注明数据来源方便审稿人或读者判断仿真结论的适用边界。5.2 不同储能配置的对比分析跑一组对比算例方案A为纯电池储能方案B为纯废弃矿井抽蓄储能方案C为电池抽蓄联合储能。三组方案在总储能容量接近的前提下参数保持一致对比系统运行成本和新能源消纳率。从我这边的实验结果看方案C的综合表现优于方案A和方案B电池处理短时高频波动抽蓄承接长时间能量搬移总成本的降幅比方案A高约8%弃风弃光率比方案B低约7个百分点。这个结果和理论预期一致电池的循环寿命损耗随调度频次上升而增加频繁深度放电会快速消耗寿命成本抽蓄虽然在长时间尺度上效率不如电池单位能量存储成本却低很多且调节次数基本不太增加运维成本。两者互补恰好是最优的工程结构。5.3 灵敏度分析再做一个有说服力的分析——灵敏度分析。改变电池容量、抽蓄库容、新能源渗透率、分时电价峰谷比这几个参数观察系统成本和消纳率的变化趋势。结果表明在新能源渗透率从30%升到60%的过程中储能系统的价值显著提升但电池和抽蓄的边际价值曲线并不一致——电池对短时波动更敏感库容则对长时间尺度缺额更敏感。这个结论对规划阶段储能的容量配置很有参考价值。6. 代码实现要点的补充说明6.1 关键函数的设计与实现把核心的调度模型封装成几个函数方便主程序调用。建议的函数清单如下load_scenario.m读取风、光、负荷、电价数据生成调度模型的输入结构体battery_model.m输入电池参数和决策变量输出SOC约束矩阵、功率约束矩阵pumped_storage_model.m输入抽蓄参数和决策变量输出水量平衡约束矩阵、功率约束矩阵build_system_model.m组合所有设备的约束构建完整的优化模型solve_schedule.m调用YALMIP和Gurobi求解返回功率调度结果plot_results.m绘制各类结果图表每个函数都应该有清晰的输入输出注释参数通过结构体传递而不是全局变量这样后续做参数扫描时会省很多事。6.2 处理多时间尺度的技巧如果你的项目需要同时考虑日前计划和实时修正两个时间尺度可以写两层循环外层循环跑日前优化得到计划曲线内层循环在每个时段用最新的超短期预测更新一段较短窗口比如未来4小时的调度计划并把滚动时域的结果作为实际执行值。这种做法统称模型预测控制MPC是实际工程中应对预测不确定性的标准解决方案。在Matlab里实现MPC需要小心的是初始状态的处理——每次滚动时域的初值重设必须与实际测量值保持一致否则前后计划会出现严重偏差。6.3 加密发布与性能优化如果代码是用于GitHub开源或论文复现建议把参数文件和数据文件拆出来单独放并写一个README.md说明算例数据的来源和格式。性能优化上可以在Matlab中用profiler找到耗时瓶颈通常是大规模矩阵拼接时的循环赋值替代方案是向量化操作或sparse函数存储稀疏矩阵。约束矩阵是稀疏的——大部分元素为零用稀疏存储能将内存占用降低一个数量级求解速度也会有明显改善。6.4 与其他代码的对比验证在完成代码调试后至少要对中结果的正确性做一次验证。常见做法是设置一个极为简单的特殊场景比如无风光、只有负荷和电池手工推导出最优解再和代码输出做对比。如果码输出与手工推导一致说明核心模型方程实现正确后续扩展逻辑才有可信基础。我每一版调度代码在发布前都会跑一遍这个“手动锚定测试”强烈建议你也养成这个习惯。7. 项目扩展方向与个人心得这套风光储调度模型按我的经验后续可以从几个方向继续扩展。第一个方向是在建模中考虑随机不确定性用随机规划或鲁棒优化的方法替代确定性预测从期望最优走向“最坏情况有保障”。第二个方向是加入网架拓扑约束把节点功率平衡变成潮流方程分析储能布局对线路阻塞的影响。第三个方向是结合碳交易机制和绿色证书交易在目标函数里加入碳成本和社会效益让调度结果更贴合双碳目标的宏观要求。最后分享一点个人体会跑调度优化这个方向Matlab不是全部核心还是对物理模型的理解。我见过不少同学把intlinprog调得很溜但对电池SOC效率和抽蓄水量平衡的物理意义说不清楚代码里藏了一堆逻辑漏洞也不自知。建议你拿到模型后先手算一个小例子跑通后再上代码这个习惯让我的代码一次成型率远远高于直接上手写程序的阶段。也期待着读完这篇文章的你能在自己的算例里跑出一份漂亮的风光储联合调度曲线——那种看到功率曲线完美匹配负荷需求时的舒畅感是这片领域最让人上瘾的瞬间。