如果你最近也在做储能调度相关的仿真,应该会经常看到“源储荷协调”“多时间尺度”这类词。我前几个月亲手用Matlab实现了一个叫“考虑特性分布的储能电站接入的电网多时间尺度源储荷协调调度策略”的完整项目,从建模到代码再到结果分析,踩了不少坑,也理顺了很多之前没想明白的点。这篇博文就把整个项目从头到尾拆开讲一遍,内容偏工程实操,适合做电力系统优化调度方向的同学、刚入门或正在做类似仿真的工程师参考。
项目的核心其实就一句话:电网里接入的储能电站不是一个“理想大水桶”,每台储能单元的容量、健康状态、充放电效率都有差异,调度时得把这些特性分布考虑进去,同时在日前、日内、实时几个时间尺度上把电源、储能、负荷协调起来优化。下面我按思路、建模、代码、实验、避坑五个部分展开。
1. 项目到底在解决什么问题
1.1 为什么储能不能当成一个“大水桶”
国内很多储能调度研究在最开始都会做一个简化假设:把整个储能电站看成一台“聚合储能”,容量是各台之和,最大功率也是各台之和,充放电效率取一个平均值。这个假设在项目初期可以理解,但一旦落到实际运行场景,问题就很明显。
一个数百兆瓦时的储能电站,往往由几十个甚至上百个电池簇并联组成。电池簇之间因为出厂批次不同、运行温度不同、循环次数不同,实际容量和健康状态SOH(State of Health)会慢慢分开。有的簇SOH还有95%,有的已经掉到80%,内阻变大、可用容量变小、充放电损耗也更高。如果调度模型里把整个电站当成一个均质储能,那么指令下发后很可能出现“部分单元过充过放,部分单元根本没有用上”的情况,长期跑下去个别电池簇会加速老化,甚至触发保护动作,整个电站的可用性都会下降。
所以这个项目把“特性分布”作为一个中心词来对待,不是锦上添花,而是把储能从“单体等效模型”换成“集群差异化模型”,让每个储能单元在调度模型里有自己独立的容量、功率上限、效率、SOC约束和老化成本。
1.2 单时间尺度调度为什么不够用
电网调度如果只做一个时间尺度,通常面临一个矛盾:日前计划的颗粒度太粗,根本吃不下当天突发的新能源功率波动和负荷变化。比如我做了个日前优化,按小时算好未来24小时每台机组出多少电、储能充多少放多少,但到了实际运行环节,光伏突然被云遮住,风电预测误差超过20%,这时如果还死守日前计划,要么靠旋转备用硬扛,要么就得弃风弃光,经济性和清洁能源消纳率都不理想。
反过来只做实时调度也不行。实时调度虽然能跟上变化,但它没有全局视野,容易做出“眼前最优、整体次优”的决策,比如为了应付某一刻的功率缺口让储能深度放电,结果晚上负荷尖峰时储能没电了。这种情况本质上是在单时间尺度下“只盯着局部,看不到明天”。
所以现实且工程上可落地的方案,就是多层次时间尺度配合。我这次做的是两层结构在Matlab里实现:最上面一层是日前调度,时间粒度为1小时,决策周期24小时,做的是全天的资源安排;下面一层是日内滚动优化,时间粒度缩小到15分钟,滚动窗口设为4小时,根据更新的超短期预测把日前计划逐步修正。两层模型共用同一套储能特性分布数据和源、储、荷设备模型,只是目标函数和约束细节上有差异。
1.3 源、储、荷三个角色为什么要放进同一个模型
很多储能调度项目会默认负荷不可调,机组出力恒定,只把储能当“削峰填谷的工具”。这种做法做演示没问题,离真实电网的协调运行差得比较远。
实际上,源侧的常规机组有爬坡速率限制和最小出力约束,新能源机组的出力具有随机性;荷侧有一部分可通过需求响应调整的灵活性负荷,用户侧还有电动汽车充电桩这类可平移负荷。储能如果只跟电源配合,不考虑负荷的可调节空间,相当于放弃了一部分成本更低、响应更快的手段。反过来,如果只跟负荷配合,电源侧的硬约束又可能会卡住整体可行性。
这个项目把源、储、荷三个角色放到同一个优化问题里,统一用一组决策变量描述它们的出力/用电计划,共享同一条功率平衡约束。这样做的效果是动态的:光伏大发时,常规机组压到最低出力,储能充电,可调负荷增加用电;晚高峰时,储能放电,可调负荷削减,常规机组爬坡加出力,三者互相补位。本质上不是让某一侧单独去“硬扛”,而是让整个系统在不同的时间尺度上分工配合。
2. 调度策略的框架与数学模型
2.1 多时间尺度框架怎么定
我在项目里定义了一条比较清晰的时间线。日前调度在当天00:00前完成,生成未来24小时、每小时一个点的计划,决策内容包括常规机组开关机和出力计划、储能每个单元的充放电功率与SOC轨迹、可调负荷基线调整量、弃风弃光量。日内滚动优化从当日00:00开始,每15分钟滚动执行一次,每次优化未来4小时(16个点),用最新的超短期预测刷新输入,同时对日前计划偏离量加惩罚,避免大幅度偏离已下发计划导致实际运行混乱。
这里有一个细节值得说:日内滚动优化的决策周期4小时为什么定成这个长度。太短了,比如1小时,优化器看不到负荷晚高峰的到来,很容易在滚动窗口末尾把储能放空;太长了,比如8小时,超短期预测在后半段的精度明显下降,优化器的“远见”是假的。4小时在预测精度和前瞻视野之间比较平衡,这也是我实测多个窗口长度之后定的。
2.2 日前调度模型的构成
日前调度模型是典型的大规模线性规划问题。目标函数我分了五块成本,全部做量纲统一后相加:
- 常规机组燃料成本,用二次函数描述,我在实现里将其分段线性化处理;
- 机组启停成本,只在日前模型中考虑,日内模型中默认机组状态不变;
- 储能充放电的运行维护成本,按充放电电量计费;
- 弃风弃光惩罚成本,这里要设置一个足够高的惩罚系数,确保优化器只有在系统实在消纳不了时才弃新能源;
- 可调负荷调整补偿成本,负荷上调和下调都产生费用,费用系数不同。
约束条件里,最重要的是功率平衡约束,公式可以写成P_g + P_w + P_s_discharge = P_load + P_s_charge + P_curtail。其次是常规机组出力上下限和爬坡约束,储能单元各自的充放电功率上下限、SOC递推关系和SOC上下限,以及可调负荷调整量的上下限。
关于储能SOC递推,我使用的是离散形式:SOC(k+1) = SOC(k) + ηc * Pc(k) * Δt / Q - Pd(k) * Δt / (ηd * Q)。其中ηc和ηd分别是充电和放电效率,Q是储能单元额定容量,Δt是时间步长(日前是1小时,日内是0.25小时)。每个储能单元都有自己的效率参数,这本身就是“特性分布”在模型里最直观的体现。
2.3 特性分布怎么落到模型里
特性分布不是简单地把参数改一改就行,关键在怎么建模才贴近实际情况。我在项目里采用了“参数分布+成本惩罚”两个层面的处理方式。
参数分布层面,按SOH把储能电站内的单元分成若干个集群,每个集群有自己的额定容量Q、额定功率上限Pmax、充电效率ηc、放电效率ηd、SOC约束区间。例如SOH高的集群可以允许SOC在0.1到0.9之间运行,SOH低的集群把范围收缩到0.2到0.8,避免深度充放电进一步加速老化。这些参数全部从一组基础数据里读取,形成一个表格化的“储能特性参数矩阵”,这样换数据集时只需要改表格,不用改模型。
成本惩罚层面,我在目标函数里给每台储能单元加了一个老化惩罚项,大小与该单元的放电深度DOD正相关。简化表达为老化成本 = k_dod * (SOC低点到高点之差)^2,这个二次项可以从0到1连续变化。这样做的好处是优化器会自动避开让某个单元频繁深度充放电,转而把功率需求分摊到更多单元上,结果是整个储能电站的SOC轨迹更均衡,而不是“逮着一组电池使劲薅”。
2.4 日内滚动修正模型
日内滚动优化与日前模型最大的不同,在于目标函数里多了一个“计划偏离惩罚”。决策变量在每个优化周期都会重新求解,但它们被要求尽量贴近日前计划中同一时刻的值。
实现方式是对比日前计划与日内求解结果的偏差,并对偏差施加线性惩罚。我测试过,如果不加这个惩罚,日内优化经常“推翻重来”,比如某台机组在日前计划里是满发,到日内优化里突然被压到最小出力,另一个时段又爬上去,实际调度根本没法执行。加了偏离惩罚之后,日内优化只会在“系统安全不可行”或“经济性差异非常大”的时候才去改变日前计划,整体运行既能响应新信息,又不会太跳脱。
3. Matlab实现的关键细节
3.1 建模工具与求解器选择
这个项目用的是Matlab下的Yalmip工具箱做建模,求解器选的是某个成熟商业求解器的Matlab接口。Yalmip的好处在于它把变量、约束、目标函数的定义方式和求解器解耦,写起来跟数学公式的思维方式很像,调试时也容易定位约束冲突。而且它自带头文件式的报错提示,能直接告诉你有问题的约束在下标哪里,这一点比手写矩阵系数要省心太多。
求解器方面,如果模型全线性(不含0-1变量),直接用求解器连续求解就好,速度很快。如果加了机组启停变量或者储能充放电状态变量,模型会变成混合整数线性规划,求解时间会明显拉长。最终我把日前模型做成包含0-1变量的MILP,日内模型默认机组状态保持不变,只保留连续变量,这样两层模型的求解时间都控制在分钟级别以内,符合在线滚动调度的要求。
3.2 核心变量定义与构造思路
在Matlab自己编码时,变量定义这一关一定要想清楚,特别是储能变量多了一个“单元维度”,比普通单储能模型复杂不少。我的定义方式如下:
n_G = 6; % 常规机组数 n_S = 4; % 储能单元集群数 n_W = 2; % 新能源场站数 n_L = 3; % 可调负荷数 T = 24; % 日前时段数 % 常规机组出力 P_g = sdpvar(n_G, T, 'full'); % 储能充电和放电功率,第三维区分充电/放电 P_s_charge = sdpvar(n_S, T, 'full'); P_s_discharge = sdpvar(n_S, T, 'full'); % SOC变量,时段数比决策时段多一个(初值+24个末值) SOC = sdpvar(n_S, T+1, 'full'); % 风电/光伏出力(小于等于预测值) P_w = sdpvar(n_W, T, 'full'); % 可调负荷调整量 P_l = sdpvar(n_L, T, 'full');这里建议把储能充电功率和放电功率分开定义,不要合在一个变量里用正负号区分。虽然合并写法代码更短,但我试过,后续加约束、读结果、画图都容易搞混,而且从数值求解角度,分开后每列变量语义更清晰,调试时看求解结果比较直观。要是担心“同时充放电”的冗余解,可以通过约束P_s_charge和P_s_discharge的互斥条件来处理。
3.3 核心约束的代码写法
约束定义是整个模型最核心的部分,我最开始写的时候因为功率平衡约束少加了一项,导致求解器一直报不可行,排查了很久。下面给出关键约束的示范代码,注意每一段约束我都用“约束累加”的方式写,方便后期用Yalmip直接传给求解器。
% 节点/系统功率平衡:各电源出力之和等于负荷与充电功率之和 Constraints = []; Constraints = [Constraints, ... sum(P_g,1) + sum(P_w,1) + sum(P_s_discharge,1) ... == base_load(1:T) + sum(P_l,1) + sum(P_s_charge,1)]; % 常规机组上下限约束 for i = 1:n_G Constraints = [Constraints, ... P_gmin(i) <= P_g(i,:) <= P_gmax(i)]; end % 储能充放电功率上下限 for s = 1:n_S Constraints = [Constraints, ... 0 <= P_s_charge(s,:) <= P_cmax(s,:)]; Constraints = [Constraints, ... 0 <= P_s_discharge(s,:) <= P_dmax(s,:)]; end % SOC递推约束:SOC_{k+1} = SOC_k + ηc*Pc*Δt/Q - Pd*Δt/(ηd*Q) for s = 1:n_S Constraints = [Constraints, ... SOC(s,2:end) == SOC(s,1:end-1) ... + eta_c(s) * P_s_charge(s,:) * dt / Q_s(s) ... - P_s_discharge(s,:) * dt / (eta_d(s) * Q_s(s))]; end % SOC上下限 for s = 1:n_S Constraints = [Constraints, ... SOC_min(s) <= SOC(s,:) <= SOC_max(s)]; end % 新能源出力不超过预测值 Constraints = [Constraints, ... 0 <= P_w <= P_w_forecast];有个细节要注意:SOC变量的维度是n_S×(T+1),这里T+1的最后一个点是周期末的SOC快照,便于观察周期结束时储能有没有回到合理范围。如果要做连续多日仿真,应该把最后一点SOC作为下一日的初始SOC传下去,而不是每次从固定初值开始。
3.4 求解效率调优
我第一次把整套模型写完后,跑一次日前调度花了十几分钟还经常不收敛,后来才意识到问题出在几个地方。
第一,MILP的0-1变量过多。如果对每个机组的每个时段都引入开机变量和停机变量,变量规模会爆炸。我改成只对常规机组的开停机状态在日前模型里建模,日内模型完全不考虑启停,只优化出力,这样大幅减少了整数变量数量,求解时间明显降下来。
第二,目标函数里的二次燃料成本直接用sdpvar二次项时,Yalmip会将其交给求解器做二次规划,收敛速度会比较慢。我用分段线性化把二次曲线近似为若干段线性函数,整个模型变成纯线性规划,稳定性提升不少。
第三,约束里尽量不要出现两个决策变量相乘的情况,这类双线性项会让问题变成非凸问题,求解器基本束手无策。遇到这类需求,要么通过代数转换避开,要么用分段线性化处理。
改完这三个点之后,日前模型求解时间大约在20到50秒,日内模型单次滚动在5秒以内,完全满足我仿真几百个场景的需求。
4. 仿真算例设计与结果解读
4.1 算例参数怎么设
测试系统我用了考虑负荷和新能源接入的某个标准30节点测试系统的修改版,常规机组6台,新能源场站2个(一个风电场、一个光伏电站),储能电站由3个特性不同的储能集群组成。三类储能集群的参数我设计成有区分度:集群1SOH最高,容量最大,效率也高;集群2是中等状态;集群3容量小且效率低,SOC运行范围也最窄。这样做是为了让特性分布在结果里“看得见”,如果三个集群参数完全一样,那就退化回聚合储能模型了。
负荷曲线和新能源预测数据采用典型日数据,并叠加随机误差。日前预测误差我设置成标准差为10%的高斯扰动,日内超短期预测误差标准差设为3%,这样两层时间尺度在数据精度上的差异就体现出来了。
4.2 三组对比实验的设计
为了让项目结果有说服力,我设计了三个方案进行对比:
方案A是基准方案,不含储能,只做常规机组经济调度,这是对照组的origin状态。方案B是储能接入但采用聚合模型,只做日前调度,不考虑特性分布,也不做日内滚动。方案C就是本项目的完整策略,储能集群差异化建模,日前加日内滚动协调。
三个方案跑完后,我对比了系统总运行成本、弃风弃光率、储能单元SOC极差、常规机组爬坡次数这几个指标。结果符合预期:方案C比方案B的总成本下降了约4%,比方案A下降了约7%,弃风弃光率下降幅度更明显。更关键的是SOC均衡度,方案B中所有储能单元的SOC轨迹完全重合(因为模型里它们就是同一个变量),而方案C中不同SOH的储能集群SOC轨迹明显拉开,低SOH集群的充放电深度明显更浅,这正好说明了特性分布确实改变了调度决策。
4.3 从输出曲线里看什么
这个项目画图我用的是Matlab的figure加plot,重点看四类曲线:常规机组出力堆叠图、储能各集群的充放电功率和SOC轨迹、新能源出力及弃风弃光量、系统总负荷与净负荷曲线。
最值得仔细看的是净负荷曲线变化。如果储能策略有效,“净负荷”曲线应该是被削峰填谷后的平滑形态。我印象最深的是方案B虽然也削峰填谷,但由于聚合模型没有考虑不同储能单元的效率差异,优化器总是优先用“平均效率”去决策,实际落到低效率集群时损耗偏大,最终净负荷曲线比方案C的略差。这种差别从单日曲线看不大,但累计到一个月,成本差异就非常明显。
还有一点:看储能SOC曲线是否越界。方案B由于所有储能当成一个,SOC曲线看着很平缓,但实际上单个低SOH集群的真实SOC区间可能早就越限了。方案C的每个集群SOC曲线都严格落在对应区间内,这才是调度方案可以下发给实际设备执行的前提。
5. 常见问题与排查手册
5.1 不可行解怎么排查
我在项目开发中遇到过多次Yalmip报infeasible,这一步要系统排查,最笨但最有效的方法是逐段注释约束。先把储能约束全部注释掉,跑通一个最简版,然后再把储能约束逐块加回来,当加某一类约束时报错,问题就定位在那部分。
另外,从数据角度检查一下量纲。这个问题出现过一次:负荷数据单位是MW,新能源预测数据单位是kW,结果功率平衡约束内部差了1000倍,模型看起来怎么会可行。单位统一这件事,放在数据预处理函数里统一处理好,不要在建模代码里再去乘系数,否则很容易出错。
5.2 求解时间爆炸怎么办
如果模型本身不复杂但求解速度特别慢,优先怀疑是不是无意中引入了整数变量。比如储能充放电功率如果用了二元变量做互斥约束,每个时段多一组0-1变量,百来个时段就是几百个整数变量,求解时间翻十倍很正常。我的经验是先跑一次纯连续模型,确认结果合理后再考虑要不要加整数约束。
5.3 滚动优化里SOC越跑越漂的问题
这个问题在日内滚动时特别容易碰到。因为每次滚动优化的决策周期只有4小时,优化器会在这个窗口内尽量使用储能调节,但不考虑窗口结束后的事情,结果就是SOC被逐次优化“推”到边界,长期滚动后漂移出合理范围。
解决办法有两种。一是给SOC设定一个“参考轨迹”,让调度倾向于回归到日前计划的SOC曲线上。二是每次滚动求解后,把实际执行的SOC末端值作为下一次滚动的初值,并且在目标函数中对偏离参考SOC给予惩罚。我两种都试过,参考轨迹方式更稳定,推荐优先采用。
5.4 一些数据层面的思考
储能特性参数分布如果做得太细,每个单元都单独建模,模型规模会大到没法在线求解。我最终选择按SOH分簇,一个电站分成三到五个集群,这样既保留了特性差异,又控制了变量数量。实际上,如果项目后续做更大规模的系统,可以考虑把储能集群也聚合到更粗的层级上,前提是参数差异不能太大,不然聚合误差又会变大。
这个项目做下来,我最深的体会是:调度模型本身并不追求数学上多复杂,真正有价值的是对运行细节的刻画和参数之间的耦合。特性分布模型加多时间尺度协调,本质上就是在“计算精度”和“可计算性”之间找一个实用的平衡点。如果让我重新做一遍,我会把储能特性参数模块从一开始就独立成函数,而不是反复修改变量和约束,这样换数据集、换参数时改动最小,调试效率也会高很多。