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