Equipment Assignment (IP)#
This problem is taken from Prof Jack Ponton’s Modelling, Simulation and Optimisation Online Course. Specifically, the LP relaxation is discussed in Section 5.4.2: Linear Programming Problems and the IP is discussed in Section 5.4.3: Mixed Integer Linear Programming (MILP) Problems.
A company wants to optimize profit through the sales of products (A, B, C)
Processing Time (in hours)
Product ↓ / Process → |
Reactor |
Crystalliser |
Centrifuge |
|---|---|---|---|
A |
0.8 |
0.4 |
0.2 |
B |
0.2 |
0.3 |
- |
C |
0.3 |
- |
0.1 |
Available Process Time (in minutes)
Process ↓ |
Time |
|---|---|
Reactor |
40 |
Crytallizer |
35 |
Centrifuge |
35 |
Profit on Sales (in $)
Product ↓ |
Profit |
|---|---|
A |
20 |
B |
6 |
C |
8 |
Sales limit (in units)
Product ↓ |
Limit |
|---|---|
C |
20 |
General Definition#
A function is made to handle the general definition of the problem, since the indices and most parameters remain the same, irrespective of how we treat the variables
# !pip install gana # uncomment and run to install Gana, if not in environment
from gana import Prg, V, P, I, sup
def general_def(name: str, itg: bool = True, rhs: list[int] = [20, 10, 5]) -> Prg:
p = Prg(name)
p.products = I('a', 'b', 'c')
p.processes = I('reactor', 'crystalliser', 'centrifuge')
p.profit = P(p.products, _=[20, 6, 8])
p.time = P(p.products, p.processes, _=[0.8, 0.4, 0.2, 0.2, 0.3, 0, 0.3, 0, 0.1])
p.tmax = P(p.processes, _=rhs)
p.nbatch = V(p.products, itg=itg)
p.cons_time = (
sum(p.time(product, p.processes) * p.nbatch(product) for product in p.products)
<= p.tmax
)
p.sales_lm = p.nbatch(p.c) <= 20
p.o = sup(sum(p.profit(product) * p.nbatch(product) for product in p.products))
return p
LP Relaxation#
Strictly speaking the number of batches processed should be a integer values. Nevertheless, let us solve the relaxed problem first.
p1 = general_def(name='lp_rlx', itg=False)
p1.show()
Mathematical Program for lp_rlx
Index Sets
Objective
s.t.
Inequality Constraint Sets
Functions
Optimizing the program
p1.opt()
📝 Generated lp_rlx.mps ⏱ 0.0007 s
Set parameter Username
Academic license - for non-commercial use only - expires 2026-08-01
Read MPS format model from file lp_rlx.mps
Reading time = 0.01 seconds
LP_RLX: 4 rows, 3 columns, 8 nonzeros
📝 Generated gurobipy model. See .formulation ⏱ 0.0133 s
Gurobi Optimizer version 12.0.3 build v12.0.3rc0 (win64 - Windows 11.0 (26100.2))
CPU model: 13th Gen Intel(R) Core(TM) i7-13700, instruction set [SSE2|AVX|AVX2]
Thread count: 16 physical cores, 24 logical processors, using up to 24 threads
Optimize a model with 4 rows, 3 columns and 8 nonzeros
Model fingerprint: 0x6d41737c
Coefficient statistics:
Matrix range [1e-01, 1e+00]
Objective range [6e+00, 2e+01]
Bounds range [0e+00, 0e+00]
RHS range [5e+00, 2e+01]
Presolve removed 1 rows and 0 columns
Presolve time: 0.00s
Presolved: 3 rows, 3 columns, 7 nonzeros
Iteration Objective Primal Inf. Dual Inf. Time
0 -6.0000000e+02 5.331617e+01 0.000000e+00 0s
2 -5.2500000e+02 0.000000e+00 0.000000e+00 0s
Solved in 2 iterations and 0.00 seconds (0.00 work units)
Optimal objective -5.250000000e+02
📝 Generated Solution object for lp_rlx. See .solution ⏱ 0.0000 s
✅ lp_rlx optimized using gurobi. Display using .output() ⏱ 0.0198 s
The number of batches (nbatch) have non-integer solutions.
p1.nbatch.output(asdict=True)
{(a,): 13.749999999999998, (b,): 15.0, (c,): 20.0}
With an objective value of:
p1.o.output(asfloat=True)
-525.0
IP Problem#
p2 = general_def(name='IP')
p2.opt()
📝 Generated IP.mps ⏱ 0.0005 s
Read MPS format model from file IP.mps
Reading time = 0.01 seconds
IP: 4 rows, 3 columns, 8 nonzeros
📝 Generated gurobipy model. See .formulation ⏱ 0.0085 s
Gurobi Optimizer version 12.0.3 build v12.0.3rc0 (win64 - Windows 11.0 (26100.2))
CPU model: 13th Gen Intel(R) Core(TM) i7-13700, instruction set [SSE2|AVX|AVX2]
Thread count: 16 physical cores, 24 logical processors, using up to 24 threads
Optimize a model with 4 rows, 3 columns and 8 nonzeros
Model fingerprint: 0xaaa99143
Variable types: 0 continuous, 3 integer (0 binary)
Coefficient statistics:
Matrix range [1e-01, 1e+00]
Objective range [6e+00, 2e+01]
Bounds range [0e+00, 0e+00]
RHS range [5e+00, 2e+01]
Found heuristic solution: objective -500.0000000
Presolve removed 1 rows and 0 columns
Presolve time: 0.00s
Presolved: 3 rows, 3 columns, 7 nonzeros
Variable types: 0 continuous, 3 integer (0 binary)
Root relaxation: objective -5.250000e+02, 2 iterations, 0.00 seconds (0.00 work units)
Nodes | Current Node | Objective Bounds | Work
Expl Unexpl | Obj Depth IntInf | Incumbent BestBd Gap | It/Node Time
0 0 -525.00000 0 1 -500.00000 -525.00000 5.00% - 0s
H 0 0 -510.0000000 -525.00000 2.94% - 0s
H 0 0 -524.0000000 -525.00000 0.19% - 0s
0 0 -525.00000 0 1 -524.00000 -525.00000 0.19% - 0s
Explored 1 nodes (2 simplex iterations) in 0.01 seconds (0.00 work units)
Thread count was 24 (of 24 available processors)
Solution count 3: -524 -510 -500
No other solutions better than -524
Optimal solution found (tolerance 1.00e-04)
Best objective -5.240000000000e+02, best bound -5.240000000000e+02, gap 0.0000%
📝 Generated Solution object for IP. See .solution ⏱ 0.0000 s
✅ IP optimized using gurobi. Display using .output() ⏱ 0.0247 s
The solution obtained from the LP relaxation is pretty close to the actual integer solution, with a lower objective value
p2.output()
Solution for IP
Objective
Variables