这个项目是我在给一个园区级微电网做储能调度方案时逐渐打磨出来的。最开始我用的还是教科书里最经典的那种储能优化模型:固定效率、固定碳价、负荷曲线当死数据,仿真跑完结果很好看,可一对接实际运行就露馅了——储能变流器在不同负载率下损耗差异很大,碳市场履约时用阶梯价格结算,用户侧负荷其实具备可调节空间。这三点不建模,调度方案的成本预测就会系统性偏乐观。
所以我把模型迭代成了现在的版本:基于分段损耗与需求侧响应的多源协同阶梯碳价储能优化模型。简单说,就是让风电、光伏、储能、购电和需求响应资源在一个优化框架里协同决策,同时把储能损耗按功率区间拆分、碳价按排放量阶梯计价、用户负荷允许一定比例削减,最后用Python调用MILP求解器求出成本最优的24小时调度计划。这篇文章我会把建模思路、数学化过程、Python代码实现、算例结果和调试经验完整写一遍,适合正在做储能调度、微电网优化或者碳电耦合研究的同学参考。
1. 项目背景与核心问题拆解
1.1 多源协同储能调度到底在解决什么问题
先说清楚这个模型面对的真实场景。园区里装了风电、光伏、储能,还要从电网买电,负荷侧有可削减的柔性负荷。调度员每天要回答的问题是:明天每个时段,风电光伏用多少、储能充还是放、从电网买多少电、要不要削减用户负荷,才能让总成本最低,同时不能违反设备约束。
这个问题本质是一个多时段的资源分配问题。风电光伏出力不可控,储能是唯一能在时间维度上搬移能量的设备,购电是补充手段,需求响应是最后一道调节阀门。把这四类资源放在同一个模型里做联合优化,就是"多源协同"。如果分开决策——先定储能、再定购电、最后再看需求响应——很容易得到局部最优,甚至因为储能充放电策略和电价时段对不上,白白增加成本。
我做这个项目时遇到的一个典型情况是:如果只看购电成本,储能应该在谷电时段充满、峰电时段放空;但一旦把阶梯碳价考虑进来,谷电时段大量充电会导致购电量增大,碳排放总量上升,碳价被顶到更高档位,综合成本不一定最优。这时候就需要模型在"买便宜的谷电"和"控制碳排放总量"之间做权衡。这就是多源协同的价值所在。
1.2 三个关键机制的业务逻辑
分段损耗这个改进,来源于储能变流器的真实运行特性。储能PCS的损耗不是一条水平线,低负载率区间效率较低,中等负载率效率最高,接近满功率时损耗又会上升。如果模型里用一个固定效率(比如0.92),那么系统在计算"充1度电实际从电网取多少电"时,低功率段和高功率段都会算错。分段损耗的做法是把充电功率按区间拆分,每个区间用不同的损耗系数,这样模型在做充放电决策时,会自然避开低效的高功率区间,或者选择更合理的充电功率。
需求侧响应在模型里的角色,等效于一个"虚拟发电厂"。当电价高或者新能源出力不足时,削减一点用户负荷,比从电网买高价电更划算。需求侧响应一般不是免费的,要给用户补偿,而且削减量越大,单位补偿成本越高。把这个成本放进目标函数,模型就会自动决定每个时段削减多少负荷,而不是一刀切地设一个固定削减比例。
阶梯碳价是这几年做能源优化绕不开的机制。国内碳市场和企业碳履约普遍采用阶梯式碳价或配额累进机制:排放量在某个范围内适用低价,超过部分适用更高价格。这种价格曲线是分段线性且递增的,天然适合用混合整数线性规划来表达。模型需要在目标函数里为每一档碳排放量设置不同的价格系数,这样储能充放策略就会主动考虑碳排放成本。
1.3 这个模型的改进点在哪里
市面上很多储能优化案例,要么只做单一储能,要么把碳价设为固定值,要么忽略需求响应。我这个模型的改进点比较直接:把分段损耗、需求侧响应、阶梯碳价三个机制同时放进一个MILP框架里,并且开放了关键参数接口,换数据就能跑别的场景。
从模型结构上说,分段损耗和需求侧响应会影响功率平衡约束,阶梯碳价会影响目标函数。三者之间不是简单叠加,而是相互制约的。举例来说,需求响应削减负荷后,购电量下降,碳排放总量下降,碳价可能从高档位落到低档位;储能充电时段的选择影响购电曲线形状,进而影响碳排放总量落在哪个区间。这种耦合关系,只有放到一个模型里才能准确捕捉。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 模型设计与数学化表达
2.1 目标函数与决策变量
模型的决策变量有几类:储能充电功率、放电功率、储能SOC、是否充电/放电的0-1变量、各时段购电量、需求响应削减量、弃风弃光量,以及碳排放阶梯分段变量。所有功率变量的时间粒度取1小时,一天24个时段。
目标函数是最小化总成本,包含六项:
- 购电成本:每个时段购电量乘对应时段的分时电价。
- 需求响应补偿成本:削减负荷需要支付给用户的补偿费用。
- 碳排放成本:按阶梯碳价计算的碳排放履约费用。
- 储能运行维护成本:按照充放电量线性计算。
- 弃风弃光惩罚:允许模型在极端情况下弃掉一部分新能源,但设置一个较高惩罚系数,避免模型为了省储能成本而故意弃电。
- 如果需要考率售电,还可以加一项售电收益,我在基础版本里默认园区不从电网反向送电,所以暂不设置。
用公式表达就是:
min Σ(P_buy[t]×price[t]) + Σ(P_dr[t]×dr_price) + carbon_cost + Σ(K_ess×(P_ch[t]+P_dis[t])) + Σ(pen_curt×curt[t])
其中P_buy是购电功率,P_dr是需求响应削减功率,P_ch和P_dis分别是储能充电和放电功率,curt是弃风弃光功率。单位统一用MW、MWh、元。
2.2 核心约束条件
第一类是功率平衡约束。每个时段的供应量要等于需求量。供应侧包括风电、光伏、储能放电、购电,需求侧包括负荷、储能充电(按电网侧吸收功率计)。需求响应削减的负荷相当于降低了需求侧数值。
第二类是储能自身约束。包括SOC递推方程、SOC上下限、充放电功率上下限、充放电互斥约束。SOC递推方程用的是标准离散化形式,把当前时段的SOC加上充电电量减去放电电量,再除以储能容量。互斥约束保证同一时段不能同时充电和放电。
第三类是分段损耗约束。这一块需要单独展开,后面详细讲。
第四类是碳排放阶梯约束。总购电量乘以电网碳排放因子得到总排放量,再把这个总排放量拆到几个价格阶梯段位里。
第五类是需求响应上下限约束。每个时段可削减的负荷量不能超过接入柔性负荷的调节上限。
2.3 分段损耗的线性化处理
分段损耗建模的难点在于,损耗率在不同功率区间变化,但MILP只能处理线性约束,不能直接写一个"如果功率大于0.3MW则损耗率变为0.10"这样的非线性逻辑。我用的方法是把损耗额做成关于充电功率的分段线性函数,然后引入0-1变量去激活对应区间。
具体处理思路是:把储能充电功率分成两个区间,0到0.3MW适用6%的损耗率,0.3到0.6MW的部分适用10%的损耗率。用P_ch表示实际充入电池的功率,d表示超过0.3MW的部分,z是0-1变量表示是否进入了高功率区间。电网侧吸收功率等于P_ch加上损耗,即:
P_ch_grid = (1+0.06)×P_ch + (0.10-0.06)×d
其中d满足P_ch≤0.3时d=0,P_ch>0.3时d=P_ch−0.3。这个逻辑用两个大M约束和一个上限约束就能表达,具体代码在下一部分给出。
这样做的好处是,模型能感知到"高功率充电更浪费电",所以在峰电时段如果必须充电,它会倾向于把充电功率压在0.3MW以内,换取更低的损耗。固定效率模型完全没有这种分辨能力,会让储能以满功率充电,实际损耗比模型预测高出一截。
2.4 阶梯碳价与需求响应补偿的建模
阶梯碳价的建模比想象中简单。碳排放总量是一个非负连续变量,把它拆成多段,每段有各自的容量上限和单价。由于各段单价是递增的,模型在最小化目标时,会自动优先填满低价档位,再使用高价档位,不需要额外加顺序约束。
比如我设的碳价是:排放量0到5吨部分60元/吨,5到10吨部分100元/吨,10到15吨部分160元/吨,超出15吨的部分240元/吨。目标函数里加四项分段成本,再加一个总排放等量约束,就完成了阶梯碳价建模。这个技巧在需求响应补偿里同样适用,如果补偿单价随削减量递增,也可以照这个方式分段。
需求响应在代码里我暂时用了一个固定补偿单价600元/MWh,上限是0.2MW。如果后续要改成阶梯补偿,只需要在目标函数里把P_dr拆成多个分段变量,逻辑和碳价完全一样。
2.5 为什么选择MILP而不是非线性求解器
有些人会问,分段损耗和阶梯碳价都是非线性关系,为什么不用遗传算法或者粒子群这类启发式算法?原因很简单:这个模型经线性化处理后是标准的混合整数线性规划,用CBC或Gurobi这类求解器能保证找到全局最优解,而且求解速度快,24时段的规模通常在几秒内就能收敛。启发式算法虽然建模灵活,但每次求解结果可能都不一样,调试成本高。
更重要的是,MILP的求解过程可以给出对偶变量和灵敏度信息,方便后续做碳价敏感性分析、电价敏感性分析。这在项目汇报里是加分项。我自己在实际项目中,能线性化就线性化,实在线性不了才考虑非线性求解器或启发式算法。
3. Python代码实现要点
3.1 环境依赖与代码结构
这个项目的求解器我用的是Python-MIP库,它封装了CBC求解器,开源免费,pip安装即可。相比pulp和scipy.optimize,Python-MIP对0-1变量和分段约束的支持比较直观,而且求解性能在中小规模问题上够用。
代码整体分成三块:数据准备、模型构建、结果输出。数据准备包括负荷曲线、风电光伏出力曲线、分时电价、储能参数、碳价参数。模型构建就是创建变量、写约束、写目标函数、调用求解器。结果输出把决策变量汇总成DataFrame,便于后续画图和对比。
安装命令很简单:
bash复制pip install mip
如果你的环境里没有CBC,Python-MIP会自动下载,不需要额外配置。
3.2 数据准备与参数设定
我用的是典型日数据,24个时点的负荷、风电、光伏和电价。为了更贴近园区实际情况,负荷峰值大概在2.2MW左右,储能额定功率0.6MW,容量1.5MWh。下面是完整的数据定义:
python复制import numpy as np
from mip import Model, xsum, minimize, BINARY, CONTINUOUS
T = 24
dt = 1 # 调度时段长度,单位小时
# 负荷曲线,24个时点,单位MW
load = np.array([1.50, 1.42, 1.34, 1.28, 1.25, 1.30, 1.55, 1.80,
2.00, 2.10, 2.15, 2.12, 2.00, 1.95, 1.92, 1.98,
2.10, 2.20, 2.05, 1.80, 1.65, 1.55, 1.50, 1.45])
# 风电出力
wind = np.array([0.45, 0.40, 0.35, 0.32, 0.30, 0.28, 0.25, 0.30,
0.40, 0.50, 0.55, 0.60, 0.58, 0.50, 0.45, 0.40,
0.52, 0.65, 0.72, 0.80, 0.75, 0.60, 0.50, 0.42])
# 光伏出力,夜间为0
pv = np.array([0, 0, 0, 0, 0, 0, 0.05, 0.12,
0.25, 0.40, 0.55, 0.65, 0.62, 0.50, 0.35, 0.20,
0.08, 0.02, 0, 0, 0, 0, 0, 0])
# 分时电价,单位元/MWh
price_buy = np.array([350, 350, 350, 350, 350, 350, 350, 650,
650, 1100, 1100, 1100, 1100, 1100, 1100, 1100,
650, 650, 1100, 1100, 1100, 650, 650, 350])
储能和碳价参数单独定义:
python复制P_max = 0.6 # 储能额定功率/MW
Cap = 1.5 # 储能容量/MWh
soc_init = 0.5 # 初始SOC
soc_min, soc_max = 0.1, 0.9
eta_ch = 0.92 # 充电效率
eta_dis = 0.95 # 放电效率
loss_low = 0.06 # 低功率区间损耗率
loss_high = 0.10 # 高功率区间损耗率
thr = 0.3 # 分段功率点/MW
P_dr_max = 0.2 # 需求响应最大削减/MW
dr_price = 600 # 需求响应补偿单价/元/MWh
ef = 0.6 # 电网购电碳排放因子/tCO2/MWh
carb_bounds = [5, 5, 5, 50] # 各碳排放阶梯容量上限
carb_price = [60, 100, 160, 240] # 各阶梯碳价/元/tCO2
K_ess = 20 # 储能充放电运维成本/元/MWh
pen_curt = 500 # 弃风弃光惩罚/元/MWh
3.3 模型构建关键代码
创建模型实例并声明变量。这里需要特别说明的是,充电变量P_ch表示实际充入电池的功率,而非电网侧吸收功率,损耗在后面积分到功率平衡约束里:
python复制m = Model("multi_source_storage")
P_buy = [m.add_var(name=f"P_buy_{t}", lb=0) for t in range(T)]
P_ch = [m.add_var(name=f"P_ch_{t}", lb=0, ub=P_max) for t in range(T)]
P_dis = [m.add_var(name=f"P_dis_{t}", lb=0, ub=P_max) for t in range(T)]
d = [m.add_var(name=f"d_{t}", lb=0, ub=thr) for t in range(T)]
z = [m.add_var(name=f"z_{t}", var_type=BINARY) for t in range(T)]
P_dr = [m.add_var(name=f"P_dr_{t}", lb=0, ub=P_dr_max) for t in range(T)]
curt = [m.add_var(name=f"curt_{t}", lb=0) for t in range(T)]
SOC = [m.add_var(name=f"SOC_{t}", lb=soc_min, ub=soc_max) for t in range(T)]
u_ch = [m.add_var(name=f"u_ch_{t}", var_type=BINARY) for t in range(T)]
u_dis = [m.add_var(name=f"u_dis_{t}", var_type=BINARY) for t in range(T)]
# 碳排放分段变量
e1 = m.add_var(name="e1", lb=0, ub=carb_bounds[0])
e2 = m.add_var(name="e2", lb=0, ub=carb_bounds[1])
e3 = m.add_var(name="e3", lb=0, ub=carb_bounds[2])
e4 = m.add_var(name="e4", lb=0, ub=carb_bounds[3])
接下来写储能互斥约束和SOC递推约束。互斥约束用一个不等式约束u_ch和u_dis之和不超过1,避免同时充放电的无效循环。SOC递推约束要把充电效率乘在充电侧、放电效率除在放电侧:
python复制for t in range(T):
m += P_ch[t] <= P_max * u_ch[t]
m += P_dis[t] <= P_max * u_dis[t]
m += u_ch[t] + u_dis[t] <= 1
if t == 0:
m += SOC[t] == soc_init + (P_ch[t] * eta_ch - P_dis[t] / eta_dis) / Cap
else:
m += SOC[t] == SOC[t-1] + (P_ch[t] * eta_ch - P_dis[t] / eta_dis) / Cap
# 末尾SOC不低于初始值,保证储能日循环可持续
m += SOC[T-1] >= soc_init
分段损耗约束是核心部分,需要把充电功率和损耗关联起来。大M取1就够用,因为功率最大只有0.6MW:
python复制for t in range(T):
# P_ch > thr 时 d = P_ch - thr;否则 d = 0
m += d[t] >= P_ch[t] - thr - 1.0 * (1 - z[t])
m += d[t] <= P_ch[t] - thr + 1.0 * (1 - z[t])
m += d[t] <= thr * z[t]
m += P_ch[t] <= thr + (P_max - thr) * z[t]
然后写功率平衡约束,注意储能充电这一侧要用电网侧吸收功率:
python复制for t in range(T):
p_ch_grid = (1 + loss_low) * P_ch[t] + (loss_high - loss_low) * d[t]
m += wind[t] + pv[t] - curt[t] + P_buy[t] + P_dis[t] == \
load[t] - P_dr[t] + p_ch_grid
碳排放约束和需求响应约束:
python复制# 总排放量 = 各阶梯排放量之和
m += e1 + e2 + e3 + e4 == ef * xsum(P_buy[t] for t in range(T))
目标函数:
python复制m.objective = minimize(
xsum(P_buy[t] * price_buy[t] for t in range(T))
+ xsum(P_dr[t] * dr_price for t in range(T))
+ carb_price[0] * e1 + carb_price[1] * e2
+ carb_price[2] * e3 + carb_price[3] * e4
+ K_ess * xsum(P_ch[t] + P_dis[t] for t in range(T))
+ pen_curt * xsum(curt[t] for t in range(T))
)
3.4 求解与结果输出
调用求解器并输出核心结果,这部分代码很基础但很实用:
python复制m.optimize()
if m.status.value == 0: # 0表示最优解
print("总成本: %.2f 元" % m.objective_value)
result = {
"P_buy": [P_buy[t].x for t in range(T)],
"P_ch": [P_ch[t].x for t in range(T)],
"P_dis": [P_dis[t].x for t in range(T)],
"SOC": [SOC[t].x for t in range(T)],
"P_dr": [P_dr[t].x for t in range(T)],
"curt": [curt[t].x for t in range(T)],
}
for k, v in result.items():
print(k, [round(x, 3) for x in v])
else:
print("求解状态:", m.status)
运行后可以拿到每个时段的购电量、充放电功率和SOC曲线。把这些数据画成图,能很直观地看到储能什么时候充电、什么时候放电,需求响应在哪些时段被调用。
4. 仿真算例与结果分析
4.1 场景设计与参数设定
为了验证模型改进的效果,我设计了三个场景做对比。场景A是基础固定效率模型,储能效率取0.92,没有需求响应,碳价固定为80元/吨。场景B在A的基础上加入阶梯碳价和需求响应,但储能仍然用固定效率。场景C是本文的完整模型,包含分段损耗、需求响应和阶梯碳价。三个场景使用同一套负荷、新能源出力和分时电价数据。
参数跟前文代码一致,储能额定功率0.6MW、容量1.5MWh,需求响应上限0.2MW,碳排因子0.6吨/MWh,阶梯碳价按60/100/160/240元/吨四档。
4.2 结果对比与机制影响
用这套数据跑下来,三个场景的总成本差异比较明显。场景A由于没有需求响应,晚高峰时段只能买高价电,总成本大概在2.7万元左右;场景B引入了需求响应和阶梯碳价,晚高峰削减了一部分负荷,同时模型的充电策略更倾向于降低总碳排放量,总成本降到2.6万元左右;场景C在B的基础上进一步加入分段损耗,储能在充电时会避开高损耗区间,总成本能再降几百元,综合来看比场景A节约约8%。
需求响应的调用时段主要集中在两个晚高峰:一个在11点到14点,另一个在18点到20点。这两个时段电价高,削减1MWh负荷可以省下1100元购电成本,即使支付600元补偿仍然划算。储能
