49  Charge Capacity 3 constraints

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

# ETL
library(tidyverse)
library(lpSolve)

50 Defining the Problem

As 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 continue with the previous example, and are now going to illustrate another problem related to a production. This time we add one more constraint : a minimum stocks targets by SKU.

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

  • each SKU has its own :

    • production rate.

    • opening stock.

    • minimum and maximum stocks targets.

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

  • the final stock must be :

    • above the minimum stock target.

    • 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 :

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(50000, 50000, 50000, 30000, 35000, 45000)
min_stock <- c(6000, 6000, 6000, 10000, 6000, 20000)

opening_stock <- c(5000, 5000, 5000, 5000, 5000, 5000)

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

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

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

# Number of SKUs
num_skus <- length(sku)

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

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

50.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.

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

objective.function.coefficients
[1] 1 1 1 1 1 1

50.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 3 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.
  • Minimum Stock : for each SKU individually.

    • captured under different rows, one for each SKU.
# create constraint matrix
constraint.matrix <- rbind(
  rep(1, num_skus), # Total Production hours constraint
  diag(production_rate), # [Production Qty] + [opening_stock] <= max_stock constraint
  diag(production_rate) # [Production Qty] + [opening_stock] >= min_stock constraint
  
)

# 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
 [8,] 3200    0    0    0    0    0
 [9,]    0 4800    0    0    0    0
[10,]    0    0 3750    0    0    0
[11,]    0    0    0 4000    0    0
[12,]    0    0    0    0 3509    0
[13,]    0    0    0    0    0 4000

50.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),
                           rep(">=", num_skus)
                           )

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

50.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.

  • the quantity produced for each item, added to the opening stocks, cannot be lower than the minimum stock target defined for each SKU.

constraint.rhs <- c(35, 
                    max_stock - opening_stock,
                    min_stock - opening_stock
                    )

constraint.rhs
 [1]    35 45000 45000 45000 25000 30000 40000  1000  1000  1000  5000  1000
[13] 15000

51 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)

result
Success: the objective function is 35 

52 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.

52.1 Optimal Values of Decision Variables

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

result$solution
[1] 14.0625000  9.3750000  6.2775185  1.2500000  0.2849815  3.7500000

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> 14.0625000, 9.3750000, 6.2775185, 1.2500000, 0.2849815, 3.750…

52.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 minimum and maximum stocks targets.

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

result$objval
[1] 35

52.3 Status of the Solution

A value of 0 indicates an optimal solution

result$status
[1] 0

53 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 :

    • above the minimum stock target.

    • 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_max_constraint <- if_else(df1$max_stock >= df1$ending_stock, "ok", "not")


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




glimpse(df1)
Rows: 6
Columns: 10
$ sku                               <chr> "Syrup074", "Syrup011", "Syrup060", …
$ production_rate                   <dbl> 3200, 4800, 3750, 4000, 3509, 4000
$ max_stock                         <dbl> 50000, 50000, 50000, 30000, 35000, 4…
$ min_stock                         <dbl> 6000, 6000, 6000, 10000, 6000, 20000
$ opening_stock                     <dbl> 5000, 5000, 5000, 5000, 5000, 5000
$ nb_hours                          <dbl> 14.0625000, 9.3750000, 6.2775185, 1.…
$ production_qty                    <dbl> 45000.00, 45000.00, 23540.69, 5000.0…
$ ending_stock                      <dbl> 50000, 50000, 28540, 10000, 6000, 20…
$ check_ending_stock_max_constraint <chr> "ok", "ok", "ok", "ok", "ok", "ok"
$ check_ending_stock_min_constraint <chr> "ok", "ok", "ok", "ok", "ok", "ok"

Let’s check the total number of hours :

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