验证FFT算法的正确性

时间:2014-06-19 21:50:48

标签: java algorithm math fft correctness

今天我写了一个算法来计算表示离散函数的给定点阵列的快速傅里叶变换。现在我试图测试它是否有效。我尝试了十几种不同的输入集,它们似乎与我在网上找到的例子相匹配。然而,对于我的最终测试,我给它输入cos(i / 2),i从0到31,并且我根据我使用的求解器获得了3个不同的结果。我的解决方案似乎是最不准确的:

Data

这是否表明我的算法存在问题,或者仅仅是数据集相对较小的结果?

我的代码如下,如果它有帮助:

/**
 * Slices the original array, starting with start, grabbing every stride elements.
 * For example, slice(A, 3, 4, 5) would return elements 3, 8, 13, and 18 from array A.
 * @param array     The array to be sliced
 * @param start     The starting index
 * @param newLength The length of the final array
 * @param stride    The spacing between elements to be selected
 * @return          A sliced copy of the input array
 */
public double[] slice(double[] array, int start, int newLength, int stride) {
    double[] newArray = new double[newLength];
    int count = 0;
    for (int i = start; count < newLength && i < array.length; i += stride) {
        newArray[count++] = array[i];
    }
    return newArray;
}

/**
 * Calculates the fast fourier transform of the given function.  The parameters are updated with the calculated values
 * To ignore all imaginary output, leave imaginary null
 * @param real An array representing the real part of a discrete-time function
 * @param imaginary An array representing the imaginary part of a discrete-time function
 * Pre: If imaginary is not null, the two arrays must be the same length, which must be a power of 2
 */
public void fft(double[] real, double[] imaginary) throws IllegalArgumentException {
    if (real == null) {
        throw new NullPointerException("Real array cannot be null");
    }

    int N = real.length;

    // Make sure the length is a power of 2
    if ((Math.log(N) / Math.log(2)) % 1 != 0) {
        throw new IllegalArgumentException("The array length must be a power of 2");
    }

    if (imaginary != null && imaginary.length != N) {
        throw new IllegalArgumentException("The two arrays must be the same length");
    }

    if (N == 1) {
        return;
    }

    double[] even_re = slice(real, 0, N/2, 2);
    double[] odd_re = slice(real, 1, N/2, 2);

    double[] even_im = null;
    double[] odd_im = null;
    if (imaginary != null) {
        even_im = slice(imaginary, 0, N/2, 2);
        odd_im = slice(imaginary, 1, N/2, 2);
    }

    fft(even_re, even_im);
    fft(odd_re, odd_im);

    // F[k] = real[k] + imaginary[k]

    //              even   odd
    //       F[k] = E[k] + O[k] * e^(-i*2*pi*k/N)
    // F[k + N/2] = E[k] - O[k] * e^(-i*2*pi*k/N)

    // Split complex arrays into component arrays:
    // E[k] = er[k] + i*ei[k]
    // O[k] = or[k] + i*oi[k]

    // e^ix = cos(x) + i*sin(x)

    // Let x = -2*pi*k/N
    // F[k] = er[k] + i*ei[k] + (or[k] + i*oi[k])(cos(x) + i*sin(x))
    //      = er[k] + i*ei[k] + or[k]cos(x) + i*or[k]sin(x) + i*oi[k]cos(x) - oi[k]sin(x)
    //      = (er[k] + or[k]cos(x) - oi[k]sin(x)) + i*(ei[k] + or[k]sin(x) + oi[k]cos(x))
    //        {               real              }     {            imaginary            }

    // F[k + N/2] = (er[k] - or[k]cos(x) + oi[k]sin(x)) + i*(ei[k] - or[k]sin(x) - oi[k]cos(x))
    //              {               real              }     {            imaginary            }

    // Ignoring all imaginary parts (oi = 0):
    //       F[k] = er[k] + or[k]cos(x)
    // F[k + N/2] = er[k] - or[k]cos(x)

    for (int k = 0; k < N/2; ++k) {
        double t = odd_re[k] * Math.cos(-2 * Math.PI * k/N);
        real[k]       = even_re[k] + t;
        real[k + N/2] = even_re[k] - t;

        if (imaginary != null) {
            t = odd_im[k] * Math.sin(-2 * Math.PI * k/N);
            real[k]       -= t;
            real[k + N/2] += t;

            double t1 = odd_re[k] * Math.sin(-2 * Math.PI * k/N);
            double t2 = odd_im[k] * Math.cos(-2 * Math.PI * k/N);
            imaginary[k]       = even_im[k] + t1 + t2;
            imaginary[k + N/2] = even_im[k] - t1 - t2;
        }
    }
}

2 个答案:

答案 0 :(得分:1)

  1. <强>验证

    看这里:slow DFT,iDFT最后是 DFT iDFT 的慢速实施,它们经过测试和纠正。我过去也使用它们进行快速实现验证。

  2. 您的代码

    停止递归是错误的(你忘记设置返回元素)我看起来像这样:

    if (n<=1) { if (n==1) { dst[0]=src[0]*2.0; dst[1]=src[1]*2.0; } return; }
    

    所以当N==1Re=2.0*real[0], Im=2.0*imaginary[0]之前将输出元素设置为return时。此外,我在复杂的数学(t,t1,t2)中有点迷失,懒得分析。

  3. 只是为了确保这是我的快速实施。它需要来自类层次结构的太多东西,所以它不会再与你的代码进行视觉比较。

    我的快速实施(cc表示复杂的输出和输入):

    //---------------------------------------------------------------------------
    void transform::DFFTcc(double *dst,double *src,int n)
        {
        if (n>N) init(n);
        if (n<=1) { if (n==1) { dst[0]=src[0]*2.0; dst[1]=src[1]*2.0; } return; }
        int i,j,n2=n>>1,q,dq=+N/n,mq=N-1;
        // reorder even,odd (buterfly)
        for (j=0,i=0;i<n+n;) { dst[j]=src[i]; i++; j++; dst[j]=src[i]; i+=3; j++; }
        for (    i=2;i<n+n;) { dst[j]=src[i]; i++; j++; dst[j]=src[i]; i+=3; j++; }
        // recursion
        DFFTcc(src  ,dst  ,n2); // even
        DFFTcc(src+n,dst+n,n2); // odd
        // reorder and weight back (buterfly)
        double a0,a1,b0,b1,a,b;
        for (q=0,i=0,j=n;i<n;i+=2,j+=2,q=(q+dq)&mq)
            {
            a0=src[j  ]; a1=+_cos[q];
            b0=src[j+1]; b1=+_sin[q];
            a=(a0*a1)-(b0*b1);
            b=(a0*b1)+(a1*b0);
            a0=src[i  ]; a1=a;
            b0=src[i+1]; b1=b;
            dst[i  ]=(a0+a1)*0.5;
            dst[i+1]=(b0+b1)*0.5;
            dst[j  ]=(a0-a1)*0.5;
            dst[j+1]=(b0-b1)*0.5;
            }
        }
    //---------------------------------------------------------------------------
    


    dst[]src[]不重叠!所以你不能将数组转换为自身。
    _cos_sincossin值的预先计算表(由init()函数计算如下:

        double a,da; int i;
        da=2.0*M_PI/double(N);
        for (a=0.0,i=0;i<N;i++,a+=da) { _cos[i]=cos(a); _sin[i]=sin(a); }
    


    N2的强大功能(数据集的填充大小为零)(来自n来电的最后init(n)

    在这里完成复杂的慢速版本很复杂:

    //---------------------------------------------------------------------------
    void transform::DFTcc(double *dst,double *src,int n)
        {
        int i,j;
        double a,b,a0,a1,_n,b0,b1,q,qq,dq;
        dq=+2.0*M_PI/double(n); _n=2.0/double(n);
        for (q=0.0,j=0;j<n;j++,q+=dq)
            {
            a=0.0; b=0.0;
            for (qq=0.0,i=0;i<n;i++,qq+=q)
                {
                a0=src[i+i  ]; a1=+cos(qq);
                b0=src[i+i+1]; b1=+sin(qq);
                a+=(a0*a1)-(b0*b1);
                b+=(a0*b1)+(a1*b0);
                }
            dst[j+j  ]=a*_n;
            dst[j+j+1]=b*_n;
            }
        }
    //---------------------------------------------------------------------------
    

答案 1 :(得分:0)

我会使用像Wolfram Alpha这样权威的东西进行验证。

如果我为cos(i/2)评估0 <= i < 32,我会得到这个数组:

[1,0.878,0.540,0.071,-0.416,-0.801,-0.990,-0.936,-0.654,-0.211,0.284,0.709,0.960,0.977,0.754,0.347,-0.146,-0.602,-0.911,-0.997,-0.839,-0.476,0.004,0.483,0.844,0.998,0.907,0.595,0.137,-0.355,-0.760,-0.978]

如果我将其作为Wolfram Alpha的FFT函数的输入,我得到this result

我得到的plot看起来是对称的,这是有道理的。情节看起来与您提供的情节完全不同。