Skip to content

Commit 8e6cb6a

Browse files
authored
Add FFT in Rust (#688)
1 parent 6d02f4e commit 8e6cb6a

File tree

2 files changed

+126
-0
lines changed

2 files changed

+126
-0
lines changed

Diff for: contents/cooley_tukey/code/rust/fft.rs

+120
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
extern crate rand;
2+
extern crate rustfft;
3+
4+
use rand::prelude::*;
5+
use rustfft::num_complex::Complex;
6+
use rustfft::FFTplanner;
7+
use std::f64::consts::PI;
8+
9+
// This is based on the Python and C implementations.
10+
11+
fn fft(x: &[Complex<f64>]) -> Vec<Complex<f64>> {
12+
let n = x.len();
13+
let mut new_x = x.to_vec();
14+
let mut y = vec![Complex::new(0.0_f64, 0.0_f64); n];
15+
16+
let mut planner = FFTplanner::new(false);
17+
let this_fft = planner.plan_fft(n);
18+
this_fft.process(new_x.as_mut_slice(), y.as_mut_slice());
19+
20+
// y.into_iter().map(|i| i / (n as f64).sqrt()).collect()
21+
y
22+
}
23+
24+
fn dft(x: &[Complex<f64>]) -> Vec<Complex<f64>> {
25+
let n = x.len();
26+
(0..n)
27+
.map(|i| {
28+
(0..n)
29+
.map(|k| {
30+
x[k] * (Complex::new(0.0_f64, -2.0_f64) * PI * (i as f64) * (k as f64)
31+
/ (n as f64))
32+
.exp()
33+
})
34+
.sum()
35+
})
36+
.collect()
37+
}
38+
39+
fn cooley_tukey(x: &[Complex<f64>]) -> Vec<Complex<f64>> {
40+
let n = x.len();
41+
if n <= 1 {
42+
return x.to_owned();
43+
}
44+
let even = cooley_tukey(&x.iter().step_by(2).cloned().collect::<Vec<_>>());
45+
let odd = cooley_tukey(&x.iter().skip(1).step_by(2).cloned().collect::<Vec<_>>());
46+
47+
let mut temp = vec![Complex::new(0.0_f64, 0.0_f64); n];
48+
for k in 0..(n / 2) {
49+
temp[k] = even[k]
50+
+ (Complex::new(0.0_f64, -2.0_f64) * PI * (k as f64) / (n as f64)).exp() * odd[k];
51+
temp[k + n / 2] = even[k]
52+
- (Complex::new(0.0_f64, -2.0_f64) * PI * (k as f64) / (n as f64)).exp() * odd[k];
53+
}
54+
temp
55+
}
56+
57+
fn bit_reverse(x: &[Complex<f64>]) -> Vec<Complex<f64>> {
58+
let n = x.len();
59+
let mut temp = vec![Complex::new(0.0_f64, 0.0_f64); n];
60+
for k in 0..n {
61+
let b: usize = (0..((n as f64).log2() as usize))
62+
.filter(|i| k >> i & 1 != 0)
63+
.map(|i| 1 << ((((n as f64).log2()) as usize) - 1 - i))
64+
.sum();
65+
temp[k] = x[b];
66+
temp[b] = x[k];
67+
}
68+
temp
69+
}
70+
71+
fn iterative_cooley_tukey(x: &[Complex<f64>]) -> Vec<Complex<f64>> {
72+
let n = x.len();
73+
74+
let mut new_x = bit_reverse(x);
75+
76+
for i in 1..=((n as f64).log2() as usize) {
77+
let stride = 2_u128.pow(i as u32);
78+
let w = (Complex::new(0.0_f64, -2.0_f64) * PI / (stride as f64)).exp();
79+
for j in (0..n).step_by(stride as usize) {
80+
let mut v = Complex::new(1.0_f64, 0.0_f64);
81+
for k in 0..((stride / 2) as usize) {
82+
new_x[k + j + ((stride / 2) as usize)] =
83+
new_x[k + j] - v * new_x[k + j + ((stride / 2) as usize)];
84+
new_x[k + j] =
85+
new_x[k + j] - (new_x[k + j + ((stride / 2) as usize)] - new_x[k + j]);
86+
v *= w;
87+
}
88+
}
89+
}
90+
91+
new_x
92+
}
93+
94+
fn main() {
95+
let mut x = Vec::with_capacity(64);
96+
let mut rng = thread_rng();
97+
for _i in 0..64 {
98+
let real = rng.gen_range(0.0_f64, 1.0_f64);
99+
x.push(Complex::new(real, 0.0_f64));
100+
}
101+
let v = fft(&x);
102+
let y = cooley_tukey(&x);
103+
let z = iterative_cooley_tukey(&x);
104+
let t = dft(&x);
105+
106+
println!(
107+
"{}",
108+
v.iter().zip(y.iter()).all(|i| (i.0 - i.1).norm() < 1.0)
109+
);
110+
println!(
111+
"{}",
112+
v.iter().zip(z.iter()).all(|i| (i.0 - i.1).norm() < 1.0)
113+
);
114+
println!(
115+
"{}",
116+
v.iter()
117+
.zip(t.into_iter())
118+
.all(|i| (i.0 - i.1).norm() < 1.0)
119+
);
120+
}

Diff for: contents/cooley_tukey/cooley_tukey.md

+6
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,8 @@ For some reason, though, putting code to this transformation really helped me fi
8787
[import:15-74, lang:"asm-x64"](code/asm-x64/fft.s)
8888
{% sample lang="js" %}
8989
[import:3-15, lang:"javascript"](code/javascript/fft.js)
90+
{% sample lang="rs" %}
91+
[import:24-37, lang:"rust"](code/rust/fft.rs)
9092
{% endmethod %}
9193

9294
In this function, we define `n` to be a set of integers from $$0 \rightarrow N-1$$ and arrange them to be a column.
@@ -138,6 +140,8 @@ In the end, the code looks like:
138140
[import:76-165, lang:"asm-x64"](code/asm-x64/fft.s)
139141
{% sample lang="js" %}
140142
[import:17-39, lang="javascript"](code/javascript/fft.js)
143+
{% sample lang="rs" %}
144+
[import:39-55, lang:"rust"](code/rust/fft.rs)
141145
{% endmethod %}
142146

143147
As a side note, we are enforcing that the array must be a power of 2 for the operation to work.
@@ -249,6 +253,8 @@ Some rather impressive scratch code was submitted by Jie and can be found here:
249253
[import, lang:"asm-x64"](code/asm-x64/fft.s)
250254
{% sample lang="js" %}
251255
[import, lang:"javascript"](code/javascript/fft.js)
256+
{% sample lang="rs" %}
257+
[import, lang:"rust"](code/rust/fft.rs)
252258
{% endmethod %}
253259

254260
<script>

0 commit comments

Comments
 (0)