在工厂屋顶光伏项目做并网联调时,我遇到过一件挺打脸的事。那套储能系统装好之后,运营负责人坚持用“最简单可靠的策略”:光伏出力大的午间时段固定给电池充满,晚上高峰时段固定放电。结果一个多云天气的下午,光伏出力从350kW在三分钟内掉到120kW,储能电池还停在满充状态,厂区负载同时上涨,功率缺口全部压到变压器上,最终触发过载保护,逆变器脱网。问题出在哪?调度策略没有把“光伏预测波动”“电池电量状态”“实时电价信号”放到一个模型里统筹考虑。从那以后,我开始认真对待光储优化调度这件事,并确定了一个基本思路:把调度问题做成一个最大可解释、可验证、可部署的数学规划模型,核心工具就是整数规划,更准确地说是混合整数线性规划(MILP)。
这篇内容我会完整地讲一遍从问题定义、模型搭建、代码实现到现场验证的过程,也会把运行中踩过的坑一并列出来。内容面向做光储项目开发、运维和搞能量管理系统(EMS)的工程师,也适合刚入门想系统理解优化调度原理的同学。这里头没有太多虚的“算法包装”,全是能直接落到代码和现场的东西。
1. 光储调度的问题本质:离散决策让“最优”变成了组合爆炸
1.1 调度员每天要做的三个核心决策
光伏储能联合系统的调度,表面上看是“什么时候充电、什么时候放电”,实际上每一时刻调度员都在做一组离散选择:储能系统处于充电状态、放电状态还是待机状态?同时还要决定从电网买电还是向电网卖电。这些状态不是连续可调的旋钮,而是互斥的动作开关。以常见的工商业光储系统为例,功率调度周期通常取15分钟或1小时,一天24个或96个时段,每个时段都要确定上述状态组合。如果只是按人工经验做“午充晚放”的时间表,遇到电价波动、负荷波动和光伏预测偏差叠加时,很容易出现我前面说的那种事故。
1.2 为什么连续优化模型搞不定这个场景
很多人第一反应是:光储调度不就是线性规划(LP)吗?把充放电功率、购售电功率全部设为连续变量,目标函数是费用最小化,约束是功率平衡和SOC递推,然后调用求解器一把梭。问题在于,光储系统的物理约束中有不少“非此即彼”的逻辑。
比如:
- 储能电池在同一个调度时段内不允许同时充电和放电,这个约束用两个连续变量P_ch和P_dis没法天然表达。若不强制互斥,优化器为了“套利”可能会让电池一边充满一边放空,得出一个物理上无意义的答案。
- 系统在某个时段只能选择购电或售电之一,同样存在状态互斥。
- 如果系统包含多台PCS(储能变流器)或柴油发电机,还有机组启停问题,那是更典型的0-1决策。
这些逻辑必须靠整数变量(通常取0或1)来建模。引入整数变量之后,问题从线性规划升级为混合整数线性规划。千万别小看这个“升级”,数学性质有了本质变化:可行域从连续凸集变成离散点集的组合,求解难度不再是多项式时间可保证,而是典型的NP难问题。上面提到的“午充晚放”经验法则,恰恰是对这个离散问题的一种极其粗糙的启发式近似。
1.3 一个0-1变量如何解决“互斥”难题
关于充放电互斥,标准做法是引入两个二进制变量u_ch和u_dis,分别代表充电状态和放电状态,然后把充电/放电功率的上限约束写成:
P_ch[t] ≤ P_max × u_ch[t]
P_dis[t] ≤ P_max × u_dis[t]
u_ch[t] + u_dis[t] ≤ 1
这样一来,当u_ch=1时P_ch可以在0到P_max之间取任意值,但u_dis必须为0,P_dis只能为0。反之亦然。两个变量同时为0时,储能处于待机状态。这组约束就是整数规划在光储调度中最经典的落地场景。
这种建模方式看似简单,但它是整个优化调度的基石。没有这个0-1互斥机制,任何求解器给出的“最优解”都可能是物理上无法执行的一纸空文。后面我会详细展开,当实际部署时,如果约束写得不够严谨,求解结果看起来费用很低,但送到PCS执行时会直接报警拒动。
2. 为什么是整数规划:与其他优化方法的正面较量
2.1 MILP,远比想象的“线性”问题灵活
混合整数线性规划这个名词容易让人误解,以为只能处理线性关系,限制很大。实际上,工程中大量非线性关系可以通过分段线性化、大M法、逻辑约束等方式转换成MILP可处理的形式。光储调度里的核心关系——SOC递推、充放电效率、功率上下限——本身就是线性或近似线性的,所以MILP几乎是量身定做的工具。
用一个通俗类比:MILP就像在棋盘格子上下棋,有些变量是连续的“棋子位置”,有些变量是离散的“棋子数量”,而约束就是“棋规”。线性目标函数让你清楚知道每走一步的代价,整数变量则保证每一步都符合实际规则。求解器在背后用分支定界、割平面等方法,在规则范围内找到成本最低的那套走法。
MILP还有一个其他算法很难替代的优势:它给出的是全局最优解(在模型和预测数据准确的前提下)。对于光储这种投资动辄几十万到上百万的设备,一个“接近最优”的局部解可能意味着每年几万块钱的收益损失,这个账要算清楚。
2.2 动态规划:可行但会撞上“维度诅咒”
动态规划(DP)也常被用来做储能调度。核心思路是把调度问题视为多阶段决策,每个阶段的状态是电池SOC,决策是充放电功率,用递推方程逐步求最优。DP的原理很漂亮,但它面临严重的“维度诅咒”:如果SOC离散成100个档位,调度周期96个时段,状态转移是100×96次计算;但如果系统不只一个储能,还有多个可控负荷、多台机组,状态变量变成高维向量时,计算量呈指数级爆炸。
我曾在项目里尝试用DP做“光伏+储能+柴油发电机”的联合调度,SOC和油箱油位两个状态变量就导致计算时间到了不可接受的程度。相比之下,MILP求解器对中等规模的光储问题(几十个整数变量、几百个约束)基本上几秒到几分钟内就能求出全局最优解。
2.3 启发式算法:为什么只能当备胎
遗传算法、粒子群这类启发式算法,在很多优化场景里很流行。但用在光储调度上,我持保留态度。启发式算法的问题在于:不保证最优性,甚至不保证每次运行结果一致;调参困难,种群大小、交叉概率、变异概率都得人工试;收敛判据模糊,你不知道什么时候该停;约束处理麻烦,充放电互斥、SOC边界这类约束处理不好容易产生大量不可行解。
不是说启发式算法一无是处,而是在光储调度这类“模型清晰、规模适中、需要高可信度”的场景里,MILP是更稳妥的基线方案。启发式更适合那些模型难以精确建模、维度极高、对最优性要求不高的探索性问题。
下表归纳了几种方法在我实际使用中的对比:
| 方法 | 最优性保证 | 计算速度(典型光储场景) | 建模复杂度 | 适用场景 |
|---|---|---|---|---|
| 线性规划LP | 有(但无法表达离散逻辑) | 极快 | 低 | 不含互斥/启停的简化问题 |
| 混合整数线性规划MILP | 有全局最优 | 秒级到分钟级 | 中 | 主流光储调度、微网调度 |
| 动态规划DP | 有(离散化精度内) | 状态维数高时极慢 | 中高 | 单储能/小状态空间 |
| 启发式算法(GA/PSO) | 无严格保证 | 中 | 低 | 探索性、大规模近似问题 |
3. 模型搭建的完整推演:从物理过程到数学约束
3.1 决策变量:把“一天24小时”变成求解器的世界
开始建模前,先把系统边界和决策变量定义清楚。以一个典型的工商业光储系统为例:光伏装机500kW,储能电池额定容量1000kWh,PCS额定功率500kW,系统与电网通过一台800kVA变压器连接。
时间尺度上,我习惯用1小时为一时段,一天24个时段;如果现场对功率波动敏感,可以细化到15分钟(96时段),模型结构不变,只是变量数量变为4倍。
决策变量分为两类:
- 连续变量:P_ch[t](充电功率,kW)、P_dis[t](放电功率,kW)、P_buy[t](电网购电功率,kW)、P_sell[t](向电网售电功率,kW)、SOC[t](储能荷电状态,%)。
- 整数/二进制变量:u_ch[t]、u_dis[t](充放电互斥标志)、u_grid[t](购售电状态标志,1表示购电,0表示售电)。
这里u_grid[t]的引入是为了避免系统在同一时段既从电网买电又向电网卖电。虽然后面加上网电价小于购电价时优化器可能不会故意做“低卖高买”的事,但现场存在光伏反送和负荷波动叠加的情况,不加互斥约束的话,优化结果中可能出现极短时段内购售电同时发生的伪解。所以这个0-1变量建议从一开始就加上。
3.2 目标函数:成本最小化不是唯一答案
光储调度的目标函数,不同项目差异很大。如果是用户侧储能,通常是全天运行费用最小,即购电费用减去售电收益。如果是电网侧项目,可能还要考虑峰谷套利收益、需量电费管理、辅助服务收益等。建议第一版模型只做最核心的:全天购电成本 - 售电收益,同时把储能充放电循环造成的寿命损耗折算成成本纳入目标函数,这个细节容易被忽略但很关键。
目标函数可以写成:
min Σ_{t=1}^{24} [ price_buy[t] × P_buy[t] - price_sell[t] × P_sell[t] + c_degrad × (P_ch[t] + P_dis[t]) ]
其中price_buy[t]是t时段购电价,price_sell[t]是上网电价或者售电价,c_degrad是储能的单位充放电折旧成本。把折旧成本放进目标函数后,优化器就不会为了让几块钱电价差去做无意义的深度充放,从而提高电池循环寿命。
3.3 约束条件:每一行数学公式对应一个现场物理限制
约束条件是最容易出问题的地方。少一个约束,求解器就能“钻空子”给出一个无法执行的方案;多一个冗余约束,求解时间可能成倍增加。以下是光储调度最关键的几组约束,也是我每次建模的“标准体检项”。
第一组是功率平衡。任意时刻,系统内所有功率必须平衡:光伏出力 + 储能放电 + 电网购电 = 负荷 + 储能充电 + 电网售电。写成:
P_pv[t] + P_dis[t] + P_buy[t] = P_load[t] + P_ch[t] + P_sell[t]
这个约束是能源管理的物理基础,不存在任何讨价还价的余地。
第二组是SOC递推关系。储能电池的荷电状态是随时间累积的,上一时刻SOC加上本时段充入或放出的能量,得到当前SOC:
SOC[t+1] = SOC[t] + (η_ch × P_ch[t] - P_dis[t] / η_dis) × Δt / E_rated
η_ch和η_dis分别是充电和放电效率,工程上锂电池通常取0.92~0.97。这里有个新手容易踩坑的细节:充电效率是“存进去多少”,放电效率是“放出来多少”,两个方向不能混用,否则SOC递推会产生系统偏差,运行十几个小时后SOC就漂移到边界外了。
第三组是充放电互斥和功率限幅。前面已经写过,用两个二进制变量加P_max限幅,即可保证储能不会“边充边放”。同时还有电池本身的功率上限约束:P_ch[t] ≤ PCS额定功率、P_dis[t] ≤ PCS额定功率。
第四组是SOC上下限约束。出于安全和寿命考虑,电池SOC通常限制在10%~90%之间,不允许过充过放:
SOC_min ≤ SOC[t] ≤ SOC_max
第五组是末端SOC恢复约束。调度周期结束时,为了避免第二天无电可用,通常要求SOC回到初始值附近:
SOC[T] = SOC_init
这个约束还可以写成SOC[T] ≥ SOC_end_min的松弛形式。需要注意的是,如果强制要求精确等于初始值,在光伏预测偏差较大的情况下可能导致整个调度计划不可行,实际部署时建议给一个上下浮动区间。
第六组是并网功率约束。变压器容量有限,从电网购电和向电网售电的功率都要小于变压器允许的最大值:
P_buy[t] ≤ P_trans_max × u_grid[t]
P_sell[t] ≤ P_trans_max × (1 - u_grid[t])
这组约束同时实现了购售电互斥。
3.4 为什么这些约束一个都不能少
我在交付项目时,总会被问一个问题:“这些约束是不是写得有点多?能不能简化?”我的回答是,每砍掉一组约束,求解器一定会找到利用这个“漏洞”的方案,只是时间早晚问题。
举个例子:如果不加SOC上下限约束,优化器会在低谷电价时段把电池充到100%,哪怕最后几个时段只有几分钱电价差,它也会为了最小化目标函数把电池榨干到0%。电池管理系统(BMS)层面虽然会强制保护,但调度指令和BMS保护冲突时,最终结果往往是PCS反复启停,系统稳定性严重受损。
再比如,不加入末端SOC恢复约束,求解器会在最后一个时段把SOC“清零”,导致第二天早晨无电可用。这个错误我在早期版本里真实犯过,当时调试时看到调度计划最后几小时放电功率特别大,直觉不对,一查果然是末端约束漏了。
4. 代码实现:用Pyomo把一个调度问题变成可执行程序
4.1 工程选型:为什么我选了Python+Pyomo+CBC
建模工具方面,工业界常驻选项是GAMS、MATLAB+YALMIP、Python+Pyomo。我目前的项目基本都基于Python+Pyomo,原因有三条:一是开源免费,客户现场不需要额外买许可证;二是生态好,数据预处理、结果可视化跟pandas、matplotlib无缝衔接;三是模型可读性强,非算法背景的同事看代码也能大致理解。
求解器方面,开源首选CBC(COIN-OR Branch and Cut),它处理中小规模的MILP问题够用。如果模型规模较大(比如96时段、多储能、多机组),建议换Gurobi或CPLEX,商用求解器的分支定界效率确实高一个数量级,但许可证费用不低。前期开发验证用CBC完全足够,交付阶段再根据实际规模评估是否升级。
4.2 模型代码逐段拆解
下面给出一段可运行的核心框架代码(省略具体数据加载部分),展示如何用Pyomo实现前面推导的模型。这里采用1小时时段、24个调度周期。
import pandas as pd from pyomo.environ import * # ---------- 参数输入 ---------- T = 24 dt = 1.0 # 时段长度,小时 E_rated = 1000.0 # 电池额定容量,kWh P_pcs = 500.0 # PCS额定功率,kW eta_ch = 0.95 # 充电效率 eta_dis = 0.95 # 放电效率 SOC_min, SOC_max = 0.1, 0.9 # SOC上下限 SOC_init = 0.2 # 初始SOC SOC_end_min = 0.2 # 末端SOC下限 P_trans_max = 800.0 # 变压器最大交换功率 # 从外部读入的序列数据 price_buy = [...] # 分时购电价,长度24 price_sell = [...] # 上网电价,长度24 pv_forecast = [...] # 光伏出力预测,长度24 load_forecast = [...] # 负荷预测,长度24 # ---------- 模型定义 ---------- m = ConcreteModel() m.t = RangeSet(0, T - 1) # 连续决策变量 m.P_ch = Var(m.t, domain=NonNegativeReals, bounds=(0, P_pcs)) m.P_dis = Var(m.t, domain=NonNegativeReals, bounds=(0, P_pcs)) m.P_buy = Var(m.t, domain=NonNegativeReals, bounds=(0, P_trans_max)) m.P_sell = Var(m.t, domain=NonNegativeReals, bounds=(0, P_trans_max)) m.SOC = Var(RangeSet(0, T), domain=NonNegativeReals, bounds=(SOC_min, SOC_max)) # 二进制变量 m.u_ch = Var(m.t, domain=Binary) m.u_dis = Var(m.t, domain=Binary) m.u_grid = Var(m.t, domain=Binary) # 目标函数:购电成本 - 售电收益 + 电池折旧 def obj_rule(m): deg_cost = 0.02 # 单位充放电折旧成本,元/kWh return sum( price_buy[t] * m.P_buy[t] - price_sell[t] * m.P_sell[t] + deg_cost * (m.P_ch[t] + m.P_dis[t]) for t in m.t ) m.obj = Objective(rule=obj_rule, sense=minimize) # 约束1:功率平衡 def power_balance_rule(m, t): return (pv_forecast[t] + m.P_dis[t] + m.P_buy[t] == load_forecast[t] + m.P_ch[t] + m.P_sell[t]) m.power_balance = Constraint(m.t, rule=power_balance_rule) # 约束2:SOC递推 def soc_update_rule(m, t): return m.SOC[t+1] == m.SOC[t] + ( eta_ch * m.P_ch[t] - m.P_dis[t] / eta_dis ) * dt / E_rated m.soc_update = Constraint(m.t, rule=soc_update_rule) # 约束3:末端SOC不低于下限 def soc_end_rule(m): return m.SOC[T] >= SOC_end_min m.soc_end = Constraint(rule=soc_end_rule) # 约束4:充放电互斥(大M法) def ch_power_limit_rule(m, t): return m.P_ch[t] <= P_pcs * m.u_ch[t] def dis_power_limit_rule(m, t): return m.P_dis[t] <= P_pcs * m.u_dis[t] def mutex_rule(m, t): return m.u_ch[t] + m.u_dis[t] <= 1 m.ch_limit = Constraint(m.t, rule=ch_power_limit_rule) m.dis_limit = Constraint(m.t, rule=dis_power_limit_rule) m.mutex = Constraint(m.t, rule=mutex_rule) # 约束5:购售电互斥 def grid_buy_limit_rule(m, t): return m.P_buy[t] <= P_trans_max * m.u_grid[t] def grid_sell_limit_rule(m, t): return m.P_sell[t] <= P_trans_max * (1 - m.u_grid[t]) m.grid_buy_limit = Constraint(m.t, rule=grid_buy_limit_rule) m.grid_sell_limit = Constraint(m.t, rule=grid_sell_limit_rule) # 初始SOC赋值 m.SOC[0].fix(SOC_init) # ---------- 求解 ---------- solver = SolverFactory('cbc') result = solver.solve(m, tee=False) # ---------- 结果落盘 ---------- plan = pd.DataFrame({ 'hour': list(range(1, T+1)), 'pv': pv_forecast, 'load': load_forecast, 'P_ch': [m.P_ch[t].value for t in m.t], 'P_dis': [m.P_dis[t].value for t in m.t], 'P_buy': [m.P_buy[t].value for t in m.t], 'P_sell': [m.P_sell[t].value for t in m.t], 'SOC': [m.SOC[t].value for t in m.t], }) print(plan)这段代码虽然简洁,但已经覆盖了光储调度最核心的建模要素。实际项目中你还需要加上数据清洗、异常值处理、单位校验、结果校验等环节。特别注意单位一致性:如果P的单位是kW,E的电池容量单位是kWh,时间单位是小时,那么SOC递推公式中功率乘以时间得到的是kWh,不需要额外的换算系数;但如果时间步长是分钟,就需要除以60。
4.3 结果如何解读:调度计划与费用对比
运行完代码,求解器会给出每个时段的充放电功率、购售电功率和SOC曲线。解读结果时要重点看三件事:
一是SOC曲线是否在允许范围内平滑变化。正常情况下,SOC应该呈现“谷充峰放”的锯齿形曲线,不会出现频繁的高低跳变。二是充放电互斥是否成立。看看有没有同一个时段P_ch和P_dis同时大于0的情况,如果有,说明约束写错了或者求解器数值有问题。三是购售电互斥是否成立。检查P_buy和P_sell是否同时出现正值。
我曾经一次部署时发现CBC求解后的P_sell和P_buy在某个时段同时为正值,排查半天发现是u_grid约束中P_trans_max写成0了,导致二进制变量被强制取值1,模型退化成购电模式。这类问题往往不是算法问题,而是参数初始化错误,写代码时一定要做参数合理性检查。
5. 三种典型天气下的调度行为实测
模型跑通之后,我习惯用三组典型场景去验证调度行为的合理性:晴朗夏日、多云天气、阴雨时段。下面结合实测数据,分析每个场景下整数规划给出的调度策略有什么特征。
5.1 晴朗夏日:光伏削峰与晚高峰放电的配合
晴朗夏日的特点是光伏出力大且稳定,中午时段光伏出力远超负荷需求。以500kW光伏、300kW平均负荷的工商业场景为例,中午光伏峰值可达430kW,而负荷可能只有200kW,富余电力开始向储能充电。整数规划给出的策略通常是:
- 早上7点前后,电价尚在平时段,光伏出力爬升,储能开始小功率充电;
- 上午10点到下午2点,光伏大发,储能以接近额定功率充电,尽量把午间富余电力存下来;
- 傍晚17点到21点高峰电价时段,储能开始放电,替代高价电网购电;
- 夜间低谷时段,如果末端SOC约束允许,储能保持待机或少量充电,为次日早晨做准备。
这一策略的本质是光伏消纳和峰谷套利的叠加。光伏大发时段充电相当于把本来可能的“低价上网电”变成“高峰自用电”,避免了低卖高买的价格倒挂。实测中,这个场景下优化调度相比固定“午充晚放”策略,日运行费用能降低10%~15%,主要收益来自准确预测了晚高峰的负荷大小,从而提前预留了足够的SOC,而不是机械地“充满再放”。
5.2 多云天气:预测偏差下的“保守策略”特征
多云天气是光储调度最麻烦的场景。光伏出力波动剧烈,预测曲线和实际曲线可能相差30%以上。此时整数规划会表现出明显的“保守”特征:储能不会在光伏大发的第一个时段就满功率充电,而是预留一部分SOC空间,以应对后续可能的光伏骤降。
举个例子,一个多云日的中午,预测显示12点到14点光伏平均出力300kW,但实际13点云层遮挡后出力掉到120kW。优化模型在12点时段给出的充电功率可能只有150kW,而不是满功率的500kW。这种“不那么贪婪”的策略,恰恰是在权衡了充电收益和失负荷风险之后的最优选择。
这里要特别提醒:单次静态优化(一次性求解全天24小时)对预测误差非常敏感。如果预测严重偏差,模型给出的计划可能在执行到一半时不再可行。实际项目里更稳健的做法是采用滚动时域调度(RHC),每隔15分钟或1小时用最新的预测数据重新求解未来4~6小时的调度计划。MILP在这个场景下的优势是求解速度快,重新求解一次只需要几十秒,完全满足滚动周期的需求。
5.3 阴雨时段:纯峰谷套利的经济账
阴雨天光伏出力很低,基本可以忽略。此时调度问题退化成纯粹的峰谷套利:低谷电价时段充电,高峰电价时段放电。整数规划给出的策略高度依赖于峰谷价差和电池折旧成本。
假设谷电0.4元/kWh,峰电1.1元/kWh,储能综合效率是0.95×0.95≈0.9。一次完整充放循环(充1kWh、放0.9kWh)的毛利是0.9×1.1 - 1×0.4 = 0.59元/kWh。如果我把电池折旧成本设定为0.02元/kWh,那么套利利润是正的,优化器会尽量在低谷充满、高峰放完。但如果峰谷价差缩小到0.3元/kWh以下,加上折旧成本后套利空间为负,优化器会把电池SOC维持在较高水平但不进行频繁充放,甚至干脆待机。
这个结论很有工程意义:不是所有光储系统都适合每天做满充满放的循环。峰谷价差不足时,储能更合理的定位是“备用容量”而非“套利工具”。目标函数中c_degrad这个系数的设定,直接影响调度策略是激进还是保守,需要结合电池的实际循环寿命成本来标定。
5.4 三场景经济性对比
为了更直观地说明优化调度的价值,下表列出了三种天气下“固定策略”和“整数规划优化策略”的日运行费用对比(单位:元,场景数据来自一个典型工商业项目):
| 天气类型 | 固定策略日费用 | 优化调度日费用 | 降费比例 | 主要收益来源 |
|---|---|---|---|---|
| 晴朗夏日 | 2850 | 2450 | 约14% | 光伏消纳+峰谷套利 |
| 多云天气 | 3130 | 2820 | 约10% | 预测感知+决策留有余量 |
| 阴雨时段 | 3560 | 3320 | 约7% | 纯峰谷套利+减少无效循环 |
需要注意,这个对比的前提是电量电价模型足够准确,且实际执行时能基本跟踪调度指令。如果现场通信延迟大、PCS响应慢,优化收益会被显著侵蚀,这就需要在下文的部署经验部分做针对性处理。
6. 现场部署踩坑与进阶方向
6.1 求解器选型和求解时间控制
选型上,开源CBC适合中小模型,但有几个问题:一是数值稳定性偶尔会出问题,二是分支定界策略比较“愣”,遇到大规模整数变量时求解时间可能失控。我遇到过24时段、只有48个二进制变量的小模型,CBC用了十几分钟还没收敛,换Gurobi后两秒出解。所以交付商业项目时,我倾向于直接用商用求解器,前期验证才用CBC。
如果预算有限必须留在开源方案,可以考虑用CBC求解时设置时间限制和相对MIP Gap限制。比如设定gap=1%或时间上限120秒,这样即使没有收敛,也能拿到一个质量足够好的可行解,对于调度场景完全够用。注意不要以默认参数直接跑大模型,否则一旦陷入分支定界深谷,调度任务根本无法按时执行。
6.2 SOC数值漂移与约束松弛
前面提到过,SOC递推公式中充电和放电效率如果处理不当,模型内部计算的SOC和BMS实际反馈的SOC会产生偏差。这个偏差虽然每个时段只有零点几个百分点,但累计运行几天后可能达到5%~10%,导致调度计划的置信度下降。
解决思路有两个。一是定期校准:每6小时或每天凌晨,用BMS上报的真实SOC覆盖模型中的SOC初始值,重新滚动求解。二是把SOC当作目标函数中的软约束,加上惩罚项,让优化器尽量把SOC保持在目标范围内,而不是生硬地规定边界。这样即使BMS反馈和模型计算存在偏差,也不会出现调度指令撞上BMS保护边界的尴尬。
6.3 从单日优化到滚动调度的进阶路线
前面已经多次提到滚动时域调度,这里给出一个具体的工程建议:
- 将调度周期从24小时缩短为“未来6小时”,每15分钟滚动一次;
- 每次滚动时读取最新的光伏预测、负荷预测、BMS的SOC和电价信号;
- 用MILP求解出未来6小时的调度计划,但只执行下一个15分钟窗口的指令;
- 下一周期重复上述过程,实现模型预测控制(MPC)式的闭环调度。
这种模式对预测误差有天然的抗干扰能力,也能及时响应电网调度指令。实测下来,滚动调度相比单日静态调度,在多云天气下的运行费用还能再降3%~5%,同时基本杜绝了“计划不可行”的问题。唯一的代价是计算频率提高,但对MILP求解器来说,6小时、24个时段的模型规模完全不是负担。
6.4 从单储能到多资源的模型扩展
如果项目后期加入柴油发电机、可调负荷、多台储能,MILP模型可以非常自然地扩展。每加一台机组,就增加一组0-1变量和启停约束;每加一个可调负荷,就增加一个连续变量和功率可调区间。模型规模线性增长,求解器依然能够处理。
我现在的标准做法是把调度引擎封装成一个微服务,输入是预测数据和系统参数,输出是功率调度计划,接口用标准JSON格式。这样不管是接入现有EMS,还是后期扩展为配合电网需求响应,都能快速迭代,不用推倒重来。
最后再分享一个经验:整数规划模型不是写完就完事,它需要持续根据现场运行数据进行标定和修正。光伏预测模型在换季之后精度会下降,电池的实际容量会随着老化衰减,电价政策也可能调整。我一般建议每季度回头重新校准一次模型参数,特别是电池效率、可用容量和折旧成本这三个系数。踩过几次坑之后,你会明白调度算法真正上线的难点不在“求解”,而在“让模型始终贴近物理世界”。这需要你把每个约束、每个参数背后对应的现场设备逻辑都吃透,而这恰恰是光储优化调度项目里最值钱的功夫。