Pyomo索引块与标准建模性能对比咨询:大规模时序能源优化
在Pyomo大规模时序优化问题中,标准建模是否比索引块建模更快?
我正在用Pyomo构建含大量时段(如8760小时)的时序能源优化模型,参考《Pyomo Book》8.6.2节的索引块方式,为每个时段创建独立Block。但实际测试发现,索引块建模的耗时远高于无块的标准建模,且模型越复杂差距越大。以下是光伏-电池系统成本最小化的两种实现代码,逻辑完全一致,但索引块建模的建模耗时约为标准建模的4倍。
标准建模实现
import pyomo.environ as pyo from pyomo.common.timing import TicTocTimer import random # 生成PV和负荷随机数据 pv = [random.randint(0, 5) for _ in range(8760)] pv_dict = dict(enumerate(pv, 1)) load_el = [random.randint(0, 8) for _ in range(8760)] load_el_dict = dict(enumerate(load_el, 1)) # 创建标准形式Pyomo模型 timer = TicTocTimer() timer.tic('Start Sandard formulation') model = pyo.ConcreteModel() # 定义时段集合 model.T = pyo.RangeSet(len(pv_dict)) # 定义参数 model.pv = pyo.Param(model.T, initialize=pv_dict) model.load_el = pyo.Param(model.T, initialize=load_el_dict) model.grid_cost_buy = pyo.Param(model.T, initialize=0.4) model.battery_eoCH = pyo.Param(initialize=1.0) model.battery_eoDCH = pyo.Param(initialize=0.1) model.battery_capacity = pyo.Param(initialize=5) # 定义变量 model.battery_power_CH = pyo.Var(model.T, domain=pyo.NonNegativeReals) # 电池充电功率 model.battery_power_DCH = pyo.Var(model.T, domain=pyo.NonNegativeReals) # 电池放电功率 model.battery_soc = pyo.Var(model.T, bounds=(model.battery_eoDCH, model.battery_eoCH)) # 电池SOC model.grid_power_import = pyo.Var(model.T, domain=pyo.NonNegativeReals) # 电网购电功率 model.grid_power_export = pyo.Var(model.T, domain=pyo.NonNegativeReals) # 电网售电功率 # 电池约束 def battery_end_of_CH_rule(m, t): return m.battery_soc[t] <= model.battery_eoCH model.battery_eoCH_c = pyo.Constraint(model.T, rule=battery_end_of_CH_rule) def battery_end_of_DCH_rule(m, t): return m.battery_soc[t] >= model.battery_eoDCH model.battery_eoDCH_c = pyo.Constraint(model.T, rule=battery_end_of_DCH_rule) def battery_soc_rule(m, t): if t == m.T.first(): return m.battery_soc[t] == ((m.battery_power_CH[t] - m.battery_power_DCH[t]) / model.battery_capacity) return m.battery_soc[t] == m.battery_soc[t-1] + ((m.battery_power_CH[t] - m.battery_power_DCH[t]) / model.battery_capacity) model.battery_soc_c = pyo.Constraint(model.T, rule=battery_soc_rule) # 电力平衡约束 def balanced_bus_rule(m, t): return (0 == (m.pv[t] - m.load_el[t] + m.battery_power_DCH[t] - m.battery_power_CH[t] + m.grid_power_import[t] - m.grid_power_export[t])) model.bus_c = pyo.Constraint(model.T, rule=balanced_bus_rule) # 目标函数 def obj_rule(m): return sum(m.grid_power_import[t]*m.grid_cost_buy[t] for t in m.T) model.obj = pyo.Objective(rule=obj_rule, sense=1) timer.toc('Built model') # 求解 solver = pyo.SolverFactory('gurobi') results = solver.solve(model) timer.toc('Wrote LP file and solved') print('Total operation costs:', pyo.value(model.obj))
索引块建模实现
import pyomo.environ as pyo from pyomo.common.timing import TicTocTimer import random # 生成PV和负荷随机数据 pv = [random.randint(0, 5) for _ in range(8760)] pv_dict = dict(enumerate(pv, 1)) load_el = [random.randint(0, 8) for _ in range(8760)] load_el_dict = dict(enumerate(load_el, 1)) # 创建索引块形式Pyomo模型 timer = TicTocTimer() timer.tic('Start Indexed Block formulation') model = pyo.ConcreteModel() model.T = pyo.RangeSet(len(pv_dict)) def block_rule(b, t): # 定义参数 b.pv = pyo.Param(initialize=pv_dict[t]) b.load_el = pyo.Param(initialize=load_el_dict[t]) b.grid_cost_buy = pyo.Param(initialize=0.4) b.battery_eoCH = pyo.Param(initialize=1.0) b.battery_eoDCH = pyo.Param(initialize=0.1) b.battery_capacity = pyo.Param(initialize=5) # 定义变量 b.battery_power_CH = pyo.Var(domain=pyo.NonNegativeReals) # 电池充电功率 b.battery_power_DCH = pyo.Var(domain=pyo.NonNegativeReals) # 电池放电功率 b.battery_soc = pyo.Var(bounds=(b.battery_eoDCH, b.battery_eoCH)) # 电池SOC b.battery_soc_initial = pyo.Var() # 初始SOC b.grid_power_import = pyo.Var(domain=pyo.NonNegativeReals) # 电网购电功率 b.grid_power_export = pyo.Var(domain=pyo.NonNegativeReals) # 电网售电功率 # 约束定义 def balanced_bus_rule(_b): return (0 == (_b.pv - _b.load_el + _b.battery_power_DCH - _b.battery_power_CH + _b.grid_power_import - _b.grid_power_export)) b.bus_c = pyo.Constraint(rule=balanced_bus_rule) def battery_end_of_CH_rule(_b): return (_b.battery_eoCH >= _b.battery_soc) b.battery_eoCH_c = pyo.Constraint(rule=battery_end_of_CH_rule) def battery_end_of_DCH_rule(_b): return (_b.battery_eoDCH <= _b.battery_soc) b.battery_eoDCH_c = pyo.Constraint(rule=battery_end_of_DCH_rule) def battery_soc_rule(_b): return (_b.battery_soc == _b.battery_soc_initial + ((_b.battery_power_CH - _b.battery_power_DCH) / _b.battery_capacity)) b.battery_soc_c = pyo.Constraint(rule=battery_soc_rule) # 初始化每个时段的Block model.pvbatb = pyo.Block(model.T, rule=block_rule) # 跨块SOC链接约束 def battery_soc_linking_rule(m, t): if t == m.T.first(): return m.pvbatb[t].battery_soc_initial == 0 return m.pvbatb[t].battery_soc_initial == m.pvbatb[t-1].battery_soc model.battery_soc_linking = pyo.Constraint(model.T, rule=battery_soc_linking_rule) # 目标函数 def obj_rule(m): return sum(m.pvbatb[t].grid_power_import * m.pvbatb[t].grid_cost_buy for t in m.T) model.obj = pyo.Objective(rule=obj_rule, sense=1) timer.toc('Built model') # 求解 solver = pyo.SolverFactory('gurobi') results = solver.solve(model) timer.toc('Wrote LP file and solved') print('Total operation costs:', pyo.value(model.obj))
测试输出
[ 0.00] Start Sandard formulation [+ 0.92] Built model [+ 3.37] Wrote LP file and solved Total operation costs: 5706.199999999935 [ 4.31] Start Indexed Block formulation [+ 12.50] Built model [+ 4.62] Wrote LP file and solved Total operation costs: 5706.199999999934
回答
结论:是的,大规模时序优化场景下标准建模通常比索引块建模更快
核心原因
- 对象创建开销差异:标准建模通过集合索引一次性创建批量的Param、Var、Constraint对象,Pyomo内部采用更高效的数组式管理;而索引块为每个时段创建独立Block,每个Block内又重复创建大量独立对象,8760个时段会带来8760倍的单个对象初始化开销,累计后差距显著。
- 跨时段约束的额外成本:索引块建模需要额外定义跨块的链接约束(如SOC时序衔接),这部分逻辑需要遍历所有Block并建立关联,比标准建模中直接通过
t-1索引关联的方式更耗时。 - Pyomo内部处理复杂度:嵌套的Block结构会增加Pyomo遍历模型组件、生成LP文件时的复杂度,尤其是在大规模场景下,这种嵌套层级会拖慢模型预处理速度。
索引块建模的适用场景
尽管性能劣势明显,索引块仍有其价值:
- 模块化复用:当需要重复使用时段子模型逻辑(如多场景扩展、多设备类型建模),Block的封装能提升代码可维护性。
- 复杂子模型管理:若每个时段的子模型包含大量独立逻辑(如多设备耦合、多约束组),用Block封装可让代码结构更清晰,此时性能开销可能在可接受范围内。
索引块建模的优化建议
如果必须使用索引块,可尝试以下方式降低开销:
- 共享全局参数:将电池容量、充放电效率等全局参数定义在顶层模型中,Block内部直接引用,避免重复创建大量相同参数对象。
- 简化链接约束:尽量采用批量方式定义跨块约束,减少循环遍历的开销。
- 使用轻量Block:用
pyomo.core.base.block.SimpleBlock替代默认Block,它去掉了一些高级功能,初始化开销更低。
内容的提问来源于stack exchange,提问作者fabmid
相关产品推荐
相关产品推荐

