Prime Factor Algorithm (PFA) では,分解された互いに素な長さの多次元 DFT を Row-Column 法 (一次元 DFT の直積) で計算するという単純な方法を用います.例えば, 二次元 DFT
N1-1 N2-1 j1 k1 j2 k2 A = Σ Σ a W W k1, k2 j1=0 j2=0 j1, j2 N1 N2
は
N2-1 N1-1 j1 k1 j2 k2 A = Σ ( Σ a W ) W k1, k2 j2=0 j1=0 j1, j2 N1 N2
のようにして,まず長さ N1 の DFT を N2 回行い,次に長さ N2 の DFT を N1 回行って計算します.当然,この演算の順序は交換が可能で,長さ N2 の DFT を先に行い,次に長さ N1 の DFT を行うこともできます.PFA では この順序は任意に選べ,演算量は同じになります.
次に,PFA の計算量について少し考えてみます.最初に,長さ Ni の Short DFT の計算は μi 回の乗算と αi 回の加算でできると仮定して おきます.このとき,長さ N = N1 × N2 の PFA の乗算回数 μ と 加算回数 α は
μ = N2 μ1 + N1 μ2 α = N2 α1 + N1 α2
となることがわかります.さらに一般に,長さ N が
d N = Π N i=1 i
のように d 個の因数に分解されたとき,PFA の演算量は
d μ = Σ (N / N ) μ i=1 i i d α = Σ (N / N ) α i=1 i i
となります.
もし,長さ Ni の Short DFT が Ni^2 回の乗算で計算されるならば, PFA の乗算回数は
d μ = N Σ N i=1 i
となり,もし,長さ Ni の Short DFT が Ni*log(Ni) 回の乗算で計算される ならば,
μ = N log N
となります.さらに,長さ Ni の Short DFT が最適に設計され,Ni に 比例する乗算回数で計算されるならば
μ = 定数・d N
となります.したがって,PFA の演算量のオーダーは,Short DFT の演算量に 依存します.そのため,PFA では,Short DFT アルゴリズムの設計が特に重要に なります.
次に,個別の Short DFT アルゴリズムを用いた PFA ルーチンの例を 示します.
リスト1.3.2-1. 個別の Short DFT を用いた PFA |
---|
/* Prime Factor FFT (PFA) n must be n = 2^m2 * 3^m3 * 5^m5, 0<=m2<=2, 0<=m3<=1, 0<=m5<=1 */ #include <math.h> #define NFACTOR 4 void length2dft(int n, int n2, int n2t2, double ar[], double ai[]); void length3dft(int n, int n2, int n2t2, double ar[], double ai[]); void length4dft(int n, int n2, int n2t2, double ar[], double ai[]); void length5dft(int n, int n2, int n2t2, double ar[], double ai[]); void fft(int n, double ar[], double ai[], double br[], double bi[]) { static int factor[NFACTOR] = {5, 4, 3, 2}; int ndiv, mtbl, n1tbl[NFACTOR], n2tbl[NFACTOR]; int n1, n2, n2sum, n2t2, m, j, k; /* ---- factorization ---- */ ndiv = n; mtbl = 0; for (m = 0; m < NFACTOR; m++) { n1 = factor[m]; if (ndiv % n1 != 0) continue; ndiv /= n1; n2 = n / n1; n1tbl[mtbl] = n1; n2tbl[mtbl++] = n2; } /* ---- scrambler ---- */ n2sum = 0; for (m = 0; m < mtbl; m++) { n2sum += n2tbl[m]; } n2sum %= n; j = 0; for (k = 0; k < n; k++) { br[k] = ar[j]; bi[k] = ai[j]; j += n2sum; if (j >= n) j -= n; } /* ---- short DFTs ---- */ for (m = 0; m < mtbl; m++) { n1 = n1tbl[m]; n2 = n2tbl[m]; for (n2t2 = n2; n2t2 < n; n2t2 += n2) { if (n2t2 % n1 == 1) break; } switch (n1) { case 2: length2dft(n, n2, n2t2, br, bi); break; case 3: length3dft(n, n2, n2t2, br, bi); break; case 4: length4dft(n, n2, n2t2, br, bi); break; case 5: length5dft(n, n2, n2t2, br, bi); break; /* case 7: */ /* ... etc. */ default : break; } } } void length2dft(int n, int n2, int n2t2, double ar[], double ai[]) { int i, j, j0, j1; double x0r, x0i; j0 = 0; j1 = n2t2; for (i = 0; i < n2; i++) { x0r = ar[j0] - ar[j1]; x0i = ai[j0] - ai[j1]; ar[j0] += ar[j1]; ai[j0] += ai[j1]; ar[j1] = x0r; ai[j1] = x0i; j = j1 + 1; j1 = j0 + 1; j0 = j; } } #define W3A 0.86602540378443864676 /* sin(2*pi/3) */ void length3dft(int n, int n2, int n2t2, double ar[], double ai[]) { int i, j, j0, j1, j2; double x0r, x0i, x1r, x1i, x2r, x2i; j0 = 0; j1 = n2t2; j2 = j1 + n2t2; if (j2 >= n) j2 -= n; for (i = 0; i < n2; i++) { x0r = ar[j1] + ar[j2]; x0i = ai[j1] + ai[j2]; x1r = W3A * (ar[j1] - ar[j2]); x1i = W3A * (ai[j1] - ai[j2]); x2r = ar[j0] - 0.5 * x0r; x2i = ai[j0] - 0.5 * x0i; ar[j0] += x0r; ai[j0] += x0i; ar[j1] = x2r + x1i; ai[j1] = x2i - x1r; ar[j2] = x2r - x1i; ai[j2] = x2i + x1r; j = j2 + 1; j2 = j1 + 1; j1 = j0 + 1; j0 = j; } } void length4dft(int n, int n2, int n2t2, double ar[], double ai[]) { int i, j, j0, j1, j2, j3; double x0r, x0i, x1r, x1i, x2r, x2i, x3r, x3i; j0 = 0; j1 = n2t2; j2 = j1 + n2t2; if (j2 >= n) j2 -= n; j3 = j2 + n2t2; if (j3 >= n) j3 -= n; for (i = 0; i < n2; i++) { x0r = ar[j0] + ar[j2]; x0i = ai[j0] + ai[j2]; x1r = ar[j0] - ar[j2]; x1i = ai[j0] - ai[j2]; x2r = ar[j1] + ar[j3]; x2i = ai[j1] + ai[j3]; x3r = ar[j1] - ar[j3]; x3i = ai[j1] - ai[j3]; ar[j0] = x0r + x2r; ai[j0] = x0i + x2i; ar[j2] = x0r - x2r; ai[j2] = x0i - x2i; ar[j1] = x1r + x3i; ai[j1] = x1i - x3r; ar[j3] = x1r - x3i; ai[j3] = x1i + x3r; j = j3 + 1; j3 = j2 + 1; j2 = j1 + 1; j1 = j0 + 1; j0 = j; } } #define W5A 0.55901699437494742410 /* (cos(2*pi/5)-cos(4*pi/5))/2 */ #define W5B 0.95105651629515357212 /* sin(2*pi/5) */ #define W5C 0.58778525229247312917 /* sin(4*pi/5) */ void length5dft(int n, int n2, int n2t2, double ar[], double ai[]) { int i, j, j0, j1, j2, j3, j4; double x0r, x0i, x1r, x1i, x2r, x2i, x3r, x3i, x4r, x4i; j0 = 0; j1 = n2t2; j2 = j1 + n2t2; if (j2 >= n) j2 -= n; j3 = j2 + n2t2; if (j3 >= n) j3 -= n; j4 = j3 + n2t2; if (j4 >= n) j4 -= n; for (i = 0; i < n2; i++) { x0r = ar[j1] + ar[j4]; x0i = ai[j1] + ai[j4]; x1r = ar[j1] - ar[j4]; x1i = ai[j1] - ai[j4]; x2r = ar[j2] + ar[j3]; x2i = ai[j2] + ai[j3]; x3r = ar[j2] - ar[j3]; x3i = ai[j2] - ai[j3]; x4r = W5A * (x0r - x2r); x4i = W5A * (x0i - x2i); x0r += x2r; x0i += x2i; x2r = ar[j0] - 0.25 * x0r; x2i = ai[j0] - 0.25 * x0i; ar[j0] += x0r; ai[j0] += x0i; x0r = x2r - x4r; x0i = x2i - x4i; x2r += x4r; x2i += x4i; x4r = W5B * x1r + W5C * x3r; x4i = W5B * x1i + W5C * x3i; x3r = W5C * x1r - W5B * x3r; x3i = W5C * x1i - W5B * x3i; ar[j1] = x2r + x4i; ai[j1] = x2i - x4r; ar[j4] = x2r - x4i; ai[j4] = x2i + x4r; ar[j2] = x0r + x3i; ai[j2] = x0i - x3r; ar[j3] = x0r - x3i; ai[j3] = x0i + x3r; j = j4 + 1; j4 = j3 + 1; j3 = j2 + 1; j2 = j1 + 1; j1 = j0 + 1; j0 = j; } } |
このルーチンは,長さ 2, 3, 4, 5 の四つの Short DFT を用いています. したがって,計算可能な DFT の長さは最大 N=60 までです.このルーチンに, 長さ 7, 8, 9, 16 の Short DFT を追加すれば,計算可能な長さは最大 N=5040 となり,さらに長さ 11, 13 の Short DFT を追加すれば,最大 N=720720 となります.
このルーチンは,Prime Factor 型 FFT の二番目の分解式
N1-1 N2-1 j1 k1 j2 k2 A = Σ Σ a W W N1 t1 k2 + N2 t2 k1 j1=0 j2=0 N1 j2 + N2 j1 N1 N2
を用いています.具体的にはさらに,この分解の入力に対して
a = b N1 j2 + N2 j1 N1 t1 j2 + N2 t2 j1
と添字の変換(スクランブル)を行い
N1-1 N2-1 j1k1 j2k2 A = Σ Σ b W W N1t1k2 + N2t2k1 j1=0 j2=0 N1t1j2 + N2t2j1 N1 N2
としています.この式で,j1, j2 は,変換後の添字 N1t1j2 + N2t2j1 の変化に 対して連続になり,したがってメモリアクセスも連続になります.さらに, 入力と出力の添字変換が同じになり,多次元 DFT の計算で in-place 演算が 可能となります.しかし,スクランブルルーチンは in-place にはなりません. 完全に in-place 演算にするためには,三番目の分解式
N1-1 N2-1 t2j1k1 t1j2k2 A = Σ Σ a W W N1t1k2 + N2t2k1 j1=0 j2=0 N1t1j2 + N2t2j1 N1 N2
を用います.このとき,スクランブル/アンスクランブルルーチンは不必要で セルフソートとなります.それと引き換えに W を t2 だけ回転させた特殊な Short DFT ルーチンが必要になります.この Short DFT ルーチンは,多くの 場合,通常の Short DFT ルーチン内の三角関数データを回転するだけで 得られます.しかし,このとき ±1 による不要だった乗算が必要になることが あり,演算量を最小にするには,さらに場合分けが必要になります.
この PFA ルーチンでは,N^2 算法の Short DFT ルーチンは用いず,個別に 演算量を削減した効率的な Short DFT ルーチンを用いています.一般に, PFA の Short DFT ルーチンは,加算と乗算の両方を減らすように設計します. この効率的なルーチンは,長さが 5 ぐらいまでならば,力任せの式の変形で 得られます.しかし,サイズが少し大きくなると力任せは無理で,理論的に 設計しなければなりません.Short DFT ルーチンの設計の概略は後に示します.