From d0c4baf7b3bcfb712a57c33e5daaab5e8ae05923 Mon Sep 17 00:00:00 2001 From: Oleksii Trekhleb Date: Tue, 14 Aug 2018 23:12:17 +0300 Subject: [PATCH] Add DFT. --- .../__test__/fastFourierTransform.test.js | 85 ------------------- .../README.md | 0 .../__test__/discreteFourierTransform.test.js | 9 ++ .../__test__/fastFourierTransform.test.js | 85 +++++++++++++++++++ .../discreteFourierTransform.js | 55 ++++++++++++ .../fastFourierTransform.js | 0 6 files changed, 149 insertions(+), 85 deletions(-) delete mode 100644 src/algorithms/math/fast-fourier-transform/__test__/fastFourierTransform.test.js rename src/algorithms/math/{fast-fourier-transform => fourier-transform}/README.md (100%) create mode 100644 src/algorithms/math/fourier-transform/__test__/discreteFourierTransform.test.js create mode 100644 src/algorithms/math/fourier-transform/__test__/fastFourierTransform.test.js create mode 100644 src/algorithms/math/fourier-transform/discreteFourierTransform.js rename src/algorithms/math/{fast-fourier-transform => fourier-transform}/fastFourierTransform.js (100%) diff --git a/src/algorithms/math/fast-fourier-transform/__test__/fastFourierTransform.test.js b/src/algorithms/math/fast-fourier-transform/__test__/fastFourierTransform.test.js deleted file mode 100644 index 49cda8cc..00000000 --- a/src/algorithms/math/fast-fourier-transform/__test__/fastFourierTransform.test.js +++ /dev/null @@ -1,85 +0,0 @@ -import fastFourierTransform from '../fastFourierTransform'; -import ComplexNumber from '../../complex-number/ComplexNumber'; - -/** - * @param {ComplexNumber[]} [seq1] - * @param {ComplexNumber[]} [seq2] - * @param {Number} [eps] - * @return {boolean} - */ -function approximatelyEqual(seq1, seq2, eps) { - if (seq1.length !== seq2.length) { return false; } - - for (let i = 0; i < seq1.length; i += 1) { - if (Math.abs(seq1[i].real - seq2[i].real) > eps) { return false; } - if (Math.abs(seq1[i].complex - seq2[i].complex) > eps) { return false; } - } - - return true; -} - -describe('fastFourierTransform', () => { - it('should calculate the radix-2 discrete fourier transform after zero padding', () => { - const eps = 1e-6; - const in1 = [new ComplexNumber({ real: 0, imaginary: 0 })]; - const expOut1 = [new ComplexNumber({ real: 0, imaginary: 0 })]; - const out1 = fastFourierTransform(in1); - const invOut1 = fastFourierTransform(out1, true); - expect(approximatelyEqual(expOut1, out1, eps)).toBe(true); - expect(approximatelyEqual(in1, invOut1, eps)).toBe(true); - - const in2 = [ - new ComplexNumber({ real: 1, imaginary: 2 }), - new ComplexNumber({ real: 2, imaginary: 3 }), - new ComplexNumber({ real: 8, imaginary: 4 }), - ]; - - const expOut2 = [ - new ComplexNumber({ real: 11, imaginary: 9 }), - new ComplexNumber({ real: -10, imaginary: 0 }), - new ComplexNumber({ real: 7, imaginary: 3 }), - new ComplexNumber({ real: -4, imaginary: -4 }), - ]; - const out2 = fastFourierTransform(in2); - const invOut2 = fastFourierTransform(out2, true); - expect(approximatelyEqual(expOut2, out2, eps)).toBe(true); - expect(approximatelyEqual(in2, invOut2, eps)).toBe(true); - - const in3 = [ - new ComplexNumber({ real: -83656.9359385182, imaginary: 98724.08038374918 }), - new ComplexNumber({ real: -47537.415125808424, imaginary: 88441.58381765135 }), - new ComplexNumber({ real: -24849.657029355192, imaginary: -72621.79007878687 }), - new ComplexNumber({ real: 31451.27290052717, imaginary: -21113.301128347346 }), - new ComplexNumber({ real: 13973.90836288876, imaginary: -73378.36721594246 }), - new ComplexNumber({ real: 14981.520420492234, imaginary: 63279.524958963884 }), - new ComplexNumber({ real: -9892.575367044381, imaginary: -81748.44671677813 }), - new ComplexNumber({ real: -35933.00356823792, imaginary: -46153.47157161784 }), - new ComplexNumber({ real: -22425.008561855735, imaginary: -86284.24507370662 }), - new ComplexNumber({ real: -39327.43830818355, imaginary: 30611.949874562706 }), - ]; - - const expOut3 = [ - new ComplexNumber({ real: -203215.3322151, imaginary: -100242.4827503 }), - new ComplexNumber({ real: 99217.0805705, imaginary: 270646.9331932 }), - new ComplexNumber({ real: -305990.9040412, imaginary: 68224.8435751 }), - new ComplexNumber({ real: -14135.7758282, imaginary: 199223.9878095 }), - new ComplexNumber({ real: -306965.6350922, imaginary: 26030.1025439 }), - new ComplexNumber({ real: -76477.6755206, imaginary: 40781.9078990 }), - new ComplexNumber({ real: -48409.3099088, imaginary: 54674.7959662 }), - new ComplexNumber({ real: -329683.0131713, imaginary: 164287.7995937 }), - new ComplexNumber({ real: -50485.2048527, imaginary: -330375.0546527 }), - new ComplexNumber({ real: 122235.7738708, imaginary: 91091.6398019 }), - new ComplexNumber({ real: 47625.8850387, imaginary: 73497.3981523 }), - new ComplexNumber({ real: -15619.8231136, imaginary: 80804.8685410 }), - new ComplexNumber({ real: 192234.0276101, imaginary: 160833.3072355 }), - new ComplexNumber({ real: -96389.4195635, imaginary: 393408.4543872 }), - new ComplexNumber({ real: -173449.0825417, imaginary: 146875.7724104 }), - new ComplexNumber({ real: -179002.5662573, imaginary: 239821.0124341 }), - ]; - - const out3 = fastFourierTransform(in3); - const invOut3 = fastFourierTransform(out3, true); - expect(approximatelyEqual(expOut3, out3, eps)).toBe(true); - expect(approximatelyEqual(in3, invOut3, eps)).toBe(true); - }); -}); diff --git a/src/algorithms/math/fast-fourier-transform/README.md b/src/algorithms/math/fourier-transform/README.md similarity index 100% rename from src/algorithms/math/fast-fourier-transform/README.md rename to src/algorithms/math/fourier-transform/README.md diff --git a/src/algorithms/math/fourier-transform/__test__/discreteFourierTransform.test.js b/src/algorithms/math/fourier-transform/__test__/discreteFourierTransform.test.js new file mode 100644 index 00000000..1ce96729 --- /dev/null +++ b/src/algorithms/math/fourier-transform/__test__/discreteFourierTransform.test.js @@ -0,0 +1,9 @@ +import discreteFourierTransform from '../discreteFourierTransform'; + +describe('discreteFourierTransform', () => { + it('should calculate split signal into frequencies', () => { + const frequencies = discreteFourierTransform([1, 0, 0, 0]); + + expect(frequencies).toBeDefined(); + }); +}); diff --git a/src/algorithms/math/fourier-transform/__test__/fastFourierTransform.test.js b/src/algorithms/math/fourier-transform/__test__/fastFourierTransform.test.js new file mode 100644 index 00000000..0f972a92 --- /dev/null +++ b/src/algorithms/math/fourier-transform/__test__/fastFourierTransform.test.js @@ -0,0 +1,85 @@ +import fastFourierTransform from '../fastFourierTransform'; +import ComplexNumber from '../../complex-number/ComplexNumber'; + +/** + * @param {ComplexNumber[]} [seq1] + * @param {ComplexNumber[]} [seq2] + * @param {Number} [eps] + * @return {boolean} + */ +function approximatelyEqual(seq1, seq2, eps) { + if (seq1.length !== seq2.length) { return false; } + + for (let i = 0; i < seq1.length; i += 1) { + if (Math.abs(seq1[i].real - seq2[i].real) > eps) { return false; } + if (Math.abs(seq1[i].complex - seq2[i].complex) > eps) { return false; } + } + + return true; +} + +describe('fastFourierTransform', () => { + it('should calculate the radix-2 discrete fourier transform after zero padding', () => { + const eps = 1e-6; + const in1 = [new ComplexNumber({ re: 0, im: 0 })]; + const expOut1 = [new ComplexNumber({ re: 0, im: 0 })]; + const out1 = fastFourierTransform(in1); + const invOut1 = fastFourierTransform(out1, true); + expect(approximatelyEqual(expOut1, out1, eps)).toBe(true); + expect(approximatelyEqual(in1, invOut1, eps)).toBe(true); + + const in2 = [ + new ComplexNumber({ re: 1, im: 2 }), + new ComplexNumber({ re: 2, im: 3 }), + new ComplexNumber({ re: 8, im: 4 }), + ]; + + const expOut2 = [ + new ComplexNumber({ re: 11, im: 9 }), + new ComplexNumber({ re: -10, im: 0 }), + new ComplexNumber({ re: 7, im: 3 }), + new ComplexNumber({ re: -4, im: -4 }), + ]; + const out2 = fastFourierTransform(in2); + const invOut2 = fastFourierTransform(out2, true); + expect(approximatelyEqual(expOut2, out2, eps)).toBe(true); + expect(approximatelyEqual(in2, invOut2, eps)).toBe(true); + + const in3 = [ + new ComplexNumber({ re: -83656.9359385182, im: 98724.08038374918 }), + new ComplexNumber({ re: -47537.415125808424, im: 88441.58381765135 }), + new ComplexNumber({ re: -24849.657029355192, im: -72621.79007878687 }), + new ComplexNumber({ re: 31451.27290052717, im: -21113.301128347346 }), + new ComplexNumber({ re: 13973.90836288876, im: -73378.36721594246 }), + new ComplexNumber({ re: 14981.520420492234, im: 63279.524958963884 }), + new ComplexNumber({ re: -9892.575367044381, im: -81748.44671677813 }), + new ComplexNumber({ re: -35933.00356823792, im: -46153.47157161784 }), + new ComplexNumber({ re: -22425.008561855735, im: -86284.24507370662 }), + new ComplexNumber({ re: -39327.43830818355, im: 30611.949874562706 }), + ]; + + const expOut3 = [ + new ComplexNumber({ re: -203215.3322151, im: -100242.4827503 }), + new ComplexNumber({ re: 99217.0805705, im: 270646.9331932 }), + new ComplexNumber({ re: -305990.9040412, im: 68224.8435751 }), + new ComplexNumber({ re: -14135.7758282, im: 199223.9878095 }), + new ComplexNumber({ re: -306965.6350922, im: 26030.1025439 }), + new ComplexNumber({ re: -76477.6755206, im: 40781.9078990 }), + new ComplexNumber({ re: -48409.3099088, im: 54674.7959662 }), + new ComplexNumber({ re: -329683.0131713, im: 164287.7995937 }), + new ComplexNumber({ re: -50485.2048527, im: -330375.0546527 }), + new ComplexNumber({ re: 122235.7738708, im: 91091.6398019 }), + new ComplexNumber({ re: 47625.8850387, im: 73497.3981523 }), + new ComplexNumber({ re: -15619.8231136, im: 80804.8685410 }), + new ComplexNumber({ re: 192234.0276101, im: 160833.3072355 }), + new ComplexNumber({ re: -96389.4195635, im: 393408.4543872 }), + new ComplexNumber({ re: -173449.0825417, im: 146875.7724104 }), + new ComplexNumber({ re: -179002.5662573, im: 239821.0124341 }), + ]; + + const out3 = fastFourierTransform(in3); + const invOut3 = fastFourierTransform(out3, true); + expect(approximatelyEqual(expOut3, out3, eps)).toBe(true); + expect(approximatelyEqual(in3, invOut3, eps)).toBe(true); + }); +}); diff --git a/src/algorithms/math/fourier-transform/discreteFourierTransform.js b/src/algorithms/math/fourier-transform/discreteFourierTransform.js new file mode 100644 index 00000000..1de84d01 --- /dev/null +++ b/src/algorithms/math/fourier-transform/discreteFourierTransform.js @@ -0,0 +1,55 @@ +/** + * @param {number[]} data + * @return {*[]} + */ +export default function discreteFourierTransform(data) { + const N = data.length; + const frequencies = []; + + // for every frequency... + for (let frequency = 0; frequency < N; frequency += 1) { + let re = 0; + let im = 0; + + // for every point in time... + for (let t = 0; t < N; t += 1) { + // Spin the signal _backwards_ at each frequency (as radians/s, not Hertz) + const rate = -1 * (2 * Math.PI) * frequency; + + // How far around the circle have we gone at time=t? + const time = t / N; + const distance = rate * time; + + // Data-point * e^(-i*2*pi*f) is complex, store each part. + const rePart = data[t] * Math.cos(distance); + const imPart = data[t] * Math.sin(distance); + + // add this data point's contribution + re += rePart; + im += imPart; + } + + // Close to zero? You're zero. + if (Math.abs(re) < 1e-10) { + re = 0; + } + + if (Math.abs(im) < 1e-10) { + im = 0; + } + + // Average contribution at this frequency + re /= N; + im /= N; + + frequencies[frequency] = { + re, + im, + frequency, + amp: Math.sqrt((re ** 2) + (im ** 2)), + phase: Math.atan2(im, re) * 180 / Math.PI, // in degrees + }; + } + + return frequencies; +} diff --git a/src/algorithms/math/fast-fourier-transform/fastFourierTransform.js b/src/algorithms/math/fourier-transform/fastFourierTransform.js similarity index 100% rename from src/algorithms/math/fast-fourier-transform/fastFourierTransform.js rename to src/algorithms/math/fourier-transform/fastFourierTransform.js