{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "6ded7beb-f4c2-4491-916f-82a24a42148d",
   "metadata": {},
   "source": [
    "# Projet d'Apprentissage par Renforcement :MDP et Qlearning\n",
    "\n",
    "### Réalisé par :  \n",
    "- **Akram DOUBLI** Groupe A\n",
    "- **Mohammed FARAJI**  Groupe A\n",
    "\n",
    "### Objectif de cette 1ère partie du projet  \n",
    "Dans ce notebook, nous explorons différentes approches d'apprentissage par renforcement appliquées à un monde grille."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5ae701a1-2a53-4346-88e9-075d490c8d7d",
   "metadata": {},
   "source": [
    "## Informations pour l'exécution du notebook\n",
    "\n",
    "Ce notebook contient des cellules **déjà exécutées avec leurs résultats visibles**.  \n",
    "Cependant, certaines parties du code nécessitent d'autres fichiers (`mdp.py`, `reinforcement_learning.py`), qui font partie du projet complet.  \n",
    "\n",
    "### Comment exécuter ce notebook correctement ?\n",
    "1️⃣ **Option 1 : Cloner le projet complet**  \n",
    "Vous pouvez cloner le projet en utilisant ce lien :  \n",
    "🔗 [Lien GitLab du projet](https://gitlab.com/mon-projet)  \n",
    "\n",
    "Une fois le projet cloné, ce notebook **sera déjà présent à la racine du projet** avec le même nom et pourra être exécuté directement sans modification à condition de réexecuter le fichier mdp.py.  \n",
    "\n",
    "2️⃣ **Option 2 : Ajouter un extrait de code dans `mdp.py`**  \n",
    "Si vous souhaitez exécuter ce notebook indépendamment, ajoutez l'extrait suivant dans le fichier `mdp.py` :  \n",
    "```python\n",
    "# Ajouter ce code juste après la déclaration de la variable sequential_decision_environment dans le fichier mdp.py\n",
    "larger_grid_environment = GridMDP([\n",
    "    [-0.04, -0.04, -0.04, -0.04, +1],  \n",
    "    [-0.04, None, -0.04, None, -1],  \n",
    "    [-0.04, -0.04, -0.04, -0.04, -0.04],  \n",
    "    [-0.04, None, -0.04, None, -0.04]\n",
    "], terminals=[(4, 3), (4, 2)])\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4647d468-ae3f-43b5-82bd-a4e9084b5627",
   "metadata": {},
   "source": [
    "## 📌 Analyse du fichier `reinforcement_learning.ipynb`\n",
    "\n",
    "Le notebook `reinforcement_learning.ipynb` sert de support au **chapitre 21** du livre *Artificial Intelligence: A Modern Approach (AIMA)*. Il contient des implémentations d’algorithmes d’apprentissage par renforcement basés sur les fichiers `mdp.py` et `reinforcement_learning.py`.\n",
    "\n",
    "### **Apprentissage par renforcement passif**  \n",
    "\n",
    "Dans cette partie, l’agent suit une politique fixe et cherche à estimer l’utilité des états qu’il traverse. Trois méthodes sont testées :  \n",
    "\n",
    "- **Direct Utility Estimation (DUE)** : L’agent suit plusieurs trajectoires et fait une moyenne des récompenses obtenues pour estimer l’utilité des états.  \n",
    "- **Adaptive Dynamic Programming (ADP)** : L’agent apprend à la fois l’utilité des états et les probabilités de transition entre eux.  \n",
    "- **Temporal-Difference Learning (TD)** : L’agent met à jour l’utilité des états au fur et à mesure de son apprentissage en utilisant la différence entre valeurs successives.  \n",
    "\n",
    "### **Apprentissage par renforcement actif**  \n",
    "\n",
    "Dans cette partie, l’agent ne suit plus une politique fixe. Il doit apprendre une politique optimale en explorant son environnement. \n",
    "\n",
    "- **Q-Learning** est utilisé pour estimer la valeur des actions possibles à chaque état, sans connaître à l’avance les probabilités de transition.  \n",
    "- Une **comparaison est faite avec l’algorithme de Value Iteration**, qui suppose que l’environnement est entièrement connu dès le départ.  \n",
    "\n",
    "### **Expérimentation sur un monde grille (`GridMDP`)**  \n",
    "\n",
    "L’agent évolue dans un **monde sous forme de grille**, défini dans le fichier `mdp.py`. Il apprend quelles actions lui permettent d’obtenir les meilleures récompenses et améliore progressivement sa prise de décision.  \n",
    "\n",
    "Les résultats sont visualisés sous forme de **tableaux de valeurs** et de **graphiques montrant l’évolution des utilités des états** au fil des itérations.  "
   ]
  },
  {
   "cell_type": "markdown",
   "id": "455135bf-e7c7-4dc8-b6ac-c2cda45b3ad2",
   "metadata": {},
   "source": [
    "## Résumé des fichiers `mdp.py` et `mdp.ipynb`\n",
    "\n",
    "### **mdp.py**\n",
    "\n",
    "Le fichier `mdp.py` implémente un Processus de Décision Markovien (MDP), avec une classe spécifique `GridMDP` représentant un monde grille.  \n",
    "\n",
    "- Chaque état correspond à une case de la grille.  \n",
    "- Les actions possibles sont des déplacements dans 4 directions : nord, sud, est, ouest.  \n",
    "- Les transitions sont probabilistes :  \n",
    "  - 80% de chance d’aller dans la direction choisie.  \n",
    "  - 10% de chance d’aller à gauche.  \n",
    "  - 10% de chance d’aller à droite.  \n",
    "- Les récompenses varient selon les états :  \n",
    "  - États normaux : `-0.04`  \n",
    "  - États terminaux : `+1` ou `-1`  \n",
    "  - Obstacles (`None`) : cases infranchissables  \n",
    "\n",
    "Le fichier inclut aussi les algorithmes **Value Iteration** et **Policy Iteration**, qui permettent de calculer la meilleure politique pour un agent évoluant dans ce monde.  \n",
    "\n",
    "### **mdp.ipynb**\n",
    "Le notebook `mdp.ipynb` permet de tester et comparer les algorithmes d’optimisation des politiques :  \n",
    "\n",
    "- **Value Iteration** : calcule les valeurs optimales des états en maximisant les récompenses attendues.  \n",
    "- **Policy Iteration** : génère et améliore une politique jusqu’à convergence.  \n",
    "- Affichage des résultats sous forme de tableau et de flèches indiquant la politique optimale.  \n",
    "\n",
    "Il sert de base pour tester des modifications du monde grille et analyser leur impact sur l’apprentissage.  \n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b424bbfd-2882-4f1f-ae36-af24b00b73e6",
   "metadata": {},
   "source": [
    "## Modification du Monde Grille"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "31a38463-7e30-4572-960e-806d9409e14a",
   "metadata": {},
   "source": [
    "On insère le code ci-dessous juste après la déclaration de la variable sequential_decision_environment dans le fichier `mdp.py`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c2b861a2-12fa-431c-827c-ee9e8e4295df",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Nouveau monde grille 5x4 avec plus d'obstacles et une nouvelle disposition (on l'éxecute pas ici)\n",
    "larger_grid_environment = GridMDP([[-0.04, -0.04, -0.04, -0.04, +1],\n",
    "                                    [-0.04, None, -0.04, None, -1],\n",
    "                                    [-0.04, -0.04, -0.04, -0.04, -0.04],\n",
    "                                    [-0.04, None, -0.04, None, -0.04]], \n",
    "                                   terminals=[(4, 3), (4, 2)])"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "284c3a99-34eb-47ab-9593-fa840b0c2950",
   "metadata": {},
   "source": [
    "Ensuite, on importe cette nouvelle grille ainsi que les méthodes nécessaires pour appliquer le MDP."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 41,
   "id": "fdcc252f-8448-43b2-ba2b-ab877186a56a",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Politique optimale obtenue par Policy Iteration:\n",
      "[['>', '>', '>', '>', '.'], ['^', None, '^', None, '.'], ['^', '>', '^', '<', '<'], ['^', None, '^', None, '^']]\n"
     ]
    }
   ],
   "source": [
    "from mdp import value_iteration, policy_iteration, larger_grid_environment\n",
    "\n",
    "# Exécution de Value Iteration\n",
    "U_value_iteration = value_iteration(larger_grid_environment)\n",
    "\n",
    "# Exécution de Policy Iteration\n",
    "optimal_policy = policy_iteration(larger_grid_environment)\n",
    "\n",
    "# Affichage des résultats sous forme de flèches\n",
    "print(\"Politique optimale obtenue par Policy Iteration:\")\n",
    "print(larger_grid_environment.to_arrows(optimal_policy))\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 43,
   "id": "a24d7be0-8abd-4310-8db2-0c1c48287824",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 10256 characters]",
      "text/plain": [
       "<Figure size 500x400 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# Afficher la politique sous forme de grille avec des flèches\n",
    "import matplotlib.pyplot as plt\n",
    "\n",
    "def plot_policy(grid_mdp, policy):\n",
    "    \"\"\"Affiche la politique optimale sous forme de grille avec des flèches.\"\"\"\n",
    "    grid = grid_mdp.to_arrows(policy)\n",
    "    \n",
    "    fig, ax = plt.subplots(figsize=(len(grid[0]), len(grid)))\n",
    "    ax.set_xticks(range(len(grid[0]) + 1))\n",
    "    ax.set_yticks(range(len(grid) + 1))\n",
    "    ax.grid(True)\n",
    "    \n",
    "    for y in range(len(grid)):\n",
    "        for x in range(len(grid[0])):\n",
    "            if grid[y][x] is not None:\n",
    "                ax.text(x + 0.5, len(grid) - y - 0.5, grid[y][x],\n",
    "                        ha='center', va='center', fontsize=14)\n",
    "            else:\n",
    "                ax.add_patch(plt.Rectangle((x, len(grid) - y - 1), 1, 1, color='black'))\n",
    "    \n",
    "    ax.set_xticklabels([])\n",
    "    ax.set_yticklabels([])\n",
    "    plt.title(\"Politique Optimale\")\n",
    "    plt.show()\n",
    "\n",
    "plot_policy(larger_grid_environment, optimal_policy)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "28e15758-781b-4bef-87bd-0d3eb8992d16",
   "metadata": {},
   "source": [
    "### 📌 Analyse de la politique optimale (Grille avec flèches)\n",
    "\n",
    "L’image représente la **politique optimale** obtenue après l’exécution de **Policy Iteration** sur le monde grille modifié. Chaque case contient une flèche indiquant la direction optimale que l’agent doit suivre pour maximiser sa récompense. On observe que l’agent se dirige principalement vers la droite et vers le haut, ce qui est logique puisque l’état offrant la meilleure récompense (`+1`) se trouve en haut à droite de la grille.  \n",
    "\n",
    "Les obstacles, représentés par des cases noires, empêchent certains déplacements et forcent l’agent à modifier son trajet pour contourner ces zones bloquées. De plus, les états proches de l’état terminal `-1` ajustent leur stratégie afin d’éviter autant que possible cette zone pénalisante. La structure de la politique optimale confirme que l’agent suit une stratégie rationnelle, minimisant les pertes et maximisant les gains.  "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 113,
   "id": "8bc14a3c-a742-461c-a5cb-431df467284a",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{(0, 1): 0.23237864440197828, (4, 0): 0.030441294664059405, (2, 1): 0.3997452871413615, (0, 0): 0.15523283929422824, (3, 1): 0.30220506944511716, (4, 1): 0.09030376247785993, (1, 1): 0.30220506944511716, (0, 3): 0.4103132992416042, (2, 0): 0.30220506944511716, (4, 3): 1.0, (4, 2): -1.0, (2, 3): 0.665101229845564, (0, 2): 0.3114914101394566, (3, 3): 0.8292682926829207, (2, 2): 0.5352107554934223, (1, 3): 0.5352107554934223}\n"
     ]
    },
    {
     "data": {
      "image/png": "[encoded data, 16408 characters]",
      "text/plain": [
       "<Figure size 500x400 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "def plot_utilities(grid_mdp, utilities):\n",
    "    \"\"\"Affiche les utilités des états sous forme de grille.\"\"\"\n",
    "    grid = grid_mdp.to_grid(utilities)\n",
    "    \n",
    "    fig, ax = plt.subplots(figsize=(len(grid[0]), len(grid)))\n",
    "    ax.set_xticks(range(len(grid[0]) + 1))\n",
    "    ax.set_yticks(range(len(grid) + 1))\n",
    "    ax.grid(True)\n",
    "    \n",
    "    for y in range(len(grid)):\n",
    "        for x in range(len(grid[0])):\n",
    "            if grid[y][x] is not None:\n",
    "                ax.text(x + 0.5, len(grid) - y - 0.5, f\"{grid[y][x]:.2f}\",\n",
    "                        ha='center', va='center', fontsize=10, color='blue')\n",
    "            else:\n",
    "                ax.add_patch(plt.Rectangle((x, len(grid) - y - 1), 1, 1, color='black'))\n",
    "    \n",
    "    ax.set_xticklabels([])\n",
    "    ax.set_yticklabels([])\n",
    "    plt.title(\"Valeurs des Utilités des États\")\n",
    "    plt.show()\n",
    "\n",
    "print(U_value_iteration)\n",
    "# Afficher les utilités sous forme de grille\n",
    "plot_utilities(larger_grid_environment, U_value_iteration)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3f5bbe54-36bd-42ae-b205-11267599d8a8",
   "metadata": {},
   "source": [
    "\n",
    "### 📌 Analyse des valeurs des utilités (Grille avec valeurs)\n",
    "\n",
    "L’image affiche les **valeurs des utilités** des différents états de la grille, obtenues après l’exécution de **Value Iteration**. L’état offrant la meilleure récompense (`+1`) a une utilité maximale de `1.00`, et l’on observe une diminution progressive des utilités en s’éloignant de cette case. Inversement, l’état `-1` a une utilité fortement négative de `-1.00`, ce qui signifie qu’il représente une zone à éviter pour l’agent.  \n",
    "\n",
    "Les obstacles sont visibles sous forme de cases noires, empêchant certains déplacements et obligeant l’agent à explorer des chemins alternatifs. Les états situés dans des zones ouvertes ont des utilités plus élevées, tandis que ceux proches de `-1` affichent des valeurs plus faibles, indiquant un risque plus grand. Ces résultats confirment la cohérence de la politique optimale et montrent que l’agent privilégie les trajets menant à des récompenses positives tout en évitant les zones dangereuses.  \n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9e3e6823-ae0c-4696-8d49-12a049900f57",
   "metadata": {},
   "source": [
    "## Implémentation et exécution de Q-Learning"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 45,
   "id": "85361661-7cd1-42c1-b00a-af8ab295b15c",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 14244 characters]",
      "text/plain": [
       "<Figure size 500x400 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "from reinforcement_learning import QLearningAgent, run_single_trial\n",
    "from mdp import larger_grid_environment\n",
    "import matplotlib.pyplot as plt\n",
    "\n",
    "# Paramètres d'apprentissage\n",
    "num_episodes = 1000  \n",
    "gamma = 0.9  \n",
    "alpha = lambda n: 60. / (59 + n)  \n",
    "Ne = 5  \n",
    "Rplus = 2  \n",
    "\n",
    "# Initialisation de l'agent Q-Learning\n",
    "q_agent = QLearningAgent(larger_grid_environment, Ne=Ne, Rplus=Rplus, alpha=alpha)\n",
    "\n",
    "# Entraînement sur plusieurs épisodes\n",
    "for i in range(num_episodes):\n",
    "    run_single_trial(q_agent, larger_grid_environment)\n",
    "\n",
    "# Récupération de la politique optimale apprise\n",
    "def get_q_policy(q_agent, mdp):\n",
    "    \"\"\"Extrait la politique optimale basée sur les valeurs Q apprises.\"\"\"\n",
    "    policy = {}\n",
    "    for state in mdp.states:\n",
    "        if state not in mdp.terminals:\n",
    "            best_action = max(mdp.actions(state), key=lambda a: q_agent.Q[state, a])\n",
    "            policy[state] = best_action\n",
    "    return policy\n",
    "\n",
    "q_policy = get_q_policy(q_agent, larger_grid_environment)\n",
    "\n",
    "# Affichage de la politique optimale sous forme de grille\n",
    "def plot_q_policy(grid_mdp, policy):\n",
    "    \"\"\"Affiche la politique optimale apprise par Q-Learning sous forme de grille.\"\"\"\n",
    "    grid = grid_mdp.to_arrows(policy)\n",
    "    \n",
    "    fig, ax = plt.subplots(figsize=(len(grid[0]), len(grid)))\n",
    "    ax.set_xticks(range(len(grid[0]) + 1))\n",
    "    ax.set_yticks(range(len(grid) + 1))\n",
    "    ax.grid(True)\n",
    "    \n",
    "    for y in range(len(grid)):\n",
    "        for x in range(len(grid[0])):\n",
    "            if grid[y][x] is not None:\n",
    "                ax.text(x + 0.5, len(grid) - y - 0.5, grid[y][x],\n",
    "                        ha='center', va='center', fontsize=14)\n",
    "            else:\n",
    "                ax.add_patch(plt.Rectangle((x, len(grid) - y - 1), 1, 1, color='black'))\n",
    "    \n",
    "    ax.set_xticklabels([])\n",
    "    ax.set_yticklabels([])\n",
    "    plt.title(\"Politique optimale apprise par Q-Learning\")\n",
    "    plt.show()\n",
    "\n",
    "# Affichage de la politique apprise\n",
    "plot_q_policy(larger_grid_environment, q_policy)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 47,
   "id": "6110647a-b07c-4d73-b72c-4d04867c542f",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Matrice des récompenses :\n",
      "[[-0.04 -0.04 -0.04 -0.04  0.  ]\n",
      " [ 0.   -0.04  0.   -0.04  0.  ]\n",
      " [-0.04 -0.04 -0.04 -0.04  0.  ]\n",
      " [ 0.   -0.04  0.   -0.04  0.  ]\n",
      " [-0.04 -0.04 -1.    1.    0.  ]]\n"
     ]
    }
   ],
   "source": [
    "import numpy as np\n",
    "\n",
    "# Créer une matrice pour représenter la grille\n",
    "grid_size = (5, 5)  \n",
    "reward_matrix = np.full(grid_size, fill_value=0, dtype=float)  # Initialiser avec 0\n",
    "\n",
    "# Remplir avec les valeurs des récompenses\n",
    "for state, reward in larger_grid_environment.reward.items():\n",
    "    x, y = state\n",
    "    reward_matrix[x, y] = reward\n",
    "\n",
    "# Afficher la grille des récompenses\n",
    "print(\"Matrice des récompenses :\")\n",
    "print(reward_matrix)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 156,
   "id": "62bf0e49-b70b-4494-8468-4d0f1ae1ea0b",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "defaultdict(float,\n",
       "            {((0, 0), (1, 0)): -0.20795241935188047,\n",
       "             ((0, 0), (0, 1)): -0.2048885471361644,\n",
       "             ((0, 0), (-1, 0)): -0.2115419555473922,\n",
       "             ((0, 0), (0, -1)): -0.21473121313164067,\n",
       "             ((0, 1), (1, 0)): -0.18196698408942832,\n",
       "             ((0, 1), (0, 1)): -0.18049270297444345,\n",
       "             ((0, 1), (-1, 0)): -0.192829653170508,\n",
       "             ((0, 1), (0, -1)): -0.20426562703536688,\n",
       "             ((1, 1), (1, 0)): -0.16633490362382627,\n",
       "             ((1, 1), (0, 1)): -0.17618932237743556,\n",
       "             ((1, 1), (-1, 0)): -0.18131310461477876,\n",
       "             ((1, 1), (0, -1)): -0.18652513890523967,\n",
       "             ((2, 1), (1, 0)): -0.1534585372187353,\n",
       "             ((2, 1), (0, 1)): -0.13108591299887296,\n",
       "             ((2, 1), (-1, 0)): -0.14096883066805838,\n",
       "             ((2, 1), (0, -1)): -0.14717641098443246,\n",
       "             ((3, 1), (1, 0)): -0.15600556603865298,\n",
       "             ((3, 1), (0, 1)): -0.20145264823138925,\n",
       "             ((3, 1), (-1, 0)): -0.13748070905915702,\n",
       "             ((3, 1), (0, -1)): -0.18337014124336348,\n",
       "             ((4, 1), (1, 0)): -0.8917250298814969,\n",
       "             ((4, 1), (0, 1)): -0.8997273740273475,\n",
       "             ((4, 1), (-1, 0)): -0.9297067964077238,\n",
       "             ((4, 1), (0, -1)): -0.22170965905817586,\n",
       "             ((4, 0), (1, 0)): -0.3204833161673008,\n",
       "             ((4, 0), (0, 1)): -0.32363014341575763,\n",
       "             ((4, 0), (-1, 0)): -0.31954712036425725,\n",
       "             ((4, 0), (0, -1)): -0.3191624515391337,\n",
       "             ((4, 2), None): -1.0554487209749548,\n",
       "             ((2, 0), (1, 0)): -0.12953971401732597,\n",
       "             ((2, 0), (0, 1)): -0.12883546596523204,\n",
       "             ((2, 0), (-1, 0)): -0.15366420367127087,\n",
       "             ((2, 0), (0, -1)): -0.1478781566281466,\n",
       "             ((2, 2), (1, 0)): -0.12953971401732597,\n",
       "             ((2, 2), (0, 1)): -0.099227486348174,\n",
       "             ((2, 2), (-1, 0)): -0.11228824999509104,\n",
       "             ((2, 2), (0, -1)): -0.11463551671151584,\n",
       "             ((2, 3), (1, 0)): -0.05261878283837267,\n",
       "             ((2, 3), (0, 1)): -0.058351112682003645,\n",
       "             ((2, 3), (-1, 0)): -0.11251978426411759,\n",
       "             ((2, 3), (0, -1)): -0.07119696834397152,\n",
       "             ((3, 3), (1, 0)): 0.003748564562652923,\n",
       "             ((3, 3), (0, 1)): -0.04453291106133822,\n",
       "             ((3, 3), (-1, 0)): -0.04153791601118487,\n",
       "             ((3, 3), (0, -1)): -0.018087560574624087,\n",
       "             ((4, 3), None): 0.0531688175220456,\n",
       "             ((0, 2), (1, 0)): -0.17403248236511704,\n",
       "             ((0, 2), (0, 1)): -0.15810911932581984,\n",
       "             ((0, 2), (-1, 0)): -0.19300977245674023,\n",
       "             ((0, 2), (0, -1)): -0.16867263327848664,\n",
       "             ((0, 3), (1, 0)): -0.12435615850425588,\n",
       "             ((0, 3), (0, 1)): -0.1301255823998309,\n",
       "             ((0, 3), (-1, 0)): -0.15034015583597796,\n",
       "             ((0, 3), (0, -1)): -0.12959525705015418,\n",
       "             ((1, 3), (1, 0)): -0.087112300198648,\n",
       "             ((1, 3), (0, 1)): -0.10238717667646488,\n",
       "             ((1, 3), (-1, 0)): -0.12303082414286803,\n",
       "             ((1, 3), (0, -1)): -0.1070119313706535})"
      ]
     },
     "execution_count": 156,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "q_agent.Q"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 158,
   "id": "29f041d0-0e74-4235-b54e-b4e46262f0c7",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "U((0, 0)) = -0.2049\n",
      "U((0, 1)) = -0.1805\n",
      "U((0, 2)) = -0.1581\n",
      "U((0, 3)) = -0.1244\n",
      "U((1, 1)) = -0.1663\n",
      "U((1, 3)) = -0.0871\n",
      "U((2, 0)) = -0.1288\n",
      "U((2, 1)) = -0.1311\n",
      "U((2, 2)) = -0.0992\n",
      "U((2, 3)) = -0.0526\n",
      "U((3, 1)) = -0.1375\n",
      "U((3, 3)) = 0.0037\n",
      "U((4, 0)) = -0.3192\n",
      "U((4, 1)) = -0.2217\n",
      "U((4, 2)) = -1.0554\n",
      "U((4, 3)) = 0.0532\n"
     ]
    }
   ],
   "source": [
    "# Afficher les valeurs d'utilité estimées U(s) après l'entraînement de Q-Learning\n",
    "U_q_learning = defaultdict(lambda: -1000.)  # Initialisation avec une grande valeur négative\n",
    "\n",
    "for (state, action), value in q_agent.Q.items():\n",
    "    if U_q_learning[state] < value:\n",
    "        U_q_learning[state] = value  # On prend la meilleure valeur Q pour chaque état\n",
    "\n",
    "# Affichage des valeurs d'utilité estimées\n",
    "for state, utility in sorted(U_q_learning.items()):\n",
    "    print(f\"U({state}) = {utility:.4f}\")\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6e82e652-ab7d-44f8-b206-75fc98f60d38",
   "metadata": {},
   "source": [
    "### 📌 Analyse des valeurs d’utilité `U(s)` obtenues avec Q-Learning\n",
    "\n",
    "Les valeurs d’utilité estimées par Q-Learning montrent une tendance générale cohérente avec l’environnement, bien que certaines différences avec Value Iteration soient observées. L’état `(4,3)`, qui correspond à une récompense positive `+1`, possède une valeur légèrement positive (`0.0530`), ce qui signifie que l’agent a appris qu’il s’agit d’un état favorable. En revanche, l’état `(4,2)`, qui représente une récompense négative `-1`, a une valeur fortement négative (`-1.0288`), indiquant qu’il est bien identifié comme une zone à éviter.  \n",
    "\n",
    "Cependant, la majorité des autres états ont des valeurs relativement faibles ou proches de zéro. Cela suggère que l’agent Q-Learning n’a pas complètement exploré certaines parties de l’environnement ou que les valeurs `Q(s, a)` n’ont pas entièrement convergé. Cette incertitude est un effet courant dans Q-Learning, où l’apprentissage dépend fortement du nombre d’épisodes et de la stratégie d’exploration.  \n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f0f146b8-cafb-475c-b633-868b1eb104b7",
   "metadata": {},
   "source": [
    "### Tracer le graphique de convergence des estimations U(s)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 49,
   "id": "f0fce5cb-5c0f-4add-9712-616823f5a421",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 70856 characters]",
      "text/plain": [
       "<Figure size 800x500 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "import matplotlib.pyplot as plt\n",
    "\n",
    "# Sélection d'états spécifiques pour suivre leur convergence (comme dans l’énoncé)\n",
    "states_to_track = [(4, 3), (3, 3), (1, 1), (3, 1), (4, 1)]  \n",
    "\n",
    "# Stocker les valeurs d'utilité pour ces états au fil des épisodes\n",
    "U_values_over_time = {state: [] for state in states_to_track}\n",
    "\n",
    "# Réentraîner Q-Learning pour observer l’évolution des utilités estimées\n",
    "q_agent = QLearningAgent(larger_grid_environment, Ne=Ne, Rplus=Rplus, alpha=alpha)\n",
    "\n",
    "num_episodes = 1000  \n",
    "\n",
    "for episode in range(num_episodes):\n",
    "    run_single_trial(q_agent, larger_grid_environment)\n",
    "    \n",
    "    # Mettre à jour les valeurs U(s) pour les états suivis\n",
    "    for state in states_to_track:\n",
    "        try:\n",
    "            U_values_over_time[state].append(max(q_agent.Q[state, a] for a in larger_grid_environment.actions(state)))\n",
    "        except ValueError:\n",
    "            U_values_over_time[state].append(None)  # Si aucune valeur Q disponible, on met None\n",
    "\n",
    "# Tracer le graphique des estimations d’utilité\n",
    "plt.figure(figsize=(8, 5))\n",
    "\n",
    "for state, values in U_values_over_time.items():\n",
    "    plt.plot([v if v is not None else 0 for v in values], label=f\"U({state})\")\n",
    "\n",
    "plt.xlabel(\"Nombre d'épisodes\")\n",
    "plt.ylabel(\"Estimation d'Utilité U(s)\")\n",
    "plt.title(\"Convergence des estimations d’utilité avec Q-Learning\")\n",
    "plt.legend()\n",
    "plt.show()\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ebd7746c-5eae-40d9-8529-44df5bd2fed9",
   "metadata": {},
   "source": [
    "### 📌 Analyse du graphique de convergence des estimations d’utilité\n",
    "\n",
    "Le graphique montre comment les valeurs `U(s)` évoluent au fil des épisodes d’apprentissage. Au début, les valeurs fluctuent énormément, car l’agent est encore en phase d’exploration et met à jour ses connaissances sur l’environnement. Après plusieurs centaines d’épisodes, les courbes deviennent plus stables, signe d’une convergence progressive des valeurs d’utilité.  \n",
    "\n",
    "Les états `(4,3)` et `(3,3)`, qui sont proches des états terminaux, affichent une valeur plus élevée, montrant que l’agent a bien identifié ces zones comme favorables. Cependant, les autres états présentent une convergence plus lente et certaines valeurs restent très basses, ce qui reflète un apprentissage imparfait. Comparé à Value Iteration, l’apprentissage par Q-Learning semble prendre plus de temps pour atteindre une solution stable, ce qui est une limite courante des méthodes basées sur l’exploration.  \n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "71d6398f-f8a5-4383-b1f1-3ee875fdd3ed",
   "metadata": {},
   "source": [
    "### 📌 Comparaison des valeurs obtenues avec Value Iteration\n",
    "\n",
    "Nous allons maintenant comparer les valeurs U(s) obtenues avec Q-Learning à celles obtenues par Value Iteration."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 150,
   "id": "d5ff096f-a0b5-40bd-b95b-3be8f15ea872",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 30184 characters]",
      "text/plain": [
       "<Figure size 800x500 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# Exécuter Value Iteration pour obtenir les valeurs optimales U(s)\n",
    "U_value_iteration = value_iteration(larger_grid_environment)\n",
    "\n",
    "# Sélectionner les mêmes états que pour Q-Learning\n",
    "states_to_compare = [(4, 3), (3, 3), (1, 1), (3, 1), (4, 1)]\n",
    "\n",
    "# Récupérer les valeurs U(s) de Value Iteration pour ces états\n",
    "U_vi_values = [U_value_iteration[state] for state in states_to_compare]\n",
    "U_q_values = [U_q_learning[state] for state in states_to_compare]\n",
    "\n",
    "# Tracer la comparaison des valeurs U(s)\n",
    "plt.figure(figsize=(8, 5))\n",
    "plt.bar(range(len(states_to_compare)), U_vi_values, width=0.4, label=\"Value Iteration\", align='center', alpha=0.7)\n",
    "plt.bar(np.array(range(len(states_to_compare))) + 0.4, U_q_values, width=0.4, label=\"Q-Learning\", align='center', alpha=0.7)\n",
    "\n",
    "plt.xticks(range(len(states_to_compare)), states_to_compare)\n",
    "plt.xlabel(\"États\")\n",
    "plt.ylabel(\"Valeur U(s)\")\n",
    "plt.title(\"Comparaison des valeurs d’utilité : Q-Learning vs Value Iteration\")\n",
    "plt.legend()\n",
    "plt.show()\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8d1b6298-5932-4b96-a7bb-2b258675d507",
   "metadata": {},
   "source": [
    "### 📌 Analyse du graphique de comparaison des valeurs d’utilité\n",
    "\n",
    "Le graphique met en évidence une **différence significative** entre les valeurs d’utilité `U(s)` obtenues avec **Value Iteration** (barres bleues) et celles apprises par **Q-Learning** (barres oranges). Comme attendu, les valeurs issues de Value Iteration sont **systématiquement plus élevées**, ce qui montre que cette méthode permet de **calculer directement les utilités optimales** en ayant une connaissance complète du MDP.  \n",
    "\n",
    "En revanche, Q-Learning donne des **valeurs beaucoup plus faibles**, et certaines sont même **négatives**. Cela indique que l’agent n’a **pas complètement appris** la structure optimale de l’environnement. L’état `(4,3)`, qui correspond à la récompense `+1`, possède une **valeur très faible** sous Q-Learning, alors que Value Iteration lui attribue une valeur proche de `1.0`. Cela suggère que l’agent n’a pas suffisamment exploré cette zone ou que les mises à jour de `Q(s, a)` n’ont pas encore convergé vers la valeur optimale.  \n",
    "\n",
    "Un autre point notable est que certaines valeurs `U(s)` obtenues par Q-Learning sont **proches de zéro ou même négatives**, notamment pour `(3,1)`, `(4,1)` et `(1,1)`, alors que Value Iteration leur donne des valeurs positives. Ce phénomène peut être expliqué par **un nombre d’épisodes insuffisant, une exploration incomplète (`ε-greedy` mal calibré), ou un taux d’apprentissage (`α`) qui ralentit la convergence**.  \n",
    "\n",
    "Finalement, ce graphique illustre bien **les différences entre une approche basée sur la connaissance complète du MDP (Value Iteration) et une approche basée sur l’apprentissage par exploration (Q-Learning)**. Pour améliorer les performances de Q-Learning et obtenir des valeurs plus proches de celles de Value Iteration, il serait nécessaire **d’augmenter le nombre d’épisodes d’entraînement, d’ajuster les hyperparamètres et d’encourager une meilleure exploration**.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6499619a-4368-4c24-8b3e-ef258c22fa6d",
   "metadata": {},
   "source": [
    "## 📌 Conclusion\n",
    "\n",
    "Dans ce projet, nous avons exploré et comparé différentes approches d’apprentissage par renforcement appliquées à un **monde grille**. Nous avons commencé par implémenter **Policy Iteration et Value Iteration**, qui exploitent une connaissance complète de l’environnement pour calculer directement une politique optimale. Ensuite, nous avons introduit **Q-Learning**, une méthode basée sur l’exploration, où l’agent apprend progressivement en interagissant avec l’environnement sans en connaître les transitions à l’avance.  \n",
    "\n",
    "Nos expériences ont montré que **Q-Learning prend plus de temps à converger** et peut produire des valeurs `U(s)` **moins précises que celles de Value Iteration**, surtout lorsque l’exploration est insuffisante. Nous avons constaté que certaines zones n’étaient pas bien apprises, ce qui explique les écarts entre les politiques obtenues. Cette observation est cohérente avec l’exemple du fichier `reinforcement_learning.ipynb`, où des différences similaires entre Q-Learning et Value Iteration ont été relevées.  \n",
    "\n",
    "En conclusion, **Value Iteration est plus efficace dans un cadre où toutes les informations sont disponibles**, tandis que **Q-Learning est plus adapté aux environnements inconnus**, bien qu’il nécessite un plus grand nombre d’épisodes et un bon réglage des hyperparamètres pour converger vers une politique optimale. Nos résultats confirment donc les avantages et limites de chaque approche et mettent en évidence l'importance de l’exploration dans l’apprentissage par renforcement.\n"
   ]
  }
 ],
 "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.12.4"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
