-
-
Notifications
You must be signed in to change notification settings - Fork 359
FFT Javascript Implementation #599
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 3 commits
1f12dfc
389c593
2976d1b
7be14f4
fc2e7ef
af1d3b6
fe2a610
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
const Complex = require('complex.js'); | ||
|
||
function dft(x) { | ||
const N = x.length; | ||
|
||
// Initialize an array with N elements, filled with 0s | ||
return Array(N).fill(new Complex(0, 0)).map((temp, i) => { | ||
// Reduce x into the sum of x_k * exp(-2*sqrt(-1)*pi*i*k/N) | ||
return x.reduce((a, b, k) => { | ||
return a.add(b.mul(new Complex(0, -2 * Math.PI * i * k / N).exp())); | ||
}, new Complex(0, 0)); // Start accumulating from 0 | ||
}); | ||
} | ||
|
||
function cooley_tukey(x) { | ||
const N = x.length; | ||
const half = Math.floor(N / 2); | ||
if (N <= 1) { | ||
return x; | ||
} | ||
|
||
// Extract even and odd indexed elements with remainder mod 2 | ||
const evens = cooley_tukey(x.filter((_, idx) => !(idx % 2))); | ||
const odds = cooley_tukey(x.filter((_, idx) => idx % 2)); | ||
|
||
// Fill an array with null values | ||
let temp = Array(N).fill(null); | ||
|
||
for (let i = 0; i < half; i++) { | ||
const arg = odds[i].mul(new Complex(0, -2*Math.PI*i/N).exp()); | ||
|
||
temp[i] = evens[i].add(arg); | ||
temp[i + half] = evens[i].sub(arg); | ||
} | ||
|
||
return temp; | ||
} | ||
|
||
function bit_reverse_idxs(n) { | ||
if (!n) { | ||
return [0]; | ||
} else { | ||
const twice = bit_reverse_idxs(n-1).map(x => 2*x); | ||
return twice.concat(twice.map(x => x+1)); | ||
} | ||
} | ||
|
||
function bit_reverse(x) { | ||
const N = x.length; | ||
const indexes = bit_reverse_idxs(Math.log2(N)); | ||
return x.map((_, i) => x[indexes[i]]); | ||
} | ||
|
||
// Assumes log_2(N) is an integer | ||
function iterative_cooley_tukey(x) { | ||
const N = x.length; | ||
|
||
x = bit_reverse(x); | ||
|
||
for (let i = 1; i <= Math.log2(N); i++) { | ||
const stride = 2 ** i; | ||
const half = stride/2; | ||
const w = new Complex(0, -2 * Math.PI / stride).exp(); | ||
for (let j = 0; j < N; j += stride) { | ||
let v = new Complex(1, 0); | ||
for (let k = 0; k < half; k++) { | ||
// perform butterfly multiplication | ||
x[k + j + half] = x[k + j].sub(v.mul(x[k + j + half])); | ||
x[k + j] = x[k + j].sub(x[k + j + half].sub(x[k + j])); | ||
// accumulate v as powers of w | ||
v = v.mul(w); | ||
} | ||
} | ||
} | ||
|
||
return x; | ||
} | ||
|
||
const X = Array.from(Array(8), () => new Complex(Math.random(), 0)); | ||
const Y = cooley_tukey(X); | ||
const Z = iterative_cooley_tukey(X); | ||
const T = dft(X); | ||
|
||
let ydiff = 0; | ||
let zdiff = 0; | ||
for (let i = 0; i < X.length; i++) { | ||
ydiff += (Y[i].sub(T[i])).abs(); | ||
zdiff += (Z[i].sub(T[i])).abs(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In the Julia code the approximation-check has been made a seperate function, it might be a good idea to do that here as well. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For consistency or syntactically? I personally think that it's fine where it is and that another function would be too much for this simple piece of code, but I'm not set in that opinion. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it is a bit more clear in a function. At the very least, a comment would be appreciated. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done! |
||
} | ||
|
||
console.log(ydiff > 1e-12); | ||
console.log(zdiff > 1e-12); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this comment floating?
The Julia code also doesn't asume log2(n) to be an integer, does this output the same results?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a radix-2 cooley-tukey implementation if I'm not mistaken, and requires that N be a power of two. The bit reversal algorithm also operates with radix-2 so it requires that N be a power of two, and thus that log2(N) be integral.
Here in particular, the displacement array is generated, and will always be of power-of-two length. The recursive nature of the
bit_reverse_idxs
function requires that the input be a positive integer or it will infinitely recurse as n goes into the negative non-integers.