2023-06-15 15:28:27 +02:00

1.2 MiB

Importing necessary libraries

In [16]:
from sklearn.cluster import KMeans
import matplotlib.pyplot as plt
import numpy as np
import random, time, math

Function to gereate cities :

In [9]:
def generate_cities(howmany, max_coordinates=1000):
    return [random.sample(range(max_coordinates), 2) for _ in range(howmany)]

Function to split citites into multiple clusters depending on the number of trucks

In [10]:
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)

    while True:
        # 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

Usual function to calculate distances

In [11]:
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))])

Function to show graphics

In [12]:
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

Function to calculate heuristic for each cluster

In [13]:
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

Main code

In [17]:
start_time_generate = time.time()
cities = generate_cities(20)      # generate 100 cities
cities[0] = [500, 500]          # placing depot at the center
stop_time_generate = time.time()

nb_truck = 3

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)
    print("Final solution for cluster ", i, ":", best_route)
    print("Total distance: ", total_distance(best_route))

plt.show()
8
8
9

---- TIME ----
generate cities time:  0.0009984970092773438
split cities time:  0.12865757942199707
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
Final solution for cluster  0 : [[214, 814], [477, 508], [500, 500], [500, 500], [80, 410], [30, 684], [82, 731], [169, 908]]
Total distance:  1507.4366001497751
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
Final solution for cluster  1 : [[421, 284], [500, 500], [500, 500], [685, 360], [692, 106], [473, 167], [85, 13], [389, 296]]
Total distance:  1810.3866608133385
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
Final solution for cluster  2 : [[500, 500], [500, 500], [804, 655], [955, 570], [987, 536], [894, 990], [628, 950], [590, 983], [540, 817]]
Total distance:  1836.8325428173955