ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

考虑储能特性分布的多时间尺度源储荷协调调度Matlab实现

考虑储能特性分布的多时间尺度源储荷协调调度Matlab实现 1. 从标题到代码这个项目到底在解决什么问题写这篇分享之前我刚好在办公室跑完一版基于Matlab的储能调度仿真。项目名字很长“考虑特性分布的储能电站接入的电网多时间尺度源储荷协调调度策略Matlab代码实现”。念起来有些拗口但落地到代码里其实就是一件事当储能电站不是一台“大电池”而是几十个甚至上百个特性不一的储能单元时电网怎么在不同时间尺度上把源、储、荷安排明白。这篇文章按我的实际复现思路来写适合正在做电力系统优化调度、储能参与电网运行或者刚拿到类似题目还没想清楚从哪下手的同学参考。1.1 拆开标题储能电站不是一台“大电池”“特性分布”这四个字是整个题目的题眼。很多课程设计或论文里储能电站常常被简化成一个聚合模型给定额定功率和额定容量SOC按一个统一状态量更新。这种做法做宏观分析没问题但真实工程里储能电站一般由多个电池簇、变流器并联组成各电池簇出厂批次不同、运行温度不同、充放电深度不同导致SOC、SOH、可充放电功率都存在明显差异。如果调度指令只下发到一个聚合储能执行层再盲目平均分配功率很容易出现部分单元过充过放、另一部分单元却闲置的情况久而久之整站可用容量和寿命都会明显下降。我举个很直观的例子。假设一个储能电站有两台电池簇1号容量2MWh、最大功率1MW2号容量0.5MWh、最大功率0.25MW。如果调度模型把它们聚合成“总容量2.5MWh、总功率1.25MW”的一台储能系统优化后给出的计划可能是“以0.6MW充电一小时”。这个功率单独看没问题执行层如果按额定功率比例分配给两个单元1号分到0.48MW2号分到0.12MW两个单元都正常。但如果执行层按平均功率0.3MW去分配2号就会直接超过额定功率SOC也会迅速越限。现实中的分配算法当然不会这么粗糙但聚合模型本身会掩盖很多类似的执行风险。所以在Matlab代码实现里这一步通常体现为把原来单个储能变量改成多个储能单元变量每个单元有自己的SOC初值、容量上限、功率上限、健康系数甚至在目标函数里加入各单元SOC一致性惩罚项。模型维度变大求解难度上升但换来的结果是调度指令更可靠储能电站内部不会出现“旱的旱死、涝的涝死”。1.2 场景定位多时间尺度源储荷协调在做什么储能接入电网后可以参与调峰、调频、备用、缓解阻塞等多种服务。但这些服务的时间常数差别很大调峰是小时到天级别的能量搬运调频是秒到分钟级别的功率跟踪阻塞管理和电压支撑则需要在更短的时间窗口内响应。任何单一时间尺度的模型都没法同时把这些事情说清楚。这就是“多时间尺度”的来源。我在复现中采用了常见的三层结构日前调度计划以1小时为分辨率决定未来24小时传统机组启停、机组出力、联络线功率和储能各单元充放电计划时间尺度最长主要解决经济性和电力平衡问题日内滚动优化以15分钟为周期基于超短期负荷预测和新能源预测修正机组出力与储能计划重点跟踪新能源波动和负荷预测偏差实时控制层以1分钟甚至更短为周期只做小幅调整和偏差平抑负责把调度指令落实到实际功率。三层之间不是各干各的而是通过储能SOC目标值、机组出力基点、联络线计划值层层传递。源储荷协调则强调不能只看火电和储能。新能源出力波动大的时候负荷侧也需要配合比如可中断负荷、可转移负荷、电动汽车充电桩等柔性资源在高峰时段削减用电或在低谷时段增加用电给储能和机组减轻压力。整篇代码最终输出的是一个包含传统机组出力、新能源消纳功率、储能充放电功率、负荷调整量的综合运行方案而不是孤立的储能充放电曲线。我在写代码时把变量分成了“源侧变量”、“储能侧变量”、“负荷侧变量”三个模块这样后续加新资源、新约束都会方便很多。2. 方案选型与建模思路为什么这样做2.1 储能特性分布怎么进优化模型储能电站内部特性分布建模时有两种层次。第一种是把每个储能单元作为独立优化变量约束里分别写SOC动态、充放电功率上下限目标函数里加上各单元之间SOC偏离基准值的惩罚项。比如第i个储能单元在t时刻的SOC记为soc(i,t)充电功率pch(i,t)放电功率pdis(i,t)动态约束写成soc(i,t1)soc(i,t)eta_ch*pch(i,t)/E(i)-pdis(i,t)/(E(i)*eta_dis)同时pch(i,t)和pdis(i,t)不能同时大于0并受额定功率限制。这样储能电站的总功率就是所有单元功率之和。第二种是用“分布式特性聚合”的方式把储能单元按SOC区间或SOH档位聚合成若干组每组特性内部一致再把组作为优化变量。这种方法可以控制变量数目适合储能单元特别多的大规模电站。我在Matlab仿真里为了同时兼顾真实性和求解速度取了12个储能单元特性参数按正态分布随机生成——初始SOC分布在0.2到0.8之间额定容量在0.5到1.5倍基准值之间健康系数在0.85到1.0之间。这样既能看到特性差异对调度结果的影响又不会让模型膨胀到没法在普通笔记本上求解。选型时还要考虑特性分布到底该放进目标函数还是约束里我的做法是两边都放。约束保证每个储能单元都不能越限这是硬约束目标函数里加入SOC均衡惩罚鼓励各个单元在运行过程中SOC保持接近这是软约束。软约束的作用非常大没有它求解器只会在满足约束的前提下把某个单元往死里用其他单元闲着一周下来就会发现部分单元频繁满充满放寿命衰减明显。提示如果做论文复现尽量不要把特性分布写死成固定数组。用随机生成并固定随机种子结果可重复又方便做多场景对比。2.2 多时间尺度协调调度的整体框架三层优化在Matlab里通常不是一次跑完而是用一个时序循环嵌套实现。我把整体框架设计成三层循环加一个状态更新函数最外层是日前调度1小时一个段跑24段中间层是日内滚动每15分钟一个点每个日前段内做96个点的滚动修正最内层是实时控制以1分钟为间隔模拟系统在日内计划基础上对分钟级扰动做出的响应。实际仿真里并不需要把三层都做得特别复杂。我复现时把重点放在日前和日内两层实时控制层做了简化用一个比例调节器模拟储能快速响应。这样既满足“多时间尺度”的主题又不会因为模型过于复杂导致代码调试周期无限拉长。下表是我在代码中使用的各时间尺度配置时间尺度分辨率优化周期主要决策变量更新触发条件日前调度1小时24小时机组启停、机组出力、储能日前计划、可中断负荷计划每天开始前日内滚动15分钟4小时向前滚动机组出力修正、储能功率修正、新能源弃用调整每个15分钟开始时实时控制1分钟1分钟储能快速功率补偿、备用响应每个采样点层与层之间传递的核心状态是储能SOC。日前调度结束时会给每个储能单元一个24小时后的目标SOC日内滚动优化会尽量追踪日前SOC曲线实时控制则更新最新SOC并把当前SOC作为下一轮滚动优化的初始条件。我在代码里用了一个结构体state_storage.soc来存所有储能单元的SOC向量每一层在做完计算后都调用同一个update_soc()函数更新。如果哪一层忘了更新SOC后面所有结果都会偏这个问题后面会详细说。2.3 目标函数与约束构建细节目标函数不是越复杂越好但要能体现调度的经济性和安全性。我的代码中采用加权求和的形式第一项是传统机组运行成本用二次函数表示煤耗成本必要时还要加上启停成本第二项是弃风弃光惩罚目的是让调度结果尽量消纳新能源第三项是储能单元SOC均衡惩罚计算公式是各单元SOC与全站平均SOC差值的平方和乘一个权重第四项是负荷调整惩罚因为可中断负荷不能随便切切得越多成本越高。约束方面我按类型分了四组系统级约束任意时刻电源总出力加上储能放电功率、减去储能充电功率等于负荷功率减去实际削减的可中断负荷。这是必须满足的等式约束也是最容易导致模型不可行的约束。机组约束每台机组的出力上下限、爬坡速率限制、最小启停时间限制。在日前模型中启停是0/1整数变量在日内滚动模型中我把启停状态固定为日前结果只调整连续出力变量这是为了降低求解难度。储能约束每个储能单元的SOC上下限、充放电功率上下限、充放电互斥、SOC动态更新以及各单元SOC尽量接近的软约束。另外还要考虑同一储能电站内总功率与外部交互的功率限制。网络约束如果用的是IEEE节点系统还需要考虑直流潮流或线性化交流潮流约束比如线路有功潮流不超过上限、节点电压偏差在允许范围内。我的算例用了修改后的IEEE 33节点配电网所以加入了简化的DistFlow线性潮流约束。数学形式在论文里可以写得很严谨但代码实现时我建议先做“无网络约束”的版本把功率平衡和储能约束跑通再加潮流约束。一步到位很容易出现一堆infeasible排查起来非常头大。尤其是潮流约束里的节点电压幅值如果写成非凸的非线性形式Matlab求解器根本没法直接处理必须线性化或者用锥规划近似。这也是我在做配电网模型时踩过的坑后面会单独说。3. Matlab代码实现的核心环节3.1 环境准备YALMIP Gurobi 这套组合Matlab里做优化调度最舒服的还是YALMIP加外部求解器。YALMIP是一个建模层能帮你把优化变量、约束和目标函数组织成标准形式再调用底层求解器。Gurobi在求解混合整数线性规划方面性能很好学术许可申请也方便。如果机器上没有Gurobi可以先用Matlab自带的intlinprog跑小算例但一旦储能单元数量超过20个、整数变量超过100个intlinprog的速度会让你怀疑人生。安装时需要注意YALMIP不是MATLAB自带工具箱。下载后把压缩包解压到任意目录在MATLAB里addpath(genpath(你的YALMIP目录))然后savepath防止下次重启失效。Gurobi安装完后要在MATLAB里运行gurobi_setup配置接口。我第一次配置时遇到一个常见问题64位Gurobi与32位MATLAB不匹配会直接报Undefined function gurobi_mex。解决方法很简单换用64位MATLAB。如果只是想复现调度逻辑不想折腾商业求解器可以用YALMIP的默认求解器Sedumi或SDPT3但MILP问题还是建议装Gurobi。我实测下来同样一个包含12个储能单元、24小时日前调度模型Gurobi用十几秒就能找到最优解而intlinprog可能需要几分钟甚至更久。另外MATLAB R2021a以后自带的optimproblem框架也能写线性规划但处理混合整数变量和复杂高阶约束时还是YALMIP更灵活。要不要装Gurobi我的建议是只要你的项目不是只跑一次就装。磨刀不误砍柴工。3.2 数据准备与参数初始化代码的第一步是准备数据。我把所有输入数据放在一个结构体data里这样后续函数传参方便也容易做批处理。典型字段包括data.load_forecast未来24小时负荷预测维度24x1data.wind_forecast、data.pv_forecast新能源出力预测维度24x1data.gen传统机组的各项参数包括额定出力上下限、爬坡速率、煤耗成本系数data.storage储能电站参数包含单元数n_storage每个单元的额定容量、额定功率、SOC初值、健康系数、充放电效率data.price分时电价。关键是储能参数怎么生成。我在初始化时用了固定随机种子rng(2024); n 12; data.storage.E 1.0 0.5 * rand(n,1); % 额定容量MWh data.storage.P 0.5 0.2 * rand(n,1); % 额定功率MW data.storage.soc0 0.2 0.6 * rand(n,1); % 初始SOC data.storage.eta_ch 0.95; data.storage.eta_dis 0.95; data.storage.health 0.85 0.15 * rand(n,1); % 健康系数这样做的好处是每次运行结果一致但各储能单元特性明显不一样正好体现“特性分布”这个主题。如果你希望验证不同分布的影响可以改随机种子或换用makedist生成特定分布的数据。参数初始化里最容易忽略的是基准值设置。调度模型里很多量级相差很大比如负荷功率几百兆瓦SOC只有0到1目标函数里煤耗成本系数几百元/兆瓦时。如果直接混在一起求解器数值条件会很差。我的习惯是把所有功率和容量先标幺到100MVA基准或者至少统一到同一量纲。SOC保持0-1但目标函数里的SOC均衡惩罚项要乘一个合适的权重比如1元/单元单位偏差不然会被煤耗成本淹掉。3.3 日前调度模块实现日前调度模块是整段代码的核心。我用YALMIP建模关键代码如下% 日前调度模型 Nt 24; Pg sdpvar(Ng, Nt); % 机组出力 u binvar(Ng, Nt); % 机组启停状态 Pch sdpvar(Ns, Nt); % 储能充电功率 Pdis sdpvar(Ns, Nt); % 储能放电功率 soc sdpvar(Ns, Nt1); % 储能SOC Pcut sdpvar(Nt, 1); % 弃风弃光功率 Pload_shed sdpvar(Nt, 1); % 可中断负荷削减 Constraints []; % 系统功率平衡 for t 1:Nt Constraints [Constraints, ... sum(Pg(:,t)) sum(Pdis(:,t)) data.wind_forecast(t) - Pcut(t) ... data.load_forecast(t) sum(Pch(:,t)) - Pload_shed(t)]; end % 机组约束、储能约束省略... % 储能SOC递推 for i 1:Ns for t 1:Nt Constraints [Constraints, ... soc(i,t1) soc(i,t) data.storage.eta_ch * Pch(i,t) / data.storage.E(i) ... - Pdis(i,t) / (data.storage.E(i) * data.storage.eta_dis)]; end end Objective sum(sum(gen_cost_coeff .* Pg)) ... wind_penalty * sum(Pcut) ... shed_penalty * sum(Pload_shed) ... soc_balance_penalty * sum(sum((soc - mean(soc,1)).^2)); ops sdpsettings(solver,gurobi,verbose,2); optimize(Constraints, Objective, ops);代码里我用sdpvar定义连续变量、binvar定义启停变量。注意SOC变量多定义了一列soc(:,Nt1)存的是调度结束时SOC用来追踪终值约束。功率平衡的等式约束里左边是出力右边是负荷千万别把储能充电功率放到等式左边否则方向反了会直接导致模型无解。这里还要补充一个容易漏掉的终值约束如果想让储能电站24小时后SOC回到初始值需要额外加soc(:,Nt1) data.storage.soc0。但加了之后模型可能变得不可行因为某个储能单元容量太小夜间放完电后白天根本充不回去。我的做法是把这个硬约束改成软约束在目标函数里加一个终值偏差惩罚这样既保证每天SOC不会漂移太远又不会因为个别单元容量特性导致整个调度无解。日前模块跑完后我会把机组启停状态、储能SOC曲线存到result.dayahead里作为下一层的输入。这里有个小技巧在进入日内滚动之前先把日前SOC曲线画出来看一眼。如果SOC曲线出现很频繁的满充满放突变第一件事不是去调算法而是检查储能容量参数是不是忘了乘健康系数。很多次“模型跑出来结果很怪”的问题最后都出在参数初始化上。3.4 日内滚动优化与实时修正模块日内滚动优化实际上是一个在每个15分钟开始时刻执行的循环。我按照仿真时间顺序来写state_storage.soc data.storage.soc0; state_gen.P result.dayahead.Pg(:,1); % 初始机组出力 for k 1:96 % 生成未来4小时预测这里用实测值加噪声模拟超短期预测 pred_horizon 16; % 15min点数4小时16点 wind_pred data.wind_true(k:kpred_horizon-1) 0.05 * randn(...); load_pred data.load_true(k:kpred_horizon-1) 0.02 * randn(...); % 以日前机组的启停状态为固定整数变量仅优化连续出力 Pg_adj sdpvar(Ng, pred_horizon); Pch_adj sdpvar(Ns, pred_horizon); Pdis_adj sdpvar(Ns, pred_horizon); soc_adj sdpvar(Ns, pred_horizon1); soc_adj(:,1) state_storage.soc; % 当前SOC作为初值 % 构建目标函数和约束并求解 % 滚动窗口只取第一个时刻的决策量作为实际执行值 % 更新state_storage.soc state_storage.soc value(soc_adj(:,2)); end这里最需要留意的是滚动窗口的初值传递。很多第一次写滚动优化的同学会忘记把soc_adj(:,1)设为当前实际SOC导致每个窗口都从默认初值开始算结果事后再看SOC曲线完全对不上。另外一个容易踩的坑是预测序列是在仿真时间窗口内连续取数不是每个15分钟都从头开始预测。如果写法不对相当于用全知预测做滚动优化结果会好得不真实论文审稿人一眼就能看出来。实时控制层我做了个简化在日内计划的基础上计算系统实际频率偏差或功率偏差然后用一个比例系数让储能做秒级补偿。这一层不涉及复杂的优化求解只是模拟储能快速响应特性。如果你开发的是微网或配电网项目可以把实时层升级成无模型预测控制或基于下垂控制的仿真但作为策略验证比例调节已经完全够用。还需要注意的是实时控制层的输出要限幅不能超过当前储能单元剩余可充放电空间否则仿真里会出现负SOC这种物理上不可能的数值。3.5 结果可视化与指标计算代码跑完不是终点还要把结果转化成看得懂的曲线和指标。我的Matlab脚本里最后专门有一个plot_results.m至少输出三类图第一类是功率平衡图把负荷、火电出力、风电光伏出力、储能充放电功率叠在一张图上看出力结构是否合理第二类是储能SOC图把所有储能单元的SOC曲线画在一起颜色区分单元可以直观看到特性分布对SOC的影响第三类是机组出力与电价关系图看储能是否在电价低时充电、电价高时放电是否符合“低储高发”逻辑。画图时我一般会把字体调大、图例摆在外面坐标轴单位写完整。Matlab默认配色在黑白打印时区分度不高我习惯换成parula或者自定义ColorOrder。如果同一张图上要画12条SOC曲线最好用细线加透明度不然叠在一起根本看不清。我的做法是只挑几个有代表性的单元画线其余单元用阴影带表示范围这样既展示特性分布又不会让图变成一团乱麻。指标方面我会统计弃风弃光率、系统运行成本、储能单元SOC标准差、峰谷差削减比例等。其中“储能单元SOC标准差”这个指标很能说明“特性分布”是否被考虑如果调度策略忽略了特性分布不同单元SOC会越拉越开如果策略有效各单元SOC应该始终保持在较窄的带内同时不牺牲经济性。我复现时发现加入SOC均衡惩罚后SOC标准差能下降40%以上系统成本只增加2%左右这个交换非常值得。4. 常见问题与实操排查4.1 模型不可行怎么定位储能调度模型最让人头疼的就是一运行就返回Infeasible problem。这不是代码语法错而是约束之间互相矛盾。我的排查顺序是先看功率平衡约束把等式右边减去左边在测试数据里手动验算某个时刻是否平衡再看储能SOC终值约束比如要求24小时结束时SOC等于初始值但容量设置太小根本充不回去不可行就出现了最后看机组爬坡约束尤其在新能源骤升骤降时常规机组爬坡速率不够需要加弃风变量或储能调节变量。建议在每个约束拼接处加注释并写一个小的可行域检查脚本。更快的做法是给模型添加松弛变量在功率平衡约束里加上正负偏差项并给偏差项加一个极大的惩罚系数。如果求解器返回的松弛变量非零就能快速定位是哪个时段的平衡出了问题。我上次调试一个考虑配电网潮流约束的版本时发现松弛变量总是在晚间负荷高峰时段非零排查后才知道是储能放电功率上限设置得太保守导致晚间高峰时段系统功率缺额。这个排查过程用了不到十分钟比对着约束式子猜要高效太多。4.2 SOC
返回列表