1.2 Cooley-Tukey 型 FFT

1.2.4 Split-Radix FFT アルゴリズム

 Split-Radix FFT は,いかなる基数の Cooley-Tukey FFT よりも 浮動小数点演算回数が少ないアルゴリズムとして知られています [参考文献]. Split-Radix FFT の説明の前に,なぜ大きい基数の FFT の演算量が 削減されるのかについて少し考えてみます.具体例として基数 4 の FFT の バタフライの構造について調べます.基数 4 の FFT は,基数 2 の FFT の 四つのバタフライ演算を,一つにまとめたものに相当します.このとき, 四つあった乗算は,三つに減ります.この変形は次のようになります.

図1.2.4-1. 基数 2 と基数 4 のバタフライ

この演算量の削減は,下半分のデータのバタフライ演算での,重なった 回転因子を一つにまとめることで行っていることがわかります.一方, 上半分のデータのバタフライ演算では演算の削減は行われておらず, 無意味な展開をしていることになります.この無意味な展開を削除したものが Split-Radix FFT アルゴリズムです.

 Split-Radix FFT アルゴリズムは,偶数の添字の項に対しては,基数 2 の 分解を用い,奇数の添字の項に対しては,基数 4 の分解を用います. すなわち,DFT の基本式

          N-1      jk            -2πi / N    
    A  =  Σ   a   W   ,   W  = e             
     k    j=0  j   N       N                 

を偶数項に関しては

             N/2-1               jk   
    A     =   Σ   (a + a     )  W     
     2k      j=0    j   N/2+j    N/2  

と分解し,奇数項に対しては

             N/4-1                                  j   jk   
    A     =   Σ   (a − a     − i(a     − a      )) W   W     
     4k+1    j=0    j   N/2+j     N/4+j   3N/4+j    N   N/4  


             N/4-1                                  3j  jk   
    A     =   Σ   (a − a     + i(a     − a      )) W   W     
     4k+3    j=0    j   N/2+j     N/4+j   3N/4+j    N   N/4  

と分解します.この分解による Split-Radix FFT は L 字の形をした,異なる 深さのバタフライ演算で構成されます.このため,log(N) 段ある演算は, 単純に一段ずつ終了させることができず,構造が多少複雑になります.例えば, N = 8 の場合のフローグラフは次のようになります.

図1.2.4-2. Split-Radix FFT のデータフロー

この L 字のバタフライ演算は,基数 2 の分解の部分では一段階しか進まず, 基数 4 の分解の部分で二段階進みます.また,最後の段で演算を完了させる ために,基数 2 の演算が別に必要になります.

 次に,周波数間引きの Split-Radix FFT のルーチン例を示します.

リスト1.2.4-1. Split-Radix 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, w3r, w3i;
    double x0r, x0i, x1r, x1i, x3r, x3i;
    
    /* ---- L shaped butterflies ---- */
    theta = -8 * atan(1.0) / n;
    for (m = n; m > 2; m >>= 1) {
        mq = m >> 2;
        for (i = 0; i < mq; i++) {
            w1r = cos(theta * i);
            w1i = sin(theta * i);
            w3r = cos(theta * 3 * i);
            w3i = sin(theta * 3 * i);
            for (k = m; k <= n; k <<= 2) {
                for (j = k - m + i; j < n; j += 2 * k) {
                    j1 = j + mq;
                    j2 = j1 + mq;
                    j3 = j2 + mq;
                    x1r = ar[j] - ar[j2];
                    x1i = ai[j] - ai[j2];
                    ar[j] += ar[j2];
                    ai[j] += ai[j2];
                    x3r = ar[j3] - ar[j1];
                    x3i = ai[j3] - ai[j1];
                    ar[j1] += ar[j3];
                    ai[j1] += ai[j3];
                    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 *= 2;
    }
    /* ---- radix 2 butterflies ---- */
    for (k = 2; k <= n; k <<= 2) {
        for (j = k - 2; j < n; j += 2 * k) {
            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;
        }
    }
}
 

このルーチンには,W=1,exp(πi/4) の場合に不要な乗算が含まれます. これらの不要な演算を取り除くためには,場合分けを行い,さらに二つの バタフライを追加します.基数が 2 のベキ乗の FFT の場合は 5 個の バタフライが必要ですが,Split-Radix FFT の場合は 3 個のバタフライで 済みます.さらに,高速化するためには内側のループでデータ参照を 連続にします.次に,これらの変更を施したルーチンの一例を示します.

リスト1.2.4-2. 3バタフライの Split-Radix FFT
#include <math.h>

void fft(int n, double ar[], double ai[])
{
    int m, mq, irev, i, j, j1, j2, j3, k;
    double theta, w1r, w1i, w3r, w3i;
    double x0r, x0i, x1r, x1i, x3r, x3i;
    
    /* ---- scrambler ---- */
    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;
        }
    }
    /* ---- L shaped butterflies ---- */
    theta = -2 * atan(1.0) / n;
    for (m = 4; m <= n; m <<= 1) {
        mq = m >> 2;
        /* ---- W == 1 ---- */
        for (k = mq; k >= 1; k >>= 2) {
            for (j = mq - k; j < mq - (k >> 1); j++) {
                j1 = j + mq;
                j2 = j1 + mq;
                j3 = j2 + mq;
                x1r = ar[j] - ar[j1];
                x1i = ai[j] - ai[j1];
                ar[j] += ar[j1];
                ai[j] += ai[j1];
                x3r = ar[j3] - ar[j2];
                x3i = ai[j3] - ai[j2];
                ar[j2] += ar[j3];
                ai[j2] += ai[j3];
                ar[j1] = x1r - x3i;
                ai[j1] = x1i + x3r;
                ar[j3] = x1r + x3i;
                ai[j3] = x1i - x3r;
            }
        }
        if (m == n) continue;
        /* ---- W == exp(-pi*i/4) ---- */
        irev = n >> 1;
        w1r = cos(theta * irev);
        for (k = mq; k >= 1; k >>= 2) {
            for (j = m + mq - k; j < m + mq - (k >> 1); j++) {
                j1 = j + mq;
                j2 = j1 + mq;
                j3 = j2 + mq;
                x1r = ar[j] - ar[j1];
                x1i = ai[j] - ai[j1];
                ar[j] += ar[j1];
                ai[j] += ai[j1];
                x3r = ar[j3] - ar[j2];
                x3i = ai[j3] - ai[j2];
                ar[j2] += ar[j3];
                ai[j2] += ai[j3];
                x0r = x1r - x3i;
                x0i = x1i + x3r;
                ar[j1] = w1r * (x0r + x0i);
                ai[j1] = w1r * (x0i - x0r);
                x0r = x1r + x3i;
                x0i = x1i - x3r;
                ar[j3] = w1r * (-x0r + x0i);
                ai[j3] = w1r * (-x0i - x0r);
            }
        }
        /* ---- W != 1, exp(-pi*i/4) ---- */
        for (i = 2 * m; i < n; i += m) {
            for (k = n >> 1; k > (irev ^= k); k >>= 1);
            w1r = cos(theta * irev);
            w1i = sin(theta * irev);
            w3r = cos(theta * 3 * irev);
            w3i = sin(theta * 3 * irev);
            for (k = mq; k >= 1; k >>= 2) {
                for (j = i + mq - k; j < i + mq - (k >> 1); j++) {
                    j1 = j + mq;
                    j2 = j1 + mq;
                    j3 = j2 + mq;
                    x1r = ar[j] - ar[j1];
                    x1i = ai[j] - ai[j1];
                    ar[j] += ar[j1];
                    ai[j] += ai[j1];
                    x3r = ar[j3] - ar[j2];
                    x3i = ai[j3] - ai[j2];
                    ar[j2] += ar[j3];
                    ai[j2] += ai[j3];
                    x0r = x1r - x3i;
                    x0i = x1i + x3r;
                    ar[j1] = w1r * x0r - w1i * x0i;
                    ai[j1] = w1r * x0i + w1i * x0r;
                    x0r = x1r + x3i;
                    x0i = x1i - x3r;
                    ar[j3] = w3r * x0r - w3i * x0i;
                    ai[j3] = w3r * x0i + w3i * x0r;
                }
            }
        }
    }
    /* ---- radix 2 butterflies ---- */
    mq = n >> 1;
    for (k = mq; k >= 1; k >>= 2) {
        for (j = mq - k; j < mq - (k >> 1); j++) {
            j1 = mq + j;
            x0r = ar[j] - ar[j1];
            x0i = ai[j] - ai[j1];
            ar[j] += ar[j1];
            ai[j] += ai[j1];
            ar[j1] = x0r;
            ai[j1] = x0i;
        }
    }
}
 

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

         4             38           2       log N  
    μ = --- N log N - ---- N + 6 + --- (-1)        
         3             9            9              


         8             16           2       log N  
    α = --- N log N - ---- N + 2 - --- (-1)        
         3             9            9              

これは不要な演算を取り除く 3 バタフライルーチンを用いて,複素乗算を 4 回の実数乗算と 2 回の実数加算で計算したときの値です.これは,基数 4 の FFT に比べて乗算回数が約 8/9 に減り,加算回数が約 32/33 に減ります. さらに,複素乗算を 3 回の実数乗算と 3 回の実数加算で計算する方法を用いると,

                          
    μ = N log N - 3N + 4  
                          

                           
    α = 3N log N - 3N + 4  
                           

となります.これは,基数 2 の FFT に比べて加算回数はそのままで 乗算回数を約半分にした値です.Split-Radix FFT は,通常の Cooley-Tukey 型 FFT よりも複雑なループ構造となります.しかし,浮動小数点演算量は他の いかなる基数の FFT よりも少なくなります.また,長さが 16 以下の 場合には,Split-Radix FFT は理論的に最適な演算回数となることが わかっています.


目次へ戻る