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.
Answers:
Issues:
-
Always check the problem status
print(f'Status: {LpStatus[Problem.status]}')
-
Printing the solution is missing
for c in Crates:
v = value(Use[c])
if v > 0.5: print(f'crate type {c}: {v}')
-
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.
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.
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.
Issues:
-
Always check the problem status
print(f'Status: {LpStatus[Problem.status]}')
-
Printing the solution is missing
for c in Crates: v = value(Use[c]) if v > 0.5: print(f'crate type {c}: {v}')
-
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.
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.