4. 单目标优化调度:以最小化运行成本为目标
好,咱们今天聊点实在的。前面几章我们把储能系统的硬件、通信、BMS 都过了一遍,现在终于到了最核心的部分——调度算法。
我个人习惯把调度问题分成两类:单目标和多目标。这一章,咱们先啃单目标。说白了,就是只盯着一个指标优化,比如成本最低、收益最高、或者寿命最长。今天咱们选的是——最小化运行成本。
4.1 问题定义:我们到底要优化什么?
先想一个问题:一个储能系统,每天充放电,到底花多少钱?
嗯,这里要注意,成本不只是电费。我遇到过不少项目,只盯着峰谷价差算收益,结果忽略了电池老化、运维费用,最后算下来根本不赚钱。
所以,咱们的成本模型至少包含三部分:
- 购电成本:从电网买电花的钱
- 售电收益:向电网卖电赚的钱(负成本)
- 电池退化成本:充放电一次,电池寿命缩短一点,折算成钱
目标函数就是:最小化 总成本 = 购电成本 - 售电收益 + 退化成本
4.2 线性规划模型:用数学说话
有了目标,接下来就是建模。我习惯用线性规划(LP),因为它简单、求解快、而且 PuLP 和 Scipy 都能搞定。
咱们把一天分成 24 个时段,每个时段 1 小时。定义变量:
P_charge[t]:t 时段的充电功率(kW)P_discharge[t]:t 时段的放电功率(kW)SOC[t]:t 时段末的电池荷电状态(%)
约束条件有哪些?你想想看:
- 功率约束:充放电功率不能超过额定值
- SOC 约束:不能过充过放,一般 10%~90%
- 能量守恒:SOC 变化 = 充电量 - 放电量 - 自损耗
- 始末 SOC 一致:一天结束,SOC 回到初始值,方便第二天调度
4.3 代码实现:用 PuLP 求解
好,理论讲完了,咱们直接上代码。我用的是 PuLP,因为它语法清晰,适合教学。
import pulp
import numpy as np
# 参数设置
T = 24 # 时段数
P_max = 100 # 最大充放电功率 (kW)
E_cap = 500 # 电池容量 (kWh)
SOC_min = 0.1
SOC_max = 0.9
SOC_init = 0.5
eta = 0.95 # 充放电效率
# 电价数据(元/kWh)
price_buy = [0.3, 0.3, 0.3, 0.3, 0.3, 0.3,
0.8, 0.8, 0.8, 1.2, 1.2, 1.2,
0.8, 0.8, 0.8, 1.2, 1.2, 1.2,
0.8, 0.8, 0.8, 0.3, 0.3, 0.3]
price_sell = [0.2, 0.2, 0.2, 0.2, 0.2, 0.2,
0.6, 0.6, 0.6, 1.0, 1.0, 1.0,
0.6, 0.6, 0.6, 1.0, 1.0, 1.0,
0.6, 0.6, 0.6, 0.2, 0.2, 0.2]
# 退化成本系数(元/kWh)
degradation_cost = 0.05
# 创建问题
prob = pulp.LpProblem("Minimize_Cost", pulp.LpMinimize)
# 定义变量
P_ch = [pulp.LpVariable(f"P_ch_{t}", 0, P_max) for t in range(T)]
P_dis = [pulp.LpVariable(f"P_dis_{t}", 0, P_max) for t in range(T)]
SOC = [pulp.LpVariable(f"SOC_{t}", SOC_min, SOC_max) for t in range(T)]
# 目标函数:购电成本 - 售电收益 + 退化成本
cost_buy = pulp.lpSum([price_buy[t] * P_ch[t] for t in range(T)])
revenue_sell = pulp.lpSum([price_sell[t] * P_dis[t] for t in range(T)])
cost_degrad = pulp.lpSum([degradation_cost * (P_ch[t] + P_dis[t]) for t in range(T)])
prob += cost_buy - revenue_sell + cost_degrad
# 约束条件
# SOC 递推关系
for t in range(T):
if t == 0:
prob += SOC[t] == SOC_init + (P_ch[t] * eta - P_dis[t] / eta) / E_cap
else:
prob += SOC[t] == SOC[t-1] + (P_ch[t] * eta - P_dis[t] / eta) / E_cap
# 始末 SOC 一致
prob += SOC[T-1] == SOC_init
# 求解
prob.solve()
# 输出结果
print(f"总成本: {pulp.value(prob.objective):.2f} 元")
for t in range(T):
print(f"时段 {t}: 充电={pulp.value(P_ch[t]):.1f}kW, 放电={pulp.value(P_dis[t]):.1f}kW, SOC={pulp.value(SOC[t]):.2f}")
4.4 结果分析:算法到底学到了什么?
跑完代码,你会发现一个规律:算法会在电价低的时候充电,电价高的时候放电。这听起来像废话,但细节值得琢磨。
比如,为什么算法不在所有低价时段都充满?因为 SOC 有上限,而且充多了会退化。为什么不在所有高价时段都放光?因为 SOC 有下限,而且始末要一致。
说白了,这是一个带约束的套利问题。算法在电价曲线和 SOC 约束之间找平衡点。
4.5 知识体系图
下面这张图,是我自己总结的本章知识结构。你看一眼,就能把整个逻辑串起来。
4.6 避坑指南:我踩过的三个坑
做这个模型,我至少踩过三个坑,今天全部分享给你:
- 效率系数搞反了:充电时能量有损耗,所以充电功率要乘以效率;放电时反过来,放电功率要除以效率。我曾经写反了,结果算出来的 SOC 越充越少。
- 电价数据没对齐:有些项目用的电价是 15 分钟一个点,但模型是 1 小时一个点。记得做重采样,不然结果完全不对。
- 退化成本设为零:初期为了简化,我把退化成本设为零。结果算法疯狂充放电,一天充放 10 次,电池寿命直接砍半。后来加了退化成本,调度才变得合理。
4.7 总结
这一章,我们从问题定义到数学建模,再到代码实现,完整走了一遍单目标优化调度的流程。核心就三句话:
- 目标要明确:最小化运行成本,不是最大化收益
- 约束要完整:功率、SOC、能量守恒、始末一致,一个都不能少
- 参数要合理:退化成本、效率系数,直接影响结果质量
下一章,我们会把单目标扩展到多目标,看看如何在成本和电池寿命之间找平衡。但那是后话了,先把今天的内容消化掉。
公众号:蓝海资料掘金营,微信deep3321