ARTICLE DETAIL

资讯详情

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

基于MATLAB的共享储能电站工业用户日前优化调度与容量配置

基于MATLAB的共享储能电站工业用户日前优化调度与容量配置 最近在帮两家制造企业算用储能的账时我发现一个问题很多工业用户一开始想的是“自己买一套储能”但真把账算完之后多数人沉默了几秒钟然后开始问共享储能电站的租赁报价。原因很简单单个用户的自建储能投资门槛高、日循环次数不稳定账很难算平。于是“共享储能电站”这种模式跑得越来越快电站由第三方持有容量按比例租给多个工业用户大家共用一套电池、共用并网容量把一次性重资产投入变成按天计费的服务成本。这个项目的核心内容我直接用一句话概括用 MATLAB 写一套共享储能电站下的工业用户日前优化经济调度代码再把这个模型用起来做电池容量优化配置。代码的目标很明确——在 24 小时前瞻窗口内综合考虑分时电价、用户负荷、充放电功率限制、电池 SOC 约束和容量租赁成本求出每个时刻储能怎么充、怎么放用户从电网买多少电。整份代码不依赖商用电网仿真平台纯 MATLAB 加优化工具箱就能跑适合想快速复现调度模型的工程师、研究生也适合做园区能源规划的人拿去做前评估。共享储能电站为什么会成为工业用户调度的“枢纽”1.1 自建储能的账为什么常常算不平先聊点行业背景。一个典型的中型工业用户白天负荷七八兆瓦晚上只有两三兆瓦安装一台 1MW/2MWh 的工商业储能设备按当前锂电池系统成本来估算设备加施工加并网通常要砸进去两百万到三百万。等到实际运行问题就来了厂里负荷曲线不配合白天可能要充放电但往往一天只能跑一个循环甚至跑不满一个循环电池利用率低回本周期拉长。更麻烦的是运维电池管理系统报警、散热、容量衰减、消防检查每一项都要人力跟。很多用户在性价比测算阶段就把自建方案否决了。共享储能的模式则把规模效应释放了出来。一个园区里可能有五六家用户负荷高峰时段各不一样有的是白天连续生产有的是夜间错峰用电有的则周期性冲击负荷。把它们的负荷曲线叠在一起储能充放电的窗口就被“填”得更满电站的循环次数、利用率都提上去了。用户那边也不用承担前期巨额投资只需要按月支付容量租赁费和服务费。所以最近两年各地上马的共享储能项目明显比单一用户侧储能多政策导向上也更鼓励这种集中规划、统一调度的模式。1.2 日前调度逻辑发生了什么变化自建储能场景下用户指令简单电价低谷充电电价高峰放电跑完全天看看省多少钱。但共享储能场景下问题立刻复杂化。电站是上一级的资源同时服务 N 个用户站里的总功率、总容量有限这就变成了一个资源分配问题谁在什么时段可以用多少充电功率和放电功率站里电池的剩余电量在用户之间怎么分配电网购电的总需求怎么分摊。这里引入“日前优化经济调度”是最自然的建模方式。所谓日前就是在运行日前一天根据预测的次日 24 点负荷曲线、分时电价、需量电价、站内电池参数生成次日 0 点到 24 点的充放电计划。这个计划要在数学上满足所有的物理约束在目标上做到总成本最低。代码本质是把调度规则翻译成一组目标函数和约束再用优化求解器去搜可行解。1.3 本文研究的模型边界为了让代码清晰可复现我把模型做了几个明确假设。共享储能电站由第三方运营站内电池的额定功率和额定容量是待配置的决策变量或者是给定的外生参数服务对象是园区内多个工业用户各用户负荷曲线已知采用分时电价和需量电价计费日前调度以 1 小时为时间步长全天 24 个时段电站可以同时给多个用户放电也可以同时给多个用户充电但总充放电功率不能超过站内额定功率总能量不能超过电站额定容量。模型的最终输出是每个用户逐时购电量、逐时充放电功率、电池 SOC 曲线以及给定容量方案下的最低日运行成本。日前优化经济调度的数学模型把运行规则变成可求解的约束2.1 目标函数不能只盯着电费很多初学调度的朋友会以为目标函数就是“购电费用最小”其实不够完整。对共享储能电站做经济调度至少要扣掉四个成本块电网购电费用、容量需求费用、电池充放电损耗成本、容量租赁或运维分摊费用。变量定义如下。设园区内有 I 个工业用户调度时段 t 1,2,…,T T24。用 Pgrid(i,t) 表示用户 i 在时段 t 从电网购入的功率Pch(i,t) 表示共享储能电站给用户 i 充电的功率Pdis(i,t) 表示共享储能电站给用户 i 放电的功率E(t) 表示整个电站电池在时段 t 末的剩余电量E0 为初始电量Erated 为电站额定容量Prated 为电站额定功率。分时电价为 cbuy(t)需量电价为 cdemand电池充放一次的损耗代价系数为 cdeg容量分摊的日成本为 ccap。目标函数可以写成min J sum_i sum_t cbuy(t) * Pgrid(i,t) * dt cdemand * P_peak sum_i sum_t cdeg * (Pch(i,t) Pdis(i,t)) * dt ccap * Erated第一项是电网购电费用第二项是需量费用第三项是电池循环老化成本第四项是储能容量成本和运维成本在日尺度上的分摊。如果只做日前调度、暂不考虑容量优化配置第四项可以暂时拿掉让代码专注于运行策略如果要同时分析容量规模就得把 Erated 也作为决策变量拿进优化框架这就变成了双层问题后面专门讲。2.2 约束条件的线性化处理约束条件我建议分成四组来写这样代码结构不会乱。第一组是每个用户的功率平衡约束。用户在任意时刻的负荷 Load(i,t)要么由电网买电满足要么由储能放电满足同时如果储能处于充电状态则会增加用户的等效用电需求Pgrid(i,t) Pdis(i,t) Load(i,t) Pch(i,t)第二组是充放电功率限制和运行模式互斥约束。同一时刻不能既充电又放电这是储能变流器的物理限制。引入二值变量 u(i,t)u1 表示充电u0 表示放电0 ≤ Pch(i,t) ≤ u(i,t) * Prated 0 ≤ Pdis(i,t) ≤ (1 - u(i,t)) * Prated第三组是电池能量变化约束和 SOC 上下限约束。电池电量变化等于充入能量减去放出能量同时要考虑充放电效率。这里我引入一个容易踩坑的细节E 的下标和优化变量的维度要对应好。如果定义 E 的维度是 T1 而不是 T则 t 时段末的能量 E(t1) 等于上一时段 E(t) 加上本时段充放电净变化E(t1) E(t) (eta_ch * sum_i Pch(i,t) - sum_i Pdis(i,t) / eta_dis) * dtSOC 上下限约束通常写成soc_min * Erated ≤ E(t1) ≤ soc_max * Erated最后还要加上初始终端电量约束。日前调度的典型做法是要求终端电量不低于初始电量否则每天都会把电池“掏空”第二天没得用E(1) E0 E(T1) ≥ E0第四组是共享资源约束。这是“共享”二字最直接的体现。站内所有用户的充电功率之和、放电功率之和都不能超过电站的额定功率sum_i Pch(i,t) ≤ Prated sum_i Pdis(i,t) ≤ Prated如果容量进一步细化每个用户还可以设置各自签约的充电峰值和放电峰值相当于在总功率下面再套一层用户级功率限制代码里多设两个矩阵参数即可。2.3 需要不需要整数变量这里有个技术判断要说明。因为充放电互斥约束必须靠二值变量 u(i,t) 来实现所以模型本质上是一个混合整数线性规划问题。三个用户、24 小时一共就是 3×24 72 个二值变量规模不大Gurobi、CPLEX 这类商业求解器两三秒内就能解完。MATLAB 自带的 intlinprog 也能处理但大一点的问题性能会弱一些。也有人会问能不能不加二进制变量直接用非线性约束去表达互斥理论上可以比如加 Pch * Pdis 0但这就是非线性约束求解器会变成 MINLP求解难度和稳定性都差很多。工程上我建议老老实实用二进制变量加线性化这是目前最可靠、最容易被审稿人和评审接受的做法。MATLAB代码架构从数据输入到调用求解器3.1 整体文件结构与数据准备这份代码我建议按“数据—建模—求解—输出”四段式拆成函数文件别把所有逻辑塞进一个 main 脚本。我自己常用的结构是shared_storage_project/ ├── main_dispatch.m % 主入口调用以下所有函数 ├── load_case_data.m % 读取负荷、电价、电池参数 ├── build_model.m % 用 YALMIP 定义变量、约束和目标 ├── solve_model.m % 调用求解器并返回结果 ├── plot_results.m % 画充放电曲线和 SOC 曲线 └── data/ ├── load_profile.xlsx % 各用户 24 小时负荷 ├── price_profile.xlsx % 分时电价 └── storage_para.xlsx % 电池额定参数数据准备阶段有一个容易忽略的细节单位统一。我的建议是功率全部用 MW能量全部用 MWh电价单位用元/MWh时间步长 dt 用小时。如果你一会儿用 kW、一会儿用 MW最后变量的数值会差出三个数量级求解器判断可行域时很容易出现数值病态这也是后面踩坑里我要专门说的一条。实际加载数据时我用的是 readtable 读取 Excel再转成 double 矩阵。对负荷曲线建议按列存放每一列是一个用户每一行是一个时段这样后续维度关系非常清楚。% load_case_data.m Load table2array(readtable(data/load_profile.xlsx)); price table2array(readtable(data/price_profile.xlsx)); % 期望 Load 是 T x Iprice 是 T x 1 T size(Load, 1); I size(Load, 2); dt 1; % 单位小时3.2 用 YALMIP 搭模型的关键代码我习惯用 YALMIP 建模因为它对变量维度、约束拼接、求解器调用的封装都很方便代码量比直接写 linprog 少很多。核心建模部分长这样% build_model.m P_grid sdpvar(T, I, full); % 电网购电 P_ch sdpvar(T, I, full); % 充电功率 P_dis sdpvar(T, I, full); % 放电功率 E sdpvar(T1, 1, full); % 电池电量维度 T1 u binvar(T, I, full); % 充放电状态变量 P_peak sdpvar(1, 1); % 峰值需量变量 Constraints []; % 功率平衡 Constraints [Constraints, P_grid P_dis Load P_ch]; % 充放电互斥和功率上限 Constraints [Constraints, P_ch u .* P_rated / I 1e-6]; Constraints [Constraints, P_dis (1-u) .* P_rated / I 1e-6]; Constraints [Constraints, P_ch 0, P_dis 0]; % 电池能量转移 Constraints [Constraints, E(2:T1) E(1:T) eta_ch * sum(P_ch, 2) * dt ... - sum(P_dis, 2) / eta_dis * dt]; % SOC 上下限 Constraints [Constraints, soc_min * E_rated E soc_max * E_rated]; % 初始和终端电量 Constraints [Constraints, E(1) E0]; Constraints [Constraints, E(T1) E0]; % 峰值需量约束 Constraints [Constraints, P_peak sum(P_grid, 2)]; % 目标函数 objective sum(price .* sum(P_grid, 2)) * dt c_demand * P_peak ... c_deg * (sum(P_ch(:)) sum(P_dis(:))) * dt ... c_cap * E_rated;上面代码里有一个非常关键的处理用户级充电功率上限我写成了P_rated / I意思是把电站额定功率平均预分配到每个用户。这是“均分共享”的简单假设。如果实际场景中用户签了不同的容量协议你就需要把P_rated / I替换成一个 T×I 的矩阵每个元素是各用户在各时段的可用功率上限。真实工程里我更推荐用后者因为不同用户的负荷特性和付费意愿差异很大均分会造成一部分用户充电受限、另一部分用户的配额被浪费。3.3 求解器选择与参数设置模型搭好之后求解器选择直接决定代码能不能在合理时间内稳定跑出结果。我的建议是优先 Gurobi其次 CPLEX都没有就用 MATLAB 自带的 intlinprog。在 YALMIP 里切换求解器只需改一行 sdpsettings。% solve_model.m ops sdpsettings(solver, gurobi, verbose, 2); ops.mip.tolerances.mipgap 0.01; % 允许1%的次优解大幅加速 ops.mip.maxnodes 50000; % 防止求解器陷入无意义的深度搜索 result optimize(Constraints, objective, ops);关于 mipgap 的设置我多说一句。默认 Gurobi 的 MIP gap 通常设为 1e-4对于 72 个二进制变量的小模型也能轻松完成。但当你把模型扩展到更多用户、更多时段或者额外加入多日联合调度整数变量数量会涨到几百上千1e-4 的 gap 会让求解器一直卡在搜索树里不出来。先放宽到 1%得到次优可行解再逐步收紧这在工程上是非常实用的招法。如果完全没有商业求解器授权直接用 intlinprog 也可以跑。只要把 YALMIP 的 solver 设为intlinprog即可。但要注意 intlinprog 对整数变量规模比较敏感超过两三百个二进制变量时速度会明显下降。对本文这种三用户 24 时段的小案例它完全够用这个我在 4G 内存的笔记本上也实测过。容量优化配置如何从日前调度结果反推储能规模4.1 容量优化到底在优化什么做完整套日前调度后很多人会继续问一个更上层的问题电站额定功率选多大、额定容量选多大才能在满足用户负荷需求的前提下让年化总成本最低这就是容量优化配置。如果只做调度E_rated 和 P_rated 是已知参数但如果要做容量配置它们就变成了决策变量。麻烦之处在于这两个决策变量并不是直接出现在目标函数里那么简单。不同的额定功率决定了每个时段充放电的上限不同的额定容量决定了 SOC 可用的“桶”有多大它们通过调度约束影响每天的可运行方案进而影响日运行成本。调度和容量配置就像一个双层嵌套外层给定容量内层做最优调度算出一个目标值外层再根据这个目标值调整容量直到找到最优。4.2 用双层循环枚举容量方案最简单、也最不容易出错的容量优化方法就是枚举加日内调度。对于工业园区前期规划阶段容量选项往往并不需要做到多精细按 0.5 MW、0.5 MWh 的步长扫一个网格完全够用。核心代码结构如下% main_capacity_optimization.m P_rated_list 0.5:0.25:2.0; % MW E_rated_list 1.0:0.5:4.0; % MWh annual_cost_map zeros(length(P_rated_list), length(E_rated_list)); for p_idx 1:length(P_rated_list) for e_idx 1:length(E_rated_list) P_rated P_rated_list(p_idx); E_rated E_rated_list(e_idx); % 调用日前调度模型得到日运行成本 daily_cost run_dispatch(P_rated, E_rated, Load, price, para); % 年化总成本 日运行成本 * 365 年投资分摊 年运维 annual_cost_map(p_idx, e_idx) daily_cost * 365 inv_cost(P_rated, E_rated); end end投资分摊的计算也要说清楚。假设锂电池系统的单位容量投资单价为 1800 元/kWh功率变换系统单价为 800 元/kW系统使用寿命 10 年年运维成本按初始投资的 2% 计。那么年化投资成本可以简化为inv_cost(P_rated, E_rated) (800 * P_rated * 1000 1800 * E_rated * 1000) * (1 0.02) / 10注意单位换算P_rated 是 MW乘 1000 变成 kWE_rated 乘 1000 变成 kWh。这个函数虽然粗糙但做前期比选足够。真正项目落地时还要考虑电芯更换成本、衰减、保险、场地租金等那是另一个维度的精细工作了。4.3 容量配置结果怎么解读扫完网格后你会得到一张二维的成本表用 contour 或 surf 画出来可以很直观地看到最优容量所在的位置。通常结果会有两个特征一是最优 E_rated 和负荷峰谷差强相关峰谷差越大需要的容量越大二是最优 P_rated 往往比最大负荷小不少因为储能的角色是削峰和平抑波动不需要把全部负荷都扛下来。有一个容易踩的误区是只看单日运行成本最小忘了容量本身的投资成本。如果模型中 c_cap 没有算进运行成本那么容量越大通常越“省电费”因为更大的电池意味着低谷充电、高峰放电的能力更强。但现实是电池容量本身要钱容量配置的收益曲线会在某个点之后出现边际递减甚至转负。我跑出来的很多案例都表明容量增加到一定程度后节省的电费已经撑不起多出来的投资分摊这时候再加大容量就是纯浪费了。算例验证用典型工业负荷曲线检验模型5.1 场景设置与基础数据为了让大家对代码跑出来的结果有个具体概念我构造了一个三用户园区场景。用户 A 是小型铸造厂白天负荷高、夜间低用户 B 是机械加工车间负荷相对平稳用户 C 是建材厂夜间生产为主白天负荷较低。三条负荷曲线互相错峰正好体现共享储能的优势。分时电价我按国内典型的工商业峰谷电价设置高峰时段 08:00-11:00 和 18:00-23:00电价 1.15 元/kWh平段 06:00-07:00 和 11:00-17:00电价 0.75 元/kWh低谷时段 23:00-次日 05:00电价 0.35 元/kWh。需量电价按 42 元/kW/月计。储能采用磷酸铁锂参数充放电效率均为 0.95DOD 上限 0.9初始 SOC 20%最小 SOC 10%。5.2 优化前后对比先用某个中等容量方案P_rated 1.5 MWE_rated 3 MWh跑一次日前调度。对比无储能和共享储能两种模式下的关键指标可以得到类似下面的结果指标无储能共享储能日购电费用元10830097650最大需量MW9.68.3日充放电循环次数-1.1储能利用率-约 71%日成本节约元-10650从结果看共享储能的主要收益来源有两个一是把高峰时段的购电量转移到了低谷时段通过峰谷价差直接省了电费二是把全园区的峰值负荷压低了从而削减了按需量计费的项目。要注意的是这里 10650 元并不是净利润还必须把储能容量分摊成本和运维成本扣掉。如果把 ccap 考虑进去日净节约会小不少这也再次说明为什么容量配置必须和调度联合优化不能只看调度侧的电费减少。5.3 峰谷价差对最优容量配置的影响我还做了一组灵敏度测试把峰谷价差从 0.6 元/kWh 逐步调到 1.2 元/kWh再重新跑双层容量优化。结果显示随着价差增大最优电池容量从 1.5 MWh 左右上升到 3.5 MWh 左右最优功率也从 1.0 MW 上升到 1.75 MW。这个规律符合直觉价差越大储能每充放一度电赚的差价越多更大规模的储能就更容易回本。但有趣的是继续加大价差后容量增长出现了明显的边际递减因为电池的可用放电时段毕竟只有那么多超过需求之后多出来的容量一天只能循环一次甚至不到一次投资回收期会快速拉长。踩坑记录把MATLAB代码从论文模型落地到工程实践6.1 SOC 变量维度搞错电池电量“凭空产生”我最早写这个模型时把电池能量变量定义成E sdpvar(T,1)然后在约束里写E(t1) E(t) ...直接报错 “Index exceeds the number of array elements”。后来改成定义 T1 个变量才解决。这里想提醒大家一个更隐蔽的坑如果你把 E 定义成 T1但只在约束里管了 E(1) 到 E(T)最后 E(T1) 会被漏掉导致模型实际上少了一条终端电量约束电池电量会在一天结束时“凭空产生”或“凭空消失”结果看起来很美实际上完全不可行。我的建议是建模后校验一下 SOC 曲线的首尾值确保 E(T1) 和 E(1) 的差值不超过你设定的终端约束裕度。6.2 二进制变量一多求解器就开始“摆烂”三用户 24 时段只有 72 个二进制变量随便解。但如果你像我一样把模型扩到 10 个用户、96 个时段的多日调度二进制变量会涨到近千个。我遇到过 Gurobi 在默认 MIP gap 下跑了十几分钟还没收敛的情况。后来我做了三件事一是把模型拆成逐日滚动调度避免一次性求解多天二是把 MIP gap 放宽到 2% 甚至 5%工程上完全够用三是给每个用户的功率上限按实际签约容量去定不要统一用一个P_rated/I因为这会导致很多二进制变量在边界上来回试探搜索空间明显变大。6.3 量纲混乱目标函数里惊现“负数成本”还有一次调试经历让我印象很深目标函数算出来居然比无储能成本还低很多我一度以为储能“免费发电”了。查了半天才发现分时电价单位是元/kWh而我代码里的功率单位是 MW结果目标函数里 MW 乘以 元/kWh单位完全对不上。全部改用 MWh 和元/MWh 之后量级才正常。这类问题最坑的地方在于模型不会报错求解器也不会报错只有你盯着结果数字时才会觉得不对劲。所以我后来在代码里强制所有输入数据都经过一个 unit_check 函数把单位逐一转换并打印关键量纲这个习惯帮我省了不少无用功。6.4 日前预测误差太大计划成了纸上谈兵最后说一个模型外的问题。日前调度依赖的就是前一天对负荷和电价的预测但工业负荷常常受订单波动影响真实执行时很容易偏离计划。如果储能按计划充满放完实际负荷却比预测低多余的电量就浪费了。我在测试中把 SOC 下限从 10% 提高到 15%同时给电价高峰前预留一点电池余量相当于在计划层做一个小的安全裕度。更成熟的做法是引入滚动优化比如每 15 分钟重新跑一次短时前瞻但那就是另一个复杂度级别了。对初版代码来说先加 SOC 安全裕度是最简单的保护措施。代码整体跑通之后我最大的感受是共享储能调度本身并不复杂难的是模型假设和实际运营之间的匹配。你用什么方式分配共享容量、怎么计收容量费用、要不要考虑电池衰减都会直接改变最优调度结果和容量配置结论。如果只是复现论文里的公式跑通一个案例并不难但要拿去和园区谈落地还需要把用户契约、预测误差、设备衰减这些“脏活”一点点加回到模型里。建议你把这份 MATLAB 代码先跑通三用户的标准算例再换成自己手里的真实负荷曲线做参数调试这样得到的结果会比任何通用 demo 都有说服力。
返回列表