
最近在做一个储能电站并网调度的项目让我最头疼的并不是机组组合而是储能电站内部的“状态分化”。传统调度模型习惯把整个电站当成一个大电池SOC直接用平均值替代调度员看着总SOC觉得还有富余实际上电站里几十个电池簇的SOC早就严重分裂了——有的过充保护退出有的几乎放空整体可用容量和“平均”算出来的完全不是一回事。这个问题的本质就是标题里的四个字特性分布。这篇文章想把我用Matlab搭建的“考虑特性分布的储能电站接入电网多时间尺度源储荷协调调度策略”完整拆开讲一遍包括模型怎么设计、代码怎么组织、算例怎么对比以及我调参过程中踩过的那些坑。适合正在做储能调度、源网荷储协调优化、或者想用Matlab加Yalmip搭一套可复现代码框架的同学参考。1. 为什么要盯住储能电站的“特性分布”不放1.1 传统聚合储能模型丢掉了哪些东西大多数论文和初始代码在建模储能电站时都默认走聚合路线整个电站一个额定功率、一个额定容量、一条SOC曲线、一个充放电效率。从数学上看这样做非常干净只引入两个状态变量就能描述储能优化模型规模也小。但把这个模型放到并网运行场景里问题就出来了。储能电站内部由几十个甚至上百个电池簇并联组成每簇的初始SOC不可能完全一致充放电效率受温度和老化影响也不同容量衰减程度更是千差万别。把这些簇压成一个“平均SOC”之后调度层看到的SOC轨迹是条“合成曲线”并不是任何一簇的真实状态。我在实际调试中遇到过这样的场景调度计划下发给电站要求以0.5C倍率放电一小时。聚合模型从SOC 70%算到SOC 60%一切正常。但电站执行时高SOC簇先出力低SOC簇跟着硬扛结果部分簇不到20分钟就触发了过放保护退出运行电站实际出力直接掉到额定功率的一半以下然后调度考核罚款、设备寿命受损一串连锁反应全来了。所以聚合模型在纯规划类研究里能用但一旦涉及“储能电站并网后的实际可调度能力”这类问题就必须把内部特性分布显式建模。否则优化器给出来的解在物理上可能是不可行的——它以为能用10 MWh实际上电站只有6 MWh能放出来。1.2 “特性分布”怎么落进调度模型把特性分布放进调度模型并不一定要求对每个电池簇都逐一建模。工程上常用的是分类聚合把所有电池簇按容量、初始SOC、SOH、效率曲线这几个维度聚成K类通常K取5到15就够了。每一类有自己的额定功率、额定容量、初始SOC、充放电效率和SOC上下限不同类之间独立建模再在电站层面汇总成对外接口。数学表达上相当于把原来单台储能的变量从一组变成K组。对每一类j定义充电功率变量P_ch,j(t)、放电功率变量P_dis,j(t)和状态变量SOC_j(t)状态递推写为SOC_j(t1) SOC_j(t) η_ch,j * P_ch,j(t) * Δt / E_j - P_dis,j(t) * Δt / (η_dis,j * E_j)其中E_j是该类聚合容量η_ch,j和η_dis,j分别为充电、放电效率。这里的效率不能再拍脑袋取常数理想做法是根据历史充放电数据拟合出一条“效率-SOC”曲线然后用分段线性函数近似。代价就是模型里多出几个连续变量和二进制变量但对现在的求解器来说完全不是负担。如果N特别大还可以用统计区间化建模把SOC分成几个区间每个区间统计一批簇的状态概率和可用功率。这种思路在部分文献里叫“多状态电池堆模型”本质上和分类聚合是一回事只是表达方式不同。我最终用的是分类聚合因为它在Matlab里更好写也方便和实际电站的BMS数据对接。2. 多时间尺度调度的分层逻辑与接口设计2.1 日前—日内—实时三层各干什么多时间尺度调度说白了就是“把不同响应速度的决策放到不同时间粒度上做”。我采用的是三层结构日前计划层、日内滚动修正层、实时分配层。日前计划层的周期是未来24小时时间分辨率15分钟一共96个点。这一层解决的是大方向问题哪些机组开、哪些机组停、联络线计划怎么走、储能电站的充放电粗计划怎么排、需求响应签多少合约量。因为启停决策需要提前准备所以这层必须用混合整数规划目标函数是系统总成本最小约束里要预留旋转备用。日内滚动修正层的周期是未来4小时每15分钟滚动一次。这层做的是“修正”工作风电和光伏的超短期预测更新了负荷预测也偏差了原来日前计划必然不再最优。日内层以日前计划为基准只调整储能出力和可调负荷尽量不动机组组合。它的优化周期短、变量少可以用线性规划或者二次规划快速求解。实时分配层是执行层时间粒度秒级或分钟级。拿到日内层给出的储能电站总功率指令后把指令拆到每一个电池簇或每个特性类。这一层我不做优化而是用规则控制核心原则是“SOC高的优先放、SOC低的优先充”目标是让簇间SOC往一致方向收敛。2.2 层间衔接计划值传递与滚动修正三层结构最难的不是单层建模而是接口设计。我做了三类传递第一类是日前到日内的“基准值传递”。日内层的目标函数里加了一个二次惩罚项惩罚日内出力和日前计划值的偏差系数不能设得太大否则日内就完全锁死日前计划失去了滚动修正的意义也不能太小否则日内出力会剧烈跳动。我一般会让这个惩罚系数和机组调峰成本相匹配量纲对齐后用调试法微调。第二类是预测数据的滚动更新。日前层用风功率预测曲线日内层每次滚动时都会拿到新的预测数据进行更新相当于有限时域滚动优化。这里有一个很关键的工程点日内层必须考虑“未来信息”不能只盯当前时刻否则会陷入近视病把储能可用的调节空间全部霍霍完。第三类是实时指令到簇级分配的映射。日内层输出的是电站级总功率指令P_sto(t)实时层再加一个“估计当前可用功率”的逻辑把总指令分配到各簇。分配完后要把实际可执行的功率反馈回日内层形成闭环。我最初没做这个反馈结果日内层每次算出来的计划实际执行时都要打折后来把可用功率在线估计值加进日内约束这个问题才彻底解决。3. Matlab实现方案从数据准备到求解器调优3.1 代码整体结构怎么搭整套Matlab代码我按“数据—模型—求解—结果”四个模块组织避免把所有东西堆在一个几百行的脚本里。目录结构大致是这样的data/ # 负荷、风功率、光伏、储能参数、机组参数 model/ # 日前、日内、实时分配三个模型的构建函数 optim/ # 求解器配置与公共约束函数 scripts/ # 主运行脚本run_case.m、plot_results.m utils/ # 场景生成、效率曲线拟合、SOC一致性统计主运行脚本的流程分四步加载数据、构建三层模型、循环仿真、输出结果。仿真时按时间推进每天先把日前模型跑一遍得到基准计划然后进日内循环每15分钟更新一次预测并重优化最后在实时层模拟秒级采样把指令分配到各个特性类。我用的是Yalmip做建模求解器选Gurobi。Yalmip的好处是能够在同一套代码里切换MILP、LP和QP而且和Matlab的数据结构衔接非常顺。如果你没有Gurobi的许可证用Cplex或者Matlab自带的intlinprog也能跑只是大算例下速度会慢一些。3.2 日前MILP模型怎么写日前层的决策变量比较多各机组出力P_g(i,t)、启停状态u_g(i,t)、启停动作SU(i,t)、SD(i,t)、储能各类别充放电功率P_ch(j,t)、P_dis(j,t)、SOC_j(t)、需求响应削减量P_dr(k,t)、线路潮流P_line(l,t)。目标函数是系统总成本最小拆成五项常规机组燃料成本用二次函数近似aP_g^2 bP_g c启停成本按每次动作计费储能充放电带来的等效损耗成本按充放电量乘一个很小系数防止储能无意义空充空放弃风弃光惩罚成本保证优化器优先消纳新能源需求响应补偿成本约束部分核心是这么几条节点功率平衡常规机组出力上下限与爬坡约束储能充放电功率上下限、SOC上下限、充放电互斥备用容量约束备用量按风电预测误差的分位数设置线路传输容量约束需求响应削减量的日限值约束核心建模代码大概长这样%% 日前调度模型构建yalmip片段 P_g sdpvar(n_gen, T, full); u_g binvar(n_gen, T, full); SU sdpvar(n_gen, T, full); SD sdpvar(n_gen, T, full); P_ch sdpvar(n_sto_clusters, T, full); P_dis sdpvar(n_sto_clusters, T, full); SOC sdpvar(n_sto_clusters, T, full); % 目标函数 obj 0; for i 1:n_gen obj obj sum(a(i)*P_g(i,:).^2 b(i)*P_g(i,:) c(i)*u_g(i,:)) ... sum(SU_cost(i)*SU(i,:) SD_cost(i)*SD(i,:)); end obj obj sum(sum(P_ch P_dis)) * sto_cost; obj obj wind_curtail_penalty * sum(wind_curt) ... pv_curtail_penalty * sum(pv_curt) ... dr_cost * sum(sum(P_dr(:,1:T))); % 约束 cons []; cons [cons, P_g P_g_min .* u_g, P_g P_g_max .* u_g]; cons [cons, -ramp_down diff(P_g,1,2) ramp_up]; for t 1:T cons [cons, sum(P_g(:,t)) P_wind(t) - wind_curt(t) ... P_pv(t) - pv_curt(t) sum(P_dis(:,t)) - sum(P_ch(:,t)) ... P_buy(t) - P_sell(t) P_load(t) - sum(P_dr(:,t))]; end % 储能SOC递推 for j 1:n_sto_clusters cons [cons, SOC(j,1) SOC_init(j) ... eta_ch(j)*P_ch(j,1)*dt/E(j) - P_dis(j,1)*dt/(eta_dis(j)*E(j))]; for t 2:T cons [cons, SOC(j,t) SOC(j,t-1) ... eta_ch(j)*P_ch(j,t)*dt/E(j) - P_dis(j,t)*dt/(eta_dis(j)*E(j))]; end cons [cons, SOC_min(j) SOC(j,:) SOC_max(j)]; end ops sdpsettings(solver,gurobi,verbose,2,mipgap,0.0001); optimize(cons, obj, ops);写这么长是因为你如果不把SOC递推写清楚后面算例结果分析根本没法讲。这里有几个容易写错的地方SOC递推公式里充放电效率的位置充放电互斥约束在Yalmip里如果直接用P_chP_dis≤某个上限实际上没必要用二进制变量把两个变量的和限制在电站总功率内就够了省掉一批整数变量但如果你想精确模拟“不能同时充放”那就得加互补约束或者二进制变量我最终没做那么细因为经济性优化会自动选一个方向。3.3 日内滚动优化与实时分配规则日内层的变量比日前少很多机组出力已经固定只优化储能出力和需求响应调整量。目标函数是“跟踪日前基准计划 最小化滚动域的波动 惩罚功率偏差”。约束比日前简单主要是功率平衡、储能SOC递推和爬坡约束。实时分配层我写了一个函数按“SOC均衡优先”原则把电站总功率指令分配到各特性类function P_cluster allocate_storage(P_sto_ref, P_ch_max, P_dis_max, SOC_now, E, mode) % mode1表示放电mode0表示充电 n length(SOC_now); P_cluster zeros(1, n); if mode 1 [~, idx] sort(SOC_now, descend); % SOC高的先放 else [~, idx] sort(SOC_now, ascend); % SOC低的先充 end remain P_sto_ref; for j idx if remain 0 break; end if mode 1 avail min(P_dis_max(j), (SOC_now(j) - SOC_min(j)) .* E(j) / dt); else avail min(P_ch_max(j), (SOC_max(j) - SOC_now(j)) .* E(j) / dt); end P_cluster(j) min(avail, remain); remain remain - P_cluster(j); end end这个分配规则看起来朴素但实测下来非常有效。它没有做任何优化只是按照“SOC高先放、SOC低先充”就能让各簇在几个充放循环内把SOC拉回到相近水平避免个别簇反复触顶触底。如果你非要在这个环节做优化也行但那就要在每个秒级采样点求解一个小的LP算力开销大且收益很有限。对于工程应用规则分配其实是更稳的选择。3.4 Yalmip加Gurobi的参数配置经验求解器参数是我花了不少时间调的地方。这里有几个经验直接分享第一mipgap别设得太小。我最初设1e-6跑96点、3机组、5簇储能、10类需求响应的算例经常卡在几十分钟出不来。后来改到1e-4求解时间从半小时降到两分钟以内成本差异不到千分之一完全在工程接受范围内。第二给确定的TimeLimit。比如设240秒超时就用次优解。调度问题时效性很强尤其日内滚动层每15分钟就要重算一次如果求解器卡死整个闭环仿真就停了。加TimeLimit后即使MILP没收敛到最优也能拿到一个可行解继续跑下去。第三储能SOC递推里尽量不要用绝对值函数。Yalmip里abs()会被展开成引入整数变量的形式几十个簇乘96个时段压力很大。能用线性等式表达的就老老实实展开别图省事。4. 仿真算例设计典型系统对比两个方案4.1 测试系统与数据准备我自己搭了一个6节点测试系统没有直接用标准IEEE系统主要原因是标准系统数据要处理好久而且它的网络拓扑对我要验证的“源储荷协调”逻辑没什么帮助。自己搭的系统改起来灵活能更集中地突出储能特性分布和多时间尺度调度的效果。系统构成如下3台常规机组额定容量分别为200 MW、100 MW、50 MW爬坡率取额定容量的2%/分钟1个风电场额定装机120 MW接入节点31个光伏电站额定装机80 MW接入节点51个储能电站额定功率60 MW、容量240 MWh分3个特性类容量各80 MWh初始SOC分别设为70%、50%、30%负荷峰值约300 MW其中可调负荷占10%分两类可平移工业负荷、可削减商业负荷风电和光伏曲线取自某典型日的历史出力数据做一个缩放后使用。负荷曲线用典型冬夏季日曲线叠加热备用电系数。仿真时长取一天时间分辨率日前层15分钟日内层15分钟滚动4小时实时层1分钟采样。对比方案就两个方案A是传统聚合储能模型整站一个SOC方案B是本文的分簇特性分布模型3个特性类各自建模。4.2 结果解读成本、弃电率与SOC一致性两个方案跑完结果差异非常明显直接看表指标方案A聚合模型方案B特性分布模型系统日运行总成本万元62.858.1弃风弃光率7.5%3.2%储能SOC越限次数簇级140调度计划执行偏差MWh9.61.2各簇SOC最大偏差21%6.8%为什么成本反而下降了核心原因在于聚合模型对可用容量的误判。方案A在新能源大发时段优化器以为储能已经充得差不多了平均SOC到上限不敢再增加充电功率只能弃风。而方案B能看到细化的每一簇SOC——低SOC簇还有大量空间调度计划敢把储能充电功率加大整个系统的弃风率降下来了新能源多发的电量顶替了部分火电出力成本自然降低。SOC越限次数这项更有意思。方案A因为只有一个平均SOC约束看起来每个时刻都在限值内但把总指令按规则分到各簇后低SOC簇在放电时段直接跌破下限。统计簇级SOC后方案A全天出现14次越限方案B每次都在约束限制内。这正好回应了第一章的问题聚合模型表面上可行实际上执行层处处不可行。调度计划执行偏差是指实际可执行出力与调度计划出力的差值。方案B因为建模时就把各簇可用功率算进去了执行偏差小了一个数量级。这个指标在多时间尺度调度里特别重要——日内层给实时层的指令要是执行不了后面整条链就断了。5. 实操中踩过的坑与调参经验5.1 SOC不可行初始分布和爬坡约束第一次跑分簇模型的时候求解器直接报infeasible。查了半天问题出在初始SOC的差异上三个特性类初始SOC分别是70%、50%、30%如果日前计划让所有簇在某个时段统一放电低SOC簇跑到下限以下约束就矛盾了。解决思路是把初值差异“显式感知进优化器”。具体做法是在各簇SOC约束里加入一个可用时长约束把每一簇SOC换算成在当前最大放电功率下的可持续时长优化器自然会更谨慎地对待低SOC簇。另一个小技巧是在目标函数里加一个很小的“SOC越限软惩罚”这样即使遇到数值波动求解器也能给出一个可行解而不是报错退出。5.2 求解器卡住之后的处理MILP卡住是绕不开的。我在一次跑8个特性类、96点、6机组的算例时Gurobi跑了半小时都不收敛。后来做了三件事问题缓解非常明显第一把所有SOC变量改成连续变量别用整数第二充放电互斥的二进制变量干脆去掉靠经济目标自然驱动第三把日内滚动层的MILP降级成LP——机组启停已经固定储能和负荷调整本来就是连续问题没必要用整数。改动之后整个仿真从“一小时跑一天”降到“五分钟跑一天”。如果你也遇到长时间不收敛的问题建议先检查是不是二进制变量建多了而不是急着加求解时间。5.3 实时层指令分配的一致性我踩过的另一个坑是电站总SOC明明没问题但分簇之后个别簇在一个时间段里频繁切换充放电状态。原因在于日内层给电站的指令是按总SOC最优计算的没有考虑簇级状态。实时分配时高SOC簇一直放放到和低SOC簇一样水平以后又要切充电——一天之内来回切十几次。解决方法是加一个“滞后区间”实时分配规则里如果簇的SOC和平均SOC之差小于某个死区比如2%就让它保持在当前的充放电状态不切换。这个滞回控制思路在自动控制里很常见搬到储能分配上同样好用。加了之后簇级动作次数大幅下降对电池寿命友好得多。6. 特性分布模型后续可以怎么延伸这几轮跑下来我最深的体会是多时间尺度调度框架其实已经比较成熟了真正拉开差距的是底层建模精细度。把储能电站内部特性分布写进模型不仅没有让求解变得不可接受地慢反而因为可用容量刻画准确让整个调度计划的“可执行性”大幅提升。后续值得做的事还有不少。一是把电池寿命模型引进来把充放电深度对SOH的影响直接折算进日前成本函数这样调度策略会主动规避深充深放经济性和安全性都能兼顾。二是结合电价机制在现货市场环境下把储能充放电计划和市场出清价做联合优化看特性分布模型能不能带来额外收益。三是把容量衰减在线辨识和调度联动起来定期用实时数据更新各特性类的容量和效率参数让模型始终跟着电池的实际状态走。如果只是做课程设计或者毕业论文复现我建议先把我前面这套三层框架跑通再考虑加复杂度。最重要的不是模型多豪华而是先把“聚合模型算出的解能不能被电站实际执行”这个问题想清楚。想明白了储能并网调度这章就算真正过关了。