Skip to main content

twenty_first/math/
polynomial.rs

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