{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# La transformée de Fourier discrète\n",
    "\n",
    "# Une introduction"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Marc Lorenzi - 9 mai 2018"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "import math\n",
    "import cmath\n",
    "import random\n",
    "import matplotlib.pyplot as plt\n",
    "%matplotlib inline"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "La transformée de Fourier discrète (ou DFT pour Discrete Fourier Transform) est une opération permettant, étant donné un signal (les mathématiciens disent une fonction) périodique, d'obtenir les fréquences caractéristiques de ce signal. Pour conserver à ce notebook une longueur $L<+\\infty$, j'ai choisi de ne pas parler d'un grand nombre de choses, pourtant intéressantes, essentielles, incontournables dirais-je. D'où le titre : \"Une introduction\".\n",
    "\n",
    "Nous allons examiner deux algorithmes de calcul de la DFT. Le premier, naïf (vraiment ?) fonctionne bien tant que la taille des objets à manipuler reste petite. Hélas, la DFT doit souvent opérer sur des vecteurs de grandes tailles (des dizaines de milliers d'éléments). Nous introduirons dans ce but l'algorithme de la FFT (Fast Fourier Transform), qui, comme son nom l'indique, permet d'effectuer très rapidement une DFT. La FFT est sans doute l'un des algorithmes les plus utilisés sur notre planète (un peu emphatique, peut-être ?). "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 1. Manipulation de matrices"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 1.1 Quelques fonctions utiles"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Nous aurons besoin de manipuler des matrices à coefficients complexes (que nous représenterons par des listes de listes). J'ai dit \"pas de boîte noire\", alors nous allons définir un certain nombre de fonctions :"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- `matrice(m, n)` renvoie une matrice $m\\times n$ remplie de zéros.\n",
    "- `taille(A)` renvoie $(m, n)$ si $A$ est une matrice $m\\times n$.\n",
    "- `mult_mat(A, B)` renvoie le produit des matrices $A$ et $B$.\n",
    "- `mult_vec(A, X)` renvoie le produit de la matrice $A$ et de la liste $X$ identifiée à une matrice colonne.\n",
    "- `conjuguee(A)` renvoie la matrice dont les coefficients sont les conjugués complexes de ceux de la matrice $A$."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "__Remarque__ : dans tout le texte, nous emploierons le mot \"matrice\" pour désigner des listes de listes en Python. Nous emploierons le mot \"vecteur\" pour désigner des listes Python. Les vecteurs seront un moyen commode de représenter des matrices colonnes, plus commode que des listes de listes de taille 1, en tout cas.  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Les calculs sur les flottants renvoient des valeurs approchées. Il est toujours désagréable, lorsqu'on attend 0 comme résultat, de voir affiché à l'écran `1.0675565434567876554e-16+9.8776765454434543e-15j`. Nous allons donc également écrire quelques fonctions pour arrondir des nombres complexes, des matrices, des vecteurs.\n",
    "\n",
    "- `roundC(z, n)` arrondit la partie réelle et la partie imaginaire de $z$ à $n$ chiffres après la virgule.\n",
    "- `roundM(A, n)` applique `roundC` aux coefficients de la matrice $A$.\n",
    "- `roundV(X, n)` applique `roundC` aux coefficients du vecteur $X$."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ces fonctions ne présentent pas de difficultés particulières, et vous pouvez foncer sans dommages à la section 2 si tout cela est évident pour vous."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 1.2 Opérations matricielles"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "La fonction `matrice` renvoie une matrice $m\\times n$ remplie de zéros."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "def matrice(m, n):\n",
    "    A = m * [None]\n",
    "    for i in range(m): A[i] = n * [0]\n",
    "    return A"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[[0, 0], [0, 0], [0, 0]]"
      ]
     },
     "execution_count": 3,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "matrice(3,2)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "La fonction `taille` prend une matrice $m\\times n$ en paramètre et renvoie le couple $(m, n)$. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "def taille(A):\n",
    "    m = len(A)\n",
    "    n = len(A[0])\n",
    "    return (m, n)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(7, 13)"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "taille(matrice(7, 13))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "La fonction `mult_mat` renvoie le produit des matrices $A$ et $B$. Il faut évidemment que les matrices aient des tailles compatibles."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "def mult_mat(A, B):\n",
    "    m, n = taille(A)\n",
    "    n1, p = taille(B)\n",
    "    assert n == n1\n",
    "    C = matrice(m, p)\n",
    "    for i in range(m):\n",
    "        for j in range(p):\n",
    "            for k in range(n):\n",
    "                C[i][j] = C[i][j] + A[i][k] * B[k][j]\n",
    "    return C"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[[20, 14], [56, 41]]"
      ]
     },
     "execution_count": 7,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "mult_mat([[1, 2, 3], [4, 5, 6]], [[6, 5], [4, 3], [2, 1]])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "La fonction `mult_vec` effectue le produit de la matrice $A$ et du vecteur $X$ (une liste Python, donc), interprété comme une matrice colonne."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [],
   "source": [
    "def mult_vec(A, X):\n",
    "    m, n = taille(A)\n",
    "    n1 = len(X)\n",
    "    assert n == n1\n",
    "    Y = m * [0]\n",
    "    for i in range(n):\n",
    "        for j in range(m):\n",
    "            Y[i] = Y[i] + A[i][j] * X[j]\n",
    "    return Y"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[8, 18]"
      ]
     },
     "execution_count": 9,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "mult_vec([[1, 2], [3, 4]], [2, 3])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "La conjuguée d'une matrice à coefficients dans $\\mathbb C$ est la matrice des conjugués des coefficients."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [],
   "source": [
    "def conjuguee(A):\n",
    "    m, n = taille(A)\n",
    "    B = matrice(m, n)\n",
    "    for i in range(m):\n",
    "        for j in range(n):\n",
    "            B[i][j] = A[i][j].conjugate()\n",
    "    return B"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[[1, (1-1j)], [-1j, (2+1j)]]"
      ]
     },
     "execution_count": 11,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "conjuguee([[1,1+1j],[1j, 2-1j]])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 1.3 Arrondis"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "La fonction Python `round` prend en paramètres un flottant $x$ et un entier $n$ et renvoie $x$ arrondi à $n$ chiffres après la virgule."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "1.4142"
      ]
     },
     "execution_count": 12,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "round(math.sqrt(2), 4)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "On étend cette fonction aux nombres complexes, aux matrices et aux vecteurs."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [],
   "source": [
    "def roundC(z, n):\n",
    "    return round(z.real, n) + round(z.imag, n) * 1j"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(0.333+0.143j)"
      ]
     },
     "execution_count": 14,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "roundC(1/3+1/7*1j, 3)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [],
   "source": [
    "def roundM(A, k):\n",
    "    m, n = taille(A)\n",
    "    return [[roundC(A[i][j], k) for i in range(m)] for j in range(n)]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [],
   "source": [
    "def roundV(X, k):\n",
    "    n = len(X)\n",
    "    return [roundC(X[i], k) for i in range(n)]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 2. Une matrice aux propriétés remarquables"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2.1 Objectifs"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "__La vision \"mathématicien\"__ : Soient $n\\ge 1$, et $a_0,a_1,\\ldots,a_{n-1}\\in\\mathbb C$. Soit $f:\\mathbb C\\to\\mathbb C$ la fonction polynomiale définie par $f(t)=\\sum_{k=0}^{n-1}a_kt^k$. Si on me donne les $a_k$, il est évidemment facile de calculer $f(t)$ pour tout $t$. Inversement, __l'interpolation de Lagrange__ nous permet de retrouver les $a_k$ si on connaît la valeur de $f$ en $n$ nombres distincts. Le calcul par les formules \"standard\" nécessite un grand nombre d'opérations, et il est donc inefficace. L'idée (géniale) pour améliorer la complexité du calcul est d'__évaluer $f$ en les racines $n$-ièmes de l'unité__.\n",
    "\n",
    "Pour tout entier $n\\ge 1$, notons $\\omega_n=e^{2 i\\pi/n}$. $\\omega_n$ engendre le groupe des racines $n$ièmes de l'unité : $\\mathcal U_n=\\{\\omega_n^k,0\\le k\\le n - 1\\}$. La connaissance de $f(\\omega_n^k)$ pour $k=0,\\ldots, n-1$ est suffisante pour calculer les $a_k$. Pourquoi les racines $n$ièmes de l'unité ? Le reste du notebook est là pour nous le faire comprendre.\n",
    "\n",
    "__La vision \"physicien\"__ : soit $g(t)=\\sum_{k=0}^{n-1}a_ke^{ikt}$ un signal $2\\pi$-périodique. Le coefficient $a_k$ est l'amplitude de la \"partie du signal\" de période $\\frac{2\\pi}{k}$, ou, si l'on préfère, de fréquence $\\frac{k}{2\\pi}$. $k=1$ correspond à la \"fondamentale\", $k=2,3$, etc. aux \"harmoniques\". Comment retrouver les $a_k$ lorsqu'on dispose d'un échantillonnage du signal aux temps $t=\\frac{2k\\pi}{n}$, $k=0,\\ldots,n-1$ ?\n",
    "\n",
    "Ces deux visions ne sont que deux perspectives d'une même réalité puisqu'on a $g(t)=f(e^{it})$.\n",
    "\n",
    "Nous allons tout d'abord voir qu'une simple opérations matricielle permet de passer des fréquences au signal ET du signal aux fréquences. C'est cela la DFT."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2.2 La matrice $W_n$"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "La fonction `racine_unite` renvoie $\\omega_n=e^{\\frac{2i\\pi}{n}}$."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [],
   "source": [
    "def racine_unite(n):\n",
    "     return cmath.exp(2 * 1j * math.pi / n)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(6.123233995736766e-17+1j)"
      ]
     },
     "execution_count": 18,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "racine_unite(4)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "C'est ce que je voulais dire plus haut quand je parlais de problèmes d'arrondi, parce que en réalité, $\\omega_4=i$."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "1j"
      ]
     },
     "execution_count": 19,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "roundC(racine_unite(4), 15)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Soit $W_n\\in\\mathcal M_n(\\mathbb C)$ définie pour $0\\le i,j\\le n-1$ par $(W_n)_{ij}=\\omega_n^{-ij}$. La matrice $W_n$ est la matrice de Vandermonde associée aux racines $n$ièmes de l'unité. Votre cours sur l'interpolation de Lagrange vous montrera que c'est bien cette matrice qui nous intéresse. Et si vous avez perdu votre cours, regardez la suite qui vous le prouvera aussi. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [],
   "source": [
    "def W(n):\n",
    "    omega = racine_unite(n)\n",
    "    A = matrice(n, n)\n",
    "    for i in range(n):\n",
    "        for j in range(n):\n",
    "            A[i][j] = omega ** (-i * j)\n",
    "    return A"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[[(1+0j), (1+0j), (1+0j), (1+0j)], [(1+0j), (6.123233995736766e-17-1j), (-1-1.2246467991473532e-16j), (-1.8369701987210297e-16+1j)], [(1+0j), (-1-1.2246467991473532e-16j), (1+2.4492935982947064e-16j), (-1-3.6739403974420594e-16j)], [(1+0j), (-1.8369701987210297e-16+1j), (-1-3.6739403974420594e-16j), (5.51091059616309e-16-1j)]]\n"
     ]
    }
   ],
   "source": [
    "print(W(4))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[[(1+0j), (1+0j), (1+0j), (1+0j)],\n",
       " [(1+0j), -1j, (-1+0j), 1j],\n",
       " [(1+0j), (-1+0j), (1+0j), (-1+0j)],\n",
       " [(1+0j), 1j, (-1+0j), -1j]]"
      ]
     },
     "execution_count": 22,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "roundM(W(4), 14)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Bon, alors, pourquoi les racines $n$ièmes de l'unicité ? Eh bien à cause du théorème ci-dessous. Interpoler, c'est inverser une matrice de Vandermonde. Et en général ce n'est pas très simple. Mais ici C'EST SIMPLE."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "__Proposition__ : $\\overline{W_n}W_n=nI_n$.\n",
    "\n",
    "__Démonstration__ : Pour $0\\le i,j\\le n-1$, on a $(\\overline{W_n}W_n)_{ij}=\\sum_{k=0}^{n-1}(\\overline{W_n})_{ik}(W_n)_{kj}=\\sum_{k=0}^{n-1}\\omega_n^{ik}\\omega_n^{-kj}=\\sum_{k=0}^{n-1}(\\omega_n^{i-j})^{k}$. Comme $-n+1\\le i - j\\le n-1$, $\\omega_n^{i-j}=1$ si et seulement si $i=j$. Deux cas sont donc à considérer.\n",
    "\n",
    "- Si $i=j$, $(\\overline{W_n}W_n)_{ij}=\\sum_{k=0}^{n-1}1=n$.\n",
    "- Si $i\\ne j$, $(\\overline{W_n}W_n)_{ij}=\\frac{1-(\\omega_n^{i-j})^{n}}{1-\\omega_n^{i-j}}=0$ puisque $\\omega_n^{i-j}\\in\\mathcal U_n$.\n",
    "\n",
    "On a donc bien $\\overline{W_n}W_n=nI_n$.\n",
    "\n",
    "__Corollaire__ : La matrice $W_n$ est inversible et son inverse est $\\frac 1 n \\overline{W_n}$."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[[(5+0j), 0j, 0j, 0j, 0j], [0j, (5+0j), 0j, 0j, 0j], [0j, (-0+0j), (5+0j), 0j, 0j], [0j, 0j, (-0+0j), (5+0j), 0j], [0j, 0j, 0j, (-0+0j), (5+0j)]]\n"
     ]
    }
   ],
   "source": [
    "A = W(5)\n",
    "print(roundM(mult_mat(A, conjuguee(A)), 13))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2.3 Échantillonnage"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Dorénavant, nous privilégierons la vision du physicien, sans doute plus \"parlante\" que celle du mathématicien. Soit $f:\\mathbb R\\to\\mathbb C$ une fonction de période $2\\pi$. La fonction `sample` renvoie la liste des $f(\\frac{2k\\pi}{n})$ pour $0\\le k\\le n-1$."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {},
   "outputs": [],
   "source": [
    "def sample(f, n):\n",
    "    return [f(2 * k * math.pi / n) for k in range(n)]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[(-1+0j), (0.6909830056250525-1.6775990443005142j), (3.045084971874737+0.13875727571288743j), (1.8090169943749477+3.6654687894677265j), (-2.5450849718747364+4.028740053470408j), (-5+9.797174393178826e-16j), (-2.545084971874738-4.028740053470406j), (1.8090169943749463-3.6654687894677274j), (3.0450849718747377-0.13875727571288898j), (0.6909830056250537+1.6775990443005149j)]\n"
     ]
    }
   ],
   "source": [
    "X = sample(lambda t: 2 * cmath.exp(1j * t) - 3 * cmath.exp(1j * 2 * t), 10)\n",
    "print(X)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Que se passe-t-il si l'on calcule le produit matriciel $W_nX$ ? Supposons que $f(t)=\\sum_{j=0}^{n-1}a_je^{ijt}$, où les $a_k\\in\\mathbb C$. On a alors, pour $0\\le k\\le n-1$, $f(\\frac{2k\\pi}n)=\\sum_{j=0}^{n-1}a_j\\omega_n^{jk}$. De là,\n",
    "\n",
    "$(W_nX)_{i1}=\\sum_{k=0}^{n-1}(W_n)_{ik}X_{k1}=\\sum_{k=0}^{n-1}\\omega_n^{-ik}\\sum_{j=0}^{n-1}a_j\\omega_n^{kj}$. Inversons les sommes : $(W_nX)_{i1}=\\sum_{j=0}^{n-1}a_j\\sum_{k=0}^{n-1}\\omega_n^{k(-i+j)}$. Comme vu un peu plus haut, toutes les sommes sur $k$ sont nulles sauf lorsque $i=j$. Ainsi, $(W_nX)_{i1}=na_i$.\n",
    "\n",
    "__Bilan__ : un échantillonnage de $f$ suivi d'un produit par $W_n$ permet de récupérer les valeurs des coefficients $a_i$."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "__Remarque__ : le calcul ci-dessus renvoie $na_i$ et pas $a_i$. On pourrait s'en débarrasser mais laissons le facteur $n$ dans un coin de notre esprit. On pourra toujours diviser par $n$ le moment venu s'il le faut."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[0j, (20+0j), (-30+0j), 0j, 0j, 0j, 0j, 0j, 0j, 0j]\n"
     ]
    }
   ],
   "source": [
    "Y = mult_vec(W(10), X)\n",
    "print(roundV(Y, 10))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "L'opération que nous venons de décrire s'appelle la _transformée de Fourier discrète_."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2.4 Signaux réels"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "En général les signaux réels sont réels. Euh, enfin, je veux dire qu'une fonction qui représente un vrai signal de la vraie vie est une fonction à valeurs dans $\\mathbb R$ et pas $\\mathbb C$."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Que se passe-t-il exactement si on calcule la DFT d'une fonction à valeurs réelles ? Considérons pour simplifier notre discussion la fonction $f(t)=\\cos kt$. On a $f(t)=\\frac 1 2 e^{ikt}+\\frac 1 2 e^{-ikt}$, la somme de DEUX exponentielles. Lorsqu'on échantillonne $\\frac 1 2 e^{ikt}$ tout se passe comme vu plus haut. Mais que se passe-t-il lorsqu'on échantillonne $\\frac 1 2 e^{-ikt}$ ? Après tout, $-k$ n'est pas un entier entre 0 et $n-1$ ... Reprenons une partie du calcul fait plus haut. Un signe moins s'y glisse sournoisement et on tombe sur la somme $\\sum_{k=0}^{n-1} \\omega_n^{-k(i+j)}$, qui est nulle sauf si $i+j$ est un multiple de $n$. Comme $0\\le i, j\\le n-1$, cela ne peut arriver que si $i+j=n$, c'est à dire $j=n-i$. Dit autrement, si on échantillonne $\\cos kt$ et qu'on multiplie par $W_n$ le vecteur d'échantillonnage, on obtient DEUX coefficients non nuls, aux indices $k$ et $n-k$. Et ces coefficients sont égaux à $\\frac n 2$."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[0j, 0j, (5+0j), 0j, 0j, 0j, 0j, 0j, (5+0j), 0j]\n"
     ]
    }
   ],
   "source": [
    "X = sample(lambda t: math.cos(2 * t), 10)\n",
    "Y = mult_vec(W(10), X)\n",
    "print(roundV(Y, 10))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "__Conclusion pratique__ : si l'on veut analyser un signal réel dont les fréquences sont comprises entre 0 et $f_{max}$, il faudra faire un échantillonnage de taille au moins $2f_{max}$. Sinon, les informations relatives à des fréquences différentes vont s'additionner et seront donc perdues ! Cette constatation est un cas particulier d'un théorème de la __théorie de l'information__, appelé le théorème de Shannon.\n",
    "\n",
    "Pour prendre un exemple concret, l'oreille humaine ne peut pas entendre de sons de fréquence supérieure à 20 KHz (à part Clark Kent et quelques autres). Si l'on veut numériser fidèlement un son, il faudra donc l'échantillonner à 40 KHz, c'est à dire stocker 40000 valeurs de l'intensité du son par seconde. C'est beaucoup. Pour un morceau de musique de 3 minutes, cela représente 7200000 valeurs. En supposant que l'on stocke chaque intensité par un flottant sur 4 octets, cela fait un fichier d'environ 30 Méga-octets. Voilà pourquoi des méthodes de compression (MP3 pour citer la plus populaire) sont utilisées pour stocker la musique ou pour la transmettre sur un réseau."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2.5 La DFT"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "La fonction de DFT fait deux lignes. Il n'y a qu'à lire (et comprendre) ce qui précède."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "metadata": {},
   "outputs": [],
   "source": [
    "def DFT(X):\n",
    "    n = len(X)\n",
    "    return mult_vec(W(n), X)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Testons. Échantillonnons une fonction simple, et traçons le graphe de la DFT. Vous étiez inquiets n'est-ce pas ? Toujours aucune image :-) ..."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[0j, 0j, 0j, -15j, (-0+0j), 0j, 0j, 0j, 0j, 0j, 0j, 0j, (-0+0j), 0j, (-0+0j), (-0+0j), 0j, 0j, 0j, (-0+0j), 0j, 0j, 0j, 0j, 0j, 0j, 0j, 15j, 0j, (-0+0j)]\n"
     ]
    }
   ],
   "source": [
    "X = sample(lambda x: math.sin(3 * x), 30)\n",
    "print(roundV(DFT(X), 12))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Petit souci. Le vecteur de la DFT est à coefficients complexes. On va tracer les __modules__ des coefficients, ce qui est un bon choix pour avoir une idée de leur grandeur (pléonasme : la \"grandeur\" d'un nombre complexe c'est son module). "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "metadata": {},
   "outputs": [],
   "source": [
    "def plotC(X):\n",
    "    plt.plot([abs(z) for z in X])\n",
    "    plt.grid()\n",
    "    plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "X = sample(lambda x: math.sin(3 * x), 30)\n",
    "plotC(DFT(X))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Comme le prévoyait la théorie, il y a deux pics, un à la \"fréquence\" 3, l'autre à la \"fréquence\" 27. N'hésitez pas à expérimenter, changez la fonction, combinez des cosinus avec des fréquences différentes, etc."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "__Remarque__ : Je sais, 3 et 27 ne sont pas les fréquences mais les pulsations du signal. Divisez par $2\\pi$ si cela vous plonge dans le ravissement."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2.6 La DFT inverse"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Il est évidemment immédiat d'inverser une DFT puisqu'on connaît l'inverse de la matrice $W_n$. Et n'oublions pas de diviser par $n$, qui était rangé dans un coin de notre esprit."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "metadata": {},
   "outputs": [],
   "source": [
    "def invDFT(X):\n",
    "    n = len(X)\n",
    "    Y = mult_vec(conjuguee(W(n)), X)\n",
    "    for k in range(n):\n",
    "        Y[k] = Y[k] / n\n",
    "    return Y"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Allez, un petit test numérique. Composons `DFT` et `invDFT`. Cela devrait donner l'identité."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 33,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "3.829625773323887e-14\n"
     ]
    }
   ],
   "source": [
    "X = sample(lambda x: math.sin(x), 100)\n",
    "X1 = invDFT(DFT(X))\n",
    "err = [abs(X[i] - X1[i]) for i in range(len(X))]\n",
    "print(max(err))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Tout va bien. Pas d'erreur (à $\\varepsilon$ près :-))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2.7 Une première application : le filtrage du bruit"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Lorsqu'on cherche à analyser un \"vrai\" signal, celui-ci est en général perturbé par un certain nombre de facteurs : erreurs de mesure, erreurs de transmission, parasites, etc. Ces perturbations, que nous appellerons \"bruit\", peuvent dans certains cas être vues comme des signaux de grande fréquence. Comment éliminer le bruit et retrouver un signal sans défaut ? Voici une méthode qui peut, parfois, donner des résultats probants :\n",
    "\n",
    "1. Échantillonner le signal\n",
    "2. Faire une DFT\n",
    "3. Mettre à 0 les coefficients de la DFT de haute fréquence\n",
    "4. Faire une DFT inverse\n",
    "\n",
    "Évidemment, tout l'art de l'élimination consiste à apprécier ce que l'on considère comme une \"haute\" fréquence. Et nous sommes tout à fait conscients que :\n",
    "\n",
    "1. On risque d'éliminer une partie du vrai signal.\n",
    "2. On n'éliminera pas les composantes du bruit de basse fréquence."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Voici deux fonctions. La fonction $f$ est une très jolie fonction. La fonction $f_1$ est une perturbation de la fonction $f$."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 34,
   "metadata": {},
   "outputs": [],
   "source": [
    "def f(x): return 3 * math.sin(x) + 2 * math.cos(3 * x) - math.sin(6 * x)\n",
    "def f1(x): return f(x) + 0.5 * random.random()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "En noir le graphe de $f$, en rouge celui de $f_1$."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 35,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "n = 256\n",
    "xs = [2 * k * math.pi / n for k in range(n)]\n",
    "ys = sample(f, n)\n",
    "zs = sample(f1, n)\n",
    "plt.plot(xs, ys, 'k')\n",
    "plt.plot(xs, zs, 'r')\n",
    "plt.grid()\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Éliminons les hautes fréquences."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 36,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "n = 256\n",
    "xs = [2 * k * math.pi / n for k in range(n)]\n",
    "ys = sample(f, n)\n",
    "X = sample(f1, n)\n",
    "Y = DFT(X)\n",
    "for k in range(10, n - 10): Y[k] = 0\n",
    "X1 = [z.real for z in invDFT(Y)]\n",
    "plt.plot(xs, ys, 'k')\n",
    "plt.plot(xs, X1, 'r')\n",
    "plt.grid()\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Le signal \"débruité\" est joli mais il n'est pas exactement égal au signal espéré. Moralité, le bruit ce n'est pas que du signal de haute fréquence. On s'en contentera."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2.8 Un inconvénient rédhibitoire de la DFT"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Quel est le temps de calcul d'une DFT ? Facile, on multiplie une matrice $n\\times n$ par une matrice colonne de taille $n$. On a donc une complexité en $O(n^2)$. Essayez pour voir de lancer dans une des cellules ci-dessus la DFT d'un vecteur de taille 1000. On commence à attendre. Un peu, pas beaucoup. Puis prenez un vecteur de taille 3000. Vous attendrez $3^2\\simeq 10$ fois plus, c'est à dire beaucoup. Désolé pour le $3^2=10$, je n'ai pas pu me retenir.\n",
    "\n",
    "Pour des petites valeurs de $n$ (dans l'exemple de l'élimination du bruit, $n=256$), cela ne pose pas de problème. En revanche, le temps de calcul devient prohibitif pour des applications réelles de la DFT. Pour reprendre un exemple vu plus haut, les sons audibles par une oreille humaine ont des fréquences s'étendant jusqu'à environ 20000 Hz. Pour analyser correctement un son, il faudra donc l'échantillonner à 40000 Hz. Mais $40000^2$, cela fait environ 2 milliards ... C'est faisable en quelques secondes, mais le problème c'est que les artefacts faisant du traitement du son ont besoin de faire des DFT en temps réel. Bref, c'est impossible avec notre algorithme naïf. Il faut trouver mieux."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "__Remarque__ : c'est quoi un artefact faisant du traitement du son ? En fait, de nos jours, cela englobe tout appareillage ayant quelque chose à voir avec le son : un ordinateur, un lecteur MP3, une télévision, une enceinte Bluetooth, un téléphone, une radio, etc. \n",
    "\n",
    "Seriez-vous prêts à attendre 5 secondes pour chaque note de musique lorsque vous écoutez un morceau :-) ?"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 2. La FFT (Fast Fourier Transform)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2.1 Genèse"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "L'algorithme que nous allons décrire permet le calcul de la DFT d'un vecteur de taille $n$ en $O(n\\log n)$ opérations. Pour reprendre l'exemple du son, une centaine de milliers d'opérations pour la FFT, au lieu de 2 milliards pour le DFT naïve. 10000 fois plus rapide. \n",
    "\n",
    "L'algorithme a été (re-)découvert au début des années 1960 par John Tukey, mathématicien à Princeton, et John Cooley qui travaillait pour IBM. Tukey n'était apparemment pas très enclin à publier son algorithme, ce fut Cooley qui prit l'initiative en 1965. Pourquoi Tukey ne voulait-il pas publier ? Entre autres, il pensait que ce n'était pas très intéressant et que d'autres gens avant lui avaient sûrement déjà eu la même idée. Sur le premier point il avait tort : la FFT est sans doute l'un des algorithmes les plus universellement utilisés de nos jours, dans tous les domaines des sciences. Sur le second point il avait parfaitement raison : des ingénieurs britanniques avaient déjà utilisé la FFT dans des calculs à la main à la fin des années 1930 :-).\n",
    "\n",
    "Ne jetons pas la pierre à Tukey, dans les années 1960 on pensait que les algorithmes n'étaient pas des objets vraiment dignes d'être étudiés ou admirés. On a changé d'avis depuis. quoique en fait, il reste des gens qui pensent encore comme dans les années 60 :-). \n",
    "\n",
    "__PS__ : Redde Gaussi quae sunt Gaussis : les idées de la FFT apparaissent déjà dans des travaux de Gauss datant de 1805. La FFT a d'ailleurs été appelée le 1001ème algorithme de Gauss :-)."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2.2 L'algorithme récursif"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "__HYPOTHÈSE__ : Dorénavant, tous les tableaux (vecteurs, matrices) que nous considérerons auront une taille qui est une puissance de 2. C'est restrictif et il y a des moyens de lever cette restriction, mais faisons simple pour commencer."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Le principe de l'algorithme est le suivant. Soit à effectuer la FFT d'un vecteur $X$. Si $X$ est de taille 1, c'est pas trop dur. Sinon :\n",
    "\n",
    "1. Scinder $X$ en deux moitiés $X_1$ et $X_2$, constituées respectivement des éléments de $X$ d'indices pairs et des éléments de $X$ d'indices impairs.\n",
    "2. Effectuer récursivement les FFT $Y_1$ et $Y_2$ de $X_1$ et $X_2$.\n",
    "3. __Combiner__ $Y_1$ et $Y_2$ pour obtenir la FFT $Y$ du vecteur $X$ original.\n",
    "\n",
    "Un algorithme qui divise les objets à traiter, qui traite les sous-objets puis les recombine, est appelé un algorithme __diviser pour régner__ (Divide and Conquer)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 37,
   "metadata": {},
   "outputs": [],
   "source": [
    "def FFT(X):\n",
    "    n = len(X)\n",
    "    if n == 1: return [X[0]]\n",
    "    else:\n",
    "        w = cmath.exp(-2 * 1j * cmath.pi / n)\n",
    "        X1, X2 = pairs(X), impairs(X)\n",
    "        Y1, Y2 = FFT(X1), FFT(X2)\n",
    "        return combiner(Y1, Y2, w)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Pour `pairs` et `ìmpairs`, aucun souci bien entendu."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 38,
   "metadata": {},
   "outputs": [],
   "source": [
    "def pairs(X): return [X[i] for i in range(len(X)) if i % 2 == 0]\n",
    "def impairs(X): return [X[i] for i in range(len(X)) if i % 2 == 1]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 39,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[6, 7]\n",
      "[1, 3]\n"
     ]
    }
   ],
   "source": [
    "print(pairs([6,1,7,4]))\n",
    "print(impairs([6,1,7,3]))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "__Remarque__ : le lecteur potentiellement traumatisé par les calculs qui suivent peut les sauter en première lecture (mais pas en deuxième)."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Évidemment, le génie de l'algorithme réside dans la fonction `combiner`. Un peu de maths vont être nécessaires. Avec les notations du paragraphe 2.3, on a pour $0\\le i\\le n - 1$, $(W_nX)_{i1}=\\sum_{k=0}^{n-1}(W_n)_{ik}X_{k1}$. Rappelons que $n$ est une puissance de 2 différente de 1, donc $n$ est pair. Séparons dans la somme les termes d'indices pairs et d'indices impairs :\n",
    "\n",
    "$(W_nX)_{i1}=\\sum_{k=0}^{n/2-1}(W_n)_{i,2k}X_{2k,1}+\\sum_{k=0}^{n/2-1}(W_n)_{i,2k+1}X_{2k+1,1}=\\sum_{k=0}^{n/2-1}(W_n)_{i,2k}X'_{k1}+\\sum_{k=0}^{n/2-1}(W_n)_{i,2k+1}X''_{k1}$ où $X'$ et $X''$ sont les sous vecteurs de $X$ formés des éléments d'indices pairs et impairs respectivement.\n",
    "\n",
    "Remarquons maintenant que $(W_n)_{i,2k}=\\omega_n^{-2ik}=(\\omega_n^2)^{-ik}=\\omega_{n/2}^{-ik}$. Comprenez bien cela, c'est la clé !!! Et $(W_n)_{i,2k+1}=\\omega_n^{-i(2k+1)}=\\omega_n\\omega_{n/2}^{-ik}$. Deux cas sont à considérer.\n",
    "\n",
    "- Si $i\\le\\frac n 2 -1$, alors $(W_n)_{i,2k}=(W_{n/2})_{ik}$ et $(W_n)_{i,2k+1}=\\omega_n^i(W_{n/2})_{ik}$. Ainsi, $(W_nX)_{i1}=\\sum_{k=0}^{n/2-1}(W_{n/2})_{ik}X'_{k1}+\\omega_n^i\\sum_{k=0}^{n/2-1}(W_{n/2})_{ik}X''_{k1}=Y'_{i1}+\\omega_n^i Y''_{i1}$ où $Y'$ et $Y''$ sont les DFT de $X'$ et $X''$.\n",
    "\n",
    "- Si $i\\ge\\frac n 2$, posons $i=\\frac n 2 + j$. On a alors $(W_n)_{n/2+j,2k}=(W_{n/2})_{jk}$ et $(W_n)_{n/2+j,2k+1}=-\\omega_n^j(W_{n/2})_{jk}$. On laisse le soin au lecteur (s'il en reste un) de vérifier ces deux dernières égalités. Ainsi, $(W_nX)_{i1}=\\sum_{k=0}^{n/2-1}(W_{n/2})_{jk}X'_{k1}-\\omega_n^j\\sum_{k=0}^{n/2-1}(W_{n/2})_{jk}X''_{k1}=Y'_{j1}-\\omega_n^j Y''_{j1}$.\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "__En bref__ : Si on sait faire une DFT pour un vecteur de taille $\\frac n 2$, alors on sait faire une DFT pour un vecteur de taille $n$. Comme on sait faire pour $n=1$, on sait faire pour tous les vecteurs dont la taille est une puissance de 2. "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "On en déduit la fonction `combiner`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 40,
   "metadata": {},
   "outputs": [],
   "source": [
    "def combiner(Y1, Y2, w):\n",
    "    assert len(Y1) == len(Y2)\n",
    "    n = len(Y1)\n",
    "    pw = [w ** i for i in range(n)]\n",
    "    U = [Y1[i] + pw[i] * Y2[i] for i in range(n)]\n",
    "    V = [Y1[j] - pw[j] * Y2[j] for j in range(n)]\n",
    "    return U + V"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Testons."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 41,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[(8.260064863037343+0j),\n",
       " (-0.8690971358095985-0.704128368038694j),\n",
       " (-1.0223206383011023+0.7668425492069055j),\n",
       " (-0.5208738294644828-0.33620582465306476j),\n",
       " (1.8836123521775374-1.1212233152083426j),\n",
       " (-1.1267426455169476+0.07062825567710429j),\n",
       " (-0.5342071517569493+0.37911341319391434j),\n",
       " (-0.32529211325672486-0.10045141385447398j),\n",
       " (0.025299111826452503+0j),\n",
       " (-0.3252921132567248+0.10045141385447398j),\n",
       " (-0.5342071517569494-0.37911341319391445j),\n",
       " (-1.1267426455169476-0.07062825567710423j),\n",
       " (1.8836123521775379+1.1212233152083426j),\n",
       " (-0.5208738294644828+0.3362058246530647j),\n",
       " (-1.0223206383011025-0.7668425492069054j),\n",
       " (-0.8690971358095985+0.704128368038694j)]"
      ]
     },
     "execution_count": 41,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "FFT([random.random() for k in range(16)])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Eu oui bon c'est un exemple débile, ça renvoie des nombres ... Allez, un exemple plus sérieux."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 42,
   "metadata": {},
   "outputs": [],
   "source": [
    "X = sample(lambda x: 2 * math.sin(2 * x) - 6 * math.cos(5 * x), 256)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 43,
   "metadata": {},
   "outputs": [],
   "source": [
    "Y1 = DFT(X)\n",
    "Y2 = FFT(X)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 44,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "plt.plot([abs(y) / 256 for y in Y2][:10])\n",
    "plt.grid()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 45,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j, 0j]\n"
     ]
    }
   ],
   "source": [
    "print([roundC(abs(Y2[i] - Y1[i]), 10) for i in range(len(Y1))])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "DFT et FFT donnent le même résultat, ce qui est préférable vu que la FFT est là pour calculer vite la DFT ..."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2.3 Optimisation de la FFT"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Telle que nous l'avons écrite, la FFT possède un gros avantage et un gros inconvénient :\n",
    "\n",
    "- Son code est très lisible, ce qui est un avantage pédagogiquement indéniable\n",
    "- La fonction fait beaucoup de recopies de données, ce qui est un inconvénient matériellement indéniable pour du code qui doit s'exécuter \"en temps réel\".\n",
    "\n",
    "On peut améliorer tout cela. Vous pouvez sauter ce paragraphe si vous n'êtes pas un adepte de l'algorithmique pure et dure. Acceptez simplement le code de la fonction `iterFFT` que nous allons écrire."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### 2.3.1 L'idée"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "LE point casse-pieds de la FFT est qu'il faut scinder les vecteurs en deux moitiés qui contiennent chacune un élément sur deux du vecteur. Ce serait quand même plus simple si on pouvait juste prendre la première (les éléments de petits indices) et la deuxième moitié (les éléments de grands indices). Peut-être que si on mélangeait de façon extrêmement astucieuse les éléments avant de faire notre FFT, on pourrait effectivement couper juste en deux au milieu ... regardons sur un exemple.\n",
    "\n",
    "Prenons un vecteur $X$ de taille 8. Voyons comment les différents appels successifs de la FFT découpent en petits morceaux le vecteur $X$.\n",
    "\n",
    "Les appels récursifs du premier \"niveau\" se font sur les sous-vecteurs $[x_0,x_2,x_4,x_6]$ et $[x_1,x_3,x_5,x_7]$. Retenons cela sous la forme$(1, <x_0,x_2,x_4,x_6, x_1,x_3,x_5,x_7>)$.\n",
    "\n",
    "Au second niveau, des mélanges se font sur chaque demi-tableau. Résultat : $(2, <x_0,x_4,x_2,x_6, x_1,x_5,x_3,x_7>)$.\n",
    "\n",
    "Le troisième \"niveau\" de récursivité scinde des vecteurs de taille 2 en 2 vecteurs de taille 1 mais ne change rien à l'ordre des objets. Résultat : $(3, <x_0,x_4,x_2,x_6, x_1,x_5,x_3,x_7>)$. \n",
    "\n",
    "Écrivons maintenant les indices $(0,4,2,6,1,5,3,7)$ en base 2 sur 3 bits ($2^3=8$). On obtient $(000,100,010,110,001,101,011,111)$. Bon, et alors ? Renversons les bits, écrivons ces nombres dans le mauvais sens : $(000,001,010,011,100,101,110,111)$. On a compris : \n",
    "\n",
    "__PRINCIPE : le bon sens c'est le mauvais sens.__\n",
    "\n",
    "Pour ne pas lasser le lecteur je ne prouverai pas que cette propriété est vraie dans le cas général. Cela se fait par récurrence forte sur $n$. Tout cela suggère un algorithme __itératif__ pour calculer la FFT d'un vecteur $X$ :\n",
    "\n",
    "1. Réordonner les éléments de $X$ en les classant par \"indices écrits à l'envers en base 2\" croissants.\n",
    "2. Combiner $[x_0]$ avec $[x_1]$, $[x_2]$ avec $[x_3]$, etc.\n",
    "3. Combiner $[x_0,x_1]$ avec $[x_2,x_3]$, $[x_4,x_5]$ avec $[x_6,x_7]$, etc. (enfin les nouveaux $x_i$, parce qu'ils ont changé après l'étape 1).\n",
    "4. Combiner les sous-vecteurs de tailles 4, puis 8, puis 16, etc.\n",
    "\n",
    "L'algorithme récursif `FFT` est la vision \"d'en haut\" de la FFT. Supposant effectuées les FFT pour les vecteurs de taille $\\frac n 2$ il les combine pour effectuer la FFT d'un vecteur de taille $n$. L'algorithme itératif, lui, est une vision \"d'en bas\" de la FFT. Il combine les vecteurs de taille 1, puis 2, puis 4, etc. Ceci n'est possible que grâce à la découverte de l'ordre magique des bits inversés.  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "On se lance."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### 2.3.2 Renverser les bits d'un entier"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "La fonction `revbits` renverse les bits de l'entier $n$, supposé être un entier sur $k$ bits, et renvoie l'entier $m$ correspondant."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 46,
   "metadata": {},
   "outputs": [],
   "source": [
    "def revbits(n, k):\n",
    "    m = 0\n",
    "    for i in range(k):\n",
    "        m = 2 * m + n % 2\n",
    "        n = n // 2\n",
    "    return m"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 47,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "14"
      ]
     },
     "execution_count": 47,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "revbits(7,4)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### 2.3.3 Le logarithme en base 2"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "La fonction `log2` prend en paramètre un entier $n$ supposé être une puissance de 2 ($n=2^p$) et renvoie l'entier $p$."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 48,
   "metadata": {},
   "outputs": [],
   "source": [
    "def log2(n):\n",
    "    p = 0\n",
    "    while n > 1:\n",
    "        p = p + 1\n",
    "        n = n // 2\n",
    "    return p"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 49,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "5"
      ]
     },
     "execution_count": 49,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "log2(32)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### 2.3.4 Le mélange"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "La fonction `shuffle` effectue le mélange du tableau $X$. Elle renvoie un tableau $X_1$ tout prêt pour une FFT 100% itérative."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 50,
   "metadata": {},
   "outputs": [],
   "source": [
    "def shuffle(X):\n",
    "    n = len(X)\n",
    "    k = log2(n)\n",
    "    X1 = n * [0]\n",
    "    for i in range(n):\n",
    "        X1[revbits(i, k)] = X[i]\n",
    "    return X1"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 51,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[0, 4, 2, 6, 1, 5, 3, 7]"
      ]
     },
     "execution_count": 51,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "shuffle(range(8))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### 2.3.4 La FFT, version itérative"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Et enfin, la fonction `iterFFT` qui fait exactement la même chose que FFT, avec une complexité du même ordre de grandeur, mais sans déplacements de données (à part la création du vecteur $X_1$). Je ne commenterai pas son code."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 52,
   "metadata": {},
   "outputs": [],
   "source": [
    "def iterFFT(X):\n",
    "    n = len(X)\n",
    "    X1 = shuffle(X)\n",
    "    m = 2\n",
    "    while m <= n:\n",
    "        omega = racine_unite(m)\n",
    "        for k in range(0, n, m):\n",
    "            w = 1\n",
    "            for j in range(m // 2):\n",
    "                t = w * X1[k + j + m // 2]\n",
    "                u = X1[k + j]\n",
    "                X1[k + j] = u + t\n",
    "                X1[k + j + m // 2] = u - t\n",
    "                w = w * omega\n",
    "        m = m * 2\n",
    "    return X1"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "On teste."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 53,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[(1.0658141036401503e-14+0j), (24+6.439293542825908e-15j), (-6.280369834735097e-16+3.0101141992542428e-15j), (-1.5707459609721926e-15-1.6876683304512844e-15j), (-16+8.43769498715119e-15j), (-6.825675412720678e-16-8.868850894896565e-17j), (6.280369834735107e-16+3.898292618954367e-15j), (-5.329070518200751e-15+1.1324274851176597e-14j), (-1.0658141036401503e-14+0j), (-1.7763568394002505e-15-9.547918011776346e-15j), (6.280369834735099e-16-3.898292618954368e-15j), (1.5707459609721926e-15-2.309134558199279e-15j), (-16-8.43769498715119e-15j), (2.4589243806723183e-15+5.327777187990283e-16j), (-6.280369834735109e-16-3.010114199254242e-15j), (24.000000000000007-4.6629367034256575e-15j)]\n"
     ]
    }
   ],
   "source": [
    "X = sample(lambda x: 3 * math.cos(x) - 2 * math.cos(4*x), 16)\n",
    "Y = FFT(X)\n",
    "print(Y)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 54,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAXQAAAD8CAYAAABn919SAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADl0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uIDIuMi4wLCBodHRwOi8vbWF0cGxvdGxpYi5vcmcvFvnyVgAAIABJREFUeJztnXtwY/d1378HIAmQAMFdLQFwtSvvLhnFtur4tWpqx55WiuNUddPI8R8Ze1JXHXu6/SNuncSdynY6HXcyybiu4yQzzsuJVGmmrhXHdseOrEpW5KVU2Y7tXVnvtR6AJO6ulgDI5RIP8uJ5+sfFD4TuEiQe9/G7F+czw1kSJIizl8AX5557zvcQM0MQBEHwPyGvAxAEQRDsQQRdEAQhIIigC4IgBAQRdEEQhIAggi4IghAQRNAFQRACggi6IAhCQNhX0InoOiI6TUTPEtEzRPTx9u2fIaKLRPR4++N9zocrCIIg9IL2GywiosMADjPzY0Q0C+AsgPcD+HUAZWb+vPNhCoIgCPsxsd8PMPMlAJfan5eI6ByAI8M82Pz8PB8/fnyYu6JSqSAWiw11X7fQPUbd4wP0j1H3+ACJ0Q50i+/s2bNrzJzc9weZue8PAMcBrABIAPgMgJcBPAngTgAH97v/yZMneVhOnz499H3dQvcYdY+PWf8YdY+PWWK0A93iA3CG+9DofUsuCiKKA3gYwO8z8zeIKA1gDQAD+D2YZZmP7HK/UwBOAUA6nT55zz339PV4VsrlMuLx+FD3dQvdY9Q9PkD/GHWPD5AY7UC3+G6++eazzHzjvj/Yj+oDmATwAIDf6fH94wCe3u/3SIbuLbrHx6x/jLrHxywx2oFu8aHPDL2fLhcCcAeAc8z8ha7bD3f92K8BeLr/9xtBEATBbva9KArgXQA+DOApInq8fdunAXyIiN4Ks+TyMoB/70iEgiAIQl/00+XyKADa5Vv32R+OIAiCMCwyKSoIghAQRNAFQRACwlgKeq3Rwt/8eAWtlqzfEwTBWfJFA59/4DlkC2XHH2ssBf30c3nc/vWncHZlw+tQBEEIOJlCBV88/SJWNw3HH2ssBf3SlW3zXxcOsCAI402+ZOpMKhF1/LHGUtBzpSoA81RIEATBSVRmnk5EHH+s8RT0tpDnRNAFQXCYXLGK2FQYs9FJxx9rLAU9XzQz9Fz7X0EQBKfIlQykXSi3AGMq6KvtzHxVMnRBEBwmXzSQcqHcAoypoKtSi9TQBUFwmtWiZOiOsVVroGQ0EA4RcsWqcosUBEGwHWZGrljFggi6M6i6+c+mZ7Fdb6JoNDyOSBCEoLK5XUet0XKlZREYS0E3yyxvOToHQMougiA4h7pO50bLIjDGgv5zbUGXThdBEJxC6YvU0B1CtSy++cgBANKLLgiCcyh9kRq6Q6wWDUxPhvEzqXjna0EQBCdQJd3krJRcHCFXNLAwF8X0VBiJ6ITU0AVBcIzVooEDM5OIToZdebyxE/R8sYpU+90ynYhKDV0QBMdws2URGENB727yTyeiyJUkQxcEwRnMKVERdEcwm/zNkgsApBIR5MRCVxAEh8gVq0i7VD8HxkzQi9sNVButTsllIRFFvlSVzUWCINhOs8UolKuutSwCYyboqrzSXXJptBiXt2pehiUIQgBZL1fRbDHScyLojrBjNK8EPfKa2wVBEOyiM1QkJRdnsDb5K2HPy4VRQRBsZmfsXzJ0R8i3V88pb2J1oKV1URAEu+kkkFJycYbVTQNz0ztN/mp6S8b/BUGwm3zRQIiAQ7Ep1x5zrAQ9VzRe0+Q/GQ5hPj4lgi4Igu3kilXMxyOYCLsns+Ml6KXqVaugZFpUEAQncHNTkWKsBD2/ywE2BV0ydEEQ7CUngu4czRYjX6peZTSfTkQkQxcEwXZ20xunGRtBX6+YTf5Wo5x0Ior1ShX1ZsujyARBCBrVRhOXKzXJ0J1CLbawGuWkE1EwA4WSZOmCINhDvrOpSDJ0R7BOiSrUAZc6uiAIdpEvuT9UBPQh6ER0HRGdJqJniegZIvp4+/ZriOhBInqh/e9B58MdHuXjYi25pGbVcJEIuiAI9uD2LlFFPxl6A8AnmPkGAO8A8JtEdAOATwJ4iJmvB/BQ+2ttyRWrIALm469t8ldTXHJhVBAEu+hVEXCafQWdmS8x82Ptz0sAzgE4AuBWAHe3f+xuAO93Kkg7yBeNXZv8r5mZwkSIJEMXBME2ciUDU+EQDs5Muvq4xNy/FzgRHQfwCIA3AVhh5gPt2wnAhvracp9TAE4BQDqdPnnPPfcMFWi5XEY8Hh/qvgDwhTMGNmuM//YL01d973eWt/DGa8L4d28e7QLGqDE6je7xAfrHqHt8gMRoB6PG95dPGnhho4XP/7MZW+K5+eabzzLzjfv+IDP39QEgDuAsgA+0v75i+f7Gfr/j5MmTPCynT58e+r7MzLf88SP80bt+tOv3bv3io/wbf/UPI/1+5tFjdBrd42PWP0bd42OWGO1g1Pg++Jc/4A/82ffsCYaZAZzhPnS6ry4XIpoE8HUAX2bmb7RvzhHR4fb3DwPID/ae4y577fZbkGlRQRBsJFcyXG9ZBPrrciEAdwA4x8xf6PrWtwDc1v78NgDftD88e6g2mliv1JCe3V3QzWlREXRBEOwhX3R39Zxioo+feReADwN4iogeb9/2aQCfBfBVIvoogFcA/LozIY6OGhrq9Y6ZSkRRNBrYrjUxPRV2MzRBEAJGudpAudrQU9CZ+VEA1OPb77E3HGfo9IT2MJpfSOz0oh+fj7kWlyAIwSPX2VSkYcklCOTVAe5ZcpHhIkEQ7CHnweo5xVgI+uo+75id8X/xcxEEYUTyHk2JAmMi6LliFZNhwjU9VkGp7pfcpmTogiCMhhfLoRVjIej5ooHUbBRmw87VJKITmJ4MS8lFEISRyRUNxKbCiEf66Tmxl7EQdHMVVO8LFERkti5KyUUQhBHJF6s9GzCcZiwEvZ9VUCkZLhIEwQZyRaNnA4bTjIWg99PkL7tFBUGwg/0qAk4SeEGvVBso9dHkv9CeFuUBzMoEQRC6YWbPpkSBMRD0fpv804kojHoLRaPhRliCIASQK1t11JotEXSnUFOi1k1FVlIyXCQIwojkPFo9pwi8oKvdfr2cFhULIuiCIIzIzqYiqaE7Qr8HeGdZtLQuCoIwHF5OiQJjIOi5YhUzfTT5y7JoQRBGRelHSjJ0Z8iVDCwkek+JKqanwkhEJ0TQBUEYmtWigYMzk4hMeGPDHXhBNzcV9fduuTAnveiCIAxPzsOWRWAMBH21jylRhTlcJDV0QRCGI1/qX2+cINCCzszIFav7tiwqUrPRjne6IAjCoOQ8nBIFAi7om9t11BqtfVsWFQtzEeRLVbRaMi0qCMJgNJotFEpScnGMzuq5Pt8x04koGi3GeqXmZFiCIASQ9UoNLfauZREIuKAPajQvrYuCIAyLl6vnFIEWdHWA+62h7wwXiaALgjAYXk+JAgEXdHWBMznbf9siINOigiAMjlqQIxm6Q6wWDRyYmUR0sr8m//l4BESSoQuCMDj5ooEQmTriFYEW9EFaFgFgMhzCoVikY+glCILQL7migeRsBOHQ3lPpThJoQTenRAc7/UknIp1amCAIQr+sejwlCgRc0HPFKtJ91s8VCzItKgjCEOSLRqdTzisCK+jNFqNQHvwdM5WIBrLkcmlzG/dla7JiT/AUZsafLb+IDaPldSi2kysaWJjzrn4OBFjQ18tVNFuM9NzgJZe1cg31ZrCecH975gK++nwdL61VvA5FGGMyhTI+d/9z+N7FYK16NOpNbGzVkZYM3Rk6U6JDlFwAIF8KVtklWyi3/xVBF7wj037+XaoE60yxoEHLIhBgQR90SlSRDugqOvVCyrSFXRC8QD3/LlWCdQbcmRIdsCJgN4EV9M6U6IAHWHmnB8l1kZklQxe0QD3/ViutQF3PGdQ3yikCK+iqyf9QbGqg+6kMPUiti/lSFZVaEwCQXZMMXfAOlVhsNRAoE7xORUD3GjoR3UlEeSJ6uuu2zxDRRSJ6vP3xPmfDHJxcsYr5eAQT4cHes66ZmcJkmDpjvEEgkzdfRMlp6pReBMFtmBmZQgXXXTMNYOd5GQTyRQNTEyEcmJn0NI5+1O4uALfscvsfMfNb2x/32RvW6AyyqaibUIiQmg3WKrpMu7PlbakwLldq2AhQZiT4h8uVGja363jvGxcAANkAdVypxRb77S52mn0FnZkfAXDZhVhsJTekoANmHT0foOGiTL6Mmakw3njI9LSRsovgBers8N3XH8JUKFgZ+mrR8LzcAgATI9z3Y0T0bwCcAfAJZt7Y7YeI6BSAUwCQTqexvLw81IOVy+WB7ntxvYLDk9tDPV6oaiC73hr4voPG6BZnnjeQjDIOkAGAcN//O4vSS96eGvZC12Oo0D0+QN8YHz5fBwCsZ59Bcprx4+dWsBzPexzV7gx6DF9e3cJ1iZDnx31YQf9zAL8HgNv//iGAj+z2g8z8JQBfAoAbb7yRb7rppqEecHl5Gf3et9poonT//XjbGxZx003XD/5YxWfw/GMX+n68YWJ0k//yw+/i7ScO4nWHrmAyvI3JQ9fhppve4HVYu6LrMVToHh+gb4zfv+8cpiZexgduuRl/+/wDyNUjWsYJDH4MS9+9Hz+39DrcdNMNzgXVB0N1uTBzjpmbzNwC8FcAft7esEZDlUsGcVrsJpWIoGQ0sFXz/zSbUW/i4pVtLCXjCIcIxw/FpBdd8IRMvozF+RjCIcLhWAjnL2+h2mh6HdbIlKsNVGpNz1sWgSEFnYgOd335awCe7vWzXqC8WFJDHmBVCwtCHf2ltQqYgcVkDID5b1YEXfCA7Fql8zxciIXQYmBlfcvjqEZnZ1OR9zX0ftoWvwLgBwBeT0QXiOijAD5HRE8R0ZMAbgbw2w7HORA7Tf7DHWA1jLQagE4XlY3vCHocr6xvBc6rRtCbWqOFlctbWJyPAwAOx8xukCCcLaohxGETSDvZt4bOzB/a5eY7HIjFNkZ9xwzSblE1mbc4H0fheWApGUejxTh/eQuLybjH0QnjwsrlCpotxlJqJ0MHEIi5iFxpsN3FThLISdFcycBUOISDQzb5q6UYQSi5ZAplHDkwjekps2VRZepBeCEJ/uHF/E5iAQDRCcJCIhqIDF1VBAZdpuMEgRT0fLGK1AhN/rORCUxPhgNRcskWduqWALDUfkFJHV1wEzX78JrnYioWCG+h1U0D8cgE4pFRusDtIZCCvro5/FARABARFub8Py2qTLmWukorczOTmI9PBeKFJPiHbKGC1GwEs9Gds+bF+TiyhbLvTbryJUOL+jkQUEHPlYyR61mpWf9Pi+aKpilXd1YEmC+kIJzqCv4hUyhf/TxMxlA0Glgr+9uKYtBl9E4SSEFXJZdRSCeinYsdfkWVVZYsFz+XUrFA+WgIemOeKVaufh4mg1H+G7UiYCeBE/RytYFytTHyAV6Yi2J10/D16aC1ZVGxOB8Xky7BNdbbplzWrqogXKBnZim5OElnc8iIBzg1G0G10UJx27/ToplCBTNT4atOB1XrmJh0CW6grtcsWRKLa+emEZ0M+TpD39iqo95kKbk4RW7I1XNWOqvofFx2UZN51m4f1Trm58xI8A+9Sn+hEOGEz6/n2KU3dhE4Qc+POCWqCMJu0Uy+fNWLCACOHpzGVDjk6xeS4B8yhTIiEyFce2D6qu8tJv19PWfVpoqAXQRO0IddDm1lweer6LZrTby6ud3JxruZCIdw7NCMtC4KrpAtVHCibcplZSkZ97VJV2fsXwMvdCCAgp4r2tPk31kW7dNVdFZTLiuLSXFdFNxht5ZFxVIyhhYDr/jUpGtnSlQydEewo2URAKKTYcxNT/q25KIueO5WclG3r4hJl+AwtUYL5ze293weAv5tXVwtGrgmNoXIRNjrUAAEUNBzNq6CSicivi25qHLKifleGfqOSZcgOIUy5eqVoavnp18v0OeLBlKzemTnQAAFfbW9rNUOzOEif5ZcrKZcVpYC0AMs6I8y5eqVocciE7426coVqx27bR0IlKAzM/LFKtI2HeB0Itq56OE3rKZcVhZ9fqor+ANV+ut1pgiYcxF+TSzsrAjYQaAE/cpWHbVmy9aSS75URavlr2nR3Uy5rMxNT2I+HvFtZiT4g0y+gnTitaZcVvxq0tVotrBWrmrTsggETNDtallULCSiaLYYaxV/lV2UKZd1Ms+KuY7On5mR4A+ya+VdW2e7WUrGUPKhSddauYYW6+GDrgiUoNs19q/w66KLHQ+X/V9IkqELTsHM5mLofRMLNbnsr+ei0htdxv6BgAm6XVOiCr9Oi/YatbaylIxjY6suJl2CI6xXaigajf2fhyl1PcdfZ4t2VwTsIFCCnrN5WevOblG/ZegVxKbC+56pqMxJTLoEJ+jss90nQz+ciCI66T8rirxmY/9AwAR9tWjg4MykbU3+yXgERPDdKjpzMi++7wo+lTll8v7KjAR/kOnzTFGZdPmt4ypXrCIcIhyKi6A7Qq5YtfX0ZyIcwnw84rvWxf1aFhVHD86YJl2SoQsOkG2bch3ZxZTLypIPTbpyRQPJeGRXjxqvCJSg50v2bw5JJyK+qqFv15q4eGV3Uy4r4RDh2KEZydAFR8i0TblCfQjeog9NuuwcYrSLQAm6uQrK3gOcno1i1Uc19JfaWY5aYrEfS8m41NAFR9hvFqIbP5p0mb5R+lwQBQIk6DtN/jZn6HP+mhZV4txPhg6YF6zEpEuwm2qjifMb232V/oDu6zn+SS7sWEZvN4ER9PWK2eRvu6DPRrFeqaHW8IfgZfIVEO09at3NUtuka0VMugQbWVnfQrPFfWfo6vnqlzq6UW/iylZdSi5O4dQqKPUHK5T9UXbJrpVx7VxvUy4rndZFn/UAC3qT6bNlURGLTODwXNQ3GXq+44MuGbojKJtb22voc/7aXJQplDuDGv3g1yk9QW/6nVbuZjEZQ8YnGbraNazTUBEQIEFXNrd217SU0Zcf6uimKVcFi32WW4Adky6/9QALepMtmKZcg2wOW0r6x6RLx7F/IECCni8aCBFsb/LfmRbVX9BXiwa2+jDlsmKuo/NHZiT4g0xhf1MuK4vzpkmXH8qbTlUERiUwgp4rGkjO2t/kf3BmCpNh8kXroqqD93shSqEyI0Gwg459c5+ts4odj379k4t8qYqpiRDmpnvbAntBYAR91eYpUUUoREjN+qN1MTtE3RIwe4A3tuq4LCZdgg0oU65BM3R17ccP13NyRbNlcT97DbfZV9CJ6E4iyhPR0123XUNEDxLRC+1/Dzob5v7ki/ZPiSrSiUjnIojO9GvKZcXvi3oFvVCdKoNcnAd2TLr8kKHnNJwSBfrL0O8CcIvltk8CeIiZrwfwUPtrT3HyAKcTUV84LvZrymVFWhcFO1G95INcnAfMs+HF+bhPMnT9pkSBPgSdmR8BcNly860A7m5/fjeA99sc10AY9SY2tuqO7fZLJ6LI+aBtsV9TLisdky4fvJAE/cnk+zflsuKHLVrMrN0uUcWwNfQ0M19qf74KIG1TPENRaLcs2rUc2ko6EUWp2kCl2nDk99uBMuUa9IIoYJp0HZ+fkU4XwRaya/2bcllZSsZxYWMLRl1fk65ytYGtWhMLc/qVXPpvEu0BMzMR9WwcJaJTAE4BQDqdxvLy8lCPUy6Xe973hQ3zj5976TkslzND/f69uHyxDgD4u79/BAux3u+Be8XoNCtF8xgY+VewvHxx15/ZK75ZGHh6peJZ/Aovj2E/6B4f4H2Mz6xs4XWJ0J4x9IqxWmigxcDX7n8YR2e969nY6xi+WjZtQNYvvITl5fMuRtUHzLzvB4DjAJ7u+vo5AIfbnx8G8Fw/v+fkyZM8LKdPn+75vXufeJWP3X4vn7u0OfTv34tHXyjwsdvv5R9k1vb8ub1idJpvPX6Rj91+Lz/7au9jsFd8n7v/HC996ttcazQdiK5/vDyG/aB7fMzexmjUG3zik/fyHz7w0z1/rleMT124wsduv5fve/JVB6Lrn72OodKD77+4tx7YCYAz3IfGDvsW+C0At7U/vw3AN0d4TxmZzm4/B2vogN7DRdnCYKZcVhbnxaRLGJ2V9S20ePDWWYV6/up8PcfuZfR20k/b4lcA/ADA64noAhF9FMBnAbyXiF4A8Evtrz0jXzQwNRHCgRlnmvz9MC2aKZRx5MA0opPDrd/r9AD7xBxJ0JN+1871Qpl06XxhNGfzMno72beGzswf6vGt99gcy9ColkWnmvzjkQnMTIW1bl3MrpWHzoqA7oXR+r6QBP1RF9ZPDNFtpVhKxrU26coVDcxGJhAbwKfGLQIxKbrqcAsRESGdiGq7LJqHMOWykoiKSZcwOsOYcllZTMaQzetr0pUrGkhpWG4BAiLo+WLVsZZFRTqh77LojinXgJN5VpbEpEsYkcwAa+d6sTgfQ6mqr0lXrmhgwWG9GZZACLobTf46T4t2TLlGyNAB80KWZOjCsHDblGuY4bZudq7n6Jlc5IpVLYeKgAAIernaQKXWdPyKsynohpangZ0LUTZk6GLSJQzLWtk05Ro5Q1feQhouL2+1GPmSoeXYPxAAQd/xJXY+Q682Wtjcrjv6OMOQbZtypWZHe1MTky5hFIZ1+7RyOBHF9GRYywx9Y6uGepO1bFkEAiDoeYd2iVrZaV3Ur+yi1s6N2uWzJOvohBHIdPz4Ryu5hEKEE/MxLTN09frXbVORwveCvrPbz/mSC6BnL/qoHS6KIwenMTXhD/tSQT+yhTKikyFcOze4KZcVXU261OtfSi4OsbrpTpO/ugiim6ArU65RT3OBtknXITHpEoYju1bB8UPDmXJZWUrGcV5Dky6dp0SBAAi6W03+KU2nRdVp6agXohSyjk4YFlX6s4PFZAzMwCvrellRqJJLSrpcnMG84uz8u2V0MowDM5Pa1dDVaemorWKKxWQMK5e3UG+2bPl9wnhQbTRx/vLWyK2zCl2v56wWDRyKTWFqQk/p1DOqAcg5tEt0N9KzUe0y9EyhPJIpl5WlpGnSpVtmJOjNK21TLjszdEC/jqt8Ud+WRSAAgr666dwuUSupREQ7Qc8WKiOZcllZlNZFYQg6LYsDLobuxczUBK6di2p3PSdX0nOXqMLXgs5sNvm7JegLGk6LjmrKZUVMuoRhsMOUy4qOk8u5YlXblkXA54K+sVV3tck/nYiiUK6i2dJjWlSZco3a99tNIjqJ5GxEbHSFgcgUylhIREcy5bKiWhd1mc6uN1tYK+u5HFrha0F3a0pUkU5E0Gwx1it6ZOnKlMvODB0wzZEkQxcGYdgF5XuxlIybJl0lPV5va+UqmPVtWQR8Lug7Q0VuCXq7F31TjyeYGo22M0MHzAtbunUXCPrCzLa4LFpRbxC61NE7iy00bVkEfC7oeZeb/HWbFrW7B12xOB/DFTHpEvpkrVxDyWg4kqED+rQuqte9rta5gM8FXb1jJkc0peqXjqCXNBH0QgXxyMTIplxWVOuZbhekBD3Jjrh2rhcLbZMuXSwAdsb+peTiCKtFA9fEphCZsKdlbz/m41MIkT4GXZm297Tdq/eW5vXKjAS9ydg83KbQzaQrVzQQDhEOxUTQHSFfdK9lEQAmwiHMxyPIbeqTodthymVFTLqEQbDTlMuKTtdzcsUqUrMRhG3wqnEKXwu6OSXq7rtlOhHVouSyVWvg4pVt209zAdOk68ShmDYvJEFvMoUyTszHbTHlsrI4H8OFjW0tTLpymk+JAj4XdKeXQ+9GOhHRouTy0po6zbVf0M3fq6d9qaAf2TX7WxYVS6k4mIGX171/LpqrLvUttwA+FvRGu8nf6eXQVtQqOq/pLBNIOfRCSsbxyuUt1Bpi0iX0pmPK5VRiMa88XXQQdPd8o4bFt4K+Vq550uSfTkRxuVJDteHtKWC2bcp1/JAzgr6YjKHZYqxcFpMuoTcdUy6HMvROL7rHk8tGvYnN7brWLYuAjwW9YzTvQckFgOfTa3abclkRky6hH+w25bKiTLq8nlzutCxKycUZVl3aJWol1Rku8lbQnZjM60a3KT1BT5xqWexGB5OuzpSolFycoTMlOufuO+aCBtOirRY74p3RjTLp8vqFJOiNMuVycmPYUjKGjMcmXX6YEgV8LOi5YtWTJn8dxv9Xiwa2601HM3RAvZBE0IXeZAoVxy7MKxaTcZQ9NunyqsQ7KL4V9NWigWTc/Sb/gzOTmAyTpyUXu9fO9WIxGfe8dinoi2nfXHasfq5QicuLHiYXuaKByEQIiWlndxePim8FPVc0XG9ZBAAiQmo22in5eIEahf4ZxzP0uJh0CT1RplxOdbgodtbReZdcqJZFu2027Ma3gp4vVj1r8l+Yi3YuynpBJl9GPDLhuCnZzoVRKbsIV6OeF04NtymUSZeXz8Nc0dB6U5HCt4Kec3H1nJW0x7tF1WSe09mCMumSC6PCbrhV+guFyPPJZXPsX++WRWBEQSeil4noKSJ6nIjO2BXUfhj1Jq5s1T3bHGKWXLyroWfyzrYsKpRJl7QuCruRcdCUy4p5PcebxIKZfTElCtiTod/MzG9l5htt+F19kfe4J3RhLopStYFKteH6Y2/VGnh103DEZdGKMumSDF3YjayDplxWlpLemXSVqg1s15tar55T+LLk4vbqOSvqD+tF2SXb8XBxPkM3HycmGbqwKxmbF5TvxWLSO5OunMu7i0dhVEFnAN8horNEdMqOgPrB7eXQVlQvqheti9k1d+qWisX5OFbEpEuwUG00cWFjy/ELogp1Rqr26LqJX6ZEAWDUpsp3M/NFIkoBeJCIfsrMj3T/QFvoTwFAOp3G8vLyUA9ULpc79/3+y3UAwAtPnsGln7rfRvRq2RS35R/+BNXzO4ewO0an+O6LNRCAlWfOYnXA//sw8dXWG2i2GF+7fxnXxp0/oXPjGI6C7vEB7sR4sdRCi4FaYQXLy68OfP9BY6w2zCnRh378FGKXnxv48QalO77vXTT15uVzT8BY0byowcy2fAD4DID/tNfPnDx5kofl9OnTnc9//9vP8s/+7n3carWG/n2jUDLqfOz2e/kvll98ze3dMTrFx/73Y/zu//7QUPcdJr7HVzb42O338v1PXxrqMQcwDvbBAAANy0lEQVTFjWM4CrrHx+xOjPc9+Sofu/1efurClaHuP0yM7/yDv+ffuucnQz3eoHTH96enX+Bjt9/LlWrdlcfeDQBnuA8dHvrthohiRDSrPgfwywCetuE9Zl9y7dVzXjX5xyMTiE2FvSm5uDCZ140OQx2CfqjS3wkXLs4rvFpHl9s0MBudwMyU3lOiwGg19DSAR4noCQA/AvBtZr7fnrD2ZnXT8PyKsxer6JQplxsti4rZ6CRSsxEZLhJeQyZfxuE5Z025rCzOm73o7LJJl19aFoERaujMnAXwFhtj6Zt8qYp/dG3Ci4fukEpEXB//V6Zcbl0QVZhDHSLowg4ZB9fO9UKZdOVL7gqsOcSof8si4MO2RWbulFy8ZCHh/vh/p2XRxQxdPZ7X9qWCPnDblMuL5yHgvhVFbtN7vekX3wl6udrAVs37Jn9zt2jVVZFTT2S3en8Vi8k4NrfFpEswKZSrKBkNV4bbuvFi6Uqrxa6fEYyC7wQ959GmIiupRBS1Rgub23XXHjNbcMeUy0rnwqhY6Qro9nBxN0NfSEQxMxV2tfx3eauGRos9MwIcFB8Kuh5N/sp5zc2yi5rMc7u7R9n0er2oV9CDzpmiS9PKilCIcGLeXZMuv2wqUvhQ0PXI0HfG/91rXcwWyq5nRQBw7QHTpEsydAEwM/ToZAiHPXgNmtdz3EssOsuhpeTiDDvLob2voQPu+bkoUy636+eAadJltoxJhi7szEK4YcplZTEZw8Ur7pl06VIR6BffCXq+WNWiyV/Vsd1qXfSqbqlYTIpJl2CScXhB+V4ok66XXDpbVAlbMi41dEfQoWURAKKTYRycmXSthq7KHW63iimWkmLSJZi7CC5sbHn4PHR3cjlXrGI+PoWpCX9IpT+i7MIUdD3eLVXrohtk8mUQAccOzbjyeFYWkzE0W4yVy5KljzOvrG+hxe65fVpRVgNu1dFzRQOpWe8TyH7xoaBXO/a1XpNKuLcsOrtWwdGD04hOhl15PCvKP0bKLuNNtjML4U2GPjM1gSMHpl27nqNTAtkPvhJ0s8nfQFqTFqKFRMS1kotba+d6IQujBWDn7++mKZeVxWTMtY4rP/m4AD4T9I2tGupNfZr804koCqUqmi1np0VbLcZLaxVXXRatKJMucV0cb7KFiuumXFYW52PI5MuOT2nXmy2sV0TQHWNVkx50RSoRRYuB9bKzdXRlyrWU8i4rAszTbGldHG8ya+66fe7GUiqOSq2JfMnZ112hVAWzPnrTD74S9M5yaE1KLupMwekLo+o018sMHdhpXRSTrvGEmZHNlz27IKroXM9xeHI5p8nMyyD4StB1mRJVqHFgp+voOy6LHr+QxKRrrCmUqyhV3TflsqLOVDMO19H9NlQE+E7QzQOsS5O/W9OimUIZsx6YcllZ8sDtTtAHtaDZbQ8XK26ZdOmWQPaDrwR9tWjgUEyfJv9DsSmEyPlp0Wx7Ms+rlXsKVTuVOvp4kl1rl/48rqETmSZdTicWuaKBcIhwKDbl6OPYiR7K2Cd5TaZEFRPhEObjEcdr6F4sE9iNaw9MIyImXWNLtlDB9GTYE1MuK25coM8Vq0jNRjzxrBkWXwm6jqugFuac3VykTLm8vhAFmCZdJ9otY8L4kSmUcWI+poXAuWHSpYvNyCD4StBXN/XrCU3NRh2toXu1dq4XS8m4ZOhjSrZQ8bx+rlhywaTLb1OigI8EvdFiLZv804mIo/2wnZZFTQR9MRkTk64xxKg3cX5jy/MOF4Ubk8uSoTtIscZaNvkvJKK4XKmh2nDm1C9bqHhqymVFTLrGk1fWt8AemnJZUb3oTk0uV5uMotHQTm/2wzeCfsUwh1l0OwVSf/C8QxdGM4Uyrjs445kplxVV+nkxL4I+TmQ8NuWyMj0VdtSka0dvRNAdYaOq5wFOtd9g8iVn6uhZD5cJ7IYyZVItbMJ4kO2U/vR5Ljq5dOVKVc8Ecj/8I+iavmPuDBfZn6ErUy5dsiLANOlKJ8Ska9zIFiq4di7q+aawblTrohNWFLomkPvhG0G/UmUtm/wX2n/w1U37M/RLbVMunbIiwKxfio3ueJHxaEH5XiwmY6jUmo4kU7omkPvhG0HfMFjLJv8DM5OYCoeQc6DkktXElMvKYjKGrJh0jQ3MrF3pD3B2cvlKtYXoZAiJqD5nJP3gG0G/Um1p+W5JREglIo5cFFUDPF7b5lpZapt0rYtJ11hQKJmmXDqV/gBnWxevGIx0Iuq53cag+EjQWdsLFOZuUQcy9LWKacqliRmZYtHlRb2Ct6gLj7pl6Mqky4kLo1eqrM2qy0HwmaDreYAXEs6M/2cLFSym4tplCWLSNV6ojibdMnQicmwd3UaVtdm7MAi+EHSj3kSlru8FCsdKLoUyljSZzOvmSNukSy6MjgeZvGnKtaDh629xPm67txAzmyUXTVZdDoIvBF13X+J0IopytYHthn0XCSvVBi5tGtp4Z3QTapt0ScllPMiumVuKdGtIAMyzhlc3t7Fds29Su2g0UGvpqzd7MZKgE9EtRPQcEb1IRJ+0KygrO5tD9HzHVJmLmi6zA2U6pIt3hpWlpLQujgs6tiwqFpMx2026OgnkOJVciCgM4E8B/AsANwD4EBHdYFdg3ei2HNqKmhZV02V2oJspl5XFZAznN7bFpCvgGPUmLmxsa5tYdC7Q2zi53BH0MSu5/DyAF5k5y8w1APcAuNWesF5LXnNBV3Ft2Cjo2UIFIY1MuawsJeNi0jUGKFMuHUt/gDMmXX7cJaoYpWv+CIDzXV9fAPBPRgtnd3JFA1MhaNvkr/7wf/NcDae/8LAtvzNXNHBUI1MuKyozuu3OH2Nmyr4YK1tbiD1mzzF0At3jA+yNcatdm9Y1Q1cmXXc8+hL+7olXbfmdG1t1ADtn3n7CcYUkolMATgFAOp3G8vLywL+jcbmOk0nGww/r+0L6V0uTuHClhjBt2/L75uaAtyQbQx2vXpTLZdt+X6PF+MXrJlCs2TtcFI+0bDuGTqB7fIC9Mc5FgDe8bgK55x7D2gv2XRS187n4z4828WShBcCm/3MM+McHGT/6/qO2/D5XYeahPgC8E8ADXV9/CsCn9rrPyZMneVhOnz499H3dQvcYdY+PWf8YdY+PWWK0A93iA3CG+9DlUWroPwZwPRGdIKIpAB8E8K0R318EQRCEIRm65MLMDSL6GIAHAIQB3MnMz9gWmSAIgjAQI9XQmfk+APfZFIsgCIIwAr6YFBUEQRD2RwRdEAQhIIigC4IgBAQRdEEQhIAggi4IghAQiF3cC0lEBQCvDHn3eQBrNobjBLrHqHt8gP4x6h4fIDHagW7xHWPm5H4/5KqgjwIRnWHmG72OYy90j1H3+AD9Y9Q9PkBitAPd4+uFlFwEQRACggi6IAhCQPCToH/J6wD6QPcYdY8P0D9G3eMDJEY70D2+XfFNDV0QBEHYGz9l6IIgCMIe+ELQ3VpGPQxEdB0RnSaiZ4noGSL6uNcx9YKIwkT0EyK61+tYrBDRASL6GhH9lIjOEdE7vY7JChH9dvtv/DQRfYWIPN9RRkR3ElGeiJ7uuu0aInqQiF5o/3tQs/j+R/vv/CQR/R8iOuBVfL1i7PreJ4iIiWjei9gGRXtBd3MZ9ZA0AHyCmW8A8A4Av6lZfN18HMA5r4PowZ8AuJ+Z3wDgLdAsTiI6AuA/AriRmd8E0zL6g95GBQC4C8Atlts+CeAhZr4ewEPtr73iLlwd34MA3sTMbwbwPMzlOF5yF66OEUR0HYBfBrDidkDDor2gw8Vl1MPAzJeY+bH25yWYQnTE26iuhoiOAviXAP7a61isENEcgH8K4A4AYOYaM1/xNqpdmQAwTUQTAGYA2LPEcgSY+REAly033wrg7vbndwN4v6tBdbFbfMz8HWZutL/8BwBHXQ/stfHsdgwB4I8A/GcAvrnQ6AdB320ZtXaCCQBEdBzA2wD80NtIduWPYT45W14HsgsnABQA/M92SeiviUirrcTMfBHA52Fma5cAbDLzd7yNqidpZr7U/nwVQNrLYPbhIwD+r9dBWCGiWwFcZOYnvI5lEPwg6L6AiOIAvg7gt5i56HU83RDRrwDIM/NZr2PpwQSAtwP4c2Z+G4AKvC0TXEW7Dn0rzDefawHEiOhfexvV/rT3UWqZYRLR78IsWX7Z61i6IaIZAJ8G8F+9jmVQ/CDoFwFc1/X10fZt2kBEkzDF/MvM/A2v49mFdwH4VSJ6GWbJ6heJ6H95G9JruADgAjOrM5uvwRR4nfglAC8xc4GZ6wC+AeAXPI6pFzkiOgwA7X/zHsdzFUT0bwH8CoDfYP16p5dgvnE/0X7NHAXwGBEteBpVH/hB0LVeRk1EBLP2e46Zv+B1PLvBzJ9i5qPMfBzm8fsuM2uTXTLzKoDzRPT69k3vAfCshyHtxgqAdxDRTPtv/h5oduG2i28BuK39+W0AvulhLFdBRLfALP/9KjNveR2PFWZ+iplTzHy8/Zq5AODt7eep1mgv6O2LJ2oZ9TkAX9VsGfW7AHwYZtb7ePvjfV4H5UP+A4AvE9GTAN4K4A88juc1tM8evgbgMQBPwXzteD5NSERfAfADAK8nogtE9FEAnwXwXiJ6AeaZxWc1i++LAGYBPNh+vfyFV/HtEaMvkUlRQRCEgKB9hi4IgiD0hwi6IAhCQBBBFwRBCAgi6IIgCAFBBF0QBCEgiKALgiAEBBF0QRCEgCCCLgiCEBD+PzapCcTgSyaGAAAAAElFTkSuQmCC\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "plotC(Y)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Sur ma machine, `iterFFT` est environ 20% plus rapide que `FFT`. Reprenez l'exemple ci-dessus avec $n=2^{20}$ au lieu de 16 (chez moi, 20 secondes pour `iterFFT` et 27 secondes pour `FFT`). C'est un souci connu : écrire une fonction de FFT vraiment efficace n'est pas facile.\n",
    "\n",
    "La bibliothèque C standard plus utilisée pour faire des FFT est la bibliothèque `fftw` (Fastest Fourier Transform in the West). Allez faire un tour sur le site `fftw.org`, juste pour voir. "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 3. Un exemple de la vraie vie : analyse d'un son"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Nous allons analyser un \"vrai\" son. Enfin presque vrai, parce que mon fournisseur d'accès à Internet ne me permet pas de mettre un vrai piano sur mon site Web. Le fichier `domisol.txt` contient un son d'une seconde échantillonné à 32768 Hz. Il s'agit du son émis par un piano jouant les notes do 3 (262 Hz), mi 3 (330 Hz) et sol 3 (392 Hz). Le concepteur de ce notebook a préparé le fichier son pour que tout se passe bien :\n",
    "\n",
    "- nombre d'échantillons puissance de 2\n",
    "- export du fichier (`.wav` à l'origine) vers un fichier `.txt` contenant les amplitudes du son tous les $\\frac{1}{32768}$ ièmes de seconde, plus facilement lisible avec Python.\n",
    "\n",
    "Le logiciel utilisé (__Audacity__ en l'occurrence) affirme que l'on peut sélectionner la durée du son au $\\frac 1 {1000}$ème de seconde près. Cela dit, quelques Hertz d'erreur sur nos résultats ne devront pas être pour nous une source d'angoisse. Enfin je ne crois pas. Si ? Vous pensez ? Aïe aïe aïe, j'abandooonne ..."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Lisons tout d'abord le fichier et stockons le tout dans un vecteur $X$."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 55,
   "metadata": {},
   "outputs": [],
   "source": [
    "f = open('domisol.txt', 'r')\n",
    "n = 32768\n",
    "X = n * [0]\n",
    "for k in range(n):\n",
    "    X[k] = float(f.readline().split()[0])\n",
    "f.close()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Voici la \"forme\" du son. Évidemment on ne voit pas bien les notes. C'est un mi dièse ou un fa bémol :-) ?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 56,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "plt.plot(X)\n",
    "plt.grid()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "La FFT est notre amie ... Effectuons une FFT du vecteur $X$. Le résultat est un vecteur $Y$ contenant, pour chaque fréquence, le \"poids\" de cette fréquence dans le son."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 57,
   "metadata": {},
   "outputs": [],
   "source": [
    "Y = iterFFT(X)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Vous avez remarqué que la FFT mérite bien son nom de __Fast__ ? Zéro seconde. Avec une DFT naïve on serait encore en train d'attendre. Traçons entre 0 et 1000 Hz (rien ne vous empêche de tracer ailleurs !)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 58,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "plotC(Y[0:1000])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Voici la liste des fréquences les plus représentatives du son, triées par intensité décroissante ..."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 59,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[(262, 2916.6391049022072), (393, 2471.791246526954), (392, 2363.5557333635556), (524, 2305.003653720651), (261, 1831.7463861572046), (523, 1180.958758921733), (330, 1050.4038149424534), (394, 920.7420801005296), (391, 695.1140198624739), (525, 669.7544406316083), (263, 642.7013517123024), (395, 629.7453925690188), (990, 617.8285819000874), (331, 523.5814363603519), (522, 514.9222451901234)]\n"
     ]
    }
   ],
   "source": [
    "U = [(i, abs(Y[i])) for i in range(16384) if abs(Y[i])> 500]\n",
    "U.sort(key=lambda x: x[1], reverse=True)\n",
    "print(U[:100])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Discutons des pics par ordre décroissant de leur hauteur, en laissant tomber les pics \"fantômes\" (392 et 393 Hz, par exemple) ... de nombreuses raisons à ces fantômes (serait-ce ma faute ? Dois-je refaire tous mes calculs ??), l'une d'entre elles étant que les fréquences du signal n'ont aucune raison d'être des entiers. Une autre raison ? Faites un zoom entre 250 Hz et 280 Hz et vous comprendrez. Les pics sont pointus, mais pas archi-pointus.\n",
    "\n",
    "- Le pic le plus élevé se situe à environ 262 Hz. Il s'agit de la note DO 3.\n",
    "- Le second pic se situe à environ 392 Hz. C'est un SOL 3.\n",
    "- Nous trouvons ensuite un pic à environ 524 = 2 x 262 Hz. Il s'agit d'un DO 4, une octave au-dessus du premier DO : c'est une _harmonique_.\n",
    "- Où est le MI 3 ? Cherchez la fréquence 330 Hz. Et regardez aussi ses multiples, 660 (MI 4), 990 (MI 5) !"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 4. What Else ?"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ci-dessous quelques suggestions et compléments pour une étude plus approfondie."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 4.1 Le problème de l'arrondi"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Les calculs de FFT se font en flottants. Pour certaines applications c'est inacceptable. Il est possible, au lieu de prendre une racine $n$ième de l'unité dans $\\mathbb C$, d'en prendre une dans un __corps fini__ voire un __anneau fini__ judicieusement choisi. L'étude de cette possibilité dépasse totalement le cadre du notebook."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 4.2 Le problème de la puissance de 2"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Comment faire si on veut effectuer une FFT sur un vecteur dont la taille n'est pas une puissance de 2 ? Le nombre de méthodes est au moins égal à 2, tout dépend de ce que l'on veut faire du résultat.\n",
    "\n",
    "1. Compléter le vecteur avec des zéros pour lui donner une taille qui est une puissance de 2. Pour certaines applications cela convient. En revanche, en analyse du signal ce n'est pas une bonne idée : cela introduit des signaux factices de haute fréquence à l'intérieur du signal à analyser. Il vaut mieux dans ce cas utiliser la méthode 2.\n",
    "\n",
    "2. On ajoute des cases au tableau pour lui donner une taille \"puissance de 2\", mais on remplit ces cases par une technique de miroir. Si $X$ est de taille $n$ et $n'$ est la plus petite puissance de 2 supérieure à $n$, on pose $x_{n+j}=x_{n-j-2}$ pour $j=0,1, \\ldots,n'-n-1$."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 4.3 Produit rapide de polynômes "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Soient $P=\\sum_{k=0}^{n-1}a_k X^k$ et $Q=\\sum_{k=0}^{n-1}b_k X^k$. Comment calculer très très très efficacement les coefficients de $PQ$ ?\n",
    "\n",
    "1. On échantillonne $P$ et $Q$ à la fréquence $2n$. On multiplie les vecteurs obtenus, ce qui nous donne un échantillonnage $X$ de $PQ$. Tout cela se fait en temps $O(n)$. \n",
    "\n",
    "2. On effectue une FFT de $X$. Cela prend un temps en $O(n\\log n)$. Cela nous donne les coefficients de $PQ$.\n",
    "\n",
    "On dispose ainsi d'un algorithme permettant de multiplier $P$ et $Q$ en temps $O(n\\log n)$. À peine plus que pour une addition."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "__Remarque__ : nous avons justement ici un exemple de problème où des erreurs d'arrondis sont inacceptables. Imaginez des polynômes à coefficients entiers, avec une réponse à coefficients complexes à 16 chiffres après la virgule, pas tous exacts ... c'est là que les idées du paragraphe 4.1 peuvent être mises à profit. "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 4.4 Produit rapide d'entiers"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Un entier cela ressemble beaucoup à un polynôme : pensez à l'écriture en base $p$, ce n'est finalement qu'un polynôme en $p$. L'algorithme de produit des polynômes peut être adapté pour effectuer des produits d'entiers. Il s'agit de l'algorithme de Schönhage et Strassen. Sa complexité, pour multiplier deux entiers sur $n$ bits, est en $O(n\\log n\\log\\log n)$. Au risque de me répéter, à peine plus que pour les ajouter.\n",
    "\n",
    "Attention, cette adaptation est tout sauf triviale. À l'inverse du produit de polynômes, quand on multiplie des entiers il y a des retenues :-)."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 4.5 Parallélisme"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Vous avez peut être remarqué que $O(n\\log n)$ c'est super, mais que ce n'est pas comme cela que l'on peut vraiment espérer faire de la FFT en temps réel.\n",
    "\n",
    "L'algorithme de la FFT se prête bien à la __parallélisation__. Lorsqu'on coupe un vecteur en deux moitiés, les FFT sur les deux moitiés peuvent être effectuées simultanément si l'on dispose d'un système adapté. La recombinaison des deux moitiés peut également être faite en parallèle en temps constant. Il existe ainsi des circuits numériques entièrement dédiés à des calculs de FFT. Quel est le temps de calcul d'une FFT pour un tel circuit ? Appelons $T(n)$ le temps nécessaire au calcul de la FFT d'un vecteur de taille $n$. Dans notre algorithme Python, on a $T(n)=2T(\\frac n 2) + O(n)$. Mais si on dispose d'un circuit (ordinateur, machine, processeur, etc.) effectuant les calculs en parallèle, alors $T(n)=T(\\frac n 2) + O(1)$. Magie du parallélisme ! Ceci nous donne (admis) :\n",
    "\n",
    "$$T(n)=O(\\log n)$$\n",
    "\n",
    "C'est à dire, à toutes fins utiles, un temps quasi-constant. Nous y sommes, __le temps réel c'est l'instantanéité__."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "__Exercice__ : Prouver l'affirmation ci-dessus lorsque $n$ est une puissance de 2. "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 4.6, 4.7, ... , 4.$\\infty$ ?"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "La liste des applications de la FFT est encore longue. Après en avoir examiné zéro pourcent, arrêtons nous :-)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "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.6.4"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
