Skip to content

Commit d0c4baf

Browse files
committed
Add DFT.
1 parent 8e66189 commit d0c4baf

File tree

6 files changed

+149
-85
lines changed

6 files changed

+149
-85
lines changed

src/algorithms/math/fast-fourier-transform/__test__/fastFourierTransform.test.js

-85
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import discreteFourierTransform from '../discreteFourierTransform';
2+
3+
describe('discreteFourierTransform', () => {
4+
it('should calculate split signal into frequencies', () => {
5+
const frequencies = discreteFourierTransform([1, 0, 0, 0]);
6+
7+
expect(frequencies).toBeDefined();
8+
});
9+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import fastFourierTransform from '../fastFourierTransform';
2+
import ComplexNumber from '../../complex-number/ComplexNumber';
3+
4+
/**
5+
* @param {ComplexNumber[]} [seq1]
6+
* @param {ComplexNumber[]} [seq2]
7+
* @param {Number} [eps]
8+
* @return {boolean}
9+
*/
10+
function approximatelyEqual(seq1, seq2, eps) {
11+
if (seq1.length !== seq2.length) { return false; }
12+
13+
for (let i = 0; i < seq1.length; i += 1) {
14+
if (Math.abs(seq1[i].real - seq2[i].real) > eps) { return false; }
15+
if (Math.abs(seq1[i].complex - seq2[i].complex) > eps) { return false; }
16+
}
17+
18+
return true;
19+
}
20+
21+
describe('fastFourierTransform', () => {
22+
it('should calculate the radix-2 discrete fourier transform after zero padding', () => {
23+
const eps = 1e-6;
24+
const in1 = [new ComplexNumber({ re: 0, im: 0 })];
25+
const expOut1 = [new ComplexNumber({ re: 0, im: 0 })];
26+
const out1 = fastFourierTransform(in1);
27+
const invOut1 = fastFourierTransform(out1, true);
28+
expect(approximatelyEqual(expOut1, out1, eps)).toBe(true);
29+
expect(approximatelyEqual(in1, invOut1, eps)).toBe(true);
30+
31+
const in2 = [
32+
new ComplexNumber({ re: 1, im: 2 }),
33+
new ComplexNumber({ re: 2, im: 3 }),
34+
new ComplexNumber({ re: 8, im: 4 }),
35+
];
36+
37+
const expOut2 = [
38+
new ComplexNumber({ re: 11, im: 9 }),
39+
new ComplexNumber({ re: -10, im: 0 }),
40+
new ComplexNumber({ re: 7, im: 3 }),
41+
new ComplexNumber({ re: -4, im: -4 }),
42+
];
43+
const out2 = fastFourierTransform(in2);
44+
const invOut2 = fastFourierTransform(out2, true);
45+
expect(approximatelyEqual(expOut2, out2, eps)).toBe(true);
46+
expect(approximatelyEqual(in2, invOut2, eps)).toBe(true);
47+
48+
const in3 = [
49+
new ComplexNumber({ re: -83656.9359385182, im: 98724.08038374918 }),
50+
new ComplexNumber({ re: -47537.415125808424, im: 88441.58381765135 }),
51+
new ComplexNumber({ re: -24849.657029355192, im: -72621.79007878687 }),
52+
new ComplexNumber({ re: 31451.27290052717, im: -21113.301128347346 }),
53+
new ComplexNumber({ re: 13973.90836288876, im: -73378.36721594246 }),
54+
new ComplexNumber({ re: 14981.520420492234, im: 63279.524958963884 }),
55+
new ComplexNumber({ re: -9892.575367044381, im: -81748.44671677813 }),
56+
new ComplexNumber({ re: -35933.00356823792, im: -46153.47157161784 }),
57+
new ComplexNumber({ re: -22425.008561855735, im: -86284.24507370662 }),
58+
new ComplexNumber({ re: -39327.43830818355, im: 30611.949874562706 }),
59+
];
60+
61+
const expOut3 = [
62+
new ComplexNumber({ re: -203215.3322151, im: -100242.4827503 }),
63+
new ComplexNumber({ re: 99217.0805705, im: 270646.9331932 }),
64+
new ComplexNumber({ re: -305990.9040412, im: 68224.8435751 }),
65+
new ComplexNumber({ re: -14135.7758282, im: 199223.9878095 }),
66+
new ComplexNumber({ re: -306965.6350922, im: 26030.1025439 }),
67+
new ComplexNumber({ re: -76477.6755206, im: 40781.9078990 }),
68+
new ComplexNumber({ re: -48409.3099088, im: 54674.7959662 }),
69+
new ComplexNumber({ re: -329683.0131713, im: 164287.7995937 }),
70+
new ComplexNumber({ re: -50485.2048527, im: -330375.0546527 }),
71+
new ComplexNumber({ re: 122235.7738708, im: 91091.6398019 }),
72+
new ComplexNumber({ re: 47625.8850387, im: 73497.3981523 }),
73+
new ComplexNumber({ re: -15619.8231136, im: 80804.8685410 }),
74+
new ComplexNumber({ re: 192234.0276101, im: 160833.3072355 }),
75+
new ComplexNumber({ re: -96389.4195635, im: 393408.4543872 }),
76+
new ComplexNumber({ re: -173449.0825417, im: 146875.7724104 }),
77+
new ComplexNumber({ re: -179002.5662573, im: 239821.0124341 }),
78+
];
79+
80+
const out3 = fastFourierTransform(in3);
81+
const invOut3 = fastFourierTransform(out3, true);
82+
expect(approximatelyEqual(expOut3, out3, eps)).toBe(true);
83+
expect(approximatelyEqual(in3, invOut3, eps)).toBe(true);
84+
});
85+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/**
2+
* @param {number[]} data
3+
* @return {*[]}
4+
*/
5+
export default function discreteFourierTransform(data) {
6+
const N = data.length;
7+
const frequencies = [];
8+
9+
// for every frequency...
10+
for (let frequency = 0; frequency < N; frequency += 1) {
11+
let re = 0;
12+
let im = 0;
13+
14+
// for every point in time...
15+
for (let t = 0; t < N; t += 1) {
16+
// Spin the signal _backwards_ at each frequency (as radians/s, not Hertz)
17+
const rate = -1 * (2 * Math.PI) * frequency;
18+
19+
// How far around the circle have we gone at time=t?
20+
const time = t / N;
21+
const distance = rate * time;
22+
23+
// Data-point * e^(-i*2*pi*f) is complex, store each part.
24+
const rePart = data[t] * Math.cos(distance);
25+
const imPart = data[t] * Math.sin(distance);
26+
27+
// add this data point's contribution
28+
re += rePart;
29+
im += imPart;
30+
}
31+
32+
// Close to zero? You're zero.
33+
if (Math.abs(re) < 1e-10) {
34+
re = 0;
35+
}
36+
37+
if (Math.abs(im) < 1e-10) {
38+
im = 0;
39+
}
40+
41+
// Average contribution at this frequency
42+
re /= N;
43+
im /= N;
44+
45+
frequencies[frequency] = {
46+
re,
47+
im,
48+
frequency,
49+
amp: Math.sqrt((re ** 2) + (im ** 2)),
50+
phase: Math.atan2(im, re) * 180 / Math.PI, // in degrees
51+
};
52+
}
53+
54+
return frequencies;
55+
}

0 commit comments

Comments
 (0)