MMIS 692: Production Planning¶

Formulate LP model¶

Parameters of the LP model:¶

  • $D_i$ is the demand (number of units required) for product $i$.
  • $C_i$ is the cost (in dollars) for producing each unit of product $i$ in-house.
  • $P_i$ is the price (in dollars) for purchasing each unit of product $i$.
  • $m_i$ is the machining time (in minutes) required to produce each unit of product $i$.
  • $a_i$ is the assembly time (in minutes) required to produce each unit of product $i$.
  • $f_i$ is the finishing time (in minutes) required to produce each unit of product $i$.
  • $T_m$ is the machining time available (in minutes)
  • $T_a$ is the assembly time available (in minutes)
  • $T_f$ is the finishing time available (in minutes)

Decision Variables (all variables non-negative):¶

  • $X_i$ : for $i=1,2,...,5$: number of units of product $i$ produced.
  • $Y_i$ : for $i=1,2,...,5$: number of units of product $i$ purchased.

Objective function:¶

  • Minimize Cost = $\sum_{i=1}^5 \ (C_i X_i + P_i Y_i)$

Constraints:¶

  • Demand: $X_i + Y_i \ge D_i$ for $i=1,2,..,5$
  • Machining time: $\sum_{i=1}^5 \ m_i X_i \le T_m$
  • Assembly time: $\sum_{i=1}^5 \ a_i X_i \le T_a$
  • Finishing time: $\sum_{i=1}^5 \ f_i X_i \le T_f$

Import libraries¶

We shall use the Python library PuLP (https://pypi.org/project/PuLP/) for creating and solving the LP model.

In [ ]:
! pip install pulp # install if necessary
from pulp import * # for LP model
import pandas as pd # for data handling
Collecting pulp
  Downloading pulp-3.3.0-py3-none-any.whl.metadata (8.4 kB)
Downloading pulp-3.3.0-py3-none-any.whl (16.4 MB)
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 16.4/16.4 MB 131.8 MB/s eta 0:00:00
Installing collected packages: pulp
Successfully installed pulp-3.3.0

Get data¶

We shall extract CSV files from the zip file with data and read the data we need for this task into pandas dataframes:

  • product: with data from 'production_planning.product'.csv'
  • resource: with data from 'production_planning.resource.csv'
In [ ]:
from google.colab import drive
drive.mount('/content/drive')
Mounted at /content/drive
In [ ]:
! unzip '/content/drive/MyDrive/NSU - MS - Data Analytics and AI/MMIS 0692 - Data Analytics and AI Project/data.MMIS692.Fall2025.zip'
product = pd.read_csv('production_planning.product.csv') # dataframe with product data
resource = pd.read_csv('production_planning.resource.csv') # dataframe with resource data
! rm *.csv # remove all data files to avoid clutter
Archive:  /content/drive/MyDrive/NSU - MS - Data Analytics and AI/MMIS 0692 - Data Analytics and AI Project/data.MMIS692.Fall2025.zip
  inflating: quality_control.new_batches.csv  
  inflating: quality_control.measurements.csv  
  inflating: quality_control.defective.csv  
  inflating: production_planning.resource.csv  
  inflating: production_planning.product.csv  
  inflating: customer_segmentation.unlabeled.csv  
  inflating: customer_segmentation.valid.csv  
  inflating: customer_segmentation.train.csv  
In [ ]:
product # display product data
Out[ ]:
parameter P1 P2 P3 P4 P5
0 demand 7000 6000 4000 5000 1000
1 cost_inhouse 93 109 44 80 46
2 cost_outsource 104 123 61 90 59
3 machine_time 2 1 2 2 1
4 assembly_time 1 3 3 1 3
5 finishing_time 3 4 4 2 3
In [ ]:
resource # display resource data
Out[ ]:
resource available_hours hourly_cost
0 machine_time 585 300
1 assembly_time 675 240
2 finishing_time 1110 180

Get LP parameters¶

We shall read in parameter values for our LP model from dataframes.

In [ ]:
PRODUCTS = list(product)[1:] # list of products (all but first column header)
N = len(PRODUCTS) # number of products
D, C, P, m, a, f = product.values[:,1:].tolist() # parameter values

RESOURCES = resource.resource.tolist() # list of resources
Tm, Ta, Tf = (60*resource.available_hours).tolist() # parameter values
HOURLY_COST = resource.hourly_cost.tolist()

Create LP model¶

In [ ]:
LP_file = 'MMIS692_prodution_planning.lp' # name of LP model file
prob = LpProblem(LP_file, LpMinimize) # Create LP model object

Define decision variables¶

In [ ]:
X = [LpVariable(f'x_{i+1}',0) for i in range(N)] # quantities produced
Y = [LpVariable(f'y_{i+1}',0) for i in range(N)] # quantities purchased

Specify objective function¶

In [ ]:
prob += lpSum(C[i]*X[i] + P[i]*Y[i] for i in range(N)) # objective function

Specify demand constraints¶

In [ ]:
for i in range(N):
    prob += X[i] + Y[i] >= D[i] , 'Demand for '+ PRODUCTS[i] # demand for product i

Add resource availability constraints¶

In [ ]:
for r, t, q in zip(RESOURCES, [m, a, f], [Tm, Ta, Tf]): # for each resource
    prob += sum(t[i]*X[i] for i in range(N)) <= q, r +'_availability' # add resource availability constraint

Save LP model¶

In [ ]:
prob.writeLP(LP_file) # write model to LP file
print(open(LP_file).read()) # show LP model
\* MMIS692_prodution_planning.lp *\
Minimize
OBJ: 93 x_1 + 109 x_2 + 44 x_3 + 80 x_4 + 46 x_5 + 104 y_1 + 123 y_2 + 61 y_3
 + 90 y_4 + 59 y_5
Subject To
Demand_for_P1: x_1 + y_1 >= 7000
Demand_for_P2: x_2 + y_2 >= 6000
Demand_for_P3: x_3 + y_3 >= 4000
Demand_for_P4: x_4 + y_4 >= 5000
Demand_for_P5: x_5 + y_5 >= 1000
assembly_time_availability: x_1 + 3 x_2 + 3 x_3 + x_4 + 3 x_5 <= 40500
finishing_time_availability: 3 x_1 + 4 x_2 + 4 x_3 + 2 x_4 + 3 x_5 <= 66600
machine_time_availability: 2 x_1 + x_2 + 2 x_3 + 2 x_4 + x_5 <= 35100
End

Solve LP model¶

In [ ]:
prob.solve() # solve problem
status = LpStatus[prob.status] # optimal found?
print("Solution: ", status)

min_cost = value(prob.objective) # objective function value
print(f'Minimum cost = $ {min_cost:,.2f}') # show optimal cost
Solution:  Optimal
Minimum cost = $ 1,956,120.00

Optimal decision variable values¶

In [ ]:
# get optimal values for decision variables
X_opt = [x.varValue for x in X] # optimal quantities produced
Y_opt = [y.varValue for y in Y] # optimal quantities purchased

# Save and display optimal quantities in a dataframe
opt_qty = pd.DataFrame([X_opt, Y_opt], columns=PRODUCTS).round(2) # convert to dataframe
opt_qty.insert(0, 'Quantity', ['Produced', 'Purchased']) # add first column
opt_qty.to_csv("optimal_quantities.csv", index=False) # save results
print('Optimal quantities:')
opt_qty # display optimal quantities
Optimal quantities:
Out[ ]:
Quantity P1 P2 P3 P4 P5
0 Produced 6560.0 4980.0 4000.0 4000.0 1000.0
1 Purchased 440.0 1020.0 0.0 1000.0 0.0

Resources used¶

In [ ]:
resource_use = [] # list with results
for r in RESOURCES: # for each resource
    name = f'{r}_availability' # availability constraint name
    c = prob.constraints[name] # details for constraint
    available = -c.constant # available minutes (RHS of the constraint)
    unused = c.slack # minutes not used
    used = available - unused # minutes used
    resource_use.append([r, used, available, unused]) # append results for resource
# convert results to dataframe
resource_use = pd.DataFrame(resource_use, columns = ['resource', 'used', 'available', 'unused'])
resource_use.to_csv('resource_use.csv', index=False) # sane results
resource_use # show results
Out[ ]:
resource used available unused
0 machine_time 35100.0 35100 -0.0
1 assembly_time 40500.0 40500 -0.0
2 finishing_time 66600.0 66600 -0.0

Sensitivity analysis¶

In [ ]:
max_price = [] # results with maximum cost per additional hour
for r, hc in zip(RESOURCES, HOURLY_COST): # for each resource and its hourly cost
    name = f'{r}_availability' # constraint name
    c = prob.constraints[name] # details for constraint
    shadow_price = -c.pi # cost savings per minute of resource
    savings = 60*shadow_price # cost savings per additional hour of resource
    price = hc + savings # maximum price for an additional hour of resource
    max_price.append([r, hc, savings, price])
# convert results to dataframe
max_price = pd.DataFrame(max_price, columns = ['resource', 'cost per hour', 'savings per hour', 'max price'])
max_price.to_csv('max_price.csv', index=False) # save results
max_price # show results
Out[ ]:
resource cost per hour savings per hour max price
0 machine_time 300 168.0 468.0
1 assembly_time 240 144.0 384.0
2 finishing_time 180 60.0 240.0
In [ ]:
shadow_price
Out[ ]:
1.0