Resourcing: Solar panel cells

Try me

Open In ColabBinder

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