1use 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#[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#[derive(Clone)]
83pub struct Polynomial<'coeffs, FF: FiniteField> {
84 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
118impl<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
155impl<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 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 }
194
195 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 return &[];
208 };
209
210 &coefficients[0..=leading_coeff_idx]
211 }
212
213 pub fn into_coefficients(mut self) -> Vec<FF> {
217 self.normalize();
218 self.coefficients.into_owned()
219 }
220
221 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 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 pub fn is_x(&self) -> bool {
264 self.degree() == 1 && self.coefficients[0].is_zero() && self.coefficients[1].is_one()
265 }
266
267 pub fn formal_derivative(&self) -> Polynomial<'static, FF> {
281 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 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 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 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 pub fn evaluate_in_same_field(&self, x: FF) -> FF {
364 self.evaluate::<FF, FF>(x)
365 }
366
367 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 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 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 p2_y_times_dx / dx
430 }
431
432 #[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 #[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 #[must_use]
491 pub fn pow(&self, pow: u32) -> Polynomial<'static, FF> {
492 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 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 #[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 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 #[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 pub fn divide(
576 &self,
577 divisor: &Polynomial<'_, FF>,
578 ) -> (Polynomial<'static, FF>, Polynomial<'static, FF>) {
579 self.naive_divide(divisor)
582 }
583
584 #[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 return (Polynomial::zero(), self.clone().into_owned());
600 };
601 debug_assert!(self.degree() >= 0);
602
603 let mut rev_quotient = Vec::with_capacity(quotient_degree + 1);
605 let mut remainder = self.clone();
606 remainder.normalize();
607
608 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 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 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 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 pub fn into_owned(self) -> Polynomial<'static, FF> {
702 Polynomial::new(self.coefficients.into_owned())
703 }
704}
705
706pub(crate) const MAX_CONCURRENT_PAR_NTTS: usize = 3;
711
712pub(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
718pub(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
723pub(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
740pub(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
758pub(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
769pub(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 const FAST_MULTIPLY_CUTOFF_THRESHOLD: isize = 1 << 8;
783
784 const FAST_INTERPOLATE_CUTOFF_THRESHOLD_SEQUENTIAL: usize = 1 << 12;
790
791 const FAST_INTERPOLATE_CUTOFF_THRESHOLD_PARALLEL: usize = 1 << 8;
797
798 const FAST_MODULAR_COSET_INTERPOLATE_CUTOFF_THRESHOLD_PREFER_LAGRANGE: usize = 1 << 8;
803
804 const FAST_MODULAR_COSET_INTERPOLATE_CUTOFF_THRESHOLD_PREFER_INTT: usize = 1 << 17;
808
809 const FAST_COSET_EXTRAPOLATE_THRESHOLD: usize = 100;
813
814 const FORMAL_POWER_SERIES_INVERSE_CUTOFF: isize = 1 << 8;
819
820 const FAST_REDUCE_CUTOFF_THRESHOLD: usize = 1 << 8;
827
828 const QUADRATIC_REDUCTION_CUTOFF: usize = 1 << 14;
834
835 const REDUCE_BEFORE_EVALUATE_THRESHOLD_RATIO: isize = 4;
839
840 #[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 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 #[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 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 const CHUNK_SIZE: usize = 1 << 12;
898
899 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 unsafe { return_coefficients.set_len(num_coefficients) };
925 return_coefficients
926 }
927
928 #[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 #[must_use]
964 pub fn square(&self) -> Polynomial<'static, FF> {
965 if self.degree() == -1 {
966 return Polynomial::zero();
967 }
968
969 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 #[must_use]
998 pub fn fast_pow(&self, pow: u32) -> Polynomial<'static, FF> {
999 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 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 #[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 #[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 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 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 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 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 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 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 pub(crate) fn power_series_inverse(&self, precision: usize) -> Polynomial<'static, FF> {
1238 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 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 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 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 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 pub fn batch_multiply(factors: &[Self]) -> Polynomial<'static, FF> {
1361 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 let product_coeffs = products.pop().unwrap().coefficients.into_owned();
1389 Polynomial::new(product_coeffs)
1390 }
1391
1392 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 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 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 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 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 #[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 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 #[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 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 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 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 pub fn formal_power_series_inverse_newton(self, precision: usize) -> Polynomial<'static, FF> {
1645 let self_degree = self.degree();
1647 if self_degree == 0 {
1648 return Polynomial::from_constant(self.coefficients[0].inverse());
1649 }
1650
1651 let num_rounds = precision.next_power_of_two().ilog2();
1653
1654 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 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 switch_point >= num_rounds {
1674 return f;
1675 }
1676
1677 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 let mut current_domain_length = f.coefficients.len().next_power_of_two();
1689
1690 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 let mut f_degree = f.degree();
1698
1699 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 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 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 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 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 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
1832fn 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 const FAST_ZEROFIER_CUTOFF_THRESHOLD: usize = 100;
1861
1862 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 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 #[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 #[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 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 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 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 #[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 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 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 summand_array[0] = leading_coefficient;
2036 summand_eval = summand_eval * domain[i] + leading_coefficient;
2037
2038 let corrected_abscis = abscis / summand_eval;
2041
2042 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 #[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 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 #[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 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 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 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 fn interpolate_with_zerofier_tree(
2185 zerofier_tree: &ZerofierTree<FF>,
2186 weights: &[FF],
2187 ) -> Polynomial<'static, FF> {
2188 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 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 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 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 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 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 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 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 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 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 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 pub fn par_batch_evaluate(&self, domain: &[FF]) -> Vec<FF> {
2517 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 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 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 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 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 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 fn evaluate_scaled_remainder_leaf(
2672 leaf: &ZerofierTree<FF>,
2673 points: &[FF],
2674 scaled_remainder: &[FF],
2675 ) -> Vec<FF> {
2676 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 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 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 #[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 #[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 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 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 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 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 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 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 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 #[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 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 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 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 #[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 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 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 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 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 interpolant.reduce(modulus)
3049 }
3050
3051 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 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 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 const CLEAN_DIVIDE_CUTOFF_THRESHOLD: isize = { if cfg!(test) { 0 } else { 1 << 9 } };
3277
3278 #[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 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 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 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 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 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 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 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 pub fn new(coefficients: Vec<FF>) -> Self {
3454 let coefficients = Cow::Owned(coefficients);
3455 Self { coefficients }
3456 }
3457
3458 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 pub fn from_constant(constant: FF) -> Self {
3470 Self::new(vec![constant])
3471 }
3472
3473 #[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 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
3580pub 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
3633impl<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 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 type BfePoly = Polynomial<'static, BFieldElement>;
3724
3725 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 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 fn polynomial<const N: usize>(coeffs: [u64; N]) -> BfePoly {
4289 Polynomial::new(coeffs.map(BFieldElement::new).to_vec())
4290 }
4291
4292 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 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 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 #[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(÷nd_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(÷nd_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 let mut divisor_roots = divisor_root_indices
4786 .into_iter()
4787 .map(|i| dividend_roots[i])
4788 .collect_vec();
4789
4790 dividend_roots.push(bfe!(0));
4792 divisor_roots.push(bfe!(0));
4793
4794 let dividend = Polynomial::zerofier(÷nd_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(÷nd_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 let expected_quot = polynomial([BFieldElement::P - 1, 1, 0, 1]);
4879 assert_eq!(expected_quot, x_to_the_6_div_shah);
4880
4881 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 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 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 let mut failures = vec![];
5257 for i in 1..100 {
5258 let roots = (0..i).map(BFieldElement::new).collect_vec();
5262 let dividend = Polynomial::zerofier(&roots).formal_derivative();
5263
5264 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 prop_assert!(remainder.is_one());
5525
5526 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 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 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(÷nd_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 #[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 drop(coefficients);
5747 }
5748 #[macro_rules_attr::apply(test)]
5749 fn par_scale_agrees_with_scale() {
5750 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, ¶llel);
5792 let parallel_interpolant = Polynomial::par_fast_coset_interpolate(offset, ¶llel);
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}