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