La FFT, ou transformée de Fourier rapide, est l'un des quelques algorithmes dont la publication a provoqué une véritable révolution dans le champ technique. Généralement associé aux noms de J.W. Cooley et J.W. Tuckey qui l'ont publié en 1965, cet algorithme de calcul de la transformée de Fourier discrète avait été maintes fois << redécouvert >> depuis Gauss, notamment par Danielson et Lanczos en 1942. La FFT permet de ramener le calcul de la transformée de Fourier discrète de N2 à opérations ; cette réduction de complexité suffit à faire passer d'impossibles à facilement résolubles nombre de problèmes.
La transformée de Fourier discrète d'un n-uplet de nombres complexes
est le n-uplet
défini par
Ainsi, pour calculer la transformée de Fourier sur n points, il suffit de calculer deux transformées de Fourier sur n/2 points, de faire nmultiplications et n additions. Si n est une puissance de 2, cette décomposition peut s'appliquer récursivement, ce qui donne pour équation de complexité : . Il en résulte une complexité en opérations.
Il existe aussi une FFT sur l'anneau
Z/NZ des
entiers modulo N, quand
N = 2tn/2 +1, t est un entier
quelconque ; on pose
,
qui est une racine n-ème
principale de l'unité dans
Z/NZ ; on a toujours
n=2h. Par exemple, quand t=2 et n=23=8, on a N=257, et
; un n-uplet et sa transformée sont :
Pour traiter de façon identique le cas complexe et le cas modulo N, on utilise l'interface suivante, la première méthode servant à injecter un entier dans l'anneau (entier(0) est le zéro et entier(1) est l'unité de l'anneau) :
interface Nombre { Nombre entier(int i); Nombre add(Nombre y); Nombre sub(Nombre y); Nombre mult(Nombre y); }
On supposera que l'on dispose d'implémentations Complexe et EntierModulo de cette interface.
La définition récursive de la FFT résulte directement de cette
décomposition. La figure 2.29 représente l'arbre des
invocations de ce calcul récursif
pour n=8 points : aux feuilles, la
FFT sur 1 point, qui est l'identité. L'ordre dans lequel les ap sont
rangés dans les feuilles, de gauche
à droite, est l'inverse de la
représentation binaire de p. La fonction miroir(h,p) calcule
cet inverse binaire de p, pour
; par exemple,
miroir(3,3) =miroir(3, 0112) =
1102 = 6 ; on utilise les opérateurs binaires <<
,
>>
, &
, |
et ^
, plus efficaces pour opérer au
niveau des bits d'un entier ; pour h fixé, cette fonction est une
involution : miroir(h, miroir(h, n)) = n.
static int miroir(int h, int n) { int r; // Résultat for (r = 0; h > 0; h--) { // A chaque itération le bit de droite de n est // est tranféré dans r comme bit de droite r <<= 1; // Un nouveau bit à droite r |= (n & 1); // Positionnement de ce bit n >>= 1; // Effacement du bit de droite de n } return r; }
On va donc commencer par permuter le n-uplet a pour ranger ses éléments dans cet ordre, ce qui va permettra de construire un algorithme itératif, qui parcourt l'arbre des feuilles vers la racine :
static void permuteTableau(Object[] a) { int h = log2(a.length); // On suppose a.length = 2^h int i,j; for (i = 0; i < a.length; i++) { if ((j = miroir(h, i)) < i) { Object tmp = a[i]; a[i] = a[j]; a[j] = tmp; } } }
En utilisant la périodicité de la transformée de Fourier, et l'égalité
,
on peut réécrire la dernière expression de :
static void butterfly(Nombre[] a, int i, int j, Nombre alpha) { Nombre u = a[i]; Nombre v = alpha.mult(a[j]); a[i] = u.add(v); a[j] = u.sub(v); }
Voici enfin la fonction de calcul de la FFT ; le paramètre a est un tableau dont la longueur n doit être une puissance de 2, et racine est une racine n-ième de l'unité :
public static Nombre[] fft(Nombre[] a, Nombre racine) { int h = log2(a.length); int i, j, l; int pas = 1; // Calcul des racine^{2^l} pour 0 <= l < h Nombre[] puissancesRacine = new Nombre[h]; puissancesRacine[0] = racine; for(l = 1; l< h; l++) puissancesRacine[l] = puissancesRacine[l-1].mult(puissancesRacine[l-1]); // Permutation du tableau permuteTableau(a); // Itération for(l = h-1, pas = 1; l >=0; l--, pas *= 2) { Nombre alpha = racine.Entier(1); // Unité for (i = 0; i < pas; i++) { for (j = i; j < a.length; j += 2*pas) { butterfly(a, j, j+pas, alpha); } alpha = alpha.mult(puissancesRacine[l]); } } return a; }