feanor_math/algorithms/fft/mod.rs
1use std::ops::Deref;
2
3use crate::ring::*;
4use crate::seq::*;
5
6/// Contains the implementation [`bluestein::BluesteinFFT`] of the Bluestein FFT algorithm
7/// to compute the FFT for arbitrary lengths.
8pub mod bluestein;
9/// Contains [`complex_fft::FFTErrorEstimate`] which provides estimates for the error when
10/// computing a complex-valued FFT using floating-point numbers.
11pub mod complex_fft;
12/// Contains the implementation [`cooley_tuckey::CooleyTuckeyFFT`] of the Cooley-Tukey FFT algorithm
13/// to compute the FFT for power-of-two lengths.
14pub mod cooley_tuckey;
15/// Contains the implementation [`factor_fft::GeneralCooleyTukeyFFT`] of the Cooley-Tukey algorithm
16/// to compute the FFT for lengths that are the product of two coprime factors.
17pub mod factor_fft;
18/// Contains the implementation [`radix3::CooleyTukeyRadix3FFT`] of the Cooley-Tukey FFT algorithm
19/// to compute the FFT for power-of-three lengths.
20pub mod radix3;
21
22/// Trait for objects that can perform a fast fourier transform over some ring.
23///
24/// Usually fast implementations of FFTs have to store a lot of precomputed data
25/// (e.g. powers of roots of unity), hence they should be represented as objects
26/// implementing this trait.
27///
28/// # Note on equality
29///
30/// If you choose to implement [`PartialEq`] for an FFTTable, and `F == G`, then
31/// `F` and `G` should satisfy the following properties:
32/// - `F` and `G` support the same rings
33/// - `F.len() == G.len()`
34/// - `F.root_of_unity(ring) == G.root_of_unity(ring)` for each supported ring `ring`
35/// - `F.unordered_fft_permutation(i) == G.unordered_fft_permutation(i)` for all `i`
36/// In other words, `F` and `G` must have exactly the same output for `unordered_fft`
37/// (and thus `fft`, `inv_fft`, ...) on same inputs (w.r.t. equality given by the
38/// base rings, which are equal).
39pub trait FFTAlgorithm<R: ?Sized + RingBase> {
40 /// This FFTTable can compute the FFT of arrays of this length.
41 fn len(&self) -> usize;
42
43 /// The root of unity used for the FFT. While all primitive `n`-th roots
44 /// of unity can be used equally for computing a Fourier transform, the
45 /// concrete one used determines the order of the output values.
46 ///
47 /// Note that it is standard mathematical convention to compute the forward-transform
48 /// using the inverse of the considered root of unity. Hence, if `z` is the output
49 /// of [`FFTAlgorithm::root_of_unity()`], the forward FFT [`FFTAlgorithm::fft()`]
50 /// should compute
51 /// ```text
52 /// (a_0, ..., a_(n - 1)) -> (sum_j a_j z^(-ij))_i
53 /// ```
54 ///
55 /// See also [`FFTAlgorithm::unordered_fft_permutation()`].
56 fn root_of_unity<S>(&self, ring: S) -> &R::Element
57 where
58 S: RingStore<Type = R> + Copy;
59
60 /// On input `i`, returns `j` such that `unordered_fft(values)[i]` contains the evaluation
61 /// at `z^(-j)` of values (note the `-`, which is standard convention for Fourier transforms).
62 /// Here `z` is the value returned by [`FFTAlgorithm::root_of_unity()`].
63 ///
64 /// # Example
65 /// ```text
66 /// # use feanor_math::ring::*;
67 /// # use feanor_math::rings::zn::*;
68 /// # use feanor_math::rings::zn::zn_64::*;
69 /// # use feanor_math::algorithms::*;
70 /// # use feanor_math::field::*;
71 /// # use feanor_math::algorithms::fft::*;
72 /// let ring = Zn::new(17);
73 /// let fft = cooley_tuckey::CooleyTuckeyFFT::for_zn(&ring, 3);
74 /// let (zero, one) = (ring.zero(), ring.one());
75 /// let mut values = [zero, one, one, zero, zero, zero, zero, zero];
76 /// fft.unordered_fft(&mut values, &ring);
77 /// for i in 0..8 {
78 /// let evaluation_at = ring.pow(ring.invert(fft.root_of_unity()).unwrap(), fft.unordered_fft_permutation(i));
79 /// assert_el_eq!(ring, ring.add(evaluation_at, ring.pow(evaluation_at, 2)), &values[i]);
80 /// }
81 /// ```
82 fn unordered_fft_permutation(&self, i: usize) -> usize;
83
84 /// The inverse of [`FFTAlgorithm::unordered_fft_permutation()`], i.e. for all i, have
85 /// `self.unordered_fft_permutation_inv(self.unordered_fft_permutation(i)) == i`.
86 fn unordered_fft_permutation_inv(&self, i: usize) -> usize;
87
88 /// Computes the Fourier transform of the given `values` over the specified ring.
89 /// The output is in standard order, i.e. the `i`-th output element is the evaluation
90 /// of the input at `self.root_of_unity()^(-i)` (note the `-`, which is standard
91 /// convention for Fourier transforms).
92 ///
93 /// # Panics
94 ///
95 /// This function panics if `values.len() != self.len()`.
96 ///
97 /// TODO: On next breaking release, just take slice instead of [`VectorView`]s.
98 /// This might require the user to copy the data once, but so far most algorithms copy
99 /// it anyway, because this will make subsequent memory accesses more predictable and
100 /// better optimized.
101 ///
102 /// Maybe also consider taking the ring by `&RingBase`, since this would then allow
103 /// for dynamic dispatch.
104 fn fft<V, S>(&self, mut values: V, ring: S)
105 where
106 V: SwappableVectorViewMut<R::Element>,
107 S: RingStore<Type = R> + Copy,
108 {
109 self.unordered_fft(&mut values, ring);
110 permute::permute_inv(&mut values, |i| self.unordered_fft_permutation(i));
111 }
112
113 /// Computes the Fourier transform of the given `values` over the specified ring.
114 /// The output is in standard order, i.e. the `i`-th output element is the evaluation
115 /// of the input at `self.root_of_unity()^i`, divided by `self.len()`.
116 ///
117 /// # Panics
118 ///
119 /// This function panics if `values.len() != self.len()`.
120 ///
121 /// TODO: On next breaking release, just take slice instead of [`VectorView`]s.
122 /// This might require the user to copy the data once, but so far most algorithms copy
123 /// it anyway, because this will make subsequent memory accesses more predictable and
124 /// better optimized.
125 ///
126 /// Maybe also consider taking the ring by `&RingBase`, since this would then allow
127 /// for dynamic dispatch.
128 fn inv_fft<V, S>(&self, mut values: V, ring: S)
129 where
130 V: SwappableVectorViewMut<R::Element>,
131 S: RingStore<Type = R> + Copy,
132 {
133 permute::permute(&mut values, |i| self.unordered_fft_permutation(i));
134 self.unordered_inv_fft(&mut values, ring);
135 }
136
137 /// Computes the Fourier transform of the given values, but the output values are arbitrarily
138 /// permuted (in a way compatible with [`FFTAlgorithm::unordered_inv_fft()`]).
139 ///
140 /// Note that the FFT of a sequence `a_0, ..., a_(N - 1)` is defined as `Fa_k = sum_i a_i
141 /// z^(-ik)` where `z` is an N-th root of unity.
142 ///
143 /// TODO: On next breaking release, just take slice instead of [`VectorView`]s.
144 /// This might require the user to copy the data once, but so far most algorithms copy
145 /// it anyway, because this will make subsequent memory accesses more predictable and
146 /// better optimized.
147 ///
148 /// Maybe also consider taking the ring by `&RingBase`, since this would then allow
149 /// for dynamic dispatch.
150 fn unordered_fft<V, S>(&self, values: V, ring: S)
151 where
152 V: SwappableVectorViewMut<R::Element>,
153 S: RingStore<Type = R> + Copy;
154
155 /// Inverse to [`FFTAlgorithm::unordered_fft()`], with basically the same contract.
156 ///
157 /// TODO: On next breaking release, just take slice instead of [`VectorView`]s.
158 /// This might require the user to copy the data once, but so far most algorithms copy
159 /// it anyway, because this will make subsequent memory accesses more predictable and
160 /// better optimized.
161 ///
162 /// Maybe also consider taking the ring by `&RingBase`, since this would then allow
163 /// for dynamic dispatch.
164 fn unordered_inv_fft<V, S>(&self, values: V, ring: S)
165 where
166 V: SwappableVectorViewMut<R::Element>,
167 S: RingStore<Type = R> + Copy;
168}
169
170impl<T, R> FFTAlgorithm<R> for T
171where
172 R: ?Sized + RingBase,
173 T: Deref,
174 T::Target: FFTAlgorithm<R>,
175{
176 fn len(&self) -> usize { self.deref().len() }
177
178 fn root_of_unity<S>(&self, ring: S) -> &R::Element
179 where
180 S: RingStore<Type = R> + Copy,
181 {
182 self.deref().root_of_unity(ring)
183 }
184
185 fn unordered_fft_permutation(&self, i: usize) -> usize { self.deref().unordered_fft_permutation(i) }
186
187 fn unordered_fft_permutation_inv(&self, i: usize) -> usize { self.deref().unordered_fft_permutation_inv(i) }
188
189 fn fft<V, S>(&self, values: V, ring: S)
190 where
191 V: SwappableVectorViewMut<<R as RingBase>::Element>,
192 S: RingStore<Type = R> + Copy,
193 {
194 self.deref().fft(values, ring)
195 }
196
197 fn inv_fft<V, S>(&self, values: V, ring: S)
198 where
199 V: SwappableVectorViewMut<<R as RingBase>::Element>,
200 S: RingStore<Type = R> + Copy,
201 {
202 self.deref().inv_fft(values, ring)
203 }
204
205 fn unordered_fft<V, S>(&self, values: V, ring: S)
206 where
207 V: SwappableVectorViewMut<<R as RingBase>::Element>,
208 S: RingStore<Type = R> + Copy,
209 {
210 self.deref().unordered_fft(values, ring)
211 }
212
213 fn unordered_inv_fft<V, S>(&self, values: V, ring: S)
214 where
215 V: SwappableVectorViewMut<<R as RingBase>::Element>,
216 S: RingStore<Type = R> + Copy,
217 {
218 self.deref().unordered_inv_fft(values, ring)
219 }
220}