Skip to content

Commit 6f10b0e

Browse files
Equlnoxtrekhleb
authored andcommitted
Added Fast Fourier transform (trekhleb#135)
* Added Fast fourier transform * Adding DFT explanation * Added tests for Fast Fourier transform * Fixed some comments
1 parent 3c37ba4 commit 6f10b0e

File tree

4 files changed

+193
-0
lines changed

4 files changed

+193
-0
lines changed

Diff for: src/algorithms/math/fast-fourier-transform/README.md

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Discrete Fourier transform
2+
The Discrete Fourier transform transforms a sequence of `N` complex numbers
3+
**{x<sub>n</sub>}** := **x<sub>0</sub>, x<sub>1</sub>, x<sub>2</sub> ..., x<sub>N-1</sub>** &nbsp;&nbsp;into another sequence of complex numbers <br> **{X<sub>k</sub>}** := **X<sub>0</sub>, X<sub>1</sub>, X<sub>2</sub> ..., X<sub>N-1</sub>**&nbsp;&nbsp;&nbsp; which is defined by
4+
5+
![alt text](https://wikimedia.org/api/rest_v1/media/math/render/svg/1af0a78dc50bbf118ab6bd4c4dcc3c4ff8502223)
6+
7+
8+
## References
9+
10+
- [Wikipedia, DFT](https://www.wikiwand.com/en/Discrete_Fourier_transform)
11+
- [Wikipedia, FFT](https://www.wikiwand.com/en/Fast_Fourier_transform)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import ComplexNumber from '../complex';
2+
import fastFourierTransform from '../fastFourierTransform';
3+
/**
4+
* @param {ComplexNumber[]} [seq1]
5+
* @param {ComplexNumber[]} [seq2]
6+
* @param {Number} [eps]
7+
* @return {boolean}
8+
*/
9+
function approximatelyEqual(seq1, seq2, eps) {
10+
if (seq1.length !== seq2.length) { return false; }
11+
12+
for (let i = 0; i < seq1.length; i += 1) {
13+
if (Math.abs(seq1[i].real - seq2[i].real) > eps) { return false; }
14+
if (Math.abs(seq1[i].complex - seq2[i].complex) > eps) { return false; }
15+
}
16+
17+
return true;
18+
}
19+
20+
describe('fastFourierTransform', () => {
21+
it('should calculate the radix-2 discrete fourier transform after zero padding', () => {
22+
const eps = 1e-6;
23+
const in1 = [new ComplexNumber(0, 0)];
24+
const expOut1 = [new ComplexNumber(0, 0)];
25+
const out1 = fastFourierTransform(in1);
26+
const invOut1 = fastFourierTransform(out1, true);
27+
expect(approximatelyEqual(expOut1, out1, eps)).toBe(true);
28+
expect(approximatelyEqual(in1, invOut1, eps)).toBe(true);
29+
30+
const in2 = [new ComplexNumber(1, 2), new ComplexNumber(2, 3),
31+
new ComplexNumber(8, 4)];
32+
const expOut2 = [new ComplexNumber(11, 9), new ComplexNumber(-10, 0),
33+
new ComplexNumber(7, 3), new ComplexNumber(-4, -4)];
34+
const out2 = fastFourierTransform(in2);
35+
const invOut2 = fastFourierTransform(out2, true);
36+
expect(approximatelyEqual(expOut2, out2, eps)).toBe(true);
37+
expect(approximatelyEqual(in2, invOut2, eps)).toBe(true);
38+
39+
const in3 = [new ComplexNumber(-83656.9359385182, 98724.08038374918),
40+
new ComplexNumber(-47537.415125808424, 88441.58381765135),
41+
new ComplexNumber(-24849.657029355192, -72621.79007878687),
42+
new ComplexNumber(31451.27290052717, -21113.301128347346),
43+
new ComplexNumber(13973.90836288876, -73378.36721594246),
44+
new ComplexNumber(14981.520420492234, 63279.524958963884),
45+
new ComplexNumber(-9892.575367044381, -81748.44671677813),
46+
new ComplexNumber(-35933.00356823792, -46153.47157161784),
47+
new ComplexNumber(-22425.008561855735, -86284.24507370662),
48+
new ComplexNumber(-39327.43830818355, 30611.949874562706)];
49+
const expOut3 = [new ComplexNumber(-203215.3322151, -100242.4827503),
50+
new ComplexNumber(99217.0805705, 270646.9331932),
51+
new ComplexNumber(-305990.9040412, 68224.8435751),
52+
new ComplexNumber(-14135.7758282, 199223.9878095),
53+
new ComplexNumber(-306965.6350922, 26030.1025439),
54+
new ComplexNumber(-76477.6755206, 40781.9078990),
55+
new ComplexNumber(-48409.3099088, 54674.7959662),
56+
new ComplexNumber(-329683.0131713, 164287.7995937),
57+
new ComplexNumber(-50485.2048527, -330375.0546527),
58+
new ComplexNumber(122235.7738708, 91091.6398019),
59+
new ComplexNumber(47625.8850387, 73497.3981523),
60+
new ComplexNumber(-15619.8231136, 80804.8685410),
61+
new ComplexNumber(192234.0276101, 160833.3072355),
62+
new ComplexNumber(-96389.4195635, 393408.4543872),
63+
new ComplexNumber(-173449.0825417, 146875.7724104),
64+
new ComplexNumber(-179002.5662573, 239821.0124341)];
65+
const out3 = fastFourierTransform(in3);
66+
const invOut3 = fastFourierTransform(out3, true);
67+
expect(approximatelyEqual(expOut3, out3, eps)).toBe(true);
68+
expect(approximatelyEqual(in3, invOut3, eps)).toBe(true);
69+
});
70+
});
+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
export default class ComplexNumber {
2+
/**
3+
* @param {Number} [real]
4+
* @param {Number} [imaginary]
5+
*/
6+
constructor(real, imaginary) {
7+
this.real = real;
8+
this.imaginary = imaginary;
9+
}
10+
11+
/**
12+
* @param {ComplexNumber} [addend]
13+
* @return {ComplexNumber}
14+
*/
15+
add(addend) {
16+
return new ComplexNumber(this.real + addend.real, this.imaginary + addend.imaginary);
17+
}
18+
19+
/**
20+
* @param {ComplexNumber} [subtrahend]
21+
* @return {ComplexNumber}
22+
*/
23+
subtract(subtrahend) {
24+
return new ComplexNumber(this.real - subtrahend.real, this.imaginary - subtrahend.imaginary);
25+
}
26+
27+
/**
28+
* @param {ComplexNumber} [multiplicand]
29+
* @return {ComplexNumber}
30+
*/
31+
multiply(multiplicand) {
32+
const real = this.real * multiplicand.real - this.imaginary * multiplicand.imaginary;
33+
const imaginary = this.real * multiplicand.imaginary + this.imaginary * multiplicand.real;
34+
return new ComplexNumber(real, imaginary);
35+
}
36+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import ComplexNumber from './complex';
2+
3+
/**
4+
* Return the no of bits used in the binary representation of input
5+
* @param {Number} [input]
6+
* @return {Number}
7+
*/
8+
function bitLength(input) {
9+
let bitlen = 0;
10+
while ((1 << bitlen) <= input) {
11+
bitlen += 1;
12+
}
13+
return bitlen;
14+
}
15+
16+
/**
17+
* Returns the number which is the flipped binary representation of input
18+
* @param {Number} [input]
19+
* @param {Number} [bitlen]
20+
* @return {Number}
21+
*/
22+
function reverseBits(input, bitlen) {
23+
let reversedBits = 0;
24+
for (let i = 0; i < bitlen; i += 1) {
25+
reversedBits *= 2;
26+
if (Math.floor(input / (1 << i)) % 2 === 1) { reversedBits += 1; }
27+
}
28+
return reversedBits;
29+
}
30+
31+
/**
32+
* Returns the radix-2 fast fourier transform of the given array
33+
* Optionally computes the radix-2 inverse fast fourier transform
34+
* @param {ComplexNumber[]} [inputData]
35+
* @param {Boolean} [inverse]
36+
* @return {ComplexNumber[]}
37+
*/
38+
export default function fastFourierTransform(inputData, inverse = false) {
39+
const bitlen = bitLength(inputData.length - 1);
40+
const N = 1 << bitlen;
41+
42+
while (inputData.length < N) { inputData.push(new ComplexNumber(0, 0)); }
43+
44+
45+
const output = [];
46+
for (let i = 0; i < N; i += 1) { output[i] = inputData[reverseBits(i, bitlen)]; }
47+
48+
for (let blockLength = 2; blockLength <= N; blockLength *= 2) {
49+
let phaseStep;
50+
if (inverse) {
51+
phaseStep = new ComplexNumber(Math.cos(2 * Math.PI / blockLength),
52+
-1 * Math.sin(2 * Math.PI / blockLength));
53+
} else {
54+
phaseStep = new ComplexNumber(Math.cos(2 * Math.PI / blockLength),
55+
Math.sin(2 * Math.PI / blockLength));
56+
}
57+
58+
for (let blockStart = 0; blockStart < N; blockStart += blockLength) {
59+
let phase = new ComplexNumber(1, 0);
60+
61+
for (let idx = blockStart; idx < blockStart + blockLength / 2; idx += 1) {
62+
const upd1 = output[idx].add(output[idx + blockLength / 2].multiply(phase));
63+
const upd2 = output[idx].subtract(output[idx + blockLength / 2].multiply(phase));
64+
output[idx] = upd1;
65+
output[idx + blockLength / 2] = upd2;
66+
phase = phase.multiply(phaseStep);
67+
}
68+
}
69+
}
70+
if (inverse) {
71+
for (let idx = 0; idx < N; idx += 1) {
72+
output[idx] /= N;
73+
}
74+
}
75+
return output;
76+
}

0 commit comments

Comments
 (0)