# Función para calcular la distancia euclidiana entre dos puntos
<- function(c1, c2) {
euc_2d return(round(sqrt((c1[1] - c2[1])^2 + (c1[2] - c2[2])^2)))
}
# Función para calcular el costo de una permutación de ciudades
<- function(permutation, cities) {
cost <- 0
distance for (i in seq_along(permutation)) {
<- permutation[i]
c1 <- if (i == length(permutation)) permutation[1] else permutation[i + 1]
c2 <- distance + euc_2d(cities[c1, ], cities[c2, ])
distance
}return(distance)
}
# Función para generar una permutación aleatoria de las ciudades
<- function(cities) {
random_permutation return(sample(nrow(cities)))
}
# Función para realizar una operación de dos-opt estocástica en una permutación
<- function(permutation) {
stochastic_two_opt <- permutation
perm <- sample(length(perm), 1)
c1 <- c(c1, if (c1 == 1) length(perm) else c1 - 1, if (c1 == length(perm)) 1 else c1 + 1)
exclude <- sample(length(perm), 1)
c2 while (c2 %in% exclude) {
<- sample(length(perm), 1)
c2
}if (c2 < c1) {
<- c2
c1 <- c1
c2
}:c2] <- rev(perm[c1:c2])
perm[c1return(perm)
}
# Función para realizar una búsqueda local en el espacio de las permutaciones
<- function(best, cities, max_no_improv) {
local_search <- 0
count repeat {
<- list()
candidate $vector <- stochastic_two_opt(best$vector)
candidate$cost <- cost(candidate$vector, cities)
candidateif (candidate$cost < best$cost) {
<- 0
count <- candidate
best else {
} <- count + 1
count
}if (count >= max_no_improv) break
}return(best)
}
# Función para realizar un movimiento de doble puente en una permutación
<- function(perm) {
double_bridge_move <- 1 + sample(floor(length(perm) / 4), 1)
pos1 <- pos1 + 1 + sample(floor(length(perm) / 4), 1)
pos2 <- pos2 + 1 + sample(floor(length(perm) / 4), 1)
pos3 return(c(perm[1:pos1], perm[(pos3 + 1):length(perm)], perm[(pos2 + 1):pos3], perm[(pos1 + 1):pos2]))
}
# Función para perturbar la mejor solución encontrada hasta ahora
<- function(cities, best) {
perturbation <- list()
candidate $vector <- double_bridge_move(best$vector)
candidate$cost <- cost(candidate$vector, cities)
candidatereturn(candidate)
}
# Función de búsqueda principal
<- function(cities, max_iterations, max_no_improv) {
search <- list()
best $vector <- random_permutation(cities)
best$cost <- cost(best$vector, cities)
best<- local_search(best, cities, max_no_improv)
best # Creamos un vector para almacenar el costo del mejor vector en cada iteración
<- numeric(max_iterations)
best_costs for (iter in seq_len(max_iterations)) {
<- perturbation(cities, best)
candidate <- local_search(candidate, cities, max_no_improv)
candidate if (candidate$cost < best$cost) {
<- candidate
best
}# Almacenamos el costo del mejor vector en la iteración actual
<- best$cost
best_costs[iter] #print(paste(" > iteration", iter, ", best=", best$cost))
}return(list(best = best, best_costs = best_costs))
}
# Configuración del problema
<- matrix(c(565,575,25,185,345,750,945,685,845,655,
berlin52 880,660,25,230,525,1000,580,1175,650,1130,
1605,620,1220,580,1465,200,1530,5,845,680,
725,370,145,665,415,635,510,875,560,365,300,
465,520,585,480,415,835,625,975,580,1215,245,
1320,315,1250,400,660,180,410,250,420,555,575,
665,1150,1160,700,580,685,595,685,610,770,610,
795,645,720,635,760,650,475,960,95,260,875,920,
700,500,555,815,830,485,1170,65,830,610,605,625,
595,360,1340,725,1740,245), ncol = 2, byrow = TRUE)
# Configuración del algoritmo
<- 100
max_iterations <- 50
max_no_improv
# Ejecutar el algoritmo
<- search(berlin52, max_iterations, max_no_improv) result
Taxonomía
Iterated Local Search
es una Metaheurística
y una técnica de Optimización Global
. Es una extensión de Multi-Restar Search
y puede considerarse la base de muchos enfoques de búsqueda en dos fases, como el procedimiento de Greedy Randomized Adaptive Search Procedure
y Variable Neighborhood Search
.
Estrategia
El objetivo de Iterated Local Search
es mejorar la Multi-Restar Search
mediante el muestreo en la vecindad más amplia de soluciones candidatas y el uso de una técnica de Local Search
para refinar las soluciones a sus óptimos locales. Iterated Local Search
explora una secuencia de soluciones creadas como perturbaciones de la mejor solución actual, cuyo resultado se refina mediante una heurística integrada.
Procedimiento
Heurística
Iterated Local Search
se diseñó para, y se ha aplicado, predominantemente a dominios discretos, como los problemas de optimización combinatoria.
La perturbación de la mejor solución actual debe estar en un vecindario más allá del alcance de la heurística incorporada y no debe deshacerse fácilmente.
Las perturbaciones demasiado pequeñas hacen que el algoritmo sea demasiado codicioso, mientras que las perturbaciones demasiado grandes hacen que el algoritmo sea demasiado estocástico.
La heurística incrustada suele ser una técnica de búsqueda local específica del problema.
El punto de partida de la búsqueda puede ser una solución candidata construida aleatoriamente o mediante una heurística específica del problema (como el vecino más próximo).
Las perturbaciones pueden hacerse de forma determinista, aunque las más comunes son las estocásticas y probabilísticas (adaptativas basadas en el historial).
El procedimiento puede almacenar tanto o tan poco historial como sea necesario para utilizarlo durante la perturbación y los criterios de aceptación. La ausencia de historial representa un paseo aleatorio en un vecindario más amplio de la mejor solución y es la aplicación más común del enfoque.
El criterio de aceptación más simple y común es una mejora en el costo de las soluciones candidatas construidas.
Código
El algoritmo se aplica a la instancia Berlin52 del Traveling Saleman Problem
(TSP), tomada de la TSPLIB
. El problema busca una permutación del orden de visita de las ciudades (llamada recorrido) que minimice la distancia total recorrida. La distancia óptima del recorrido para el caso Berlín52 es de 7.542 unidades.
Iterated Local Search
se ejecuta durante un número fijo de iteraciones. La implementación se basa en un algoritmo común configuración para el TSP, donde un 'double-bridge move' (4-opt)
se utiliza como la técnica de perturbación, y un 2-opt estocástico
se utiliza como la heurística de búsqueda local incrustada. El doube-bridge move
consiste en dividir una permutación en 4 partes (a,b,c,d) y volver a unirlas en un orden específico y desordenado (a,d,c,b).
Revisamemos el comportamiento del algoritmo para encontrar la solución óptima:
# Crear un gráfico del progreso de la función objetivo
library(ggplot2)
<- data.frame(iteration = 1:max_iterations, cost = result$best_costs)
df
# Crear modificaciones al plot
<- function() {
crear_tema theme_minimal() +
theme(
plot.background = element_rect(fill = "white", color = NA),
panel.grid.major = element_line(color = "white", size = 0.2),
panel.grid.minor = element_line(color = "white", size = 0.2),
panel.background = element_rect(fill = "white", color = NA),
plot.title = element_text(face = "bold", size = 14, color = "#4d6080"),
axis.title = element_text(face = "bold", size = 12, color = "#4d6080"),
axis.text = element_text(size = 10, color = "#4d6080"),
axis.line = element_line(size = 1.5, colour = "#de6f41"),
legend.background = element_rect(fill = "#4d6080", color = NA),
legend.key = element_rect(fill = "grey90", color = NA),
axis.ticks.x = element_line(color = "#de6f41", size = 1),
axis.ticks.y = element_line(color = "#de6f41", size = 1)
)
}
ggplot(df, aes(x = iteration, y = cost)) +
geom_line() +
labs(title = "Progreso del costo a lo largo de las iteraciones", x = "Iteración", y = "Costo")+
crear_tema()
La solución óptima (con las iteraciones establecidas) es entonces:
$best$vector result
[1] 48 47 26 13 52 14 27 28 12 11 51 43 33 9 10 41 8 45 25 46 5 24 6 4 15
[26] 19 3 31 23 21 7 2 17 42 30 20 50 29 16 44 37 38 49 35 34 40 39 36 32 18
[51] 22 1
Cómo citar
@online{chiquito_valencia2023,
author = {Chiquito Valencia, Cristian},
title = {Iterated {Local} Serach},
date = {2023-11-18},
url = {https://cchiquitovalencia.github.io/posts/2023-11-18-iterated_local_search/},
langid = {en}
}