Skip to content

Commit f5d9ef6

Browse files
authored
Add Fast Fourier Transform (rust-lang#293)
1 parent b68b831 commit f5d9ef6

File tree

4 files changed

+230
-0
lines changed

4 files changed

+230
-0
lines changed

DIRECTORY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@
6464
* [Miller Rabin](https://github.com/TheAlgorithms/Rust/blob/master/src/math/miller_rabin.rs)
6565
* [Linear Sieve](https://github.com/TheAlgorithms/Rust/blob/master/src/math/linear_sieve.rs)
6666
* [Pollard's Rho algorithm](https://github.com/TheAlgorithms/Rust/blob/master/src/math/pollard_rho.rs)
67+
* [Fast Fourier Transform](https://github.com/TheAlgorithms/Rust/blob/master/src/math/fast_fourier_transform.rs)
6768
* Searching
6869
* [Binary Search](https://github.com/TheAlgorithms/Rust/blob/master/src/searching/binary_search.rs)
6970
* [Binary Search Recursive](https://github.com/TheAlgorithms/Rust/blob/master/src/searching/binary_search_recursive.rs)

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ These are for demonstration purposes only.
4949
- [X] [Prime number](./src/math/prime_numbers.rs)
5050
- [x] [Linear Sieve](./src/math/linear_sieve.rs)
5151
- [x] [Pollard's Rho algorithm](./src/math/pollard_rho.rs)
52+
- [x] [Fast Fourier Transform](./src/math/fast_fourier_transform.rs)
5253

5354
## [Dynamic Programming](./src/dynamic_programming)
5455

src/math/fast_fourier_transform.rs

Lines changed: 223 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,223 @@
1+
use std::ops::{Add, Mul, MulAssign, Sub};
2+
3+
// f64 complex
4+
#[derive(Clone, Copy, Debug)]
5+
pub struct Complex64 {
6+
pub re: f64,
7+
pub im: f64,
8+
}
9+
10+
impl Complex64 {
11+
#[inline]
12+
pub fn new(re: f64, im: f64) -> Self {
13+
Self { re, im }
14+
}
15+
16+
#[inline]
17+
pub fn square_norm(&self) -> f64 {
18+
self.re * self.re + self.im * self.im
19+
}
20+
21+
#[inline]
22+
pub fn norm(&self) -> f64 {
23+
self.square_norm().sqrt()
24+
}
25+
26+
#[inline]
27+
pub fn inverse(&self) -> Complex64 {
28+
let nrm = self.square_norm();
29+
Complex64 {
30+
re: self.re / nrm,
31+
im: -self.im / nrm,
32+
}
33+
}
34+
}
35+
36+
impl Default for Complex64 {
37+
#[inline]
38+
fn default() -> Self {
39+
Self { re: 0.0, im: 0.0 }
40+
}
41+
}
42+
43+
impl Add<Complex64> for Complex64 {
44+
type Output = Complex64;
45+
46+
#[inline]
47+
fn add(self, other: Complex64) -> Complex64 {
48+
Complex64 {
49+
re: self.re + other.re,
50+
im: self.im + other.im,
51+
}
52+
}
53+
}
54+
55+
impl Sub<Complex64> for Complex64 {
56+
type Output = Complex64;
57+
58+
#[inline]
59+
fn sub(self, other: Complex64) -> Complex64 {
60+
Complex64 {
61+
re: self.re - other.re,
62+
im: self.im - other.im,
63+
}
64+
}
65+
}
66+
67+
impl Mul<Complex64> for Complex64 {
68+
type Output = Complex64;
69+
70+
#[inline]
71+
fn mul(self, other: Complex64) -> Complex64 {
72+
Complex64 {
73+
re: self.re * other.re - self.im * other.im,
74+
im: self.re * other.im + self.im * other.re,
75+
}
76+
}
77+
}
78+
79+
impl MulAssign<Complex64> for Complex64 {
80+
#[inline]
81+
fn mul_assign(&mut self, other: Complex64) {
82+
let tmp = self.re * other.im + self.im * other.re;
83+
self.re = self.re * other.re - self.im * other.im;
84+
self.im = tmp;
85+
}
86+
}
87+
88+
pub fn fast_fourieir_transform_input_permutation(length: usize) -> Vec<usize> {
89+
let mut result = Vec::new();
90+
result.reserve_exact(length);
91+
for i in 0..length {
92+
result.push(i);
93+
}
94+
let mut reverse = 0_usize;
95+
let mut position = 1_usize;
96+
while position < length {
97+
let mut bit = length >> 1;
98+
while bit & reverse != 0 {
99+
reverse ^= bit;
100+
bit >>= 1;
101+
}
102+
reverse ^= bit;
103+
// This is equivalent to adding 1 to a reversed number
104+
if position < reverse {
105+
// Only swap each element once
106+
result.swap(position, reverse);
107+
}
108+
position += 1;
109+
}
110+
result
111+
}
112+
113+
pub fn fast_fourier_transform(input: &[f64], input_permutation: &[usize]) -> Vec<Complex64> {
114+
let n = input.len();
115+
let mut result = Vec::new();
116+
result.reserve_exact(n);
117+
for position in input_permutation {
118+
result.push(Complex64::new(input[*position], 0.0));
119+
}
120+
let mut segment_length = 1_usize;
121+
while segment_length < n {
122+
segment_length <<= 1;
123+
let angle: f64 = std::f64::consts::TAU / segment_length as f64;
124+
let w_len = Complex64::new(angle.cos(), angle.sin());
125+
for segment_start in (0..n).step_by(segment_length) {
126+
let mut w = Complex64::new(1.0, 0.0);
127+
for position in segment_start..(segment_start + segment_length / 2) {
128+
let a = result[position];
129+
let b = result[position + segment_length / 2] * w;
130+
result[position] = a + b;
131+
result[position + segment_length / 2] = a - b;
132+
w *= w_len;
133+
}
134+
}
135+
}
136+
result
137+
}
138+
139+
pub fn inverse_fast_fourier_transform(
140+
input: &[Complex64],
141+
input_permutation: &[usize],
142+
) -> Vec<f64> {
143+
let n = input.len();
144+
let mut result = Vec::new();
145+
result.reserve_exact(n);
146+
for position in input_permutation {
147+
result.push(input[*position]);
148+
}
149+
let mut segment_length = 1_usize;
150+
while segment_length < n {
151+
segment_length <<= 1;
152+
let angle: f64 = -std::f64::consts::TAU / segment_length as f64;
153+
let w_len = Complex64::new(angle.cos(), angle.sin());
154+
for segment_start in (0..n).step_by(segment_length) {
155+
let mut w = Complex64::new(1.0, 0.0);
156+
for position in segment_start..(segment_start + segment_length / 2) {
157+
let a = result[position];
158+
let b = result[position + segment_length / 2] * w;
159+
result[position] = a + b;
160+
result[position + segment_length / 2] = a - b;
161+
w *= w_len;
162+
}
163+
}
164+
}
165+
let scale = 1.0 / n as f64;
166+
result.iter().map(|x| x.re * scale).collect()
167+
}
168+
169+
#[cfg(test)]
170+
mod tests {
171+
use super::*;
172+
fn almost_equal(a: f64, b: f64, epsilon: f64) -> bool {
173+
(a - b).abs() < epsilon
174+
}
175+
176+
const EPSILON: f64 = 1e-6;
177+
178+
#[test]
179+
fn small_polynomial_returns_self() {
180+
let polynomial = vec![1.0f64, 1.0, 0.0, 2.5];
181+
let permutation = fast_fourieir_transform_input_permutation(polynomial.len());
182+
let fft = fast_fourier_transform(&polynomial, &permutation);
183+
let ifft = inverse_fast_fourier_transform(&fft, &permutation);
184+
for (x, y) in ifft.iter().zip(polynomial.iter()) {
185+
assert!(almost_equal(*x, *y, EPSILON));
186+
}
187+
}
188+
189+
#[test]
190+
fn square_small_polynomial() {
191+
let mut polynomial = vec![1.0f64, 1.0, 0.0, 2.0];
192+
polynomial.append(&mut vec![0.0; 4]);
193+
let permutation = fast_fourieir_transform_input_permutation(polynomial.len());
194+
let mut fft = fast_fourier_transform(&polynomial, &permutation);
195+
fft.iter_mut().for_each(|num| *num *= *num);
196+
let ifft = inverse_fast_fourier_transform(&fft, &permutation);
197+
let expected = vec![1.0, 2.0, 1.0, 4.0, 4.0, 0.0, 4.0, 0.0, 0.0];
198+
for (x, y) in ifft.iter().zip(expected.iter()) {
199+
assert!(almost_equal(*x, *y, EPSILON));
200+
}
201+
}
202+
203+
#[test]
204+
#[ignore]
205+
fn square_big_polynomial() {
206+
// This test case takes ~1050ms on my machine in unoptimized mode,
207+
// but it takes ~70ms in release mode.
208+
let n = 1 << 17; // ~100_000
209+
let mut polynomial = vec![1.0f64; n];
210+
polynomial.append(&mut vec![0.0f64; n]);
211+
let permutation = fast_fourieir_transform_input_permutation(polynomial.len());
212+
let mut fft = fast_fourier_transform(&polynomial, &permutation);
213+
fft.iter_mut().for_each(|num| *num *= *num);
214+
let ifft = inverse_fast_fourier_transform(&fft, &permutation);
215+
let mut expected = vec![0.0; n << 1];
216+
for i in 0..((n << 1) - 1) {
217+
expected[i] = std::cmp::min(i + 1, (n << 1) - 1 - i) as f64;
218+
}
219+
for (x, y) in ifft.iter().zip(expected.iter()) {
220+
assert!(almost_equal(*x, *y, EPSILON));
221+
}
222+
}
223+
}

src/math/mod.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
mod extended_euclidean_algorithm;
2+
mod fast_fourier_transform;
23
mod fast_power;
34
mod greatest_common_divisor;
45
mod linear_sieve;
@@ -12,6 +13,10 @@ mod square_root;
1213
mod trial_division;
1314

1415
pub use self::extended_euclidean_algorithm::extended_euclidean_algorithm;
16+
pub use self::fast_fourier_transform::{
17+
fast_fourieir_transform_input_permutation, fast_fourier_transform,
18+
inverse_fast_fourier_transform,
19+
};
1520
pub use self::fast_power::fast_power;
1621
pub use self::greatest_common_divisor::{
1722
greatest_common_divisor_iterative, greatest_common_divisor_recursive,

0 commit comments

Comments
 (0)