分布式储能容量配置优化:产销者场景下的Matlab建模与实战 📅 发布时间:2026/9/16 22:21:36 👁 浏览次数: 上个月有位做光伏代理的朋友带着一年电费单来找我。他家装了8kW光伏白天发出来的电一大半以不到四毛钱的上网电价卖给电网晚上再以一块多的峰时电价买回来一年算下来电费没省多少。他想加一组电池开口就问“你说配多少度合适”这个问题看起来是随口一问实际上是一道非常典型的产销者分布式储能容量配置问题。要回答它不能靠感觉得把负荷曲线、光伏出力、分时电价、电池成本和寿命放在同一个优化框架里算。这篇文章就把我在这类项目里的建模思路、Matlab代码结构和实际调试中踩过的坑完整写一遍适合正在做储能规划、研究产销者/产消者优化调度或者想用Matlab复现同类论文结果的读者。即使你只是刚接触“分布式储能”这个概念读完也会明白为什么别人说“配大了亏、配小了不解决问题”。1. 为什么“产销者”会让储能配置变成一道优化题1.1 产销者与普通用户的本质区别传统用户只是负荷每天从电网买电储能的价值就是低买高卖看看峰谷价差就够了。但产销者也叫产消者、prosumer指的是既消耗电能又生产电能的用户不是这样家里有光伏之后发出来的电首先要满足自家负荷用不完的可以卖给电网不够了再从电网买。这么一来储能同时承担了三个角色削峰填谷的套利工具、光伏自用比例提升器、电网交互缓冲。价值计算逻辑也跟着变了。举个最简单的例子白天光伏出力大、家里没人多出来的电只能上网拿0.4元的结算价晚上回家用电峰时购电价1.0元。这一进一出1度电的净成本是0.6元。储能如果能把中午的1度电留到晚上用就等于从这0.6元里把钱省下来。这个“购电价减去上网电价”的差值才是产销者场景下储能真实的经济价值来源。很多初次做这个问题的同学会把储能当成纯粹的“电表搬弄工具”只套峰谷价差结果算出来的收益和实际运行情况对不上。问题就在于你没有把光伏发电和负荷之间的时间错配放到模型里。1.2 只看峰谷价差配储为什么容易翻车如果只盯着电价表很容易得出一个简单粗暴的结论峰谷价差越大储能装得越多越好。但在产销者模型里价格只是半边信息另外半边是“光伏出力和负荷在时间上有多大错配”。我遇到过两个用户峰谷电价完全一样A用户白天家里没人光伏大发时负荷不到1kW晚上回来负荷飙到5kWB用户开着小作坊白天负荷5kW以上光伏发多少就用多少晚上负荷很小。同样一段电价曲线A用户配8kWh储能能把自用率从30%提到60%B用户配2kWh都嫌多。如果只按“峰值负荷乘一个比例”去配两个用户会得到几乎相同的容量建议这显然是错的。这里面真正起作用的指标是“可转移电量”一天里有多少光伏发电是被迫低价上网、同时又有多少高价购电可以用储能替代。这个量是由负荷和光伏的时序耦合关系决定的而不是由电价表单独决定的。所以我在项目里从来不用经验系数定容量一定会把全年逐时数据拉出来算。1.3 目标函数没定好配置结果就没有意义同样一组数据你优化的目标不一样算出来的最优容量差异很大。我见过三类常见目标年化总成本最小项目方最常用把储能投资换算成等年值加到每年电费上找总成本最低点。全生命周期净现值NPV最大化考虑资金时间价值把未来每年节省的收益折现再减去初始投资。这也是学术论文里最常见的写法。自给率或自用率最大化某些政策考核或绿电评价场景会这么定目标但它不一定经济最优。我在做实际项目时一律优先看NPV因为它把投资、收益、时间价值拉到同一把尺子上。自给率可以作为辅助输出但绝不能当目标函数用——否则模型会鼓励你装一堆根本回不了本的电池只为了让“光伏自用比例”从60%变成80%而这20个百分点的代价可能远超电费节省。2. 把容量配置问题写成可求解的数学模型2.1 决策变量不止“装多少度电”一个数储能容量配置听起来只有“容量”一个变量但严谨的模型里决策变量其实是两批投资层面的决策变量额定容量E_ratedkWh、额定功率P_ratedkW。前者决定能存多少电后者决定一小时能充放多少电。运行层面的决策变量每个时段t的充电功率P_ch(t)、放电功率P_dis(t)、从电网购电功率P_gm(t)、向电网售电功率P_gs(t)以及荷电状态SOC(t)。如果只优化E_rated和P_rated而不优化运行策略那么收益计算时只能硬套一个固定的充放电规则结果会低估储能的灵活性价值。严格的做法是容量寻优在外层运行寻优在内层两层嵌套。这个结构后面代码部分会细说。2.2 目标函数从年电费节省到全生命周期净现值以NPV最大化为目标可以写成这样NPV(E_rated, P_rated) Σ_{n1..N} (annual_profit - OM) / (1r)^n - (c_E * E_rated c_P * P_rated)其中annual_profit是配置储能后每年节省的电费也就是“无储能基准电费”减去“有储能电费”annual_profit Σ_d w_d * Σ_t [ (price_b(t)*P_gm_base(t) - price_s(t)*P_gs_base(t)) - (price_b(t)*P_gm(t) - price_s(t)*P_gs(t)) ] * Δt这里w_d是代表日的权重price_b是购电价price_s是上网电价下标base表示不装储能时的基准值。这样一来储能的价值来源就非常清楚只有相对基准场景真正减少的购电成本或增加的售电收益才算数。注意储能投资不是一次性把利润全部扣掉就完了。工程上建议把c_EE_rated c_PP_rated作为初始投资再按折现率r和项目周期N把未来收益折现。如果项目周期短于电池寿命按项目周期算如果电池寿命短于项目周期就得在周期内考虑更换电池或容量衰减这点后面会专门讲。2.3 约束条件功率平衡、SOC递推和互斥约束把产销者的物理约束写全其实是这份代码的核心。最基本的约束包括功率平衡每个时刻光伏出力加储能放电加购电必须等于负荷加储能充电加售电 pv(t) P_dis(t) P_gm(t) load(t) P_ch(t) P_gs(t)充放电功率限制 0 ≤ P_ch(t) ≤ P_rated * u(t) 0 ≤ P_dis(t) ≤ P_rated * v(t) 其中u、v是0-1状态变量uv≤1表示不能同时充放电。购售电互斥P_gm(t)和P_gs(t)不能同时为正否则模型会钻空子出现“从电网低价买电、再把光伏高价卖回电网”的假套利。SOC递推 SOC(t1) SOC(t) (η_ch * P_ch(t) - P_dis(t)/η_dis) * Δt / E_rated η_ch和η_dis分别是充、放电效率。SOC边界通常取0.1到0.9之间实际项目中为了保护电池寿命不太会跑到0和1。初始和终止SOC每个调度周期开始和结束时SOC必须相同比如都等于0.2否则跨周期的能量平衡就不成立。这些约束看着多但本质上就是把产销者物理世界的规则翻译成数学语言。如果哪条约束漏了优化结果一定会在真实场景里露馅后面坑的部分会具体举例。2.4 全年8760小时怎么压缩成可计算的代表日最理想的情况是用全年8760小时逐时数据直接算。数据量对单产销者来说其实不大一次复现几秒钟能跑完。但如果做多产销者、共享储能或者随机优化8760小时会让计算时间爆炸这时候就得压缩成代表日。我常用的做法是用Matlab的kmeans函数把全年的“负荷-光伏”二维曲线聚成4到12类取每一类的质心作为代表日曲线权重是这一类天数除以365。聚类的特征可以只用负荷和光伏的逐时序列也可以加温度、节假日等外生变量。聚类之后储能运行优化只需要在每个代表日上跑一遍再按权重叠加成年收益。这里有个前提分时电价如果在不同季节有不同表聚类时要按电价表把典型日分组否则会把两种电价制度下的日子混在一起算出的收益就乱了。3. Matlab实现内层调度与外层寻优3.1 整体框架两层嵌套先算运行再算收益容量配置问题在结构上天然是两层外层是容量变量内层是运行变量。外层每给定一组(E_rated, P_rated)就把这一组容量带入内层求解产销者在典型日里的最优充放电策略得到年收益然后计算NPV。外层把所有候选容量都试一遍找出NPV最大的一组。这个嵌套结构也符合工程直觉先规划容量再优化运行运行结果反哺规划决策。如果不用嵌套而是直接把容量和运行变量扔进一个大优化问题问题规模和求解难度都会明显增加而且二进制互斥变量一多普通非线性求解器根本扛不住。3.2 内层调度规则调度与优化调度怎么选内层调度有两种做法-规则调度写一套固定逻辑比如“光伏盈余先充电峰时电价再放电”。代码简单运行快逻辑透明适合方案初筛和可行性分析。缺点是无法保证最优尤其在电价结构复杂、负荷波动大的场景里会低估储能价值。优化调度把充放电看成小规模线性规划或混合整数线性规划用求解器算出给定容量下的最优运行方式。这才是论文级的严谨做法。Matlab里实现优化调度最简单的是用YALMIP工具箱搭模型求解器可以用自带的intlinprog也可以接Gurobi或CPLEX。我个人的习惯是两套都写先用规则调度快速估算再用MILP精算校验。如果两者结果差距在10%以内说明规则调度写得不错如果差距很大优先怀疑规则调度里某个边界条件写错了而不是怀疑MILP有bug。3.3 核心代码骨架枚举寻优MILP调度下面给一个可直接参考的骨架外层用网格枚举内层用YALMIP做MILP调度。网格枚举虽然“笨”但胜在稳定特别适合分析容量和功率两个变量还能直接画出NPV热力图。clear; clc; rng(1); % 载入数据load(8760,1) 为全年逐时负荷pv(8760,1) 为全年逐时光伏出力 load load_pv.mat; % 参数 para.r 0.06; % 折现率 para.N 10; % 项目周期年 para.c_E 900; % 储能容量单价元/kWh para.c_P 500; % PCS功率单价元/kW para.eta_ch 0.95; % 充电效率 para.eta_dis 0.95; % 放电效率 para.SOC_min 0.1; para.SOC_max 0.9; % 分时电价单位元/kWh假定全年不变 price_b ones(24,1) * 0.6; price_b(7:10) 1.0; price_b(18:21) 1.0; price_b(22:23) 0.3; price_b(1:6) 0.3; price_s ones(24,1) * 0.4; % 代表日选取实际项目用kmeans聚类这里简化为按月平均 [load_day, pv_day, weight_day] monthly_typical_days(load, pv); % 外层枚举容量和功率 E_list 0:0.5:16; % 0~16 kWh P_list 0:0.5:8; % 0~8 kW NPV zeros(length(E_list), length(P_list)); for ii 1:length(E_list) for jj 1:length(P_list) E E_list(ii); P P_list(jj); annual_profit 0; for d 1:length(weight_day) % 无储能基准电费 [base_cost, ~] baseline_cost(pv_day{d}, load_day{d}, price_b, price_s); % 有储能最优日电费 [opt_cost, ~] milp_daily_cost(E, P, pv_day{d}, load_day{d}, price_b, price_s, para); annual_profit annual_profit weight_day(d) * (base_cost - opt_cost); end inv_cost para.c_E * E para.c_P * P; NPV(ii,jj) annual_profit / annuity_factor(para.r, para.N) - inv_cost; end end内层MILP的核心函数像这样function [daily_cost, SOC] milp_daily_cost(E, P, pv, load, price_b, price_s, para) dt 1; T length(load); Pc sdpvar(T,1); % 充电功率 Pd sdpvar(T,1); % 放电功率 Pg sdpvar(T,1); % 购电功率 Ps sdpvar(T,1); % 售电功率 SOC sdpvar(T1,1); uc binvar(T,1); % 充电状态 ud binvar(T,1); % 放电状态 cons []; cons [cons, Pc 0, Pc P * uc]; cons [cons, Pd 0, Pd P * ud]; cons [cons, uc ud 1]; cons [cons, Pg 0, Ps 0]; cons [cons, SOC para.SOC_min, SOC para.SOC_max]; cons [cons, SOC(1) 0.2, SOC(end) 0.2]; for t 1:T cons [cons, pv(t) Pd(t) Pg(t) load(t) Pc(t) Ps(t)]; cons [cons, SOC(t1) SOC(t) ... (para.eta_ch * Pc(t) - Pd(t) / para.eta_dis) * dt / E]; end obj sum(price_b .* Pg - price_s .* Ps) * dt; ops sdpsettings(solver, gurobi, verbose, 0); optimize(cons, obj, ops); daily_cost value(obj); end这套骨架有两个地方建议你按自己数据改一是代表日选取我上面写了monthly_typical_days实际可以换成kmeans聚类二是求解器如果没装Gurobi把sdpsettings(solver,gurobi,...)改成intlinprog即可YALMIP会自动适配。3.4 求解器和可视化枚举、ga还是fmincon外层寻优方法的选择直接关系到计算稳定性和复现难度简单对比一下环节常用方法注意事项外层容量寻优枚举/网格搜索变量少、需要画图分析时最稳外层容量寻优ga遗传算法变量多时收敛快但结果有随机性外层容量寻优fmincon目标里嵌了整数规划梯度难求容易局部收敛慎用内层调度规则调度适合初筛结果不一定最优内层调度linprog去掉二进制互斥后可用适合纯线性运行问题内层调度YALMIPMILP严格处理互斥约束有Gurobi/CPLEX时推荐我个人最推荐枚举加热力图。容量和功率只占两个维度枚举就能得到完整的NPV曲面一眼看出最优区域在哪、收益对哪个变量更敏感。画图也方便figure; surf(P_list, E_list, NPV); xlabel(额定功率/kW); ylabel(额定容量/kWh); zlabel(净现值/元); colorbar;如果以后扩展到多产销者、多储能站点决策变量变多再切到ga或者用启发式算法那时候仍然可以用枚举结果做交叉验证。4. 典型算例最优容量结果与敏感性分析4.1 算例参数负荷、光伏、电价与储能成本为一个含光伏5kWp的居民产销者做容量配置基础参数如下参数数值年用电量5600 kWh光伏装机5 kWp年发电量约5200 kWh峰时电价7-22点中的峰段1.0 元/kWh平时电价0.6 元/kWh谷时电价0.3 元/kWh上网电价0.4 元/kWh储能容量单价900 元/kWhPCS功率单价500 元/kW充/放电效率0.95 / 0.95项目周期10年折现率6%这些参数是实际项目中比较典型的一组不代表某个具体地区但趋势是可信的。4.2 基准场景8kWh/4kW是最优解把上述参数带进程序最优配置落在8kWh、4kW附近。不配储能时这位产销者的年净电费大约1400元配到8kWh/4kW后年净电费降为约90元等效年节省约1310元。初始投资是8900 4500 9200元静态回收期约7年10年折现NPV约410元属于“能挣但很辛苦”的项目。从SOC曲线看典型的工作日里电池上午用光伏盈余充电傍晚峰时放电晚谷又不太动基本符合预期。光伏自用率从34%提升到61%说明储能最大的贡献不是套利而是把原本低价上网的光伏电量搬到了高价时段自用。4.3 敏感性分析电池价格和峰谷价差的影响用同一套代码做几组敏感性结果挺有信息量场景最优容量(kWh)最优功率(kW)年节省(元)回收期(年)NPV(元)基准900元/kWh峰1.08413107.0410电池降价到600元/kWh11516005.43170电池涨价到1300元/kWh000—0峰谷价差扩大峰1.210517506.61380光伏扩容到8kWp12619507.1550这组结果说明三件事。第一最优容量对电池成本非常敏感。电池从900降到600元/kWh最优容量从8跳到11kWhNPV从勉强为正变成显著为正。反过来电池成本一高最优解直接就变成“不配储能”这不是模型报错而是现实里很多项目测算出来的真实结论。第二峰谷价差拉大后储能容量和收益同步上涨。这说明电价机制是比电池成本更根本的驱动力。研究产能者问题时如果当地电价机制不允许储能获利容量配置做得再精细也是白搭。第三光伏扩容后最优容量会涨但涨幅和收益涨幅没那么夸张。原因是可转移电量受负荷端限制光伏再大负荷晚上用不了那么多储能装多了也只能闲置。这条一定要在项目汇报里说清楚不然投资人会以为“光伏加一倍储能也该加一倍”。5. 我踩过的坑和排查思路5.1 初始SOC和终止SOC的陷阱我第一次跑MILP的时候设置了SOC(1)0但没有约束SOC(end)0.2结果优化出来的SOC曲线最后几小时急剧下降收益高得离谱。原因很简单求解器发现最后时段有负荷干脆把所有剩余电量在最后一小时全部放掉甚至不惜在最后阶段卖电。这种“末日式放电”在单日优化里几乎必然出现。解决办法是强制SOC(1)SOC(T1)最好设成0.2而不是0因为0附近电池内阻大、效率差实际项目也不会真放空。另外如果用的是代表日加权注意每个代表日都要满足这个首尾一致约束否则各个代表日的调度策略无法在真实连续运行中衔接。5.2 充放电同时出现LP的“假循环”线性规划里如果没有二进制互斥约束很容易出现同一时刻P_ch和P_dis都大于0或者P_gm和P_gs都大于0的“假循环”。表面上看充电的0.1kW和放电的0.1kW在功率平衡里互相抵消目标函数不受影响但物理上电池不可能一边充一边放。解决思路有两条。一是严格加二进制变量用MILP求解我在示例代码里就是这么做的。二是在目标函数里加一个很小的线性惩罚项比如1e-4 * sum(Pc Pd)让求解器“懒得”制造无意义的充放电流。惩罚项系数要小防止扭曲真实收益如果系数设大了最优容量会被明显压低。顺带提醒购售电互斥也一样。如果不加约束模型可能同时出现“向电网购电”和“向电网售电”尤其在上网电价和购电价接近时最容易被钻空子。5.3 数据时间分辨率不一致SOC直接跳变有次我从两个数据源拿负荷和光伏数据负荷是15分钟间隔光伏是1小时间隔没做统一就直接跑优化。结果SOC曲线在某几个整点突然越界简直像物理定律失控。查了一会儿才发现是时间步长不一致导致的。调度模型里所有变量必须共享同一个Δt否则SOC递推和功率平衡会出现隐式的能量黑洞。我现在写代码前会先加一句检查t ...; % 时间序列 if length(unique(diff(t))) ~ 1 error(时间步长不一致先统一数据分辨率); end5.4 效率参数用错方向结果偏高充放电效率是容量配置里最容易写错的地方。正确递推是SOC(t1) SOC(t) (η_ch * P_ch - P_dis / η_dis) * Δt / E_rated注意放电那一项是除以η_dis不是乘以η_dis。我见过不少初版代码把放电写成P_dis * η_dis等于系统“凭空多出能量”。0.95的充、放电效率往返效率是0.9025也就是1度电充进去再放出来只有0.9度左右。如果效率方向写反算出来的储能可转移电量高估10%以上最直接的表现就是最优容量偏大、回收期偏短。调试技巧是手算一个简单例子10kWh电池以1kW恒功率充电10小时再以1kW放电最终放出能量应该是9.025kWh。用这个例子验证代码里的SOC输出一眼就能看出效率模型对不对。5.5 忘了寿命约束NPV看着漂亮却回不了本容量配置如果只看电费节省很可能算出一个“最优解”电池却在项目周期内提前报废。比如某组参数下最优配置是12kWh每天循环0.8次一年292次十年2920次看起来在6000次循环寿命内没问题。但如果你把运行策略改成深度充放SOC每天在0.1到0.9之间来回等效循环次数可能翻倍都不止。我现在的做法是在外层寻优结束后顺手计算等效全循环次数EFCEFC_per_year sum(P_dis) * dt / (E_rated * η_dis) / 365如果EFC_per_year * 项目周期 电池循环寿命就要重新考虑配置或者在目标函数里加入电池更换成本。这一步不加很多漂亮的NPV曲线都是纸面富贵。6. 从单个产销者到多产销者共享储能6.1 共享储能的规划-运行耦合单个产销者的问题想清楚之后自然会往多个产销者方向扩展。现在不少社区、园区和农村台区在做共享储能也就是不要求每家装一块电池而是在台区变压器低压侧统一装一台大储能各家按比例共享容量。模型从单个产销者的收益最大化变成整个社区社会福利最大化或者台区功率波动最小化。求解时容量变量依然在外层但内层运行的约束从“单户功率平衡”变成“多户联合功率平衡加共享容量分配”。更重要的是要处理市场机制共享储能的成本怎么分摊、各户收益怎么结算不同分配规则下每个产销者上报的用电行为都会变对应的最优配置也会变。这块做起来要复杂得多但工程价值也大得多。6.2 不确定性建模从确定性到鲁棒/随机前面的方法都是确定性模型光伏出力取期望曲线。实际预测总有偏差尤其是多云天气下中午辐照陡降SOC曲线会完全偏离预期。如果希望配置结果在不确定性下依然可靠就得引入随机规划或鲁棒优化。一种简单的做法是生成若干光伏出力场景每场景做一次内层调度再把各场景收益按概率加权。Matlab里用场景抽样的方式实现不难但计算量会成倍增长所以这种扩展更适合在容量候选集不大时使用。另一种鲁棒做法是只为“最差日期”做优化结果往往偏保守容量会偏大适合对供电可靠性要求高的用户。6.3 储能老化建模对配置结果的影响如果项目周期长电容衰减就不能忽略。可以考虑把第n年的可用容量写成E_available(n) E_rated * (1 - d * (n - 1))d是年衰减率典型的磷酸铁锂电池年衰减在1%到3%之间。把这个衰减模型代入逐年NPV计算就会发现最优容量会比不考虑衰减时略小因为最后几年电池实际能调度的电量变少了大容量装在那里是“虚胖”。这个细节在论文复现时经常被忽略但在工程投标里特别重要。最后分享一个我自己摸索出的习惯容量配置这类问题最怕一开始就闷头写MILP大模型。我现在的流程是先用手动规则调度跑一个粗糙版本让它能出结果、能画SOC曲线再逐步替换成优化调度再考虑不确定性、老化、多主体这些进阶因素。规则调度和MILP的结果差距一般不会超过15%如果哪天差距突然变大大概率不是算法不够好而是约束或参数里有一项写错了。把这条当排查方向能省掉大量在代码里找bug的时间。