调度室的大屏幕上,日前安排的机组组合曲线还安安静静地躺在那里,可到了中午,光伏出力一路往上冲,午后一场云又让出力瞬间掉了三四成。火电还在慢悠悠地爬坡,储能电站的充电功率却被卡在计划值上——不是指令下不去,而是站内好几簇电池的SOC已经顶到上限,再充就会越限告警。这就是我最近在调的多时间尺度源储荷协调调度策略里最典型的一个场景。
这个题目看起来很长,拆开其实就是三件事:储能电站不是一台电池,而是成百上千簇电池组成的一个对象,每簇之间SOC、容量、效率都有差异,这就是标题里说的“特性分布”;要让发电侧、储能、负荷侧配合起来拧成一股绳,这就是“源储荷协调”;而这种协调不能只在日前做一个静态计划,得放到日前、日内、实时多个时间尺度上去滚动修正,这才能应对预测误差和负荷波动。我这次用Matlab把整条策略从建模到代码实现完整走了一遍,这篇就把整个思路、模型、代码结构和踩过的坑一起分享出来。适合正在做储能调度、微网优化、电力系统经济调度的同学参考,尤其是想用Matlab把论文里的策略落地的朋友。
1. 为什么储能接入后,单时间尺度调度不再够用
1.1 传统调度最大的麻烦是“断层”
传统的电力调度习惯上是分段做的:日前做机组组合和发电计划,实时做AGC自动发电控制,二者之间缺了中间环节。小时级的日前计划面对分钟级甚至秒级的波动非常被动:比如上午10点前机组启停已经固定,火电爬坡能力就摆在那,风电和光伏出力又说不准,负荷每天也都不一样。真正的调度员面对的是一个连续变化的动态过程,而不是一张静态的计划单。
这三个断层在工程上特别致命:
- 时间尺度断层:日前计划是小时级或15分钟级的,实时AGC是秒级的,二者之间没有滚动修正的机制。
- 对象断层:传统调度只管机组发电,不管储能电站怎么分配内部功率,也不管可调负荷到底能调多少。
- 信息断层:预测误差出现之后,没有一套回流到计划层的机制,只能靠AGC在末端硬扛。
单时间尺度模型要么“只看当下”,要么“只按日前来”,没有中间修正空间。储能一旦接入,这个断层问题会被进一步放大。
1.2 储能引入之后,问题变成了“时间上的搬运”
储能和发电机有个本质区别:发电机是即时设备,燃料往里倒、电往外发,能量是连续的;电池则是能量在时间轴上的搬运工——中午光伏多发就把电存起来,晚上再放出去。这个搬运属性带来两个传统模型里没有的新麻烦。
第一个麻烦是SOC是一个跨时段的记忆状态。今天凌晨做调度决策的时候,必须知道晚上预期要放多少电,否则可能白天把电充过头,晚上该放的时候已经没电可放了。第二个麻烦是充放电效率、自放电率、寿命损耗并不是固定常数,而是随使用状态变化的。储能电站的等效运行成本不像煤耗曲线那么好估计,深度充放一次和浅充浅放一次对寿命的影响完全不同。
如果只在单一时间尺度里建模,短期层看不见明天的净负荷曲线,长期层又追踪不到当前的实测SOC和预测偏差,调度结果自然会出现“计划出力很大、实际根本执行不下去”的尴尬局面。
1.3 为什么必须把源、储、荷放在一起协调
源、储、荷三个对象的时间常数差别非常大。火电机组爬坡以分钟计,储能响应是毫秒到秒级,可调负荷和需求响应通常又按小时安排。如果三者各自为政,会出现一个典型的错配:新能源波动来了,火电跟不上,储能却因为日前计划没预留足够SOC区间而无法补上,可调负荷又没有被激活——整个系统只能靠弃风弃光来维持平衡。
多时间尺度协调的实质,就是让快变量和慢变量各司其职。日前尺度里,火电和储能协同安排基础运行点;日内尺度里,储能根据超短期预测快速修正偏差;实时尺度里,储能再做最后的兜底调节。源、储、荷各自的灵活性在不同时间尺度上被分层利用,这样才能把整个系统的调节能力榨干用尽。
2. “特性分布”到底在说什么?储能不是一个电池组,而是一群电池组
2.1 站内电池簇的天然不一致
工程上,一个几十兆瓦时的储能电站并不是一台大电池,而是由几十甚至上百个电池簇并联组成的。因为出厂批次差异、运行温度场不同、老化速度不同、内阻增长不同,同一时刻站内各簇的SOC可能相差5%到15%,SOH(健康状态)的差异会导致实际可用容量明显低于铭牌容量。
如果不考虑这些差异,调度模型把整个电站当成一台理想电池来处理,优化结果下发到现场之后,BMS和PCS会以“该簇SOC越限”“该簇功率超限”等理由拒绝执行指令。我在调模型时遇到过一模一样的情况:日前优化给储能电站下发了80MW充电指令,结果站内有一半的电池簇SOC已经到90%,BMS直接限制了充电功率,实际充电功率只有40MW出头。这就是“特性分布”这四个字的分量所在。
2.2 三类核心特性分布
要建模,先要厘清到底哪些特性需要纳入考虑。我按工程常用分类整理了下面这张表:
| 特性类型 | 具体内容 | 对调度的影响 | 建模处理建议 |
|---|---|---|---|
| SOC分布 | 各簇当前荷电状态参差不齐 | 决定哪些簇可充、哪些簇可放、可用调节方向 | 聚类分群,或加入SOC均衡约束 |
| SOH/容量分布 | 各簇容量衰减程度不同 | 可用容量小于铭牌值,功率能力受限 | 用SOH折算后的有效容量建模 |
| 效率/响应分布 | 充放电效率、响应速度不同 | 影响成本计算和实时分配比例 | 分群后按等效效率聚合 |
2.3 把“分布”变成模型:聚合分群加站内二次分配
考虑特性分布,不一定要把每一簇电池都塞进优化模型,否则决策变量会爆炸,仿真也跑不动。工程上我推荐两阶段方案。
阶段一:把电池簇按SOC、SOH等特征做聚类,得到若干个代表性储能群。常见做法是K-means,聚类目标可以写为:
min Σ_i Σ_k |SOC_i - C_k|² + λ·|SOH_i - H_k|²
其中C_k和H_k是第k类簇的SOC与SOH聚类中心,λ是权重系数。聚类之后,优化模型里只保留K个“等效储能单元”,每个单元维护自己的SOC递推和功率约束。
阶段二:优化结果给出各储能群的总功率P_b,k,站内再按各簇的可用功率和SOC均衡目标做二次分配。例如:
P_i = P_b,k · (SOC_i - SOC_min_i) / Σ(SOC_j - SOC_min_j)
这样既避免了变量爆炸,又保证了站内各簇不至于出现“一个簇充到顶、另一个簇还空着”的极端情况。
2.4 不考虑特性分布,后果是什么
用一组常见现象做个类比:整站聚合模型相当于把所有电池假设成同一水位的水池,但实际情况是这个水池被隔成了几十个格子,格子之间的水位差很大,抽水机能真正用到的有效容量远小于总容积。在类似规模的算例里,SOC分布方差较大的储能电站,如果按照整站聚合模型做调度,实际可用的调节能力往往只有计划值的70%到80%。具体数值和电池簇数量、温度差异、老化程度都有关,但趋势是确定的:忽略分布特性,调度计划的可执行性会显著下降。
3. 三层时间尺度框架:日前优化、日内滚动、实时调整如何分工
3.1 日前层:把“明天怎么安排”算清楚
日前层是整个策略的骨架,以净负荷预测曲线为基础,将24小时划分为96个时段(每15分钟一个时段),优化目标是系统综合运行成本最小。决策内容包括:火电机组的启停状态和出力曲线、储能电站各群在96个时段的充放电计划、可调负荷的用电计划调整量。
这里最容易犯的一个错误是:把日前计划当成唯一的依据,认为提前算好了就能照着执行。实际运行中预测误差不可避免,尤其是新能源出力预测,误差达到20%以上是常态。所以日前层作用不是“定死未来”,而是给后两层提供一个合理的运行基准和约束边界,尤其是储能SOC的走廊。
3.2 日内层:用最新预测滚动修正
日内层采用滚动时域控制,每5到15分钟调用一次。每次滚动优化都读取最新的超短期预测数据,把日前计划的热点转移过来作为软约束,重新求解未来4到6小时内的调度方案。
日内层输出的核心是两部分:一是火电出力的修正量,二是储能各群的SOC修正轨迹。比如日前计划里18:00储能SOC应该到90%,但下午实际光伏多发了,15:00储能已经充到了95%,日内层就会及时调整后续几个时段的出力安排,防止SOC越限。
3.3 实时层:秒级调节,储能兜底
实时层对应传统AGC的概念,工作周期在秒级到分钟级。当系统频率偏差或联络线功率偏差出现时,实时层需要快速决定谁来顶住这个差额。储能因为响应速度快,通常在实时层扮演兜底角色。
但实时层的分配不是简单均摊,而是要把特性分布考虑进去:SOC高的簇多放、SOC低的簇少放;SOH好的簇承担更多调节任务,SOH差的簇少动。这样既满足调节需求,又能减少对老化严重电池的损耗。
3.4 层与层之间的数据流
三层之间不是孤立的,数据流才是整个策略的灵魂。我常用下面这张表来梳理信息传递关系:
| 尺度层 | 决策周期 | 主要输入 | 主要输出 | 传递到下一层的信息 |
|---|---|---|---|---|
| 日前层 | 24小时,96时段 | 日前负荷预测、新能源预测、机组参数 | 机组启停、出力计划、储能SOC计划 | 机组运行状态、SOC走廊 |
| 日内层 | 每5~15分钟滚动4~6小时 | 超短期预测、实测SOC、日前计划 | 出力修正量、储能SOC修正轨迹 | 修正后的出力基点、SOC目标 |
| 实时层 | 秒级 | 频率偏差、实测充放电状态 | 储能功率分配指令 | 实际执行的SOC变化反馈给日内层 |
一句话概括:日前定骨架,日内修偏差,实时兜底线。
4. 目标函数与约束的数学模型拆解
4.1 目标函数设计:不只有经济成本
多时间尺度协调调度的目标函数通常不是单一的成本最小化,而是多个目标的权衡。我采用的是典型加权形式:
min F = Σ_t ( C_fuel(t) + C_bat(t) + C_curtail(t) + C_loadshift(t) )
其中:
- C_fuel(t):火电燃料成本,常按二次函数拟合,C_fuel = Σ_i (a_i·P_i² + b_i·P_i + c_i)
- C_bat(t):储能使用成本,包含充放电循环带来的寿命损耗折算,可以用等效折旧系数乘以功率绝对值来表示
- C_curtail(t):弃风弃光惩罚项,通常设一个比较高的单位惩罚系数,避免优化结果为省燃料而故意弃新能源
- C_loadshift(t):可调负荷调整带来的用户补偿成本
权重系数的设置是一门实践学问。我的经验是先把弃风弃光惩罚设为燃料成本的5到10倍,再根据仿真结果微调,否则优化器会倾向于牺牲新能源来换取燃料成本最小。
4.2 功率平衡与网络约束
每个时段t都必须满足系统功率平衡:
Σ_i P_gen,i(t) + P_wind(t) + P_pv(t) + Σ_k P_bat,k_dis(t) = P_load(t) + Σ_k P_bat,k_ch(t) + P_shift(t)
如果考虑网络安全,可以加入直流潮流约束,线路潮流限制为:
-P_l,max ≤ Σ_m GSF_l,m · P_inj,m(t) ≤ P_l,max
其中GSF是发电转移系数,P_inj是节点注入功率。算例规模不大时,忽略网络约束只做功率平衡也能看出策略框架的行为特征,但要发论文或者做实际规划,建议把网络约束加上。
4.3 储能运行约束与SOC递推方程
储能群k的约束包括功率上下限和SOC递推:
- 充放电功率上下限:0 ≤ P_ch,k(t) ≤ P_ch,k,max,0 ≤ P_dis,k(t) ≤ P_dis,k,max
- SOC递推:SOC_k(t+1) = SOC_k(t) + η_c·P_ch,k(t)·Δt/E_k - P_dis,k(t)·Δt/(η_d·E_k)
- SOC边界:SOC_k,min ≤ SOC_k(t) ≤ SOC_k,max,一般取0.1到0.9
- 充放电互斥:充放电不同时进行的约束
代表性差异通过不同的SOC初值、E_k(SOH折算后的有效容量)和效率η来体现。如果采用了K-means分群,每个群都有一套上述约束,整站约束就是K组约束的联合。
4.4 机组爬坡约束、负荷约束与多时间尺度耦合
火电机组的爬坡约束在相邻时段之间耦合:
-R_down,i·Δt ≤ P_i(t+1) - P_i(t) ≤ R_up,i·Δt
可调负荷的总调整量通常有一个比例限制,比如全系统可调负荷占比不超过总负荷的5%到10%。
时间尺度的耦合重点在SOC。日前层算出的SOC轨迹要作为日内层的参考走廊,日内层算出的SOC目标还要回传给实时层做功率分配,这种跨层传递是“多时间尺度”的模型本质。实现上,我会在日前层结束后把每个时段SOC的最优值存成一个向量,在日内层里以软约束形式加入目标函数,允许偏离但偏离要付出代价。
5. Matlab实现路线:从参数初始化到滚动时域控制
5.1 程序结构与文件划分
我习惯把代码拆成下面几个部分,方便调试和复用:
main.m % 主循环,负责调用各层模块 data_load.m % 读入负荷、新能源、价格、储能参数 init_storage.m % 初始化电池簇SOC/SOH并做聚类分群 day_ahead_scheduling.m % 日前优化求解 intraday_rolling.m % 日内滚动优化求解 real_time_dispatch.m % 实时层功率分配 compute_indicators.m % 计算弃风弃光率、SOC越限次数等指标 plot_results.m % 画曲线模块化最大的好处是可以单独改某个层面的约束而不影响其他层。比如想换一套实时分配策略,只需要改real_time_dispatch.m,其他层都不用动。
5.2 参数初始化与预测数据准备
参数初始化的质量直接决定优化结果的合理性。以我常用的测试系统为例:
| 参数 | 取值 |
|---|---|
| 调度周期 | 24小时,15分钟间隔,T=96 |
| 火电机组数量 | 3台(容量分别为200/300/100MW) |
| 新能源装机 | 风电150MW、光伏100MW |
| 储能电站 | 总容量100MWh,30个电池簇,聚类分4群 |
| 储能功率限制 | 每群最大充/放功率对应额定功率的0.25 |
| 负荷可调比例 | 总负荷的5% |
预测数据我会生成两组:一组是“完美预测”用于验证策略上限,另一组是加入随机误差的“实际预测”用于检验滚动修正的效果。这一步容易被忽略,但没有误差场景,多时间尺度的价值根本体现不出来。
5.3 日前优化核心代码逻辑
日前层用YALMIP建模的话,核心代码如下所示。这里我把风电光伏出力和负荷都作为已知参数传入:
%% 日前优化:YALMIP建模 T = 96; P_gen = sdpvar(3, T); % 三台火电机组出力 P_ch = sdpvar(4, T); % 4个储能群充电功率 P_dis = sdpvar(4, T); % 4个储能群放电功率 SOC = sdpvar(4, T); % 4个储能群SOC Constraints = []; % 功率平衡约束 for t = 1:T Constraints = [Constraints, ... sum(P_gen(:,t)) + P_wind(t) + P_pv(t) + sum(P_dis(:,t)) ... == P_load(t) + sum(P_ch(:,t))]; end % 储能SOC递推约束 for k = 1:4 for t = 1:T-1 Constraints = [Constraints, ... SOC(k,t+1) == SOC(k,t) + eta_c(k)*P_ch(k,t)*dt/E_k(k) ... - P_dis(k,t)*dt/(eta_d(k)*E_k(k))]; end end % SOC边界和功率边界 Constraints = [Constraints, 0.1 <= SOC(:) <= 0.9]; Constraints = [Constraints, 0 <= P_ch(:) <= P_ch_max(:)]; Constraints = [Constraints, 0 <= P_dis(:) <= P_dis_max(:)]; % 目标函数:燃料成本 + 储能使用成本 + 弃风弃光惩罚 Objective = 0; for t = 1:T for i = 1:3 Objective = Objective + a(i)*P_gen(i,t)^2 + b(i)*P_gen(i,t) + c(i); end Objective = Objective + sum(bat_cost(:).*(P_ch(:,t)+P_dis(:,t))); Objective = Objective + curtail_penalty*(P_wind(t)+P_pv(t) - ... min(P_wind(t)+P_pv(t), P_load(t)+sum(P_ch(:,t)))); end ops = sdpsettings('solver', 'gurobi', 'verbose', 1); optimize(Constraints, Objective, ops);注意这里每簇电池的容量E_k是用SOH折算后的有效容量,SOC的初始值是从init_storage.m聚类结果里取出来的各群加权平均SOC。
5.4 日内滚动与实时控制的循环结构
日内滚动层的主循环结构如下:
%% 日内滚动:每15分钟重调度一次 T_roll = 24; % 滚动窗口4小时,15分钟一个点,共16个点 for step = 1:96 % 更新预测:读取该时刻之后的预测序列 pred_wind = forecast_wind(step:step+T_roll); pred_pv = forecast_pv(step:step+T_roll); pred_load = forecast_load(step:step+T_roll); % 读取实时SOC反馈 soc_now = get_measured_soc(storage_plant); % 求解滚动优化,日内层目标加入SOC偏离惩罚 [P_gen_ref, P_ch_ref, P_dis_ref] = ... intraday_rolling(soc_now, pred_wind, pred_pv, pred_load, day_ahead_soc); % 把第一个时段的指令下发到实时层 real_time_dispatch(P_ch_ref(:,1), P_dis_ref(:,1), cluster_info); end实时层拿到总功率指令后,按第2章提到的比例分配方法下发给各个电池簇,同时检查每一簇的SOC边界。如果某一簇越限,就将其功率置零,把差额重新分配给其他簇。
5.5 求解器选型与YALMIP配置注意事项
在Matlab环境里做这类优化,求解器选择直接影响成败。我的经验是:
- 纯线性模型(目标函数没有二次项),可以直接用linprog,速度快、不依赖第三方求解器。
- 目标函数包含火电成本二次项时,推荐用YALMIP加Gurobi,或者直接用MATLAB自带的quadprog。
- 充放电互斥需要引入二值变量时,模型变成混合整数规划,建议用Gurobi或COPT这类商用求解器;如果机器上没有装,也可以用intlinprog先跑小规模算例。
这里特别提醒一点:不少同学在Matlab新版本里装老版本YALMIP会遇到求解器路径识别不了、许可证报错等问题。用哪个版本的求解器就在YALMIP的addpath里配套哪个版本,路径混乱是这类工具链最常见的问题来源。求解器装好后,先用一个随机小矩阵测试一下yalmip是否正常调用,再开始跑调度模型,能省很多查错时间。
6. 算例验证与结果差异分析:策略到底比不做协调好在哪里
6.1 算例与对比方案设置
为验证“多时间尺度”和“特性分布”两个核心点,我设计了三个对比方案:
- 方案A:不装储能,只靠机组和常规调度方式运行。
- 方案B:装储能,采用日前单尺度优化,储能按整站聚合建模,不考虑特性分布。
- 方案C:装储能,采用日前、日内、实时多时间尺度协调调度,储能按分群建模考虑特性分布。
输入数据保持一致:同一组负荷曲线、同一组风光预测数据(含误差),储能电站参数也一样,只有建模方式是变量。
6.2 关键指标与效果对比
评价策略好不好,我一般看四个指标:系统运行成本、弃风弃光率、储能SOC越限次数、负荷峰谷差变化。下面是类似规模算例中比较典型的一组结果趋势(具体数值会随算例数据变化,重点看相对关系):
| 指标 | 方案A | 方案B | 方案C |
|---|---|---|---|
| 运行成本均值 | 基准值 | 下降约8%~12% | 下降约12%~18% |
| 弃风弃光率 | 12%~15% | 6%~10% | 2%~5% |
| SOC越限次数 | - | 日均5~10次 | 日均0次 |
| 峰值负荷削减 | 0 | 3%~5% | 5%~8% |
方案B的主要问题是日前计划做得再漂亮,实时执行时因为SOC分布不均导致部分指令无法执行,实际效果打折扣。方案C因为日内滚动修正了SOC轨迹,实时分配又按各簇状态动态微调,几乎不会出现越限,因而调节能力被完整释放。
6.3 结果解读与灵敏度分析
这一类策略研究里,结果不只有一个“更优”的结论,更重要的是搞清楚“为什么更优”。我建议做完主场景后加一组灵敏度分析:把电池簇之间的SOC分布方差从5%调到15%,观察方案B和方案C的差距变化。
通常趋势是:SOC分布差异越大,方案C相对方案B的优势越明显。这个结论在逻辑上也讲得通——特性分布越是参差不齐,越需要一个能感知并响应这种差异的调度策略。如果你的算例里分布差异很小,两方案的结果差异也会缩小,反而是正常的,说明模型没有失真。
7. 实操中容易踩的坑与处理经验
7.1 SOC初值决定全局走向
多时间尺度模型的SOC是一个有记忆的状态变量,初值设错,整条轨迹都会偏离。最稳妥的做法是先用一段“预运行”来逼近一个相对合理的初始SOC分布,让系统运行几天之后站内SOC分布自然进入统计稳定状态,再开始正式记录指标。
7.2 充放电互斥约束的非线性陷阱
不少人为了表达储能不能同时充放电,直接写P_ch * P_dis = 0。这个约束是非线性的,还带等式形式,求解器非常容易陷入数值困难。正确做法是引入二值变量:
P_ch(t) ≤ M·u(t), P_dis(t) ≤ M·(1-u(t))
其中M是一个足够大的正数,u(t)是0/1变量。虽然模型从LP变成了MILP,但求解稳定性远好于非线性写法。我用Gurobi解这个规模的MILP,几百个变量也就几秒到几十秒的事,完全可接受。
7.3 预测误差更新频率别走极端
滚动更新的频率不是越高越好。我的经验是,5到15分钟滚动一次,既不影响实时性能,又不会让优化求解占用太多算力。每1分钟滚动一次不仅算力消耗大,而且超短期预测在该时间尺度上的信息增益已经很小,边际收益很低。
7.4 模型粒度与仿真速度的平衡
刚接触这类问题的人容易一上来就把每个电池簇、每条线路都建进去,结果模型大得跑不动,调参周期拉得很长。我的建议是分三步走:先用整站聚合模型把三层框架跑通,确认各层逻辑正确;再加入分群聚类,验证特性分布的影响;最后根据研究需要加入网络约束或其他细节。这个由简到繁的过程基本能避免“一上来就卡死在求解器里”的困境。
最后说点我在实际项目里的体会。多时间尺度源储荷协调调度这套策略,框架本身并不难理解,难点在于每一个细节都真实地影响最终结果:SOC初值、预测误差建模、储能簇间分配比例、求解器选择,任何一个环节处理草率,优化结果都会和现场执行“两张皮”。在Matlab里把这套东西落地,本质上是一个不断逼近物理现实的过程——把储能电站当成一群状态各异的电池来对待,把调度决策放进多个时间尺度里去滚动修正,结果自然不会差。