Pyomo模型求解耗时过长,请求排查约束与目标函数问题
资产年度预算调度模型求解故障排查
我基于Pyomo构建了资产年度预算调度模型,需要满足以下需求:
- 确保每年预算足以覆盖资产处理成本;
- 同组资产、同一实体的资产需安排在同一年处理;
- 每个资产有允许的处理年份范围。
预算理论上可满足需求,但求解器始终无法得出结果且持续运行。以下是可执行代码,恳请帮忙排查约束或目标函数的问题:
import pyomo.environ as pyo # estimated cost for each asset estimated_cost_dd = {0: 49327.07294, 1: 22026.18216, 2: 6613.641422, 3: 6439.077904, 4: 19092.74808, 5: 71005.47705, 6: 46632.3224, 7: 7238.580172, 8: 6072.856201, 9: 43138.03009, 10: 8775.922046, 11: 20366.35618, 12: 12138.7625, 13: 12138.7625, 14: 48251.42843, 15: 7895.688288, 16: 7972.556789, 17: 42042.41723, 18: 263501.5554, 19: 6433.760056, 20: 6433.760056, 21: 6547.57515, 22: 57935.37342, 23: 7117.017004, 24: 5682.424608, 25: 7264.794211, 26: 7264.794211, 27: 6942.685975, 28: 22603.94456, 29: 6486.832091, 30: 101135.4634} # budget for each year budget_dd = {1: 69336, 2: 430750, 3: 286106, 4: 50256, 5: 106072} # some asset are actually the same same_asset_dd = {'R00168': [14, 15], 'R00234': [19, 20], 'R00843': [25, 26], 'R00973': [12, 13]} # some assets falls into the same group. for example both 12,13 are in the group 198 same_group_dd = {10.0: [0], 54.0: [1], 63.0: [2], 112.0: [3], 121.0: [4], 132.0: [5, 6], 164.0: [7], 167.0: [8], 171.0: [9], 178.0: [10], 185.0: [11], 198.0: [12, 13], 201.0: [14, 15], 203.0: [16], 216.0: [17], 220.0: [18], 240.0: [19, 20], 261.0: [21], 263.0: [22], 285.0: [23], 291.0: [24], 330.0: [25, 26], 346.0: [27], 347.0: [28], 350.0: [29], 381.0: [30]} # year range of each assets that allows which year its treatment can be moved year_range_dd ={0: [1, 2, 3, 4, 5], 1: [1, 2, 3, 4], 2: [2, 3, 4, 5], 3: [1, 2, 3, 4], 4: [2, 3, 4, 5], 5: [1, 2, 3, 4, 5], 6: [2, 3, 4, 5], 7: [1, 2, 3, 4], 8: [1, 2, 3, 4, 5], 9: [1, 2, 3, 4, 5], 10: [1, 2, 3, 4, 5], 11: [1, 2, 3, 4, 5], 12: [1, 2, 3, 4, 5], 13: [2, 3, 4, 5], 14: [1, 2, 3, 4, 5], 15: [2, 3, 4, 5], 16: [1, 2, 3, 4], 17: [1, 2, 3, 4, 5], 18: [1, 2, 3, 4, 5], 19: [1, 2, 3, 4, 5], 20: [2, 3, 4, 5], 21: [1, 2, 3, 4], 22: [1, 2, 3, 4, 5], 23: [1, 2, 3, 4, 5], 24: [1, 2, 3, 4], 25: [1, 2, 3, 4, 5], 26: [2, 3, 4, 5], 27: [1, 2, 3, 4], 28: [1, 2, 3, 4, 5], 29: [1, 2, 3, 4], 30: [1, 2, 3, 4, 5]} #asset with current year current_year_dd={0: 2.0, 1: 1.0, 2: 5.0, 3: 1.0, 4: 5.0, 5: 2.0, 6: 5.0, 7: 1.0, 8: 2.0, 9: 4.0, 10: 2.0, 11: 2.0, 12: 2.0, 13: 5.0, 14: 2.0, 15: 5.0, 16: 1.0, 17: 2.0, 18: 3.0, 19: 2.0, 20: 5.0, 21: 1.0, 22: 2.0, 23: 4.0, 24: 1.0, 25: 2.0, 26: 5.0, 27: 1.0, 28: 3.0, 29: 1.0, 30: 2.0} def initX(model, one_id, year): if current_year_dd[one_id]==year: return 1 else: return 0 def initGroupAsset(model, one_group_ids, year): initialise=0 # one in year 2 and the other in year5 for id 5 nd 6 for n in same_group_dd[one_group_ids]: if current_year_dd[n]==year: initialise=1 return initialise def initSameAsset(model, one_asset_id, year): initialise=0 # one in year 2 and the other in year5 for id 5 nd 6 for n in same_asset_dd[one_asset_id]: if current_year_dd[n]==year: initialise=1 return initialise same_group_ids = same_group_dd.keys() unique_id = estimated_cost_dd.keys() total_id = len(unique_id) year = budget_dd.keys() same_asset_ids = same_asset_dd.keys() model = pyo.ConcreteModel() model.unique_id = pyo.Set(initialize=unique_id) model.year = pyo.Set(initialize=year) # binary variables representing if an asset is moved somewhere model.x = pyo.Var(model.unique_id, model.year, within=pyo.Binary, initialize=initX) # binary variables representing if group of assets are moved somewhere model.group_asset = pyo.Var(same_group_ids, year, within=pyo.Binary, initialize=initGroupAsset) # binary variables representing if same assets are moved somewhere model.same_asset = pyo.Var(same_asset_ids, year, within=pyo.Binary, initialize=initSameAsset) model.bin_group_asset = pyo.ConstraintList() # if any model.x[ ·, y] non-zero, model.group_asset[one_group_key, y]] must be one; else is zero to reduce the obj function # total_id*10 is to remark, but total_id was enough since max of one year yields max of total assets, the maximum possible sum model.bin_same_asset = pyo.ConstraintList() for same_asset_id_key, same_asset_id_value in same_asset_dd.items(): for y in year: model.bin_same_asset.add( expr=sum(model.x[one_id, y] for one_id in same_asset_id_value) <= model.same_asset[ same_asset_id_key, y] * total_id * 10) for one_group_key, one_group_value in same_group_dd.items(): for y in year: model.bin_group_asset.add( expr=sum(model.x[one_id, y] for one_id in one_group_value) <= model.group_asset[ one_group_key, y] * total_id * 10) # if any model.x[ ·, y] non-zero, model.same_asset[same_asset_id_key, y]] must be one; else is zero to reduce the obj function # total_id*10 is to remark, but total_id was enough since max of one year yields max of total assets, the maximum possible sum model.budget_const = pyo.ConstraintList() # Sum of estimated cost for each year should be less or equal to budget for y in model.year: model.budget_const.add( expr=sum([model.x[one_id, y] * estimated_cost_dd[one_id] for one_id in model.unique_id]) <= budget_dd[ y]) model.excluded_year = pyo.ConstraintList() # make the param to be zero if outside of move range for one_id in model.unique_id: inc = year_range_dd[one_id] exc = list(budget_dd.keys() - inc) for t in exc: model.excluded_year.add(expr=model.x[one_id, t] == 0) # each asset can only go to one bin (one year) model.one_bins = pyo.ConstraintList() for one_id in model.unique_id: model.one_bins.add(expr=sum(model.x[one_id, y] for y in model.year) == 1) def group_obj(m): obj_1 = (sum(model.same_asset[one_asset, y] for one_asset in same_asset_dd for y in year) + 1) * len( unique_id) ** 3 * max(year) ** 2 # bins is minimised for group of assets with same location.(trying to put the same location assets in the # same year as a many as possible) obj_2 = (sum(model.group_asset[one_group, y] for one_group in same_group_dd for y in year) + 1) * len( unique_id) ** 2 * max(year) # group of same assets with same id are treated as early as possible obj_3 = (sum(y * model.same_asset[one_ass, y] for one_ass in same_asset_dd for y in year) + 1) * len( unique_id) * max(year) * 5 # group of assets with same location are treated as early as possible obj_4 = (sum(y * model.group_asset[group, y] for group in same_group_dd for y in year) + 1) return obj_1 + obj_2+obj_3 + obj_4 model.obj = pyo.Objective(rule=group_obj, sense=pyo.minimize) engine = r'C:\Users\someone\solver\cbc.exe' solver = 'cbc' solver = pyo.SolverFactory(solver, executable=engine) solver.solve(model, warmstart=True)
核心问题排查与修正方案
1. 核心约束缺失(最关键)
当前代码完全没有强制同组/同实体资产必须在同一年处理的约束,仅添加了“如果组内有资产在某年份,则对应标记变量为1”的弱约束,这直接导致模型无法满足你的核心需求,同时产生海量无效解空间,拖慢求解。
修正约束示例:
# 强制同实体资产处理年份完全一致 model.same_asset_const = pyo.ConstraintList() for asset_pair in same_asset_dd.values(): id1, id2 = asset_pair[0], asset_pair[1] for y in model.year: model.same_asset_const.add(model.x[id1, y] == model.x[id2, y]) # 强制同组资产处理年份完全一致 model.same_group_const = pyo.ConstraintList() for group_assets in same_group_dd.values(): if len(group_assets) > 1: ref_id = group_assets[0] for id in group_assets[1:]: for y in model.year: model.same_group_const.add(model.x[id, y] == model.x[ref_id, y])
2. 目标函数设计错误
- 权重设置极端:
len(unique_id)**3 * max(year)**2这类超大权重会让求解器完全忽略其他目标,同时引发数值稳定性问题; - 逻辑反向:你需要“同组资产尽量同年份处理”,但当前目标是最小化
sum(same_asset[*,y]),这反而会让求解器尽量避免同组资产同年份处理; - 冗余的
+1:完全没必要,会干扰求解器分支定界逻辑。
简化目标函数示例(优先让资产尽早处理):
def group_obj(m): # 最小化所有资产处理年份的加权和,优先安排到更早的年份 return sum(y * m.x[one_id, y] for one_id in m.unique_id for y in m.year) model.obj = pyo.Objective(rule=group_obj, sense=pyo.minimize)
3. 冗余变量与初始化问题
- 无需额外引入
group_asset和same_asset变量:直接通过约束强制同组资产年份一致即可,减少变量数量降低求解复杂度; - 初始化逻辑错误:
initGroupAsset和initSameAsset的初始化会让标记变量在初始状态就不符合“同组同年份”的要求,warmstart=True反而会引导求解器进入无效区域,建议移除这两个变量和warmstart参数。
4. 约束与数值优化
- 大M值优化:把
total_id*10改为组内资产的数量(比如同组有2个资产,大M设为2),避免数值精度问题; - 简化年份约束:可以直接在
model.x变量定义时,通过domain限制允许的年份,不需要额外添加excluded_year约束。
内容的提问来源于stack exchange,提问作者user20321911
相关产品推荐
相关产品推荐

