Compare commits

...

11 Commits

9 changed files with 98 additions and 58 deletions

View File

@ -116,7 +116,7 @@ Los parámetros posibles son:
| Cualquier archivo de la carpeta data | greedy |
| | local |
También se proporciona un script que ejecuta 3 iteraciones de ambos algoritmos, con cada uno de los /datasets/, y guarda los resultados en una hoja de cálculo. Se puede ejecutar mediante el siguiente comando:
También se proporciona un script que ejecuta 1 iteración del algoritmo greedy y 3 iteraciones de la búsqueda local, con cada uno de los /datasets/, y guarda los resultados en una hoja de cálculo. Se puede ejecutar mediante el siguiente comando:
#+begin_src shell
python src/execution.py
@ -133,23 +133,23 @@ Los resultados obtenidos se encuentran en el archivo /algorithm-results.xlsx/, p
#+CAPTION: Algoritmo greedy
[[./assets/greedy.png]]
El algoritmo greedy es determinista, por lo tanto la desviación típica es prácticamente nula (varía en el tiempo de ejecución únicamente). El tiempo de ejecución varía considerablemente según el dataset:
El algoritmo greedy es determinista, por lo tanto la desviación típica es nula, dado que se ejecuta una única vez. El tiempo de ejecución varía considerablemente según el dataset:
- Dataset con n=500: 8-10 segundos
- Dataset con n=2000: 6-7 minutos
- Dataset con n=500: 7-10 segundos
- Dataset con n=2000: 5-12 minutos
Por lo tanto, el algoritmo o la implementación de éste no escalan al aumentar el número de casos.
Las distancias obtenidas son considerablemente peores que las del algoritmo de búsqueda local, aunque hay que tener en cuenta que en la implementación del algoritmo de búsqueda local la distancia del primer elemento no es 0, lo cual tiene afecta el resultado final.
La distancia total obtenida, por lo general, es inferior al algoritmo de búsqueda local, aunque no difiere significativamente.
*** Algoritmo de búsqueda local
#+CAPTION: Algoritmo de búsqueda local
[[./assets/local.png]]
El algoritmo de búsqueda local es estocástico, debido a que para la obtención de cada una de las soluciones se utiliza un generador de números pseudoaleatorio. El tiempo de ejecución es prácticamente constante con cada dataset:
El algoritmo de búsqueda local es estocástico, debido a que para la obtención de cada una de las soluciones se utiliza un generador de números pseudoaleatorio. El tiempo de ejecución varía considerablemente según el dataset:
- Dataset con n=500: 1-3 segundos
- Dataset con n=2000: 8-12 segundos
- Dataset con n=500: 1-2 minutos
- Dataset con n=2000: 20-25 minutos
Por lo tanto éste escala bien al aumentar el número de casos. Aún así, al realizar ciertas pruebas aumentando el número de iteraciones máximas, el rendimiento del algoritmo emperoaba considerablemente. Por este motivo, las ejecuciones de este algoritmo se han hecho con 100 iteraciones máximas.
La distancia total obtenida, por lo general, es superior al algoritmo greedy lo cual indica que la búsqueda local obtiene mejores resultados a expensas del tiempo de ejecución.
Debido a nuestras limitaciones computacionales, las ejecuciones de este algoritmo se hicieron con 100 iteraciones máximas.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 KiB

After

Width:  |  Height:  |  Size: 141 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 134 KiB

After

Width:  |  Height:  |  Size: 155 KiB

View File

@ -59,19 +59,17 @@ def script_execution(filenames, greedy, local, iterations=3):
script = "src/main.py"
for dataset in filenames:
print(f"Running on dataset {dataset}")
greedy_list = []
greedy_cmd = run(
[executable, script, dataset, "greedy"], capture_output=True
).stdout.splitlines()
local_list = []
for _ in range(iterations):
greedy_cmd = run(
[executable, script, dataset, "greedy"], capture_output=True
).stdout.splitlines()
local_cmd = run(
[executable, script, dataset, "local"], capture_output=True
).stdout.splitlines()
greedy_list.append(greedy_cmd)
local_list.append(local_cmd)
greedy, local = populate_dataframes(
greedy, local, greedy_list, local_list, dataset
greedy, local, [greedy_cmd], local_list, dataset
)
return greedy, local

View File

@ -29,15 +29,18 @@ def get_closest_element(element, data):
return Series(data={"point": closest_point, "distance": closest_row["distance"]})
def explore_solutions(solutions, data):
def explore_solutions(solutions, data, index):
closest_elements = solutions["point"].apply(func=get_closest_element, data=data)
furthest_index = closest_elements["distance"].astype(float).idxmax()
return closest_elements.iloc[furthest_index]
solution = closest_elements.iloc[furthest_index]
solution.name = index
return solution
def remove_duplicates(current, previous, data):
duplicate_free_df = data.query(
f"(source != {current} or destination not in @previous) and (source not in @previous or destination != {current})"
"(source != @current or destination not in @previous) and \
(source not in @previous or destination != @current)"
)
return duplicate_free_df
@ -46,8 +49,8 @@ def greedy_algorithm(n, m, data):
solutions = DataFrame(columns=["point", "distance"])
first_solution = get_first_solution(n, data)
solutions = solutions.append(first_solution, ignore_index=True)
for _ in range(m):
element = explore_solutions(solutions, data)
for iteration in range(m - 1):
element = explore_solutions(solutions, data, index=iteration + 1)
solutions = solutions.append(element)
data = remove_duplicates(
current=element["point"], previous=solutions["point"], data=data

View File

@ -1,50 +1,75 @@
from numpy.random import choice, seed
from numpy.random import choice, seed, randint
from pandas import DataFrame
def get_first_random_solution(m, data):
seed(42)
random_indexes = choice(len(data.index), size=m, replace=False)
return data.loc[random_indexes]
def element_in_dataframe(solution, element):
duplicates = solution.query(
f"(source == {element.source} and destination == {element.destination}) or (source == {element.destination} and destination == {element.source})"
def get_row_distance(source, destination, data):
row = data.query(
"""(source == @source and destination == @destination) or \
(source == @destination and destination == @source)"""
)
return not duplicates.empty
return row["distance"].values[0]
def replace_worst_element(previous, data):
solution = previous.copy()
worst_index = solution["distance"].astype(float).idxmin()
random_element = data.sample().squeeze()
while element_in_dataframe(solution=solution, element=random_element):
random_element = data.sample().squeeze()
solution.loc[worst_index] = random_element
return solution, worst_index
def compute_distance(element, solution, data):
accumulator = 0
distinct_elements = solution.query(f"point != {element}")
for _, item in distinct_elements.iterrows():
accumulator += get_row_distance(
source=element,
destination=item.point,
data=data,
)
return accumulator
def get_random_solution(previous, data):
solution, worst_index = replace_worst_element(previous, data)
previous_worst_distance = previous["distance"].loc[worst_index]
while solution.distance.loc[worst_index] <= previous_worst_distance:
solution, _ = replace_worst_element(previous=solution, data=data)
def get_first_random_solution(n, m, data):
solution = DataFrame(columns=["point", "distance"])
seed(42)
solution["point"] = choice(n, size=m, replace=False)
solution["distance"] = solution["point"].apply(
func=compute_distance, solution=solution, data=data
)
return solution
def explore_neighbourhood(element, data, max_iterations=100000):
def element_in_dataframe(solution, element):
duplicates = solution.query(f"point == {element}")
return not duplicates.empty
def replace_worst_element(previous, n, data):
solution = previous.copy()
worst_index = solution["distance"].astype(float).idxmin()
random_element = randint(n)
while element_in_dataframe(solution=solution, element=random_element):
random_element = randint(n)
solution["point"].loc[worst_index] = random_element
solution["distance"].loc[worst_index] = compute_distance(
element=solution["point"].loc[worst_index], solution=solution, data=data
)
return solution
def get_random_solution(previous, n, data):
solution = replace_worst_element(previous, n, data)
while solution["distance"].sum() <= previous["distance"].sum():
solution = replace_worst_element(previous=solution, n=n, data=data)
return solution
def explore_neighbourhood(element, n, data, max_iterations=100000):
neighbourhood = []
neighbourhood.append(element)
for _ in range(max_iterations):
previous_solution = neighbourhood[-1]
neighbour = get_random_solution(previous=previous_solution, data=data)
neighbour = get_random_solution(previous=previous_solution, n=n, data=data)
neighbourhood.append(neighbour)
return neighbour
def local_search(m, data):
first_solution = get_first_random_solution(m=m, data=data)
def local_search(n, m, data):
first_solution = get_first_random_solution(n, m, data)
best_solution = explore_neighbourhood(
element=first_solution, data=data, max_iterations=100
element=first_solution, n=n, data=data, max_iterations=100
)
return best_solution

View File

@ -1,28 +1,41 @@
from preprocessing import parse_file
from greedy import greedy_algorithm
from local_search import local_search
from local_search import local_search, get_row_distance
from sys import argv
from time import time
from itertools import combinations
def execute_algorithm(choice, n, m, data):
if choice == "greedy":
return greedy_algorithm(n, m, data)
elif choice == "local":
return local_search(m, data)
return local_search(n, m, data)
else:
print("The valid algorithm choices are 'greedy' and 'local'")
exit(1)
def show_results(solutions, time_delta):
distance_sum = solutions["distance"].sum()
def get_fitness(solutions, data):
accumulator = 0
comb = combinations(solutions.index, r=2)
for index in list(comb):
elements = solutions.loc[index, :]
accumulator += get_row_distance(
source=elements["point"].head(n=1).values[0],
destination=elements["point"].tail(n=1).values[0],
data=data,
)
return accumulator
def show_results(solutions, fitness, time_delta):
duplicates = solutions.duplicated().any()
print(solutions)
print("Total distance: " + str(distance_sum))
print(f"Total distance: {fitness}")
if not duplicates:
print("No duplicates found")
print("Execution time: " + str(time_delta))
print(f"Execution time: {time_delta}")
def usage(argv):
@ -40,7 +53,8 @@ def main():
start_time = time()
solutions = execute_algorithm(choice=argv[2], n=n, m=m, data=data)
end_time = time()
show_results(solutions, time_delta=end_time - start_time)
fitness = get_fitness(solutions, data)
show_results(solutions, fitness, time_delta=end_time - start_time)
if __name__ == "__main__":