1.2 Cooley-Tukey 型 FFT

1.2.3 混合基数アルゴリズム

 これまでに示した FFT は,DFT の添字を偶数と奇数に分けることで, 半分の長さの DFT に分解するという方法に基づいています.当然これは 拡張することができ,例えば,R で割り切れる長さの DFT は,1/R の長さの R 個の DFT に分解することができます.一般に基数 R の分解は

             N/R-1   R-1                    jk   
    A     =   Σ    ( Σ   a           )     W     
     Rk      j=0     r=0  Nr/R+j            N/R  


             N/R-1   R-1           r     j  jk   
    A     =   Σ    ( Σ   a       W   ) W   W     
     Rk+1    j=0     r=0  Nr/R+j  R     N   N/R  


             N/R-1   R-1          2r    2j  jk   
    A     =   Σ    ( Σ   a       W   ) W   W     
     Rk+2    j=0     r=0  Nr/R+j  R     N   N/R  

    ・・・

のようにします.一般には

             N/R-1   R-1          mr    mj  jk   
    A     =   Σ    ( Σ   a       W   ) W   W     
     Rk+m    j=0     r=0  Nr/R+j  R     N   N/R  

となります.この分解を繰り返すことで,基数 R の FFT が構成されます. この分解の式をよく見ると,長さ N の一次元データ a_j は,R × N/R の 二次元データ a_r,j = a_(N/R)r+j に変換されています.この変換により, 長さ N の DFT は,二重にネストされた長さ N/R の DFT と,長さ R の DFT に分解されます.途中の係数 W_N^mj は,バタフライ演算での 回転因子で,Cooley-Tukey 型 FFT 特有のものです.

 次に,この分解による FFT の演算量について考えてみます.長さ N を R の整数乗と仮定して,この分解を log_R(N) 回繰り返すことを考えます. このときの複素乗算回数は

     N   log N    
    --- ------- ((R-1) + (長さ R の DFT の乗算回数))    
     R   log R    

となることがわかります.これは,基数 R の 1 バタフライルーチンの 乗算回数です.もし,長さ R の DFT の計算に R^2 の乗算回数が必要であると 仮定しても,全体の乗算回数は N*log(N) 回のオーダーしか必要としません. また,この仮定のもとで上の乗算回数の式は,R = e (自然対数の底) で最小と なり,e = 2.718... に近い R = 3 が最適な基数ということになります. これは,Cooley and Tukey による最初の論文でも示されています.しかし 実際には,長さが 2 または 4 の DFT は,±1 または ±i の乗算のみで 実行でき,この乗算は省略できます.したがって,基数 4 の算法は, 基数 2 のに比べて乗算回数が 3/4 に減少します.さらに,基数 8 では 2/3 に,基数 16 では 5/8 に減少し,基数を大きくすると乗算回数は減少する 傾向にあります.一方,実数の加算回数は,基数が 4 のときに最小に なります.例として,基数 4 のルーチンを以下に示します.

リスト1.2.3-1. 基数 4 の FFT
#include <math.h>

void fft(int n, double ar[], double ai[])
{
    int m, mq, i, j, j1, j2, j3, k;
    double theta, w1r, w1i, w2r, w2i, w3r, w3i;
    double x0r, x0i, x1r, x1i, x2r, x2i, x3r, x3i;
    
    /* ---- radix 4 butterflies ---- */
    theta = -8 * atan(1.0) / n;
    for (m = n; (mq = m >> 2) >= 1; m = mq) {
        for (i = 0; i < mq; i++) {
            w1r = cos(theta * i);
            w1i = sin(theta * i);
            w2r = cos(theta * 2 * i);
            w2i = sin(theta * 2 * i);
            w3r = cos(theta * 3 * i);
            w3i = sin(theta * 3 * i);
            for (j = i; j < n; j += m) {
                j1 = j + mq;
                j2 = j1 + mq;
                j3 = j2 + mq;
                x0r = ar[j] + ar[j2];
                x0i = ai[j] + ai[j2];
                x1r = ar[j] - ar[j2];
                x1i = ai[j] - ai[j2];
                x2r = ar[j1] + ar[j3];
                x2i = ai[j1] + ai[j3];
                x3r = ar[j3] - ar[j1];
                x3i = ai[j3] - ai[j1];
                ar[j] = x0r + x2r;
                ai[j] = x0i + x2i;
                x0r = x0r - x2r;
                x0i = x0i - x2i;
                ar[j1] = w2r * x0r - w2i * x0i;
                ai[j1] = w2r * x0i + w2i * x0r;
                x0r = x1r - x3i;
                x0i = x1i + x3r;
                ar[j2] = w1r * x0r - w1i * x0i;
                ai[j2] = w1r * x0i + w1i * x0r;
                x0r = x1r + x3i;
                x0i = x1i - x3r;
                ar[j3] = w3r * x0r - w3i * x0i;
                ai[j3] = w3r * x0i + w3i * x0r;
            }
        }
        theta *= 4;
    }
    /* ---- radix 2 butterflies (n % 4 == 2) ---- */
    if (m == 2) {
        for (j = 0; j < n; j += 2) {
            x0r = ar[j] - ar[j + 1];
            x0i = ai[j] - ai[j + 1];
            ar[j] += ar[j + 1];
            ai[j] += ai[j + 1];
            ar[j + 1] = x0r;
            ai[j + 1] = x0i;
        }
    }
    /* ---- unscrambler ---- */
    i = 0;
    for (j = 1; j < n - 1; j++) {
        for (k = n >> 1; k > (i ^= k); k >>= 1);
        if (j < i) {
            x0r = ar[j];
            x0i = ai[j];
            ar[j] = ar[i];
            ai[j] = ai[i];
            ar[i] = x0r;
            ai[i] = x0i;
        }
    }
}
 
図1.2.3-1. 基数 4 の FFT のフローグラフ

 このルーチンは,基数 4 と基数 2 の混合基数ルーチンです.すなわち, N が 4 の整数乗で割り切れないときは,最後に基数 2 のバタフライ演算が 付け足されます.さらに,4 進数のワード反転と 2 進数のビット反転との 混合を避けるため,基数 4 のバタフライ演算の二番目と三番目の入力を入れ 換え,常にビット反転になるようにしてあります.これらの大きいの基数の FFT は,小さいの基数のルーチンの変形でも機械的に得ることができます. 例えば,基数 4 のルーチンは,基数 2 のルーチンのバタフライ演算の外側の 二つのループを同時に展開し,この 4 個のバタフライ演算に含まれる 回転因子をくくり出し,3 個の回転因子に減らしたものに相当します. この技法は,可換な加算と乗算の演算を交換し,乗算部の演算をひとまとめに するという方法で,Winograd DFT アルゴリズムや,Vector-Radix FFT での 演算の削減技法と似ています.

 次に,この基数 4 の FFT の演算量について考えます.N 点の複素 DFT の 計算に必要な実数乗算回数 μ と実数加算回数 α は次のようになります.

         3                    
    μ = --- N log N - 5N + 8  
         2                    

         11             13       8   
    α = ---- N log N - ---- N + ---  
         4              6        3   

ただし,これは 5 バタフライルーチンを用いて不要な演算を取り除き, 複素乗算を 4 回の実数乗算と 2 回の実数加算で計算したときの値です. これは,基数 2 の FFT に比べて乗算回数が約 3/4 に減り,加算回数が 約 11/12 に減っています.

 次に,長さが 2 の整数乗以外の FFT について考えます.実際の 計算では,DFT の長さは 3 の倍数や 10 の倍数などに選ばれることが よくあります.このとき,混合基数 FFT を用います.もし,DFT の長さ N が特定の因数に分解できることがわかっている場合は,それらの因数の 基数の演算をあらかじめ用意し,組み合わせて使います.さらに,N の因数が あらかじめわからない場合は可変長の基数を用います.例として,任意の 長さの FFT ルーチンを示します.

リスト1.2.3-2. 任意の長さの FFT
/*
  F[k]=Σ_j=0^n-1 a[j]*exp(±2*pi*i*j*k/n),0<=k<n を計算する.
  n はデータ数で任意, theta は ±2*PI/n, 
  ar[0...n-1], ai[0...n-1], はデータの実部, 虚部, 
  tmpr[0...n-1], tmpi[0...n-1] は作業領域.
*/
#include <math.h>

void fft(int n, double theta, double ar[], double ai[], 
        double tmpr[], double tmpi[])
{
    int radix, n_radix, j, m, r;
    double xr, xi, wr, wi;

    if (n <= 1) return;
    /* ---- factorization ---- */
    for (radix = 2; radix * radix <= n; radix++) {
        if (n % radix == 0) break;
    }
    if (n % radix != 0) radix = n;
    n_radix = n / radix;
    /* ---- butterflies ---- */
    for (j = 0; j < n_radix; j++) {
        for (m = 0; m < radix; m++) {
            xr = ar[j];
            xi = ai[j];
            for (r = n_radix; r < n; r += n_radix) {
                wr = cos(theta * m * r);
                wi = sin(theta * m * r);
                xr += wr * ar[r + j] - wi * ai[r + j];
                xi += wr * ai[r + j] + wi * ar[r + j];
            }
            wr = cos(theta * m * j);
            wi = sin(theta * m * j);
            tmpr[m * n_radix + j] = xr * wr - xi * wi;
            tmpi[m * n_radix + j] = xi * wr + xr * wi;
        }
    }
    for (r = 0; r < n; r += n_radix) {
        fft(n_radix, theta * radix, &tmpr[r], &tmpi[r], ar, ai);
    }
    for (j = 0; j < n_radix; j++) {
        for (m = 0; m < radix; m++) {
            ar[radix * j + m] = tmpr[n_radix * m + j];
            ai[radix * j + m] = tmpi[n_radix * m + j];
        }
    }
}
 

このルーチンは,長さ R の短い DFT を R^2 の計算量の算法で行っています. しかも,再帰呼び出しを用い,三角関数はその場で計算するという無駄の 多いルーチンです.しかし,演算量のオーダーは他の実用的な改良を行った Cooley-Tukey 型の混合基数ルーチンと同じです.一般に,DFT の長さ N が 合成数で小さい数の素因数に分解されるならば,混合基数 FFT の演算量は N*log(N) に比例する程度になります.しかし,DFT の長さが素数のときは 最悪となり,演算量は N^2 のオーダーになります.


目次へ戻る