This commit is contained in:
Oleksii Trekhleb 2018-08-14 23:12:17 +03:00
parent 8e66189eff
commit d0c4baf7b3
6 changed files with 149 additions and 85 deletions

View File

@ -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);
});
});

View File

@ -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();
});
});

View File

@ -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);
});
});

View File

@ -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;
}