{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "FEUILLE DE TP 4\n",
    "\n",
    "---\n",
    "# Polynôme interpolateur de Lagrange (Partie 1)\n",
    "---"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "%matplotlib inline\n",
    "%config InlineBackend.figure_format = 'retina'\n",
    "\n",
    "import numpy as np                       # pour les numpy array\n",
    "from scipy.interpolate import lagrange   # interpolation de Lagrange\n",
    "import matplotlib.pyplot as plt          # librairie graphique"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Nous rappelons le résultat d'existence et d'unicité vu en cours\n",
    "\n",
    "> **(Polynôme interpolateur de Lagrange)**<br>\n",
    "> Etant donnés $N$ un entier strictement positif, $x_1,\\ldots,x_N$ des réels deux à deux distincts et $y_1,\\ldots,y_N$ des réels, il existe un unique polynôme $P$ qui vérifie\n",
    "> $$ P\\in\\mathbb{R}_{N-1}[X], \\qquad P(x_i)=y_i, \\quad 1\\leq i\\leq N.$$"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Construction en utilisant les modules `numpy` et `scipy`\n",
    "\n",
    "Le module `numpy` permet la manipulation de polynômes réels à l'aide de la classe `poly1d`. Voici un exemple pour le polynôme $P=X^2-2$\n",
    "\n",
    "```python\n",
    "P = np.poly1d([1, 0, -2])\n",
    "print(P)\n",
    "```\n",
    "\n",
    "Il est possible de faire des manipulations algébriques sur les polynômes :\n",
    "\n",
    "```python\n",
    "X = np.poly1d([1, 0])\n",
    "P = X**2 - 2\n",
    "print(P)\n",
    "```\n",
    "\n",
    "Il est également possible d'évaluer un polynôme comme une fonction (évaluation sur un scalaire ou sur un tableau `numpy`) :\n",
    "\n",
    "```python\n",
    "print(P(1))\n",
    "print(P(np.linspace(0, 1, 11)))\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "   2\n",
      "1 x - 2\n"
     ]
    }
   ],
   "source": [
    "P = np.poly1d([1, 0, -2])\n",
    "print(P)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "   2\n",
      "1 x - 2\n"
     ]
    }
   ],
   "source": [
    "X = np.poly1d([1, 0])\n",
    "P = X**2 - 2\n",
    "print(P)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "-1\n",
      "[-2.   -1.99 -1.96 -1.91 -1.84 -1.75 -1.64 -1.51 -1.36 -1.19 -1.  ]\n"
     ]
    }
   ],
   "source": [
    "print(P(1))\n",
    "print(P(np.linspace(0, 1, 11)))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Le module `scipy.interpolate` permet de déterminer le polynôme interpolateur à l'aide de la fonction `lagrange`. Voici un exemple d'utilisation pour déterminer le polynôme interpolateur de Lagrange aux points $(0, -2)$, $(1,-1)$ et $(2, 2)$ (c'est encore et toujours le polynôme $X^2-2$...)\n",
    "\n",
    "```python\n",
    "from scipy.interpolate import lagrange\n",
    "x = np.array([0, 1, 2])\n",
    "y = np.array([-2, -1, 2])\n",
    "P = lagrange(x, y)\n",
    "print(P)\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "   2\n",
      "1 x - 2\n"
     ]
    }
   ],
   "source": [
    "from scipy.interpolate import lagrange\n",
    "x = np.array([0, 1, 2])\n",
    "y = np.array([-2, -1, 2])\n",
    "P = lagrange(x, y)\n",
    "print(P)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Question**\n",
    "\n",
    "> Proposez une fonction `interp` qui \n",
    "> * prend en argument deux `ndarray` `x` et `y` de taille `N` et un `ndarray` `xx` de taille `M` ;\n",
    "> * calcule le polynôme interpolateur de Lagrange aux points donnés par les vecteurs `x` et `y` en utilisant les fonctions des modules `numpy` et `scipy` ;\n",
    "> * retourne l'évaluation de ce polynôme aux points du vecteur `xx`.\n",
    "\n",
    "*Veillez à commenter votre fonction.*"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [],
   "source": [
    "def interp(x, y, xx):\n",
    "    P = lagrange(x, y)\n",
    "    return P(xx)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Question**\n",
    "\n",
    "> Afin de tester votre fonction : en prenant $N=5$,\n",
    "> 1. prenez $N$ points équirépartis entre 0 et 1 (ce sera notre vecteur `x`) ;\n",
    "> 2. générez $N$ valeurs aléatoires entre 0 et 1 (ce sera notre vecteur `y`) ;\n",
    "> 3. tracez dans une fenêtre graphique le nuage de points d'abscisses `x` et d'ordonnées `y` à l'aide d'une commande `scatter` ;\n",
    "> 4. ajoutez le tracé du polynôme interpolateur en prenant `xx` un vecteur de taille grande (plutôt 100 ou 200 points équi-répartis entre 0 et 1) ;\n",
    "> 5. vérifiez que le polynôme interpolateur passe bien par les points d'interpolation."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "Text(0.5, 0.98, 'Interpolation du polynome')"
      ]
     },
     "execution_count": 8,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "[encoded data, 78468 characters]\n",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {
      "image/png": {
       "height": 480,
       "width": 567
      }
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "N = 5\n",
    "x = np.linspace(0, 1, N)\n",
    "y = np.random.rand(N)\n",
    "\n",
    "plt.scatter(x, y)\n",
    "xx = np.linspace(0, 1, 200)\n",
    "\n",
    "plt.plot(xx, interp(x, y, xx), label=f'Interpolation')\n",
    "plt.legend()\n",
    "plt.xlabel(\"x\")\n",
    "plt.ylabel(\"y\")\n",
    "plt.suptitle(\"Interpolation du polynome\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Test de l'interpolation sur quelques fonctions\n",
    "\n",
    "Nous allons à présent tester l'interpolation en prenant comme points d'interpolation des points qui sont sur le graphe de ces 4 fonctions. \n",
    "\n",
    "\\begin{align}\n",
    "f_0(x) &= \\exp(-3(x-1.2)^2), &\n",
    "f_1(x) &= \\frac{x^2-2}{1+2x},\\\\\n",
    "f_2(x) &= \\frac{1}{1+(x-1.5)^2},&\n",
    "f_3(x) &= \\frac{\\sin(2\\pi x)}{1.1 - \\sin(\\pi x)}.\n",
    "\\end{align}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Question**\n",
    "\n",
    "> Définissez ces 4 fonctions et tracez les sur l'intervalle $[0, 3]$."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [],
   "source": [
    "def f0(x):\n",
    "    return np.exp(-3 * (x- 1.2)**2)\n",
    "\n",
    "def f1(x):\n",
    "    return (x**2 - 2) / (1+2 * x)\n",
    "\n",
    "def f2(x):\n",
    "    return 1/(1 + (x - 1.5)**2)\n",
    "\n",
    "def f3(x):\n",
    "    return np.sin(2 * np.pi * x) / (1.1 - np.sin(np.pi * x))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 70440 characters]\n",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {
      "image/png": {
       "height": 454,
       "width": 567
      }
     },
     "output_type": "display_data"
    },
    {
     "data": {
      "image/png": "[encoded data, 59712 characters]\n",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {
      "image/png": {
       "height": 454,
       "width": 578
      }
     },
     "output_type": "display_data"
    },
    {
     "data": {
      "image/png": "[encoded data, 77036 characters]\n",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {
      "image/png": {
       "height": 454,
       "width": 567
      }
     },
     "output_type": "display_data"
    },
    {
     "data": {
      "image/png": "[encoded data, 80828 characters]\n",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {
      "image/png": {
       "height": 454,
       "width": 565
      }
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "xx = np.linspace(0, 3, 200)\n",
    "\n",
    "for f in [f0, f1, f2, f3]:\n",
    "    plt.plot(xx, f(xx), label=f\"Courbe {f.__name__}\")\n",
    "    plt.legend()\n",
    "    plt.title(f\"Courbe {f.__name__}\")\n",
    "    plt.xlabel(f\"$x$\")\n",
    "    plt.ylabel(\"y\")\n",
    "    plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Question**\n",
    "\n",
    "> * Ajoutez sur les figures précédentes les polynômes interpolateurs qui passent par $N$ points équi-répartis des graphes des fonction avec $N\\in\\lbrace 4, 8, 12, 16, 20\\rbrace$.\n",
    "> * Que remarquez-vous lorsque $N$ augmente ?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 88972 characters]\n",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {
      "image/png": {
       "height": 480,
       "width": 567
      }
     },
     "output_type": "display_data"
    },
    {
     "data": {
      "image/png": "[encoded data, 75716 characters]\n",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {
      "image/png": {
       "height": 480,
       "width": 578
      }
     },
     "output_type": "display_data"
    },
    {
     "data": {
      "image/png": "[encoded data, 93596 characters]\n",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {
      "image/png": {
       "height": 480,
       "width": 567
      }
     },
     "output_type": "display_data"
    },
    {
     "data": {
      "image/png": "[encoded data, 72840 characters]\n",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {
      "image/png": {
       "height": 480,
       "width": 583
      }
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "N = 20 # N appartient à [4, 8, 12, 16, 20] ou N est le nombre de points pour l'interpolation\n",
    "x = np.linspace(0, 3, N)\n",
    "xx = np.linspace(0, 3, 200)\n",
    "\n",
    "for f in [f0, f1, f2, f3]:\n",
    "    y = f(x)\n",
    "    plt.plot(xx, f(xx), label=f\"Courbe {f.__name__}\")\n",
    "    plt.scatter(x, y)\n",
    "    plt.plot(xx, interp(x, y, xx), label=f'Interpolation {f.__name__}')\n",
    "    plt.legend()\n",
    "    plt.suptitle(f\"Courbe {f.__name__}\")\n",
    "    plt.xlabel(f\"$x$\")\n",
    "    plt.ylabel(\"y\")\n",
    "    plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Nous rappelons le résultat d'existence et d'unicité vu en cours\n",
    "\n",
    "> **(Polynôme interpolateur de Lagrange)**<br>\n",
    "> Etant donnés $N$ un entier strictement positif, $x_1,\\ldots,x_N$ des réels deux à deux distincts et $y_1,\\ldots,y_N$ des réels, il existe un unique polynôme $P$ qui vérifie\n",
    "> $$ P\\in\\mathbb{R}_{N-1}[X], \\qquad P(x_i)=y_i, \\quad 1\\leq i\\leq N.$$"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Construction par la méthode de la matrice de Vandermonde\n",
    "\n",
    "Nous rappelons que le vecteur $a=(a_1,\\ldots,a_N)$des coefficients du polynôme interpolateur de Lagrange dans la base canonique de $\\mathbb{R}_{N-1}[X]$ est solution du système linéaire\n",
    "$$ M a = y$$\n",
    "où $M$ est la matrice de Vandermonde associée aux points $(x_1,\\ldots, y_N)$ et $y=(y_1,\\ldots,y_N)$.\n",
    "Nous avons\n",
    "$$\n",
    "M = \\begin{pmatrix}\n",
    "1&x_1&\\ldots&x_1^{N-1}\\\\\n",
    "1&x_2&\\ldots&x_2^{N-1}\\\\\n",
    "\\vdots&\\vdots&&\\vdots\\\\\n",
    "1&x_N&\\ldots&x_N^{N-1}\n",
    "\\end{pmatrix}.\n",
    "$$"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Question**\n",
    "\n",
    "> 1. Proposez une fonction `interp_vdm_build` qui prend en argument un `ndarray` noté `x` et qui retourne la matrice de Vandermonde associée.\n",
    "> 2. Proposez une fonction `interp_vdm_poly` qui \n",
    ">    * prend en argument deux `ndarray` notés `x` et `y` ;\n",
    ">    * vérifie que les deux arguments ont bien la même taille ;\n",
    ">    * calcule la matrice de Vandermonde `M` associée au vecteur `x` ;\n",
    ">    * retourne la solution du système `Ma=y`.\n",
    "> 3. Vérifiez que vos fonctions fonctionnent correctement en affichant les résultats pour des points bien choisis (vous pouvez prendre ce que vous voulez...)\n",
    "\n",
    "*Indication : vous pourrez utiliser la fonction `fromfunction` du module `numpy`, la fonction `solve` du module `numpy.linalg`.*"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "def interp_vdm_build(x):\n",
    "    size = x.shape[0]\n",
    "    M = np.reshape(x, (size, 1))\n",
    "    power = [x for x in range(0, size)]\n",
    "    return M**power\n",
    "\n",
    "def interp_vdm_poly(x, y):\n",
    "    if x.shape != y.shape:\n",
    "        raise ValueError(\"x and y must have same dimension!\")\n",
    "    return np.linalg.solve(interp_vdm_build(x), y"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Question**\n",
    "\n",
    "> 1. Proposez une fonction `horner` qui prend en argument un `ndarray` noté `a` et un `ndarray` noté `xx` et qui retourne l'évaluation aux points du vecteur `xx` par l'algorithme de Hörner du polynôme dont les coefficients dans la base canonique sont stockés dans le vecteur `a`.\n",
    "> 2. Proposez une fonction `interp_vdm` qui \n",
    ">    * prend en argument deux `ndarray` `x` et `y` de taille `N` et un `ndarray` `xx` de taille `M` ;\n",
    ">    * calcule le polynôme interpolateur de Lagrange aux points donnés par les vecteurs `x` et `y` en utilisant la fonction `interp_vdm_poly` ;\n",
    ">    * retourne l'évaluation de ce polynôme aux points du vecteur `xx` en utilisant la fonction `horner`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "def horner(a, xx):\n",
    "    result = np.zeros(xx.shape)\n",
    "    for i in range(len(a) - 1, -1, -1):\n",
    "        result = result * xx + a[i]\n",
    "    return result\n",
    "\n",
    "def interp_vdm(x, y, xx):\n",
    "    P = interp_vdm_poly(x, y)\n",
    "    return horner(P, xx)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Question**\n",
    "\n",
    "> Afin de tester votre fonction : en prenant $N=5$,\n",
    "> 1. prenez $N$ points équirépartis entre 0 et 1 (ce sera notre vecteur `x`) ;\n",
    "> 2. générez $N$ valeurs aléatoires entre 0 et 1 (ce sera notre vecteur `y`) ;\n",
    "> 3. tracez dans une fenêtre graphique le nuage de points d'abscisses `x` et d'ordonnées `y` à l'aide d'une commande `scatter` ;\n",
    "> 4. ajoutez le tracé du polynôme interpolateur en prenant `xx` un vecteur de taille grande (plutôt 100 ou 200 points équi-répartis entre 0 et 1) ;\n",
    "> 5. vérifiez que le polynôme interpolateur passe bien par les points d'interpolation."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 33,
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[<matplotlib.lines.Line2D at 0x7fca5fe6d8a0>]"
      ]
     },
     "execution_count": 33,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "[encoded data, 59824 characters]\n",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {
      "image/png": {
       "height": 413,
       "width": 547
      }
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "N = 5\n",
    "x = np.linspace(0, 1, N)\n",
    "y = np.random.rand(N)\n",
    "xx = np.linspace(0, 1, 200)\n",
    "\n",
    "plt.scatter(x,y)\n",
    "plt.plot(xx, interp_vdm(x, y, xx))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "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.10.8"
  },
  "toc": {
   "base_numbering": 1,
   "nav_menu": {},
   "number_sections": true,
   "sideBar": true,
   "skip_h1_title": false,
   "title_cell": "Table of Contents",
   "title_sidebar": "Contents",
   "toc_cell": false,
   "toc_position": {},
   "toc_section_display": true,
   "toc_window_display": false
  },
  "varInspector": {
   "cols": {
    "lenName": 16,
    "lenType": 16,
    "lenVar": 40
   },
   "kernels_config": {
    "python": {
     "delete_cmd_postfix": "",
     "delete_cmd_prefix": "del ",
     "library": "var_list.py",
     "varRefreshCmd": "print(var_dic_list())"
    },
    "r": {
     "delete_cmd_postfix": ") ",
     "delete_cmd_prefix": "rm(",
     "library": "var_list.r",
     "varRefreshCmd": "cat(var_dic_list()) "
    }
   },
   "types_to_exclude": [
    "module",
    "function",
    "builtin_function_or_method",
    "instance",
    "_Feature"
   ],
   "window_display": false
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
