Pulp:如何 add/subtract 一起决定变量输出

Pulp: How to add/subtract decision variable outputs together

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

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

问题

要监控三个决策变量:

  1. 每个工厂的产品 availability/inventory - 注意每个工厂可以生产不同的产品。

  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,我只是简单地获取字典值,而不考虑铁路 added/removed 的数量。

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

  1. 在您已经添加了 objective 函数之后,您似乎添加了更多的表达式,这些表达式不是约束形式,例如:

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

这不是约束。 model += 之后的内容需要采用 "A == B" 或 "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]

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