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}