Resourcing: Solar panel cells¶
Try me¶
Problem Definition¶
Weyland Corp is a British firm that manufactures solar panel cells. This firm supplies three differnt types of cell called: fast, normal and ultra. There are three operations involved in the manufacturing process. The following table describes the hours/month required to manufacture each model:
Table 1: hours/month requirements
Operation |
Fast (hours/month) |
Normal (hours/month) |
Ultra (hours/month) |
|---|---|---|---|
1 |
1 |
3 |
2 |
2 |
2 |
0 |
3 |
3 |
1 |
4 |
0 |
And the following table describes the total hours available for each operation:
Table 2: Total hours/month available
Operation |
Hours/Month |
|---|---|
1 |
400 |
2 |
600 |
3 |
600 |
The profit per each model is specified in the following table: Table 3: Unit of profit per model
Model |
Profit/Unit |
|---|---|
Normal |
30 |
Economic |
20 |
Luxury |
40 |
Problem Model¶
Decision variables¶
The decision variables are:
\(x_1:\)Units of Fast cells / month
\(x_2:\)Units of Normal cells / month
\(x_3:\)Units of Ultra cells / month
Objective Function¶
The objective function is:
\(\max z=30x_1+20x_2+40x_3\)
Constraints¶
Subject to the following constraints:
\(x_1+3x_2+2x_3 \leq 400\)
\(2x_1+0x_2+3x_3 \leq 600\)
\(x_1+4x_2+0x_3 \leq 600\)
Problem Solution with Pulp¶
Let us solve the problem with pulp. We start by importing PULP and instantiating the problem:
[1]:
!pip install pandas
!pip install numpy
!pip install pulp
Requirement already satisfied: pandas in c:\users\franc\anaconda3\lib\site-packages (1.3.5)
Requirement already satisfied: pytz>=2017.3 in c:\users\franc\anaconda3\lib\site-packages (from pandas) (2019.3)
Requirement already satisfied: numpy>=1.17.3; platform_machine != "aarch64" and platform_machine != "arm64" and python_version < "3.10" in c:\users\franc\anaconda3\lib\site-packages (from pandas) (1.21.5)
Requirement already satisfied: python-dateutil>=2.7.3 in c:\users\franc\anaconda3\lib\site-packages (from pandas) (2.8.0)
Requirement already satisfied: six>=1.5 in c:\users\franc\anaconda3\lib\site-packages (from python-dateutil>=2.7.3->pandas) (1.12.0)
Requirement already satisfied: numpy in c:\users\franc\anaconda3\lib\site-packages (1.21.5)
Requirement already satisfied: pulp in c:\users\franc\anaconda3\lib\site-packages (1.6.8)
Requirement already satisfied: pyparsing>=2.0.1 in c:\users\franc\anaconda3\lib\site-packages (from pulp) (2.4.2)
[2]:
import pandas as pd
import numpy as np
from IPython.display import display, Markdown
import pulp
[3]:
# Instantiate our problem class
model = pulp.LpProblem("Maximising profits for Weyland Corp", pulp.LpMaximize)
In this problem, we have three decision variables. We could name them individually like in the first example, but this does not scale (imagine we had hundreds). Instead we will use lists and tuple lists which can be really useful to minimise the problem configuration.
First we create the lists:
[4]:
# Construct our decision variable lists
cell_types = ['fast', 'normal', 'ultra']
The decision variables have the same characteristics (lower bound of 0, continuous variables). We can use the LpVariable object’s dict functionality to create the variables from the tuples. The good thing about tuples is that we could add more dimensions (eg cell categories) and still create all decision variables in one line of code.
[5]:
cell_units = pulp.LpVariable.dicts("cells",
(i for i in cell_types),
lowBound=0,
cat='Continuous')
PuLP provides an lpSum vector calculation for the sum of a list of linear expressions. Although we only have 6 decision variables, we will use lpSum to construct the expression using this feature, again for the sake of scalibility.
[6]:
# Technological Coefficients:
unit_profits = [30, 20, 40]
# Objective Function
model += (
pulp.lpSum([
unit_profits[i] * cell_units[cell_types[i]]
for i in range(len(cell_types))])
)
Now we add the constraints using the tuplets to identify the coefficients
[7]:
# Operations
operations = ["Operation 1", "Operation 2", "Operation 3"]
# Constraints
A=[[1, 3, 2], #Coefficients of the first constraint
[2, 0, 3], #Coefficients of the second constraint
[1, 4, 0]] #Coefficients of the third constraint
# Capacities
b = [400, 600, 600]
#Iterate matrix raws
for i in range(len(A)):
model += pulp.lpSum([
A[i][j] * cell_units[cell_types[j]]
for j in range(len(cell_types))]) <= b[i] , operations[i]
#note that in this case all constraints are of type less or equal
[8]:
# Solve our problem
model.solve()
pulp.LpStatus[model.status]
[8]:
'Optimal'
[9]:
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(cell_units, orient="index",
columns = ["Variables"])
# First we add the solution. We apply a lambda function to get only two decimals:
var_df["Solution"] = var_df["Variables"].apply(lambda item: item.varValue)
# We do the same for the reduced cost:
var_df["Reduced cost"] = var_df["Variables"].apply(lambda item: item.dj)
display(var_df)
const_dict = dict(model.constraints)
con_df = pd.DataFrame.from_records(list(const_dict.items()), exclude=["Expression"], columns=["Constraint", "Expression"])
#Now we add columns for the solution, the slack and shadow price
con_df["Right Hand Side"] = con_df["Constraint"].apply(lambda item: -const_dict[item].constant)
con_df["Slack"] = con_df["Constraint"].apply(lambda item: const_dict[item].slack)
con_df["Shadow Price"] = con_df["Constraint"].apply(lambda item: const_dict[item].pi)
display(Markdown("The following table shows the constraints: "))
display(con_df)
Total profit is 9666.67 €
The following table shows the decision variables:
| Variables | Solution | Reduced cost | |
|---|---|---|---|
| fast | cells_fast | 300.000000 | -0.000000 |
| normal | cells_normal | 33.333333 | -0.000000 |
| ultra | cells_ultra | 0.000000 | -8.333333 |
The following table shows the constraints:
| Constraint | Right Hand Side | Slack | Shadow Price | |
|---|---|---|---|---|
| 0 | Operation_1 | 400 | -0.00000 | 6.666667 |
| 1 | Operation_2 | 600 | -0.00000 | 11.666667 |
| 2 | Operation_3 | 600 | 166.66667 | -0.000000 |

