diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..de9f350 --- /dev/null +++ b/.gitignore @@ -0,0 +1,21 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Datasource local storage ignored files +/../../../../:\code\AI2020BsC\.idea/dataSources/ +/dataSources.local.xml +# Editor-based HTTP Client requests +/httpRequests/ +/AI2020BsC.iml +/misc.xml +/modules.xml +/other.xml +/inspectionProfiles/profiles_settings.xml +/vcs.xml +/.idea/AI2020BsC.iml +/.idea/misc.xml +/.idea/modules.xml +/.idea/other.xml +/.idea/inspectionProfiles/profiles_settings.xml +/.idea/vcs.xml +/.idea/workspace.xml diff --git a/src/TSP_solver.py b/src/TSP_solver.py index 44e0596..43f44e4 100644 --- a/src/TSP_solver.py +++ b/src/TSP_solver.py @@ -1,26 +1,24 @@ from numpy.core._multiarray_umath import ndarray -import os from time import time as t import numpy as np import matplotlib.pyplot as plt -if 'AI' in os.getcwd(): - from src import * -else: - from AI2019.src import * + +from src.local_search import TwoOpt_loop2opt, TwoDotFiveOpt_loop2dot5opt +from src.constructive_algorithms import random_method, nearest_neighbor, best_nearest_neighbor, multi_fragment_mf class SolverTSP: solution: ndarray found_length: float - available_initializers = {"random": random_initialier.random_method, - "nearest_neighbors": nearest_neighbor.nn, - "best_nn": nearest_neighbor.best_nn, - "multi_fragment": multi_fragment.mf + available_initializers = {"random": random_method, + "nearest_neighbors": nearest_neighbor, + "best_nn": best_nearest_neighbor, + "multi_fragment": multi_fragment_mf } - available_improvements = {"2-opt": TwoOpt.loop2opt, - "2.5-opt": TwoDotFiveOpt.loop2dot5opt, + available_improvements = {"2-opt": TwoOpt_loop2opt, + "2.5-opt": TwoDotFiveOpt_loop2dot5opt, "simulated_annealing": Simulated_Annealing.sa} # , diff --git a/src/__init__.py b/src/__init__.py index 049c2e1..540f723 100644 --- a/src/__init__.py +++ b/src/__init__.py @@ -4,7 +4,7 @@ if 'AI' in os.getcwd(): from src.utils import * from src.constructive_algorithms import * from src.local_search import * - from src.meta_heuristics import * + from src.iterated_local_search import * from src.TSP_solver import * from src.io_tsp import * diff --git a/src/constructive_algorithms.py b/src/constructive_algorithms.py index 2d2bdc3..6697ad9 100644 --- a/src/constructive_algorithms.py +++ b/src/constructive_algorithms.py @@ -1,139 +1,131 @@ -import os import numpy as np -if 'AI' in os.getcwd(): - from src.utils import * -else: - from AI2019.src.utils import * + +from src.utils import compute_length -class random_initialier: - @staticmethod - def random_method(instance_): - n = int(instance_.nPoints) - solution = np.random.choice(np.arange(n), size=n, replace=False) - return np.concatenate([solution, [solution[0]]]) +def random_method(instance_): + n = int(instance_.nPoints) + solution = np.random.choice(np.arange(n), size=n, replace=False) + return np.concatenate([solution, [solution[0]]]) -class nearest_neighbor: - @staticmethod - def nn(instance_, starting_node=0): - dist_matrix = np.copy(instance_.dist_matrix) - n = int(instance_.nPoints) - node = starting_node - tour = [node] - for _ in range(n - 1): - for new_node in np.argsort(dist_matrix[node]): - if new_node not in tour: - tour.append(new_node) - node = new_node - break - tour.append(starting_node) - return np.array(tour) +def nearest_neighbor(instance_, starting_node=0): + dist_matrix = np.copy(instance_.dist_matrix) + n = int(instance_.nPoints) + node = starting_node + tour = [node] + for _ in range(n - 1): + for new_node in np.argsort(dist_matrix[node]): + if new_node not in tour: + tour.append(new_node) + node = new_node + break + tour.append(starting_node) + return np.array(tour) - @staticmethod - def best_nn(instance_): - solutions, lens = [], [] - for start in range(instance_.nPoints): - new_solution = nearest_neighbor.nn(instance_, starting_node=start) - solutions.append(new_solution) - lens.append(compute_length(new_solution, instance_.dist_matrix)) +def best_nearest_neighbor(instance_): + solutions, lens = [], [] + for start in range(instance_.nPoints): + new_solution = nearest_neighbor(instance_, starting_node=start) + solutions.append(new_solution) + lens.append(compute_length(new_solution, instance_.dist_matrix)) + + if lens is []: + return None # Todo understand this return + else: solution = solutions[np.argmin(lens)] return solution -class multi_fragment: +def multi_fragment_check_if_available(n1, n2, sol): + if len(sol[str(n1)]) < 2 and len(sol[str(n2)]) < 2: + return True + else: + return False - @staticmethod - def check_if_available(n1, n2, sol): - if len(sol[str(n1)]) < 2 and len(sol[str(n2)]) < 2: - return True - else: - return False - @staticmethod - def check_if_not_close(edge_to_append, sol): - n1, n2 = edge_to_append - from_city = n2 - if len(sol[str(from_city)]) == 0: - return True - partial_tour = [from_city] - end = False - iterazione = 0 - while not end: - if len(sol[str(from_city)]) == 1: - if from_city == n1: - return_value = False - end = True - elif iterazione > 1: - # print(f"iterazione {iterazione}, elementi dentro partial {len(partial_tour)}", - # f"from city {from_city}") - return_value = True - end = True - else: - from_city = sol[str(from_city)][0] - partial_tour.append(from_city) - iterazione += 1 +def multi_fragment_check_if_not_close(edge_to_append, sol): + n1, n2 = edge_to_append + from_city = n2 + if len(sol[str(from_city)]) == 0: + return True + partial_tour = [from_city] + end = False + iterazione = 0 + while not end: + if len(sol[str(from_city)]) == 1: + if from_city == n1: + return_value = False + end = True + elif iterazione > 1: + # print(f"iterazione {iterazione}, elementi dentro partial {len(partial_tour)}", + # f"from city {from_city}") + return_value = True + end = True else: - # print(from_city, partial_tour, sol[str(from_city)]) - for node_connected in sol[str(from_city)]: - # print(node_connected) - if node_connected not in partial_tour: - from_city = node_connected - partial_tour.append(node_connected) - # print(node_connected, sol[str(from_city)]) - iterazione += 1 - return return_value - - @staticmethod - def create_solution(start_sol, sol, n): - assert len(start_sol) == 2, "too many cities with just one link" - end = False - n1, n2 = start_sol - from_city = n2 - sol_list = [n1, n2] - iterazione = 0 - while not end: - for node_connected in sol[str(from_city)]: + from_city = sol[str(from_city)][0] + partial_tour.append(from_city) iterazione += 1 - if node_connected not in sol_list: + else: + # print(from_city, partial_tour, sol[str(from_city)]) + for node_connected in sol[str(from_city)]: + # print(node_connected) + if node_connected not in partial_tour: from_city = node_connected - sol_list.append(node_connected) - # print(f"prossimo {node_connected}", - # f"possibili {sol[str(from_city)]}", - # f"ultim tour {sol_list[-5:]}") - if iterazione > 300: - if len(sol_list) == n: - end = True - sol_list.append(n1) - return sol_list + partial_tour.append(node_connected) + # print(node_connected, sol[str(from_city)]) + iterazione += 1 + return return_value - @staticmethod - def mf(instance): - mat = np.copy(instance.dist_matrix) - mat = np.triu(mat) - mat[mat == 0] = 100000 - solution = {str(i): [] for i in range(instance.nPoints)} - start_list = [i for i in range(instance.nPoints)] - inside = 0 - for el in np.argsort(mat.flatten()): - node1, node2 = el // instance.nPoints, el % instance.nPoints - possible_edge = [node1, node2] - if multi_fragment.check_if_available(node1, node2, - solution): - if multi_fragment.check_if_not_close(possible_edge, solution): - # print("entrato", inside) - solution[str(node1)].append(node2) - solution[str(node2)].append(node1) - if len(solution[str(node1)]) == 2: - start_list.remove(node1) - if len(solution[str(node2)]) == 2: - start_list.remove(node2) - inside += 1 - # print(node1, node2, inside) - if inside == instance.nPoints - 1: - # print(f"ricostruire la solutione da {start_list}", - # f"vicini di questi due nodi {[solution[str(i)] for i in start_list]}") - solution = multi_fragment.create_solution(start_list, solution, instance.nPoints) - return solution +def multi_fragment_create_solution(start_sol, sol, n): + assert len(start_sol) == 2, "too many cities with just one link" + end = False + n1, n2 = start_sol + from_city = n2 + sol_list = [n1, n2] + iterazione = 0 + while not end: + for node_connected in sol[str(from_city)]: + iterazione += 1 + if node_connected not in sol_list: + from_city = node_connected + sol_list.append(node_connected) + # print(f"prossimo {node_connected}", + # f"possibili {sol[str(from_city)]}", + # f"ultim tour {sol_list[-5:]}") + if iterazione > 300: + if len(sol_list) == n: + end = True + sol_list.append(n1) + return sol_list + + +def multi_fragment_mf(instance): + mat = np.copy(instance.dist_matrix) + mat = np.triu(mat) + mat[mat == 0] = 100000 + solution = {str(i): [] for i in range(instance.nPoints)} + start_list = [i for i in range(instance.nPoints)] + inside = 0 + for el in np.argsort(mat.flatten()): + node1, node2 = el // instance.nPoints, el % instance.nPoints + possible_edge = [node1, node2] + if multi_fragment_check_if_available(node1, node2, + solution): + if multi_fragment_check_if_not_close(possible_edge, solution): + # print("entrato", inside) + solution[str(node1)].append(node2) + solution[str(node2)].append(node1) + if len(solution[str(node1)]) == 2: + start_list.remove(node1) + if len(solution[str(node2)]) == 2: + start_list.remove(node2) + inside += 1 + # print(node1, node2, inside) + if inside == instance.nPoints - 1: + # print(f"ricostruire la solutione da {start_list}", + # f"vicini di questi due nodi {[solution[str(i)] for i in start_list]}") + solution = multi_fragment_create_solution(start_list, solution, instance.nPoints) + return solution diff --git a/src/io_tsp.py b/src/io_tsp.py index b86e622..ea9dd94 100644 --- a/src/io_tsp.py +++ b/src/io_tsp.py @@ -17,6 +17,7 @@ class Instance: def __init__(self, name_tsp): self.read_instance(name_tsp) self.exist_opt = None # TODO determine default value + self.optimal_tour = None def read_instance(self, name_tsp): # read raw data @@ -66,8 +67,6 @@ class Instance: plt.annotate(txt, (self.points[i, 1], self.points[i, 2])) plt.show() - - def create_dist_matrix(self): self.dist_matrix = np.zeros((self.nPoints, self.nPoints)) @@ -75,4 +74,3 @@ class Instance: for j in range(i, self.nPoints): self.dist_matrix[i, j] = distance_euc(self.points[i][1:3], self.points[j][1:3]) self.dist_matrix += self.dist_matrix.T - diff --git a/src/iterated_local_search.py b/src/iterated_local_search.py new file mode 100644 index 0000000..9d31562 --- /dev/null +++ b/src/iterated_local_search.py @@ -0,0 +1,4 @@ +class Iterated_Local_Search: + + def __call__(self): + pass diff --git a/src/local_search.py b/src/local_search.py index 74a5cdc..b28b04f 100644 --- a/src/local_search.py +++ b/src/local_search.py @@ -1,134 +1,3 @@ -import os -import numpy as np - -if 'AI' in os.getcwd(): - from src.utils import * -else: - from AI2019.src.utils import * -class TwoOpt: - @staticmethod - def step2opt(solution, matrix_dist, distance): - seq_length = len(solution) - 1 - tsp_sequence = np.array(solution) - uncrosses = 0 - for i in range(1, seq_length - 1): - for j in range(i + 1, seq_length): - new_tsp_sequence = TwoOpt.swap2opt(tsp_sequence, i, j) - new_distance = distance + TwoOpt.gain(i, j, tsp_sequence, matrix_dist) - if new_distance < distance: - uncrosses += 1 - tsp_sequence = np.copy(new_tsp_sequence) - distance = new_distance - return tsp_sequence, distance, uncrosses - - @staticmethod - def swap2opt(tsp_sequence, i, j): - new_tsp_sequence = np.copy(tsp_sequence) - new_tsp_sequence[i:j + 1] = np.flip(tsp_sequence[i:j + 1], axis=0) # flip or swap ? - return new_tsp_sequence - - @staticmethod - def gain(i, j, tsp_sequence, matrix_dist): - old_link_len = (matrix_dist[tsp_sequence[i], tsp_sequence[i - 1]] + matrix_dist[ - tsp_sequence[j], tsp_sequence[j + 1]]) - changed_links_len = (matrix_dist[tsp_sequence[j], tsp_sequence[i - 1]] + matrix_dist[ - tsp_sequence[i], tsp_sequence[j + 1]]) - return - old_link_len + changed_links_len - - @staticmethod - def loop2opt(solution, instance, max_num_of_uncrosses=10000): - matrix_dist = instance.dist_matrix - new_len = compute_length(solution, matrix_dist) - new_tsp_sequence = np.copy(np.array(solution)) - uncross = 0 - while uncross < max_num_of_uncrosses: - new_tsp_sequence, new_reward, uncr_ = TwoOpt.step2opt(new_tsp_sequence, matrix_dist, new_len) - uncross += uncr_ - if new_reward < new_len: - new_len = new_reward - else: - return new_tsp_sequence.tolist() - - # return new_tsp_sequence.tolist(), new_len, uncross - return new_tsp_sequence.tolist() - - -class TwoDotFiveOpt: - - @staticmethod - def step2dot5opt(solution, matrix_dist, distance): - seq_length = len(solution) - 2 - tsp_sequence = np.array(solution) - uncrosses = 0 - for i in range(1, seq_length - 1): - for j in range(i + 1, seq_length): - # 2opt swap - twoOpt_tsp_sequence = TwoOpt.swap2opt(tsp_sequence, i, j) - twoOpt_len = distance + TwoOpt.gain(i, j, tsp_sequence, matrix_dist) - # node shift 1 - first_shift_tsp_sequence = TwoDotFiveOpt.shift1(tsp_sequence, i, j) - first_shift_len = distance + TwoDotFiveOpt.shift_gain1(i, j, tsp_sequence, matrix_dist) - # node shift 2 - second_shift_tsp_sequence = TwoDotFiveOpt.shift2(tsp_sequence, i, j) - second_shift_len = distance + TwoDotFiveOpt.shift_gain2(i, j, tsp_sequence, matrix_dist) - - best_len, best_method = min([twoOpt_len, first_shift_len, second_shift_len]), np.argmin( - [twoOpt_len, first_shift_len, second_shift_len]) - sequences = [twoOpt_tsp_sequence, first_shift_tsp_sequence, second_shift_tsp_sequence] - if best_len < distance: - uncrosses += 1 - tsp_sequence = sequences[best_method] - distance = best_len - # print(distance, best_method, [twoOpt_len, first_shift_len, second_shift_len]) - return tsp_sequence, distance, uncrosses - - @staticmethod - def shift1(tsp_sequence, i, j): - new_tsp_sequence = np.concatenate( - [tsp_sequence[:i], tsp_sequence[i + 1: j + 1], [tsp_sequence[i]], tsp_sequence[j + 1:]]) - return new_tsp_sequence - - @staticmethod - def shift_gain1(i, j, tsp_sequence, matrix_dist): - old_link_len = (matrix_dist[tsp_sequence[i], tsp_sequence[i - 1]] + - matrix_dist[tsp_sequence[i], tsp_sequence[i + 1]] + - matrix_dist[tsp_sequence[j], tsp_sequence[j + 1]]) - changed_links_len = (matrix_dist[tsp_sequence[i - 1], tsp_sequence[i + 1]] + - matrix_dist[tsp_sequence[i], tsp_sequence[j]] - + matrix_dist[tsp_sequence[i], tsp_sequence[j + 1]]) - return - old_link_len + changed_links_len - - @staticmethod - def shift2(tsp_sequence, i, j): - new_tsp_sequence = np.concatenate( - [tsp_sequence[:i], [tsp_sequence[j]], tsp_sequence[i: j], tsp_sequence[j + 1:]]) - return new_tsp_sequence - - @staticmethod - def shift_gain2(i, j, tsp_sequence, matrix_dist): - old_link_len = (matrix_dist[tsp_sequence[i], tsp_sequence[i - 1]] + matrix_dist[ - tsp_sequence[j], tsp_sequence[j - 1]] + matrix_dist[tsp_sequence[j], tsp_sequence[j + 1]]) - changed_links_len = ( - matrix_dist[tsp_sequence[j], tsp_sequence[i - 1]] + matrix_dist[tsp_sequence[i], tsp_sequence[j]] + - matrix_dist[tsp_sequence[j - 1], tsp_sequence[j + 1]]) - return - old_link_len + changed_links_len - - @staticmethod - def loop2dot5opt(solution, instance, max_num_of_changes=10000): - matrix_dist = instance.dist_matrix - actual_len = compute_length(solution, matrix_dist) - new_tsp_sequence = np.copy(np.array(solution)) - uncross = 0 - while uncross < max_num_of_changes: - new_tsp_sequence, new_len, uncr_ = TwoDotFiveOpt.step2dot5opt(new_tsp_sequence, matrix_dist, actual_len) - uncross += uncr_ - # print(new_len, uncross) - if new_len < actual_len: - actual_len = new_len - else: - return new_tsp_sequence.tolist() - - return new_tsp_sequence.tolist() \ No newline at end of file diff --git a/src/meta_heuristics.py b/src/meta_heuristics.py deleted file mode 100644 index 7cc1baf..0000000 --- a/src/meta_heuristics.py +++ /dev/null @@ -1,66 +0,0 @@ -import numpy as np -import os -if 'AI' in os.getcwd(): - from src.utils import * -else: - from AI2019.src.utils import * - - -class Simulated_Annealing: - - @staticmethod - def sa(solution, instance, constant_temperature=0.95, iterations_for_each_temp=100): - - # initial setup - temperature = instance.best_sol / np.sqrt(instance.nPoints) - current_sol = np.array(solution) - current_len = compute_length(solution, instance.dist_matrix) - best_sol = np.array(solution) - best_len = current_len - - # main loop - while temperature > 0.001: - for it in range(iterations_for_each_temp): - next_sol, delta_E = Simulated_Annealing.random_sol_from_neig(current_sol, instance) - if delta_E < 0: - current_sol = next_sol - current_len += delta_E - if current_len < best_len: - best_sol = current_sol - best_len = current_len - else: - r = np.random.uniform(0, 1) - if r < np.exp(- delta_E / temperature): - current_sol = next_sol - current_len += delta_E - - temperature *= constant_temperature - - return best_sol.tolist() - - @staticmethod - def random_sol_from_neig(solution, instance): - i, j = np.random.choice(np.arange(1, len(solution) - 1), 2, replace=False) - i, j = np.sort([i, j]) - return Simulated_Annealing.swap2opt(solution, i, j), Simulated_Annealing.gain(i, j, solution, - instance.dist_matrix) - - @staticmethod - def swap2opt(tsp_sequence, i, j): - new_tsp_sequence = np.copy(tsp_sequence) - new_tsp_sequence[i:j + 1] = np.flip(tsp_sequence[i:j + 1], axis=0) # flip or swap ? - return new_tsp_sequence - - @staticmethod - def gain(i, j, tsp_sequence, matrix_dist): - old_link_len = (matrix_dist[tsp_sequence[i], tsp_sequence[i - 1]] + matrix_dist[ - tsp_sequence[j], tsp_sequence[j + 1]]) - changed_links_len = (matrix_dist[tsp_sequence[j], tsp_sequence[i - 1]] + matrix_dist[ - tsp_sequence[i], tsp_sequence[j + 1]]) - return - old_link_len + changed_links_len - - -class Iterated_Local_Search: - - def __call__(self): - pass diff --git a/src/simulated_annealing.py b/src/simulated_annealing.py new file mode 100644 index 0000000..57dfaf9 --- /dev/null +++ b/src/simulated_annealing.py @@ -0,0 +1,52 @@ +import numpy as np + +from src import compute_length + + +def sa(solution, instance, constant_temperature=0.95, iterations_for_each_temp=100): + # initial setup + temperature = instance.best_sol / np.sqrt(instance.nPoints) + current_sol = np.array(solution) + current_len = compute_length(solution, instance.dist_matrix) + best_sol = np.array(solution) + best_len = current_len + + # main loop + while temperature > 0.001: + for it in range(iterations_for_each_temp): + next_sol, delta_E = random_sol_from_neig(current_sol, instance) + if delta_E < 0: + current_sol = next_sol + current_len += delta_E + if current_len < best_len: + best_sol = current_sol + best_len = current_len + else: + r = np.random.uniform(0, 1) + if r < np.exp(- delta_E / temperature): + current_sol = next_sol + current_len += delta_E + + temperature *= constant_temperature + + return best_sol.tolist() + + +def random_sol_from_neig(solution, instance): + i, j = np.random.choice(np.arange(1, len(solution) - 1), 2, replace=False) + i, j = np.sort([i, j]) + return swap2opt(solution, i, j), gain(i, j, solution, instance.dist_matrix) + + +def swap2opt(tsp_sequence, i, j): + new_tsp_sequence = np.copy(tsp_sequence) + new_tsp_sequence[i:j + 1] = np.flip(tsp_sequence[i:j + 1], axis=0) # flip or swap ? + return new_tsp_sequence + + +def gain(i, j, tsp_sequence, matrix_dist): + old_link_len = (matrix_dist[tsp_sequence[i], tsp_sequence[i - 1]] + matrix_dist[ + tsp_sequence[j], tsp_sequence[j + 1]]) + changed_links_len = (matrix_dist[tsp_sequence[j], tsp_sequence[i - 1]] + matrix_dist[ + tsp_sequence[i], tsp_sequence[j + 1]]) + return - old_link_len + changed_links_len diff --git a/src/two_dot_five_opt.py b/src/two_dot_five_opt.py new file mode 100644 index 0000000..6092a54 --- /dev/null +++ b/src/two_dot_five_opt.py @@ -0,0 +1,79 @@ +import numpy as np + +from src.utils import compute_length +from src.two_opt import swap2opt, gain + + +def step2dot5opt(solution, matrix_dist, distance): + seq_length = len(solution) - 2 + tsp_sequence = np.array(solution) + uncrosses = 0 + for i in range(1, seq_length - 1): + for j in range(i + 1, seq_length): + # 2opt swap + twoOpt_tsp_sequence = swap2opt(tsp_sequence, i, j) + twoOpt_len = distance + gain(i, j, tsp_sequence, matrix_dist) + # node shift 1 + first_shift_tsp_sequence = shift1(tsp_sequence, i, j) + first_shift_len = distance + shift_gain1(i, j, tsp_sequence, matrix_dist) + # node shift 2 + second_shift_tsp_sequence = shift2(tsp_sequence, i, j) + second_shift_len = distance + shift_gain2(i, j, tsp_sequence, matrix_dist) + + best_len, best_method = min([twoOpt_len, first_shift_len, second_shift_len]), np.argmin( + [twoOpt_len, first_shift_len, second_shift_len]) + sequences = [twoOpt_tsp_sequence, first_shift_tsp_sequence, second_shift_tsp_sequence] + if best_len < distance: + uncrosses += 1 + tsp_sequence = sequences[best_method] + distance = best_len + # print(distance, best_method, [twoOpt_len, first_shift_len, second_shift_len]) + return tsp_sequence, distance, uncrosses + + +def shift1(tsp_sequence, i, j): + new_tsp_sequence = np.concatenate( + [tsp_sequence[:i], tsp_sequence[i + 1: j + 1], [tsp_sequence[i]], tsp_sequence[j + 1:]]) + return new_tsp_sequence + + +def shift_gain1(i, j, tsp_sequence, matrix_dist): + old_link_len = (matrix_dist[tsp_sequence[i], tsp_sequence[i - 1]] + + matrix_dist[tsp_sequence[i], tsp_sequence[i + 1]] + + matrix_dist[tsp_sequence[j], tsp_sequence[j + 1]]) + changed_links_len = (matrix_dist[tsp_sequence[i - 1], tsp_sequence[i + 1]] + + matrix_dist[tsp_sequence[i], tsp_sequence[j]] + + matrix_dist[tsp_sequence[i], tsp_sequence[j + 1]]) + return - old_link_len + changed_links_len + + +def shift2(tsp_sequence, i, j): + new_tsp_sequence = np.concatenate( + [tsp_sequence[:i], [tsp_sequence[j]], tsp_sequence[i: j], tsp_sequence[j + 1:]]) + return new_tsp_sequence + + +def shift_gain2(i, j, tsp_sequence, matrix_dist): + old_link_len = (matrix_dist[tsp_sequence[i], tsp_sequence[i - 1]] + matrix_dist[ + tsp_sequence[j], tsp_sequence[j - 1]] + matrix_dist[tsp_sequence[j], tsp_sequence[j + 1]]) + changed_links_len = ( + matrix_dist[tsp_sequence[j], tsp_sequence[i - 1]] + matrix_dist[tsp_sequence[i], tsp_sequence[j]] + + matrix_dist[tsp_sequence[j - 1], tsp_sequence[j + 1]]) + return - old_link_len + changed_links_len + + +def loop2dot5opt(solution, instance, max_num_of_changes=10000): + matrix_dist = instance.dist_matrix + actual_len = compute_length(solution, matrix_dist) + new_tsp_sequence = np.copy(np.array(solution)) + uncross = 0 + while uncross < max_num_of_changes: + new_tsp_sequence, new_len, uncr_ = step2dot5opt(new_tsp_sequence, matrix_dist, actual_len) + uncross += uncr_ + # print(new_len, uncross) + if new_len < actual_len: + actual_len = new_len + else: + return new_tsp_sequence.tolist() + + return new_tsp_sequence.tolist() diff --git a/src/two_opt.py b/src/two_opt.py new file mode 100644 index 0000000..4a03ff3 --- /dev/null +++ b/src/two_opt.py @@ -0,0 +1,49 @@ +import numpy as np + +from src import compute_length + + +def step2opt(solution, matrix_dist, distance): + seq_length = len(solution) - 1 + tsp_sequence = np.array(solution) + uncrosses = 0 + for i in range(1, seq_length - 1): + for j in range(i + 1, seq_length): + new_tsp_sequence = swap2opt(tsp_sequence, i, j) + new_distance = distance + gain(i, j, tsp_sequence, matrix_dist) + if new_distance < distance: + uncrosses += 1 + tsp_sequence = np.copy(new_tsp_sequence) + distance = new_distance + return tsp_sequence, distance, uncrosses + + +def swap2opt(tsp_sequence, i, j): + new_tsp_sequence = np.copy(tsp_sequence) + new_tsp_sequence[i:j + 1] = np.flip(tsp_sequence[i:j + 1], axis=0) # flip or swap ? + return new_tsp_sequence + + +def gain(i, j, tsp_sequence, matrix_dist): + old_link_len = (matrix_dist[tsp_sequence[i], tsp_sequence[i - 1]] + matrix_dist[ + tsp_sequence[j], tsp_sequence[j + 1]]) + changed_links_len = (matrix_dist[tsp_sequence[j], tsp_sequence[i - 1]] + matrix_dist[ + tsp_sequence[i], tsp_sequence[j + 1]]) + return - old_link_len + changed_links_len + + +def loop2opt(solution, instance, max_num_of_uncrosses=10000): + matrix_dist = instance.dist_matrix + new_len = compute_length(solution, matrix_dist) + new_tsp_sequence = np.copy(np.array(solution)) + uncross = 0 + while uncross < max_num_of_uncrosses: + new_tsp_sequence, new_reward, uncr_ = step2opt(new_tsp_sequence, matrix_dist, new_len) + uncross += uncr_ + if new_reward < new_len: + new_len = new_reward + else: + return new_tsp_sequence.tolist() + + # return new_tsp_sequence.tolist(), new_len, uncross + return new_tsp_sequence.tolist() diff --git a/src/utils.py b/src/utils.py index 4368e98..e09ab5b 100644 --- a/src/utils.py +++ b/src/utils.py @@ -1,3 +1,6 @@ +import numpy as np + + def compute_length(solution, dist_matrix): total_length = 0 starting_node = solution[0]