{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "c000",
   "metadata": {},
   "source": [
    "# TP 6 — Circuit RC : traitement des données — **VERSION PROFESSEUR (corrigée)**\n",
    "\n",
    "Toutes les cellules sont complétées. Les réponses attendues figurent dans les encadrés *Réponse attendue*.\n",
    "\n",
    "**Mode d'emploi (rappel de la version élève)**\n",
    "- **[FOURNIE]** : cellule déjà écrite, à lire et à exécuter.\n",
    "- **[À COMPLÉTER]** : cellule dont les `____` sont à remplacer par l'élève."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c001",
   "metadata": {},
   "source": [
    "## 0. Bibliothèques  [FOURNIE]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "c002",
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import matplotlib.pyplot as plt"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c003",
   "metadata": {},
   "source": [
    "## 1. Vos mesures  [À COMPLÉTER]\n",
    "\n",
    "Reporter ici les valeurs obtenues dans les parties 1 et 2 du TP, **en unités SI** (Ω, F, s).\n",
    "\n",
    "Écriture scientifique en Python : $100\\ \\text{nF} = 100 \\times 10^{-9}\\ \\text{F}$ s'écrit `100e-9`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "c004",
   "metadata": {},
   "outputs": [],
   "source": [
    "# --- Composants (partie 1 du TP) — valeurs typiques\n",
    "R   = 998.0     # résistance mesurée (ohm)\n",
    "u_R = 5.0       # incertitude-type sur R (ohm)\n",
    "C   = 100.4e-9  # capacité mesurée (F)\n",
    "u_C = 1.5e-9    # incertitude-type sur C (F)\n",
    "\n",
    "# --- Oscilloscope (partie 2 du TP)\n",
    "tau_osc   = 104e-6   # tau mesuré par la méthode des 63 % (s)\n",
    "u_tau_osc = 3e-6     # incertitude-type sur tau_osc (s)\n",
    "\n",
    "# --- Acquisition (partie 3 du TP)\n",
    "CHARGE  = True          # True si vous étudiez la charge, False pour la décharge\n",
    "FICHIER = \"charge.txt\"  # nom de votre fichier exporté"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c005",
   "metadata": {},
   "source": [
    "Calculer la valeur attendue $\\tau_\\text{th} = RC$ et son incertitude-type :\n",
    "$$\\frac{u(\\tau_\\text{th})}{\\tau_\\text{th}} = \\sqrt{\\left(\\frac{u(R)}{R}\\right)^2 + \\left(\\frac{u(C)}{C}\\right)^2}$$\n",
    "\n",
    "**[À COMPLÉTER]** — `np.sqrt()` calcule une racine carrée."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "c006",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "tau_th = 100.2 µs  ±  1.6 µs\n"
     ]
    }
   ],
   "source": [
    "tau_th   = R*C\n",
    "u_tau_th = tau_th*np.sqrt((u_R/R)**2 + (u_C/C)**2)\n",
    "\n",
    "print(f\"tau_th = {tau_th*1e6:.1f} µs  ±  {u_tau_th*1e6:.1f} µs\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c007",
   "metadata": {},
   "source": [
    "**Auto-vérification [FOURNIE]** : si un message d'erreur apparaît, relire vos unités."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "c008",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Ordres de grandeur corrects ✔\n"
     ]
    }
   ],
   "source": [
    "assert 20e-6 < tau_th < 500e-6, \"tau_th doit être de l'ordre de 100 µs : vérifier les unités de R et C.\"\n",
    "assert 20e-6 < tau_osc < 500e-6, \"tau_osc doit être de l'ordre de 100 µs : est-il bien en secondes ?\"\n",
    "print(\"Ordres de grandeur corrects ✔\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c009",
   "metadata": {},
   "source": [
    "## 2. Lecture des données  [FOURNIE]\n",
    "\n",
    "La fonction ci-dessous lit le fichier exporté par LatisPro.\n",
    "Elle renvoie trois tableaux : le temps `t`, la tension du GBF `e` (voie EA1) et la tension du condensateur `u` (voie EA2).\n",
    "\n",
    "*Les réglages `SEPARATEUR`, `VIRGULE_DECIMALE` et `LIGNES_ENTETE` ont été testés par le professeur : ne pas les modifier.*"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "c010",
   "metadata": {},
   "outputs": [
    {
     "ename": "FileNotFoundError",
     "evalue": "[Errno 2] No such file or directory: 'charge.txt'",
     "output_type": "error",
     "traceback": [
      "\u001b[31m---------------------------------------------------------------------------\u001b[39m",
      "\u001b[31mFileNotFoundError\u001b[39m                         Traceback (most recent call last)",
      "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[5]\u001b[39m\u001b[32m, line 13\u001b[39m\n\u001b[32m     10\u001b[39m     donnees = np.loadtxt(lignes, delimiter=SEPARATEUR)\n\u001b[32m     11\u001b[39m     \u001b[38;5;28;01mreturn\u001b[39;00m donnees[:, \u001b[32m0\u001b[39m], donnees[:, \u001b[32m1\u001b[39m], donnees[:, \u001b[32m2\u001b[39m]\n\u001b[32m---> \u001b[39m\u001b[32m13\u001b[39m t, e, u = \u001b[43mlire_latispro\u001b[49m\u001b[43m(\u001b[49m\u001b[43mFICHIER\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m     14\u001b[39m \u001b[38;5;28mprint\u001b[39m(\u001b[33mf\u001b[39m\u001b[33m\"\u001b[39m\u001b[38;5;132;01m{\u001b[39;00m\u001b[38;5;28mlen\u001b[39m(t)\u001b[38;5;132;01m}\u001b[39;00m\u001b[33m points lus, de t = \u001b[39m\u001b[38;5;132;01m{\u001b[39;00mt[\u001b[32m0\u001b[39m]*\u001b[32m1e6\u001b[39m\u001b[38;5;132;01m:\u001b[39;00m\u001b[33m.0f\u001b[39m\u001b[38;5;132;01m}\u001b[39;00m\u001b[33m µs à t = \u001b[39m\u001b[38;5;132;01m{\u001b[39;00mt[-\u001b[32m1\u001b[39m]*\u001b[32m1e6\u001b[39m\u001b[38;5;132;01m:\u001b[39;00m\u001b[33m.0f\u001b[39m\u001b[38;5;132;01m}\u001b[39;00m\u001b[33m µs\u001b[39m\u001b[33m\"\u001b[39m)\n",
      "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[5]\u001b[39m\u001b[32m, line 6\u001b[39m, in \u001b[36mlire_latispro\u001b[39m\u001b[34m(nom)\u001b[39m\n\u001b[32m      5\u001b[39m \u001b[38;5;28;01mdef\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34mlire_latispro\u001b[39m(nom):\n\u001b[32m----> \u001b[39m\u001b[32m6\u001b[39m     \u001b[38;5;28;01mwith\u001b[39;00m \u001b[38;5;28;43mopen\u001b[39;49m\u001b[43m(\u001b[49m\u001b[43mnom\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mencoding\u001b[49m\u001b[43m=\u001b[49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43mlatin-1\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[43m)\u001b[49m \u001b[38;5;28;01mas\u001b[39;00m f:\n\u001b[32m      7\u001b[39m         lignes = f.readlines()[LIGNES_ENTETE:]\n\u001b[32m      8\u001b[39m     \u001b[38;5;28;01mif\u001b[39;00m VIRGULE_DECIMALE:\n",
      "\u001b[36mFile \u001b[39m\u001b[32m/opt/anaconda3/lib/python3.13/site-packages/IPython/core/interactiveshell.py:343\u001b[39m, in \u001b[36m_modified_open\u001b[39m\u001b[34m(file, *args, **kwargs)\u001b[39m\n\u001b[32m    336\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m file \u001b[38;5;129;01min\u001b[39;00m {\u001b[32m0\u001b[39m, \u001b[32m1\u001b[39m, \u001b[32m2\u001b[39m}:\n\u001b[32m    337\u001b[39m     \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mValueError\u001b[39;00m(\n\u001b[32m    338\u001b[39m         \u001b[33mf\u001b[39m\u001b[33m\"\u001b[39m\u001b[33mIPython won\u001b[39m\u001b[33m'\u001b[39m\u001b[33mt let you open fd=\u001b[39m\u001b[38;5;132;01m{\u001b[39;00mfile\u001b[38;5;132;01m}\u001b[39;00m\u001b[33m by default \u001b[39m\u001b[33m\"\u001b[39m\n\u001b[32m    339\u001b[39m         \u001b[33m\"\u001b[39m\u001b[33mas it is likely to crash IPython. If you know what you are doing, \u001b[39m\u001b[33m\"\u001b[39m\n\u001b[32m    340\u001b[39m         \u001b[33m\"\u001b[39m\u001b[33myou can use builtins\u001b[39m\u001b[33m'\u001b[39m\u001b[33m open.\u001b[39m\u001b[33m\"\u001b[39m\n\u001b[32m    341\u001b[39m     )\n\u001b[32m--> \u001b[39m\u001b[32m343\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43mio_open\u001b[49m\u001b[43m(\u001b[49m\u001b[43mfile\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43m*\u001b[49m\u001b[43margs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43m*\u001b[49m\u001b[43m*\u001b[49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n",
      "\u001b[31mFileNotFoundError\u001b[39m: [Errno 2] No such file or directory: 'charge.txt'"
     ]
    }
   ],
   "source": [
    "SEPARATEUR       = \";\"   # caractère qui sépare les colonnes\n",
    "VIRGULE_DECIMALE = True  # LatisPro en français écrit 1,5 au lieu de 1.5\n",
    "LIGNES_ENTETE    = 1     # nombre de lignes de titre à ignorer\n",
    "\n",
    "def lire_latispro(nom):\n",
    "    with open(nom, encoding=\"latin-1\") as f:\n",
    "        lignes = f.readlines()[LIGNES_ENTETE:]\n",
    "    if VIRGULE_DECIMALE:\n",
    "        lignes = [l.replace(\",\", \".\") for l in lignes]\n",
    "    donnees = np.loadtxt(lignes, delimiter=SEPARATEUR)\n",
    "    return donnees[:, 0], donnees[:, 1], donnees[:, 2]\n",
    "\n",
    "t, e, u = lire_latispro(FICHIER)\n",
    "print(f\"{len(t)} points lus, de t = {t[0]*1e6:.0f} µs à t = {t[-1]*1e6:.0f} µs\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c011",
   "metadata": {},
   "source": [
    "### Origine des temps  [FOURNIE]\n",
    "\n",
    "Le modèle suppose que $t = 0$ correspond **au front** de $e(t)$.\n",
    "On repère donc le premier point où $e$ franchit la moitié de sa variation, puis on décale tous les temps pour que ce point soit à $t = 0$.\n",
    "Les points enregistrés avant le front (pré-déclenchement) ont ainsi des temps négatifs."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "c012",
   "metadata": {},
   "outputs": [
    {
     "ename": "NameError",
     "evalue": "name 'e' is not defined",
     "output_type": "error",
     "traceback": [
      "\u001b[31m---------------------------------------------------------------------------\u001b[39m",
      "\u001b[31mNameError\u001b[39m                                 Traceback (most recent call last)",
      "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[6]\u001b[39m\u001b[32m, line 1\u001b[39m\n\u001b[32m----> \u001b[39m\u001b[32m1\u001b[39m seuil = (\u001b[43me\u001b[49m.max() + e.min())/\u001b[32m2\u001b[39m\n\u001b[32m      2\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m CHARGE:\n\u001b[32m      3\u001b[39m     i_front = np.argmax(e > seuil)   \u001b[38;5;66;03m# premier indice où e dépasse le seuil (front montant)\u001b[39;00m\n",
      "\u001b[31mNameError\u001b[39m: name 'e' is not defined"
     ]
    }
   ],
   "source": [
    "seuil = (e.max() + e.min())/2\n",
    "if CHARGE:\n",
    "    i_front = np.argmax(e > seuil)   # premier indice où e dépasse le seuil (front montant)\n",
    "else:\n",
    "    i_front = np.argmax(e < seuil)   # premier indice où e passe sous le seuil (front descendant)\n",
    "\n",
    "t = t - t[i_front]\n",
    "print(f\"Front repéré au point n°{i_front} ; t va maintenant de {t[0]*1e6:.0f} µs à {t[-1]*1e6:.0f} µs\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c013",
   "metadata": {},
   "source": [
    "## 3. Tracé des données  [À COMPLÉTER]\n",
    "\n",
    "Tracer sur un même graphique :\n",
    "- $e(t)$ en trait gris (`'-'` et `color='grey'`), avec le label `\"e(t) (GBF)\"` ;\n",
    "- $u_C(t)$ en points rouges (`'.r'`), avec le label `\"u_C(t) mesurée\"`.\n",
    "\n",
    "Pour lire les temps en microsecondes, on trace `t*1e6` plutôt que `t`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "c014",
   "metadata": {},
   "outputs": [
    {
     "ename": "NameError",
     "evalue": "name 't' is not defined",
     "output_type": "error",
     "traceback": [
      "\u001b[31m---------------------------------------------------------------------------\u001b[39m",
      "\u001b[31mNameError\u001b[39m                                 Traceback (most recent call last)",
      "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[7]\u001b[39m\u001b[32m, line 2\u001b[39m\n\u001b[32m      1\u001b[39m plt.figure()\n\u001b[32m----> \u001b[39m\u001b[32m2\u001b[39m plt.plot(\u001b[43mt\u001b[49m*\u001b[32m1e6\u001b[39m, e, \u001b[33m'\u001b[39m\u001b[33m-\u001b[39m\u001b[33m'\u001b[39m, color=\u001b[33m'\u001b[39m\u001b[33mgrey\u001b[39m\u001b[33m'\u001b[39m, label=\u001b[33m\"\u001b[39m\u001b[33me(t) (GBF)\u001b[39m\u001b[33m\"\u001b[39m)\n\u001b[32m      3\u001b[39m plt.plot(t*\u001b[32m1e6\u001b[39m, u, \u001b[33m'\u001b[39m\u001b[33m.r\u001b[39m\u001b[33m'\u001b[39m, label=\u001b[33m\"\u001b[39m\u001b[33mu_C(t) mesurée\u001b[39m\u001b[33m\"\u001b[39m)\n\u001b[32m      4\u001b[39m plt.xlabel(\u001b[33m\"\u001b[39m\u001b[33mt (µs)\u001b[39m\u001b[33m\"\u001b[39m)\n",
      "\u001b[31mNameError\u001b[39m: name 't' is not defined"
     ]
    },
    {
     "data": {
      "text/plain": [
       "<Figure size 640x480 with 0 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "plt.figure()\n",
    "plt.plot(t*1e6, e, '-', color='grey', label=\"e(t) (GBF)\")\n",
    "plt.plot(t*1e6, u, '.r', label=\"u_C(t) mesurée\")\n",
    "plt.xlabel(\"t (µs)\")\n",
    "plt.ylabel(\"tension (V)\")\n",
    "plt.legend()\n",
    "plt.grid()\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c015",
   "metadata": {},
   "source": [
    "## 4. Linéarisation — étape 1 : tous les points\n",
    "\n",
    "Le modèle (charge comme décharge) s'écrit :\n",
    "$$u_C(t) = u_\\infty + (u_0 - u_\\infty)\\,e^{-t/\\tau}\n",
    "\\quad\\Longleftrightarrow\\quad\n",
    "y = \\ln\\!\\left(\\frac{u_\\infty - u_C}{u_\\infty - u_0}\\right) = -\\frac{t}{\\tau}$$\n",
    "\n",
    "Si le modèle est valide, $y$ en fonction de $t$ est une **droite passant par l'origine**, de pente $-1/\\tau$.\n",
    "\n",
    "### Estimation de $u_0$ et $u_\\infty$  [FOURNIE]\n",
    "\n",
    "- $u_0$ : moyenne des points **avant** le front ($t < 0$) ;\n",
    "- $u_\\infty$ : moyenne des **50 derniers points**.\n",
    "\n",
    "On utilise des moyennes plutôt qu'une seule valeur (comme `max(u)`), car une valeur isolée est sensible au bruit."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "c016",
   "metadata": {},
   "outputs": [
    {
     "ename": "NameError",
     "evalue": "name 'u' is not defined",
     "output_type": "error",
     "traceback": [
      "\u001b[31m---------------------------------------------------------------------------\u001b[39m",
      "\u001b[31mNameError\u001b[39m                                 Traceback (most recent call last)",
      "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[8]\u001b[39m\u001b[32m, line 1\u001b[39m\n\u001b[32m----> \u001b[39m\u001b[32m1\u001b[39m u0   = np.mean(\u001b[43mu\u001b[49m[t < \u001b[32m0\u001b[39m])\n\u001b[32m      2\u001b[39m uinf = np.mean(u[-\u001b[32m50\u001b[39m:])\n\u001b[32m      3\u001b[39m \u001b[38;5;28mprint\u001b[39m(\u001b[33mf\u001b[39m\u001b[33m\"\u001b[39m\u001b[33mu0 = \u001b[39m\u001b[38;5;132;01m{\u001b[39;00mu0\u001b[38;5;132;01m:\u001b[39;00m\u001b[33m.3f\u001b[39m\u001b[38;5;132;01m}\u001b[39;00m\u001b[33m V   et   u_inf = \u001b[39m\u001b[38;5;132;01m{\u001b[39;00muinf\u001b[38;5;132;01m:\u001b[39;00m\u001b[33m.3f\u001b[39m\u001b[38;5;132;01m}\u001b[39;00m\u001b[33m V\u001b[39m\u001b[33m\"\u001b[39m)\n",
      "\u001b[31mNameError\u001b[39m: name 'u' is not defined"
     ]
    }
   ],
   "source": [
    "u0   = np.mean(u[t < 0])\n",
    "uinf = np.mean(u[-50:])\n",
    "print(f\"u0 = {u0:.3f} V   et   u_inf = {uinf:.3f} V\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c017",
   "metadata": {},
   "source": [
    "### Calcul et tracé de $y$  [À COMPLÉTER]\n",
    "\n",
    "`np.log()` calcule le logarithme népérien. Tracer `y` en fonction de `t` en points bleus (`'.b'`)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c018",
   "metadata": {},
   "outputs": [],
   "source": [
    "y = np.log((uinf - u)/(uinf - u0))\n",
    "\n",
    "plt.figure()\n",
    "plt.plot(t*1e6, y, '.b')\n",
    "plt.xlabel(\"t (µs)\")\n",
    "plt.ylabel(r\"$\\ln\\left(\\frac{u_\\infty - u_C}{u_\\infty - u_0}\\right)$\")\n",
    "plt.title(\"Étape 1 : tous les points\")\n",
    "plt.grid()\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c019",
   "metadata": {},
   "source": [
    "**Si un avertissement `RuntimeWarning: invalid value encountered in log` s'affiche, c'est normal.** C'est même le sujet de la question suivante.\n",
    "\n",
    "➜ Répondre aux questions de l'**étape 1** sur le sujet.\n",
    "\n",
    "> **Réponse attendue.**\n",
    "> - Pour $t < 0$ : $u_C \\simeq u_0$, donc $y \\simeq 0$ (palier bruité).\n",
    "> - Aux temps courts ($0 < t \\lesssim 3\\tau$) : les points sont alignés sur une droite décroissante passant par l'origine.\n",
    "> - Aux temps longs : $u_\\infty - u_C$ devient du même ordre que le bruit (quelques mV). Le logarithme amplifie ces petits écarts ($\\ln x \\to -\\infty$ quand $x \\to 0$), d'où une dispersion énorme.\n",
    "> - Lorsque, à cause du bruit, $u_C > u_\\infty$, l'argument devient négatif : le logarithme n'existe pas (`NaN`, d'où l'avertissement).\n",
    "> - Conclusion : ces points ne contiennent plus d'information exploitable sur $\\tau$ ; il faut les exclure de l'ajustement."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c020",
   "metadata": {},
   "source": [
    "## 5. Linéarisation — étape 2 : ne garder que les points utiles\n",
    "\n",
    "### Nouvel outil : le masque  [FOURNIE — à lire attentivement]\n",
    "\n",
    "Un **masque** est un tableau de `True` / `False` de même taille que les données.\n",
    "- On le fabrique avec une **condition** sur un tableau.\n",
    "- On l'utilise pour **ne garder que les éléments où il vaut `True`**.\n",
    "\n",
    "Exemple sur un petit tableau :"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c021",
   "metadata": {},
   "outputs": [],
   "source": [
    "temps   = np.array([0, 1, 2, 3, 4, 5])\n",
    "mesures = np.array([10, 20, 30, 40, 50, 60])\n",
    "\n",
    "masque = (temps >= 1) & (temps <= 3)   # le symbole & signifie « ET »\n",
    "print(\"masque           :\", masque)\n",
    "print(\"temps[masque]    :\", temps[masque])\n",
    "print(\"mesures[masque]  :\", mesures[masque])"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c022",
   "metadata": {},
   "source": [
    "À retenir :\n",
    "- `temps >= 1` donne `[False, True, True, True, True, True]` ;\n",
    "- `(temps >= 1) & (temps <= 3)` ne vaut `True` que si **les deux** conditions sont vraies ;\n",
    "- `mesures[masque]` ne garde que les mesures **aux mêmes positions** que les `True`.\n",
    "\n",
    "*Chaque condition doit être entre parenthèses.*\n",
    "\n",
    "### Application à nos données  [FOURNIE]\n",
    "\n",
    "On garde les points tels que $0 \\leqslant t \\leqslant 3\\tau$, en utilisant $\\tau_\\text{osc}$ mesuré à l'oscilloscope.\n",
    "On écarte aussi les éventuelles valeurs non calculables avec `np.isfinite(y)`, qui vaut `True` quand `y` est un vrai nombre."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c023",
   "metadata": {},
   "outputs": [],
   "source": [
    "masque = (t >= 0) & (t <= 3*tau_osc) & np.isfinite(y)\n",
    "\n",
    "t_m = t[masque]\n",
    "y_m = y[masque]\n",
    "print(f\"{len(t_m)} points conservés sur {len(t)}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c024",
   "metadata": {},
   "source": [
    "### Régression linéaire  [FOURNIE]\n",
    "\n",
    "`np.polyfit(x, y, 1)` cherche la meilleure droite $y = a\\,t + b$.\n",
    "L'option `cov=True` renvoie en plus de quoi calculer l'incertitude-type sur $a$.\n",
    "\n",
    "Comme $a = -1/\\tau$, on obtient $\\tau_\\text{fit} = -1/a$ et $u(\\tau_\\text{fit}) = u(a)/a^2$."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c025",
   "metadata": {},
   "outputs": [],
   "source": [
    "(a, b), cov = np.polyfit(t_m, y_m, 1, cov=True)\n",
    "u_a = np.sqrt(cov[0, 0])\n",
    "\n",
    "tau_fit   = -1/a\n",
    "u_tau_fit = u_a/a**2\n",
    "\n",
    "print(f\"pente a = {a:.4g} s^-1     ordonnée à l'origine b = {b:.3f}\")\n",
    "print(f\"tau_fit = {tau_fit*1e6:.1f} µs  ±  {u_tau_fit*1e6:.1f} µs\")\n",
    "\n",
    "plt.figure()\n",
    "plt.plot(t*1e6, y, '.', color='lightgrey', label=\"points écartés\")\n",
    "plt.plot(t_m*1e6, y_m, '.b', label=\"points conservés\")\n",
    "plt.plot(t_m*1e6, a*t_m + b, '-r', lw=2, label=\"meilleure droite\")\n",
    "plt.xlabel(\"t (µs)\")\n",
    "plt.ylabel(r\"$\\ln\\left(\\frac{u_\\infty - u_C}{u_\\infty - u_0}\\right)$\")\n",
    "plt.title(\"Étape 2 : 0 ≤ t ≤ 3τ\")\n",
    "plt.ylim(1.5*y_m.min(), 0.5)\n",
    "plt.legend()\n",
    "plt.grid()\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c026",
   "metadata": {},
   "source": [
    "➜ Répondre aux questions de l'**étape 2** sur le sujet.\n",
    "\n",
    "> **Réponse attendue.** Les points sont alignés : le modèle exponentiel est validé. L'ordonnée à l'origine $b$ est proche de 0, comme le prévoit le modèle (ce qui valide aussi le choix de l'origine des temps).\n",
    "\n",
    "**Défi (facultatif)** : remplacer `3*tau_osc` par `5*tau_osc` dans le masque.\n",
    "\n",
    "> **Réponse attendue (défi).** Les points bruités des temps longs entrent dans l'ajustement : la droite est tirée par ces points dispersés, $\\tau_\\text{fit}$ est biaisé et son incertitude augmente. Plus on se rapproche de $u_\\infty$, moins un point apporte d'information sur $\\tau$."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c027",
   "metadata": {},
   "source": [
    "## 6. Comparaison avec la courbe théorique  [À COMPLÉTER]\n",
    "\n",
    "Calculer la courbe théorique pour $t \\geqslant 0$, avec $\\tau_\\text{th}$ :\n",
    "$$u_\\text{th}(t) = u_\\infty + (u_0 - u_\\infty)\\,e^{-t/\\tau_\\text{th}}$$\n",
    "\n",
    "Puis tracer, sur un même graphique :\n",
    "- les mesures en points rouges (`'.r'`) ;\n",
    "- la courbe théorique en trait plein noir (`'-k'`)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c028",
   "metadata": {},
   "outputs": [],
   "source": [
    "t_th = np.linspace(0, t.max(), 200)\n",
    "u_th = uinf + (u0 - uinf)*np.exp(-t_th/tau_th)\n",
    "\n",
    "plt.figure()\n",
    "plt.plot(t*1e6, u, '.r', label=\"mesures\")\n",
    "plt.plot(t_th*1e6, u_th, '-k', label=\"modèle théorique (τ_th)\")\n",
    "plt.xlabel(\"t (µs)\")\n",
    "plt.ylabel(\"u_C (V)\")\n",
    "plt.legend()\n",
    "plt.grid()\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c029",
   "metadata": {},
   "source": [
    "## 7. Écarts normalisés  [À COMPLÉTER]\n",
    "\n",
    "Compléter la fonction qui calcule\n",
    "$$z = \\frac{|x_1 - x_2|}{\\sqrt{u(x_1)^2 + u(x_2)^2}}$$\n",
    "\n",
    "`abs()` donne la valeur absolue."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c030",
   "metadata": {},
   "outputs": [],
   "source": [
    "def ecart_normalise(x1, u1, x2, u2):\n",
    "    return abs(x1 - x2)/np.sqrt(u1**2 + u2**2)\n",
    "\n",
    "print(f\"tau_osc / tau_th  : z = {ecart_normalise(tau_osc, u_tau_osc, tau_th, u_tau_th):.2f}\")\n",
    "print(f\"tau_fit / tau_th  : z = {ecart_normalise(tau_fit, u_tau_fit, tau_th, u_tau_th):.2f}\")\n",
    "print(f\"tau_fit / tau_osc : z = {ecart_normalise(tau_fit, u_tau_fit, tau_osc, u_tau_osc):.2f}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c031",
   "metadata": {},
   "source": [
    "➜ Compléter le tableau du sujet et conclure.\n",
    "\n",
    "> **Réponse attendue.** $\\tau_\\text{fit}$ et $\\tau_\\text{osc}$ sont en général compatibles entre eux, mais souvent supérieurs d'environ 5 % à $RC$. Selon la précision des composants, $z$ par rapport à $\\tau_\\text{th}$ peut dépasser 2. L'incertitude sur $\\tau_\\text{fit}$ (statistique de l'ajustement) est très petite : c'est $\\tau_\\text{fit}$ qui révèle le plus nettement l'écart.\n",
    ">\n",
    "> **Explication** : la résistance de sortie du GBF, $R_g = 50\\ \\Omega$, est en série avec $R$. On a donc $\\tau' = (R + R_g)\\,C \\approx 1{,}05\\,RC$. Avec cette correction, les valeurs redeviennent compatibles. L'impédance d'entrée de la centrale (de l'ordre du MΩ) est, elle, négligeable.\n",
    "\n",
    "Pour tester l'explication proposée dans le sujet : **[À COMPLÉTER]**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c032",
   "metadata": {},
   "outputs": [],
   "source": [
    "R_g = 50   # résistance de sortie du GBF (ohm), lue sur sa face avant\n",
    "\n",
    "tau_corr   = (R + R_g)*C\n",
    "u_tau_corr = tau_corr*np.sqrt((u_R/R)**2 + (u_C/C)**2)   # on néglige l'incertitude sur R_g\n",
    "\n",
    "print(f\"tau corrigé = {tau_corr*1e6:.1f} µs  ±  {u_tau_corr*1e6:.1f} µs\")\n",
    "print(f\"tau_fit / tau corrigé : z = {ecart_normalise(tau_fit, u_tau_fit, tau_corr, u_tau_corr):.2f}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c033",
   "metadata": {},
   "source": [
    "## 8. Pour aller plus loin : résolution numérique par la méthode d'Euler\n",
    "\n",
    "Le circuit obéit à l'équation différentielle\n",
    "$$\\frac{\\mathrm{d}u}{\\mathrm{d}t} + \\frac{u}{\\tau} = \\frac{E}{\\tau},$$\n",
    "où $E$ est la tension du GBF après le front (ici $E = u_\\infty$).\n",
    "\n",
    "Si $\\Delta t$ est petit, la dérivée s'approche par un taux de variation :\n",
    "$$\\frac{\\mathrm{d}u}{\\mathrm{d}t} \\simeq \\frac{u_{n+1} - u_n}{\\Delta t}\n",
    "\\quad\\Longrightarrow\\quad\n",
    "u_{n+1} = u_n + (E - u_n)\\,\\frac{\\Delta t}{\\tau}.$$\n",
    "\n",
    "1. Compléter l'algorithme. **[À COMPLÉTER]**\n",
    "2. Tracer les mesures, le modèle théorique et la solution d'Euler sur un même graphique.\n",
    "3. Que se passe-t-il si l'on prend `Deltat = tau_th/2` ? Puis `Deltat = 2*tau_th` ?\n",
    "\n",
    "> **Réponse attendue (3).** Avec $\\Delta t = \\tau/2$, la solution d'Euler reste proche mais s'écarte visiblement de l'exponentielle (elle converge trop vite). Avec $\\Delta t = 2\\tau$, le facteur $(1 - \\Delta t/\\tau) = -1$ fait osciller la solution autour de $E$ sans jamais converger : la méthode est instable si $\\Delta t \\geqslant 2\\tau$."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c034",
   "metadata": {},
   "outputs": [],
   "source": [
    "E      = uinf            # tension imposée par le GBF après le front\n",
    "Deltat = tau_th/100      # pas de temps\n",
    "\n",
    "t_euler = np.arange(0, t.max(), Deltat)\n",
    "u_euler = np.zeros_like(t_euler)\n",
    "u_euler[0] = u0          # condition initiale\n",
    "\n",
    "for n in range(len(t_euler) - 1):\n",
    "    u_euler[n+1] = u_euler[n] + (E - u_euler[n])*Deltat/tau_th\n",
    "\n",
    "plt.figure()\n",
    "plt.plot(t*1e6, u, '.r', label=\"mesures\")\n",
    "plt.plot(t_th*1e6, u_th, '-k', label=\"modèle théorique\")\n",
    "plt.plot(t_euler*1e6, u_euler, '--b', label=\"Euler\")\n",
    "plt.xlabel(\"t (µs)\")\n",
    "plt.ylabel(\"u_C (V)\")\n",
    "plt.legend()\n",
    "plt.grid()\n",
    "plt.show()"
   ]
  }
 ],
 "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.13.9"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
