a3-algorithmique-avancee/Projet_algo.ipynb

1039 lines
43 KiB
Plaintext
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{
"cells": [
{
"attachments": {},
"cell_type": "markdown",
"id": "83c2b1e7-b401-4a15-adf0-d43cebf9ad81",
"metadata": {},
"source": [
"# Projet algorithme # \n"
]
},
{
"attachments": {},
"cell_type": "markdown",
"id": "0b4f92e5-ac40-4491-983d-890c4f0f6694",
"metadata": {},
"source": [
"## Contexte du projet: \n"
]
},
{
"attachments": {},
"cell_type": "markdown",
"id": "5f483b31-1246-4f00-ae39-718ef92ce9eb",
"metadata": {},
"source": [
"L'ADEME a lancé un appel à manifestation d'intérêt pour développer des solutions de mobilité adaptées à différents territoires. CesiCDP, en collaboration avec des partenaires, s'est spécialisé dans la Mobilité Multimodale Intelligente. Dans le cadre de cet appel, l'équipe de CesiCDP travaille sur la gestion de tournées de livraison pour minimiser les trajets et optimiser l'efficacité. L'objectif est d'obtenir de nouveaux marchés et des financements pour poursuivre le développement de l'entreprise. Des contraintes supplémentaires seront ajoutées pour rendre le problème plus réaliste et intéressant pour l'ADEME."
]
},
{
"attachments": {},
"cell_type": "markdown",
"id": "fc23daaf-9f25-4c5a-bf6c-300a7ed8d623",
"metadata": {},
"source": [
"Notre but est de réaliser un algorithme permettant de passer par tous les points de livraisons avec un temps optimisé."
]
},
{
"attachments": {},
"cell_type": "markdown",
"id": "d633beb7-8f26-46d4-9cd9-1d0093e5b5c3",
"metadata": {},
"source": [
"## Contrainte choisie :\n"
]
},
{
"attachments": {},
"cell_type": "markdown",
"id": "35fc1c3c-d7a9-4423-a948-aa00ab51dbf4",
"metadata": {},
"source": [
"La contrainte que nous avons choisi est la suivante:\n",
"- Avoir plusieurs camions disponibles simultanément pour effectuer les livraisons.\n"
]
},
{
"attachments": {},
"cell_type": "markdown",
"id": "ba356166-604a-4627-ac0f-93b76eb7a22d",
"metadata": {},
"source": [
"## Formulation du problème"
]
},
{
"attachments": {},
"cell_type": "markdown",
"id": "c4d6888b-14e6-4745-880f-0a063ebf7476",
"metadata": {},
"source": [
"Soit 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.\n",
"\n",
"Le problème consiste à trouver une tournée pour chaque camion, de manière à ce que toutes les livraisons soient effectuées en un temps minimum tout en partant et en revenant au dépôt.\n",
"\n",
"Le problème que nous avons avec les contraintes ci-dessus est le problème du VRP (Vehicule Routing Problem)."
]
},
{
"attachments": {},
"cell_type": "markdown",
"id": "6d392f68",
"metadata": {},
"source": [
"## Contraintes du problème"
]
},
{
"attachments": {},
"cell_type": "markdown",
"id": "f95dba22",
"metadata": {},
"source": [
"Liste des contraintes du problème:\n",
"\n",
"- Tous les clients doivent être déservis\n",
"- Un client ne peut être servi que par un et un seul véhicule.\n",
"- En quittant un client, un véhicule peut aller que vers un seul autre client.\n",
"\n",
"On va donc affecter chaque client à une tournée effectué par un seul véhicule."
]
},
{
"attachments": {},
"cell_type": "markdown",
"id": "16b3b8a7-4658-4509-a511-7a395654e6f1",
"metadata": {},
"source": [
"## Modélisation mathématique"
]
},
{
"attachments": {},
"cell_type": "markdown",
"id": "b54bfa8c",
"metadata": {},
"source": [
"### Ensemble et paramètres:"
]
},
{
"attachments": {},
"cell_type": "markdown",
"id": "637eb295",
"metadata": {},
"source": [
"$V=\\{0,1,2,...,n_v\\}$ : l'ensemble des villes, où 0 est la base (ou le dépôt), $1,2,...,n_v$ sont les villes de livraison. $n_v+1$ sera le dépot pour le retour. <br>\n",
"$K=\\{1,2,...,k\\}$ : l'ensemble des camions. <br>\n",
"$E$ représente les arcs entre deux clients $i,j \\in V$ <br>\n",
"$G=(V,E)$ : le graphe <br>\n",
"$d_{ij}$ : la distance (ou le temps de trajet) de la ville i à la ville $j$ (coût de déplacement) <br>\n",
"$[a_i,b_i]$ : la fenêtre de temps de livraison pour la ville $i$. <br>\n",
"$t_{ij}$ : le temps de déplacement de la ville i à la ville j. <br>\n",
"$M$ : une grande constante."
]
},
{
"attachments": {},
"cell_type": "markdown",
"id": "1219e4f2",
"metadata": {},
"source": [
"### Variables de décision:"
]
},
{
"attachments": {},
"cell_type": "markdown",
"id": "a6d398fa",
"metadata": {},
"source": [
"$x_{ijk}$ : variable binaire qui vaut 1 si le camion $k$ se déplace de la ville $i$ à la ville $j$, et 0 sinon. <br>\n",
"$t_{ik}$ : le moment où le camion $k$ arrive à la ville $i$."
]
},
{
"attachments": {},
"cell_type": "markdown",
"id": "e635cf14",
"metadata": {},
"source": [
"### Fonction objective:"
]
},
{
"attachments": {},
"cell_type": "markdown",
"id": "56652859",
"metadata": {},
"source": [
"Minimiser $Z=max_{k∈K}t_{0k}$"
]
},
{
"attachments": {},
"cell_type": "markdown",
"id": "a1465000",
"metadata": {},
"source": [
"### Contraintes et modèle mathématique du VRP:\n",
"\n",
"L'objectif est de minimiser la distance totale parcourue :\n",
"\n",
"$$\\min \\sum_{k∈K} \\sum_{i∈V} \\sum_{j∈V} d_{ij} x_{ijk} $$\n",
"\n",
"Chaque ville est visitée une fois et une seule fois : $$\\sum_{k∈K} \\sum_{j∈V} x_{ijk} = 1, ∀i \\in V \\{0\\}$$\n",
"\n",
"Si un camion se déplace de la ville ii à la ville $j$, alors le moment d'arrivée à la ville $j$ doit être plus grand que le moment d'arrivée à la ville $i$ plus le temps de trajet : $$t_{ik}+d_{ij} \\leq t_{jk}+M(1x_{ijk}),∀i,j \\in V,i \\ne j,∀k \\in K$$\n",
"\n",
"Les contraintes de flux pour garantir que si un camion entre dans une ville, il doit également en sortir :\n",
"$$i \\in V,i \\ne j∑xijk=i∈V,i \\ne j∑xjik=yjk,∀j∈V,∀k∈K$$\n",
"\n",
"Les $t_{ik}$ doivent être continues :\n",
"\n",
"$t_{ik}\\le 0 $\n",
"\n",
"\n",
"\n",
"\n",
"### Dans le cas ou l'on veut rajouter les contraintes de fenêtres de livraison (Time Windows)\n",
"Les fenêtres de temps de livraison doivent être respectées : $$a_i \\leq t_{ik} \\leq b_i, ∀i \\in V \\{0 \\},∀k \\in K$$\n",
"\n"
]
},
{
"attachments": {},
"cell_type": "markdown",
"id": "7670fdf4-884c-4352-83fa-eed0c8b50074",
"metadata": {},
"source": [
"# Initialisation de la matrice"
]
},
{
"cell_type": "code",
"execution_count": 17,
"id": "c177ac22",
"metadata": {},
"outputs": [
{
"ename": "AttributeError",
"evalue": "module 'networkx.linalg.graphmatrix' has no attribute 'to_numpy_matrix'",
"output_type": "error",
"traceback": [
"\u001b[1;31m---------------------------------------------------------------------------\u001b[0m",
"\u001b[1;31mAttributeError\u001b[0m Traceback (most recent call last)",
"Cell \u001b[1;32mIn[17], line 41\u001b[0m\n\u001b[0;32m 38\u001b[0m \u001b[39mreturn\u001b[39;00m adjacency_matrix\n\u001b[0;32m 40\u001b[0m \u001b[39m# Générer la matrice d'adjacence pondérée\u001b[39;00m\n\u001b[1;32m---> 41\u001b[0m weighted_adjacency_matrix \u001b[39m=\u001b[39m generate_weighted_adjacency_matrix(graph)\n\u001b[0;32m 43\u001b[0m \u001b[39m# Afficher la matrice d'adjacence pondérée\u001b[39;00m\n\u001b[0;32m 44\u001b[0m \u001b[39mprint\u001b[39m(weighted_adjacency_matrix)\n",
"Cell \u001b[1;32mIn[17], line 36\u001b[0m, in \u001b[0;36mgenerate_weighted_adjacency_matrix\u001b[1;34m(graph)\u001b[0m\n\u001b[0;32m 34\u001b[0m \u001b[39mdef\u001b[39;00m \u001b[39mgenerate_weighted_adjacency_matrix\u001b[39m(graph):\n\u001b[0;32m 35\u001b[0m \u001b[39m# Créer une matrice d'adjacence pondérée à partir du graphe\u001b[39;00m\n\u001b[1;32m---> 36\u001b[0m adjacency_matrix \u001b[39m=\u001b[39m graphmatrix\u001b[39m.\u001b[39;49mto_numpy_matrix(graph, weight\u001b[39m=\u001b[39m\u001b[39m'\u001b[39m\u001b[39mdistance\u001b[39m\u001b[39m'\u001b[39m)\n\u001b[0;32m 38\u001b[0m \u001b[39mreturn\u001b[39;00m adjacency_matrix\n",
"\u001b[1;31mAttributeError\u001b[0m: module 'networkx.linalg.graphmatrix' has no attribute 'to_numpy_matrix'"
]
}
],
"source": [
"\n",
"import random\n",
"import numpy as np\n",
"import networkx as nx\n",
"import matplotlib.pyplot as plt\n",
"import time as time\n",
"\n",
"from networkx.linalg import graphmatrix\n",
"\n",
"def generate_graph(num_nodes):\n",
" G = nx.Graph()\n",
" G.add_nodes_from(range(num_nodes + 1))\n",
" \n",
" for node in G.nodes():\n",
" connected_nodes = sorted(set(G.nodes()) - {node})\n",
" if len(connected_nodes) < 2:\n",
" continue\n",
" distance1 = random.randint(1, 10)\n",
" distance2 = random.randint(1, 10)\n",
" random_nodes = random.sample(connected_nodes, 2)\n",
" G.add_edges_from([(node, random_nodes[0], {'distance': distance1}),\n",
" (node, random_nodes[1], {'distance': distance2})])\n",
"\n",
" while not nx.is_connected(G):\n",
" node1, node2 = random.sample(G.nodes(), 2)\n",
" if not G.has_edge(node1, node2):\n",
" distance = random.randint(1, 10)\n",
" G.add_edge(node1, node2, distance=distance)\n",
"\n",
" return G\n",
"\n",
"graph = generate_graph(12)\n",
"A = nx.adjacency_matrix(graph)\n",
"\n",
"def generate_weighted_adjacency_matrix(graph):\n",
" # Créer une matrice d'adjacence pondérée à partir du graphe\n",
" adjacency_matrix = graphmatrix.to_numpy_matrix(graph, weight='distance')\n",
"\n",
" return adjacency_matrix\n",
"\n",
"# Générer la matrice d'adjacence pondérée\n",
"weighted_adjacency_matrix = generate_weighted_adjacency_matrix(graph)\n",
"\n",
"# Afficher la matrice d'adjacence pondérée\n",
"print(weighted_adjacency_matrix)\n",
"\n",
"\n",
"def generate_distance_matrix(graph):\n",
" num_nodes = graph.number_of_nodes()\n",
" distance_array = np.full((num_nodes, num_nodes), float('inf')) # Initialiser avec l'infini\n",
" for edge in graph.edges(data=True):\n",
" i, j, data = edge\n",
" distance_array[i][j] = data['distance']\n",
" distance_array[j][i] = data['distance'] # Pour un graphe non orienté\n",
" np.fill_diagonal(distance_array, 0) # Remplir la diagonale avec des zéros\n",
" return distance_array\n",
"\n",
"# Générer la matrice de distances\n",
"distance_matrix = generate_distance_matrix(graph)\n",
"\n",
"\n",
"# Afficher la matrice de distances\n",
"print(distance_matrix)\n",
"\n",
"\n",
"\n",
"# Dessiner le graphe\n",
"nx.draw(graph, with_labels=True)\n",
"plt.show()"
]
},
{
"attachments": {},
"cell_type": "markdown",
"id": "9aac4453",
"metadata": {},
"source": [
"# On applique l'algorithme des fourmis (ACO) sur notre graphe\n"
]
},
{
"cell_type": "code",
"execution_count": 10,
"id": "23e32e2a",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Meilleure solution : [[6, 5, 4, 8, 1, 11, 0], [9, 3, 7, 12, 10, 2, 0]]\n",
"Distance totale : 81.0\n"
]
}
],
"source": [
"import concurrent.futures\n",
"num_trucks = 2 # Nombre de camions disponibles\n",
"\n",
"\n",
"# Fonction d'évaluation de la qualité d'une solution (ici, la distance totale)\n",
"def evaluate_solution(solution, distances):\n",
" total_distance = 0\n",
" num_nodes = len(solution)\n",
"\n",
" for i in range(num_nodes - 1):\n",
" current_node = solution[i]\n",
" next_node = solution[i + 1]\n",
" total_distance += distances[current_node][next_node]\n",
"\n",
"\n",
" # Ajouter la distance de retour au dépôt\n",
" total_distance += distances[solution[-1]][solution[0]]\n",
"\n",
" return total_distance\n",
"\n",
" \n",
"def ant_colony_optimization(distances, num_ants, num_iterations, evaporation_rate, alpha, beta, num_trucks):\n",
" num_nodes = len(distances)\n",
" \n",
" pheromone = np.ones((num_nodes, num_nodes)) # Matrice de phéromones initiale\n",
" best_solution = None\n",
" best_distance = float('inf')\n",
"\n",
" start_time = time.time()\n",
"\n",
" for iteration in range(num_iterations):\n",
" # Construction de solutions par les fourmis\n",
" solutions = []\n",
"\n",
" for ant in range(num_ants):\n",
" visited = set()\n",
" current_node = random.randint(0, num_nodes - 1)\n",
" visited.add(current_node)\n",
" solution = [current_node]\n",
"\n",
" while len(visited) < num_nodes:\n",
" next_node = None\n",
" probabilities = []\n",
"\n",
" # Calcul des probabilités de choisir chaque prochain nœud\n",
" for node in range(num_nodes):\n",
" if node not in visited and (node !=0 or len(visited) == num_nodes - 1):\n",
" pheromone_value = pheromone[current_node][node]\n",
" distance_value = distances[current_node][node]\n",
" probability = (pheromone_value ** alpha) * ((1 / distance_value) ** beta)\n",
" probabilities.append((node, probability))\n",
"\n",
" total_probability = sum(prob for _, prob in probabilities)\n",
" probabilities = [(node, prob / total_probability) for node, prob in probabilities]\n",
"\n",
" \n",
" roulette_wheel = random.random()\n",
" probability_sum = 0\n",
"\n",
"\n",
" for node, probability in probabilities:\n",
" \n",
" probability_sum += probability\n",
" if probability_sum >= roulette_wheel:\n",
" next_node = node\n",
" break\n",
"\n",
" visited.add(next_node)\n",
" solution.append(next_node)\n",
" current_node = next_node\n",
"\n",
" # Ajouter le retour au dépôt central à la fin du trajet\n",
" solution.append(0)\n",
"\n",
" solutions.append(solution)\n",
"\n",
" # Évaluation des solutions et mise à jour de la meilleure solution\n",
" for solution in solutions:\n",
" distance = evaluate_solution(solution, distances)\n",
" if distance < best_distance:\n",
" best_solution = solution\n",
" best_distance = distance\n",
"\n",
" # Mise à jour des phéromones\n",
" pheromone *= evaporation_rate # Évaporation des phéromones existantes\n",
"\n",
" for solution in solutions:\n",
" delta_pheromone = 1 / evaluate_solution(solution, distances)\n",
" for i in range(num_nodes - 1):\n",
" node1 = solution[i]\n",
" node2 = solution[i + 1]\n",
" pheromone[node1][node2] += delta_pheromone\n",
" pheromone[node2][node1] += delta_pheromone\n",
"\n",
" # Séparer la meilleure solution en trajets pour chaque camion\n",
" truck_solutions = []\n",
" num_nodes_per_truck = num_nodes // num_trucks\n",
"\n",
" for i in range(num_trucks):\n",
" start_index = i * num_nodes_per_truck\n",
" end_index = start_index + num_nodes_per_truck\n",
" truck_solution = best_solution[start_index:end_index]\n",
" truck_solutions.append(truck_solution + [0])\n",
" \n",
"\n",
" return truck_solutions, best_distance\n",
"\n",
"\n",
"def trucks_thread(i, num_nodes_per_truck, best_solution, truck_solutions):\n",
" start_index = i * num_nodes_per_truck\n",
" end_index = start_index + num_nodes_per_truck\n",
" truck_solution = best_solution[start_index:end_index]\n",
" truck_solutions.append(truck_solution)\n",
" return truck_solutions\n",
"\n",
"\n",
"num_ants = 10\n",
"num_iterations = 100\n",
"evaporation_rate = 0.5\n",
"alpha = 1\n",
"beta = 1\n",
"\n",
"best_truck_solutions, best_distance = ant_colony_optimization(distance_matrix, num_ants, num_iterations, evaporation_rate, alpha, beta, num_trucks)\n",
"\n",
"\n",
"print(\"Meilleure solution :\", best_truck_solutions)\n",
"print(\"Distance totale :\", best_distance)\n",
" "
]
},
{
"cell_type": "code",
"execution_count": 11,
"id": "6928bbbd",
"metadata": {},
"outputs": [
{
"ename": "AttributeError",
"evalue": "module 'networkx' has no attribute 'from_numpy_matrix'",
"output_type": "error",
"traceback": [
"\u001b[1;31m---------------------------------------------------------------------------\u001b[0m",
"\u001b[1;31mAttributeError\u001b[0m Traceback (most recent call last)",
"Cell \u001b[1;32mIn[11], line 114\u001b[0m\n\u001b[0;32m 111\u001b[0m \u001b[39m# Pour chaque solution de camion, nous devons trouver le chemin le plus court du dépôt au premier nœud du chemin\u001b[39;00m\n\u001b[0;32m 112\u001b[0m \u001b[39mfor\u001b[39;00m i, truck_solution \u001b[39min\u001b[39;00m \u001b[39menumerate\u001b[39m(best_truck_solutions):\n\u001b[0;32m 113\u001b[0m \u001b[39m# Créer un graphe à partir de la matrice de distance\u001b[39;00m\n\u001b[1;32m--> 114\u001b[0m G \u001b[39m=\u001b[39m nx\u001b[39m.\u001b[39;49mfrom_numpy_matrix(distance_matrix)\n\u001b[0;32m 116\u001b[0m \u001b[39m# Utiliser l'algorithme de Dijkstra pour trouver le chemin le plus court du dépôt (nœud 0) au premier nœud du chemin du camion\u001b[39;00m\n\u001b[0;32m 117\u001b[0m shortest_path \u001b[39m=\u001b[39m nx\u001b[39m.\u001b[39mdijkstra_path(G, \u001b[39m0\u001b[39m, truck_solution[\u001b[39m1\u001b[39m])\n",
"\u001b[1;31mAttributeError\u001b[0m: module 'networkx' has no attribute 'from_numpy_matrix'"
]
}
],
"source": [
"num_trucks = 2 # Nombre de camions disponibles\n",
"\n",
"def evaluate_solution(solution, distances):\n",
" total_distance = 0\n",
" num_nodes = len(solution)\n",
"\n",
" # Calculer la distance entre chaque paire consécutive de nœuds dans la solution\n",
" for i in range(num_nodes - 1):\n",
" current_node = solution[i]\n",
" next_node = solution[i + 1]\n",
" total_distance += distances[current_node][next_node]\n",
"\n",
" # Ajouter la distance de retour au dépôt\n",
" total_distance += distances[solution[-1]][solution[0]]\n",
"\n",
" return total_distance\n",
"\n",
"def ant_colony_optimization(distances, num_ants, num_iterations, evaporation_rate, alpha, beta, num_trucks):\n",
" num_nodes = len(distances)\n",
" \n",
" pheromone = np.ones((num_nodes, num_nodes)) # Matrice de phéromones initiale\n",
" best_solution = None\n",
" best_distance = float('inf')\n",
"\n",
" for iteration in range(num_iterations):\n",
" # Construction de solutions par les fourmis\n",
" solutions = []\n",
"\n",
" for ant in range(num_ants):\n",
" visited = set()\n",
" current_node = 0 # Initialiser current_node à 0\n",
" visited.add(current_node)\n",
" solution = [current_node]\n",
"\n",
" while len(visited) < num_nodes:\n",
" next_node = None\n",
" probabilities = []\n",
"\n",
" # Calcul des probabilités de choisir chaque prochain nœud\n",
" for node in range(num_nodes):\n",
" if node not in visited and node < len(pheromone) and current_node < len(pheromone):\n",
" pheromone_value = pheromone[current_node][node]\n",
" distance_value = distances[current_node][node]\n",
" probability = (pheromone_value ** alpha) * ((1 / distance_value) ** beta)\n",
" probabilities.append((node, probability))\n",
"\n",
" total_probability = sum(prob for _, prob in probabilities)\n",
" probabilities = [(node, prob / total_probability) for node, prob in probabilities]\n",
"\n",
" roulette_wheel = random.random()\n",
" probability_sum = 0\n",
"\n",
" for node, probability in probabilities:\n",
" probability_sum += probability\n",
" if probability_sum >= roulette_wheel:\n",
" next_node = node\n",
" break\n",
"\n",
" visited.add(next_node)\n",
" solution.append(next_node)\n",
" current_node = next_node\n",
"\n",
" # Ajouter le retour au dépôt central à la fin du trajet\n",
" solution.append(0)\n",
"\n",
" solutions.append(solution)\n",
"\n",
" # Évaluation des solutions et mise à jour de la meilleure solution\n",
" for solution in solutions:\n",
" distance = evaluate_solution(solution, distances)\n",
" if distance < best_distance:\n",
" best_solution = solution\n",
" best_distance = distance\n",
"\n",
" # Mise à jour des phéromones\n",
" pheromone *= evaporation_rate # Évaporation des phéromones existantes\n",
"\n",
" for solution in solutions:\n",
" delta_pheromone = 1 / evaluate_solution(solution, distances)\n",
" for i in range(num_nodes - 1):\n",
" node1 = solution[i]\n",
" node2 = solution[i + 1]\n",
" pheromone[node1][node2] += delta_pheromone\n",
" pheromone[node2][node1] += delta_pheromone\n",
"\n",
" # Séparer la meilleure solution en trajets pour chaque camion\n",
" truck_solutions = []\n",
" num_nodes_per_truck = num_nodes // num_trucks\n",
"\n",
" for i in range(num_trucks):\n",
" start_index = i * num_nodes_per_truck\n",
" end_index = start_index + num_nodes_per_truck\n",
" truck_solution = best_solution[start_index:end_index]\n",
" truck_solutions.append(truck_solution + [0])\n",
" \n",
"\n",
" return truck_solutions, best_distance\n",
"\n",
"\n",
"\n",
"\n",
"\n",
"num_ants = 10\n",
"num_iterations = 100\n",
"evaporation_rate = 0.5\n",
"alpha = 1\n",
"beta = 1\n",
"\n",
"best_truck_solutions,best_distance = ant_colony_optimization(distance_matrix, num_ants, num_iterations, evaporation_rate, alpha, beta, num_trucks)\n",
"\n",
"# Pour chaque solution de camion, nous devons trouver le chemin le plus court du dépôt au premier nœud du chemin\n",
"for i, truck_solution in enumerate(best_truck_solutions):\n",
" # Créer un graphe à partir de la matrice de distance\n",
" G = nx.from_numpy_matrix(distance_matrix)\n",
" \n",
" # Utiliser l'algorithme de Dijkstra pour trouver le chemin le plus court du dépôt (nœud 0) au premier nœud du chemin du camion\n",
" shortest_path = nx.dijkstra_path(G, 0, truck_solution[1])\n",
" \n",
" # Remplacer le premier nœud du chemin du camion par le chemin le plus court trouvé\n",
" best_truck_solutions[i] = [0] + shortest_path + truck_solution[2:]\n",
"\n",
"# Calculer la distance totale pour chaque solution de camion\n",
"total_distances = [evaluate_solution(truck_solution, distance_matrix) for truck_solution in best_truck_solutions]\n",
"\n",
"# Afficher les solutions et les distances totales pour chaque camion\n",
"for i, (truck_solution, total_distance) in enumerate(zip(best_truck_solutions, total_distances)):\n",
" print(f\"Camion {i+1} :\")\n",
" print(\"Solution :\", truck_solution)\n",
" print(\"Distance totale :\", total_distance)\n",
" print()"
]
},
{
"cell_type": "markdown",
"id": "421c05e1",
"metadata": {},
"source": []
},
{
"attachments": {},
"cell_type": "markdown",
"id": "a6ffa6c9",
"metadata": {},
"source": [
"\n",
"def ant_colony_optimization(distances, num_ants, num_iterations, evaporation_rate, alpha, beta, num_trucks):\n",
" num_nodes = len(distances)\n",
" \n",
" pheromone = np.ones((num_nodes, num_nodes)) # Matrice de phéromones initiale\n",
" best_solution = None\n",
" best_distance = float('inf')\n",
"\n",
" for iteration in range(num_iterations):\n",
" # Construction de solutions par les fourmis\n",
" solutions = []\n",
"\n",
" for ant in range(num_ants):\n",
" visited = set()\n",
" current_node = 0 # Tous les camions partent du dépôt\n",
" visited.add(current_node)\n",
" solution = [current_node]\n",
"\n",
" while len(visited) < num_nodes:\n",
" next_node = None\n",
" probabilities = []\n",
"\n",
" # Calcul des probabilités de choisir chaque prochain nœud\n",
" for node in range(num_nodes):\n",
" if node not in visited:\n",
" pheromone_value = pheromone[current_node][node]\n",
" distance_value = distances[current_node][node]\n",
" probability = (pheromone_value ** alpha) * ((1 / distance_value) ** beta)\n",
" probabilities.append((node, probability))\n",
"\n",
" total_probability = sum(prob for _, prob in probabilities)\n",
" probabilities = [(node, prob / total_probability) for node, prob in probabilities]\n",
"\n",
" roulette_wheel = random.random()\n",
" probability_sum = 0\n",
"\n",
" for node, probability in probabilities:\n",
" probability_sum += probability\n",
" if probability_sum >= roulette_wheel:\n",
" next_node = node\n",
" break\n",
"\n",
" visited.add(next_node)\n",
" solution.append(next_node)\n",
" current_node = next_node\n",
"\n",
" # Ajouter le retour au dépôt central à la fin du trajet\n",
" solution.append(0)\n",
"\n",
" solutions.append(solution)\n",
"\n",
" # Évaluation des solutions et mise à jour de la meilleure solution\n",
" for solution in solutions:\n",
" distance = evaluate_solution(solution, distances)\n",
" if distance < best_distance:\n",
" best_solution = solution\n",
" best_distance = distance\n",
"\n",
" # Mise à jour des phéromones\n",
" pheromone *= evaporation_rate # Évaporation des phéromones existantes\n",
"\n",
" for solution in solutions:\n",
" delta_pheromone = 1 / evaluate_solution(solution, distances)\n",
" for i in range(num_nodes - 1):\n",
" node1 = solution[i]\n",
" node2 = solution[i + 1]\n",
" pheromone[node1][node2] += delta_pheromone\n",
" pheromone[node2][node1] += delta_pheromone\n",
"\n",
" # Séparer la meilleure solution en trajets pour chaque camion\n",
" truck_solutions = []\n",
" num_nodes_per_truck = num_nodes // num_trucks\n",
"\n",
" for i in range(num_trucks):\n",
" start_index = i * num_nodes_per_truck\n",
" end_index = start_index + num_nodes_per_truck\n",
" truck_solution = best_solution[start_index:end_index]\n",
" truck_solutions.append(truck_solution + [0])\n",
" \n",
"\n",
" return truck_solutions, best_distance\n",
"\n",
"num_ants = 10\n",
"num_iterations = 100\n",
"evaporation_rate = 0.5\n",
"alpha = 1\n",
"beta = 1\n",
"\n",
"best_truck_solutions,best_distance = ant_colony_optimization(distance_matrix, num_ants, num_iterations, evaporation_rate, alpha, beta, num_trucks)\n",
"\n",
"# Pour chaque solution de camion, nous devons trouver le chemin le plus court du dépôt au premier nœud du chemin\n",
"for i, truck_solution in enumerate(best_truck_solutions):\n",
" # Créer un graphe à partir de la matrice de distance\n",
" G = nx.from_numpy_matrix(distance_matrix)\n",
" \n",
" # Utiliser l'algorithme de Dijkstra pour trouver le chemin le plus court du dépôt (nœud 0) au premier nœud du chemin du camion\n",
" shortest_path = nx.dijkstra_path(G, 0, truck_solution[1])\n",
" \n",
" # Remplacer le premier nœud du chemin du camion par le chemin le plus court trouvé\n",
" best_truck_solutions[i] = [0] + shortest_path + truck_solution[2:]\n",
"\n",
"# Calculer la distance totale pour chaque solution de camion\n",
"total_distances = [evaluate_solution(truck_solution, distance_matrix) for truck_solution in best_truck_solutions]\n",
"\n",
"# Afficher les solutions et les distances totales pour chaque camion\n",
"for i, (truck_solution, total_distance) in enumerate(zip(best_truck_solutions, total_distances)):\n",
" print(f\"Camion {i+1} :\")\n",
" print(\"Solution :\", truck_solution)\n",
" print(\"Distance totale :\", total_distance)\n",
" print()"
]
},
{
"attachments": {},
"cell_type": "markdown",
"id": "29df0137",
"metadata": {},
"source": [
"# En rajoutant la contrainte de Time Window pour une instance de VRPTW\n",
"\n",
"Dans un premier temps on va attribuer des fenêtres de temps pour chaque clients (ville dans notre graphe)"
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "8266e414-d350-4101-8f15-9cc05ee02934",
"metadata": {
"scrolled": true
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"20\n",
"Client 0 : (0, inf)\n",
"Client 1 : (44, 94)\n",
"Client 2 : (99, 111)\n",
"Client 3 : (80, 125)\n",
"Client 4 : (85, 96)\n",
"Client 5 : (71, 85)\n",
"Client 6 : (34, 69)\n",
"Client 7 : (20, 30)\n",
"Client 8 : (46, 56)\n",
"Client 9 : (90, 109)\n",
"Client 10 : (74, 116)\n",
"Client 11 : (95, 112)\n",
"Client 12 : (93, 131)\n",
"Client 13 : (46, 82)\n",
"Client 14 : (86, 124)\n",
"Client 15 : (87, 129)\n",
"Client 16 : (13, 23)\n",
"Client 17 : (0, 28)\n",
"Client 18 : (77, 108)\n",
"Client 19 : (76, 105)\n",
"Client 20 : (78, 94)\n",
"[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]\n",
"[(0, 19), (0, 16), (0, 5), (0, 10), (1, 18), (1, 8), (1, 11), (2, 18), (2, 6), (2, 3), (2, 9), (2, 10), (2, 14), (2, 17), (3, 7), (3, 8), (3, 16), (4, 15), (4, 5), (4, 13), (5, 8), (5, 7), (6, 16), (6, 19), (7, 15), (7, 19), (8, 13), (9, 20), (10, 15), (11, 14), (11, 18), (12, 15), (12, 17), (12, 16), (13, 19), (13, 17), (13, 20), (14, 20), (14, 15), (15, 18), (15, 20)]\n"
]
}
],
"source": [
"\n",
"def assign_time_windows(graph):\n",
" # Créer un dictionnaire pour stocker les fenêtres de temps des clients\n",
" time_windows = {}\n",
"\n",
" # Définir la fenêtre de temps pour le dépôt central (nœud 0)\n",
" time_windows[0] = (0, float('inf'))\n",
"\n",
" # Assigner une fenêtre de temps à chaque client\n",
" for node in graph.nodes():\n",
" if node !=0 and node != graph.number_of_nodes() :\n",
" # Générer une fenêtre de temps aléatoire pour chaque client\n",
" start_time = random.randint(0, 100)\n",
" end_time = start_time + random.randint(10, 50)\n",
" time_windows[node] = (start_time, end_time)\n",
" \n",
"\n",
" return time_windows\n",
"\n",
"# Attribuer les fenêtres de temps aux clients\n",
"time_windows = assign_time_windows(graph)\n",
"\n",
"print(max(graph.nodes()))\n",
"# Afficher les fenêtres de temps assignées\n",
"for node, window in time_windows.items():\n",
" print(\"Client\", node, \":\", window)\n",
" \n",
"#paramètres ACO\n",
"\n",
"print(graph.nodes())\n",
"print(graph.edges())\n"
]
},
{
"attachments": {},
"cell_type": "markdown",
"id": "747541f5",
"metadata": {},
"source": [
"# On va ensuite réadapter l'algorithme pour prendre en compte les time Windows "
]
},
{
"cell_type": "code",
"execution_count": 15,
"id": "901e6b93",
"metadata": {},
"outputs": [],
"source": [
"import concurrent.futures\n",
"import random\n",
"import numpy as np\n",
"import time\n",
"import heapq\n",
"\n",
"\n",
"# Fonction d'évaluation de la qualité d'une solution (ici, la distance totale)\n",
"def evaluate_solution(solution, distances, time_windows, dijkstra_distances):\n",
" total_distance = 0\n",
" total_delay = 0\n",
" arrival_time = 0\n",
" num_nodes = len(solution)\n",
" waiting_times = []\n",
"\n",
" total_distance += dijkstra_distances[solution[1]]\n",
"\n",
" for i in range(num_nodes - 1):\n",
" current_node = solution[i]\n",
" next_node = solution[i + 1]\n",
" total_distance += distances[current_node][next_node]\n",
" arrival_time += distances[current_node][next_node]\n",
"\n",
" if arrival_time < time_windows[next_node][0]:\n",
" waiting_time = time_windows[next_node][0] - arrival_time\n",
" waiting_times.append((next_node, waiting_time))\n",
" arrival_time = time_windows[next_node][0]\n",
" elif arrival_time > time_windows[next_node][1]:\n",
" total_delay += arrival_time - time_windows[next_node][1]\n",
"\n",
" # Ajouter la distance de retour au dépôt\n",
" total_distance += distances[solution[-1]][solution[0]]\n",
"\n",
" return total_distance, total_delay, waiting_times\n",
"\n",
" \n",
"# Fonction pour l'algorithme de Dijkstra\n",
"def calculate_dijkstra(start_node, distances):\n",
" num_nodes = len(distances)\n",
" shortest_distances = [float('inf')] * num_nodes\n",
" shortest_distances[start_node] = 0\n",
" shortest_paths = [[] for _ in range(num_nodes)]\n",
" shortest_paths[start_node] = [start_node]\n",
" priority_queue = [(0, start_node)]\n",
" while priority_queue:\n",
" current_distance, current_node = heapq.heappop(priority_queue)\n",
" if current_distance == shortest_distances[current_node]:\n",
" for neighbor, distance in enumerate(distances[current_node]):\n",
" new_distance = current_distance + distance\n",
" if new_distance < shortest_distances[neighbor]:\n",
" shortest_distances[neighbor] = new_distance\n",
" shortest_paths[neighbor] = shortest_paths[current_node] + [neighbor]\n",
" heapq.heappush(priority_queue, (new_distance, neighbor))\n",
" return shortest_distances, shortest_paths\n",
"\n",
"\n",
"dijkstra_distances, dijkstra_paths = calculate_dijkstra(0, distance_matrix)\n",
"\n",
"\n",
"def ant_colony_optimization(distances, num_ants, num_iterations, evaporation_rate, alpha, beta, num_trucks):\n",
" num_nodes = len(distances)\n",
" pheromone = np.ones((num_nodes, num_nodes)) \n",
" best_solution = None\n",
" best_distance = float('inf')\n",
" best_delay = float('inf')\n",
" best_score = float('inf') \n",
" best_waiting_times = None\n",
"\n",
" for iteration in range(num_iterations):\n",
" solutions = []\n",
" for ant in range(num_ants):\n",
" visited = set()\n",
" current_node = random.randint(0, num_nodes - 1)\n",
" visited.add(current_node)\n",
" solution = [current_node]\n",
" while len(visited) < num_nodes:\n",
" next_node = None\n",
" probabilities = []\n",
" arrival_time = 0\n",
" for node in range(num_nodes):\n",
" if node not in visited and (node !=0 or len(visited) == num_nodes - 1):\n",
" pheromone_value = pheromone[current_node][node]\n",
" distance_value = distances[current_node][node]\n",
" wait_time = max(0, time_windows[node][0]- (arrival_time + distance_value))\n",
" probability = (pheromone_value ** alpha) * ((1 / (distance_value + wait_time)) ** beta)\n",
" probabilities.append((node, probability))\n",
" total_probability = sum(prob for _, prob in probabilities)\n",
" probabilities = [(node, prob / total_probability) for node, prob in probabilities]\n",
" roulette_wheel = random.random()\n",
" probability_sum = 0\n",
" for node, probability in probabilities:\n",
" probability_sum += probability\n",
" if probability_sum >= roulette_wheel:\n",
" next_node = node\n",
" break\n",
" visited.add(next_node)\n",
" solution.append(next_node)\n",
" current_node = next_node\n",
" solution.append(0)\n",
" solutions.append(solution)\n",
"\n",
" for solution in solutions:\n",
" distance, delay, waiting_times = evaluate_solution(solution, distances, time_windows, dijkstra_distances)\n",
" score = distance + delay\n",
" if score < best_score:\n",
" best_solution = solution\n",
" best_distance = distance\n",
" best_delay = delay\n",
" best_waiting_times = waiting_times\n",
" pheromone *= evaporation_rate\n",
" for solution in solutions:\n",
" delta_pheromone = 1 / (evaluate_solution(solution, distances, time_windows, dijkstra_distances)[0] + 0.01)\n",
" for i in range(num_nodes - 1):\n",
" node1 = solution[i]\n",
" node2 = solution[i + 1]\n",
" pheromone[node1][node2] += delta_pheromone\n",
" pheromone[node2][node1] += delta_pheromone\n",
" truck_solutions = []\n",
" num_nodes_per_truck = num_nodes // num_trucks\n",
" for i in range(num_trucks):\n",
" start_index = i * num_nodes_per_truck\n",
" end_index = start_index + num_nodes_per_truck\n",
" truck_solution = best_solution[start_index:end_index]\n",
" \n",
" # Ajoutez le chemin le plus court entre le nœud 0 et le premier nœud du chemin\n",
" dijkstra_path_to_first_node = dijkstra_paths[truck_solution[0]]\n",
" truck_solution = dijkstra_path_to_first_node + truck_solution\n",
" \n",
" # Ajoutez le chemin le plus court entre le dernier nœud du chemin et le dépôt (nœud 0)\n",
" dijkstra_path_to_depot = dijkstra_paths[truck_solution[-1]]\n",
" truck_solution = truck_solution + dijkstra_path_to_depot\n",
"\n",
" truck_solutions.append(truck_solution)\n",
" return truck_solutions, best_distance, best_waiting_times\n",
"\n",
"\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": 16,
"id": "f24a5980",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Meilleures solutions :\n",
"Camion 1 : [0, 9, 9, 8, 17, 7, 1, 13, 14, 0, 14]\n",
"Camion 2 : [0, 12, 12, 18, 4, 5, 20, 19, 15, 0, 15]\n",
"Camion 3 : [0, 10, 10, 3, 2, 16, 6, 11, 0, 0]\n",
"Distance totale : 48.0\n"
]
}
],
"source": [
"num_thread = 1\n",
"num_ants = 10\n",
"num_iterations = 100\n",
"evaporation_rate = 0.5\n",
"alpha = 1\n",
"beta = 1\n",
"\n",
"num_trucks = 3 # Nombre de camions disponibles\n",
"\n",
"\n",
"\n",
"with concurrent.futures.ThreadPoolExecutor(max_workers=num_thread) as executor:\n",
" futures = [executor.submit(ant_colony_optimization, distance_matrix, num_ants, num_iterations, evaporation_rate, alpha, beta, num_trucks) for _ in range(num_thread)]\n",
"\n",
" for future in concurrent.futures.as_completed(futures):\n",
" best_truck_solutions, best_distance, best_waiting_times = future.result()\n",
" print(\"Meilleures solutions :\")\n",
" for i, truck_solution in enumerate(best_truck_solutions):\n",
" print(f\"Camion {i+1} : {truck_solution}\")\n",
" print(\"Distance totale :\", best_distance)"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.3"
}
},
"nbformat": 4,
"nbformat_minor": 5
}