PuLP Optimization Problem: fitting items into crates with restrictions

Question:

I would like some assistance with an optimization problem in Python..

Here is some background:

  • Objective is to minimize the number of Crates (c) needed while satisfying Unit_ids (u) needed.
  • Each crate contains any number (from 0-inf) number of different types of Units, with varying amounts to each
  • Each Unit_id has a required demand to meet from any number of Crates …… for example, if 10 items of Unit_id 123 are required, we may get all 10 from Crate A, or 2 from Crate X and 8 from crate Z. The choice of crate for 10 items of Unit_ID 123 depends on how many other Unit_ids are present in crate A vs X and Z.
  • There are many crates, but we do not need every Crate



Definitions:
Crates -> list of all crate_ids

Units -> list of all unit_ids

Unit_crates[u] -> keys are unit_ids, values are list of crates that contain unit u

Demand[u] -> keys are unit_ids, values are number units of demand required

Content[u][c] -> dictionary within dictionary, the number of a type of unit_id in a crate c



Code:

from pulp import *
import pandas as pd

Crates = [1, 2, 3, 4, 5]
Units = ['a', 'b', 'c', 'd']
Unit_crates = {'a': [1, 5], 'b': [1, 2], 'c': [1, 2, 3, 4],  'd': [2, 3, 4]}
Demand = { 'a': 2, 'b': 2, 'c': 4, 'd': 4}
Content = pd.DataFrame({'unit_id':['a','b','c','b','c','d','c','d','c','d','a'],'crate_id':[1, 1, 1, 2, 2, 2, 3, 3, 4, 4, 5],  'content':[1, 1, 1, 2, 2, 4, 3, 5,1,2,3]})
Content=(Content.groupby(['unit_id','crate_id']).agg(int).unstack(0).droplevel(0,1).agg(lambda x:dict(x.dropna())).to_dict())


Problem = LpProblem("Minimize_Number_of_Crates", LpMinimize)

Use = LpVariable.dicts('use', Crates, cat = 'Integer') 

Problem += lpSum(Use[c] for c in Crates) 

for u in Units:
   Problem += lpSum(Use[c] * Content[u][c] for c in Unit_crates[u]) >= Demand[u]

Problem.solve()
...

It’s hard to tell if this actually fucntions as intended. Also, this only works for assigning 1 unit_id to one crate_id… It cannot assign 1 unit_id to multiple crates, which may or may not be another valid way of assigning units to crates.

Asked By: spacetime1.0

||

Answers:

Issues:

  1. Always check the problem status

    print(f'Status: {LpStatus[Problem.status]}')
    
  2. Printing the solution is missing

    for c in Crates:
        v = value(Use[c])
        if v > 0.5: print(f'crate type {c}: {v}')
    
  3. Use should be non-negative:

     Use = LpVariable.dicts('use',  Crates, lowBound = 0, cat = 'Integer') 
    

This will show:

Status: Optimal
crate type 2: 1.0
crate type 3: 1.0
crate type 5: 1.0

Note that this solution is not unique. However, it is guaranteed there is no solution with fewer crates being used.

Here is a picture showing typical behavior of a MIP solver.

enter image description here

Here I used random data (400 units, 4000 crates). We see that the optimal solution was already found after 124 seconds. The rest of the time (up to 1644 seconds) was used to prove optimality (the blue line stays flat at 30). In practice, we could have stopped much earlier. Note: the big jump at the end for the red line is because the solver knows the objective jumps by 1 (all cost coefficients are 1). This property is exploited here. The solver has proven optimality when the red line and blue line meet.

Answered By: Erwin Kalvelagen
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.