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 ルーチンの設計の概略は後に示します.