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
|
ipython_config.py
|
||||||
|
|
||||||
# pyenv
|
# 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:
|
# intended to run in multiple environments; otherwise, check them in:
|
||||||
# .python-version
|
# .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
|
import os
|
||||||
|
|
||||||
dir = "/Users/maggicl/Git/AI2020BsC/c_prob/"
|
dir = "c_prob/"
|
||||||
def ant_colony_opt(instance):
|
def ant_colony_opt(instance):
|
||||||
fname = dir + instance.name + ".txt"
|
fname = dir + instance.name + ".txt"
|
||||||
|
|
||||||
|
@ -12,7 +12,7 @@ def ant_colony_opt(instance):
|
||||||
# single core params
|
# single core params
|
||||||
params = {
|
params = {
|
||||||
"eil76": (0.9, 8, 0.4, 1000, 500, 200, 30), # 543
|
"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
|
"ch130": (0.9, 8, 0.4, 1000, 1750, 25, 30), # 6212
|
||||||
"kroA100": (0.9, 8, 0.4, 1000, 750, 100, 30), # 21378
|
"kroA100": (0.9, 8, 0.4, 1000, 750, 100, 30), # 21378
|
||||||
"lin318": (0.9, 8, 0.4, 1000, 500, 32, 30), # 43171
|
"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
|
"pr439": (0.9, 8, 0.4, 1000, 450, 24, 2), # 11734
|
||||||
"rat783": (0.9, 8, 0.4, 1000, 450, 24, 2), # 9232
|
"rat783": (0.9, 8, 0.4, 1000, 450, 24, 2), # 9232
|
||||||
"u1060": (0.9, 8, 0.4, 1000, 350, 6, 3), # 238025
|
"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]
|
alpha, beta, evap, weight, ants, loops, optruns = params[instance.name]
|
||||||
|
|
||||||
# Call C++ program
|
# Call C++ program
|
||||||
cmd = dir + "aco " + str(instance.nPoints) + " " + str(loops) + " " + str(ants) + \
|
cmd = dir + "aco " + str(instance.nPoints) + " " + str(loops) + " " + str(ants) + \
|
||||||
" " + str(alpha) + " " + str(beta) + " " + str(evap) + " " + str(weight) + " " + str(optruns) + \
|
" " + str(alpha) + " " + str(beta) + " " + str(evap) + " " + str(weight) + \
|
||||||
" < " + fname + " &2>/dev/null"
|
" " + str(optruns) + " < " + fname + " &2>/dev/null"
|
||||||
print(cmd)
|
print(cmd)
|
||||||
solution = eval(os.popen(cmd).read())
|
solution = eval(os.popen(cmd).read())
|
||||||
solution.append(solution[0])
|
solution.append(solution[0])
|
||||||
print(solution)
|
|
||||||
print(len(solution))
|
|
||||||
return solution
|
return solution
|
|
@ -1,11 +1,8 @@
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from matplotlib import pyplot as plt
|
from matplotlib import pyplot as plt
|
||||||
|
from aco.utils import distance_euc
|
||||||
from src.utils import distance_euc
|
|
||||||
|
|
||||||
|
|
||||||
class ProblemInstance:
|
class ProblemInstance:
|
||||||
|
|
||||||
def __init__(self, name_tsp):
|
def __init__(self, name_tsp):
|
||||||
self.exist_opt = False
|
self.exist_opt = False
|
||||||
self.optimal_tour = None
|
self.optimal_tour = None
|
||||||
|
@ -32,7 +29,9 @@ class ProblemInstance:
|
||||||
self.points[i, 2] = line_i[2]
|
self.points[i, 2] = line_i[2]
|
||||||
|
|
||||||
self.create_dist_matrix()
|
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"]:
|
"../problems/kroA100.tsp"]:
|
||||||
self.exist_opt = True
|
self.exist_opt = True
|
||||||
file_object = open(self.file_name.replace(".tsp", ".opt.tour"))
|
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 glob
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
from src.io_tsp import ProblemInstance
|
from aco.io_tsp import ProblemInstance
|
||||||
from src.TSP_solver import TSPSolver, available_improvers, available_solvers
|
from aco.TSP_solver import TSPSolver
|
||||||
import numpy as np
|
import os
|
||||||
|
|
||||||
|
|
||||||
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()
|
|
||||||
|
|
||||||
|
|
||||||
def run(show_plots=False, verbose=False):
|
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 = glob.glob('./problems/*.tsp')
|
||||||
|
|
||||||
# problems = ["./problems/fl1577.tsp"]
|
problems = ["./problems/fl1577.tsp"]
|
||||||
|
|
||||||
solvers_names = available_solvers.keys()
|
|
||||||
improvers_names = available_improvers.keys()
|
|
||||||
results = []
|
results = []
|
||||||
index = []
|
index = []
|
||||||
for problem_path in problems:
|
for problem_path in problems:
|
||||||
|
@ -42,35 +20,30 @@ def run(show_plots=False, verbose=False):
|
||||||
if show_plots:
|
if show_plots:
|
||||||
prob_instance.plot_data()
|
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:
|
with open("sol/" + prob_instance.name + ".sol", "w") as f:
|
||||||
solver = TSPSolver(solver_name, prob_instance)
|
print(solution, file=f)
|
||||||
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()
|
|
||||||
|
|
||||||
if prob_instance.exist_opt and show_plots:
|
if show_plots:
|
||||||
solver = TSPSolver("optimal", prob_instance)
|
|
||||||
solver.solved = True
|
|
||||||
solver.solution = np.concatenate([prob_instance.optimal_tour, [prob_instance.optimal_tour[0]]])
|
|
||||||
solver.plot_solution()
|
solver.plot_solution()
|
||||||
|
|
||||||
#index = pd.MultiIndex.from_tuples(index, names=['problem', 'method'])
|
return pd.DataFrame(results, index=index,
|
||||||
|
columns=["tour length", "optimal solution",
|
||||||
#return None
|
"gap", "time to solve"])
|
||||||
return pd.DataFrame(results, index=index, columns=["tour length", "optimal solution", "gap", "time to solve"])
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
df = run(show_plots=True, verbose=True)
|
df = run(show_plots=False, verbose=True)
|
||||||
df.to_csv("./results.csv")
|
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