ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

需求侧资源可调节能力评估与优化调度:Python+MILP复现指南

需求侧资源可调节能力评估与优化调度:Python+MILP复现指南

简介:电力系统需求侧资源可调节能力评估及优化调度研究的复现资料,面向电力系统研究人员、调度及需求侧管理专业人士、电气工程专业研究生,围绕双碳目标下储能、电动汽车、温控负荷等需求侧资源的可调节能力评估与优化调度问题展开。内容完整给出考虑功率容量、爬坡速率和状态约束的通用模型,多时段可调节功率域计算方法,基于闵可夫斯基和与边界近似的集群可调节功率域聚合方法,以及日前调度与日内实时调控两阶段负荷准线型优化控制策略。压缩包共1个文件,为835KB的PDF文档,包含详细可运行的Python代码及逐步解释,并探讨了机器学习增强方法、区块链应用场景、工程通信协议设计与边缘计算部署等扩展方向,兼顾理论深度与实践可操作性。已有89人浏览学习,适合需要复现论文、掌握需求侧资源建模与聚合算法、提升新能源消纳能力的读者。

1. 需求侧资源可调节能力评估与优化调度:这篇论文复现的关键是把评估和调度打通

电网在下午两点打来电话:明天 14:00—16:00 有一轮需求响应,需要你这边削减 800kW 负荷、持续 2 小时,确认后按补偿单价结算。你手上有储能、可转移的热水器负荷、可削减的空调负荷,这时最尴尬的问题不是“愿不愿意调”,而是“台账上根本算不出能调多少、能撑多久、代价多大”。电力系统需求侧资源可调节能力评估及优化调度研究,拆开看就是两件事:先评估“可调节能力”——量化工商业负荷、储能、分布式电源在特定时段能抬升或削减多少功率;再做“优化调度”——在这些能力边界内,用最小成本安排每台设备的启停和出力。这篇复现笔记会把两段链路都用 Python 串起来跑通,覆盖从数据构造、评估指标到 MILP 求解的完整落地路径。适合正在做论文复现的研究生、负荷聚合商和虚拟电厂方向的技术人员。

2. 可调节能力评估:先量化“能调多少、能调多久、响应多快”,再谈优化调度

2.1 四种需求侧资源的可调节能力画像

很多人拿到这个课题,第一反应是直接跳到优化调度模型里,把目标函数一写、约束一列就以为完事了。实际做下来你会发现,调度模型的输入——各时段可调容量上界、持续时间、响应速率——全部来自可调节能力评估这一步。评估做得糙,后面调度模型给出再漂亮的曲线,也是一个建立在错误可行域上的黑匣子输出。

需求侧资源大致分四类,评估侧重点完全不一样:

  • 可削减负荷:空调、照明、部分非连续工业负荷,评估重点是削减比例上限和最长可持续时间。这类负荷向上削减只能持续有限时长,比如空调削减超过 40 分钟后房间温度越界。
  • 可转移负荷:热水器、洗衣机、蓄热电锅炉,评估重点是允许转移的时间窗和日总能量。这些负荷本身必须完成固定电量,调度只能选时段。
  • 储能设备:评估重点是当前 SOC、最大充放电功率、容量,以及由这些参数推出来的各时段可充可放电量。
  • 可中断负荷与充电桩:响应速度快、可短时中断,评估重点放在分钟级爬坡速率和中断时长约束上。

四种资源的时间尺度差异很大。储能和充电桩可以做到秒级到分钟级响应,空调削减需要 5—15 分钟爬坡,蓄热式设备则要提前 1—2 小时安排。这就是为什么评估不能只用一个“可调容量”数,必须落到每个调度时段上,按 15 分钟或 1 小时粒度输出一条可调能力曲线。

2.2 评估模型:可调容量、持续时间与爬坡速率的定义和公式

评估指标我一般收敛到三个:可调容量、持续时间、爬坡速率。可调容量是一个功率值,定义在某个时段 t、持续 Δt 内的平均功率变化量;持续时间受能量约束限制,比如储能能放满功率多久取决于剩余电量,可削减负荷能撑多久取决于舒适度边界;爬坡速率则是从当前运行点到目标出力的每分钟变化上限,直接决定响应邀约的最短提前量。

以向下调节(减少从电网购电)为例,t 时段最大可调容量可以写成:

R_down(t) = α_max · P_base(t) + min(P_dch_max, (E_soc(t) − E_min) / Δt)

这里 α_max 是可削减负荷的最大削减比例,P_base(t) 是该时段基线负荷,第二项是储能的最大放电功率与实际可用电量的较小值。向上调节(增加购电)侧则是可转移负荷功率加储能充电能力:

R_up(t) = P_trans_max + min(P_ch_max, (E_max − E_soc(t)) / Δt)

注意,储能部分的计算里 SOC 是逐时段变化的。评估阶段如果没有时序递推,只能做静态近似,比如假设 SOC 保持在某个水平;这种近似在持续时间短的场景下误差足够小,但一旦评估周期覆盖全天多段,就必须把 SOC 递推放进后续的调度模型里联合求解。我在 2.3 节的脚本里先给静态近似版本,第 4 章调度模型会补上时序递推,这也是两段代码之间的衔接逻辑。

2.3 评估计算脚本:输入负荷曲线,输出 96 点可调潜力

这一段给一个可以直接跑的评估脚本。数据是 24 小时、15 分钟粒度,共 96 个时段;负荷曲线先用一个带随机波动的正弦基线,后续替换成真实抄表数据只需改数据读取部分。

import numpy as np import pandas as pd T = 96 dt = 0.25 # 15分钟,单位小时 # 构造一条典型工业负荷日曲线,单位 kW t = np.arange(T) / 4.0 # 单位小时 load_base = 1800 + 420 * np.sin(2 * np.pi * (t - 6) / 24) \ + 260 * np.sin(2 * np.pi * (t - 14) / 12) rng = np.random.default_rng(42) load_base = np.clip(load_base + rng.normal(0, 40, T), 1200, 2600) # 评估参数 alpha_max = 0.25 # 可削减负荷最大比例 p_trans_max = 150.0 # 可转移负荷额定功率 kW p_ess_max = 300.0 # 储能最大充放功率 kW cap_ess = 600.0 # 储能容量 kWh soc_init = 0.5 # 评估时假设 SOC 保持 50% soc_min, soc_max = 0.2, 0.9 ess_up = np.minimum(p_ess_max, (soc_max - soc_init) * cap_ess / dt) ess_down = np.minimum(p_ess_max, (soc_init - soc_min) * cap_ess / dt) df = pd.DataFrame({ "时段": np.arange(T), "基线负荷_kW": load_base, "可削减下调_kW": alpha_max * load_base, "储能放电下调_kW": ess_down, # 静态近似,逐时段相同 "储能充电上调_kW": ess_up, # 静态近似,逐时段相同 "可转移上调_kW": p_trans_max # 简化:全时段可转移 }) df["总下调能力_kW"] = df["可削减下调_kW"] + df["储能放电下调_kW"] df["总上调能力_kW"] = df["储能充电上调_kW"] + df["可转移上调_kW"] df.to_csv("adjustable_capacity.csv", index=False) print(df.head(6))

这段代码在评估阶段做的是“静态近似”:储能部分的 SOC 固定为 50%,没有逐时段递推。逻辑是先把各资源的能力上限算出来叠加,给出一个当天的能力上限包络。注意“总下调能力”里可削减负荷和储能简单相加,但在实际运行中两者可能相互挤占,比如削减负荷后电费支出减少、储能放电收益降低,这种经济互斥会在调度模型里通过目标函数自动权衡,评估阶段只负责给上限。

参数说明里最容易被忽略的是soc_min。评估模型里如果把 SOC 下限定得太低,比如 10%,算出来的储能持续放电能力会偏乐观;实际运行时为了电池寿命和紧急备用,调度模型的下限往往设在 20%—30%。评估和调度两个阶段的边界参数如果不一致,后面复现结果对不上,这是这类课题最常见的返工原因之一。

3. 优化调度模型:目标函数、约束条件与求解器选型

3.1 目标函数怎么建:购电成本、调节成本与稳定性的取舍

评估给出了“能调多少”,优化调度解决“怎么调最划算”。最直接的建模思路是以 96 点调度周期内的总运行成本最小为目标,成本项包括向电网购电的费用、对可削减负荷的补偿、储能充放循环损耗折算。目标函数写出来是:

min Σ [ π(t)·P_grid(t) + c_cur·P_cur(t) + c_ess·(P_ess_c(t) + P_ess_d(t)) ]·Δt

其中 π(t) 是分时电价,P_grid(t) 是时段购电功率,P_cur(t) 是削减负荷功率,c_cur 是削减补偿单价,P_ess_c 和 P_ess_d 是储能充放电功率。为什么不给可转移负荷加启停成本?因为它做的是时间平移,本身用电总量不变,加一个很小的惩罚系数只是为了修正在多解情况下避免无意义的频繁启停。

这里有一个新手很容易绕晕的微妙点:削减负荷已经通过功率平衡约束降低了购电功率,相当于省了一笔电费,为什么还要额外给补偿成本?原因是补偿单价决定了削减行为的经济触发条件——只有当削负荷省下的购电费大于补偿支出时,模型才会启动削减。如果补偿单价定得比峰值电价还高,模型会疯狂削减,相当于用高成本买响应量;定得太低则削减永不发生。复现论文时,这一项参数会直接决定调度结果像不像文献里的曲线,值得单独做一组灵敏度分析。

3.2 三类核心约束:功率平衡、SOC 递推与设备边界

调度模型的约束分三类。第一类是功率平衡,每个时段电网购电加光伏出力加储能放电,等于基线负荷减去削减量、加上储能充电和可转移负荷功率:

P_grid(t) + P_pv(t) + P_ess_d(t) = P_base(t) − P_cur(t) + P_ess_c(t) + z(t)·P_trans

这里 z(t) 是 0—1 变量,表示可转移负荷是否在该时段运行。移项之后写成 Ax = b 的形式交给求解器。

第二类是储能 SOC 递推:

soc(t) = soc(t−1) + η_c·P_ess_c(t)·Δt − P_ess_d(t)·Δt / η_d

SOC 以 kWh 计,初始值和末值都要给约束。初始值是当前实测 SOC,末值通常要求不低于初始值,否则模型会在最后一个时段把电池放空,得到一个看着省电、实际不可持续的“假最优”。我在第 5 章会把这条作为踩坑记录展开。

第三类是设备边界:储能充放电功率都在 0 到额定功率之间、SOC 在 20%—90% 之间、削减量不超过评估给出的上限、可转移负荷只能在允许时间窗内启动。边界里的每一个数值都应来自第 2 章评估输出,而不是随便填。

比较有争议的是储能充放电互斥约束。严格建模要加一对 0—1 变量,保证同一时段不同时充放;工程上只要目标函数里充放电都有损耗成本,最优解天然不会同时充放,因为同时充放必然带来损耗而没有任何收益。复现文献时如果对不上别人的 SOC 曲线,可以先检查对方是不是加了互斥变量,这两种模型在边界参数很紧时会给出不同解。

3.3 求解器选型:从 Gurobi 到 SciPy MILP 的取舍

调度模型是混合整数线性规划(MILP),求解器选择直接影响复现难度。论文复现阶段我的建议顺序是:先用 SciPy 内置的 MILP 跑通逻辑,再考虑换商业求解器验证大规模场景。下面这个表是我在项目中反复用到的选型参考。

求解器授权适用规模上手成本备注
SciPymilp开源数百变量极低内置 HiGHS,论文复现够用
CBC开源数千变量低老牌,通过 PuLP 或 OR-Tools 调用
HiGHS开源万级变量中性能接近商业求解器
Gurobi / CPLEX商业十万级变量高多日滚动、随机优化时的主力

2019 年前的论文复现代码大多是 PuLP 加 CBC 的组合,现在的 SciPy 1.9 以上版本把 HiGHS 内嵌进来,写起来更简洁,也不需要额外装求解器二进制。96 时段的单日模型,变量数在 600 左右,HiGHS 秒解。真正要考虑换求解器是多日滚动、上千台设备聚合、或者随机场景抽样的算例,那种规模下 CBC 会明显卡顿。

4. 复现路径:一段能直接跑的 Python 调度代码拆解

4.1 先造一组可信的仿真数据:负荷、光伏与分时电价

没有真实数据的复现,第一步是构造一组参数和场景都说得通的仿真数据。常见做法是基线负荷用实测曲线加随机波动,光伏出力用典型日的半峰形状,电价按峰平谷三段设置。我一般会把数据构造写成独立脚本,因为后面做灵敏度分析和多场景对比时,只改这里就能批量出结果。

# 延续 2.3 节的 t、load_base,追加光伏与电价 # 光伏:07:00—19:00 之间近似半抛物线,峰值 600kW pv = np.zeros(T) sunrise, sunset = 28, 76 # 7:00 和 19:00 对应的时段索引 window = t[sunrise:sunset] pv[sunrise:sunset] = 600 * np.sin(np.pi * (window - 7) / 12) # 分时电价,单位 元/kWh:峰 1.05,平 0.62,谷 0.32 price = np.full(T, 0.62) price[40:56] = 1.05 # 10:00—14:00 峰 price[68:88] = 1.05 # 17:00—22:00 晚峰 price[0:28] = 0.32 # 0:00—7:00 谷 price[92:96] = 0.32 # 23:00—24:00 谷 np.save("load_base.npy", load_base) np.save("pv.npy", pv) np.save("price.npy", price)

数据参数要和场景绑定说明白:光伏峰值 600kW,晚峰在 17:00—22:00,储能在低谷充电、晚峰放电,这个参数组合下调度结果会呈现典型的“低储高发”形态。如果读者要复现某篇文献,直接把文献的算例参数(负荷峰值、光伏容量、分时电价表)替换到这里即可。注意t是从 0 开始的小时数,t[sunrise:sunset]对应 7 点到 19 点,索引区间两端都包含,这也是 15 分钟粒度数据里最容易看错的地方。

4.2 把评估结果转成调度模型的输入参数

评估脚本输出的是每个时段的能力上限,调度模型需要的是这些上限作为约束边界。衔接的关键是保证两侧参数一致:评估时用的alpha_max、soc_min必须和调度模型的边界参数是同一个来源,不能在评估里用 0.25、调度里却填 0.3,差出来的 20% 削减量会直接表现为调度结果里的越限。

衔接代码可以简单到几行,但逻辑上要明确:评估阶段算出的“总下调能力”在调度阶段不直接作为约束,而是拆解回各资源的独立边界。因为调度模型要决策的是每台设备的动作,而不是一个汇总后的总削减量。所以正确做法是把alpha_max * load_base作为削减变量上限、把p_ess_max作为储能功率上限、把评估里的时间窗作为 z 变量可取值区间,再让目标函数去决定实际用多少。

alpha_max = 0.25 p_trans_max = 150.0 e_trans_total = 600.0 # 可转移负荷日总能量 kWh trans_start, trans_end = 68, 88 # 17:00—22:00 p_ess_max = 300.0 cap_ess = 600.0 soc_init, soc_min, soc_max = 0.5, 0.2, 0.9 eta_c, eta_d = 0.95, 0.95

这些参数里e_trans_total是最容易被忽略的。可转移负荷的特点是“总量固定、时段可变”,总量约束一旦缺失,模型会让热水器一个时段都不启动,因为启动只会产生购电成本;加了总量约束,它才必须完成当天 600kWh 的用电任务,调度做的就是把这 600kWh 移到电价最低的时段。

4.3 MILP 装配与求解:变量顺序、约束矩阵与结果落盘

前面数据就位后,这一节把完整求解代码拆开讲。变量共 6 组、576 个,顺序固定为:p_grid、p_cur_down、z_trans、p_ess_c、p_ess_d、soc。所有约束装配成稀疏矩阵后交给milp求解。

import numpy as np from scipy.optimize import milp, LinearConstraint, Bounds from scipy import sparse T = 96 dt = 0.25 load_base = np.load("load_base.npy") pv = np.load("pv.npy") price = np.load("price.npy") n = 6 * T # ---- 目标系数,单位元 ---- c = np.zeros(n) c[0:T] = price * dt # 购电成本 c[T:2*T] = 1.0 * dt # 削减补偿 1.0 元/kWh c[2*T:3*T] = 0.01 * dt # 可转移启停小惩罚 c[3*T:4*T] = 0.05 * dt # 储能充电损耗成本 c[4*T:5*T] = 0.05 * dt # 储能放电损耗成本 # ---- 整数标记:z_trans 是 0-1 ---- integrality = np.zeros(n) integrality[2*T:3*T] = 1 # ---- 边界 ---- lb = np.zeros(n); ub = np.full(n, np.inf) lb[0:T] = -np.inf # 允许余电上网,购电为负代表售电 ub[T:2*T] = alpha_max * load_base # 削减上限 ub[2*T:3*T] = 0.0 ub[2*T+trans_start:2*T+trans_end] = 1.0 # 仅在时间窗内可启动 ub[3*T:4*T] = p_ess_max ub[4*T:5*T] = p_ess_max lb[5*T:6*T] = soc_min * cap_ess ub[5*T:6*T] = soc_max * cap_ess bounds = Bounds(lb, ub) # ---- 等式约束装配 ---- rows, cols, vals = [], [], [] b_eq = [] # 1) 功率平衡 (T 行) for t in range(T): # p_grid + p_cur - z*P_trans - p_ess_c - p_ess_d = load_base - pv rows += [t]*5 cols += [t, T+t, 2*T+t, 3*T+t, 4*T+t] vals += [1.0, 1.0, -p_trans_max, -1.0, -1.0] b_eq.append(load_base[t] - pv[t]) # 2) SOC 递推 (T 行) for t in range(T): r = T + t rows += [r, r, r] cols += [5*T+t, 3*T+t, 4*T+t] vals += [1.0, -eta_c*dt, dt/eta_d] if t > 0: rows.append(r); cols.append(5*T+t-1); vals.append(-1.0) b_eq.append(0.0) else: b_eq.append(soc_init * cap_ess) # 3) 可转移负荷日总能量 (1 行) r = 2*T for t in range(trans_start, trans_end): rows.append(r); cols.append(2*T+t); vals.append(p_trans_max * dt) b_eq.append(e_trans_total) A_eq = sparse.coo_matrix((vals, (rows, cols)), shape=(2*T+1, n)).tocsr() cons_eq = LinearConstraint(A_eq, np.array(b_eq), np.array(b_eq)) # ---- 不等式约束:SOC 末值不低于初始值 ---- A_ineq = sparse.csr_matrix(([1.0], ([0], [5*T+95])), shape=(1, n)) cons_ineq = LinearConstraint(A_ineq, soc_init * cap_ess, np.inf) # ---- 求解 ---- res = milp(c=c, constraints=[cons_eq, cons_ineq], integrality=integrality, bounds=bounds, options={"time_limit": 60}) if res.success: x = res.x p_grid = x[0:T] p_cur = x[T:2*T] z = x[2*T:3*T] ess_c = x[3*T:4*T] ess_d = x[4*T:5*T] soc = x[5*T:6*T] np.save("schedule_result.npy", x)

提示:需要 SciPy 1.9 及以上版本才能使用milp,旧版本只有linprog,不支持整数变量。安装或升级用 pip install scipy --upgrade 即可。

代码里有两个设计决策值得展开。第一是功率平衡约束写成“购电加削减减去转移减去充放等于净负荷”,把削减变量放在等式左边之后,削减行为会直接改变购电量,这在物理上是对的:削减 1kWh 负荷,电网购电就少 1kWh。第二是储能损耗成本对称设置,充放各 0.05 元/kWh,目的是让求解器自动规避无意义的循环,这也是我在 3.2 节说的互斥约束替代方案。

结果拆回后,建议先做一次平衡校验:对任意时段,p_grid + pv + ess_d 应该等于 load_base − p_cur + ess_c + z·p_trans_max。误差超过 1e-6 就说明约束矩阵装配有错,这是排查代码最快的方法。落盘时除了保存原始结果向量,最好同时输出一个便于画图的 DataFrame,列包含时段、基线、光伏、购电、削减、储能充放、SOC、转移负荷状态,这样后续画 Gantt 图和成本分解图就不用再翻原始向量。

5. 避坑指南:复现这个方向最容易翻车的 5 个地方

5.1 量纲混用:MW、kWh 和 15 分钟粒度怎么统一都写不对

现象:求解结果要么功率大得离谱,要么SOC曲线溢出边界。

原因:负荷和功率用 MW、电量用 MWh 是一套体系,换成 kW 和 kWh 后放电功率乘时间步长才能得到电量。15 分钟粒度下 Δt=0.25 小时,很多人忘记把功率乘 0.25 再累加进 SOC,导致一小时后 SOC 变化远大于实际。

解决:全部数据统一用 kW 和 kWh,时间步长单独定义为dt = 0.25,SOC 递推公式里每一项都显式乘上 dt。我在第 4 章代码里就是这么处理的,约束矩阵里的eta_c*dt和dt/eta_d两次出现,都是功率到电量的换算。

5.2 光伏大于负荷时模型直接不可行:缺了一个弃光出口

现象:同样的代码,换一组光伏数据后res.success变成 False,求解器报不可行。

原因:功率平衡等式写死p_grid = load − pv − ...,当 pv 大于负荷和储能充电能力之和时,等式要求 p_grid 为负。如果 p_grid 的下界是 0,模型无解;如果没给下界约束,p_grid 为负代表上网售电,但很多论文场景不允许倒送。

解决:按场景分两种处理。电网允许余电上网就把购电变量下界设为负无穷,售电按同一电价结算(第 4 章代码采用的就是这个);不允许上网就必须加弃光变量或削减光伏变量,并在目标函数里加重惩罚,让求解器宁可少用光伏也不破坏等式。复现文献时看对方有没有弃光项,能直接判断它的光伏渗透率假设。

5.3 储能 SOC 末端约束缺失,调度结果虚高

现象:结果里储能从 22:00 开始疯狂放电到 24:00 放空,整体购电成本显著低于正常水平。

原因:模型只关心 24 小时内的购电成本,最后时段 SOC 越低越省钱,没有“明天还要继续运行”的视角。这是单日优化模型的天生缺陷。

解决:加终端约束soc[95] >= soc_init * cap_ess,代码在 4.3 节的不等式约束里已经体现。多日滚动场景下还应该在每个优化周期的末端留出安全电量,比如 20%—30% SOC,否则第二天开局就处于低电量状态,可调能力评估结果全部偏乐观。

5.4 可转移负荷被拆成碎片:缺了连续运行约束

现象:热水器一天启动 10 次,每次只有 15 分钟,z 变量序列在时间窗内高频跳动。

原因:总能量约束只限制了总用电量,没有限制“一旦启动必须连续运行至少 K 个时段”。MILP 求解器发现每次短时运行可以精确匹配电价低谷,就产生了碎片化调度,真实设备根本不能这样启停。

解决:加入最小连续运行约束,公式是z[t]-z[t-1] <= z[t+1]...z[t+K-1]的线性组合,实际代码里常见写法是:

for t in range(trans_start, trans_end - 8): rows.append(...); cols += [2*T+i for i in range(t, t+8)]; vals.append([1]*8)

这个约束的含义是:如果第 t 时段启动,那么接下来 8 个时段必须全部运行。代价是增加几十行约束,对求解时间影响很小。复现时如果文献里的可转移负荷是空调或蓄热设备,几乎必然带这类约束。

5.5 整数变量与连续变量的数值尺度差距过大,求解器假死

现象:模型规模没变,但求解时间从秒级变成几分钟,或者结果里 z 变量出现 0.9999 这种不干净的值。

原因:目标函数的成本系数有的在 0.001 数量级,有的在 1.0 数量级,边界约束里功率动辄几百上千。HiGHS 对整数变量采用分支定界,目标尺度相差三个数量级时,最优性上下界的收敛判据会变得很敏感。

解决:把所有目标系数统一到相近数量级,方法就是把各项成本都乘以 dt 并做规格化。我在 4.3 节的目标系数全部是“元/kWh 乘小时数”,数值落在 0.001—0.3 之间。如果发现求解器收敛异常,先打印c数组看极差,超过两个数量级就做归一化,这是这章节最玄学、也最实用的排错手段。

6. 验证与进阶:灵敏度分析、多场景对比和滚动调度的扩展思路

如何验证一套调度模型是真的可用而不是凑出好看曲线?我的习惯是先做三条物理校验:逐时段功率平衡误差低于 1e-6、SOC 递推回代残差低于 1e-6、削减量和转移量的总和不超过评估阶段给出的各资源上限。这三条过了,模型没有结构性错误,接下来才做经济性验证——把调度方案和“零调节”基线对比,算出削峰量、成本节省和储能循环次数,这些数字是论文里最核心的表格数据。

进阶方向里最推荐先做灵敏度分析。方法很简单:把alpha_max从 0.1 步进到 0.4,每次重跑一遍模型,记录总成本和晚峰购电功率。得到的结果通常是一条成本下降但逐渐变平缓的曲线,拐点就是这个资源集合的可调节能力经济边界。这个分析在论文里能直接支撑“可调节能力评估为调度提供边界依据”这个结论,也能用来反推评估阶段参数定得是否合理。

再进一步,可以改造为滚动时域调度。把 96 时段模型改成每 15 分钟滚动一次、预测时域缩到 4 小时,每次用实测负荷更新预测值,重算一次 MILP。SciPy 的milp在这个规模下单次求解在几十毫秒量级,滚动 96 次也就是秒级,完全扛得住。滚动版本需要额外处理的一个细节是 SOC 初值不再固定,每次用上一轮求解得到的末值带入,这时第 5 章说的末端约束要从“回到底值”改为“留出安全余量”。

我第一次跑通这套模型是在复现文献时,原论文的算例结果里总成本是 78,430 元,我用同样的参数跑出来是 82,150 元,差了快 5%。查了一整天才发现是对方储能充放效率取了 0.9,而我默认了 0.95,SOC 递推里的损耗差一点,整天成本就差出一截。这也是这类课题的常态——模型结构本身不复杂,但对参数细节极其敏感,每一个系数都要有数据来源或文献依据。希望这份笔记能帮你少走这一步弯路。

本文还有配套的精品资源,点击获取

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进