Skip to main content

twenty_first/math/
polynomial.rs

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