code
This commit is contained in:
parent
412c87ef2a
commit
bcce2ac0d5
20 changed files with 173 additions and 533 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -109,7 +109,7 @@ profile_default/
|
|||
ipython_config.py
|
||||
|
||||
# pyenv
|
||||
# For a library or package, you might want to ignore these files since the code is
|
||||
# For a library or package, you might want to ignore these files since the aco is
|
||||
# intended to run in multiple environments; otherwise, check them in:
|
||||
# .python-version
|
||||
|
||||
|
|
Binary file not shown.
78
aco/TSP_solver.py
Normal file
78
aco/TSP_solver.py
Normal file
|
@ -0,0 +1,78 @@
|
|||
from time import time as t
|
||||
import numpy as np
|
||||
import matplotlib.pyplot as plt
|
||||
from aco.ant_colony import ant_colony_opt
|
||||
|
||||
class TSPSolver:
|
||||
def __init__(self, problem_instance):
|
||||
self.duration = np.inf
|
||||
self.found_length = np.inf
|
||||
self.solved = False
|
||||
self.problem_instance = problem_instance
|
||||
self.solution = None
|
||||
|
||||
def compute_solution(self, verbose=True, return_value=True):
|
||||
self.solved = False
|
||||
if verbose:
|
||||
print(f"### solving with 'C++ ant colony optimization' ####")
|
||||
start_time = t()
|
||||
self.solution = ant_colony_opt(self.problem_instance)
|
||||
if not self.check_if_solution_is_valid():
|
||||
print(f"Error the solution of 'C++ ant colony optimization'"
|
||||
f" for problem {self.problem_instance.name} is not valid")
|
||||
if return_value:
|
||||
return False
|
||||
|
||||
end_time = t()
|
||||
self.duration = np.around(end_time - start_time, 3)
|
||||
self.solved = True
|
||||
self.evaluate_solution()
|
||||
self._gap()
|
||||
if return_value:
|
||||
return self.solution
|
||||
|
||||
def plot_solution(self):
|
||||
assert self.solved, "You can't plot the solution, you need to compute it first!"
|
||||
plt.figure(figsize=(8, 8))
|
||||
self._gap()
|
||||
plt.title(f"{self.problem_instance.name} solved with 'C++ ant colony optimization'"
|
||||
f" solver, gap {self.gap}")
|
||||
ordered_points = self.problem_instance.points[self.solution]
|
||||
plt.plot(ordered_points[:, 1], ordered_points[:, 2], 'b-')
|
||||
plt.show()
|
||||
|
||||
def check_if_solution_is_valid(self):
|
||||
rights_values = np.sum(
|
||||
[self.check_validation(i, self.solution[:-1]) for i in
|
||||
np.arange(self.problem_instance.nPoints)])
|
||||
return rights_values == self.problem_instance.nPoints
|
||||
|
||||
def check_validation(self, node, solution):
|
||||
if np.sum(solution == node) == 1:
|
||||
return 1
|
||||
else:
|
||||
return 0
|
||||
|
||||
def evaluate_solution(self, return_value=False):
|
||||
total_length = 0
|
||||
starting_node = self.solution[0]
|
||||
from_node = starting_node
|
||||
for node in self.solution[1:]:
|
||||
total_length += self.problem_instance.dist_matrix[from_node, node]
|
||||
from_node = node
|
||||
|
||||
self.found_length = total_length
|
||||
if return_value:
|
||||
return total_length
|
||||
|
||||
def pass_and_check_if_solution_is_valid(self, solution):
|
||||
rights_values = np.sum(
|
||||
[self.check_validation(i, solution[:-1]) for i in
|
||||
np.arange(self.problem_instance.nPoints)])
|
||||
return rights_values == self.problem_instance.nPoints
|
||||
|
||||
def _gap(self):
|
||||
self.evaluate_solution(return_value=False)
|
||||
self.gap = np.round(
|
||||
((self.found_length - self.problem_instance.best_sol) /
|
||||
self.problem_instance.best_sol) * 100, 2)
|
|
@ -1,6 +1,6 @@
|
|||
import os
|
||||
|
||||
dir = "/Users/maggicl/Git/AI2020BsC/c_prob/"
|
||||
dir = "c_prob/"
|
||||
def ant_colony_opt(instance):
|
||||
fname = dir + instance.name + ".txt"
|
||||
|
||||
|
@ -12,7 +12,7 @@ def ant_colony_opt(instance):
|
|||
# single core params
|
||||
params = {
|
||||
"eil76": (0.9, 8, 0.4, 1000, 500, 200, 30), # 543
|
||||
"d198": (0.9, 8, 0.4, 1000, 500, 60, 30), # 16042
|
||||
"d198": (0.9, 8, 0.4, 1000, 250, 125, 5), # 15871
|
||||
"ch130": (0.9, 8, 0.4, 1000, 1750, 25, 30), # 6212
|
||||
"kroA100": (0.9, 8, 0.4, 1000, 750, 100, 30), # 21378
|
||||
"lin318": (0.9, 8, 0.4, 1000, 500, 32, 30), # 43171
|
||||
|
@ -20,22 +20,17 @@ def ant_colony_opt(instance):
|
|||
"pr439": (0.9, 8, 0.4, 1000, 450, 24, 2), # 11734
|
||||
"rat783": (0.9, 8, 0.4, 1000, 450, 24, 2), # 9232
|
||||
"u1060": (0.9, 8, 0.4, 1000, 350, 6, 3), # 238025
|
||||
"fl1577": (0.9, 8, 0.4, 1000, 50, 9, 3), # 24145
|
||||
#"fl1577": (0.9, 8, 0.4, 1000, 50, 9, 3), # 24145
|
||||
"fl1577": (0.9, 8, 0.4, 1000, 50, 10, 5), # 24145
|
||||
}
|
||||
# params_multicore = {
|
||||
# "d198": (0.9, 8, 0.4, 1000, 750, 200, 30), # 15970
|
||||
# "ch130": (0.9, 8, 0.4, 1000, 1750, 200, 30), # 6173
|
||||
# }
|
||||
|
||||
alpha, beta, evap, weight, ants, loops, optruns = params[instance.name]
|
||||
|
||||
# Call C++ program
|
||||
cmd = dir + "aco " + str(instance.nPoints) + " " + str(loops) + " " + str(ants) + \
|
||||
" " + str(alpha) + " " + str(beta) + " " + str(evap) + " " + str(weight) + " " + str(optruns) + \
|
||||
" < " + fname + " &2>/dev/null"
|
||||
" " + str(alpha) + " " + str(beta) + " " + str(evap) + " " + str(weight) + \
|
||||
" " + str(optruns) + " < " + fname + " &2>/dev/null"
|
||||
print(cmd)
|
||||
solution = eval(os.popen(cmd).read())
|
||||
solution.append(solution[0])
|
||||
print(solution)
|
||||
print(len(solution))
|
||||
return solution
|
|
@ -1,11 +1,8 @@
|
|||
import numpy as np
|
||||
from matplotlib import pyplot as plt
|
||||
|
||||
from src.utils import distance_euc
|
||||
|
||||
from aco.utils import distance_euc
|
||||
|
||||
class ProblemInstance:
|
||||
|
||||
def __init__(self, name_tsp):
|
||||
self.exist_opt = False
|
||||
self.optimal_tour = None
|
||||
|
@ -32,7 +29,9 @@ class ProblemInstance:
|
|||
self.points[i, 2] = line_i[2]
|
||||
|
||||
self.create_dist_matrix()
|
||||
if self.file_name in ["./problems/eil76.tsp", "./problems/kroA100.tsp", "../problems/eil76.tsp",
|
||||
if self.file_name in ["./problems/eil76.tsp",
|
||||
"./problems/kroA100.tsp",
|
||||
"../problems/eil76.tsp",
|
||||
"../problems/kroA100.tsp"]:
|
||||
self.exist_opt = True
|
||||
file_object = open(self.file_name.replace(".tsp", ".opt.tour"))
|
8
aco/utils.py
Normal file
8
aco/utils.py
Normal file
|
@ -0,0 +1,8 @@
|
|||
import numpy as np
|
||||
|
||||
def distance_euc(point_i, point_j):
|
||||
rounding = 0
|
||||
x_i, y_i = point_i[0], point_i[1]
|
||||
x_j, y_j = point_j[0], point_j[1]
|
||||
distance = np.sqrt((x_i - x_j) ** 2 + (y_i - y_j) ** 2)
|
||||
return round(distance, rounding)
|
|
@ -1,6 +0,0 @@
|
|||
ants = 10
|
||||
alpha = 0.1
|
||||
rho = 0.1
|
||||
q0 = 0.95 = 1 - (15/nCities)
|
||||
beta = b1 = 1
|
||||
|
35
execute.md
Normal file
35
execute.md
Normal file
|
@ -0,0 +1,35 @@
|
|||
# Instructions for execution
|
||||
|
||||
## Requirements
|
||||
In order to reproduce the results reported in the Excel table,
|
||||
one must have the following requirements:
|
||||
|
||||
- A Python 3 interpreter and all Python dependencies required for the `AI2020BsC` repository installed, namely
|
||||
`time`, `matplotlib` and `numpy`;
|
||||
- A working C++11 or greater compiler accessible from the `c++` command (Modern versions of clang and g++ are both
|
||||
acceptable compilers).
|
||||
|
||||
## Execution
|
||||
`cd` in the `code` directory and execute `python3 ./run.py` from the terminal.
|
||||
The script will automatically compile the C++ companion program and start the TSP computation for each problem.
|
||||
|
||||
Results will be saved in `<problemname>.sol` files where `<problemname>` is respectively the name
|
||||
of the problem solved.
|
||||
|
||||
Execution time for each problem will be computed using the same criteria used for the original `AI2020BsC` repository.
|
||||
Times and lengths found will be saved in a file named `results.csv` which will be located in the same directory as
|
||||
`run.py`.
|
||||
|
||||
## `.sol` file contents
|
||||
Solution files are made up of just one line, containing a valid Python expression representing an array.
|
||||
The array in question contains the order in which cities must be visited, representing each city by its number.
|
||||
The array starts and ends with the starting city of the tour.
|
||||
|
||||
Here is the Backus Naur grammar for `.sol` files:
|
||||
```
|
||||
<.sol file> ::= <city array> '\n'
|
||||
<city array> ::= '[' <start city> ", " <cities> <start city> ']'
|
||||
<cities> ::= <city> ", " <cities> | <city> ", "
|
||||
<start city> ::= unsigned integer
|
||||
<city> ::= unsigned integer
|
||||
```
|
3
readme.md
Normal file
3
readme.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
# 2020 AI Cup submission - Claudio Maggioni
|
||||
For instructions on how to compile and run my submission in order to reproduce
|
||||
result please read file `../execute.md`.
|
77
run.py
77
run.py
|
@ -1,38 +1,16 @@
|
|||
import glob
|
||||
import pandas as pd
|
||||
from src.io_tsp import ProblemInstance
|
||||
from src.TSP_solver import TSPSolver, available_improvers, available_solvers
|
||||
import numpy as np
|
||||
|
||||
|
||||
def use_solver_to_compute_solution(solver, improve, index, results, name, verbose, show_plots):
|
||||
#solver.bind(improve)
|
||||
# solver.bind("2-opt")
|
||||
# solver.bind("2.5-opt")
|
||||
solver.compute_solution(return_value=False, verbose=verbose)
|
||||
# solver.pop()
|
||||
# solver.pop()
|
||||
|
||||
if verbose:
|
||||
print(f"the total length for the solution found is {solver.found_length}",
|
||||
f"while the optimal length is {solver.problem_instance.best_sol}",
|
||||
f"the gap is {solver.gap}%",
|
||||
f"the solution is found in {solver.duration} seconds", sep="\n")
|
||||
|
||||
index.append((name, solver.name_method))
|
||||
results.append([solver.found_length, solver.problem_instance.best_sol, solver.gap, solver.duration])
|
||||
|
||||
if show_plots:
|
||||
solver.plot_solution()
|
||||
|
||||
from aco.io_tsp import ProblemInstance
|
||||
from aco.TSP_solver import TSPSolver
|
||||
import os
|
||||
|
||||
def run(show_plots=False, verbose=False):
|
||||
os.system("rm -f " + " ".join(glob.glob("sol/*") + glob.glob("c_prob/*")))
|
||||
os.system("c++ -O2 -lpthread --std=c++11 -o c_prob/aco aco.cc opt.cc")
|
||||
problems = glob.glob('./problems/*.tsp')
|
||||
|
||||
# problems = ["./problems/fl1577.tsp"]
|
||||
problems = ["./problems/fl1577.tsp"]
|
||||
|
||||
solvers_names = available_solvers.keys()
|
||||
improvers_names = available_improvers.keys()
|
||||
results = []
|
||||
index = []
|
||||
for problem_path in problems:
|
||||
|
@ -42,35 +20,30 @@ def run(show_plots=False, verbose=False):
|
|||
if show_plots:
|
||||
prob_instance.plot_data()
|
||||
|
||||
solver = TSPSolver(prob_instance)
|
||||
solution = solver.compute_solution(verbose=verbose)
|
||||
if verbose:
|
||||
print(f"the total length for the solution found is {solver.found_length}",
|
||||
f"while the optimal length is {solver.problem_instance.best_sol}",
|
||||
f"the gap is {solver.gap}%",
|
||||
f"the solution is found in {solver.duration} seconds", sep="\n")
|
||||
index.append((problem_path, "'C++ ant colony optimization'"))
|
||||
results.append([solver.found_length,
|
||||
solver.problem_instance.best_sol,
|
||||
solver.gap,
|
||||
solver.duration])
|
||||
|
||||
for solver_name in solvers_names:
|
||||
solver = TSPSolver(solver_name, prob_instance)
|
||||
use_solver_to_compute_solution(solver, None, index, results, problem_path, verbose, show_plots)
|
||||
# for improve in improvers_names:
|
||||
# solver = TSPSolver(solver_name, prob_instance)
|
||||
# use_solver_to_compute_solution(solver, improve, index, results, problem_path, verbose, show_plots)
|
||||
# for improve2 in [j for j in improvers_names if j not in [improve]]:
|
||||
# use_solver_to_compute_solution(solver, improve2, index, results, problem_path, verbose, show_plots)
|
||||
#
|
||||
# for improve3 in [j for j in improvers_names if j not in [improve, improve2]]:
|
||||
# use_solver_to_compute_solution(solver, improve3, index, results, problem_path, verbose,
|
||||
# show_plots)
|
||||
# solver.pop()
|
||||
#
|
||||
# solver.pop()
|
||||
with open("sol/" + prob_instance.name + ".sol", "w") as f:
|
||||
print(solution, file=f)
|
||||
|
||||
if prob_instance.exist_opt and show_plots:
|
||||
solver = TSPSolver("optimal", prob_instance)
|
||||
solver.solved = True
|
||||
solver.solution = np.concatenate([prob_instance.optimal_tour, [prob_instance.optimal_tour[0]]])
|
||||
if show_plots:
|
||||
solver.plot_solution()
|
||||
|
||||
#index = pd.MultiIndex.from_tuples(index, names=['problem', 'method'])
|
||||
|
||||
#return None
|
||||
return pd.DataFrame(results, index=index, columns=["tour length", "optimal solution", "gap", "time to solve"])
|
||||
return pd.DataFrame(results, index=index,
|
||||
columns=["tour length", "optimal solution",
|
||||
"gap", "time to solve"])
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
df = run(show_plots=True, verbose=True)
|
||||
df = run(show_plots=False, verbose=True)
|
||||
df.to_csv("./results.csv")
|
||||
|
|
1
sol/fl1577.sol
Normal file
1
sol/fl1577.sol
Normal file
File diff suppressed because one or more lines are too long
|
@ -1,126 +0,0 @@
|
|||
from time import time as t
|
||||
import numpy as np
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
from src.two_opt import loop2opt
|
||||
from src.two_dot_five_opt import loop2dot5opt
|
||||
from src.simulated_annealing import sa
|
||||
from src.constructive_algorithms import random_method, nearest_neighbor, best_nearest_neighbor, multi_fragment_mf
|
||||
from src.ant_colony import ant_colony_opt
|
||||
|
||||
# available_solvers = {"random": random_method,
|
||||
# "nearest_neighbors": nearest_neighbor,
|
||||
# "best_nn": best_nearest_neighbor,
|
||||
# "multi_fragment": multi_fragment_mf
|
||||
# }
|
||||
available_improvers = {"2-opt": loop2opt,
|
||||
"2.5-opt": loop2dot5opt,
|
||||
"simulated_annealing": sa}
|
||||
|
||||
# available_solvers = {}
|
||||
# for i in range(1, 10):
|
||||
# for j in range(1, 10):
|
||||
# available_solvers["aco_" + str(i) + "_" + str(j)] = ant_colony_opt(i/10, j)
|
||||
|
||||
available_solvers = {"aco": ant_colony_opt}
|
||||
|
||||
class TSPSolver:
|
||||
def __init__(self, algorithm_name, problem_instance, passed_avail_solvers=None, passed_avail_improvers=None):
|
||||
# assert algorithm_name in available_solvers, f"the {algorithm_name} initializer is not available currently."
|
||||
if passed_avail_improvers is None:
|
||||
passed_avail_improvers = available_improvers
|
||||
if passed_avail_solvers is None:
|
||||
passed_avail_solvers = available_solvers
|
||||
self.available_improvers = passed_avail_improvers
|
||||
self.available_solvers = passed_avail_solvers
|
||||
self.duration = np.inf
|
||||
self.found_length = np.inf
|
||||
self.algorithm_name = algorithm_name
|
||||
self.algorithms = [algorithm_name]
|
||||
self.name_method = "initialized with " + algorithm_name
|
||||
self.solved = False
|
||||
self.problem_instance = problem_instance
|
||||
self.solution = None
|
||||
|
||||
def bind(self, local_or_meta):
|
||||
assert local_or_meta in self.available_improvers, f"the {local_or_meta} method is not available currently."
|
||||
self.algorithms.append(local_or_meta)
|
||||
self.name_method += ", improved with " + local_or_meta
|
||||
|
||||
def pop(self):
|
||||
self.algorithms.pop()
|
||||
self.name_method = self.name_method[::-1][self.name_method[::-1].find("improved"[::-1]) + len("improved") + 2:][
|
||||
::-1]
|
||||
|
||||
def compute_solution(self, verbose=True, return_value=True):
|
||||
self.solved = False
|
||||
if verbose:
|
||||
print(f"### solving with {self.algorithms} ####")
|
||||
start_time = t()
|
||||
self.solution = self.available_solvers[self.algorithms[0]](self.problem_instance)
|
||||
if not self.check_if_solution_is_valid():
|
||||
print(f"Error the solution of {self.algorithm_name} for problem {self.problem_instance.name} is not valid")
|
||||
if return_value:
|
||||
return False
|
||||
for i in range(1, len(self.algorithms)):
|
||||
improver = self.algorithms[i]
|
||||
self.solution = self.available_improvers[improver](self.solution, self.problem_instance)
|
||||
if not self.check_if_solution_is_valid():
|
||||
print(
|
||||
f"Error the solution of {self.algorithm_name} with {improver} for problem {self.problem_instance.name} is not valid")
|
||||
if return_value:
|
||||
return False
|
||||
|
||||
end_time = t()
|
||||
self.duration = np.around(end_time - start_time, 3)
|
||||
self.solved = True
|
||||
self.evaluate_solution()
|
||||
self._gap()
|
||||
if return_value:
|
||||
return self.solution
|
||||
|
||||
def plot_solution(self):
|
||||
assert self.solved, "You can't plot the solution, you need to compute it first!"
|
||||
plt.figure(figsize=(8, 8))
|
||||
self._gap()
|
||||
plt.title(f"{self.problem_instance.name} solved with {self.name_method} solver, gap {self.gap}")
|
||||
ordered_points = self.problem_instance.points[self.solution]
|
||||
plt.plot(ordered_points[:, 1], ordered_points[:, 2], 'b-')
|
||||
plt.show()
|
||||
|
||||
def check_if_solution_is_valid(self):
|
||||
rights_values = np.sum(
|
||||
[self.check_validation(i, self.solution[:-1]) for i in np.arange(self.problem_instance.nPoints)])
|
||||
# rights_values = np.sum(
|
||||
# [1 if np.sum(self.solution[:-1] == i) == 1 else 0 for i in np.arange(self.problem_instance.nPoints)])
|
||||
return rights_values == self.problem_instance.nPoints
|
||||
|
||||
def check_validation(self, node, solution):
|
||||
if np.sum(solution == node) == 1:
|
||||
return 1
|
||||
else:
|
||||
return 0
|
||||
|
||||
def evaluate_solution(self, return_value=False):
|
||||
total_length = 0
|
||||
starting_node = self.solution[0]
|
||||
from_node = starting_node
|
||||
for node in self.solution[1:]:
|
||||
total_length += self.problem_instance.dist_matrix[from_node, node]
|
||||
from_node = node
|
||||
|
||||
self.found_length = total_length
|
||||
if return_value:
|
||||
return total_length
|
||||
|
||||
def pass_and_check_if_solution_is_valid(self, solution):
|
||||
rights_values = np.sum(
|
||||
[self.check_validation(i, solution[:-1]) for i in np.arange(self.problem_instance.nPoints)])
|
||||
# rights_values = np.sum(
|
||||
# [1 if np.sum(solution[:-1] == i) == 1 else 0 for i in np.arange(self.problem_instance.nPoints)])
|
||||
return rights_values == self.problem_instance.nPoints
|
||||
|
||||
def _gap(self):
|
||||
self.evaluate_solution(return_value=False)
|
||||
self.gap = np.round(
|
||||
((self.found_length - self.problem_instance.best_sol) / self.problem_instance.best_sol) * 100, 2)
|
|
@ -1,129 +0,0 @@
|
|||
import numpy as np
|
||||
|
||||
from src.utils import compute_length
|
||||
|
||||
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 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)
|
||||
|
||||
|
||||
def best_nearest_neighbor(instance_):
|
||||
solutions, lengths = [], []
|
||||
for start in range(instance_.nPoints):
|
||||
new_solution = nearest_neighbor(instance_, starting_node=start)
|
||||
solutions.append(new_solution)
|
||||
lengths.append(compute_length(new_solution, instance_.dist_matrix))
|
||||
|
||||
if lengths is []:
|
||||
return None
|
||||
else:
|
||||
solution = solutions[np.argmin(lengths)]
|
||||
return solution
|
||||
|
||||
|
||||
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
|
||||
|
||||
|
||||
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
|
||||
iteration = 0
|
||||
while not end:
|
||||
if len(sol[str(from_city)]) == 1:
|
||||
if from_city == n1:
|
||||
return_value = False
|
||||
end = True
|
||||
elif iteration > 1:
|
||||
# print(f"iterazione {iteration}, 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)
|
||||
iteration += 1
|
||||
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)])
|
||||
iteration += 1
|
||||
return return_value
|
||||
|
||||
|
||||
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]
|
||||
iteration = 0
|
||||
while not end:
|
||||
for node_connected in sol[str(from_city)]:
|
||||
iteration += 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 iteration > 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
|
|
@ -1,4 +0,0 @@
|
|||
class IteratedLocalSearch:
|
||||
|
||||
def __call__(self):
|
||||
pass
|
|
@ -1,52 +0,0 @@
|
|||
import numpy as np
|
||||
|
||||
from src.utils 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_neigh(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_neigh(solution, instance):
|
||||
i, j = np.random.choice(np.arange(1, len(solution) - 1), 2, replace=False)
|
||||
i, j = np.sort([i, j])
|
||||
return sa_swap2opt(solution, i, j), gain(i, j, solution, instance.dist_matrix)
|
||||
|
||||
|
||||
def sa_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
|
|
@ -1,80 +0,0 @@
|
|||
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
|
||||
two_opt_tsp_sequence = swap2opt(tsp_sequence, i, j)
|
||||
two_opt_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([two_opt_len, first_shift_len, second_shift_len]), np.argmin(
|
||||
[two_opt_len, first_shift_len, second_shift_len])
|
||||
sequences = [two_opt_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(i, j, 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=2500):
|
||||
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()
|
|
@ -1,48 +0,0 @@
|
|||
import numpy as np
|
||||
|
||||
from src.utils 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()
|
19
src/utils.py
19
src/utils.py
|
@ -1,19 +0,0 @@
|
|||
import numpy as np
|
||||
|
||||
|
||||
def compute_length(solution, dist_matrix):
|
||||
total_length = 0
|
||||
starting_node = solution[0]
|
||||
from_node = starting_node
|
||||
for node in solution[1:]:
|
||||
total_length += dist_matrix[from_node, node]
|
||||
from_node = node
|
||||
return total_length
|
||||
|
||||
|
||||
def distance_euc(point_i, point_j):
|
||||
rounding = 0
|
||||
x_i, y_i = point_i[0], point_i[1]
|
||||
x_j, y_j = point_j[0], point_j[1]
|
||||
distance = np.sqrt((x_i - x_j) ** 2 + (y_i - y_j) ** 2)
|
||||
return round(distance, rounding)
|
12
sysinfo.md
Normal file
12
sysinfo.md
Normal file
|
@ -0,0 +1,12 @@
|
|||
# System information
|
||||
|
||||
Here is a summary of the system characteristics of the machine used to measure the execution time and
|
||||
execution results for my AI cup submission:
|
||||
|
||||
| Characteristic | Value |
|
||||
| :------------- | :----------: |
|
||||
| OS Version | MacOS Catalina 10.15.7 |
|
||||
| Kernel | Mach 19.6.0 |
|
||||
| Compiler used | Apple clang version 12.0.0 (clang-1200.0.32.21) |
|
||||
| Compiler optimization level | `-O2` |
|
||||
| CPU | Intel i7-8750H (12) @ 2.20GHz |
|
Reference in a new issue