In this section here, we will learn about the concepts of Vehicle Routing Problem in Supply Chain and how to implement in R.
The dataset you just need 3 main columns: longitude, latitude and demand.
#Call packages:
pacman::p_load(sf,
rio,
here,
janitor,
tidyverse,
dplyr,
magrittr,
ggplot2,
purrr,
lubridate,
mice,
plotly)
optimize<-read.csv(r"(C:\\Users\\locca\\Documents\\Xuân Lộc\\VILAS\\Final project\\Optimize_df.csv)")
#New manufacter:
new_manufacter= data.frame(Lat =c(21.12256201),
Long = c(105.9150683))
route<-data.frame("Manufacter",
new_manufacter[,c("Long","Lat")],
0)
colnames(route)<-c("Customers",
"Longitude",
"Latitude",
"Total.transactions")
route<-rbind(route,
optimize%>% select(Customers,
Longitude,
Latitude,
Total.transactions))
colnames(route)[4]<-"demand"
route$node<-1:nrow(route)
There are functions to solve the VRP that I learned from Github manufactering by Argaadya author.
I tested all so you confidently copy and paste to your script. Quick explanation such as:
giant_path function is for calculate the least distance between multiple points based on the Hamiltonian Cycle algorithm.
assign_vehicle function is for separating the output from giant_path to groups segment appropriate to the conditions such as: the limited capacity of vehicle, the limited fuel,…
fitness is for computation base on the Genetic algorithms which is stochastic search algorithms inspired by the basic principles of biological evolution and natural selection.
# Giant_path function for the Hamiltonian Cycle:
giant_path <- function(distance, demand){
visited_spot <- NULL
vehicle_num <- 1
post <- 1:ncol(distance)
names(demand) <- 1:length(demand)
# Randomly select initial spot
initial_spot <- sample(2:length(demand), 1)
while (any(demand != 0)) {
available_spot <- which(demand != 0)
# Calculate the distance to unvisited spot
initial_dist <- distance[ c(available_spot), initial_spot]
initial_dist <- initial_dist[ which(names(initial_dist) != initial_spot)]
visited_spot <- c(visited_spot, initial_spot)
demand[ initial_spot ] <- 0
if ( length(initial_dist)>1) {
initial_spot <- which(initial_dist == min(initial_dist)) %>% names() %>% as.numeric()
} else {
initial_spot <- which(demand != 0)
}
}
visited_spot <- c(1, visited_spot, 1)
names(visited_spot) <- NULL
total_distance <- embed(visited_spot, 2)[ , 2:1] %>% distance[.] %>% sum()
result <- list(route = visited_spot,
total_distance = total_distance)
return(result)
}
# Assign Vehicle function to assign the route:
assign_vehicle <- function(x, demand, capacity, distance){
vehicle_load <- capacity
visited_spot <- NULL
vehicle_num <- 1
for (i in x) {
initial_spot <- i
if (vehicle_load >= demand[i]) {
visited_spot <- c(visited_spot, initial_spot)
vehicle_load <- vehicle_load - demand[ initial_spot ]
} else {
# Go back to depot
vehicle_num <- vehicle_num + 1
vehicle_load <- capacity
visited_spot <- c(visited_spot, 1)
# Revisit the spot
visited_spot <- c(visited_spot, initial_spot)
vehicle_load <- vehicle_load - demand[ initial_spot ]
}
}
visited_spot <- c(visited_spot)
total_distance <- embed(visited_spot, 2)[ , 2:1] %>% distance[.] %>% sum()
return(list(distance = total_distance,
route = visited_spot,
num_vehicle = vehicle_num))
}
#Fitness function for ga method:
fitness <- function(x, capacity, demand, distance, ...){
vehicle_load <- capacity
visited_spot <- 1
vehicle_num <- 1
for (i in x) {
initial_spot <- i
if (vehicle_load > demand[initial_spot]) {
# Go to the spot
visited_spot <- c(visited_spot, initial_spot)
vehicle_load <- vehicle_load - demand[ initial_spot ]
} else {
# Go back to depot
vehicle_load <- capacity
visited_spot <- c(visited_spot, 1)
vehicle_num <- vehicle_num + 1
# Go to the spot
visited_spot <- c(visited_spot, initial_spot)
vehicle_load <- vehicle_load - demand[ initial_spot ]
}
}
visited_spot <- c(visited_spot, 1)
total_distance <- embed(visited_spot, 2)[ , 2:1] %>% distance[.] %>% sum()
return(-total_distance)
}
fitness_explain <- function(x, capacity, demand, distance, ...){
vehicle_load <- capacity
visited_spot <- 1
vehicle_num <- 1
total_demand <- NULL
for (i in x) {
initial_spot <- i
if (vehicle_load > demand[initial_spot]) {
# Go to the spot
visited_spot <- c(visited_spot, initial_spot)
vehicle_load <- vehicle_load - demand[ initial_spot ]
} else {
total_demand <- c(total_demand, 6000 - vehicle_load)
# Go back to depot
vehicle_load <- capacity
visited_spot <- c(visited_spot, 1)
vehicle_num <- vehicle_num + 1
# Go to the spot
visited_spot <- c(visited_spot, initial_spot)
vehicle_load <- vehicle_load - demand[ initial_spot ]
}
}
total_demand <- c(total_demand, 6000 - vehicle_load)
visited_spot <- c(visited_spot, 1)
total_distance <- embed(visited_spot, 2)[ , 2:1] %>% distance[.] %>% sum()
result <- list(route = visited_spot,
total_distance = total_distance,
vehicle_num = vehicle_num,
total_demand = total_demand)
return(result)
}
Let say the limited capacity of vehicle in one run is 3000 transactions.
The output show that the output in list form:
$total_distance is 18.39416 meaning that if we don’t need to change our travel route and just go to all the points
$distance is 22.77717 higher that 18.39416 because we added the assumption that the limited capacity of vehicle is 3000 transactions.
$num_vehicle is 5 meaning 5 is suitable route number.
#Create distance matrix for route:
route_distance <- dist(route[ ,c("Longitude","Latitude")],
upper = T,
diag = T) %>%
as.matrix()
#
best_route <- list(total_distance = Inf)
for (i in 1:10^4) {
try_route <- giant_path(demand = route$demand,
distance = route_distance)
if (best_route$total_distance > try_route$total_distance) {
best_route <- try_route
}
}
best_route
## $route
## [1] 1 18 36 20 34 37 42 50 24 22 8 35 23 41 3 2 40 10 31 9 11 49 19 28 38
## [26] 25 4 45 33 21 5 47 17 15 46 29 32 13 27 16 7 6 44 14 26 43 12 48 39 51
## [51] 30 1
##
## $total_distance
## [1] 18.39416
nearest_result <- assign_vehicle(x = best_route$route,
demand = route$demand,
capacity = 3000,
distance = route_distance)
nearest_result
## $distance
## [1] 22.77717
##
## $route
## [1] 1 18 36 20 34 37 42 50 24 22 1 8 35 23 41 3 2 40 10 31 1 9 11 49 19
## [26] 28 38 25 4 45 33 21 5 1 47 17 15 46 29 32 13 27 16 1 7 6 44 14 26 43
## [51] 12 48 39 51 30 1
##
## $num_vehicle
## [1] 5
Now we will plot the output. I will present by 2 way:
ggplot package but I suggest if there are little numbers of points. You can see that the plot is not clear and coherent.
leaflet package is the best approach if you don’t have to show in a static version.
#ggplot:
library(ggrepel)
nearest_result$route %>%
as.data.frame() %>%
rename(from = ".") %>%
mutate(to = lead(from)) %>%
slice(- nrow(.)) %>%
left_join(route,
by = c("from" = "node")) %>%
rename(from_x1 = Longitude,
from_x2 = Latitude) %>%
left_join(route,
by = c("to" = "node")) %>%
rename(to_x1 = Longitude,
to_x2 = Latitude) %>%
rename(demand = demand.y) %>%
select(-demand.x) %>%
mutate(unit = c( rep("A", 10),
rep("B",10),
rep("C", 13),
rep("D", 10),
rep("E",12))) %>%
ggplot() +
geom_point(data = route,
aes(Longitude,
Latitude),
show.legend = F) +
geom_curve(aes(x = from_x1,
xend = to_x1,
y = from_x2,
yend = to_x2,
color = unit),
curvature = -0.05,
size = 0.5,
alpha = 0.7,
arrow = arrow(type = "closed",
angle = 30,
length = unit(3, "mm")) ) +
geom_point(data = route[1,],
aes(Longitude,
Latitude),
color = "red",
size = 3) +
geom_label_repel(data = route,
aes(Longitude,
Latitude,
label = node),
alpha = 0.7,
max.overlaps = Inf) +
scale_color_manual(values = c("firebrick",
"orange",
"dodgerblue",
"green3",
"purple"))+
theme_minimal() +
theme(panel.grid = element_blank()) +
theme(legend.position = "top") +
labs(x = quote(x[1]),
y = quote(x[2]),
title = "Vehicle Routing Problem with Nearest Neighbour",
subtitle = "Vehicle Capacity = 3000",
color = "Unit")
## Warning: Using `size` aesthetic for lines was deprecated in ggplot2 3.4.0.
## ℹ Please use `linewidth` instead.
Also, I use osrm package to calculate the closest value of distance in real life.
#leaflet:
df<-nearest_result$route %>%
as.data.frame() %>%
rename(from = ".") %>%
mutate(to = lead(from)) %>%
slice(- nrow(.)) %>%
left_join(route,
by = c("from" = "node")) %>%
mutate(unit = c( rep("A", 10),
rep("B",10),
rep("C", 13),
rep("D", 10),
rep("E", 12)))
# Calculate the shortest round trip between the facility locations
library(osrm)
## Warning: package 'osrm' was built under R version 4.2.3
## Data: (c) OpenStreetMap contributors, ODbL 1.0 - http://www.openstreetmap.org/copyright
## Routing: OSRM - http://project-osrm.org/
## Trip A:
dfA<-df %>% filter(unit == "A")
tripA <- osrmTrip(dfA[,c("Longitude","Latitude")],
overview = "full")
dfB<-df %>% filter(unit == "B")
tripB <- osrmTrip(dfB[,c("Longitude","Latitude")],
overview = "full")
dfC<-df %>% filter(unit == "C")
tripC <- osrmTrip(dfC[,c("Longitude","Latitude")],
overview = "full")
dfD<-df %>% filter(unit == "D")
tripD <- osrmTrip(dfD[,c("Longitude","Latitude")],
overview = "full")
dfE<-df %>% filter(unit == "E")
tripE <- osrmTrip(dfE[,c("Longitude","Latitude")],
overview = "full")
##Create matrix for summary:
summary = matrix(c(as.numeric(tripA[[1]]$summary),
as.numeric(tripB[[1]]$summary),
as.numeric(tripC[[1]]$summary),
as.numeric(tripD[[1]]$summary),
as.numeric(tripE[[1]]$summary)),
ncol = 2,
nrow = 5,
byrow = T)
colnames(summary) = c("Duration","Distance")
rownames(summary) = c("Trip A","Trip B","Trip C","Trip D","Trip E")
library(DT)
datatable(summary)
#Create labels:
labels<- paste0("<strong> Customers </strong> ",
optimize$Customers, "<br/> ",
"<strong> Longitude: </strong> ",
optimize$Longitude, "<br/> ",
"<strong> Latitude: </strong> ",
optimize$Latitude, "<br/> ",
"<strong> Total Transactions </strong> ",
optimize$Total.transactions, "<br/> ") %>%
lapply(htmltools::HTML)
#Plot map with leaflet:
library(leaflet)
leaflet(data = optimize) %>%
addTiles() %>%
addMarkers(lng = optimize$Longitude,
lat = optimize$Latitude,
label = ~labels) %>%
addPolylines(data = tripA[[1]]$trip,
label = "Trip A",
color = "firebrick") %>%
addPolylines(data = tripB[[1]]$trip,
label = "Trip B",
color = "orange") %>%
addPolylines(data = tripC[[1]]$trip,
label = "Trip C",
color = "dodgerblue") %>%
addPolylines(data = tripD[[1]]$trip,
label = "Trip D",
color = "red") %>%
addPolylines(data = tripE[[1]]$trip,
label = "Trip E",
color = "purple")
R will take a little time to run so you just be patient to wait.
The output show that the final distance is 20.0774 comparing to the above output is better.(20.0774 < 22.77717)
suggestion_route <- matrix(ncol = (max(route$node)-1), nrow = 100)
for (i in 1:100) {
try_route <- giant_path(demand = route$demand, distance = route_distance)
suggestion_route[i, ] <- try_route$route[2:max(route$node)] %>% as.numeric()
}
head(suggestion_route)
## [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8] [,9] [,10] [,11] [,12] [,13] [,14]
## [1,] 23 8 35 24 50 37 34 42 22 20 18 36 29 46
## [2,] 14 26 17 2 40 3 41 35 8 23 24 50 37 34
## [3,] 38 25 28 19 49 11 31 9 10 40 2 3 41 35
## [4,] 28 38 25 49 19 4 45 33 21 5 11 31 9 10
## [5,] 18 36 20 34 37 42 50 24 22 8 35 23 41 3
## [6,] 27 13 32 46 29 16 36 18 35 8 23 24 50 37
## [,15] [,16] [,17] [,18] [,19] [,20] [,21] [,22] [,23] [,24] [,25] [,26]
## [1,] 32 13 27 16 41 3 2 40 10 31 9 11
## [2,] 42 22 20 18 36 29 46 32 13 27 16 15
## [3,] 8 23 24 50 37 34 42 22 20 18 36 29
## [4,] 40 2 3 41 35 8 23 24 50 37 34 42
## [5,] 2 40 10 31 9 11 49 19 28 38 25 4
## [6,] 34 42 22 20 41 3 2 40 10 31 9 11
## [,27] [,28] [,29] [,30] [,31] [,32] [,33] [,34] [,35] [,36] [,37] [,38]
## [1,] 49 19 28 38 25 4 45 33 21 5 47 17
## [2,] 44 6 7 51 39 48 12 43 10 31 9 11
## [3,] 46 32 13 27 16 15 17 44 14 26 43 6
## [4,] 22 20 18 36 29 46 32 13 27 16 15 17
## [5,] 45 33 21 5 47 17 15 46 29 32 13 27
## [6,] 49 19 28 38 25 4 45 33 21 5 47 17
## [,39] [,40] [,41] [,42] [,43] [,44] [,45] [,46] [,47] [,48] [,49] [,50]
## [1,] 15 44 14 26 43 6 7 51 39 48 12 30
## [2,] 49 19 28 38 25 4 45 33 21 5 47 30
## [3,] 7 51 39 48 12 4 45 33 21 5 47 30
## [4,] 44 14 26 43 6 7 51 39 48 12 30 47
## [5,] 16 7 6 44 14 26 43 12 48 39 51 30
## [6,] 15 44 14 26 43 6 7 51 39 48 12 30
library(GA)
## Warning: package 'GA' was built under R version 4.2.3
## Loading required package: foreach
##
## Attaching package: 'foreach'
## The following objects are masked from 'package:purrr':
##
## accumulate, when
## Loading required package: iterators
## Package 'GA' version 3.2.3
## Type 'citation("GA")' for citing this R package in publications.
##
## Attaching package: 'GA'
## The following object is masked from 'package:utils':
##
## de
ga_route <- ga(type = "permutation",
fitness = fitness,
capacity = 3000,
demand = route$demand,
distance = route_distance,
lower = 2,
upper = max(route$node),
mutation = gaperm_swMutation,
popSize = 100,
pmutation = 0.1,
maxiter = 5 * 10^4,
suggestions = suggestion_route,
monitor = F,
seed = 123)
summary(ga_route)
## ── Genetic Algorithm ───────────────────
##
## GA settings:
## Type = permutation
## Population size = 100
## Number of generations = 50000
## Elitism = 5
## Crossover probability = 0.8
## Mutation probability = 0.1
## Suggestions =
## x1 x2 x3 x4 x5 x6 x7 x8 x9 x10 ... x49 x50
## 1 23 8 35 24 50 37 34 42 22 20 12 30
## 2 14 26 17 2 40 3 41 35 8 23 47 30
## 3 38 25 28 19 49 11 31 9 10 40 47 30
## 4 28 38 25 49 19 4 45 33 21 5 30 47
## 5 18 36 20 34 37 42 50 24 22 8 51 30
## 6 27 13 32 46 29 16 36 18 35 8 12 30
## 7 31 9 10 40 2 3 41 35 8 23 47 30
## 8 38 25 28 19 49 11 31 9 10 40 47 30
## 9 3 41 35 8 23 24 50 37 34 42 12 30
## 10 8 35 23 24 50 37 34 42 22 20 12 30
## ...
## 99 41 3 2 40 10 31 9 11 49 19 51 30
## 100 42 37 34 22 20 50 24 23 8 35 12 30
##
## GA results:
## Iterations = 50000
## Fitness function value = -20.55624
## Solution =
## x1 x2 x3 x4 x5 x6 x7 x8 x9 x10 ... x49 x50
## [1,] 8 23 24 50 42 37 34 20 22 3 36 18
plot(ga_route)