radix-4 FFT 实现

信息处理 fft
2022-01-15 16:15:48

我实现了一个 4 点 radix-4 FFT,发现我需要对输出项进行一些操作以使其与 dft 匹配。

我的代码是矩阵公式的一个非常直接的实现,所以我不清楚问题是什么

//                                | 
// radix-4 butterfly matrix form  |  complex multiplication
//                                | 
//        +-          -+ +-  -+   |    a+ib
// X[0] = | 1  1  1  1 | |x[0]|   |  * c+id
// X[1] = | 1 -i -1  i | |x[1]|   |    -------
// X[2] = | 1 -1  1 -1 | |x[2]|   |    ac + ibc
// X[3] = | 1  i -1 -i | |x[3]|   |         iad - bd
//        +-          -+ +-  -+   |    ------------------
//                                |    (ac-bd) + i(bc+ad)  
//                                | 

谁能发现我哪里出错了?

谢谢,

-大卫

typedef double fp; // base floating-point type


// naiive N-point DFT implementation as reference to check fft implementation against
//
void dft(int inv, struct cfp *x, struct cfp *y, int N) {

  long int i, j;
  struct cfp w;
  fp ang;

  for(i=0; i<N; i++) { // do N-point FFT/IFFT
    y[i].r = y[i].i = 0;
    if (inv) ang =  2*PI*(fp)i/(fp)N;
    else     ang = -2*PI*(fp)i/(fp)N;
    for (j=0; j<N; j++) {
      w.r = cos(j*ang);
      w.i = sin(j*ang);
      y[i].r += (x[j].r * w.r - x[j].i * w.i);
      y[i].i += (x[j].r * w.i + x[j].i * w.r);
    }
  }

  // scale output in the case of an IFFT
  if (inv) {  
    for (i=0; i<N; i++) {
      y[i].r = y[i].r/(fp)N;
      y[i].i = y[i].i/(fp)N;
    }
  }

} // dft()


void r4fft4(int inv, int reorder, struct cfp *x, struct cfp *y) {
  struct cfp x1[4], w[4];
  fp         ang, temp;
  int        i;

  //                                | 
  // radix-4 butterfly matrix form  |  complex multiplication
  //                                | 
  //        +-          -+ +-  -+   |    a+ib
  // y[0] = | 1  1  1  1 | |x[0]|   |  * c+id
  // y[1] = | 1 -i -1  i | |x[1]|   |    -------
  // y[2] = | 1 -1  1 -1 | |x[2]|   |    ac + ibc
  // y[3] = | 1  i -1 -i | |x[3]|   |         iad - bd
  //        +-          -+ +-  -+   |    ------------------
  //                                |    (ac-bd) + i(bc+ad)  
  //                                | 

  if (inv) ang =  2*PI/(fp)4; // invert sign for IFFT
  else     ang = -2*PI/(fp)4;
  //
  w[1].r = cos(ang*1); w[1].i = sin(ang*1); // twiddle1 = exp(-2*pi/4 * 1);
  w[2].r = cos(ang*2); w[2].i = sin(ang*2); // twiddle2 = exp(-2*pi/4 * 2);
  w[3].r = cos(ang*3); w[3].i = sin(ang*3); // twiddle3 = exp(-2*pi/4 * 3);

  //         *1       *1       *1       *1
  y[0].r  = x[0].r + x[1].r + x[2].r + x[3].r;
  y[0].i  = x[0].i + x[1].i + x[2].i + x[3].i;
  //         *1       *-i      *-1      *i
  x1[1].r = x[0].r + x[1].i - x[2].r - x[3].i;               
  x1[1].i = x[0].i - x[1].r - x[2].i + x[3].r;               
  //         *1       *-1      *1       *-1
  x1[2].r = x[0].r - x[1].r + x[2].r - x[3].r;
  x1[2].i = x[0].i - x[1].i + x[2].i - x[3].i;
  //         *1       *i       *-1      *-i
  x1[3].r = x[0].r - x[1].i - x[2].r + x[3].i;
  x1[3].i = x[0].i + x[1].r - x[2].i - x[3].r;
  //
  y[1].r = x1[1].r*w[1].r - x1[1].i*w[1].i; // scale radix-4 output
  y[1].i = x1[1].i*w[1].r + x1[1].r*w[1].i;
  //
  y[2].r = x1[2].r*w[2].r - x1[2].i*w[2].i; // scale radix-4 output
  y[2].i = x1[2].i*w[2].r + x1[2].r*w[2].i;
  //
  y[3].r = x1[3].r*w[3].r - x1[3].i*w[3].i; // scale radix-4 output
  y[3].i = x1[3].i*w[3].r + x1[3].r*w[3].i;

  // reorder output stage ... mystery as to why I need this
  if (reorder) {
    temp = y[1].r; 
    y[1].r = -1*y[1].i; 
    y[1].i = temp;
    //
    y[2].r = -1*y[2].r; 
    //
    temp = y[3].r; 
    y[3].r = y[3].i; 
    y[3].i = -1*temp;
  }

  // scale output for inverse FFT
  if (inv) {
    for (i=0; i<4; i++) { // scale output by 1/N for IFFT
      y[i].r = y[i].r/(fp)4;
      y[i].i = y[i].i/(fp)4;
    }
  }

} // r4fft4()
2个回答

首先,您所谓的“基数 4 蝴蝶”是 4 点 DFT,而不是 FFT。它有 16 个复杂的(即:N 平方)操作。典型的 4 点 FFT 将只有 Nlog(base 2)N(N = 4 时 = 8)。其次,您有一些不属于的假定 w[ ].r 和 w[ ].i “比例”因子。也许您是从较大图表中显示的 radix-4 蝴蝶中获得它们的。这样的蝴蝶会附加一些阶段间的旋转,但它们实际上并不是蝴蝶的一部分。当为负指数 FFT 设计时,4 点 FFT 仅具有 -j 的内部蝶形。

与其尝试修复您的代码,不如编写我自己的代码,如下所示(DevC++ 编译器;输出附加在代码末尾):

#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <cmath>
using namespace std;
void fft4(double* r, double* i);    // prototype declaration
int main (int nNumberofArgs, char* pszArgs[ ] ) { // arguments needed for Dev C++ I/O

double r[4] = {1.5, -2.3, 4.65, -3.51}, i[4] = {-1.0, 2.6, 3.75, -2.32} ;
long n, k, j;      double  yr[4] = {0.}, yi[4] = {0.};
double ang, C, S, twopi = 6.2831853071795865;

cout<<"\n original real/imag data";
cout<<"\n n         r[n]            i[n]\n";
for (n = 0; n < 4; n++)  {
    printf("%2d\t%9.4f\t%9.4f\n",n,r[n],i[n]);
} //end for loop over n

// 4 point DFT
for (k = 0; k < 4; k++) {
    ang = twopi*k/4;
    for (j = 0; j < 4; j++) {
        C = cos(j*ang);       S = sin(j*ang);
        yr[k] = yr[k] + r[j]*C + i[j]*S;   // ( C - jS )*( r + ji )
        yi[k] = yi[k] + i[j]*C - r[j]*S;   // = ( rC + iS ) + j( iC - rS )
    }
}

cout<<"\n 4 point DFT results";
cout<<"\n n         yr[n]           yi[n]           amplitude       phase(radians)\n";
double amp, phase;
for (n = 0; n < 4; n++)  {
    yr[n] = yr[n]/4 ;      yi[n] = yi[n]/4 ;  // scale outputs
    amp = sqrt( yr[n]*yr[n] + yi[n]*yi[n] ) ;
    phase = atan2( yi[n], yr[n] ) ; 
    printf("%2d\t%9.4f\t%9.4f\t%9.4f\t%9.4f\n",n,yr[n],yi[n],amp,phase);
} //end for loop over n

fft4(r, i) ;

cout<<"\n 4 point FFT results";
cout<<"\n n         r[n]            i[n]            amplitude       phase(radians)\n";

for (n = 0; n < 4; n++)  {
    r[n] = r[n]/4 ;      i[n] = i[n]/4 ;  // scale outputs
    amp = sqrt( r[n]*r[n] + i[n]*i[n] ) ;
    phase = atan2( i[n], r[n] ) ; 
    printf("%2d\t%9.4f\t%9.4f\t%9.4f\t%9.4f\n",n,r[n],i[n],amp,phase);
} //end for loop over n

fft4(i, r); // this is an inverse FFT (complex in/out routine)

cout<<"\n 4 point inverse FFT results";
cout<<"\n n         r[n]            i[n]\n";
for (n = 0; n < 4; n++)  {
    printf("%2d\t%9.4f\t%9.4f\n",n,r[n],i[n]);
} //end for loop over n

system ("PAUSE");
return 0;
} // end main
//************************ fft4 **********
void fft4(double* r, double* i) {
double t;

t = r[0]; r[0] = t + r[2]; r[2] = t - r[2];
t = i[0]; i[0] = t + i[2]; i[2] = t - i[2];
t = r[1]; r[1] = t + r[3]; r[3] = t - r[3];
t = i[1]; i[1] = t + i[3]; i[3] = t - i[3];

t = r[3]; r[3] = i[3]; i[3] = -t; // (r + ji)*(-j)

t = r[0]; r[0] = t + r[1]; r[1] = t - r[1];
t = i[0]; i[0] = t + i[1]; i[1] = t - i[1];
t = r[2]; r[2] = t + r[3]; r[3] = t - r[3];
t = i[2]; i[2] = t + i[3]; i[3] = t - i[3];

t = r[1]; r[1] = r[2]; r[2] = t;  // swap 1
t = i[1]; i[1] = i[2]; i[2] = t;  //  and 2
} // end fft4




 original real/imag data
 n         r[n]            i[n]
 0         1.5000         -1.0000
 1        -2.3000          2.6000
 2         4.6500          3.7500
 3        -3.5100         -2.3200

 4 point DFT results
 n         yr[n]           yi[n]           amplitude       phase(radians)
 0         0.0850          0.7575          0.7623          1.4591
 1         0.4425         -1.4900          1.5543         -1.2821
 2         2.9900          0.6175          3.0531          0.2037
 3        -2.0175         -0.8850          2.2031         -2.7282

 4 point FFT results
 n         r[n]            i[n]            amplitude       phase(radians)
 0         0.0850          0.7575          0.7623          1.4591
 1         0.4425         -1.4900          1.5543         -1.2821
 2         2.9900          0.6175          3.0531          0.2037
 3        -2.0175         -0.8850          2.2031         -2.7282

 4 point inverse FFT results
 n         r[n]            i[n]
 0         1.5000         -1.0000
 1        -2.3000          2.6000
 2         4.6500          3.7500
 3        -3.5100         -2.3200

首先,打印出输入数据(4 个实数,4 个虚数)。然后进行 4 点 DFT。结果(yr[ ] 和 yi[ ] 加上 amp/phase)被打印出来。由于 r[ ] 和 i[ ] 原始数据在进行 DFT 时没有被覆盖,因此这些输入被重新用作 4 点 FFT 的输入。请注意,后者的 +/- 操作比 DFT 少。

FFT 的代码不是特别优雅,也不是特别高效——有很多做蝴蝶的方法。上面的代码对应于 Rabiner 和 Gold 的书“数字信号处理的理论和应用”(第 580 页,图 10.9)中显示的四个 radix-2 蝴蝶,修改了旋转以反映负指数(用于书中的数字是正面的)。请注意,代码中只有一个 -j 旋转,这不需要乘法(它是交换/符号更改)。

在 FFT 之后,打印结果。它们与 DFT 相同

最后,将 FFT 的缩放结果用作逆 FFT 的输入。这是通过“交换”或“反转列表”方法完成的(即:如果 FFT(r,i) 是前向 FFT,则 FFT(i,r) 是逆向 - 当然,前提是 FFT 是能够处理复杂的输入/输出——换句话说——没有“仅真实的”例程,通常假定虚输入为零)。这种方法在近 25 年前被描述在:

P. Duhamel, B. Piron, JM Etcheto,“计算逆 DFT”,IEEE Transactions on Acoustics、Speech and Signal Processing,vol。36,1988 年 2 月,第 285-286 页。

然后打印出逆运算的结果。它与原始输入数据相同。

我刚刚将 radix-4 DIF fft 从 S. Burrus Fortran 代码移植到 Java。实际上它缺乏一些优化,首先是表格驱动的旋转因子(应该预先计算正弦和余弦因子)。这应该会加快 fft 的速度(可能是 50%)。我必须为此做一些修改,但如果有人有正确的答案,我会非常高兴和感激。我会尽快发布优化的代码,希望可以通过一些速度测试与 radix-2 算法进行比较。

此外,不会删除乘以 1 和 sqrt(-1) 的结果。删除它们会加快速度。但总体而言,恕我直言,radix-4 似乎比 radix-2 快不超过 25%,所以我不知道速度/复杂度比是否真的值得。请记住,像 FFTW 这样非常优化的库在很大程度上是可用和使用的,所以这种努力可能只是个人的“消遣”!

这是java代码。将其移植到 C、C++ 或 C# 应该非常容易。

public static void FFTR4(double[] X, double[] Y, int N, int M) {
    // N = 4 ^ M
    int N1,N2;
    int I1, I2, I3;
    double CO1,CO2,CO3,SI1,SI2,SI3;
    double A,B,C,E;
    double R1,R2,R3,R4;
    double S1,S2,S3,S4;
    // N = 1 << (M+M);
    N2 = N;
    I2 = 0; I3 = 0;
    for (int K=0; K<M; ++K) {
        N1 = N2;
        N2 = N2 / 4;
        E = PI2 / (double)N1;
        A = 0.0;
        for (int J=0; J < N2; ++J) {
            A = J*E;
            B = A + A;
            C = A + B;
            //Should be pre-calculated for optimization
            CO1 = Math.cos(A);
            CO2 = Math.cos(B);
            CO3 = Math.cos(C);
            SI1 = Math.sin(A);
            SI2 = Math.sin(B);
            SI3 = Math.sin(C);
            for (int I = J; I<N; I+=N1) {
                I1 = I + N2;
                I2 = I1 + N2;
                I3 = I2 + N2;
                R1 = X[I] + X[I2];
                R3 = X[I] - X[I2];
                S1 = Y[I] + Y[I2];
                S3 = Y[I] - Y[I2];
                R2 = X[I1] + X[I3];
                R4 = X[I1] - X[I3];
                S2 = Y[I1] + Y[I3];
                S4 = Y[I1] - Y[I3];
                X[I] = R1 + R2;
                R2 = R1 - R2;
                R1 = R3 - S4;
                R3 = R3 + S4;
                Y[I] = S1 + S2;
                S2 = S1 - S2;
                S1 = S3 + R4;
                S3 = S3 - R4;
                X[I1] = CO1*R3 + SI1*S3;
                Y[I1] = CO1*S3 - SI1*R3;
                X[I2] = CO2*R2 + SI2*S2;
                Y[I2] = CO2*S2 - SI2*R2;
                X[I3] = CO3*R1 + SI3*S1;
                Y[I3] = CO3*S1 - SI3*R1;
            }
        }
    }

    // Radix-4 bit-reverse
    double T;
    int J = 0;
    N2 = N>>2;
    for (int I=0; I < N-1; I++) {
        if (I < J) {
            T = X[I];
            X[I] = X[J];
            X[J] = T;
            T = Y[I];
            Y[I] = Y[J];
            Y[J] = T;
        }
        N1 = N2;
        while ( J >= 3*N1 ) {
            J -= 3*N1;
            N1 >>= 2;
        }
        J += N1;
    }
}

这是 Sidney Burrus 的原始 Radix-4 DIF FORTRAN 代码:

Radix-4、DIF、一只蝴蝶 FFT