{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# EE350: Lab 1 ‒  Signaux déterministes"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "On vous fournis des fonctions de plot pour verifier vos résultats dans le fichier `utils.py`. \n",
    "\n",
    "**Veuillez ne pas modifier le script utils.py.**\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#import matplotlib.pyplot as plt\n",
    "import numpy as np\n",
    "from utils import *\n",
    "\n",
    "\n",
    "%matplotlib inline"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 1. Introduction - Signaux numériques\n",
    "\n",
    "Dans cette première partie, vous allez apprendre à générer et manipuler des signaux numériques simples. Cette partie servira également d'introduction au domaine.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "\n",
    "\n",
    "### 1.1 Créer un signal\n",
    "\n",
    "Premièrement, utilisez la bibliothèque Python `NumPy` pour générer un simple signal périodique. En particulier les methodes `np.sin` et `np.linspace`. Pour savoir comment ces methodes marchent et son propos, consultez la [documentation oficiel](https://numpy.org/doc/2.3/reference/index.html#reference) de `Numpy`. \n",
    "\n",
    "Tracez $N_c=4$ périodes d'un sinus oscillant à $f_c=20$ Hz et échantillonné à $f_s=200$ Hz. \n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# TODO\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "\n",
    "plot_1_1(t, x) #t: time points -> np.array , x: signal values -> np.array\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Pourriez-vous expliquer en quelques mots les termes suivants? Justifiez vos explications avec le signal que vous avez créé (n'oubliez pas de mentionner les unités lorsque vous vous référez à des variables physiques):\n",
    "\n",
    "- L'amplitude d'un signal\n",
    "- La période d'un signal\n",
    "- La fréquence d'échantillonnage\n",
    "- La fréquence normalisée"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 1.2 Domaine des fréquences\n",
    "\n",
    "Le signal ci-dessus oscille à une fréquence de $20$ Hz. Dans le domaine temporel, cela est illustré par une \"onde\" qui fait une boucle entre les mêmes valeurs dans le temps. Le domaine temporel est très inefficace pour traiter les signaux oscillants. Heureusement, le domaine fréquentiel est plus pertinent pour ce type de signaux. Ces domaines sont liés par la transformée de Fourier. Pour prouver cette affirmation, tracez la transformée de Fourier de notre signal. \n",
    "\n",
    "Encore une fois, utilisez `NumPy` pour calculer la transformée de Fourier de votre signal définie ci-dessus en utilisant la fonction `fft` du module du même nom. Une fois que vous avez calculé la transformée de Fourier, tracez sa valeur absolue, sa partie réelle et sa partie imaginaire.\n",
    "\n",
    "**Attention**: la fonction `fft` retourne la transformée de Fourier pour l'interval de fréquence normalisée $[0, \\pi]$ et non $[-\\frac{\\pi}{2}, \\frac{\\pi}{2}]$. Pensez donc à centrer votre transformé de Fourier."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# TODO"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "plot_1_2(f, X_real, X_imag, X_mag) # f: frequency points -> np.array, X_real: real part -> np.array, X_imag: imaginary part -> np.array, X_mag: magnitude -> np.array"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 1.3 Signaux à fréquences multiples\n",
    "\n",
    "Générez un nouveau signal périodique. Celui-ci est la somme d'un sinus et d'un cosinus échantillonnés à $f_s=400$ Hz, le premier oscillant à $f_{c1}=20$ Hz et le second à $f_{c2}=50$ Hz. Tracez $N_c=4$ périodes du signal et la valeur absolue de sa transformée de Fourier."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# TODO"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "plot_1_3(t, x1, x2, x, f, X_mag) # t: time points -> np.array, x1: first signal values -> np.array, x2: second signal values -> np.array, x: combined signal values -> np.array, f: frequency points -> np.array, X_mag: magnitude -> np.array"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 1.4 Effet de la fréquence d'échantillonnage\n",
    "\n",
    "Réutilisez le signal ci-dessus, mais changez la fréquence d'échantillonnage à $f_s = 120$ Hz, $60$ Hz et $10$ Hz. Tracez le signal ainsi que la valeur absolue de la transformée de Fourier pour chacune de ces fréquences. Que se passe-t-il?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# TODO"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "plot_1_4(fss, ts, xs, Xs, f_s) # fss: list of sampling frequencies -> list, ts: list of time points -> list of np.array, xs: list of signal values -> list of np.array, Xs: list of FFT values -> list of np.array, f_s: list of frequency points -> list of np.array"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 1.5 Effet du bruit\n",
    "\n",
    "Jusqu'à présent, nous nous sommes concentrés sur les signaux purement oscillants. Essayez un autre type de signal, beaucoup plus proche de la réalité. Réutilisez la fonction précédente, mais ajoutez-y différent niveaux de bruit gaussien avec la fonction Numpy `np.random.randn`. Le bruit gaussien doit avoir une moyenne $\\mu=0$ et un écart-type de $\\sigma=[0.0, 0.5, 1, 2, 5] $. Tracez le signal ainsi que la valeur absolue de la transformée de Fourier.\n",
    "\n",
    "\n",
    "A partir de vos résultats :\n",
    "- Pour chaque domaine, pourriez-vous décider si le signal est périodique ou non ? Discutez de la robustesse de la représentation au bruit.\n",
    "\n",
    "- Pourriez-vous expliquer pourquoi une fréquence d'échantillonnage élevée n'est pas toujours la meilleure solution ?\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# TODO"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "plot_1_5(sigmas, t, f, xns, Xns) # sigmas: noise levels -> list, t: time points -> np.array, f: frequency points -> np.array, xns: noisy signal values -> list of np.array, Xns: FFTs of noisy signals -> list of np.array"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "\n",
    "## 2. Transformée de Fourier\n",
    "\n",
    "Calculer la transformée de Fourier des fonctions suivantes:\n",
    "\n",
    "\n",
    "### 2.1 Théorie\n",
    "* $x_1(t) = \\cos(2 \\pi 10 t)$"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<center style=\"color:blue\">Votre réponse ici</center>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "* $x_2(t) = \\Pi(t) \\cos(2 \\pi 10 t)$ et $\\Pi(t) = \\begin{cases}\n",
    "A, \\, \\forall t \\in [-\\frac{1}{2}, \\frac{1}{2}]\\\\\n",
    "0, \\, \\text{sinon}.\n",
    "\\end{cases}$"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<center style=\"color:blue\">Votre réponse ici</center>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "* $x_3(t) = \\Lambda(t) \\cos(2 \\pi 10 t)$ et $\\Lambda(t) = \\begin{cases}\n",
    "1-|t|, \\, \\forall t \\in [-1, 1]\\\\\n",
    "0, \\, \\text{sinon}.\n",
    "\\end{cases}$"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "vscode": {
     "languageId": "plaintext"
    }
   },
   "source": [
    "<center style=\"color:blue\">Votre réponse ici</center>\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2.2 Application\n",
    "\n",
    "Tracer les fonctions $x_1$, $x_2$, et $x_3$ dans l'interval $t=[-1, 1]$ ainsi que les modules des transformées de Fourier. Utiliser une fréquence de sampling $f_s = 100$ Hz, $A = 1$."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# TODO"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "plot_2_2(xs, Xs, t, f) # xs: list of signal values -> list of np.array, Xs: list of FFT values -> list of np.array, t: time points -> np.array, f: frequency points -> np.array\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "\n",
    "## 3. Propriétés des signaux\n",
    "\n",
    "\n",
    "Soit le signal $x(t) = \\Pi(\\frac{t}{B})$, avec\n",
    "$\\Pi(t) = \\begin{cases}\n",
    "A, \\, \\forall t \\in [-\\frac{1}{2}, \\frac{1}{2}]\\\\\n",
    "0, \\, \\text{sinon}.\n",
    "\\end{cases} $, \n",
    "\n",
    "calculer:\n",
    "\n",
    "* $\\bar{x}(T)$"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "\n",
    "<center style=\"color:blue\">Votre réponse ici</center>\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "* $P_x(T)$"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "\n",
    "<center style=\"color:blue\">Votre réponse ici</center>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "* $P_x$"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<center style=\"color:blue\">Votre réponse ici</center>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "* $x_{eff}$"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<center style=\"color:blue\">Votre réponse ici</center>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Tracez la fonction $\\Pi(\\frac{t}{B})$ sur la periode $t \\in [-\\frac{T}{2}, \\frac{T}{2}]$, avec $T = 1$, $A = 1$. Testez deux valeur de $B = 0.5\\, \\text{et}\\,  2$. La fréquence d'échantillonnage est fixée à $f_s = 1000$ [Hz]."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# TODO"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "plot_3(Bs, t, pis) # Bs: list of widths -> list, t: time points -> np.array, pis: list of signal values -> list of np.array"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "vscode": {
     "languageId": "plaintext"
    }
   },
   "source": [
    "## 4. Approximation d’une fonction\n",
    "\n",
    "\n",
    "### 4.1 Théorie"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Soit $x(t), t\\in[−1,1]$ la fonction:\n",
    "\n",
    "$$x(t) = \\begin{cases}\n",
    "-t^2, \\, \\forall t \\in [-1, 0[\\\\\n",
    "t^2, \\, \\forall t \\in [0, 1].\n",
    "\\end{cases}$$\n",
    "\n",
    "et l’ensemble des polynômes de Tchebychev $\\{\\nu_k\\}, k \\in \\mathrm{N}$ définis comme suit:\n",
    "\n",
    "\n",
    "$$\\nu_k(t) = \\begin{cases}\n",
    "1 ,\\, k = 0\\\\\n",
    "t ,\\, k = 1\\\\\n",
    "2t  \\,\\nu_{k-1}  - \\nu_{k-2}  ,\\, \\text{sinon}.\n",
    "\\end{cases}$$\n",
    "\n",
    "\n",
    "1. Prover que les polynômes $\\{\\nu_k\\}$ constituent une base orthogonale par rapport à la fonction $\\omega(t) = \\frac{1}{\\sqrt{1-t^2}}$ sur l'interval $[-1, 1]$. \n",
    "\n",
    "**Conseil**: Utiliser le changement de variable $t = \\cos(\\theta) $ ainsi que l’egalité $\\nu_n(\\cos(\\theta)) = \\cos(n\\theta) $ propre aux polynômes de Tchebychev."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<center style=\"color:blue\">Votre réponse ici</center>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "2. Calculer les coefficients  $\\lambda_{kp}, \\, k,p \\in \\mathrm{N}$ et en déduire $\\Lambda$."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<center style=\"color:blue\">Votre réponse ici</center>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "\n",
    "3. Calculer les coefficients $\\alpha_{k}$ et $\\gamma_{k}, \\, k \\in \\mathrm{N}$."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<center style=\"color:blue\">Votre réponse ici</center>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 4.2 Application\n",
    "\n",
    "Notre objectif est de reconstruire le signal $x(t)$ en utilisant la base ci-dessus. Dans un premier temps, complétez la fonction `x` ci-dessous. Votre implementation doit passer les tests `assert` au-dessous.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def x(t: np.ndarray):\n",
    "    \"\"\" Evaluate the signal at time t\n",
    "\n",
    "    Parameters\n",
    "    ----------\n",
    "    t : np.ndarray (N, )\n",
    "        Time values\n",
    "        \n",
    "    Return\n",
    "    ------\n",
    "    fx: np.ndarray (N, )\n",
    "        Evaluated function at given times\n",
    "    \"\"\"\n",
    "    \n",
    "    # Empty array \n",
    "    fx = np.zeros_like(t)\n",
    "    \n",
    "    # TODO\n",
    "    \n",
    "    return fx"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Define dummy values to evaluate function\n",
    "t_test = np.array([-1, -0.5, 0, 0.5, 0.8, 1])\n",
    "x_test = x(t=t_test)\n",
    "\n",
    "# Check output with expected terms\n",
    "assert np.all(np.isclose(x_test, [-1, -1, 0, 0.25, 0.64, 1]))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "\n",
    "A présent, construisez les termes alphas, calculés plus haut. Votre implementation doit passer les tests `assert` au-dessous.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def alphas(K: int):\n",
    "    \"\"\" Compute the alpha coefs given K.\n",
    "\n",
    "    Parameters\n",
    "    ----------\n",
    "    K : int\n",
    "        Number of coeffs k = [0, ... , K-1]\n",
    "\n",
    "    Returns\n",
    "    -------\n",
    "    alpha: np.ndarray (K, )\n",
    "        Coeffs for a given K.\n",
    "    \"\"\"\n",
    "    \n",
    "    # Index\n",
    "    k = np.arange(0, K)\n",
    "    # Coefficients\n",
    "    alpha = np.zeros_like(k)\n",
    "    \n",
    "    # TODO\n",
    "        \n",
    "    return alpha"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Define dummy values to evaluate function\n",
    "a_test = alphas(K=6)\n",
    "\n",
    "# Check output with expected terms\n",
    "assert np.all(np.isclose(a_test, [-0.25, 1.061032, 0.25, -0.127323, 0, 0.115197]))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "\n",
    "Tracez la fonction $x(t)$ dans l'interval $[-1, 1]$ ainsi que l'estimation $\\hat{x}(t)$ obtenue en reconstruisant la fonction avec la base de chebychev et leur coefficients. Présenter les résultats pour $k = 5, 10, 50, 200$. De plus, montrez l'erreur de reconstruction entre $x(t)$ et son estimation en fonction du nombre de coefficients $k$. Utilisez une fréquence d'échantillonnage à $f_s = 1000$ [Hz]. Pensez à utiliser la fonction `chebval`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from numpy.polynomial.chebyshev import chebval"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# TODO"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "plot_4(Ks, t, x, x_hats, K_error, errs) # Ks: list of numbers of coefficients -> list, t: time points -> np.array, x: original signal function -> function, x_hats: list of approximated signal values -> list of np.array, K_error: range of K for error computation -> np.array, errs: list of errors -> list"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 5. Orthonormalisation de Gram-Schmidt\n",
    "\n",
    "### 5.1 Théorie"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Considérons les signaux suivants $\\{ s_{n} (t) \\}$, $n \\in  \\{0, 1, 2, \\ldots \\}$, avec\n",
    "\n",
    "\\begin{equation*}\n",
    "\ts_n (t) = t ^{n} \\left( \\text{sgn}(t) + \\left( -1 \\right) ^{n} \\cdot \\text{sgn} (t) + 1 \\right), \\quad t \\in [-1, 1] \\subset \\mathcal{R}.\n",
    "\\end{equation*}\n",
    "\n",
    "\n",
    "1. Est-ce que les signaux $\\{ s_n (t) \\}$ forment une base orthonormale? Justifiez votre réponse."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<center style=\"color:blue\">Votre réponse ici</center>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "2. Considérons uniquement le signal $s_n (t)$ avec $n = 0, 1, 2$. A l'aide de l'algorithme de Gram-Schmidt, calculez une base orthonormale pour l'espace vectoriel formé par les trois signaux."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<center style=\"color:blue\">Votre réponse ici</center>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "3. Donnez une représentation vectorielle des signaux $s_n (t)$, $n = 0, 1, 2$, par rapport à la base orthonormale calculée précédemment.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<center style=\"color:blue\">Votre réponse ici</center>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 5.2 Implementation\n",
    "\n",
    "Dans un premier temps, tracez les fonctions $s_n(t)$ pour $n={0, 1, 2}$ dans l'interval $[-1, 1]$ en utilisant une fréquence d'échantillonnage $f_s = 10,000$ [Hz]. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "plot_5_2(t, Sn) # t: time points -> np.array, Sn: basis functions values -> np.ndarray (N=3, len(t))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "À continuation, nous implementons une fonction qui contrôle l'orthonormalité de vos fonctions. Celle-ci doit prendre en entrée une base de signaux et retourner un boolean (True/False) qui indique si la base est orthonormale. Vérifiez si la base $s_n(t)$ est orthonormale pour $n = {0, 1, 2}$.\n",
    "\n",
    "\n",
    "**Attention**: Dans la partie théorique, nous utilisons une intégrale pour calculer l'orthonormalité des deux fonctions. En discrétisant, nous obtenons l'estimation pour une fréquence de sampling $f_s$:\n",
    "\n",
    "$$\\int_{a}^{b} x_1(t) \\cdot x_2(t) dt \\simeq \\frac{1}{f_s}  \\sum_{t=a}^{b}  x_1(t) \\cdot x_2(t) $$\n",
    "\n",
    "Par conséquent, le résultat doit être normalisé par un facteur $f_s$."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def orthocheck(B: np.ndarray, fs: int):\n",
    "    \"\"\" This function check if the given base is orthonormal\n",
    "\n",
    "    Parameters\n",
    "    ----------\n",
    "    B : np.ndarray (b, t)\n",
    "        Base to check with b vectors and t times\n",
    "\n",
    "    Returns\n",
    "    -------\n",
    "    Boolean\n",
    "        True if base is orthonormal, False otherwise\n",
    "    \"\"\"\n",
    "    \n",
    "    # Check projection of base\n",
    "    G = (B @ B.T) / fs - np.eye(B.shape[0])\n",
    "\n",
    "    # The matrix should be empty\n",
    "    is_ortho = np.all(np.isclose(G, 0, atol=1e-3))\n",
    "    \n",
    "    return is_ortho"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "is_ortho = orthocheck(B=Sn, fs=fs)\n",
    "print(\"The base orthonormal: {}\".format(is_ortho))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Implémentez la base $\\phi_n$ pour $n={0, 1, 2}$, tracez-là et contrôlez son orthonormalité."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# TODO"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "plot_5_2_2(t, Phi) # t: time points -> np.array, Phi: basis functions values -> np.ndarray (N=3, len(t))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "is_ortho = orthocheck(B=Phi, fs=fs)\n",
    "print(\"The base orthonormal: {}\".format(is_ortho))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 5.3 Algorithm \n",
    "\n",
    "L'un des avantages de Gram-Schmidt est qu'il peut être implementé numériquement. \n",
    "\n",
    "Implémentez la fonction `gram_schmidt(...)`, qui prend la matrice $S$ en entrée et applique l'algorithme de Gram-Schmidt aux signaux $s_n(t)$ et estime la nouvelle base orthonormée $\\phi_n(t)$ pour n=10.\n",
    "\n",
    "Il s'avère que la procédure de Gram-Schmidt que nous avons introduite dans le cours souffre d'instabilité numérique : les erreurs d'arrondi peuvent s'accumuler et détruire l'orthogonalité des vecteurs résultants. C'est pourquoi une procédure Gram-Schmidt modifiée est introduite pour aider à remédier à ce problème. Pensez à utiliser l'opèrateur `@` pour multiplier les matrices.  \n",
    "\n",
    "```\n",
    "def gram_schmidt(S):\n",
    "\n",
    "    # Initialize vectors \n",
    "    for j = 1:n\n",
    "        bj=sj\n",
    "        \n",
    "    # Compute new base\n",
    "    for j=1:n\n",
    "    \n",
    "        # Substract previous projections\n",
    "        for k = 1:j\n",
    "            bj = bj − (sj' bk) bk\n",
    "\n",
    "        # Normalize term\n",
    "        bj = bj / ‖bj‖\n",
    "\n",
    "return B\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def gram_schmidt(S: np.ndarray):\n",
    "    \"\"\" Normalize base\n",
    "\n",
    "    Parameters\n",
    "    ----------\n",
    "    S : np.ndarray(n, t)\n",
    "        Original base to normalize\n",
    "\n",
    "    Returns\n",
    "    -------\n",
    "    B: np.ndarray(n, t)\n",
    "        Normalized base\n",
    "    \"\"\"\n",
    "    \n",
    "    # Init base\n",
    "    B = np.zeros_like(S)\n",
    "    \n",
    "    # TODO\n",
    "    \n",
    "    return B\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "plot_5_3(t, S, B) # t: time points -> np.array, S: original basis functions values -> np.ndarray(n, t), B: Normalized base ->np.ndarray(n, t)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Considérons le signal $f(t)$ défini comme :\n",
    "    \n",
    "$$\n",
    "f\\left(t\\right) = t^2 + \\frac{1}{2}\\sum_{i=0}^2 (-1)^i * \\text{rect}(t(i+1)) \n",
    "$$\n",
    "\n",
    "\n",
    "Projetez $f(t)$ sur la base $\\phi_i\\left(n\\right)$, $i \\in {1,2,...,K}$ avec $K \\in [1, 2, 5, 10, 20, 30]$. Tracez le signal $f(t)$ et ses approximations $\\hat{f}_K\\left(n\\right)$ pour $K \\in [1, 2, 5, 10, 20, 30]$ et commentez ce que vous obtenez.\n",
    "\n",
    "L'erreur de reconstruction entre $f(n)$ et $\\hat{f}_K$ est mesurée à l'aide de l'erreur quadratique moyenne (*MSE*) et est définie comme\n",
    "$$\n",
    "MSE\\left(K\\right) = \\frac{1}{N}\\left|\\left|f -  \\hat{f}_K\\right|\\right|^2\n",
    "$$\n",
    "\n",
    "Tracez l'évolution de l'erreur quadratique moyenne par rapport à $K$ et commentez ce que vous observez."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<center style=\"color:blue\">Votre réponse ici</center>"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "plot_5_proj(t, f, Ks, f_hats, mses) # t: time points -> np.array, f: original function values -> np.array, Ks: list of numbers of coefficients -> list, f_hats: list of approximated function values -> list of np.array, mses: list of mean squared errors -> list"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 6. Propriétés de la Transformée de Fourier\n",
    "\n",
    "Calculez la transformée de Fourier de la fonction: \n",
    "\n",
    "$$ x(t) = \\cos{(4 \\pi t - \\frac{\\pi}{2})} \\sin{(4 \\pi t)^2} \\cos{(2 \\pi t)} \\exp{(2 j\\pi t)} $$\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<center style=\"color:blue\">Votre réponse ici</center>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Verifiez votre réponse numériquement. Utilisez une fréquence d'échantillonnage $f_s=100$ Hz sur l'interval $t\\in [0, 1]$"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# TODO"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "plot_6(t, f, x_real, x_imag, X_real, X_imag) # t: time points -> np.array, f: frequency points -> np.array, x_real: real part of signal values -> np.array, x_imag: imaginary part of signal values -> np.array, X_real: real part of FFT values -> np.array, X_imag: imaginary part of FFT values -> np.array"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": ".venv",
   "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.3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
