Skip to content

Commit 0c969cb

Browse files
benchislettleios
authored andcommitted
FFT Javascript Implementation (#599)
* implement cooley tukey in javascript * Add Ben Chislett to CONTRIBUTORS.md * add newline at end of file * make approximate equality check a function in fft-js * add words to approximation confirmation in fft-js * attempt to fix bounds on fft-js markdown
1 parent 63af5ac commit 0c969cb

File tree

3 files changed

+110
-4
lines changed

3 files changed

+110
-4
lines changed

CONTRIBUTORS.md

+1
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ This file lists everyone, who contributed to this repo and wanted to show up her
4444
- Raven-Blue Dragon
4545
- Björn Heinrichs
4646
- Olav Sundfør
47+
- Ben Chislett
4748
- dovisutu
4849
- Antetokounpo
4950
- Akash Dhiman
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
const Complex = require("complex.js");
2+
3+
function dft(x) {
4+
const N = x.length;
5+
6+
// Initialize an array with N elements, filled with 0s
7+
return Array(N)
8+
.fill(new Complex(0, 0))
9+
.map((temp, i) => {
10+
// Reduce x into the sum of x_k * exp(-2*sqrt(-1)*pi*i*k/N)
11+
return x.reduce((a, b, k) => {
12+
return a.add(b.mul(new Complex(0, (-2 * Math.PI * i * k) / N).exp()));
13+
}, new Complex(0, 0)); // Start accumulating from 0
14+
});
15+
}
16+
17+
function cooley_tukey(x) {
18+
const N = x.length;
19+
const half = Math.floor(N / 2);
20+
if (N <= 1) {
21+
return x;
22+
}
23+
24+
// Extract even and odd indexed elements with remainder mod 2
25+
const evens = cooley_tukey(x.filter((_, idx) => !(idx % 2)));
26+
const odds = cooley_tukey(x.filter((_, idx) => idx % 2));
27+
28+
// Fill an array with null values
29+
let temp = Array(N).fill(null);
30+
31+
for (let i = 0; i < half; i++) {
32+
const arg = odds[i].mul(new Complex(0, (-2 * Math.PI * i) / N).exp());
33+
34+
temp[i] = evens[i].add(arg);
35+
temp[i + half] = evens[i].sub(arg);
36+
}
37+
38+
return temp;
39+
}
40+
41+
function bit_reverse_idxs(n) {
42+
if (!n) {
43+
return [0];
44+
} else {
45+
const twice = bit_reverse_idxs(n - 1).map(x => 2 * x);
46+
return twice.concat(twice.map(x => x + 1));
47+
}
48+
}
49+
50+
function bit_reverse(x) {
51+
const N = x.length;
52+
const indexes = bit_reverse_idxs(Math.log2(N));
53+
return x.map((_, i) => x[indexes[i]]);
54+
}
55+
56+
// Assumes log_2(N) is an integer
57+
function iterative_cooley_tukey(x) {
58+
const N = x.length;
59+
60+
x = bit_reverse(x);
61+
62+
for (let i = 1; i <= Math.log2(N); i++) {
63+
const stride = 2 ** i;
64+
const half = stride / 2;
65+
const w = new Complex(0, (-2 * Math.PI) / stride).exp();
66+
for (let j = 0; j < N; j += stride) {
67+
let v = new Complex(1, 0);
68+
for (let k = 0; k < half; k++) {
69+
// perform butterfly multiplication
70+
x[k + j + half] = x[k + j].sub(v.mul(x[k + j + half]));
71+
x[k + j] = x[k + j].sub(x[k + j + half].sub(x[k + j]));
72+
// accumulate v as powers of w
73+
v = v.mul(w);
74+
}
75+
}
76+
}
77+
78+
return x;
79+
}
80+
81+
// Check if two arrays of complex numbers are approximately equal
82+
function approx(x, y, tol = 1e-12) {
83+
let diff = 0;
84+
for (let i = 0; i < x.length; i++) {
85+
diff += x[i].sub(y[i]).abs();
86+
}
87+
return diff < tol;
88+
}
89+
90+
const X = Array.from(Array(8), () => new Complex(Math.random(), 0));
91+
const Y = cooley_tukey(X);
92+
const Z = iterative_cooley_tukey(X);
93+
const T = dft(X);
94+
95+
// Check if the calculations are correct within a small tolerance
96+
console.log("Cooley tukey approximation is accurate: ", approx(Y, T));
97+
console.log("Iterative cooley tukey approximation is accurate: ", approx(Z, T));

contents/cooley_tukey/cooley_tukey.md

+12-4
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,8 @@ For some reason, though, putting code to this transformation really helped me fi
8585
[import:4-13, lang:"julia"](code/julia/fft.jl)
8686
{% sample lang="asm-x64" %}
8787
[import:15-74, lang:"asm-x64"](code/asm-x64/fft.s)
88+
{% sample lang="js" %}
89+
[import:3-15, lang:"javascript"](code/javascript/fft.js)
8890
{% endmethod %}
8991

9092
In this function, we define `n` to be a set of integers from $$0 \rightarrow N-1$$ and arrange them to be a column.
@@ -101,7 +103,7 @@ M = [1.0+0.0im 1.0+0.0im 1.0+0.0im 1.0+0.0im;
101103

102104
It was amazing to me when I saw the transform for what it truly was: an actual transformation matrix!
103105
That said, the Discrete Fourier Transform is slow -- primarily because matrix multiplication is slow, and as mentioned before, slow code is not particularly useful.
104-
So what was the trick that everyone used to go from a Discrete Fourier Transform to a *Fast* Fourier Transform?
106+
So what was the trick that everyone used to go from a Discrete Fourier Transform to a _Fast_ Fourier Transform?
105107

106108
Recursion!
107109

@@ -134,6 +136,8 @@ In the end, the code looks like:
134136
[import:16-32, lang:"julia"](code/julia/fft.jl)
135137
{% sample lang="asm-x64" %}
136138
[import:76-165, lang:"asm-x64"](code/asm-x64/fft.s)
139+
{% sample lang="js" %}
140+
[import:17-39, lang="javascript"](code/javascript/fft.js)
137141
{% endmethod %}
138142

139143
As a side note, we are enforcing that the array must be a power of 2 for the operation to work.
@@ -142,6 +146,7 @@ This is a limitation of the fact that we are using recursion and dividing the ar
142146
The above method is a perfectly valid FFT; however, it is missing the pictorial heart and soul of the Cooley-Tukey algorithm: Butterfly Diagrams.
143147

144148
### Butterfly Diagrams
149+
145150
Butterfly Diagrams show where each element in the array goes before, during, and after the FFT.
146151
As mentioned, the FFT must perform a DFT.
147152
This means that even though we need to be careful about how we add elements together, we are still ultimately performing the following operation:
@@ -214,8 +219,8 @@ I'll definitely come back to this at some point, so let me know what you liked a
214219

215220
To be clear, the example code this time will be complicated and requires the following functions:
216221

217-
* An FFT library (either in-built or something like FFTW)
218-
* An approximation function to tell if two arrays are similar
222+
- An FFT library (either in-built or something like FFTW)
223+
- An approximation function to tell if two arrays are similar
219224

220225
As mentioned in the text, the Cooley-Tukey algorithm may be implemented either recursively or non-recursively, with the recursive method being much easier to implement.
221226
I would ask that you implement either the recursive or non-recursive methods (or both, if you feel so inclined).
@@ -242,9 +247,10 @@ Note: I implemented this in Julia because the code seems more straightforward in
242247
Some rather impressive scratch code was submitted by Jie and can be found here: https://scratch.mit.edu/projects/37759604/#editor
243248
{% sample lang="asm-x64" %}
244249
[import, lang:"asm-x64"](code/asm-x64/fft.s)
250+
{% sample lang="js" %}
251+
[import, lang:"javascript"](code/javascript/fft.js)
245252
{% endmethod %}
246253

247-
248254
<script>
249255
MathJax.Hub.Queue(["Typeset",MathJax.Hub]);
250256
</script>
@@ -262,6 +268,7 @@ The text of this chapter was written by [James Schloss](https://github.com/leios
262268
[<p><img class="center" src="../cc/CC-BY-SA_icon.svg" /></p>](https://creativecommons.org/licenses/by-sa/4.0/)
263269

264270
##### Images/Graphics
271+
265272
- The image "[FTexample](res/FT_example.png)" was created by [James Schloss](https://github.com/leios) and is licenced under the [Creative Commons Attribution-ShareAlike 4.0 International License](https://creativecommons.org/licenses/by-sa/4.0/legalcode).
266273
- The image "[radix2positive](res/radix-2screen_positive.jpg)" was created by [James Schloss](https://github.com/leios) and is licenced under the [Creative Commons Attribution-ShareAlike 4.0 International License](https://creativecommons.org/licenses/by-sa/4.0/legalcode).
267274
- The image "[radix2](res/radix-2screen.jpg)" was created by [James Schloss](https://github.com/leios) and is licenced under the [Creative Commons Attribution-ShareAlike 4.0 International License](https://creativecommons.org/licenses/by-sa/4.0/legalcode).
@@ -271,4 +278,5 @@ The text of this chapter was written by [James Schloss](https://github.com/leios
271278
##### Pull Requests
272279

273280
After initial licensing ([#560](https://github.com/algorithm-archivists/algorithm-archive/pull/560)), the following pull requests have modified the text or graphics of this chapter:
281+
274282
- none

0 commit comments

Comments
 (0)