接手考虑特性分布的储能电站并网调度项目之前,我一直觉得储能调度不过是把电池包当成一个巨型充电宝:求个平均SOC,加个总功率,套上约束跑一版计划就完事。直到我把电站的机组级数据拉出来,才发现这个想法错得离谱。同一座电站里,几十个电池簇的可用容量、衰减速率、响应延迟、温度状态可以相差很远。把这些特性差异当成噪声平均掉,调度指令下发之后,实际执行结果跟计划对不上几乎是常态。
这篇内容围绕一个具体方案展开:考虑特性分布的储能电站接入电网后,如何用多时间尺度源储荷协调调度策略把计划做细、做稳,并用Matlab把整套流程从数据接入、建模、求解到结果分析跑通。适合正在做储能并网调度算法复现、毕业论文仿真、或想从单时间尺度过渡到多时间尺度协调调度的工程师参考。我会把建模思路、关键代码、求解器选型和踩坑记录都摊开讲,尽量让不同基础的读者都能直接抄作业。
1. 储能特性分布:等效聚合模型的隐患与建模思路
1.1 同一座储能电站里,电池簇之间的差异到底有多大
实际项目中,一座储能电站往往由几十个甚至上百个电池簇构成,每个簇配独立PCS变流器,经升压变汇集后并网。电站上报给调度中心的通常是聚合值:总功率上限、总容量、当前SOC。但把机组级数据拉出来看,差异大得惊人。
首先是SOC差异。由于充放电历史不同、簇间温度分布不同、自放电率不同,同一时刻各簇SOC可能相差15%到20%。我碰过一个真实的例子:三分之一的簇SOC在85%以上,另外三分之一在30%以下,聚合SOC恰好卡在60%。看起来一切正常,结果调度下达充电指令后,高位簇直接触发过充保护退出运行,低位簇又面临过放风险,电站实际可充功率远远低于上报值。
其次是可用容量差异。电池衰减是不均匀的,靠近风口的簇温度低衰减慢,箱体内部的簇温度高衰减快。投运三年后,簇与簇之间的SOH(健康度)可以拉开10个百分点以上。这意味着名义上100MWh的电站,实际可用容量可能只有85MWh左右,而且这个数字还在随时间变化。
还有响应速度差异。不同批次PCS的控制响应时间、调节死区都不一样。有的簇5秒内能跟到指令,有的要十几秒,还经常出现超调。在实时调节场景里,这种差异直接影响频率支撑质量。
把这些差异当作噪声平均掉,相当于让一支队伍里跑得快和跑得慢的人统一按同一个配速指令跑步,结果必然是有人冲过头、有人掉队。
1.2 聚合模型带来的连锁问题
聚合模型的本质是用一组平均参数替代整站特性。在全局电量层面它勉强够用,一旦涉及功率分配、SOC管理和频率调节质量,问题就接踵而至。
一是计划执行偏差。日前计划给储能电站下达"03:00到06:00充电10MW",但部分簇SOC已经偏高,实际可充电量不足,电站只能提前降功率,调峰预期直接落空。
二是SOC越限与保护动作。过充过放保护一旦触发就整簇退出,而调度侧毫不知情,下一轮计划又基于错误的储能状态继续优化,误差越滚越大。
三是备用能力虚标。聚合模型算出来的上备用、下备用能力,实际可能根本达不到。备用虚标的后果在事故工况下会被放大——调度以为储能还有10MW的快速爬坡能力,实际只能出4MW,频率支撑就成了空头支票。
我最早做储能参与调峰的仿真时,聚合模型算出的日运行成本非常漂亮,结果对照现场数据,好几个时段出力曲线跟计划差了10%以上。这不是数值精度问题,是从建模层面就错了。
1.3 特性分布建模的三种思路对比
把"分布"纳入模型,工程上有三条路可走,各有取舍。
概率分布描述:把SOC、可用容量、响应速度看成随机变量,用正态分布或Beta分布描述整体特征,优化时用期望值或分位点做约束。优点是信息压缩率高,缺点是对极端工况不够敏感——某簇刚好顶到边界时,模型完全没有感知。
分群聚类:用K-means或者按SOH分桶,把特性相近的簇聚成3到5个群,每个群用一台等效机组参与优化。群内差异被平均化,群间差异被保留,精度和求解复杂度的平衡是三条路里最好的。这也是我在项目里实际采用的方式。
逐台建模:每簇单独建模,精度最高,但整数变量和约束数量爆炸。日前调度本来就是大规模的混合整数线性规划,逐台建模会让求解时间彻底失控。
我的建议很直接:做调度策略验证用分群聚类,起步3群就行——高SOC群、中SOC群、低SOC群。跑完敏感性分析再看要不要增加到5群。只有做实时功率分配控制层时,才需要考虑逐台建模。
2. 多时间尺度协调调度的骨架:日前、日内、实时各干各的活
2.1 三个时间尺度的分工逻辑
储能并网的电网调度,单靠一个时间尺度解决不了问题。原因很朴素:预测不可能完美,电网却要求每时每刻功率平衡。这就逼出了多时间尺度框架。
日前调度:时间窗24小时,决策粒度1小时,负责定大局。机组启停、火电基荷出力、储能日充放电计划、新能源削减量,都在这一层定下来。日前层面对的是负荷和新能源的日前预测,精度有限,但胜在提前量大,能安排火电这种响应慢的资源。
日内滚动:时间窗4到8小时,决策粒度15分钟到1小时,负责修正。随着时间推进,新能源和负荷的最新预测不断刷新,日内层利用储能和可调负荷的快速调整能力,把日前计划的偏差一点一点修正回来。
实时控制:时间窗5到15分钟,负责兜底。储能在这层做分钟级功率跟踪和频率支撑,平抑短时波动,尽量贴合日内计划值。
| 时间尺度 | 时间窗 | 决策粒度 | 主要决策内容 |
|---|---|---|---|
| 日前调度 | 24小时 | 1小时 | 机组启停、火电出力、储能充放电计划、新能源削减 |
| 日内滚动 | 4-8小时 | 15分钟-1小时 | 修正日前计划,跟踪最新预测 |
| 实时控制 | 5-15分钟 | 分钟级 | 储能快速响应、频率支撑、功率跟踪 |
我常用一个类比:日前是看地图做路书,日内是根据实时路况调整路线,实时是握方向盘应付坑洼。三层各自要解决的问题不同,决策频率完全不同,不能混为一谈。
2.2 时间尺度之间的衔接设计
三层不是孤立的,有严格的主从关系。衔接不好,整个体系会互相打架。
日前到日内的衔接,我采用"软约束加惩罚项"。把日前计划中的储能SOC轨迹作为日内优化的参考值,在目标函数里加一个偏离惩罚项,日内计划尽量贴近参考,但允许因预测变化适度偏离。这个设计非常关键。最初我用硬约束,遇到一次风电预测大幅上调,日内优化需要多充电,硬约束却死死顶着日前SOC轨迹不让动,整个模型直接不可行。改成软约束后,系统至少永远存在可行解。
日内到实时的衔接更简单:日内计划给出储能功率设定值,实时层在此基础上做增量调节,只处理偏差,不重新规划大局。
这个思路本质上是滚动时域控制(模型预测控制)在多时间尺度上的应用。每个采样时刻,用最新状态作为初值,重新优化未来一段时间窗,执行第一个时段的决策,再滚动到下一个时刻。我在项目里按这个逻辑实现后,计划的适应性和稳定性都有了明显提升。
2.3 源储荷三侧的角色定位
源储荷协调不是把三方放在一起做联合优化就完事。三方在时间尺度和响应特性上的天然差异,决定了各自该承担什么角色。
电源侧:火电响应慢但有持续出力能力,承担基荷和中长期调节,约束上主要体现在爬坡率限制和最小启停时间。新能源出力不可控,把它当作"负的负荷",通过削减量参与平衡。
储能侧:储能是唯一既能快速响应、又能双向调节、还能跨时段转移电量的资源。但它能量有限、容量成本高,所以设计上让储能平时少动,关键时刻顶上去,充当"尖峰时段的多面手"。
负荷侧:可调负荷响应慢、用户意愿不确定,但规模化之后削峰填谷潜力大。温控负荷、可中断工业负荷适合在日前和日内尺度参与,实时尺度一般不指望它。
实际建模时,我把三方的时间常数写进了约束:火电爬坡约束按小时尺度,储能SOC递推按15分钟尺度,可调负荷用响应时间约束。这样一来,每个时间尺度的决策自然会优先调用可调度性更强的资源。
3. 数学模型:目标函数和约束体系怎么搭
3.1 目标函数:经济性、新能源消纳和储能寿命的三角权衡
日前-日内统一的目标函数可以写成:运行总成本等于火电燃料成本加火电启停成本加储能运行老化成本加弃风弃光惩罚再加负荷侧调节成本。
% 目标函数系数向量,按决策变量顺序排列: % [火电出力P_g; 启停动作u_g; 储能充电P_ch; 储能放电P_dis; 新能源削减P_cur; 可调负荷P_flex] f = [C_fuel; C_startup; C_ch; C_dis; C_curtail; C_flex];储能运行成本需要特别处理。简单做法是给充放电功率乘一个单位成本系数,用来反映等效循环老化。讲究点的做法是先算日累计放电量,折算成等效循环次数,再乘单次循环成本。后者更贴近实际电池寿命模型,但会引入非线性项,需要借助分段线性化处理。
弃风弃光惩罚系数是个"手感"问题。设得太大,系统为了消纳新能源会付出过高的储能和火电成本;设得太小,新能源削减基本不受约束。我的做法是先跑两遍:惩罚系数设为0,看新能源削减率上界;设成极大值,看削减率下界。然后在中间取一个让消纳率达到95%附近的最小惩罚值作为基准。这个系数没有标准答案,做一次敏感性分析是最靠谱的定参方式。
3.2 约束体系的四个层次
约束全部堆在一个大矩阵里,既难读又难调试。我习惯按层级组织。
系统层:各时段功率平衡等式,系统上、下备用容量不等式。备用来源要定义清楚——我的设定是火电承担慢备用,储能承担快备用,新能源不承担备用。
机组层:火电出力上下限、爬坡约束、最小启停时间。启停状态用0-1变量表示,启停成本用"开启动作"变量刻画,避免把维持运行费当成启停费重复计。
储能层:这是特性分布模型的核心。每个特性群独立建立SOC递推方程、充放电功率上下限、充放电状态互斥、SOC上下限,以及日始末SOC一致性约束。
负荷侧:可调负荷调节量上下限、响应时间约束、日累计调节量约束,防止用户侧过度疲劳调节。
储能层SOC递推的YALMIP写法如下:
for t = 2:T for k = 1:K % K个特性群 Constraints = [Constraints, ... soc(k,t) == soc(k,t-1) + eta_ch(k)*P_ch(k,t)*dt/Cap(k) ... - P_dis(k,t)*dt/(eta_dis(k)*Cap(k))]; end end注意dt的单位:日前层是1小时,日内层如果粒度是15分钟,dt写成0.25。这个细节如果搞错,SOC演化会以莫名其妙的倍率漂移,排查起来相当痛苦。
3.3 MILP建模的几个实用技巧
第一,充放电互斥的Big-M写法。P_ch和P_dis不能同时为正,引入0-1变量z,配合大M约束:P_ch ≤ M乘z,P_dis ≤ M乘(1-z)。M不能取得过大,否则求解器数值稳定性变差。我按最大功率的1.2倍取M,实测稳定性和收敛速度都很好。
第二,互斥变量要不要加。理论上,如果目标函数里充放电都有成本,求解器不会给出同时充放电的解。但工程上我仍然加互斥变量。原因很简单:储能同时充放电意味着能量在电池内部循环,不赚钱还白白损耗寿命,这种"数学最优、工程荒谬"的解一旦出现,汇报时很难解释。加一个0-1变量只增加很小的求解负担,换来结果的可解释性,完全值得。
第三,非线性项的分段线性化。机组煤耗曲线、储能老化成本都是非线性项,把它们写成分段线性函数,模型保持MILP结构,可以继续用分支定界类求解器。分段点取3到5个足够,太多会让约束矩阵线性膨胀。
注意:YALMIP变量定义的维度要和时段数、群数严格对应。维度不匹配是最常见的报错来源,错误信息往往只提示维度不一致,看不出到底哪里对不上,只能逐行查。
4. Matlab实现细节:从数据表到可运行的调度程序
4.1 数据组织方式
Matlab做调度优化,数据组织是最容易乱的地方。我习惯所有输入数据放CSV或Excel,主程序开头一次性读入,转成struct统一管理。
% 读入负荷、风电、光伏预测数据(行=时段,列=场景) load_forecast = readmatrix('data/load_forecast.csv'); wind_forecast = readmatrix('data/wind_forecast.csv'); pv_forecast = readmatrix('data/pv_forecast.csv');储能参数库用表格存储每簇参数:容量、初始SOC、充放电效率、最大功率、SOH。然后在脚本里做分群聚类,把群标签写回表格。这样调参只需要改CSV,不用动模型代码。项目做敏感性分析时,这种数据与模型分离的结构价值会被完全放大——改一个参数重新跑一遍,整个流程不用重写任何逻辑。
4.2 求解器怎么选
纯教学演示或论文复现,Matlab内置的intlinprog就够用。不需要额外许可证,命令简单,几十个整数变量的模型能跑出不错的结果。
options = optimoptions('intlinprog', ... 'Display', 'iter', ... 'MaxTime', 3600, ... 'IntegerTolerance', 1e-4); [x_opt, fval, exitflag] = intlinprog(f, intcon, A, b, Aeq, beq, lb, ub, options);模型规模大、追求速度时,上YALMIP加商用求解器的组合。YALMIP负责优雅建模,求解器负责快速计算。我在项目里实测,百MWh级储能的24节点电网日前模型,商用求解器通常几十秒出结果,intlinprog可能要几分钟到十几分钟。如果时窗拉长到48小时甚至一周,内置求解器基本吃不消,这时专业求解器的优势非常明显。
4.3 主程序结构
整个流程我拆成五个模块:
- run_dispatch.m:主程序,设置flag,控制跑日前、日内还是全文;
- load_data.m:读数据,做分群聚类;
- build_dayahead_model.m:构建日前MILP模型;
- build_intraday_model.m:构建日内滚动模型,读入日前SOC轨迹作为软约束;
- plot_result.m:结果可视化。
这种模块化结构最大的好处是,调试时可以单独跑某一块,不用每次从头到尾重算。问题定位也方便:报错在哪一层,直接进对应脚本看。项目里做参数敏感性分析时,全文跑一遍大约三分钟,每次只改一个参数就要全文重跑——模块化至少保证改参数不会改坏别的地方。
4.4 求解不可行和求解慢的处理经验
模型报"infeasible"时,别急着改约束。按这个顺序排查。
先检查功率平衡等式有没有多写或少写项。再检查储能SOC递推方向——我犯过把充电效率乘到放电项的错误,SOC演化方向完全反向,约束当然无解。然后用"逐步注释法"定位:先只保留系统层和机组层,能解了就加储能层,再加负荷层,哪一步开始不可行,问题就在哪一步。最后检查Big-M是否过小,导致合法解被错误剪掉。
求解慢有三个通用对策:放宽MIP gap,工程上1%以内的次优解完全可接受,默认的1e-4可以放宽到1e-2;把24小时时窗切成两个12小时子问题滚动求解;给整数变量设置初值,用上一轮解的关键变量做warm start,能显著缩短求解时间。
提示:MIP gap从默认的1e-4放宽到1e-2,对最终结果的影响通常小于1%,求解时间却可能缩短一个数量级。工程场景下这笔交易非常划算。
5. 算例构造与结果对比:模型值不值得复杂化,用数据说话
5.1 测试系统怎么搭
我改造了一个24节点标准测试系统作为仿真平台,接入两台储能电站、三台火电机组、一个风电场和一个光伏电站,负荷数据取夏季典型日。储能电站A包含12个电池簇,簇间SOH从82%到96%不等,初始SOC从20%到90%不等,特性差异拉得很开;储能电站B特性相对均匀,作为对照组。
关键的两组算例,一组用分群建模(3群),另一组把所有簇聚合成一个等效储能,其余条件完全一致。这样才能把"特性分布建模"和"聚合建模"的差异干净地分离出来,不被其他因素干扰。
5.2 对比实验设计
我跑了四组方案,分别回答不同的问题。
| 方案 | 储能建模方式 | 时间尺度 | 考察内容 |
|---|---|---|---|
| A | 分群建模(3群) | 日前+日内+实时 | 完整方案的总体性能 |
| B | 聚合建模 | 日前+日内+实时 | 特性分布建模的价值 |
| C | 分群建模 | 仅日前 | 多时间尺度的价值 |
| D | 聚合建模 | 仅日前 | 基准下限 |
评价指标选了四个:系统日运行成本、新能源消纳率、储能SOC越限次数、日前计划与日内实际执行的平均偏差。这四类指标分别对应经济性、环保性、安全性和可执行性,缺一不可。
5.3 结果说明了什么
四组算例跑下来,结论非常清晰。
A比B的日运行成本低约3%。幅度看着不大,但折算到全年、放到整个系统层面,燃料费用节省相当可观。更关键的是SOC越限次数:A为0次,B出现了3次。这意味着聚合建模的方案在真实运行中大概率会触发电池保护动作,储能实际能提供的调节能力大打折扣。
A比C的新能源消纳率高约4个百分点。只做分群建模、没有日内滚动时,遇到日内预测修正,储能已经没有余量去消纳新增的风电。加上日内滚动后,储能能够跟随最新预测调整功率,把多出来的风电吃掉。这正好印证了多时间尺度协调的核心价值:灵活性是要靠时间尺度之间的信息传递换来的。
日前计划与日内实际执行的平均偏差,A只有B的三分之一左右。这个指标在工程上比运行成本更重要——偏差小,意味着调度中心对系统状态的预判准确,实际运行风险大幅降低。
当然,以上数字基于我构造的算例,不同系统结构和参数下具体数值会变化,但趋势是稳定的:特性分布建模的价值主要在减少执行偏差和避免越限,多时间尺度的价值主要在提升新能源消纳和整体灵活性。
6. 实际项目落地中的几个坑,以及我的处理方式
6.1 储能寿命成本系数别乱设
最初我把储能老化成本系数设得偏高,结果日前优化几乎不用储能,所有调节压力全部压给火电,新能源消纳率反而下降。后来改成结合电池循环测试数据来定系数:查到电池在给定充放电深度下的循环寿命,算出每次满充满放对应的折算成本,再按实际放电深度折算到单位电量成本。如果手头没有厂家数据,宁可设得保守偏低,让储能在算例中多出力,再人工检查循环次数是否在可接受范围内。模型的目的本来就是暴露问题,不是把储能藏着不用。
6.2 多时间尺度的衔接最容易翻车
第一次做日前加日内两级优化时,我把日前计划的SOC轨迹设成了日内优化的硬等式约束。结果遇到新能源预测大幅上调,日内优化需要多充电,硬约束死死顶着日前SOC轨迹不让动,整个模型直接不可行。改成软约束加惩罚项后才彻底解决——日前SOC轨迹变成参考值而不是死命令,日内优化既保持整体方向一致,又有修正空间。惩罚权重我取的是储能运行成本系数的2倍左右,经过一步敏感性测试再微调。
另一个衔接问题是时间边界。日内滚动每次都要用当前时刻的实测SOC作为初始条件,而实测值和日前计划值几乎必然不一致。这时必须以实测值为准,否则第一个时段的功率分配就会出错。这个逻辑要写进代码注释里,防止后来维护的人改错。
6.3 实时层可以简化,但要简化得聪明
严格来说,实时控制层应该考虑频率动态、一次调频、二次调频等机制。这个项目里我做的是分钟级功率跟踪:储能按日内计划的设定值,用PI控制去跟踪,建模到这一层就收住。工程验证场景下,这个简化可以接受。
真要上现场,实时层还需要补三块:PCS功率限幅逻辑、低SOC时的最小功率保持策略、电池保护动作后的状态重估。这些并不完全靠调度算法解决,需要跟储能EMS联调。做仿真的人容易忽略这类"油泥"细节,但现场出问题往往就出在这里。
6.4 Matlab代码层面的最后几条建议
SOC递推里的dt单位必须统一为小时,日前是1,日内15分钟就是0.25。我吃过一次亏,SOC演化以奇怪倍率漂移,排查半天才发现是单位没统一。
结果可视化别只画功率曲线,把SOC分群曲线也画出来。一图就能看出每个群的电池有没有被公平使用。如果某群SOC长期顶在边界,说明这个群的参与度设计有问题,要么功率上下限设错,要么寿命成本系数不合理。
每次改参数,记录求解时间和目标函数值,形成参数敏感度表。这张表在项目结题汇报时非常有用,也是判断当前参数是否合理的硬证据。
最后再分享一个我在调试阶段的小技巧。每次跑完日前模型,我都会把储能各群的SOC轨迹画在一张图上,盯着看几秒钟。如果某条曲线在边界上来回流连,说明这个群的约束边界设置有问题;如果所有曲线都在中间区域温吞水一样挪动,说明储能价格参数给得太高,决策变量根本不敢动。这两类问题光看目标函数值根本发现不了,一画图全暴露了。这套代码我从数据接入、分群聚类到日前日内模型一路调下来,最大的感受是:调度算法的价值不取决于模型有多花哨,而取决于每个细节是否经得起推敲——尤其是特性分布建模后的分群参数,和多时间尺度衔接处的软约束设置。这两处做扎实,整套调度策略基本就立住了。