Table des matières PDFPython

Transformée de Fourier rapide

1. Introduction

La transformée de Fourier discrète (TFD) permet de calculer le spectre en fréquence d'un signal numérique comportant N échantillons. La TFD de N nombres uk (réels ou complexes) est constituée des N nombres complexes définis par :

FN,n=k=0N-1ukexp-j2πnkN(0nN-1)(1)

La TFD à N termes est en fait un signal discret de période N car :

FN,n+N=FN,n(2)

Le calcul direct des N termes comporte le calcul de N sommes à N termes, soit un nombre d'opérations proportionnel à N2.

L'algorithme de transformée de Fourier rapide (Fast Fourier Transform ou FFT) repose sur la décomposition suivante, consistant à séparer les termes de rang pair et les termes de rang impair de la somme (on suppose que N=2p) :

FN,n=k=0N2-1u2kexp-j2πn2kN+exp-j2πnNk=0N2-1u2k+1exp-j2πn2kN(3)

Les deux sommes sont des TFD à N/2 termes, qui sont calculées récursivement de la même manière.

2. Regroupement récursif pair-impair

Soit une liste de N nombres. On suppose que N=2pp est un entier.

Par exemple pour p=3, on a la liste :

[L[0],L[1],L[2],L[3],L[4],L[5],L[6],L[7]](4)

On regroupe les termes d'indice pair et ceux d'indice impair :

[L[0],L[2],L[4],L[6]][L[1],L[3],L[5],L[7]](5)

Les deux sous-listes comportant chacune N/2=4 éléments subissent aussi un regroupement des termes d'indice pair et des termes d'indice impair, ce qui conduit à :

[L[0],L[4]][L[2],L[6]][L[1],L[5]][L[3],L[7]](6)

On obtient ainsi une liste comportant les mêmes nombres que la liste initiale, mais dans un ordre différent.

Écrire une fonction separation(liste,liste_finale) qui effectue cette séparation pair-impair de manière récursive et remplit liste_finale avec les éléments dans l'ordre final.

Définir une liste comportant les N=2p nombres 0,1,2...N-1 puis tester la fonction avec cette liste.

Exprimer le temps d'exécution en fonction de N.

3. Représentation binaire et inversion des bits

La représentation binaire (en base 2) d'un nombre entier x codé sur p bits est constituée de p bits b0,b1,...bp-1 (valant chacun 0 ou 1) tels que :

x=b020+b121+bp-12p-1(7)

b0 est le bits de poids faible, bp-1 est le bit de poids fort.

On convient de stocker les bits dans une liste, le premier étant le bit de poids faible. Pour obtenir la représentation binaire à p bits d'un nombre entier, on peut procéder de manière récursive. Le bit de poids faible d'un nombre est le reste de sa division entière par 2. Pour enlever le bit de poids faible, il suffit de diviser le nombre par 2. Le reste de la division par 2 du nombre résultant est le deuxième bit b1. On procède ainsi récursivement jusqu'au bit bp-1.

Écrire une fonction bits(nombre,liste_bits,p) qui calcule récursivement les p bits d'un nombre entier strictement inférieur à 2p.

Écrire une fonction nombre(liste_bits) qui calcule un nombre entier à partir de sa représentation binaire.

Il peut être utile d'inverser l'ordre des bits d'un nombre. Par exemple, le nombre à 4 bits (b0,b1,b2,b3) devient après inversion (b3,b2,b1,b0).

Écrire une fonction inversion_bits(nombre,p) qui effectue l'inversion des bits d'un nombre entier et renvoie le nombre en décimal.

Soit une liste comportant N=2p éléments. Effectuons l'inversion des bits des indices de cette liste et rangeons les éléments par ordre croissant de ces indices à bits inversés.

Écrire une fonction ranger_ordre_bits_inverse(liste,p) qui range les éléments d'une liste en inversant les bits des indices. La nouvelle liste est renvoyée.

Tester cette fonction sur la liste 0,1,2,...2p-1 et comparer au résultat de la fonction separation(liste,liste_finale). Expliquer pourquoi ces deux fonctions conduisent au même résultat.

4. Transformée de Fourier rapide

L'algorithme de transformée de Fourier rapide est un algorithme de type diviser pour régner reposant sur la décomposition suivante

FN,n=k=0N2-1u2kexp-j2πn2kN+exp-j2πnNk=0N2-1u2k+1exp-j2πn2kN(8)=PN/2,n+exp-j2πnNIN/2,n(9)

PN/2,n regroupe les termes de rang pair et IN/2,n regroupe les termes de rang impair. Pour n variant de 0 à N/2-1, PN/2,n est en fait une TFD à N/2 termes. Il en est de même pour IN/2,n.

La relation (9) doit être appliquée pour n variant de 0 à N-1. Afin d'accéder aux valeurs des TFD à N/2 termes pour n>N/2-1, on fait appel à la propriété de périodicité :

PN/2,n+N/2=PN/2,n(10)IN/2,n+N/2=IN/2,n(11)

Voyons par exemple comment la TFD à N=8 termes est calculée à partir des deux TFD à 4 termes :

F8,0=P4,0+exp-j2π08I4,0F8,1=P4,1+exp-j2π18I4,1F8,2=P4,2+exp-j2π28I4,2F8,3=P4,3+exp-j2π38I4,3F8,4=P4,0+exp-j2π48I4,0F8,5=P4,1+exp-j2π58I4,1F8,6=P4,2+exp-j2π68I4,2F8,7=P4,3+exp-j2π78I4,3

Les N nombres FN,n sont donc calculés à partir des deux TFD à N/2 termes. Si N est une puissance de 2, soit N=2p, la procédure est répétée récursivement jusqu'aux TFD à N=1 terme. La TFD à un terme est simplement l'identité :

F1,0=u0(12)

Autrement dit, la TFD d'un seul nombre est ce nombre lui même.

Écrire une fonction fft(liste) qui fait ce calcul par des appels récursifs et renvoie la liste contenant la TFD.

Pour tester la fonction fft, utiliser le script suivant, qui consiste à calculer la TFD d'une fonction périodique échantillonnée exactement sur une période :

import numpy
p = 5
N = 2**p
u = numpy.zeros(N,dtype=complex)
k = numpy.arange(N)
u = numpy.sin(2*numpy.pi*k/N)+0.5*numpy.sin(4*numpy.pi*k/N)+0.25*numpy.cos(10*numpy.pi*k/N)
tfd = fft(u)
         
from matplotlib.pyplot import *
spectre = numpy.absolute(tfd)*2/N
figure(figsize=(10,4))
stem(k,spectre,'r')

show()
               

On doit retrouver dans le spectre les amplitudes des harmoniques définies dans le signal.

Exprimer le temps d'exécution en fonction de N. Comparer à un calcul direct des sommes.

5. Solution

Voici la fonction de regroupement récursif pair-impair :

def separation(liste,liste_finale):
    n = len(liste)
    pair = []
    impair = []
    for k in range(0,n,2):
        pair.append(liste[k])
    for k in range(1,n,2):
        impair.append(liste[k])
    if len(pair)>2:
        separation(pair,liste_finale)
    else:
        liste_finale.append(pair[0])
    if len(impair)>2:
        separation(impair,liste_finale)
    else:
        liste_finale.append(impair[0])
            
p=4
N=2**p
liste=[]
for i in range(N):
    liste.append(i)
liste_finale = []
separation(liste,liste_finale)
            
print(liste_finale)
--> [0, 4, 2, 6, 1, 5, 3, 7]

Voici la fonction fournissant les bits d'un nombre entier :

def bits(nombre,liste_bits,p):
    liste_bits.append(nombre%2)
    if p >1:
        bits(int(nombre/2),liste_bits,p-1)
            
liste_bits=[]
bits(10,liste_bits,p)
            
print(liste_bits)
--> [0, 1, 0, 1]

Voici la fonction calculant un nombre en décimal à partir de la liste de ses bits :

def nombre(liste_bits):
    x = 0
    u=1
    for i in range(len(liste_bits)):
        x += liste_bits[i]*u
        u *= 2
    return x
            
print(nombre(liste_bits))
--> 10

Voici la fonction d'inversion des bits d'un nombre :

def inversion_bits(nombre,p):
    liste_bits=[]
    bits(nombre,liste_bits,p)
    x=0
    n=len(liste_bits)
    u = 1
    for i in range(n):
        x += liste_bits[n-1-i]*u
        u *= 2
    return x
            
print(inversion_bits(6,p))
--> 6

Voici la fonction permettant de ranger les éléments d'une liste par ordre croissant des indices à bits inversés :

def ranger_ordre_bits_inverse(liste,p):
    liste_inverse = liste.copy()
    for i in range(len(liste)):
        liste_inverse[inversion_bits(i,p)] = liste[i]
    return liste_inverse
            
print(ranger_ordre_bits_inverse(liste,p))
--> [0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15]

Voici l'implémentation récursif de l'algorithme de transformée de Fourier rapide :

def fft(liste):
    N = len(liste)
    if N==1:
        return liste
    pair = []
    impair = []
    for k in range(0,N,2):
        pair.append(liste[k])
    for k in range(1,N,2):
        impair.append(liste[k])
    tfd_pair = fft(pair)
    tfd_impair = fft(impair)
    tfd = [0]*N
    W = numpy.exp(-1j*2*numpy.pi/N)
    N2 = int(N/2)
    for n in range(N2):
        tfd[n] = tfd_pair[n]+tfd_impair[n]*W**n
    for n in range(N2,N):
        tfd[n] = tfd_pair[n-N2]+tfd_impair[n-N2]*W**n
    return tfd
            

On teste avec un polynôme trigonométrique :

import numpy
from matplotlib.pyplot import *

p = 5
N = 2**p
u = numpy.zeros(N,dtype=complex)
k = numpy.arange(N)
u = numpy.sin(2*numpy.pi*k/N)+0.5*numpy.sin(4*numpy.pi*k/N)+0.25*numpy.cos(10*numpy.pi*k/N)
tfd = fft(u)
         
from matplotlib.pyplot import *
spectre = numpy.absolute(tfd)*2/N
figure(figsize=(10,4))
stem(k,spectre,'r')
            
figA.svgFigure pleine page
Creative Commons LicenseTextes et figures sont mis à disposition sous contrat Creative Commons.