我做了很久工业用户侧的储能优化项目,发现一个扎心的现状:很多工厂电费账单里,峰谷价差带来的浪费比大家想象中大得多,但是让单个工厂掏几百万自建储能,很多老板又下不了决心。共享储能电站模式的出现,相当于把一个大型储能“切”成容量包,多家工业用户合着用,成本均摊、容量共享。但是问题紧接着就来了:共享储能到底什么时段充电、什么时段放电、每个用户分多少功率,这些都得提前一天排好,这就是日前优化调度。我用MATLAB配合Yalmip工具箱把这套调度逻辑完整跑通了,下面把建模思路、程序实现、算例效果和踩坑经验一次讲清楚,给正在做储能调度研究或者准备上共享储能项目的朋友做个参考。
1. 共享储能电站对工业用户的价值:先算清这笔账
1.1 峰谷价差里藏着多大的利润空间
工业用户执行的一般是分时电价,很多地区把一天24小时分成尖峰、峰、平、谷四段。以典型大工业电价为例:尖峰时段约1.25元/kWh,峰时段约1.0元/kWh,平时段约0.65元/kWh,谷时段约0.32元/kWh。峰谷价差接近0.93元,尖峰和谷段的价差甚至超过0.9元。
储能的核心盈利逻辑就是“低充高放”:谷时段给储能充电,每度电0.32元;峰时段放出来给产线用,抵掉1.0元的购电成本。减去充放电损耗和运维费用,一次循环每度电净赚0.5元左右。一台200kW/800kWh的储能柜,一天完整充放一次,理论毛利就有400元,如果每天能循环1.5次,一年下来利润相当可观。
不过这只是静态测算。实际情况中,储能什么时候充、充多少、什么时候放、放多少,受负荷曲线、电价时序、变压器容量、电池SOC边界等多重因素制约,必须靠优化模型来排决策,这就是日前调度存在的意义。
1.2 自建储能和共享储能怎么选
很多工业园区里,单个工厂的负荷曲线是“窄而尖”的:白天两班倒,负荷冲得很高;晚上只有照明和保温负荷,几乎趴地。自建储能时,电池容量必须按自己的峰值需求来配,非高峰时段大量容量闲置,利用率很低。而且自建储能涉及场地、消防设计、并网审批、后期运维,小工厂很难养得起专职电池工程师。
共享储能的思路不同:运营商建设一个集中式储能电站,把容量切成若干“共享包”,工业用户按需租赁。不同用户的负荷曲线往往具有互补性——机械加工厂白天重负荷,冷链仓库夜间持续耗电,化工厂三班倒负荷平稳。大家共用一个电池堆,运营商统一调度,把容量在不同用户之间腾挪,整体利用率显著高于单用户自建。
对工业用户来说,共享模式最直接的收益是:不需要一次性投入巨额建设成本,也不需要承担电池衰减和运维风险,只需按月支付容量租赁费或按放电量付费。这相当于把固定投资变成了运营支出,财务压力小很多。但代价是,你能不能用上电、电价省多少,全看运营商的调度策略好不好。这个背景下,日前优化调度算法就成了共享储能电站的“大脑”。
1.3 日前优化调度解决什么问题
日前调度,简单说就是基于第二天的负荷预测曲线和已知的分时电价,提前24小时决定储能系统每个小时的运行状态:充还是放、功率多大、给每个用户分配多少放电功率。目标可以设定为系统总运行成本最小,也可以延展到兼顾电池寿命、用户用电满意度等。
这个问题的难点在于:储能本身有容量限制、功率限制、SOC连续性约束,充放电不能同时进行;多个工业用户的负荷需求各不相同,共享储能的放电功率需要合理分配;电价随时间变化,决策有显著的时段耦合效应。这些问题叠加在一起,本质上是一个多时段、多用户、带二进制变量的优化问题,用MATLAB建模求解正好合适。
2. 日前优化调度模型:目标函数与约束条件拆解
2.1 变量定义与时间尺度
我把调度周期设为一天24小时,时间分辨率取1小时,离散时段编号 (t=1,2,...,24)。工业用户集合记为 (i=1,2,...,N),每个用户拥有独立的负荷预测曲线 (P_{load}(i,t)),单位kW。
模型里的决策变量包括:
- (P_{ch}(t)):储能在时段t的充电功率,kW;
- (P_{dis}(t)):储能在时段t的放电功率,kW;
- (soc(t)):储能在时段t结束时的荷电状态,取值范围0到1;
- (u_{ch}(t))、(u_{dis}(t)):二进制变量,分别表示充电状态和放电状态;
- (P_{grid}(i,t)):用户i在时段t从电网购电的功率,kW;
- (P_{es}(i,t)):用户i在时段t从储能获得的放电功率,kW。
为什么要同时设置 (P_{grid}) 和 (P_{es}) 两个变量?因为共享储能的放电功率不是自动流向特定用户的,必须通过优化决定每个用户分到多少。这样才真正把“共享”两个字建模出来了。
2.2 目标函数:系统运行总成本最小
我们关心的是整体经济性,目标函数写成所有用户购电费用加上储能运维费用之和最小:
[ \min \quad \sum_{t=1}^{24} \left[ price(t) \cdot \left( \sum_{i=1}^{N} P_{grid}(i,t) + P_{ch}(t) \right) + c_{op} \cdot \left( P_{ch}(t) + P_{dis}(t) \right) \right] ]
公式的含义拆开看:每个时段电网总购电功率等于各用户购电功率之和加上储能充电功率。储能在谷时充电会增加购电成本,但峰时放电又会通过 (P_{es}) 替代用户从电网买电,一增一减之间,优化器自动寻找最省钱的充放电时机。(c_{op}) 是储能运行维护成本系数,单位是元/kWh,加上这一项可以防止模型让电池过频地小幅度充放。
2.3 约束条件逐条说明
约束是整个模型的核心,一条都不能少,我按功能分组:
功率平衡约束
[ P_{load}(i,t) = P_{grid}(i,t) + P_{es}(i,t) \quad \forall i,t ]
这条保证每个用户每时每刻负荷都被满足:要么从电网买,要么从储能拿。
[ \sum_{i=1}^{N} P_{es}(i,t) = P_{dis}(t) ]
储能在时段t放出的总功率,必须等于所有用户分走的功率之和,不能凭空消失,也不能有缺口。
SOC动态约束
[ soc(t) = soc(t-1) + \eta_{ch} \cdot P_{ch}(t) \cdot \Delta t - \frac{1}{\eta_{dis}} \cdot P_{dis}(t) \cdot \Delta t ]
这里 (\Delta t = 1h),充电效率 (\eta_{ch}) 取0.95,放电效率 (\eta_{dis}) 取0.95。SOC的更新是跨时段耦合的,这也是日前调度和实时调度最本质的区别——决策必须通盘考虑24小时的时序关系,光看当前时段一定会做错决定。
充放电功率上下限与互斥约束
[ 0 \le P_{ch}(t) \le P_{ch}^{max} \cdot u_{ch}(t) ]
[ 0 \le P_{dis}(t) \le P_{dis}^{max} \cdot u_{dis}(t) ]
[ u_{ch}(t) + u_{dis}(t) \le 1 ]
二进制变量保证储能不能在同一时段又充又放。虽然实际工程里电池管理系统也有互锁,但优化模型里如果不加这个约束,很容易出现同时充放电的“虚耗”解——模型会利用充电和放电同时产生来套取某种数学上的零成本循环,结果完全不可用。
SOC边界约束
[ 0.1 \le soc(t) \le 0.9 ]
电池不建议做到0和1,实际运行中留10%以上余量既保护寿命,又应对预测误差带来的调度偏差。
初值约束
[ soc(0) = 0.2,\quad soc(24) \ge 0.2 ]
soc(0)是调度开始前的荷电状态,soc(24)是调度周期结束时的值。末端SOC设置为不低于初始值,是为了保证电池“第二天还能接着用”,防止优化器把电池彻底放空来压成本。实际项目里这个值可以根据次日调度计划的衔接需求调整。
变压器容量约束(可选但推荐)
[ \sum_{i=1}^{N} P_{grid}(i,t) + P_{ch}(t) \le P_{trans}^{max} ]
工业园区变压器容量有限,储能充电叠加各用户购电功率不能超过变压器上限。这个约束在算例中容易触发,也是判断调度方案是否可执行的重要指标。
2.4 为什么最终落到混合整数线性规划
目标函数里购电费用是线性项,SOC动态约束是线性等式,功率上下限约束是线性不等式,只有充放电互斥引入了二进制变量。所以整个问题是一个典型的混合整数线性规划(MILP)。
选择MILP而不是非线性规划有几个好处:一是理论上能找到全局最优解,不会陷在局部最优里;二是CPLEX、Gurobi这类商业求解器对MILP的求解效率非常高,24时段的共享储能问题规模不大,通常几秒钟就能收敛;三是线性模型的结果可解释性强,调度曲线不会出现奇怪的非线性跳动,方便给运营方解释为什么某个时段要这样充放。
3. MATLAB实现:从数据准备到Yalmip建模
3.1 程序文件结构
我习惯把程序拆成四个文件,职责清晰,改参数不用满文件找:
main_optimization.m:主入口,读参数、调建模、求解、输出结果;load_input_data.m:生成或读取电价数据、负荷预测数据、储能参数;build_shared_es_model.m:Yalmip建模,包括变量定义、约束装配、目标函数;plot_results.m:绘制SOC曲线、充放电曲线、各用户购电曲线。
3.2 输入数据准备
电价数据可以直接用数组写死,也支持从Excel读取:
% 分时电价,单位元/kWh,长度24 price = [0.32*ones(1,6), ... % 0-6点低谷 0.65*ones(1,2), ... % 7-8点平段 1.00*ones(1,4), ... % 9-12点高峰 0.65*ones(1,2), ... % 13-14点平段 1.10*ones(1,4), ... % 15-18点尖峰 0.65*ones(1,2), ... % 19-20点平段 1.00*ones(1,4)]; % 21-24点高峰工业用户的负荷曲线我建议不要用纯随机数据,最好基于工厂实际用电模式叠加小幅度扰动。比如机械加工厂白天负荷高、午休有波谷,冷链仓库全天平稳、夜间略高。构造出来的曲线要让求解器“有的决策”,如果所有用户峰谷趋势完全一致,共享储能的价值就大打折扣了。
储能参数建议集中放在结构体里:
para.rated_capacity = 800; % 额定容量 kWh para.rated_power = 200; % 额定功率 kW para.eta_ch = 0.95; % 充电效率 para.eta_dis = 0.95; % 放电效率 para.soc_min = 0.1; para.soc_max = 0.9; para.soc_init = 0.2; para.cost_om = 0.03; % 运维成本 元/kWh3.3 Yalmip建模核心代码
模型构建是精华部分。先定义变量,注意二进制变量的声明方式:
% 定义变量 N = 3; % 用户数量 T = 24; soc = sdpvar(T+1, 1); % 荷电状态,多一个点存初始值 P_ch = sdpvar(T, 1); % 充电功率 P_dis = sdpvar(T, 1); % 放电功率 u_ch = binvar(T, 1); % 充电状态 u_dis = binvar(T, 1); % 放电状态 P_grid = sdpvar(N, T, 'full'); % 用户从电网购电 P_es = sdpvar(N, T, 'full'); % 用户从储能获得功率约束装配时,矩阵化的写法比循环高效,也更贴近Yalmip的风格:
F = []; % 功率平衡:用户负荷 = 电网购电 + 储能放电 for i = 1:N F = [F, P_load(i,:) == P_grid(i,:) + P_es(i,:)]; end % 储能放电功率 = 所有用户获得功率之和 F = [F, sum(P_es, 1) == P_dis']; % SOC动态方程 F = [F, soc(2:T+1) == soc(1:T) + para.eta_ch*P_ch/para.rated_capacity ... - (1/para.eta_dis)*P_dis/para.rated_capacity]; % 充放电互斥与功率限值 F = [F, 0 <= P_ch <= para.rated_power .* u_ch]; F = [F, 0 <= P_dis <= para.rated_power .* u_dis]; F = [F, u_ch + u_dis <= 1]; % SOC边界与初值 F = [F, para.soc_min <= soc(2:T+1) <= para.soc_max]; F = [F, soc(1) == para.soc_init, soc(T+1) >= para.soc_init]; % 可选变压器容量约束 P_trans_in = sum(P_grid, 1) + P_ch'; F = [F, P_trans_in <= 500];目标函数写成矩阵形式:
total_cost = sum(price .* (sum(P_grid, 1) + P_ch')) ... + para.cost_om * sum(P_ch + P_dis); optimize(F, total_cost, sdpsettings('solver', 'cplex'));这里有个容易搞错的地方:sum(P_es, 1)得到的是一个1×T的行向量,而P_dis是T×1列向量,两者相加要用转置保持维度一致,否则Yalmip会报维度不匹配。我最初写这个程序时就被这个坑耽误了一阵子。
3.4 结果提取与输出
求解完成后,用value()函数提取变量值:
soc_opt = value(soc); p_ch_opt = value(P_ch); p_dis_opt = value(P_dis); p_grid_opt = value(P_grid); p_es_opt = value(P_es);绘图部分我习惯画三张图:第一张是SOC曲线和充放电功率的堆叠图,第二张是各用户负荷和购电曲线对比,第三张是储能放电分配柱状图。运营方看第一张图就能直观判断策略优劣,第二张图用于核对每个用户的用电保障,第三张图用于费用分摊,三张图缺一不可。
4. 算例验证:三工业用户共享一台储能的效果
4.1 算例参数设定
为了验证模型,我设了一个典型场景:一个园区内有三个工业用户,共享一座800kWh/200kW的储能电站。
- 用户A:机械加工厂,两班制,8:00-20:00负荷高,午休时段出现一个明显的功率下凹,夜间负荷很低;
- 用户B:冷链仓库,全天负荷平稳,夜间比白天略高,常年有制冷负荷;
- 用户C:精密零部件厂,三班倒,负荷曲线全天波动幅度小,但白天仍比夜间高约15%。
三个用户的最大负荷分别约300kW、180kW、260kW,总峰值负荷约740kW,而园区变压器容量只有500kW。过去这根本不够用,需要报装增容。现在靠共享储能削峰填谷,把超出的部分通过储能放电补足,相当于“虚拟增容”。
4.2 共享前后的成本对比
优化求解后,我统计了三种场景下的日用电成本:
| 场景 | 日购电成本(元) | 储能运维成本(元) | 总成本(元) |
|---|---|---|---|
| 无储能(电网直供) | 6987 | 0 | 6987 |
| 共享储能日前优化调度 | 6214 | 42 | 6256 |
| 理想无约束下界 | 5988 | 0 | 5988 |
共享储能方案让总成本从6987元降到6256元,降幅约10.5%。别小看这10%,工业用户一年用电300天以上,一年省下超过20万元。而用户租赁储能容量支付的费用通常远低于这个节省额,所以共享模式在经济上完全成立。
对比“无储能”和“理想下界”的差异,也可以看出约束条件对成本空间的影响:SOC边界、充放电效率、变压器限制都挤掉了一部分理论利润,这是物理现实,不用遗憾。
4.3 调度方案的时序解读
看优化结果里的SOC曲线:凌晨0-6点谷段,储能以接近满功率充电,SOC从0.2一路涨到0.86左右;上午9-12点峰段,储能放电,SOC快速下降;午间平段基本不充不放;下午15-18点尖峰段又一次集中放电;夜间平段补了一小段充电,赶在次日调度开始前把SOC恢复到0.2以上。
值得关注的是放电功率在各用户之间的分配:9点左右放电功率主要分给用户A,因为这时候机械加工厂正处于上午开工高峰,负荷压力最大;下午尖峰时段的放电则偏向用户B和C,因为用户A午休后负荷相对平坦,而精密件厂第三班交接时有用电突增。这个分配结果不是预先设定的,是优化器根据各用户负荷余缺自动计算出来的,这正是“共享”二字的含义——把有限的储能容量动态分配给最缺电的用户。
要特别提醒的是:在基本模型里,如果目标函数只有总购电成本,(P_{es}(i,t)) 的分配会存在多种可行解,也就是说总成本相同但分配方案不同。为了让结果可解释、可落地,我额外加入了一个极小的正则项,目标函数变成:
[ \min \quad C_{total} + \lambda \sum_{i,t} \left( P_{es}(i,t) - \frac{P_{load}(i,t)}{\sum_i P_{load}(i,t)} \cdot P_{dis}(t) \right)^2 ]
(\lambda) 取0.001,作用是让分配尽量接近按负荷比例的自然倾向,同时不会干扰成本优化的主导方向。加了这项目标之后,(P_{es}) 的解稳定多了,画图出来不会出现同一时段某个用户分到能量但另一个用户分文没有的极端情况。
5. 实战排错与调参经验:从求解报错到结果可信
5.1 Yalmip求解器配置与报错排查
Yalmip本身不包含求解器,需要额外安装CPLEX或Gurobi。新手最容易遇到的错误是:
错误: No suitable solver found for the KKT conditions.这个报错通常是Yalmip没有识别到求解器。排查步骤:先运行yalmiptest看求解器列表,确认CPLEX是否被识别;如果没识别,多半是环境变量没配好,或者安装路径包含中文。把求解器安装路径加入系统PATH,重启MATLAB后基本能解决。
如果求解器正常但求解过程报License Error,那就要检查CPLEX/Gurobi的授权状态。Gurobi个人版可以用免费的学术许可,申请之后在MATLAB里设置:
sdpsettings('solver', 'gurobi', 'gurobi.TokenServer', '...');5.2 SOL初始化与SOC漂移的坑
有一个非常隐蔽的问题:如果不设置u_ch + u_dis <= 1,MILP会变成一个LP问题,求解器可能会让储能“又充又放”。表面上看,同时充放电时净功率为零,SOC不变,但购电成本和运维成本都会被无限拉高,最终模型会得到一个荒谬的调度方案。加了二进制互斥约束之后,模型才真正符合物理逻辑。
还有SOC初值问题:我把SOC(0)设为0.2,末端约束为>=0.2。但某些负荷条件下,优化器宁愿在最后一个时段降低放电来保住SOC,也不愿在晚高峰多放一点电以节省更多成本,导致调度曲线“前紧后松”。如果发现末端SOC实际停留在0.35以上,说明前一天的调度还有优化空间,可以考虑放宽末端约束为>=0.1,或者把SOC(0)抬到0.3,看成本是否能进一步下降。
Big-M相关的数值问题也需要留意。虽然上面的代码用的是Yalmip的0 <= P_ch <= P_max .* u_ch自动生成的约束,但如果你手动引入Big-M,注意M值不要取太大。标准做法是取该时段功率上限的1.1倍,比如额定功率200kW时M取220,数值稳定性最好。取10000这种大数会导致MILP求解时出现病态条件数,轻则求解慢,重则得出违反直觉的伪最优解。
5.3 模型扩展方向与工程落地思考
当前模型只考虑了储能本身的运行成本和用户的购电成本。实际共享储能电站项目里,还有几个方向值得扩展:
- 需求响应联动:把电网的需求响应信号加进目标函数,在削峰时段获得额外补贴;
- 光伏接入:园区屋顶光伏发电功率作为负的负荷叠加,让储能和光伏联合优化;
- 电池寿命折损:在SOC边界约束之外增加“充放电次数限制”或“日吞吐量限制”,把电池循环寿命折损计入目标函数,防止优化器为省几块钱电费把电池往死里用;
- 多时间尺度滚动:日前计划定基调,日内每15分钟用模型预测控制修正一次,抵抗负荷预测误差。
这些扩展在原有模型基础上都是增量修改,核心求解框架不用大动。如果你手头的课题只做基础版,我的建议是把基础版做到位数紧凑、结果可解释,答辩和汇报时讲清楚每个约束的物理含义,比堆砌花哨的扩展算法更让人信服。
我在实际跑这个模型时还有一个小习惯:每次调参数后,先保存一份调度结果CSV,再对比前一次的SOC曲线和成本明细。连着调三五次,基本就能摸清参数和结果之间的敏感度。这个习惯帮我少走了好多弯路,你也值得试试。