Solving problems using Python¶
We will solve LP problems using Python. In case you are less familiar with Python, I recommend you to go over the Python Review section.
In this course we will mostly use Gurobi to solve the optimization problems. The Gurobi optimizer is a mathematical optimization software library for solving mixed-integer linear and quadratic optimization problems.
For more information, see: https://www.gurobi.com/academia/academic-program-and-licenses/
Other useful resources can be found here
# install the Gurobi package
!pip install gurobipy
Requirement already satisfied: gurobipy in /usr/local/lib/python3.7/dist-packages (9.5.0)
# Import gurobi library
from gurobipy import * # This command imports the Gurobi functions and classes.
# Create new model
m = Model('') # The Model() constructor creates a model object m. The name of this new model is 'Factory'.
# This new model m initially contains no decision variables, constraints, or objective function.
Restricted license - for non-production use only - expires 2023-10-25
# Create decision variables
# This method adds a decision variable to the model object m, one by one; i.e. x1 and then x2.
# The argument of the method gives the name of added decision variable.
# The default values are applied here; i.e. the decision variables are of type continuous and non-negative, with no upper bound.
x1 = m.addVar(lb=0, vtype = GRB.CONTINUOUS, name='chairs')
x2 = m.addVar(lb=0, vtype = GRB.CONTINUOUS, name='tables')
# Define objective function
# This method adds the objective function to the model object m. The first
# argument is a linear expression and the second argument defines
# the sense of the optimization.
m.setObjective(40*x1+50*x2, GRB.MAXIMIZE)
# Add constraints
# This method adds a constraint to the model object m
m.addConstr(1*x1+2*x2<=40, name='wood')
m.addConstr(4*x1+3*x2<=120, name= 'labor')
<gurobi.Constr *Awaiting Model Update*>
# Run optimization engine
# This method runs the optimization engine to solve the LP problem in the model object m
m.optimize()
Gurobi Optimizer version 9.5.0 build v9.5.0rc5 (linux64)
Thread count: 1 physical cores, 2 logical processors, using up to 2 threads
Optimize a model with 2 rows, 2 columns and 4 nonzeros
Model fingerprint: 0x3a526911
Coefficient statistics:
Matrix range [1e+00, 4e+00]
Objective range [4e+01, 5e+01]
Bounds range [0e+00, 0e+00]
RHS range [4e+01, 1e+02]
Presolve time: 0.01s
Presolved: 2 rows, 2 columns, 4 nonzeros
Iteration Objective Primal Inf. Dual Inf. Time
0 9.0000000e+31 3.250000e+30 9.000000e+01 0s
2 1.3600000e+03 0.000000e+00 0.000000e+00 0s
Solved in 2 iterations and 0.02 seconds (0.00 work units)
Optimal objective 1.360000000e+03
#display optimal production plan
for v in m.getVars():
print(v.varName, v.x)
print('optimal total revenue:', m.objVal)
chairs 24.0
tables 8.0
optimal total revenue: 1360.0
Given the constraints, the maximum revenue is $1360 structured such that we produce 24 chairs and 8 tables.
What if our LP problem has hundreds of thousands varibales and constraints?
The Gurobi python code just presented is too manual and would take too long too build a large scale LP problem. We should use appropriate data structures, python functions and objects to abstract the problem to any size.
Python list comprehension:
#List comprehension is compact way to create lists
sqrd = [i*i for i in range(10)]
print(sqrd)
#Can be used to create subsequences that satisfy certain conditions (ex: filtering a list)
bigsqrd = [i*i for i in range(10) if i*i >= 5]
print(bigsqrd)
#Can be used with multiple for loops (ex: all combinations)
prod = [i+j for i in range(3) for j in range(3)]
print(prod)
#Generator expression is similar, but no brackets (ex: argument to aggregate sum)
sum_sq = sum(i for i in range(11))
print(sum_sq)
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
[9, 16, 25, 36, 49, 64, 81]
[0, 1, 2, 1, 2, 3, 2, 3, 4]
55
# Resource data
# The multidict function returns a list which maps each resource (key) to its capacity value.
resources, capacity = multidict({
'wood': 40,
'labor': 120 })
print(resources, capacity)
['wood', 'labor'] {'wood': 40, 'labor': 120}
# Products data
# This multidict function returns a list which maps each product (key) to its price value.
products, price = multidict({
'chair': 40,
'table': 50 })
print(products, price)
['chair', 'table'] {'chair': 40, 'table': 50}
# Bill of materials: resources required by each product
# This dictionary has a 2-tuple as a key, mapping the resource required by a product with its quantity per.
bom={
('wood','chair'):1,
('wood','table'):2,
('labor','chair'):4,
('labor','table'):3
}
print(bom)
{('wood', 'chair'): 1, ('wood', 'table'): 2, ('labor', 'chair'): 4, ('labor', 'table'): 3}
m = Model('Factory')
# This method adds decision variables to the model object m
make = m.addVars(products, name='make')
# This method adds constraints to the model object m
res = m.addConstrs(((sum(bom[r,p]*make[p] for p in products) <= capacity[r]) for r in resources), name='R')
# This method adds the objective function to the model object m.
# The first argument is a linear expression which is generated by the 'prod' method.
# The 'prod' method is the product of the object (revenue) with the object (make)
# for each product p in the set (products). The second argument defines the sense of the optimization.
m.setObjective(make.prod(price), GRB.MAXIMIZE)
#save model for inspection
m.write('factory.lp')
cat factory.lp
\ Model Factory
\ LP format - for model browsing. Use MPS format to capture full model detail.
Maximize
40 make[chair] + 50 make[table]
Subject To
R[wood]: make[chair] + 2 make[table] <= 40
R[labor]: 4 make[chair] + 3 make[table] <= 120
Bounds
End
# run optimization engine
m.optimize()
Gurobi Optimizer version 9.5.0 build v9.5.0rc5 (linux64)
Thread count: 1 physical cores, 2 logical processors, using up to 2 threads
Optimize a model with 2 rows, 2 columns and 4 nonzeros
Model fingerprint: 0x3a526911
Coefficient statistics:
Matrix range [1e+00, 4e+00]
Objective range [4e+01, 5e+01]
Bounds range [0e+00, 0e+00]
RHS range [4e+01, 1e+02]
Presolve time: 0.01s
Presolved: 2 rows, 2 columns, 4 nonzeros
Iteration Objective Primal Inf. Dual Inf. Time
0 9.0000000e+31 3.250000e+30 9.000000e+01 0s
2 1.3600000e+03 0.000000e+00 0.000000e+00 0s
Solved in 2 iterations and 0.03 seconds (0.00 work units)
Optimal objective 1.360000000e+03
#display optimal production plan
for v in m.getVars():
print(v.varName, v.x)
print('optimal total revenue:', m.objVal)
make[chair] 24.0
make[table] 8.0
optimal total revenue: 1360.0
Sensitivity analysis of LP problems¶
Solving LP problems provides more information than only the values of the decision variables and the value of the objective function.
for v in m.getVars():
print(v.varName, v.x)
print('optimal total revenue:', m.objVal)
make[chair] 24.0
make[table] 8.0
optimal total revenue: 1360.0
for var in m.getVars(): # descision variable
print(var.varName, '=', var.x, (var.obj,var.SAObjLow, var.SAObjUp, var.RC))
make[chair] = 24.0 (40.0, 25.0, 66.66666666666667, 0.0)
make[table] = 8.0 (50.0, 30.0, 80.0, 0.0)
for con in m.getConstrs(): # constraints
print(con.ConstrName, ': slack =', con.slack,', shadow price=',
con.pi,',', (con.RHS, con.SARHSLow, con.SARHSUp))
R[wood] : slack = 0.0 , shadow price= 16.0 , (40.0, 30.0, 80.0)
R[labor] : slack = 0.0 , shadow price= 6.0 , (120.0, 60.0, 160.0)
print("objective value =", m.objVal)
objective value = 1360.0
Objective value coefficients¶
Optimally, we produce 24 chairs (\(x1\) has the optimal value of 24). We sell a chair for $40 (its objective coefficient is 40). While holding the other objective coefficients fix, the values of 40 can change within the range of (25.0, 66.67) without affecting the optimal solution of (24,8). However, changing the objective coefficient will change the objective value!
Similarly, for tables, the objective coefficient is 50 but can vary (42.5, 80.0) without affecting the optimal solution point.
Constraint quantity values¶
The constraint quantity values are 40 m\(^2\) and 120 hours. Modifying these values will change the feasible area. Here, we are looking for the range of values over which the quantity values can change without changing the solution variable mix including slack.
For the wood constraint, the RHS value is 40 m\(^2\) and it can change within the range of (30, 80) without changing the solution variable mix.
For the labor constraint, the RHS value is 120 hours, and it can change within the range of (60, 160) without changing the solution variable mix.
Shadow prices¶
Associated with an LP optimal solution there are shadow prices (also known as: dual values, or marginal values) for the constraints. The shadow price of a constraint associated with the optimal solution represents the change in the value of the objective function per unit of increase in the RHS value of that constraint.
Suppose the wood capacity is increased from 40 m\(^2\) to 41 m\(^2\), then the objective function value will increase from the optimal value of 1360 to 1360+16. The shadow price of the wood constraint is thus $16.
Similarly, suppose the labor capacity is increased from 120 hours to 121 hours, then the objective function value will increase from the optimal value of 1360 to 1360+6. The shadow price of the labor constraint is thus $6.
Sensitivity of the shadow price¶
The sensitivity range for a constraint quantity value is also the range over which the shadow price is valid (i.e., before a slack/surplus value is added to the mix). For example, the shadow price of $16 is valid over the range of (60, 160) hours for labor.
Adding new variable and/or new constraint¶
Is it profitable to make a third product, like benches? Assume that the price of a bench is $30, and a bench consumes 1.2 units of wood and 2 units of labor, then we can formulate the LP model using the previous resources constraints on wood and labor as follows:
# Adding new variable
products, price = multidict({
'chair': 40,
'table': 50,
'bench' : 30})
bom={
('wood','chair'):1,
('wood','table'):2,
('wood','bench'):1.2,
('labor','chair'):4,
('labor','table'):3,
('labor','bench'):2,
}
m = Model('Factory')
make = m.addVars(products,name='make')
res = m.addConstrs(((sum(bom[r,p]*make[p] for p in products) <= capacity[r]) for r in resources),name='R')
m.setObjective(make.prod(price), GRB.MAXIMIZE)
m.write('factory.lp')
cat factory.lp
\ Model Factory
\ LP format - for model browsing. Use MPS format to capture full model detail.
Maximize
40 make[chair] + 50 make[table] + 30 make[bench]
Subject To
R[wood]: make[chair] + 2 make[table] + 1.2 make[bench] <= 40
R[labor]: 4 make[chair] + 3 make[table] + 2 make[bench] <= 120
Bounds
End
m.optimize()
#display optimal production plan
for v in m.getVars():
print(v.varName, v.x)
print('optimal total revenue:', m.objVal)
for var in m.getVars(): # descision variable
print(var.varName, '=', var.x, (var.obj,var.SAObjLow, var.SAObjUp, var.RC))
for con in m.getConstrs(): # constraints
print(con.ConstrName, ': slack =', con.slack,', shadow price=',
con.pi,',', (con.RHS, con.SARHSLow, con.SARHSUp))
print("objective value =", m.objVal)
Gurobi Optimizer version 9.5.0 build v9.5.0rc5 (linux64)
Thread count: 1 physical cores, 2 logical processors, using up to 2 threads
Optimize a model with 2 rows, 3 columns and 6 nonzeros
Model fingerprint: 0x3b912b2a
Coefficient statistics:
Matrix range [1e+00, 4e+00]
Objective range [3e+01, 5e+01]
Bounds range [0e+00, 0e+00]
RHS range [4e+01, 1e+02]
Presolve time: 0.03s
Presolved: 2 rows, 3 columns, 6 nonzeros
Iteration Objective Primal Inf. Dual Inf. Time
0 1.2000000e+32 4.350000e+30 1.200000e+02 0s
2 1.3600000e+03 0.000000e+00 0.000000e+00 0s
Solved in 2 iterations and 0.05 seconds (0.00 work units)
Optimal objective 1.360000000e+03
make[chair] 24.0
make[table] 8.0
make[bench] 0.0
optimal total revenue: 1360.0
make[chair] = 24.0 (40.0, 25.00000000000002, 66.66666666666667, 0.0)
make[table] = 8.0 (50.0, 47.85714285714286, 80.0, 0.0)
make[bench] = 0.0 (30.0, -inf, 31.2, -1.1999999999999993)
R[wood] : slack = 0.0 , shadow price= 16.0 , (40.0, 30.0, 80.0)
R[labor] : slack = 0.0 , shadow price= 6.0 , (120.0, 60.0, 160.0)
objective value = 1360.0
What about adding a new constraint as packaging and varying costs for chairs and tables?
# Adding new constraint
resources, capacity = multidict({
'wood': 40,
'labor': 120,
'packaging': 5 })
products, price = multidict({
'chair': 40,
'table': 50})
bom={
('wood','chair'):1,
('wood','table'):2,
('labor','chair'):4,
('labor','table'):3,
('packaging','chair'):0.2,
('packaging','table'):0.1,
}
m = Model('Factory')
make = m.addVars(products,name='make')
res = m.addConstrs(((sum(bom[r,p]*make[p] for p in products) <= capacity[r]) for r in resources),name='R')
m.setObjective(make.prod(price), GRB.MAXIMIZE)
m.write('factory.lp')
cat factory.lp
\ Model Factory
\ LP format - for model browsing. Use MPS format to capture full model detail.
Maximize
40 make[chair] + 50 make[table]
Subject To
R[wood]: make[chair] + 2 make[table] <= 40
R[labor]: 4 make[chair] + 3 make[table] <= 120
R[packaging]: 0.2 make[chair] + 0.1 make[table] <= 5
Bounds
End
m.optimize()
#display optimal production plan
for v in m.getVars():
print(v.varName, v.x)
print('optimal total revenue:', m.objVal)
for var in m.getVars(): # descision variable
print(var.varName, '=', var.x, (var.obj,var.SAObjLow, var.SAObjUp, var.RC))
for con in m.getConstrs(): # constraints
print(con.ConstrName, ': slack =', con.slack,', shadow price=',
con.pi,',', (con.RHS, con.SARHSLow, con.SARHSUp))
print("objective value =", m.objVal)
Gurobi Optimizer version 9.5.0 build v9.5.0rc5 (linux64)
Thread count: 1 physical cores, 2 logical processors, using up to 2 threads
Optimize a model with 3 rows, 2 columns and 6 nonzeros
Model fingerprint: 0x0fe37c52
Coefficient statistics:
Matrix range [1e-01, 4e+00]
Objective range [4e+01, 5e+01]
Bounds range [0e+00, 0e+00]
RHS range [5e+00, 1e+02]
Presolve time: 0.02s
Presolved: 3 rows, 2 columns, 6 nonzeros
Iteration Objective Primal Inf. Dual Inf. Time
0 9.0000000e+31 4.450000e+30 9.000000e+01 0s
3 1.3000000e+03 0.000000e+00 0.000000e+00 0s
Solved in 3 iterations and 0.04 seconds (0.00 work units)
Optimal objective 1.300000000e+03
make[chair] 20.0
make[table] 10.0
optimal total revenue: 1300.0
make[chair] = 20.0 (40.0, 25.0, 100.0, 0.0)
make[table] = 10.0 (50.0, 20.0, 80.0, 0.0)
R[wood] : slack = 0.0 , shadow price= 20.0 , (40.0, 25.0, 55.0)
R[labor] : slack = 10.0 , shadow price= 0.0 , (120.0, 110.0, inf)
R[packaging] : slack = 0.0 , shadow price= 100.0 , (5.0, 2.0, 5.6)
objective value = 1300.0
Problem example: C2Q1¶
Solve the following LP problem:
c = [10, 6]
A = [[3, 8 ],
[45, 30 ]]
b = [20, 180]
import numpy as np
print(np.array(c).shape,np.array(A).shape,np.array(b).shape)
decision_variables = range(len(c))
constraints = range(np.array(A).shape[0])
m = Model("C2Q1")
x = []
for i in decision_variables:
x.append(m.addVar(lb = 0, vtype = GRB.CONTINUOUS, name = 'x' + str(i)))
m.setObjective(quicksum(c[i] * x[i] for i in decision_variables) , GRB.MAXIMIZE)
m.addConstrs((quicksum(A[j][i] * x[i] for i in decision_variables)
<= b[j] for j in constraints), "constraints")
m.optimize()
for var in m.getVars(): # descision variable
print(var.varName, '=', var.x, (var.obj,var.SAObjLow, var.SAObjUp, var.RC))
for con in m.getConstrs(): # constraints
print(con.ConstrName, ': slack =', con.slack,', shadow price=',
con.pi,',', (con.RHS, con.SARHSLow, con.SARHSUp))
print("objective value =", m.objVal)
(2,) (2, 2) (2,)
Gurobi Optimizer version 9.5.0 build v9.5.0rc5 (linux64)
Thread count: 1 physical cores, 2 logical processors, using up to 2 threads
Optimize a model with 2 rows, 2 columns and 4 nonzeros
Model fingerprint: 0xbd442586
Coefficient statistics:
Matrix range [3e+00, 4e+01]
Objective range [6e+00, 1e+01]
Bounds range [0e+00, 0e+00]
RHS range [2e+01, 2e+02]
Presolve time: 0.01s
Presolved: 2 rows, 2 columns, 4 nonzeros
Iteration Objective Primal Inf. Dual Inf. Time
0 2.7500000e+30 2.828125e+30 2.750000e+00 0s
1 4.0000000e+01 0.000000e+00 0.000000e+00 0s
Solved in 1 iterations and 0.02 seconds (0.00 work units)
Optimal objective 4.000000000e+01
x0 = 4.0 (10.0, 9.000000000000002, inf, 0.0)
x1 = 0.0 (6.0, -inf, 6.666666666666666, -0.6666666666666661)
constraints[0] : slack = 8.0 , shadow price= 0.0 , (20.0, 12.0, inf)
constraints[1] : slack = 0.0 , shadow price= 0.2222222222222222 , (180.0, 0.0, 300.0)
objective value = 40.0
Problem example: C2Q2¶
Solve the following LP problem:
c = [0.5, 0.03]
A = [[8,6 ],
[1,2 ]]
b = [48,12]
print(np.array(c).shape,np.array(A).shape,np.array(b).shape)
decision_variables = range(len(c))
constraints = range(np.array(A).shape[0])
m = Model("C2Q1")
x = []
for i in decision_variables:
x.append(m.addVar(lb = 0, vtype = GRB.CONTINUOUS, name = 'x' + str(i)))
m.setObjective(quicksum(c[i] * x[i] for i in decision_variables) , GRB.MINIMIZE)
m.addConstrs((quicksum(A[j][i] * x[i] for i in decision_variables)
>= b[j] for j in constraints), "constraints")
m.optimize()
for var in m.getVars(): # descision variable
print(var.varName, '=', var.x, (var.obj,var.SAObjLow, var.SAObjUp, var.RC))
for con in m.getConstrs(): # constraints
print(con.ConstrName, ': slack =', con.slack,', shadow price=',
con.pi,',', (con.RHS, con.SARHSLow, con.SARHSUp))
print("objective value =", m.objVal)
(2,) (2, 2) (2,)
Gurobi Optimizer version 9.5.0 build v9.5.0rc5 (linux64)
Thread count: 1 physical cores, 2 logical processors, using up to 2 threads
Optimize a model with 2 rows, 2 columns and 4 nonzeros
Model fingerprint: 0x4d3e8f82
Coefficient statistics:
Matrix range [1e+00, 8e+00]
Objective range [3e-02, 5e-01]
Bounds range [0e+00, 0e+00]
RHS range [1e+01, 5e+01]
Presolve time: 0.02s
Presolved: 2 rows, 2 columns, 4 nonzeros
Iteration Objective Primal Inf. Dual Inf. Time
0 0.0000000e+00 1.200000e+01 0.000000e+00 0s
1 2.4000000e-01 0.000000e+00 0.000000e+00 0s
Solved in 1 iterations and 0.03 seconds (0.00 work units)
Optimal objective 2.400000000e-01
x0 = 0.0 (0.5, 0.03999999999999998, inf, 0.46)
x1 = 8.0 (0.03, -3.469446951953614e-18, 0.375, 0.0)
constraints[0] : slack = 0.0 , shadow price= 0.005 , (48.0, 36.0, inf)
constraints[1] : slack = -4.0 , shadow price= 0.0 , (12.0, -inf, 16.0)
objective value = 0.24
Problem example: C4Q8¶
Solve the following LP problem:
c = [4,3,2]
A = [[2,4,1 ],
[3,2,1 ]]
b = [16,12]
print(np.array(c).shape,np.array(A).shape,np.array(b).shape)
decision_variables = range(len(c))
constraints = range(np.array(A).shape[0])
m = Model("C4Q8")
x = []
for i in decision_variables:
x.append(m.addVar(lb = 0, vtype = GRB.CONTINUOUS, name = 'x' + str(i)))
m.setObjective(quicksum(c[i] * x[i] for i in decision_variables) , GRB.MINIMIZE)
m.addConstrs((quicksum(A[j][i] * x[i] for i in decision_variables)
>= b[j] for j in constraints), "constraints")
m.optimize()
for var in m.getVars(): # descision variable
print(var.varName, '=', var.x, (var.obj,var.SAObjLow, var.SAObjUp, var.RC))
for con in m.getConstrs(): # constraints
print(con.ConstrName, ': slack =', con.slack,', shadow price=',
con.pi,',', (con.RHS, con.SARHSLow, con.SARHSUp))
print("objective value =", m.objVal)
(3,) (2, 3) (2,)
Gurobi Optimizer version 9.5.0 build v9.5.0rc5 (linux64)
Thread count: 1 physical cores, 2 logical processors, using up to 2 threads
Optimize a model with 2 rows, 3 columns and 6 nonzeros
Model fingerprint: 0xf87028b1
Coefficient statistics:
Matrix range [1e+00, 4e+00]
Objective range [2e+00, 4e+00]
Bounds range [0e+00, 0e+00]
RHS range [1e+01, 2e+01]
Presolve time: 0.01s
Presolved: 2 rows, 3 columns, 6 nonzeros
Iteration Objective Primal Inf. Dual Inf. Time
0 0.0000000e+00 7.000000e+00 0.000000e+00 0s
2 1.7000000e+01 0.000000e+00 0.000000e+00 0s
Solved in 2 iterations and 0.02 seconds (0.00 work units)
Optimal objective 1.700000000e+01
x0 = 2.0000000000000004 (4.0, 1.4999999999999996, 4.5, 0.0)
x1 = 2.9999999999999996 (3.0, 2.6666666666666665, 8.0, 0.0)
x2 = 0.0 (2.0, 1.375, inf, 0.625)
constraints[0] : slack = 0.0 , shadow price= 0.12500000000000003 , (16.0, 8.0, 24.000000000000004)
constraints[1] : slack = 0.0 , shadow price= 1.25 , (12.0, 7.999999999999999, 24.0)
objective value = 17.0
Problem example: C4Q32¶
Solve the following LP problem:
c = [22,18,35,41,30,28,25,36,18]
A = [[1,1,1,0,0,0,0,0,0 ],
[0,0,0,1,1,1,0,0,0 ],
[0,0,0,0,0,0,1,1,1 ],
[1,0,0,1,0,0,1,0,0 ],
[0,1,0,0,1,0,0,1,0 ],
[0,0,1,0,0,1,0,0,1 ]]
b = [1,1,1,1,1,1]
print(np.array(c).shape,np.array(A).shape,np.array(b).shape)
decision_variables = range(len(c))
constraints = range(np.array(A).shape[0])
m = Model("C4Q32")
x = []
for i in decision_variables:
x.append(m.addVar(lb = 0, vtype = GRB.CONTINUOUS, name = 'x' + str(i)))
m.setObjective(quicksum(c[i] * x[i] for i in decision_variables) , GRB.MINIMIZE)
m.addConstrs((quicksum(A[j][i] * x[i] for i in decision_variables)
== b[j] for j in constraints), "constraints")
m.optimize()
for var in m.getVars(): # descision variable
print(var.varName, '=', var.x, (var.obj,var.SAObjLow, var.SAObjUp, var.RC))
for con in m.getConstrs(): # constraints
print(con.ConstrName, ': slack =', con.slack,', shadow price=',
con.pi,',', (con.RHS, con.SARHSLow, con.SARHSUp))
print("objective value =", m.objVal)
(9,) (6, 9) (6,)
Gurobi Optimizer version 9.5.0 build v9.5.0rc5 (linux64)
Thread count: 1 physical cores, 2 logical processors, using up to 2 threads
Optimize a model with 6 rows, 9 columns and 18 nonzeros
Model fingerprint: 0xadb8a4fb
Coefficient statistics:
Matrix range [1e+00, 1e+00]
Objective range [2e+01, 4e+01]
Bounds range [0e+00, 0e+00]
RHS range [1e+00, 1e+00]
Presolve time: 0.03s
Presolved: 6 rows, 9 columns, 18 nonzeros
Iteration Objective Primal Inf. Dual Inf. Time
0 6.4000000e+01 2.000000e+00 0.000000e+00 0s
2 7.0000000e+01 0.000000e+00 0.000000e+00 0s
Solved in 2 iterations and 0.06 seconds (0.00 work units)
Optimal objective 7.000000000e+01
x0 = 1.0 (22.0, -inf, 23.0, 0.0)
x1 = 0.0 (18.0, 17.0, 37.0, 0.0)
x2 = 0.0 (35.0, 16.0, inf, 19.0)
x3 = 0.0 (41.0, 34.0, inf, 7.0)
x4 = 1.0 (30.0, 11.0, 31.0, 0.0)
x5 = 0.0 (28.0, 27.0, 47.0, 0.0)
x6 = 0.0 (25.0, 24.0, inf, 1.0)
x7 = 0.0 (36.0, 20.0, inf, 16.0)
x8 = 1.0 (18.0, -inf, 19.0, 0.0)
constraints[0] : slack = 0.0 , shadow price= 18.0 , (1.0, 1.0, 1.0)
constraints[1] : slack = 0.0 , shadow price= 30.0 , (1.0, 1.0, 1.0)
constraints[2] : slack = 0.0 , shadow price= 20.0 , (1.0, 1.0, 1.0)
constraints[3] : slack = 0.0 , shadow price= 4.0 , (1.0, 1.0, 1.0)
constraints[4] : slack = 0.0 , shadow price= 0.0 , (1.0, 1.0, 1.0)
constraints[5] : slack = 0.0 , shadow price= -2.0 , (1.0, 1.0, 1.0)
objective value = 70.0
This would require the model to be reformulated with three new variables, \(x_{10}, x_{11}, x_{12}\), representing Kelly’s assignment to the press, lathe, and grinder. The model would be reformulated as,
c = [22,18,35,41,30,28,25,36,18,20,20,20]
A = [[1,1,1,0,0,0,0,0,0,0,0,0 ],
[0,0,0,1,1,1,0,0,0,0,0,0 ],
[0,0,0,0,0,0,1,1,1,0,0,0 ],
[0,0,0,0,0,0,0,0,0,1,1,1 ],
[1,0,0,1,0,0,1,0,0,1,0,0 ],
[0,1,0,0,1,0,0,1,0,0,1,0 ],
[0,0,1,0,0,1,0,0,1,0,0,1 ]]
b = [1,1,1,1,1,1,1]
print(np.array(c).shape,np.array(A).shape,np.array(b).shape)
decision_variables = range(len(c))
constraints = range(np.array(A).shape[0])
m = Model("C4Q32")
x = []
for i in decision_variables:
x.append(m.addVar(lb = 0, vtype = GRB.CONTINUOUS, name = 'x' + str(i)))
m.setObjective(quicksum(c[i] * x[i] for i in decision_variables) , GRB.MINIMIZE)
m.addConstrs((quicksum(A[j][i] * x[i] for i in decision_variables)
<= b[j] for j in range(4)), "constraints")
m.addConstrs((quicksum(A[j][i] * x[i] for i in decision_variables)
== b[j] for j in range(4,7)), "constraints")
m.optimize()
for var in m.getVars(): # descision variable
print(var.varName, '=', var.x, (var.obj,var.SAObjLow, var.SAObjUp, var.RC))
for con in m.getConstrs(): # constraints
print(con.ConstrName, ': slack =', con.slack,', shadow price=',
con.pi,',', (con.RHS, con.SARHSLow, con.SARHSUp))
print("objective value =", m.objVal)
(12,) (7, 12) (7,)
Gurobi Optimizer version 9.5.0 build v9.5.0rc5 (linux64)
Thread count: 1 physical cores, 2 logical processors, using up to 2 threads
Optimize a model with 7 rows, 12 columns and 24 nonzeros
Model fingerprint: 0x447648af
Coefficient statistics:
Matrix range [1e+00, 1e+00]
Objective range [2e+01, 4e+01]
Bounds range [0e+00, 0e+00]
RHS range [1e+00, 1e+00]
Presolve time: 0.01s
Presolved: 7 rows, 12 columns, 24 nonzeros
Iteration Objective Primal Inf. Dual Inf. Time
0 5.6000000e+01 0.000000e+00 0.000000e+00 0s
Solved in 0 iterations and 0.02 seconds (0.00 work units)
Optimal objective 5.600000000e+01
x0 = 0.0 (22.0, 20.0, inf, 2.0)
x1 = 1.0 (18.0, -inf, 20.0, 0.0)
x2 = 0.0 (35.0, 18.0, inf, 17.0)
x3 = 0.0 (41.0, 20.0, inf, 21.0)
x4 = 0.0 (30.0, 18.0, inf, 12.0)
x5 = 0.0 (28.0, 18.0, inf, 10.0)
x6 = 0.0 (25.0, 20.0, inf, 5.0)
x7 = 0.0 (36.0, 18.0, inf, 18.0)
x8 = 1.0 (18.0, -inf, 20.0, 0.0)
x9 = 1.0 (20.0, -inf, 22.0, 0.0)
x10 = 0.0 (20.0, 18.0, inf, 2.0)
x11 = 0.0 (20.0, 18.0, inf, 2.0)
constraints[0] : slack = 0.0 , shadow price= 0.0 , (1.0, 1.0, inf)
constraints[1] : slack = 1.0 , shadow price= 0.0 , (1.0, 0.0, inf)
constraints[2] : slack = 0.0 , shadow price= 0.0 , (1.0, 1.0, inf)
constraints[3] : slack = 0.0 , shadow price= 0.0 , (1.0, 1.0, inf)
constraints[4] : slack = 0.0 , shadow price= 20.0 , (1.0, 0.0, 1.0)
constraints[5] : slack = 0.0 , shadow price= 18.0 , (1.0, 0.0, 1.0)
constraints[6] : slack = 0.0 , shadow price= 18.0 , (1.0, 0.0, 1.0)
objective value = 56.0
Problem example: C4Q33¶
Solve the following LP problem:
This is a transportation problem
c = [40,65,70,30]
A = [[1,1,0,0 ],
[0,0,1,1 ],
[1,0,1,0 ],
[0,1,0,1 ]]
b = [250, 400, 300, 350]
print(np.array(c).shape,np.array(A).shape,np.array(b).shape)
decision_variables = range(len(c))
constraints = range(np.array(A).shape[0])
m = Model("C4Q33")
x = []
for i in decision_variables:
x.append(m.addVar(lb = 0, vtype = GRB.CONTINUOUS, name = 'x' + str(i)))
m.setObjective(quicksum(c[i] * x[i] for i in decision_variables) , GRB.MINIMIZE)
m.addConstrs((quicksum(A[j][i] * x[i] for i in decision_variables)
== b[j] for j in constraints), "constraints")
m.optimize()
for var in m.getVars(): # descision variable
print(var.varName, '=', var.x, (var.obj,var.SAObjLow, var.SAObjUp, var.RC))
for con in m.getConstrs(): # constraints
print(con.ConstrName, ': slack =', con.slack,', shadow price=',
con.pi,',', (con.RHS, con.SARHSLow, con.SARHSUp))
print("objective value =", m.objVal)
(4,) (4, 4) (4,)
Gurobi Optimizer version 9.5.0 build v9.5.0rc5 (linux64)
Thread count: 1 physical cores, 2 logical processors, using up to 2 threads
Optimize a model with 4 rows, 4 columns and 8 nonzeros
Model fingerprint: 0x1da74e02
Coefficient statistics:
Matrix range [1e+00, 1e+00]
Objective range [3e+01, 7e+01]
Bounds range [0e+00, 0e+00]
RHS range [2e+02, 4e+02]
Presolve removed 4 rows and 4 columns
Presolve time: 0.03s
Presolve: All rows and columns removed
Iteration Objective Primal Inf. Dual Inf. Time
0 2.4000000e+04 0.000000e+00 0.000000e+00 0s
Solved in 0 iterations and 0.06 seconds (0.00 work units)
Optimal objective 2.400000000e+04
x0 = 250.0 (40.0, -inf, 105.0, 0.0)
x1 = 0.0 (65.0, 0.0, inf, 65.0)
x2 = 50.0 (70.0, 5.0, inf, 0.0)
x3 = 350.0 (30.0, -inf, 95.0, 0.0)
constraints[0] : slack = 0.0 , shadow price= -0.0 , (250.0, 250.0, 250.0)
constraints[1] : slack = 0.0 , shadow price= 30.0 , (400.0, 400.0, 400.0)
constraints[2] : slack = 0.0 , shadow price= 40.0 , (300.0, 300.0, 300.0)
constraints[3] : slack = 0.0 , shadow price= 0.0 , (350.0, 350.0, 350.0)
objective value = 24000.0