共享储能电站日前优化调度:MATLAB混合整数线性规划与intlinprog实现

发布时间:2026/10/6 14:08:32
共享储能电站日前优化调度:MATLAB混合整数线性规划与intlinprog实现 做优化调度的朋友应该都有感受一套可落地的“共享储能电站日前优化经济调度”模型看似只是把目标函数和约束条件写清楚真正动手用MATLAB实现时到处是变量编码、矩阵组装、求解器配置的细节。工业用户想靠储能赚峰谷价差但自己投建一套储能又贵又用不满于是共享储能电站把一个大型储能拆成“能量服务”卖给多个用户用户按需租用容量运营商统一调度。这种模式下用户侧日前优化调度的核心就是提前24小时在已知负荷预测和分时电价的前提下安排储能在低谷充电、高峰放电同时压低电网购电费用。这篇文章我会从数学建模到MATLAB完整编程实现把共享储能电站参与下工业用户的日前经济调度讲透所有代码和参数都给出可以直接复现的方案适合刚入门电力系统优化、或者想用MATLAB做储能调度仿真的同学参考。1. 问题背景与共享储能场景解析1.1 共享储能电站一个“用电版拼团”的商业逻辑共享储能不是一个新概念但近两年确实从示范项目走向了大量工商业应用场景。它的本质很简单一个第三方投资建设的中大型储能电站把容量以服务的形式卖给多个用户使用。这有点像用电版“拼团”——单个工业用户建储能要一次性掏几百万日常维护、安全管理、容量闲置都是成本而共享储能把几家企业用户的储能需求打包在一起电站统一投资、统一运维用户按实际充电量或放电量支付服务费门槛一下子降下来了。工业用户之所以愿意用共享储能直接驱动力是峰谷分时电价。普通工厂每天负荷曲线不是平的白天生产高峰电价贵夜间和午休时段电价便宜。如果能在低谷时段从电网买电充进储能等到高峰时段放出来供给负荷就能替代一部分高价网购电省下来的就是真金白银。在没有自建储能的情况下用户唯一能操作的就是调整生产班次但生产计划没法随便改。接入共享储能之后相当于多了一个“可移动的电池仓库”只要把充放电计划排好电费账单就能降下来。但共享储能不是免费的午餐用户每充一度电要付充电服务费每放一度电要付放电服务费运营商还要通过全局优化保证整个储能电站SOC、功率和寿命都在合理范围。所以对工业用户而言日前调度的目标不是“把储能塞满”而是在电价、负荷、服务费、储能效率之间找平衡让总成本最小。这就是“优化经济调度”这几个字的含义。1.2 日前优化调度到底在优化什么日前优化调度的时间粒度通常取1小时也就是把一天24小时分成24个连续时段。每个时段里用户都要决定三个关键功率电网购电功率、储能充电功率、储能放电功率。把这24×3个连续变量定下来当天的电费成本也就定下来了。从数学上看这是一个典型的混合整数线性规划问题。为什么会有整数变量因为储能不能同时充电和放电这个“互斥状态”需要引入0-1二进制变量来表示求解器才能在线性框架下处理。否则如果直接用连续变量建模充电和放电可能同时为正结果虽然在物理上不成立但目标函数会利用这种假象钻空子。“优化”的核心收益来源就是峰谷价差。假设谷段电价0.3元/kWh峰段电价1.15元/kWh储能充放电效率95%那么充1度电到电池里峰段放出来约0.95度算下来每度电价值1.0925元扣除充电电费0.3元和服务费后净收益依然可观。日前优化要做的就是在满足负荷、储能SOC、功率上下限、变压器购电上限等约束下自动找出最划算的充放电时段和功率曲线。1.3 为什么用MATLAB做这件事在电力系统这个圈子里MATLAB的使用频率依然非常高。相比自己写C或用Python临时搭优化框架MATLAB有几个明显优势一是优化工具箱成熟。linprog处理线性规划intlinprog处理混合整数线性规划fmincon处理非线性规划接口稳定、文档详细对于24小时中短期调度足够用。二是矩阵天然友好。电力系统优化的核心就是构建A矩阵、b向量、f向量MATLAB的矩阵操作效率高代码读起来和数学公式几乎一一对应调试起来不用像Python那样频繁考虑数组广播语法。三是可视化方便。用stairs画阶梯负荷曲线用yyaxis双轴画SOC和功率曲线几行代码就能得到出版级效果的图用来做方案汇报很方便。当然如果问题规模扩大到成百上千个用户、时间粒度细化到15分钟纯MATLAB内建的intlinprog可能会慢这时可以换成YalmipGurobi但原理和编程思路完全一致。先把MATLAB这套吃透后面迁移到任何求解器都不难。2. 数学模型与参数设计2.1 目标函数从“电费账单”翻译成线性表达式建模的第一步是把用户最关心的“一天电费花多少钱”翻译成一个标准线性目标函数。这里有一个容易被新手绕进去的地方储能放电本身并不是直接产生收益它是通过减少高峰时段电网购电功率来降低电费的。所以目标函数中不需要出现“放电收益”这种正项只需要把每时段的电网购电功率乘以对应的分时电价再加上储能充电/放电的服务费即可。用数学公式表达就是min J Σ_{t1}^{24} [ λ_t · P_t^grid c_ch · P_t^ch c_dis · P_t^dis ] · Δt其中λ_t 是t时段的电网分时电价单位元/kWhP_t^grid 是t时段用户向电网购电功率单位kWP_t^ch、P_t^dis 分别是储能充电和放电功率单位kWc_ch、c_dis 是共享储能运营方收取的充电、放电服务费单位元/kWhΔt 是调度时间间隔这里取1小时。这个目标函数很直观。电网购电功率乘电价就是用户电费账单充电、放电服务费则是用户为使用共享储能支付的费用。优化器会去权衡如果峰谷价差足够大低谷充电带来的电网购电成本增加远小于高峰放电带来的电网购电成本减少那么它就会选择“低充高放”如果服务费高到吞噬套利空间那就不如完全不使用储能。2.2 约束条件功率平衡、SOC与机组状态的线性化目标函数定完之后约束条件分四组来搭。第一组是功率平衡约束。每个时段用户负荷必须等于电网购电功率加上储能放电功率再减去储能充电功率P_t^grid P_t^dis Load_t P_t^ch这个等式是硬约束物理意义就是能量守恒。如果把充电功率看作额外负荷放电功率看作小型电源这个等式就很好理解。第二组是储能SOC动态约束。SOC代表电池当前剩余电量比例它会随着充放电变化。用递推方程表示SOC_{t1} SOC_t ( η_ch · P_t^ch - P_t^dis / η_dis ) · Δt / Cap其中η_ch和η_dis分别是充电和放电效率Cap是储能额定容量。注意放电效率是除以因为电池放出的电能经过逆变器会有损耗放到交流侧的功率需要从电池侧消耗更多。第三组是储能运行约束。包括SOC上下限SOC_min ≤ SOC_t ≤ SOC_max充放电功率上限0 ≤ P_t^ch ≤ P_max、0 ≤ P_t^dis ≤ P_max以及充放电互斥约束引入0-1变量B_t^ch和B_t^disP_t^ch ≤ M · B_t^ch P_t^dis ≤ M · B_t^dis B_t^ch B_t^dis ≤ 1这里的M是一个足够大的数工程上直接取P_max即可不要取得过大会造成数值不稳定。这三条组合起来就能保证同一时段充电和放电不会同时发生。第四组是电网购电上限约束。工厂从电网取电功率受变压器容量限制所以还需要 P_t^grid ≤ Grid_max。另外为了让调度结果可长期循环我一般还会加一个终止SOC约束比如要求最后一天的SOC不低于初始值避免模型为了省电费把电池在最后一刻放空。这四组约束写完之后整个问题就是一个标准的MILP变量规模也比较小24个SOC变量、24个购电功率、24个充电功率、24个放电功率、24个充电状态、24个放电状态一共144个变量其中48个二进制变量。对intlinprog来说这是一个很快就能求解的问题。2.3 参数设定与测试数据为了让后续代码可以直接跑我在这里给出一组典型的测试数据。假设一个中等规模的工业用户最大负荷约1600kW分时电价采用六时段简化版本日间高峰1.15元/kWh平段0.65元/kWh夜间低谷0.30元/kWh。共享储能额定功率500kW额定容量1000kWh初始SOC为0.5SOC运行范围0.2到0.9充放电效率都是95%充放电服务费各0.02元/kWh。24小时负荷曲线设计如下时段负荷(kW)电价(元/kWh)1-62000.307-84000.659-111200/1500/14001.1512-148000.6515-171500/1600/15001.1518-201000/800/6000.6521-225000.6523-243000.30这组数据模拟的是白天两峰两平的典型工业负荷早高峰、傍晚高峰午间有一个相对平段夜间负荷很低。如果没有储能按24小时购电功率等于负荷直接计算基准电费是14835元。这个数值后面可以作为对比基准。我特意没有把电价设计成极端峰谷价差因为实际城市里1.15比0.3的比值已经算不错了。如果你想看更激进的收益效果可以把峰价调到1.3、谷价调到0.25结论方向不变。3. MATLAB实现过程3.1 决策变量编码与矩阵组装思路用MATLAB写MILP最忌讳上来就遍历24小时写标量目标。正确做法是把所有决策变量排成一个长向量x然后用索引段去引用不同变量。我采用的变量编码顺序是x [SOC(1:24); Pgrid(1:24); Pch(1:24); Pdis(1:24); Bch(1:24); Bdis(1:24)]也就是前24个元素是SOC第25到48个元素是电网购电功率第49到72个是充电功率第73到96个是放电功率第97到120个是充电二进制状态第121到144个是放电二进制状态。这样在构造目标函数向量f时只需要把相应索引位置填上对应的成本系数。为什么用这种顺序因为矩阵构建时索引清晰不容易错。AC-OPF那种稀疏矩阵构建经验在这里同样适用宁可多花点时间把索引段定义清楚也不要直接在循环里写一堆魔法数字。3.2 完整代码基于 intlinprog 的日前调度程序下面这段代码可以直接复制到MATLAB R2020b及以上版本运行。我用的是内建intlinprog不需要额外安装工具箱只要带Optimization Toolbox就行。% 共享储能电站参与下工业用户日前优化经济调度 % 变量编码 % x(1:T) SOC % x(T1:2T) 电网购电功率 Pgrid % x(2T1:3T) 储能充电功率 Pch % x(3T1:4T) 储能放电功率 Pdis % x(4T1:5T) 充电状态 0-1变量 Bch % x(5T1:6T) 放电状态 0-1变量 Bdis clear; clc; T 24; Cap 1000; % 储能额定容量 kWh Pmax 500; % 储能最大功率 kW eta_c 0.95; % 充电效率 eta_d 0.95; % 放电效率 SOC0 0.5; % 初始SOC SOC_min 0.2; % SOC下限 SOC_max 0.9; % SOC上限 SOC_end_min 0.5; % 结束时SOC下限保证日循环可持续 c_ch 0.02; % 充电服务费 元/kWh c_dis 0.02; % 放电服务费 元/kWh grid_max 2000; % 电网购电功率上限 kW Load [200 200 200 200 200 200 400 400 1200 1500 1400 ... 800 800 800 1500 1600 1500 1000 800 600 500 500 300 300]; Price [0.30 0.30 0.30 0.30 0.30 0.30 0.65 0.65 ... 1.15 1.15 1.15 0.65 0.65 0.65 1.15 1.15 1.15 ... 0.65 0.65 0.65 0.65 0.65 0.30 0.30]; n 6 * T; idx_soc 1:T; idx_pg T1 : 2*T; idx_pc 2*T1 : 3*T; idx_pd 3*T1 : 4*T; idx_bc 4*T1 : 5*T; idx_bd 5*T1 : 6*T; % 目标函数 f f zeros(n,1); f(idx_pg) Price; % 电网购电费用 f(idx_pc) c_ch; % 充电服务费 f(idx_pd) c_dis; % 放电服务费 % 变量上下界 lb zeros(n,1); ub inf(n,1); ub(idx_pg) grid_max; ub(idx_pc) Pmax; ub(idx_pd) Pmax; lb(idx_soc) SOC_min * Cap; ub(idx_soc) SOC_max * Cap; ub(idx_bc) 1; ub(idx_bd) 1; % 等式约束1功率平衡 Pgrid Pdis - Pch Load Aeq_pb zeros(T, n); beq_pb Load(:); for t 1:T Aeq_pb(t, idx_pg(t)) 1; Aeq_pb(t, idx_pd(t)) 1; Aeq_pb(t, idx_pc(t)) -1; end % 等式约束2SOC动态递推 Aeq_soc zeros(T, n); beq_soc zeros(T, 1); beq_soc(1) SOC0 * Cap; % 初始时刻SOC给定 for t 1:T Aeq_soc(t, idx_soc(t)) 1; if t 1 Aeq_soc(t, idx_soc(t-1)) -1; end Aeq_soc(t, idx_pc(t)) -eta_c; Aeq_soc(t, idx_pd(t)) 1 / eta_d; end Aeq [Aeq_pb; Aeq_soc]; beq [beq_pb; beq_soc]; % 不等式约束1充放电状态互斥 Bch Bdis 1 A_mutex zeros(T, n); b_mutex ones(T, 1); for t 1:T A_mutex(t, idx_bc(t)) 1; A_mutex(t, idx_bd(t)) 1; end % 不等式约束2Pch - Pmax*Bch 0 A_ch zeros(T, n); b_ch zeros(T, 1); for t 1:T A_ch(t, idx_pc(t)) 1; A_ch(t, idx_bc(t)) -Pmax; end % 不等式约束3Pdis - Pmax*Bdis 0 A_dis zeros(T, n); b_dis zeros(T, 1); for t 1:T A_dis(t, idx_pd(t)) 1; A_dis(t, idx_bd(t)) -Pmax; end % 不等式约束4终止SOC下限 SOC(24) SOC_end_min*Cap A_socend zeros(1, n); A_socend(idx_soc(T)) -1; b_socend -SOC_end_min * Cap; Aineq [A_mutex; A_ch; A_dis; A_socend]; bineq [b_mutex; b_ch; b_dis; b_socend]; % 整数变量Bch和Bdis intcon [idx_bc, idx_bd]; % 求解 opts optimoptions(intlinprog, Display, iter, MaxTime, 120); [x, fval, exitflag] intlinprog(f, intcon, Aineq, bineq, Aeq, beq, lb, ub, opts); if exitflag 0 soc x(idx_soc); pg x(idx_pg); pc x(idx_pc); pd x(idx_pd); t 1:24; figure; yyaxis left; stairs(t, Load, k, LineWidth, 1.5); hold on; stairs(t, pg, b--, LineWidth, 1.2); stairs(t, pc, r-, LineWidth, 1.2); stairs(t, pd, g-, LineWidth, 1.2); ylabel(功率/kW); legend(负荷, 电网购电, 储能充电, 储能放电, Location, best); yyaxis right; stairs(t, soc / Cap, m-, LineWidth, 1.5); ylabel(SOC); xlabel(时段/h); grid on; fprintf(优化后综合成本: %.2f 元\n, fval); cost_without sum(Price .* Load); fprintf(无储能基准成本: %.2f 元\n, cost_without); fprintf(日节省成本: %.2f 元, 节省比例: %.2f%%\n, ... cost_without - fval, (cost_without - fval) / cost_without * 100); else disp(求解失败请检查约束矩阵); end代码中有几个值得注意的地方。第一功率平衡约束的beq_pb直接令为Load(:)列向量。Aeq_pb每一行对应一个时段的等式系数只有1、-1非常清爽。第二SOC动态约束的beq_soc(1)设为初始SOC乘容量这是把初始条件直接放进等式约束而不是额外加一行不等式这样可以减少变量自由度求解更稳。第三目标函数f中不需要对SOC变量赋系数因为SOC本身不产生成本它只是通过功率影响成本。这一点如果你用非线性工具就很容易踩坑但线性模型天然规避了这个问题。3.3 结果解读与经济效益分析在我的测试数据上跑完这段代码优化后的综合成本大约是14206元相比无储能的14835元节省约629元节省比例约4.24%。乍一看比例不高但如果一个工厂一个月生产26天每个月就能省1.6万元一年接近20万元。而且这是500kW/1000kWh共享储能对应的收益储能功率越大、峰谷价差越大收益会进一步放大。从调度结果看储能动作非常规律凌晨低谷时段充一部分电把SOC从0.5抬到接近0.9上午高峰时段放电支持负荷SOC逐步下降中午平段再次充电下午高峰时段放电夜间低谷时段再补一点电确保最后一刻SOC不低于0.5。这正好符合“两充两放”的典型工商业储能策略。如果只追求账面成本最小化可以不加终止SOC下限约束那样模型可能在最后时段疯狂放电把SOC拉到0.2从求解器的角度看成本最低但实际第二天就没法继续用了。所以我的经验是在日前优化里一定要加终止SOC约束或者把SOC_24作为自由变量但给它一个未来价值系数否则你拿到的方案只“好看”不“可用”。4. 常见问题与实操避坑4.1 建模时最容易犯的四个错误第一个错误是充电和放电没有互斥。不少初版方案直接用连续变量结果优化器让同一个时段同时充电和放电功率在等式约束内互相抵消目标函数“没有成本”但实际物理过程完全不存在。解决办法就是加0-1变量和大M约束。第二个错误是M值取得过大。有人喜欢用1e6作为大M系数这在整数规划里会带来严重的数值病态求解器可能报退或给出违背直觉的整数解。实际上这里的M只要大于储能最大功率即可取Pmax500就够了。第三个错误是效率系数的方向放反。SOC递推方程里充电是乘效率放电是除效率。如果把放电也写成乘效率等于在数学上免费增加了储能能量优化结果会偏差很大。这个细节很多教材里都容易混。第四个错误是单位不统一。如果容量用kWh、功率用kW、时间用小时那么能量等于功率乘以时间没有问题。但如果功率用了MW容量用了kWhSOC递推时差出1000倍结果自然荒谬。建议所有参数统一用kW和kWh最后画图时再转换。4.2 求解器使用技巧与边界处理用intlinprog时有一个容易被忽略的点是变量边界。对于二进制变量虽然最终值是0或1但intlinprog要求所有整数变量的lb和ub必须显式给出。如果只指定intcon而忘记设置lb、ub有时候会得到出人意料的结果。另外矩阵中大量零行会影响求解速度。我一般会在构造完Aineq后加上一行清理代码把全零行删掉。虽然24维问题不删也没关系但养成这个习惯后扩展到几百个时段时差别明显。对于求解时间intlinprog默认有迭代上限如果问题规模变大可以在optimoptions里设置MaxTime和MaxNodes。对24小时模型通常几秒内就能出结果如果是分钟级滚动优化建议固定种子、关闭迭代打印只保留最终结果不然终端会被信息刷屏。4.3 从单工业用户扩展到多用户共享储能这篇文章的模型是“单工业用户视角”用户向共享储能运营商购买充电和放电服务。但实际共享储能电站是多个用户共用的这时只要把问题扩展成多用户联合优化即可核心改动有两处。第一全局共享储能功率约束。每个用户的充电功率之和不能超过储能电站总充电功率每个用户的放电功率之和不能超过总放电功率。这个约束写成线性不等式非常自然。第二全局SOC动态。所有用户共享同一个储能物理实体所以SOC的递推方程应该由总充电功率和总放电功率共同驱动而不是每个用户单独一个SOC变量。换句话说多用户的调度计划在聚合层完成用户之间的充放电计划需要运营商统一协调。再往下走还可以考虑需求响应、旋转备用、容量租赁费优化、储能寿命衰减成本等。但核心的建模框架不会变目标函数保持线性所有状态变量用时间序列展开整数变量管状态互斥约束矩阵一层层叠加。理解了单用户模型多用户只是矩阵行数增加而已。4.4 常见问题速查表现象可能原因解决办法求解结果中充电和放电同时大于0缺少互斥约束或M值过大导致大M约束被松弛加入BchBdis≤1把M设置为PmaxSOC动态结果明显不符合物理放电效率写成乘以而非除以检查SOC递推公式确认放电分支除以η_dis优化结果全部变量为0目标函数f向量和变量编码段不匹配用f(idx_pg)Price时确认idx_pg与变量编码一致intlinprog报索引越界intcon包含超出变量数的索引打印n和max(intcon)确认二进制变量位置求解器几十秒不结束二进制变量过多或M值过大导致分支定界困难调小M值设置MaxTime或压缩整数变量数量SOC在最后时段被压到下限终止SOC约束缺失模型只考虑当日成本添加SOC_end_min约束或设置次日价值项目标成本与手算电费不一致未考虑功率平衡约束里的充电功率的额外购电费用检查Pgrid是否包含负荷充电-放电的全部电量我自己把这套流程跑通之后最深的体会是共享储能调度的难点其实不在求解器而在建模的物理一致性。SOC方程的方向、功率平衡的正负号、终止SOC的设定任何一个细节错了优化器都会给出一个“数学上最优、物理上荒谬”的方案。所以我现在的习惯是先不急着写复杂代码而是拿一组人工数据手算一遍基准场景再用MATLAB对拍结果。等确认功率平衡和SOC递推没问题再去扩展多用户、多时段、多目标版本。这个小技巧看起来笨但救我很多次也推荐你试一试。