twenty_first/math/
polynomial.rs

1use std::borrow::Cow;
2use std::collections::HashMap;
3use std::fmt::Debug;
4use std::fmt::Display;
5use std::fmt::Formatter;
6use std::hash::Hash;
7use std::ops::Add;
8use std::ops::AddAssign;
9use std::ops::Div;
10use std::ops::Mul;
11use std::ops::MulAssign;
12use std::ops::Neg;
13use std::ops::Rem;
14use std::ops::Sub;
15use std::thread::available_parallelism;
16
17use arbitrary::Arbitrary;
18use arbitrary::Unstructured;
19use itertools::EitherOrBoth;
20use itertools::Itertools;
21use num_traits::ConstOne;
22use num_traits::ConstZero;
23use num_traits::One;
24use num_traits::Zero;
25use rayon::prelude::*;
26
27use super::traits::PrimitiveRootOfUnity;
28use super::zerofier_tree::ZerofierTree;
29use crate::math::ntt::intt;
30use crate::math::ntt::ntt;
31use crate::math::traits::FiniteField;
32use crate::math::traits::ModPowU32;
33use crate::prelude::BFieldElement;
34use crate::prelude::Inverse;
35use crate::prelude::XFieldElement;
36
37impl<FF: FiniteField> Zero for Polynomial<'static, FF> {
38    fn zero() -> Self {
39        Self::new(vec![])
40    }
41
42    fn is_zero(&self) -> bool {
43        *self == Self::zero()
44    }
45}
46
47impl<FF: FiniteField> One for Polynomial<'static, FF> {
48    fn one() -> Self {
49        Self::new(vec![FF::ONE])
50    }
51
52    fn is_one(&self) -> bool {
53        self.degree() == 0 && self.coefficients[0].is_one()
54    }
55}
56
57/// Data produced by the preprocessing phase of a batch modular interpolation.
58/// Marked `pub` for benchmarking purposes. Not part of the public API.
59#[doc(hidden)]
60#[derive(Debug, Clone)]
61pub struct ModularInterpolationPreprocessingData<'coeffs, FF: FiniteField> {
62    pub even_zerofiers: Vec<Polynomial<'coeffs, FF>>,
63    pub odd_zerofiers: Vec<Polynomial<'coeffs, FF>>,
64    pub shift_coefficients: Vec<FF>,
65    pub tail_length: usize,
66}
67
68/// A univariate polynomial with coefficients in a [finite field](FiniteField), in monomial form.
69#[derive(Clone)]
70pub struct Polynomial<'coeffs, FF: FiniteField> {
71    /// The polynomial's coefficients, in order of increasing degree. That is, the
72    /// leading coefficient is `coefficients.last()`. See [`Polynomial::normalize`]
73    /// and [`Polynomial::coefficients`] for caveats of that statement.
74    coefficients: Cow<'coeffs, [FF]>,
75}
76
77impl<'a, FF> Arbitrary<'a> for Polynomial<'static, FF>
78where
79    FF: FiniteField + Arbitrary<'a>,
80{
81    fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
82        Ok(Self::new(u.arbitrary()?))
83    }
84}
85
86impl<FF: FiniteField> Debug for Polynomial<'_, FF> {
87    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
88        f.debug_struct("Polynomial")
89            .field("coefficients", &self.coefficients)
90            .finish()
91    }
92}
93
94// Not derived because `PartialEq` is also not derived.
95impl<FF: FiniteField> Hash for Polynomial<'_, FF> {
96    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
97        self.coefficients.hash(state);
98    }
99}
100
101impl<FF: FiniteField> Display for Polynomial<'_, FF> {
102    fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
103        let degree = match self.degree() {
104            -1 => return write!(f, "0"),
105            d => d as usize,
106        };
107
108        for pow in (0..=degree).rev() {
109            let coeff = self.coefficients[pow];
110            if coeff.is_zero() {
111                continue;
112            }
113
114            if pow != degree {
115                write!(f, " + ")?;
116            }
117            if !coeff.is_one() || pow == 0 {
118                write!(f, "{coeff}")?;
119            }
120            match pow {
121                0 => (),
122                1 => write!(f, "x")?,
123                _ => write!(f, "x^{pow}")?,
124            }
125        }
126
127        Ok(())
128    }
129}
130
131// Manually implemented to correctly handle leading zeros.
132impl<FF: FiniteField> PartialEq<Polynomial<'_, FF>> for Polynomial<'_, FF> {
133    fn eq(&self, other: &Polynomial<'_, FF>) -> bool {
134        if self.degree() != other.degree() {
135            return false;
136        }
137
138        self.coefficients
139            .iter()
140            .zip(other.coefficients.iter())
141            .all(|(x, y)| x == y)
142    }
143}
144
145impl<FF: FiniteField> Eq for Polynomial<'_, FF> {}
146
147impl<FF> Polynomial<'_, FF>
148where
149    FF: FiniteField,
150{
151    pub fn degree(&self) -> isize {
152        let mut deg = self.coefficients.len() as isize - 1;
153        while deg >= 0 && self.coefficients[deg as usize].is_zero() {
154            deg -= 1;
155        }
156
157        deg // -1 for the zero polynomial
158    }
159
160    /// The polynomial's coefficients, in order of increasing degree. That is, the
161    /// leading coefficient is the slice's last element.
162    ///
163    /// The leading coefficient is guaranteed to be non-zero. Consequently, the
164    /// zero-polynomial is the empty slice.
165    ///
166    /// See also [`into_coefficients()`][Self::into_coefficients].
167    pub fn coefficients(&self) -> &[FF] {
168        let coefficients = self.coefficients.as_ref();
169
170        let Some(leading_coeff_idx) = coefficients.iter().rposition(|&c| !c.is_zero()) else {
171            // `coefficients` contains no elements or only zeroes
172            return &[];
173        };
174
175        &coefficients[0..=leading_coeff_idx]
176    }
177
178    /// Like [`coefficients()`][Self::coefficients], but consumes `self`.
179    ///
180    /// Only clones the underlying coefficients if they are not already owned.
181    pub fn into_coefficients(mut self) -> Vec<FF> {
182        self.normalize();
183        self.coefficients.into_owned()
184    }
185
186    /// Remove any leading coefficients that are 0.
187    ///
188    /// Notably, does _not_ make `self` monic.
189    fn normalize(&mut self) {
190        while self.coefficients.last().is_some_and(Zero::is_zero) {
191            self.coefficients.to_mut().pop();
192        }
193    }
194
195    /// The coefficient of the polynomial's term of highest power. `None` if (and only if) `self`
196    /// [is zero](Self::is_zero).
197    ///
198    /// Furthermore, is never `Some(FF::ZERO)`.
199    ///
200    /// # Examples
201    ///
202    /// ```
203    /// # use twenty_first::prelude::*;
204    /// # use num_traits::Zero;
205    /// let f = Polynomial::new(bfe_vec![1, 2, 3]);
206    /// assert_eq!(Some(bfe!(3)), f.leading_coefficient());
207    /// assert_eq!(None, Polynomial::<XFieldElement>::zero().leading_coefficient());
208    /// ```
209    pub fn leading_coefficient(&self) -> Option<FF> {
210        match self.degree() {
211            -1 => None,
212            n => Some(self.coefficients[n as usize]),
213        }
214    }
215
216    pub fn is_x(&self) -> bool {
217        self.degree() == 1 && self.coefficients[0].is_zero() && self.coefficients[1].is_one()
218    }
219
220    pub fn formal_derivative(&self) -> Polynomial<'static, FF> {
221        // not `enumerate()`ing: `FiniteField` is trait-bound to `From<u64>` but not `From<usize>`
222        let coefficients = (0..)
223            .zip(self.coefficients.iter())
224            .map(|(i, &coefficient)| FF::from(i) * coefficient)
225            .skip(1)
226            .collect();
227
228        Polynomial::new(coefficients)
229    }
230
231    /// Evaluate `self` in an indeterminate.
232    ///
233    /// For a specialized version, with fewer type annotations needed, see
234    /// [`Self::evaluate_in_same_field`].
235    pub fn evaluate<Ind, Eval>(&self, x: Ind) -> Eval
236    where
237        Ind: Clone,
238        Eval: Mul<Ind, Output = Eval> + Add<FF, Output = Eval> + Zero,
239    {
240        let mut acc = Eval::zero();
241        for &c in self.coefficients.iter().rev() {
242            acc = acc * x.clone() + c;
243        }
244
245        acc
246    }
247    /// Evaluate `self` in an indeterminate.
248    ///
249    /// For a generalized version, with more type annotations needed, see
250    /// [`Self::evaluate`].
251    // todo: try to remove this once specialization is stabilized; see
252    //  https://rust-lang.github.io/rfcs/1210-impl-specialization.html
253    pub fn evaluate_in_same_field(&self, x: FF) -> FF {
254        self.evaluate::<FF, FF>(x)
255    }
256
257    pub fn are_colinear_3(p0: (FF, FF), p1: (FF, FF), p2: (FF, FF)) -> bool {
258        if p0.0 == p1.0 || p1.0 == p2.0 || p2.0 == p0.0 {
259            return false;
260        }
261
262        let dy = p0.1 - p1.1;
263        let dx = p0.0 - p1.0;
264
265        dx * (p2.1 - p0.1) == dy * (p2.0 - p0.0)
266    }
267
268    pub fn are_colinear(points: &[(FF, FF)]) -> bool {
269        if points.len() < 3 {
270            return false;
271        }
272
273        if !points.iter().map(|(x, _)| x).all_unique() {
274            return false;
275        }
276
277        // Find 1st degree polynomial through first two points
278        let (p0_x, p0_y) = points[0];
279        let (p1_x, p1_y) = points[1];
280        let a = (p0_y - p1_y) / (p0_x - p1_x);
281        let b = p0_y - a * p0_x;
282
283        points.iter().skip(2).all(|&(x, y)| a * x + b == y)
284    }
285
286    pub fn get_colinear_y(p0: (FF, FF), p1: (FF, FF), p2_x: FF) -> FF {
287        assert_ne!(p0.0, p1.0, "Line must not be parallel to y-axis");
288        let dy = p0.1 - p1.1;
289        let dx = p0.0 - p1.0;
290        let p2_y_times_dx = dy * (p2_x - p0.0) + dx * p0.1;
291
292        // Can we implement this without division?
293        p2_y_times_dx / dx
294    }
295
296    /// Slow square implementation that does not use NTT
297    #[must_use]
298    pub fn slow_square(&self) -> Polynomial<'static, FF> {
299        let degree = self.degree();
300        if degree == -1 {
301            return Polynomial::zero();
302        }
303
304        let squared_coefficient_len = self.degree() as usize * 2 + 1;
305        let zero = FF::ZERO;
306        let one = FF::ONE;
307        let two = one + one;
308        let mut squared_coefficients = vec![zero; squared_coefficient_len];
309
310        for i in 0..self.coefficients.len() {
311            let ci = self.coefficients[i];
312            squared_coefficients[2 * i] += ci * ci;
313
314            // TODO: Review.
315            for j in i + 1..self.coefficients.len() {
316                let cj = self.coefficients[j];
317                squared_coefficients[i + j] += two * ci * cj;
318            }
319        }
320
321        Polynomial::new(squared_coefficients)
322    }
323
324    /// Only `pub` to allow benchmarking; not considered part of the public API.
325    #[doc(hidden)]
326    pub fn naive_multiply<FF2>(
327        &self,
328        other: &Polynomial<FF2>,
329    ) -> Polynomial<'static, <FF as Mul<FF2>>::Output>
330    where
331        FF: Mul<FF2>,
332        FF2: FiniteField,
333        <FF as Mul<FF2>>::Output: FiniteField,
334    {
335        let Ok(degree_lhs) = usize::try_from(self.degree()) else {
336            return Polynomial::zero();
337        };
338        let Ok(degree_rhs) = usize::try_from(other.degree()) else {
339            return Polynomial::zero();
340        };
341
342        let mut product = vec![<FF as Mul<FF2>>::Output::ZERO; degree_lhs + degree_rhs + 1];
343        for i in 0..=degree_lhs {
344            for j in 0..=degree_rhs {
345                product[i + j] += self.coefficients[i] * other.coefficients[j];
346            }
347        }
348
349        Polynomial::new(product)
350    }
351
352    /// Multiply `self` with itself `pow` times.
353    ///
354    /// Similar to [`Self::fast_pow`], but slower and slightly more general.
355    #[must_use]
356    pub fn pow(&self, pow: u32) -> Polynomial<'static, FF> {
357        // special case: 0^0 = 1
358        let Some(bit_length) = pow.checked_ilog2() else {
359            return Polynomial::one();
360        };
361
362        if self.degree() < 0 {
363            return Polynomial::zero();
364        }
365
366        // square-and-multiply
367        let mut acc = Polynomial::one();
368        for i in 0..=bit_length {
369            acc = acc.slow_square();
370            let bit_is_set = (pow >> (bit_length - i)) & 1 == 1;
371            if bit_is_set {
372                acc = acc * self.clone();
373            }
374        }
375
376        acc
377    }
378
379    /// Multiply a polynomial with x^power
380    #[must_use]
381    pub fn shift_coefficients(self, power: usize) -> Polynomial<'static, FF> {
382        let mut coefficients = self.coefficients.into_owned();
383        coefficients.splice(0..0, vec![FF::ZERO; power]);
384        Polynomial::new(coefficients)
385    }
386
387    /// Multiply a polynomial with a scalar, _i.e._, compute `scalar ยท self(x)`.
388    ///
389    /// Slightly faster but slightly less general than [`Self::scalar_mul`].
390    ///
391    /// # Examples
392    ///
393    /// ```
394    /// # use twenty_first::prelude::*;
395    /// let mut f = Polynomial::new(bfe_vec![1, 2, 3]);
396    /// f.scalar_mul_mut(bfe!(2));
397    /// assert_eq!(Polynomial::new(bfe_vec![2, 4, 6]), f);
398    /// ```
399    pub fn scalar_mul_mut<S>(&mut self, scalar: S)
400    where
401        S: Clone,
402        FF: MulAssign<S>,
403    {
404        let mut coefficients = std::mem::take(&mut self.coefficients).into_owned();
405        for coefficient in &mut coefficients {
406            *coefficient *= scalar.clone();
407        }
408        self.coefficients = Cow::Owned(coefficients);
409    }
410
411    /// Multiply a polynomial with a scalar, _i.e._, compute `scalar ยท self(x)`.
412    ///
413    /// Slightly slower but slightly more general than [`Self::scalar_mul_mut`].
414    ///
415    /// # Examples
416    ///
417    /// ```
418    /// # use twenty_first::prelude::*;
419    /// let f = Polynomial::new(bfe_vec![1, 2, 3]);
420    /// let g = f.scalar_mul(bfe!(2));
421    /// assert_eq!(Polynomial::new(bfe_vec![2, 4, 6]), g);
422    /// ```
423    #[must_use]
424    pub fn scalar_mul<S, FF2>(&self, scalar: S) -> Polynomial<'static, FF2>
425    where
426        S: Clone,
427        FF: Mul<S, Output = FF2>,
428        FF2: FiniteField,
429    {
430        let coeff_iter = self.coefficients.iter();
431        let new_coeffs = coeff_iter.map(|&c| c * scalar.clone()).collect();
432        Polynomial::new(new_coeffs)
433    }
434
435    /// Divide `self` by some `divisor`, returning (`quotient`, `remainder`).
436    ///
437    /// # Panics
438    ///
439    /// Panics if the `divisor` is zero.
440    pub fn divide(
441        &self,
442        divisor: &Polynomial<'_, FF>,
443    ) -> (Polynomial<'static, FF>, Polynomial<'static, FF>) {
444        // There is an NTT-based division algorithm, but for no practical
445        // parameter set is it faster than long division.
446        self.naive_divide(divisor)
447    }
448
449    /// Return (quotient, remainder).
450    ///
451    /// Only `pub` to allow benchmarking; not considered part of the public API.
452    #[doc(hidden)]
453    pub fn naive_divide(
454        &self,
455        divisor: &Polynomial<'_, FF>,
456    ) -> (Polynomial<'static, FF>, Polynomial<'static, FF>) {
457        let divisor_lc_inv = divisor
458            .leading_coefficient()
459            .expect("divisor should be non-zero")
460            .inverse();
461
462        let Ok(quotient_degree) = usize::try_from(self.degree() - divisor.degree()) else {
463            // self.degree() < divisor.degree()
464            return (Polynomial::zero(), self.clone().into_owned());
465        };
466        debug_assert!(self.degree() >= 0);
467
468        // quotient is built from back to front, must be reversed later
469        let mut rev_quotient = Vec::with_capacity(quotient_degree + 1);
470        let mut remainder = self.clone();
471        remainder.normalize();
472
473        // The divisor is also iterated back to front.
474        // It is normalized manually to avoid it being a `&mut` argument.
475        let rev_divisor = divisor.coefficients.iter().rev();
476        let normal_rev_divisor = rev_divisor.skip_while(|c| c.is_zero());
477
478        let mut remainder_coefficients = remainder.coefficients.into_owned();
479        for _ in 0..=quotient_degree {
480            let remainder_lc = remainder_coefficients.pop().unwrap();
481            let quotient_coeff = remainder_lc * divisor_lc_inv;
482            rev_quotient.push(quotient_coeff);
483
484            if quotient_coeff.is_zero() {
485                continue;
486            }
487
488            let remainder_degree = remainder_coefficients.len().saturating_sub(1);
489
490            // skip divisor's leading coefficient: it has already been dealt with
491            for (i, &divisor_coeff) in normal_rev_divisor.clone().skip(1).enumerate() {
492                remainder_coefficients[remainder_degree - i] -= quotient_coeff * divisor_coeff;
493            }
494        }
495
496        rev_quotient.reverse();
497
498        let quot = Polynomial::new(rev_quotient);
499        let rem = Polynomial::new(remainder_coefficients);
500        (quot, rem)
501    }
502
503    /// Extended Euclidean algorithm with polynomials. Computes the greatest
504    /// common divisor `gcd` as a monic polynomial, as well as the corresponding
505    /// Bรฉzout coefficients `a` and `b`, satisfying `gcd = aยทx + bยทy`
506    ///
507    /// # Example
508    ///
509    /// ```
510    /// # use twenty_first::prelude::Polynomial;
511    /// # use twenty_first::prelude::BFieldElement;
512    /// let x = Polynomial::<BFieldElement>::from([1, 0, 1]);
513    /// let y = Polynomial::<BFieldElement>::from([1, 1]);
514    /// let (gcd, a, b) = Polynomial::xgcd(x.clone(), y.clone());
515    /// assert_eq!(gcd, a * x + b * y);
516    /// ```
517    pub fn xgcd(
518        x: Self,
519        y: Polynomial<'_, FF>,
520    ) -> (
521        Polynomial<'static, FF>,
522        Polynomial<'static, FF>,
523        Polynomial<'static, FF>,
524    ) {
525        let mut x = x.into_owned();
526        let mut y = y.into_owned();
527        let (mut a_factor, mut a1) = (Polynomial::one(), Polynomial::zero());
528        let (mut b_factor, mut b1) = (Polynomial::zero(), Polynomial::one());
529
530        while !y.is_zero() {
531            let (quotient, remainder) = x.naive_divide(&y);
532            let c = a_factor - quotient.clone() * a1.clone();
533            let d = b_factor - quotient * b1.clone();
534
535            x = y;
536            y = remainder;
537            a_factor = a1;
538            a1 = c;
539            b_factor = b1;
540            b1 = d;
541        }
542
543        // normalize result to ensure the gcd, _i.e._, `x` has leading coefficient 1
544        let lc = x.leading_coefficient().unwrap_or(FF::ONE);
545        let normalize = |poly: Self| poly.scalar_mul(lc.inverse());
546
547        let [x, a, b] = [x, a_factor, b_factor].map(normalize);
548        (x, a, b)
549    }
550
551    /// Given a polynomial f(X), find the polynomial g(X) of degree at most n
552    /// such that f(X) * g(X) = 1 mod X^{n+1} where n is the precision.
553    /// # Panics
554    ///
555    /// Panics if f(X) does not have an inverse in the formal power series
556    /// ring, _i.e._ if its constant coefficient is zero.
557    fn formal_power_series_inverse_minimal(&self, precision: usize) -> Polynomial<'static, FF> {
558        let lc_inv = self.coefficients.first().unwrap().inverse();
559        let mut g = vec![lc_inv];
560
561        // invariant: product[i] = 0
562        for _ in 1..(precision + 1) {
563            let inner_product = self
564                .coefficients
565                .iter()
566                .skip(1)
567                .take(g.len())
568                .zip(g.iter().rev())
569                .map(|(l, r)| *l * *r)
570                .fold(FF::ZERO, |l, r| l + r);
571            g.push(-inner_product * lc_inv);
572        }
573
574        Polynomial::new(g)
575    }
576
577    pub(crate) fn reverse(&self) -> Polynomial<'static, FF> {
578        let degree = self.degree();
579        let new_coefficients = self
580            .coefficients
581            .iter()
582            .take((degree + 1) as usize)
583            .copied()
584            .rev()
585            .collect_vec();
586        Polynomial::new(new_coefficients)
587    }
588
589    /// Return a polynomial that owns its coefficients. Clones the coefficients
590    /// if they are not already owned.
591    pub fn into_owned(self) -> Polynomial<'static, FF> {
592        Polynomial::new(self.coefficients.into_owned())
593    }
594}
595
596impl<FF> Polynomial<'_, FF>
597where
598    FF: FiniteField + MulAssign<BFieldElement>,
599{
600    /// [Fast multiplication](Self::multiply) is slower than [naรฏve multiplication](Self::mul)
601    /// for polynomials of degree less than this threshold.
602    ///
603    /// Extracted from `cargo bench --bench poly_mul` on mjolnir.
604    const FAST_MULTIPLY_CUTOFF_THRESHOLD: isize = 1 << 8;
605
606    /// [Fast interpolation](Self::fast_interpolate) is slower than
607    /// [Lagrange interpolation](Self::lagrange_interpolate) below this threshold.
608    ///
609    /// Extracted from `cargo bench --bench interpolation` on mjolnir.
610    const FAST_INTERPOLATE_CUTOFF_THRESHOLD_SEQUENTIAL: usize = 1 << 12;
611
612    /// [Parallel Fast interpolation](Self::par_fast_interpolate) is slower than
613    /// [Lagrange interpolation](Self::lagrange_interpolate) below this threshold.
614    ///
615    /// Extracted from `cargo bench --bench interpolation` on mjolnir.
616    const FAST_INTERPOLATE_CUTOFF_THRESHOLD_PARALLEL: usize = 1 << 8;
617
618    /// Regulates the recursion depth at which
619    /// [Fast modular coset interpolation](Self::fast_modular_coset_interpolate)
620    /// is slower and switches to
621    /// [Lagrange interpolation](Self::lagrange_interpolate).
622    const FAST_MODULAR_COSET_INTERPOLATE_CUTOFF_THRESHOLD_PREFER_LAGRANGE: usize = 1 << 8;
623
624    /// Regulates the recursion depth at which
625    /// [Fast modular coset interpolation](Self::fast_modular_coset_interpolate)
626    /// is slower and switches to [INTT][intt]-then-[reduce](Self::reduce).
627    const FAST_MODULAR_COSET_INTERPOLATE_CUTOFF_THRESHOLD_PREFER_INTT: usize = 1 << 17;
628
629    /// Regulates when to prefer the [Fast coset extrapolation](Self::fast_coset_extrapolate)
630    /// over the [naรฏve method](Self::naive_coset_extrapolate). Threshold found
631    /// using `cargo criterion --bench extrapolation`.
632    const FAST_COSET_EXTRAPOLATE_THRESHOLD: usize = 100;
633
634    /// Inside `formal_power_series_inverse`, when to multiply naively and when
635    /// to use NTT-based multiplication. Use benchmark
636    /// `formal_power_series_inverse` to find the optimum. Based on benchmarks,
637    /// the optimum probably lies somewhere between 2^5 and 2^9.
638    const FORMAL_POWER_SERIES_INVERSE_CUTOFF: isize = 1 << 8;
639
640    /// Modular reduction is made fast by first finding a multiple of the
641    /// denominator that allows for chunk-wise reduction, and then finishing off
642    /// by reducing by the plain denominator using plain long division. The
643    /// "fast"ness comes from using NTT-based multiplication in the chunk-wise
644    /// reduction step. This const regulates the chunk size and thus the domain
645    /// size of the NTT.
646    const FAST_REDUCE_CUTOFF_THRESHOLD: usize = 1 << 8;
647
648    /// When doing batch evaluation, sometimes it makes sense to reduce the
649    /// polynomial modulo the zerofier of the domain first. This const regulates
650    /// when.
651    const REDUCE_BEFORE_EVALUATE_THRESHOLD_RATIO: isize = 4;
652
653    /// Return the polynomial which corresponds to the transformation `x โ†’ ฮฑยทx`.
654    ///
655    /// Given a polynomial P(x), produce P'(x) := P(ฮฑยทx). Evaluating P'(x) then corresponds to
656    /// evaluating P(ฮฑยทx).
657    #[must_use]
658    pub fn scale<S, XF>(&self, alpha: S) -> Polynomial<'static, XF>
659    where
660        S: Clone + One,
661        FF: Mul<S, Output = XF>,
662        XF: FiniteField,
663    {
664        let mut power_of_alpha = S::one();
665        let mut return_coefficients = Vec::with_capacity(self.coefficients.len());
666        for &coefficient in self.coefficients.iter() {
667            return_coefficients.push(coefficient * power_of_alpha.clone());
668            power_of_alpha = power_of_alpha * alpha.clone();
669        }
670        Polynomial::new(return_coefficients)
671    }
672
673    /// It is the caller's responsibility that this function is called with sufficiently large input
674    /// to be safe and to be faster than `square`.
675    #[must_use]
676    pub fn fast_square(&self) -> Polynomial<'static, FF> {
677        let degree = self.degree();
678        if degree == -1 {
679            return Polynomial::zero();
680        }
681        if degree == 0 {
682            return Polynomial::from_constant(self.coefficients[0] * self.coefficients[0]);
683        }
684
685        let result_degree: u64 = 2 * self.degree() as u64;
686        let order = (result_degree + 1).next_power_of_two();
687
688        let mut coefficients = self.coefficients.to_vec();
689        coefficients.resize(order as usize, FF::ZERO);
690        ntt::<FF>(&mut coefficients);
691
692        for element in coefficients.iter_mut() {
693            *element = element.to_owned() * element.to_owned();
694        }
695
696        intt::<FF>(&mut coefficients);
697        coefficients.truncate(result_degree as usize + 1);
698
699        Polynomial::new(coefficients)
700    }
701
702    #[must_use]
703    pub fn square(&self) -> Polynomial<'static, FF> {
704        let degree = self.degree();
705        if degree == -1 {
706            return Polynomial::zero();
707        }
708
709        // A benchmark run on sword_smith's PC revealed that `fast_square` was faster when the input
710        // size exceeds a length of 64.
711        let squared_coefficient_len = self.degree() as usize * 2 + 1;
712        if squared_coefficient_len > 64 {
713            return self.fast_square();
714        }
715
716        let zero = FF::ZERO;
717        let one = FF::ONE;
718        let two = one + one;
719        let mut squared_coefficients = vec![zero; squared_coefficient_len];
720
721        // TODO: Review.
722        for i in 0..self.coefficients.len() {
723            let ci = self.coefficients[i];
724            squared_coefficients[2 * i] += ci * ci;
725
726            for j in i + 1..self.coefficients.len() {
727                let cj = self.coefficients[j];
728                squared_coefficients[i + j] += two * ci * cj;
729            }
730        }
731
732        Polynomial::new(squared_coefficients)
733    }
734
735    /// Multiply `self` with itself `pow` times.
736    ///
737    /// Similar to [`Self::pow`], but faster and slightly less general.
738    #[must_use]
739    pub fn fast_pow(&self, pow: u32) -> Polynomial<'static, FF> {
740        // special case: 0^0 = 1
741        let Some(bit_length) = pow.checked_ilog2() else {
742            return Polynomial::one();
743        };
744
745        if self.degree() < 0 {
746            return Polynomial::zero();
747        }
748
749        // square-and-multiply
750        let mut acc = Polynomial::one();
751        for i in 0..=bit_length {
752            acc = acc.square();
753            let bit_is_set = (pow >> (bit_length - i)) & 1 == 1;
754            if bit_is_set {
755                acc = self.multiply(&acc);
756            }
757        }
758
759        acc
760    }
761
762    /// Multiply `self` by `other`.
763    ///
764    /// Prefer this over [`self * other`](Self::mul) since it chooses the fastest multiplication
765    /// strategy.
766    #[must_use]
767    pub fn multiply<FF2>(
768        &self,
769        other: &Polynomial<'_, FF2>,
770    ) -> Polynomial<'static, <FF as Mul<FF2>>::Output>
771    where
772        FF: Mul<FF2>,
773        FF2: FiniteField + MulAssign<BFieldElement>,
774        <FF as Mul<FF2>>::Output: FiniteField + MulAssign<BFieldElement>,
775    {
776        if self.degree() + other.degree() < Self::FAST_MULTIPLY_CUTOFF_THRESHOLD {
777            self.naive_multiply(other)
778        } else {
779            self.fast_multiply(other)
780        }
781    }
782
783    /// Use [Self::multiply] instead. Only `pub` to allow benchmarking; not considered part of the
784    /// public API.
785    ///
786    /// This method is asymptotically faster than [naive multiplication](Self::naive_multiply). For
787    /// small instances, _i.e._, polynomials of low degree, it is slower.
788    ///
789    /// The time complexity of this method is in O(nยทlog(n)), where `n` is the sum of the degrees
790    /// of the operands. The time complexity of the naive multiplication is in O(n^2).
791    #[doc(hidden)]
792    pub fn fast_multiply<FF2>(
793        &self,
794        other: &Polynomial<FF2>,
795    ) -> Polynomial<'static, <FF as Mul<FF2>>::Output>
796    where
797        FF: Mul<FF2>,
798        FF2: FiniteField + MulAssign<BFieldElement>,
799        <FF as Mul<FF2>>::Output: FiniteField + MulAssign<BFieldElement>,
800    {
801        let Ok(degree) = usize::try_from(self.degree() + other.degree()) else {
802            return Polynomial::zero();
803        };
804        let order = (degree + 1).next_power_of_two();
805
806        let mut lhs_coefficients = self.coefficients.to_vec();
807        let mut rhs_coefficients = other.coefficients.to_vec();
808
809        lhs_coefficients.resize(order, FF::ZERO);
810        rhs_coefficients.resize(order, FF2::ZERO);
811
812        ntt(&mut lhs_coefficients);
813        ntt(&mut rhs_coefficients);
814
815        let mut hadamard_product = lhs_coefficients
816            .into_iter()
817            .zip(rhs_coefficients)
818            .map(|(l, r)| l * r)
819            .collect_vec();
820
821        intt(&mut hadamard_product);
822        hadamard_product.truncate(degree + 1);
823        Polynomial::new(hadamard_product)
824    }
825
826    /// Multiply a bunch of polynomials together.
827    pub fn batch_multiply(factors: &[Self]) -> Polynomial<'static, FF> {
828        // Build a tree-like structure of multiplications to keep the degrees of the
829        // factors roughly equal throughout the process. This makes efficient use of
830        // the `.multiply()` dispatcher.
831        // In contrast, using a simple `.reduce()`, the accumulator polynomial would
832        // have a much higher degree than the individual factors.
833        // todo: benchmark the current approach against the โ€œreduceโ€ approach.
834
835        if factors.is_empty() {
836            return Polynomial::one();
837        }
838        let mut products = factors.to_vec();
839        while products.len() != 1 {
840            products = products
841                .chunks(2)
842                .map(|chunk| match chunk.len() {
843                    2 => chunk[0].multiply(&chunk[1]),
844                    1 => chunk[0].clone(),
845                    _ => unreachable!(),
846                })
847                .collect();
848        }
849
850        // If any multiplications happened, `into_owned()` will not clone anything.
851        // If no multiplications happened,
852        //   a) what is the caller doing?
853        //   b) a `'static` lifetime needs to be guaranteed, requiring `into_owned()`.
854        let product_coeffs = products.pop().unwrap().coefficients.into_owned();
855        Polynomial::new(product_coeffs)
856    }
857
858    /// Parallel version of [`batch_multiply`](Self::batch_multiply).
859    pub fn par_batch_multiply(factors: &[Self]) -> Polynomial<'static, FF> {
860        if factors.is_empty() {
861            return Polynomial::one();
862        }
863        let num_threads = available_parallelism()
864            .map(|non_zero_usize| non_zero_usize.get())
865            .unwrap_or(1);
866        let mut products = factors.to_vec();
867        while products.len() != 1 {
868            let chunk_size = usize::max(2, products.len() / num_threads);
869            products = products
870                .par_chunks(chunk_size)
871                .map(Self::batch_multiply)
872                .collect();
873        }
874
875        let product_coeffs = products.pop().unwrap().coefficients.into_owned();
876        Polynomial::new(product_coeffs)
877    }
878
879    /// Divide (with remainder) and throw away the quotient. Note that the self
880    /// object is the numerator and the argument is the denominator (or
881    /// modulus).
882    pub fn reduce(&self, modulus: &Polynomial<'_, FF>) -> Polynomial<'static, FF> {
883        const FAST_REDUCE_MAKES_SENSE_MULTIPLE: isize = 4;
884        if modulus.degree() < 0 {
885            panic!("Cannot divide by zero; needed for reduce.");
886        } else if modulus.degree() == 0 {
887            Polynomial::zero()
888        } else if self.degree() < modulus.degree() {
889            self.clone().into_owned()
890        } else if self.degree() > FAST_REDUCE_MAKES_SENSE_MULTIPLE * modulus.degree() {
891            self.fast_reduce(modulus)
892        } else {
893            self.reduce_long_division(modulus)
894        }
895    }
896
897    /// Compute the remainder after division of one polynomial by another. This
898    /// method first reduces the numerator by a multiple of the denominator that
899    /// was constructed to enable NTT-based chunk-wise reduction, before
900    /// invoking the standard long division based algorithm to finalize. As a
901    /// result, it works best for large numerators being reduced by small
902    /// denominators.
903    pub fn fast_reduce(&self, modulus: &Self) -> Polynomial<'static, FF> {
904        if modulus.degree() == 0 {
905            return Polynomial::zero();
906        }
907        if self.degree() < modulus.degree() {
908            return self.clone().into_owned();
909        }
910
911        // 1. Chunk-wise reduction in NTT domain.
912        // We generate a structured multiple of the modulus of the form
913        // 1, (many zeros), *, *, *, *, *; where
914        //                  -------------
915        //                        |- m coefficients
916        //    ---------------------------
917        //               |- n=2^k coefficients.
918        // This allows us to reduce the numerator's coefficients in chunks of
919        // n-m using NTT-based multiplication over a domain of size n = 2^k.
920
921        let (shift_factor_ntt, tail_size) = modulus.shift_factor_ntt_with_tail_length();
922        let mut intermediate_remainder =
923            self.reduce_by_ntt_friendly_modulus(&shift_factor_ntt, tail_size);
924
925        // 2. Chunk-wise reduction with schoolbook multiplication.
926        // We generate a smaller structured multiple of the denominator
927        // that also admits chunk-wise reduction but not NTT-based
928        // multiplication within. While asymptotically on par with long
929        // division, this schoolbook chunk-wise reduction is concretely more
930        // performant.
931        if intermediate_remainder.degree() > 4 * modulus.degree() {
932            let structured_multiple = modulus.structured_multiple();
933            intermediate_remainder =
934                intermediate_remainder.reduce_by_structured_modulus(&structured_multiple);
935        }
936
937        // 3. Long division based reduction by the (unmultiplied) modulus.
938        intermediate_remainder.reduce_long_division(modulus)
939    }
940
941    /// Only marked `pub` for benchmarking purposes. Not considered part of the
942    /// public API.
943    #[doc(hidden)]
944    pub fn shift_factor_ntt_with_tail_length(&self) -> (Vec<FF>, usize)
945    where
946        FF: 'static,
947    {
948        let n = usize::max(
949            Self::FAST_REDUCE_CUTOFF_THRESHOLD,
950            self.degree() as usize * 2,
951        )
952        .next_power_of_two();
953        let ntt_friendly_multiple = self.structured_multiple_of_degree(n);
954
955        // m = 1 + degree(ntt_friendly_multiple - leading term)
956        let m = 1 + ntt_friendly_multiple
957            .coefficients
958            .iter()
959            .enumerate()
960            .rev()
961            .skip(1)
962            .find_map(|(i, c)| if !c.is_zero() { Some(i) } else { None })
963            .unwrap_or(0);
964        let mut shift_factor_ntt = ntt_friendly_multiple.coefficients[..n].to_vec();
965        ntt(&mut shift_factor_ntt);
966        (shift_factor_ntt, m)
967    }
968
969    /// Reduces f(X) by a structured modulus, which is of the form
970    /// X^{m+n} + (something of degree less than m). When the modulus has this
971    /// form, polynomial modular reductions can be computed faster than in the
972    /// generic case.
973    ///
974    /// This method uses NTT-based multiplication, meaning that the unstructured
975    /// part of the structured multiple must be given in NTT-domain.
976    ///
977    /// This function is marked `pub` for benchmarking. Not considered part of
978    /// the public API
979    #[doc(hidden)]
980    pub fn reduce_by_ntt_friendly_modulus(
981        &self,
982        shift_ntt: &[FF],
983        tail_length: usize,
984    ) -> Polynomial<'static, FF> {
985        let domain_length = shift_ntt.len();
986        assert!(domain_length.is_power_of_two());
987        let chunk_size = domain_length - tail_length;
988
989        if self.coefficients.len() < chunk_size + tail_length {
990            return self.clone().into_owned();
991        }
992        let num_reducible_chunks =
993            (self.coefficients.len() - (tail_length + chunk_size)).div_ceil(chunk_size);
994
995        let range_start = num_reducible_chunks * chunk_size;
996        let mut working_window = if range_start >= self.coefficients.len() {
997            vec![FF::ZERO; chunk_size + tail_length]
998        } else {
999            self.coefficients[range_start..].to_vec()
1000        };
1001        working_window.resize(chunk_size + tail_length, FF::ZERO);
1002
1003        for chunk_index in (0..num_reducible_chunks).rev() {
1004            let mut product = [
1005                working_window[tail_length..].to_vec(),
1006                vec![FF::ZERO; tail_length],
1007            ]
1008            .concat();
1009            ntt(&mut product);
1010            product
1011                .iter_mut()
1012                .zip(shift_ntt.iter())
1013                .for_each(|(l, r)| *l *= *r);
1014            intt(&mut product);
1015
1016            working_window = [
1017                vec![FF::ZERO; chunk_size],
1018                working_window[0..tail_length].to_vec(),
1019            ]
1020            .concat();
1021            for (i, wwi) in working_window.iter_mut().enumerate().take(chunk_size) {
1022                *wwi = self.coefficients[chunk_index * chunk_size + i];
1023            }
1024
1025            for (i, wwi) in working_window
1026                .iter_mut()
1027                .enumerate()
1028                .take(chunk_size + tail_length)
1029            {
1030                *wwi -= product[i];
1031            }
1032        }
1033
1034        Polynomial::new(working_window)
1035    }
1036
1037    /// Given a polynomial f(X) of degree n >= 0, find a multiple of f(X) of the
1038    /// form X^{3*n+1} + (something of degree at most 2*n).
1039    ///
1040    /// # Panics
1041    ///
1042    /// Panics if f(X) = 0.
1043    fn structured_multiple(&self) -> Polynomial<'static, FF> {
1044        let n = usize::try_from(self.degree()).expect("cannot compute multiple of zero");
1045        self.structured_multiple_of_degree(3 * n + 1)
1046    }
1047
1048    /// Given a polynomial f(X) and an integer n, find a multiple of f(X) of the
1049    /// form X^n + (something of much smaller degree).
1050    ///
1051    /// # Panics
1052    ///
1053    /// Panics if the polynomial is zero, or if its degree is larger than n
1054    pub fn structured_multiple_of_degree(&self, n: usize) -> Polynomial<'static, FF> {
1055        let Ok(degree) = usize::try_from(self.degree()) else {
1056            panic!("cannot compute multiples of zero");
1057        };
1058        assert!(degree <= n, "cannot compute multiple of smaller degree.");
1059        if degree == 0 {
1060            return Polynomial::new(
1061                [vec![FF::ZERO; n], vec![self.coefficients[0].inverse()]].concat(),
1062            );
1063        }
1064
1065        let reverse = self.reverse();
1066
1067        // The next function gives back a polynomial g(X) of degree at most arg,
1068        // such that f(X) * g(X) = 1 mod X^arg.
1069        // Without modular reduction, the degree of the product f(X) * g(X) is
1070        // deg(f) + arg -- even after coefficient reversal. So n = deg(f) + arg
1071        // and arg = n - deg(f).
1072        let inverse_reverse = reverse.formal_power_series_inverse_minimal(n - degree);
1073        let product_reverse = reverse.multiply(&inverse_reverse);
1074        let product = product_reverse.reverse();
1075
1076        // Coefficient reversal drops trailing zero. Correct for that.
1077        let product_degree = product.degree() as usize;
1078        product.shift_coefficients(n - product_degree)
1079    }
1080
1081    /// Reduces f(X) by a structured modulus, which is of the form
1082    /// X^{m+n} + (something of degree less than m). When the modulus has this
1083    /// form, polynomial modular reductions can be computed faster than in the
1084    /// generic case.
1085    ///
1086    /// # Panics
1087    ///
1088    /// Panics if
1089    ///  - multiple is a constant
1090    ///  - multiple is not monic
1091    fn reduce_by_structured_modulus(&self, multiple: &Self) -> Polynomial<'static, FF> {
1092        assert_ne!(0, multiple.degree());
1093        let multiple_degree = usize::try_from(multiple.degree()).expect("cannot reduce by zero");
1094        assert_eq!(
1095            Some(FF::ONE),
1096            multiple.leading_coefficient(),
1097            "multiple must be monic"
1098        );
1099        let leading_term = Polynomial::x_to_the(multiple_degree);
1100        let shift_polynomial = multiple.clone() - leading_term.clone();
1101        assert!(shift_polynomial.degree() < multiple.degree());
1102
1103        let tail_length = usize::try_from(shift_polynomial.degree())
1104            .map(|unsigned_degree| unsigned_degree + 1)
1105            .unwrap_or(0);
1106        let window_length = multiple_degree;
1107        let chunk_size = window_length - tail_length;
1108        if self.coefficients.len() < chunk_size + tail_length {
1109            return self.clone().into_owned();
1110        }
1111        let num_reducible_chunks =
1112            (self.coefficients.len() - (tail_length + chunk_size)).div_ceil(chunk_size);
1113
1114        let window_stop = (tail_length + chunk_size) + num_reducible_chunks * chunk_size;
1115        let mut window_start = window_stop - window_length;
1116        let mut working_window = self.coefficients[window_start..].to_vec();
1117        working_window.resize(chunk_size + tail_length, FF::ZERO);
1118
1119        for _ in (0..num_reducible_chunks).rev() {
1120            let overflow = Polynomial::new(working_window[tail_length..].to_vec());
1121            let product = overflow.multiply(&shift_polynomial);
1122
1123            window_start -= chunk_size;
1124            working_window = [
1125                self.coefficients[window_start..window_start + chunk_size].to_vec(),
1126                working_window[0..tail_length].to_vec(),
1127            ]
1128            .concat();
1129
1130            for (i, wwi) in working_window
1131                .iter_mut()
1132                .enumerate()
1133                .take(chunk_size + tail_length)
1134            {
1135                *wwi -= *product.coefficients.get(i).unwrap_or(&FF::ZERO);
1136            }
1137        }
1138
1139        Polynomial::new(working_window)
1140    }
1141
1142    fn reduce_long_division(&self, modulus: &Polynomial<'_, FF>) -> Polynomial<'static, FF> {
1143        let (_quotient, remainder) = self.divide(modulus);
1144        remainder
1145    }
1146
1147    /// Compute a polynomial g(X) from a given polynomial f(X) such that
1148    /// g(X) * f(X) = 1 mod X^n , where n is the precision.
1149    ///
1150    /// In formal terms, g(X) is the approximate multiplicative inverse in
1151    /// the formal power series ring, where elements obey the same
1152    /// algebraic rules as polynomials do but can have an infinite number of
1153    /// coefficients. To represent these elements on a computer, one has to
1154    /// truncate the coefficient vectors somewhere. The resulting truncation
1155    /// error is considered "small" when it lives on large powers of X. This
1156    /// function works by applying Newton's method in this ring.
1157    ///
1158    /// # Example
1159    ///
1160    /// ```
1161    /// # use num_traits::One;
1162    /// # use twenty_first::prelude::*;
1163    /// let precision = 8;
1164    /// let f = Polynomial::new(bfe_vec![42; precision]);
1165    /// let g = f.clone().formal_power_series_inverse_newton(precision);
1166    /// let x_to_the_n = Polynomial::one().shift_coefficients(precision);
1167    /// let (_quotient, remainder) = g.multiply(&f).divide(&x_to_the_n);
1168    /// assert!(remainder.is_one());
1169    /// ```
1170    /// # Panics
1171    ///
1172    /// Panics when f(X) is not invertible in the formal power series ring,
1173    /// _i.e._, when its constant coefficient is zero.
1174    pub fn formal_power_series_inverse_newton(self, precision: usize) -> Polynomial<'static, FF> {
1175        // polynomials of degree zero are non-zero and have an exact inverse
1176        let self_degree = self.degree();
1177        if self_degree == 0 {
1178            return Polynomial::from_constant(self.coefficients[0].inverse());
1179        }
1180
1181        // otherwise we need to run some iterations of Newton's method
1182        let num_rounds = precision.next_power_of_two().ilog2();
1183
1184        // for small polynomials we use standard multiplication,
1185        // but for larger ones we want to stay in the ntt domain
1186        let switch_point = if Self::FORMAL_POWER_SERIES_INVERSE_CUTOFF < self_degree {
1187            0
1188        } else {
1189            (Self::FORMAL_POWER_SERIES_INVERSE_CUTOFF / self_degree).ilog2()
1190        };
1191
1192        let cc = self.coefficients[0];
1193
1194        // standard part
1195        let mut f = Polynomial::from_constant(cc.inverse());
1196        for _ in 0..u32::min(num_rounds, switch_point) {
1197            let sub = f.multiply(&f).multiply(&self);
1198            f.scalar_mul_mut(FF::from(2));
1199            f = f - sub;
1200        }
1201
1202        // if we already have the required precision, terminate early
1203        if switch_point >= num_rounds {
1204            return f;
1205        }
1206
1207        // ntt-based multiplication from here on out
1208
1209        // final NTT domain
1210        let full_domain_length =
1211            ((1 << (num_rounds + 1)) * self_degree as usize).next_power_of_two();
1212
1213        let mut self_ntt = self.coefficients.into_owned();
1214        self_ntt.resize(full_domain_length, FF::ZERO);
1215        ntt(&mut self_ntt);
1216
1217        // while possible, we calculate over a smaller domain
1218        let mut current_domain_length = f.coefficients.len().next_power_of_two();
1219
1220        // migrate to a larger domain as necessary
1221        let lde = |v: &mut [FF], old_domain_length: usize, new_domain_length: usize| {
1222            intt(&mut v[..old_domain_length]);
1223            ntt(&mut v[..new_domain_length]);
1224        };
1225
1226        // use degree to track when domain-changes are necessary
1227        let mut f_degree = f.degree();
1228
1229        // allocate enough space for f and set initial values of elements used later to zero
1230        let mut f_ntt = f.coefficients.into_owned();
1231        f_ntt.resize(full_domain_length, FF::ZERO);
1232        ntt(&mut f_ntt[..current_domain_length]);
1233
1234        for _ in switch_point..num_rounds {
1235            f_degree = 2 * f_degree + self_degree;
1236            if f_degree as usize >= current_domain_length {
1237                let next_domain_length = (1 + f_degree as usize).next_power_of_two();
1238                lde(&mut f_ntt, current_domain_length, next_domain_length);
1239                current_domain_length = next_domain_length;
1240            }
1241            f_ntt
1242                .iter_mut()
1243                .zip(
1244                    self_ntt
1245                        .iter()
1246                        .step_by(full_domain_length / current_domain_length),
1247                )
1248                .for_each(|(ff, dd)| *ff = FF::from(2) * *ff - *ff * *ff * *dd);
1249        }
1250
1251        intt(&mut f_ntt[..current_domain_length]);
1252        Polynomial::new(f_ntt)
1253    }
1254
1255    /// Fast evaluate on a coset domain, which is the group generated by `generator^i * offset`.
1256    ///
1257    /// # Performance
1258    ///
1259    /// If possible, use a [base field element](BFieldElement) as the offset.
1260    ///
1261    /// # Panics
1262    ///
1263    /// Panics if the order of the domain generated by the `generator` is smaller than or equal to
1264    /// the degree of `self`.
1265    pub fn fast_coset_evaluate<S>(&self, offset: S, order: usize) -> Vec<FF>
1266    where
1267        S: Clone + One,
1268        FF: Mul<S, Output = FF> + 'static,
1269    {
1270        // NTT's input and output are of the same size. For domains of an order that is larger than
1271        // or equal to the number of coefficients of the polynomial, padding with leading zeros
1272        // (a no-op to the polynomial) achieves this requirement. However, if the order is smaller
1273        // than the number of coefficients in the polynomial, this would mean chopping off leading
1274        // coefficients, which changes the polynomial. Therefore, this method is currently limited
1275        // to domain orders greater than the degree of the polynomial.
1276        // todo: move Triton VM's solution for above issue in here
1277        assert!(
1278            (order as isize) > self.degree(),
1279            "`Polynomial::fast_coset_evaluate` is currently limited to domains of order \
1280            greater than the degree of the polynomial."
1281        );
1282
1283        let mut coefficients = self.scale(offset).coefficients.into_owned();
1284        coefficients.resize(order, FF::ZERO);
1285        ntt(&mut coefficients);
1286
1287        coefficients
1288    }
1289}
1290
1291impl<FF> Polynomial<'static, FF>
1292where
1293    FF: FiniteField + MulAssign<BFieldElement>,
1294{
1295    /// Computing the [fast zerofier][fast] is slower than computing the [smart zerofier][smart] for
1296    /// domain sizes smaller than this threshold. The [naรฏve zerofier][naive] is always slower to
1297    /// compute than the [smart zerofier][smart] for domain sizes smaller than the threshold.
1298    ///
1299    /// Extracted from `cargo bench --bench zerofier`.
1300    ///
1301    /// [naive]: Self::naive_zerofier
1302    /// [smart]: Self::smart_zerofier
1303    /// [fast]: Self::fast_zerofier
1304    const FAST_ZEROFIER_CUTOFF_THRESHOLD: usize = 100;
1305
1306    /// Compute the lowest degree polynomial with the provided roots.
1307    /// Also known as โ€œvanishing polynomial.โ€
1308    ///
1309    /// # Example
1310    ///
1311    /// ```
1312    /// # use num_traits::Zero;
1313    /// # use twenty_first::prelude::*;
1314    /// let roots = bfe_array![2, 4, 6];
1315    /// let zerofier = Polynomial::zerofier(&roots);
1316    ///
1317    /// assert_eq!(3, zerofier.degree());
1318    /// assert_eq!(bfe_vec![0, 0, 0], zerofier.batch_evaluate(&roots));
1319    ///
1320    /// let  non_roots = bfe_vec![0, 1, 3, 5];
1321    /// assert!(zerofier.batch_evaluate(&non_roots).iter().all(|x| !x.is_zero()));
1322    /// ```
1323    pub fn zerofier(roots: &[FF]) -> Self {
1324        if roots.len() < Self::FAST_ZEROFIER_CUTOFF_THRESHOLD {
1325            Self::smart_zerofier(roots)
1326        } else {
1327            Self::fast_zerofier(roots)
1328        }
1329    }
1330
1331    /// Parallel version of [`zerofier`](Self::zerofier).
1332    pub fn par_zerofier(roots: &[FF]) -> Self {
1333        if roots.is_empty() {
1334            return Polynomial::one();
1335        }
1336        let num_threads = available_parallelism()
1337            .map(|non_zero_usize| non_zero_usize.get())
1338            .unwrap_or(1);
1339        let chunk_size = roots
1340            .len()
1341            .div_ceil(num_threads)
1342            .max(Self::FAST_ZEROFIER_CUTOFF_THRESHOLD);
1343        let factors = roots
1344            .par_chunks(chunk_size)
1345            .map(|chunk| Self::zerofier(chunk))
1346            .collect::<Vec<_>>();
1347        Polynomial::par_batch_multiply(&factors)
1348    }
1349
1350    /// Only `pub` to allow benchmarking; not considered part of the public API.
1351    #[doc(hidden)]
1352    pub fn smart_zerofier(roots: &[FF]) -> Self {
1353        let mut zerofier = vec![FF::ZERO; roots.len() + 1];
1354        zerofier[0] = FF::ONE;
1355        let mut num_coeffs = 1;
1356        for &root in roots {
1357            for k in (1..=num_coeffs).rev() {
1358                zerofier[k] = zerofier[k - 1] - root * zerofier[k];
1359            }
1360            zerofier[0] = -root * zerofier[0];
1361            num_coeffs += 1;
1362        }
1363        Self::new(zerofier)
1364    }
1365
1366    /// Only `pub` to allow benchmarking; not considered part of the public API.
1367    #[doc(hidden)]
1368    pub fn fast_zerofier(roots: &[FF]) -> Self {
1369        let mid_point = roots.len() / 2;
1370        let left = Self::zerofier(&roots[..mid_point]);
1371        let right = Self::zerofier(&roots[mid_point..]);
1372
1373        left.multiply(&right)
1374    }
1375
1376    /// Construct the lowest-degree polynomial interpolating the given points.
1377    ///
1378    /// ```
1379    /// # use twenty_first::prelude::*;
1380    /// let domain = bfe_vec![0, 1, 2, 3];
1381    /// let values = bfe_vec![1, 3, 5, 7];
1382    /// let polynomial = Polynomial::interpolate(&domain, &values);
1383    ///
1384    /// assert_eq!(1, polynomial.degree());
1385    /// assert_eq!(bfe!(9), polynomial.evaluate(bfe!(4)));
1386    /// ```
1387    ///
1388    /// # Panics
1389    ///
1390    /// - Panics if the provided domain is empty.
1391    /// - Panics if the provided domain and values are not of the same length.
1392    pub fn interpolate(domain: &[FF], values: &[FF]) -> Self {
1393        assert!(
1394            !domain.is_empty(),
1395            "interpolation must happen through more than zero points"
1396        );
1397        assert_eq!(
1398            domain.len(),
1399            values.len(),
1400            "The domain and values lists have to be of equal length."
1401        );
1402
1403        if domain.len() <= Self::FAST_INTERPOLATE_CUTOFF_THRESHOLD_SEQUENTIAL {
1404            Self::lagrange_interpolate(domain, values)
1405        } else {
1406            Self::fast_interpolate(domain, values)
1407        }
1408    }
1409
1410    /// Parallel version of [`interpolate`](Self::interpolate).
1411    ///
1412    /// # Panics
1413    ///
1414    /// See [`interpolate`](Self::interpolate).
1415    pub fn par_interpolate(domain: &[FF], values: &[FF]) -> Self {
1416        assert!(
1417            !domain.is_empty(),
1418            "interpolation must happen through more than zero points"
1419        );
1420        assert_eq!(
1421            domain.len(),
1422            values.len(),
1423            "The domain and values lists have to be of equal length."
1424        );
1425
1426        // Reuse sequential threshold. We don't know how speed up this task with
1427        // parallelism below this threshold.
1428        if domain.len() <= Self::FAST_INTERPOLATE_CUTOFF_THRESHOLD_PARALLEL {
1429            Self::lagrange_interpolate(domain, values)
1430        } else {
1431            Self::par_fast_interpolate(domain, values)
1432        }
1433    }
1434
1435    /// Any fast interpolation will use NTT, so this is mainly used for testing/integrity
1436    /// purposes. This also means that it is not pivotal that this function has an optimal
1437    /// runtime.
1438    #[doc(hidden)]
1439    pub fn lagrange_interpolate_zipped(points: &[(FF, FF)]) -> Self {
1440        assert!(
1441            !points.is_empty(),
1442            "interpolation must happen through more than zero points"
1443        );
1444        assert!(
1445            points.iter().map(|x| x.0).all_unique(),
1446            "Repeated x values received. Got: {points:?}",
1447        );
1448
1449        let xs: Vec<FF> = points.iter().map(|x| x.0.to_owned()).collect();
1450        let ys: Vec<FF> = points.iter().map(|x| x.1.to_owned()).collect();
1451        Self::lagrange_interpolate(&xs, &ys)
1452    }
1453
1454    #[doc(hidden)]
1455    pub fn lagrange_interpolate(domain: &[FF], values: &[FF]) -> Self {
1456        debug_assert!(
1457            !domain.is_empty(),
1458            "interpolation domain cannot have zero points"
1459        );
1460        debug_assert_eq!(domain.len(), values.len());
1461
1462        let zero = FF::ZERO;
1463        let zerofier = Self::zerofier(domain).coefficients;
1464
1465        // In each iteration of this loop, accumulate into the sum one polynomial that evaluates
1466        // to some abscis (y-value) in the given ordinate (domain point), and to zero in all other
1467        // ordinates.
1468        let mut lagrange_sum_array = vec![zero; domain.len()];
1469        let mut summand_array = vec![zero; domain.len()];
1470        for (i, &abscis) in values.iter().enumerate() {
1471            // divide (X - domain[i]) out of zerofier to get unweighted summand
1472            let mut leading_coefficient = zerofier[domain.len()];
1473            let mut supporting_coefficient = zerofier[domain.len() - 1];
1474            let mut summand_eval = zero;
1475            for j in (1..domain.len()).rev() {
1476                summand_array[j] = leading_coefficient;
1477                summand_eval = summand_eval * domain[i] + leading_coefficient;
1478                leading_coefficient = supporting_coefficient + leading_coefficient * domain[i];
1479                supporting_coefficient = zerofier[j - 1];
1480            }
1481
1482            // avoid `j - 1` for j == 0 in the loop above
1483            summand_array[0] = leading_coefficient;
1484            summand_eval = summand_eval * domain[i] + leading_coefficient;
1485
1486            // summand does not necessarily evaluate to 1 in domain[i]: correct for this value
1487            let corrected_abscis = abscis / summand_eval;
1488
1489            // accumulate term
1490            for j in 0..domain.len() {
1491                lagrange_sum_array[j] += corrected_abscis * summand_array[j];
1492            }
1493        }
1494
1495        Self::new(lagrange_sum_array)
1496    }
1497
1498    /// Only `pub` to allow benchmarking; not considered part of the public API.
1499    #[doc(hidden)]
1500    pub fn fast_interpolate(domain: &[FF], values: &[FF]) -> Self {
1501        debug_assert!(
1502            !domain.is_empty(),
1503            "interpolation domain cannot have zero points"
1504        );
1505        debug_assert_eq!(domain.len(), values.len());
1506
1507        // prevent edge case failure where the left half would be empty
1508        if domain.len() == 1 {
1509            return Self::from_constant(values[0]);
1510        }
1511
1512        let mid_point = domain.len() / 2;
1513        let left_domain_half = &domain[..mid_point];
1514        let left_values_half = &values[..mid_point];
1515        let right_domain_half = &domain[mid_point..];
1516        let right_values_half = &values[mid_point..];
1517
1518        let left_zerofier = Self::zerofier(left_domain_half);
1519        let right_zerofier = Self::zerofier(right_domain_half);
1520
1521        let left_offset = right_zerofier.batch_evaluate(left_domain_half);
1522        let right_offset = left_zerofier.batch_evaluate(right_domain_half);
1523
1524        let hadamard_mul = |x: &[_], y: Vec<_>| x.iter().zip(y).map(|(&n, d)| n * d).collect_vec();
1525        let interpolate_half = |offset, domain_half, values_half| {
1526            let offset_inverse = FF::batch_inversion(offset);
1527            let targets = hadamard_mul(values_half, offset_inverse);
1528            Self::interpolate(domain_half, &targets)
1529        };
1530        let (left_interpolant, right_interpolant) = (
1531            interpolate_half(left_offset, left_domain_half, left_values_half),
1532            interpolate_half(right_offset, right_domain_half, right_values_half),
1533        );
1534
1535        let (left_term, right_term) = (
1536            left_interpolant.multiply(&right_zerofier),
1537            right_interpolant.multiply(&left_zerofier),
1538        );
1539
1540        left_term + right_term
1541    }
1542
1543    /// Only `pub` to allow benchmarking; not considered part of the public API.
1544    #[doc(hidden)]
1545    pub fn par_fast_interpolate(domain: &[FF], values: &[FF]) -> Self {
1546        debug_assert!(
1547            !domain.is_empty(),
1548            "interpolation domain cannot have zero points"
1549        );
1550        debug_assert_eq!(domain.len(), values.len());
1551
1552        // prevent edge case failure where the left half would be empty
1553        if domain.len() == 1 {
1554            return Self::from_constant(values[0]);
1555        }
1556
1557        let mid_point = domain.len() / 2;
1558        let left_domain_half = &domain[..mid_point];
1559        let left_values_half = &values[..mid_point];
1560        let right_domain_half = &domain[mid_point..];
1561        let right_values_half = &values[mid_point..];
1562
1563        let (left_zerofier, right_zerofier) = rayon::join(
1564            || Self::zerofier(left_domain_half),
1565            || Self::zerofier(right_domain_half),
1566        );
1567
1568        let (left_offset, right_offset) = rayon::join(
1569            || right_zerofier.par_batch_evaluate(left_domain_half),
1570            || left_zerofier.par_batch_evaluate(right_domain_half),
1571        );
1572
1573        let hadamard_mul = |x: &[_], y: Vec<_>| x.iter().zip(y).map(|(&n, d)| n * d).collect_vec();
1574        let interpolate_half = |offset, domain_half, values_half| {
1575            let offset_inverse = FF::batch_inversion(offset);
1576            let targets = hadamard_mul(values_half, offset_inverse);
1577            Self::par_interpolate(domain_half, &targets)
1578        };
1579        let (left_interpolant, right_interpolant) = rayon::join(
1580            || interpolate_half(left_offset, left_domain_half, left_values_half),
1581            || interpolate_half(right_offset, right_domain_half, right_values_half),
1582        );
1583
1584        let (left_term, right_term) = rayon::join(
1585            || left_interpolant.multiply(&right_zerofier),
1586            || right_interpolant.multiply(&left_zerofier),
1587        );
1588
1589        left_term + right_term
1590    }
1591
1592    pub fn batch_fast_interpolate(
1593        domain: &[FF],
1594        values_matrix: &[Vec<FF>],
1595        primitive_root: BFieldElement,
1596        root_order: usize,
1597    ) -> Vec<Self> {
1598        debug_assert_eq!(
1599            primitive_root.mod_pow_u32(root_order as u32),
1600            BFieldElement::ONE,
1601            "Supplied element โ€œprimitive_rootโ€ must have supplied order.\
1602            Supplied element was: {primitive_root:?}\
1603            Supplied order was: {root_order:?}"
1604        );
1605
1606        assert!(
1607            !domain.is_empty(),
1608            "Cannot fast interpolate through zero points.",
1609        );
1610
1611        let mut zerofier_dictionary: HashMap<(FF, FF), Polynomial<FF>> = HashMap::default();
1612        let mut offset_inverse_dictionary: HashMap<(FF, FF), Vec<FF>> = HashMap::default();
1613
1614        Self::batch_fast_interpolate_with_memoization(
1615            domain,
1616            values_matrix,
1617            &mut zerofier_dictionary,
1618            &mut offset_inverse_dictionary,
1619        )
1620    }
1621
1622    fn batch_fast_interpolate_with_memoization(
1623        domain: &[FF],
1624        values_matrix: &[Vec<FF>],
1625        zerofier_dictionary: &mut HashMap<(FF, FF), Polynomial<'static, FF>>,
1626        offset_inverse_dictionary: &mut HashMap<(FF, FF), Vec<FF>>,
1627    ) -> Vec<Self> {
1628        // This value of 16 was found to be optimal through a benchmark on sword_smith's
1629        // machine.
1630        const OPTIMAL_CUTOFF_POINT_FOR_BATCHED_INTERPOLATION: usize = 16;
1631        if domain.len() < OPTIMAL_CUTOFF_POINT_FOR_BATCHED_INTERPOLATION {
1632            return values_matrix
1633                .iter()
1634                .map(|values| Self::lagrange_interpolate(domain, values))
1635                .collect();
1636        }
1637
1638        // calculate everything related to the domain
1639        let half = domain.len() / 2;
1640
1641        let left_key = (domain[0], domain[half - 1]);
1642        let left_zerofier = match zerofier_dictionary.get(&left_key) {
1643            Some(z) => z.to_owned(),
1644            None => {
1645                let left_zerofier = Self::zerofier(&domain[..half]);
1646                zerofier_dictionary.insert(left_key, left_zerofier.clone());
1647                left_zerofier
1648            }
1649        };
1650        let right_key = (domain[half], *domain.last().unwrap());
1651        let right_zerofier = match zerofier_dictionary.get(&right_key) {
1652            Some(z) => z.to_owned(),
1653            None => {
1654                let right_zerofier = Self::zerofier(&domain[half..]);
1655                zerofier_dictionary.insert(right_key, right_zerofier.clone());
1656                right_zerofier
1657            }
1658        };
1659
1660        let left_offset_inverse = match offset_inverse_dictionary.get(&left_key) {
1661            Some(vector) => vector.to_owned(),
1662            None => {
1663                let left_offset: Vec<FF> = Self::batch_evaluate(&right_zerofier, &domain[..half]);
1664                let left_offset_inverse = FF::batch_inversion(left_offset);
1665                offset_inverse_dictionary.insert(left_key, left_offset_inverse.clone());
1666                left_offset_inverse
1667            }
1668        };
1669        let right_offset_inverse = match offset_inverse_dictionary.get(&right_key) {
1670            Some(vector) => vector.to_owned(),
1671            None => {
1672                let right_offset: Vec<FF> = Self::batch_evaluate(&left_zerofier, &domain[half..]);
1673                let right_offset_inverse = FF::batch_inversion(right_offset);
1674                offset_inverse_dictionary.insert(right_key, right_offset_inverse.clone());
1675                right_offset_inverse
1676            }
1677        };
1678
1679        // prepare target matrices
1680        let all_left_targets: Vec<_> = values_matrix
1681            .par_iter()
1682            .map(|values| {
1683                values[..half]
1684                    .iter()
1685                    .zip(left_offset_inverse.iter())
1686                    .map(|(n, d)| n.to_owned() * *d)
1687                    .collect()
1688            })
1689            .collect();
1690        let all_right_targets: Vec<_> = values_matrix
1691            .par_iter()
1692            .map(|values| {
1693                values[half..]
1694                    .par_iter()
1695                    .zip(right_offset_inverse.par_iter())
1696                    .map(|(n, d)| n.to_owned() * *d)
1697                    .collect()
1698            })
1699            .collect();
1700
1701        // recurse
1702        let left_interpolants = Self::batch_fast_interpolate_with_memoization(
1703            &domain[..half],
1704            &all_left_targets,
1705            zerofier_dictionary,
1706            offset_inverse_dictionary,
1707        );
1708        let right_interpolants = Self::batch_fast_interpolate_with_memoization(
1709            &domain[half..],
1710            &all_right_targets,
1711            zerofier_dictionary,
1712            offset_inverse_dictionary,
1713        );
1714
1715        // add vectors of polynomials
1716        let interpolants = left_interpolants
1717            .par_iter()
1718            .zip(right_interpolants.par_iter())
1719            .map(|(left_interpolant, right_interpolant)| {
1720                let left_term = left_interpolant.multiply(&right_zerofier);
1721                let right_term = right_interpolant.multiply(&left_zerofier);
1722
1723                left_term + right_term
1724            })
1725            .collect();
1726
1727        interpolants
1728    }
1729
1730    /// Evaluate the polynomial on a batch of points.
1731    pub fn batch_evaluate(&self, domain: &[FF]) -> Vec<FF> {
1732        if self.is_zero() {
1733            vec![FF::ZERO; domain.len()]
1734        } else if self.degree()
1735            >= Self::REDUCE_BEFORE_EVALUATE_THRESHOLD_RATIO * (domain.len() as isize)
1736        {
1737            self.reduce_then_batch_evaluate(domain)
1738        } else {
1739            let zerofier_tree = ZerofierTree::new_from_domain(domain);
1740            self.divide_and_conquer_batch_evaluate(&zerofier_tree)
1741        }
1742    }
1743
1744    fn reduce_then_batch_evaluate(&self, domain: &[FF]) -> Vec<FF> {
1745        let zerofier_tree = ZerofierTree::new_from_domain(domain);
1746        let zerofier = zerofier_tree.zerofier();
1747        let remainder = self.fast_reduce(&zerofier);
1748        remainder.divide_and_conquer_batch_evaluate(&zerofier_tree)
1749    }
1750
1751    /// Parallel version of [`batch_evaluate`](Self::batch_evaluate).
1752    pub fn par_batch_evaluate(&self, domain: &[FF]) -> Vec<FF> {
1753        if domain.is_empty() || self.is_zero() {
1754            return vec![FF::ZERO; domain.len()];
1755        }
1756        let num_threads = available_parallelism()
1757            .map(|non_zero_usize| non_zero_usize.get())
1758            .unwrap_or(1);
1759        let chunk_size = domain.len().div_ceil(num_threads);
1760        domain
1761            .par_chunks(chunk_size)
1762            .flat_map(|ch| self.batch_evaluate(ch))
1763            .collect()
1764    }
1765
1766    /// Only marked `pub` for benchmarking; not considered part of the public API.
1767    #[doc(hidden)]
1768    pub fn iterative_batch_evaluate(&self, domain: &[FF]) -> Vec<FF> {
1769        domain.iter().map(|&p| self.evaluate(p)).collect()
1770    }
1771
1772    /// Only `pub` to allow benchmarking; not considered part of the public API.
1773    #[doc(hidden)]
1774    pub fn divide_and_conquer_batch_evaluate(&self, zerofier_tree: &ZerofierTree<FF>) -> Vec<FF> {
1775        match zerofier_tree {
1776            ZerofierTree::Leaf(leaf) => self
1777                .reduce(&zerofier_tree.zerofier())
1778                .iterative_batch_evaluate(&leaf.points),
1779            ZerofierTree::Branch(branch) => [
1780                self.divide_and_conquer_batch_evaluate(&branch.left),
1781                self.divide_and_conquer_batch_evaluate(&branch.right),
1782            ]
1783            .concat(),
1784            ZerofierTree::Padding => vec![],
1785        }
1786    }
1787
1788    /// The inverse of [`Self::fast_coset_evaluate`].
1789    ///
1790    /// # Performance
1791    ///
1792    /// If possible, use a [base field element](BFieldElement) as the offset.
1793    ///
1794    /// # Panics
1795    ///
1796    /// Panics if the length of `values` is
1797    /// - not a power of 2
1798    /// - larger than [`u32::MAX`]
1799    pub fn fast_coset_interpolate<S>(offset: S, values: &[FF]) -> Self
1800    where
1801        S: Clone + One + Inverse,
1802        FF: Mul<S, Output = FF>,
1803    {
1804        let mut mut_values = values.to_vec();
1805
1806        intt(&mut mut_values);
1807        let poly = Polynomial::new(mut_values);
1808
1809        poly.scale(offset.inverse())
1810    }
1811
1812    /// The degree-`k` polynomial with the same `k + 1` leading coefficients as `self`. To be more
1813    /// precise: The degree of the result will be the minimum of `k` and [`Self::degree()`]. This
1814    /// implies, among other things, that if `self` [is zero](Self::is_zero()), the result will also
1815    /// be zero, independent of `k`.
1816    ///
1817    /// # Examples
1818    ///
1819    /// ```
1820    /// # use twenty_first::prelude::*;
1821    /// let f = Polynomial::new(bfe_vec![0, 1, 2, 3, 4]); // 4xโด + 3xยณ + 2xยฒ + 1xยน + 0
1822    /// let g = f.truncate(2);                            // 4xยฒ + 3xยน + 2
1823    /// assert_eq!(Polynomial::new(bfe_vec![2, 3, 4]), g);
1824    /// ```
1825    pub fn truncate(&self, k: usize) -> Self {
1826        let coefficients = self.coefficients.iter().copied();
1827        let coefficients = coefficients.rev().take(k + 1).rev().collect();
1828        Self::new(coefficients)
1829    }
1830
1831    /// `self % x^n`
1832    ///
1833    /// A special case of [Self::rem], and faster.
1834    ///
1835    /// # Examples
1836    ///
1837    /// ```
1838    /// # use twenty_first::prelude::*;
1839    /// let f = Polynomial::new(bfe_vec![0, 1, 2, 3, 4]); // 4xโด + 3xยณ + 2xยฒ + 1xยน + 0
1840    /// let g = f.mod_x_to_the_n(2);                      // 1xยน + 0
1841    /// assert_eq!(Polynomial::new(bfe_vec![0, 1]), g);
1842    /// ```
1843    pub fn mod_x_to_the_n(&self, n: usize) -> Self {
1844        let num_coefficients_to_retain = n.min(self.coefficients.len());
1845        Self::new(self.coefficients[..num_coefficients_to_retain].into())
1846    }
1847
1848    /// Preprocessing data for
1849    /// [fast modular coset interpolation](Self::fast_modular_coset_interpolate).
1850    /// Marked `pub` for benchmarking. Not considered part of the public API.
1851    #[doc(hidden)]
1852    pub fn fast_modular_coset_interpolate_preprocess(
1853        n: usize,
1854        offset: BFieldElement,
1855        modulus: &Polynomial<FF>,
1856    ) -> ModularInterpolationPreprocessingData<'static, FF> {
1857        let omega = BFieldElement::primitive_root_of_unity(n as u64).unwrap();
1858        // a list of polynomials whose ith element is X^(2^i) mod m(X)
1859        let modular_squares = (0..n.ilog2())
1860            .scan(Polynomial::<FF>::x_to_the(1), |acc, _| {
1861                let yld = acc.clone();
1862                *acc = acc.multiply(acc).reduce(modulus);
1863                Some(yld)
1864            })
1865            .collect_vec();
1866        let even_zerofiers = (0..n.ilog2())
1867            .map(|i| offset.inverse().mod_pow(1u64 << i))
1868            .zip(modular_squares.iter())
1869            .map(|(lc, sq)| sq.scalar_mul(FF::from(lc.value())) - Polynomial::one())
1870            .collect_vec();
1871        let odd_zerofiers = (0..n.ilog2())
1872            .map(|i| (offset * omega).inverse().mod_pow(1u64 << i))
1873            .zip(modular_squares.iter())
1874            .map(|(lc, sq)| sq.scalar_mul(FF::from(lc.value())) - Polynomial::one())
1875            .collect_vec();
1876
1877        // precompute NTT-friendly multiple of the modulus
1878        let (shift_coefficients, tail_length) = modulus.shift_factor_ntt_with_tail_length();
1879
1880        ModularInterpolationPreprocessingData {
1881            even_zerofiers,
1882            odd_zerofiers,
1883            shift_coefficients,
1884            tail_length,
1885        }
1886    }
1887
1888    /// Compute f(X) mod m(X) where m(X) is a given modulus and f(X) is the
1889    /// interpolant of a list of n values on a domain which is a coset of the
1890    /// size-n subgroup that is identified by some offset.
1891    fn fast_modular_coset_interpolate(
1892        values: &[FF],
1893        offset: BFieldElement,
1894        modulus: &Polynomial<FF>,
1895    ) -> Self {
1896        let preprocessing_data =
1897            Self::fast_modular_coset_interpolate_preprocess(values.len(), offset, modulus);
1898        Self::fast_modular_coset_interpolate_with_zerofiers_and_ntt_friendly_multiple(
1899            values,
1900            offset,
1901            modulus,
1902            &preprocessing_data,
1903        )
1904    }
1905
1906    /// Only marked `pub` for benchmarking purposes. Not considered part of the
1907    /// interface.
1908    #[doc(hidden)]
1909    pub fn fast_modular_coset_interpolate_with_zerofiers_and_ntt_friendly_multiple(
1910        values: &[FF],
1911        offset: BFieldElement,
1912        modulus: &Polynomial<FF>,
1913        preprocessed: &ModularInterpolationPreprocessingData<FF>,
1914    ) -> Self {
1915        if modulus.degree() < 0 {
1916            panic!("cannot reduce modulo zero")
1917        };
1918        let n = values.len();
1919        let omega = BFieldElement::primitive_root_of_unity(n as u64).unwrap();
1920
1921        if n < Self::FAST_MODULAR_COSET_INTERPOLATE_CUTOFF_THRESHOLD_PREFER_LAGRANGE {
1922            let domain = (0..n)
1923                .scan(FF::from(offset.value()), |acc: &mut FF, _| {
1924                    let yld = *acc;
1925                    *acc *= omega;
1926                    Some(yld)
1927                })
1928                .collect::<Vec<FF>>();
1929            let interpolant = Self::lagrange_interpolate(&domain, values);
1930            return interpolant.reduce(modulus);
1931        } else if n <= Self::FAST_MODULAR_COSET_INTERPOLATE_CUTOFF_THRESHOLD_PREFER_INTT {
1932            let mut coefficients = values.to_vec();
1933            intt(&mut coefficients);
1934            let interpolant = Polynomial::new(coefficients);
1935
1936            return interpolant
1937                .scale(FF::from(offset.inverse().value()))
1938                .reduce_by_ntt_friendly_modulus(
1939                    &preprocessed.shift_coefficients,
1940                    preprocessed.tail_length,
1941                )
1942                .reduce(modulus);
1943        }
1944
1945        // Use even-odd domain split.
1946        // Even: {offset * omega^{2*i} | i in 0..n/2}
1947        // Odd: {offset * omega^{2*i+1} | i in 0..n/2}
1948        //      = {(offset * omega) * omega^{2*i} | i in 0..n/2}
1949        // But we don't actually need to represent the domains explicitly.
1950
1951        // 1. Get zerofiers.
1952        // The zerofiers are sparse because the domain is structured.
1953        // Even: (offset^-1 * X)^(n/2) - 1 = offset^{-n/2} * X^{n/2} - 1
1954        // Odd: ((offset * omega)^-1 * X)^(n/2) - 1
1955        //      = offset^{-n/2} * omega^{n/2} * X^{n/2} - 1
1956        // Note that we are getting the (modularly reduced) zerofiers as
1957        // function arguments.
1958
1959        // 2. Evaluate zerofiers on opposite domains.
1960        // Actually, the values are compressible because the zerofiers are
1961        // sparse and the domains are structured (compatibly).
1962        // Even zerofier on odd domain:
1963        // (offset^-1 * X)^(n/2) - 1 on
1964        // {(offset * omega) * omega^{2*i} | i in 0..n/2}
1965        // = {omega^{n/2}-1 | i in 0..n/2} = {-2, -2, -2, ...}
1966        // Odd zerofier on even domain: {omega^-i | i in 0..n/2}
1967        // ((offset * omega)^-1 * X)^(n/2) - 1
1968        // on {offset * omega^{2*i} | i in 0..n/2}
1969        // = {omega^{-n/2} - 1 | i in 0..n/2} = {-2, -2, -2, ...}
1970        // Since these values are always the same, there's no point generating
1971        // them at runtime. Moreover, we need their batch-inverses in the next
1972        // step.
1973
1974        // 3. Batch-invert zerofiers on opposite domains.
1975        // The batch-inversion is actually not performed because we already know
1976        // the result: {(-2)^-1, (-2)^-1, (-2)^-1, ...}.
1977        const MINUS_TWO_INVERSE: BFieldElement = BFieldElement::MINUS_TWO_INVERSE;
1978        let even_zerofier_on_odd_domain_inverted = vec![FF::from(MINUS_TWO_INVERSE.value()); n / 2];
1979        let odd_zerofier_on_even_domain_inverted = vec![FF::from(MINUS_TWO_INVERSE.value()); n / 2];
1980
1981        // 4. Construct interpolation values through Hadamard products.
1982        let mut odd_domain_targets = even_zerofier_on_odd_domain_inverted;
1983        let mut even_domain_targets = odd_zerofier_on_even_domain_inverted;
1984        for i in 0..(n / 2) {
1985            even_domain_targets[i] *= values[2 * i];
1986            odd_domain_targets[i] *= values[2 * i + 1];
1987        }
1988
1989        // 5. Interpolate using recursion
1990        let even_interpolant =
1991            Self::fast_modular_coset_interpolate(&even_domain_targets, offset, modulus);
1992        let odd_interpolant =
1993            Self::fast_modular_coset_interpolate(&odd_domain_targets, offset * omega, modulus);
1994
1995        // 6. Multiply with zerofiers and add.
1996        let interpolant = even_interpolant
1997            .multiply(&preprocessed.odd_zerofiers[(n / 2).ilog2() as usize])
1998            + odd_interpolant.multiply(&preprocessed.even_zerofiers[(n / 2).ilog2() as usize]);
1999
2000        // 7. Reduce by modulus and return.
2001        interpolant.reduce(modulus)
2002    }
2003
2004    /// Extrapolate a Reed-Solomon codeword, defined relative to a coset of the
2005    /// subgroup of order n (codeword length), in new points.
2006    pub fn coset_extrapolate(
2007        domain_offset: BFieldElement,
2008        codeword: &[FF],
2009        points: &[FF],
2010    ) -> Vec<FF> {
2011        if points.len() < Self::FAST_COSET_EXTRAPOLATE_THRESHOLD {
2012            Self::fast_coset_extrapolate(domain_offset, codeword, points)
2013        } else {
2014            Self::naive_coset_extrapolate(domain_offset, codeword, points)
2015        }
2016    }
2017
2018    fn naive_coset_extrapolate_preprocessing(points: &[FF]) -> (ZerofierTree<FF>, Vec<FF>, usize) {
2019        let zerofier_tree = ZerofierTree::new_from_domain(points);
2020        let (shift_coefficients, tail_length) =
2021            Self::shift_factor_ntt_with_tail_length(&zerofier_tree.zerofier());
2022        (zerofier_tree, shift_coefficients, tail_length)
2023    }
2024
2025    fn naive_coset_extrapolate(
2026        domain_offset: BFieldElement,
2027        codeword: &[FF],
2028        points: &[FF],
2029    ) -> Vec<FF> {
2030        let mut coefficients = codeword.to_vec();
2031        intt(&mut coefficients);
2032        let interpolant =
2033            Polynomial::new(coefficients).scale(FF::from(domain_offset.inverse().value()));
2034        interpolant.batch_evaluate(points)
2035    }
2036
2037    fn fast_coset_extrapolate(
2038        domain_offset: BFieldElement,
2039        codeword: &[FF],
2040        points: &[FF],
2041    ) -> Vec<FF> {
2042        let zerofier_tree = ZerofierTree::new_from_domain(points);
2043        let minimal_interpolant = Self::fast_modular_coset_interpolate(
2044            codeword,
2045            domain_offset,
2046            &zerofier_tree.zerofier(),
2047        );
2048        minimal_interpolant.divide_and_conquer_batch_evaluate(&zerofier_tree)
2049    }
2050
2051    /// Extrapolate many Reed-Solomon codewords, defined relative to the same
2052    /// coset of the subgroup of order `codeword_length`, in the same set of
2053    /// new points.
2054    ///
2055    /// # Example
2056    /// ```
2057    /// # use twenty_first::prelude::*;
2058    /// let n = 1 << 5;
2059    /// let domain_offset = bfe!(7);
2060    /// let codewords = [bfe_vec![3; n], bfe_vec![2; n]].concat();
2061    /// let points = bfe_vec![0, 1];
2062    /// assert_eq!(
2063    ///     bfe_vec![3, 3, 2, 2],
2064    ///     Polynomial::<BFieldElement>::batch_coset_extrapolate(
2065    ///         domain_offset,
2066    ///         n,
2067    ///         &codewords,
2068    ///         &points
2069    ///     )
2070    /// );
2071    /// ```
2072    ///
2073    /// # Panics
2074    /// Panics if the `codeword_length` is not a power of two.
2075    pub fn batch_coset_extrapolate(
2076        domain_offset: BFieldElement,
2077        codeword_length: usize,
2078        codewords: &[FF],
2079        points: &[FF],
2080    ) -> Vec<FF> {
2081        if points.len() < Self::FAST_COSET_EXTRAPOLATE_THRESHOLD {
2082            Self::batch_fast_coset_extrapolate(domain_offset, codeword_length, codewords, points)
2083        } else {
2084            Self::batch_naive_coset_extrapolate(domain_offset, codeword_length, codewords, points)
2085        }
2086    }
2087
2088    fn batch_fast_coset_extrapolate(
2089        domain_offset: BFieldElement,
2090        codeword_length: usize,
2091        codewords: &[FF],
2092        points: &[FF],
2093    ) -> Vec<FF> {
2094        let n = codeword_length;
2095
2096        let zerofier_tree = ZerofierTree::new_from_domain(points);
2097        let modulus = zerofier_tree.zerofier();
2098        let preprocessing_data = Self::fast_modular_coset_interpolate_preprocess(
2099            codeword_length,
2100            domain_offset,
2101            &modulus,
2102        );
2103
2104        (0..codewords.len() / n)
2105            .flat_map(|i| {
2106                let codeword = &codewords[i * n..(i + 1) * n];
2107                let minimal_interpolant =
2108                    Self::fast_modular_coset_interpolate_with_zerofiers_and_ntt_friendly_multiple(
2109                        codeword,
2110                        domain_offset,
2111                        &modulus,
2112                        &preprocessing_data,
2113                    );
2114                minimal_interpolant.divide_and_conquer_batch_evaluate(&zerofier_tree)
2115            })
2116            .collect()
2117    }
2118
2119    fn batch_naive_coset_extrapolate(
2120        domain_offset: BFieldElement,
2121        codeword_length: usize,
2122        codewords: &[FF],
2123        points: &[FF],
2124    ) -> Vec<FF> {
2125        let (zerofier_tree, shift_coefficients, tail_length) =
2126            Self::naive_coset_extrapolate_preprocessing(points);
2127        let n = codeword_length;
2128
2129        (0..codewords.len() / n)
2130            .flat_map(|i| {
2131                let mut coefficients = codewords[i * n..(i + 1) * n].to_vec();
2132                intt(&mut coefficients);
2133                Polynomial::new(coefficients)
2134                    .scale(FF::from(domain_offset.inverse().value()))
2135                    .reduce_by_ntt_friendly_modulus(&shift_coefficients, tail_length)
2136                    .divide_and_conquer_batch_evaluate(&zerofier_tree)
2137            })
2138            .collect()
2139    }
2140
2141    /// Parallel version of [`batch_coset_extrapolate`](Self::batch_coset_extrapolate).
2142    pub fn par_batch_coset_extrapolate(
2143        domain_offset: BFieldElement,
2144        codeword_length: usize,
2145        codewords: &[FF],
2146        points: &[FF],
2147    ) -> Vec<FF> {
2148        if points.len() < Self::FAST_COSET_EXTRAPOLATE_THRESHOLD {
2149            Self::par_batch_fast_coset_extrapolate(
2150                domain_offset,
2151                codeword_length,
2152                codewords,
2153                points,
2154            )
2155        } else {
2156            Self::par_batch_naive_coset_extrapolate(
2157                domain_offset,
2158                codeword_length,
2159                codewords,
2160                points,
2161            )
2162        }
2163    }
2164
2165    fn par_batch_fast_coset_extrapolate(
2166        domain_offset: BFieldElement,
2167        codeword_length: usize,
2168        codewords: &[FF],
2169        points: &[FF],
2170    ) -> Vec<FF> {
2171        let n = codeword_length;
2172
2173        let zerofier_tree = ZerofierTree::new_from_domain(points);
2174        let modulus = zerofier_tree.zerofier();
2175        let preprocessing_data = Self::fast_modular_coset_interpolate_preprocess(
2176            codeword_length,
2177            domain_offset,
2178            &modulus,
2179        );
2180
2181        (0..codewords.len() / n)
2182            .into_par_iter()
2183            .flat_map(|i| {
2184                let codeword = &codewords[i * n..(i + 1) * n];
2185                let minimal_interpolant =
2186                    Self::fast_modular_coset_interpolate_with_zerofiers_and_ntt_friendly_multiple(
2187                        codeword,
2188                        domain_offset,
2189                        &modulus,
2190                        &preprocessing_data,
2191                    );
2192                minimal_interpolant.divide_and_conquer_batch_evaluate(&zerofier_tree)
2193            })
2194            .collect()
2195    }
2196
2197    fn par_batch_naive_coset_extrapolate(
2198        domain_offset: BFieldElement,
2199        codeword_length: usize,
2200        codewords: &[FF],
2201        points: &[FF],
2202    ) -> Vec<FF> {
2203        let (zerofier_tree, shift_coefficients, tail_length) =
2204            Self::naive_coset_extrapolate_preprocessing(points);
2205        let n = codeword_length;
2206
2207        (0..codewords.len() / n)
2208            .into_par_iter()
2209            .flat_map(|i| {
2210                let mut coefficients = codewords[i * n..(i + 1) * n].to_vec();
2211                intt(&mut coefficients);
2212                Polynomial::new(coefficients)
2213                    .scale(FF::from(domain_offset.inverse().value()))
2214                    .reduce_by_ntt_friendly_modulus(&shift_coefficients, tail_length)
2215                    .divide_and_conquer_batch_evaluate(&zerofier_tree)
2216            })
2217            .collect()
2218    }
2219}
2220
2221impl Polynomial<'_, BFieldElement> {
2222    /// [Clean division](Self::clean_divide) is slower than [naรฏve division](Self::naive_divide) for
2223    /// polynomials of degree less than this threshold.
2224    ///
2225    /// Extracted from `cargo bench --bench poly_clean_div` on mjolnir.
2226    const CLEAN_DIVIDE_CUTOFF_THRESHOLD: isize = {
2227        if cfg!(test) {
2228            0
2229        } else {
2230            1 << 9
2231        }
2232    };
2233
2234    /// A fast way of dividing two polynomials. Only works if division is clean, _i.e._, if the
2235    /// remainder of polynomial long division is [zero]. This **must** be known ahead of time. If
2236    /// division is unclean, this method might panic or produce a wrong result.
2237    /// Use [`Polynomial::divide`] for more generality.
2238    ///
2239    /// # Panics
2240    ///
2241    /// Panics if
2242    /// - the divisor is [zero], or
2243    /// - division is not clean, _i.e._, if polynomial long division leaves some non-zero remainder.
2244    ///
2245    /// [zero]: Polynomial::is_zero
2246    #[must_use]
2247    #[expect(clippy::shadow_unrelated)]
2248    pub fn clean_divide(self, divisor: Self) -> Polynomial<'static, BFieldElement> {
2249        let dividend = self;
2250        if divisor.degree() < Self::CLEAN_DIVIDE_CUTOFF_THRESHOLD {
2251            let (quotient, remainder) = dividend.divide(&divisor);
2252            debug_assert!(remainder.is_zero());
2253            return quotient;
2254        }
2255
2256        // Incompleteness workaround: Manually check whether 0 is a root of the divisor.
2257        // f(0) == 0 <=> f's constant term is 0
2258        let mut dividend_coefficients = dividend.coefficients.into_owned();
2259        let mut divisor_coefficients = divisor.coefficients.into_owned();
2260        if divisor_coefficients.first().is_some_and(Zero::is_zero) {
2261            // Clean division implies the dividend also has 0 as a root.
2262            assert!(dividend_coefficients[0].is_zero());
2263            dividend_coefficients.remove(0);
2264            divisor_coefficients.remove(0);
2265        }
2266        let dividend = Polynomial::new(dividend_coefficients);
2267        let divisor = Polynomial::new(divisor_coefficients);
2268
2269        // Incompleteness workaround: Move both dividend and divisor to an extension field.
2270        let offset = XFieldElement::from([0, 1, 0]);
2271        let mut dividend_coefficients = dividend.scale(offset).coefficients.into_owned();
2272        let mut divisor_coefficients = divisor.scale(offset).coefficients.into_owned();
2273
2274        // See the comment in `fast_coset_evaluate` why this bound is necessary.
2275        let dividend_deg_plus_1 = usize::try_from(dividend.degree() + 1).unwrap();
2276        let order = dividend_deg_plus_1.next_power_of_two();
2277
2278        dividend_coefficients.resize(order, XFieldElement::ZERO);
2279        divisor_coefficients.resize(order, XFieldElement::ZERO);
2280
2281        ntt(&mut dividend_coefficients);
2282        ntt(&mut divisor_coefficients);
2283
2284        let divisor_inverses = XFieldElement::batch_inversion(divisor_coefficients);
2285        let mut quotient_codeword = dividend_coefficients
2286            .into_iter()
2287            .zip(divisor_inverses)
2288            .map(|(l, r)| l * r)
2289            .collect_vec();
2290
2291        intt(&mut quotient_codeword);
2292        let quotient = Polynomial::new(quotient_codeword);
2293
2294        // If the division was clean, โ€œunscalingโ€ brings all coefficients back to the base field.
2295        let Cow::Owned(coeffs) = quotient.scale(offset.inverse()).coefficients else {
2296            unreachable!();
2297        };
2298
2299        Polynomial::new(coeffs.into_iter().map(|c| c.unlift().unwrap()).collect())
2300    }
2301}
2302
2303impl<const N: usize, FF, E> From<[E; N]> for Polynomial<'static, FF>
2304where
2305    FF: FiniteField,
2306    E: Into<FF>,
2307{
2308    fn from(coefficients: [E; N]) -> Self {
2309        Self::new(coefficients.into_iter().map(|x| x.into()).collect())
2310    }
2311}
2312
2313impl<'c, FF> From<&'c [FF]> for Polynomial<'c, FF>
2314where
2315    FF: FiniteField,
2316{
2317    fn from(coefficients: &'c [FF]) -> Self {
2318        Self::new_borrowed(coefficients)
2319    }
2320}
2321
2322impl<FF, E> From<Vec<E>> for Polynomial<'static, FF>
2323where
2324    FF: FiniteField,
2325    E: Into<FF>,
2326{
2327    fn from(coefficients: Vec<E>) -> Self {
2328        Self::new(coefficients.into_iter().map(|c| c.into()).collect())
2329    }
2330}
2331
2332impl From<XFieldElement> for Polynomial<'static, BFieldElement> {
2333    fn from(xfe: XFieldElement) -> Self {
2334        Self::new(xfe.coefficients.to_vec())
2335    }
2336}
2337
2338impl<FF> Polynomial<'static, FF>
2339where
2340    FF: FiniteField,
2341{
2342    /// Create a new polynomial with the given coefficients. The first coefficient
2343    /// is the constant term, the last coefficient has the highest degree.
2344    ///
2345    /// See also [`Self::new_borrowed`].
2346    pub fn new(coefficients: Vec<FF>) -> Self {
2347        let coefficients = Cow::Owned(coefficients);
2348        Self { coefficients }
2349    }
2350
2351    /// `x^n`
2352    pub fn x_to_the(n: usize) -> Self {
2353        let mut coefficients = vec![FF::ZERO; n + 1];
2354        coefficients[n] = FF::ONE;
2355        Self::new(coefficients)
2356    }
2357
2358    pub fn from_constant(constant: FF) -> Self {
2359        Self::new(vec![constant])
2360    }
2361
2362    /// Only `pub` to allow benchmarking; not considered part of the public API.
2363    #[doc(hidden)]
2364    pub fn naive_zerofier(domain: &[FF]) -> Self {
2365        domain
2366            .iter()
2367            .map(|&r| Self::new(vec![-r, FF::ONE]))
2368            .reduce(|accumulator, linear_poly| accumulator * linear_poly)
2369            .unwrap_or_else(Self::one)
2370    }
2371}
2372
2373impl<'coeffs, FF> Polynomial<'coeffs, FF>
2374where
2375    FF: FiniteField,
2376{
2377    /// Like [`Self::new`], but without owning the coefficients.
2378    pub fn new_borrowed(coefficients: &'coeffs [FF]) -> Self {
2379        let coefficients = Cow::Borrowed(coefficients);
2380        Self { coefficients }
2381    }
2382}
2383
2384impl<FF> Div<Polynomial<'_, FF>> for Polynomial<'_, FF>
2385where
2386    FF: FiniteField + 'static,
2387{
2388    type Output = Polynomial<'static, FF>;
2389
2390    fn div(self, other: Polynomial<'_, FF>) -> Self::Output {
2391        let (quotient, _) = self.naive_divide(&other);
2392        quotient
2393    }
2394}
2395
2396impl<FF> Rem<Polynomial<'_, FF>> for Polynomial<'_, FF>
2397where
2398    FF: FiniteField + 'static,
2399{
2400    type Output = Polynomial<'static, FF>;
2401
2402    fn rem(self, other: Polynomial<'_, FF>) -> Self::Output {
2403        let (_, remainder) = self.naive_divide(&other);
2404        remainder
2405    }
2406}
2407
2408impl<FF> Add<Polynomial<'_, FF>> for Polynomial<'_, FF>
2409where
2410    FF: FiniteField + 'static,
2411{
2412    type Output = Polynomial<'static, FF>;
2413
2414    fn add(self, other: Polynomial<'_, FF>) -> Self::Output {
2415        let summed = self
2416            .coefficients
2417            .iter()
2418            .zip_longest(other.coefficients.iter())
2419            .map(|a| match a {
2420                EitherOrBoth::Both(&l, &r) => l + r,
2421                EitherOrBoth::Left(&c) | EitherOrBoth::Right(&c) => c,
2422            })
2423            .collect();
2424
2425        Polynomial::new(summed)
2426    }
2427}
2428
2429impl<FF: FiniteField> AddAssign<Polynomial<'_, FF>> for Polynomial<'_, FF> {
2430    fn add_assign(&mut self, rhs: Polynomial<'_, FF>) {
2431        let rhs_len = rhs.coefficients.len();
2432        let self_len = self.coefficients.len();
2433        let mut self_coefficients = std::mem::take(&mut self.coefficients).into_owned();
2434
2435        for (l, &r) in self_coefficients.iter_mut().zip(rhs.coefficients.iter()) {
2436            *l += r;
2437        }
2438
2439        if rhs_len > self_len {
2440            self_coefficients.extend(&rhs.coefficients[self_len..]);
2441        }
2442
2443        self.coefficients = Cow::Owned(self_coefficients);
2444    }
2445}
2446
2447impl<FF> Sub<Polynomial<'_, FF>> for Polynomial<'_, FF>
2448where
2449    FF: FiniteField + 'static,
2450{
2451    type Output = Polynomial<'static, FF>;
2452
2453    fn sub(self, other: Polynomial<'_, FF>) -> Self::Output {
2454        let coefficients = self
2455            .coefficients
2456            .iter()
2457            .zip_longest(other.coefficients.iter())
2458            .map(|a| match a {
2459                EitherOrBoth::Both(&l, &r) => l - r,
2460                EitherOrBoth::Left(&l) => l,
2461                EitherOrBoth::Right(&r) => FF::ZERO - r,
2462            })
2463            .collect();
2464
2465        Polynomial::new(coefficients)
2466    }
2467}
2468
2469/// Use the barycentric Lagrange evaluation formula to evaluate a polynomial in
2470/// โ€œvalue formโ€, also known as a codeword. This is generally more efficient
2471/// than first [interpolating](Polynomial::interpolate), then
2472/// [evaluating](Polynomial::evaluate).
2473///
2474/// [Credit] for (re)discovering this formula goes to Al-Kindi.
2475///
2476/// # Panics
2477///
2478/// Panics if the codeword is some length that is
2479/// - not a power of 2, or
2480/// - greater than (1 << 32).
2481///
2482/// [Credit]: https://github.com/0xPolygonMiden/miden-vm/issues/568
2483//
2484// The trait bounds of the form `A: Mul<B, Output = C>` allow using both
2485// base & extension field elements for both `A` and `B`, giving the greatest
2486// generality in using the function.
2487//
2488// It is possible to remove one of the generics by returning type
2489// `<<Coeff as Mul<Ind>>::Output as Mul<Ind>>::Output`
2490// (and changing a few trait bounds) but who would want to read that?
2491pub fn barycentric_evaluate<Ind, Coeff, Eval>(
2492    codeword: &[Coeff],
2493    indeterminate: Ind,
2494) -> <Eval as Mul<Ind>>::Output
2495where
2496    Ind: FiniteField + Mul<BFieldElement, Output = Ind> + Sub<BFieldElement, Output = Ind>,
2497    Coeff: FiniteField + Mul<Ind, Output = Eval>,
2498    Eval: FiniteField + Mul<Ind>,
2499{
2500    let root_order = codeword.len().try_into().unwrap();
2501    let generator = BFieldElement::primitive_root_of_unity(root_order).unwrap();
2502    let domain_iter = (0..root_order).scan(BFieldElement::ONE, |acc, _| {
2503        let to_yield = Some(*acc);
2504        *acc *= generator;
2505        to_yield
2506    });
2507
2508    let domain_shift = domain_iter.clone().map(|d| indeterminate - d).collect();
2509    let domain_shift_inverses = Ind::batch_inversion(domain_shift);
2510    let domain_over_domain_shift = domain_iter
2511        .zip(domain_shift_inverses)
2512        .map(|(d, inv)| inv * d);
2513    let denominator = domain_over_domain_shift.clone().fold(Ind::ZERO, Ind::add);
2514    let numerator = domain_over_domain_shift
2515        .zip(codeword)
2516        .map(|(dsi, &abscis)| abscis * dsi)
2517        .fold(Eval::ZERO, Eval::add);
2518
2519    numerator * denominator.inverse()
2520}
2521
2522// It is impossible to
2523// `impl<FF: FiniteField> Mul<Polynomial<FF>> for FF`
2524// because of Rust's orphan rules [E0210]. Citing RFC 2451:
2525//
2526// > Rustโ€™s orphan rule always permits an impl if either the trait or the type
2527// > being implemented are local to the current crate. Therefore, we canโ€™t allow
2528// > `impl<T> ForeignTrait<LocalTypeCrateA> for T`, because it might conflict
2529// > with another crate writing `impl<T> ForeignTrait<T> for LocalTypeCrateB`,
2530// > which we will always permit.
2531
2532impl<FF, FF2> Mul<Polynomial<'_, FF>> for BFieldElement
2533where
2534    FF: FiniteField + Mul<BFieldElement, Output = FF2>,
2535    FF2: 'static + FiniteField,
2536{
2537    type Output = Polynomial<'static, FF2>;
2538
2539    fn mul(self, other: Polynomial<FF>) -> Self::Output {
2540        other.scalar_mul(self)
2541    }
2542}
2543
2544impl<FF, FF2> Mul<Polynomial<'_, FF>> for XFieldElement
2545where
2546    FF: FiniteField + Mul<XFieldElement, Output = FF2>,
2547    FF2: 'static + FiniteField,
2548{
2549    type Output = Polynomial<'static, FF2>;
2550
2551    fn mul(self, other: Polynomial<FF>) -> Self::Output {
2552        other.scalar_mul(self)
2553    }
2554}
2555
2556impl<S, FF, FF2> Mul<S> for Polynomial<'_, FF>
2557where
2558    S: FiniteField,
2559    FF: FiniteField + Mul<S, Output = FF2>,
2560    FF2: 'static + FiniteField,
2561{
2562    type Output = Polynomial<'static, FF2>;
2563
2564    fn mul(self, other: S) -> Self::Output {
2565        self.scalar_mul(other)
2566    }
2567}
2568
2569impl<FF, FF2> Mul<Polynomial<'_, FF2>> for Polynomial<'_, FF>
2570where
2571    FF: FiniteField + Mul<FF2>,
2572    FF2: FiniteField,
2573    <FF as Mul<FF2>>::Output: 'static + FiniteField,
2574{
2575    type Output = Polynomial<'static, <FF as Mul<FF2>>::Output>;
2576
2577    fn mul(self, other: Polynomial<'_, FF2>) -> Polynomial<'static, <FF as Mul<FF2>>::Output> {
2578        self.naive_multiply(&other)
2579    }
2580}
2581
2582impl<FF> Neg for Polynomial<'_, FF>
2583where
2584    FF: FiniteField + 'static,
2585{
2586    type Output = Polynomial<'static, FF>;
2587
2588    fn neg(mut self) -> Self::Output {
2589        self.scalar_mul_mut(-FF::ONE);
2590
2591        // communicate the cloning that has already happened in `scalar_mul_mut()`
2592        self.into_owned()
2593    }
2594}
2595
2596#[cfg(test)]
2597mod test_polynomials {
2598    use num_traits::ConstZero;
2599    use proptest::collection::size_range;
2600    use proptest::collection::vec;
2601    use proptest::prelude::*;
2602    use proptest_arbitrary_interop::arb;
2603    use test_strategy::proptest;
2604
2605    use super::*;
2606    use crate::prelude::*;
2607
2608    /// A type alias exclusive to the test module.
2609    type BfePoly = Polynomial<'static, BFieldElement>;
2610
2611    /// A type alias exclusive to the test module.
2612    type XfePoly = Polynomial<'static, XFieldElement>;
2613
2614    impl proptest::arbitrary::Arbitrary for BfePoly {
2615        type Parameters = ();
2616
2617        fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {
2618            arb().boxed()
2619        }
2620
2621        type Strategy = BoxedStrategy<Self>;
2622    }
2623
2624    impl proptest::arbitrary::Arbitrary for XfePoly {
2625        type Parameters = ();
2626
2627        fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {
2628            arb().boxed()
2629        }
2630
2631        type Strategy = BoxedStrategy<Self>;
2632    }
2633
2634    #[test]
2635    fn polynomial_can_be_debug_printed() {
2636        let polynomial = Polynomial::new(bfe_vec![1, 2, 3]);
2637        println!("{polynomial:?}");
2638    }
2639
2640    #[proptest]
2641    fn unequal_hash_implies_unequal_polynomials(poly_0: BfePoly, poly_1: BfePoly) {
2642        let hash = |poly: &Polynomial<_>| {
2643            let mut hasher = std::hash::DefaultHasher::new();
2644            poly.hash(&mut hasher);
2645            std::hash::Hasher::finish(&hasher)
2646        };
2647
2648        // The `Hash` trait requires:
2649        // poly_0 == poly_1 => hash(poly_0) == hash(poly_1)
2650        //
2651        // By De-Morgan's law, this is equivalent to the more meaningful test:
2652        // hash(poly_0) != hash(poly_1) => poly_0 != poly_1
2653        if hash(&poly_0) != hash(&poly_1) {
2654            prop_assert_ne!(poly_0, poly_1);
2655        }
2656    }
2657
2658    #[test]
2659    fn polynomial_display_test() {
2660        fn polynomial<const N: usize>(coeffs: [u64; N]) -> BfePoly {
2661            Polynomial::new(coeffs.map(BFieldElement::new).to_vec())
2662        }
2663
2664        assert_eq!("0", polynomial([]).to_string());
2665        assert_eq!("0", polynomial([0]).to_string());
2666        assert_eq!("0", polynomial([0, 0]).to_string());
2667
2668        assert_eq!("1", polynomial([1]).to_string());
2669        assert_eq!("2", polynomial([2, 0]).to_string());
2670        assert_eq!("3", polynomial([3, 0, 0]).to_string());
2671
2672        assert_eq!("x", polynomial([0, 1]).to_string());
2673        assert_eq!("2x", polynomial([0, 2]).to_string());
2674        assert_eq!("3x", polynomial([0, 3]).to_string());
2675
2676        assert_eq!("5x + 2", polynomial([2, 5]).to_string());
2677        assert_eq!("9x + 7", polynomial([7, 9, 0, 0, 0]).to_string());
2678
2679        assert_eq!("4x^4 + 3x^3", polynomial([0, 0, 0, 3, 4]).to_string());
2680        assert_eq!("2x^4 + 1", polynomial([1, 0, 0, 0, 2]).to_string());
2681    }
2682
2683    #[proptest]
2684    fn leading_coefficient_of_zero_polynomial_is_none(#[strategy(0usize..30)] num_zeros: usize) {
2685        let coefficients = vec![BFieldElement::ZERO; num_zeros];
2686        let polynomial = Polynomial::new(coefficients);
2687        prop_assert!(polynomial.leading_coefficient().is_none());
2688    }
2689
2690    #[proptest]
2691    fn leading_coefficient_of_non_zero_polynomial_is_some(
2692        polynomial: BfePoly,
2693        leading_coefficient: BFieldElement,
2694        #[strategy(0usize..30)] num_leading_zeros: usize,
2695    ) {
2696        let mut coefficients = polynomial.coefficients.into_owned();
2697        coefficients.push(leading_coefficient);
2698        coefficients.extend(vec![BFieldElement::ZERO; num_leading_zeros]);
2699        let polynomial_with_leading_zeros = Polynomial::new(coefficients);
2700        prop_assert_eq!(
2701            leading_coefficient,
2702            polynomial_with_leading_zeros.leading_coefficient().unwrap()
2703        );
2704    }
2705
2706    #[test]
2707    fn normalizing_canonical_zero_polynomial_has_no_effect() {
2708        let mut zero_polynomial = Polynomial::<BFieldElement>::zero();
2709        zero_polynomial.normalize();
2710        assert_eq!(Polynomial::zero(), zero_polynomial);
2711    }
2712
2713    #[proptest]
2714    fn spurious_leading_zeros_dont_affect_equality(
2715        polynomial: BfePoly,
2716        #[strategy(0usize..30)] num_leading_zeros: usize,
2717    ) {
2718        let mut coefficients = polynomial.clone().coefficients.into_owned();
2719        coefficients.extend(vec![BFieldElement::ZERO; num_leading_zeros]);
2720        let polynomial_with_leading_zeros = Polynomial::new(coefficients);
2721
2722        prop_assert_eq!(polynomial, polynomial_with_leading_zeros);
2723    }
2724
2725    #[proptest]
2726    fn normalizing_removes_spurious_leading_zeros(
2727        polynomial: BfePoly,
2728        #[filter(!#leading_coefficient.is_zero())] leading_coefficient: BFieldElement,
2729        #[strategy(0usize..30)] num_leading_zeros: usize,
2730    ) {
2731        let mut coefficients = polynomial.clone().coefficients.into_owned();
2732        coefficients.push(leading_coefficient);
2733        coefficients.extend(vec![BFieldElement::ZERO; num_leading_zeros]);
2734        let mut polynomial_with_leading_zeros = Polynomial::new(coefficients);
2735        polynomial_with_leading_zeros.normalize();
2736
2737        let num_inserted_coefficients = 1;
2738        let expected_num_coefficients = polynomial.coefficients.len() + num_inserted_coefficients;
2739        let num_coefficients = polynomial_with_leading_zeros.coefficients.len();
2740
2741        prop_assert_eq!(expected_num_coefficients, num_coefficients);
2742    }
2743
2744    #[test]
2745    fn accessing_coefficients_of_empty_polynomial_gives_empty_slice() {
2746        let poly = BfePoly::new(vec![]);
2747        assert!(poly.coefficients().is_empty());
2748        assert!(poly.into_coefficients().is_empty());
2749    }
2750
2751    #[proptest]
2752    fn accessing_coefficients_of_polynomial_with_only_zero_coefficients_gives_empty_slice(
2753        #[strategy(0_usize..30)] num_zeros: usize,
2754    ) {
2755        let poly = Polynomial::new(vec![BFieldElement::ZERO; num_zeros]);
2756        prop_assert!(poly.coefficients().is_empty());
2757        prop_assert!(poly.into_coefficients().is_empty());
2758    }
2759
2760    #[proptest]
2761    fn accessing_the_coefficients_is_equivalent_to_normalizing_then_raw_access(
2762        mut coefficients: Vec<BFieldElement>,
2763        #[strategy(0_usize..30)] num_leading_zeros: usize,
2764    ) {
2765        coefficients.extend(vec![BFieldElement::ZERO; num_leading_zeros]);
2766        let mut polynomial = Polynomial::new(coefficients);
2767
2768        let accessed_coefficients_borrow = polynomial.coefficients().to_vec();
2769        let accessed_coefficients_owned = polynomial.clone().into_coefficients();
2770
2771        polynomial.normalize();
2772        let raw_coefficients = polynomial.coefficients.into_owned();
2773
2774        prop_assert_eq!(&raw_coefficients, &accessed_coefficients_borrow);
2775        prop_assert_eq!(&raw_coefficients, &accessed_coefficients_owned);
2776    }
2777
2778    #[test]
2779    fn x_to_the_0_is_constant_1() {
2780        assert!(Polynomial::<BFieldElement>::x_to_the(0).is_one());
2781        assert!(Polynomial::<XFieldElement>::x_to_the(0).is_one());
2782    }
2783
2784    #[test]
2785    fn x_to_the_1_is_x() {
2786        assert!(Polynomial::<BFieldElement>::x_to_the(1).is_x());
2787        assert!(Polynomial::<XFieldElement>::x_to_the(1).is_x());
2788    }
2789
2790    #[proptest]
2791    fn x_to_the_n_to_the_m_is_homomorphic(
2792        #[strategy(0_usize..50)] n: usize,
2793        #[strategy(0_usize..50)] m: usize,
2794    ) {
2795        let to_the_n_times_m = Polynomial::<BFieldElement>::x_to_the(n * m);
2796        let to_the_n_then_to_the_m = Polynomial::x_to_the(n).pow(m as u32);
2797        prop_assert_eq!(to_the_n_times_m, to_the_n_then_to_the_m);
2798    }
2799
2800    #[test]
2801    fn scaling_a_polynomial_works_with_different_fields_as_the_offset() {
2802        let bfe_poly = Polynomial::new(bfe_vec![0, 1, 2]);
2803        let _ = bfe_poly.scale(bfe!(42));
2804        let _ = bfe_poly.scale(xfe!(42));
2805
2806        let xfe_poly = Polynomial::new(xfe_vec![0, 1, 2]);
2807        let _ = xfe_poly.scale(bfe!(42));
2808        let _ = xfe_poly.scale(xfe!(42));
2809    }
2810
2811    #[proptest]
2812    fn polynomial_scaling_is_equivalent_in_extension_field(
2813        bfe_polynomial: BfePoly,
2814        alpha: BFieldElement,
2815    ) {
2816        let bfe_coefficients = bfe_polynomial.coefficients.iter();
2817        let xfe_coefficients = bfe_coefficients.map(|bfe| bfe.lift()).collect();
2818        let xfe_polynomial = Polynomial::<XFieldElement>::new(xfe_coefficients);
2819
2820        let xfe_poly_bfe_scalar = xfe_polynomial.scale(alpha);
2821        let bfe_poly_xfe_scalar = bfe_polynomial.scale(alpha.lift());
2822        prop_assert_eq!(xfe_poly_bfe_scalar, bfe_poly_xfe_scalar);
2823    }
2824
2825    #[proptest]
2826    fn evaluating_scaled_polynomial_is_equivalent_to_evaluating_original_in_offset_point(
2827        polynomial: BfePoly,
2828        alpha: BFieldElement,
2829        x: BFieldElement,
2830    ) {
2831        let scaled_polynomial = polynomial.scale(alpha);
2832        prop_assert_eq!(
2833            polynomial.evaluate_in_same_field(alpha * x),
2834            scaled_polynomial.evaluate_in_same_field(x)
2835        );
2836    }
2837
2838    #[proptest]
2839    fn polynomial_multiplication_with_scalar_is_equivalent_for_the_two_methods(
2840        mut polynomial: BfePoly,
2841        scalar: BFieldElement,
2842    ) {
2843        let new_polynomial = polynomial.scalar_mul(scalar);
2844        polynomial.scalar_mul_mut(scalar);
2845        prop_assert_eq!(polynomial, new_polynomial);
2846    }
2847
2848    #[proptest]
2849    fn polynomial_multiplication_with_scalar_is_equivalent_for_all_mul_traits(
2850        polynomial: BfePoly,
2851        scalar: BFieldElement,
2852    ) {
2853        let bfe_rhs = polynomial.clone() * scalar;
2854        let xfe_rhs = polynomial.clone() * scalar.lift();
2855        let bfe_lhs = scalar * polynomial.clone();
2856        let xfe_lhs = scalar.lift() * polynomial;
2857
2858        prop_assert_eq!(bfe_lhs.clone(), bfe_rhs);
2859        prop_assert_eq!(xfe_lhs.clone(), xfe_rhs);
2860
2861        prop_assert_eq!(bfe_lhs * XFieldElement::ONE, xfe_lhs);
2862    }
2863
2864    #[test]
2865    fn polynomial_multiplication_with_scalar_works_for_various_types() {
2866        let bfe_poly = Polynomial::new(bfe_vec![0, 1, 2]);
2867        let _: Polynomial<BFieldElement> = bfe_poly.scalar_mul(bfe!(42));
2868        let _: Polynomial<XFieldElement> = bfe_poly.scalar_mul(xfe!(42));
2869
2870        let xfe_poly = Polynomial::new(xfe_vec![0, 1, 2]);
2871        let _: Polynomial<XFieldElement> = xfe_poly.scalar_mul(bfe!(42));
2872        let _: Polynomial<XFieldElement> = xfe_poly.scalar_mul(xfe!(42));
2873
2874        let mut bfe_poly = bfe_poly;
2875        bfe_poly.scalar_mul_mut(bfe!(42));
2876
2877        let mut xfe_poly = xfe_poly;
2878        xfe_poly.scalar_mul_mut(bfe!(42));
2879        xfe_poly.scalar_mul_mut(xfe!(42));
2880    }
2881
2882    #[proptest]
2883    fn slow_lagrange_interpolation(
2884        polynomial: BfePoly,
2885        #[strategy(Just(#polynomial.coefficients.len().max(1)))] _min_points: usize,
2886        #[any(size_range(#_min_points..8 * #_min_points).lift())] points: Vec<BFieldElement>,
2887    ) {
2888        let evaluations = points
2889            .into_iter()
2890            .map(|x| (x, polynomial.evaluate(x)))
2891            .collect_vec();
2892        let interpolation_polynomial = Polynomial::lagrange_interpolate_zipped(&evaluations);
2893        prop_assert_eq!(polynomial, interpolation_polynomial);
2894    }
2895
2896    #[proptest]
2897    fn three_colinear_points_are_colinear(
2898        p0: (BFieldElement, BFieldElement),
2899        #[filter(#p0.0 != #p1.0)] p1: (BFieldElement, BFieldElement),
2900        #[filter(#p0.0 != #p2_x && #p1.0 != #p2_x)] p2_x: BFieldElement,
2901    ) {
2902        let line = Polynomial::lagrange_interpolate_zipped(&[p0, p1]);
2903        let p2 = (p2_x, line.evaluate(p2_x));
2904        prop_assert!(Polynomial::are_colinear_3(p0, p1, p2));
2905    }
2906
2907    #[proptest]
2908    fn three_non_colinear_points_are_not_colinear(
2909        p0: (BFieldElement, BFieldElement),
2910        #[filter(#p0.0 != #p1.0)] p1: (BFieldElement, BFieldElement),
2911        #[filter(#p0.0 != #p2_x && #p1.0 != #p2_x)] p2_x: BFieldElement,
2912        #[filter(!#disturbance.is_zero())] disturbance: BFieldElement,
2913    ) {
2914        let line = Polynomial::lagrange_interpolate_zipped(&[p0, p1]);
2915        let p2 = (p2_x, line.evaluate_in_same_field(p2_x) + disturbance);
2916        prop_assert!(!Polynomial::are_colinear_3(p0, p1, p2));
2917    }
2918
2919    #[proptest]
2920    fn colinearity_check_needs_at_least_three_points(
2921        p0: (BFieldElement, BFieldElement),
2922        #[filter(#p0.0 != #p1.0)] p1: (BFieldElement, BFieldElement),
2923    ) {
2924        prop_assert!(!Polynomial::<BFieldElement>::are_colinear(&[]));
2925        prop_assert!(!Polynomial::are_colinear(&[p0]));
2926        prop_assert!(!Polynomial::are_colinear(&[p0, p1]));
2927    }
2928
2929    #[proptest]
2930    fn colinearity_check_with_repeated_points_fails(
2931        p0: (BFieldElement, BFieldElement),
2932        #[filter(#p0.0 != #p1.0)] p1: (BFieldElement, BFieldElement),
2933    ) {
2934        prop_assert!(!Polynomial::are_colinear(&[p0, p1, p1]));
2935    }
2936
2937    #[proptest]
2938    fn colinear_points_are_colinear(
2939        p0: (BFieldElement, BFieldElement),
2940        #[filter(#p0.0 != #p1.0)] p1: (BFieldElement, BFieldElement),
2941        #[filter(!#additional_points_xs.contains(&#p0.0))]
2942        #[filter(!#additional_points_xs.contains(&#p1.0))]
2943        #[filter(#additional_points_xs.iter().all_unique())]
2944        #[any(size_range(1..100).lift())]
2945        additional_points_xs: Vec<BFieldElement>,
2946    ) {
2947        let line = Polynomial::lagrange_interpolate_zipped(&[p0, p1]);
2948        let additional_points = additional_points_xs
2949            .into_iter()
2950            .map(|x| (x, line.evaluate(x)))
2951            .collect_vec();
2952        let all_points = [p0, p1].into_iter().chain(additional_points).collect_vec();
2953        prop_assert!(Polynomial::are_colinear(&all_points));
2954    }
2955
2956    #[test]
2957    #[should_panic(expected = "Line must not be parallel to y-axis")]
2958    fn getting_point_on_invalid_line_fails() {
2959        let one = BFieldElement::ONE;
2960        let two = one + one;
2961        let three = two + one;
2962        Polynomial::<BFieldElement>::get_colinear_y((one, one), (one, three), two);
2963    }
2964
2965    #[proptest]
2966    fn point_on_line_and_colinear_point_are_identical(
2967        p0: (BFieldElement, BFieldElement),
2968        #[filter(#p0.0 != #p1.0)] p1: (BFieldElement, BFieldElement),
2969        x: BFieldElement,
2970    ) {
2971        let line = Polynomial::lagrange_interpolate_zipped(&[p0, p1]);
2972        let y = line.evaluate_in_same_field(x);
2973        let y_from_get_point_on_line = Polynomial::get_colinear_y(p0, p1, x);
2974        prop_assert_eq!(y, y_from_get_point_on_line);
2975    }
2976
2977    #[proptest]
2978    fn point_on_line_and_colinear_point_are_identical_in_extension_field(
2979        p0: (XFieldElement, XFieldElement),
2980        #[filter(#p0.0 != #p1.0)] p1: (XFieldElement, XFieldElement),
2981        x: XFieldElement,
2982    ) {
2983        let line = Polynomial::lagrange_interpolate_zipped(&[p0, p1]);
2984        let y = line.evaluate_in_same_field(x);
2985        let y_from_get_point_on_line = Polynomial::get_colinear_y(p0, p1, x);
2986        prop_assert_eq!(y, y_from_get_point_on_line);
2987    }
2988
2989    #[proptest]
2990    fn shifting_polynomial_coefficients_by_zero_is_the_same_as_not_shifting_it(poly: BfePoly) {
2991        prop_assert_eq!(poly.clone(), poly.shift_coefficients(0));
2992    }
2993
2994    #[proptest]
2995    fn shifting_polynomial_one_is_equivalent_to_raising_polynomial_x_to_the_power_of_the_shift(
2996        #[strategy(0usize..30)] shift: usize,
2997    ) {
2998        let shifted_one = Polynomial::one().shift_coefficients(shift);
2999        let x_to_the_shift = Polynomial::<BFieldElement>::from([0, 1]).pow(shift as u32);
3000        prop_assert_eq!(shifted_one, x_to_the_shift);
3001    }
3002
3003    #[test]
3004    fn polynomial_shift_test() {
3005        fn polynomial<const N: usize>(coeffs: [u64; N]) -> BfePoly {
3006            Polynomial::new(coeffs.map(BFieldElement::new).to_vec())
3007        }
3008
3009        assert_eq!(
3010            polynomial([17, 14]),
3011            polynomial([17, 14]).shift_coefficients(0)
3012        );
3013        assert_eq!(
3014            polynomial([0, 17, 14]),
3015            polynomial([17, 14]).shift_coefficients(1)
3016        );
3017        assert_eq!(
3018            polynomial([0, 0, 0, 0, 17, 14]),
3019            polynomial([17, 14]).shift_coefficients(4)
3020        );
3021
3022        let poly = polynomial([17, 14]);
3023        let poly_shift_0 = poly.clone().shift_coefficients(0);
3024        assert_eq!(polynomial([17, 14]), poly_shift_0);
3025
3026        let poly_shift_1 = poly.clone().shift_coefficients(1);
3027        assert_eq!(polynomial([0, 17, 14]), poly_shift_1);
3028
3029        let poly_shift_4 = poly.clone().shift_coefficients(4);
3030        assert_eq!(polynomial([0, 0, 0, 0, 17, 14]), poly_shift_4);
3031    }
3032
3033    #[proptest]
3034    fn shifting_a_polynomial_means_prepending_zeros_to_its_coefficients(
3035        poly: BfePoly,
3036        #[strategy(0usize..30)] shift: usize,
3037    ) {
3038        let shifted_poly = poly.clone().shift_coefficients(shift);
3039        let mut expected_coefficients = vec![BFieldElement::ZERO; shift];
3040        expected_coefficients.extend(poly.coefficients.to_vec());
3041        prop_assert_eq!(expected_coefficients, shifted_poly.coefficients.to_vec());
3042    }
3043
3044    #[proptest]
3045    fn any_polynomial_to_the_power_of_zero_is_one(poly: BfePoly) {
3046        let poly_to_the_zero = poly.pow(0);
3047        prop_assert_eq!(Polynomial::one(), poly_to_the_zero);
3048    }
3049
3050    #[proptest]
3051    fn any_polynomial_to_the_power_one_is_itself(poly: BfePoly) {
3052        let poly_to_the_one = poly.pow(1);
3053        prop_assert_eq!(poly, poly_to_the_one);
3054    }
3055
3056    #[proptest]
3057    fn polynomial_one_to_any_power_is_one(#[strategy(0u32..30)] exponent: u32) {
3058        let one_to_the_exponent = Polynomial::<BFieldElement>::one().pow(exponent);
3059        prop_assert_eq!(Polynomial::one(), one_to_the_exponent);
3060    }
3061
3062    #[test]
3063    fn pow_test() {
3064        fn polynomial<const N: usize>(coeffs: [u64; N]) -> BfePoly {
3065            Polynomial::new(coeffs.map(BFieldElement::new).to_vec())
3066        }
3067
3068        let pol = polynomial([0, 14, 0, 4, 0, 8, 0, 3]);
3069        let pol_squared = polynomial([0, 0, 196, 0, 112, 0, 240, 0, 148, 0, 88, 0, 48, 0, 9]);
3070        let pol_cubed = polynomial([
3071            0, 0, 0, 2744, 0, 2352, 0, 5376, 0, 4516, 0, 4080, 0, 2928, 0, 1466, 0, 684, 0, 216, 0,
3072            27,
3073        ]);
3074
3075        assert_eq!(pol_squared, pol.pow(2));
3076        assert_eq!(pol_cubed, pol.pow(3));
3077
3078        let parabola = polynomial([5, 41, 19]);
3079        let parabola_squared = polynomial([25, 410, 1871, 1558, 361]);
3080        assert_eq!(parabola_squared, parabola.pow(2));
3081    }
3082
3083    #[proptest]
3084    fn pow_arbitrary_test(poly: BfePoly, #[strategy(0u32..15)] exponent: u32) {
3085        let actual = poly.pow(exponent);
3086        let fast_actual = poly.fast_pow(exponent);
3087        let mut expected = Polynomial::one();
3088        for _ in 0..exponent {
3089            expected = expected.clone() * poly.clone();
3090        }
3091
3092        prop_assert_eq!(expected.clone(), actual);
3093        prop_assert_eq!(expected, fast_actual);
3094    }
3095
3096    #[proptest]
3097    fn polynomial_zero_is_neutral_element_for_addition(a: BfePoly) {
3098        prop_assert_eq!(a.clone() + Polynomial::zero(), a.clone());
3099        prop_assert_eq!(Polynomial::zero() + a.clone(), a);
3100    }
3101
3102    #[proptest]
3103    fn polynomial_one_is_neutral_element_for_multiplication(a: BfePoly) {
3104        prop_assert_eq!(a.clone() * Polynomial::<BFieldElement>::one(), a.clone());
3105        prop_assert_eq!(Polynomial::<BFieldElement>::one() * a.clone(), a);
3106    }
3107
3108    #[proptest]
3109    fn multiplication_by_zero_is_zero(a: BfePoly) {
3110        let zero = Polynomial::<BFieldElement>::zero();
3111
3112        prop_assert_eq!(Polynomial::zero(), a.clone() * zero.clone());
3113        prop_assert_eq!(Polynomial::zero(), zero * a);
3114    }
3115
3116    #[proptest]
3117    fn polynomial_addition_is_commutative(a: BfePoly, b: BfePoly) {
3118        prop_assert_eq!(a.clone() + b.clone(), b + a);
3119    }
3120
3121    #[proptest]
3122    fn polynomial_multiplication_is_commutative(a: BfePoly, b: BfePoly) {
3123        prop_assert_eq!(a.clone() * b.clone(), b * a);
3124    }
3125
3126    #[proptest]
3127    fn polynomial_addition_is_associative(a: BfePoly, b: BfePoly, c: BfePoly) {
3128        prop_assert_eq!((a.clone() + b.clone()) + c.clone(), a + (b + c));
3129    }
3130
3131    #[proptest]
3132    fn polynomial_multiplication_is_associative(a: BfePoly, b: BfePoly, c: BfePoly) {
3133        prop_assert_eq!((a.clone() * b.clone()) * c.clone(), a * (b * c));
3134    }
3135
3136    #[proptest]
3137    fn polynomial_multiplication_is_distributive(a: BfePoly, b: BfePoly, c: BfePoly) {
3138        prop_assert_eq!(
3139            (a.clone() + b.clone()) * c.clone(),
3140            (a * c.clone()) + (b * c)
3141        );
3142    }
3143
3144    #[proptest]
3145    fn polynomial_subtraction_of_self_is_zero(a: BfePoly) {
3146        prop_assert_eq!(Polynomial::zero(), a.clone() - a);
3147    }
3148
3149    #[proptest]
3150    fn polynomial_division_by_self_is_one(#[filter(!#a.is_zero())] a: BfePoly) {
3151        prop_assert_eq!(Polynomial::one(), a.clone() / a);
3152    }
3153
3154    #[proptest]
3155    fn polynomial_division_removes_common_factors(a: BfePoly, #[filter(!#b.is_zero())] b: BfePoly) {
3156        prop_assert_eq!(a.clone(), a * b.clone() / b);
3157    }
3158
3159    #[proptest]
3160    fn polynomial_multiplication_raises_degree_at_maximum_to_sum_of_degrees(
3161        a: BfePoly,
3162        b: BfePoly,
3163    ) {
3164        let sum_of_degrees = (a.degree() + b.degree()).max(-1);
3165        prop_assert!((a * b).degree() <= sum_of_degrees);
3166    }
3167
3168    #[test]
3169    fn leading_zeros_dont_affect_polynomial_division() {
3170        // This test was used to catch a bug where the polynomial division was wrong when the
3171        // divisor has a leading zero coefficient, i.e. when it was not normalized
3172
3173        fn polynomial<const N: usize>(coeffs: [u64; N]) -> BfePoly {
3174            Polynomial::new(coeffs.map(BFieldElement::new).to_vec())
3175        }
3176
3177        // x^3 - x + 1 / y = x
3178        let numerator = polynomial([1, BFieldElement::P - 1, 0, 1]);
3179        let numerator_with_leading_zero = polynomial([1, BFieldElement::P - 1, 0, 1, 0]);
3180
3181        let divisor_normalized = polynomial([0, 1]);
3182        let divisor_not_normalized = polynomial([0, 1, 0]);
3183        let divisor_more_leading_zeros = polynomial([0, 1, 0, 0, 0, 0, 0, 0, 0]);
3184
3185        let expected = polynomial([BFieldElement::P - 1, 0, 1]);
3186
3187        // Verify that the divisor need not be normalized
3188        assert_eq!(expected, numerator.clone() / divisor_normalized.clone());
3189        assert_eq!(expected, numerator.clone() / divisor_not_normalized.clone());
3190        assert_eq!(expected, numerator / divisor_more_leading_zeros.clone());
3191
3192        // Verify that numerator need not be normalized
3193        let res_numerator_not_normalized_0 =
3194            numerator_with_leading_zero.clone() / divisor_normalized;
3195        let res_numerator_not_normalized_1 =
3196            numerator_with_leading_zero.clone() / divisor_not_normalized;
3197        let res_numerator_not_normalized_2 =
3198            numerator_with_leading_zero / divisor_more_leading_zeros;
3199        assert_eq!(expected, res_numerator_not_normalized_0);
3200        assert_eq!(expected, res_numerator_not_normalized_1);
3201        assert_eq!(expected, res_numerator_not_normalized_2);
3202    }
3203
3204    #[proptest]
3205    fn leading_coefficient_of_truncated_polynomial_is_same_as_original_leading_coefficient(
3206        poly: BfePoly,
3207        #[strategy(..50_usize)] truncation_point: usize,
3208    ) {
3209        let Some(lc) = poly.leading_coefficient() else {
3210            let reason = "test is only sensible if polynomial has a leading coefficient";
3211            return Err(TestCaseError::Reject(reason.into()));
3212        };
3213        let truncated_poly = poly.truncate(truncation_point);
3214        let Some(trunc_lc) = truncated_poly.leading_coefficient() else {
3215            let reason = "test is only sensible if truncated polynomial has a leading coefficient";
3216            return Err(TestCaseError::Reject(reason.into()));
3217        };
3218        prop_assert_eq!(lc, trunc_lc);
3219    }
3220
3221    #[proptest]
3222    fn truncated_polynomial_is_of_degree_min_of_truncation_point_and_poly_degree(
3223        poly: BfePoly,
3224        #[strategy(..50_usize)] truncation_point: usize,
3225    ) {
3226        let expected_degree = poly.degree().min(truncation_point.try_into().unwrap());
3227        prop_assert_eq!(expected_degree, poly.truncate(truncation_point).degree());
3228    }
3229
3230    #[proptest]
3231    fn truncating_zero_polynomial_gives_zero_polynomial(
3232        #[strategy(..50_usize)] truncation_point: usize,
3233    ) {
3234        let poly = Polynomial::<BFieldElement>::zero().truncate(truncation_point);
3235        prop_assert!(poly.is_zero());
3236    }
3237
3238    #[proptest]
3239    fn truncation_negates_degree_shifting(
3240        #[strategy(0_usize..30)] shift: usize,
3241        #[strategy(..50_usize)] truncation_point: usize,
3242        #[filter(#poly.degree() >= #truncation_point as isize)] poly: BfePoly,
3243    ) {
3244        prop_assert_eq!(
3245            poly.truncate(truncation_point),
3246            poly.shift_coefficients(shift).truncate(truncation_point)
3247        );
3248    }
3249
3250    #[proptest]
3251    fn zero_polynomial_mod_any_power_of_x_is_zero_polynomial(power: usize) {
3252        let must_be_zero = Polynomial::<BFieldElement>::zero().mod_x_to_the_n(power);
3253        prop_assert!(must_be_zero.is_zero());
3254    }
3255
3256    #[proptest]
3257    fn polynomial_mod_some_power_of_x_results_in_polynomial_of_degree_one_less_than_power(
3258        #[filter(!#poly.is_zero())] poly: BfePoly,
3259        #[strategy(..=usize::try_from(#poly.degree()).unwrap())] power: usize,
3260    ) {
3261        let remainder = poly.mod_x_to_the_n(power);
3262        prop_assert_eq!(isize::try_from(power).unwrap() - 1, remainder.degree());
3263    }
3264
3265    #[proptest]
3266    fn polynomial_mod_some_power_of_x_shares_low_degree_terms_coefficients_with_original_polynomial(
3267        #[filter(!#poly.is_zero())] poly: BfePoly,
3268        power: usize,
3269    ) {
3270        let remainder = poly.mod_x_to_the_n(power);
3271        let min_num_coefficients = poly.coefficients.len().min(remainder.coefficients.len());
3272        prop_assert_eq!(
3273            &poly.coefficients[..min_num_coefficients],
3274            &remainder.coefficients[..min_num_coefficients]
3275        );
3276    }
3277
3278    #[proptest]
3279    fn fast_multiplication_by_zero_gives_zero(poly: BfePoly) {
3280        let product = poly.fast_multiply(&Polynomial::<BFieldElement>::zero());
3281        prop_assert_eq!(Polynomial::zero(), product);
3282    }
3283
3284    #[proptest]
3285    fn fast_multiplication_by_one_gives_self(poly: BfePoly) {
3286        let product = poly.fast_multiply(&Polynomial::<BFieldElement>::one());
3287        prop_assert_eq!(poly, product);
3288    }
3289
3290    #[proptest]
3291    fn fast_multiplication_is_commutative(a: BfePoly, b: BfePoly) {
3292        prop_assert_eq!(a.fast_multiply(&b), b.fast_multiply(&a));
3293    }
3294
3295    #[proptest]
3296    fn fast_multiplication_and_normal_multiplication_are_equivalent(a: BfePoly, b: BfePoly) {
3297        let product = a.fast_multiply(&b);
3298        prop_assert_eq!(a * b, product);
3299    }
3300
3301    #[proptest]
3302    fn batch_multiply_agrees_with_iterative_multiply(a: Vec<BfePoly>) {
3303        let mut acc = Polynomial::one();
3304        for factor in &a {
3305            acc = acc.multiply(factor);
3306        }
3307        prop_assert_eq!(acc, Polynomial::batch_multiply(&a));
3308    }
3309
3310    #[proptest]
3311    fn par_batch_multiply_agrees_with_batch_multiply(a: Vec<BfePoly>) {
3312        prop_assert_eq!(
3313            Polynomial::batch_multiply(&a),
3314            Polynomial::par_batch_multiply(&a)
3315        );
3316    }
3317
3318    #[proptest(cases = 50)]
3319    fn naive_zerofier_and_fast_zerofier_are_identical(
3320        #[any(size_range(..Polynomial::<BFieldElement>::FAST_ZEROFIER_CUTOFF_THRESHOLD * 2).lift())]
3321        roots: Vec<BFieldElement>,
3322    ) {
3323        let naive_zerofier = Polynomial::naive_zerofier(&roots);
3324        let fast_zerofier = Polynomial::fast_zerofier(&roots);
3325        prop_assert_eq!(naive_zerofier, fast_zerofier);
3326    }
3327
3328    #[proptest(cases = 50)]
3329    fn smart_zerofier_and_fast_zerofier_are_identical(
3330        #[any(size_range(..Polynomial::<BFieldElement>::FAST_ZEROFIER_CUTOFF_THRESHOLD * 2).lift())]
3331        roots: Vec<BFieldElement>,
3332    ) {
3333        let smart_zerofier = Polynomial::smart_zerofier(&roots);
3334        let fast_zerofier = Polynomial::fast_zerofier(&roots);
3335        prop_assert_eq!(smart_zerofier, fast_zerofier);
3336    }
3337
3338    #[proptest(cases = 50)]
3339    fn zerofier_and_naive_zerofier_are_identical(
3340        #[any(size_range(..Polynomial::<BFieldElement>::FAST_ZEROFIER_CUTOFF_THRESHOLD * 2).lift())]
3341        roots: Vec<BFieldElement>,
3342    ) {
3343        let zerofier = Polynomial::zerofier(&roots);
3344        let naive_zerofier = Polynomial::naive_zerofier(&roots);
3345        prop_assert_eq!(zerofier, naive_zerofier);
3346    }
3347
3348    #[proptest(cases = 50)]
3349    fn zerofier_is_zero_only_on_domain(
3350        #[any(size_range(..1024).lift())] domain: Vec<BFieldElement>,
3351        #[filter(#out_of_domain_points.iter().all(|p| !#domain.contains(p)))]
3352        out_of_domain_points: Vec<BFieldElement>,
3353    ) {
3354        let zerofier = Polynomial::zerofier(&domain);
3355        for point in domain {
3356            prop_assert_eq!(BFieldElement::ZERO, zerofier.evaluate(point));
3357        }
3358        for point in out_of_domain_points {
3359            prop_assert_ne!(BFieldElement::ZERO, zerofier.evaluate(point));
3360        }
3361    }
3362
3363    #[proptest]
3364    fn zerofier_has_leading_coefficient_one(domain: Vec<BFieldElement>) {
3365        let zerofier = Polynomial::zerofier(&domain);
3366        prop_assert_eq!(BFieldElement::ONE, zerofier.leading_coefficient().unwrap());
3367    }
3368    #[proptest]
3369    fn par_zerofier_agrees_with_zerofier(domain: Vec<BFieldElement>) {
3370        prop_assert_eq!(
3371            Polynomial::zerofier(&domain),
3372            Polynomial::par_zerofier(&domain)
3373        );
3374    }
3375
3376    #[test]
3377    fn fast_evaluate_on_hardcoded_domain_and_polynomial() {
3378        let domain = bfe_array![6, 12];
3379        let x_to_the_5_plus_x_to_the_3 = Polynomial::new(bfe_vec![0, 0, 0, 1, 0, 1]);
3380
3381        let manual_evaluations = domain.map(|x| x.mod_pow(5) + x.mod_pow(3)).to_vec();
3382        let fast_evaluations = x_to_the_5_plus_x_to_the_3.batch_evaluate(&domain);
3383        assert_eq!(manual_evaluations, fast_evaluations);
3384    }
3385
3386    #[proptest]
3387    fn slow_and_fast_polynomial_evaluation_are_equivalent(
3388        poly: BfePoly,
3389        #[any(size_range(..1024).lift())] domain: Vec<BFieldElement>,
3390    ) {
3391        let evaluations = domain
3392            .iter()
3393            .map(|&x| poly.evaluate_in_same_field(x))
3394            .collect_vec();
3395        let fast_evaluations = poly.batch_evaluate(&domain);
3396        prop_assert_eq!(evaluations, fast_evaluations);
3397    }
3398
3399    #[test]
3400    #[should_panic(expected = "zero points")]
3401    fn interpolation_through_no_points_is_impossible() {
3402        let _ = Polynomial::<BFieldElement>::interpolate(&[], &[]);
3403    }
3404
3405    #[test]
3406    #[should_panic(expected = "zero points")]
3407    fn lagrange_interpolation_through_no_points_is_impossible() {
3408        let _ = Polynomial::<BFieldElement>::lagrange_interpolate(&[], &[]);
3409    }
3410
3411    #[test]
3412    #[should_panic(expected = "zero points")]
3413    fn zipped_lagrange_interpolation_through_no_points_is_impossible() {
3414        let _ = Polynomial::<BFieldElement>::lagrange_interpolate_zipped(&[]);
3415    }
3416
3417    #[test]
3418    #[should_panic(expected = "zero points")]
3419    fn fast_interpolation_through_no_points_is_impossible() {
3420        let _ = Polynomial::<BFieldElement>::fast_interpolate(&[], &[]);
3421    }
3422
3423    #[test]
3424    #[should_panic(expected = "equal length")]
3425    fn interpolation_with_domain_size_different_from_number_of_points_is_impossible() {
3426        let domain = bfe_array![1, 2, 3];
3427        let points = bfe_array![1, 2];
3428        let _ = Polynomial::interpolate(&domain, &points);
3429    }
3430
3431    #[test]
3432    #[should_panic(expected = "Repeated")]
3433    fn zipped_lagrange_interpolate_using_repeated_domain_points_is_impossible() {
3434        let domain = bfe_array![1, 1, 2];
3435        let points = bfe_array![1, 2, 3];
3436        let zipped = domain.into_iter().zip(points).collect_vec();
3437        let _ = Polynomial::lagrange_interpolate_zipped(&zipped);
3438    }
3439
3440    #[proptest]
3441    fn interpolating_through_one_point_gives_constant_polynomial(
3442        x: BFieldElement,
3443        y: BFieldElement,
3444    ) {
3445        let interpolant = Polynomial::lagrange_interpolate(&[x], &[y]);
3446        let polynomial = Polynomial::from_constant(y);
3447        prop_assert_eq!(polynomial, interpolant);
3448    }
3449
3450    #[proptest(cases = 10)]
3451    fn lagrange_and_fast_interpolation_are_identical(
3452        #[any(size_range(1..2048).lift())]
3453        #[filter(#domain.iter().all_unique())]
3454        domain: Vec<BFieldElement>,
3455        #[strategy(vec(arb(), #domain.len()))] values: Vec<BFieldElement>,
3456    ) {
3457        let lagrange_interpolant = Polynomial::lagrange_interpolate(&domain, &values);
3458        let fast_interpolant = Polynomial::fast_interpolate(&domain, &values);
3459        prop_assert_eq!(lagrange_interpolant, fast_interpolant);
3460    }
3461
3462    #[proptest(cases = 10)]
3463    fn par_fast_interpolate_and_fast_interpolation_are_identical(
3464        #[any(size_range(1..2048).lift())]
3465        #[filter(#domain.iter().all_unique())]
3466        domain: Vec<BFieldElement>,
3467        #[strategy(vec(arb(), #domain.len()))] values: Vec<BFieldElement>,
3468    ) {
3469        let par_fast_interpolant = Polynomial::par_fast_interpolate(&domain, &values);
3470        let fast_interpolant = Polynomial::fast_interpolate(&domain, &values);
3471        prop_assert_eq!(par_fast_interpolant, fast_interpolant);
3472    }
3473
3474    #[test]
3475    fn fast_interpolation_through_a_single_point_succeeds() {
3476        let zero_arr = bfe_array![0];
3477        let _ = Polynomial::fast_interpolate(&zero_arr, &zero_arr);
3478    }
3479
3480    #[proptest(cases = 20)]
3481    fn interpolation_then_evaluation_is_identity(
3482        #[any(size_range(1..2048).lift())]
3483        #[filter(#domain.iter().all_unique())]
3484        domain: Vec<BFieldElement>,
3485        #[strategy(vec(arb(), #domain.len()))] values: Vec<BFieldElement>,
3486    ) {
3487        let interpolant = Polynomial::fast_interpolate(&domain, &values);
3488        let evaluations = interpolant.batch_evaluate(&domain);
3489        prop_assert_eq!(values, evaluations);
3490    }
3491
3492    #[proptest(cases = 1)]
3493    fn fast_batch_interpolation_is_equivalent_to_fast_interpolation(
3494        #[any(size_range(1..2048).lift())]
3495        #[filter(#domain.iter().all_unique())]
3496        domain: Vec<BFieldElement>,
3497        #[strategy(vec(vec(arb(), #domain.len()), 0..10))] value_vecs: Vec<Vec<BFieldElement>>,
3498    ) {
3499        let root_order = domain.len().next_power_of_two();
3500        let root_of_unity = BFieldElement::primitive_root_of_unity(root_order as u64).unwrap();
3501
3502        let interpolants = value_vecs
3503            .iter()
3504            .map(|values| Polynomial::fast_interpolate(&domain, values))
3505            .collect_vec();
3506
3507        let batched_interpolants =
3508            Polynomial::batch_fast_interpolate(&domain, &value_vecs, root_of_unity, root_order);
3509        prop_assert_eq!(interpolants, batched_interpolants);
3510    }
3511
3512    fn coset_domain_of_size_from_generator_with_offset(
3513        size: usize,
3514        generator: BFieldElement,
3515        offset: BFieldElement,
3516    ) -> Vec<BFieldElement> {
3517        let mut domain = vec![offset];
3518        for _ in 1..size {
3519            domain.push(domain.last().copied().unwrap() * generator);
3520        }
3521        domain
3522    }
3523
3524    #[proptest]
3525    fn fast_coset_evaluation_and_fast_evaluation_on_coset_are_identical(
3526        polynomial: BfePoly,
3527        offset: BFieldElement,
3528        #[strategy(0..8usize)]
3529        #[map(|x: usize| 1 << x)]
3530        // due to current limitation in `Polynomial::fast_coset_evaluate`
3531        #[filter((#root_order as isize) > #polynomial.degree())]
3532        root_order: usize,
3533    ) {
3534        let root_of_unity = BFieldElement::primitive_root_of_unity(root_order as u64).unwrap();
3535        let domain =
3536            coset_domain_of_size_from_generator_with_offset(root_order, root_of_unity, offset);
3537
3538        let fast_values = polynomial.batch_evaluate(&domain);
3539        let fast_coset_values = polynomial.fast_coset_evaluate(offset, root_order);
3540        prop_assert_eq!(fast_values, fast_coset_values);
3541    }
3542
3543    #[proptest]
3544    fn fast_coset_interpolation_and_and_fast_interpolation_on_coset_are_identical(
3545        #[filter(!#offset.is_zero())] offset: BFieldElement,
3546        #[strategy(1..8usize)]
3547        #[map(|x: usize| 1 << x)]
3548        root_order: usize,
3549        #[strategy(vec(arb(), #root_order))] values: Vec<BFieldElement>,
3550    ) {
3551        let root_of_unity = BFieldElement::primitive_root_of_unity(root_order as u64).unwrap();
3552        let domain =
3553            coset_domain_of_size_from_generator_with_offset(root_order, root_of_unity, offset);
3554
3555        let fast_interpolant = Polynomial::fast_interpolate(&domain, &values);
3556        let fast_coset_interpolant = Polynomial::fast_coset_interpolate(offset, &values);
3557        prop_assert_eq!(fast_interpolant, fast_coset_interpolant);
3558    }
3559
3560    #[proptest]
3561    fn naive_division_gives_quotient_and_remainder_with_expected_properties(
3562        a: BfePoly,
3563        #[filter(!#b.is_zero())] b: BfePoly,
3564    ) {
3565        let (quot, rem) = a.naive_divide(&b);
3566        prop_assert!(rem.degree() < b.degree());
3567        prop_assert_eq!(a, quot * b + rem);
3568    }
3569
3570    #[proptest]
3571    fn clean_naive_division_gives_quotient_and_remainder_with_expected_properties(
3572        #[filter(!#a_roots.is_empty())] a_roots: Vec<BFieldElement>,
3573        #[strategy(vec(0..#a_roots.len(), 1..=#a_roots.len()))]
3574        #[filter(#b_root_indices.iter().all_unique())]
3575        b_root_indices: Vec<usize>,
3576    ) {
3577        let b_roots = b_root_indices.into_iter().map(|i| a_roots[i]).collect_vec();
3578        let a = Polynomial::zerofier(&a_roots);
3579        let b = Polynomial::zerofier(&b_roots);
3580        let (quot, rem) = a.naive_divide(&b);
3581        prop_assert!(rem.is_zero());
3582        prop_assert_eq!(a, quot * b);
3583    }
3584
3585    #[proptest]
3586    fn clean_division_agrees_with_divide_on_clean_division(
3587        #[strategy(arb())] a: BfePoly,
3588        #[strategy(arb())]
3589        #[filter(!#b.is_zero())]
3590        b: BfePoly,
3591    ) {
3592        let product = a.clone() * b.clone();
3593        let (naive_quotient, naive_remainder) = product.naive_divide(&b);
3594        let clean_quotient = product.clone().clean_divide(b.clone());
3595        let err = format!("{product} / {b} == {naive_quotient} != {clean_quotient}");
3596        prop_assert_eq!(naive_quotient, clean_quotient, "{}", err);
3597        prop_assert_eq!(Polynomial::<BFieldElement>::zero(), naive_remainder);
3598    }
3599
3600    #[proptest]
3601    fn clean_division_agrees_with_division_if_divisor_has_only_0_as_root(
3602        #[strategy(arb())] mut dividend_roots: Vec<BFieldElement>,
3603    ) {
3604        dividend_roots.push(bfe!(0));
3605        let dividend = Polynomial::zerofier(&dividend_roots);
3606        let divisor = Polynomial::zerofier(&[bfe!(0)]);
3607
3608        let (naive_quotient, remainder) = dividend.naive_divide(&divisor);
3609        let clean_quotient = dividend.clean_divide(divisor);
3610        prop_assert_eq!(naive_quotient, clean_quotient);
3611        prop_assert_eq!(Polynomial::<BFieldElement>::zero(), remainder);
3612    }
3613
3614    #[proptest]
3615    fn clean_division_agrees_with_division_if_divisor_has_only_0_as_multiple_root(
3616        #[strategy(arb())] mut dividend_roots: Vec<BFieldElement>,
3617        #[strategy(0_usize..300)] num_roots: usize,
3618    ) {
3619        let multiple_roots = bfe_vec![0; num_roots];
3620        let divisor = Polynomial::zerofier(&multiple_roots);
3621        dividend_roots.extend(multiple_roots);
3622        let dividend = Polynomial::zerofier(&dividend_roots);
3623
3624        let (naive_quotient, remainder) = dividend.naive_divide(&divisor);
3625        let clean_quotient = dividend.clean_divide(divisor);
3626        prop_assert_eq!(naive_quotient, clean_quotient);
3627        prop_assert_eq!(Polynomial::<BFieldElement>::zero(), remainder);
3628    }
3629
3630    #[proptest]
3631    fn clean_division_agrees_with_division_if_divisor_has_0_as_root(
3632        #[strategy(arb())] mut dividend_roots: Vec<BFieldElement>,
3633        #[strategy(vec(0..#dividend_roots.len(), 0..=#dividend_roots.len()))]
3634        #[filter(#divisor_root_indices.iter().all_unique())]
3635        divisor_root_indices: Vec<usize>,
3636    ) {
3637        // ensure clean division: make divisor's roots a subset of dividend's roots
3638        let mut divisor_roots = divisor_root_indices
3639            .into_iter()
3640            .map(|i| dividend_roots[i])
3641            .collect_vec();
3642
3643        // ensure clean division: make 0 a root of both dividend and divisor
3644        dividend_roots.push(bfe!(0));
3645        divisor_roots.push(bfe!(0));
3646
3647        let dividend = Polynomial::zerofier(&dividend_roots);
3648        let divisor = Polynomial::zerofier(&divisor_roots);
3649        let quotient = dividend.clone().clean_divide(divisor.clone());
3650        prop_assert_eq!(dividend / divisor, quotient);
3651    }
3652
3653    #[proptest]
3654    fn clean_division_agrees_with_division_if_divisor_has_0_through_9_as_roots(
3655        #[strategy(arb())] additional_dividend_roots: Vec<BFieldElement>,
3656    ) {
3657        let divisor_roots = (0..10).map(BFieldElement::new).collect_vec();
3658        let divisor = Polynomial::zerofier(&divisor_roots);
3659        let dividend_roots = [additional_dividend_roots, divisor_roots].concat();
3660        let dividend = Polynomial::zerofier(&dividend_roots);
3661        dbg!(dividend.to_string());
3662        dbg!(divisor.to_string());
3663        let quotient = dividend.clone().clean_divide(divisor.clone());
3664        prop_assert_eq!(dividend / divisor, quotient);
3665    }
3666
3667    #[proptest]
3668    fn clean_division_gives_quotient_and_remainder_with_expected_properties(
3669        #[filter(!#a_roots.is_empty())] a_roots: Vec<BFieldElement>,
3670        #[strategy(vec(0..#a_roots.len(), 1..=#a_roots.len()))]
3671        #[filter(#b_root_indices.iter().all_unique())]
3672        b_root_indices: Vec<usize>,
3673    ) {
3674        let b_roots = b_root_indices.into_iter().map(|i| a_roots[i]).collect_vec();
3675        let a = Polynomial::zerofier(&a_roots);
3676        let b = Polynomial::zerofier(&b_roots);
3677        let quotient = a.clone().clean_divide(b.clone());
3678        prop_assert_eq!(a, quotient * b);
3679    }
3680
3681    #[proptest]
3682    fn dividing_constant_polynomials_is_equivalent_to_dividing_constants(
3683        a: BFieldElement,
3684        #[filter(!#b.is_zero())] b: BFieldElement,
3685    ) {
3686        let a_poly = Polynomial::from_constant(a);
3687        let b_poly = Polynomial::from_constant(b);
3688        let expected_quotient = Polynomial::from_constant(a / b);
3689        prop_assert_eq!(expected_quotient, a_poly / b_poly);
3690    }
3691
3692    #[proptest]
3693    fn dividing_any_polynomial_by_a_constant_polynomial_results_in_remainder_zero(
3694        a: BfePoly,
3695        #[filter(!#b.is_zero())] b: BFieldElement,
3696    ) {
3697        let b_poly = Polynomial::from_constant(b);
3698        let (_, remainder) = a.naive_divide(&b_poly);
3699        prop_assert_eq!(Polynomial::zero(), remainder);
3700    }
3701
3702    #[test]
3703    fn polynomial_division_by_and_with_shah_polynomial() {
3704        fn polynomial<const N: usize>(coeffs: [u64; N]) -> BfePoly {
3705            Polynomial::new(coeffs.map(BFieldElement::new).to_vec())
3706        }
3707
3708        let shah = XFieldElement::shah_polynomial();
3709        let x_to_the_3 = polynomial([1]).shift_coefficients(3);
3710        let (shah_div_x_to_the_3, shah_mod_x_to_the_3) = shah.naive_divide(&x_to_the_3);
3711        assert_eq!(polynomial([1]), shah_div_x_to_the_3);
3712        assert_eq!(polynomial([1, BFieldElement::P - 1]), shah_mod_x_to_the_3);
3713
3714        let x_to_the_6 = polynomial([1]).shift_coefficients(6);
3715        let (x_to_the_6_div_shah, x_to_the_6_mod_shah) = x_to_the_6.naive_divide(&shah);
3716
3717        // x^3 + x - 1
3718        let expected_quot = polynomial([BFieldElement::P - 1, 1, 0, 1]);
3719        assert_eq!(expected_quot, x_to_the_6_div_shah);
3720
3721        // x^2 - 2x + 1
3722        let expected_rem = polynomial([1, BFieldElement::P - 2, 1]);
3723        assert_eq!(expected_rem, x_to_the_6_mod_shah);
3724    }
3725
3726    #[test]
3727    fn xgcd_does_not_panic_on_input_zero() {
3728        let zero = Polynomial::<BFieldElement>::zero;
3729        let (gcd, a, b) = Polynomial::xgcd(zero(), zero());
3730        assert_eq!(zero(), gcd);
3731        println!("a = {a}");
3732        println!("b = {b}");
3733    }
3734
3735    #[proptest]
3736    fn xgcd_b_field_pol_test(x: BfePoly, y: BfePoly) {
3737        let (gcd, a, b) = Polynomial::xgcd(x.clone(), y.clone());
3738        // Bezout relation
3739        prop_assert_eq!(gcd, a * x + b * y);
3740    }
3741
3742    #[proptest]
3743    fn xgcd_x_field_pol_test(x: XfePoly, y: XfePoly) {
3744        let (gcd, a, b) = Polynomial::xgcd(x.clone(), y.clone());
3745        // Bezout relation
3746        prop_assert_eq!(gcd, a * x + b * y);
3747    }
3748
3749    #[proptest]
3750    fn add_assign_is_equivalent_to_adding_and_assigning(a: BfePoly, b: BfePoly) {
3751        let mut c = a.clone();
3752        c += b.clone();
3753        prop_assert_eq!(a + b, c);
3754    }
3755
3756    #[test]
3757    fn only_monic_polynomial_of_degree_1_is_x() {
3758        fn polynomial<const N: usize>(coeffs: [u64; N]) -> BfePoly {
3759            Polynomial::new(coeffs.map(BFieldElement::new).to_vec())
3760        }
3761
3762        assert!(polynomial([0, 1]).is_x());
3763        assert!(polynomial([0, 1, 0]).is_x());
3764        assert!(polynomial([0, 1, 0, 0]).is_x());
3765
3766        assert!(!polynomial([]).is_x());
3767        assert!(!polynomial([0]).is_x());
3768        assert!(!polynomial([1]).is_x());
3769        assert!(!polynomial([1, 0]).is_x());
3770        assert!(!polynomial([0, 2]).is_x());
3771        assert!(!polynomial([0, 0, 1]).is_x());
3772    }
3773
3774    #[test]
3775    fn hardcoded_polynomial_squaring() {
3776        fn polynomial<const N: usize>(coeffs: [u64; N]) -> BfePoly {
3777            Polynomial::new(coeffs.map(BFieldElement::new).to_vec())
3778        }
3779
3780        assert_eq!(Polynomial::zero(), polynomial([]).square());
3781
3782        let x_plus_1 = polynomial([1, 1]);
3783        assert_eq!(polynomial([1, 2, 1]), x_plus_1.square());
3784
3785        let x_to_the_15 = polynomial([1]).shift_coefficients(15);
3786        let x_to_the_30 = polynomial([1]).shift_coefficients(30);
3787        assert_eq!(x_to_the_30, x_to_the_15.square());
3788
3789        let some_poly = polynomial([14, 1, 3, 4]);
3790        assert_eq!(
3791            polynomial([196, 28, 85, 118, 17, 24, 16]),
3792            some_poly.square()
3793        );
3794    }
3795
3796    #[proptest]
3797    fn polynomial_squaring_is_equivalent_to_multiplication_with_self(poly: BfePoly) {
3798        prop_assert_eq!(poly.clone() * poly.clone(), poly.square());
3799    }
3800
3801    #[proptest]
3802    fn slow_and_normal_squaring_are_equivalent(poly: BfePoly) {
3803        prop_assert_eq!(poly.slow_square(), poly.square());
3804    }
3805
3806    #[proptest]
3807    fn normal_and_fast_squaring_are_equivalent(poly: BfePoly) {
3808        prop_assert_eq!(poly.square(), poly.fast_square());
3809    }
3810
3811    #[test]
3812    fn constant_zero_eq_constant_zero() {
3813        let zero_polynomial1 = Polynomial::<BFieldElement>::zero();
3814        let zero_polynomial2 = Polynomial::<BFieldElement>::zero();
3815
3816        assert_eq!(zero_polynomial1, zero_polynomial2)
3817    }
3818
3819    #[test]
3820    fn zero_polynomial_is_zero() {
3821        assert!(Polynomial::<BFieldElement>::zero().is_zero());
3822        assert!(Polynomial::<XFieldElement>::zero().is_zero());
3823    }
3824
3825    #[proptest]
3826    fn zero_polynomial_is_zero_independent_of_spurious_leading_zeros(
3827        #[strategy(..500usize)] num_zeros: usize,
3828    ) {
3829        let coefficients = vec![0; num_zeros];
3830        prop_assert_eq!(
3831            Polynomial::zero(),
3832            Polynomial::<BFieldElement>::from(coefficients)
3833        );
3834    }
3835
3836    #[proptest]
3837    fn no_constant_polynomial_with_non_zero_coefficient_is_zero(
3838        #[filter(!#constant.is_zero())] constant: BFieldElement,
3839    ) {
3840        let constant_polynomial = Polynomial::from_constant(constant);
3841        prop_assert!(!constant_polynomial.is_zero());
3842    }
3843
3844    #[test]
3845    fn constant_one_eq_constant_one() {
3846        let one_polynomial1 = Polynomial::<BFieldElement>::one();
3847        let one_polynomial2 = Polynomial::<BFieldElement>::one();
3848
3849        assert_eq!(one_polynomial1, one_polynomial2)
3850    }
3851
3852    #[test]
3853    fn one_polynomial_is_one() {
3854        assert!(Polynomial::<BFieldElement>::one().is_one());
3855        assert!(Polynomial::<XFieldElement>::one().is_one());
3856    }
3857
3858    #[proptest]
3859    fn one_polynomial_is_one_independent_of_spurious_leading_zeros(
3860        #[strategy(..500usize)] num_leading_zeros: usize,
3861    ) {
3862        let spurious_leading_zeros = vec![0; num_leading_zeros];
3863        let mut coefficients = vec![1];
3864        coefficients.extend(spurious_leading_zeros);
3865        prop_assert_eq!(
3866            Polynomial::one(),
3867            Polynomial::<BFieldElement>::from(coefficients)
3868        );
3869    }
3870
3871    #[proptest]
3872    fn no_constant_polynomial_with_non_one_coefficient_is_one(
3873        #[filter(!#constant.is_one())] constant: BFieldElement,
3874    ) {
3875        let constant_polynomial = Polynomial::from_constant(constant);
3876        prop_assert!(!constant_polynomial.is_one());
3877    }
3878
3879    #[test]
3880    fn formal_derivative_of_zero_is_zero() {
3881        assert!(Polynomial::<BFieldElement>::zero()
3882            .formal_derivative()
3883            .is_zero());
3884        assert!(Polynomial::<XFieldElement>::zero()
3885            .formal_derivative()
3886            .is_zero());
3887    }
3888
3889    #[proptest]
3890    fn formal_derivative_of_constant_polynomial_is_zero(constant: BFieldElement) {
3891        let formal_derivative = Polynomial::from_constant(constant).formal_derivative();
3892        prop_assert!(formal_derivative.is_zero());
3893    }
3894
3895    #[proptest]
3896    fn formal_derivative_of_non_zero_polynomial_is_of_degree_one_less_than_the_polynomial(
3897        #[filter(!#poly.is_zero())] poly: BfePoly,
3898    ) {
3899        prop_assert_eq!(poly.degree() - 1, poly.formal_derivative().degree());
3900    }
3901
3902    #[proptest]
3903    fn formal_derivative_of_product_adheres_to_the_leibniz_product_rule(a: BfePoly, b: BfePoly) {
3904        let product_formal_derivative = (a.clone() * b.clone()).formal_derivative();
3905        let product_rule = a.formal_derivative() * b.clone() + a * b.formal_derivative();
3906        prop_assert_eq!(product_rule, product_formal_derivative);
3907    }
3908
3909    #[test]
3910    fn zero_is_zero() {
3911        let f = Polynomial::new(vec![BFieldElement::new(0)]);
3912        assert!(f.is_zero());
3913    }
3914
3915    #[proptest]
3916    fn formal_power_series_inverse_newton(
3917        #[strategy(2usize..20)] precision: usize,
3918        #[filter(!#f.coefficients.is_empty())]
3919        #[filter(!#f.coefficients[0].is_zero())]
3920        #[filter(#precision > 1 + #f.degree() as usize)]
3921        f: BfePoly,
3922    ) {
3923        let g = f.clone().formal_power_series_inverse_newton(precision);
3924        let mut coefficients = bfe_vec![0; precision + 1];
3925        coefficients[precision] = BFieldElement::ONE;
3926        let xn = Polynomial::new(coefficients);
3927        let (_quotient, remainder) = g.multiply(&f).divide(&xn);
3928        prop_assert!(remainder.is_one());
3929    }
3930
3931    #[test]
3932    fn formal_power_series_inverse_newton_concrete() {
3933        let f = Polynomial::new(vec![
3934            BFieldElement::new(3618372803227210457),
3935            BFieldElement::new(14620511201754172786),
3936            BFieldElement::new(2577803283145951105),
3937            BFieldElement::new(1723541458268087404),
3938            BFieldElement::new(4119508755381840018),
3939            BFieldElement::new(8592072587377832596),
3940            BFieldElement::new(236223201225),
3941        ]);
3942        let precision = 8;
3943
3944        let g = f.clone().formal_power_series_inverse_newton(precision);
3945        let mut coefficients = vec![BFieldElement::ZERO; precision + 1];
3946        coefficients[precision] = BFieldElement::ONE;
3947        let xn = Polynomial::new(coefficients);
3948        let (_quotient, remainder) = g.multiply(&f).divide(&xn);
3949        assert!(remainder.is_one());
3950    }
3951
3952    #[proptest]
3953    fn formal_power_series_inverse_minimal(
3954        #[strategy(2usize..20)] precision: usize,
3955        #[filter(!#f.coefficients.is_empty())]
3956        #[filter(!#f.coefficients[0].is_zero())]
3957        #[filter(#precision > 1 + #f.degree() as usize)]
3958        f: BfePoly,
3959    ) {
3960        let g = f.formal_power_series_inverse_minimal(precision);
3961        let mut coefficients = vec![BFieldElement::ZERO; precision + 1];
3962        coefficients[precision] = BFieldElement::ONE;
3963        let xn = Polynomial::new(coefficients);
3964        let (_quotient, remainder) = g.multiply(&f).divide(&xn);
3965
3966        // inverse in formal power series ring
3967        prop_assert!(remainder.is_one());
3968
3969        // minimal?
3970        prop_assert!(g.degree() <= precision as isize);
3971    }
3972
3973    #[proptest]
3974    fn structured_multiple_is_multiple(
3975        #[filter(#coefficients.iter().any(|c|!c.is_zero()))]
3976        #[strategy(vec(arb(), 1..30))]
3977        coefficients: Vec<BFieldElement>,
3978    ) {
3979        let polynomial = Polynomial::new(coefficients);
3980        let multiple = polynomial.structured_multiple();
3981        let remainder = multiple.reduce_long_division(&polynomial);
3982        prop_assert!(remainder.is_zero());
3983    }
3984
3985    #[proptest]
3986    fn structured_multiple_of_modulus_with_trailing_zeros_is_multiple(
3987        #[filter(!#raw_modulus.is_zero())] raw_modulus: BfePoly,
3988        #[strategy(0usize..100)] num_trailing_zeros: usize,
3989    ) {
3990        let modulus = raw_modulus.shift_coefficients(num_trailing_zeros);
3991        let multiple = modulus.structured_multiple();
3992        prop_assert!(multiple.reduce_long_division(&modulus).is_zero());
3993    }
3994
3995    #[proptest]
3996    fn structured_multiple_generates_structure(
3997        #[filter(#coefficients.iter().filter(|c|!c.is_zero()).count() >= 3)]
3998        #[strategy(vec(arb(), 1..30))]
3999        coefficients: Vec<BFieldElement>,
4000    ) {
4001        let polynomial = Polynomial::new(coefficients);
4002        let n = polynomial.degree();
4003        let structured_multiple = polynomial.structured_multiple();
4004        assert!(structured_multiple.degree() <= 3 * n + 1);
4005
4006        let x3np1 = Polynomial::x_to_the((3 * n + 1) as usize);
4007        let remainder = structured_multiple.reduce_long_division(&x3np1);
4008        assert!(2 * n >= remainder.degree());
4009
4010        let structured_mul_minus_rem = structured_multiple - remainder;
4011        assert_eq!(0, structured_mul_minus_rem.clone().reverse().degree());
4012        assert_eq!(
4013            BFieldElement::ONE,
4014            *structured_mul_minus_rem.coefficients.last().unwrap(),
4015        );
4016    }
4017
4018    #[test]
4019    fn structured_multiple_generates_structure_concrete() {
4020        let polynomial = Polynomial::new(
4021            [884763262770, 0, 51539607540, 14563891882495327437]
4022                .map(BFieldElement::new)
4023                .to_vec(),
4024        );
4025        let n = polynomial.degree();
4026        let structured_multiple = polynomial.structured_multiple();
4027        assert_eq!(3 * n + 1, structured_multiple.degree());
4028
4029        let x3np1 = Polynomial::x_to_the((3 * n + 1) as usize);
4030        let remainder = structured_multiple.reduce_long_division(&x3np1);
4031        assert!(2 * n >= remainder.degree());
4032
4033        let structured_mul_minus_rem = structured_multiple - remainder;
4034        assert_eq!(0, structured_mul_minus_rem.clone().reverse().degree());
4035        assert_eq!(
4036            BFieldElement::ONE,
4037            *structured_mul_minus_rem.coefficients.last().unwrap(),
4038        );
4039    }
4040
4041    #[proptest]
4042    fn structured_multiple_of_degree_is_multiple(
4043        #[strategy(2usize..100)] n: usize,
4044        #[filter(#coefficients.iter().any(|c|!c.is_zero()))]
4045        #[strategy(vec(arb(), 1..usize::min(30, #n)))]
4046        coefficients: Vec<BFieldElement>,
4047    ) {
4048        let polynomial = Polynomial::new(coefficients);
4049        let multiple = polynomial.structured_multiple_of_degree(n);
4050        let remainder = multiple.reduce_long_division(&polynomial);
4051        prop_assert!(remainder.is_zero());
4052    }
4053
4054    #[proptest]
4055    fn structured_multiple_of_degree_generates_structure(
4056        #[strategy(4usize..100)] n: usize,
4057        #[strategy(vec(arb(), 3..usize::min(30, #n)))] mut coefficients: Vec<BFieldElement>,
4058    ) {
4059        *coefficients.last_mut().unwrap() = BFieldElement::ONE;
4060        let polynomial = Polynomial::new(coefficients);
4061        let structured_multiple = polynomial.structured_multiple_of_degree(n);
4062
4063        let xn =
4064            Polynomial::new([vec![BFieldElement::ZERO; n], vec![BFieldElement::ONE; 1]].concat());
4065        let remainder = structured_multiple.reduce_long_division(&xn);
4066        assert_eq!(
4067            (structured_multiple.clone() - remainder.clone())
4068                .reverse()
4069                .degree() as usize,
4070            0
4071        );
4072        assert_eq!(
4073            BFieldElement::ONE,
4074            *(structured_multiple.clone() - remainder)
4075                .coefficients
4076                .last()
4077                .unwrap()
4078        );
4079    }
4080
4081    #[proptest]
4082    fn structured_multiple_of_degree_has_given_degree(
4083        #[strategy(2usize..100)] n: usize,
4084        #[filter(#coefficients.iter().any(|c|!c.is_zero()))]
4085        #[strategy(vec(arb(), 1..usize::min(30, #n)))]
4086        coefficients: Vec<BFieldElement>,
4087    ) {
4088        let polynomial = Polynomial::new(coefficients);
4089        let multiple = polynomial.structured_multiple_of_degree(n);
4090        prop_assert_eq!(
4091            multiple.degree() as usize,
4092            n,
4093            "polynomial: {} whereas multiple {}",
4094            polynomial,
4095            multiple
4096        );
4097    }
4098
4099    #[proptest]
4100    fn reverse_polynomial_with_nonzero_constant_term_twice_gives_original_back(f: BfePoly) {
4101        let fx_plus_1 = f.shift_coefficients(1) + Polynomial::from_constant(bfe!(1));
4102        prop_assert_eq!(fx_plus_1.clone(), fx_plus_1.reverse().reverse());
4103    }
4104
4105    #[proptest]
4106    fn reverse_polynomial_with_zero_constant_term_twice_gives_shift_back(
4107        #[filter(!#f.is_zero())] f: BfePoly,
4108    ) {
4109        let fx_plus_1 = f.shift_coefficients(1);
4110        prop_assert_ne!(fx_plus_1.clone(), fx_plus_1.reverse().reverse());
4111        prop_assert_eq!(
4112            fx_plus_1.clone(),
4113            fx_plus_1.reverse().reverse().shift_coefficients(1)
4114        );
4115    }
4116
4117    #[proptest]
4118    fn reduce_by_structured_modulus_and_reduce_long_division_agree(
4119        #[strategy(1usize..10)] n: usize,
4120        #[strategy(1usize..10)] m: usize,
4121        #[strategy(vec(arb(), #m))] b_coefficients: Vec<BFieldElement>,
4122        #[strategy(1usize..100)] _deg_a: usize,
4123        #[strategy(vec(arb(), #_deg_a + 1))] _a_coefficients: Vec<BFieldElement>,
4124        #[strategy(Just(Polynomial::new(#_a_coefficients)))] a: BfePoly,
4125    ) {
4126        let mut full_modulus_coefficients = b_coefficients.clone();
4127        full_modulus_coefficients.resize(m + n + 1, BFieldElement::from(0));
4128        *full_modulus_coefficients.last_mut().unwrap() = BFieldElement::from(1);
4129        let full_modulus = Polynomial::new(full_modulus_coefficients);
4130
4131        let long_remainder = a.reduce_long_division(&full_modulus);
4132        let structured_remainder = a.reduce_by_structured_modulus(&full_modulus);
4133
4134        prop_assert_eq!(long_remainder, structured_remainder);
4135    }
4136
4137    #[test]
4138    fn reduce_by_structured_modulus_and_reduce_agree_long_division_concrete() {
4139        let a = Polynomial::new(
4140            [1, 0, 0, 3, 4, 3, 1, 5, 1, 0, 1, 2, 9, 2, 0, 3, 1]
4141                .into_iter()
4142                .map(BFieldElement::new)
4143                .collect_vec(),
4144        );
4145        let mut full_modulus_coefficients =
4146            [5, 6, 3].into_iter().map(BFieldElement::new).collect_vec();
4147        let m = full_modulus_coefficients.len();
4148        let n = 2;
4149        full_modulus_coefficients.resize(m + n + 1, BFieldElement::from(0));
4150        *full_modulus_coefficients.last_mut().unwrap() = BFieldElement::from(1);
4151        let full_modulus = Polynomial::new(full_modulus_coefficients);
4152
4153        let long_remainder = a.reduce_long_division(&full_modulus);
4154        let structured_remainder = a.reduce_by_structured_modulus(&full_modulus);
4155
4156        assert_eq!(
4157            long_remainder, structured_remainder,
4158            "naive: {}\nstructured: {}",
4159            long_remainder, structured_remainder
4160        );
4161    }
4162
4163    #[proptest]
4164    fn reduce_by_ntt_friendly_modulus_and_reduce_long_division_agree(
4165        #[strategy(1usize..10)] m: usize,
4166        #[strategy(vec(arb(), #m))] b_coefficients: Vec<BFieldElement>,
4167        #[strategy(1usize..100)] _deg_a: usize,
4168        #[strategy(vec(arb(), #_deg_a + 1))] _a_coefficients: Vec<BFieldElement>,
4169        #[strategy(Just(Polynomial::new(#_a_coefficients)))] a: BfePoly,
4170    ) {
4171        let b = Polynomial::new(b_coefficients.clone());
4172        if b.is_zero() {
4173            return Err(TestCaseError::Reject("some reason".into()));
4174        }
4175        let n = (b_coefficients.len() + 1).next_power_of_two();
4176        let mut full_modulus_coefficients = b_coefficients.clone();
4177        full_modulus_coefficients.resize(n + 1, BFieldElement::from(0));
4178        *full_modulus_coefficients.last_mut().unwrap() = BFieldElement::from(1);
4179        let full_modulus = Polynomial::new(full_modulus_coefficients);
4180
4181        let long_remainder = a.reduce_long_division(&full_modulus);
4182
4183        let mut shift_ntt = b_coefficients.clone();
4184        shift_ntt.resize(n, BFieldElement::from(0));
4185        ntt(&mut shift_ntt);
4186        let structured_remainder = a.reduce_by_ntt_friendly_modulus(&shift_ntt, m);
4187
4188        prop_assert_eq!(long_remainder, structured_remainder);
4189    }
4190
4191    #[test]
4192    fn reduce_by_ntt_friendly_modulus_and_reduce_agree_concrete() {
4193        let m = 1;
4194        let a_coefficients = bfe_vec![0, 0, 75944580];
4195        let a = Polynomial::new(a_coefficients);
4196        let b_coefficients = vec![BFieldElement::new(944892804900)];
4197        let n = (b_coefficients.len() + 1).next_power_of_two();
4198        let mut full_modulus_coefficients = b_coefficients.clone();
4199        full_modulus_coefficients.resize(n + 1, BFieldElement::from(0));
4200        *full_modulus_coefficients.last_mut().unwrap() = BFieldElement::from(1);
4201        let full_modulus = Polynomial::new(full_modulus_coefficients);
4202
4203        let long_remainder = a.reduce_long_division(&full_modulus);
4204
4205        let mut shift_ntt = b_coefficients.clone();
4206        shift_ntt.resize(n, BFieldElement::from(0));
4207        ntt(&mut shift_ntt);
4208        let structured_remainder = a.reduce_by_ntt_friendly_modulus(&shift_ntt, m);
4209
4210        assert_eq!(
4211            long_remainder, structured_remainder,
4212            "full modulus: {}",
4213            full_modulus
4214        );
4215    }
4216
4217    #[proptest]
4218    fn reduce_fast_and_reduce_long_division_agree(
4219        numerator: BfePoly,
4220        #[filter(!#modulus.is_zero())] modulus: BfePoly,
4221    ) {
4222        prop_assert_eq!(
4223            numerator.fast_reduce(&modulus),
4224            numerator.reduce_long_division(&modulus)
4225        );
4226    }
4227
4228    #[test]
4229    fn reduce_and_fast_reduce_long_division_agree_on_fixed_input() {
4230        // The bug exhibited by this minimal failing test case has since been
4231        // fixed. The comments are kept as-is for historical accuracy and
4232        // didactics, and do not reflect an on-going bug hunt anymore.
4233        let mut failures = vec![];
4234        for i in 1..100 {
4235            // Is this setup convoluted? Maybe. It's the only way I've managed to trigger
4236            // the discrepancy so far.
4237            // The historic context of finding Bezout coefficients shimmers through. :)
4238            let roots = (0..i).map(BFieldElement::new).collect_vec();
4239            let dividend = Polynomial::zerofier(&roots).formal_derivative();
4240
4241            // Fractions of 1/4th, 1/5th, 1/6th, and so on trigger the failure. Fraction
4242            // 1/5th seems to trigger both a failure for the smallest `i` (10) and the most
4243            // failures (90 out of 100). Fractions 1/2 or 1/3rd don't trigger the failure.
4244            let divisor_roots = &roots[..roots.len() / 5];
4245            let divisor = Polynomial::zerofier(divisor_roots);
4246
4247            let long_div_remainder = dividend.reduce_long_division(&divisor);
4248            let preprocessed_remainder = dividend.fast_reduce(&divisor);
4249
4250            if long_div_remainder != preprocessed_remainder {
4251                failures.push(i);
4252            }
4253        }
4254
4255        assert_eq!(0, failures.len(), "failures at indices: {failures:?}");
4256    }
4257
4258    #[test]
4259    fn reduce_long_division_and_fast_reduce_agree_simple_fixed() {
4260        let roots = (0..10).map(BFieldElement::new).collect_vec();
4261        let numerator = Polynomial::zerofier(&roots).formal_derivative();
4262        println!("numerator: {}", numerator);
4263
4264        let divisor_roots = &roots[..roots.len() / 5];
4265        let denominator = Polynomial::zerofier(divisor_roots);
4266        println!("modulus: {}", denominator);
4267
4268        let (quotient, remainder) = numerator.divide(&denominator);
4269        assert_eq!(
4270            numerator,
4271            denominator.clone() * quotient + remainder.clone()
4272        );
4273
4274        let long_div_remainder = numerator.reduce_long_division(&denominator);
4275        println!("long div remainder: {}", long_div_remainder);
4276        assert_eq!(remainder, long_div_remainder);
4277
4278        let preprocessed_remainder = numerator.fast_reduce(&denominator);
4279        println!("fast remainder: {}", preprocessed_remainder);
4280
4281        assert_eq!(long_div_remainder, preprocessed_remainder);
4282    }
4283
4284    #[proptest(cases = 100)]
4285    fn batch_evaluate_methods_are_equivalent(
4286        #[strategy(vec(arb(), (1<<10)..(1<<11)))] coefficients: Vec<BFieldElement>,
4287        #[strategy(vec(arb(), (1<<5)..(1<<7)))] domain: Vec<BFieldElement>,
4288    ) {
4289        let polynomial = Polynomial::new(coefficients);
4290        let evaluations_iterative = polynomial.iterative_batch_evaluate(&domain);
4291        let zerofier_tree = ZerofierTree::new_from_domain(&domain);
4292        let evaluations_fast = polynomial.divide_and_conquer_batch_evaluate(&zerofier_tree);
4293        let evaluations_reduce_then = polynomial.reduce_then_batch_evaluate(&domain);
4294
4295        prop_assert_eq!(evaluations_iterative.clone(), evaluations_fast);
4296        prop_assert_eq!(evaluations_iterative, evaluations_reduce_then);
4297    }
4298
4299    #[proptest]
4300    fn reduce_agrees_with_division(a: BfePoly, #[filter(!#b.is_zero())] b: BfePoly) {
4301        prop_assert_eq!(a.divide(&b).1, a.reduce(&b));
4302    }
4303
4304    #[proptest]
4305    fn structured_multiple_of_monomial_term_is_actually_multiple_and_of_right_degree(
4306        #[strategy(1usize..1000)] degree: usize,
4307        #[filter(!#leading_coefficient.is_zero())] leading_coefficient: BFieldElement,
4308        #[strategy(#degree+1..#degree+200)] target_degree: usize,
4309    ) {
4310        let coefficients = [bfe_vec![0; degree], vec![leading_coefficient]].concat();
4311        let polynomial = Polynomial::new(coefficients);
4312        let multiple = polynomial.structured_multiple_of_degree(target_degree);
4313        prop_assert_eq!(Polynomial::zero(), multiple.reduce(&polynomial));
4314        prop_assert_eq!(multiple.degree() as usize, target_degree);
4315    }
4316
4317    #[proptest]
4318    fn monomial_term_divided_by_smaller_monomial_term_gives_clean_division(
4319        #[strategy(100usize..102)] high_degree: usize,
4320        #[filter(!#high_lc.is_zero())] high_lc: BFieldElement,
4321        #[strategy(83..#high_degree)] low_degree: usize,
4322        #[filter(!#low_lc.is_zero())] low_lc: BFieldElement,
4323    ) {
4324        let numerator = Polynomial::new([bfe_vec![0; high_degree], vec![high_lc]].concat());
4325        let denominator = Polynomial::new([bfe_vec![0; low_degree], vec![low_lc]].concat());
4326        let (quotient, remainder) = numerator.divide(&denominator);
4327        prop_assert_eq!(
4328            quotient
4329                .coefficients
4330                .iter()
4331                .filter(|c| !c.is_zero())
4332                .count(),
4333            1
4334        );
4335        prop_assert_eq!(Polynomial::zero(), remainder);
4336    }
4337
4338    #[proptest]
4339    fn fast_modular_coset_interpolate_agrees_with_interpolate_then_reduce_property(
4340        #[filter(!#modulus.is_zero())] modulus: BfePoly,
4341        #[strategy(0usize..10)] _logn: usize,
4342        #[strategy(Just(1 << #_logn))] n: usize,
4343        #[strategy(vec(arb(), #n))] values: Vec<BFieldElement>,
4344        #[strategy(arb())] offset: BFieldElement,
4345    ) {
4346        let omega = BFieldElement::primitive_root_of_unity(n as u64).unwrap();
4347        let domain = (0..n)
4348            .scan(offset, |acc: &mut BFieldElement, _| {
4349                let yld = *acc;
4350                *acc *= omega;
4351                Some(yld)
4352            })
4353            .collect_vec();
4354        prop_assert_eq!(
4355            Polynomial::fast_modular_coset_interpolate(&values, offset, &modulus),
4356            Polynomial::interpolate(&domain, &values).reduce(&modulus)
4357        )
4358    }
4359
4360    #[test]
4361    fn fast_modular_coset_interpolate_agrees_with_interpolate_then_reduce_concrete() {
4362        let logn = 8;
4363        let n = 1u64 << logn;
4364        let modulus = Polynomial::new(bfe_vec![2, 3, 1]);
4365        let values = (0..n).map(|i| BFieldElement::new(i / 5)).collect_vec();
4366        let offset = BFieldElement::new(7);
4367
4368        let omega = BFieldElement::primitive_root_of_unity(n).unwrap();
4369        let mut domain = bfe_vec![0; n as usize];
4370        domain[0] = offset;
4371        for i in 1..n as usize {
4372            domain[i] = domain[i - 1] * omega;
4373        }
4374        assert_eq!(
4375            Polynomial::interpolate(&domain, &values).reduce(&modulus),
4376            Polynomial::fast_modular_coset_interpolate(&values, offset, &modulus),
4377        )
4378    }
4379
4380    #[proptest(cases = 100)]
4381    fn coset_extrapolation_methods_agree_with_interpolate_then_evaluate(
4382        #[strategy(0usize..10)] _logn: usize,
4383        #[strategy(Just(1 << #_logn))] n: usize,
4384        #[strategy(vec(arb(), #n))] values: Vec<BFieldElement>,
4385        #[strategy(arb())] offset: BFieldElement,
4386        #[strategy(vec(arb(), 1..1000))] points: Vec<BFieldElement>,
4387    ) {
4388        let omega = BFieldElement::primitive_root_of_unity(n as u64).unwrap();
4389        let domain = (0..n)
4390            .scan(offset, |acc: &mut BFieldElement, _| {
4391                let yld = *acc;
4392                *acc *= omega;
4393                Some(yld)
4394            })
4395            .collect_vec();
4396        let fast_coset_extrapolation = Polynomial::fast_coset_extrapolate(offset, &values, &points);
4397        let naive_coset_extrapolation =
4398            Polynomial::naive_coset_extrapolate(offset, &values, &points);
4399        let interpolation_then_evaluation =
4400            Polynomial::interpolate(&domain, &values).batch_evaluate(&points);
4401        prop_assert_eq!(fast_coset_extrapolation.clone(), naive_coset_extrapolation);
4402        prop_assert_eq!(fast_coset_extrapolation, interpolation_then_evaluation);
4403    }
4404
4405    #[proptest]
4406    fn coset_extrapolate_and_batch_coset_extrapolate_agree(
4407        #[strategy(1usize..10)] _logn: usize,
4408        #[strategy(Just(1<<#_logn))] n: usize,
4409        #[strategy(0usize..5)] _m: usize,
4410        #[strategy(vec(arb(), #_m*#n))] codewords: Vec<BFieldElement>,
4411        #[strategy(vec(arb(), 0..20))] points: Vec<BFieldElement>,
4412    ) {
4413        let offset = BFieldElement::new(7);
4414
4415        let one_by_one_dispatch = codewords
4416            .chunks(n)
4417            .flat_map(|chunk| Polynomial::coset_extrapolate(offset, chunk, &points))
4418            .collect_vec();
4419        let batched_dispatch = Polynomial::batch_coset_extrapolate(offset, n, &codewords, &points);
4420        let par_batched_dispatch =
4421            Polynomial::par_batch_coset_extrapolate(offset, n, &codewords, &points);
4422        prop_assert_eq!(one_by_one_dispatch.clone(), batched_dispatch);
4423        prop_assert_eq!(one_by_one_dispatch, par_batched_dispatch);
4424
4425        let one_by_one_fast = codewords
4426            .chunks(n)
4427            .flat_map(|chunk| Polynomial::fast_coset_extrapolate(offset, chunk, &points))
4428            .collect_vec();
4429        let batched_fast = Polynomial::batch_fast_coset_extrapolate(offset, n, &codewords, &points);
4430        let par_batched_fast =
4431            Polynomial::par_batch_fast_coset_extrapolate(offset, n, &codewords, &points);
4432        prop_assert_eq!(one_by_one_fast.clone(), batched_fast);
4433        prop_assert_eq!(one_by_one_fast, par_batched_fast);
4434
4435        let one_by_one_naive = codewords
4436            .chunks(n)
4437            .flat_map(|chunk| Polynomial::naive_coset_extrapolate(offset, chunk, &points))
4438            .collect_vec();
4439        let batched_naive =
4440            Polynomial::batch_naive_coset_extrapolate(offset, n, &codewords, &points);
4441        let par_batched_naive =
4442            Polynomial::par_batch_naive_coset_extrapolate(offset, n, &codewords, &points);
4443        prop_assert_eq!(one_by_one_naive.clone(), batched_naive);
4444        prop_assert_eq!(one_by_one_naive, par_batched_naive);
4445    }
4446
4447    #[test]
4448    fn fast_modular_coset_interpolate_thresholds_relate_properly() {
4449        type BfePoly = Polynomial<'static, BFieldElement>;
4450
4451        let intt = BfePoly::FAST_MODULAR_COSET_INTERPOLATE_CUTOFF_THRESHOLD_PREFER_INTT;
4452        let lagrange = BfePoly::FAST_MODULAR_COSET_INTERPOLATE_CUTOFF_THRESHOLD_PREFER_LAGRANGE;
4453        assert!(intt > lagrange);
4454    }
4455
4456    #[proptest]
4457    fn interpolate_and_par_interpolate_agree(
4458        #[filter(!#points.is_empty())] points: Vec<BFieldElement>,
4459        #[strategy(vec(arb(), #points.len()))] domain: Vec<BFieldElement>,
4460    ) {
4461        let expected_interpolant = Polynomial::interpolate(&domain, &points);
4462        let observed_interpolant = Polynomial::par_interpolate(&domain, &points);
4463        prop_assert_eq!(expected_interpolant, observed_interpolant);
4464    }
4465
4466    #[proptest]
4467    fn batch_evaluate_agrees_with_par_batch_evalaute(
4468        polynomial: BfePoly,
4469        points: Vec<BFieldElement>,
4470    ) {
4471        prop_assert_eq!(
4472            polynomial.batch_evaluate(&points),
4473            polynomial.par_batch_evaluate(&points)
4474        );
4475    }
4476
4477    #[proptest(cases = 20)]
4478    fn polynomial_evaluation_and_barycentric_evaluation_are_equivalent(
4479        #[strategy(1_usize..8)] _log_num_coefficients: usize,
4480        #[strategy(1_usize..6)] log_expansion_factor: usize,
4481        #[strategy(vec(arb(), 1 << #_log_num_coefficients))] coefficients: Vec<XFieldElement>,
4482        #[strategy(arb())] indeterminate: XFieldElement,
4483    ) {
4484        let domain_len = coefficients.len() * (1 << log_expansion_factor);
4485        let domain_gen = BFieldElement::primitive_root_of_unity(domain_len.try_into()?).unwrap();
4486        let domain = (0..domain_len)
4487            .scan(XFieldElement::ONE, |acc, _| {
4488                let current = *acc;
4489                *acc *= domain_gen;
4490                Some(current)
4491            })
4492            .collect_vec();
4493
4494        let polynomial = Polynomial::new(coefficients);
4495        let codeword = polynomial.batch_evaluate(&domain);
4496        prop_assert_eq!(
4497            polynomial.evaluate_in_same_field(indeterminate),
4498            barycentric_evaluate(&codeword, indeterminate)
4499        );
4500    }
4501
4502    #[test]
4503    fn regular_evaluation_works_with_various_types() {
4504        let bfe_poly = Polynomial::new(bfe_vec![1]);
4505        let _: BFieldElement = bfe_poly.evaluate(bfe!(0));
4506        let _: XFieldElement = bfe_poly.evaluate(bfe!(0));
4507        let _: XFieldElement = bfe_poly.evaluate(xfe!(0));
4508
4509        let xfe_poly = Polynomial::new(xfe_vec![1]);
4510        let _: XFieldElement = xfe_poly.evaluate(bfe!(0));
4511        let _: XFieldElement = xfe_poly.evaluate(xfe!(0));
4512    }
4513
4514    #[test]
4515    fn barycentric_evaluation_works_with_many_types() {
4516        let bfe_codeword = bfe_array![1];
4517        let _ = barycentric_evaluate(&bfe_codeword, bfe!(0));
4518        let _ = barycentric_evaluate(&bfe_codeword, xfe!(0));
4519
4520        let xfe_codeword = xfe_array![[1; 3]];
4521        let _ = barycentric_evaluate(&xfe_codeword, bfe!(0));
4522        let _ = barycentric_evaluate(&xfe_codeword, xfe!(0));
4523    }
4524
4525    #[test]
4526    fn various_multiplications_work_with_various_types() {
4527        let b = Polynomial::<BFieldElement>::zero;
4528        let x = Polynomial::<XFieldElement>::zero;
4529
4530        let _ = b() * b();
4531        let _ = b() * x();
4532        let _ = x() * b();
4533        let _ = x() * x();
4534
4535        let _ = b().multiply(&b());
4536        let _ = b().multiply(&x());
4537        let _ = x().multiply(&b());
4538        let _ = x().multiply(&x());
4539
4540        let _ = b().naive_multiply(&b());
4541        let _ = b().naive_multiply(&x());
4542        let _ = x().naive_multiply(&b());
4543        let _ = x().naive_multiply(&x());
4544
4545        let _ = b().fast_multiply(&b());
4546        let _ = b().fast_multiply(&x());
4547        let _ = x().fast_multiply(&b());
4548        let _ = x().fast_multiply(&x());
4549    }
4550
4551    #[test]
4552    fn evaluating_polynomial_with_borrowed_coefficients_leaves_coefficients_borrowed() {
4553        let coefficients = bfe_vec![4, 5, 6];
4554        let poly = Polynomial::new_borrowed(&coefficients);
4555        let _ = poly.evaluate_in_same_field(bfe!(0));
4556        let _ = poly.evaluate::<_, XFieldElement>(bfe!(0));
4557        let _ = poly.fast_coset_evaluate(bfe!(3), 128);
4558
4559        let Cow::Borrowed(_) = poly.coefficients else {
4560            panic!("evaluating must not clone the coefficient vector")
4561        };
4562
4563        // make sure the coefficients are still owned by this scope
4564        drop(coefficients);
4565    }
4566}