Skip to main content

ark_poly/domain/
mod.rs

1//! This module contains an `EvaluationDomain` abstraction for
2//! performing various kinds of polynomial arithmetic on top of
3//! fields that are friendly to fast-fourier-transforms (FFTs).
4//!
5//! A field is FFT-friendly if it contains enough
6//! roots of unity to perform the FFT in O(n log n) time.
7//! These roots of unity comprise the domain over which
8//! polynomial arithmetic is performed.
9
10use ark_ff::{FftField, Zero};
11use ark_serialize::{CanonicalDeserialize, CanonicalSerialize};
12use ark_std::{fmt, hash, rand::Rng, vec, vec::*};
13
14#[cfg(feature = "parallel")]
15use rayon::prelude::*;
16
17pub mod general;
18pub mod mixed_radix;
19pub mod radix2;
20pub(crate) mod utils;
21
22pub use general::GeneralEvaluationDomain;
23pub use mixed_radix::MixedRadixEvaluationDomain;
24pub use radix2::Radix2EvaluationDomain;
25
26/// Defines a domain over which finite field (I)FFTs can be performed.
27///
28/// The size of the supported FFT depends on the size of the multiplicative
29/// subgroup. For efficiency, we recommend that the field has at least one large
30/// subgroup generated by a root of unity.
31pub trait EvaluationDomain<F: FftField>:
32    Copy + Clone + hash::Hash + Eq + PartialEq + fmt::Debug + CanonicalSerialize + CanonicalDeserialize
33{
34    /// The type of the elements iterator.
35    type Elements: Iterator<Item = F> + Sized;
36
37    /// Sample an element that is *not* in the domain.
38    fn sample_element_outside_domain<R: Rng>(&self, rng: &mut R) -> F {
39        let mut t = F::rand(rng);
40        while self.evaluate_vanishing_polynomial(t).is_zero() {
41            t = F::rand(rng);
42        }
43        t
44    }
45
46    /// Construct a domain that is large enough for evaluations of a polynomial
47    /// having `num_coeffs` coefficients.
48    fn new(num_coeffs: usize) -> Option<Self>;
49
50    /// Construct a coset domain that is large enough for evaluations of a polynomial
51    /// having `num_coeffs` coefficients.
52    fn new_coset(num_coeffs: usize, offset: F) -> Option<Self> {
53        Self::new(num_coeffs)?.get_coset(offset)
54    }
55
56    /// Construct a coset domain from a subgroup domain
57    fn get_coset(&self, offset: F) -> Option<Self>;
58
59    /// Return the size of a domain that is large enough for evaluations of a
60    /// polynomial having `num_coeffs` coefficients.
61    fn compute_size_of_domain(num_coeffs: usize) -> Option<usize>;
62
63    /// Return the size of `self`.
64    fn size(&self) -> usize;
65
66    /// Return the size of `self` as a field element.
67    fn size_as_field_element(&self) -> F {
68        F::from(self.size() as u64)
69    }
70
71    /// Return log_2(size) of `self`.
72    fn log_size_of_group(&self) -> u64;
73
74    /// Return the inverse of `self.size_as_field_element()`.
75    fn size_inv(&self) -> F;
76
77    /// Return the generator for the multiplicative subgroup that defines this domain.
78    fn group_gen(&self) -> F;
79
80    /// Return the group inverse of `self.group_gen()`.
81    fn group_gen_inv(&self) -> F;
82
83    /// Return the group offset that defines this domain.
84    fn coset_offset(&self) -> F;
85
86    /// Return the inverse of `self.offset()`.
87    fn coset_offset_inv(&self) -> F;
88
89    /// Return `offset^size`.
90    fn coset_offset_pow_size(&self) -> F;
91
92    /// Compute a FFT.
93    #[inline]
94    fn fft<T: DomainCoeff<F>>(&self, coeffs: &[T]) -> Vec<T> {
95        let mut coeffs = coeffs.to_vec();
96        self.fft_in_place(&mut coeffs);
97        coeffs
98    }
99
100    /// Compute a FFT, modifying the vector in place.
101    fn fft_in_place<T: DomainCoeff<F>>(&self, coeffs: &mut Vec<T>);
102
103    /// Compute a IFFT.
104    #[inline]
105    fn ifft<T: DomainCoeff<F>>(&self, evals: &[T]) -> Vec<T> {
106        let mut evals = evals.to_vec();
107        self.ifft_in_place(&mut evals);
108        evals
109    }
110
111    /// Compute a IFFT, modifying the vector in place.
112    fn ifft_in_place<T: DomainCoeff<F>>(&self, evals: &mut Vec<T>);
113
114    /// Multiply the `i`-th element of `coeffs` with `g^i`.
115    fn distribute_powers<T: DomainCoeff<F>>(coeffs: &mut [T], g: F) {
116        Self::distribute_powers_and_mul_by_const(coeffs, g, F::one());
117    }
118
119    /// Multiply the `i`-th element of `coeffs` with `c*g^i`.
120    #[cfg(not(feature = "parallel"))]
121    fn distribute_powers_and_mul_by_const<T: DomainCoeff<F>>(coeffs: &mut [T], g: F, c: F) {
122        // invariant: pow = c*g^i at the ith iteration of the loop
123        let mut pow = c;
124        coeffs.iter_mut().for_each(|coeff| {
125            *coeff *= pow;
126            pow *= &g
127        })
128    }
129
130    /// Multiply the `i`-th element of `coeffs` with `c*g^i`.
131    #[cfg(feature = "parallel")]
132    fn distribute_powers_and_mul_by_const<T: DomainCoeff<F>>(coeffs: &mut [T], g: F, c: F) {
133        use ark_std::cmp::max;
134        let min_parallel_chunk_size = 1024;
135        let num_cpus_available = rayon::current_num_threads();
136        let num_elem_per_thread = max(coeffs.len() / num_cpus_available, min_parallel_chunk_size);
137
138        ark_std::cfg_chunks_mut!(coeffs, num_elem_per_thread)
139            .enumerate()
140            .for_each(|(i, chunk)| {
141                let offset = c * g.pow([(i * num_elem_per_thread) as u64]);
142                let mut pow = offset;
143                chunk.iter_mut().for_each(|coeff| {
144                    *coeff *= pow;
145                    pow *= &g
146                })
147            });
148    }
149
150    /// Evaluate all the lagrange polynomials defined by this domain at the
151    /// point `tau`. This is computed in time O(|domain|).
152    /// Then given the evaluations of a degree d polynomial P over this domain,
153    /// where d < |domain|, `P(tau)` can be computed as
154    /// `P(tau) = sum_{i in [|Domain|]} L_{i, Domain}(tau) * P(g^i)`.
155    /// `L_{i, Domain}` is the value of the i-th lagrange coefficient
156    /// in the returned vector.
157    fn evaluate_all_lagrange_coefficients(&self, tau: F) -> Vec<F> {
158        // Evaluate all Lagrange polynomials at tau to get the lagrange coefficients.
159        // Define the following as
160        // - H: The coset we are in, with generator g and offset h
161        // - m: The size of the coset H
162        // - Z_H: The vanishing polynomial for H. Z_H(x) = prod_{i in m} (x - hg^i) = x^m - h^m
163        // - v_i: A sequence of values, where v_0 = 1/(m * h^(m-1)), and v_{i + 1} = g * v_i
164        //
165        // We then compute L_{i,H}(tau) as `L_{i,H}(tau) = Z_H(tau) * v_i / (tau - h * g^i)`
166        //
167        // However, if tau in H, both the numerator and denominator equal 0
168        // when i corresponds to the value tau equals, and the coefficient is 0
169        // everywhere else. We handle this case separately, and we can easily
170        // detect by checking if the vanishing poly is 0.
171        let size = self.size();
172        let z_h_at_tau = self.evaluate_vanishing_polynomial(tau);
173        let offset = self.coset_offset();
174        let group_gen = self.group_gen();
175        if z_h_at_tau.is_zero() {
176            // In this case, we know that tau = hg^i, for some value i.
177            // Then i-th lagrange coefficient in this case is then simply 1,
178            // and all other lagrange coefficients are 0.
179            // Thus we find i by brute force.
180            let mut u = vec![F::zero(); size];
181            let mut omega_i = offset;
182            for u_i in u.iter_mut().take(size) {
183                if omega_i == tau {
184                    *u_i = F::one();
185                    break;
186                }
187                omega_i *= &group_gen;
188            }
189            u
190        } else {
191            // In this case we have to compute `Z_H(tau) * v_i / (tau - h g^i)`
192            // for i in 0..size
193            // We actually compute this by computing (Z_H(tau) * v_i)^{-1} * (tau - h g^i)
194            // and then batch inverting to get the correct lagrange coefficients.
195            // We let `l_i = (Z_H(tau) * v_i)^-1` and `r_i = tau - h g^i`
196            // Notice that since Z_H(tau) is i-independent,
197            // and v_i = g * v_{i-1}, it follows that
198            // l_i = g^-1 * l_{i-1}
199            // TODO: consider caching the computation of l_i to save N multiplications
200            use ark_ff::fields::batch_inversion;
201
202            let group_gen_inv = self.group_gen_inv();
203
204            // v_0_inv = m * h^(m-1)
205            let v_0_inv = self.size_as_field_element() * offset.pow([size as u64 - 1]);
206            let mut l_i = z_h_at_tau.inverse().unwrap() * v_0_inv;
207            let mut negative_cur_elem = -offset;
208            let mut lagrange_coefficients_inverse = vec![F::zero(); size];
209            for coeff in &mut lagrange_coefficients_inverse {
210                let r_i = tau + negative_cur_elem;
211                *coeff = l_i * r_i;
212                // Increment l_i and negative_cur_elem
213                l_i *= &group_gen_inv;
214                negative_cur_elem *= &group_gen;
215            }
216
217            // Invert the lagrange coefficients inverse, to get the actual coefficients,
218            // and return these
219            batch_inversion(lagrange_coefficients_inverse.as_mut_slice());
220            lagrange_coefficients_inverse
221        }
222    }
223
224    /// Return the sparse vanishing polynomial.
225    fn vanishing_polynomial(&self) -> crate::univariate::SparsePolynomial<F> {
226        let constant_coeff = self.coset_offset_pow_size();
227        let coeffs = vec![(0, -constant_coeff), (self.size(), F::one())];
228        crate::univariate::SparsePolynomial::from_coefficients_vec(coeffs)
229    }
230
231    /// This evaluates the vanishing polynomial for this domain at tau.
232    fn evaluate_vanishing_polynomial(&self, tau: F) -> F {
233        // TODO: Consider precomputed exponentiation tables if we need this to be
234        // faster.
235        tau.pow([self.size() as u64]) - self.coset_offset_pow_size()
236    }
237
238    /// Return the filter polynomial of `self` with respect to the subdomain `subdomain`.
239    /// Assumes that `subdomain` is contained within `self`.
240    ///
241    /// # Panics
242    ///
243    /// Panics if `subdomain` is not contained within `self`.
244    fn filter_polynomial(&self, subdomain: &Self) -> crate::univariate::DensePolynomial<F> {
245        use crate::univariate::DenseOrSparsePolynomial;
246        let self_vanishing_poly = DenseOrSparsePolynomial::from(
247            &self.vanishing_polynomial()
248                * (subdomain.size_as_field_element()
249                    * subdomain.coset_offset().pow([subdomain.size() as u64])),
250        );
251        let subdomain_vanishing_poly = DenseOrSparsePolynomial::from(
252            &subdomain.vanishing_polynomial() * self.size_as_field_element(),
253        );
254        let (quotient, remainder) = self_vanishing_poly
255            .divide_with_q_and_r(&subdomain_vanishing_poly)
256            .unwrap();
257        assert!(remainder.is_zero());
258        quotient
259    }
260
261    /// This evaluates at `tau` the filter polynomial for `self` with respect
262    /// to the subdomain `subdomain`.
263    fn evaluate_filter_polynomial(&self, subdomain: &Self, tau: F) -> F {
264        let v_subdomain_of_tau = subdomain.evaluate_vanishing_polynomial(tau);
265        if v_subdomain_of_tau.is_zero() {
266            F::one()
267        } else {
268            subdomain.size_as_field_element() * self.evaluate_vanishing_polynomial(tau)
269                / (self.size_as_field_element() * v_subdomain_of_tau)
270        }
271    }
272
273    /// Returns the `i`-th element of the domain.
274    fn element(&self, i: usize) -> F {
275        let mut result = self.group_gen().pow([i as u64]);
276        if !self.coset_offset().is_one() {
277            result *= self.coset_offset()
278        }
279        result
280    }
281
282    /// Return an iterator over the elements of the domain.
283    fn elements(&self) -> Self::Elements;
284
285    /// Given an index which assumes the first elements of this domain are the
286    /// elements of another (sub)domain,
287    /// this returns the actual index into this domain.
288    fn reindex_by_subdomain(&self, other: Self, index: usize) -> usize {
289        assert!(self.size() >= other.size());
290        // Let this subgroup be G, and the subgroup we're re-indexing by be S.
291        // Since its a subgroup, the 0th element of S is at index 0 in G, the first
292        // element of S is at index |G|/|S|, the second at 2*|G|/|S|, etc.
293        // Thus for an index i that corresponds to S, the index in G is i*|G|/|S|
294        let period = self.size() / other.size();
295        if index < other.size() {
296            index * period
297        } else {
298            // Let i now be the index of this element in G \ S
299            // Let x be the number of elements in G \ S, for every element in S. Then x =
300            // (|G|/|S| - 1). At index i in G \ S, the number of elements in S
301            // that appear before the index in G to which i corresponds to, is
302            // floor(i / x) + 1. The +1 is because index 0 of G is S_0, so the
303            // position is offset by at least one. The floor(i / x) term is
304            // because after x elements in G \ S, there is one more element from S
305            // that will have appeared in G.
306            let i = index - other.size();
307            let x = period - 1;
308            i + (i / x) + 1
309        }
310    }
311
312    /// Perform O(n) multiplication of two polynomials that are presented by
313    /// their evaluations in the domain.
314    /// Returns the evaluations of the product over the domain.
315    ///
316    /// Assumes that the domain is large enough to allow for successful
317    /// interpolation after multiplication.
318    #[must_use]
319    fn mul_polynomials_in_evaluation_domain(&self, self_evals: &[F], other_evals: &[F]) -> Vec<F> {
320        assert_eq!(self_evals.len(), other_evals.len());
321        let mut result = self_evals.to_vec();
322
323        ark_std::cfg_iter_mut!(result)
324            .zip(other_evals)
325            .for_each(|(a, b)| *a *= b);
326
327        result
328    }
329}
330
331/// Types that can be FFT-ed must implement this trait.
332pub trait DomainCoeff<F: FftField>:
333    Copy
334    + Send
335    + Sync
336    + core::ops::Add<Output = Self>
337    + core::ops::Sub<Output = Self>
338    + core::ops::AddAssign
339    + core::ops::SubAssign
340    + ark_ff::Zero
341    + core::ops::MulAssign<F>
342    + core::fmt::Debug
343    + PartialEq
344{
345}
346
347impl<T, F> DomainCoeff<F> for T
348where
349    F: FftField,
350    T: Copy
351        + Send
352        + Sync
353        + core::ops::Add<Output = Self>
354        + core::ops::Sub<Output = Self>
355        + core::ops::AddAssign
356        + core::ops::SubAssign
357        + ark_ff::Zero
358        + core::ops::MulAssign<F>
359        + core::fmt::Debug
360        + PartialEq,
361{
362}