halo2_axiom/poly/
domain.rs

1//! Contains utilities for performing polynomial arithmetic over an evaluation
2//! domain that is of a suitable size for the application.
3
4use crate::{
5    arithmetic::{best_fft, parallelize},
6    fft::recursive::FFTData,
7    plonk::Assigned,
8};
9
10use super::{Coeff, ExtendedLagrangeCoeff, LagrangeCoeff, Polynomial, Rotation};
11
12use group::ff::{BatchInvert, Field, WithSmallOrderMulGroup};
13
14use std::{collections::HashMap, marker::PhantomData};
15
16/// This structure contains precomputed constants and other details needed for
17/// performing operations on an evaluation domain of size $2^k$ and an extended
18/// domain of size $2^{k} * j$ with $j \neq 0$.
19#[derive(Clone, Debug)]
20pub struct EvaluationDomain<F: Field> {
21    n: u64,
22    k: u32,
23    extended_k: u32,
24    omega: F,
25    omega_inv: F,
26    extended_omega: F,
27    extended_omega_inv: F,
28    g_coset: F,
29    g_coset_inv: F,
30    quotient_poly_degree: u64,
31    ifft_divisor: F,
32    extended_ifft_divisor: F,
33    t_evaluations: Vec<F>,
34    barycentric_weight: F,
35
36    // Recursive stuff
37    fft_data: HashMap<usize, FFTData<F>>,
38}
39
40impl<F: WithSmallOrderMulGroup<3>> EvaluationDomain<F> {
41    /// This constructs a new evaluation domain object based on the provided
42    /// values $j, k$.
43    pub fn new(j: u32, k: u32) -> Self {
44        // quotient_poly_degree * params.n - 1 is the degree of the quotient polynomial
45        let quotient_poly_degree = (j - 1) as u64;
46
47        // n = 2^k
48        let n = 1u64 << k;
49
50        // We need to work within an extended domain, not params.k but params.k + i
51        // for some integer i such that 2^(params.k + i) is sufficiently large to
52        // describe the quotient polynomial.
53        let mut extended_k = k;
54        while (1 << extended_k) < (n * quotient_poly_degree) {
55            extended_k += 1;
56        }
57        #[cfg(feature = "profile")]
58        println!("k: {}, extended_k: {}", k, extended_k);
59
60        // ensure extended_k <= S
61        assert!(extended_k <= F::S);
62
63        let mut extended_omega = F::ROOT_OF_UNITY;
64
65        // Get extended_omega, the 2^{extended_k}'th root of unity
66        // The loop computes extended_omega = omega^{2 ^ (S - extended_k)}
67        // Notice that extended_omega ^ {2 ^ extended_k} = omega ^ {2^S} = 1.
68        for _ in extended_k..F::S {
69            extended_omega = extended_omega.square();
70        }
71        let extended_omega = extended_omega;
72
73        // Get omega, the 2^{k}'th root of unity (i.e. n'th root of unity)
74        // The loop computes omega = extended_omega ^ {2 ^ (extended_k - k)}
75        //           = (omega^{2 ^ (S - extended_k)})  ^ {2 ^ (extended_k - k)}
76        //           = omega ^ {2 ^ (S - k)}.
77        // Notice that omega ^ {2^k} = omega ^ {2^S} = 1.
78        let mut omegas = Vec::with_capacity((extended_k - k + 1) as usize);
79        let mut omega = extended_omega;
80        omegas.push(omega);
81        for _ in k..extended_k {
82            omega = omega.square();
83            omegas.push(omega);
84        }
85        let omega = omega;
86        omegas.reverse();
87        let mut omegas_inv = omegas.clone(); // Inversion computed later
88
89        // We use zeta here because we know it generates a coset, and it's available
90        // already.
91        // The coset evaluation domain is:
92        // zeta {1, extended_omega, extended_omega^2, ..., extended_omega^{(2^extended_k) - 1}}
93        let g_coset = F::ZETA;
94        let g_coset_inv = g_coset.square();
95
96        let mut t_evaluations = Vec::with_capacity(1 << (extended_k - k));
97        {
98            // Compute the evaluations of t(X) = X^n - 1 in the coset evaluation domain.
99            // We don't have to compute all of them, because it will repeat.
100            let orig = F::ZETA.pow_vartime([n]);
101            let step = extended_omega.pow_vartime([n]);
102            let mut cur = orig;
103            loop {
104                t_evaluations.push(cur);
105                cur *= &step;
106                if cur == orig {
107                    break;
108                }
109            }
110            assert_eq!(t_evaluations.len(), 1 << (extended_k - k));
111
112            // Subtract 1 from each to give us t_evaluations[i] = t(zeta * extended_omega^i)
113            for coeff in &mut t_evaluations {
114                *coeff -= &F::ONE;
115            }
116
117            // Invert, because we're dividing by this polynomial.
118            // We invert in a batch, below.
119        }
120
121        let mut ifft_divisor = F::from(1 << k); // Inversion computed later
122        let mut extended_ifft_divisor = F::from(1 << extended_k); // Inversion computed later
123
124        // The barycentric weight of 1 over the evaluation domain
125        // 1 / \prod_{i != 0} (1 - omega^i)
126        let mut barycentric_weight = F::from(n); // Inversion computed later
127
128        // Compute batch inversion
129        t_evaluations
130            .iter_mut()
131            .chain(Some(&mut ifft_divisor))
132            .chain(Some(&mut extended_ifft_divisor))
133            .chain(Some(&mut barycentric_weight))
134            .chain(&mut omegas_inv)
135            .batch_invert();
136
137        let omega_inv = omegas_inv[0];
138        let extended_omega_inv = *omegas_inv.last().unwrap();
139        let mut fft_data = HashMap::new();
140        for (i, (omega, omega_inv)) in omegas.into_iter().zip(omegas_inv).enumerate() {
141            let intermediate_k = k as usize + i;
142            let len = 1usize << intermediate_k;
143            fft_data.insert(len, FFTData::<F>::new(len, omega, omega_inv));
144        }
145
146        EvaluationDomain {
147            n,
148            k,
149            extended_k,
150            omega,
151            omega_inv,
152            extended_omega,
153            extended_omega_inv,
154            g_coset,
155            g_coset_inv,
156            quotient_poly_degree,
157            ifft_divisor,
158            extended_ifft_divisor,
159            t_evaluations,
160            barycentric_weight,
161            fft_data,
162        }
163    }
164
165    /// Obtains a polynomial in Lagrange form when given a vector of Lagrange
166    /// coefficients of size `n`; panics if the provided vector is the wrong
167    /// length.
168    pub fn lagrange_from_vec(&self, values: Vec<F>) -> Polynomial<F, LagrangeCoeff> {
169        assert_eq!(values.len(), self.n as usize);
170
171        Polynomial {
172            values,
173            _marker: PhantomData,
174        }
175    }
176
177    pub fn lagrange_assigned_from_vec(
178        &self,
179        values: Vec<Assigned<F>>,
180    ) -> Polynomial<Assigned<F>, LagrangeCoeff> {
181        assert_eq!(values.len(), self.n as usize);
182
183        Polynomial {
184            values,
185            _marker: PhantomData,
186        }
187    }
188
189    /// Obtains a polynomial in coefficient form when given a vector of
190    /// coefficients of size `n`; panics if the provided vector is the wrong
191    /// length.
192    pub fn coeff_from_vec(&self, values: Vec<F>) -> Polynomial<F, Coeff> {
193        assert_eq!(values.len(), self.n as usize);
194
195        Polynomial {
196            values,
197            _marker: PhantomData,
198        }
199    }
200
201    /// Obtains a polynomial in ExtendedLagrange form when given a vector of
202    /// Lagrange polynomials with total size `extended_n`; panics if the
203    /// provided vector is the wrong length.
204    pub fn lagrange_vec_to_extended(
205        &self,
206        values: Vec<Polynomial<F, LagrangeCoeff>>,
207    ) -> Polynomial<F, ExtendedLagrangeCoeff> {
208        assert_eq!(values.len(), self.extended_len() >> self.k);
209        assert_eq!(values[0].len(), self.n as usize);
210
211        // transpose the values in parallel
212        let mut transposed = vec![vec![F::ZERO; values.len()]; self.n as usize];
213        values.into_iter().enumerate().for_each(|(i, p)| {
214            parallelize(&mut transposed, |transposed, start| {
215                for (transposed, p) in transposed.iter_mut().zip(p.values[start..].iter()) {
216                    transposed[i] = *p;
217                }
218            });
219        });
220
221        Polynomial {
222            values: transposed.into_iter().flatten().collect(),
223            _marker: PhantomData,
224        }
225    }
226
227    /// Obtains a polynomial in ExtendedLagrange form when given a vector of
228    /// Lagrange polynomials with total size `extended_n`; panics if the
229    /// provided vector is the wrong length.
230    pub fn extended_from_lagrange_vec(
231        &self,
232        values: Vec<Polynomial<F, LagrangeCoeff>>,
233    ) -> Polynomial<F, ExtendedLagrangeCoeff> {
234        assert_eq!(values.len(), self.extended_len() >> self.k);
235        assert_eq!(values[0].len(), self.n as usize);
236
237        // transpose the values in parallel
238        let mut transposed = vec![vec![F::ZERO; values.len()]; self.n as usize];
239        values.into_iter().enumerate().for_each(|(i, p)| {
240            parallelize(&mut transposed, |transposed, start| {
241                for (transposed, p) in transposed.iter_mut().zip(p.values[start..].iter()) {
242                    transposed[i] = *p;
243                }
244            });
245        });
246
247        Polynomial {
248            values: transposed.into_iter().flatten().collect(),
249            _marker: PhantomData,
250        }
251    }
252
253    /// Returns an empty (zero) polynomial in the coefficient basis
254    pub fn empty_coeff(&self) -> Polynomial<F, Coeff> {
255        Polynomial {
256            values: vec![F::ZERO; self.n as usize],
257            _marker: PhantomData,
258        }
259    }
260
261    /// Returns an empty (zero) polynomial in the Lagrange coefficient basis
262    pub fn empty_lagrange(&self) -> Polynomial<F, LagrangeCoeff> {
263        Polynomial {
264            values: vec![F::ZERO; self.n as usize],
265            _marker: PhantomData,
266        }
267    }
268
269    /// Returns an empty (zero) polynomial in the Lagrange coefficient basis, with
270    /// deferred inversions.
271    pub(crate) fn empty_lagrange_assigned(&self) -> Polynomial<Assigned<F>, LagrangeCoeff> {
272        Polynomial {
273            values: vec![F::ZERO.into(); self.n as usize],
274            _marker: PhantomData,
275        }
276    }
277
278    /// Returns a constant polynomial in the Lagrange coefficient basis
279    pub fn constant_lagrange(&self, scalar: F) -> Polynomial<F, LagrangeCoeff> {
280        Polynomial {
281            values: vec![scalar; self.n as usize],
282            _marker: PhantomData,
283        }
284    }
285
286    /// Returns an empty (zero) polynomial in the extended Lagrange coefficient
287    /// basis
288    pub fn empty_extended(&self) -> Polynomial<F, ExtendedLagrangeCoeff> {
289        Polynomial {
290            values: vec![F::ZERO; self.extended_len()],
291            _marker: PhantomData,
292        }
293    }
294
295    /// Returns a constant polynomial in the extended Lagrange coefficient
296    /// basis
297    pub fn constant_extended(&self, scalar: F) -> Polynomial<F, ExtendedLagrangeCoeff> {
298        Polynomial {
299            values: vec![scalar; self.extended_len()],
300            _marker: PhantomData,
301        }
302    }
303
304    /// This takes us from an n-length vector into the coefficient form.
305    ///
306    /// This function will panic if the provided vector is not the correct
307    /// length.
308    pub fn lagrange_to_coeff(&self, mut a: Polynomial<F, LagrangeCoeff>) -> Polynomial<F, Coeff> {
309        assert_eq!(a.values.len(), 1 << self.k);
310
311        // Perform inverse FFT to obtain the polynomial in coefficient form
312        self.ifft(&mut a.values, self.omega_inv, self.k, self.ifft_divisor);
313
314        Polynomial {
315            values: a.values,
316            _marker: PhantomData,
317        }
318    }
319
320    /// This takes us from an n-length coefficient vector into a coset of the extended
321    /// evaluation domain, rotating by `rotation` if desired.
322    pub fn coeff_to_extended(
323        &self,
324        p: &Polynomial<F, Coeff>,
325    ) -> Polynomial<F, ExtendedLagrangeCoeff> {
326        assert_eq!(p.values.len(), 1 << self.k);
327
328        let mut a = Vec::with_capacity(self.extended_len());
329        a.extend(&p.values);
330
331        self.distribute_powers_zeta(&mut a, true);
332        a.resize(self.extended_len(), F::ZERO);
333        self.fft_inner(&mut a, self.extended_omega, self.extended_k, false);
334
335        Polynomial {
336            values: a,
337            _marker: PhantomData,
338        }
339    }
340
341    /// This takes us from an n-length coefficient vector into parts of the
342    /// extended evaluation domain. For example, for a polynomial with size n,
343    /// and an extended domain of size mn, we can compute all parts
344    /// independently, which are
345    ///     `FFT(f(zeta * X), n)`
346    ///     `FFT(f(zeta * extended_omega * X), n)`
347    ///     ...
348    ///     `FFT(f(zeta * extended_omega^{m-1} * X), n)`
349    pub fn coeff_to_extended_parts(
350        &self,
351        a: &Polynomial<F, Coeff>,
352    ) -> Vec<Polynomial<F, LagrangeCoeff>> {
353        assert_eq!(a.values.len(), 1 << self.k);
354
355        let num_parts = self.extended_len() >> self.k;
356        let mut extended_omega_factor = F::ONE;
357        (0..num_parts)
358            .map(|_| {
359                let part = self.coeff_to_extended_part(a.clone(), extended_omega_factor);
360                extended_omega_factor *= self.extended_omega;
361                part
362            })
363            .collect()
364    }
365
366    /// This takes us from several n-length coefficient vectors each into parts
367    /// of the extended evaluation domain. For example, for a polynomial with
368    /// size n, and an extended domain of size mn, we can compute all parts
369    /// independently, which are
370    ///     `FFT(f(zeta * X), n)`
371    ///     `FFT(f(zeta * extended_omega * X), n)`
372    ///     ...
373    ///     `FFT(f(zeta * extended_omega^{m-1} * X), n)`
374    pub fn batched_coeff_to_extended_parts(
375        &self,
376        a: &[Polynomial<F, Coeff>],
377    ) -> Vec<Vec<Polynomial<F, LagrangeCoeff>>> {
378        assert_eq!(a[0].values.len(), 1 << self.k);
379
380        let mut extended_omega_factor = F::ONE;
381        let num_parts = self.extended_len() >> self.k;
382        (0..num_parts)
383            .map(|_| {
384                let a_lagrange = a
385                    .iter()
386                    .map(|poly| self.coeff_to_extended_part(poly.clone(), extended_omega_factor))
387                    .collect();
388                extended_omega_factor *= self.extended_omega;
389                a_lagrange
390            })
391            .collect()
392    }
393
394    /// This takes us from an n-length coefficient vector into a part of the
395    /// extended evaluation domain. For example, for a polynomial with size n,
396    /// and an extended domain of size mn, we can compute one of the m parts
397    /// separately, which is
398    ///     `FFT(f(zeta * extended_omega_factor * X), n)`
399    /// where `extended_omega_factor` is `extended_omega^i` with `i` in `[0, m)`.
400    pub fn coeff_to_extended_part(
401        &self,
402        mut a: Polynomial<F, Coeff>,
403        extended_omega_factor: F,
404    ) -> Polynomial<F, LagrangeCoeff> {
405        assert_eq!(a.values.len(), 1 << self.k);
406
407        self.distribute_powers(&mut a.values, self.g_coset * extended_omega_factor);
408        let data = self.get_fft_data(a.len());
409        best_fft(&mut a.values, self.omega, self.k, data, false);
410
411        Polynomial {
412            values: a.values,
413            _marker: PhantomData,
414        }
415    }
416
417    /// Rotate the extended domain polynomial over the original domain.
418    pub fn rotate_extended(
419        &self,
420        poly: &Polynomial<F, ExtendedLagrangeCoeff>,
421        rotation: Rotation,
422    ) -> Polynomial<F, ExtendedLagrangeCoeff> {
423        let new_rotation = ((1 << (self.extended_k - self.k)) * rotation.0.abs()) as usize;
424
425        let mut poly = poly.clone();
426
427        if rotation.0 >= 0 {
428            poly.values.rotate_left(new_rotation);
429        } else {
430            poly.values.rotate_right(new_rotation);
431        }
432
433        poly
434    }
435
436    /// This takes us from the extended evaluation domain and gets us the
437    /// quotient polynomial coefficients.
438    ///
439    /// This function will panic if the provided vector is not the correct
440    /// length.
441    // TODO/FIXME: caller should be responsible for truncating
442    pub fn extended_to_coeff(&self, mut a: Polynomial<F, ExtendedLagrangeCoeff>) -> Vec<F> {
443        assert_eq!(a.values.len(), self.extended_len());
444
445        // Inverse FFT
446        self.ifft(
447            &mut a.values,
448            self.extended_omega_inv,
449            self.extended_k,
450            self.extended_ifft_divisor,
451        );
452
453        // Distribute powers to move from coset; opposite from the
454        // transformation we performed earlier.
455        self.distribute_powers_zeta(&mut a.values, false);
456
457        // Truncate it to match the size of the quotient polynomial; the
458        // evaluation domain might be slightly larger than necessary because
459        // it always lies on a power-of-two boundary.
460        a.values
461            .truncate((&self.n * self.quotient_poly_degree) as usize);
462
463        a.values
464    }
465
466    /// This takes us from the a list of lagrange-based polynomials with
467    /// different degrees and gets their extended lagrange-based summation.
468    pub fn lagrange_vecs_to_extended(
469        &self,
470        mut a: Vec<Vec<Polynomial<F, LagrangeCoeff>>>,
471    ) -> Polynomial<F, ExtendedLagrangeCoeff> {
472        let mut result_poly = if a[a.len() - 1].len() == 1 << (self.extended_k - self.k) {
473            self.lagrange_vec_to_extended(a.pop().unwrap())
474        } else {
475            self.empty_extended()
476        };
477
478        // Transform from each cluster of lagrange representations to coeff representations.
479        let mut ifft_divisor = self.extended_ifft_divisor;
480        let mut omega_inv = self.extended_omega_inv;
481        {
482            let mut i = a.last().unwrap().len() << self.k;
483            while i < (1 << self.extended_k) {
484                ifft_divisor = ifft_divisor + ifft_divisor;
485                omega_inv = omega_inv * omega_inv;
486                i <<= 1;
487            }
488        }
489
490        let mut result = vec![F::ZERO; 1 << self.extended_k as usize];
491        for (i, a_parts) in a.into_iter().enumerate().rev() {
492            // transpose the values in parallel
493            assert_eq!(1 << i, a_parts.len());
494            let mut a_poly: Vec<F> = {
495                let mut transposed = vec![vec![F::ZERO; a_parts.len()]; self.n as usize];
496                a_parts.into_iter().enumerate().for_each(|(j, p)| {
497                    parallelize(&mut transposed, |transposed, start| {
498                        for (transposed, p) in transposed.iter_mut().zip(p.values[start..].iter()) {
499                            transposed[j] = *p;
500                        }
501                    });
502                });
503                transposed.into_iter().flatten().collect()
504            };
505
506            self.ifft(&mut a_poly, omega_inv, self.k + i as u32, ifft_divisor);
507            ifft_divisor = ifft_divisor + ifft_divisor;
508            omega_inv = omega_inv * omega_inv;
509
510            parallelize(&mut result[0..(self.n << i) as usize], |result, start| {
511                for (other, current) in result.iter_mut().zip(a_poly[start..].iter()) {
512                    *other += current;
513                }
514            });
515        }
516        let data = self.get_fft_data(result.len());
517        best_fft(
518            &mut result,
519            self.extended_omega,
520            self.extended_k,
521            data,
522            false,
523        );
524        parallelize(&mut result_poly.values, |values, start| {
525            for (value, other) in values.iter_mut().zip(result[start..].iter()) {
526                *value += other;
527            }
528        });
529        result_poly
530    }
531
532    /// This divides the polynomial (in the extended domain) by the vanishing
533    /// polynomial of the $2^k$ size domain.
534    pub fn divide_by_vanishing_poly(
535        &self,
536        mut a: Polynomial<F, ExtendedLagrangeCoeff>,
537    ) -> Polynomial<F, ExtendedLagrangeCoeff> {
538        assert_eq!(a.values.len(), self.extended_len());
539
540        // Divide to obtain the quotient polynomial in the coset evaluation
541        // domain.
542        parallelize(&mut a.values, |h, mut index| {
543            for h in h {
544                *h *= &self.t_evaluations[index % self.t_evaluations.len()];
545                index += 1;
546            }
547        });
548
549        Polynomial {
550            values: a.values,
551            _marker: PhantomData,
552        }
553    }
554
555    /// Given a slice of group elements `[a_0, a_1, a_2, ...]`, this returns
556    /// `[a_0, [zeta]a_1, [zeta^2]a_2, a_3, [zeta]a_4, [zeta^2]a_5, a_6, ...]`,
557    /// where zeta is a cube root of unity in the multiplicative subgroup with
558    /// order (p - 1), i.e. zeta^3 = 1.
559    ///
560    /// `into_coset` should be set to `true` when moving into the coset,
561    /// and `false` when moving out. This toggles the choice of `zeta`.
562    fn distribute_powers_zeta(&self, a: &mut [F], into_coset: bool) {
563        let coset_powers = if into_coset {
564            [self.g_coset, self.g_coset_inv]
565        } else {
566            [self.g_coset_inv, self.g_coset]
567        };
568        parallelize(a, |a, mut index| {
569            for a in a {
570                // Distribute powers to move into/from coset
571                let i = index % (coset_powers.len() + 1);
572                if i != 0 {
573                    *a *= &coset_powers[i - 1];
574                }
575                index += 1;
576            }
577        });
578    }
579
580    /// Given a slice of group elements `[a_0, a_1, a_2, ...]`, this returns
581    /// `[a_0, [c]a_1, [c^2]a_2, [c^3]a_3, [c^4]a_4, ...]`,
582    ///
583    fn distribute_powers(&self, a: &mut [F], c: F) {
584        parallelize(a, |a, index| {
585            let mut c_power = c.pow_vartime([index as u64]);
586            for a in a {
587                *a *= c_power;
588                c_power *= c;
589            }
590        });
591    }
592
593    fn ifft(&self, a: &mut Vec<F>, omega_inv: F, log_n: u32, divisor: F) {
594        let fft_data = self.get_fft_data(a.len());
595        crate::fft::parallel::fft(a, omega_inv, log_n, fft_data, true);
596        // self.fft_inner(a, omega_inv, log_n, true);
597        parallelize(a, |a, _| {
598            for a in a {
599                // Finish iFFT
600                *a *= &divisor;
601            }
602        });
603    }
604
605    fn fft_inner(&self, a: &mut Vec<F>, omega: F, log_n: u32, inverse: bool) {
606        let fft_data = self.get_fft_data(a.len());
607        best_fft(a, omega, log_n, fft_data, inverse)
608    }
609
610    /// Get the size of the domain
611    pub fn k(&self) -> u32 {
612        self.k
613    }
614
615    /// Get the size of the extended domain
616    pub fn extended_k(&self) -> u32 {
617        self.extended_k
618    }
619
620    /// Get the size of the extended domain
621    pub fn extended_len(&self) -> usize {
622        1 << self.extended_k
623    }
624
625    /// Get $\omega$, the generator of the $2^k$ order multiplicative subgroup.
626    pub fn get_omega(&self) -> F {
627        self.omega
628    }
629
630    /// Get $\omega^{-1}$, the inverse of the generator of the $2^k$ order
631    /// multiplicative subgroup.
632    pub fn get_omega_inv(&self) -> F {
633        self.omega_inv
634    }
635
636    /// Get the generator of the extended domain's multiplicative subgroup.
637    pub fn get_extended_omega(&self) -> F {
638        self.extended_omega
639    }
640
641    /// Multiplies a value by some power of $\omega$, essentially rotating over
642    /// the domain.
643    pub fn rotate_omega(&self, value: F, rotation: Rotation) -> F {
644        let mut point = value;
645        if rotation.0 >= 0 {
646            point *= &self.get_omega().pow_vartime([rotation.0 as u64]);
647        } else {
648            point *= &self
649                .get_omega_inv()
650                .pow_vartime([(rotation.0 as i64).unsigned_abs()]);
651        }
652        point
653    }
654
655    /// Computes evaluations (at the point `x`, where `xn = x^n`) of Lagrange
656    /// basis polynomials `l_i(X)` defined such that `l_i(omega^i) = 1` and
657    /// `l_i(omega^j) = 0` for all `j != i` at each provided rotation `i`.
658    ///
659    /// # Implementation
660    ///
661    /// The polynomial
662    ///     $$\prod_{j=0,j \neq i}^{n - 1} (X - \omega^j)$$
663    /// has a root at all points in the domain except $\omega^i$, where it evaluates to
664    ///     $$\prod_{j=0,j \neq i}^{n - 1} (\omega^i - \omega^j)$$
665    /// and so we divide that polynomial by this value to obtain $l_i(X)$. Since
666    ///     $$\prod_{j=0,j \neq i}^{n - 1} (X - \omega^j)
667    ///       = \frac{X^n - 1}{X - \omega^i}$$
668    /// then $l_i(x)$ for some $x$ is evaluated as
669    ///     $$\left(\frac{x^n - 1}{x - \omega^i}\right)
670    ///       \cdot \left(\frac{1}{\prod_{j=0,j \neq i}^{n - 1} (\omega^i - \omega^j)}\right).$$
671    /// We refer to
672    ///     $$1 \over \prod_{j=0,j \neq i}^{n - 1} (\omega^i - \omega^j)$$
673    /// as the barycentric weight of $\omega^i$.
674    ///
675    /// We know that for $i = 0$
676    ///     $$\frac{1}{\prod_{j=0,j \neq i}^{n - 1} (\omega^i - \omega^j)} = \frac{1}{n}.$$
677    ///
678    /// If we multiply $(1 / n)$ by $\omega^i$ then we obtain
679    ///     $$\frac{1}{\prod_{j=0,j \neq 0}^{n - 1} (\omega^i - \omega^j)}
680    ///       = \frac{1}{\prod_{j=0,j \neq i}^{n - 1} (\omega^i - \omega^j)}$$
681    /// which is the barycentric weight of $\omega^i$.
682    pub fn l_i_range<I: IntoIterator<Item = i32> + Clone>(
683        &self,
684        x: F,
685        xn: F,
686        rotations: I,
687    ) -> Vec<F> {
688        let mut results;
689        {
690            let rotations = rotations.clone().into_iter();
691            results = Vec::with_capacity(rotations.size_hint().1.unwrap_or(0));
692            for rotation in rotations {
693                let rotation = Rotation(rotation);
694                let result = x - self.rotate_omega(F::ONE, rotation);
695                results.push(result);
696            }
697            results.iter_mut().batch_invert();
698        }
699
700        let common = (xn - F::ONE) * self.barycentric_weight;
701        for (rotation, result) in rotations.into_iter().zip(results.iter_mut()) {
702            let rotation = Rotation(rotation);
703            *result = self.rotate_omega(*result * common, rotation);
704        }
705
706        results
707    }
708
709    /// Gets the quotient polynomial's degree (as a multiple of n)
710    pub fn get_quotient_poly_degree(&self) -> usize {
711        self.quotient_poly_degree as usize
712    }
713
714    /// Obtain a pinned version of this evaluation domain; a structure with the
715    /// minimal parameters needed to determine the rest of the evaluation
716    /// domain.
717    pub fn pinned(&self) -> PinnedEvaluationDomain<'_, F> {
718        PinnedEvaluationDomain {
719            k: &self.k,
720            extended_k: &self.extended_k,
721            omega: &self.omega,
722        }
723    }
724
725    /// Get the private field `n`
726    pub fn get_n(&self) -> u64 {
727        self.n
728    }
729
730    /// Get the private `fft_data`
731    pub fn get_fft_data(&self, l: usize) -> &FFTData<F> {
732        self.fft_data
733            .get(&l)
734            .expect("log_2(l) must be in k..=extended_k")
735    }
736}
737
738/// Represents the minimal parameters that determine an `EvaluationDomain`.
739#[allow(dead_code)]
740#[derive(Debug)]
741pub struct PinnedEvaluationDomain<'a, F: Field> {
742    k: &'a u32,
743    extended_k: &'a u32,
744    omega: &'a F,
745}
746
747#[test]
748fn test_rotate() {
749    use rand_core::OsRng;
750
751    use crate::arithmetic::eval_polynomial;
752    use halo2curves::pasta::pallas::Scalar;
753
754    let domain = EvaluationDomain::<Scalar>::new(1, 3);
755    let rng = OsRng;
756
757    let mut poly = domain.empty_lagrange();
758    assert_eq!(poly.len(), 8);
759    for value in poly.iter_mut() {
760        *value = Scalar::random(rng);
761    }
762
763    let poly_rotated_cur = poly.rotate(Rotation::cur());
764    let poly_rotated_next = poly.rotate(Rotation::next());
765    let poly_rotated_prev = poly.rotate(Rotation::prev());
766
767    let poly = domain.lagrange_to_coeff(poly);
768    let poly_rotated_cur = domain.lagrange_to_coeff(poly_rotated_cur);
769    let poly_rotated_next = domain.lagrange_to_coeff(poly_rotated_next);
770    let poly_rotated_prev = domain.lagrange_to_coeff(poly_rotated_prev);
771
772    let x = Scalar::random(rng);
773
774    assert_eq!(
775        eval_polynomial(&poly[..], x),
776        eval_polynomial(&poly_rotated_cur[..], x)
777    );
778    assert_eq!(
779        eval_polynomial(&poly[..], x * domain.omega),
780        eval_polynomial(&poly_rotated_next[..], x)
781    );
782    assert_eq!(
783        eval_polynomial(&poly[..], x * domain.omega_inv),
784        eval_polynomial(&poly_rotated_prev[..], x)
785    );
786}
787
788#[test]
789fn test_l_i() {
790    use rand_core::OsRng;
791
792    use crate::arithmetic::{eval_polynomial, lagrange_interpolate};
793    use halo2curves::pasta::pallas::Scalar;
794    let domain = EvaluationDomain::<Scalar>::new(1, 3);
795
796    let mut l = vec![];
797    let mut points = vec![];
798    for i in 0..8 {
799        points.push(domain.omega.pow([i]));
800    }
801    for i in 0..8 {
802        let mut l_i = vec![Scalar::zero(); 8];
803        l_i[i] = Scalar::ONE;
804        let l_i = lagrange_interpolate(&points[..], &l_i[..]);
805        l.push(l_i);
806    }
807
808    let x = Scalar::random(OsRng);
809    let xn = x.pow([8]);
810
811    let evaluations = domain.l_i_range(x, xn, -7..=7);
812    for i in 0..8 {
813        assert_eq!(eval_polynomial(&l[i][..], x), evaluations[7 + i]);
814        assert_eq!(eval_polynomial(&l[(8 - i) % 8][..], x), evaluations[7 - i]);
815    }
816}
817
818#[test]
819fn test_coeff_to_extended_part() {
820    use halo2curves::pasta::pallas::Scalar;
821    use rand_core::OsRng;
822
823    let domain = EvaluationDomain::<Scalar>::new(1, 3);
824    let rng = OsRng;
825    let mut poly = domain.empty_coeff();
826    assert_eq!(poly.len(), 8);
827    for value in poly.iter_mut() {
828        *value = Scalar::random(rng);
829    }
830
831    let want = domain.coeff_to_extended(&poly);
832    let got = {
833        let parts = domain.coeff_to_extended_parts(&poly);
834        domain.lagrange_vec_to_extended(parts)
835    };
836    assert_eq!(want.values, got.values);
837}
838
839#[test]
840fn bench_coeff_to_extended_parts() {
841    use halo2curves::pasta::pallas::Scalar;
842    use rand_core::OsRng;
843    use std::time::Instant;
844
845    let k = 20;
846    let domain = EvaluationDomain::<Scalar>::new(3, k);
847    let rng = OsRng;
848    let mut poly1 = domain.empty_coeff();
849    assert_eq!(poly1.len(), 1 << k);
850
851    for value in poly1.iter_mut() {
852        *value = Scalar::random(rng);
853    }
854
855    let poly2 = poly1.clone();
856
857    let coeff_to_extended_timer = Instant::now();
858    let _ = domain.coeff_to_extended(&poly1);
859    println!(
860        "domain.coeff_to_extended time: {}s",
861        coeff_to_extended_timer.elapsed().as_secs_f64()
862    );
863
864    let coeff_to_extended_parts_timer = Instant::now();
865    let _ = domain.coeff_to_extended_parts(&poly2);
866    println!(
867        "domain.coeff_to_extended_parts time: {}s",
868        coeff_to_extended_parts_timer.elapsed().as_secs_f64()
869    );
870}
871
872#[test]
873fn test_lagrange_vecs_to_extended() {
874    use halo2curves::pasta::pallas::Scalar;
875    use rand_core::OsRng;
876
877    let rng = OsRng;
878    let domain = EvaluationDomain::<Scalar>::new(8, 10);
879    let mut poly_vec = vec![];
880    let mut poly_lagrange_vecs = vec![];
881    let mut want = domain.empty_extended();
882    let mut omega = domain.extended_omega;
883    for i in (0..(domain.extended_k - domain.k + 1)).rev() {
884        let mut poly = vec![Scalar::zero(); (1 << i) * domain.n as usize];
885        for value in poly.iter_mut() {
886            *value = Scalar::random(rng);
887        }
888        // poly under coeff representation.
889        poly_vec.push(poly.clone());
890        // poly under lagrange vector representation.
891        let mut poly2 = poly.clone();
892        let data = domain.get_fft_data(poly2.len());
893        best_fft(&mut poly2, omega, i + domain.k, data, false);
894        let transposed_poly: Vec<Polynomial<Scalar, LagrangeCoeff>> = (0..(1 << i))
895            .map(|j| {
896                let mut p = domain.empty_lagrange();
897                for k in 0..domain.n {
898                    p[k as usize] = poly2[j + (k as usize) * (1 << i)];
899                }
900                p
901            })
902            .collect();
903        poly_lagrange_vecs.push(transposed_poly);
904        // poly under extended representation.
905        poly.resize(domain.extended_len(), Scalar::zero());
906        let data = domain.get_fft_data(poly.len());
907        best_fft(
908            &mut poly,
909            domain.extended_omega,
910            domain.extended_k,
911            data,
912            false,
913        );
914        let poly = {
915            let mut p = domain.empty_extended();
916            p.values = poly;
917            p
918        };
919        want = want + &poly;
920        omega = omega * omega;
921    }
922    poly_lagrange_vecs.reverse();
923    let got = domain.lagrange_vecs_to_extended(poly_lagrange_vecs);
924    assert_eq!(want.values, got.values);
925}
926
927#[test]
928fn bench_lagrange_vecs_to_extended() {
929    use halo2curves::pasta::pallas::Scalar;
930    use rand_core::OsRng;
931    use std::time::Instant;
932
933    let rng = OsRng;
934    let domain = EvaluationDomain::<Scalar>::new(8, 10);
935    let mut poly_vec = vec![];
936    let mut poly_lagrange_vecs = vec![];
937    let mut poly_extended_vecs = vec![];
938    let mut omega = domain.extended_omega;
939
940    for i in (0..(domain.extended_k - domain.k + 1)).rev() {
941        let mut poly = vec![Scalar::zero(); (1 << i) * domain.n as usize];
942        for value in poly.iter_mut() {
943            *value = Scalar::random(rng);
944        }
945        // poly under coeff representation.
946        poly_vec.push(poly.clone());
947        // poly under lagrange vector representation.
948        let mut poly2 = poly.clone();
949        let data = domain.get_fft_data(poly2.len());
950        best_fft(&mut poly2, omega, i + domain.k, data, false);
951        let transposed_poly: Vec<Polynomial<Scalar, LagrangeCoeff>> = (0..(1 << i))
952            .map(|j| {
953                let mut p = domain.empty_lagrange();
954                for k in 0..domain.n {
955                    p[k as usize] = poly2[j + (k as usize) * (1 << i)];
956                }
957                p
958            })
959            .collect();
960        poly_lagrange_vecs.push(transposed_poly);
961        // poly under extended representation.
962        poly.resize(domain.extended_len(), Scalar::zero());
963        let data = domain.get_fft_data(poly.len());
964        best_fft(
965            &mut poly,
966            domain.extended_omega,
967            domain.extended_k,
968            data,
969            false,
970        );
971        let poly = {
972            let mut p = domain.empty_extended();
973            p.values = poly;
974            p
975        };
976        poly_extended_vecs.push(poly);
977        omega = omega * omega;
978    }
979
980    let want_timer = Instant::now();
981    let _ = poly_extended_vecs
982        .iter()
983        .fold(domain.empty_extended(), |acc, p| acc + p);
984    println!("want time: {}s", want_timer.elapsed().as_secs_f64());
985    poly_lagrange_vecs.reverse();
986    let got_timer = Instant::now();
987    let _ = domain.lagrange_vecs_to_extended(poly_lagrange_vecs);
988    println!("got time: {}s", got_timer.elapsed().as_secs_f64());
989}