【问题标题】:Pulp: How to add/subtract decision variable outputs together纸浆:如何将决策变量输出加/减在一起
【发布时间】:2020-08-06 18:26:03
【问题描述】:

我正在研究将产品从生产工厂转移到存储设施以满足需求的铁路调度问题。

我是纸浆新手,所以很难理解为什么这不起作用,不幸的是,关于这个主题的文档很少。

问题

需要监控三个决策变量:

  1. 每个工厂的产品可用性/库存 - 请注意每个工厂可以生产不同的产品。

  2. 铁路 - 从每个工厂中运输每种产品的数量。每列火车可运载8400吨。

  3. 存储设施中每种产品的库存。

运行程序后,铁路决策变量正常工作,即输出符合预期,但工厂和存储设施的库存未显示铁路移除和随后添加的数量。

以下数据和代码:

import pulp as pulp
import pandas as pd
import datetime

#rail capacity df from plant: no_trains_per_day max
rail_capacity_df_daily = {'ABC': {'capacity_per_day': 1, 'max': 19},
                          'DEF': {'capacity_per_day': 1, 'max': 50}}
rail_capacity_df = pd.DataFrame.from_dict(rail_capacity_df_daily ,orient='Index')

# facilities_df
facilities_inventory = {'BZL': {'current': 100000, 'max': 210000}, 
                        'AFM': {'current': 100000, 'max': 190000},
                        'PRE': {'current': 100000, 'max': 245000}}
facilities_df = pd.DataFrame.from_dict(facilities_inventory, orient='Index')


# plants_df
plant_df_inventory = {('ABC', 'PRE'): {'inventory': 196710, 'daily_production': 6000},
                      ('ABC', 'AFM'): {'inventory': 199910, 'daily_production': 5000},
                      ('DEF', 'BZL'): {'inventory': 127110, 'daily_production': 5000},
                      ('DEF', 'PRE'): {'inventory': 227100, 'daily_production': 6000}}  

plants_df = pd.DataFrame.from_dict(plant_df_inventory,orient='Index').rename_axis(['plant', 'product'])

# Sales demand

sales_demand = {'2020-04-24': {'AFM': 10000, 'PRE': 15000, 'BZL': 10000},
                '2020-04-25': {'AFM': 10000, 'PRE': 15000, 'BZL': 10000},
                '2020-04-26': {'AFM': 10000, 'PRE': 15000, 'BZL': 10000},
                '2020-04-27': {'AFM': 10000, 'PRE': 15000, 'BZL': 10000},
                '2020-04-28': {'AFM': 10000, 'PRE': 15000, 'BZL': 10000},
                '2020-04-29': {'AFM': 10000, 'PRE': 15000, 'BZL': 10000},}

sales_df = pd.DataFrame.from_dict(sales_demand, orient='Index').rename_axis(['date'])

# Demand: Current Sales Demand
sales_demand = sales_df.to_dict(orient='index')

# PLANNING HORIZON PARAMS  
_current_date = pd.to_datetime(datetime.datetime.today().strftime('%Y%m%d'))
planning_horizon_max = datetime.datetime.today() + datetime.timedelta(4)
planning_horizon_max = pd.to_datetime(planning_horizon_max.strftime('%Y%m%d'))

# COMBINATION VARS
dates = [d.strftime('%F') for d in pd.date_range(_current_date,planning_horizon_max)]
plant_combinations = [(plant, product) for plant, product in plants_df.index]
products = [p for p in facilities_df.index] 
plants = ['ABC', 'DEF']

# Sales Demand: Grade Combinations by Date
demand_requirements = [(d, p) for d in dates for p in products]

# INVENTORY 
# Initial Storage Inventory
storage_inv = dict(zip(facilities_df.index, facilities_df['current']))

storage_max = dict(zip(facilities_df.index, facilities_df['max']))

# Initial Plant Inventory
plant_current_inventory = dict(zip(plants_df.index, plants_df.inventory))
plant_daily_production = dict(zip(plants_df.index, plants_df.daily_production))


# DECISION VARIABLES
# Plant facility vars
plant_inventory_vars = pulp.LpVariable.dicts(
    'Plant Inventory',
    ((date, plant, product) for date in dates for (plant, product) in plant_combinations),
    cat='Continuous',
    lowBound=0) 

# Storage Facility Vars
storage_facility_vars = pulp.LpVariable.dicts(
    'Storage Inventory',
    ((d, p) for d in dates for p in products),
    cat='Integer',
    lowBound=0)

# Total train capacity per plant dict
train_load_limit_daily = dict(zip(rail_capacity_df.index, 
                                  rail_capacity_df.capacity_per_day))

# Decision Vars: date, plant, product
train_consignment_variables = pulp.LpVariable.dicts(
    'Rail Loadings From plant',
    ((date, plant, product) for date in dates for (plant, product) in plant_combinations),
    cat='Continuous',
    lowBound=0) 


# OPTIMISATION

# Instantiate 
model = pulp.LpProblem('Rail Optimisation', pulp.LpMinimize)

solver = pulp.PULP_CBC_CMD()
solver.tmpDir = 'Users\CPrice2'

# Objective Function
model += pulp.lpSum(storage_max[product] 
    - storage_facility_vars[(date, product)] for (date, product) in storage_facility_vars), 'Minimise stockpile shortfalls'

    # PLANT INVENTORY
for date in dates:
  current_date = datetime.date.today().strftime('%F')
  date_t_minus_one = datetime.datetime.strptime(date, '%Y-%m-%d') - datetime.timedelta(days=1)
  date_t_minus_one = date_t_minus_one.strftime('%F')
  for plant, product in plant_combinations:
    if date == current_date:
      # Set current inventory
      model += plant_current_inventory[(plant, product)] - \
          train_consignment_variables[(date, plant, product)] == \
          plant_inventory_vars[(date, plant, product)] + \
          plant_daily_production[(plant, product)]
    else:
      # Get inventory from t-1
      model += plant_inventory_vars[(f'{date_t_minus_one}', plant, product)] - \
          train_consignment_variables[(date, plant, product)] == \
          plant_inventory_vars[(date, plant, product)] + \
          plant_daily_production[(plant, product)]

# Trains: Daily Rail Out Constraint 
for date in dates:
  for plant in plants:
    plant_product_combination = [tup for tup in plant_combinations if tup[0] == plant]
    variable_list = []
    for (plant_, product_) in plant_product_combination:
      variable = train_consignment_variables[(date, plant_, product_)]
      variable_list.append(variable)
    model += pulp.lpSum(var for var in variable_list) == train_load_limit_daily[plant] * 8400

# STORAGE FACILITY 
for date in dates:
  current_date = datetime.date.today().strftime('%F')
  date_t_minus_one = datetime.datetime.strptime(date, '%Y-%m-%d') - datetime.timedelta(days=1)
  date_t_minus_one = date_t_minus_one.strftime('%F')
  for plant, product in plant_combinations:
    if date == current_date:
      # Current Inv == current inventory + train in
      model += storage_inv[product] + \
          train_consignment_variables[(date, plant, product)] == \
          storage_facility_vars[(date, product)] - sales_demand[date][product] 
    else:
      model += storage_facility_vars[(f'{date_t_minus_one}', product)] + \
          train_consignment_variables[(date, plant, product)] == \
          storage_facility_vars[(date, product)] - sales_demand[date][product]

# Run solver
model.solve(solver)
pulp.LpStatus[model.status]

# Storage Out
storage_facility_out = []

for (date, product) in storage_facility_vars:
  var_out = {
      'Date': date,
      'Product': product,
      'Out Inventory': storage_facility_vars[(date, product)].varValue
  }
  storage_facility_out.append(var_out)

storage_facility_out_df = pd.DataFrame.from_records(storage_facility_out).sort_values(['Date', 'Product'])
storage_facility_out_df.set_index(['Date', 'Product'], inplace=True)

# Rail Out
rail_optimisation_outputs = []

for date, plant, product in train_consignment_variables:
  var_output = {
      'Date': date,
      'Plant': plant,
      'Product': product,
      'Rail_Out': train_consignment_variables[(date, plant, product)].varValue
  }
  rail_optimisation_outputs.append(var_output)

output_df = pd.DataFrame.from_records(rail_optimisation_outputs).sort_values(['Date', 'Plant', 'Product'])
output_df.set_index(['Date', 'Plant', 'Product'], inplace=True)

# Production Plant Out
plant_stock_out = []

for date, plant, product in plant_inventory_vars:
  var_out = {
      'Date': date,
      'Plant': plant,
      'Product': product,
      'Out Inventory': plant_inventory_vars[(date, plant, product)].varValue
      }
  plant_stock_out.append(var_out)
plant_stock_out_df = pd.DataFrame.from_records(plant_stock_out).sort_values(['Date', 'Product'])
plant_stock_out_df.set_index(['Date', 'Plant', 'Product'], inplace=True)
plant_stock_out_df

当我访问每个决策变量的输出时:

train_consignment_vars.varValue = output ok.

对于工厂和存储设施,我得到以下信息:

storage_facility_vars.varValue = AttributeError: 'float' 对象没有属性 'value'。如果我不调用 .varValue,我只是获取字典值而不考虑铁路添加/删除的数量。

【问题讨论】:

  • 你有行 ` storage_facility_vars[(date, product)] = plant_current_inv[product] ` 用一个值填充曾经是纸浆变量的值。这会将您的变量字典更改为带有变量的字典,有时还会浮动
  • 感谢您的评论!这是为了设置初始库存。有没有不调整类型的常规设置方法?
  • 两个建议:1) 共享允许其他人重现您的错误的代码 - 即在代码本身和所有必需的库导入中包含最小/玩具数据的代码。 2)在陷入编码之前,先用数学方法表述你的问题。祝你好运!
  • 谢谢@kabdulla,公平点。我已将数据添加为数据框,包括导入。回复:问题,这已经在数学上定义和公式化了,但是我发现 Pulp 文档在尝试复制每个表达式时非常有限。
  • 不要上传 csv,将少量玩具数据硬编码到问题本身 - 足以显示您遇到的问题。有关更多建议,请参阅minimal reproducible example

标签: python optimization linear-programming pulp mixed-integer-programming


【解决方案1】:

如果没有您的代码形式的可重现示例,我无法确定所有问题,但这里有一些:

  1. 在您已添加目标函数后,您似乎还添加了其他非约束形式的表达式,例如:

model += pulp.lpSum(plant_inventory_vars[(date, plant, product)]) - pulp.lpSum(train_consignment_variables[(date, plant, product)])

这不是一个约束。 model += 之后的东西需要采用“A == B”,或“A = B”的形式。你的表情没有。

这里还有一个:

model += pulp.lpSum(port_inventory_vars[(date, product)]) + pulp.lpSum(train_consignment_variables[(date, plant, product)] for plant, product in plant_combinations)

  1. 正如@pchtsp 所指出的,您正在覆盖一些纸浆变量:

storage_facility_vars[(date, product)] = plant_current_inv[product]

线性规划的一般方法是声明变量、要优化的目标以及存在的约束。在 PULP 中,使用model += 语法将目标和约束添加到问题中。您在这里所做的是获取您创建的线性变量并用plant_current_inv[product' 中的任何内容覆盖它。我认为您想要做的是设置一个等式约束并将其添加到问题中。

【讨论】:

  • 感谢@kabdulla,我现在已经添加了所有内容 - 这应该会运行并产生与我希望看到的完全相同的输出!
  • 感谢更新代码。当我按照你现在的方式运行代码时,我得到NameError: name 'dates' is not defined。在声明对象之前使用对象datesplants_production_daily 也没有定义。我建议从一个新的/干净的 python 实例运行您的代码,以检查您是否确实按照要求声明了所有内容。
  • 谢谢@kabdulla - 抱歉我不得不重新定义一些东西(比如虚拟数据),所以这些可能已经被定义,然后在它们之前添加了具​​有依赖关系的代码。再次感谢您。
  • 好多了。您需要删除 solver.tmpDir = 'Users\CPrice2'。此外,我的建议是避免在您的示例中使用datetime.datetime.today()。如果有人明天运行它,它的行为会有所不同。可能对您部署的代码有意义 - 但不是您的示例。另外两个建议:1.我会将您的日期索引切换为从地平线开始的整数天 - 按日期字符串进行索引很混乱并且会妨碍您。 2. 目前处理索引元组的方式非常繁琐。
  • 请参阅 coin-or.org/PuLP/CaseStudies/a_transportation_problem.html 以获取可能有帮助的示例。
猜你喜欢
  • 2020-12-19
  • 2020-09-16
  • 2022-01-10
  • 1970-01-01
  • 1970-01-01
  • 2020-08-28
  • 1970-01-01
  • 2021-02-08
  • 1970-01-01
相关资源
最近更新 更多