4
$\begingroup$

I am working on a rail scheduling problem that moves product from a production plant to a storage facility to satisfy demand. I am new to PuLP so finding this difficult to understand why this isn't working, and unfortunately there is very little documentation on the subject.

There are three decision variables to monitor:

  1. The availability/inventory of product at each plant - note each plant can manufacture different products.
  2. Rail - how much to move of each product from each plant. Each train can move 8400 tons.
  3. The inventory of product at the storage facility.

Upon running the program, the rail decision variable works correctly i.e. the output is as expected, however the inventory at the plant and storage facility is not showing the amount removed and subsequently added by the rail.

Data as per below:

#rail capacity df (plant: no_trains_per_day)
rail_capacity_df_daily = {'ABC': 3,
'DEF':1}

# facilities_df
facilities_inventory = {'BZL': 98057,
 'AFM': 8663,
 'PRE': 28997}

facilities_max = {'BZL': 210000,
 'AFM': 190000,
 'PRE': 245000}

# plants_df
plant_df_inventory = {('ABC', 'PRE'): 196710,
 ('ABC', 'AFM'): 197940,
 ('DEF', 'BZL'): 294750,
 ('DEF', 'PRE'): 129180}

# Plant production daily
plants_production_daily = {('ABC', 'PRE'): 6000,
     ('ABC', 'AFM'): 1000,
     ('DEF', 'BZL'): 5000,
     ('DEF', 'PRE'): 4000} 

Code:

# PLANNING HORIZON PARAMS  
_current_date = pd.to_datetime(datetime.datetime.today().strftime('%Y%m%d'))
planning_horizon_max = datetime.datetime.today() + datetime.timedelta(30)
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)]

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

# 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='Integer',
    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='Integer',
    lowBound=0) 


# OPTIMISATION
# Objective Function
model += pulp.lpSum(stockpile_max[product] 
    - inventory_vars[(date, product)] for (date, product) in inventory_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
      plant_inventory_vars[(date, plant, product)] = plant_current_inventory[(plant, product)] + plant_daily_production[(plant, product)]
    else:
      # Get inventory from t-1
      plant_inventory_vars[(date, plant, product)] = plant_inventory_vars[(f'{date_t_minus_one}', wplant, product)] + plant_daily_production[(plant, product)]
    model += pulp.lpSum(plant_inventory_vars[(date, plant, product)]) - pulp.lpSum(train_consignment_variables[(date, 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[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 product in products:
    if date == current_date:
      storage_facility_vars[(date, product)] = plant_current_inv[product] 
    else:
      port_inventory_vars[(date, product)] = port_inventory_vars[(f'{date_t_minus_one}', product)] 
    model += pulp.lpSum(port_inventory_vars[(date, product)]) + pulp.lpSum(train_consignment_variables[(date, plant, product)] for plant, product in plant_combinations)

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

When I access the outputs of each decision variable:

train_consignment_vars.varValue = output ok.

For both plant and storage facilities I get the following:

storage_facility_vars.varValue = AttributeError: 'float' object has no attribute 'value'. If I don't call .varValue, I simply get the dictionary values without accounting for the amount added/removed by rail.

Any help greatly appreciated, thank you.

$\endgroup$
  • $\begingroup$ Welcome to OR.SE! It's possible that someone will be able to answer your question just by looking at it, without running any code. However, if you don't get any answers, it might help to include the data as code rather than as tables. In other words, give us a minimum reproducible example so we can easily run the code and try to debug it. $\endgroup$ – LarrySnyder610 Apr 23 at 13:58
  • $\begingroup$ Hi @LarrySnyder610, I am unable to attach a csv/excel to the post containing the required data sadly, unless you know of another method? thank you. $\endgroup$ – cmp Apr 23 at 14:04
  • $\begingroup$ But maybe you can post some Python code that essentially "reads in" (or even hard-codes) the data tables that you posted at the top? $\endgroup$ – LarrySnyder610 Apr 23 at 14:13
  • $\begingroup$ In both of the variables you mentioned, I see that you set the variable to a constant depending on a condition. Consider doing that using a constraint. For example for storage_facility_vars something like model + = storage_facility_vars[(date, product)] == plant_current_inv[product] $\endgroup$ – EhsanK Apr 23 at 14:37
  • 1
    $\begingroup$ @cmp, As others mentioned too, would you provide a mathematical notation of your model instead of PuLP representation? $\endgroup$ – A.Omidi May 22 at 13:04
1
$\begingroup$

I don't see any part that "storage_facility_vars" is added to your model in your code. You use it only after define as:

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

Please check it. Also "plant_current_inv[product]" is used only above row, never defined or initialized.

| improve this answer | |
$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.