{
"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": [
"## Contraintes choisies :\n"
]
},
{
"attachments": {},
"cell_type": "markdown",
"id": "35fc1c3c-d7a9-4423-a948-aa00ab51dbf4",
"metadata": {},
"source": [
"Les contraintes que nous avons choisi sont les suivantes:\n",
"- Avoir plusieurs camions disponible simultanément pour effectuer les livraisons.\n",
"- Fenêtre de temps de livraison pour chaque object\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. Chaque ville $v∈V$ a une fenêtre de temps de livraison $[a_v,b_v]$, où $a_v$ est le début de la fenêtre et $b_v$ est la fin de la fenêtre. 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 dans leurs fenêtres de temps respectives et que la date de retour du dernier camion à la base soit minimisée.\n",
"\n",
"\n",
"Le problème que nous avons avec les contraintes ci-dessus est le problème du VRPTW (Vehicule Routing Problem avec la contrainte supplémentaire de fenêtres d'ouverture)."
]
},
{
"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",
"- Un véhicule ne peut servir un client que s'il a assez de capacité pour servir le 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\\}$ : l'ensemble des villes, où 0 est la base (ou le dépôt) et $1,2,...,n$ sont les villes de livraison.
\n",
"$K=\\{1,2,...,k\\}$ : l'ensemble des camions.
\n",
"$d_{ij}$ : la distance (ou le temps de trajet) de la ville ii à la ville $j$.
\n",
"$[a_i,b_i]$ : la fenêtre de temps de livraison pour la ville $i$.
\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.
\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:\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(1−x_{ijk}),∀i,j \\in V,i \\ne j,∀k \\in K$$\n",
"\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",
"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$$"
]
},
{
"attachments": {},
"cell_type": "markdown",
"id": "286caeb7-26cb-486a-b5f0-94a3f24811fd",
"metadata": {},
"source": [
"Nous allons représenter notre problème par un graphe
\n",
"$G=(V,E)$\n",
"\n",
"$V$ représente les sommets du graphe qui correspondent aux clients\n",
"\n",
"$E$ représente les arcs entre deux clients $i,j \\in V$\n",
"\n",
"\n",
"\n",
"On a un ensemble $C=\\{1,2,...,n_c\\}$ de clients qui doivent obtenir leur livraison qui provient du dépot.\n",
"\n",
"\n",
"L'ensemble des emplacements des clients est défini comme :\n",
"$V = C \\cup \\{0, n_c+1\\} $\n",
"\n",
"$0$ et $n_c+1$ représente le dépot, puisqu'on doit revenir au point de départ le dépots est le dernier client plus un. \n",
"\n",
"\n",
"On a un ensemble $V=\\{1,2,...,n_v\\}$ de véhicule disponible et chaque véhicule possède une capacité $Q$.\n",
"\n",
"\n",
"\n",
"# Variables de décisions: \n",
"\n",
"\n",
"\n",
"\n",
"\n",
"\n",
"\n",
"\n",
"\n",
"\n",
"\n",
"\n",
"\n",
"\n",
"\n"
]
},
{
"attachments": {},
"cell_type": "markdown",
"id": "5a3512bc-02da-4e2d-b8f6-65e70c3fe686",
"metadata": {},
"source": [
"Variables de décision:\n",
"- Un ensemble de k véhicule\n"
]
},
{
"attachments": {},
"cell_type": "markdown",
"id": "7670fdf4-884c-4352-83fa-eed0c8b50074",
"metadata": {},
"source": [
"# Initialisation de la matrice"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "337db031-2e38-4b6d-9d57-74e277d7f033",
"metadata": {
"scrolled": true
},
"outputs": [],
"source": [
"import random\n",
"import networkx as nx\n",
"import matplotlib.pyplot as plt\n",
"import numpy as np\n",
"\n",
"def generate_graph(num_nodes):\n",
" # Créer un graphe vide\n",
" G = nx.Graph()\n",
" # Ajouter les sommets au graphe\n",
" G.add_nodes_from(range(1, num_nodes + 1))\n",
" # Ajouter au moins deux arêtes par sommet\n",
" for node in G.nodes():\n",
" connected_nodes = sorted(set(G.nodes()) - {node}) # Exclure le sommet lui-même et trier les nœuds\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",
" # Ajouter des arêtes supplémentaires de manière aléatoire\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",
" return G\n",
"\n",
"graph = generate_graph(100)\n",
"A = nx.adjacency_matrix(graph).todense()\n",
"\n",
"\n",
"\n",
"\n",
"print(A)\n",
"\n",
"# Dessiner le graphe\n",
"nx.draw(graph, with_labels=True)\n",
"plt.show()"
]
},
{
"attachments": {},
"cell_type": "markdown",
"id": "0bcb13e3-9f98-4601-8140-8a2147757aed",
"metadata": {},
"source": [
"Définition des données du problème VRPTW"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "8266e414-d350-4101-8f15-9cc05ee02934",
"metadata": {
"scrolled": true
},
"outputs": [],
"source": [
"#k Nombre de camion disponible\n",
"k = 4\n",
"\n",
"#Q capacité de marchandise de chaque camion\n",
"Q = 10\n",
"\n",
"# Implémentez les étapes 1 à 6 de l'algorithme ACO ici\n",
"\n",
"# 1. Définir les données du problème VRPTW\n",
"# Votre code pour attribuer les demandes et les fenêtres de temps aux clients\n",
"# Attribuer des fenêtres de temps aux clients\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 !=100 :\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",
" 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",
"\n",
" \n",
"\n",
"\n",
"\n",
"# 3. Initialiser la matrice de phéromones\n",
"# Votre code pour initialiser la matrice de phéromones\n",
"\n",
"# 4. Implémenter la fonction de construction de la solution par une fourmi\n",
"# V\n",
"\n",
"\n",
"\n",
"\n"
]
},
{
"attachments": {},
"cell_type": "markdown",
"id": "4fcc61cf-b917-4d41-b7c4-e79ffc0c7bcd",
"metadata": {},
"source": [
"# Définition de l'algorithme des fourmis (marche pas encore)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "5a55bfc8-a5b5-4b25-b924-ee906b7b3d7e",
"metadata": {},
"outputs": [],
"source": [
"# 2. Définir les paramètres de l'algorithme ACO\n",
"num_ants = 10 # Nombre de fourmis\n",
"num_iterations = 100 # Nombre d'itérations\n",
"\n",
"# 3. Initialiser la matrice de phéromones\n",
"pheromones = 0.1 * np.ones((num_nodes, num_nodes)) # Matrice de phéromones initiale\n",
"\n",
"# 4. Implémenter la fonction de construction de la solution par une fourmi\n",
"def construct_solution(pheromones, visibility, demands, time_windows):\n",
" solutions = []\n",
" costs = []\n",
" for ant in range(num_ants):\n",
" current_node = 0 # Dépôt central\n",
" unvisited_nodes = set(range(1, num_nodes)) # Tous les clients non visités\n",
" path = [current_node]\n",
" current_time = 0\n",
" current_capacity = Q\n",
"\n",
" # Construction du chemin de la fourmi\n",
" while unvisited_nodes:\n",
" feasible_nodes = []\n",
" feasible_probabilities = []\n",
" for next_node in unvisited_nodes:\n",
" if current_node != 0 and next_node != 0 and 'distance' in graph.edges[current_node, next_node]:\n",
" if current_time + graph.edges[current_node, next_node]['distance'] <= time_windows[next_node][1] and \\\n",
" current_capacity >= demands[next_node]:\n",
" feasible_nodes.append(next_node)\n",
" probability = pheromones[current_node][next_node] ** pheromone_importance \\\n",
" * visibility[current_node][next_node] ** distance_importance\n",
" feasible_probabilities.append(probability)\n",
" if feasible_nodes:\n",
" feasible_probabilities = np.array(feasible_probabilities)\n",
" feasible_probabilities /= np.sum(feasible_probabilities)\n",
" next_node = random.choices(feasible_nodes, weights=feasible_probabilities)[0]\n",
" current_node = next_node\n",
" path.append(next_node)\n",
" unvisited_nodes.remove(next_node)\n",
" current_time += graph.edges[current_node, next_node]['distance']\n",
" current_capacity -= demands[next_node]\n",
" else:\n",
" break\n",
"\n",
" # Ajout du retour au dépôt central\n",
" path.append(0)\n",
"\n",
" # Calcul du coût du chemin\n",
" cost = sum(A[path[i]][path[i + 1]] for i in range(len(path) - 1))\n",
"\n",
" solutions.append(path)\n",
" costs.append(cost)\n",
"\n",
" return solutions, costs\n",
"\n",
"# 5. Mettre à jour les phéromones\n",
"def update_pheromones(pheromones, solutions, costs):\n",
" for solution, cost in zip(solutions, costs):\n",
" for i in range(len(solution) - 1):\n",
" current_node = solution[i]\n",
" next_node = solution[i + 1]\n",
" if cost != 0:\n",
" pheromones[current_node][next_node] += 1 / cost\n",
" return pheromones\n",
"\n",
"# 6. Répéter les étapes 4 et 5 pour un nombre donné d'itérations\n",
"best_solution = None\n",
"best_cost = float('inf')\n",
"\n",
"for iteration in range(num_iterations):\n",
" # Construction des solutions par les fourmis\n",
" solutions, costs = construct_solution(pheromones, visibility, demands, time_windows)\n",
"\n",
" # Mise à jour des phéromones\n",
" pheromones = update_pheromones(pheromones, solutions, costs)\n",
"\n",
" # Recherche de la meilleure solution\n",
" best_index = np.argmin(costs)\n",
" if costs[best_index] < best_cost:\n",
" best_solution = solutions[best_index]\n",
" best_cost = costs[best_index]\n",
"\n",
"# Affichage de la meilleure solution trouvée\n",
"print(\"Meilleure solution :\", best_solution)\n",
"print(\"Coût de la meilleure solution :\", best_cost)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "301fb5e8-9804-4592-9193-5915f1b9ecad",
"metadata": {},
"outputs": [],
"source": [
"\n",
"\n",
"\n",
"\n",
"\n",
"\n",
"\n",
"\n",
"\n",
"\n",
"\n",
"\n",
"\n",
"import random\n",
"import numpy as np\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",
" # 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",
"# Algorithme ACO\n",
"def ant_colony_optimization(distances, num_ants, num_iterations, evaporation_rate, alpha, beta):\n",
" num_nodes = len(distances)\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 = 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:\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",
" # Choix du prochain nœud basé sur les probabilités\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",
" 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",
" return best_solution, best_distance\n",
"\n",
"# Exemple d'utilisation\n",
"distances = [[0, 2, 9, 10],\n",
" [2, 0, 6, 4],\n",
" [9, 6, 0, 8],\n",
" [10, 4, 8, 0]]\n",
"\n",
"num_ants = 10\n",
"num_iterations = 100\n",
"evaporation_rate = 0.5\n",
"alpha = 1\n",
"beta = 1\n",
"\n",
"best_solution, best_distance = ant_colony_optimization(distances, num_ants, num_iterations, evaporation_rate, alpha, beta)\n",
"\n",
"print(\"Meilleure solution :\", best_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
}