12

I'm trying to implement a discrete fourier transform, but it's not working. I'm probably have written a bug somewhere, but I haven't found it yet.

Based on the following formula:

terere

This function does the first loop, looping over X0 - Xn-1... enter image description here

    public Complex[] Transform(Complex[] data, bool reverse)
    {
        var transformed = new Complex[data.Length];
        for(var i = 0; i < data.Length; i++)
        {
            //I create a method to calculate a single value
            transformed[i] = TransformSingle(i, data, reverse);
        }
        return transformed;
    }

And the actual calculating, this is probably where the bug is.

    private Complex TransformSingle(int k, Complex[] data, bool reverse)
    {
        var sign = reverse ? 1.0: -1.0;
        var transformed = Complex.Zero;
        var argument = sign*2.0*Math.PI*k/data.Length;
        for(var i = 0; i < data.Length; i++)
        {
            transformed += data[i]*Complex.FromPolarCoordinates(1, argument*i);
        }
        return transformed;
    }

Next the explaination of the rest of the code:

var sign = reverse ? 1.0: -1.0; The reversed DFT will not have -1 in the argument, while a regular DFT does have a -1 in the argument.

enter image description here

var argument = sign*2.0*Math.PI*k/data.Length; is the argument of the algorithm. This part:

enter image description here

then the last part

transformed += data[i]*Complex.FromPolarCoordinates(1, argument*i);

I think I carefully copied the algorithm, so I don't see where I made the mistake...

Additional information

As Adam Gritt has shown in his answer, there is a nice implementation of this algorithm by AForge.net. I can probably solve this problem in 30 seconds by just copying their code. However, I still don't know what I have done wrong in my implementation.

I'm really curious where my flaw is, and what I have interpreted wrong.

Timo Willemsen
  • 8,717
  • 9
  • 51
  • 82

2 Answers2

5

My days of doing complex mathematics are a ways behind me right now so I may be missing something myself. However, it appears to me that you are doing the following line:

transformed += data[i]*Complex.FromPolarCoordinates(1, argument*i);

when it should probably be more like:

transformed += data[i]*Math.Pow(Math.E, Complex.FromPolarCoordinates(1, argument*i));

Unless you have this wrapped up into the method FromPolarCoordinates()

UPDATE: I found the following bit of code in the AForge.NET Framework library and it shows additional Cos/Sin operations being done that are not being handled in your code. This code can be found in full context in the Sources\Math\FourierTransform.cs: DFT method.

for ( int i = 0; i < n; i++ )
{
    dst[i] = Complex.Zero;

    arg = - (int) direction * 2.0 * System.Math.PI * (double) i / (double) n;

    // sum source elements
    for ( int j = 0; j < n; j++ )
    {
        cos = System.Math.Cos( j * arg );
        sin = System.Math.Sin( j * arg );

        dst[i].Re += ( data[j].Re * cos - data[j].Im * sin );
        dst[i].Im += ( data[j].Re * sin + data[j].Im * cos );
    }
}

It is using a custom Complex class (as it was pre-4.0). Most of the math is similar to what you have implemented but the inner iteration is doing additional mathematical operations on the Real and Imaginary portions.

FURTHER UPDATE: After some implementation and testing I found that the code above and the code provided in the question produce the same results. I also found, based on the comments what the difference is between what is generated from this code and what is produced by WolframAlpha. The difference in the results is that it would appear that Wolfram is applying a normalization of 1/sqrt(N) to the results. In the Wolfram Link provided if each value is multiplied by Sqrt(2) then the values are the same as those generated by the above code (rounding errors aside). I tested this by passing 3, 4, and 5 values into Wolfram and found that my results were different by Sqrt(3), Sqrt(4) and Sqrt(5) respectfully. Based on the Discrete Fourier Transform information provided by wikipedia it does mention a normalization to make the transforms for DFT and IDFT unitary. This might be the avenue that you need to look down to either modify your code or understand what Wolfram may be doing.

Adam Gritt
  • 2,654
  • 18
  • 20
  • I believe that's not going to solve it. But then again, I might be wrong xD But transformed is a complex number, and Math.Pow() returns a Real number. Also `Complex.FromPolarCoordinates` returns a new Complex number, based on the Polar Coordinates. – Timo Willemsen Apr 19 '11 at 12:18
  • Also as you can see it in here, we shouldn't do anything with e, because it's just a different way of writing a complex number (euler's formula) http://upload.wikimedia.org/math/1/2/6/126e6896f450adebaacd498f206bfd4e.png – Timo Willemsen Apr 19 '11 at 12:20
  • Ah good old Euler. Like I said, my complex math skills are a ways behind me. Based on Euler's Formula e^ix = cos x + i sin x and http://en.wikipedia.org/wiki/Polar_coordinate_system, it seems that perhaps you should be doing Complex.FromPolarCoordinates(argument*i, argument*i). Without seeing the code inside FromPolarCoordinates to get a complete picture of the other transformations on the data I don't know what else could be wrong. If this doesn't help I am sorry. Truth be told I haven't work with Polars for 20+ years. – Adam Gritt Apr 19 '11 at 12:33
  • Ah I see, but Complex.FromPolarCoordinates is just a function from the Complex struct http://msdn.microsoft.com/en-us/library/system.numerics.complex.aspx. It just creates a complex struct, based on it's argument and magnitude. And no, thanks a lot :D You're helping me :D :D – Timo Willemsen Apr 19 '11 at 12:38
  • I think I said it wrong. You can express a complex number, in polar coordinates as following: re^iϕ with: r = magnitude, and ϕ = argument. In this case the magnitude is `1` and the argument is `argument*i`. And `FromPolarCoordinates` just returns a Complex number, that has as input the polar coordinates. – Timo Willemsen Apr 19 '11 at 12:44
  • Thanks Adam. I've also found that library. Only, that is a different way of calculating it. However, I don't know why my code isn't working. As far as I'm concerned, by code is correct too. Well I know it's not, but I can't see where it's flawed. Those `sin` `cos` functions are just to convert the polar coordinates to the real and imaginary part (thus the same as FromPolarCoordinate()) – Timo Willemsen Apr 19 '11 at 15:02
  • From looking at the code for FromPolarCoordinate in Reflector it is doing Real = 1 * Cos(argument * i), Imag = 1 * Sin(argument * i) based on your code. This is the same as the first two lines of the For-J loop. However, the calculation after that is what is different and seems to be the primary difference between the library code and your code. – Adam Gritt Apr 19 '11 at 16:26
  • Thanks for all your trouble Adam, but I'm trying to understand exactly what I'm doing. That's why I'm making this library, I mean I'm not going to be able to make the best library available. However, still if I look at my code and the algorithm, I don't see where I'm doing it wrong. – Timo Willemsen Apr 20 '11 at 06:03
  • What are you using to verify your results against that makes you believe your algorithm isn't working? – Adam Gritt Apr 20 '11 at 12:34
  • Wolfram alpha. http://www.wolframalpha.com/input/?i=FFT%5B2%2B1i%2C1%2B2i%5D produces way different values. – Timo Willemsen Apr 20 '11 at 13:28
  • Thanks :D You really helped me A LOT. You're awesome, thanks for ALL the trouble you went for me. I'm really grateful :) – Timo Willemsen Apr 21 '11 at 06:15
2

Your code is actually almost right (you are missing an 1/N on the inverse transform). The thing is, the formula you used is typically used for computations because it's lighter, but on purely theorical environments (and in Wolfram), you would use a normalization by 1/sqrt(N) to make the transforms unitary.

i.e. your formulas would be:

Xk = 1/sqrt(N) * sum(x[n] * exp(-i*2*pi/N * k*n))

x[n] = 1/sqrt(N) * sum(Xk * exp(i*2*pi/N * k*n))

It's just a matter of convention in normalisation, only amplitudes change so your results weren't that bad (if you hadn't forgotten the 1/N in the reverse transform).

Cheers

jcane86
  • 681
  • 4
  • 17
  • Hey, thanks I indeed didn't include the 1/N on the inverse transform (didn't want to make it more complicated). And I'll go test it now. And how strange, I knew about normalisation. But I didn't see it documented on wolfram alpha/mathworld :) – Timo Willemsen Apr 21 '11 at 05:57
  • Are you kidding me xD Haha, it's producing the same results as WolframAlpha now :) – Timo Willemsen Apr 21 '11 at 05:59