☰
基于MILP的微网储能容量优化配置:Matlab+YALMIP实战解析
2026/10/3 14:53:40 网站建设 项目流程

去年接到一个园区微网项目,业主上来就问储能怎么配:光伏装了200kW,负荷峰值接近250kW,想上一套储能,但容量和功率该选多少,厂家报价单上一排配置让人心里没底。我最初也走过捷径,按“负荷的20%配储能”的行业惯例估了一套50kW/100kWh,结果一跑整年运行数据,储能在午间光伏大发时段很快充满,傍晚峰段电量又不够放,全年套利收益连设备折旧都覆盖不了。这件事让我彻底改了思路——储能容量配置必须当成一个数学规划问题来解,不能靠拍脑袋。

这篇文章就把我后来实际落地的那套方法完整梳理一遍:基于混合整数规划(MILP)做微网储能容量优化配置,编程环境是Matlab,建模用YALMIP,求解器用intlinprog或Gurobi。内容包括问题拆解、数学模型、完整代码、数据预处理、结果复盘和坑位清单,适合正在做微网规划、储能经济性分析相关课题的研究生工程师,也适合入行不久想搞懂“储能到底怎么配才合理”的设计人员。你把这套方法跑通之后,换成自己的负荷和光伏曲线就能直接用。

1. 为什么是混合整数规划:储能配置问题的数学本质

1.1 拍脑袋定容量的教训

先说那个50kW/100kWh的失败案例。当时我的逻辑是:负荷峰值250kW,取20%做储能功率,再按2小时倍率配容量,这算是工程上最常见的快速估算办法。但问题出在哪儿呢?微网里的储能价值主要由三部分构成:峰谷价差套利、光伏弃电消纳、以及故障或极端天气下的支撑能力。这三者全都和负荷曲线、光伏出力曲线在时间维度上的耦合强相关。

我实测算了一下:那条园区负荷曲线白天的谷底大约在80kW附近,但中午光伏大发时系统净负荷接近0甚至反向,下午4点以后负荷爬升到240kW,而此时光伏已经衰减到不足50kW。50kW的储能功率看起来够用,但100kWh的容量在午间一个小时内就会被光伏盈余充满,续充能力趋近于零;到晚间峰段,100kWh按95%放电效率放完也就95kWh,对应电价最高的3小时,平均只能提供30kW出头的支撑功率,对峰段负荷削减几乎杯水车薪。

这个案例说明一个道理:储能配置的“最优解”和“可行解”之间差距可能非常大。工程经验可以作为初值参考,但最终必须落到数学优化上,让模型自己去权衡“多配一度电、多配一千瓦,换来多少年度收益”。

1.2 问题拆解:投资决策层和运行调度层的耦合

储能容量优化配置这个问题的结构其实很清晰——它天然是两层决策耦合在一起。

外层是投资决策:选多大的额定功率P_ess(kW)、多大的额定容量E_ess(kWh)。这两个变量决定了储能系统的初始投资,属于长期决策,一旦建成就很难改。

内层是运行调度:在给定的容量和功率下,逐小时决定储能充多少、放多少、什么时候从电网买电、什么时候卖电、要不要切负荷、要不要弃光。这些是短期运行决策,在全年8760个小时里每天都在变化。

关键在于,这两层不是独立的。你配置的容量大了,运行层的调节空间就大,逐时能捕捉到的价差收益就多,但投资成本也上去了;容量配小了,投资省了,但运行层处处受限,该充的时候充不进去,该放的时候放不出来,购电成本降不下来。所以必须把两层放在同一个优化框架里同时求解。这也是为什么储能容量优化不能像单纯的经济调度那样只管运行,也不能像简单的静态评估那样只算经济账。

1.3 为什么LP不够,MILP刚刚好

有人会问:这问题看起来就是个线性规划(LP)嘛,目标函数和约束都是线性的,直接用线性规划求解不就行了?

理论上,如果你允许储能系统“同时充电和放电”,那么LP确实能给出一个结果。但那个结果在物理上毫无意义——模型会发现,让一股电流充进去、另一股放出来,能量被效率系数白白吃掉两次,却能同时满足功率平衡和SOC约束。为了避免这种虚功率解,你必须引入充放电互斥逻辑,也就是“充电状态”和“放电状态”不能同时为1。这种0-1状态变量就是整数变量。

同理,购电和售电状态在任何时刻也只能取其一,又要一组0-1变量。再加上容量本身可以选择离散档位(比如按50kWh一档递增),整型变量会进一步增加。

所以这个问题的标准形态就是混合整数线性规划(MILP):一部分连续变量表达各时段的功率和能量状态,一部分0-1整数变量表达互斥状态和离散决策,目标函数和所有约束均保持线性。MILP算法成熟,求解器通过分支定界法处理整数变量,Matlab自带的intlinprog就能解,规模大了可以换Gurobi或CPLEX,开箱即用。

2. 完整数学模型:目标函数、约束体系与线性化处理

2.1 目标函数怎么拆:从一次性投资到年化运行成本

储能容量优化的目标函数,我没直接用“总投资最小”,而是用“年化总成本最小”。原因是投资和运行在不同时间尺度上,不折算到同一基准没法比较。

年化总成本拆成四块:

  • C_inv:储能系统投资成本的等年值。一次性投资c_E·E_ess + c_P·P_ess,通过等年值系数CRF折算到每年。
  • C_om:年运行维护成本,通常按初始投资的2%~3%估算。
  • C_ope:年运行成本,主要是从电网购电的费用,减去向电网售电的收益。
  • C_pen:可靠性惩罚成本,包括失负荷惩罚和弃光惩罚,用来保证优化不会为了省钱而牺牲供电质量或可再生能源消纳。

目标函数写出来就是:

min C_total = C_inv + C_om + C_ope + C_pen

其中等年值系数的公式是:

CRF = rho * (1+rho)^Y / ((1+rho)^Y - 1)

rho是折现率,Y是储能系统寿命年数。这里我踩过一次坑:一开始偷懒,把一次性投资直接除以寿命年数平均摊,结果忽略了资金的时间价值。对一个10年寿命的项目,直接用简单平均和用CRF折现,年化成本能差10%以上,直接影响最优容量档位的选择。

2.2 变量体系:连续量与0-1整数量的分工

建模前先列一张变量表,把模型的结构看清楚。我的习惯是先把变量都列出来再写约束,不然写到一半容易乱。

变量类型含义维度
E_ess连续/离散储能额定容量(kWh)标量
P_ess连续/离散储能额定功率(kW)标量
P_ch(t)连续t时段充电功率(kW)T×1
P_dis(t)连续t时段放电功率(kW)T×1
u_ch(t)0-1整数充电状态标志T×1
u_dis(t)0-1整数放电状态标志T×1
SOE(t)连续t时段储能能量状态(kWh)(T+1)×1
P_buy(t)连续t时段向电网购电功率(kW)T×1
P_sellout(t)连续t时段向电网售电功率(kW)T×1
u_buy(t)0-1整数购电状态标志T×1
u_sell(t)0-1整数售电状态标志T×1
P_shed(t)连续t时段切负荷功率(kW)T×1
P_curtail(t)连续t时段弃光功率(kW)T×1

这里一个小设计点:我没有直接用百分比SOC作为状态变量,而是用能量型变量SOE(单位kWh)。这个选择后面会解释,它直接避开了一类双线性项,是建模时的关键简化。

2.3 约束体系:功率平衡、能量递推与互斥逻辑

约束体系是这个模型的重头戏,我按功能拆成五组来写。

功率平衡约束。每个时段,负荷功率必须等于光伏出力减去弃光、加上储能放电减去充电、加上购电减去售电、再加上切负荷:

P_load(t) = P_pv(t) - P_curtail(t) + P_dis(t) - P_ch(t) + P_buy(t) - P_sellout(t) + P_shed(t)

这里P_shed和P_curtail是松弛变量,让模型在极端情况下不会无解。正常工况下,惩罚系数会把它们压到0。

储能能量递推约束。储能能量在相邻时段的关系是:

SOE(t+1) = SOE(t) + eta_ch * P_ch(t) * dt - P_dis(t) * dt / eta_dis

eta_ch和eta_dis分别是充电和放电效率。这一步必须用能量型SOE,如果改成SOE(t)除以E_ess得到百分比SOC,再乘以E_ess变量,就会出现变量和变量相乘的双线性项,MILP直接变MINLP,求解难度完全不同。我的做法是全程用kWh做能量单位,百分比SOC只在约束边界里出现,通过soc_min·E_ess和soc_max·E_ess来限定SOE的范围。

充放电功率上下限与互斥约束:

0 ≤ P_ch(t) ≤ P_ess · u_ch(t) 0 ≤ P_dis(t) ≤ P_ess · u_dis(t) u_ch(t) + u_dis(t) ≤ 1

前两条把充放电功率限制在额定功率范围内,第三条保证任何时刻不能同时充放。没有这三条,求解器一定会用“同时充放”的虚功率解占便宜。

储能能量边界与日循环约束:

soc_min · E_ess ≤ SOE(t) ≤ soc_max · E_ess SOE(1) = SOE(T+1)

第二条强制储能一个运行周期(典型日)结束后回到初始能量状态,保证优化结果是可重复执行的运行策略,而不是慢慢把电池能量耗尽得到的一次性收益。

购售电互斥约束:

0 ≤ P_buy(t) ≤ M · u_buy(t) 0 ≤ P_sellout(t) ≤ M · u_sell(t) u_buy(t) + u_sell(t) ≤ 1

M是一个足够大的常数(big-M),一般取负荷峰值的1.2到1.5倍就够。这组约束防止模型在同一时段又买电又卖电,在分时电价场景下这是必然要加的,不然模型会在低谷时段低价买电、立即高价卖出,制造出根本不存在的套利空间。

2.4 线性化处理:避开双线性项的常规操作

MILP建模最需要留意的地方就是双线性项。上面提到的百分比SOC和E_ess相乘是一个,另一个常见陷阱是效率系数和功率的乘积看起来线性,但如果充放电效率被建模成功率的函数,模型就变非线性了。工程项目中,把效率视为常数是完全可以接受的简化,实际锂电池在常规工作区间内效率差异不大。

big-M的选择也需要点经验。M取太大,会放大求解器数值误差,导致0-1判断失真;M取太小,又会错误砍掉可行域。我的经验值是:M取负荷峰值的1.5倍,或储能额定功率上限的1.2倍,都能稳定收敛。切记不要图省事直接填1e6,那种做法在小规模模型里偶尔能跑,场景一复杂就出各种诡异问题。

3. Matlab实现路线:YALMIP建模与求解器联调实战

3.1 环境准备:YALMIP和求解器怎么配

Matlab里做MILP有两条路:直接用自带intlinprog,或者装YALMIP工具箱统一建模。我都试过,结论是:如果你只需要跑一次、规模不大,intlinprog足够;如果你要反复改模型结构、加约束、换求解器对比,YALMIP的效率高得多。

YALMIP是一个Matlab的建模层工具,把优化模型用符号方式写出来,然后自动转化为求解器需要的标准形式。安装很成熟,从YALMIP官网下载压缩包,解压后加进Matlab路径就行。求解器方面,Matlab R2014a之后的intlinprog可以直接被YALMIP调用,不需要额外装东西;如果学校或公司有Gurobi或CPLEX许可证,安装后在YALMIP里指定solver='gurobi'即可。

我用一张表对比下几个求解器的实际体验:

求解器获取难度小规模T≤24中规模T=8760简化说明
intlinprogMatlab自带秒级可能数分钟无需额外安装,够用
Gurobi商业许可/学术许可毫秒级明显更快工业界主流,分支定界实现优秀
CPLEX商业许可/学术许可毫秒级明显更快和Gurobi同级别
SCIP免费中等较慢开源方案,依赖配起来略麻烦

我的建议:先用手头就有的intlinprog跑通模型,确认数学层没问题,再决定要不要引入商业求解器。模型写错了,换再快的求解器也只是更快地得到错误答案。

3.2 核心代码逐段拆解

下面这段是完整的单典型日建模代码,以24小时为例,你跑通后可以直接把T改成任意时段。代码设计成“先参数、再变量、后约束”的顺序,方便定位问题。

%% 基础参数设置 T = 24; % 时间步数,24表示一天 dt = 1; % 时间步长,单位小时 eta_ch = 0.95; % 充电效率 eta_dis = 0.95; % 放电效率 rho = 0.08; % 折现率 Y = 10; % 储能寿命年数 cE = 1500; % 单位容量成本 [元/kWh] cP = 800; % 单位功率成本 [元/kW] CRF = rho * (1+rho)^Y / ((1+rho)^Y - 1); % 等年值系数 % 分时电价,示意数据:峰、平、谷三段 c_buy = ones(24,1) * 0.5; c_buy(8:11) = 1.0; % 早高峰 c_buy(18:21) = 1.2; % 晚高峰 p_sell = 0.35; % 上网电价(固定) % 负荷和光伏曲线,示意数据 P_load = [120;110;105;100;95;90;85;80;100;150;180;190;... 170;160;150;145;155;180;220;240;230;200;160;135]; P_pv = [0;0;0;0;0;10;40;80;120;150;170;180;... 175;160;140;100;60;20;0;0;0;0;0;0]; %% 决策变量 E_ess = sdpvar(1,1); % 额定容量 kWh P_ess = sdpvar(1,1); % 额定功率 kW P_ch = sdpvar(T,1); % 充电功率 P_dis = sdpvar(T,1); % 放电功率 u_ch = binvar(T,1); % 充电状态 0-1 u_dis = binvar(T,1); % 放电状态 0-1 SOE = sdpvar(T+1,1); % 储能能量状态 kWh P_buy = sdpvar(T,1); % 购电功率 P_sellout = sdpvar(T,1); % 售电功率 u_buy = binvar(T,1); % 购电状态 0-1 u_sell = binvar(T,1); % 售电状态 0-1 P_shed = sdpvar(T,1); % 切负荷功率 P_curtail = sdpvar(T,1); % 弃光功率 %% 约束条件 C = []; % 容量边界 E_min = 20; E_max = 500; P_min = 10; P_max = 200; C = [C, E_min <= E_ess <= E_max]; C = [C, P_min <= P_ess <= P_max]; % 充放电功率边界与互斥 C = [C, 0 <= P_ch <= P_ess .* u_ch]; C = [C, 0 <= P_dis <= P_ess .* u_dis]; C = [C, u_ch + u_dis <= 1]; % 能量递推 C = [C, SOE(2:T+1) == SOE(1:T) + eta_ch*P_ch*dt - P_dis*dt/eta_dis]; % 能量上下界与日循环约束 soc_min = 0.1; soc_max = 0.9; C = [C, soc_min*E_ess <= SOE(1:T+1) <= soc_max*E_ess]; C = [C, SOE(1) == SOE(T+1)]; % 购售电互斥 M = 1.5 * max(P_load); C = [C, 0 <= P_buy <= M .* u_buy]; C = [C, 0 <= P_sellout <= M .* u_sell]; C = [C, u_buy + u_sell <= 1]; % 功率平衡 C = [C, P_load == P_pv - P_curtail + P_dis - P_ch + P_buy - P_sellout + P_shed]; % 虚拟变量非负 C = [C, P_shed >= 0, P_curtail >= 0]; %% 目标函数 C_inv = CRF * (cE * E_ess + cP * P_ess) / 1000; % 年化,单位千元 C_om = 0.02 * (cE * E_ess + cP * P_ess) / 1000; % 运维 C_ope = sum(c_buy .* P_buy * dt - p_sell * P_sellout * dt) / 1000; C_pen = (1e4 * sum(P_shed) + 1e3 * sum(P_curtail)) / 1000; Objective = C_inv + C_om + C_ope + C_pen; %% 求解 options = sdpsettings('solver', 'intlinprog', 'verbose', 2); sol = optimize(C, Objective, options); if sol.problem == 0 fprintf('最优容量: %.1f kWh, 最优功率: %.1f kW\n', value(E_ess), value(P_ess)); else disp(['求解失败: ', sol.info]); end

这段代码里有几个地方值得单独说。

第一,功率平衡约束我直接写成了一个向量等式,YALMIP会把24个时段的等式自动展开,不需要写循环。

第二,目标函数里我把单位统一成千元,避免数值量级差太多导致求解器收敛困难。成本系数动辄几百万,功率变量是几十几百,量级差太远会让线性规划的预处理出问题。这个习惯帮我省过很多次排查时间。

第三,sol.problem == 0是YALMIP的标准判定方式,表示求解成功且找到了最优解。如果返回4或5,分别对应无界和无可行解,这时需要回头查约束,而不是怀疑求解器。

3.3 从单日模型扩展到全年典型日加权

单日模型跑通只能算入门,实际项目中我从来不用一条典型曲线代表全年,那样误差太大。更稳妥的做法是:从全年数据里挑出几个典型日,每个典型日代表一段时期,按天数加权叠加运行成本,容量变量仍然全局共享。

如果选了D个典型日,第d类日对应的权重是w_d(比如夏季工作日占60天,权重就是60/365),目标函数中的运行成本部分改成:

C_ope = sum_d w_d * sum_t ( c_buy(t) * P_buy_d(t) * dt - p_sell * P_sellout_d(t) * dt )

约束里所有逐时变量都要加一个下标d,比如P_ch_d(t)、SOE_d(t)。容量变量E_ess和P_ess不带d,因为它们是一次性投资决策,对所有典型日一视同仁。这样模型求解出来的容量,是在全年多个典型场景下“综合表现最优”的方案,而不是只对某一天最优。

我实际做过对比:只用一个典型日,配出来的容量比用12个典型日加权少了约22%,年化总成本却只便宜了3%,相当于为了省3%的成本,牺牲了大量极端场景下的供电可靠性。所以多场景加权不是可选优化,是必选项。

4. 数据准备与边界条件:典型日浓缩与参数标幺化

4.1 从8760小时到典型日的降维处理

全年8760个小时的数据直接扔进MILP,T=8760会带来8760组二进制变量,求解规模直接爆炸。最靠谱的做法是先做典型日浓缩,把数据降维到几十个时段再优化。

我的标准流程分三步:

第一步,把全年数据按“天气类型+工作日/非工作日”粗分类,比如夏季晴朗工作日、夏季阴雨工作日、冬季晴朗周末、过渡季阴雨天等。这一步先用人的经验把极端工况框住,避免聚类结果里全是形状相似的中等工况。

第二步,对每一类内部用K-means聚类,选取类中心曲线作为候选典型日。特征向量可以选负荷曲线和光伏出力曲线的组合,也可以用净负荷曲线(负荷减光伏)作为主特征,因为净负荷才是储能真正需要响应的对象。

第三步,根据每类的天数占比确定权重w_d。最终典型日数量控制在4到12个之间。数量太少,日内光伏尖峰和晚峰负荷的错峰特征会丢失;数量太多,求解时间成倍上升,可边际信息增量递减。

我常用的一个组合是8个典型日:春、夏、秋、冬各一个晴朗工作日加一个多云/阴雨日。这样既覆盖了光伏出力差异,也覆盖了负荷的季节性差异。

4.2 边界条件和成本参数的参考取值

参数取值直接决定优化结果,不能随便拍。下面是截至当前比较常见的一组参考值,具体项目里务必拿厂商实际报价和当地电网政策替换。

参数典型取值说明
磷酸铁锂电芯成本800~1500元/kWh含电芯、模组,逐年下降
PCS变流器成本500~1200元/kW双向变流器
BMS与安装集成300~800元/kWh视项目复杂度而定
储能寿命8~12年磷酸铁锂循环寿命对应
折现率6%~10%采用项目资本金内部收益率要求
充电效率0.92~0.97含DC/DC、PCS损耗
放电效率0.92~0.97同上
SOC运行范围0.1~0.9延长寿命与可用容量折中
峰时购电价1.0~1.3元/kWh各省差异大,按实际峰谷时段
谷时购电价0.3~0.5元/kWh同上
失负荷惩罚1e4元/kWh约等于中断供电的用户损失
弃光惩罚1e3元/kWh低于售电价但高于零,体现消纳意愿

这里特别说下失负荷惩罚的取值逻辑:它本质上不是真实成本,而是“宁可多花钱建储能,也不允许切负荷”的权重表达。把惩罚系数设成购电电价的100倍以上,模型在可行时就不会轻易选择切负荷。同时对切负荷变量设置非负约束,保证模型可以通过增加储能容量而不是丢掉约束来找到可行解。

4.3 量纲陷阱:单位不统一定会悄悄毁掉结果

Matlab默认忽略单位,所有数字就是一串浮点数,所以单位统一完全靠建模的人自己把握。这个坑我掉进去过好几次,现在总结成三条铁律。

第一条,功率和能量的单位必须显式区分。功率是kW,能量是kWh,两者通过时间步长dt(小时)相乘转换。dt=1时可以偷懒不乘,但只要把步长改成15分钟(dt=0.25),忘了乘dt的模型结果会完全失真。我这边的工程模板里,所有涉及能量的公式都显式写上dt,哪怕dt=1也不省略。

第二条,成本和价格的量纲要统一。购电价单位是元/kWh,功率单位是kW,所以单时段购电成本是c_buy * P_buy * dt,单位是元/kWh × kW × h = 元,对得上。有人直接把c_buy * P_buy当成成本,当dt不等于1小时时就会悄悄差出一个倍数。

第三条,惩罚系数和投资成本的量纲也要对齐。如果储能投资成本算完是“千元”,而运行成本算出来是“元”,两者直接相加,惩罚项会被无限放大或忽略。我在代码里统一在目标函数各项后面除以1000转成千元,这个习惯持续到现在。

5. 结果复盘与敏感性验证:配置数字是否可信

5.1 运行曲线怎么读:储能行为是否符合物理直觉

优化跑完拿到一组容量和功率数字之后,我的第一反应不是看数字本身,而是把优化出来的典型日逐时运行曲线画出来,用物理直觉去验证一遍。

正常的运行曲线应该长这样:夜间低谷电价时段,储能从电网购电充电,保持一个较低但不在SOC下限的能量水平;上午光伏逐渐爬升,若负荷不高,光伏盈余先充入储能;午间光伏大发,如果光伏功率超过负荷与充电需求之和,多余部分售电或弃光(取决于上网电价);傍晚光伏跌落、负荷爬升,储能开始放电削峰,晚峰时段尽可能维持到放电结束;日末SOC回到与日初相同的位置,准备第二天循环。

如果曲线出现以下任一现象,基本可以判定约束写漏了:储能充放电切换非常频繁且无规律;SOC在日初和日末差异很大却不影响目标函数;某时段充放电功率同时为正。前两种大概率是漏了日循环约束或切换惩罚,后一种是漏了互斥约束。

我还会单独检查一次“不配储能”对比工况。把E_ess和P_ess强制为0,模型退化成纯购电优化,这时候的运行成本是微网在无储能条件下的基准。储能工况的年度总成本必须显著低于基准值,否则说明在给定的参数条件下,储能根本不值得配,这时候“优化结果”告诉你的是别建,而不是给你一个凑合的数字。

5.2 敏感性分析:光伏渗透率、峰谷价差和可靠性惩罚

容量优化只跑一版参数结果,在项目汇报时是站不住脚的。业主一定会问:如果光伏再多装100kW,储能要不要跟着加?如果峰谷电价差缩小了,投资回报还有没有?所以敏感性分析不是加分项,是必交付项。

我常用的三个维度如下:

光伏渗透率变化。把光伏容量系数从0.5变到2.0,观察最优E_ess和P_ess的走势。趋势通常是这样:光伏渗透率较低时,储能主要做峰谷套利,容量随渗透率缓慢上升;渗透率超过某个阈值后,午间弃光风险陡增,储能容量加速上升,因为它还要承接越来越多的光伏盈余转移。这个拐点位置本身就是很有价值的规划信息。

峰谷价差变化。把峰时电价从0.8元/kWh拉伸到1.6元/kWh,谷时电价不变。价差越大,储能每度电的套利空间越厚,最优容量基本单调上升。如果价差缩小到不足以覆盖单位容量年化成本,最优容量会直接跳变到0,模型给出的答案是“不建”。

失负荷惩罚变化。惩罚从1e3提到1e5,储能容量配置会明显上升,因为模型倾向于用储能保障供电而不切负荷。这个维度的敏感性结果,可以反向推导出项目对供电可靠性的重视程度体现在造价上值多少钱。

每次敏感性分析,我会在参数循环外套一层for循环,固定其他参数不变,单独扫一个参数,把结果画成曲线。通常半小时就够跑完,但对项目汇报的说服力提升是决定性的。

5.3 用全年仿真给优化结果对账

优化模型基于典型日计算,本质上是降维近似,所以拿到配置结果后,必须做一次全年逐时仿真对账,确认配置方案在真实8760小时数据上的表现和优化模型估计值偏差不大。

对账方法很简单:固定优化得到的E_ess和P_ess,把典型日加权模型换成全年逐时模型,但整数变量只保留运行层(充放互斥、购售互斥),容量变量不再参与决策。逐时模型是MILP但规模不算太离谱,用intlinprog可以跑,跑不动就退化成启发式规则调度。

对账的核心指标有两个:一是年购电成本,二是全年SOC越界次数和能量不平衡总量。偏差在5%以内说明典型日浓缩质量很好;偏差超过10%,回头检查典型日的数量和聚类特征是否选得不合适。

还有一个容易被忽视的偏差来源:典型日模型每天强制SOC回到起点,但实际连续运行中储能可以从前一天带过一个已经部分充电的状态。典型日模型因此会略微低估储能可用的跨日调节能力,导致容量配置偏保守。解决方法是把典型日按连续日期顺序连接起来仿真,或者在典型日模型中允许SOE(1)和SOE(T+1)在前后几小时内小幅浮动。

6. 求解效率优化与高频坑位自检

6.1 三种降低求解规模的做法

即使是8个典型日、每典型日24个时段,MILP的整数变量规模也到了8×24×4=768个0-1变量,加上连续变量,intlinprog还能对付。但如果你把时域拉到8760小时直接求解,整数变量会到3万多个,分支定界树能把你等哭。我的经验是优先用下面三种手段控制规模。

第一种是典型日压缩,前面已经讲过,不再重复。

第二种是容量变量离散化。与其让E_ess连续取值,不如在建模前生成候选容量序列,比如50、100、150、200、250kWh五档,用五个0-1变量选一档。这样容量决策的整数结构更清晰,还能直接输出可采购的标准规格。代价是可能错失非标准档位的最优解,但工程上本来就会采购标准规格,这个损失可以接受。

第三种是投资层和运行层解耦迭代。先固定一组容量候选值,对每个候选值求解纯运行调度问题(只用连续变量,很快);然后用运行结果拟合成本曲线,再对容量做整数搜索。这个启发式的“两步法”不一定保证全局最优,但收敛速度快一个数量级。我一般先用两步法摸个底,再用全MILP精确校核最优解附近的一个小区间,兼顾速度和精度。

6.2 高频坑位自检清单

最后分享一份我每次交付前都会过一遍的自检清单,里面每条都是实际流过的血:

坑位现象根本原因解法
求解结果同时充放电漏了充放互斥约束增加u_ch+u_dis≤1
模型提示无可行解功率平衡里缺松弛变量增加P_shed和P_curtail及惩罚项
SOC越界但运行正常忘记把SOE限制到soc_min·E_ess和soc_max·E_ess之间补上能量上下界约束
求解时间过长big-M取得太大,数值病态M改为负荷峰值的1.5倍左右
目标函数单位混乱投资、运行、惩罚单位未统一全部除以1000转成千元
典型日结果年化偏差大于10%典型日数量太少或不代表极端场景增加典型日数量,覆盖阴雨天
容量结果为0储能年化成本高于套利收益检查参数,这可能是正确结论
反复振荡不收敛效率系数和加载逻辑出现非线性检查是否有变量相乘项,改成常数效率

其中“容量结果为0”那条最容易误判。我有一次给一个电价极低地区的微网做优化,模型给出的最优配置是0——因为当地工业电价便宜,峰谷价差又小,储能的年化成本确实收不回来。起初我以为模型写错了,排查两天后确认参数无误,最后如实告诉业主“当前电价结构下不建议建储能”。这个结论虽然不漂亮,但比拍一个误导投资的容量数字负责任得多。

还有一条关于初始化的经验:如果你在YALMIP里设置sdpsettings('solver','intlinprog','intlinprog.MaxNodes',1e6)限制了分支节点数,求解器报告“达到节点上限退出”时,拿到的不是严格最优解。这时要么放宽节点限制,要么把当前可行解作为初值,在它周围加一个小邻域重新求解,可以显著改善解质量。

我自己在项目交付时有个固定动作:跑完优化后,会把典型日扩展到12个日类型再做一次复核,看容量结果是否稳定在同一个区间。如果两次配置结果差异在10%以内,说明结果对典型日选取不敏感,可以放心写入报告;如果差异大,通常意味着原始数据里存在被聚类抹掉的重要工况,这时候回看数据分布,比在模型里补约束更有效。

这套基于混合整数规划的储能容量优化方法,我前后在三个不同场景的微网项目里落地过——园区光伏微网、海岛独立微网和厂区光储充一体化项目,每次都把典型日加权、敏感性分析和全年对账三步走完才交付。Matlab+YALMIP这条链路最大的优势不是求解速度,而是改模型快:加了新的约束、换了目标函数,代码结构不动,跑一版就知道影响有多大。如果你也在做类似的项目,建议先把单典型日模型跑通,再逐步扩到多场景加权,一步一个脚印地把每个阶段的输出都验证一遍,最后拿到的容量配置一定有底气。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询