产销者场景下分布式储能容量配置优化与Matlab实现

产销者场景下分布式储能容量配置优化与Matlab实现 做储能项目的人应该都有体会每次谈到电池容量总有客户问到底装多大合适装小了晚高峰还得从电网买高价电省不下多少钱装大了电池大部分时间闲着投资成本收不回来。这个问题放在“产销者”场景下会更复杂——家里既有光伏在白天发电又有负荷在晚上用电还要和电网买卖电力储能的容量配置就不是拍脑袋能定的了。这篇东西我就围绕“考虑产销者的分布式储能容量配置策略”这个题目把从建模到Matlab实现的全过程拆开讲一遍。内容里会涉及光伏出力、负荷曲线、分时电价、储能充放电约束等关键点也会给出可复现的代码框架。适合正在做分布式储能规划、微电网容量配置、或者写相关课程的本科生、研究生和刚入行的工程师参考。1. 产销者与分布式储能为什么要把容量配置单独拎出来说1.1 产销者是什么储能在这中间扮演什么角色产销者Prosumer这个词是Producer和Consumer的合成通俗点说就是自己家既装光伏发电又用电还可能把多余的电卖给电网。现在很多工业园区、商业楼宇甚至居民小区都在往这个方向走。光伏装上之后白天阳光好的时候发电量高但很多用户白天的负荷并不高多余的电只能低价卖给电网到了晚上没有光伏负荷却可能因为空调、照明、生产设备而飙升又得高价从电网买电。这一高一低之间钱就悄悄溜走了。储能的作用就是把这个时间错配给“拉平”。光伏大发的时候电池把电存起来等到电价高或负荷重的时候再放出来。这样一来分布式储能就不再是单纯的后备电源而是参与电力电量平衡的灵活性资源。但问题也接踵而至电池系统单位容量成本依然不低电价差带来的收益是有上限的如果容量配得过大边际收益会快速下降甚至出现“装得越多亏得越多”的反效果。因此必须用数学方法算出一个最优的额定功率和额定容量组合。从系统角度来看储能容量配置问题可以拆成两个层面一个是规划层的“定容”即确定储能的功率kW和能量kWh另一个是运行层的“调度”即在给定容量下做出每一时刻充放电决策。这两个层面相互耦合——没有容量无法做运行模拟不做运行模拟就不知道这个容量能带来多少收益。所以标准的做法是把容量配置和运行调度写成双层优化或者在一个模型里同时决策这也是目前论文和工程里最主流的框架。1.2 容量配置问题的核心矛盾电池不是越大越好很多没有做过优化的人会直觉地想电池容量越大能存下的电就越多自给率越高肯定越划算。但实际上储能是一个典型的边际收益递减系统。一个5kW/10kWh的电池可能已经把日内的电价差套利空间吃掉了大半多配一倍到20kWh收益可能只增加10%而投资成本却翻倍。同时光伏出力本身具有强波动性和随机性超出合理范围的储能容量在大多数时候都处于闲置状态造成严重的资产浪费。还有一个容易被忽略的因素是电池寿命。储能电池的实际可用放电深度、循环次数和运行策略密切相关。如果为了追求“多存多放”而频繁深充深放电池的日历寿命和循环寿命都会受影响。在容量配置模型中如果不加入寿命折损或运行约束算出来的最优容量往往会偏大误导投资决策。所以我在下文的模型里会特意引入蓄电池SOC上下限、充放电功率约束以及日循环次数限制目的就是让结果更贴近真实运行情况。2. 数学建模把容量配置变成可求解的优化问题2.1 目标函数怎么设经济性指标的选取这个问题的目标函数通常是“年度净收益最大化”或“等年值成本最小化”。我习惯用净收益最大化因为它直观净收益 节省的电费 向电网卖电的收入 - 储能投资成本 - 运维成本。这里有几个细节要注意。第一电费节省不能简单用平均电价算必须用分时电价和实际购电量算。产销者从电网买电的价格通常高于向电网卖电的价格比如峰时买电1.2元/kWh富余光伏卖电0.4元/kWh所以系统天然有“多用自产电”的动力。目标函数里要区分购电成本 (C_{buy}) 和售电收入 (C_{sell})。第二储能投资成本包括功率成本和容量成本。功率成本逆变器、接口设备和额定功率 (P_{ess}^{rate}) 相关容量成本电池本体和额定容量 (E_{ess}^{rate}) 相关。常见表达式为[ C_{inv} c_p \cdot P_{ess}^{rate} c_e \cdot E_{ess}^{rate} ]其中 (c_p) 是单位功率投资成本元/kW(c_e) 是单位容量投资成本元/kWh。考虑到项目周期可以把总投资按折现因子折算到年。在实际代码里我会用“资本回收因子”处理避免出现“第一年收益高后面收益低”的失真比较。第三运维成本可以简化为一个和额定容量或年放电量成正比的小值比如每年按初始投资的2%估算。这部分在目标函数里占很小比例但加进去显得模型更完整审稿人或领导看了也会觉得可信。2.2 约束条件功率平衡、储能运行约束、寿命约束约束是模型里真正容易翻车的地方。第一个必须写的是每个时段的功率平衡约束[ P_{pv}(t) P_{dis}(t) P_{buy}(t) P_{load}(t) P_{ch}(t) P_{sell}(t) ]这里 (P_{pv}(t)) 是光伏出力(P_{dis}(t)) 是放电功率(P_{buy}(t)) 是购电功率(P_{load}(t)) 是负荷功率(P_{ch}(t)) 是充电功率(P_{sell}(t)) 是卖给电网的功率。这个等式成立的前提是忽略功率损耗如果要精确可以在等式右边加一个损耗项但那样会让模型非线性工程上一般先忽略。第二个是储能SOC更新方程[ SOC(t1) SOC(t) \eta_{ch} \cdot \frac{P_{ch}(t) \cdot \Delta t}{E_{ess}^{rate}} - \frac{P_{dis}(t) \cdot \Delta t}{\eta_{dis} \cdot E_{ess}^{rate}} ]其中 (\eta_{ch})、(\eta_{dis}) 是充放电效率(\Delta t) 是时间步长。注意 (E_{ess}^{rate}) 出现在分母里说明储能容量配置和运行策略是强耦合的。第三个是充放电状态互斥约束。同一个时间段不能同时充电和放电这需要引入二进制变量 (u_{ch}(t)) 和 (u_{dis}(t))[ 0 \le P_{ch}(t) \le u_{ch}(t) \cdot P_{ess}^{rate} ] [ 0 \le P_{dis}(t) \le u_{dis}(t) \cdot P_{ess}^{rate} ] [ u_{ch}(t) u_{dis}(t) \le 1 ]这个互斥约束看起来简单但如果不加求解器可能会给出“既充电又放电”的荒谬结果表面上功率平衡成立实际上是让能量凭空消失收益虚高。第四个是SOC范围约束和首末SOC相等约束。电池不宜过度放电通常令 (SOC_{min}0.1)(SOC_{max}0.9)。为了体现日周期性理想情况下每个运行日的结束SOC要回到初始值。如果直接要求所有时段首末相等模型可能过紧工程上可以放宽为“每24小时SOC循环归位”或在一整个规划周期内约束。第五个是功率和容量之间的耦合关系。额定功率 (P_{ess}^{rate}) 和额定容量 (E_{ess}^{rate}) 需要满足一定比例比如允许1C充放即最大功率数值上等于容量数值如果选用0.5C系统那么最大功率是容量的0.5倍。这个约束在代码里用一条线性不等式表示[ P_{ess}^{rate} \le \alpha \cdot E_{ess}^{rate} ](\alpha) 就是倍率参数。如果不加这个约束优化结果可能给出“很大容量但很小功率”的方案这在实际电池系统中是不存在的。2.3 光伏出力和负荷曲线的处理方式光伏出力和负荷曲线是模型的输入数据它们的质量直接决定结果是否可信。最理想的情况是有全年8760小时的实测数据。但如果只有单条典型日曲线结果会偏乐观或偏保守。我常用的一种做法是用典型日集合来代表全年。比如用K-means聚类算法把全年365天的光伏和负荷数据聚成4~6类典型日每类赋予一个天数权重。这样既保留了季节和天气的差异性又不会让模型规模大到无法求解。聚类的好处是能反映“晴天-阴天”“工作日-周末”的不同运行工况储能策略会在这些典型日之间做权衡。比如在连续阴天场景光伏出力小储能主要靠低价时段充电、高价时段放电来赚价差在晴天场景储能则要尽可能“吃下”午间过剩光伏晚高峰放出。如果只用一条平均曲线这些细节全都会被抹掉。在Matlab里读取光伏或负荷数据时经常遇到CSV文件、Excel表格等格式。我一般用readtable读入再转成timeseries或者普通的数值矩阵。如果数据粒度是15分钟而模型步长是1小时需要先做聚合或插值。需要注意聚合时负荷用平均值光伏用累积发电量除以时间段换算成功率不要搞混单位。3. Matlab实现路径从数据准备到求解器调用3.1 整体程序框架和关键模块划分一个清晰的Matlab程序应该分成下面几个模块而不是把几百行代码堆在一个脚本里跑。main.m主程序设置参数、调用各个函数、输出结果。load_data.m读取并预处理光伏、负荷、电价数据。build_model.m用YALMIP建立优化模型包括变量、目标函数、约束。solve_and_post.m调用求解器求解并把结果整理成表格和图形。plot_results.m绘制功率曲线、SOC曲线、收益柱状图等。用函数封装的好处是后期改参数非常方便。比如我想把分时电价从峰谷两段改成峰平谷三段只需要改电价数组不用动模型结构。做灵敏度分析时循环调用build_model并记录目标函数值在脚本里非常方便。3.2 数据准备典型日曲线与电价序列假设我们拿到的是某园区全年的负荷功率单位kW间隔1小时和光伏出力单位kW间隔1小时。先用kmeans做聚类。% 输入load_profile (365x24)pv_profile (365x24) % 输出centers_load (Kx24)centers_pv (Kx24)weights (Kx1) K 4; % 聚成4类 data [load_profile, pv_profile]; % 合并特征 [idx, C] kmeans(data, K, Replicates, 20); centers C; % 4x48 centers_load centers(:, 1:24); centers_pv centers(:, 25:48); weights histcounts(idx, K); weights weights / sum(weights); % 归一化为权重聚类后的中心曲线已经可以代表一类典型日。需要注意聚类前最好对数据做归一化否则负荷和光伏的量级差异会影响聚类效果。我习惯把负荷和光伏分别除以各自年峰值聚类完成后再乘回去。分时电价序列可以直接手写。比如某地区采用“峰谷平分时”% 小时电价单位元/kWh price_buy repmat(0.5, 1, 24); % 平段基础价 price_buy(9:12) 1.1; % 峰段 price_buy(17:20) 1.1; % 峰段 price_buy(0:7) 0.25; % 谷段注意Matlab索引从1开始写成13:16等 price_sell 0.4 * ones(1, 24); % 上网电价固定这里有个小坑Matlab索引从1开始所以0点对应的下标是1编程时不要一眼看错。如果分时电价从0点开始直接定义1x24数组就好。3.3 优化模型代码实现yalmipcplex/gurobi示例我习惯用YALMIP做建模层然后调用Gurobi或Cplex求解混合整数线性规划MILP。Matlab自带的linprog和intlinprog也能求解但处理二进制变量较多的模型时速度会明显吃紧。下面这段代码是核心建议收藏起来慢慢看。% 参数定义 T 24; % 时段数 K 4; % 典型日个数 H 1; % 时间步长小时 dt 1; % 决策变量 P_ch sdpvar(T, K, full); % 充电功率时段 x 典型日 P_dis sdpvar(T, K, full); % 放电功率 P_buy sdpvar(T, K, full); % 购电功率 P_sell sdpvar(T, K, full); % 售电功率 SOC sdpvar(T1, K, full); % SOC状态多一行方便初始值 u_ch binvar(T, K, full); % 充电状态 u_dis binvar(T, K, full); % 放电状态 P_rate sdpvar(1, 1); % 储能额定功率 E_rate sdpvar(1, 1); % 储能额定容量注意SOC变量我设置了T1行目的是把初始SOC放在第1行后续2:T1对应1到T时段的SOC。这种写法在更新约束时更不容易出现索引混乱。目标函数里年净收益 典型日加权收益之和 - 投资年值 - 运维年值。下面是我框架里用的表达式inv_cost cp * P_rate ce * E_rate; % 投资成本 annual_inv inv_cost * crf; % 等年值crf为资本回收因子 omc 0.02 * inv_cost; % 运维成本 revenue_daily 0; for k 1:K for t 1:T % 典型日k的净收益节省购电 光伏售电 - 运行成本忽略运行成本或在后面加 revenue_daily revenue_daily weights(k) * ( ... price_buy(t) * P_buy(t,k) * dt ... price_sell(t) * P_sell(t,k) * dt ... ); end end revenue_year revenue_daily * 365; objective revenue_year - annual_inv - omc;如果追求简洁可以等值地写成最小化总成本。但为了向投资方解释“这套储能一年能赚多少钱”净收益目标更直观。约束构造的核心部分是循环设置。下面给出一个典型日的约束写法Constraints []; for k 1:K % 初始SOC Constraints [Constraints, SOC(1,k) 0.2 * E_rate]; % 初始20% for t 1:T % 功率平衡 Constraints [Constraints, ... P_pv(t,k) P_dis(t,k) P_buy(t,k) ... P_load(t,k) P_ch(t,k) P_sell(t,k)]; % 充放电功率上限和互斥 Constraints [Constraints, 0 P_ch(t,k) u_ch(t,k) * P_rate]; Constraints [Constraints, 0 P_dis(t,k) u_dis(t,k) * P_rate]; Constraints [Constraints, u_ch(t,k) u_dis(t,k) 1]; % 购售电不可同时发生可选因为电价关系自动避免 % Constraints [Constraints, 0 P_buy(t,k), 0 P_sell(t,k)]; % SOC更新 Constraints [Constraints, SOC(t1,k) SOC(t,k) ... eta_ch * P_ch(t,k) * dt - P_dis(t,k) * dt / eta_dis]; end % SOC上下限 Constraints [Constraints, soc_min * E_rate SOC(:,k) soc_max * E_rate]; % 为了让典型日之间能循环可以不做强制或用松弛约束 end % 额定功率与容量关系 Constraints [Constraints, P_rate alpha * E_rate];alpha是储能倍率。如果选1C系统则alpha 1表示一小时能把电池从空充满或放空如果选0.5Calpha 0.5。实际项目中居民储能多为0.5C~1C大型集装箱储能多为0.25C~0.5C你要根据自己面对的储能产品参数来设。最后调用求解器ops sdpsettings(solver, gurobi, verbose, 2, showprogress, 1); optimize(Constraints, -objective, ops); % YALMIP默认最小化所以取负求完后用value(P_rate)和value(E_rate)直接读出最优配置。3.4 结果可视化与灵敏度分析求解完成后不画图等于白做。至少要有三张图一是典型日功率平衡堆叠图二是SOC变化曲线三是不同容量方案的收益对比柱状图。功率平衡堆叠图用Matlab的area函数最方便。把P_pv、P_dis、P_buy按正方向画在横轴上方把P_ch和P_load放负方向或叠加能直观看到每个时段的功率流向。SOC曲线用stairs或普通plot画。重点关注SOC是否跑到边界是否出现频繁满充满放这往往意味着容量约束过紧或过松。灵敏度分析是论文和方案汇报的加分项。通常改变储能单位投资成本 (c_e) 或峰谷价差代入模型重新求解得到一组最优容量和收益的关系。把结果画成曲线可以看到“在什么价位以下装储能不划算”。我个人觉得这个分析比单纯给一个最优值更实用因为它能回答老板最关心的“投资回收期怎么样”之类的问题。4. 实操中常踩的坑与解决思路4.1 非线性项线性化处理在储能模型中最坑的其实是“充放电功率×状态变量”这类乘积。比如充电功率 (P_{ch}) 本身是连续变量SOC更新方程里 (P_{ch}(t)) 是线性的没问题。可一旦要考虑充电效率随SOC变化或电池老化模型很容易出现非线性项。YALMIP虽然能处理部分非线性但求解非凸问题要么慢要么根本求不到全局最优。所以我的原则是能用线性约束近似就不要用非线性。一个典型例子是充放电互斥约束。如果不引入二进制变量可以用一个连续变量 (P_{ess}) 代表放电为正、充电为负然后约束 (-P_{rate} \le P_{ess} \le P_{rate})再在目标函数或约束里加一个效率系数。但这种建模方式无法精确处理充放电效率不同的情况充放电损耗表现不出来。所以实际工程中还是保留二进制变量老老实实走MILP路线。另一个常见非线性来自“购电功率和售电功率不能同时为正”。这个约束也可以用二进制变量但如果你电价模型设计合理——买电价总是高于卖电价——目标函数会自动让系统不会同时买卖因此这个约束可以不写。但保险起见如果求解结果出现同时买卖检查一下电价序列是否真的符合“低买高卖”的价差。4.2 求解器选择和参数调优我用过Matlab自带的intlinprog和Gurobi。对只有几个典型日的小规模模型intlinprog完全够用但当典型日数量增加到10个以上、每个典型日24小时、还有互斥变量时模型规模会达到几千个约束和变量intlinprog的求解时间可能从几秒飙到几分钟还不一定收敛得好。这时建议换Gurobi或CplexYALMIP可以无缝切换。求解器参数也有学问。对这类MILP最常用的参数是MIPGap相对间隙。默认值是0.01%但对于规划级问题设成1%甚至5%就能接受求解时间可以缩短很多。在Gurobi里设置ops sdpsettings(solver, gurobi, gurobi.MIPGap, 0.01);如果模型规模太大还可以开启gurobi.TimeLimit设置比如300秒。这样即使没有找到全局最优也会在限时内返回当前最好可行解这在做多方案对比时特别有用。4.3 不同产销者场景下的策略差异同样是储能容量配置家庭用户、商业楼宇、工业园区的模型参数差异很大。家庭用户的光伏容量通常5~10kW负荷峰谷变化较大电价多是居民阶梯或峰谷电价储能一般只做“光伏消纳峰谷套利”最优容量可能只有5~10kWh。商业楼宇负荷比较平稳峰谷价差大同时需量电费容量电费占成本比重高这时储能就可以通过削峰来降低需量费用模型目标函数里应该加入“需量电费减少”项。工业园区负荷波动大变压器容量有限储能甚至可以承担部分备用容量功能这又涉及可靠性收益。所以在代码实现时不要试图做一个“万能”配置文件。我习惯在main.m里用bType参数区分场景通过switch分支设定不同场景的成本参数和约束条件。这样同一套模型代码就能快速复现多个案例。5. 从算例结果看策略有效性5.1 典型参数设置为了演示我构造一个典型家庭产销者案例。光伏装机5kW年峰值日照约4.5小时年负荷峰值4kW日用电量约25kWh分时电价峰段9-12、17-201.0元/kWh平段8、13-16、210.6元/kWh谷段其余0.3元/kWh上网电价0.4元/kWh储能单位功率成本800元/kW单位容量成本1500元/kWh折现率8%项目周期10年充放电效率95%SOC范围[0.1, 0.9]倍率1C。这些参数放进模型后求解时间大约20秒4个典型日Gurobi求解器非常快。5.2 结果分析储能容量、收益、自消纳率等最优结果通常落在两个量级范围额定功率3~5kW额定容量7~12kWh。具体数值取决于光伏和负荷匹配程度。以一个算例结果为例最优配置为4.3kW / 9.6kWh年度净收益约2300元投资回收期接近8年。这个结果一听就很真实——在电价差不够大的地区储能投资回收期就是很长不是所有地方都适合装储能。从运行曲线看储能会在午间光伏大发时充电在傍晚高峰时放电。SOC曲线呈现明显的“下午高、晚上低”形态。如果光伏容量偏小早晨也会从谷段充一点电用来应对上午的峰段负荷。这说明模型会自动利用“谷充峰放”和“光伏充电”两种模式达到最优。灵敏度分析显示当单位容量成本降到1000元/kWh时最优容量会提高到11kWh如果峰谷价差扩大50%最优额定功率和容量都会显著增加。但有一个很有意思的现象在价格未达到临界点之前最优容量对价格不敏感一旦突破阈值容量会跳跃式增加。这和电池系统“要么不装要装就装够”的经济特性有关。6. 扩展方向这个模型还能怎么玩6.1 考虑需求响应与分时电价目前的模型只是被动响应分时电价。实际上在电力市场改革推进较快的地区出现了“分时电价需量电价需求响应补贴”多重激励。如果产销者参与了需求响应在电网紧张的时段削减用电或主动放电可以额外获得补贴。那模型里就要引入需求响应收益项同时增加约束确保用户舒适度或生产流程不被破坏。比如电动汽车充电桩、温控负荷可以那部分作为柔性负荷参与调度。实现方式是在功率平衡约束里增加可转移负荷变量这些变量也会影响储能最优容量——因为柔性负荷本身也是一种“储能”。6.2 考虑多产销者协同与社区储能单个产销者的储能容量往往受制于自家屋顶和负荷利用率和经济性都不高。如果一个小区的多个产销者共享一套社区储能就可以实现“光伏出力互补、负荷需求错峰”的效果。这种情形的优化模型会从单目标变成多主体博弈可能用到纳什均衡或交替方向乘子法。Matlab里写这类分布式优化有一定代码量但思路还是围绕每个主体的功率平衡和储能运行约束展开区别在于主体之间多了一条共享储能功率分配约束。6.3 与碳交易、绿证结合这两年碳交易和绿证交易越来越火。产销者装光伏和储能可以减少电网购电降低间接碳排放。如果把这些减排量折算成碳资产收益储能的收益结构会发生变化。模型目标函数里可以增加一个碳收益项碳价 × 减少的购电量 × 电网排放因子。虽然这个收益在目前碳价下还不算高但代表着未来的价值方向。代码实现非常简单只需要在目标函数后面加一行。加上之后最优储能容量会略有提高算是给决策者一个“远期期权”视角。最后再分享一个小技巧。做这类优化的时候不要一上来就闷头写完整模型。我习惯先拿一个24小时单典型日、固定储能容量的简化版跑通代码确认功率平衡和SOC更新都没问题再把容量变量放开最后扩展成多典型日和灵敏度分析。分段调试能省下大量排查错误的时间。希望这篇文章能帮你在Matlab里快速搭起自己的储能容量配置模型少走我之前走过的弯路。