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::Unstructured;
20use itertools::EitherOrBoth;
21use itertools::Itertools;
22use num_traits::ConstOne;
23use num_traits::ConstZero;
24use num_traits::One;
25use num_traits::Zero;
26use rayon::current_num_threads;
27use rayon::prelude::*;
28
29use super::traits::PrimitiveRootOfUnity;
30use super::zerofier_tree::ZerofierTree;
31use crate::math::ntt::intt;
32use crate::math::ntt::ntt;
33use crate::math::traits::FiniteField;
34use crate::math::traits::ModPowU32;
35use crate::prelude::BFieldElement;
36use crate::prelude::Inverse;
37use crate::prelude::XFieldElement;
38
39impl<FF: FiniteField> Zero for Polynomial<'static, FF> {
40 fn zero() -> Self {
41 Self::new(vec![])
42 }
43
44 fn is_zero(&self) -> bool {
45 *self == Self::zero()
46 }
47}
48
49impl<FF: FiniteField> One for Polynomial<'static, FF> {
50 fn one() -> Self {
51 Self::new(vec![FF::ONE])
52 }
53
54 fn is_one(&self) -> bool {
55 self.degree() == 0 && self.coefficients[0].is_one()
56 }
57}
58
59#[doc(hidden)]
62#[derive(Debug, Clone)]
63pub struct ModularInterpolationPreprocessingData<'coeffs, FF: FiniteField> {
64 pub even_zerofiers: Vec<Polynomial<'coeffs, FF>>,
65 pub odd_zerofiers: Vec<Polynomial<'coeffs, FF>>,
66 pub shift_coefficients: Vec<FF>,
67 pub tail_length: usize,
68}
69
70#[derive(Clone)]
77pub struct Polynomial<'coeffs, FF: FiniteField> {
78 coefficients: Cow<'coeffs, [FF]>,
83}
84
85impl<'a, FF> Arbitrary<'a> for Polynomial<'static, FF>
86where
87 FF: FiniteField + Arbitrary<'a>,
88{
89 fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
90 Ok(Self::new(u.arbitrary()?))
91 }
92}
93
94impl<FF: FiniteField> Debug for Polynomial<'_, FF> {
95 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
96 f.debug_struct("Polynomial")
97 .field("coefficients", &self.coefficients)
98 .finish()
99 }
100}
101
102impl<FF: FiniteField> Hash for Polynomial<'_, FF> {
104 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
105 self.coefficients.hash(state);
106 }
107}
108
109impl<FF: FiniteField> Display for Polynomial<'_, FF> {
110 fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
111 let degree = match self.degree() {
112 -1 => return write!(f, "0"),
113 d => d as usize,
114 };
115
116 for pow in (0..=degree).rev() {
117 let coeff = self.coefficients[pow];
118 if coeff.is_zero() {
119 continue;
120 }
121
122 if pow != degree {
123 write!(f, " + ")?;
124 }
125 if !coeff.is_one() || pow == 0 {
126 write!(f, "{coeff}")?;
127 }
128 match pow {
129 0 => (),
130 1 => write!(f, "x")?,
131 _ => write!(f, "x^{pow}")?,
132 }
133 }
134
135 Ok(())
136 }
137}
138
139impl<FF: FiniteField> PartialEq<Polynomial<'_, FF>> for Polynomial<'_, FF> {
141 fn eq(&self, other: &Polynomial<'_, FF>) -> bool {
142 if self.degree() != other.degree() {
143 return false;
144 }
145
146 self.coefficients
147 .iter()
148 .zip(other.coefficients.iter())
149 .all(|(x, y)| x == y)
150 }
151}
152
153impl<FF: FiniteField> Eq for Polynomial<'_, FF> {}
154
155impl<FF> Polynomial<'_, FF>
156where
157 FF: FiniteField,
158{
159 pub fn degree(&self) -> isize {
171 let mut deg = self.coefficients.len() as isize - 1;
172 while deg >= 0 && self.coefficients[deg as usize].is_zero() {
173 deg -= 1;
174 }
175
176 deg }
178
179 pub fn coefficients(&self) -> &[FF] {
187 let coefficients = self.coefficients.as_ref();
188
189 let Some(leading_coeff_idx) = coefficients.iter().rposition(|&c| !c.is_zero()) else {
190 return &[];
192 };
193
194 &coefficients[0..=leading_coeff_idx]
195 }
196
197 pub fn into_coefficients(mut self) -> Vec<FF> {
201 self.normalize();
202 self.coefficients.into_owned()
203 }
204
205 fn normalize(&mut self) {
209 while self.coefficients.last().is_some_and(Zero::is_zero) {
210 self.coefficients.to_mut().pop();
211 }
212 }
213
214 pub fn leading_coefficient(&self) -> Option<FF> {
229 match self.degree() {
230 -1 => None,
231 n => Some(self.coefficients[n as usize]),
232 }
233 }
234
235 pub fn is_x(&self) -> bool {
248 self.degree() == 1 && self.coefficients[0].is_zero() && self.coefficients[1].is_one()
249 }
250
251 pub fn formal_derivative(&self) -> Polynomial<'static, FF> {
265 let coefficients = (0..)
268 .zip(self.coefficients.iter())
269 .map(|(i, &coefficient)| FF::from(i) * coefficient)
270 .skip(1)
271 .collect();
272
273 Polynomial::new(coefficients)
274 }
275
276 pub fn evaluate<Ind, Eval>(&self, x: Ind) -> Eval
299 where
300 Ind: Clone,
301 Eval: Mul<Ind, Output = Eval> + Add<FF, Output = Eval> + Zero,
302 {
303 let mut acc = Eval::zero();
304 for &c in self.coefficients.iter().rev() {
305 acc = acc * x.clone() + c;
306 }
307
308 acc
309 }
310 pub fn evaluate_in_same_field(&self, x: FF) -> FF {
317 self.evaluate::<FF, FF>(x)
318 }
319
320 pub fn are_colinear(points: &[(FF, FF)]) -> bool {
338 if points.len() < 3 {
339 return false;
340 }
341
342 if !points.iter().map(|(x, _)| x).all_unique() {
343 return false;
344 }
345
346 let (p0_x, p0_y) = points[0];
348 let (p1_x, p1_y) = points[1];
349 let a = (p0_y - p1_y) / (p0_x - p1_x);
350 let b = p0_y - a * p0_x;
351
352 points.iter().skip(2).all(|&(x, y)| a * x + b == y)
353 }
354
355 pub fn get_colinear_y(p0: (FF, FF), p1: (FF, FF), p2_x: FF) -> FF {
376 assert_ne!(p0.0, p1.0, "Line must not be parallel to y-axis");
377 let dy = p0.1 - p1.1;
378 let dx = p0.0 - p1.0;
379 let p2_y_times_dx = dy * (p2_x - p0.0) + dx * p0.1;
380
381 p2_y_times_dx / dx
383 }
384
385 #[must_use]
390 pub fn slow_square(&self) -> Polynomial<'static, FF> {
391 if self.degree() < 0 {
392 return Polynomial::zero();
393 }
394
395 let squared_coefficient_len = self.degree() as usize * 2 + 1;
396 let mut squared_coefficients = vec![FF::ZERO; squared_coefficient_len];
397
398 let two = FF::ONE + FF::ONE;
399 for i in 0..self.coefficients.len() {
400 let ci = self.coefficients[i];
401 squared_coefficients[2 * i] += ci * ci;
402
403 for j in i + 1..self.coefficients.len() {
404 let cj = self.coefficients[j];
405 squared_coefficients[i + j] += two * ci * cj;
406 }
407 }
408
409 Polynomial::new(squared_coefficients)
410 }
411
412 #[doc(hidden)]
414 pub fn naive_multiply<FF2>(
415 &self,
416 other: &Polynomial<FF2>,
417 ) -> Polynomial<'static, <FF as Mul<FF2>>::Output>
418 where
419 FF: Mul<FF2>,
420 FF2: FiniteField,
421 <FF as Mul<FF2>>::Output: FiniteField,
422 {
423 let Ok(degree_lhs) = usize::try_from(self.degree()) else {
424 return Polynomial::zero();
425 };
426 let Ok(degree_rhs) = usize::try_from(other.degree()) else {
427 return Polynomial::zero();
428 };
429
430 let mut product = vec![<FF as Mul<FF2>>::Output::ZERO; degree_lhs + degree_rhs + 1];
431 for i in 0..=degree_lhs {
432 for j in 0..=degree_rhs {
433 product[i + j] += self.coefficients[i] * other.coefficients[j];
434 }
435 }
436
437 Polynomial::new(product)
438 }
439
440 #[must_use]
444 pub fn pow(&self, pow: u32) -> Polynomial<'static, FF> {
445 let Some(bit_length) = pow.checked_ilog2() else {
447 return Polynomial::one();
448 };
449
450 if self.degree() < 0 {
451 return Polynomial::zero();
452 }
453
454 let mut acc = Polynomial::one();
456 for i in 0..=bit_length {
457 acc = acc.slow_square();
458 let bit_is_set = (pow >> (bit_length - i)) & 1 == 1;
459 if bit_is_set {
460 acc = acc * self.clone();
461 }
462 }
463
464 acc
465 }
466
467 #[must_use]
469 pub fn shift_coefficients(self, power: usize) -> Polynomial<'static, FF> {
470 let mut coefficients = self.coefficients.into_owned();
471 coefficients.splice(0..0, vec![FF::ZERO; power]);
472 Polynomial::new(coefficients)
473 }
474
475 pub fn scalar_mul_mut<S>(&mut self, scalar: S)
488 where
489 S: Clone,
490 FF: MulAssign<S>,
491 {
492 let mut coefficients = std::mem::take(&mut self.coefficients).into_owned();
493 for coefficient in &mut coefficients {
494 *coefficient *= scalar.clone();
495 }
496 self.coefficients = Cow::Owned(coefficients);
497 }
498
499 #[must_use]
512 pub fn scalar_mul<S, FF2>(&self, scalar: S) -> Polynomial<'static, FF2>
513 where
514 S: Clone,
515 FF: Mul<S, Output = FF2>,
516 FF2: FiniteField,
517 {
518 let coeff_iter = self.coefficients.iter();
519 let new_coeffs = coeff_iter.map(|&c| c * scalar.clone()).collect();
520 Polynomial::new(new_coeffs)
521 }
522
523 pub fn divide(
529 &self,
530 divisor: &Polynomial<'_, FF>,
531 ) -> (Polynomial<'static, FF>, Polynomial<'static, FF>) {
532 self.naive_divide(divisor)
535 }
536
537 #[doc(hidden)]
541 pub fn naive_divide(
542 &self,
543 divisor: &Polynomial<'_, FF>,
544 ) -> (Polynomial<'static, FF>, Polynomial<'static, FF>) {
545 let divisor_lc_inv = divisor
546 .leading_coefficient()
547 .expect("divisor should be non-zero")
548 .inverse();
549
550 let Ok(quotient_degree) = usize::try_from(self.degree() - divisor.degree()) else {
551 return (Polynomial::zero(), self.clone().into_owned());
553 };
554 debug_assert!(self.degree() >= 0);
555
556 let mut rev_quotient = Vec::with_capacity(quotient_degree + 1);
558 let mut remainder = self.clone();
559 remainder.normalize();
560
561 let rev_divisor = divisor.coefficients.iter().rev();
564 let normal_rev_divisor = rev_divisor.skip_while(|c| c.is_zero());
565
566 let mut remainder_coefficients = remainder.coefficients.into_owned();
567 for _ in 0..=quotient_degree {
568 let remainder_lc = remainder_coefficients.pop().unwrap();
569 let quotient_coeff = remainder_lc * divisor_lc_inv;
570 rev_quotient.push(quotient_coeff);
571
572 if quotient_coeff.is_zero() {
573 continue;
574 }
575
576 let remainder_degree = remainder_coefficients.len().saturating_sub(1);
577
578 for (i, &divisor_coeff) in normal_rev_divisor.clone().skip(1).enumerate() {
580 remainder_coefficients[remainder_degree - i] -= quotient_coeff * divisor_coeff;
581 }
582 }
583
584 rev_quotient.reverse();
585
586 let quot = Polynomial::new(rev_quotient);
587 let rem = Polynomial::new(remainder_coefficients);
588 (quot, rem)
589 }
590
591 pub fn xgcd(
606 x: Self,
607 y: Polynomial<'_, FF>,
608 ) -> (
609 Polynomial<'static, FF>,
610 Polynomial<'static, FF>,
611 Polynomial<'static, FF>,
612 ) {
613 let mut x = x.into_owned();
614 let mut y = y.into_owned();
615 let (mut a_factor, mut a1) = (Polynomial::one(), Polynomial::zero());
616 let (mut b_factor, mut b1) = (Polynomial::zero(), Polynomial::one());
617
618 while !y.is_zero() {
619 let (quotient, remainder) = x.naive_divide(&y);
620 let c = a_factor - quotient.clone() * a1.clone();
621 let d = b_factor - quotient * b1.clone();
622
623 x = y;
624 y = remainder;
625 a_factor = a1;
626 a1 = c;
627 b_factor = b1;
628 b1 = d;
629 }
630
631 let lc = x.leading_coefficient().unwrap_or(FF::ONE);
634 let normalize = |poly: Self| poly.scalar_mul(lc.inverse());
635
636 let [x, a, b] = [x, a_factor, b_factor].map(normalize);
637 (x, a, b)
638 }
639
640 fn formal_power_series_inverse_minimal(&self, precision: usize) -> Polynomial<'static, FF> {
647 let lc_inv = self.coefficients.first().unwrap().inverse();
648 let mut g = vec![lc_inv];
649
650 for _ in 1..(precision + 1) {
652 let inner_product = self
653 .coefficients
654 .iter()
655 .skip(1)
656 .take(g.len())
657 .zip(g.iter().rev())
658 .map(|(l, r)| *l * *r)
659 .fold(FF::ZERO, |l, r| l + r);
660 g.push(-inner_product * lc_inv);
661 }
662
663 Polynomial::new(g)
664 }
665
666 pub(crate) fn reverse(&self) -> Polynomial<'static, FF> {
667 let degree = self.degree();
668 let new_coefficients = self
669 .coefficients
670 .iter()
671 .take((degree + 1) as usize)
672 .copied()
673 .rev()
674 .collect_vec();
675 Polynomial::new(new_coefficients)
676 }
677
678 pub fn into_owned(self) -> Polynomial<'static, FF> {
681 Polynomial::new(self.coefficients.into_owned())
682 }
683}
684
685impl<FF> Polynomial<'_, FF>
686where
687 FF: FiniteField + MulAssign<BFieldElement>,
688{
689 const FAST_MULTIPLY_CUTOFF_THRESHOLD: isize = 1 << 8;
694
695 const FAST_INTERPOLATE_CUTOFF_THRESHOLD_SEQUENTIAL: usize = 1 << 12;
701
702 const FAST_INTERPOLATE_CUTOFF_THRESHOLD_PARALLEL: usize = 1 << 8;
708
709 const FAST_MODULAR_COSET_INTERPOLATE_CUTOFF_THRESHOLD_PREFER_LAGRANGE: usize = 1 << 8;
714
715 const FAST_MODULAR_COSET_INTERPOLATE_CUTOFF_THRESHOLD_PREFER_INTT: usize = 1 << 17;
719
720 const FAST_COSET_EXTRAPOLATE_THRESHOLD: usize = 100;
724
725 const FORMAL_POWER_SERIES_INVERSE_CUTOFF: isize = 1 << 8;
730
731 const FAST_REDUCE_CUTOFF_THRESHOLD: usize = 1 << 8;
738
739 const REDUCE_BEFORE_EVALUATE_THRESHOLD_RATIO: isize = 4;
743
744 #[must_use]
749 pub fn scale<S, XF>(&self, alpha: S) -> Polynomial<'static, XF>
750 where
751 S: Clone + One,
752 FF: Mul<S, Output = XF>,
753 XF: FiniteField,
754 {
755 let mut power_of_alpha = S::one();
756 let mut return_coefficients = Vec::with_capacity(self.coefficients.len());
757 for &coefficient in self.coefficients.iter() {
758 return_coefficients.push(coefficient * power_of_alpha.clone());
759 power_of_alpha = power_of_alpha * alpha.clone();
760 }
761 Polynomial::new(return_coefficients)
762 }
763
764 #[must_use]
769 pub fn fast_square(&self) -> Polynomial<'static, FF> {
770 let result_degree = match self.degree() {
771 -1 => return Polynomial::zero(),
772 0 => return Polynomial::from_constant(self.coefficients[0] * self.coefficients[0]),
773 d => 2 * d as u64 + 1,
774 };
775 let ntt_size = result_degree.next_power_of_two();
776
777 let mut coefficients = self.coefficients.to_vec();
778 coefficients.resize(ntt_size as usize, FF::ZERO);
779 ntt(&mut coefficients);
780 for element in &mut coefficients {
781 *element = *element * *element;
782 }
783 intt(&mut coefficients);
784 coefficients.truncate(result_degree as usize);
785
786 Polynomial::new(coefficients)
787 }
788
789 #[must_use]
800 pub fn square(&self) -> Polynomial<'static, FF> {
801 if self.degree() == -1 {
802 return Polynomial::zero();
803 }
804
805 let squared_coefficient_len = self.degree() as usize * 2 + 1;
808 if squared_coefficient_len > 64 {
809 return self.fast_square();
810 }
811
812 let zero = FF::ZERO;
813 let one = FF::ONE;
814 let two = one + one;
815 let mut squared_coefficients = vec![zero; squared_coefficient_len];
816
817 for i in 0..self.coefficients.len() {
818 let ci = self.coefficients[i];
819 squared_coefficients[2 * i] += ci * ci;
820
821 for j in i + 1..self.coefficients.len() {
822 let cj = self.coefficients[j];
823 squared_coefficients[i + j] += two * ci * cj;
824 }
825 }
826
827 Polynomial::new(squared_coefficients)
828 }
829
830 #[must_use]
834 pub fn fast_pow(&self, pow: u32) -> Polynomial<'static, FF> {
835 let Some(bit_length) = pow.checked_ilog2() else {
837 return Polynomial::one();
838 };
839
840 if self.degree() < 0 {
841 return Polynomial::zero();
842 }
843
844 let mut acc = Polynomial::one();
846 for i in 0..=bit_length {
847 acc = acc.square();
848 let bit_is_set = (pow >> (bit_length - i)) & 1 == 1;
849 if bit_is_set {
850 acc = self.multiply(&acc);
851 }
852 }
853
854 acc
855 }
856
857 #[must_use]
862 pub fn multiply<FF2>(
863 &self,
864 other: &Polynomial<'_, FF2>,
865 ) -> Polynomial<'static, <FF as Mul<FF2>>::Output>
866 where
867 FF: Mul<FF2>,
868 FF2: FiniteField + MulAssign<BFieldElement>,
869 <FF as Mul<FF2>>::Output: FiniteField + MulAssign<BFieldElement>,
870 {
871 if self.degree() + other.degree() < Self::FAST_MULTIPLY_CUTOFF_THRESHOLD {
872 self.naive_multiply(other)
873 } else {
874 self.fast_multiply(other)
875 }
876 }
877
878 #[doc(hidden)]
889 pub fn fast_multiply<FF2>(
890 &self,
891 other: &Polynomial<FF2>,
892 ) -> Polynomial<'static, <FF as Mul<FF2>>::Output>
893 where
894 FF: Mul<FF2>,
895 FF2: FiniteField + MulAssign<BFieldElement>,
896 <FF as Mul<FF2>>::Output: FiniteField + MulAssign<BFieldElement>,
897 {
898 let Ok(degree) = usize::try_from(self.degree() + other.degree()) else {
899 return Polynomial::zero();
900 };
901 let order = (degree + 1).next_power_of_two();
902
903 let mut lhs_coefficients = self.coefficients.to_vec();
904 let mut rhs_coefficients = other.coefficients.to_vec();
905
906 lhs_coefficients.resize(order, FF::ZERO);
907 rhs_coefficients.resize(order, FF2::ZERO);
908
909 ntt(&mut lhs_coefficients);
910 ntt(&mut rhs_coefficients);
911
912 let mut hadamard_product = lhs_coefficients
913 .into_iter()
914 .zip(rhs_coefficients)
915 .map(|(l, r)| l * r)
916 .collect_vec();
917
918 intt(&mut hadamard_product);
919 hadamard_product.truncate(degree + 1);
920 Polynomial::new(hadamard_product)
921 }
922
923 pub fn batch_multiply(factors: &[Self]) -> Polynomial<'static, FF> {
925 if factors.is_empty() {
933 return Polynomial::one();
934 }
935 let mut products = factors.to_vec();
936 while products.len() != 1 {
937 products = products
938 .chunks(2)
939 .map(|chunk| match chunk.len() {
940 2 => chunk[0].multiply(&chunk[1]),
941 1 => chunk[0].clone(),
942 _ => unreachable!(),
943 })
944 .collect();
945 }
946
947 let product_coeffs = products.pop().unwrap().coefficients.into_owned();
953 Polynomial::new(product_coeffs)
954 }
955
956 pub fn par_batch_multiply(factors: &[Self]) -> Polynomial<'static, FF> {
958 if factors.is_empty() {
959 return Polynomial::one();
960 }
961 let num_threads = current_num_threads().max(1);
962 let mut products = factors.to_vec();
963 while products.len() != 1 {
964 let chunk_size = usize::max(2, products.len() / num_threads);
965 products = products
966 .par_chunks(chunk_size)
967 .map(Self::batch_multiply)
968 .collect();
969 }
970
971 let product_coeffs = products.pop().unwrap().coefficients.into_owned();
972 Polynomial::new(product_coeffs)
973 }
974
975 pub fn reduce(&self, modulus: &Polynomial<'_, FF>) -> Polynomial<'static, FF> {
979 const FAST_REDUCE_MAKES_SENSE_MULTIPLE: isize = 4;
980 if modulus.degree() < 0 {
981 panic!("Cannot divide by zero; needed for reduce.");
982 } else if modulus.degree() == 0 {
983 Polynomial::zero()
984 } else if self.degree() < modulus.degree() {
985 self.clone().into_owned()
986 } else if self.degree() > FAST_REDUCE_MAKES_SENSE_MULTIPLE * modulus.degree() {
987 self.fast_reduce(modulus)
988 } else {
989 self.reduce_long_division(modulus)
990 }
991 }
992
993 pub fn fast_reduce(&self, modulus: &Self) -> Polynomial<'static, FF> {
1000 if modulus.degree() == 0 {
1001 return Polynomial::zero();
1002 }
1003 if self.degree() < modulus.degree() {
1004 return self.clone().into_owned();
1005 }
1006
1007 let (shift_factor_ntt, tail_size) = modulus.shift_factor_ntt_with_tail_length();
1018 let mut intermediate_remainder =
1019 self.reduce_by_ntt_friendly_modulus(&shift_factor_ntt, tail_size);
1020
1021 if intermediate_remainder.degree() > 4 * modulus.degree() {
1028 let structured_multiple = modulus.structured_multiple();
1029 intermediate_remainder =
1030 intermediate_remainder.reduce_by_structured_modulus(&structured_multiple);
1031 }
1032
1033 intermediate_remainder.reduce_long_division(modulus)
1035 }
1036
1037 #[doc(hidden)]
1040 pub fn shift_factor_ntt_with_tail_length(&self) -> (Vec<FF>, usize)
1041 where
1042 FF: 'static,
1043 {
1044 let n = usize::max(
1045 Self::FAST_REDUCE_CUTOFF_THRESHOLD,
1046 self.degree() as usize * 2,
1047 )
1048 .next_power_of_two();
1049 let ntt_friendly_multiple = self.structured_multiple_of_degree(n);
1050
1051 let m = 1 + ntt_friendly_multiple
1053 .coefficients
1054 .iter()
1055 .enumerate()
1056 .rev()
1057 .skip(1)
1058 .find_map(|(i, c)| if !c.is_zero() { Some(i) } else { None })
1059 .unwrap_or(0);
1060 let mut shift_factor_ntt = ntt_friendly_multiple.coefficients[..n].to_vec();
1061 ntt(&mut shift_factor_ntt);
1062 (shift_factor_ntt, m)
1063 }
1064
1065 #[doc(hidden)]
1076 pub fn reduce_by_ntt_friendly_modulus(
1077 &self,
1078 shift_ntt: &[FF],
1079 tail_length: usize,
1080 ) -> Polynomial<'static, FF> {
1081 let domain_length = shift_ntt.len();
1082 assert!(domain_length.is_power_of_two());
1083 let chunk_size = domain_length - tail_length;
1084
1085 if self.coefficients.len() < chunk_size + tail_length {
1086 return self.clone().into_owned();
1087 }
1088 let num_reducible_chunks =
1089 (self.coefficients.len() - (tail_length + chunk_size)).div_ceil(chunk_size);
1090
1091 let range_start = num_reducible_chunks * chunk_size;
1092 let mut working_window = if range_start >= self.coefficients.len() {
1093 vec![FF::ZERO; chunk_size + tail_length]
1094 } else {
1095 self.coefficients[range_start..].to_vec()
1096 };
1097 working_window.resize(chunk_size + tail_length, FF::ZERO);
1098
1099 for chunk_index in (0..num_reducible_chunks).rev() {
1100 let mut product = [
1101 working_window[tail_length..].to_vec(),
1102 vec![FF::ZERO; tail_length],
1103 ]
1104 .concat();
1105 ntt(&mut product);
1106 product
1107 .iter_mut()
1108 .zip(shift_ntt.iter())
1109 .for_each(|(l, r)| *l *= *r);
1110 intt(&mut product);
1111
1112 working_window = [
1113 vec![FF::ZERO; chunk_size],
1114 working_window[0..tail_length].to_vec(),
1115 ]
1116 .concat();
1117 for (i, wwi) in working_window.iter_mut().enumerate().take(chunk_size) {
1118 *wwi = self.coefficients[chunk_index * chunk_size + i];
1119 }
1120
1121 for (i, wwi) in working_window
1122 .iter_mut()
1123 .enumerate()
1124 .take(chunk_size + tail_length)
1125 {
1126 *wwi -= product[i];
1127 }
1128 }
1129
1130 Polynomial::new(working_window)
1131 }
1132
1133 fn structured_multiple(&self) -> Polynomial<'static, FF> {
1140 let n = usize::try_from(self.degree()).expect("cannot compute multiple of zero");
1141 self.structured_multiple_of_degree(3 * n + 1)
1142 }
1143
1144 pub fn structured_multiple_of_degree(&self, n: usize) -> Polynomial<'static, FF> {
1151 let Ok(degree) = usize::try_from(self.degree()) else {
1152 panic!("cannot compute multiples of zero");
1153 };
1154 assert!(degree <= n, "cannot compute multiple of smaller degree.");
1155 if degree == 0 {
1156 return Polynomial::new(
1157 [vec![FF::ZERO; n], vec![self.coefficients[0].inverse()]].concat(),
1158 );
1159 }
1160
1161 let reverse = self.reverse();
1162
1163 let inverse_reverse = reverse.formal_power_series_inverse_minimal(n - degree);
1169 let product_reverse = reverse.multiply(&inverse_reverse);
1170 let product = product_reverse.reverse();
1171
1172 let product_degree = product.degree() as usize;
1174 product.shift_coefficients(n - product_degree)
1175 }
1176
1177 fn reduce_by_structured_modulus(&self, multiple: &Self) -> Polynomial<'static, FF> {
1188 assert_ne!(0, multiple.degree());
1189 let multiple_degree = usize::try_from(multiple.degree()).expect("cannot reduce by zero");
1190 assert_eq!(
1191 Some(FF::ONE),
1192 multiple.leading_coefficient(),
1193 "multiple must be monic"
1194 );
1195 let leading_term = Polynomial::x_to_the(multiple_degree);
1196 let shift_polynomial = multiple.clone() - leading_term.clone();
1197 assert!(shift_polynomial.degree() < multiple.degree());
1198
1199 let tail_length = usize::try_from(shift_polynomial.degree())
1200 .map(|unsigned_degree| unsigned_degree + 1)
1201 .unwrap_or(0);
1202 let window_length = multiple_degree;
1203 let chunk_size = window_length - tail_length;
1204 if self.coefficients.len() < chunk_size + tail_length {
1205 return self.clone().into_owned();
1206 }
1207 let num_reducible_chunks =
1208 (self.coefficients.len() - (tail_length + chunk_size)).div_ceil(chunk_size);
1209
1210 let window_stop = (tail_length + chunk_size) + num_reducible_chunks * chunk_size;
1211 let mut window_start = window_stop - window_length;
1212 let mut working_window = self.coefficients[window_start..].to_vec();
1213 working_window.resize(chunk_size + tail_length, FF::ZERO);
1214
1215 for _ in (0..num_reducible_chunks).rev() {
1216 let overflow = Polynomial::new(working_window[tail_length..].to_vec());
1217 let product = overflow.multiply(&shift_polynomial);
1218
1219 window_start -= chunk_size;
1220 working_window = [
1221 self.coefficients[window_start..window_start + chunk_size].to_vec(),
1222 working_window[0..tail_length].to_vec(),
1223 ]
1224 .concat();
1225
1226 for (i, wwi) in working_window
1227 .iter_mut()
1228 .enumerate()
1229 .take(chunk_size + tail_length)
1230 {
1231 *wwi -= *product.coefficients.get(i).unwrap_or(&FF::ZERO);
1232 }
1233 }
1234
1235 Polynomial::new(working_window)
1236 }
1237
1238 fn reduce_long_division(&self, modulus: &Polynomial<'_, FF>) -> Polynomial<'static, FF> {
1239 let (_quotient, remainder) = self.divide(modulus);
1240 remainder
1241 }
1242
1243 pub fn formal_power_series_inverse_newton(self, precision: usize) -> Polynomial<'static, FF> {
1271 let self_degree = self.degree();
1273 if self_degree == 0 {
1274 return Polynomial::from_constant(self.coefficients[0].inverse());
1275 }
1276
1277 let num_rounds = precision.next_power_of_two().ilog2();
1279
1280 let switch_point = if Self::FORMAL_POWER_SERIES_INVERSE_CUTOFF < self_degree {
1283 0
1284 } else {
1285 (Self::FORMAL_POWER_SERIES_INVERSE_CUTOFF / self_degree).ilog2()
1286 };
1287
1288 let cc = self.coefficients[0];
1289
1290 let mut f = Polynomial::from_constant(cc.inverse());
1292 for _ in 0..u32::min(num_rounds, switch_point) {
1293 let sub = f.multiply(&f).multiply(&self);
1294 f.scalar_mul_mut(FF::from(2));
1295 f = f - sub;
1296 }
1297
1298 if switch_point >= num_rounds {
1300 return f;
1301 }
1302
1303 let full_domain_length =
1307 ((1 << (num_rounds + 1)) * self_degree as usize).next_power_of_two();
1308
1309 let mut self_ntt = self.coefficients.into_owned();
1310 self_ntt.resize(full_domain_length, FF::ZERO);
1311 ntt(&mut self_ntt);
1312
1313 let mut current_domain_length = f.coefficients.len().next_power_of_two();
1315
1316 let lde = |v: &mut [FF], old_domain_length: usize, new_domain_length: usize| {
1318 intt(&mut v[..old_domain_length]);
1319 ntt(&mut v[..new_domain_length]);
1320 };
1321
1322 let mut f_degree = f.degree();
1324
1325 let mut f_ntt = f.coefficients.into_owned();
1328 f_ntt.resize(full_domain_length, FF::ZERO);
1329 ntt(&mut f_ntt[..current_domain_length]);
1330
1331 for _ in switch_point..num_rounds {
1332 f_degree = 2 * f_degree + self_degree;
1333 if f_degree as usize >= current_domain_length {
1334 let next_domain_length = (1 + f_degree as usize).next_power_of_two();
1335 lde(&mut f_ntt, current_domain_length, next_domain_length);
1336 current_domain_length = next_domain_length;
1337 }
1338 f_ntt
1339 .iter_mut()
1340 .zip(
1341 self_ntt
1342 .iter()
1343 .step_by(full_domain_length / current_domain_length),
1344 )
1345 .for_each(|(ff, dd)| *ff = FF::from(2) * *ff - *ff * *ff * *dd);
1346 }
1347
1348 intt(&mut f_ntt[..current_domain_length]);
1349 Polynomial::new(f_ntt)
1350 }
1351
1352 pub fn fast_coset_evaluate<S>(&self, offset: S, order: usize) -> Vec<FF>
1364 where
1365 S: Clone + One,
1366 FF: Mul<S, Output = FF> + 'static,
1367 {
1368 assert!(
1378 (order as isize) > self.degree(),
1379 "`Polynomial::fast_coset_evaluate` is currently limited to domains of order \
1380 greater than the degree of the polynomial."
1381 );
1382
1383 let mut coefficients = self.scale(offset).coefficients.into_owned();
1384 coefficients.resize(order, FF::ZERO);
1385 ntt(&mut coefficients);
1386
1387 coefficients
1388 }
1389}
1390
1391impl<FF> Polynomial<'static, FF>
1392where
1393 FF: FiniteField + MulAssign<BFieldElement>,
1394{
1395 const FAST_ZEROFIER_CUTOFF_THRESHOLD: usize = 100;
1406
1407 pub fn zerofier(roots: &[FF]) -> Self {
1425 if roots.len() < Self::FAST_ZEROFIER_CUTOFF_THRESHOLD {
1426 Self::smart_zerofier(roots)
1427 } else {
1428 Self::fast_zerofier(roots)
1429 }
1430 }
1431
1432 pub fn par_zerofier(roots: &[FF]) -> Self {
1434 if roots.is_empty() {
1435 return Polynomial::one();
1436 }
1437 let num_threads = current_num_threads().max(1);
1438 let chunk_size = roots
1439 .len()
1440 .div_ceil(num_threads)
1441 .max(Self::FAST_ZEROFIER_CUTOFF_THRESHOLD);
1442 let factors = roots
1443 .par_chunks(chunk_size)
1444 .map(|chunk| Self::zerofier(chunk))
1445 .collect::<Vec<_>>();
1446 Polynomial::par_batch_multiply(&factors)
1447 }
1448
1449 #[doc(hidden)]
1451 pub fn smart_zerofier(roots: &[FF]) -> Self {
1452 let mut zerofier = vec![FF::ZERO; roots.len() + 1];
1453 zerofier[0] = FF::ONE;
1454 let mut num_coeffs = 1;
1455 for &root in roots {
1456 for k in (1..=num_coeffs).rev() {
1457 zerofier[k] = zerofier[k - 1] - root * zerofier[k];
1458 }
1459 zerofier[0] = -root * zerofier[0];
1460 num_coeffs += 1;
1461 }
1462 Self::new(zerofier)
1463 }
1464
1465 #[doc(hidden)]
1467 pub fn fast_zerofier(roots: &[FF]) -> Self {
1468 let mid_point = roots.len() / 2;
1469 let left = Self::zerofier(&roots[..mid_point]);
1470 let right = Self::zerofier(&roots[mid_point..]);
1471
1472 left.multiply(&right)
1473 }
1474
1475 pub fn interpolate(domain: &[FF], values: &[FF]) -> Self {
1492 assert!(
1493 !domain.is_empty(),
1494 "interpolation must happen through more than zero points"
1495 );
1496 assert_eq!(
1497 domain.len(),
1498 values.len(),
1499 "The domain and values lists have to be of equal length."
1500 );
1501
1502 if domain.len() <= Self::FAST_INTERPOLATE_CUTOFF_THRESHOLD_SEQUENTIAL {
1503 Self::lagrange_interpolate(domain, values)
1504 } else {
1505 Self::fast_interpolate(domain, values)
1506 }
1507 }
1508
1509 pub fn par_interpolate(domain: &[FF], values: &[FF]) -> Self {
1515 assert!(
1516 !domain.is_empty(),
1517 "interpolation must happen through more than zero points"
1518 );
1519 assert_eq!(
1520 domain.len(),
1521 values.len(),
1522 "The domain and values lists have to be of equal length."
1523 );
1524
1525 if domain.len() <= Self::FAST_INTERPOLATE_CUTOFF_THRESHOLD_PARALLEL {
1528 Self::lagrange_interpolate(domain, values)
1529 } else {
1530 Self::par_fast_interpolate(domain, values)
1531 }
1532 }
1533
1534 #[doc(hidden)]
1538 pub fn lagrange_interpolate_zipped(points: &[(FF, FF)]) -> Self {
1539 assert!(
1540 !points.is_empty(),
1541 "interpolation must happen through more than zero points"
1542 );
1543 assert!(
1544 points.iter().map(|x| x.0).all_unique(),
1545 "Repeated x values received. Got: {points:?}",
1546 );
1547
1548 let xs: Vec<FF> = points.iter().map(|x| x.0.to_owned()).collect();
1549 let ys: Vec<FF> = points.iter().map(|x| x.1.to_owned()).collect();
1550 Self::lagrange_interpolate(&xs, &ys)
1551 }
1552
1553 #[doc(hidden)]
1554 pub fn lagrange_interpolate(domain: &[FF], values: &[FF]) -> Self {
1555 debug_assert!(
1556 !domain.is_empty(),
1557 "interpolation domain cannot have zero points"
1558 );
1559 debug_assert_eq!(domain.len(), values.len());
1560
1561 let zero = FF::ZERO;
1562 let zerofier = Self::zerofier(domain).coefficients;
1563
1564 let mut lagrange_sum_array = vec![zero; domain.len()];
1568 let mut summand_array = vec![zero; domain.len()];
1569 for (i, &abscis) in values.iter().enumerate() {
1570 let mut leading_coefficient = zerofier[domain.len()];
1572 let mut supporting_coefficient = zerofier[domain.len() - 1];
1573 let mut summand_eval = zero;
1574 for j in (1..domain.len()).rev() {
1575 summand_array[j] = leading_coefficient;
1576 summand_eval = summand_eval * domain[i] + leading_coefficient;
1577 leading_coefficient = supporting_coefficient + leading_coefficient * domain[i];
1578 supporting_coefficient = zerofier[j - 1];
1579 }
1580
1581 summand_array[0] = leading_coefficient;
1583 summand_eval = summand_eval * domain[i] + leading_coefficient;
1584
1585 let corrected_abscis = abscis / summand_eval;
1588
1589 for j in 0..domain.len() {
1591 lagrange_sum_array[j] += corrected_abscis * summand_array[j];
1592 }
1593 }
1594
1595 Self::new(lagrange_sum_array)
1596 }
1597
1598 #[doc(hidden)]
1600 pub fn fast_interpolate(domain: &[FF], values: &[FF]) -> Self {
1601 debug_assert!(
1602 !domain.is_empty(),
1603 "interpolation domain cannot have zero points"
1604 );
1605 debug_assert_eq!(domain.len(), values.len());
1606
1607 if domain.len() == 1 {
1609 return Self::from_constant(values[0]);
1610 }
1611
1612 let mid_point = domain.len() / 2;
1613 let left_domain_half = &domain[..mid_point];
1614 let left_values_half = &values[..mid_point];
1615 let right_domain_half = &domain[mid_point..];
1616 let right_values_half = &values[mid_point..];
1617
1618 let left_zerofier = Self::zerofier(left_domain_half);
1619 let right_zerofier = Self::zerofier(right_domain_half);
1620
1621 let left_offset = right_zerofier.batch_evaluate(left_domain_half);
1622 let right_offset = left_zerofier.batch_evaluate(right_domain_half);
1623
1624 let hadamard_mul = |x: &[_], y: Vec<_>| x.iter().zip(y).map(|(&n, d)| n * d).collect_vec();
1625 let interpolate_half = |offset, domain_half, values_half| {
1626 let offset_inverse = FF::batch_inversion(offset);
1627 let targets = hadamard_mul(values_half, offset_inverse);
1628 Self::interpolate(domain_half, &targets)
1629 };
1630 let (left_interpolant, right_interpolant) = (
1631 interpolate_half(left_offset, left_domain_half, left_values_half),
1632 interpolate_half(right_offset, right_domain_half, right_values_half),
1633 );
1634
1635 let (left_term, right_term) = (
1636 left_interpolant.multiply(&right_zerofier),
1637 right_interpolant.multiply(&left_zerofier),
1638 );
1639
1640 left_term + right_term
1641 }
1642
1643 #[doc(hidden)]
1645 pub fn par_fast_interpolate(domain: &[FF], values: &[FF]) -> Self {
1646 debug_assert!(
1647 !domain.is_empty(),
1648 "interpolation domain cannot have zero points"
1649 );
1650 debug_assert_eq!(domain.len(), values.len());
1651
1652 if domain.len() == 1 {
1654 return Self::from_constant(values[0]);
1655 }
1656
1657 let mid_point = domain.len() / 2;
1658 let left_domain_half = &domain[..mid_point];
1659 let left_values_half = &values[..mid_point];
1660 let right_domain_half = &domain[mid_point..];
1661 let right_values_half = &values[mid_point..];
1662
1663 let (left_zerofier, right_zerofier) = rayon::join(
1664 || Self::zerofier(left_domain_half),
1665 || Self::zerofier(right_domain_half),
1666 );
1667
1668 let (left_offset, right_offset) = rayon::join(
1669 || right_zerofier.par_batch_evaluate(left_domain_half),
1670 || left_zerofier.par_batch_evaluate(right_domain_half),
1671 );
1672
1673 let hadamard_mul = |x: &[_], y: Vec<_>| x.iter().zip(y).map(|(&n, d)| n * d).collect_vec();
1674 let interpolate_half = |offset, domain_half, values_half| {
1675 let offset_inverse = FF::batch_inversion(offset);
1676 let targets = hadamard_mul(values_half, offset_inverse);
1677 Self::par_interpolate(domain_half, &targets)
1678 };
1679 let (left_interpolant, right_interpolant) = rayon::join(
1680 || interpolate_half(left_offset, left_domain_half, left_values_half),
1681 || interpolate_half(right_offset, right_domain_half, right_values_half),
1682 );
1683
1684 let (left_term, right_term) = rayon::join(
1685 || left_interpolant.multiply(&right_zerofier),
1686 || right_interpolant.multiply(&left_zerofier),
1687 );
1688
1689 left_term + right_term
1690 }
1691
1692 pub fn batch_fast_interpolate(
1693 domain: &[FF],
1694 values_matrix: &[Vec<FF>],
1695 primitive_root: BFieldElement,
1696 root_order: usize,
1697 ) -> Vec<Self> {
1698 debug_assert_eq!(
1699 primitive_root.mod_pow_u32(root_order as u32),
1700 BFieldElement::ONE,
1701 "Supplied element “primitive_root” must have supplied order.\
1702 Supplied element was: {primitive_root:?}\
1703 Supplied order was: {root_order:?}"
1704 );
1705
1706 assert!(
1707 !domain.is_empty(),
1708 "Cannot fast interpolate through zero points.",
1709 );
1710
1711 let mut zerofier_dictionary: HashMap<(FF, FF), Polynomial<FF>> = HashMap::default();
1712 let mut offset_inverse_dictionary: HashMap<(FF, FF), Vec<FF>> = HashMap::default();
1713
1714 Self::batch_fast_interpolate_with_memoization(
1715 domain,
1716 values_matrix,
1717 &mut zerofier_dictionary,
1718 &mut offset_inverse_dictionary,
1719 )
1720 }
1721
1722 fn batch_fast_interpolate_with_memoization(
1723 domain: &[FF],
1724 values_matrix: &[Vec<FF>],
1725 zerofier_dictionary: &mut HashMap<(FF, FF), Polynomial<'static, FF>>,
1726 offset_inverse_dictionary: &mut HashMap<(FF, FF), Vec<FF>>,
1727 ) -> Vec<Self> {
1728 const OPTIMAL_CUTOFF_POINT_FOR_BATCHED_INTERPOLATION: usize = 16;
1731 if domain.len() < OPTIMAL_CUTOFF_POINT_FOR_BATCHED_INTERPOLATION {
1732 return values_matrix
1733 .iter()
1734 .map(|values| Self::lagrange_interpolate(domain, values))
1735 .collect();
1736 }
1737
1738 let half = domain.len() / 2;
1740
1741 let left_key = (domain[0], domain[half - 1]);
1742 let left_zerofier = match zerofier_dictionary.get(&left_key) {
1743 Some(z) => z.to_owned(),
1744 None => {
1745 let left_zerofier = Self::zerofier(&domain[..half]);
1746 zerofier_dictionary.insert(left_key, left_zerofier.clone());
1747 left_zerofier
1748 }
1749 };
1750 let right_key = (domain[half], *domain.last().unwrap());
1751 let right_zerofier = match zerofier_dictionary.get(&right_key) {
1752 Some(z) => z.to_owned(),
1753 None => {
1754 let right_zerofier = Self::zerofier(&domain[half..]);
1755 zerofier_dictionary.insert(right_key, right_zerofier.clone());
1756 right_zerofier
1757 }
1758 };
1759
1760 let left_offset_inverse = match offset_inverse_dictionary.get(&left_key) {
1761 Some(vector) => vector.to_owned(),
1762 None => {
1763 let left_offset: Vec<FF> = Self::batch_evaluate(&right_zerofier, &domain[..half]);
1764 let left_offset_inverse = FF::batch_inversion(left_offset);
1765 offset_inverse_dictionary.insert(left_key, left_offset_inverse.clone());
1766 left_offset_inverse
1767 }
1768 };
1769 let right_offset_inverse = match offset_inverse_dictionary.get(&right_key) {
1770 Some(vector) => vector.to_owned(),
1771 None => {
1772 let right_offset: Vec<FF> = Self::batch_evaluate(&left_zerofier, &domain[half..]);
1773 let right_offset_inverse = FF::batch_inversion(right_offset);
1774 offset_inverse_dictionary.insert(right_key, right_offset_inverse.clone());
1775 right_offset_inverse
1776 }
1777 };
1778
1779 let all_left_targets: Vec<_> = values_matrix
1781 .par_iter()
1782 .map(|values| {
1783 values[..half]
1784 .iter()
1785 .zip(left_offset_inverse.iter())
1786 .map(|(n, d)| n.to_owned() * *d)
1787 .collect()
1788 })
1789 .collect();
1790 let all_right_targets: Vec<_> = values_matrix
1791 .par_iter()
1792 .map(|values| {
1793 values[half..]
1794 .par_iter()
1795 .zip(right_offset_inverse.par_iter())
1796 .map(|(n, d)| n.to_owned() * *d)
1797 .collect()
1798 })
1799 .collect();
1800
1801 let left_interpolants = Self::batch_fast_interpolate_with_memoization(
1803 &domain[..half],
1804 &all_left_targets,
1805 zerofier_dictionary,
1806 offset_inverse_dictionary,
1807 );
1808 let right_interpolants = Self::batch_fast_interpolate_with_memoization(
1809 &domain[half..],
1810 &all_right_targets,
1811 zerofier_dictionary,
1812 offset_inverse_dictionary,
1813 );
1814
1815 left_interpolants
1817 .par_iter()
1818 .zip(right_interpolants.par_iter())
1819 .map(|(left_interpolant, right_interpolant)| {
1820 let left_term = left_interpolant.multiply(&right_zerofier);
1821 let right_term = right_interpolant.multiply(&left_zerofier);
1822
1823 left_term + right_term
1824 })
1825 .collect()
1826 }
1827
1828 pub fn batch_evaluate(&self, domain: &[FF]) -> Vec<FF> {
1830 if self.is_zero() {
1831 vec![FF::ZERO; domain.len()]
1832 } else if self.degree()
1833 >= Self::REDUCE_BEFORE_EVALUATE_THRESHOLD_RATIO * (domain.len() as isize)
1834 {
1835 self.reduce_then_batch_evaluate(domain)
1836 } else {
1837 let zerofier_tree = ZerofierTree::new_from_domain(domain);
1838 self.divide_and_conquer_batch_evaluate(&zerofier_tree)
1839 }
1840 }
1841
1842 fn reduce_then_batch_evaluate(&self, domain: &[FF]) -> Vec<FF> {
1843 let zerofier_tree = ZerofierTree::new_from_domain(domain);
1844 let zerofier = zerofier_tree.zerofier();
1845 let remainder = self.fast_reduce(&zerofier);
1846 remainder.divide_and_conquer_batch_evaluate(&zerofier_tree)
1847 }
1848
1849 pub fn par_batch_evaluate(&self, domain: &[FF]) -> Vec<FF> {
1851 if domain.is_empty() || self.is_zero() {
1852 return vec![FF::ZERO; domain.len()];
1853 }
1854 let num_threads = current_num_threads().max(1);
1855 let chunk_size = domain.len().div_ceil(num_threads);
1856 domain
1857 .par_chunks(chunk_size)
1858 .flat_map(|ch| self.batch_evaluate(ch))
1859 .collect()
1860 }
1861
1862 #[doc(hidden)]
1865 pub fn iterative_batch_evaluate(&self, domain: &[FF]) -> Vec<FF> {
1866 domain.iter().map(|&p| self.evaluate(p)).collect()
1867 }
1868
1869 #[doc(hidden)]
1871 pub fn divide_and_conquer_batch_evaluate(&self, zerofier_tree: &ZerofierTree<FF>) -> Vec<FF> {
1872 match zerofier_tree {
1873 ZerofierTree::Leaf(leaf) => self
1874 .reduce(&zerofier_tree.zerofier())
1875 .iterative_batch_evaluate(&leaf.points),
1876 ZerofierTree::Branch(branch) => [
1877 self.divide_and_conquer_batch_evaluate(&branch.left),
1878 self.divide_and_conquer_batch_evaluate(&branch.right),
1879 ]
1880 .concat(),
1881 ZerofierTree::Padding => vec![],
1882 }
1883 }
1884
1885 pub fn fast_coset_interpolate<S>(offset: S, values: &[FF]) -> Self
1897 where
1898 S: Clone + One + Inverse,
1899 FF: Mul<S, Output = FF>,
1900 {
1901 let mut mut_values = values.to_vec();
1902
1903 intt(&mut mut_values);
1904 let poly = Polynomial::new(mut_values);
1905
1906 poly.scale(offset.inverse())
1907 }
1908
1909 pub fn truncate(&self, k: usize) -> Self {
1926 let coefficients = self.coefficients.iter().copied();
1927 let coefficients = coefficients.rev().take(k + 1).rev().collect();
1928 Self::new(coefficients)
1929 }
1930
1931 pub fn mod_x_to_the_n(&self, n: usize) -> Self {
1944 let num_coefficients_to_retain = n.min(self.coefficients.len());
1945 Self::new(self.coefficients[..num_coefficients_to_retain].into())
1946 }
1947
1948 #[doc(hidden)]
1952 pub fn fast_modular_coset_interpolate_preprocess(
1953 n: usize,
1954 offset: BFieldElement,
1955 modulus: &Polynomial<FF>,
1956 ) -> ModularInterpolationPreprocessingData<'static, FF> {
1957 let omega = BFieldElement::primitive_root_of_unity(n as u64).unwrap();
1958 let modular_squares = (0..n.ilog2())
1960 .scan(Polynomial::<FF>::x_to_the(1), |acc, _| {
1961 let yld = acc.clone();
1962 *acc = acc.multiply(acc).reduce(modulus);
1963 Some(yld)
1964 })
1965 .collect_vec();
1966 let even_zerofiers = (0..n.ilog2())
1967 .map(|i| offset.inverse().mod_pow(1u64 << i))
1968 .zip(modular_squares.iter())
1969 .map(|(lc, sq)| sq.scalar_mul(FF::from(lc.value())) - Polynomial::one())
1970 .collect_vec();
1971 let odd_zerofiers = (0..n.ilog2())
1972 .map(|i| (offset * omega).inverse().mod_pow(1u64 << i))
1973 .zip(modular_squares.iter())
1974 .map(|(lc, sq)| sq.scalar_mul(FF::from(lc.value())) - Polynomial::one())
1975 .collect_vec();
1976
1977 let (shift_coefficients, tail_length) = modulus.shift_factor_ntt_with_tail_length();
1979
1980 ModularInterpolationPreprocessingData {
1981 even_zerofiers,
1982 odd_zerofiers,
1983 shift_coefficients,
1984 tail_length,
1985 }
1986 }
1987
1988 fn fast_modular_coset_interpolate(
1992 values: &[FF],
1993 offset: BFieldElement,
1994 modulus: &Polynomial<FF>,
1995 ) -> Self {
1996 let preprocessing_data =
1997 Self::fast_modular_coset_interpolate_preprocess(values.len(), offset, modulus);
1998 Self::fast_modular_coset_interpolate_with_zerofiers_and_ntt_friendly_multiple(
1999 values,
2000 offset,
2001 modulus,
2002 &preprocessing_data,
2003 )
2004 }
2005
2006 #[doc(hidden)]
2009 pub fn fast_modular_coset_interpolate_with_zerofiers_and_ntt_friendly_multiple(
2010 values: &[FF],
2011 offset: BFieldElement,
2012 modulus: &Polynomial<FF>,
2013 preprocessed: &ModularInterpolationPreprocessingData<FF>,
2014 ) -> Self {
2015 if modulus.degree() < 0 {
2016 panic!("cannot reduce modulo zero")
2017 };
2018 let n = values.len();
2019 let omega = BFieldElement::primitive_root_of_unity(n as u64).unwrap();
2020
2021 if n < Self::FAST_MODULAR_COSET_INTERPOLATE_CUTOFF_THRESHOLD_PREFER_LAGRANGE {
2022 let domain = (0..n)
2023 .scan(FF::from(offset.value()), |acc: &mut FF, _| {
2024 let yld = *acc;
2025 *acc *= omega;
2026 Some(yld)
2027 })
2028 .collect::<Vec<FF>>();
2029 let interpolant = Self::lagrange_interpolate(&domain, values);
2030 return interpolant.reduce(modulus);
2031 } else if n <= Self::FAST_MODULAR_COSET_INTERPOLATE_CUTOFF_THRESHOLD_PREFER_INTT {
2032 let mut coefficients = values.to_vec();
2033 intt(&mut coefficients);
2034 let interpolant = Polynomial::new(coefficients);
2035
2036 return interpolant
2037 .scale(FF::from(offset.inverse().value()))
2038 .reduce_by_ntt_friendly_modulus(
2039 &preprocessed.shift_coefficients,
2040 preprocessed.tail_length,
2041 )
2042 .reduce(modulus);
2043 }
2044
2045 const MINUS_TWO_INVERSE: BFieldElement = BFieldElement::MINUS_TWO_INVERSE;
2078 let even_zerofier_on_odd_domain_inverted = vec![FF::from(MINUS_TWO_INVERSE.value()); n / 2];
2079 let odd_zerofier_on_even_domain_inverted = vec![FF::from(MINUS_TWO_INVERSE.value()); n / 2];
2080
2081 let mut odd_domain_targets = even_zerofier_on_odd_domain_inverted;
2083 let mut even_domain_targets = odd_zerofier_on_even_domain_inverted;
2084 for i in 0..(n / 2) {
2085 even_domain_targets[i] *= values[2 * i];
2086 odd_domain_targets[i] *= values[2 * i + 1];
2087 }
2088
2089 let even_interpolant =
2091 Self::fast_modular_coset_interpolate(&even_domain_targets, offset, modulus);
2092 let odd_interpolant =
2093 Self::fast_modular_coset_interpolate(&odd_domain_targets, offset * omega, modulus);
2094
2095 let interpolant = even_interpolant
2097 .multiply(&preprocessed.odd_zerofiers[(n / 2).ilog2() as usize])
2098 + odd_interpolant.multiply(&preprocessed.even_zerofiers[(n / 2).ilog2() as usize]);
2099
2100 interpolant.reduce(modulus)
2102 }
2103
2104 pub fn coset_extrapolate(
2107 domain_offset: BFieldElement,
2108 codeword: &[FF],
2109 points: &[FF],
2110 ) -> Vec<FF> {
2111 if points.len() < Self::FAST_COSET_EXTRAPOLATE_THRESHOLD {
2112 Self::fast_coset_extrapolate(domain_offset, codeword, points)
2113 } else {
2114 Self::naive_coset_extrapolate(domain_offset, codeword, points)
2115 }
2116 }
2117
2118 fn naive_coset_extrapolate_preprocessing(
2119 points: &[FF],
2120 ) -> (ZerofierTree<'_, FF>, Vec<FF>, usize) {
2121 let zerofier_tree = ZerofierTree::new_from_domain(points);
2122 let (shift_coefficients, tail_length) =
2123 Self::shift_factor_ntt_with_tail_length(&zerofier_tree.zerofier());
2124 (zerofier_tree, shift_coefficients, tail_length)
2125 }
2126
2127 fn naive_coset_extrapolate(
2128 domain_offset: BFieldElement,
2129 codeword: &[FF],
2130 points: &[FF],
2131 ) -> Vec<FF> {
2132 let mut coefficients = codeword.to_vec();
2133 intt(&mut coefficients);
2134 let interpolant =
2135 Polynomial::new(coefficients).scale(FF::from(domain_offset.inverse().value()));
2136 interpolant.batch_evaluate(points)
2137 }
2138
2139 fn fast_coset_extrapolate(
2140 domain_offset: BFieldElement,
2141 codeword: &[FF],
2142 points: &[FF],
2143 ) -> Vec<FF> {
2144 let zerofier_tree = ZerofierTree::new_from_domain(points);
2145 let minimal_interpolant = Self::fast_modular_coset_interpolate(
2146 codeword,
2147 domain_offset,
2148 &zerofier_tree.zerofier(),
2149 );
2150 minimal_interpolant.divide_and_conquer_batch_evaluate(&zerofier_tree)
2151 }
2152
2153 pub fn batch_coset_extrapolate(
2178 domain_offset: BFieldElement,
2179 codeword_length: usize,
2180 codewords: &[FF],
2181 points: &[FF],
2182 ) -> Vec<FF> {
2183 if points.len() < Self::FAST_COSET_EXTRAPOLATE_THRESHOLD {
2184 Self::batch_fast_coset_extrapolate(domain_offset, codeword_length, codewords, points)
2185 } else {
2186 Self::batch_naive_coset_extrapolate(domain_offset, codeword_length, codewords, points)
2187 }
2188 }
2189
2190 fn batch_fast_coset_extrapolate(
2191 domain_offset: BFieldElement,
2192 codeword_length: usize,
2193 codewords: &[FF],
2194 points: &[FF],
2195 ) -> Vec<FF> {
2196 let n = codeword_length;
2197
2198 let zerofier_tree = ZerofierTree::new_from_domain(points);
2199 let modulus = zerofier_tree.zerofier();
2200 let preprocessing_data = Self::fast_modular_coset_interpolate_preprocess(
2201 codeword_length,
2202 domain_offset,
2203 &modulus,
2204 );
2205
2206 (0..codewords.len() / n)
2207 .flat_map(|i| {
2208 let codeword = &codewords[i * n..(i + 1) * n];
2209 let minimal_interpolant =
2210 Self::fast_modular_coset_interpolate_with_zerofiers_and_ntt_friendly_multiple(
2211 codeword,
2212 domain_offset,
2213 &modulus,
2214 &preprocessing_data,
2215 );
2216 minimal_interpolant.divide_and_conquer_batch_evaluate(&zerofier_tree)
2217 })
2218 .collect()
2219 }
2220
2221 fn batch_naive_coset_extrapolate(
2222 domain_offset: BFieldElement,
2223 codeword_length: usize,
2224 codewords: &[FF],
2225 points: &[FF],
2226 ) -> Vec<FF> {
2227 let (zerofier_tree, shift_coefficients, tail_length) =
2228 Self::naive_coset_extrapolate_preprocessing(points);
2229 let n = codeword_length;
2230
2231 (0..codewords.len() / n)
2232 .flat_map(|i| {
2233 let mut coefficients = codewords[i * n..(i + 1) * n].to_vec();
2234 intt(&mut coefficients);
2235 Polynomial::new(coefficients)
2236 .scale(FF::from(domain_offset.inverse().value()))
2237 .reduce_by_ntt_friendly_modulus(&shift_coefficients, tail_length)
2238 .divide_and_conquer_batch_evaluate(&zerofier_tree)
2239 })
2240 .collect()
2241 }
2242
2243 pub fn par_batch_coset_extrapolate(
2245 domain_offset: BFieldElement,
2246 codeword_length: usize,
2247 codewords: &[FF],
2248 points: &[FF],
2249 ) -> Vec<FF> {
2250 if points.len() < Self::FAST_COSET_EXTRAPOLATE_THRESHOLD {
2251 Self::par_batch_fast_coset_extrapolate(
2252 domain_offset,
2253 codeword_length,
2254 codewords,
2255 points,
2256 )
2257 } else {
2258 Self::par_batch_naive_coset_extrapolate(
2259 domain_offset,
2260 codeword_length,
2261 codewords,
2262 points,
2263 )
2264 }
2265 }
2266
2267 fn par_batch_fast_coset_extrapolate(
2268 domain_offset: BFieldElement,
2269 codeword_length: usize,
2270 codewords: &[FF],
2271 points: &[FF],
2272 ) -> Vec<FF> {
2273 let n = codeword_length;
2274
2275 let zerofier_tree = ZerofierTree::new_from_domain(points);
2276 let modulus = zerofier_tree.zerofier();
2277 let preprocessing_data = Self::fast_modular_coset_interpolate_preprocess(
2278 codeword_length,
2279 domain_offset,
2280 &modulus,
2281 );
2282
2283 (0..codewords.len() / n)
2284 .into_par_iter()
2285 .flat_map(|i| {
2286 let codeword = &codewords[i * n..(i + 1) * n];
2287 let minimal_interpolant =
2288 Self::fast_modular_coset_interpolate_with_zerofiers_and_ntt_friendly_multiple(
2289 codeword,
2290 domain_offset,
2291 &modulus,
2292 &preprocessing_data,
2293 );
2294 minimal_interpolant.divide_and_conquer_batch_evaluate(&zerofier_tree)
2295 })
2296 .collect()
2297 }
2298
2299 fn par_batch_naive_coset_extrapolate(
2300 domain_offset: BFieldElement,
2301 codeword_length: usize,
2302 codewords: &[FF],
2303 points: &[FF],
2304 ) -> Vec<FF> {
2305 let (zerofier_tree, shift_coefficients, tail_length) =
2306 Self::naive_coset_extrapolate_preprocessing(points);
2307 let n = codeword_length;
2308
2309 (0..codewords.len() / n)
2310 .into_par_iter()
2311 .flat_map(|i| {
2312 let mut coefficients = codewords[i * n..(i + 1) * n].to_vec();
2313 intt(&mut coefficients);
2314 Polynomial::new(coefficients)
2315 .scale(FF::from(domain_offset.inverse().value()))
2316 .reduce_by_ntt_friendly_modulus(&shift_coefficients, tail_length)
2317 .divide_and_conquer_batch_evaluate(&zerofier_tree)
2318 })
2319 .collect()
2320 }
2321}
2322
2323impl Polynomial<'_, BFieldElement> {
2324 const CLEAN_DIVIDE_CUTOFF_THRESHOLD: isize = { if cfg!(test) { 0 } else { 1 << 9 } };
2330
2331 #[must_use]
2346 #[expect(clippy::shadow_unrelated)]
2347 pub fn clean_divide(self, divisor: Self) -> Polynomial<'static, BFieldElement> {
2348 let dividend = self;
2349 if divisor.degree() < Self::CLEAN_DIVIDE_CUTOFF_THRESHOLD {
2350 let (quotient, remainder) = dividend.divide(&divisor);
2351 debug_assert!(remainder.is_zero());
2352 return quotient;
2353 }
2354
2355 let mut dividend_coefficients = dividend.coefficients.into_owned();
2359 let mut divisor_coefficients = divisor.coefficients.into_owned();
2360 if divisor_coefficients.first().is_some_and(Zero::is_zero) {
2361 assert!(dividend_coefficients[0].is_zero());
2363 dividend_coefficients.remove(0);
2364 divisor_coefficients.remove(0);
2365 }
2366 let dividend = Polynomial::new(dividend_coefficients);
2367 let divisor = Polynomial::new(divisor_coefficients);
2368
2369 let offset = XFieldElement::from([0, 1, 0]);
2372 let mut dividend_coefficients = dividend.scale(offset).coefficients.into_owned();
2373 let mut divisor_coefficients = divisor.scale(offset).coefficients.into_owned();
2374
2375 let dividend_deg_plus_1 = usize::try_from(dividend.degree() + 1).unwrap();
2377 let order = dividend_deg_plus_1.next_power_of_two();
2378
2379 dividend_coefficients.resize(order, XFieldElement::ZERO);
2380 divisor_coefficients.resize(order, XFieldElement::ZERO);
2381
2382 ntt(&mut dividend_coefficients);
2383 ntt(&mut divisor_coefficients);
2384
2385 let divisor_inverses = XFieldElement::batch_inversion(divisor_coefficients);
2386 let mut quotient_codeword = dividend_coefficients
2387 .into_iter()
2388 .zip(divisor_inverses)
2389 .map(|(l, r)| l * r)
2390 .collect_vec();
2391
2392 intt(&mut quotient_codeword);
2393 let quotient = Polynomial::new(quotient_codeword);
2394
2395 let Cow::Owned(coeffs) = quotient.scale(offset.inverse()).coefficients else {
2398 unreachable!();
2399 };
2400
2401 Polynomial::new(coeffs.into_iter().map(|c| c.unlift().unwrap()).collect())
2402 }
2403}
2404
2405impl<const N: usize, FF, E> From<[E; N]> for Polynomial<'static, FF>
2406where
2407 FF: FiniteField,
2408 E: Into<FF>,
2409{
2410 fn from(coefficients: [E; N]) -> Self {
2411 Self::new(coefficients.into_iter().map(|x| x.into()).collect())
2412 }
2413}
2414
2415impl<'c, FF> From<&'c [FF]> for Polynomial<'c, FF>
2416where
2417 FF: FiniteField,
2418{
2419 fn from(coefficients: &'c [FF]) -> Self {
2420 Self::new_borrowed(coefficients)
2421 }
2422}
2423
2424impl<FF, E> From<Vec<E>> for Polynomial<'static, FF>
2425where
2426 FF: FiniteField,
2427 E: Into<FF>,
2428{
2429 fn from(coefficients: Vec<E>) -> Self {
2430 Self::new(coefficients.into_iter().map(|c| c.into()).collect())
2431 }
2432}
2433
2434impl From<XFieldElement> for Polynomial<'static, BFieldElement> {
2435 fn from(xfe: XFieldElement) -> Self {
2436 Self::new(xfe.coefficients.to_vec())
2437 }
2438}
2439
2440impl<FF> Polynomial<'static, FF>
2441where
2442 FF: FiniteField,
2443{
2444 pub fn new(coefficients: Vec<FF>) -> Self {
2450 let coefficients = Cow::Owned(coefficients);
2451 Self { coefficients }
2452 }
2453
2454 pub fn x_to_the(n: usize) -> Self {
2457 let mut coefficients = vec![FF::ZERO; n + 1];
2458 coefficients[n] = FF::ONE;
2459 Self::new(coefficients)
2460 }
2461
2462 pub fn from_constant(constant: FF) -> Self {
2466 Self::new(vec![constant])
2467 }
2468
2469 #[doc(hidden)]
2471 pub fn naive_zerofier(domain: &[FF]) -> Self {
2472 domain
2473 .iter()
2474 .map(|&r| Self::new(vec![-r, FF::ONE]))
2475 .reduce(|accumulator, linear_poly| accumulator * linear_poly)
2476 .unwrap_or_else(Self::one)
2477 }
2478}
2479
2480impl<'coeffs, FF> Polynomial<'coeffs, FF>
2481where
2482 FF: FiniteField,
2483{
2484 pub fn new_borrowed(coefficients: &'coeffs [FF]) -> Self {
2486 let coefficients = Cow::Borrowed(coefficients);
2487 Self { coefficients }
2488 }
2489}
2490
2491impl<FF> Div<Polynomial<'_, FF>> for Polynomial<'_, FF>
2492where
2493 FF: FiniteField + 'static,
2494{
2495 type Output = Polynomial<'static, FF>;
2496
2497 fn div(self, other: Polynomial<'_, FF>) -> Self::Output {
2498 let (quotient, _) = self.naive_divide(&other);
2499 quotient
2500 }
2501}
2502
2503impl<FF> Rem<Polynomial<'_, FF>> for Polynomial<'_, FF>
2504where
2505 FF: FiniteField + 'static,
2506{
2507 type Output = Polynomial<'static, FF>;
2508
2509 fn rem(self, other: Polynomial<'_, FF>) -> Self::Output {
2510 let (_, remainder) = self.naive_divide(&other);
2511 remainder
2512 }
2513}
2514
2515impl<FF> Add<Polynomial<'_, FF>> for Polynomial<'_, FF>
2516where
2517 FF: FiniteField + 'static,
2518{
2519 type Output = Polynomial<'static, FF>;
2520
2521 fn add(self, other: Polynomial<'_, FF>) -> Self::Output {
2522 let summed = self
2523 .coefficients
2524 .iter()
2525 .zip_longest(other.coefficients.iter())
2526 .map(|a| match a {
2527 EitherOrBoth::Both(&l, &r) => l + r,
2528 EitherOrBoth::Left(&c) | EitherOrBoth::Right(&c) => c,
2529 })
2530 .collect();
2531
2532 Polynomial::new(summed)
2533 }
2534}
2535
2536impl<FF: FiniteField> AddAssign<Polynomial<'_, FF>> for Polynomial<'_, FF> {
2537 fn add_assign(&mut self, rhs: Polynomial<'_, FF>) {
2538 let rhs_len = rhs.coefficients.len();
2539 let self_len = self.coefficients.len();
2540 let mut self_coefficients = std::mem::take(&mut self.coefficients).into_owned();
2541
2542 for (l, &r) in self_coefficients.iter_mut().zip(rhs.coefficients.iter()) {
2543 *l += r;
2544 }
2545
2546 if rhs_len > self_len {
2547 self_coefficients.extend(&rhs.coefficients[self_len..]);
2548 }
2549
2550 self.coefficients = Cow::Owned(self_coefficients);
2551 }
2552}
2553
2554impl<FF> Sub<Polynomial<'_, FF>> for Polynomial<'_, FF>
2555where
2556 FF: FiniteField + 'static,
2557{
2558 type Output = Polynomial<'static, FF>;
2559
2560 fn sub(self, other: Polynomial<'_, FF>) -> Self::Output {
2561 let coefficients = self
2562 .coefficients
2563 .iter()
2564 .zip_longest(other.coefficients.iter())
2565 .map(|a| match a {
2566 EitherOrBoth::Both(&l, &r) => l - r,
2567 EitherOrBoth::Left(&l) => l,
2568 EitherOrBoth::Right(&r) => FF::ZERO - r,
2569 })
2570 .collect();
2571
2572 Polynomial::new(coefficients)
2573 }
2574}
2575
2576pub fn barycentric_evaluate<Ind, Coeff, Eval>(
2599 codeword: &[Coeff],
2600 indeterminate: Ind,
2601) -> <Eval as Mul<Ind>>::Output
2602where
2603 Ind: FiniteField + Mul<BFieldElement, Output = Ind> + Sub<BFieldElement, Output = Ind>,
2604 Coeff: FiniteField + Mul<Ind, Output = Eval>,
2605 Eval: FiniteField + Mul<Ind>,
2606{
2607 let root_order = codeword.len().try_into().unwrap();
2608 let generator = BFieldElement::primitive_root_of_unity(root_order).unwrap();
2609 let domain_iter = (0..root_order).scan(BFieldElement::ONE, |acc, _| {
2610 let to_yield = Some(*acc);
2611 *acc *= generator;
2612 to_yield
2613 });
2614
2615 let domain_shift = domain_iter.clone().map(|d| indeterminate - d).collect();
2616 let domain_shift_inverses = Ind::batch_inversion(domain_shift);
2617 let domain_over_domain_shift = domain_iter
2618 .zip(domain_shift_inverses)
2619 .map(|(d, inv)| inv * d);
2620 let denominator = domain_over_domain_shift.clone().fold(Ind::ZERO, Ind::add);
2621 let numerator = domain_over_domain_shift
2622 .zip(codeword)
2623 .map(|(dsi, &abscis)| abscis * dsi)
2624 .fold(Eval::ZERO, Eval::add);
2625
2626 numerator * denominator.inverse()
2627}
2628
2629impl<FF, FF2> Mul<Polynomial<'_, FF>> for BFieldElement
2640where
2641 FF: FiniteField + Mul<BFieldElement, Output = FF2>,
2642 FF2: 'static + FiniteField,
2643{
2644 type Output = Polynomial<'static, FF2>;
2645
2646 fn mul(self, other: Polynomial<FF>) -> Self::Output {
2647 other.scalar_mul(self)
2648 }
2649}
2650
2651impl<FF, FF2> Mul<Polynomial<'_, FF>> for XFieldElement
2652where
2653 FF: FiniteField + Mul<XFieldElement, Output = FF2>,
2654 FF2: 'static + FiniteField,
2655{
2656 type Output = Polynomial<'static, FF2>;
2657
2658 fn mul(self, other: Polynomial<FF>) -> Self::Output {
2659 other.scalar_mul(self)
2660 }
2661}
2662
2663impl<S, FF, FF2> Mul<S> for Polynomial<'_, FF>
2664where
2665 S: FiniteField,
2666 FF: FiniteField + Mul<S, Output = FF2>,
2667 FF2: 'static + FiniteField,
2668{
2669 type Output = Polynomial<'static, FF2>;
2670
2671 fn mul(self, other: S) -> Self::Output {
2672 self.scalar_mul(other)
2673 }
2674}
2675
2676impl<FF, FF2> Mul<Polynomial<'_, FF2>> for Polynomial<'_, FF>
2677where
2678 FF: FiniteField + Mul<FF2>,
2679 FF2: FiniteField,
2680 <FF as Mul<FF2>>::Output: 'static + FiniteField,
2681{
2682 type Output = Polynomial<'static, <FF as Mul<FF2>>::Output>;
2683
2684 fn mul(self, other: Polynomial<'_, FF2>) -> Polynomial<'static, <FF as Mul<FF2>>::Output> {
2685 self.naive_multiply(&other)
2686 }
2687}
2688
2689impl<FF> Neg for Polynomial<'_, FF>
2690where
2691 FF: FiniteField + 'static,
2692{
2693 type Output = Polynomial<'static, FF>;
2694
2695 fn neg(mut self) -> Self::Output {
2696 self.scalar_mul_mut(-FF::ONE);
2697
2698 self.into_owned()
2700 }
2701}
2702
2703#[cfg(test)]
2704#[cfg_attr(coverage_nightly, coverage(off))]
2705mod tests {
2706 use num_traits::ConstZero;
2707 use proptest::collection::size_range;
2708 use proptest::collection::vec;
2709 use proptest::prelude::*;
2710 use proptest_arbitrary_interop::arb;
2711 use test_strategy::proptest;
2712
2713 use super::*;
2714 use crate::prelude::*;
2715
2716 type BfePoly = Polynomial<'static, BFieldElement>;
2718
2719 type XfePoly = Polynomial<'static, XFieldElement>;
2721
2722 impl proptest::arbitrary::Arbitrary for BfePoly {
2723 type Parameters = ();
2724
2725 fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {
2726 arb().boxed()
2727 }
2728
2729 type Strategy = BoxedStrategy<Self>;
2730 }
2731
2732 impl proptest::arbitrary::Arbitrary for XfePoly {
2733 type Parameters = ();
2734
2735 fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {
2736 arb().boxed()
2737 }
2738
2739 type Strategy = BoxedStrategy<Self>;
2740 }
2741
2742 #[test]
2743 fn polynomial_can_be_debug_printed() {
2744 let polynomial = Polynomial::new(bfe_vec![1, 2, 3]);
2745 println!("{polynomial:?}");
2746 }
2747
2748 #[proptest]
2749 fn unequal_hash_implies_unequal_polynomials(poly_0: BfePoly, poly_1: BfePoly) {
2750 let hash = |poly: &Polynomial<_>| {
2751 let mut hasher = std::hash::DefaultHasher::new();
2752 poly.hash(&mut hasher);
2753 std::hash::Hasher::finish(&hasher)
2754 };
2755
2756 if hash(&poly_0) != hash(&poly_1) {
2762 prop_assert_ne!(poly_0, poly_1);
2763 }
2764 }
2765
2766 #[test]
2767 fn polynomial_display_test() {
2768 fn polynomial<const N: usize>(coeffs: [u64; N]) -> BfePoly {
2769 Polynomial::new(coeffs.map(BFieldElement::new).to_vec())
2770 }
2771
2772 assert_eq!("0", polynomial([]).to_string());
2773 assert_eq!("0", polynomial([0]).to_string());
2774 assert_eq!("0", polynomial([0, 0]).to_string());
2775
2776 assert_eq!("1", polynomial([1]).to_string());
2777 assert_eq!("2", polynomial([2, 0]).to_string());
2778 assert_eq!("3", polynomial([3, 0, 0]).to_string());
2779
2780 assert_eq!("x", polynomial([0, 1]).to_string());
2781 assert_eq!("2x", polynomial([0, 2]).to_string());
2782 assert_eq!("3x", polynomial([0, 3]).to_string());
2783
2784 assert_eq!("5x + 2", polynomial([2, 5]).to_string());
2785 assert_eq!("9x + 7", polynomial([7, 9, 0, 0, 0]).to_string());
2786
2787 assert_eq!("4x^4 + 3x^3", polynomial([0, 0, 0, 3, 4]).to_string());
2788 assert_eq!("2x^4 + 1", polynomial([1, 0, 0, 0, 2]).to_string());
2789 }
2790
2791 #[proptest]
2792 fn leading_coefficient_of_zero_polynomial_is_none(#[strategy(0usize..30)] num_zeros: usize) {
2793 let coefficients = vec![BFieldElement::ZERO; num_zeros];
2794 let polynomial = Polynomial::new(coefficients);
2795 prop_assert!(polynomial.leading_coefficient().is_none());
2796 }
2797
2798 #[proptest]
2799 fn leading_coefficient_of_non_zero_polynomial_is_some(
2800 polynomial: BfePoly,
2801 leading_coefficient: BFieldElement,
2802 #[strategy(0usize..30)] num_leading_zeros: usize,
2803 ) {
2804 let mut coefficients = polynomial.coefficients.into_owned();
2805 coefficients.push(leading_coefficient);
2806 coefficients.extend(vec![BFieldElement::ZERO; num_leading_zeros]);
2807 let polynomial_with_leading_zeros = Polynomial::new(coefficients);
2808 prop_assert_eq!(
2809 leading_coefficient,
2810 polynomial_with_leading_zeros.leading_coefficient().unwrap()
2811 );
2812 }
2813
2814 #[test]
2815 fn normalizing_canonical_zero_polynomial_has_no_effect() {
2816 let mut zero_polynomial = Polynomial::<BFieldElement>::zero();
2817 zero_polynomial.normalize();
2818 assert_eq!(Polynomial::zero(), zero_polynomial);
2819 }
2820
2821 #[proptest]
2822 fn spurious_leading_zeros_dont_affect_equality(
2823 polynomial: BfePoly,
2824 #[strategy(0usize..30)] num_leading_zeros: usize,
2825 ) {
2826 let mut coefficients = polynomial.clone().coefficients.into_owned();
2827 coefficients.extend(vec![BFieldElement::ZERO; num_leading_zeros]);
2828 let polynomial_with_leading_zeros = Polynomial::new(coefficients);
2829
2830 prop_assert_eq!(polynomial, polynomial_with_leading_zeros);
2831 }
2832
2833 #[proptest]
2834 fn normalizing_removes_spurious_leading_zeros(
2835 polynomial: BfePoly,
2836 #[filter(!#leading_coefficient.is_zero())] leading_coefficient: BFieldElement,
2837 #[strategy(0usize..30)] num_leading_zeros: usize,
2838 ) {
2839 let mut coefficients = polynomial.clone().coefficients.into_owned();
2840 coefficients.push(leading_coefficient);
2841 coefficients.extend(vec![BFieldElement::ZERO; num_leading_zeros]);
2842 let mut polynomial_with_leading_zeros = Polynomial::new(coefficients);
2843 polynomial_with_leading_zeros.normalize();
2844
2845 let num_inserted_coefficients = 1;
2846 let expected_num_coefficients = polynomial.coefficients.len() + num_inserted_coefficients;
2847 let num_coefficients = polynomial_with_leading_zeros.coefficients.len();
2848
2849 prop_assert_eq!(expected_num_coefficients, num_coefficients);
2850 }
2851
2852 #[test]
2853 fn accessing_coefficients_of_empty_polynomial_gives_empty_slice() {
2854 let poly = BfePoly::new(vec![]);
2855 assert!(poly.coefficients().is_empty());
2856 assert!(poly.into_coefficients().is_empty());
2857 }
2858
2859 #[proptest]
2860 fn accessing_coefficients_of_polynomial_with_only_zero_coefficients_gives_empty_slice(
2861 #[strategy(0_usize..30)] num_zeros: usize,
2862 ) {
2863 let poly = Polynomial::new(vec![BFieldElement::ZERO; num_zeros]);
2864 prop_assert!(poly.coefficients().is_empty());
2865 prop_assert!(poly.into_coefficients().is_empty());
2866 }
2867
2868 #[proptest]
2869 fn accessing_the_coefficients_is_equivalent_to_normalizing_then_raw_access(
2870 mut coefficients: Vec<BFieldElement>,
2871 #[strategy(0_usize..30)] num_leading_zeros: usize,
2872 ) {
2873 coefficients.extend(vec![BFieldElement::ZERO; num_leading_zeros]);
2874 let mut polynomial = Polynomial::new(coefficients);
2875
2876 let accessed_coefficients_borrow = polynomial.coefficients().to_vec();
2877 let accessed_coefficients_owned = polynomial.clone().into_coefficients();
2878
2879 polynomial.normalize();
2880 let raw_coefficients = polynomial.coefficients.into_owned();
2881
2882 prop_assert_eq!(&raw_coefficients, &accessed_coefficients_borrow);
2883 prop_assert_eq!(&raw_coefficients, &accessed_coefficients_owned);
2884 }
2885
2886 #[test]
2887 fn x_to_the_0_is_constant_1() {
2888 assert!(Polynomial::<BFieldElement>::x_to_the(0).is_one());
2889 assert!(Polynomial::<XFieldElement>::x_to_the(0).is_one());
2890 }
2891
2892 #[test]
2893 fn x_to_the_1_is_x() {
2894 assert!(Polynomial::<BFieldElement>::x_to_the(1).is_x());
2895 assert!(Polynomial::<XFieldElement>::x_to_the(1).is_x());
2896 }
2897
2898 #[proptest]
2899 fn x_to_the_n_to_the_m_is_homomorphic(
2900 #[strategy(0_usize..50)] n: usize,
2901 #[strategy(0_usize..50)] m: usize,
2902 ) {
2903 let to_the_n_times_m = Polynomial::<BFieldElement>::x_to_the(n * m);
2904 let to_the_n_then_to_the_m = Polynomial::x_to_the(n).pow(m as u32);
2905 prop_assert_eq!(to_the_n_times_m, to_the_n_then_to_the_m);
2906 }
2907
2908 #[test]
2909 fn scaling_a_polynomial_works_with_different_fields_as_the_offset() {
2910 let bfe_poly = Polynomial::new(bfe_vec![0, 1, 2]);
2911 let _ = bfe_poly.scale(bfe!(42));
2912 let _ = bfe_poly.scale(xfe!(42));
2913
2914 let xfe_poly = Polynomial::new(xfe_vec![0, 1, 2]);
2915 let _ = xfe_poly.scale(bfe!(42));
2916 let _ = xfe_poly.scale(xfe!(42));
2917 }
2918
2919 #[proptest]
2920 fn polynomial_scaling_is_equivalent_in_extension_field(
2921 bfe_polynomial: BfePoly,
2922 alpha: BFieldElement,
2923 ) {
2924 let bfe_coefficients = bfe_polynomial.coefficients.iter();
2925 let xfe_coefficients = bfe_coefficients.map(|bfe| bfe.lift()).collect();
2926 let xfe_polynomial = Polynomial::<XFieldElement>::new(xfe_coefficients);
2927
2928 let xfe_poly_bfe_scalar = xfe_polynomial.scale(alpha);
2929 let bfe_poly_xfe_scalar = bfe_polynomial.scale(alpha.lift());
2930 prop_assert_eq!(xfe_poly_bfe_scalar, bfe_poly_xfe_scalar);
2931 }
2932
2933 #[proptest]
2934 fn evaluating_scaled_polynomial_is_equivalent_to_evaluating_original_in_offset_point(
2935 polynomial: BfePoly,
2936 alpha: BFieldElement,
2937 x: BFieldElement,
2938 ) {
2939 let scaled_polynomial = polynomial.scale(alpha);
2940 prop_assert_eq!(
2941 polynomial.evaluate_in_same_field(alpha * x),
2942 scaled_polynomial.evaluate_in_same_field(x)
2943 );
2944 }
2945
2946 #[proptest]
2947 fn polynomial_multiplication_with_scalar_is_equivalent_for_the_two_methods(
2948 mut polynomial: BfePoly,
2949 scalar: BFieldElement,
2950 ) {
2951 let new_polynomial = polynomial.scalar_mul(scalar);
2952 polynomial.scalar_mul_mut(scalar);
2953 prop_assert_eq!(polynomial, new_polynomial);
2954 }
2955
2956 #[proptest]
2957 fn polynomial_multiplication_with_scalar_is_equivalent_for_all_mul_traits(
2958 polynomial: BfePoly,
2959 scalar: BFieldElement,
2960 ) {
2961 let bfe_rhs = polynomial.clone() * scalar;
2962 let xfe_rhs = polynomial.clone() * scalar.lift();
2963 let bfe_lhs = scalar * polynomial.clone();
2964 let xfe_lhs = scalar.lift() * polynomial;
2965
2966 prop_assert_eq!(bfe_lhs.clone(), bfe_rhs);
2967 prop_assert_eq!(xfe_lhs.clone(), xfe_rhs);
2968
2969 prop_assert_eq!(bfe_lhs * XFieldElement::ONE, xfe_lhs);
2970 }
2971
2972 #[test]
2973 fn polynomial_multiplication_with_scalar_works_for_various_types() {
2974 let bfe_poly = Polynomial::new(bfe_vec![0, 1, 2]);
2975 let _: Polynomial<BFieldElement> = bfe_poly.scalar_mul(bfe!(42));
2976 let _: Polynomial<XFieldElement> = bfe_poly.scalar_mul(xfe!(42));
2977
2978 let xfe_poly = Polynomial::new(xfe_vec![0, 1, 2]);
2979 let _: Polynomial<XFieldElement> = xfe_poly.scalar_mul(bfe!(42));
2980 let _: Polynomial<XFieldElement> = xfe_poly.scalar_mul(xfe!(42));
2981
2982 let mut bfe_poly = bfe_poly;
2983 bfe_poly.scalar_mul_mut(bfe!(42));
2984
2985 let mut xfe_poly = xfe_poly;
2986 xfe_poly.scalar_mul_mut(bfe!(42));
2987 xfe_poly.scalar_mul_mut(xfe!(42));
2988 }
2989
2990 #[proptest]
2991 fn slow_lagrange_interpolation(
2992 polynomial: BfePoly,
2993 #[strategy(Just(#polynomial.coefficients.len().max(1)))] _min_points: usize,
2994 #[any(size_range(#_min_points..8 * #_min_points).lift())] points: Vec<BFieldElement>,
2995 ) {
2996 let evaluations = points
2997 .into_iter()
2998 .map(|x| (x, polynomial.evaluate(x)))
2999 .collect_vec();
3000 let interpolation_polynomial = Polynomial::lagrange_interpolate_zipped(&evaluations);
3001 prop_assert_eq!(polynomial, interpolation_polynomial);
3002 }
3003
3004 #[proptest]
3005 fn three_colinear_points_are_colinear(
3006 p0: (BFieldElement, BFieldElement),
3007 #[filter(#p0.0 != #p1.0)] p1: (BFieldElement, BFieldElement),
3008 #[filter(#p0.0 != #p2_x && #p1.0 != #p2_x)] p2_x: BFieldElement,
3009 ) {
3010 let line = Polynomial::lagrange_interpolate_zipped(&[p0, p1]);
3011 let p2 = (p2_x, line.evaluate(p2_x));
3012 prop_assert!(Polynomial::are_colinear(&[p0, p1, p2]));
3013 }
3014
3015 #[proptest]
3016 fn three_non_colinear_points_are_not_colinear(
3017 p0: (BFieldElement, BFieldElement),
3018 #[filter(#p0.0 != #p1.0)] p1: (BFieldElement, BFieldElement),
3019 #[filter(#p0.0 != #p2_x && #p1.0 != #p2_x)] p2_x: BFieldElement,
3020 #[filter(!#disturbance.is_zero())] disturbance: BFieldElement,
3021 ) {
3022 let line = Polynomial::lagrange_interpolate_zipped(&[p0, p1]);
3023 let p2 = (p2_x, line.evaluate_in_same_field(p2_x) + disturbance);
3024 prop_assert!(!Polynomial::are_colinear(&[p0, p1, p2]));
3025 }
3026
3027 #[proptest]
3028 fn colinearity_check_needs_at_least_three_points(
3029 p0: (BFieldElement, BFieldElement),
3030 #[filter(#p0.0 != #p1.0)] p1: (BFieldElement, BFieldElement),
3031 ) {
3032 prop_assert!(!Polynomial::<BFieldElement>::are_colinear(&[]));
3033 prop_assert!(!Polynomial::are_colinear(&[p0]));
3034 prop_assert!(!Polynomial::are_colinear(&[p0, p1]));
3035 }
3036
3037 #[proptest]
3038 fn colinearity_check_with_repeated_points_fails(
3039 p0: (BFieldElement, BFieldElement),
3040 #[filter(#p0.0 != #p1.0)] p1: (BFieldElement, BFieldElement),
3041 ) {
3042 prop_assert!(!Polynomial::are_colinear(&[p0, p1, p1]));
3043 }
3044
3045 #[proptest]
3046 fn colinear_points_are_colinear(
3047 p0: (BFieldElement, BFieldElement),
3048 #[filter(#p0.0 != #p1.0)] p1: (BFieldElement, BFieldElement),
3049 #[filter(!#additional_points_xs.contains(&#p0.0))]
3050 #[filter(!#additional_points_xs.contains(&#p1.0))]
3051 #[filter(#additional_points_xs.iter().all_unique())]
3052 #[any(size_range(1..100).lift())]
3053 additional_points_xs: Vec<BFieldElement>,
3054 ) {
3055 let line = Polynomial::lagrange_interpolate_zipped(&[p0, p1]);
3056 let additional_points = additional_points_xs
3057 .into_iter()
3058 .map(|x| (x, line.evaluate(x)))
3059 .collect_vec();
3060 let all_points = [p0, p1].into_iter().chain(additional_points).collect_vec();
3061 prop_assert!(Polynomial::are_colinear(&all_points));
3062 }
3063
3064 #[test]
3065 #[should_panic(expected = "Line must not be parallel to y-axis")]
3066 fn getting_point_on_invalid_line_fails() {
3067 let one = BFieldElement::ONE;
3068 let two = one + one;
3069 let three = two + one;
3070 Polynomial::<BFieldElement>::get_colinear_y((one, one), (one, three), two);
3071 }
3072
3073 #[proptest]
3074 fn point_on_line_and_colinear_point_are_identical(
3075 p0: (BFieldElement, BFieldElement),
3076 #[filter(#p0.0 != #p1.0)] p1: (BFieldElement, BFieldElement),
3077 x: BFieldElement,
3078 ) {
3079 let line = Polynomial::lagrange_interpolate_zipped(&[p0, p1]);
3080 let y = line.evaluate_in_same_field(x);
3081 let y_from_get_point_on_line = Polynomial::get_colinear_y(p0, p1, x);
3082 prop_assert_eq!(y, y_from_get_point_on_line);
3083 }
3084
3085 #[proptest]
3086 fn point_on_line_and_colinear_point_are_identical_in_extension_field(
3087 p0: (XFieldElement, XFieldElement),
3088 #[filter(#p0.0 != #p1.0)] p1: (XFieldElement, XFieldElement),
3089 x: XFieldElement,
3090 ) {
3091 let line = Polynomial::lagrange_interpolate_zipped(&[p0, p1]);
3092 let y = line.evaluate_in_same_field(x);
3093 let y_from_get_point_on_line = Polynomial::get_colinear_y(p0, p1, x);
3094 prop_assert_eq!(y, y_from_get_point_on_line);
3095 }
3096
3097 #[proptest]
3098 fn shifting_polynomial_coefficients_by_zero_is_the_same_as_not_shifting_it(poly: BfePoly) {
3099 prop_assert_eq!(poly.clone(), poly.shift_coefficients(0));
3100 }
3101
3102 #[proptest]
3103 fn shifting_polynomial_one_is_equivalent_to_raising_polynomial_x_to_the_power_of_the_shift(
3104 #[strategy(0usize..30)] shift: usize,
3105 ) {
3106 let shifted_one = Polynomial::one().shift_coefficients(shift);
3107 let x_to_the_shift = Polynomial::<BFieldElement>::from([0, 1]).pow(shift as u32);
3108 prop_assert_eq!(shifted_one, x_to_the_shift);
3109 }
3110
3111 #[test]
3112 fn polynomial_shift_test() {
3113 fn polynomial<const N: usize>(coeffs: [u64; N]) -> BfePoly {
3114 Polynomial::new(coeffs.map(BFieldElement::new).to_vec())
3115 }
3116
3117 assert_eq!(
3118 polynomial([17, 14]),
3119 polynomial([17, 14]).shift_coefficients(0)
3120 );
3121 assert_eq!(
3122 polynomial([0, 17, 14]),
3123 polynomial([17, 14]).shift_coefficients(1)
3124 );
3125 assert_eq!(
3126 polynomial([0, 0, 0, 0, 17, 14]),
3127 polynomial([17, 14]).shift_coefficients(4)
3128 );
3129
3130 let poly = polynomial([17, 14]);
3131 let poly_shift_0 = poly.clone().shift_coefficients(0);
3132 assert_eq!(polynomial([17, 14]), poly_shift_0);
3133
3134 let poly_shift_1 = poly.clone().shift_coefficients(1);
3135 assert_eq!(polynomial([0, 17, 14]), poly_shift_1);
3136
3137 let poly_shift_4 = poly.clone().shift_coefficients(4);
3138 assert_eq!(polynomial([0, 0, 0, 0, 17, 14]), poly_shift_4);
3139 }
3140
3141 #[proptest]
3142 fn shifting_a_polynomial_means_prepending_zeros_to_its_coefficients(
3143 poly: BfePoly,
3144 #[strategy(0usize..30)] shift: usize,
3145 ) {
3146 let shifted_poly = poly.clone().shift_coefficients(shift);
3147 let mut expected_coefficients = vec![BFieldElement::ZERO; shift];
3148 expected_coefficients.extend(poly.coefficients.to_vec());
3149 prop_assert_eq!(expected_coefficients, shifted_poly.coefficients.to_vec());
3150 }
3151
3152 #[proptest]
3153 fn any_polynomial_to_the_power_of_zero_is_one(poly: BfePoly) {
3154 let poly_to_the_zero = poly.pow(0);
3155 prop_assert_eq!(Polynomial::one(), poly_to_the_zero);
3156 }
3157
3158 #[proptest]
3159 fn any_polynomial_to_the_power_one_is_itself(poly: BfePoly) {
3160 let poly_to_the_one = poly.pow(1);
3161 prop_assert_eq!(poly, poly_to_the_one);
3162 }
3163
3164 #[proptest]
3165 fn polynomial_one_to_any_power_is_one(#[strategy(0u32..30)] exponent: u32) {
3166 let one_to_the_exponent = Polynomial::<BFieldElement>::one().pow(exponent);
3167 prop_assert_eq!(Polynomial::one(), one_to_the_exponent);
3168 }
3169
3170 #[test]
3171 fn pow_test() {
3172 fn polynomial<const N: usize>(coeffs: [u64; N]) -> BfePoly {
3173 Polynomial::new(coeffs.map(BFieldElement::new).to_vec())
3174 }
3175
3176 let pol = polynomial([0, 14, 0, 4, 0, 8, 0, 3]);
3177 let pol_squared = polynomial([0, 0, 196, 0, 112, 0, 240, 0, 148, 0, 88, 0, 48, 0, 9]);
3178 let pol_cubed = polynomial([
3179 0, 0, 0, 2744, 0, 2352, 0, 5376, 0, 4516, 0, 4080, 0, 2928, 0, 1466, 0, 684, 0, 216, 0,
3180 27,
3181 ]);
3182
3183 assert_eq!(pol_squared, pol.pow(2));
3184 assert_eq!(pol_cubed, pol.pow(3));
3185
3186 let parabola = polynomial([5, 41, 19]);
3187 let parabola_squared = polynomial([25, 410, 1871, 1558, 361]);
3188 assert_eq!(parabola_squared, parabola.pow(2));
3189 }
3190
3191 #[proptest]
3192 fn pow_arbitrary_test(poly: BfePoly, #[strategy(0u32..15)] exponent: u32) {
3193 let actual = poly.pow(exponent);
3194 let fast_actual = poly.fast_pow(exponent);
3195 let mut expected = Polynomial::one();
3196 for _ in 0..exponent {
3197 expected = expected.clone() * poly.clone();
3198 }
3199
3200 prop_assert_eq!(expected.clone(), actual);
3201 prop_assert_eq!(expected, fast_actual);
3202 }
3203
3204 #[proptest]
3205 fn polynomial_zero_is_neutral_element_for_addition(a: BfePoly) {
3206 prop_assert_eq!(a.clone() + Polynomial::zero(), a.clone());
3207 prop_assert_eq!(Polynomial::zero() + a.clone(), a);
3208 }
3209
3210 #[proptest]
3211 fn polynomial_one_is_neutral_element_for_multiplication(a: BfePoly) {
3212 prop_assert_eq!(a.clone() * Polynomial::<BFieldElement>::one(), a.clone());
3213 prop_assert_eq!(Polynomial::<BFieldElement>::one() * a.clone(), a);
3214 }
3215
3216 #[proptest]
3217 fn multiplication_by_zero_is_zero(a: BfePoly) {
3218 let zero = Polynomial::<BFieldElement>::zero();
3219
3220 prop_assert_eq!(Polynomial::zero(), a.clone() * zero.clone());
3221 prop_assert_eq!(Polynomial::zero(), zero * a);
3222 }
3223
3224 #[proptest]
3225 fn polynomial_addition_is_commutative(a: BfePoly, b: BfePoly) {
3226 prop_assert_eq!(a.clone() + b.clone(), b + a);
3227 }
3228
3229 #[proptest]
3230 fn polynomial_multiplication_is_commutative(a: BfePoly, b: BfePoly) {
3231 prop_assert_eq!(a.clone() * b.clone(), b * a);
3232 }
3233
3234 #[proptest]
3235 fn polynomial_addition_is_associative(a: BfePoly, b: BfePoly, c: BfePoly) {
3236 prop_assert_eq!((a.clone() + b.clone()) + c.clone(), a + (b + c));
3237 }
3238
3239 #[proptest]
3240 fn polynomial_multiplication_is_associative(a: BfePoly, b: BfePoly, c: BfePoly) {
3241 prop_assert_eq!((a.clone() * b.clone()) * c.clone(), a * (b * c));
3242 }
3243
3244 #[proptest]
3245 fn polynomial_multiplication_is_distributive(a: BfePoly, b: BfePoly, c: BfePoly) {
3246 prop_assert_eq!(
3247 (a.clone() + b.clone()) * c.clone(),
3248 (a * c.clone()) + (b * c)
3249 );
3250 }
3251
3252 #[proptest]
3253 fn polynomial_subtraction_of_self_is_zero(a: BfePoly) {
3254 prop_assert_eq!(Polynomial::zero(), a.clone() - a);
3255 }
3256
3257 #[proptest]
3258 fn polynomial_division_by_self_is_one(#[filter(!#a.is_zero())] a: BfePoly) {
3259 prop_assert_eq!(Polynomial::one(), a.clone() / a);
3260 }
3261
3262 #[proptest]
3263 fn polynomial_division_removes_common_factors(a: BfePoly, #[filter(!#b.is_zero())] b: BfePoly) {
3264 prop_assert_eq!(a.clone(), a * b.clone() / b);
3265 }
3266
3267 #[proptest]
3268 fn polynomial_multiplication_raises_degree_at_maximum_to_sum_of_degrees(
3269 a: BfePoly,
3270 b: BfePoly,
3271 ) {
3272 let sum_of_degrees = (a.degree() + b.degree()).max(-1);
3273 prop_assert!((a * b).degree() <= sum_of_degrees);
3274 }
3275
3276 #[test]
3277 fn leading_zeros_dont_affect_polynomial_division() {
3278 fn polynomial<const N: usize>(coeffs: [u64; N]) -> BfePoly {
3283 Polynomial::new(coeffs.map(BFieldElement::new).to_vec())
3284 }
3285
3286 let numerator = polynomial([1, BFieldElement::P - 1, 0, 1]);
3288 let numerator_with_leading_zero = polynomial([1, BFieldElement::P - 1, 0, 1, 0]);
3289
3290 let divisor_normalized = polynomial([0, 1]);
3291 let divisor_not_normalized = polynomial([0, 1, 0]);
3292 let divisor_more_leading_zeros = polynomial([0, 1, 0, 0, 0, 0, 0, 0, 0]);
3293
3294 let expected = polynomial([BFieldElement::P - 1, 0, 1]);
3295
3296 assert_eq!(expected, numerator.clone() / divisor_normalized.clone());
3298 assert_eq!(expected, numerator.clone() / divisor_not_normalized.clone());
3299 assert_eq!(expected, numerator / divisor_more_leading_zeros.clone());
3300
3301 let res_numerator_not_normalized_0 =
3303 numerator_with_leading_zero.clone() / divisor_normalized;
3304 let res_numerator_not_normalized_1 =
3305 numerator_with_leading_zero.clone() / divisor_not_normalized;
3306 let res_numerator_not_normalized_2 =
3307 numerator_with_leading_zero / divisor_more_leading_zeros;
3308 assert_eq!(expected, res_numerator_not_normalized_0);
3309 assert_eq!(expected, res_numerator_not_normalized_1);
3310 assert_eq!(expected, res_numerator_not_normalized_2);
3311 }
3312
3313 #[proptest]
3314 fn leading_coefficient_of_truncated_polynomial_is_same_as_original_leading_coefficient(
3315 poly: BfePoly,
3316 #[strategy(..50_usize)] truncation_point: usize,
3317 ) {
3318 let Some(lc) = poly.leading_coefficient() else {
3319 let reason = "test is only sensible if polynomial has a leading coefficient";
3320 return Err(TestCaseError::Reject(reason.into()));
3321 };
3322 let truncated_poly = poly.truncate(truncation_point);
3323 let Some(trunc_lc) = truncated_poly.leading_coefficient() else {
3324 let reason = "test is only sensible if truncated polynomial has a leading coefficient";
3325 return Err(TestCaseError::Reject(reason.into()));
3326 };
3327 prop_assert_eq!(lc, trunc_lc);
3328 }
3329
3330 #[proptest]
3331 fn truncated_polynomial_is_of_degree_min_of_truncation_point_and_poly_degree(
3332 poly: BfePoly,
3333 #[strategy(..50_usize)] truncation_point: usize,
3334 ) {
3335 let expected_degree = poly.degree().min(truncation_point.try_into().unwrap());
3336 prop_assert_eq!(expected_degree, poly.truncate(truncation_point).degree());
3337 }
3338
3339 #[proptest]
3340 fn truncating_zero_polynomial_gives_zero_polynomial(
3341 #[strategy(..50_usize)] truncation_point: usize,
3342 ) {
3343 let poly = Polynomial::<BFieldElement>::zero().truncate(truncation_point);
3344 prop_assert!(poly.is_zero());
3345 }
3346
3347 #[proptest]
3348 fn truncation_negates_degree_shifting(
3349 #[strategy(0_usize..30)] shift: usize,
3350 #[strategy(..50_usize)] truncation_point: usize,
3351 #[filter(#poly.degree() >= #truncation_point as isize)] poly: BfePoly,
3352 ) {
3353 prop_assert_eq!(
3354 poly.truncate(truncation_point),
3355 poly.shift_coefficients(shift).truncate(truncation_point)
3356 );
3357 }
3358
3359 #[proptest]
3360 fn zero_polynomial_mod_any_power_of_x_is_zero_polynomial(power: usize) {
3361 let must_be_zero = Polynomial::<BFieldElement>::zero().mod_x_to_the_n(power);
3362 prop_assert!(must_be_zero.is_zero());
3363 }
3364
3365 #[proptest]
3366 fn polynomial_mod_some_power_of_x_results_in_polynomial_of_degree_one_less_than_power(
3367 #[filter(!#poly.is_zero())] poly: BfePoly,
3368 #[strategy(..=usize::try_from(#poly.degree()).unwrap())] power: usize,
3369 ) {
3370 let remainder = poly.mod_x_to_the_n(power);
3371 prop_assert_eq!(isize::try_from(power).unwrap() - 1, remainder.degree());
3372 }
3373
3374 #[proptest]
3375 fn polynomial_mod_some_power_of_x_shares_low_degree_terms_coefficients_with_original_polynomial(
3376 #[filter(!#poly.is_zero())] poly: BfePoly,
3377 power: usize,
3378 ) {
3379 let remainder = poly.mod_x_to_the_n(power);
3380 let min_num_coefficients = poly.coefficients.len().min(remainder.coefficients.len());
3381 prop_assert_eq!(
3382 &poly.coefficients[..min_num_coefficients],
3383 &remainder.coefficients[..min_num_coefficients]
3384 );
3385 }
3386
3387 #[proptest]
3388 fn fast_multiplication_by_zero_gives_zero(poly: BfePoly) {
3389 let product = poly.fast_multiply(&Polynomial::<BFieldElement>::zero());
3390 prop_assert_eq!(Polynomial::zero(), product);
3391 }
3392
3393 #[proptest]
3394 fn fast_multiplication_by_one_gives_self(poly: BfePoly) {
3395 let product = poly.fast_multiply(&Polynomial::<BFieldElement>::one());
3396 prop_assert_eq!(poly, product);
3397 }
3398
3399 #[proptest]
3400 fn fast_multiplication_is_commutative(a: BfePoly, b: BfePoly) {
3401 prop_assert_eq!(a.fast_multiply(&b), b.fast_multiply(&a));
3402 }
3403
3404 #[proptest]
3405 fn fast_multiplication_and_normal_multiplication_are_equivalent(a: BfePoly, b: BfePoly) {
3406 let product = a.fast_multiply(&b);
3407 prop_assert_eq!(a * b, product);
3408 }
3409
3410 #[proptest]
3411 fn batch_multiply_agrees_with_iterative_multiply(a: Vec<BfePoly>) {
3412 let mut acc = Polynomial::one();
3413 for factor in &a {
3414 acc = acc.multiply(factor);
3415 }
3416 prop_assert_eq!(acc, Polynomial::batch_multiply(&a));
3417 }
3418
3419 #[proptest]
3420 fn par_batch_multiply_agrees_with_batch_multiply(a: Vec<BfePoly>) {
3421 prop_assert_eq!(
3422 Polynomial::batch_multiply(&a),
3423 Polynomial::par_batch_multiply(&a)
3424 );
3425 }
3426
3427 #[proptest(cases = 50)]
3428 fn naive_zerofier_and_fast_zerofier_are_identical(
3429 #[any(size_range(..Polynomial::<BFieldElement>::FAST_ZEROFIER_CUTOFF_THRESHOLD * 2).lift())]
3430 roots: Vec<BFieldElement>,
3431 ) {
3432 let naive_zerofier = Polynomial::naive_zerofier(&roots);
3433 let fast_zerofier = Polynomial::fast_zerofier(&roots);
3434 prop_assert_eq!(naive_zerofier, fast_zerofier);
3435 }
3436
3437 #[proptest(cases = 50)]
3438 fn smart_zerofier_and_fast_zerofier_are_identical(
3439 #[any(size_range(..Polynomial::<BFieldElement>::FAST_ZEROFIER_CUTOFF_THRESHOLD * 2).lift())]
3440 roots: Vec<BFieldElement>,
3441 ) {
3442 let smart_zerofier = Polynomial::smart_zerofier(&roots);
3443 let fast_zerofier = Polynomial::fast_zerofier(&roots);
3444 prop_assert_eq!(smart_zerofier, fast_zerofier);
3445 }
3446
3447 #[proptest(cases = 50)]
3448 fn zerofier_and_naive_zerofier_are_identical(
3449 #[any(size_range(..Polynomial::<BFieldElement>::FAST_ZEROFIER_CUTOFF_THRESHOLD * 2).lift())]
3450 roots: Vec<BFieldElement>,
3451 ) {
3452 let zerofier = Polynomial::zerofier(&roots);
3453 let naive_zerofier = Polynomial::naive_zerofier(&roots);
3454 prop_assert_eq!(zerofier, naive_zerofier);
3455 }
3456
3457 #[proptest(cases = 50)]
3458 fn zerofier_is_zero_only_on_domain(
3459 #[any(size_range(..1024).lift())] domain: Vec<BFieldElement>,
3460 #[filter(#out_of_domain_points.iter().all(|p| !#domain.contains(p)))]
3461 out_of_domain_points: Vec<BFieldElement>,
3462 ) {
3463 let zerofier = Polynomial::zerofier(&domain);
3464 for point in domain {
3465 prop_assert_eq!(BFieldElement::ZERO, zerofier.evaluate(point));
3466 }
3467 for point in out_of_domain_points {
3468 prop_assert_ne!(BFieldElement::ZERO, zerofier.evaluate(point));
3469 }
3470 }
3471
3472 #[proptest]
3473 fn zerofier_has_leading_coefficient_one(domain: Vec<BFieldElement>) {
3474 let zerofier = Polynomial::zerofier(&domain);
3475 prop_assert_eq!(BFieldElement::ONE, zerofier.leading_coefficient().unwrap());
3476 }
3477 #[proptest]
3478 fn par_zerofier_agrees_with_zerofier(domain: Vec<BFieldElement>) {
3479 prop_assert_eq!(
3480 Polynomial::zerofier(&domain),
3481 Polynomial::par_zerofier(&domain)
3482 );
3483 }
3484
3485 #[test]
3486 fn fast_evaluate_on_hardcoded_domain_and_polynomial() {
3487 let domain = bfe_array![6, 12];
3488 let x_to_the_5_plus_x_to_the_3 = Polynomial::new(bfe_vec![0, 0, 0, 1, 0, 1]);
3489
3490 let manual_evaluations = domain.map(|x| x.mod_pow(5) + x.mod_pow(3)).to_vec();
3491 let fast_evaluations = x_to_the_5_plus_x_to_the_3.batch_evaluate(&domain);
3492 assert_eq!(manual_evaluations, fast_evaluations);
3493 }
3494
3495 #[proptest]
3496 fn slow_and_fast_polynomial_evaluation_are_equivalent(
3497 poly: BfePoly,
3498 #[any(size_range(..1024).lift())] domain: Vec<BFieldElement>,
3499 ) {
3500 let evaluations = domain
3501 .iter()
3502 .map(|&x| poly.evaluate_in_same_field(x))
3503 .collect_vec();
3504 let fast_evaluations = poly.batch_evaluate(&domain);
3505 prop_assert_eq!(evaluations, fast_evaluations);
3506 }
3507
3508 #[test]
3509 #[should_panic(expected = "zero points")]
3510 fn interpolation_through_no_points_is_impossible() {
3511 let _ = Polynomial::<BFieldElement>::interpolate(&[], &[]);
3512 }
3513
3514 #[test]
3515 #[should_panic(expected = "zero points")]
3516 fn lagrange_interpolation_through_no_points_is_impossible() {
3517 let _ = Polynomial::<BFieldElement>::lagrange_interpolate(&[], &[]);
3518 }
3519
3520 #[test]
3521 #[should_panic(expected = "zero points")]
3522 fn zipped_lagrange_interpolation_through_no_points_is_impossible() {
3523 let _ = Polynomial::<BFieldElement>::lagrange_interpolate_zipped(&[]);
3524 }
3525
3526 #[test]
3527 #[should_panic(expected = "zero points")]
3528 fn fast_interpolation_through_no_points_is_impossible() {
3529 let _ = Polynomial::<BFieldElement>::fast_interpolate(&[], &[]);
3530 }
3531
3532 #[test]
3533 #[should_panic(expected = "equal length")]
3534 fn interpolation_with_domain_size_different_from_number_of_points_is_impossible() {
3535 let domain = bfe_array![1, 2, 3];
3536 let points = bfe_array![1, 2];
3537 let _ = Polynomial::interpolate(&domain, &points);
3538 }
3539
3540 #[test]
3541 #[should_panic(expected = "Repeated")]
3542 fn zipped_lagrange_interpolate_using_repeated_domain_points_is_impossible() {
3543 let domain = bfe_array![1, 1, 2];
3544 let points = bfe_array![1, 2, 3];
3545 let zipped = domain.into_iter().zip(points).collect_vec();
3546 let _ = Polynomial::lagrange_interpolate_zipped(&zipped);
3547 }
3548
3549 #[proptest]
3550 fn interpolating_through_one_point_gives_constant_polynomial(
3551 x: BFieldElement,
3552 y: BFieldElement,
3553 ) {
3554 let interpolant = Polynomial::lagrange_interpolate(&[x], &[y]);
3555 let polynomial = Polynomial::from_constant(y);
3556 prop_assert_eq!(polynomial, interpolant);
3557 }
3558
3559 #[proptest(cases = 10)]
3560 fn lagrange_and_fast_interpolation_are_identical(
3561 #[any(size_range(1..2048).lift())]
3562 #[filter(#domain.iter().all_unique())]
3563 domain: Vec<BFieldElement>,
3564 #[strategy(vec(arb(), #domain.len()))] values: Vec<BFieldElement>,
3565 ) {
3566 let lagrange_interpolant = Polynomial::lagrange_interpolate(&domain, &values);
3567 let fast_interpolant = Polynomial::fast_interpolate(&domain, &values);
3568 prop_assert_eq!(lagrange_interpolant, fast_interpolant);
3569 }
3570
3571 #[proptest(cases = 10)]
3572 fn par_fast_interpolate_and_fast_interpolation_are_identical(
3573 #[any(size_range(1..2048).lift())]
3574 #[filter(#domain.iter().all_unique())]
3575 domain: Vec<BFieldElement>,
3576 #[strategy(vec(arb(), #domain.len()))] values: Vec<BFieldElement>,
3577 ) {
3578 let par_fast_interpolant = Polynomial::par_fast_interpolate(&domain, &values);
3579 let fast_interpolant = Polynomial::fast_interpolate(&domain, &values);
3580 prop_assert_eq!(par_fast_interpolant, fast_interpolant);
3581 }
3582
3583 #[test]
3584 fn fast_interpolation_through_a_single_point_succeeds() {
3585 let zero_arr = bfe_array![0];
3586 let _ = Polynomial::fast_interpolate(&zero_arr, &zero_arr);
3587 }
3588
3589 #[proptest(cases = 20)]
3590 fn interpolation_then_evaluation_is_identity(
3591 #[any(size_range(1..2048).lift())]
3592 #[filter(#domain.iter().all_unique())]
3593 domain: Vec<BFieldElement>,
3594 #[strategy(vec(arb(), #domain.len()))] values: Vec<BFieldElement>,
3595 ) {
3596 let interpolant = Polynomial::fast_interpolate(&domain, &values);
3597 let evaluations = interpolant.batch_evaluate(&domain);
3598 prop_assert_eq!(values, evaluations);
3599 }
3600
3601 #[proptest(cases = 1)]
3602 fn fast_batch_interpolation_is_equivalent_to_fast_interpolation(
3603 #[any(size_range(1..2048).lift())]
3604 #[filter(#domain.iter().all_unique())]
3605 domain: Vec<BFieldElement>,
3606 #[strategy(vec(vec(arb(), #domain.len()), 0..10))] value_vecs: Vec<Vec<BFieldElement>>,
3607 ) {
3608 let root_order = domain.len().next_power_of_two();
3609 let root_of_unity = BFieldElement::primitive_root_of_unity(root_order as u64).unwrap();
3610
3611 let interpolants = value_vecs
3612 .iter()
3613 .map(|values| Polynomial::fast_interpolate(&domain, values))
3614 .collect_vec();
3615
3616 let batched_interpolants =
3617 Polynomial::batch_fast_interpolate(&domain, &value_vecs, root_of_unity, root_order);
3618 prop_assert_eq!(interpolants, batched_interpolants);
3619 }
3620
3621 fn coset_domain_of_size_from_generator_with_offset(
3622 size: usize,
3623 generator: BFieldElement,
3624 offset: BFieldElement,
3625 ) -> Vec<BFieldElement> {
3626 let mut domain = vec![offset];
3627 for _ in 1..size {
3628 domain.push(domain.last().copied().unwrap() * generator);
3629 }
3630 domain
3631 }
3632
3633 #[proptest]
3634 fn fast_coset_evaluation_and_fast_evaluation_on_coset_are_identical(
3635 polynomial: BfePoly,
3636 offset: BFieldElement,
3637 #[strategy(0..8usize)]
3638 #[map(|x: usize| 1 << x)]
3639 #[filter((#root_order as isize) > #polynomial.degree())]
3641 root_order: usize,
3642 ) {
3643 let root_of_unity = BFieldElement::primitive_root_of_unity(root_order as u64).unwrap();
3644 let domain =
3645 coset_domain_of_size_from_generator_with_offset(root_order, root_of_unity, offset);
3646
3647 let fast_values = polynomial.batch_evaluate(&domain);
3648 let fast_coset_values = polynomial.fast_coset_evaluate(offset, root_order);
3649 prop_assert_eq!(fast_values, fast_coset_values);
3650 }
3651
3652 #[proptest]
3653 fn fast_coset_interpolation_and_and_fast_interpolation_on_coset_are_identical(
3654 #[filter(!#offset.is_zero())] offset: BFieldElement,
3655 #[strategy(1..8usize)]
3656 #[map(|x: usize| 1 << x)]
3657 root_order: usize,
3658 #[strategy(vec(arb(), #root_order))] values: Vec<BFieldElement>,
3659 ) {
3660 let root_of_unity = BFieldElement::primitive_root_of_unity(root_order as u64).unwrap();
3661 let domain =
3662 coset_domain_of_size_from_generator_with_offset(root_order, root_of_unity, offset);
3663
3664 let fast_interpolant = Polynomial::fast_interpolate(&domain, &values);
3665 let fast_coset_interpolant = Polynomial::fast_coset_interpolate(offset, &values);
3666 prop_assert_eq!(fast_interpolant, fast_coset_interpolant);
3667 }
3668
3669 #[proptest]
3670 fn naive_division_gives_quotient_and_remainder_with_expected_properties(
3671 a: BfePoly,
3672 #[filter(!#b.is_zero())] b: BfePoly,
3673 ) {
3674 let (quot, rem) = a.naive_divide(&b);
3675 prop_assert!(rem.degree() < b.degree());
3676 prop_assert_eq!(a, quot * b + rem);
3677 }
3678
3679 #[proptest]
3680 fn clean_naive_division_gives_quotient_and_remainder_with_expected_properties(
3681 #[filter(!#a_roots.is_empty())] a_roots: Vec<BFieldElement>,
3682 #[strategy(vec(0..#a_roots.len(), 1..=#a_roots.len()))]
3683 #[filter(#b_root_indices.iter().all_unique())]
3684 b_root_indices: Vec<usize>,
3685 ) {
3686 let b_roots = b_root_indices.into_iter().map(|i| a_roots[i]).collect_vec();
3687 let a = Polynomial::zerofier(&a_roots);
3688 let b = Polynomial::zerofier(&b_roots);
3689 let (quot, rem) = a.naive_divide(&b);
3690 prop_assert!(rem.is_zero());
3691 prop_assert_eq!(a, quot * b);
3692 }
3693
3694 #[proptest]
3695 fn clean_division_agrees_with_divide_on_clean_division(
3696 #[strategy(arb())] a: BfePoly,
3697 #[strategy(arb())]
3698 #[filter(!#b.is_zero())]
3699 b: BfePoly,
3700 ) {
3701 let product = a.clone() * b.clone();
3702 let (naive_quotient, naive_remainder) = product.naive_divide(&b);
3703 let clean_quotient = product.clone().clean_divide(b.clone());
3704 let err = format!("{product} / {b} == {naive_quotient} != {clean_quotient}");
3705 prop_assert_eq!(naive_quotient, clean_quotient, "{}", err);
3706 prop_assert_eq!(Polynomial::<BFieldElement>::zero(), naive_remainder);
3707 }
3708
3709 #[proptest]
3710 fn clean_division_agrees_with_division_if_divisor_has_only_0_as_root(
3711 #[strategy(arb())] mut dividend_roots: Vec<BFieldElement>,
3712 ) {
3713 dividend_roots.push(bfe!(0));
3714 let dividend = Polynomial::zerofier(÷nd_roots);
3715 let divisor = Polynomial::zerofier(&[bfe!(0)]);
3716
3717 let (naive_quotient, remainder) = dividend.naive_divide(&divisor);
3718 let clean_quotient = dividend.clean_divide(divisor);
3719 prop_assert_eq!(naive_quotient, clean_quotient);
3720 prop_assert_eq!(Polynomial::<BFieldElement>::zero(), remainder);
3721 }
3722
3723 #[proptest]
3724 fn clean_division_agrees_with_division_if_divisor_has_only_0_as_multiple_root(
3725 #[strategy(arb())] mut dividend_roots: Vec<BFieldElement>,
3726 #[strategy(0_usize..300)] num_roots: usize,
3727 ) {
3728 let multiple_roots = bfe_vec![0; num_roots];
3729 let divisor = Polynomial::zerofier(&multiple_roots);
3730 dividend_roots.extend(multiple_roots);
3731 let dividend = Polynomial::zerofier(÷nd_roots);
3732
3733 let (naive_quotient, remainder) = dividend.naive_divide(&divisor);
3734 let clean_quotient = dividend.clean_divide(divisor);
3735 prop_assert_eq!(naive_quotient, clean_quotient);
3736 prop_assert_eq!(Polynomial::<BFieldElement>::zero(), remainder);
3737 }
3738
3739 #[proptest]
3740 fn clean_division_agrees_with_division_if_divisor_has_0_as_root(
3741 #[strategy(arb())] mut dividend_roots: Vec<BFieldElement>,
3742 #[strategy(vec(0..#dividend_roots.len(), 0..=#dividend_roots.len()))]
3743 #[filter(#divisor_root_indices.iter().all_unique())]
3744 divisor_root_indices: Vec<usize>,
3745 ) {
3746 let mut divisor_roots = divisor_root_indices
3748 .into_iter()
3749 .map(|i| dividend_roots[i])
3750 .collect_vec();
3751
3752 dividend_roots.push(bfe!(0));
3754 divisor_roots.push(bfe!(0));
3755
3756 let dividend = Polynomial::zerofier(÷nd_roots);
3757 let divisor = Polynomial::zerofier(&divisor_roots);
3758 let quotient = dividend.clone().clean_divide(divisor.clone());
3759 prop_assert_eq!(dividend / divisor, quotient);
3760 }
3761
3762 #[proptest]
3763 fn clean_division_agrees_with_division_if_divisor_has_0_through_9_as_roots(
3764 #[strategy(arb())] additional_dividend_roots: Vec<BFieldElement>,
3765 ) {
3766 let divisor_roots = (0..10).map(BFieldElement::new).collect_vec();
3767 let divisor = Polynomial::zerofier(&divisor_roots);
3768 let dividend_roots = [additional_dividend_roots, divisor_roots].concat();
3769 let dividend = Polynomial::zerofier(÷nd_roots);
3770 dbg!(dividend.to_string());
3771 dbg!(divisor.to_string());
3772 let quotient = dividend.clone().clean_divide(divisor.clone());
3773 prop_assert_eq!(dividend / divisor, quotient);
3774 }
3775
3776 #[proptest]
3777 fn clean_division_gives_quotient_and_remainder_with_expected_properties(
3778 #[filter(!#a_roots.is_empty())] a_roots: Vec<BFieldElement>,
3779 #[strategy(vec(0..#a_roots.len(), 1..=#a_roots.len()))]
3780 #[filter(#b_root_indices.iter().all_unique())]
3781 b_root_indices: Vec<usize>,
3782 ) {
3783 let b_roots = b_root_indices.into_iter().map(|i| a_roots[i]).collect_vec();
3784 let a = Polynomial::zerofier(&a_roots);
3785 let b = Polynomial::zerofier(&b_roots);
3786 let quotient = a.clone().clean_divide(b.clone());
3787 prop_assert_eq!(a, quotient * b);
3788 }
3789
3790 #[proptest]
3791 fn dividing_constant_polynomials_is_equivalent_to_dividing_constants(
3792 a: BFieldElement,
3793 #[filter(!#b.is_zero())] b: BFieldElement,
3794 ) {
3795 let a_poly = Polynomial::from_constant(a);
3796 let b_poly = Polynomial::from_constant(b);
3797 let expected_quotient = Polynomial::from_constant(a / b);
3798 prop_assert_eq!(expected_quotient, a_poly / b_poly);
3799 }
3800
3801 #[proptest]
3802 fn dividing_any_polynomial_by_a_constant_polynomial_results_in_remainder_zero(
3803 a: BfePoly,
3804 #[filter(!#b.is_zero())] b: BFieldElement,
3805 ) {
3806 let b_poly = Polynomial::from_constant(b);
3807 let (_, remainder) = a.naive_divide(&b_poly);
3808 prop_assert_eq!(Polynomial::zero(), remainder);
3809 }
3810
3811 #[test]
3812 fn polynomial_division_by_and_with_shah_polynomial() {
3813 fn polynomial<const N: usize>(coeffs: [u64; N]) -> BfePoly {
3814 Polynomial::new(coeffs.map(BFieldElement::new).to_vec())
3815 }
3816
3817 let shah = XFieldElement::shah_polynomial();
3818 let x_to_the_3 = polynomial([1]).shift_coefficients(3);
3819 let (shah_div_x_to_the_3, shah_mod_x_to_the_3) = shah.naive_divide(&x_to_the_3);
3820 assert_eq!(polynomial([1]), shah_div_x_to_the_3);
3821 assert_eq!(polynomial([1, BFieldElement::P - 1]), shah_mod_x_to_the_3);
3822
3823 let x_to_the_6 = polynomial([1]).shift_coefficients(6);
3824 let (x_to_the_6_div_shah, x_to_the_6_mod_shah) = x_to_the_6.naive_divide(&shah);
3825
3826 let expected_quot = polynomial([BFieldElement::P - 1, 1, 0, 1]);
3828 assert_eq!(expected_quot, x_to_the_6_div_shah);
3829
3830 let expected_rem = polynomial([1, BFieldElement::P - 2, 1]);
3832 assert_eq!(expected_rem, x_to_the_6_mod_shah);
3833 }
3834
3835 #[test]
3836 fn xgcd_does_not_panic_on_input_zero() {
3837 let zero = Polynomial::<BFieldElement>::zero;
3838 let (gcd, a, b) = Polynomial::xgcd(zero(), zero());
3839 assert_eq!(zero(), gcd);
3840 println!("a = {a}");
3841 println!("b = {b}");
3842 }
3843
3844 #[proptest]
3845 fn xgcd_b_field_pol_test(x: BfePoly, y: BfePoly) {
3846 let (gcd, a, b) = Polynomial::xgcd(x.clone(), y.clone());
3847 prop_assert_eq!(gcd, a * x + b * y);
3849 }
3850
3851 #[proptest]
3852 fn xgcd_x_field_pol_test(x: XfePoly, y: XfePoly) {
3853 let (gcd, a, b) = Polynomial::xgcd(x.clone(), y.clone());
3854 prop_assert_eq!(gcd, a * x + b * y);
3856 }
3857
3858 #[proptest]
3859 fn add_assign_is_equivalent_to_adding_and_assigning(a: BfePoly, b: BfePoly) {
3860 let mut c = a.clone();
3861 c += b.clone();
3862 prop_assert_eq!(a + b, c);
3863 }
3864
3865 #[test]
3866 fn only_monic_polynomial_of_degree_1_is_x() {
3867 fn polynomial<const N: usize>(coeffs: [u64; N]) -> BfePoly {
3868 Polynomial::new(coeffs.map(BFieldElement::new).to_vec())
3869 }
3870
3871 assert!(polynomial([0, 1]).is_x());
3872 assert!(polynomial([0, 1, 0]).is_x());
3873 assert!(polynomial([0, 1, 0, 0]).is_x());
3874
3875 assert!(!polynomial([]).is_x());
3876 assert!(!polynomial([0]).is_x());
3877 assert!(!polynomial([1]).is_x());
3878 assert!(!polynomial([1, 0]).is_x());
3879 assert!(!polynomial([0, 2]).is_x());
3880 assert!(!polynomial([0, 0, 1]).is_x());
3881 }
3882
3883 #[test]
3884 fn hardcoded_polynomial_squaring() {
3885 fn polynomial<const N: usize>(coeffs: [u64; N]) -> BfePoly {
3886 Polynomial::new(coeffs.map(BFieldElement::new).to_vec())
3887 }
3888
3889 assert_eq!(Polynomial::zero(), polynomial([]).square());
3890
3891 let x_plus_1 = polynomial([1, 1]);
3892 assert_eq!(polynomial([1, 2, 1]), x_plus_1.square());
3893
3894 let x_to_the_15 = polynomial([1]).shift_coefficients(15);
3895 let x_to_the_30 = polynomial([1]).shift_coefficients(30);
3896 assert_eq!(x_to_the_30, x_to_the_15.square());
3897
3898 let some_poly = polynomial([14, 1, 3, 4]);
3899 assert_eq!(
3900 polynomial([196, 28, 85, 118, 17, 24, 16]),
3901 some_poly.square()
3902 );
3903 }
3904
3905 #[proptest]
3906 fn polynomial_squaring_is_equivalent_to_multiplication_with_self(poly: BfePoly) {
3907 prop_assert_eq!(poly.clone() * poly.clone(), poly.square());
3908 }
3909
3910 #[proptest]
3911 fn slow_and_normal_squaring_are_equivalent(poly: BfePoly) {
3912 prop_assert_eq!(poly.slow_square(), poly.square());
3913 }
3914
3915 #[proptest]
3916 fn normal_and_fast_squaring_are_equivalent(poly: BfePoly) {
3917 prop_assert_eq!(poly.square(), poly.fast_square());
3918 }
3919
3920 #[test]
3921 fn constant_zero_eq_constant_zero() {
3922 let zero_polynomial1 = Polynomial::<BFieldElement>::zero();
3923 let zero_polynomial2 = Polynomial::<BFieldElement>::zero();
3924
3925 assert_eq!(zero_polynomial1, zero_polynomial2)
3926 }
3927
3928 #[test]
3929 fn zero_polynomial_is_zero() {
3930 assert!(Polynomial::<BFieldElement>::zero().is_zero());
3931 assert!(Polynomial::<XFieldElement>::zero().is_zero());
3932 }
3933
3934 #[proptest]
3935 fn zero_polynomial_is_zero_independent_of_spurious_leading_zeros(
3936 #[strategy(..500usize)] num_zeros: usize,
3937 ) {
3938 let coefficients = vec![0; num_zeros];
3939 prop_assert_eq!(
3940 Polynomial::zero(),
3941 Polynomial::<BFieldElement>::from(coefficients)
3942 );
3943 }
3944
3945 #[proptest]
3946 fn no_constant_polynomial_with_non_zero_coefficient_is_zero(
3947 #[filter(!#constant.is_zero())] constant: BFieldElement,
3948 ) {
3949 let constant_polynomial = Polynomial::from_constant(constant);
3950 prop_assert!(!constant_polynomial.is_zero());
3951 }
3952
3953 #[test]
3954 fn constant_one_eq_constant_one() {
3955 let one_polynomial1 = Polynomial::<BFieldElement>::one();
3956 let one_polynomial2 = Polynomial::<BFieldElement>::one();
3957
3958 assert_eq!(one_polynomial1, one_polynomial2)
3959 }
3960
3961 #[test]
3962 fn one_polynomial_is_one() {
3963 assert!(Polynomial::<BFieldElement>::one().is_one());
3964 assert!(Polynomial::<XFieldElement>::one().is_one());
3965 }
3966
3967 #[proptest]
3968 fn one_polynomial_is_one_independent_of_spurious_leading_zeros(
3969 #[strategy(..500usize)] num_leading_zeros: usize,
3970 ) {
3971 let spurious_leading_zeros = vec![0; num_leading_zeros];
3972 let mut coefficients = vec![1];
3973 coefficients.extend(spurious_leading_zeros);
3974 prop_assert_eq!(
3975 Polynomial::one(),
3976 Polynomial::<BFieldElement>::from(coefficients)
3977 );
3978 }
3979
3980 #[proptest]
3981 fn no_constant_polynomial_with_non_one_coefficient_is_one(
3982 #[filter(!#constant.is_one())] constant: BFieldElement,
3983 ) {
3984 let constant_polynomial = Polynomial::from_constant(constant);
3985 prop_assert!(!constant_polynomial.is_one());
3986 }
3987
3988 #[test]
3989 fn formal_derivative_of_zero_is_zero() {
3990 let bfe_0_poly = Polynomial::<BFieldElement>::zero();
3991 assert!(bfe_0_poly.formal_derivative().is_zero());
3992
3993 let xfe_0_poly = Polynomial::<XFieldElement>::zero();
3994 assert!(xfe_0_poly.formal_derivative().is_zero());
3995 }
3996
3997 #[proptest]
3998 fn formal_derivative_of_constant_polynomial_is_zero(constant: BFieldElement) {
3999 let formal_derivative = Polynomial::from_constant(constant).formal_derivative();
4000 prop_assert!(formal_derivative.is_zero());
4001 }
4002
4003 #[proptest]
4004 fn formal_derivative_of_non_zero_polynomial_is_of_degree_one_less_than_the_polynomial(
4005 #[filter(!#poly.is_zero())] poly: BfePoly,
4006 ) {
4007 prop_assert_eq!(poly.degree() - 1, poly.formal_derivative().degree());
4008 }
4009
4010 #[proptest]
4011 fn formal_derivative_of_product_adheres_to_the_leibniz_product_rule(a: BfePoly, b: BfePoly) {
4012 let product_formal_derivative = (a.clone() * b.clone()).formal_derivative();
4013 let product_rule = a.formal_derivative() * b.clone() + a * b.formal_derivative();
4014 prop_assert_eq!(product_rule, product_formal_derivative);
4015 }
4016
4017 #[test]
4018 fn zero_is_zero() {
4019 let f = Polynomial::new(vec![BFieldElement::new(0)]);
4020 assert!(f.is_zero());
4021 }
4022
4023 #[proptest]
4024 fn formal_power_series_inverse_newton(
4025 #[strategy(2usize..20)] precision: usize,
4026 #[filter(!#f.coefficients.is_empty())]
4027 #[filter(!#f.coefficients[0].is_zero())]
4028 #[filter(#precision > 1 + #f.degree() as usize)]
4029 f: BfePoly,
4030 ) {
4031 let g = f.clone().formal_power_series_inverse_newton(precision);
4032 let mut coefficients = bfe_vec![0; precision + 1];
4033 coefficients[precision] = BFieldElement::ONE;
4034 let xn = Polynomial::new(coefficients);
4035 let (_quotient, remainder) = g.multiply(&f).divide(&xn);
4036 prop_assert!(remainder.is_one());
4037 }
4038
4039 #[test]
4040 fn formal_power_series_inverse_newton_concrete() {
4041 let f = Polynomial::new(vec![
4042 BFieldElement::new(3618372803227210457),
4043 BFieldElement::new(14620511201754172786),
4044 BFieldElement::new(2577803283145951105),
4045 BFieldElement::new(1723541458268087404),
4046 BFieldElement::new(4119508755381840018),
4047 BFieldElement::new(8592072587377832596),
4048 BFieldElement::new(236223201225),
4049 ]);
4050 let precision = 8;
4051
4052 let g = f.clone().formal_power_series_inverse_newton(precision);
4053 let mut coefficients = vec![BFieldElement::ZERO; precision + 1];
4054 coefficients[precision] = BFieldElement::ONE;
4055 let xn = Polynomial::new(coefficients);
4056 let (_quotient, remainder) = g.multiply(&f).divide(&xn);
4057 assert!(remainder.is_one());
4058 }
4059
4060 #[proptest]
4061 fn formal_power_series_inverse_minimal(
4062 #[strategy(2usize..20)] precision: usize,
4063 #[filter(!#f.coefficients.is_empty())]
4064 #[filter(!#f.coefficients[0].is_zero())]
4065 #[filter(#precision > 1 + #f.degree() as usize)]
4066 f: BfePoly,
4067 ) {
4068 let g = f.formal_power_series_inverse_minimal(precision);
4069 let mut coefficients = vec![BFieldElement::ZERO; precision + 1];
4070 coefficients[precision] = BFieldElement::ONE;
4071 let xn = Polynomial::new(coefficients);
4072 let (_quotient, remainder) = g.multiply(&f).divide(&xn);
4073
4074 prop_assert!(remainder.is_one());
4076
4077 prop_assert!(g.degree() <= precision as isize);
4079 }
4080
4081 #[proptest]
4082 fn structured_multiple_is_multiple(
4083 #[filter(#coefficients.iter().any(|c|!c.is_zero()))]
4084 #[strategy(vec(arb(), 1..30))]
4085 coefficients: Vec<BFieldElement>,
4086 ) {
4087 let polynomial = Polynomial::new(coefficients);
4088 let multiple = polynomial.structured_multiple();
4089 let remainder = multiple.reduce_long_division(&polynomial);
4090 prop_assert!(remainder.is_zero());
4091 }
4092
4093 #[proptest]
4094 fn structured_multiple_of_modulus_with_trailing_zeros_is_multiple(
4095 #[filter(!#raw_modulus.is_zero())] raw_modulus: BfePoly,
4096 #[strategy(0usize..100)] num_trailing_zeros: usize,
4097 ) {
4098 let modulus = raw_modulus.shift_coefficients(num_trailing_zeros);
4099 let multiple = modulus.structured_multiple();
4100 prop_assert!(multiple.reduce_long_division(&modulus).is_zero());
4101 }
4102
4103 #[proptest]
4104 fn structured_multiple_generates_structure(
4105 #[filter(#coefficients.iter().filter(|c|!c.is_zero()).count() >= 3)]
4106 #[strategy(vec(arb(), 1..30))]
4107 coefficients: Vec<BFieldElement>,
4108 ) {
4109 let polynomial = Polynomial::new(coefficients);
4110 let n = polynomial.degree();
4111 let structured_multiple = polynomial.structured_multiple();
4112 assert!(structured_multiple.degree() <= 3 * n + 1);
4113
4114 let x3np1 = Polynomial::x_to_the((3 * n + 1) as usize);
4115 let remainder = structured_multiple.reduce_long_division(&x3np1);
4116 assert!(2 * n >= remainder.degree());
4117
4118 let structured_mul_minus_rem = structured_multiple - remainder;
4119 assert_eq!(0, structured_mul_minus_rem.clone().reverse().degree());
4120 assert_eq!(
4121 BFieldElement::ONE,
4122 *structured_mul_minus_rem.coefficients.last().unwrap(),
4123 );
4124 }
4125
4126 #[test]
4127 fn structured_multiple_generates_structure_concrete() {
4128 let polynomial = Polynomial::new(
4129 [884763262770, 0, 51539607540, 14563891882495327437]
4130 .map(BFieldElement::new)
4131 .to_vec(),
4132 );
4133 let n = polynomial.degree();
4134 let structured_multiple = polynomial.structured_multiple();
4135 assert_eq!(3 * n + 1, structured_multiple.degree());
4136
4137 let x3np1 = Polynomial::x_to_the((3 * n + 1) as usize);
4138 let remainder = structured_multiple.reduce_long_division(&x3np1);
4139 assert!(2 * n >= remainder.degree());
4140
4141 let structured_mul_minus_rem = structured_multiple - remainder;
4142 assert_eq!(0, structured_mul_minus_rem.clone().reverse().degree());
4143 assert_eq!(
4144 BFieldElement::ONE,
4145 *structured_mul_minus_rem.coefficients.last().unwrap(),
4146 );
4147 }
4148
4149 #[proptest]
4150 fn structured_multiple_of_degree_is_multiple(
4151 #[strategy(2usize..100)] n: usize,
4152 #[filter(#coefficients.iter().any(|c|!c.is_zero()))]
4153 #[strategy(vec(arb(), 1..usize::min(30, #n)))]
4154 coefficients: Vec<BFieldElement>,
4155 ) {
4156 let polynomial = Polynomial::new(coefficients);
4157 let multiple = polynomial.structured_multiple_of_degree(n);
4158 let remainder = multiple.reduce_long_division(&polynomial);
4159 prop_assert!(remainder.is_zero());
4160 }
4161
4162 #[proptest]
4163 fn structured_multiple_of_degree_generates_structure(
4164 #[strategy(4usize..100)] n: usize,
4165 #[strategy(vec(arb(), 3..usize::min(30, #n)))] mut coefficients: Vec<BFieldElement>,
4166 ) {
4167 *coefficients.last_mut().unwrap() = BFieldElement::ONE;
4168 let polynomial = Polynomial::new(coefficients);
4169 let structured_multiple = polynomial.structured_multiple_of_degree(n);
4170
4171 let xn =
4172 Polynomial::new([vec![BFieldElement::ZERO; n], vec![BFieldElement::ONE; 1]].concat());
4173 let remainder = structured_multiple.reduce_long_division(&xn);
4174 assert_eq!(
4175 (structured_multiple.clone() - remainder.clone())
4176 .reverse()
4177 .degree() as usize,
4178 0
4179 );
4180 assert_eq!(
4181 BFieldElement::ONE,
4182 *(structured_multiple.clone() - remainder)
4183 .coefficients
4184 .last()
4185 .unwrap()
4186 );
4187 }
4188
4189 #[proptest]
4190 fn structured_multiple_of_degree_has_given_degree(
4191 #[strategy(2usize..100)] n: usize,
4192 #[filter(#coefficients.iter().any(|c|!c.is_zero()))]
4193 #[strategy(vec(arb(), 1..usize::min(30, #n)))]
4194 coefficients: Vec<BFieldElement>,
4195 ) {
4196 let polynomial = Polynomial::new(coefficients);
4197 let multiple = polynomial.structured_multiple_of_degree(n);
4198 prop_assert_eq!(
4199 multiple.degree() as usize,
4200 n,
4201 "polynomial: {} whereas multiple {}",
4202 polynomial,
4203 multiple
4204 );
4205 }
4206
4207 #[proptest]
4208 fn reverse_polynomial_with_nonzero_constant_term_twice_gives_original_back(f: BfePoly) {
4209 let fx_plus_1 = f.shift_coefficients(1) + Polynomial::from_constant(bfe!(1));
4210 prop_assert_eq!(fx_plus_1.clone(), fx_plus_1.reverse().reverse());
4211 }
4212
4213 #[proptest]
4214 fn reverse_polynomial_with_zero_constant_term_twice_gives_shift_back(
4215 #[filter(!#f.is_zero())] f: BfePoly,
4216 ) {
4217 let fx_plus_1 = f.shift_coefficients(1);
4218 prop_assert_ne!(fx_plus_1.clone(), fx_plus_1.reverse().reverse());
4219 prop_assert_eq!(
4220 fx_plus_1.clone(),
4221 fx_plus_1.reverse().reverse().shift_coefficients(1)
4222 );
4223 }
4224
4225 #[proptest]
4226 fn reduce_by_structured_modulus_and_reduce_long_division_agree(
4227 #[strategy(1usize..10)] n: usize,
4228 #[strategy(1usize..10)] m: usize,
4229 #[strategy(vec(arb(), #m))] b_coefficients: Vec<BFieldElement>,
4230 #[strategy(1usize..100)] _deg_a: usize,
4231 #[strategy(vec(arb(), #_deg_a + 1))] _a_coefficients: Vec<BFieldElement>,
4232 #[strategy(Just(Polynomial::new(#_a_coefficients)))] a: BfePoly,
4233 ) {
4234 let mut full_modulus_coefficients = b_coefficients.clone();
4235 full_modulus_coefficients.resize(m + n + 1, BFieldElement::from(0));
4236 *full_modulus_coefficients.last_mut().unwrap() = BFieldElement::from(1);
4237 let full_modulus = Polynomial::new(full_modulus_coefficients);
4238
4239 let long_remainder = a.reduce_long_division(&full_modulus);
4240 let structured_remainder = a.reduce_by_structured_modulus(&full_modulus);
4241
4242 prop_assert_eq!(long_remainder, structured_remainder);
4243 }
4244
4245 #[test]
4246 fn reduce_by_structured_modulus_and_reduce_agree_long_division_concrete() {
4247 let a = Polynomial::new(
4248 [1, 0, 0, 3, 4, 3, 1, 5, 1, 0, 1, 2, 9, 2, 0, 3, 1]
4249 .into_iter()
4250 .map(BFieldElement::new)
4251 .collect_vec(),
4252 );
4253 let mut full_modulus_coefficients =
4254 [5, 6, 3].into_iter().map(BFieldElement::new).collect_vec();
4255 let m = full_modulus_coefficients.len();
4256 let n = 2;
4257 full_modulus_coefficients.resize(m + n + 1, BFieldElement::from(0));
4258 *full_modulus_coefficients.last_mut().unwrap() = BFieldElement::from(1);
4259 let full_modulus = Polynomial::new(full_modulus_coefficients);
4260
4261 let long_remainder = a.reduce_long_division(&full_modulus);
4262 let structured_remainder = a.reduce_by_structured_modulus(&full_modulus);
4263
4264 assert_eq!(
4265 long_remainder, structured_remainder,
4266 "naive: {long_remainder}\nstructured: {structured_remainder}",
4267 );
4268 }
4269
4270 #[proptest]
4271 fn reduce_by_ntt_friendly_modulus_and_reduce_long_division_agree(
4272 #[strategy(1usize..10)] m: usize,
4273 #[strategy(vec(arb(), #m))] b_coefficients: Vec<BFieldElement>,
4274 #[strategy(1usize..100)] _deg_a: usize,
4275 #[strategy(vec(arb(), #_deg_a + 1))] _a_coefficients: Vec<BFieldElement>,
4276 #[strategy(Just(Polynomial::new(#_a_coefficients)))] a: BfePoly,
4277 ) {
4278 let b = Polynomial::new(b_coefficients.clone());
4279 if b.is_zero() {
4280 return Err(TestCaseError::Reject("some reason".into()));
4281 }
4282 let n = (b_coefficients.len() + 1).next_power_of_two();
4283 let mut full_modulus_coefficients = b_coefficients.clone();
4284 full_modulus_coefficients.resize(n + 1, BFieldElement::from(0));
4285 *full_modulus_coefficients.last_mut().unwrap() = BFieldElement::from(1);
4286 let full_modulus = Polynomial::new(full_modulus_coefficients);
4287
4288 let long_remainder = a.reduce_long_division(&full_modulus);
4289
4290 let mut shift_ntt = b_coefficients.clone();
4291 shift_ntt.resize(n, BFieldElement::from(0));
4292 ntt(&mut shift_ntt);
4293 let structured_remainder = a.reduce_by_ntt_friendly_modulus(&shift_ntt, m);
4294
4295 prop_assert_eq!(long_remainder, structured_remainder);
4296 }
4297
4298 #[test]
4299 fn reduce_by_ntt_friendly_modulus_and_reduce_agree_concrete() {
4300 let m = 1;
4301 let a_coefficients = bfe_vec![0, 0, 75944580];
4302 let a = Polynomial::new(a_coefficients);
4303 let b_coefficients = vec![BFieldElement::new(944892804900)];
4304 let n = (b_coefficients.len() + 1).next_power_of_two();
4305 let mut full_modulus_coefficients = b_coefficients.clone();
4306 full_modulus_coefficients.resize(n + 1, BFieldElement::from(0));
4307 *full_modulus_coefficients.last_mut().unwrap() = BFieldElement::from(1);
4308 let full_modulus = Polynomial::new(full_modulus_coefficients);
4309
4310 let long_remainder = a.reduce_long_division(&full_modulus);
4311
4312 let mut shift_ntt = b_coefficients.clone();
4313 shift_ntt.resize(n, BFieldElement::from(0));
4314 ntt(&mut shift_ntt);
4315 let structured_remainder = a.reduce_by_ntt_friendly_modulus(&shift_ntt, m);
4316
4317 assert_eq!(
4318 long_remainder, structured_remainder,
4319 "full modulus: {full_modulus}",
4320 );
4321 }
4322
4323 #[proptest]
4324 fn reduce_fast_and_reduce_long_division_agree(
4325 numerator: BfePoly,
4326 #[filter(!#modulus.is_zero())] modulus: BfePoly,
4327 ) {
4328 prop_assert_eq!(
4329 numerator.fast_reduce(&modulus),
4330 numerator.reduce_long_division(&modulus)
4331 );
4332 }
4333
4334 #[test]
4335 fn reduce_and_fast_reduce_long_division_agree_on_fixed_input() {
4336 let mut failures = vec![];
4340 for i in 1..100 {
4341 let roots = (0..i).map(BFieldElement::new).collect_vec();
4345 let dividend = Polynomial::zerofier(&roots).formal_derivative();
4346
4347 let divisor_roots = &roots[..roots.len() / 5];
4352 let divisor = Polynomial::zerofier(divisor_roots);
4353
4354 let long_div_remainder = dividend.reduce_long_division(&divisor);
4355 let preprocessed_remainder = dividend.fast_reduce(&divisor);
4356
4357 if long_div_remainder != preprocessed_remainder {
4358 failures.push(i);
4359 }
4360 }
4361
4362 assert_eq!(0, failures.len(), "failures at indices: {failures:?}");
4363 }
4364
4365 #[test]
4366 fn reduce_long_division_and_fast_reduce_agree_simple_fixed() {
4367 let roots = (0..10).map(BFieldElement::new).collect_vec();
4368 let numerator = Polynomial::zerofier(&roots).formal_derivative();
4369
4370 let divisor_roots = &roots[..roots.len() / 5];
4371 let denominator = Polynomial::zerofier(divisor_roots);
4372
4373 let (quotient, remainder) = numerator.divide(&denominator);
4374 assert_eq!(
4375 numerator,
4376 denominator.clone() * quotient + remainder.clone()
4377 );
4378
4379 let long_div_remainder = numerator.reduce_long_division(&denominator);
4380 assert_eq!(remainder, long_div_remainder);
4381
4382 let preprocessed_remainder = numerator.fast_reduce(&denominator);
4383
4384 assert_eq!(long_div_remainder, preprocessed_remainder);
4385 }
4386
4387 #[proptest(cases = 100)]
4388 fn batch_evaluate_methods_are_equivalent(
4389 #[strategy(vec(arb(), (1<<10)..(1<<11)))] coefficients: Vec<BFieldElement>,
4390 #[strategy(vec(arb(), (1<<5)..(1<<7)))] domain: Vec<BFieldElement>,
4391 ) {
4392 let polynomial = Polynomial::new(coefficients);
4393 let evaluations_iterative = polynomial.iterative_batch_evaluate(&domain);
4394 let zerofier_tree = ZerofierTree::new_from_domain(&domain);
4395 let evaluations_fast = polynomial.divide_and_conquer_batch_evaluate(&zerofier_tree);
4396 let evaluations_reduce_then = polynomial.reduce_then_batch_evaluate(&domain);
4397
4398 prop_assert_eq!(evaluations_iterative.clone(), evaluations_fast);
4399 prop_assert_eq!(evaluations_iterative, evaluations_reduce_then);
4400 }
4401
4402 #[proptest]
4403 fn reduce_agrees_with_division(a: BfePoly, #[filter(!#b.is_zero())] b: BfePoly) {
4404 prop_assert_eq!(a.divide(&b).1, a.reduce(&b));
4405 }
4406
4407 #[proptest]
4408 fn structured_multiple_of_monomial_term_is_actually_multiple_and_of_right_degree(
4409 #[strategy(1usize..1000)] degree: usize,
4410 #[filter(!#leading_coefficient.is_zero())] leading_coefficient: BFieldElement,
4411 #[strategy(#degree+1..#degree+200)] target_degree: usize,
4412 ) {
4413 let coefficients = [bfe_vec![0; degree], vec![leading_coefficient]].concat();
4414 let polynomial = Polynomial::new(coefficients);
4415 let multiple = polynomial.structured_multiple_of_degree(target_degree);
4416 prop_assert_eq!(Polynomial::zero(), multiple.reduce(&polynomial));
4417 prop_assert_eq!(multiple.degree() as usize, target_degree);
4418 }
4419
4420 #[proptest]
4421 fn monomial_term_divided_by_smaller_monomial_term_gives_clean_division(
4422 #[strategy(100usize..102)] high_degree: usize,
4423 #[filter(!#high_lc.is_zero())] high_lc: BFieldElement,
4424 #[strategy(83..#high_degree)] low_degree: usize,
4425 #[filter(!#low_lc.is_zero())] low_lc: BFieldElement,
4426 ) {
4427 let numerator = Polynomial::new([bfe_vec![0; high_degree], vec![high_lc]].concat());
4428 let denominator = Polynomial::new([bfe_vec![0; low_degree], vec![low_lc]].concat());
4429 let (quotient, remainder) = numerator.divide(&denominator);
4430 prop_assert_eq!(
4431 quotient
4432 .coefficients
4433 .iter()
4434 .filter(|c| !c.is_zero())
4435 .count(),
4436 1
4437 );
4438 prop_assert_eq!(Polynomial::zero(), remainder);
4439 }
4440
4441 #[proptest]
4442 fn fast_modular_coset_interpolate_agrees_with_interpolate_then_reduce_property(
4443 #[filter(!#modulus.is_zero())] modulus: BfePoly,
4444 #[strategy(0usize..10)] _logn: usize,
4445 #[strategy(Just(1 << #_logn))] n: usize,
4446 #[strategy(vec(arb(), #n))] values: Vec<BFieldElement>,
4447 #[strategy(arb())] offset: BFieldElement,
4448 ) {
4449 let omega = BFieldElement::primitive_root_of_unity(n as u64).unwrap();
4450 let domain = (0..n)
4451 .scan(offset, |acc: &mut BFieldElement, _| {
4452 let yld = *acc;
4453 *acc *= omega;
4454 Some(yld)
4455 })
4456 .collect_vec();
4457 prop_assert_eq!(
4458 Polynomial::fast_modular_coset_interpolate(&values, offset, &modulus),
4459 Polynomial::interpolate(&domain, &values).reduce(&modulus)
4460 )
4461 }
4462
4463 #[test]
4464 fn fast_modular_coset_interpolate_agrees_with_interpolate_then_reduce_concrete() {
4465 let logn = 8;
4466 let n = 1u64 << logn;
4467 let modulus = Polynomial::new(bfe_vec![2, 3, 1]);
4468 let values = (0..n).map(|i| BFieldElement::new(i / 5)).collect_vec();
4469 let offset = BFieldElement::new(7);
4470
4471 let omega = BFieldElement::primitive_root_of_unity(n).unwrap();
4472 let mut domain = bfe_vec![0; n as usize];
4473 domain[0] = offset;
4474 for i in 1..n as usize {
4475 domain[i] = domain[i - 1] * omega;
4476 }
4477 assert_eq!(
4478 Polynomial::interpolate(&domain, &values).reduce(&modulus),
4479 Polynomial::fast_modular_coset_interpolate(&values, offset, &modulus),
4480 )
4481 }
4482
4483 #[proptest(cases = 100)]
4484 fn coset_extrapolation_methods_agree_with_interpolate_then_evaluate(
4485 #[strategy(0usize..10)] _logn: usize,
4486 #[strategy(Just(1 << #_logn))] n: usize,
4487 #[strategy(vec(arb(), #n))] values: Vec<BFieldElement>,
4488 #[strategy(arb())] offset: BFieldElement,
4489 #[strategy(vec(arb(), 1..1000))] points: Vec<BFieldElement>,
4490 ) {
4491 let omega = BFieldElement::primitive_root_of_unity(n as u64).unwrap();
4492 let domain = (0..n)
4493 .scan(offset, |acc: &mut BFieldElement, _| {
4494 let yld = *acc;
4495 *acc *= omega;
4496 Some(yld)
4497 })
4498 .collect_vec();
4499 let fast_coset_extrapolation = Polynomial::fast_coset_extrapolate(offset, &values, &points);
4500 let naive_coset_extrapolation =
4501 Polynomial::naive_coset_extrapolate(offset, &values, &points);
4502 let interpolation_then_evaluation =
4503 Polynomial::interpolate(&domain, &values).batch_evaluate(&points);
4504 prop_assert_eq!(fast_coset_extrapolation.clone(), naive_coset_extrapolation);
4505 prop_assert_eq!(fast_coset_extrapolation, interpolation_then_evaluation);
4506 }
4507
4508 #[proptest]
4509 fn coset_extrapolate_and_batch_coset_extrapolate_agree(
4510 #[strategy(1usize..10)] _logn: usize,
4511 #[strategy(Just(1<<#_logn))] n: usize,
4512 #[strategy(0usize..5)] _m: usize,
4513 #[strategy(vec(arb(), #_m*#n))] codewords: Vec<BFieldElement>,
4514 #[strategy(vec(arb(), 0..20))] points: Vec<BFieldElement>,
4515 ) {
4516 let offset = BFieldElement::new(7);
4517
4518 let one_by_one_dispatch = codewords
4519 .chunks(n)
4520 .flat_map(|chunk| Polynomial::coset_extrapolate(offset, chunk, &points))
4521 .collect_vec();
4522 let batched_dispatch = Polynomial::batch_coset_extrapolate(offset, n, &codewords, &points);
4523 let par_batched_dispatch =
4524 Polynomial::par_batch_coset_extrapolate(offset, n, &codewords, &points);
4525 prop_assert_eq!(one_by_one_dispatch.clone(), batched_dispatch);
4526 prop_assert_eq!(one_by_one_dispatch, par_batched_dispatch);
4527
4528 let one_by_one_fast = codewords
4529 .chunks(n)
4530 .flat_map(|chunk| Polynomial::fast_coset_extrapolate(offset, chunk, &points))
4531 .collect_vec();
4532 let batched_fast = Polynomial::batch_fast_coset_extrapolate(offset, n, &codewords, &points);
4533 let par_batched_fast =
4534 Polynomial::par_batch_fast_coset_extrapolate(offset, n, &codewords, &points);
4535 prop_assert_eq!(one_by_one_fast.clone(), batched_fast);
4536 prop_assert_eq!(one_by_one_fast, par_batched_fast);
4537
4538 let one_by_one_naive = codewords
4539 .chunks(n)
4540 .flat_map(|chunk| Polynomial::naive_coset_extrapolate(offset, chunk, &points))
4541 .collect_vec();
4542 let batched_naive =
4543 Polynomial::batch_naive_coset_extrapolate(offset, n, &codewords, &points);
4544 let par_batched_naive =
4545 Polynomial::par_batch_naive_coset_extrapolate(offset, n, &codewords, &points);
4546 prop_assert_eq!(one_by_one_naive.clone(), batched_naive);
4547 prop_assert_eq!(one_by_one_naive, par_batched_naive);
4548 }
4549
4550 #[test]
4551 fn fast_modular_coset_interpolate_thresholds_relate_properly() {
4552 type BfePoly = Polynomial<'static, BFieldElement>;
4553
4554 let intt = BfePoly::FAST_MODULAR_COSET_INTERPOLATE_CUTOFF_THRESHOLD_PREFER_INTT;
4555 let lagrange = BfePoly::FAST_MODULAR_COSET_INTERPOLATE_CUTOFF_THRESHOLD_PREFER_LAGRANGE;
4556 assert!(intt > lagrange);
4557 }
4558
4559 #[proptest]
4560 fn interpolate_and_par_interpolate_agree(
4561 #[filter(!#points.is_empty())] points: Vec<BFieldElement>,
4562 #[strategy(vec(arb(), #points.len()))] domain: Vec<BFieldElement>,
4563 ) {
4564 let expected_interpolant = Polynomial::interpolate(&domain, &points);
4565 let observed_interpolant = Polynomial::par_interpolate(&domain, &points);
4566 prop_assert_eq!(expected_interpolant, observed_interpolant);
4567 }
4568
4569 #[proptest]
4570 fn batch_evaluate_agrees_with_par_batch_evalaute(
4571 polynomial: BfePoly,
4572 points: Vec<BFieldElement>,
4573 ) {
4574 prop_assert_eq!(
4575 polynomial.batch_evaluate(&points),
4576 polynomial.par_batch_evaluate(&points)
4577 );
4578 }
4579
4580 #[proptest(cases = 20)]
4581 fn polynomial_evaluation_and_barycentric_evaluation_are_equivalent(
4582 #[strategy(1_usize..8)] _log_num_coefficients: usize,
4583 #[strategy(1_usize..6)] log_expansion_factor: usize,
4584 #[strategy(vec(arb(), 1 << #_log_num_coefficients))] coefficients: Vec<XFieldElement>,
4585 #[strategy(arb())] indeterminate: XFieldElement,
4586 ) {
4587 let domain_len = coefficients.len() * (1 << log_expansion_factor);
4588 let domain_gen = BFieldElement::primitive_root_of_unity(domain_len.try_into()?).unwrap();
4589 let domain = (0..domain_len)
4590 .scan(XFieldElement::ONE, |acc, _| {
4591 let current = *acc;
4592 *acc *= domain_gen;
4593 Some(current)
4594 })
4595 .collect_vec();
4596
4597 let polynomial = Polynomial::new(coefficients);
4598 let codeword = polynomial.batch_evaluate(&domain);
4599 prop_assert_eq!(
4600 polynomial.evaluate_in_same_field(indeterminate),
4601 barycentric_evaluate(&codeword, indeterminate)
4602 );
4603 }
4604
4605 #[test]
4606 fn regular_evaluation_works_with_various_types() {
4607 let bfe_poly = Polynomial::new(bfe_vec![1]);
4608 let _: BFieldElement = bfe_poly.evaluate(bfe!(0));
4609 let _: XFieldElement = bfe_poly.evaluate(bfe!(0));
4610 let _: XFieldElement = bfe_poly.evaluate(xfe!(0));
4611
4612 let xfe_poly = Polynomial::new(xfe_vec![1]);
4613 let _: XFieldElement = xfe_poly.evaluate(bfe!(0));
4614 let _: XFieldElement = xfe_poly.evaluate(xfe!(0));
4615 }
4616
4617 #[test]
4618 fn barycentric_evaluation_works_with_many_types() {
4619 let bfe_codeword = bfe_array![1];
4620 let _ = barycentric_evaluate(&bfe_codeword, bfe!(0));
4621 let _ = barycentric_evaluate(&bfe_codeword, xfe!(0));
4622
4623 let xfe_codeword = xfe_array![[1; 3]];
4624 let _ = barycentric_evaluate(&xfe_codeword, bfe!(0));
4625 let _ = barycentric_evaluate(&xfe_codeword, xfe!(0));
4626 }
4627
4628 #[test]
4629 fn various_multiplications_work_with_various_types() {
4630 let b = Polynomial::<BFieldElement>::zero;
4631 let x = Polynomial::<XFieldElement>::zero;
4632
4633 let _ = b() * b();
4634 let _ = b() * x();
4635 let _ = x() * b();
4636 let _ = x() * x();
4637
4638 let _ = b().multiply(&b());
4639 let _ = b().multiply(&x());
4640 let _ = x().multiply(&b());
4641 let _ = x().multiply(&x());
4642
4643 let _ = b().naive_multiply(&b());
4644 let _ = b().naive_multiply(&x());
4645 let _ = x().naive_multiply(&b());
4646 let _ = x().naive_multiply(&x());
4647
4648 let _ = b().fast_multiply(&b());
4649 let _ = b().fast_multiply(&x());
4650 let _ = x().fast_multiply(&b());
4651 let _ = x().fast_multiply(&x());
4652 }
4653
4654 #[test]
4655 fn evaluating_polynomial_with_borrowed_coefficients_leaves_coefficients_borrowed() {
4656 let coefficients = bfe_vec![4, 5, 6];
4657 let poly = Polynomial::new_borrowed(&coefficients);
4658 let _ = poly.evaluate_in_same_field(bfe!(0));
4659 let _ = poly.evaluate::<_, XFieldElement>(bfe!(0));
4660 let _ = poly.fast_coset_evaluate(bfe!(3), 128);
4661
4662 let Cow::Borrowed(_) = poly.coefficients else {
4663 panic!("evaluating must not clone the coefficient vector")
4664 };
4665
4666 drop(coefficients);
4668 }
4669}