a3-algorithmique-avancee/Projet_algo.ipynb
2023-06-21 13:30:57 +02:00

960 KiB
Raw Blame History

Project Algorithmique avancée

Contexte du projet

L'ADEME a lancé un appel pour promouvoir de nouvelles solutions de mobilité. CesiCDP, avec son équipe de 4 personnes, répond à cet appel en se concentrant sur la gestion des tournées de livraison. L'objectif est de minimiser la durée totale de la tournée en tenant compte des contraintes de trafic et autres. Un financement attractif est en jeu pour développer l'activité de CesiCDP. L'ajout de contraintes supplémentaires au problème rendra l'étude plus réaliste et plus difficile à résoudre, mais démontrera l'expertise de l'équipe.

Notre objectif est de développer un algorithme qui nous permettra de passer par tous les points de livraison dans un temps optimisé.

Contraintes choisies

La contrainte que nous avons choisie est la suivante :

  • Avoir plusieurs camions disponibles simultanément pour effectuer les livraisons.

Formulation du problème

Considérons un graphe G=(V,E), où V est l'ensemble des villes (ou points de livraison) et E est l'ensemble des routes entre les villes. Il y a k camions disponibles pour effectuer les livraisons.

Le problème consiste à trouver un itinéraire pour chaque camion, de sorte que toutes les livraisons soient effectuées dans le temps le plus court possible.

Le problème que nous avons avec les contraintes mentionnées ci-dessus est le VRP (Problème de Routage des Véhicules).

Contraintes du problème

Liste des contraintes du problème :

  • Tous les clients doivent être servis
  • Un client ne peut être servi que par un seul véhicule.
  • En quittant un client, un véhicule ne peut se rendre que chez un seul autre client.

Nous allons donc affecter chaque client à une route desservie par un seul véhicule.

Démonstration de la complexité du problème de routage des véhicules (VRP)

Introduction

Le Problème de Routage des Véhicules (VRP) est une extension du Problème du Voyageur de Commerce (TSP) et est connu pour être un problème NP-difficile. Nous allons démontrer la complexité du VRP en nous basant sur le problème de la chaîne Hamiltonienne, qui est connu pour être NP-complet.

Définitions préliminaires

  • Problème de la chaîne Hamiltonienne : le problème consiste à déterminer si un graphe non orienté donné possède une chaîne Hamiltonienne, c'est-à-dire un chemin qui visite chaque sommet exactement une fois.

  • Problème du voyageur de commerce (TSP) : le problème consiste à trouver le circuit le plus court possible qui visite chaque ville d'un ensemble donné de villes et revient à la ville d'origine.

  • Problème de routage des véhicules (VRP) : le problème consiste à livrer une série de clients avec plusieurs véhicules tout en minimisant le coût total, tel que le temps de livraison ou la distance parcourue.

Preuve de complexité du TSP

Le TSP (Traveling Salesman Problem) est une extension du problème de chaîne Hamiltonienne. En fait, un cas particulier du TSP est le problème de chaîne Hamiltonienne, dans lequel toutes les arêtes ont le même poids (ou coût). Dans ce cas, trouver le circuit le plus court dans le TSP revient à trouver une chaîne Hamiltonienne dans le graphe. Puisque le problème de chaîne Hamiltonienne est NP-complet, le TSP doit être au moins aussi difficile, donc le TSP est NP-complet.

Preuve de complexité du VRP

Le VRP (Vehicle Routing Problem) est une extension du TSP (Traveling Salesman Problem). En fait, un cas particulier du VRP est le TSP où il n'y a qu'un seul véhicule disponible pour effectuer les livraisons. Dans ce cas, trouver la solution au VRP revient à trouver la solution au TSP.

Puisque le TSP est NP-complet, le VRP doit être au moins aussi difficile, donc le VRP est NP-difficile. De plus, le VRP introduit des contraintes supplémentaires, telles que la présence de plusieurs véhicules et éventuellement des capacités de véhicule et des fenêtres de temps, ce qui le rend encore plus complexe.

Conclusion

En conclusion, nous avons démontré que le problème de VRP (Vehicle Routing Problem) est un problème NP-difficile en le réduisant au problème TSP (Traveling Salesman Problem) NP-complet, qui à son tour peut être réduit au problème de chaîne Hamiltonienne NP-complet. Cette démonstration met en évidence la difficulté de résoudre le VRP, en particulier pour de grandes instances. En pratique, des méthodes (méta)heuristiques et approximatives sont souvent utilisées pour résoudre le VRP, comme nous le verrons plus tard.

Modélisation mathématique du VRP

Paramètres

$V=\{0,1,2,...,n_v\}$ : l'ensemble des villes, où 0 est la base (ou dépôt), $1,2,...,n_v$ sont les villes de livraison. $n_v+1$ sera le dépôt pour le retour.
$K=\{1,2,...,k\}$ : tous les camions.
$E$ représente les arcs entre deux clients $i,j \in V$
$G=(V,E)$ : le graphe avec V comme sommets (villes) et E comme arêtes
$d_{ij}$ : distance (ou temps de voyage) de la ville i à la ville $j$ (coût de voyage)

Variables de décision

$x_{ijk}$ : variable binaire qui vaut 1 si le camion $k$ va d'une ville $i$ à une ville $j$, sinon elle vaut 0.

Fonction objective

$$\min \sum_{k∈K} \sum_{i∈V} \sum_{j∈V} d_{ij} x_{ijk} $$

Contraintes du VRP

  • Chaque ville est visitée qu'une et une seule fois : $$\sum_{k \in K} \sum_{j \in V} x_{ijk} = 1, \forall i \in V, i \ne 0$$

  • Si un camion arrive à une ville il doit en partir : $$\sum_{i \in V} x_{ijk} = \sum_{j \in V} x_{ijk}, \forall k \in K, \forall i \in V, \forall j \in V $$

  • Contrainte d'élimination des sous-tours (pour s'assurer que chaque camion effectue un tour complet) $$\sum_{i \in S, j \notin S} x_{ijk} \geq 1, \forall k \in K, \forall \; sous-ensemble \; S \; de \; V, 0 \in S, S \ne V $$

Modélisation mathématique du VRPTW

Note: Les éléments suivants viennent en addition à la modélisation mathématique du VRP.

Variable de décision

$[a_i, b_i]$ la fenêtre de temps pour le client $i$
$T_{ik}$ le moment auquel le véhicule $k$ arrive au client $i$

**Contrainte de TW **

$T_{ik}$ est le moment auquel le véhicule $k$ arrive au client $i$.
$d_{ij}$ est le temps nécessaire pour se rendre du client $i$ au client $j$.
$T_{jk}$ est le moment auquel le véhicule $k$ arrive au client $j$.
$M$ est une grande constante

  • Les fenêtres de temps doivent être respectées :

$$ a_j \leq T_{jk} \leq b_j,∀k∈ K ,∀j∈V$$

  • Les temps d'arrivé aux différentes villes pour chaque camion doivent être cohérent :

$$ T_{ik} + d_{ij} \leq T_{jk} + M(1-x_{ijk}), ∀i, j ∈ V, ∀k∈ K $$

Algorithme de résolution

Dans le cadre de notre projet, nous avons envisagé d'adopter trois algorithmes heuristiques distincts pour résoudre notre problème d'optimisation de trajet entre les villes. Notre objectif est de les comparer afin de déterminer celui qui nous fournira l'itinéraire le plus proche de l'optimal. Les trois algorithmes que nous allons mettre en œuvre sont les suivants :

  • L'algorithme des colonies de fourmis
  • L'algorithme du recuit simulé
  • L'algorithme des essaims particulaires

Nous examinerons ensuite leur mise en œuvre et leur fonctionnement pour évaluer leur performance.

Algorithme par colonies de fourmis

L'algorithme par colonies de fourmis est une approche inspirée du comportement des fourmis dans la recherche de chemins optimaux. Cet algorithme permet de résoudre des problèmes d'optimisation de manière efficace.

In [1]:
# Variables to edit:
nb_ville = 100
max_coords = 1000
nb_truck = 4
max_time = 4
nb_ants = 10
alpha = 1
beta = 6
evaporation = 0.5
intensification = 2
In [2]:
# Import necessary libraries
import matplotlib.pyplot as plt
import random, time
from tests.libs.clustering import split_tour_across_clusters
from tests.libs.aco import AntColony, total_distance

# Function to generate city coordinates
def generate_cities(nb, max_coords=1000):
    # Generate a list of nb cities, each with random coordinates within the range of max_coords
    return [random.sample(range(max_coords), 2) for _ in range(nb)]

# Calculate maximum time per cluster
max_time_per_cluster = max_time / nb_truck

# Track the time taken to generate cities
start_time_generate = time.time()
# Generate cities
cities = generate_cities(nb_ville, max_coords)
# Set the first city's coordinates at the center
cities[0] = [max_coords/2, max_coords/2]
stop_time_generate = time.time()

# Track the time taken to split cities across clusters
start_time_split = time.time()
# Split cities into clusters
clusters = split_tour_across_clusters(cities, nb_truck)
stop_time_split = time.time()

# Print out the time taken for each process
print("\n---- TIME ----")
print("Generate cities time: ", stop_time_generate - start_time_generate)
print("Split cities time: ", stop_time_split - start_time_split)

# Initialize a new figure for displaying paths
plt.figure()
plt.title("Ant Colony Optimization")
# List of colors for plotting
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
]

# Initialize a list for storing the best routes
best_routes = []

print("\n---- CLUSTER ----")
# Iterate through each cluster
for i, cluster_indices in enumerate(clusters.values()):
    # Select a color for the cluster
    color = colors[i % len(colors)]
    
    # Retrieve city coordinates for the cluster
    cluster_cities = [cities[index] for index in cluster_indices]

    # Run the AntColony algorithm and store the best route
    ant_colony = AntColony(cluster_cities, n_ants=nb_ants, max_time=max_time_per_cluster, alpha=alpha, beta=beta, evaporation=evaporation, intensification=intensification)
    best_route = ant_colony.run()
    best_routes.append((best_route, color))

    # Print the total distance for the cluster
    print("Total distance for cluster {} ({} cities) : {}".format(i+1, len(cluster_cities), total_distance(best_route)))

# Calculate and print total distance for all clusters
full_total_distance = 0
for route, color in best_routes:
    full_total_distance += total_distance(route)
print("Total distance for all clusters: ", full_total_distance)

# Plot each route
for i, (route, color) in enumerate(best_routes):
    x = [city[0] for city in route]
    y = [city[1] for city in route]
    # Close the loop of the route
    x.append(x[0])
    y.append(y[0])
    # Plot the route
    plt.plot(x, y, color=color, marker='o', linestyle='-', label=f"Cluster {i}")

# Display the plot
plt.show()
---- TIME ----
Generate cities time:  0.0
Split cities time:  0.14944958686828613

---- CLUSTER ----
Total distance for cluster 1 (27 cities) : 2164.794990827718
Total distance for cluster 2 (27 cities) : 2122.813485403375
Total distance for cluster 3 (26 cities) : 2278.707718333369
Total distance for cluster 4 (27 cities) : 2599.5739910855473
Total distance for all clusters:  9165.890185650009
No description has been provided for this image

Algorithm du recuit simulé

L'algorithme du recuit simulé est une technique d'optimisation inspirée du processus de recuit utilisé dans la métallurgie. Il permet d'explorer de manière probabiliste l'espace des solutions en acceptant occasionnellement des mouvements qui peuvent initialement sembler moins favorables. Cette approche permet de trouver des solutions globales et d'éviter de rester bloqué dans des optima locaux.

In [3]:
# Variables to edit:
nb_ville = 100
max_coords = 1000
nb_truck = 4
temperature = 10000
cooling_rate = 0.9999
temperature_ok = 0.001
In [4]:
# Import necessary libraries
import matplotlib.pyplot as plt
import random, time
from tests.libs.clustering import split_tour_across_clusters
from tests.libs.simulated_annealing import SimulatedAnnealing, total_distance

# Function to generate city coordinates
def generate_cities(nb, max_coords=1000):
    # Generate a list of nb cities, each with random coordinates within the range of max_coords
    return [random.sample(range(max_coords), 2) for _ in range(nb)]

# Track the time taken to generate cities
start_time_generate = time.time()
# Generate cities
cities = generate_cities(nb_ville, max_coords)
# Set the first city's coordinates at the center
cities[0] = [max_coords/2, max_coords/2]
stop_time_generate = time.time()

# Track the time taken to split cities across clusters
start_time_split = time.time()
# Split cities into clusters
clusters = split_tour_across_clusters(cities, nb_truck)
stop_time_split = time.time()

# Print out the time taken for each process
print("\n---- TIME ----")
print("Generate cities time: ", stop_time_generate - start_time_generate)
print("Split cities time: ", stop_time_split - start_time_split)

# Initialize a new figure for displaying paths
plt.figure()
plt.title("Simulated Annealing")
# List of colors for plotting
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
]

# Initialize a list for storing the best routes
best_routes = []

# Iterate through each cluster
for i, cluster_indices in enumerate(clusters.values()):
    # Select a color for the cluster
    color = colors[i % len(colors)]
    
    # Retrieve city coordinates for the cluster
    cluster_cities = [cities[index] for index in cluster_indices]

    # Run the SimulatedAnnealing algorithm and store the best route
    simulated_annealing = SimulatedAnnealing(cluster_cities, temperature=10000, cooling_rate=0.999, temperature_ok=0.01)
    best_route = simulated_annealing.run()
    best_routes.append((best_route, color))

    # Print the total distance for the cluster
    print("Total distance for cluster {} ({} cities) : {}".format(i+1, len(cluster_cities), total_distance(best_route)))

# Calculate and print total distance for all clusters
full_total_distance = 0
for route, color in best_routes:
    full_total_distance += total_distance(route)
print("Total distance for all clusters: ", full_total_distance)

# Plot each route
for i, (route, color) in enumerate(best_routes):
    x = [city[0] for city in route]
    y = [city[1] for city in route]
    # Close the loop of the route
    x.append(x[0])
    y.append(y[0])
    # Plot the route
    plt.plot(x, y, color=color, marker='o', linestyle='-', label=f"Cluster {i}")
# Add legend to the plot
plt.legend(loc="best")

# Display the plot
plt.show()
---- TIME ----
Generate cities time:  0.0009992122650146484
Split cities time:  0.05595564842224121
Total distance for cluster 1 (27 cities) : 2812.9786606142725
Total distance for cluster 2 (27 cities) : 2870.0631219087168
Total distance for cluster 3 (27 cities) : 2660.992000654059
Total distance for cluster 4 (27 cities) : 3193.529103806893
Total distance for all clusters:  11537.562886983942
No description has been provided for this image

Algorithme par essaim de particules

L'algorithme des essaims particulaires est une méthode d'optimisation basée sur le comportement collectif des essaims d'animaux, tels que les oiseaux ou les poissons. Il utilise des particules qui se déplacent dans l'espace de recherche et communiquent entre elles pour trouver des solutions optimales. Cet algorithme est efficace pour explorer l'espace des solutions et trouver des optima locaux et globaux.

In [5]:
# Variables to edit:
num_cities = 50
num_trucks = 5
num_particles = 50
max_iterations = 300
inertia_weight = 2
cognitive_weight = 1
social_weight = 1.5
incompleteness_probability = 0.8
start_time = 8
end_time = 18
infinite_distance_value = 1e6
In [6]:
# Importing necessary libraries
import random
import numpy as np
import networkx as nx
import matplotlib.pyplot as plt
import matplotlib
from IPython.display import display, Markdown
import matplotlib.cm as cm
from tests.libs.pso import Particle

# Function to generate a matrix of distances between cities
def generate_distance_matrix(num_cities, incompleteness_probability):
    # Initialize an empty matrix with zeros
    distances = np.zeros((num_cities, num_cities))
    # Iterate over each city
    for i in range(num_cities):
        # For each city, iterate over every other city to calculate distance
        for j in range(i+1, num_cities):
            # With certain probability, some distances will be missing (infinite)
            if random.random() < incompleteness_probability:
                distances[i, j] = float('inf')
                distances[j, i] = float('inf')
            else:
                # Assign random integer value to the distances between i and j
                distances[i, j] = random.randint(1, 10)
                distances[j, i] = distances[i, j]
    return distances

# Function to generate random time windows for each city
def generate_time_windows(num_cities, start_time, end_time):
    time_windows = []
    # For each city, generate a random start and end time
    for _ in range(num_cities):
        start = random.randint(start_time, end_time - 1)
        end = random.randint(start + 1, end_time)
        time_windows.append((start, end))
    return time_windows

# Function to generate a graph representation of the cities and the distances between them
def generate_city_graph(distances, time_windows):
    num_cities = distances.shape[0]
    # Create an empty graph
    G = nx.Graph()
    # Add cities as nodes with their corresponding time windows
    for i in range(num_cities):
        G.add_node(i+1, time_window=time_windows[i])
    # Add edges between cities with their corresponding distances
    for u in range(num_cities):
        for v in range(u+1, num_cities):
            if distances[u, v] != float('inf'):
                G.add_edge(u+1, v+1, distance=distances[u, v])
    return G

# Generate the distances and time windows
distances = generate_distance_matrix(num_cities, incompleteness_probability)
time_windows = generate_time_windows(num_cities, start_time, end_time)
# Generate the city graph
G = generate_city_graph(distances, time_windows)

# Initialize the particles and the global best position and cost
particles = []
global_best_position = np.random.permutation(range(1, num_cities+1))
global_best_cost = float('inf')
for _ in range(num_particles):
    particle = Particle(num_cities, num_trucks, distances, time_windows, infinite_distance_value)
    particles.append(particle)
    particle_cost = particle.evaluate_cost()
    if particle_cost < global_best_cost:
        global_best_cost = particle_cost
        global_best_position = particle.position.copy()

# Main loop for the particle swarm optimization algorithm
iteration = 0
best_costs = []
while iteration < max_iterations:
    for particle in particles:
        particle.update_velocity(global_best_position, inertia_weight, cognitive_weight, social_weight)
        particle.update_position()
        particle_cost = particle.evaluate_cost()
        if particle_cost < global_best_cost:
            global_best_cost = particle_cost
            global_best_position = particle.position.copy()
    best_costs.append(global_best_cost)
    iteration += 1

# Display the best solution found
best_position_str = ", ".join(str(node) for node in global_best_position)
best_cost_str = str(global_best_cost)
markdown_text = f"### Best solution found:\n\n- **City Positions**: {best_position_str}\n- **Total Cost**: {best_cost_str}"
display(Markdown(markdown_text))

# ---------------- Plot the best cost for each iteration ----------------------------------------------------------------------

# Compute the layout positions for the nodes using the spring layout algorithm
pos = nx.spring_layout(G)

# Create labels for each node in the graph
labels = {node: str(node) for node in G.nodes()}

# Draw the nodes of the graph using the computed positions
nx.draw_networkx_nodes(G, pos)

# Draw the edges of the graph using the computed positions
nx.draw_networkx_edges(G, pos)

# Draw the labels for the nodes using the computed positions and labels
nx.draw_networkx_labels(G, pos, labels)

# Set the title of the plot
plt.title("Graph of cities with time windows")

# Turn off the axis display
plt.axis("off")

# Display the plot
plt.show()

# ---------------- Plot Truck Paths ----------------------------------------------------------------------

# Initialize an empty list for truck paths
truck_paths = []

# Create a colormap for the trucks
cmap = matplotlib.colormaps['gist_ncar']
colors = [cmap(i/num_trucks) for i in range(num_trucks)]

# Split the global best position into separate paths for each truck
for truck in range(num_trucks):
    path = []
    for i in range(len(global_best_position)):
        if i % num_trucks == truck:
            city = global_best_position[i]
            path.append(int(city))
    path.append(path[0])  # Making the path circular by adding the first city to the end of the path
    truck_paths.append(path)

# Plot the truck paths on a graph
plt.figure(figsize=(10, 6))
plt.title("Truck Paths")
plt.axis("off")
for i in range(num_trucks):
    path = truck_paths[i]
    path_str = " -> ".join(str(city) for city in path)
    text = f"Truck {i+1}: {path_str}"
    display(Markdown(text))
    edges = [(path[j], path[j+1]) for j in range(len(path)-1)]
    nx.draw_networkx_edges(G, pos, edgelist=edges, edge_color=colors[i], label=f"Truck {i+1}")
    nx.draw_networkx_labels(G, pos, labels)
plt.legend()
plt.show()

# ---------------- Plot path cost for Each Truck ----------------------------------------------------------------------

# Initialize an empty list for the total cost of each path
path_costs = []

# Calculate the total cost of each path
for path in truck_paths:
    path_cost = 0
    for i in range(len(path) - 1):
        u, v = path[i] - 1, path[i + 1] - 1  # Adjust the indices
        # If the distance is infinite, use a replacement value.
        distance = distances[u, v] if distances[u, v] != float('inf') else infinite_distance_value
        path_cost += distance
    path_costs.append(path_cost)

# Plot a histogram of the path costs
plt.figure(figsize=(10, 6))
plt.bar(range(1, num_trucks + 1), path_costs, color=cm.rainbow(np.linspace(0, 1, num_trucks)))
plt.xlabel("Truck")
plt.ylabel("Path Cost")
plt.title("Path Cost for Each Truck")
plt.show()

# ---------------- Plot Evolution of the Best Cost Over Iterations ----------------------------------------------------------------------

# Plot a graph of the evolution of the best cost
plt.figure(figsize=(10, 6))
plt.plot(range(1, max_iterations + 1), best_costs)
plt.title("Evolution of the Best Cost Over Iterations")
plt.xlabel("Iteration")
plt.ylabel("Best Cost")
plt.grid(True)
plt.show()

Best solution found:

  • City Positions: 31, 27, 40, 38, 2, 7, 34, 46, 42, 4, 32, 47, 18, 30, 44, 5, 20, 33, 1, 36, 43, 10, 19, 6, 11, 21, 13, 37, 16, 23, 25, 17, 22, 28, 29, 49, 50, 26, 8, 35, 48, 3, 24, 45, 14, 15, 9, 39, 12, 41
  • Total Cost: 96000856.0
No description has been provided for this image

Truck 1: 31 -> 7 -> 32 -> 5 -> 43 -> 21 -> 25 -> 49 -> 48 -> 15 -> 31

Truck 2: 27 -> 34 -> 47 -> 20 -> 10 -> 13 -> 17 -> 50 -> 3 -> 9 -> 27

Truck 3: 40 -> 46 -> 18 -> 33 -> 19 -> 37 -> 22 -> 26 -> 24 -> 39 -> 40

Truck 4: 38 -> 42 -> 30 -> 1 -> 6 -> 16 -> 28 -> 8 -> 45 -> 12 -> 38

Truck 5: 2 -> 4 -> 44 -> 36 -> 11 -> 23 -> 29 -> 35 -> 14 -> 41 -> 2

No description has been provided for this image
No description has been provided for this image
No description has been provided for this image

Plan d'expérience et statistiques

L'objectif du plan d'expérience est de comparer différentes configurations et paramètres des algorithmes afin d'optimiser leurs performances. Cela nous permettra d'identifier les réglages les plus efficaces pour obtenir un itinéraire proche de l'optimal.

Algorithme par colonies de fourmis (avec suite de valeurs : ATT48)

D'après de nombreuses recherches sur internet, les valeurs optimales des paramètres de l'algorithme des colonies de fourmis sont les suivantes :

  • Nombre de fourmis (nb_ants) : 10
  • Facteur d'importance de la phéromone (alpha) : 1
  • Facteur d'importance de la distance (beta) : 4
  • Facteur d'évaporation (evaporation) : 0.5
  • Facteur de dépot de phéromones (intensification) : 2

A l'aide des scripts Python suivants, nous pouvons tester pour une configuration précise et pour chaque limite de temps (max_times), la variation des résultats en fonction des valeurs de Alpha et Beta

Note: Pour la réalisation de nos tests, nous avons utilisé la suite de valeur ATT48, donc l'optimal est connu : 33523

Identification de la valeur optimale de Alpha
In [17]:
# Variables to edit:
max_times = [0.1, 0.2, 0.5, 1, 2]
alphas = [1, 2, 4, 6]


n_ants = 10
beta = 4
evaporation = 0.5
intensification = 2
n_runs = 2  # Number of times each configuration will be run to obtain an average
In [15]:
import matplotlib.pyplot as plt
import numpy as np
from tests.libs.clustering import split_tour_across_clusters
from tests.libs.aco import AntColony, total_distance

cities = [[6734, 1453], [2233, 10], [5530, 1424], [401, 841], [3082, 1644], [7608, 4458], [7573, 3716], [7265, 1268], [6898, 1885], [1112, 2049], [5468, 2606], [5989, 2873], [4706, 2674], [4612, 2035], [6347, 2683], [6107, 669], [7611, 5184], [7462, 3590], [7732, 4723], [5900, 3561], [4483, 3369], [6101, 1110], [5199, 2182], [1633, 2809], [4307, 2322], [675, 1006], [7555, 4819], [7541, 3981], [3177, 756], [7352, 4506], [7545, 2801], [3245, 3305], [6426, 3173], [4608, 1198], [23, 2216], [7248, 3779], [7762, 4595], [7392, 2244], [3484, 2829], [6271, 2135], [4985, 140], [1916, 1569], [7280, 4899], [7509, 3239], [10, 2676], [6807, 2993], [5185, 3258], [3023, 1942]]
optimal = 33523

average_best_route_per_max_time_per_alpha = []

def calculer_temps_total(max_times, alphas, n_runs):
    temps_total = 0
    for max_time in max_times:
        for alpha in alphas:
            temps_total += max_time * n_runs
    return temps_total

print("The script will take at least : {} seconds".format(calculer_temps_total(max_times, alphas, n_runs)))

for alpha in alphas:
    average_best_route_per_max_time = []
    for max_time in max_times:

        total_best_route_length = 0

        for _ in range(n_runs):
            clusters = split_tour_across_clusters(cities, 1)

            total_distance_for_run = 0

            for i, cluster_indices in enumerate(clusters.values()):
                cluster_cities = [cities[index] for index in cluster_indices]

                ant_colony = AntColony(cluster_cities, n_ants=n_ants, max_time=max_time, alpha=alpha, beta=beta, evaporation=evaporation, intensification=intensification)
                best_route = ant_colony.run()
                total_distance_for_run += total_distance(best_route)

            total_best_route_length += total_distance_for_run

        average_best_route_length = total_best_route_length / n_runs
        average_best_route_per_max_time.append(average_best_route_length)

    average_best_route_per_max_time_per_alpha.append(average_best_route_per_max_time)

colors = ['b', 'g', 'r', 'c', 'm', 'y', 'k']

total_bars_per_group = len(alphas)
total_groups = len(max_times)

unit_space_per_group = 1
bars_space_per_group = total_bars_per_group / (total_bars_per_group + 1)
bar_width = bars_space_per_group / total_bars_per_group

bar_positions = np.arange(total_groups)

plt.figure(figsize=(10, 7))

for i, average_best_route_per_max_time in enumerate(average_best_route_per_max_time_per_alpha):
    plt.bar(bar_positions + i * bar_width, average_best_route_per_max_time, width=bar_width, color=colors[i], label=f'Alpha={alphas[i]}')
    for j, v in enumerate(average_best_route_per_max_time):
        plt.text(j + i * bar_width, v + 0.01 + (i*0.2), int(v), va='bottom', ha='center')

plt.axhline(y=optimal, color='r')

plt.xticks(bar_positions + bar_width / 2, max_times)  # Set the x-axis labels to be the max_time values
plt.xlabel('Max time')
plt.ylabel('Average best route length')
plt.legend()

title = ""
title += "Average best route length ({} iterations) for different max times\n".format(n_runs)
title += "Nb cities: " + str(len(cities)) + " / "
title += "Ants: " + str(n_ants) + " / "
title += "Alpha: " + str(alphas) + " / "
title += "Beta: " + str(beta) + " / "
title += "Evaporation: " + str(evaporation) + " / "
title += "Intensification: " + str(intensification)
plt.title(title)

plt.show()
The script will take at least : 30.4 seconds
No description has been provided for this image

Grâce à cet histogramme, nous pouvons constater que la meilleure valeur pour alpha est 1.

Identification de la valeur optimale de Beta
In [16]:
# Variables to edit:
max_times = [0.1, 0.2, 0.5, 1, 2]
betas = [2, 4, 6, 8]

n_ants = 10
alpha = 1
evaporation = 0.5
intensification = 2
n_runs = 2  #  Number of times each configuration will be run to obtain an average
In [18]:
import matplotlib.pyplot as plt
from tests.libs.clustering import split_tour_across_clusters
from tests.libs.aco import AntColony, total_distance
import numpy as np

cities = [[6734, 1453], [2233, 10], [5530, 1424], [401, 841], [3082, 1644], [7608, 4458], [7573, 3716], [7265, 1268], [6898, 1885], [1112, 2049], [5468, 2606], [5989, 2873], [4706, 2674], [4612, 2035], [6347, 2683], [6107, 669], [7611, 5184], [7462, 3590], [7732, 4723], [5900, 3561], [4483, 3369], [6101, 1110], [5199, 2182], [1633, 2809], [4307, 2322], [675, 1006], [7555, 4819], [7541, 3981], [3177, 756], [7352, 4506], [7545, 2801], [3245, 3305], [6426, 3173], [4608, 1198], [23, 2216], [7248, 3779], [7762, 4595], [7392, 2244], [3484, 2829], [6271, 2135], [4985, 140], [1916, 1569], [7280, 4899], [7509, 3239], [10, 2676], [6807, 2993], [5185, 3258], [3023, 1942]]
optimal = 33523

average_best_route_per_max_time_per_beta = []

def calculer_temps_total(max_times, betas, n_runs):
    temps_total = 0
    for max_time in max_times:
        for beta in betas:
            temps_total += max_time * n_runs
    return temps_total

print("The script will take at least : {} seconds".format(calculer_temps_total(max_times, betas, n_runs)))

for beta in betas:
    average_best_route_per_max_time = []

    for max_time in max_times:

        total_best_route_length = 0

        for _ in range(n_runs):
            clusters = split_tour_across_clusters(cities, 1)

            total_distance_for_run = 0

            for i, cluster_indices in enumerate(clusters.values()):
                cluster_cities = [cities[index] for index in cluster_indices]

                ant_colony = AntColony(cluster_cities, n_ants=n_ants, max_time=max_time, alpha=alpha, beta=beta, evaporation=evaporation, intensification=intensification)
                best_route = ant_colony.run()
                total_distance_for_run += total_distance(best_route)

            total_best_route_length += total_distance_for_run

        average_best_route_length = total_best_route_length / n_runs
        average_best_route_per_max_time.append(average_best_route_length)

    average_best_route_per_max_time_per_beta.append(average_best_route_per_max_time)

# Maintenant, nous avons toutes les valeurs moyennes, créons un histogramme
colors = ['b', 'g', 'r', 'c', 'm', 'y', 'k']

total_bars_per_group = len(betas)
total_groups = len(max_times)

# Unité d'espace pour chaque groupe de barres (incluant l'espace entre les groupes)
unit_space_per_group = 1

# Espace qui serait occupé par les barres dans chaque groupe
bars_space_per_group = total_bars_per_group / (total_bars_per_group + 1)

# La largeur de chaque barre serait
bar_width = bars_space_per_group / total_bars_per_group
index = np.arange(len(max_times))

plt.figure()

for i, average_best_route_per_max_time in enumerate(average_best_route_per_max_time_per_beta):
    plt.bar(index + i * bar_width, average_best_route_per_max_time, bar_width, color=colors[i], label='Beta = '+str(betas[i]))
    for j, v in enumerate(average_best_route_per_max_time):
        plt.text(j + i * bar_width, v + 0.01 + (i*0.2), int(v), va='bottom', ha='center')

plt.axhline(y=optimal, color='r')
plt.xlabel('Max time')
plt.ylabel('Average best route length')

title = ""
title += "Comparaison of betas with average best route length ({} iterations) for different max times and each betas\n".format(n_runs)
title += "Nb cities: " + str(len(cities)) + " / "
title += "Ants: " + str(n_ants) + " / "
title += "Alpha: " + str(alpha) + " / "
title += "Evaporation: " + str(evaporation) + " / "
title += "Intensification: " + str(intensification)
plt.title(title)

plt.xticks(index + bar_width / 2, max_times)
plt.legend()

plt.tight_layout()
plt.show()
The script will take at least : 30.4 seconds
No description has been provided for this image

Grâce à cet histogramme, nous pouvons constater que la meilleure valeur pour beta est 4 ou 6 voire 8.

Résultats pour les valeurs optimales de Alpha (1) et Beta (4) pour chaque temps maximum
In [25]:
# Variables to edit:
max_times = [0.01, 0.1, 0.2, 0.5, 1, 2, 5, 10]
n_ants = 10
alpha = 1
beta = 4
evaporation = 0.5
intensification = 2
n_runs = 3  # Number of times each configuration will be run to obtain an average
In [26]:
import matplotlib.pyplot as plt
from tests.libs.clustering import split_tour_across_clusters
from tests.libs.aco import AntColony, total_distance

cities = [[6734, 1453], [2233, 10], [5530, 1424], [401, 841], [3082, 1644], [7608, 4458], [7573, 3716], [7265, 1268], [6898, 1885], [1112, 2049], [5468, 2606], [5989, 2873], [4706, 2674], [4612, 2035], [6347, 2683], [6107, 669], [7611, 5184], [7462, 3590], [7732, 4723], [5900, 3561], [4483, 3369], [6101, 1110], [5199, 2182], [1633, 2809], [4307, 2322], [675, 1006], [7555, 4819], [7541, 3981], [3177, 756], [7352, 4506], [7545, 2801], [3245, 3305], [6426, 3173], [4608, 1198], [23, 2216], [7248, 3779], [7762, 4595], [7392, 2244], [3484, 2829], [6271, 2135], [4985, 140], [1916, 1569], [7280, 4899], [7509, 3239], [10, 2676], [6807, 2993], [5185, 3258], [3023, 1942]]
optimal = 33523

average_best_route_per_max_time = []

def calculer_temps_total(max_times, n_runs):
    temps_total = 0
    for max_time in max_times:
        temps_total += max_time * n_runs
    return temps_total

print("The script will take at least : {} seconds".format(calculer_temps_total(max_times, n_runs)))

for max_time in max_times:

    total_best_route_length = 0

    for _ in range(n_runs):
        clusters = split_tour_across_clusters(cities, 1)

        total_distance_for_run = 0

        for i, cluster_indices in enumerate(clusters.values()):
            cluster_cities = [cities[index] for index in cluster_indices]

            ant_colony = AntColony(cluster_cities, n_ants=n_ants, max_time=max_time, alpha=alpha, beta=beta, evaporation=evaporation, intensification=intensification)
            best_route = ant_colony.run()
            total_distance_for_run += total_distance(best_route)

        total_best_route_length += total_distance_for_run

    average_best_route_length = total_best_route_length / n_runs
    average_best_route_per_max_time.append(average_best_route_length)

# Maintenant, nous avons toutes les valeurs moyennes, créons un histogramme
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
]

plt.figure()
bar_width = 0.8
bar_positions = range(len(max_times))  # Crée une liste d'indices pour chaque barre
plt.bar(bar_positions, average_best_route_per_max_time, width=bar_width, color=colors[:len(max_times)])
plt.axhline(y=optimal, color='r')

# Ajouter des valeurs au-dessus des barres
for i, v in enumerate(average_best_route_per_max_time):
    plt.text(i - 0.15, v + 0.01, round(v, 2))

plt.xticks(bar_positions, max_times)  # Fixe les labels sur l'axe des x aux valeurs de max_time
plt.xlabel('Max time')
plt.ylabel('Average best route length')

title = ""
title += "Average best route length ({} iterations) for different max times\n".format(n_runs)
title += "Nb cities: " + str(len(cities)) + " / "
title += "Ants: " + str(n_ants) + " / "
title += "Alpha: " + str(alpha) + " / "
title += "Beta: " + str(beta) + " / "
title += "Evaporation: " + str(evaporation) + " / "
title += "Intensification: " + str(intensification)
plt.title(title)

plt.show()
The script will take at least : 56.43 seconds
No description has been provided for this image
Résultats pour les valeurs optimales de Alpha (1) et Beta (6) pour chaque temps maximum
In [23]:
# Variables to edit:
max_times = [0.01, 0.1, 0.2, 0.5, 1, 2, 5, 10]
n_ants = 10
alpha = 1
beta = 6
evaporation = 0.5
intensification = 2
n_runs = 3  # Number of times each configuration will be run to obtain an average
In [27]:
import matplotlib.pyplot as plt
from tests.libs.clustering import split_tour_across_clusters
from tests.libs.aco import AntColony, total_distance

cities = [[6734, 1453], [2233, 10], [5530, 1424], [401, 841], [3082, 1644], [7608, 4458], [7573, 3716], [7265, 1268], [6898, 1885], [1112, 2049], [5468, 2606], [5989, 2873], [4706, 2674], [4612, 2035], [6347, 2683], [6107, 669], [7611, 5184], [7462, 3590], [7732, 4723], [5900, 3561], [4483, 3369], [6101, 1110], [5199, 2182], [1633, 2809], [4307, 2322], [675, 1006], [7555, 4819], [7541, 3981], [3177, 756], [7352, 4506], [7545, 2801], [3245, 3305], [6426, 3173], [4608, 1198], [23, 2216], [7248, 3779], [7762, 4595], [7392, 2244], [3484, 2829], [6271, 2135], [4985, 140], [1916, 1569], [7280, 4899], [7509, 3239], [10, 2676], [6807, 2993], [5185, 3258], [3023, 1942]]
optimal = 33523

average_best_route_per_max_time = []

def calculer_temps_total(max_times, n_runs):
    temps_total = 0
    for max_time in max_times:
        temps_total += max_time * n_runs
    return temps_total

print("The script will take at least : {} seconds".format(calculer_temps_total(max_times, n_runs)))

for max_time in max_times:

    total_best_route_length = 0

    for _ in range(n_runs):
        clusters = split_tour_across_clusters(cities, 1)

        total_distance_for_run = 0

        for i, cluster_indices in enumerate(clusters.values()):
            cluster_cities = [cities[index] for index in cluster_indices]

            ant_colony = AntColony(cluster_cities, n_ants=n_ants, max_time=max_time, alpha=alpha, beta=beta, evaporation=evaporation, intensification=intensification)
            best_route = ant_colony.run()
            total_distance_for_run += total_distance(best_route)

        total_best_route_length += total_distance_for_run

    average_best_route_length = total_best_route_length / n_runs
    average_best_route_per_max_time.append(average_best_route_length)

# Maintenant, nous avons toutes les valeurs moyennes, créons un histogramme
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
]

plt.figure()
bar_width = 0.8
bar_positions = range(len(max_times))  # Crée une liste d'indices pour chaque barre
plt.bar(bar_positions, average_best_route_per_max_time, width=bar_width, color=colors[:len(max_times)])
plt.axhline(y=optimal, color='r')

# Ajouter des valeurs au-dessus des barres
for i, v in enumerate(average_best_route_per_max_time):
    plt.text(i - 0.15, v + 0.01, round(v, 2))

plt.xticks(bar_positions, max_times)  # Fixe les labels sur l'axe des x aux valeurs de max_time
plt.xlabel('Max time')
plt.ylabel('Average best route length')

title = ""
title += "Average best route length ({} iterations) for different max times\n".format(n_runs)
title += "Nb cities: " + str(len(cities)) + " / "
title += "Ants: " + str(n_ants) + " / "
title += "Alpha: " + str(alpha) + " / "
title += "Beta: " + str(beta) + " / "
title += "Evaporation: " + str(evaporation) + " / "
title += "Intensification: " + str(intensification)
plt.title(title)

plt.show()
The script will take at least : 56.43 seconds
No description has been provided for this image

Peu importe

Test de la variation du coût optimal en faisant varier le paramètre "inertia weight"

In [44]:
# List of inertia values to test
inertia_values = np.linspace(0.1, 5, 10)

# Initialize list to store optimal cost for each inertia value
optimal_costs = []

# Loop over each inertia value
for inertia_weight in inertia_values:
    # Initialize particles and the global best cost
    particles = []
    global_best_position = np.random.permutation(range(1, num_cities+1))
    global_best_cost = float('inf')
    
    # Initialize particles and find the best cost
    for _ in range(num_particles):
        particle = Particle(num_cities, num_trucks, distances, time_windows, infinite_distance_value)
        particles.append(particle)
        particle_cost = particle.evaluate_cost()
        if particle_cost < global_best_cost:
            global_best_cost = particle_cost
            global_best_position = particle.position.copy()
    
    # Iterate to find the optimal solution
    iteration = 0
    best_costs = []
    while iteration < max_iterations:
        for particle in particles:
            particle.update_velocity(global_best_position, inertia_weight, cognitive_weight, social_weight)
            particle.update_position()
            particle_cost = particle.evaluate_cost()
            if particle_cost < global_best_cost:
                global_best_cost = particle_cost
                global_best_position = particle.position.copy()
        best_costs.append(global_best_cost)
        iteration += 1

    # Store the optimal cost for this inertia value
    optimal_costs.append(global_best_cost)

# Plot the evolution of the optimal cost as a function of inertia
plt.figure(figsize=(10, 6))
plt.plot(inertia_values, optimal_costs)
plt.title("Evolution of Optimal Cost as a Function of Inertia")
plt.xlabel("Inertia")
plt.ylabel("Optimal Cost")
plt.show()
No description has been provided for this image

Test de la variation du coût optimal en faisant varier le paramètre "cognitive weight"

In [45]:
# List of cognitive weight values to test
cognitive_values = np.linspace(0.1, 5, 10)  

# Initialize list to store optimal cost for each cognitive weight value
optimal_costs = []

# Loop over each cognitive weight value
for cognitive_weight in cognitive_values:
    # Initialize particles and the global best cost
    particles = []
    global_best_position = np.random.permutation(range(1, num_cities+1))
    global_best_cost = float('inf')
    
    # Initialize particles and find the best cost
    for _ in range(num_particles):
        particle = Particle(num_cities, num_trucks, distances, time_windows, infinite_distance_value)
        particles.append(particle)
        particle_cost = particle.evaluate_cost()
        if particle_cost < global_best_cost:
            global_best_cost = particle_cost
            global_best_position = particle.position.copy()
    
    # Iterate to find the optimal solution
    iteration = 0
    best_costs = []
    while iteration < max_iterations:
        for particle in particles:
            particle.update_velocity(global_best_position, inertia_weight, cognitive_weight, social_weight)
            particle.update_position()
            particle_cost = particle.evaluate_cost()
            if particle_cost < global_best_cost:
                global_best_cost = particle_cost
                global_best_position = particle.position.copy()
        best_costs.append(global_best_cost)
        iteration += 1

    # Store the optimal cost for this cognitive weight value
    optimal_costs.append(global_best_cost)

# Plot the evolution of the optimal cost as a function of cognitive weight
plt.figure(figsize=(10, 6))
plt.plot(cognitive_values, optimal_costs)
plt.title("Evolution of Optimal Cost as a Function of Cognitive Weight")
plt.xlabel("Cognitive Weight")
plt.ylabel("Optimal Cost")
plt.show()
No description has been provided for this image