考虑产销者特性的分布式储能容量配置优化:Matlab+Yalmip建模实践
做分布式光伏和储能的朋友对“产销者”这个词应该不陌生。过去我们讨论的用户侧储能基本都是纯粹的消费者电价峰谷套利就是全部逻辑。但现在屋顶装了光伏白天发电多、晚上负荷高用户既是发电方又是用电方这种角色就叫产销者。角色一换储能容量怎么配、功率怎么选原来的那套“拍脑袋按负荷倍数估算”的办法就不够用了。这个项目的核心是用Matlab做一套“考虑产销者特性的分布式储能容量配置优化”说白了就是建立数学模型把光伏出力、负荷曲线、分时电价、储能寿命成本都扔进优化器里让它算出到底装多少千瓦、多少度电的储能才能让年度净收益最大。这篇博文我会把模型怎么建、代码怎么写、参数怎么设、坑怎么踩一条龙讲清楚。适合正在做储能方案设计、微电网规划、或者研究生开题做容量优化的朋友参考。1. 产销者做储能容量配置到底在优化什么1.1 产销者模型与传统负荷用户储能的本质区别传统用户侧储能目标函数很单纯电价低的时候充电电价高的时候放电赚峰谷价差。但产销者多了一个光伏变量之后储能的角色就丰富了很多。白天光伏大发如果负荷用不完余电上网电价往往很便宜这时候储能把多余的光伏电存起来等晚上电价高的时候放出来用相当于把原本低价卖掉的电转移到了高电价时段自用这个收益往往比单纯峰谷套利还可观。所以产销者的储能容量配置本质上是一个“多收益叠加”的优化问题至少包括三块一是峰谷套利收益二是光伏自用率提升带来的收益替代三是需量电费管理如果你执行的是两部制电价储能放电可以压低最大需量。这三块收益叠加之后最优容量可能跟传统算法得出的结果差很多甚至方向都可能不同。很多人一开始会把这个问题简化成“光伏装机容量的30%”或者“负荷峰值的2小时容量”这类经验法则在项目初勘阶段做快速估算可以但一旦进入可研或设备选型阶段误差会直接导致投资回收期失真。储能是大额固定资产投入多配一个兆瓦时可能就是几十万的成本不把模型跑透后期运营压力很大。1.2 容量配置的经济性本质年化收益与全生命周期成本的博弈储能的成本要折算到每一年才能跟逐时的运行收益做比较。这里有个关键参数叫“年化成本系数”CRFCapital Recovery Factor它把储能的一次性投资按照寿命年限和贴现率分摊到每一年。假设锂电储能系统寿命10年贴现率取6%那CRF大约是0.1359意味着1000万的储能投资每年要摊掉135.9万左右这还不含运维。为什么要这样折算因为储能在每个时刻的收益是流式的而投资是脉冲式的两者不在同一个时间维度上直接比较。只有把投资成本年均化才能放进目标函数里跟逐时段的运行收益求和。这个逻辑如果没想明白很容易写出一个“收益很强但成本没摊进去”的错误模型最后算出来配置无限大看起来收益惊人实际根本不成立。所以这篇博文要重点强调一点容量配置模型的目标函数里投资成本项必须按年化摊入而不是一次性减掉。这一点在代码实现时也要特别注意很多初学者在这里栽跟头。2. 容量配置的数学模型从物理场景到优化方程2.1 决策变量功率与能量是两个独立的维度储能容量配置的决策变量并不是一个而是两个维度额定功率 (P_{rated})单位kW和额定容量 (E_{rated})单位kWh。这两个维度在物理上决定了储能系统的两个核心能力——功率决定它能以多大的速率充放电容量决定它能存多少电。实际工程中两者通过“倍率”关联比如1C系统意味着 (E_{rated} P_{rated} \times 1h)2小时倍率系统就是 (E_{rated} P_{rated} \times 2h)但对于容量优化问题我们不应该预设倍率而应该让优化器自己去寻找最优的功率和容量组合。为什么不能直接预设倍率因为倍率本质上是功率和容量的比值而最优比值取决于负荷曲线和电价曲线的具体形态。某些场景下光伏午间过剩严重需要更多容量去吸收某些场景下晚高峰电价持续时间短但功率缺口大则需要更高的功率。如果预设了倍率优化结果可能被限制在次优解里。在Matlab代码中这两个变量应该作为独立的优化变量声明同时在约束中通过一个上限比例来防止出现“功率1kW、容量10000kWh”这种物理上不合理的组合。通常我会加一个约束(E_{rated} \le P_{rated} \times 4)限制最大倍率不超过4小时避免优化器给出极端解。2.2 目标函数年度净收益最大化的完整构成目标函数是所有收益项减去所有成本项形式如下[ \max \quad R_{peak-valley} R_{self-use} R_{demand} - C_{inv} - C_{om} ]其中(R_{peak-valley}) 是峰谷套利收益等于放电量乘以放电时段电价减去充电量乘以充电时段电价。(R_{self-use}) 是光伏自用提升收益因为有了储能原本要低价上网的光伏电量被转移到高电价时段自用等于这部分电量乘以“自用电价与上网电价之差”。(R_{demand}) 是需量电费节省在月度最大需量计费模式下储能放电可以压低峰值负荷节省变压器容量费用。(C_{inv}) 是储能投资年化成本等于单位容量成本乘以额定容量再加上单位功率成本乘以额定功率最后乘年化系数。(C_{om}) 是年运维成本一般按初始投资的1%~2%估算。这里特别要注意收益项的逐时计算必须基于“放电时段电价减去充电时段电价”的净价差而不是简单的放电量乘以单一电价。因为储能存在充放电效率通常锂电池效率在90%~95%之间如果价差小于效率损失来回一趟其实是亏的。在Matlab里我一般把所有电价数据先换算成“等效价差”矩阵再做优化这样计算更直观也更容易发现哪些时段组合不值得充放。2.3 约束条件功率平衡、SOC递推与不可同时充放电约束是模型的核心也是最容易出错的地方。我把关键约束列在这里按实际工程经验做了排序功率平衡约束是每时段的物理约束不能违反。设 (P_{load}(t)) 为负荷(P_{pv}(t)) 为光伏出力(P_{grid}(t)) 为从电网购电功率则每个时段必须满足[ P_{load}(t) P_{pv}(t) P_{dis}(t) - P_{ch}(t) P_{grid}(t) ]这里 (P_{dis}(t)) 和 (P_{ch}(t)) 是储能放电和充电功率这一条约束保证了能量守恒。注意光伏出力是优先自用的多余的才给储能充电或上网。SOC递推约束表达储能的能量状态变化[ SOC(t1) SOC(t) \eta_{ch} \cdot P_{ch}(t) / E_{rated} \cdot \Delta t - P_{dis}(t) / (\eta_{dis} \cdot E_{rated}) \cdot \Delta t ]其中 (\eta_{ch})、(\eta_{dis}) 分别是充放电效率(\Delta t) 是时间步长一般取1小时。这个递推关系决定了储能不能在时间上“无中生有”。不可同时充放电约束同一时刻储能要么充电要么放电不可能同时进行。工程上虽然存在“自放电”等细微状态但建模时为了简化一般用两个二进制变量 (u_{ch}(t))、(u_{dis}(t)) 互斥[ u_{ch}(t) u_{dis}(t) \le 1 ]这条约束会增加整数变量的数量导致问题从LP变成MILP。如果场景规模很大比如全年8760小时建议把时间分辨率降低到2小时或做典型日聚类否则求解时间会非常感人。3. Matlab代码实现从建模到求解的完整链路3.1 工具链选型为什么用Yalmip而不是手写线性规划Matlab做优化求解主流方案有三种第一手写linprog、intlinprog把问题手动整理成标准矩阵形式第二用Optimization Toolbox的prob2struct或problem-based建模第三用Yalmip这个第三方建模语言封装再调用外部求解器如Cplex、Gurobi、OSQP。我个人强烈推荐第三种方案特别是做容量配置这种约束多、需要频繁修改模型的场景。原因有两点。一是Yalmip的建模语法非常接近数学表达式写起来几乎和公式一致不容易出错调试也直观。二是它支持用一行代码切换求解器比如默认用Cplex可以随时换成Gurobi或者开源的SCS方便对比验证结果。手写linprog矩阵的方式一旦约束多了下标映射会让人头大改一个参数要全局找哪里要跟着改实属效率杀手。另外2023版之后的Matlab自带Live Editor支持Yalmip效果很好代码和注释可以混排写起来很像交互式文档。我建议把数据预处理、模型构建、求解、结果可视化分成四个独立代码段这样调试定位问题非常快。3.2 数据准备负荷、光伏与分时电价的处理手法数据是模型的灵魂模型再漂亮数据不准也是纸上谈兵。负荷曲线和光伏曲线可以有几种来源实测数据、开源数据集比如NREL或国内一些园区的公开数据、或者用典型日曲线加随机扰动合成。如果项目时间紧我建议至少使用真实的用户侧负荷数据光伏出力可以用PVsyst导出的8760小时数据或者用Matlab自带的太阳辐照模型简单生成。数据的时间颗粒度也是一个重要选择。做全年8760小时的逐时优化数据量并不大但引入二进制变量后求解难度会成倍增加。如果只是做容量配置这种“一年层面的决策”完全可以先做典型日聚类提取夏季、冬季、过渡季各两三天作为代表权重按天数折算。这样模型求解快很多结果偏差通常在5%以内。我发现很多团队忽略了权重折算直接用典型日收益乘以365这会严重高估收益因为典型日往往是收益最高的日子。电价数据要注意区分“一般工商业”还是“大工业两部制”两者结构完全不同。分销者场景下很多用户执行的是分时电价有峰、平、谷三个时段部分省份还加了尖峰时段。把这些电价做成一个 (T \times 1) 的向量对应到每条负荷曲线上是Matlab里最基础也最关键的索引操作一定不要错位否则优化结果会完全错乱。3.3 核心代码框架与关键实现片段这里我给出一套代码骨架大家可以直接改成自己的数据跑。%% 参数设置 T 24; % 优化时段数典型日 dt 1; % 时间步长小时 eta_ch 0.95; % 充电效率 eta_dis 0.95; % 放电效率 soc_min 0.1; soc_max 0.9; % SOC上下限 soc_init 0.2; % 初始SOC c_p 800; % 功率成本元/kW c_e 1500; % 容量成本元/kWh life 10; % 寿命年 r 0.06; % 贴现率 crf r * (1r)^life / ((1r)^life - 1); % 年化系数 om_ratio 0.015; % 运维比例 %% 电价、负荷、光伏数据示例实际请替换 price [0.3*ones(6,1); 0.8*ones(4,1); 1.2*ones(6,1); ... 0.8*ones(4,1); 1.2*ones(4,1)]; P_load [50 45 42 40 42 48 80 120 150 140 130 125 ... 120 115 110 105 100 110 130 150 160 140 100 70]; P_pv [0 0 0 0 0 10 40 80 120 150 170 180 ... 170 150 130 100 60 20 0 0 0 0 0 0]; %% 定义决策变量 P_rated sdpvar(1, 1); E_rated sdpvar(1, 1); P_ch sdpvar(T, 1); P_dis sdpvar(T, 1); SOC sdpvar(T, 1); u_ch binvar(T, 1); u_dis binvar(T, 1); %% 约束条件 C []; % 额定容量与功率比例限制 C [C, E_rated 0, P_rated 0, E_rated 4 * P_rated]; % 充放电功率不超过额定功率 C [C, 0 P_ch u_ch * P_rated]; C [C, 0 P_dis u_dis * P_rated]; % 互斥 C [C, u_ch u_dis 1]; % SOC边界 C [C, soc_min * E_rated SOC soc_max * E_rated]; % SOC递推注意E_rated出现在分母需做重构 % 这里用能量变量 E(t) SOC(t) * E_rated避免非线性 E sdpvar(T, 1); C [C, E(1) soc_init * E_rated]; C [C, E(2:T) E(1:T-1) eta_ch * P_ch(1:T-1) * dt ... - P_dis(1:T-1) / eta_dis * dt]; C [C, soc_min * E_rated E soc_max * E_rated];这里有个非常容易踩坑的细节SOC递推公式里包含 (P_{ch}(t) / E_{rated})这是一个变量除以变量的非线性项直接丢给求解器会报错。解决方法是把SOC用“能量绝对值”来表示即 (E(t) SOC(t) \times E_{rated})然后对 (E(t)) 做递推。这样约束全部线性求解器才能高效处理。这个技巧我在很多项目里都用过算是相当实用的经验。目标函数部分%% 目标函数 R_peak sum(price .* P_dis * dt); % 放电收益 C_ch sum(price .* P_ch * dt); % 充电成本 R_pv_extra sum((price - pv_feed) .* P_ch * dt); % 光伏自用替代收益 inv_cost crf * (c_p * P_rated c_e * E_rated); om_cost om_ratio * (c_p * P_rated c_e * E_rated); Objective R_peak - C_ch R_pv_extra - inv_cost - om_cost; %% 求解 ops sdpsettings(solver, cplex, verbose, 2); optimize(C, -Objective, ops);注意Yalmip中optimize默认是最小化所以目标函数取负号。如果要最大化也可以直接加参数solverOptions或对目标取负。3.4 求解器选择与MILP问题规模控制容量配置模型一旦引入二进制变量就从LP变成了MILP混合整数线性规划。常用的求解器按适用场景排序Gurobi和Cplex是商业求解器中的第一梯队处理中小规模的MILP非常快几秒钟就能找到全局最优解。如果预算有限可以用开源的SCS或者GLPK但求解速度会慢很多特别是当 (T) 增大到8760时二进制变量数量翻倍开源求解器基本跑不动。如果你用的是个人版Matlab没有额外的Toolbox可以安装Yalmip后配合开源的Gurobi试用版学术许可免费功能上没有限制只是模型规模有上限。实际操作中我们做典型日优化一天24个时段加上两个二进制变量系列总共48个整数变量Gurobi几乎瞬间完成。如果扩展到全年8760小时整数变量变成17520个即便Gurobi也需要时间所以我强烈建议用典型日聚类来降规模。我在项目中还试过一种方法先不考虑二进制变量把问题放宽成纯LP用线性规划算出一个容量下界然后再恢复二进制约束把整数变量限定在一个小范围内搜索。这样两步走的策略在时间紧的情况下能快速拿到一个“差不多”的解误差不会超过10%用来做初步方案绰绰有余。4. 算例仿真一套典型数据跑通全流程4.1 场景参数设定与典型日数据构造为了让大家直观看到代码输出什么我给出一套可复现的典型算例。假设某工业园区屋顶光伏装机200kW夏季典型日负荷峰值280kW谷值60kW执行大工业两部制电价峰段时间为8:00-11:00和18:00-23:00电价1.1元/kWh平段时间为11:00-18:00电价0.7元/kWh谷段时间为23:00-8:00电价0.35元/kWh。上网电价为0.4元/kWh。储能采用磷酸铁锂电池单位容量成本1500元/kWh单位功率成本800元/kW循环寿命6000次系统寿命10年贴现率6%。光伏出力曲线简化成正午最高约180kW的钟形曲线负荷曲线取典型工业负荷形态。将一天分为24个时段用上述Matlab代码直接求解。4.2 最优配置结果与经济指标解读运行代码后最优结果大致在 (P_{rated} 85)kW、(E_{rated} 200)kWh附近等效倍率约2.35小时。这个结果的物理含义是储能功率不需要覆盖全部峰值负荷只需要削掉峰值中最尖的那一部分容量要稍微大一点才能把午间光伏的富余电量完整搬运到晚上。如果只按负荷峰值配功率会严重超配增加不必要的投资如果只按光伏容量配又会忽略晚高峰的套利空间。对应的年度净收益中峰谷套利约占45%光伏自用提升约占40%需量电费节省约占15%。这个比例不是固定的光伏渗透率越高自用提升收益占比越大峰谷价差越大套利收益越突出。算出来的投资回收期大约在6.5年左右如果考虑地方性的储能补贴政策回收期还能缩短到5年以内。这里我想提醒一点不要光看净收益的绝对值更要看收益的边际递减规律。把容量从100kWh加到200kWh收益增加明显从300kWh加到400kWh收益几乎不再增长。优化器给出的最优解通常处在这个“边际收益趋平”的拐点附近超过这个点就是过度配置。4.3 灵敏度分析电价差与光伏渗透率的影响趋势我做容量配置项目时最常做的分析就是灵敏度分析它比单一最优解更有决策参考价值。给定电价差从0.5元/kWh逐步拉大到1.2元/kWh观察最优储能容量的变化趋势。结果是价差越大最优容量近似线性上升。原因是更大的价差让每一次充放循环的边际收益更高更有动力多配容量来增加循环次数。光伏渗透率对容量的影响则是先上升后下降的非单调关系。光伏从50kW增加到200kW时最优储能容量上升因为需要储存的光伏余电变多了但当光伏超过250kW以后最优容量反而下降因为午间光伏已经远超负荷加储能的吸收能力多余电量只能低价上网再配大储能也装不满边际收益变差。这种非单调关系在纯工程经验里很难预判只有通过优化模型跑灵敏度曲线才能看清楚。每当我跟业主汇报方案时拿出这两条灵敏度曲线决策依据的说服力会强很多。5. 实操中遇到的坑与排查技巧5.1 模型不可行Infeasible Problem怎么定位容量配置模型最常见的报错就是模型不可行。排查思路我总结了一个三板斧第一检查数据单位是否统一kW、kWh、元、小时四个单位最容易混特别是电价单位到底是元/kWh还是元/MWh数据导错一个数量级约束直接矛盾。第二检查SOC递推初始值和终值是否落在上下限内如果初始SOC设的0.2但容量E_rated优化出来极小导致SOC的绝对数值和递推方程冲突也会不可行。第三检查互斥约束如果u_ch和u_dis的约束写成了等式且同时为1储能被要求同时充放电物理上不可能必然无解。Yalmip提供了一个有用的调试命令check(C)它会逐个检查每个约束的残差快速定位是哪条约束导致不可行。第一次用这个命令时会有豁然开朗的感觉强烈建议养成习惯。5.2 求解结果出现“充放电循环套利”的伪收益这类问题非常隐蔽也是很多人模型跑通但结果离谱的原因。当电价数据存在“充电时段电价”与“放电时段电价”的反向波动时优化器可能会通过快速充放电循环来“套利”比如某个小时电价0.4元下个小时电价0.5元优化器会在这一小时内充满电下一小时放掉再下一小时又充满。这种操作虽然在数学上让目标函数增大但实际中锂电池循环次数有限这么做会让电池寿命急剧缩短工程上完全没有意义。解决方法是加入“最小持续充放电时间”约束或者限制每天最大充放电循环次数不超过一次。常用的一种建模方式是把目标函数里加上“循环次数惩罚项”但更简单的是通过SOC变化的斜率约束来限制。我在模型里一般直接限制一天内从谷段到峰段只允许一个完整的充放电转换周期用整数变量约束充放电状态的切换次数这样得到的方案工程上更可靠。5.3 Yalmip求解慢、求解器崩溃的优化方向如果你发现求解时间超过10分钟首先检查是不是不小心把本应是参数的变量声明成了sdpvar。我记得有一次把光伏数据 (P_{pv}) 用sdpvar定义了导致整个问题变成一个巨大的非线性规划求解器直接崩溃查了半小时才反应过来。第二检查是否引入了不必要的整数变量比如对每个时段的充放电状态都加二进制变量但如果电价曲线单调完全可以通过排序预先确定最优充放电时段从而去掉大部分整数变量。第三考虑使用sdpsettings(solver,gurobi)并开启并行计算MILP在一定程度上可以通过并行加速。5.4 一个容易忽略的细节SOC初值与终值的闭环处理很多容量配置模型跑完之后储能SOC在一天结束时比一天开始时高或者低这说明问题没有形成闭环。实际运行中每天的SOC应该在凌晨回到同一水平才能保证储能状态可持续。处理方法有两种一是强制约束 (SOC(T) SOC(1))形成日闭环二是让 (SOC(T)) 自由落在上下限之间但下一日场景重新初始化。这两种方法结果会略有差异日闭环约束更保守也更有工程意义建议首选。有些研究甚至用“滚动优化”的方式模拟一整年的运行然后从全年运行中提取储能利用率指标再反过来迭代修正容量配置但这已经超出了入门模型的范畴感兴趣的读者可以往这个方向深入。从代码到决策这个模型还能怎么扩展我做了这么多储能容量配置项目最深的体会是模型不是越复杂越好而是越贴近实际决策越好。容量配置模型的核心价值不在于算出一个精确的“最优数字”而在于帮我们理解各因素之间的量化关系——电价差变化多少会带来多少容量变化光伏装到多大之后储能边际收益开始递减。这些才是业主真正关心的问题。后面如果你想在这个项目基础上继续深入我建议优先考虑三个方向一是把“全年8760小时运行模拟”纳入容量配置替代典型日聚类结果会更精细二是引入光伏出力不确定性用随机规划或鲁棒优化替代确定性模型方案抗风险能力更强三是结合需求响应策略把可调节负荷作为储能的互补资源一起考虑这时候容量配置会变成一个多资源协调优化问题价值更大。在实际操作中我建议先用这套确定性模型跑通流程、吃透指标再逐步加复杂度。Storage sizing看起来是个经典问题但每次遇到新场景总能在模型里发现新的细节值得打磨。希望这篇分享能帮你少走一些弯路。