{
 "cells": [
  {
   "attachments": {},
   "cell_type": "markdown",
   "id": "83c2b1e7-b401-4a15-adf0-d43cebf9ad81",
   "metadata": {},
   "source": [
    "# **Algorithm project** # \n"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "id": "0b4f92e5-ac40-4491-983d-890c4f0f6694",
   "metadata": {},
   "source": [
    "## <u>Project context </u>\n"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "id": "5f483b31-1246-4f00-ae39-718ef92ce9eb",
   "metadata": {},
   "source": [
    "ADEME has launched a call to promote new mobility solutions. CesiCDP, with its team of 4 people, is responding to this call by focusing on the management of delivery rounds. The aim is to minimize the total duration of the round, taking into account traffic and other constraints. Attractive financing is at stake to develop CesiCDP's business. Adding further constraints to the problem will make the study more realistic and more difficult to solve, but it will demonstrate the team's expertise."
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "id": "fc23daaf-9f25-4c5a-bf6c-300a7ed8d623",
   "metadata": {},
   "source": [
    "Our aim is to develop an algorithm that will enable us to pass through all the delivery points in an optimized time."
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "id": "d633beb7-8f26-46d4-9cd9-1d0093e5b5c3",
   "metadata": {},
   "source": [
    "## <u>Selected constraint</u>\n"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "id": "35fc1c3c-d7a9-4423-a948-aa00ab51dbf4",
   "metadata": {},
   "source": [
    "The constraint we chose was the following:\n",
    "- To have several trucks available simultaneously to make deliveries."
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "id": "ba356166-604a-4627-ac0f-93b76eb7a22d",
   "metadata": {},
   "source": [
    "## <u>Formulation of the problem </u>"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "id": "c4d6888b-14e6-4745-880f-0a063ebf7476",
   "metadata": {},
   "source": [
    "Consider a graph $G=(V,E)$, where $V$ is the set of cities (or delivery points) and $E$ is the set of roads between cities. There are $k$ trucks available to make deliveries.\n",
    "\n",
    "The problem is to find a route for each truck, so that all deliveries are made in the shortest possible time, both to and from the depot.\n",
    "\n",
    "The problem we have with the above constraints is the VRP (Vehicle Routing Problem)."
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "id": "6d392f68",
   "metadata": {},
   "source": [
    "## <u> Problem constraints </u>"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "id": "f95dba22",
   "metadata": {},
   "source": [
    "List of problem constraints:\n",
    "\n",
    "- All customers must be served\n",
    "- A customer can only be served by one vehicle.\n",
    "- When leaving a customer, a vehicle can only go to one other customer.\n",
    "\n",
    "We will therefore assign each customer to a route served by a single vehicle."
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "id": "c1ca5507",
   "metadata": {},
   "source": [
    "## <u>Demonstrating the complexity of the vehicle routing problem (VRP) </u>"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "id": "37632b4b",
   "metadata": {},
   "source": [
    "#### **Introduction**\n",
    "\n",
    "The Vehicle Routing Problem (VRP) is an extension of the Traveling Salesman Problem (TSP), and is known to be an NP-hard problem. We will demonstrate the complexity of VRP based on the Hamiltonian chain problem, which is known to be NP-complete."
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "id": "6a63522a",
   "metadata": {},
   "source": [
    "#### **Preliminary definitions**\n",
    "\n",
    "- Hamiltonian chain problem**: the problem consists in determining whether a given undirected graph has a Hamiltonian chain, i.e. a path that visits each vertex exactly once.\n",
    "\n",
    "- Traveling Salesman Problem (TSP)**: the problem consists in finding the shortest possible circuit that visits each city in a given set of cities and returns to the original city.\n",
    "\n",
    "- Vehicle Routing Problem (VRP)**: the problem consists in delivering a series of customers with several vehicles while minimizing the total cost, such as delivery time or distance traveled."
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "id": "589a1874",
   "metadata": {},
   "source": [
    "#### **Proof of the complexity of the TSP**.\n",
    "\n",
    "The TSP is an extension of the Hamiltonian chain problem. In fact, a special case of the TSP is the Hamiltonian chain problem, in which all edges have the same weight (or cost). In this case, finding the shortest circuit in the TSP is equivalent to finding a Hamiltonian chain in the graph. Since the Hamiltonian chain problem is NP-complete, the TSP must be at least as difficult, so the TSP is NP-complete."
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "id": "92658e81",
   "metadata": {},
   "source": [
    "#### **Proof of the complexity of the VRP**.\n",
    "\n",
    "The VRP is an extension of the TSP. In fact, a special case of the VRP is the TSP where there is only one vehicle available to make deliveries. In this case, finding the solution to the VRP is the same as finding the solution to the TSP.\n",
    "\n",
    "Since the TSP is NP-complete, the VRP must be at least as difficult, so the VRP is NP-difficult. Furthermore, VRP introduces additional constraints, such as multiple vehicles and potentially vehicle capacities and time windows, making it even more complex."
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "id": "a4106346",
   "metadata": {},
   "source": [
    "#### **Conclusion**\n",
    "\n",
    "In conclusion, we have shown that VRP is an NP-hard problem by reducing it to the NP-complete TSP problem, which in turn can be reduced to the NP-complete Hamiltonian chain problem. This demonstration highlights the difficulty of solving the VRP, particularly for large instances. In practice, (meta)heuristic and approximate methods are often used to solve the VRP, as we shall see later."
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "id": "16b3b8a7-4658-4509-a511-7a395654e6f1",
   "metadata": {},
   "source": [
    "## <u> Mathematical modeling </u>"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "id": "b54bfa8c",
   "metadata": {},
   "source": [
    "#### **Set and parameters**"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "id": "637eb295",
   "metadata": {},
   "source": [
    "$V=\\{0,1,2,...,n_v\\}$ : the set of cities, where 0 is the base (or depot), $1,2,...,n_v$ are the delivery cities. $n_v+1$ will be the depot for the return. <br>\n",
    "$K=\\{1,2,...,k\\}$ : all trucks. <br>\n",
    "$E$ represents the arcs between two customers $i,j \\in V$ <br>\n",
    "$G=(V,E)$ : the graph with V as vertices (cities) and E as edges <br>\n",
    "$d_{ij}$​ : distance (or travel time) from city i to city $j$ (travel cost) <br>\n",
    "$M$ : a great constant."
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "id": "1219e4f2",
   "metadata": {},
   "source": [
    "#### **Decision variables**"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "id": "a6d398fa",
   "metadata": {},
   "source": [
    "$x_{ijk}$​ : binary variable worth 1 if truck $k$ moves from city $i$ to city $j$, and 0 otherwise."
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "id": "e635cf14",
   "metadata": {},
   "source": [
    "#### **Objective function**"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "id": "56652859",
   "metadata": {},
   "source": [
    "$$\\min \\sum_{k∈K} \\sum_{⁡i∈V} \\sum_{⁡j∈V} d_{ij} x_{ijk} $$"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "id": "a1465000",
   "metadata": {},
   "source": [
    "#### **VRP constraints**\n",
    "\n",
    "- Each city is visited once and only once:\n",
    "$$\\sum_{⁡k \\in K} \\sum_{⁡j \\in V} x_{ijk} = 1, \\forall i \\in V, i \\ne 0$$\n",
    "\n",
    "- If a truck visits a city, it must leave it:\n",
    "$$\\sum_{i \\in V} x_{ijk} = \\sum_{j \\in V} x_{ijk}, \\forall k \\in K, \\forall i \\in V, \\forall j \\in V $$\n",
    "<br> \n",
    "\n",
    "- Sub-tour elimination constraint (to ensure that each truck completes a full tour) :\n",
    "$$\\sum_{i \\in S, j \\notin S} x_{ijk} \\geq 1, \\forall k \\in K, \\forall \\; subset \\; S \\; de \\; V, 0 \\in S, S \\ne V $$"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "id": "0833096d",
   "metadata": {},
   "source": [
    "#### **VRPTW constraints**\n",
    "\n",
    "- Each city is visited once and only once:\n",
    "∑𝑘∈𝐾∑𝑗∈𝑉𝑥𝑖𝑗𝑘=1, ∀𝑖∈𝑉, 𝑖≠0\n",
    "\n",
    "- If a truck visits a city, it must leave it:\n",
    "∑𝑖∈𝑉𝑥𝑖𝑗𝑘=∑𝑗∈𝑉𝑥𝑖𝑗𝑘, ∀𝑘∈𝐾, ∀𝑖∈𝑉, ∀𝑗∈𝑉\n",
    "\n",
    "- Sub-tour elimination constraint (to ensure that each truck completes a full tour):\n",
    "∑𝑖∈𝑆,𝑗∉𝑆𝑥𝑖𝑗𝑘≥1, ∀𝑘∈𝐾, ∀𝑠𝑢𝑏𝑠𝑒𝑡𝑆𝑑𝑒𝑉, 0∈𝑆, 𝑆≠𝑉\n",
    "\n",
    "- Each truck arrives within the time window of each city:\n",
    "a_ik + t_ij ≤ a_jk + M(1 - x_ijk), ∀k∈K, ∀(i, j)∈A, ∀a_ik ∈ [e_i, l_i]\n",
    "\n",
    "- Each truck leaves the city after the service time:\n",
    "a_ik + s_i = d_ik, ∀k∈K, ∀i∈V, ∀a_ik ∈ [e_i, l_i]\n",
    "\n",
    "Note:\n",
    "Here, a_ik is the arrival time of vehicle k at node i, t_ij is the travel time from node i to node j, M is a large positive number, x_ijk is a binary variable that is 1 if vehicle k travels from node i to node j and 0 otherwise, e_i is the earliest time that a service can start at node i, l_i is the latest time that a service can start at node i, and s_i is the service time at node i.\n",
    "\n",
    "These constraints take into account not just the need to visit each city once, but also the requirement to arrive and depart within specified time windows, which makes the problem more complex than the standard VRP."
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "id": "7670fdf4-884c-4352-83fa-eed0c8b50074",
   "metadata": {},
   "source": [
    "## <u>Resolution algorithm (with coordinates)</u>"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "id": "45133ac2",
   "metadata": {},
   "source": [
    "#### Importing the necessary libraries"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "794657d7",
   "metadata": {},
   "outputs": [],
   "source": [
    "from sklearn.cluster import KMeans\n",
    "import matplotlib.pyplot as plt\n",
    "import numpy as np\n",
    "import random, time, math\n",
    "from tests.libs.clustering import split_tour_across_clusters"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "id": "115e14eb",
   "metadata": {},
   "source": [
    "## <u>Resolution algorithm (with matrixes)</u>\n"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "id": "be1210b9",
   "metadata": {},
   "source": [
    "### Importation des bibliothèques nécessaires"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 49,
   "id": "30eca565",
   "metadata": {},
   "outputs": [],
   "source": [
    "import random\n",
    "import numpy as np\n",
    "import networkx as nx\n",
    "import matplotlib.pyplot as plt\n",
    "import matplotlib\n",
    "from IPython.display import display, Markdown\n",
    "from sklearn.cluster import KMeans\n",
    "import matplotlib.cm as cm"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "id": "37f80b5b",
   "metadata": {},
   "source": [
    "### Fonction pour générer les villes (distances, fenêtres de temps et graphe)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 50,
   "id": "9a72af6d",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Fonction pour générer une matrice de distances aléatoires entre les villes\n",
    "def generer_matrice_distances(num_villes, probabilite_incompletude):\n",
    "    distances = np.zeros((num_villes, num_villes))  # Création d'une matrice de zéros de taille num_villes*num_villes\n",
    "    # Remplir la matrice avec des distances aléatoires ou infinies (si les villes ne sont pas connectées)\n",
    "    for i in range(num_villes):\n",
    "        for j in range(i+1, num_villes):\n",
    "            if random.random() < probabilite_incompletude:\n",
    "                distances[i, j] = float('inf')\n",
    "                distances[j, i] = float('inf')\n",
    "            else:\n",
    "                distances[i, j] = random.randint(1, 10)\n",
    "                distances[j, i] = distances[i, j]\n",
    "    return distances\n",
    "\n",
    "# Fonction pour générer les fenêtres de temps pour chaque ville\n",
    "def generer_fenetres_temps(num_villes, heure_debut, heure_fin):\n",
    "    fenetres_temps = []\n",
    "    for _ in range(num_villes):\n",
    "        debut = random.randint(heure_debut, heure_fin - 1)\n",
    "        fin = random.randint(debut + 1, heure_fin)\n",
    "        fenetres_temps.append((debut, fin))\n",
    "    return fenetres_temps\n",
    "\n",
    "# Fonction pour générer le graphe de villes en utilisant Networkx\n",
    "def generer_graphe_villes(distances, fenetres_temps):\n",
    "    num_villes = distances.shape[0]\n",
    "    G = nx.Graph()  # Création d'un graphe vide\n",
    "    # Ajouter des nœuds au graphe (chaque ville est un nœud)\n",
    "    for i in range(num_villes):\n",
    "        G.add_node(i+1, fenetre_temps=fenetres_temps[i])\n",
    "    # Ajouter des arêtes au graphe (les distances entre les villes)\n",
    "    for u in range(num_villes):\n",
    "        for v in range(u+1, num_villes):\n",
    "            if distances[u, v] != float('inf'):\n",
    "                G.add_edge(u+1, v+1, distance=distances[u, v])\n",
    "    return G\n"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "id": "99571e1b",
   "metadata": {},
   "source": [
    "### Classe réprésentant une particule dans l'algorithme de PSO"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 51,
   "id": "b92d9954",
   "metadata": {},
   "outputs": [],
   "source": [
    "class Particle:\n",
    "    def __init__(self, num_villes, num_camions, distances, fenetres_temps, infinite_distance_value=1e6):\n",
    "        self.position = np.random.permutation(range(1, num_villes+1))\n",
    "        self.velocity = np.zeros_like(self.position)\n",
    "        self.best_position = self.position.copy()\n",
    "        self.best_cost = float('inf')\n",
    "        self.num_camions = num_camions\n",
    "        self.distances = distances\n",
    "        self.fenetres_temps = fenetres_temps\n",
    "        self.infinite_distance_value = infinite_distance_value\n",
    "\n",
    "        # Nouveau : regroupement des villes par clustering\n",
    "        self.villes_cluster = self.cluster_villes()\n",
    "\n",
    "    #Méthode pour regrouper les villes par clustering\n",
    "    def cluster_villes(self):\n",
    "        # Remplacer les distances infinies par une valeur élevée pour le clustering\n",
    "        distances_clustering = np.where(self.distances == float('inf'), self.infinite_distance_value, self.distances)\n",
    "        kmeans = KMeans(n_clusters=self.num_camions, n_init=10)\n",
    "        clusters = kmeans.fit_predict(distances_clustering)\n",
    "        return clusters\n",
    "\n",
    "    def evaluate_cost(self):\n",
    "        # Modification : assigner les villes aux camions en fonction des clusters\n",
    "        camions = [[] for _ in range(self.num_camions)]\n",
    "        for i in range(len(self.position)):\n",
    "            ville_actuelle = int(self.position[i])\n",
    "            camion = self.villes_cluster[ville_actuelle - 1]\n",
    "            camions[camion].append(ville_actuelle)\n",
    "\n",
    "        total_cost = 0\n",
    "        for camion in camions:\n",
    "            if len(camion) > 0:\n",
    "                heure_depart = self.fenetres_temps[camion[0] - 1][0]\n",
    "                for i in range(len(camion) - 1):\n",
    "                    u, v = int(camion[i]), int(camion[i+1])\n",
    "                    distance = self.distances[u-1, v-1] if self.distances[u-1, v-1] != float('inf') else self.infinite_distance_value\n",
    "                    total_cost += distance\n",
    "                    heure_arrivee = heure_depart + distance\n",
    "                    fenetre_temps = self.fenetres_temps[v-1]\n",
    "                    if heure_arrivee < fenetre_temps[0]:\n",
    "                        total_cost += fenetre_temps[0] - heure_arrivee\n",
    "                        heure_depart = fenetre_temps[0]\n",
    "                    elif heure_arrivee > fenetre_temps[1]:\n",
    "                        total_cost += heure_arrivee - fenetre_temps[1]\n",
    "                        heure_depart = heure_arrivee\n",
    "                    else:\n",
    "                        heure_depart = heure_arrivee\n",
    "        return total_cost\n",
    "\n",
    "    # Fonction pour mettre à jour la vitesse de la particule\n",
    "    def update_velocity(self, global_best_position, inertia_weight, cognitive_weight, social_weight):\n",
    "        r1 = np.random.random()\n",
    "        r2 = np.random.random()\n",
    "        cognitive_component = cognitive_weight * r1 * (self.best_position - self.position)\n",
    "        social_component = social_weight * r2 * (global_best_position - self.position)\n",
    "        self.velocity = inertia_weight * self.velocity + cognitive_component + social_component\n",
    "\n",
    "    # Fonction pour mettre à jour la position de la particule\n",
    "    def update_position(self):\n",
    "        indices = np.random.choice(range(len(self.position)), 2, replace=False)\n",
    "        self.position[indices[0]], self.position[indices[1]] = self.position[indices[1]], self.position[indices[0]]\n",
    "        current_cost = self.evaluate_cost()\n",
    "        if current_cost < self.best_cost:\n",
    "            self.best_cost = current_cost\n",
    "            self.best_position = self.position.copy()\n"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "id": "c36a49cb",
   "metadata": {},
   "source": [
    "### Définition des paramètres et génération d'une ville"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 52,
   "id": "611beff3",
   "metadata": {},
   "outputs": [],
   "source": [
    "num_villes = 50\n",
    "num_camions = 5\n",
    "num_particles = 50\n",
    "max_iterations = 300\n",
    "inertia_weight = 2\n",
    "cognitive_weight = 1\n",
    "social_weight = 1.5\n",
    "probabilite_incompletude = 0.8\n",
    "heure_debut = 8\n",
    "heure_fin = 18\n",
    "infinite_distance_value = 1e6\n",
    "\n",
    "\n",
    "distances = generer_matrice_distances(num_villes, probabilite_incompletude)\n",
    "fenetres_temps = generer_fenetres_temps(num_villes, heure_debut, heure_fin)\n",
    "G = generer_graphe_villes(distances, fenetres_temps)"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "id": "aecb5aa1",
   "metadata": {},
   "source": [
    "### Graphe de la ville"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 53,
   "id": "937f74b0",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 187656 characters]",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "pos = nx.spring_layout(G)\n",
    "labels = {node: str(node) for node in G.nodes()}\n",
    "nx.draw_networkx_nodes(G, pos)\n",
    "nx.draw_networkx_edges(G, pos)\n",
    "nx.draw_networkx_labels(G, pos, labels)\n",
    "plt.title(\"Graphe des villes avec fenêtres de temps\")\n",
    "plt.axis(\"off\")\n",
    "plt.show()"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "id": "9513d5b3",
   "metadata": {},
   "source": [
    "### Algorithme PSO"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 54,
   "id": "9e81fc9b",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/markdown": [
       "### Meilleure solution trouvée:\n",
       "\n",
       "- **Positions des villes**: 22, 28, 5, 12, 50, 34, 32, 30, 46, 21, 17, 27, 49, 14, 6, 9, 24, 1, 19, 43, 15, 4, 11, 35, 38, 18, 31, 33, 13, 41, 2, 7, 42, 48, 26, 37, 29, 45, 20, 3, 25, 36, 16, 39, 8, 40, 44, 10, 23, 47\n",
       "- **Coût total**: 87000972.0"
      ],
      "text/plain": [
       "<IPython.core.display.Markdown object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "particles = []\n",
    "global_best_position = np.random.permutation(range(1, num_villes+1))\n",
    "global_best_cost = float('inf')\n",
    "for _ in range(num_particles):\n",
    "    particle = Particle(num_villes, num_camions, distances, fenetres_temps, infinite_distance_value)\n",
    "    particles.append(particle)\n",
    "    particle_cost = particle.evaluate_cost()\n",
    "    if particle_cost < global_best_cost:\n",
    "        global_best_cost = particle_cost\n",
    "        global_best_position = particle.position.copy()\n",
    "\n",
    "iteration = 0\n",
    "best_costs = []\n",
    "while iteration < max_iterations:\n",
    "    for particle in particles:\n",
    "        particle.update_velocity(global_best_position, inertia_weight, cognitive_weight, social_weight)\n",
    "        particle.update_position()\n",
    "        particle_cost = particle.evaluate_cost()\n",
    "        if particle_cost < global_best_cost:\n",
    "            global_best_cost = particle_cost\n",
    "            global_best_position = particle.position.copy()\n",
    "    best_costs.append(global_best_cost)\n",
    "    iteration += 1\n",
    "\n",
    "\n",
    "best_position_str = \", \".join(str(node) for node in global_best_position)\n",
    "best_cost_str = str(global_best_cost)\n",
    "markdown_text = f\"### Meilleure solution trouvée:\\n\\n- **Positions des villes**: {best_position_str}\\n- **Coût total**: {best_cost_str}\"\n",
    "display(Markdown(markdown_text))"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "id": "ee1b08b8",
   "metadata": {},
   "source": [
    "### Génération de la carte représentant le chemin de chaque camion"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 55,
   "id": "3b8d07aa",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/markdown": [
       "Camion 1: 22 -> 34 -> 17 -> 9 -> 15 -> 18 -> 2 -> 37 -> 25 -> 40 -> 22"
      ],
      "text/plain": [
       "<IPython.core.display.Markdown object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/markdown": [
       "Camion 2: 28 -> 32 -> 27 -> 24 -> 4 -> 31 -> 7 -> 29 -> 36 -> 44 -> 28"
      ],
      "text/plain": [
       "<IPython.core.display.Markdown object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/markdown": [
       "Camion 3: 5 -> 30 -> 49 -> 1 -> 11 -> 33 -> 42 -> 45 -> 16 -> 10 -> 5"
      ],
      "text/plain": [
       "<IPython.core.display.Markdown object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/markdown": [
       "Camion 4: 12 -> 46 -> 14 -> 19 -> 35 -> 13 -> 48 -> 20 -> 39 -> 23 -> 12"
      ],
      "text/plain": [
       "<IPython.core.display.Markdown object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/markdown": [
       "Camion 5: 50 -> 21 -> 6 -> 43 -> 38 -> 41 -> 26 -> 3 -> 8 -> 47 -> 50"
      ],
      "text/plain": [
       "<IPython.core.display.Markdown object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "image/png": "[encoded data, 132720 characters]",
      "text/plain": [
       "<Figure size 1000x600 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "chemins_camions = []\n",
    "# Génération de la carte des couleurs\n",
    "cmap = matplotlib.colormaps['gist_ncar']\n",
    "colors = [cmap(i/num_camions) for i in range(num_camions)]\n",
    "\n",
    "for camion in range(num_camions):\n",
    "    chemin = []\n",
    "    for i in range(len(global_best_position)):\n",
    "        if i % num_camions == camion:\n",
    "            ville = global_best_position[i]\n",
    "            chemin.append(int(ville))\n",
    "    chemin.append(chemin[0])  \n",
    "    chemins_camions.append(chemin)\n",
    "\n",
    "plt.figure(figsize=(10, 6))\n",
    "plt.title(\"Chemins des camions\")\n",
    "plt.axis(\"off\")\n",
    "for i in range(num_camions):\n",
    "    chemin = chemins_camions[i]\n",
    "    chemin_str = \" -> \".join(str(ville) for ville in chemin)\n",
    "    text = f\"Camion {i+1}: {chemin_str}\"\n",
    "    display(Markdown(text))\n",
    "    edges = [(chemin[j], chemin[j+1]) for j in range(len(chemin)-1)]\n",
    "    nx.draw_networkx_edges(G, pos, edgelist=edges, edge_color=colors[i], label=f\"Camion {i+1}\")\n",
    "    nx.draw_networkx_labels(G, pos, labels)\n",
    "plt.legend()\n",
    "plt.show()"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "id": "aa26c4b1",
   "metadata": {},
   "source": [
    "### Génération de l'histogramme représentant le coût total de chaque camion"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 56,
   "id": "84c17bad",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 21168 characters]",
      "text/plain": [
       "<Figure size 1000x600 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# Calculer le coût total de chaque chemin\n",
    "couts_parcours = []\n",
    "for chemin in chemins_camions:\n",
    "    cout_parcours = 0\n",
    "    for i in range(len(chemin) - 1):\n",
    "        u, v = chemin[i] - 1, chemin[i + 1] - 1  # Ajuster les indices\n",
    "        # Si la distance est infinie, utilisez une valeur de remplacement.\n",
    "        distance = distances[u, v] if distances[u, v] != float('inf') else infinite_distance_value\n",
    "        cout_parcours += distance\n",
    "    couts_parcours.append(cout_parcours)\n",
    "\n",
    "# Créer un histogramme des coûts du parcours\n",
    "plt.figure(figsize=(10, 6))\n",
    "plt.bar(range(1, num_camions + 1), couts_parcours, color=cm.rainbow(np.linspace(0, 1, num_camions)))\n",
    "plt.xlabel(\"Camion\")\n",
    "plt.ylabel(\"Coût du parcours\")\n",
    "plt.title(\"Coût du parcours pour chaque camion\")\n",
    "plt.show()\n"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "id": "2a44993c",
   "metadata": {},
   "source": [
    "### Génération du graphique du meilleur coût en fonction des itérations\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 57,
   "id": "86aa4aaa",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 31256 characters]",
      "text/plain": [
       "<Figure size 1000x600 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# Graphique de l'évolution du meilleur coût\n",
    "plt.figure(figsize=(10, 6))\n",
    "plt.plot(range(1, max_iterations + 1), best_costs)\n",
    "plt.title(\"Évolution du meilleur coût au fil des itérations\")\n",
    "plt.xlabel(\"Itération\")\n",
    "plt.ylabel(\"Meilleur coût\")\n",
    "plt.grid(True)\n",
    "plt.show()\n"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "id": "7920c3f6",
   "metadata": {},
   "source": [
    "### Test de la variation du coût optimal en faisant varier le paramètre \"inertia weight\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 58,
   "id": "7b503f38",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 49684 characters]",
      "text/plain": [
       "<Figure size 1000x600 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# Liste des valeurs d'inertie à tester\n",
    "inertia_values = np.linspace(0.1, 5, 10) \n",
    "\n",
    "# Liste pour stocker le coût optimal pour chaque valeur d'inertie\n",
    "optimal_costs = []\n",
    "\n",
    "# Boucle sur chaque valeur d'inertie\n",
    "for inertia_weight in inertia_values:\n",
    "    # Initialisation des particules et du meilleur coût global\n",
    "    particles = []\n",
    "    global_best_position = np.random.permutation(range(1, num_villes+1))\n",
    "    global_best_cost = float('inf')\n",
    "    \n",
    "    for _ in range(num_particles):\n",
    "        particle = Particle(num_villes, num_camions, distances, fenetres_temps, infinite_distance_value)\n",
    "        particles.append(particle)\n",
    "        particle_cost = particle.evaluate_cost()\n",
    "        if particle_cost < global_best_cost:\n",
    "            global_best_cost = particle_cost\n",
    "            global_best_position = particle.position.copy()\n",
    "    \n",
    "    iteration = 0\n",
    "    best_costs = []\n",
    "    while iteration < max_iterations:\n",
    "        for particle in particles:\n",
    "            particle.update_velocity(global_best_position, inertia_weight, cognitive_weight, social_weight)\n",
    "            particle.update_position()\n",
    "            particle_cost = particle.evaluate_cost()\n",
    "            if particle_cost < global_best_cost:\n",
    "                global_best_cost = particle_cost\n",
    "                global_best_position = particle.position.copy()\n",
    "        best_costs.append(global_best_cost)\n",
    "        iteration += 1\n",
    "\n",
    "    # Stocker le coût optimal pour cette valeur d'inertie\n",
    "    optimal_costs.append(global_best_cost)\n",
    "\n",
    "# Graphique de l'évolution du coût optimal en fonction de l'inertie\n",
    "plt.figure(figsize=(10, 6))\n",
    "plt.plot(inertia_values, optimal_costs)\n",
    "plt.title(\"Évolution du coût optimal en fonction de l'inertie\")\n",
    "plt.xlabel(\"Inertie\")\n",
    "plt.ylabel(\"Coût optimal\")\n",
    "plt.show()\n",
    "\n"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "id": "f3b2b02d",
   "metadata": {},
   "source": [
    "### Test de la variation du coût optimal en faisant varier le paramètre \"cognitive weight\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 59,
   "id": "c33fdf6c",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 62404 characters]",
      "text/plain": [
       "<Figure size 1000x600 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# Liste des valeurs de poids cognitif à tester\n",
    "cognitive_values = np.linspace(0.1, 5, 10)  \n",
    "\n",
    "# Liste pour stocker le coût optimal pour chaque valeur de poids cognitif\n",
    "optimal_costs = []\n",
    "\n",
    "# Boucle sur chaque valeur d'inertie\n",
    "for cognitive_weight in cognitive_values:\n",
    "    # Initialisation des particules et du meilleur coût global\n",
    "    particles = []\n",
    "    global_best_position = np.random.permutation(range(1, num_villes+1))\n",
    "    global_best_cost = float('inf')\n",
    "    \n",
    "    for _ in range(num_particles):\n",
    "        particle = Particle(num_villes, num_camions, distances, fenetres_temps, infinite_distance_value)\n",
    "        particles.append(particle)\n",
    "        particle_cost = particle.evaluate_cost()\n",
    "        if particle_cost < global_best_cost:\n",
    "            global_best_cost = particle_cost\n",
    "            global_best_position = particle.position.copy()\n",
    "    \n",
    "    iteration = 0\n",
    "    best_costs = []\n",
    "    while iteration < max_iterations:\n",
    "        for particle in particles:\n",
    "            particle.update_velocity(global_best_position, inertia_weight, cognitive_weight, social_weight)\n",
    "            particle.update_position()\n",
    "            particle_cost = particle.evaluate_cost()\n",
    "            if particle_cost < global_best_cost:\n",
    "                global_best_cost = particle_cost\n",
    "                global_best_position = particle.position.copy()\n",
    "        best_costs.append(global_best_cost)\n",
    "        iteration += 1\n",
    "\n",
    "    # Stocker le coût optimal pour cette valeur de poids cognitif\n",
    "    optimal_costs.append(global_best_cost)\n",
    "\n",
    "# Graphique de l'évolution du coût optimal en fonction du poids cognitif\n",
    "plt.figure(figsize=(10, 6))\n",
    "plt.plot(cognitive_values, optimal_costs)\n",
    "plt.title(\"Évolution du coût optimal en fonction de cognitive Weight\")\n",
    "plt.xlabel(\"Cognitive Weight\")\n",
    "plt.ylabel(\"Coût optimal\")\n",
    "plt.show()\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "4b51ba43",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "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
}
