Petroleum Blending (Solved)

Try me

Open In ColabBinder

Problem Definition

Harry Stamper is an engineer working on a petroleum company. The company produces three grades of motor oil – super, premium, and extra. The three grades are made from the same 3 ingredients, named component 1, component 2 and component 3. The company wants to determine the optimal mix of the 3 components in each grade of motor oil that will maximize profit.

The maximum availability of each component and their cost per barrel are as follows:

Component

Max barrels available / day

Cost / barrel

Component 1

4.500

12€

Component 2

2.700

10€

Component 3

3.500

14€

To ensure the appropriate blend, each grade must have a minimum amount of component 1 plus a combination of other components as follows:

Grade

Component 1

Component 2

Component 3

Selling price/barrel

Super

At least 50%

No more than 30%

23€

Premium

At least 40%

No more than 25%

20€

Extra

At least 60%

At least 10%

18€

The company wants to make at least 3000 barrels of each blend. Formulate a LP to help Harry Stamper find the optimal blend that maximises profit.

Problem Model

The decision variables are:

\(x_{ij}\) = amount in barrels of component i in grade j

Objective function

max \(z = 23*(x_{11} + x_{21} + x_{31}) + 20*(x_{12} + x_{22} + x_{32})+18*(x_{13} + x_{23} + x_{33})-12*(x_{11} + x_{12} + x_{13}) - 10*(x_{21} + x_{22} + x_{23})-14*(x_{31} + x_{32} + x_{33})\)

Constraints

\(x_{11} \geq 0.5*(x_{11} + x_{21} + x_{31})\) Component 1 requirement in Super

\(x_{21} \leq 0.3*(x_{11} + x_{21} + x_{31})\) Component 2 requirement in Super

\(x_{12} \geq 0.4*(x_{12} + x_{22} + x_{32})\) Component 1 requirement in Premium

\(x_{32} \leq 0.25*(x_{12} + x_{22} + x_{32})\) Component 3 requirement in Premium

\(x_{13} \geq 0.6*(x_{13} + x_{23} + x_{33})\) Component 1 requirement in Extra

\(x_{23} \geq 0.1*(x_{13} + x_{23} + x_{33})\) Component 2 requirement in Extra

\(x_{11}+x_{12}+x_{13} \leq 4500\) Component 1 availability

\(x_{21}+x_{22}+x_{23} \leq 2700\) Component 1 availability

\(x_{31}+x_{32}+x_{33} \leq 3500\) Component 1 availability

\(x_{11}+x_{21}+x_{31} \geq 3000\) Super minimum production

\(x_{12}+x_{22}+x_{32} \geq 3000\) Premium minimum production

\(x_{13}+x_{23}+x_{33} \geq 3000\) Extra minimum production

[ ]:
!pip install pandas
!pip install numpy
!pip install pulp
[50]:
import pandas as pd
import numpy as np
from IPython.display import display, Markdown
import pulp
[51]:
# Instantiate our problem class
model = pulp.LpProblem("Maximising profits for Harry Stamper", pulp.LpMaximize)
[52]:
# Construct our decision variable lists
components = ['Component 1', 'Component 2', 'Component 3']
blends = ['Super', 'Premium', 'Extra']
#decision variables
variables = pulp.LpVariable.dicts("Quantity in barrels",
                                     ((i, j) for i in blends for j in components),
                                     lowBound=0,
                                     cat='Continuous')

# barrels is Super Component 1, super component 2, ..., extra component 3: x11, x21, x31, ...
cost = [12, 10, 14]
price = [23, 20, 18]
coefficients =[price[i] - cost[j] for i in range(len(price)) for j in range(len(cost))]
# Objective Function
model += (
    pulp.lpSum([
        ((price[i]-cost[j]) * variables[(blends[i], components[j])]
        for i in range(len(blends)) for j in range(len(components)))])
)

# Requirements

model += variables['Super','Component 1']>= 0.5*pulp.lpSum([variables['Super',components[j]] for j in range(len(components))]), "Component 1 requirement in super"
model += variables['Super','Component 2']<= 0.3*pulp.lpSum([variables['Super',components[j]] for j in range(len(components))]), "Component 2 requirement in super"

model += variables['Premium','Component 1']>= 0.4*pulp.lpSum([variables['Premium',components[j]] for j in range(len(components))]), "Component 1 requirement in premium"
model += variables['Premium','Component 3']<= 0.25*pulp.lpSum([variables['Premium',components[j]] for j in range(len(components))]), "Component 3 requirement in premium"

model += variables['Extra','Component 1']>= 0.6*pulp.lpSum([variables['Extra',components[j]] for j in range(len(components))]), "Component 1 requirement in Extra"
model += variables['Extra','Component 2']>= 0.1*pulp.lpSum([variables['Extra',components[j]] for j in range(len(components))]), "Component 2 requirement in Extra"


model += pulp.lpSum([variables[blends[i], 'Component 1'] for i in range(len(blends))]) <= 4500, "Component 1 availability"
model += pulp.lpSum([variables[blends[i], 'Component 2'] for i in range(len(blends))]) <= 2700, "Component 2 availability"
model += pulp.lpSum([variables[blends[i], 'Component 3'] for i in range(len(blends))]) <= 3500, "Component 3 availability"

model += pulp.lpSum([variables['Super', components[j]] for j in range(len(components))]) >= 3000, "Super minimum production"
model += pulp.lpSum([variables['Premium', components[j]] for j in range(len(components))]) >= 3000, "Premium minimum production"
model += pulp.lpSum([variables['Extra', components[j]] for j in range(len(components))]) >= 3000, "Extra minimum production"

[53]:
# Solve our problem
model.solve(solver=pulp.solvers.GUROBI(msg = 0))
pulp.LpStatus[model.status]
[53]:
'Optimal'
[54]:
total_profit = pulp.value(model.objective)
display(Markdown("Total profit is %0.2f €"%total_profit))

display(Markdown("The following table shows the decision variables: "))
var_df = pd.DataFrame.from_dict(variables, orient="index",
                                columns = ["Variables"])
var_df["Solution (GRB)"] = var_df["Variables"].apply(lambda item: "{:.3f}".format(item.solverVar.X))
var_df["Reduced cost (GRB)"] = var_df["Variables"].apply(lambda item: "{:.3f}".format(item.solverVar.RC))
var_df["Objective Coefficient (GRB)"] = var_df["Variables"].apply(lambda item: "{:.3f}".format(item.solverVar.Obj))
var_df["Objective Lower bound (GRB)"] = var_df["Variables"].apply(lambda item: "{:.3f}".format(item.solverVar.SAObjLow) if item.solverVar.SAObjLow > -0.1 else "-Inf" )
var_df["Objective Upper bound (GRB)"] = var_df["Variables"].apply(lambda item: "{:.3f}".format(item.solverVar.SAObjUp) if item.solverVar.SAObjUp != item.solverVar.UB else "Inf")


display(var_df)


const_dict = dict(model.constraints)
con_df = pd.DataFrame.from_records(list(const_dict.items()), exclude=["Expression"], columns=["Constraint", "Expression"])
con_df["Right Hand Side"]=con_df["Constraint"].apply(lambda item: "{:.2f}".format(const_dict[item].solverConstraint.RHS))
con_df["Shadow Price"]=con_df["Constraint"].apply(lambda item: "{:.2f}".format(const_dict[item].solverConstraint.Pi))
con_df["Slack"]=con_df["Constraint"].apply(lambda item: "{:.2f}".format(const_dict[item].solverConstraint.Slack))
con_df["Min RHS"]=con_df["Constraint"].apply(lambda item: "{:.2f}".format(const_dict[item].solverConstraint.SARHSLow) if const_dict[item].solverConstraint.SARHSLow > -1e10 else "-Inf")
con_df["Max RHS"]=con_df["Constraint"].apply(lambda item: "{:.2f}".format(const_dict[item].solverConstraint.SARHSUp) if const_dict[item].solverConstraint.SARHSUp < 1e10 else "Inf" )


display(Markdown("The following table shows the constraints: "))
display(con_df)

Total profit is 76800.00 €

The following table shows the decision variables:

Variables Solution (GRB) Reduced cost (GRB) Objective Coefficient (GRB) Objective Lower bound (GRB) Objective Upper bound (GRB)
(Super, Component 1) Quantity_in_barrels_('Super',_'Component_1') 1500.000 0.000 11.000 8.000 Inf
(Super, Component 2) Quantity_in_barrels_('Super',_'Component_2') 450.000 0.000 13.000 13.000 13.000
(Super, Component 3) Quantity_in_barrels_('Super',_'Component_3') 1050.000 0.000 9.000 9.000 9.000
(Premium, Component 1) Quantity_in_barrels_('Premium',_'Component_1') 1200.000 0.000 8.000 -Inf 11.000
(Premium, Component 2) Quantity_in_barrels_('Premium',_'Component_2') 1050.000 0.000 10.000 -Inf 10.000
(Premium, Component 3) Quantity_in_barrels_('Premium',_'Component_3') 750.000 0.000 6.000 6.000 10.800
(Extra, Component 1) Quantity_in_barrels_('Extra',_'Component_1') 1800.000 0.000 6.000 -Inf 17.333
(Extra, Component 2) Quantity_in_barrels_('Extra',_'Component_2') 1200.000 0.000 8.000 8.000 25.000
(Extra, Component 3) Quantity_in_barrels_('Extra',_'Component_3') 0.000 -0.000 4.000 -Inf 4.000

The following table shows the constraints:

Constraint Right Hand Side Shadow Price Slack Min RHS Max RHS
0 Component_1_requirement_in_super 0.00 -18.00 0.00 -850.00 0.00
1 Component_2_requirement_in_super 0.00 0.00 450.00 -450.00 Inf
2 Component_1_requirement_in_premium 0.00 -18.00 0.00 -450.00 0.00
3 Component_3_requirement_in_premium 0.00 0.00 0.00 -450.00 450.00
4 Component_1_requirement_in_Extra 0.00 -18.00 0.00 -450.00 0.00
5 Component_2_requirement_in_Extra 0.00 0.00 -900.00 -Inf 900.00
6 Component_1_availability 4500.00 20.00 0.00 4500.00 6200.00
7 Component_2_availability 2700.00 4.00 0.00 2250.00 3150.00
8 Component_3_availability 3500.00 0.00 1700.00 1800.00 Inf
9 Super_minimum_production 3000.00 0.00 -0.00 -Inf 3000.00
10 Premium_minimum_production 3000.00 -1.20 0.00 -0.00 3000.00
11 Extra_minimum_production 3000.00 -6.80 0.00 0.00 3000.00