Factory Planning 2¶
Reference¶
SAS/OR example: http://go.documentation.sas.com/?docsetId=ormpex&docsetTarget=ormpex_ex4_toc.htm&docsetVersion=15.1&locale=en
SAS/OR code for example: http://support.sas.com/documentation/onlinedoc/or/ex_code/151/mpex04.html
Model¶
import sasoptpy as so
import pandas as pd
def test(cas_conn):
m = so.Model(name='factory_planning_2', session=cas_conn)
# Input data
product_list = ['prod{}'.format(i) for i in range(1, 8)]
product_data = pd.DataFrame([10, 6, 8, 4, 11, 9, 3],
columns=['profit'], index=product_list)
demand_data = [
[500, 1000, 300, 300, 800, 200, 100],
[600, 500, 200, 0, 400, 300, 150],
[300, 600, 0, 0, 500, 400, 100],
[200, 300, 400, 500, 200, 0, 100],
[0, 100, 500, 100, 1000, 300, 0],
[500, 500, 100, 300, 1100, 500, 60]]
demand_data = pd.DataFrame(
demand_data, columns=product_list, index=range(1, 7))
machine_type_product_data = [
['grinder', 0.5, 0.7, 0, 0, 0.3, 0.2, 0.5],
['vdrill', 0.1, 0.2, 0, 0.3, 0, 0.6, 0],
['hdrill', 0.2, 0, 0.8, 0, 0, 0, 0.6],
['borer', 0.05, 0.03, 0, 0.07, 0.1, 0, 0.08],
['planer', 0, 0, 0.01, 0, 0.05, 0, 0.05]]
machine_type_product_data = \
pd.DataFrame(machine_type_product_data, columns=['machine_type'] +
product_list).set_index(['machine_type'])
machine_types_data = [
['grinder', 4, 2],
['vdrill', 2, 2],
['hdrill', 3, 3],
['borer', 1, 1],
['planer', 1, 1]]
machine_types_data = pd.DataFrame(machine_types_data, columns=[
'machine_type', 'num_machines', 'num_machines_needing_maintenance'])\
.set_index(['machine_type'])
store_ub = 100
storage_cost_per_unit = 0.5
final_storage = 50
num_hours_per_period = 24 * 2 * 8
# Problem definition
PRODUCTS = product_list
profit = product_data['profit']
PERIODS = range(1, 7)
MACHINE_TYPES = machine_types_data.index.tolist()
num_machines = machine_types_data['num_machines']
make = m.add_variables(PRODUCTS, PERIODS, lb=0, name='make')
sell = m.add_variables(PRODUCTS, PERIODS, lb=0, ub=demand_data.transpose(),
name='sell')
store = m.add_variables(PRODUCTS, PERIODS, lb=0, ub=store_ub, name='store')
for p in PRODUCTS:
store[p, 6].set_bounds(lb=final_storage, ub=final_storage)
storageCost = so.expr_sum(
storage_cost_per_unit * store[p, t] for p in PRODUCTS for t in PERIODS)
revenue = so.expr_sum(profit[p] * sell[p, t]
for p in PRODUCTS for t in PERIODS)
m.set_objective(revenue-storageCost, sense=so.MAX, name='total_profit')
num_machines_needing_maintenance = \
machine_types_data['num_machines_needing_maintenance']
numMachinesDown = m.add_variables(MACHINE_TYPES, PERIODS, vartype=so.INT,
lb=0, name='numMachinesDown')
production_time = machine_type_product_data
m.add_constraints((
so.expr_sum(production_time.at[mc, p] * make[p, t] for p in PRODUCTS)
<= num_hours_per_period *
(num_machines[mc] - numMachinesDown[mc, t])
for mc in MACHINE_TYPES for t in PERIODS), name='machine_hours_con')
m.add_constraints((so.expr_sum(numMachinesDown[mc, t] for t in PERIODS) ==
num_machines_needing_maintenance[mc]
for mc in MACHINE_TYPES), name='maintenance_con')
m.add_constraints(((store[p, t-1] if t-1 in PERIODS else 0) + make[p, t] ==
sell[p, t] + store[p, t]
for p in PRODUCTS for t in PERIODS),
name='flow_balance_con')
res = m.solve()
if res is not None:
print(so.get_solution_table(make, sell, store))
print(so.get_solution_table(numMachinesDown).unstack(level=-1))
print(m.get_solution_summary())
print(m.get_problem_summary())
return m.get_objective_value()
Output¶
In [1]: import os
In [2]: hostname = os.getenv('CASHOST')
In [3]: port = os.getenv('CASPORT')
In [4]: from swat import CAS
In [5]: cas_conn = CAS(hostname, port)
In [6]: import sasoptpy
In [7]: from examples.client_side.factory_planning_2 import test
In [8]: test(cas_conn)
NOTE: Initialized model factory_planning_2.
NOTE: Added action set 'optimization'.
NOTE: Converting model factory_planning_2 to OPTMODEL.
NOTE: Submitting OPTMODEL code to CAS server.
NOTE: Problem generation will use 8 threads.
NOTE: The problem has 156 variables (0 free, 13 fixed).
NOTE: The problem has 0 binary and 30 integer variables.
NOTE: The problem has 77 linear constraints (30 LE, 47 EQ, 0 GE, 0 range).
NOTE: The problem has 341 linear constraint coefficients.
NOTE: The problem has 0 nonlinear constraints (0 LE, 0 EQ, 0 GE, 0 range).
NOTE: The OPTMODEL presolver is disabled for linear problems.
NOTE: The initial MILP heuristics are applied.
NOTE: The MILP presolver value AUTOMATIC is applied.
NOTE: The MILP presolver removed 27 variables and 15 constraints.
NOTE: The MILP presolver removed 63 constraint coefficients.
NOTE: The MILP presolver modified 16 constraint coefficients.
NOTE: The presolved problem has 129 variables, 62 constraints, and 278 constraint coefficients.
NOTE: The MILP solver is called.
NOTE: The parallel Branch and Cut algorithm is used.
NOTE: The Branch and Cut algorithm is using up to 8 threads.
Node Active Sols BestInteger BestBound Gap Time
0 1 2 92755.0000000 116455 20.35% 0
0 1 2 92755.0000000 116455 20.35% 0
0 1 2 92755.0000000 116141 20.14% 0
0 1 2 92755.0000000 115660 19.80% 0
0 1 2 92755.0000000 114136 18.73% 0
0 1 2 92755.0000000 113334 18.16% 0
0 1 2 92755.0000000 112487 17.54% 0
0 1 2 92755.0000000 111392 16.73% 0
0 1 2 92755.0000000 111136 16.54% 0
0 1 2 92755.0000000 110056 15.72% 0
0 1 2 92755.0000000 109718 15.46% 0
0 1 2 92755.0000000 109122 15.00% 0
0 1 2 92755.0000000 108904 14.83% 0
0 1 2 92755.0000000 108868 14.80% 0
0 1 2 92755.0000000 108855 14.79% 0
0 1 3 108855 108855 0.00% 0
NOTE: The MILP solver added 38 cuts with 136 cut coefficients at the root.
NOTE: Optimal within relative gap.
NOTE: Objective = 108855.00961.
NOTE: The output table 'SOLUTION' in caslib 'CASUSER(casuser)' has 156 rows and 6 columns.
NOTE: The output table 'DUAL' in caslib 'CASUSER(casuser)' has 77 rows and 4 columns.
NOTE: The CAS table 'solutionSummary' in caslib 'CASUSER(casuser)' has 18 rows and 4 columns.
NOTE: The CAS table 'problemSummary' in caslib 'CASUSER(casuser)' has 20 rows and 4 columns.
make sell store
(prod1, 1) 500.000000 500.000000 0.000000
(prod1, 2) 600.000200 600.000000 0.000200
(prod1, 3) 399.999201 300.000000 99.999401
(prod1, 4) 0.001198 100.000599 0.000000
(prod1, 5) 0.000000 0.000000 0.000000
(prod1, 6) 550.000000 500.000000 50.000000
(prod2, 1) 1000.000000 1000.000000 0.000000
(prod2, 2) 500.000188 500.000000 0.000188
(prod2, 3) 699.998215 599.999002 99.999401
(prod2, 4) 0.001797 100.001198 0.000000
(prod2, 5) 100.002196 100.000000 0.002196
(prod2, 6) 549.997804 500.000000 50.000000
(prod3, 1) 300.000000 300.000000 0.000000
(prod3, 2) 200.000000 200.000000 0.000000
(prod3, 3) 100.000000 0.000000 100.000000
(prod3, 4) 0.001283 100.001283 0.000000
(prod3, 5) 500.000072 500.000000 0.000072
(prod3, 6) 149.999928 100.000000 50.000000
(prod4, 1) 300.000000 300.000000 0.000000
(prod4, 2) 0.000000 0.000000 0.000000
(prod4, 3) 99.999401 0.000000 99.999401
(prod4, 4) 0.002994 100.002396 0.000000
(prod4, 5) 100.000000 100.000000 0.000000
(prod4, 6) 350.000000 300.000000 50.000000
(prod5, 1) 800.000522 800.000000 0.000522
(prod5, 2) 399.999338 399.999460 0.000399
(prod5, 3) 599.998374 499.999201 99.999572
(prod5, 4) 0.001027 100.000599 0.000000
(prod5, 5) 1000.006512 1000.000000 0.006512
(prod5, 6) 1149.992981 1099.999493 50.000000
(prod6, 1) 200.000000 200.000000 0.000000
(prod6, 2) 300.000000 300.000000 0.000000
(prod6, 3) 400.000000 400.000000 0.000000
(prod6, 4) 0.000000 0.000000 0.000000
(prod6, 5) 300.000000 300.000000 0.000000
(prod6, 6) 550.000000 500.000000 50.000000
(prod7, 1) 100.000000 100.000000 0.000000
(prod7, 2) 150.000000 150.000000 0.000000
(prod7, 3) 199.999572 100.000000 99.999572
(prod7, 4) 0.000428 100.000000 0.000000
(prod7, 5) 0.000000 0.000000 0.000000
(prod7, 6) 110.000000 60.000000 50.000000
numMachinesDown (grinder, 1) -0.000000e+00
numMachinesDown (grinder, 2) -0.000000e+00
numMachinesDown (grinder, 3) -0.000000e+00
numMachinesDown (grinder, 4) 2.000000e+00
numMachinesDown (grinder, 5) -0.000000e+00
numMachinesDown (grinder, 6) -0.000000e+00
numMachinesDown (vdrill, 1) 0.000000e+00
numMachinesDown (vdrill, 2) -0.000000e+00
numMachinesDown (vdrill, 3) -0.000000e+00
numMachinesDown (vdrill, 4) 1.999994e+00
numMachinesDown (vdrill, 5) 3.955573e-06
numMachinesDown (vdrill, 6) 2.033239e-06
numMachinesDown (hdrill, 1) 1.000000e+00
numMachinesDown (hdrill, 2) 2.000000e+00
numMachinesDown (hdrill, 3) -0.000000e+00
numMachinesDown (hdrill, 4) -0.000000e+00
numMachinesDown (hdrill, 5) -0.000000e+00
numMachinesDown (hdrill, 6) -0.000000e+00
numMachinesDown (borer, 1) 0.000000e+00
numMachinesDown (borer, 2) -0.000000e+00
numMachinesDown (borer, 3) 1.996271e-06
numMachinesDown (borer, 4) 9.999940e-01
numMachinesDown (borer, 5) -0.000000e+00
numMachinesDown (borer, 6) 3.992541e-06
numMachinesDown (planer, 1) 0.000000e+00
numMachinesDown (planer, 2) 1.798545e-06
numMachinesDown (planer, 3) 1.996271e-06
numMachinesDown (planer, 4) 9.999957e-01
numMachinesDown (planer, 5) 8.120488e-16
numMachinesDown (planer, 6) 4.829074e-07
dtype: float64
Selected Rows from Table SOLUTIONSUMMARY
Value
Label
Solver MILP
Algorithm Branch and Cut
Objective Function total_profit
Solution Status Optimal within Relative Gap
Objective Value 108855.00961
Relative Gap 3.0634424E-6
Absolute Gap 0.3334720743
Primal Infeasibility 5.684342E-14
Bound Infeasibility 0
Integer Infeasibility 5.9888119E-6
Best Bound 108855.34308
Nodes 1
Solutions Found 3
Iterations 377
Presolve Time 0.00
Solution Time 0.33
Selected Rows from Table PROBLEMSUMMARY
Value
Label
Objective Sense Maximization
Objective Function total_profit
Objective Type Linear
Number of Variables 156
Bounded Above 0
Bounded Below 72
Bounded Below and Above 71
Free 0
Fixed 13
Binary 0
Integer 30
Number of Constraints 77
Linear LE (<=) 30
Linear EQ (=) 47
Linear GE (>=) 0
Linear Range 0
Constraint Coefficients 341
Out[8]: 108855.00960810453