From cff833f61d1b66f19a5b9b493a3d563a822a2852 Mon Sep 17 00:00:00 2001 From: Louis Date: Thu, 15 Jun 2023 15:51:51 +0200 Subject: [PATCH] =?UTF-8?q?Ajout=20des=20codes=20de=20tests=20et=20valeurs?= =?UTF-8?q?=20d=C3=A9mo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/01_cluster_splitter.py | 64 +++++++++ tests/02_cluster_recuit_live_animation.py | 108 ++++++++++++++ tests/03_cluster_recuit_no_animation.py | 115 +++++++++++++++ tests/04_cluster_ant_colony_no_animation.py | 133 ++++++++++++++++++ tests/clustering.py | 81 +++++++++++ tests/data_sample/15_cities_minimum_293.txt | 1 + tests/data_sample/48_cities_minimum_33523.txt | 3 + 7 files changed, 505 insertions(+) create mode 100644 tests/01_cluster_splitter.py create mode 100644 tests/02_cluster_recuit_live_animation.py create mode 100644 tests/03_cluster_recuit_no_animation.py create mode 100644 tests/04_cluster_ant_colony_no_animation.py create mode 100644 tests/clustering.py create mode 100644 tests/data_sample/15_cities_minimum_293.txt create mode 100644 tests/data_sample/48_cities_minimum_33523.txt diff --git a/tests/01_cluster_splitter.py b/tests/01_cluster_splitter.py new file mode 100644 index 0000000..08ed30d --- /dev/null +++ b/tests/01_cluster_splitter.py @@ -0,0 +1,64 @@ +from sklearn.cluster import KMeans +import matplotlib.pyplot as plt +import numpy as np +import random, time +from clustering import split_tour_across_clusters + +def generate_cities(nb, max_coords=1000): + return [random.sample(range(max_coords), 2) for _ in range(nb)] + +def plot_clusters(cities, clusters): + # Création d'une liste de couleurs pour les différents clusters + colors = ['b', 'g', 'r', 'c', 'm', 'y', 'k'] + + # Création d'un nouveau graphique + plt.figure() + + # Pour chaque cluster + for i, cluster in clusters.items(): + # Sélection d'une couleur pour le cluster + color = colors[i % len(colors)] + + # Pour chaque ville dans le cluster + for city_index in cluster: + # Récupération des coordonnées de la ville + city = cities[city_index] + + # Ajout de la ville au graphique + plt.scatter(city[0], city[1], c=color, s=20) + + # show first city in black and twice bigger + plt.scatter(cities[0][0], cities[0][1], c='k', s=200) + + + # Affichage du graphique + plt.show() + + +nb_ville = 100 +max_coords = 1000 +nb_truck = 4 + +# Define the coordinates of the cities +# And set depot at the first city in the middle of the map +start_time_generate = time.time() +cities = generate_cities(nb_ville, max_coords) +cities[0] = [max_coords/2, max_coords/2] +stop_time_generate = time.time() + +# Split the tour across clusters with nb_truck trucks +start_time_split = time.time() +clusters = split_tour_across_clusters(cities, nb_truck) +stop_time_split = time.time() + +# show the number of cities in each cluster +for cluster in clusters.values(): + print(len(cluster)) + +# show the time +print("\n---- TIME ----") +print("generate cities time: ", stop_time_generate - start_time_generate) +print("split cities time: ", stop_time_split - start_time_split) + +# show the clusters +plot_clusters(cities, clusters) \ No newline at end of file diff --git a/tests/02_cluster_recuit_live_animation.py b/tests/02_cluster_recuit_live_animation.py new file mode 100644 index 0000000..ca46ddf --- /dev/null +++ b/tests/02_cluster_recuit_live_animation.py @@ -0,0 +1,108 @@ +from sklearn.cluster import KMeans +import matplotlib.pyplot as plt +import numpy as np +import random, time, math +from clustering import split_tour_across_clusters + +random.seed(1) + +def generate_cities(nb, max_coords=1000): + return [random.sample(range(max_coords), 2) for _ in range(nb)] + +def distance(city1, city2): + return math.sqrt((city1[0] - city2[0]) ** 2 + (city1[1] - city2[1]) ** 2) + +def total_distance(cities): + return sum([distance(cities[i - 1], cities[i]) for i in range(len(cities))]) + +previous_route = None + +def draw_cities(cities, previous_route, color='b', title=' '): + plt.title(title) + + # If there's a previous route, we remove it. + if previous_route is not None: + previous_route.remove() + + x = [city[0] for city in cities] + y = [city[1] for city in cities] + x.append(x[0]) + y.append(y[0]) + + # We plot the route with the specified color and keep a reference to the Line2D object. + previous_route, = plt.plot(x, y, color=color, marker='x', linestyle='-') + + plt.draw() + plt.pause(0.005) + + # We return the reference so we can remove this route when a new one is found. + return previous_route + + +def simulated_annealing(cities, color='b', temperature=100000, cooling_rate=0.9999, temperature_ok=0.001): + interration = 0 + plt.ion() + current_solution = cities.copy() + best_solution = cities.copy() + previous_route = draw_cities(best_solution, None, color, 'Initial solution') + while temperature > temperature_ok: + new_solution = current_solution.copy() + # Swap two cities in the route + i = random.randint(0, len(new_solution) - 1) + j = random.randint(0, len(new_solution) - 1) + new_solution[i], new_solution[j] = new_solution[j], new_solution[i] + # Calculate the acceptance probability + current_energy = total_distance(current_solution) + new_energy = total_distance(new_solution) + delta = new_energy - current_energy + if delta < 0 or random.random() < math.exp(-delta / temperature): + current_solution = new_solution + if total_distance(current_solution) < total_distance(best_solution): + best_solution = current_solution + previous_route = draw_cities(best_solution, previous_route, color, 'Current best solution, total distance = ' + str(total_distance(best_solution)) + ', iteration = ' + str(interration)) + # Cool down + temperature *= cooling_rate + interration += 1 + plt.ioff() + return best_solution + +nb_ville = 100 +max_coords = 1000 +nb_truck = 4 +temperature = 10000 +cooling_rate = 0.999 +temperature_ok = 0.000001 + +start_time_generate = time.time() +cities = generate_cities(nb_ville, max_coords) # generate 100 cities +cities[0] = [max_coords/2, max_coords/2] # placing depot at the center +stop_time_generate = time.time() + + +start_time_split = time.time() +clusters = split_tour_across_clusters(cities, nb_truck) +stop_time_split = time.time() + +for cluster in clusters.values(): + print(len(cluster)) +print("\n---- TIME ----") +print("generate cities time: ", stop_time_generate - start_time_generate) +print("split cities time: ", stop_time_split - start_time_split) + +# create new figure for annealing paths +plt.figure() +colors = ['b', 'g', 'r', 'c', 'm', 'y', 'k'] + +for i, cluster_indices in enumerate(clusters.values()): + # Sélection d'une couleur pour le cluster + color = colors[i % len(colors)] + + # Récupération des coordonnées de la ville + cluster_cities = [cities[index] for index in cluster_indices] + + # Appel de la fonction simulated_annealing avec la couleur choisie + best_route = simulated_annealing(cluster_cities, color, temperature, cooling_rate, temperature_ok) + print("Final solution for cluster ", i, ":", best_route) + print("Total distance: ", total_distance(best_route)) + +plt.show() \ No newline at end of file diff --git a/tests/03_cluster_recuit_no_animation.py b/tests/03_cluster_recuit_no_animation.py new file mode 100644 index 0000000..3c07ce2 --- /dev/null +++ b/tests/03_cluster_recuit_no_animation.py @@ -0,0 +1,115 @@ +from sklearn.cluster import KMeans +import matplotlib.pyplot as plt +import numpy as np +import random, time, math +from clustering import split_tour_across_clusters + +def generate_cities(nb, max_coords=1000): + return [random.sample(range(max_coords), 2) for _ in range(nb)] + +def distance(city1, city2): + return math.sqrt((city1[0] - city2[0]) ** 2 + (city1[1] - city2[1]) ** 2) + +def total_distance(cities): + return sum([distance(cities[i - 1], cities[i]) for i in range(len(cities))]) + +previous_route = None + +def simulated_annealing(cities, temperature=10000, cooling_rate=0.9999, temperature_ok=0.001, cluster_index=0): + interration = 0 + current_solution = cities.copy() + best_solution = cities.copy() + while temperature > temperature_ok: + new_solution = current_solution.copy() + # Swap two cities in the route + i = random.randint(0, len(new_solution) - 1) + j = random.randint(0, len(new_solution) - 1) + new_solution[i], new_solution[j] = new_solution[j], new_solution[i] + # Calculate the acceptance probability + current_energy = total_distance(current_solution) + new_energy = total_distance(new_solution) + delta = new_energy - current_energy + if delta < 0 or random.random() < math.exp(-delta / temperature): + current_solution = new_solution + if total_distance(current_solution) < total_distance(best_solution): + best_solution = current_solution + # Cool down + temperature *= cooling_rate + interration += 1 + # Print every 1000 iterations + if interration % 1000 == 0: + print("Cluster", cluster_index, ": iteration", interration, "with current total distance", total_distance(current_solution)) + return best_solution + + +nb_ville = 20 +max_coords = 1000 +nb_truck = 4 +temperature = 10000 +cooling_rate = 0.999 +temperature_ok = 0.001 + +start_time_generate = time.time() +cities = generate_cities(nb_ville, max_coords) +cities[0] = [max_coords/2, max_coords/2] +stop_time_generate = time.time() + +start_time_split = time.time() +clusters = split_tour_across_clusters(cities, nb_truck) +stop_time_split = time.time() + +for cluster in clusters.values(): + print(len(cluster)) +print("\n---- TIME ----") +print("generate cities time: ", stop_time_generate - start_time_generate) +print("split cities time: ", stop_time_split - start_time_split) + +# create new figure for annealing paths +plt.figure() +colors = [ + '#1f77b4', # Bleu moyen + '#ff7f0e', # Orange + '#2ca02c', # Vert + '#d62728', # Rouge + '#9467bd', # Violet + '#8c564b', # Marron + '#e377c2', # Rose + '#7f7f7f', # Gris + '#bcbd22', # Vert olive + '#17becf', # Turquoise + '#1b9e77', # Vert Teal + '#d95f02', # Orange foncé + '#7570b3', # Violet moyen + '#e7298a', # Fuchsia + '#66a61e', # Vert pomme + '#e6ab02', # Jaune or + '#a6761d', # Bronze + '#666666', # Gris foncé + '#f781bf', # Rose clair + '#999999', # Gris moyen +] + +best_routes = [] + +for i, cluster_indices in enumerate(clusters.values()): + # Sélection d'une couleur pour le cluster + color = colors[i % len(colors)] + + # Récupération des coordonnées de la ville + cluster_cities = [cities[index] for index in cluster_indices] + + # Appel de la fonction simulated_annealing + best_route = simulated_annealing(cluster_cities, temperature, cooling_rate, temperature_ok) + best_routes.append((best_route, color)) + + print("Final solution for cluster ", i, ":", best_route) + print("Total distance: ", total_distance(best_route)) + +for i, (route, color) in enumerate(best_routes): + x = [city[0] for city in route] + y = [city[1] for city in route] + x.append(x[0]) + y.append(y[0]) + plt.plot(x, y, color=color, marker='x', linestyle='-', label=f"Cluster {i}") +plt.legend(loc="best") +plt.show() \ No newline at end of file diff --git a/tests/04_cluster_ant_colony_no_animation.py b/tests/04_cluster_ant_colony_no_animation.py new file mode 100644 index 0000000..bbde84f --- /dev/null +++ b/tests/04_cluster_ant_colony_no_animation.py @@ -0,0 +1,133 @@ +from sklearn.cluster import KMeans +import matplotlib.pyplot as plt +import numpy as np +import random, time, math +from clustering import split_tour_across_clusters + +random.seed(2) + +def generate_cities(nb, max_coords=1000): + return [random.sample(range(max_coords), 2) for _ in range(nb)] + +def distance(city1, city2): + return math.sqrt((city1[0] - city2[0]) ** 2 + (city1[1] - city2[1]) ** 2) + 1e-10 + +def total_distance(cities): + return sum([distance(cities[i - 1], cities[i]) for i in range(len(cities))]) + +class AntColony: + def __init__(self, cities, n_ants, alpha=1, beta=2, evaporation=0.5, intensification=2, max_time=0.1): + self.cities = cities + self.n = len(cities) + self.n_ants = n_ants + self.alpha = alpha + self.beta = beta + self.evaporation = evaporation + self.intensification = intensification + self.max_time = max_time + self.pheromones = [[1 / self.n for _ in range(self.n)] for __ in range(self.n)] + + def choose_next_city(self, ant): + unvisited_cities = [i for i in range(self.n) if i not in ant] + probabilities = [self.pheromones[ant[-1]][i] ** self.alpha * ((1 / distance(self.cities[ant[-1]], self.cities[i])) ** self.beta) for i in unvisited_cities] + total = sum(probabilities) + if total == 0: + probabilities = [1 / len(unvisited_cities) for _ in unvisited_cities] + else: + probabilities = [p / total for p in probabilities] + return np.random.choice(unvisited_cities, p=probabilities) + + def update_pheromones(self, ant): + pheromones_delta = self.intensification / total_distance([self.cities[i] for i in ant]) + for i in range(len(ant) - 1): + self.pheromones[ant[i]][ant[i+1]] += pheromones_delta + + def run(self): + best_ant = [] + best_distance = float('inf') + start_time = time.time() + while time.time() - start_time < self.max_time: + ants = [[random.randint(0, self.n - 1)] for _ in range(self.n_ants)] + for ant in ants: + for _ in range(self.n - 1): + ant.append(self.choose_next_city(ant)) + ant_distance = total_distance([self.cities[i] for i in ant]) + if ant_distance < best_distance: + best_distance = ant_distance + best_ant = ant.copy() + self.update_pheromones(ant) + self.pheromones = [[(1 - self.evaporation) * p for p in row] for row in self.pheromones] + return [self.cities[i] for i in best_ant] + +nb_ville = 200 +max_coords = 1000 +nb_truck = 4 +max_time = 5 + +start_time_generate = time.time() +cities = generate_cities(nb_ville, max_coords) +cities[0] = [max_coords/2, max_coords/2] +stop_time_generate = time.time() + +start_time_split = time.time() +clusters = split_tour_across_clusters(cities, nb_truck) +stop_time_split = time.time() + +for cluster in clusters.values(): + print(len(cluster)) +print("\n---- TIME ----") +print("generate cities time: ", stop_time_generate - start_time_generate) +print("split cities time: ", stop_time_split - start_time_split) + +# create new figure for annealing paths +plt.figure() +colors = [ + '#1f77b4', # Bleu moyen + '#ff7f0e', # Orange + '#2ca02c', # Vert + '#d62728', # Rouge + '#9467bd', # Violet + '#8c564b', # Marron + '#e377c2', # Rose + '#7f7f7f', # Gris + '#bcbd22', # Vert olive + '#17becf', # Turquoise + '#1b9e77', # Vert Teal + '#d95f02', # Orange foncé + '#7570b3', # Violet moyen + '#e7298a', # Fuchsia + '#66a61e', # Vert pomme + '#e6ab02', # Jaune or + '#a6761d', # Bronze + '#666666', # Gris foncé + '#f781bf', # Rose clair + '#999999', # Gris moyen +] + +best_routes = [] + +for i, cluster_indices in enumerate(clusters.values()): + # Sélection d'une couleur pour le cluster + color = colors[i % len(colors)] + + # Récupération des coordonnées de la ville + cluster_cities = [cities[index] for index in cluster_indices] + + # Appel de la fonction AntColony.run + ant_colony = AntColony(cluster_cities, n_ants=10, max_time=max_time) + best_route = ant_colony.run() + best_routes.append((best_route, color)) + + print("Final solution for cluster ", i, ":", best_route) + print("Total distance: ", total_distance(best_route)) + +for i, (route, color) in enumerate(best_routes): + x = [city[0] for city in route] + y = [city[1] for city in route] + x.append(x[0]) + y.append(y[0]) + plt.plot(x, y, color=color, marker='x', linestyle='-', label=f"Cluster {i}") + # add title with nb_ville, nb_truck and max_time + plt.title(f"nb_ville = {nb_ville}, nb_truck = {nb_truck}, max_time = {max_time}") + +plt.show() \ No newline at end of file diff --git a/tests/clustering.py b/tests/clustering.py new file mode 100644 index 0000000..fb8c959 --- /dev/null +++ b/tests/clustering.py @@ -0,0 +1,81 @@ +from sklearn.cluster import KMeans +import numpy as np + +def split_tour_across_clusters(cities, nb_truck): + if nb_truck == 1: + return {0: list(range(len(cities)))} + + # clustering initial + kmeans = KMeans(n_clusters=nb_truck, random_state=0).fit(cities) + clusters = {i:[] for i in range(nb_truck)} + + # assignation des indices des villes aux clusters + for i, label in enumerate(kmeans.labels_): + clusters[label].append(i) + + max_iterations = len(cities)**2 + iteration = 0 + + while True: + iteration += 1 + if iteration > max_iterations: + print("Le nombre maximum d'itérations a été atteint. La boucle a été interrompue.") + break + # calcul des tailles de clusters + cluster_sizes = {i:len(clusters[i]) for i in range(nb_truck)} + + # identification du cluster le plus grand et du plus petit + max_cluster = max(cluster_sizes, key=cluster_sizes.get) + min_cluster = min(cluster_sizes, key=cluster_sizes.get) + + # s'il n'y a pas de grande disparité, on arrête la boucle + if cluster_sizes[max_cluster] - cluster_sizes[min_cluster] <= 1: + break + + # calcul du centre de chaque cluster + cluster_centers = {i:np.mean([cities[index] for index in clusters[i]], axis=0) for i in range(nb_truck)} + + # calcul des distances entre le centre du cluster le plus grand et les autres + distances = {i:np.linalg.norm(cluster_centers[max_cluster]-cluster_centers[i]) for i in range(nb_truck)} + del distances[max_cluster] # on supprime la distance vers lui-même + + if nb_truck >= 3: + # on identifie les 2 clusters les plus proches + closest_clusters = sorted(distances, key=distances.get)[:2] + + + # parmi les deux clusters les plus proches, on choisit le plus petit + if cluster_sizes[closest_clusters[0]] <= cluster_sizes[closest_clusters[1]]: + target_cluster = closest_clusters[0] + else: + target_cluster = closest_clusters[1] + else: + closest_clusters = sorted(distances, key=distances.get)[:1] + target_cluster = closest_clusters[0] + + + + # si le transfert va créer une plus grande disparité, on arrête la boucle + if cluster_sizes[target_cluster] >= cluster_sizes[max_cluster]: + break + + # calcul des distances entre le centre du cluster cible et les villes du cluster le plus grand + distances_to_target = {index:np.linalg.norm(cluster_centers[target_cluster]-cities[index]) + for index in clusters[max_cluster]} + + # on identifie la ville la plus proche du centre du cluster cible + closest_city_index = min(distances_to_target, key=distances_to_target.get) + + # on transfère la ville du cluster le plus grand au cluster cible + clusters[target_cluster].append(closest_city_index) + clusters[max_cluster].remove(closest_city_index) + + # Ajout du point de départ et d'arrivée pour chaque cluster + depot_index = 0 + for cluster in clusters.values(): + if cluster[0] != depot_index: + cluster.insert(0, depot_index) + if cluster[-1] != depot_index: + cluster.append(depot_index) + + return clusters \ No newline at end of file diff --git a/tests/data_sample/15_cities_minimum_293.txt b/tests/data_sample/15_cities_minimum_293.txt new file mode 100644 index 0000000..9e077dd --- /dev/null +++ b/tests/data_sample/15_cities_minimum_293.txt @@ -0,0 +1 @@ +[[-0.0, 0.0], [-21.5, -7.3], [-28.9, -0.0], [-43.1, -14.6], [-50.5, -7.4], [-64.7, -21.9], [-72.1, -0.2], [-79.3, 21.4], [-65.1, 36.1], [-57.6, 43.3], [-50.6, 21.6], [-36.0, 21.6], [-29.1, 43.2], [-14.7, 43.4], [-0.1, 28.7], [-0.0, 0.0]] \ No newline at end of file diff --git a/tests/data_sample/48_cities_minimum_33523.txt b/tests/data_sample/48_cities_minimum_33523.txt new file mode 100644 index 0000000..7ae51b2 --- /dev/null +++ b/tests/data_sample/48_cities_minimum_33523.txt @@ -0,0 +1,3 @@ +[[6734.0, 1453.0], [2233.0, 10.0], [5530.0, 1424.0], [401.0, 841.0], [3082.0, 1644.0], [7608.0, 4458.0], [7573.0, 3716.0], [7265.0, 1268.0], [6898.0, 1885.0], [1112.0, 2049.0], [5468.0, 2606.0], [5989.0, 2873.0], [4706.0, 2674.0], [4612.0, 2035.0], [6347.0, 2683.0], [6107.0, 669.0], [7611.0, 5184.0], [7462.0, 3590.0], +[7732.0, 4723.0], [5900.0, 3561.0], [4483.0, 3369.0], [6101.0, 1110.0], [5199.0, 2182.0], [1633.0, 2809.0], [4307.0, 2322.0], [675.0, 1006.0], [7555.0, 4819.0], [7541.0, 3981.0], [3177.0, 756.0], [7352.0, 4506.0], [7545.0, 2801.0], [3245.0, 3305.0], [6426.0, 3173.0], [4608.0, 1198.0], [23.0, 2216.0], [7248.0, 3779.0], +[7762.0, 4595.0], [7392.0, 2244.0], [3484.0, 2829.0], [6271.0, 2135.0], [4985.0, 140.0], [1916.0, 1569.0], [7280.0, 4899.0], [7509.0, 3239.0], [10.0, 2676.0], [6807.0, 2993.0], [5185.0, 3258.0], [3023.0, 1942.0]] \ No newline at end of file