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; }