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

Pyomo:变量实例与目标函数变量值不一致问题求解

投资成本优化模型问题解决

问题背景

我们要在满足最低投资约束的前提下,最小化一段时期内的总投资成本:

  • 变量X[i,t]:项目i在时间段t的投资额
  • 参数Y[i,t]:单位投资额的成本
  • 总投资成本定义:投资额 × 项目i首次投资年份的单位成本(比如2024年首次投资,所有年份的该项目投资都用2024年的单位成本)

问题现象

单位成本Y随时间变化,投资年份的取值直接影响结果。运行模型后,单独调用t_inv规则能得到正确的投资年份,但把t_inv放到总成本函数作为目标函数时,计算出的投资年份错误(本该用2022年却误用了2025年)。

原因推测

问题出在变量X初始化为0,目标函数建模阶段调用t_inv时,使用的是X的初始值而非求解后的实际值,触发了t_inv中X=0时返回2025的逻辑。关键错误:在Pyomo建模规则中直接访问变量的.value属性,这只会取初始化值,而非求解后的最优值。

需求

修改模型,让目标函数能使用求解后的正确首次投资年份计算成本。

可运行原代码

from pyomo.environ import *

times = [2022, 2023, 2024, 2025]
projects = ["P1", "P2", "P3"]

model = AbstractModel()
model.time = Set(initialize=times)
model.project_type = Set(initialize=projects)
model.Y = Param(model.project_type, model.time)
model.X = Var(model.project_type, model.time, initialize=0)  # 投资额变量

# 推导项目首次投资年份的规则
def t_inv(i, times, X):
    all_inv_years = []
    for t in times:
        if X[i, t].value > 0:
            all_inv_years.append(t)
    return min(all_inv_years) if all_inv_years else 2025

# 计算单一年份总成本的规则
def total_costs_rule(t, project_types, times, Y, X):
    summation_terms = [X[i, t] * Y[i, t_inv(i, times, X)] for i in project_types]
    return sum(summation_terms)

# 目标函数规则
def cost_rule(model):
    return sum(total_costs_rule(t, model.project_type, model.time, model.Y, model.X) for t in model.time)

model.cost = Objective(rule=cost_rule, sense=minimize)

# 年度最低投资约束
def investments_constraint_rule(model, t):
    return total_costs_rule(t, model.project_type, model.time, model.Y, model.X) >= 10

model.Investments_Constraint = Constraint(model.time, rule=investments_constraint_rule)

# 数据输入
data = {None: {'Y': {('P1',2022): 1, ('P2',2022): 8, ('P3',2022): 12 , ('P1',2023): 0.9 , ('P2',2023): 7.5 ,
                ('P3',2023): 10, ('P1',2024): 0.8 ,('P2',2024): 7 ,('P3',2024): 8, ('P1',2025): 0.7 , 
                ('P2',2025): 6.5 ,('P3',2025): 6}}}

# 求解并输出结果
instance = model.create_instance(data)
solver = SolverFactory('ipopt')
results = solver.solve(instance, tee=True)
instance.pprint()

for i in instance.project_type:
    print(t_inv(i, instance.time, instance.X))

已尝试方法

曾尝试将t_inv设为表达式对象或变量,但未成功(刚接触Pyomo,操作可能有误)。


解决方案

核心思路:用二进制变量建模首次投资年份的逻辑,而非依赖变量的.value属性。通过引入二进制变量标记首次投资年,再用约束关联投资行为和首次投资年,让Pyomo能在优化过程中正确计算成本。

修改后的代码

from pyomo.environ import *

times = [2022, 2023, 2024, 2025]
projects = ["P1", "P2", "P3"]
# 定义大M值,需大于最大可能的投资额(这里假设单项目单年最大投资不超过1000)
M = 1000

model = AbstractModel()
model.time = Set(initialize=times, ordered=True)  # 设为有序集合,方便处理年份先后
model.project_type = Set(initialize=projects)

model.Y = Param(model.project_type, model.time)
model.X = Var(model.project_type, model.time, domain=NonNegativeReals, initialize=0)  # 投资额,非负

# 二进制变量:first_inv[i,t] = 1 表示项目i的首次投资年是t,否则0
model.first_inv = Var(model.project_type, model.time, domain=Binary, initialize=0)

# 约束1:每个项目只能有一个首次投资年
def single_first_inv_rule(model, i):
    return sum(model.first_inv[i, t] for t in model.time) == 1
model.Single_First_Inv = Constraint(model.project_type, rule=single_first_inv_rule)

# 约束2:如果项目i在年份t'有投资,那么首次投资年必须≤t'
# 即:对于所有t',X[i,t'] ≤ M * sum(first_inv[i,t] for t ≤ t')
def investment_after_first_rule(model, i, t_prime):
    # 筛选所有≤t_prime的年份
    earlier_times = [t for t in model.time if t <= t_prime]
    return model.X[i, t_prime] <= M * sum(model.first_inv[i, t] for t in earlier_times)
model.Investment_After_First = Constraint(model.project_type, model.time, rule=investment_after_first_rule)

# 约束3:首次投资年之前不能有投资(可选,进一步强化逻辑)
def no_investment_before_first_rule(model, i, t):
    # 筛选所有<t的年份
    before_times = [t_prime for t_prime in model.time if t_prime < t]
    return sum(model.X[i, t_prime] for t_prime in before_times) <= M * (1 - model.first_inv[i, t])
model.No_Investment_Before_First = Constraint(model.project_type, model.time, rule=no_investment_before_first_rule)

# 目标函数:总投资成本 = sum( X[i,t] * sum( Y[i,t_first] * first_inv[i,t_first] for t_first in model.time ) )
def cost_rule(model):
    total_cost = 0
    for i in model.project_type:
        # 计算项目i的首次投资年对应的单位成本
        first_year_cost = sum(model.Y[i, t_first] * model.first_inv[i, t_first] for t_first in model.time)
        # 所有年份的投资额乘以该成本
        total_cost += first_year_cost * sum(model.X[i, t] for t in model.time)
    return total_cost

model.cost = Objective(rule=cost_rule, sense=minimize)

# 年度最低投资约束:每年总投资额对应的成本≥10
def investments_constraint_rule(model, t):
    yearly_cost = sum( 
        model.X[i, t] * sum(model.Y[i, t_first] * model.first_inv[i, t_first] for t_first in model.time) 
        for i in model.project_type 
    )
    return yearly_cost >= 10
model.Investments_Constraint = Constraint(model.time, rule=investments_constraint_rule)

# 数据输入
data = {None: {'Y': {('P1',2022): 1, ('P2',2022): 8, ('P3',2022): 12 , ('P1',2023): 0.9 , ('P2',2023): 7.5 ,
                ('P3',2023): 10, ('P1',2024): 0.8 ,('P2',2024): 7 ,('P3',2024): 8, ('P1',2025): 0.7 , 
                ('P2',2025): 6.5 ,('P3',2025): 6}}}

# 求解并输出结果
instance = model.create_instance(data)
# 二进制变量需要MIP求解器,比如Gurobi、CBC,IPOPT不支持整数变量
solver = SolverFactory('cbc')  # 若有Gurobi可替换为SolverFactory('gurobi')
results = solver.solve(instance, tee=True)

# 输出结果
print("=== 投资额结果 ===")
for i in instance.project_type:
    for t in instance.time:
        print(f"项目{i} 年份{t} 投资额: {instance.X[i,t].value:.2f}")

print("\n=== 首次投资年 ===")
for i in instance.project_type:
    for t in instance.time:
        if instance.first_inv[i,t].value == 1:
            print(f"项目{i} 首次投资年: {t}")

print("\n=== 总成本 ===")
print(f"总投资成本: {instance.cost():.2f}")

关键修改说明

  1. 引入二进制变量first_inv:明确标记每个项目的首次投资年,避免依赖变量的.value属性。
  2. 添加约束关联投资与首次投资年:确保投资行为只能发生在首次投资年及之后,且每个项目仅有一个首次投资年。
  3. 重构目标函数:通过first_inv变量的加权求和,直接计算首次投资年的单位成本,再乘以该项目所有年份的投资额总和,确保成本计算逻辑完全由Pyomo的优化变量驱动。
  4. 更换求解器:因为引入了二进制变量,需要使用混合整数规划(MIP)求解器,比如CBC或Gurobi,IPOPT仅支持连续变量优化。

内容的提问来源于stack exchange,提问作者Julia Schepp

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.25 07:45:55