资讯动态

考虑经济性的储能运行优化:MATLAB线性规划建模与求解

发布时间:2026/10/13 19:46:45 来源:尧图企业网站定制
简介这是一份面向电力系统与储能领域研究者的 MATLAB 开源代码聚焦电价套利背景下储能系统的最优运行优化问题。通过建立以经济效益最大化为目标函数的优化框架并考虑充放电约束、电价波动等关键条件帮助用户在分时电价机制下制定合理的储能调度策略。代码基于两篇国外相关文献的方法提供了三种独立可运行的求解路线简单直接的运行策略搜索、能够处理随机性的蒙特卡洛模拟法以及基于 fmincon 函数的数学规划优化方法。三种方法相互对照便于理解不同思路在收敛性、求解速度与结果精度上的差异也方便使用者按需替换或扩展。压缩包仅约114KB共10个文件以 MATLAB 的 .m 脚本为主5个同时包含电价时间序列文本、spot_price_2013.mat 数据、README 说明文档、许可证等目录精简、结构清晰下载后即可按脚本逐步运行。目前已有973人学习下载适合具有一定 MATLAB 基础、关注储能经济调度或电力市场套利的入门与进阶学习者用于算法复现、对比实验或课题参考。1. 考虑经济性的储能运行优化这套开源 MATLAB 代码解决什么问题把「考虑经济性的储能运行优化」这套开源 MATLAB 代码跑通你会得到一个可以直接复用的调度决策模板。它的典型场景是某园区装了 200 kWh 储能柜分时电价峰谷价差 0.9 元/kWh看着每天倒腾一次就能赚一百多但真跑起来就发现没那么简单——什么时候充、什么时候放、充放多快、要不要留容量给下一个峰段每个决定都直接影响最终收益而充放次数多了还会加速电池老化。这套代码做的事就是把上述决策写成一个线性规划问题用 MATLAB 内置的 linprog 求解。输入只要三样24 小时分时电价、储能电气参数容量、功率、效率、SOC 上下限输出是一天逐小时的充放电功率和 SOC 曲线目标是在全部运行约束下让净收益最大。它不需要额外安装求解器有 MATLAB 基础就能改能跑。适合三类人做储能调度研究的学生拿它当基准结果做对比评估储能项目收益的工程师用它算不同价差下的理论收益上限以及想学优化建模、需要一份干净模板的开发者。下面从建模开始一步步把来龙去脉讲清楚。2. 储能运行优化建模目标函数、约束条件与求解器选型先讲透优化模型动手写代码之前先花十分钟把数学形式写清楚这比调试省时间得多。储能运行优化的标准形式是给定一个调度周期通常 24 小时、步长 1 小时在满足电池自身运行约束的前提下最大化周期内的净收益。核心就三件事目标函数里放什么、约束条件有哪些、用哪类求解器。2.1 目标函数怎么定峰谷套利、老化成本与需量电费的取舍最基础的目标函数是峰谷套利。每个时段的收益等于放电收入减去充电成本写成max Σ ( p_t * q_dis,t - p_t * q_ch,t ) * dt其中 p_t 是该时段电价q_dis,t 是放电功率q_ch,t 是充电功率dt 是步长。这个形式只反映了电费差忽略了一个关键问题电池每放一度电循环寿命都在缩短这部分损耗是真实成本。所以第二个层次是把老化成本折算进目标函数放电收益变成 (p_t - c_deg) * q_dis,t * dt其中 c_deg 是每放电 1 kWh 折算出的电池损耗费用具体折算方法见第 4 章。第三个层次是需量电费管理。国内工商业电价常见两部制除了电度电费还有按最大需量计收的基本电费。储能削减峰值负荷能直接省下这部分钱目标函数里就要加入「需量电费单价 × 削减的峰值功率」这一项。但注意这一项会让模型从纯线性变成带 max 算子的分段线性问题通常需要用 epigraph 变换或引入辅助变量才能保持线性。对绝大多数入门场景来说先做前两个层次就够了套利加老化成本。第三个层次建议在基础版本跑通后再扩展否则一边学模型一边处理 max 算子的重构很容易卡住。2.2 约束条件怎么写SOC 递推、功率边界与充放电互斥约束条件里最重要的一条是 SOC 递推方程它描述电池能量随时间的变化SOC(t1) SOC(t) [ eta_ch * q_ch,t - q_dis,t / eta_dis ] * dt / E_ratedeta_ch 和 eta_dis 分别是充放电效率E_rated 是额定容量。这条约束把相邻时段的 SOC 串起来是整个模型的骨架。除了递推方程还有几类边界约束SOC 上下限比如 0.1 到 0.9防止过充过放、充放电功率上下限0 到 P_max、初始 SOC 固定、以及终端 SOC 约束——要求周期结束时回到初始值避免优化器把初始存电当免费资源一次性放空。还有一个容易纠结的点充放电互斥约束 q_ch * q_dis 0。这是非线性约束但绝大多数情况下可以安全丢弃。原因是只要充电和放电发生在同一时段能量先充进去再放出来要经过一次充放效率损耗0.95 × 0.95 ≈ 0.9白白损失 10% 能量电价是正的这种自我抵消的行为永远不可能是最优解。省掉这个约束问题就从非线性规划退化成纯线性规划求解速度和稳定性都大幅提升。只有当模型里加了最小充放电功率、启停成本这类条款时才需要引入 0-1 二进制变量重新变成 MILP。2.3 为什么用线性规划linprog 与 YALMIP/启发式算法的边界储能价格套利问题本质上是 LP目标线性、约束线性、变量连续。线性规划的优点是能保证全局最优、求解可复现、速度快不存在启发式算法那种每次运行结果不稳定、需要反复调参数的玄学。常见做法是直接用 MATLAB 内置 linprog或者用 YALMIP 搭模型再调用底层求解器。YALMIP 的好处是建模语法接近数学表达式后续要加二进制变量、做 MILP 扩展时不用重写整套矩阵。三者的选择边界大致如下求解方案能处理的模型获取难度适用场景MATLAB linprog连续变量 LP内置 Optimization Toolbox纯价格套利、无二进制变量的场景YALMIP GLPK/SCSLP/MILP免费求解器需下载并配置路径学生做原型、验证算法YALMIP Gurobi/CPLEX大规模 LP/MILP商业授权或学术授权高维问题、强耦合约束经验是先别急着上 YALMIP 和商业求解器。用 linprog 把纯 LP 版本跑通确认模型逻辑没问题再按需升级。很多人一上来就用遗传算法之类的启发式方法反而把简单问题复杂化——非线性效率曲线、滞回特性这类模型才值得动用启发式线性能解的问题用线性方法拿全局最优解才是正路。3. 用 MATLAB 跑通储能经济性优化linprog 建模代码逐段拆解模型立住之后代码就是把数学翻译成矩阵。下面用一组常见参数做演示200 kWh / 50 kW 储能系统充放电效率 0.95SOC 范围 0.10.9三段式分时电价。代码按数据准备、模型构建、结果处理三块走。3.1 参数与分时电价数据准备一份可直接改的输入模板%% 储能参数与分时电价定义 clc; clear; close all; E_rated 200; % 额定容量/kWh P_max 50; % 最大充放电功率/kW SOC_init 0.5; % 初始荷电状态 SOC_min 0.1; % SOC 下限 SOC_max 0.9; % SOC 上限 eta_ch 0.95; % 充电效率 eta_dis 0.95; % 放电效率 dt 1; % 时间步长/h % 24小时分时电价单位元/kWh % 0-7点谷段0.38-17点平段0.818-21点峰段1.222-23点平段0.8 price [0.3*ones(1,8), 0.8*ones(1,10), 1.2*ones(1,4), 0.8*ones(1,2)]; N 24; % 优化周期内时段数这里几个要点price 向量必须按时间顺序排列且长度等于 N效率值用的是小数而不是百分数dt 的单位要和电价单位匹配电价是元/kWh、功率是 kW乘上 dt 小时得到元。如果要做多日连续仿真把 price 向量按天拼接即可但要注意 SOC 会在日与日之间传递不在本章范围第 6 章会讲滚动处理。3.2 核心建模与求解代码决策变量、约束矩阵与 linprog 调用%% 决策变量布局 % x [q_ch(1..N), q_dis(1..N), SOC(1..N1)] % SOC(1)是第1时段开始时SOCSOC(N1)是周期结束时SOC n_var 3*N 1; % 目标函数linprog 求最小值所以充电成本为正、放电收益取负 f zeros(n_var, 1); f(1:N) price * dt; % 充电花费 f(N1:2*N) -price * dt; % 放电收入取负号 %% 等式约束SOC 递推 % SOC(t1) SOC(t) eta_ch*q_ch(t)*dt/E_rated - q_dis(t)*dt/(E_rated*eta_dis) Aeq zeros(N, n_var); beq zeros(N, 1); for t 1:N Aeq(t,t) eta_ch * dt / E_rated; % q_ch(t) 系数 Aeq(t,Nt) -dt / (E_rated * eta_dis); % q_dis(t) 系数 Aeq(t,2*Nt) -1; % -SOC(t) Aeq(t,2*Nt1) 1; % SOC(t1) end % 初始 SOC 固定为 SOC_init Aeq [Aeq; zeros(1, n_var)]; Aeq(end, 2*N1) 1; beq [beq; SOC_init]; % 终端 SOC 约束周期结束回到初始值 Aeq [Aeq; zeros(1, n_var)]; Aeq(end, 3*N1) 1; beq [beq; SOC_init]; %% 变量边界 lb zeros(n_var, 1); ub inf(n_var, 1); lb(1:2*N) 0; ub(1:2*N) P_max; % 充放电功率边界 lb(2*N1:end) SOC_min; ub(2*N1:end) SOC_max; % SOC 边界 lb(2*N1) SOC_init; ub(2*N1) SOC_init; % 初始 SOC 双边界固定 %% 求解 options optimoptions(linprog,Algorithm,dual-simplex,Display,iter); [x_opt, fval, exitflag] linprog(f, [], [], Aeq, beq, lb, ub, options);逐段说明一下。决策变量把充电功率、放电功率、SOC 排成一个长向量长度是 3N1。SOC 多一个点是用来表示周期结束状态的这个布局决定了后面所有矩阵的列索引改的时候要非常小心。目标函数里充电对应正系数因为 linprog 是最小化放电对应负系数最小化负的收益等价于最大化收益。SOC 变量的系数是 0它本身没有直接成本。SOC 递推约束用循环逐时段生成每个时段一行等式系数来自递推公式的移项。这里最容易犯的错是 eta_dis 写错位置——它是放在放电功率的分母上不是乘在分子上写反了结果会差一个效率的平方。最后两个等式分别固定初始 SOC 和终端 SOC。终端 SOC 约束是防止优化器把初始电量当免费午餐的后悔药如果去掉典型结果会是最后一个峰段把电池放到 SOC 下限第二天无电可用。提示初次调试时把Display设为iter能看到单纯形迭代过程确认模型稳定后再改回final减少刷屏。3.3 结果提取与绘图充放电曲线、SOC 曲线与收益统计%% 结果提取 q_ch x_opt(1:N); % 各时段充电功率/kW q_dis x_opt(N1:2*N); % 各时段放电功率/kW SOC x_opt(2*N1:end); % SOC序列长度 N1 profit -fval; % 净收益 -目标函数值 E_ch sum(q_ch) * dt; % 总充电电量/kWh E_dis sum(q_dis) * dt; % 总放电电量/kWh fprintf(净收益: %.2f 元\n, profit); fprintf(充电电量: %.2f kWh, 放电电量: %.2f kWh\n, E_ch, E_dis); %% 绘图功率、SOC、电价三联图 t 1:N; figure(Color,w,Position,[100 100 900 650]); subplot(3,1,1); bar(t, q_ch, 0.6, FaceColor,[0.2 0.5 1]); hold on; bar(t, -q_dis, 0.6, FaceColor,[1 0.6 0.2]); legend(充电,放电,Location,best); ylabel(功率/kW); ylim([-1.2*P_max 1.2*P_max]); xlim([0.5 N0.5]); subplot(3,1,2); stairs([t, N1], SOC, LineWidth,1.5); ylabel(SOC); ylim([0 1]); xlim([0.5 N0.5]); grid on; subplot(3,1,3); stairs(t, price, k, LineWidth,1.5); ylabel(电价/(元/kWh)); xlabel(时间/h); xlim([0.5 N0.5]); grid on;结果提取的关键是理解变量索引x_opt 里前 N 个是充电功率接着 N 个是放电功率最后 N1 个是 SOC。profit 取 -fval因为 linprog 最小化的目标函数值本身是负的净收益。绘图时把放电功率画成负值这样一眼能看出充放时段和电价的对应关系。正常的优化结果应该是谷段充电功率为正、SOC 抬升峰段放电功率为负、SOC 回落平段基本不动。如果看到谷段不充、峰段不放或者功率在某个平台期反复跳动就是第 5 章要讲的典型问题。4. 参数敏感性分析容量配比、老化成本与电价结构怎么影响收益代码跑通只是第一步真正决定这套优化有没有价值的是参数。同一个模型换一组容量配比或电价数据结论可能完全相反。这一章把三个最影响结果的参数讲透。4.1 容量与功率配比C-rate决定策略可行域P_max 与 E_rated 的比值就是 C-rate。C-rate 0.25 意味着满功率充放电需要 4 小时0.5C 需要 2 小时。这个指标直接决定电池能不能在谷段窗口内充满。以本文参数为例谷段持续 8 小时50 kW 对 200 kWh 是 0.25C理论上 4 小时就能从空充到满谷段绰绰有余。但如果谷段只有 2 小时0.25C 系统只能充 100 kWh浪费一半容量这时把 P_max 提到 100 kW0.5C才能吃满谷段电量。反过来P_max 也不是越大越好——变流器成本随功率上升而且功率过大时充放电过程更激进对电池寿命的影响在老化成本模型里会进一步放大。C-rate满充时间对 2 小时谷段的适配0.25C4 小时只能充一半容量收益受限0.5C2 小时刚好充满1C1 小时能充满但变流器成本高、寿命压力大调参时建议固定 E_rated扫描 P_max 看净收益曲线。收益随 P_max 上升会先快速增长、后趋于饱和饱和点就是经济上最优的功率配比。曲线拐点附近再叠加变流器成本才是真正的工程最优解。4.2 循环寿命与老化成本折算目标函数里最容易被忽略的一项老化成本折算的常见做法是线性折旧把电池总购置成本平摊到全生命周期放电量上。c_deg 电池单位容量成本 / (循环寿命 * 放电深度)假设电池 500 元/kWh循环寿命 4000 次、按 80% 放电深度计则 c_deg 500 / (4000 × 0.8) ≈ 0.156 元/kWh也就是每放 1 kWh 电要计提约 0.16 元损耗。不同循环寿命下的折算值如下循环寿命80% DoDc_deg元/kWh 放电2000 次0.314000 次0.166000 次0.10加入 c_deg 之后目标函数里放电收益从 p_t 变成 p_t - c_deg。这个改动会真实改变优化结果某些价差小、原本勉强可套利的时段扣掉老化成本后变成亏损优化器会主动放弃在那段时间放电。这是模型从「看起来赚钱」走向「真实赚钱」的关键一步。注意线性折旧是个简化模型实际电池老化与放电深度、倍率、温度呈非线性关系。做方案对比用线性折算足够做精确寿命预测则需要换用雨流计数或电化学老化模型那个就要上 MILP 或启发式算法了。4.3 电价结构与盈亏平衡价差先算门槛再调参判断一个场景值不值得做储能套利先算盈亏平衡价差不用跑代码p_peak_min p_valley / (eta_ch * eta_dis) c_deg以谷价 0.3 元/kWh、双效率 0.95、c_deg 0.156 为例p_peak_min 0.3 / 0.9025 0.156 ≈ 0.49 元/kWh。也就是说峰段电价超过 0.49 元时才有正收益超过越多赚得越多。如果峰价只有 0.5 元每度电毛利不到一分钱扣掉运维成本基本白干如果把效率换成 0.9门槛直接升到 0.53。电价结构里还有一个常被忽略的因素是谷段时长。谷段只有 2 小时而 C-rate 是 0.25C 时电池充不满实际套利电量只有理论的一半。所以评估一个项目时不能只看峰谷价差还要看「价差 × 可套利电量 × 年循环次数」三者乘积。这个视角能解释为什么同一套代码换到某些省份的电价数据后收益大幅缩水——不是代码错了是电价结构根本不匹配。5. 储能运行优化避坑指南MATLAB 求解中的五个常见问题排查理论和代码都讲完了下面是实际调试中踩过的坑按「现象 → 原因 → 解决」写每条都是血泪经验照着排查能省好几个晚上。5.1 linprog 报错与求解器路径问题现象运行到 linprog 那行报Undefined function or variable linprog或者optimoptions直接报错。原因MATLAB 没装 Optimization Toolbox。linprog 不是基础函数它属于工具箱optimoptions 是 R2013b 之后引入的写法老版本也不认。解决先在命令行执行ver(optim)如果显示未安装需要安装对应工具箱。确认装了但还是报错检查是不是用了老版本语法老版本用optimset而不是optimoptions。如果不想装工具箱就改用 YALMIP 加免费求解器 GLPK建模代码要相应改成 YALMIP 语法。5.2 充放电曲线震荡退化最优解的判定与处理现象结果里相邻时段功率剧烈交替比如 19 点放 50 kW、20 点充 50 kW、21 点又放SOC 曲线像锯齿。目标函数值看着正常但曲线完全不像能实际执行的策略。原因电价平台期存在退化解。LP 保证目标函数最优但不保证解唯一当多个方案取得相同收益时单纯形法返回的顶点解可能毫无规律。常见于峰段与峰段之间、平段与谷段边界处。解决最干净的方案是在目标函数里加切换惩罚项用辅助变量把相邻时段功率差值的绝对值计入目标惩罚系数取一个很小的值比如 1e-3 量级既不影响总收益又能压住震荡。工程上更快的做法是如果震荡幅度不大直接对结果做后处理平滑再重新核算目标函数值如果收益损失在 1% 以内说明震荡只是退化解平滑后的策略可以放心用。记住先算收益差再决定要不要平滑别凭感觉改。5.3 终端 SOC 失控优化结果把电池放空的后悔药现象结果里最后一个峰段电池被放到 SOC 下限曲线看起来很「完美」地把每一度电都赚干净了但第二天这个策略根本没法继续执行——电池没电了。原因没加终端 SOC 约束。初始 SOC 是给定的、免费的优化器会把它当成可消耗资源在最后一个高价时段全部变现。解决在等式约束里加上 SOC(N1) SOC_init。如果做的是滚动优化每一步的初始 SOC 来自实测不需要强绑终端值但至少加一个 SOC(N1) ≥ SOC_min 的下限约束兜底。这个坑极其隐蔽因为单日结果看起来完全正常要连续跑多日才会暴露。5.4 最优收益为负先查数据再查模型现象linprog 正常返回exitflag 是 1但净收益算出来是负的。原因要么电价数据有问题要么这个场景本身就不经济。常见数据错误包括峰谷价差写反、电价单位不一致比如误用元/MWh、效率参数填成 0.95 但实际想表达 95% 却写成了 95。解决先用第 4.3 节的盈亏平衡公式手算一遍门槛价看峰段电价是否高于门槛。如果高于门槛但结果还是负逐个检查效率、电价单位、老化成本取值。如果确实低于门槛那负收益就是正确结论——说明这个场景不该上储能套利模型给你省了一笔投资这本身就是价值。5.5 单位不一致导致的数值翻车现象收益大得离谱比如 200 kWh 的电池一天赚了几万块或者 SOC 曲线乱跳、约束怎么调都矛盾。原因单位混用了——容量用 MWh、功率用 kW、电价用元/MWh导致约束矩阵里各项数量级差好几个量级数值病态。解决统一到 kW / kWh / 元/kWh 三个基础单位。改完数据后在模型构建前加一句打印核对各数组的最大最小值尤其是 E_rated 和 P_max 的比例关系。单位错误这种事排查半天往往最后发现就是一处分母写错属于最冤的翻车方式。6. 进阶从单日优化到滚动优化与规则策略对比验证6.1 滚动优化只执行第一步每小时重算单日优化适合算理论收益上限但真拿去运行会有两个问题一是电价预测不可能完全准二是实际执行的充放电量和计划总会有偏差SOC 越偏越远。常见做法是把单日求解封装成函数然后用滚动优化的框架循环调用每个小时基于当前实测 SOC 和未来一段时间的电价预测重新求解一次但只执行第一步。%% 滚动优化主循环 T_total 24 * 7; % 仿真一周 H 24; % 优化窗口长度 soc_now SOC_init; result_q_ch zeros(T_total, 1); result_q_dis zeros(T_total, 1); for k 1:T_total % 取未来 H 小时电价此处用真实电价代替预测 idx mod((k-1:kH-2), 24) 1; price_fc price(idx); % 调用封装好的单次优化函数 [q_ch, q_dis, ~] solve_storage_one_day(price_fc, E_rated, P_max, ... soc_now, SOC_min, SOC_max, eta_ch, eta_dis, dt); % 只执行第一步 result_q_ch(k) q_ch(1); result_q_dis(k) q_dis(1); % 按实际充放电量更新 SOC soc_now soc_now (eta_ch * q_ch(1) - q_dis(1) / eta_dis) * dt / E_rated; end这一步的意义在于让优化永远从真实状态出发。单日优化里终端 SOC 约束导致的可调度容量损失在滚动框架下自然消失——每天每步都在重新初始化。窗口长度 H 的选取有个折中太短看不到完整谷峰周期太长对预测精度要求高且求解规模变大我一般取 24 小时。6.2 与规则策略对比用基准线验证优化增益滚动优化跑出来的结果好不好看需要跟一个规则基准对比。最简单的规则策略是「谷段充满、峰段放完」谷段按最大功率充电峰段按最大功率放电其他时段不动。把同一组电价和参数喂给规则策略算出收益再和优化结果对比。两者的差值就是优化带来的增益。如果增益很小说明电价结构太简单、规则策略已经接近最优如果增益明显说明复杂电价结构下优化确实值得。这个对比在项目汇报里特别有用——评审不会只看绝对收益更关心你比拍脑袋策略多赚了多少。我最早跑这个题目时没加终端 SOC 约束结果每日收益虚高跟规则策略一对比才发现自己把初始存电免费放掉了。后来把约束补齐、单位统一收益数字才真正经得起复核。这类代码模型写简单一点没关系边界条件和单位才是真正翻车的地方。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑