You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.15 05:45:33