mirror of
https://github.moeyy.xyz/https://github.com/trekhleb/javascript-algorithms.git
synced 2024-12-26 07:01:18 +08:00
Add DFT.
This commit is contained in:
parent
8e66189eff
commit
d0c4baf7b3
@ -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);
|
||||
});
|
||||
});
|
@ -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();
|
||||
});
|
||||
});
|
@ -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);
|
||||
});
|
||||
});
|
@ -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;
|
||||
}
|
Loading…
Reference in New Issue
Block a user