48  Simple Charge Capacity problem

As usual, we start by uploading the libraries that we’re going to use.

# ETL
library(tidyverse)
library(lpSolve)

49 Defining the Problem

We saw previously that to solve a linear programming problem, we need to define 3 elements :

  • the objective function.

  • constraints and their directions.

  • the right-hand side values.

We are now going to illustrate a problem related to a production.

  • we have 6 SKUs (syrups) that we manufacture.

  • each SKU has its own :

    • production rate.

    • opening stock.

    • maximum stock target.

We want to find the optimized production hours (and related quantities) for each item, considering 2 constraints :

  • the final stock must be below the maximum stock target.

  • the total number of production hours must be equal to 35 hours.

Here is a data frame initial_data containing those inputs :

# create dimensions
sku <- c("Syrup074", "Syrup011", "Syrup060", "Syrup057", "Syrup051", "Syrup056")
production_rate <- c(3200, 4800, 3750, 4000, 3509, 4000)

# Adjusted stock limits for feasibility
max_stock <- c(20000, 15000, 25000, 30000, 35000, 45000)
opening_stock <- c(5000, 5000, 5000, 5000, 5000, 5000)

# create dataframe
initial_data <- data.frame(sku,
                           production_rate,
                           max_stock,
                           opening_stock)

# display
initial_data
       sku production_rate max_stock opening_stock
1 Syrup074            3200     20000          5000
2 Syrup011            4800     15000          5000
3 Syrup060            3750     25000          5000
4 Syrup057            4000     30000          5000
5 Syrup051            3509     35000          5000
6 Syrup056            4000     45000          5000

Let’s create a variable num_skus which represents the number of SKUs.

# Number of SKUs
num_skus <- length(sku)

# display
num_skus
[1] 6

Now we can define the 3 elements to express the problem we want to solve :

  • objective function | constraint matrix | constraint direction and RHS.

49.1 Objective Function

It represents the coefficients of our objective function as a numeric vector.

We set those coefficients to 1, because the sum of each one of them will be equal to our target of 35 hours.

# create objective function
objective.function.coefficients <- rep(1, num_skus) # Maximize Production hours

# display
objective.function.coefficients
[1] 1 1 1 1 1 1

49.2 Constraint Matrix

We create a matrix representing the coefficients of our decision variables in the constraints. Each row corresponds to a constraint.

In our case here we have 2 constraints :

  • Total Production hours constraint : related to the sum of the production hours of each SKU.

    • captured under a single row.
  • Maximum Stock : for each SKU individually.

    • captured under different rows, one for each SKU.

Note :

  • To define constraints that require a variable to be both greater than or equal to one value and less than or equal to another, we need to set up two separate constraints for each condition.

  • In linear programming, each condition is represented by a separate row in the constraint matrix, and the corresponding direction is specified in the constraint.directions vector.

# create constraint matrix
constraint.matrix <- rbind(
  rep(1, num_skus),
  diag(production_rate)
  
)

# display constraint matrix
constraint.matrix
     [,1] [,2] [,3] [,4] [,5] [,6]
[1,]    1    1    1    1    1    1
[2,] 3200    0    0    0    0    0
[3,]    0 4800    0    0    0    0
[4,]    0    0 3750    0    0    0
[5,]    0    0    0 4000    0    0
[6,]    0    0    0    0 3509    0
[7,]    0    0    0    0    0 4000

49.3 Constraint Directions

A character vector indicating the direction of each constraint (e.g., “<=”, “>=”, “=”).

Those constraints are related to each row of the constraint matrix.

# create constraint
constraint.directions <- c("<=", 
                           rep("<=", num_skus)
                           )

# display
constraint.directions
[1] "<=" "<=" "<=" "<=" "<=" "<=" "<="

49.4 Right-Hand Side (RHS) Values

A numeric vector containing the right-hand side values of the constraints.

Here :

  • the sum of each production hours must be equal to 35 hours.

  • the quantity produced for each item, added to the opening stocks, cannot exceed the maximum stock target defined for each SKU.

# create right-hand side
constraint.rhs <- c(35, max_stock - opening_stock)

# display
constraint.rhs
[1]    35 15000 10000 20000 25000 30000 40000

50 Solving the Problem

Now, we use the lp() function to solve the linear programming problem.

We need to specify whether we are minimizing or maximizing.

# For maximization
result <- lp("max",
             objective.function.coefficients,
             constraint.matrix,
             constraint.directions,
             constraint.rhs)

# For minimization
# result <- lp("min",
#              objective.function.coefficients,
#              constraint.matrix,
#              constraint.directions,
#              constraint.rhs)

# display
result
Success: the objective function is 35 

We already can notice that the sum of the production hours reached our objective of 35 hours, which is good. Now let’s look at the results more in details.

51 Interpreting the Results

The result object contains various information about the solution, especially 3 :

  • Optimal Values of Decision Variables.
  • Optimal objective value (based on the optimal values of the Decision Variables).
  • Status of the solution.

Let’s look at those 3.

51.1 Optimal Values of Decision Variables

It’s the optimum number of production hours for each SKU.

result$solution
[1] 4.687500 2.083333 5.333333 6.250000 8.549444 8.096389

We also can notice that the sum of those production hours is equal to 35 hours.

It was one of the constraints of the variable constraint.rhs .

sum(result$solution)
[1] 35

Get Results as data frame

Let’s get those results, the calculated optimum decisions variables, creating a data frame.

# format as data frame
df1 <- as.data.frame(result$solution)

# rename
df1 <- df1 |> rename(nb_hours = `result$solution`)

# keep results
result_data <- df1

glimpse(df1)
Rows: 6
Columns: 1
$ nb_hours <dbl> 4.687500, 2.083333, 5.333333, 6.250000, 8.549444, 8.096389

51.2 Optimal Objective Value

It indicates the final score (maximum or minimum) of the solution that we found.

In our example, we wanted to maximize the function : 1*x1 + 1*x2 + 1*x3 + 1*x4 + 1*x5 + 1*x6

  • bringing it as <= 35 hours.

  • respecting the maximum stocks targets.

  • x1 ,…, x6 being the optimum values of Decision Variables.

result$objval
[1] 35

51.3 Status of the Solution

A value of 0 indicates an optimal solution.

result$status
[1] 0

52 Get & Check Results

Now, let’s check whether the decisions variables that we obtained are well aligned with our constraints.

First, we calculate the production quantity and ending stocks of each SKU, using the optimum decisions variables.

Then we check :

  • whether the ending stock is below the maximum stock target.

  • if the total of the production hours of each SKU is inferior or equal to 35 hours.

#---------------
# Calculate using the optimum decisions variables
#---------------

# combine
df1 <- cbind(initial_data, result_data)


# calculate production_qty
df1$production_qty <- df1$production_rate * df1$nb_hours

# calculate ending inventories
df1$ending_stock <- df1$opening_stock + df1$production_qty

# formatting
df1$ending_stock <- floor(df1$ending_stock) 

#---------------
# Check constraints
#---------------

# constraint [ending_stock] <= [max_stock] 
df1$check_ending_stock_constraint <- if_else(df1$max_stock >= df1$ending_stock, "ok", "not")

# display
df1
       sku production_rate max_stock opening_stock nb_hours production_qty
1 Syrup074            3200     20000          5000 4.687500       15000.00
2 Syrup011            4800     15000          5000 2.083333       10000.00
3 Syrup060            3750     25000          5000 5.333333       20000.00
4 Syrup057            4000     30000          5000 6.250000       25000.00
5 Syrup051            3509     35000          5000 8.549444       30000.00
6 Syrup056            4000     45000          5000 8.096389       32385.56
  ending_stock check_ending_stock_constraint
1        20000                            ok
2        15000                            ok
3        25000                            ok
4        30000                            ok
5        35000                            ok
6        37385                            ok

We can validate that both constraints are fully respected, and that total number of hours is maximized, reaching 35 hours.

# check total [nb_hours]
sum(df1$nb_hours)
[1] 35