{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "FEUILLE DE TP 9\n",
    "\n",
    "---\n",
    "# Intégration numérique (Partie 3)\n",
    "---"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "%matplotlib inline\n",
    "%config InlineBackend.figure_format = 'retina'\n",
    "\n",
    "import numpy as np\n",
    "from scipy.integrate import quad\n",
    "import matplotlib.pyplot as plt"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Dans ce TP, nous allons programmer les formules d'intégration de Gauss pour calculer\n",
    "\\begin{equation*}\n",
    "I = \\int\\limits_{ -1 }^{ 1 } f( x ) dx.\n",
    "\\tag{1}\\label{1}\n",
    "\\end{equation*}\n",
    "Nous les testerons sur la fonction suivante :\n",
    "\\begin{equation*}\n",
    "f_0( x ) = \\exp( -x ), \\quad f_1(x) = \\exp( -x^2 / 2 )%\\sqrt{\\cos ( \\pi x / 2) }.\n",
    "\\end{equation*}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Question 1.**\n",
    "\n",
    "> Définissez ces fonctions et tracez les sur l'intervalle $[-1, 1]$."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "Text(0, 0.5, '$f(x)$')"
      ]
     },
     "execution_count": 8,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "[encoded data, 79816 characters]\n",
      "text/plain": [
       "<Figure size 600x600 with 1 Axes>"
      ]
     },
     "metadata": {
      "image/png": {
       "height": 525,
       "width": 536
      }
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "def f0(x):\n",
    "    return np.exp(-x)\n",
    "\n",
    "def f1(x):\n",
    "    return np.exp(-np.power(x, 2)/2)\n",
    "\n",
    "x = np.linspace(-1, 1, 200)\n",
    "fig, ax = plt.subplots(figsize=(6,6))\n",
    "\n",
    "for f in [f0, f1]:\n",
    "    ax.plot(x, f(x), label=f'Courbe {f.__name__}')\n",
    "\n",
    "ax.legend()\n",
    "ax.set_xlabel(f'$x$')\n",
    "ax.set_ylabel(f'$f(x)$')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "On rappelle que l’idée de la quadrature de Gauss est d'approcher la fonction $ f $ sous le signe de l'intégrale (1) par son polynome interpolateur aux points $ x_1, x_2, \\ldots, x_N $ espacés de manière non-régulière, en cherchant un ``bon choix'' des $ x_k $ qui, pour $ N > 0 $ fixé,\n",
    "rendrait la formule de quadrature précise de degré le plus grand possible.\n",
    "\n",
    "Soit $ J( f ) = \\sum_{ i = 1 }^N \\lambda_k f( x_k ) $ la formule de quadrature de Gauss \\`a $ N $ points.  On peut montrer que\n",
    "- les positions $ x_k $ des points de quadrature sont les racines du polynome de Legendre $ P_N $ de degré $ N $.\n",
    "Les polynomes de Legendre peuvent se calculer par récurrence avec la relation\n",
    "\\begin{align*}\n",
    "& P_0 = 1, \\\\\n",
    "& P_1 = x, \\\\\n",
    "& P_{ N } = \\frac{ ( 2N -1 ) x P_{ N - 1 }( x ) - ( N - 1 ) P_{ N - 2 }( x ) }{ N }.\n",
    "\\end{align*}\n",
    "Leurs racines n’ont pas toujours d’expression analytique, et le plus souvent, elles doivent etre calculées\n",
    "numériquement.\n",
    "- les $ N $ coefficients associés aux points de quadrature sont $ \\lambda_k = 2 w_k $ avec:\n",
    "$$\n",
    "w_k = \\frac{ 1 }{ ( 1 - x_k^2 ) P'_{N}( x_k )^2 } = \\frac{ ( 1 - x_k^2 ) }{ N^2 P_{N-1}^2( x_k ) }.\n",
    "$$\n",
    "On peut montrer que $ \\forall N > 0 $ $ \\sum\\limits_{k=1}^{N} w_k = 1 $."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Question 2.**\n",
    "> 1. Ecrire une fonction recursive `poly_legendre( x, n )` qui prend en argument un `ndarray` `x` et un entier `n` et qui retourne une évaluation de $n$-ième polynome de Legendre $ P_n $ aux points `x`.\n",
    "> 2. Tracer les polynomes $ P_n $, $ n = 1, 2, \\ldots, 6 $ sur $  [ -1, 1 ]$.\n",
    "> 3. En se servant du graphique donner des valeurs approchées de points de quadrature $ x_k $, $ k = 0, \\ldots, n $ pour $ n = 1, 2, 3 $. Les résultats que vous obtenez pour $ n = 1, 2 $ sont-ils conformes aux résultats vus en cours? "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 42,
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "def poly_legendre(x, n):\n",
    "    if n == 0:\n",
    "        return np.ones_like(x)\n",
    "    if n == 1:\n",
    "        return x\n",
    "    else:\n",
    "        return ((2*n-1) * x * poly_legendre(x, n-1) - (n - 1) * poly_legendre(x, n-2)) / n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 49,
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "Text(0, 0.5, '$f(x)$')"
      ]
     },
     "execution_count": 49,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "[encoded data, 257920 characters]\n",
      "text/plain": [
       "<Figure size 600x600 with 1 Axes>"
      ]
     },
     "metadata": {
      "image/png": {
       "height": 525,
       "width": 557
      }
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "x = np.linspace(-1, 1, 200)\n",
    "fig, ax = plt.subplots(figsize=(6,6))\n",
    "\n",
    "for n in [x for x in range(1, 7)]:\n",
    "    ax.plot(x, poly_legendre(x, n), label=f'Courbe n={n}')\n",
    "\n",
    "ax.legend()\n",
    "ax.set_xlabel(f'$x$')t\n",
    "ax.set_ylabel(f'$f(x)$')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Question 3.**\n",
    "> 1. Ecrire une fonction `poids_gauss( x, n )` qui prend en argument un `ndarray` `x` et un entier `n` et qui retourne un `ndarray` des coéficients $ \\lambda_1, \\ldots, \\lambda_n $ de la formule de Gauss à `n` points avec les points de quadrature `x`.\n",
    "> 2. Testez votre fonction pour $ n = 1 $, $ x = [ 0 ] $ et $ n = 2 $, $ x = [ -1/\\sqrt{3}, 1/ \\sqrt{3} ] $."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 194,
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "def poids_gauss(x, n):\n",
    "    return 2 * (1 - x**2) / (n**2 * poly_legendre(x, n-1)**2)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 195,
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[2.]\n",
      "[1. 1.]\n"
     ]
    }
   ],
   "source": [
    "print(poids_gauss(np.array([0]), 1))\n",
    "print(poids_gauss(np.array([-1/np.sqrt(3), 1/np.sqrt(3)]), 2))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "La fonction `roots_legendre` de `scipy.special` permet de calculer les valeurs approchées des racines $ x_1, x_2, \\ldots, x_n $ du $n$-ième polynome de Legendre ainsi que les poids $ \\lambda_1, \\ldots, \\lambda_n $ de la formule de quadrature de Gauss associée. \n",
    "\n",
    "**Question 4.**\n",
    "> 1. En utilisant la fonction `roots_legendre` de `scipy.special` calculer les racines des polynomes de Legendre $ P_n $ pour $ n = 1, \\ldots, 6 $. Ajouter une représentation graphique des racines à l'image de la Question 2.2.\n",
    "> 2. Comparer les coéficients de la formule de quadrature de Gauss à $ n $ points obtenus via la fonction `roots_legendre` et votre fonction `poids_gauss` pour quelques différentes valeurs de $ n $.  "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 196,
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "Text(0, 0.5, '$f(x)$')"
      ]
     },
     "execution_count": 196,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "[encoded data, 399236 characters]\n",
      "text/plain": [
       "<Figure size 1200x600 with 1 Axes>"
      ]
     },
     "metadata": {
      "image/png": {
       "height": 525,
       "width": 1022
      }
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "from scipy.special import roots_legendre\n",
    "\n",
    "fig, ax = plt.subplots(figsize=(12,6))\n",
    "\n",
    "for n in [x for x in range(1, 7)]:\n",
    "    x = np.linspace(-1, 1, 200)\n",
    "    roots, weight = roots_legendre(n)\n",
    "    ax.plot(x, poly_legendre(x, n), label=f'Courbe n={n}')\n",
    "    ax.scatter(roots, np.zeros_like(roots), label=f'Racines n={n}')\n",
    "    \n",
    "ax.plot(x, np.zeros_like(x), color='black', label=f'$y=0$')\n",
    "ax.legend()\n",
    "ax.grid()\n",
    "ax.set_xlabel(f'$x$')\n",
    "ax.set_ylabel(f'$f(x)$')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 197,
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Les poids de roots_legendre sont w=[2.]\n",
      "Les poids de poids_gauss sont w=[2.]\n",
      "\n",
      "\n",
      "Les poids de roots_legendre sont w=[1. 1.]\n",
      "Les poids de poids_gauss sont w=[1. 1.]\n",
      "\n",
      "\n",
      "Les poids de roots_legendre sont w=[0.55555556 0.88888889 0.55555556]\n",
      "Les poids de poids_gauss sont w=[0.55555556 0.88888889 0.55555556]\n",
      "\n",
      "\n",
      "Les poids de roots_legendre sont w=[0.34785485 0.65214515 0.65214515 0.34785485]\n",
      "Les poids de poids_gauss sont w=[0.34785485 0.65214515 0.65214515 0.34785485]\n",
      "\n",
      "\n",
      "Les poids de roots_legendre sont w=[0.23692689 0.47862867 0.56888889 0.47862867 0.23692689]\n",
      "Les poids de poids_gauss sont w=[0.23692689 0.47862867 0.56888889 0.47862867 0.23692689]\n",
      "\n",
      "\n",
      "Les poids de roots_legendre sont w=[0.17132449 0.36076157 0.46791393 0.46791393 0.36076157 0.17132449]\n",
      "Les poids de poids_gauss sont w=[0.17132449 0.36076157 0.46791393 0.46791393 0.36076157 0.17132449]\n",
      "\n",
      "\n"
     ]
    }
   ],
   "source": [
    "for n in [x for x in range(1, 7)]:\n",
    "    roots, weight = roots_legendre(n)\n",
    "    poids = poids_gauss(roots, n)\n",
    "    print(f'Les poids de roots_legendre sont w={weight}')\n",
    "    print(f'Les poids de poids_gauss sont w={poids}')\n",
    "    print('\\n')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Question 5.**\n",
    "> 1. Programmez une fonction `quad_gauss( f, x, n )` qui prend en argument une fonction `f` et un entier `n` et qui retourne la valeur approchée de $I$ obtenue par la formule de quadrature de Gauss à $ n $ points.\n",
    "> 2. Pour $ n = 6 $ vérifier numériquement que la formule de quadratue de Gauss à $ n $ points est précise de degré $ 2n -1 $."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 198,
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "def quad_gauss(f, n):\n",
    "    roots, _ = roots_legendre(n)\n",
    "    lk = poids_gauss(roots, n)\n",
    "\n",
    "    return np.sum(lk * f(roots))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 199,
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Précision de I(f0): 1.5694112676101213e-12\n",
      "Précision de I(f0): 1.2742946919885867e-08\n",
      "Degré de précision est 11\n"
     ]
    }
   ],
   "source": [
    "n = 6\n",
    "print(f'Précision de I(f0): {np.abs(quad_gauss(f0, n) - quad(f0, -1, 1)[0])}')\n",
    "print(f'Précision de I(f0): {np.abs(quad_gauss(f1, n) - quad(f1, -1, 1)[0])}')\n",
    "print(f'Degré de précision est {2*n-1}')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Question 6.**\n",
    "> 1. Comparez la valeur approchée de $I$ obtenue à l'aide de la fonction `quad_gauss` avec sa valeur exacte pour des différents nombres de points $n = 1, 2, \\ldots, 13$. Que se passe-t-il quand $n$ augmente ?\n",
    "> 2. Tracez le logarithme d'erreur entre l'intégrale approchée et la valeur exacte en fonction de $n$. Que observez-vous?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 203,
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "--- [ n = 1 ] ---\n",
      "I(f0) par quad_gauss: 2.0\n",
      "I(f0) par quad: 2.3504023872876028\n",
      "I(f1) par quad_gauss: 2.0\n",
      "I(f1) par quad: 1.7112487837842973\n",
      "\n",
      "--- [ n = 2 ] ---\n",
      "I(f0) par quad_gauss: 2.342696087909731\n",
      "I(f0) par quad: 2.3504023872876028\n",
      "I(f1) par quad_gauss: 1.6929634497812287\n",
      "I(f1) par quad: 1.7112487837842973\n",
      "\n",
      "--- [ n = 3 ] ---\n",
      "I(f0) par quad_gauss: 2.3503369286800107\n",
      "I(f0) par quad: 2.3504023872876028\n",
      "I(f1) par quad_gauss: 1.712020245201908\n",
      "I(f1) par quad: 1.7112487837842973\n",
      "\n",
      "--- [ n = 4 ] ---\n",
      "I(f0) par quad_gauss: 2.3504020921563775\n",
      "I(f0) par quad: 2.3504023872876028\n",
      "I(f1) par quad_gauss: 1.7112245045994903\n",
      "I(f1) par quad: 1.7112487837842973\n",
      "\n",
      "--- [ n = 5 ] ---\n",
      "I(f0) par quad_gauss: 2.350402386462827\n",
      "I(f0) par quad: 2.3504023872876028\n",
      "I(f1) par quad_gauss: 1.7112493935244661\n",
      "I(f1) par quad: 1.7112487837842973\n",
      "\n",
      "--- [ n = 6 ] ---\n",
      "I(f0) par quad_gauss: 2.3504023872860333\n",
      "I(f0) par quad: 2.3504023872876028\n",
      "I(f1) par quad_gauss: 1.7112487710413504\n",
      "I(f1) par quad: 1.7112487837842973\n",
      "\n",
      "--- [ n = 7 ] ---\n",
      "I(f0) par quad_gauss: 2.3504023872876103\n",
      "I(f0) par quad: 2.3504023872876028\n",
      "I(f1) par quad_gauss: 1.7112487840123798\n",
      "I(f1) par quad: 1.7112487837842973\n",
      "\n",
      "--- [ n = 8 ] ---\n",
      "I(f0) par quad_gauss: 2.350402387287602\n",
      "I(f0) par quad: 2.3504023872876028\n",
      "I(f1) par quad_gauss: 1.7112487837807273\n",
      "I(f1) par quad: 1.7112487837842973\n",
      "\n",
      "--- [ n = 9 ] ---\n",
      "I(f0) par quad_gauss: 2.3504023872876076\n",
      "I(f0) par quad: 2.3504023872876028\n",
      "I(f1) par quad_gauss: 1.711248783784349\n",
      "I(f1) par quad: 1.7112487837842973\n",
      "\n",
      "--- [ n = 10 ] ---\n",
      "I(f0) par quad_gauss: 2.3504023872875996\n",
      "I(f0) par quad: 2.3504023872876028\n",
      "I(f1) par quad_gauss: 1.7112487837842956\n",
      "I(f1) par quad: 1.7112487837842973\n",
      "\n",
      "--- [ n = 11 ] ---\n",
      "I(f0) par quad_gauss: 2.350402387287603\n",
      "I(f0) par quad: 2.3504023872876028\n",
      "I(f1) par quad_gauss: 1.7112487837842976\n",
      "I(f1) par quad: 1.7112487837842973\n",
      "\n",
      "--- [ n = 12 ] ---\n",
      "I(f0) par quad_gauss: 2.350402387287596\n",
      "I(f0) par quad: 2.3504023872876028\n",
      "I(f1) par quad_gauss: 1.7112487837842947\n",
      "I(f1) par quad: 1.7112487837842973\n",
      "\n",
      "--- [ n = 13 ] ---\n",
      "I(f0) par quad_gauss: 2.3504023872876036\n",
      "I(f0) par quad: 2.3504023872876028\n",
      "I(f1) par quad_gauss: 1.7112487837842976\n",
      "I(f1) par quad: 1.7112487837842973\n",
      "\n"
     ]
    }
   ],
   "source": [
    "for n in [x for x in range(1, 14)]:\n",
    "    print(f'--- [ n = {n} ] ---')\n",
    "    for f in [f0, f1]:\n",
    "        print(f'I({f.__name__}) par quad_gauss: {quad_gauss(f, n)}')\n",
    "        print(f'I({f.__name__}) par quad: {quad(f, -1, 1)[0]}')\n",
    "    print('')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Question 7.**\n",
    "> Pour les méthodes composées du rectangles, du trapèze et de Simpson tracez sur le meme graphique le logarithme d'erreur entre l'intégrale approchée et la valeur exacte en fonction de $n$, où $ n $ est le nombre de points utilisés pour intégrer la fonction $ f $. Commentez. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "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"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
