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}