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
2711 use super::*;
2712 use crate::prelude::*;
2713 use crate::proptest_arbitrary_interop::arb;
2714 use crate::tests::proptest;
2715 use crate::tests::test;
2716
2717 type BfePoly = Polynomial<'static, BFieldElement>;
2719
2720 type XfePoly = Polynomial<'static, XFieldElement>;
2722
2723 impl proptest::arbitrary::Arbitrary for BfePoly {
2724 type Parameters = ();
2725
2726 fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {
2727 arb().boxed()
2728 }
2729
2730 type Strategy = BoxedStrategy<Self>;
2731 }
2732
2733 impl proptest::arbitrary::Arbitrary for XfePoly {
2734 type Parameters = ();
2735
2736 fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {
2737 arb().boxed()
2738 }
2739
2740 type Strategy = BoxedStrategy<Self>;
2741 }
2742
2743 #[macro_rules_attr::apply(test)]
2744 fn polynomial_can_be_debug_printed() {
2745 let polynomial = Polynomial::new(bfe_vec![1, 2, 3]);
2746 println!("{polynomial:?}");
2747 }
2748
2749 #[macro_rules_attr::apply(proptest)]
2750 fn unequal_hash_implies_unequal_polynomials(poly_0: BfePoly, poly_1: BfePoly) {
2751 let hash = |poly: &Polynomial<_>| {
2752 let mut hasher = std::hash::DefaultHasher::new();
2753 poly.hash(&mut hasher);
2754 std::hash::Hasher::finish(&hasher)
2755 };
2756
2757 if hash(&poly_0) != hash(&poly_1) {
2763 prop_assert_ne!(poly_0, poly_1);
2764 }
2765 }
2766
2767 #[macro_rules_attr::apply(test)]
2768 fn polynomial_display_test() {
2769 fn polynomial<const N: usize>(coeffs: [u64; N]) -> BfePoly {
2770 Polynomial::new(coeffs.map(BFieldElement::new).to_vec())
2771 }
2772
2773 assert_eq!("0", polynomial([]).to_string());
2774 assert_eq!("0", polynomial([0]).to_string());
2775 assert_eq!("0", polynomial([0, 0]).to_string());
2776
2777 assert_eq!("1", polynomial([1]).to_string());
2778 assert_eq!("2", polynomial([2, 0]).to_string());
2779 assert_eq!("3", polynomial([3, 0, 0]).to_string());
2780
2781 assert_eq!("x", polynomial([0, 1]).to_string());
2782 assert_eq!("2x", polynomial([0, 2]).to_string());
2783 assert_eq!("3x", polynomial([0, 3]).to_string());
2784
2785 assert_eq!("5x + 2", polynomial([2, 5]).to_string());
2786 assert_eq!("9x + 7", polynomial([7, 9, 0, 0, 0]).to_string());
2787
2788 assert_eq!("4x^4 + 3x^3", polynomial([0, 0, 0, 3, 4]).to_string());
2789 assert_eq!("2x^4 + 1", polynomial([1, 0, 0, 0, 2]).to_string());
2790 }
2791
2792 #[macro_rules_attr::apply(proptest)]
2793 fn leading_coefficient_of_zero_polynomial_is_none(#[strategy(0usize..30)] num_zeros: usize) {
2794 let coefficients = vec![BFieldElement::ZERO; num_zeros];
2795 let polynomial = Polynomial::new(coefficients);
2796 prop_assert!(polynomial.leading_coefficient().is_none());
2797 }
2798
2799 #[macro_rules_attr::apply(proptest)]
2800 fn leading_coefficient_of_non_zero_polynomial_is_some(
2801 polynomial: BfePoly,
2802 leading_coefficient: BFieldElement,
2803 #[strategy(0usize..30)] num_leading_zeros: usize,
2804 ) {
2805 let mut coefficients = polynomial.coefficients.into_owned();
2806 coefficients.push(leading_coefficient);
2807 coefficients.extend(vec![BFieldElement::ZERO; num_leading_zeros]);
2808 let polynomial_with_leading_zeros = Polynomial::new(coefficients);
2809 prop_assert_eq!(
2810 leading_coefficient,
2811 polynomial_with_leading_zeros.leading_coefficient().unwrap()
2812 );
2813 }
2814
2815 #[macro_rules_attr::apply(test)]
2816 fn normalizing_canonical_zero_polynomial_has_no_effect() {
2817 let mut zero_polynomial = Polynomial::<BFieldElement>::zero();
2818 zero_polynomial.normalize();
2819 assert_eq!(Polynomial::zero(), zero_polynomial);
2820 }
2821
2822 #[macro_rules_attr::apply(proptest)]
2823 fn spurious_leading_zeros_dont_affect_equality(
2824 polynomial: BfePoly,
2825 #[strategy(0usize..30)] num_leading_zeros: usize,
2826 ) {
2827 let mut coefficients = polynomial.clone().coefficients.into_owned();
2828 coefficients.extend(vec![BFieldElement::ZERO; num_leading_zeros]);
2829 let polynomial_with_leading_zeros = Polynomial::new(coefficients);
2830
2831 prop_assert_eq!(polynomial, polynomial_with_leading_zeros);
2832 }
2833
2834 #[macro_rules_attr::apply(proptest)]
2835 fn normalizing_removes_spurious_leading_zeros(
2836 polynomial: BfePoly,
2837 #[filter(!#leading_coefficient.is_zero())] leading_coefficient: BFieldElement,
2838 #[strategy(0usize..30)] num_leading_zeros: usize,
2839 ) {
2840 let mut coefficients = polynomial.clone().coefficients.into_owned();
2841 coefficients.push(leading_coefficient);
2842 coefficients.extend(vec![BFieldElement::ZERO; num_leading_zeros]);
2843 let mut polynomial_with_leading_zeros = Polynomial::new(coefficients);
2844 polynomial_with_leading_zeros.normalize();
2845
2846 let num_inserted_coefficients = 1;
2847 let expected_num_coefficients = polynomial.coefficients.len() + num_inserted_coefficients;
2848 let num_coefficients = polynomial_with_leading_zeros.coefficients.len();
2849
2850 prop_assert_eq!(expected_num_coefficients, num_coefficients);
2851 }
2852
2853 #[macro_rules_attr::apply(test)]
2854 fn accessing_coefficients_of_empty_polynomial_gives_empty_slice() {
2855 let poly = BfePoly::new(vec![]);
2856 assert!(poly.coefficients().is_empty());
2857 assert!(poly.into_coefficients().is_empty());
2858 }
2859
2860 #[macro_rules_attr::apply(proptest)]
2861 fn accessing_coefficients_of_polynomial_with_only_zero_coefficients_gives_empty_slice(
2862 #[strategy(0_usize..30)] num_zeros: usize,
2863 ) {
2864 let poly = Polynomial::new(vec![BFieldElement::ZERO; num_zeros]);
2865 prop_assert!(poly.coefficients().is_empty());
2866 prop_assert!(poly.into_coefficients().is_empty());
2867 }
2868
2869 #[macro_rules_attr::apply(proptest)]
2870 fn accessing_the_coefficients_is_equivalent_to_normalizing_then_raw_access(
2871 mut coefficients: Vec<BFieldElement>,
2872 #[strategy(0_usize..30)] num_leading_zeros: usize,
2873 ) {
2874 coefficients.extend(vec![BFieldElement::ZERO; num_leading_zeros]);
2875 let mut polynomial = Polynomial::new(coefficients);
2876
2877 let accessed_coefficients_borrow = polynomial.coefficients().to_vec();
2878 let accessed_coefficients_owned = polynomial.clone().into_coefficients();
2879
2880 polynomial.normalize();
2881 let raw_coefficients = polynomial.coefficients.into_owned();
2882
2883 prop_assert_eq!(&raw_coefficients, &accessed_coefficients_borrow);
2884 prop_assert_eq!(&raw_coefficients, &accessed_coefficients_owned);
2885 }
2886
2887 #[macro_rules_attr::apply(test)]
2888 fn x_to_the_0_is_constant_1() {
2889 assert!(Polynomial::<BFieldElement>::x_to_the(0).is_one());
2890 assert!(Polynomial::<XFieldElement>::x_to_the(0).is_one());
2891 }
2892
2893 #[macro_rules_attr::apply(test)]
2894 fn x_to_the_1_is_x() {
2895 assert!(Polynomial::<BFieldElement>::x_to_the(1).is_x());
2896 assert!(Polynomial::<XFieldElement>::x_to_the(1).is_x());
2897 }
2898
2899 #[macro_rules_attr::apply(proptest)]
2900 fn x_to_the_n_to_the_m_is_homomorphic(
2901 #[strategy(0_usize..50)] n: usize,
2902 #[strategy(0_usize..50)] m: usize,
2903 ) {
2904 let to_the_n_times_m = Polynomial::<BFieldElement>::x_to_the(n * m);
2905 let to_the_n_then_to_the_m = Polynomial::x_to_the(n).pow(m as u32);
2906 prop_assert_eq!(to_the_n_times_m, to_the_n_then_to_the_m);
2907 }
2908
2909 #[macro_rules_attr::apply(test)]
2910 fn scaling_a_polynomial_works_with_different_fields_as_the_offset() {
2911 let bfe_poly = Polynomial::new(bfe_vec![0, 1, 2]);
2912 let _ = bfe_poly.scale(bfe!(42));
2913 let _ = bfe_poly.scale(xfe!(42));
2914
2915 let xfe_poly = Polynomial::new(xfe_vec![0, 1, 2]);
2916 let _ = xfe_poly.scale(bfe!(42));
2917 let _ = xfe_poly.scale(xfe!(42));
2918 }
2919
2920 #[macro_rules_attr::apply(proptest)]
2921 fn polynomial_scaling_is_equivalent_in_extension_field(
2922 bfe_polynomial: BfePoly,
2923 alpha: BFieldElement,
2924 ) {
2925 let bfe_coefficients = bfe_polynomial.coefficients.iter();
2926 let xfe_coefficients = bfe_coefficients.map(|bfe| bfe.lift()).collect();
2927 let xfe_polynomial = Polynomial::<XFieldElement>::new(xfe_coefficients);
2928
2929 let xfe_poly_bfe_scalar = xfe_polynomial.scale(alpha);
2930 let bfe_poly_xfe_scalar = bfe_polynomial.scale(alpha.lift());
2931 prop_assert_eq!(xfe_poly_bfe_scalar, bfe_poly_xfe_scalar);
2932 }
2933
2934 #[macro_rules_attr::apply(proptest)]
2935 fn evaluating_scaled_polynomial_is_equivalent_to_evaluating_original_in_offset_point(
2936 polynomial: BfePoly,
2937 alpha: BFieldElement,
2938 x: BFieldElement,
2939 ) {
2940 let scaled_polynomial = polynomial.scale(alpha);
2941 prop_assert_eq!(
2942 polynomial.evaluate_in_same_field(alpha * x),
2943 scaled_polynomial.evaluate_in_same_field(x)
2944 );
2945 }
2946
2947 #[macro_rules_attr::apply(proptest)]
2948 fn polynomial_multiplication_with_scalar_is_equivalent_for_the_two_methods(
2949 mut polynomial: BfePoly,
2950 scalar: BFieldElement,
2951 ) {
2952 let new_polynomial = polynomial.scalar_mul(scalar);
2953 polynomial.scalar_mul_mut(scalar);
2954 prop_assert_eq!(polynomial, new_polynomial);
2955 }
2956
2957 #[macro_rules_attr::apply(proptest)]
2958 fn polynomial_multiplication_with_scalar_is_equivalent_for_all_mul_traits(
2959 polynomial: BfePoly,
2960 scalar: BFieldElement,
2961 ) {
2962 let bfe_rhs = polynomial.clone() * scalar;
2963 let xfe_rhs = polynomial.clone() * scalar.lift();
2964 let bfe_lhs = scalar * polynomial.clone();
2965 let xfe_lhs = scalar.lift() * polynomial;
2966
2967 prop_assert_eq!(bfe_lhs.clone(), bfe_rhs);
2968 prop_assert_eq!(xfe_lhs.clone(), xfe_rhs);
2969
2970 prop_assert_eq!(bfe_lhs * XFieldElement::ONE, xfe_lhs);
2971 }
2972
2973 #[macro_rules_attr::apply(test)]
2974 fn polynomial_multiplication_with_scalar_works_for_various_types() {
2975 let bfe_poly = Polynomial::new(bfe_vec![0, 1, 2]);
2976 let _: Polynomial<BFieldElement> = bfe_poly.scalar_mul(bfe!(42));
2977 let _: Polynomial<XFieldElement> = bfe_poly.scalar_mul(xfe!(42));
2978
2979 let xfe_poly = Polynomial::new(xfe_vec![0, 1, 2]);
2980 let _: Polynomial<XFieldElement> = xfe_poly.scalar_mul(bfe!(42));
2981 let _: Polynomial<XFieldElement> = xfe_poly.scalar_mul(xfe!(42));
2982
2983 let mut bfe_poly = bfe_poly;
2984 bfe_poly.scalar_mul_mut(bfe!(42));
2985
2986 let mut xfe_poly = xfe_poly;
2987 xfe_poly.scalar_mul_mut(bfe!(42));
2988 xfe_poly.scalar_mul_mut(xfe!(42));
2989 }
2990
2991 #[macro_rules_attr::apply(proptest)]
2992 fn slow_lagrange_interpolation(
2993 polynomial: BfePoly,
2994 #[strategy(Just(#polynomial.coefficients.len().max(1)))] _min_points: usize,
2995 #[any(size_range(#_min_points..8 * #_min_points).lift())] points: Vec<BFieldElement>,
2996 ) {
2997 let evaluations = points
2998 .into_iter()
2999 .map(|x| (x, polynomial.evaluate(x)))
3000 .collect_vec();
3001 let interpolation_polynomial = Polynomial::lagrange_interpolate_zipped(&evaluations);
3002 prop_assert_eq!(polynomial, interpolation_polynomial);
3003 }
3004
3005 #[macro_rules_attr::apply(proptest)]
3006 fn three_colinear_points_are_colinear(
3007 p0: (BFieldElement, BFieldElement),
3008 #[filter(#p0.0 != #p1.0)] p1: (BFieldElement, BFieldElement),
3009 #[filter(#p0.0 != #p2_x && #p1.0 != #p2_x)] p2_x: BFieldElement,
3010 ) {
3011 let line = Polynomial::lagrange_interpolate_zipped(&[p0, p1]);
3012 let p2 = (p2_x, line.evaluate(p2_x));
3013 prop_assert!(Polynomial::are_colinear(&[p0, p1, p2]));
3014 }
3015
3016 #[macro_rules_attr::apply(proptest)]
3017 fn three_non_colinear_points_are_not_colinear(
3018 p0: (BFieldElement, BFieldElement),
3019 #[filter(#p0.0 != #p1.0)] p1: (BFieldElement, BFieldElement),
3020 #[filter(#p0.0 != #p2_x && #p1.0 != #p2_x)] p2_x: BFieldElement,
3021 #[filter(!#disturbance.is_zero())] disturbance: BFieldElement,
3022 ) {
3023 let line = Polynomial::lagrange_interpolate_zipped(&[p0, p1]);
3024 let p2 = (p2_x, line.evaluate_in_same_field(p2_x) + disturbance);
3025 prop_assert!(!Polynomial::are_colinear(&[p0, p1, p2]));
3026 }
3027
3028 #[macro_rules_attr::apply(proptest)]
3029 fn colinearity_check_needs_at_least_three_points(
3030 p0: (BFieldElement, BFieldElement),
3031 #[filter(#p0.0 != #p1.0)] p1: (BFieldElement, BFieldElement),
3032 ) {
3033 prop_assert!(!Polynomial::<BFieldElement>::are_colinear(&[]));
3034 prop_assert!(!Polynomial::are_colinear(&[p0]));
3035 prop_assert!(!Polynomial::are_colinear(&[p0, p1]));
3036 }
3037
3038 #[macro_rules_attr::apply(proptest)]
3039 fn colinearity_check_with_repeated_points_fails(
3040 p0: (BFieldElement, BFieldElement),
3041 #[filter(#p0.0 != #p1.0)] p1: (BFieldElement, BFieldElement),
3042 ) {
3043 prop_assert!(!Polynomial::are_colinear(&[p0, p1, p1]));
3044 }
3045
3046 #[macro_rules_attr::apply(proptest)]
3047 fn colinear_points_are_colinear(
3048 p0: (BFieldElement, BFieldElement),
3049 #[filter(#p0.0 != #p1.0)] p1: (BFieldElement, BFieldElement),
3050 #[filter(!#additional_points_xs.contains(&#p0.0))]
3051 #[filter(!#additional_points_xs.contains(&#p1.0))]
3052 #[filter(#additional_points_xs.iter().all_unique())]
3053 #[any(size_range(1..100).lift())]
3054 additional_points_xs: Vec<BFieldElement>,
3055 ) {
3056 let line = Polynomial::lagrange_interpolate_zipped(&[p0, p1]);
3057 let additional_points = additional_points_xs
3058 .into_iter()
3059 .map(|x| (x, line.evaluate(x)))
3060 .collect_vec();
3061 let all_points = [p0, p1].into_iter().chain(additional_points).collect_vec();
3062 prop_assert!(Polynomial::are_colinear(&all_points));
3063 }
3064
3065 #[macro_rules_attr::apply(test)]
3066 #[should_panic(expected = "Line must not be parallel to y-axis")]
3067 fn getting_point_on_invalid_line_fails() {
3068 let one = BFieldElement::ONE;
3069 let two = one + one;
3070 let three = two + one;
3071 Polynomial::<BFieldElement>::get_colinear_y((one, one), (one, three), two);
3072 }
3073
3074 #[macro_rules_attr::apply(proptest)]
3075 fn point_on_line_and_colinear_point_are_identical(
3076 p0: (BFieldElement, BFieldElement),
3077 #[filter(#p0.0 != #p1.0)] p1: (BFieldElement, BFieldElement),
3078 x: BFieldElement,
3079 ) {
3080 let line = Polynomial::lagrange_interpolate_zipped(&[p0, p1]);
3081 let y = line.evaluate_in_same_field(x);
3082 let y_from_get_point_on_line = Polynomial::get_colinear_y(p0, p1, x);
3083 prop_assert_eq!(y, y_from_get_point_on_line);
3084 }
3085
3086 #[macro_rules_attr::apply(proptest)]
3087 fn point_on_line_and_colinear_point_are_identical_in_extension_field(
3088 p0: (XFieldElement, XFieldElement),
3089 #[filter(#p0.0 != #p1.0)] p1: (XFieldElement, XFieldElement),
3090 x: XFieldElement,
3091 ) {
3092 let line = Polynomial::lagrange_interpolate_zipped(&[p0, p1]);
3093 let y = line.evaluate_in_same_field(x);
3094 let y_from_get_point_on_line = Polynomial::get_colinear_y(p0, p1, x);
3095 prop_assert_eq!(y, y_from_get_point_on_line);
3096 }
3097
3098 #[macro_rules_attr::apply(proptest)]
3099 fn shifting_polynomial_coefficients_by_zero_is_the_same_as_not_shifting_it(poly: BfePoly) {
3100 prop_assert_eq!(poly.clone(), poly.shift_coefficients(0));
3101 }
3102
3103 #[macro_rules_attr::apply(proptest)]
3104 fn shifting_polynomial_one_is_equivalent_to_raising_polynomial_x_to_the_power_of_the_shift(
3105 #[strategy(0usize..30)] shift: usize,
3106 ) {
3107 let shifted_one = Polynomial::one().shift_coefficients(shift);
3108 let x_to_the_shift = Polynomial::<BFieldElement>::from([0, 1]).pow(shift as u32);
3109 prop_assert_eq!(shifted_one, x_to_the_shift);
3110 }
3111
3112 #[macro_rules_attr::apply(test)]
3113 fn polynomial_shift_test() {
3114 fn polynomial<const N: usize>(coeffs: [u64; N]) -> BfePoly {
3115 Polynomial::new(coeffs.map(BFieldElement::new).to_vec())
3116 }
3117
3118 assert_eq!(
3119 polynomial([17, 14]),
3120 polynomial([17, 14]).shift_coefficients(0)
3121 );
3122 assert_eq!(
3123 polynomial([0, 17, 14]),
3124 polynomial([17, 14]).shift_coefficients(1)
3125 );
3126 assert_eq!(
3127 polynomial([0, 0, 0, 0, 17, 14]),
3128 polynomial([17, 14]).shift_coefficients(4)
3129 );
3130
3131 let poly = polynomial([17, 14]);
3132 let poly_shift_0 = poly.clone().shift_coefficients(0);
3133 assert_eq!(polynomial([17, 14]), poly_shift_0);
3134
3135 let poly_shift_1 = poly.clone().shift_coefficients(1);
3136 assert_eq!(polynomial([0, 17, 14]), poly_shift_1);
3137
3138 let poly_shift_4 = poly.clone().shift_coefficients(4);
3139 assert_eq!(polynomial([0, 0, 0, 0, 17, 14]), poly_shift_4);
3140 }
3141
3142 #[macro_rules_attr::apply(proptest)]
3143 fn shifting_a_polynomial_means_prepending_zeros_to_its_coefficients(
3144 poly: BfePoly,
3145 #[strategy(0usize..30)] shift: usize,
3146 ) {
3147 let shifted_poly = poly.clone().shift_coefficients(shift);
3148 let mut expected_coefficients = vec![BFieldElement::ZERO; shift];
3149 expected_coefficients.extend(poly.coefficients.to_vec());
3150 prop_assert_eq!(expected_coefficients, shifted_poly.coefficients.to_vec());
3151 }
3152
3153 #[macro_rules_attr::apply(proptest)]
3154 fn any_polynomial_to_the_power_of_zero_is_one(poly: BfePoly) {
3155 let poly_to_the_zero = poly.pow(0);
3156 prop_assert_eq!(Polynomial::one(), poly_to_the_zero);
3157 }
3158
3159 #[macro_rules_attr::apply(proptest)]
3160 fn any_polynomial_to_the_power_one_is_itself(poly: BfePoly) {
3161 let poly_to_the_one = poly.pow(1);
3162 prop_assert_eq!(poly, poly_to_the_one);
3163 }
3164
3165 #[macro_rules_attr::apply(proptest)]
3166 fn polynomial_one_to_any_power_is_one(#[strategy(0u32..30)] exponent: u32) {
3167 let one_to_the_exponent = Polynomial::<BFieldElement>::one().pow(exponent);
3168 prop_assert_eq!(Polynomial::one(), one_to_the_exponent);
3169 }
3170
3171 #[macro_rules_attr::apply(test)]
3172 fn pow_test() {
3173 fn polynomial<const N: usize>(coeffs: [u64; N]) -> BfePoly {
3174 Polynomial::new(coeffs.map(BFieldElement::new).to_vec())
3175 }
3176
3177 let pol = polynomial([0, 14, 0, 4, 0, 8, 0, 3]);
3178 let pol_squared = polynomial([0, 0, 196, 0, 112, 0, 240, 0, 148, 0, 88, 0, 48, 0, 9]);
3179 let pol_cubed = polynomial([
3180 0, 0, 0, 2744, 0, 2352, 0, 5376, 0, 4516, 0, 4080, 0, 2928, 0, 1466, 0, 684, 0, 216, 0,
3181 27,
3182 ]);
3183
3184 assert_eq!(pol_squared, pol.pow(2));
3185 assert_eq!(pol_cubed, pol.pow(3));
3186
3187 let parabola = polynomial([5, 41, 19]);
3188 let parabola_squared = polynomial([25, 410, 1871, 1558, 361]);
3189 assert_eq!(parabola_squared, parabola.pow(2));
3190 }
3191
3192 #[macro_rules_attr::apply(proptest)]
3193 fn pow_arbitrary_test(poly: BfePoly, #[strategy(0u32..15)] exponent: u32) {
3194 let actual = poly.pow(exponent);
3195 let fast_actual = poly.fast_pow(exponent);
3196 let mut expected = Polynomial::one();
3197 for _ in 0..exponent {
3198 expected = expected.clone() * poly.clone();
3199 }
3200
3201 prop_assert_eq!(expected.clone(), actual);
3202 prop_assert_eq!(expected, fast_actual);
3203 }
3204
3205 #[macro_rules_attr::apply(proptest)]
3206 fn polynomial_zero_is_neutral_element_for_addition(a: BfePoly) {
3207 prop_assert_eq!(a.clone() + Polynomial::zero(), a.clone());
3208 prop_assert_eq!(Polynomial::zero() + a.clone(), a);
3209 }
3210
3211 #[macro_rules_attr::apply(proptest)]
3212 fn polynomial_one_is_neutral_element_for_multiplication(a: BfePoly) {
3213 prop_assert_eq!(a.clone() * Polynomial::<BFieldElement>::one(), a.clone());
3214 prop_assert_eq!(Polynomial::<BFieldElement>::one() * a.clone(), a);
3215 }
3216
3217 #[macro_rules_attr::apply(proptest)]
3218 fn multiplication_by_zero_is_zero(a: BfePoly) {
3219 let zero = Polynomial::<BFieldElement>::zero();
3220
3221 prop_assert_eq!(Polynomial::zero(), a.clone() * zero.clone());
3222 prop_assert_eq!(Polynomial::zero(), zero * a);
3223 }
3224
3225 #[macro_rules_attr::apply(proptest)]
3226 fn polynomial_addition_is_commutative(a: BfePoly, b: BfePoly) {
3227 prop_assert_eq!(a.clone() + b.clone(), b + a);
3228 }
3229
3230 #[macro_rules_attr::apply(proptest)]
3231 fn polynomial_multiplication_is_commutative(a: BfePoly, b: BfePoly) {
3232 prop_assert_eq!(a.clone() * b.clone(), b * a);
3233 }
3234
3235 #[macro_rules_attr::apply(proptest)]
3236 fn polynomial_addition_is_associative(a: BfePoly, b: BfePoly, c: BfePoly) {
3237 prop_assert_eq!((a.clone() + b.clone()) + c.clone(), a + (b + c));
3238 }
3239
3240 #[macro_rules_attr::apply(proptest)]
3241 fn polynomial_multiplication_is_associative(a: BfePoly, b: BfePoly, c: BfePoly) {
3242 prop_assert_eq!((a.clone() * b.clone()) * c.clone(), a * (b * c));
3243 }
3244
3245 #[macro_rules_attr::apply(proptest)]
3246 fn polynomial_multiplication_is_distributive(a: BfePoly, b: BfePoly, c: BfePoly) {
3247 prop_assert_eq!(
3248 (a.clone() + b.clone()) * c.clone(),
3249 (a * c.clone()) + (b * c)
3250 );
3251 }
3252
3253 #[macro_rules_attr::apply(proptest)]
3254 fn polynomial_subtraction_of_self_is_zero(a: BfePoly) {
3255 prop_assert_eq!(Polynomial::zero(), a.clone() - a);
3256 }
3257
3258 #[macro_rules_attr::apply(proptest)]
3259 fn polynomial_division_by_self_is_one(#[filter(!#a.is_zero())] a: BfePoly) {
3260 prop_assert_eq!(Polynomial::one(), a.clone() / a);
3261 }
3262
3263 #[macro_rules_attr::apply(proptest)]
3264 fn polynomial_division_removes_common_factors(a: BfePoly, #[filter(!#b.is_zero())] b: BfePoly) {
3265 prop_assert_eq!(a.clone(), a * b.clone() / b);
3266 }
3267
3268 #[macro_rules_attr::apply(proptest)]
3269 fn polynomial_multiplication_raises_degree_at_maximum_to_sum_of_degrees(
3270 a: BfePoly,
3271 b: BfePoly,
3272 ) {
3273 let sum_of_degrees = (a.degree() + b.degree()).max(-1);
3274 prop_assert!((a * b).degree() <= sum_of_degrees);
3275 }
3276
3277 #[macro_rules_attr::apply(test)]
3278 fn leading_zeros_dont_affect_polynomial_division() {
3279 fn polynomial<const N: usize>(coeffs: [u64; N]) -> BfePoly {
3284 Polynomial::new(coeffs.map(BFieldElement::new).to_vec())
3285 }
3286
3287 let numerator = polynomial([1, BFieldElement::P - 1, 0, 1]);
3289 let numerator_with_leading_zero = polynomial([1, BFieldElement::P - 1, 0, 1, 0]);
3290
3291 let divisor_normalized = polynomial([0, 1]);
3292 let divisor_not_normalized = polynomial([0, 1, 0]);
3293 let divisor_more_leading_zeros = polynomial([0, 1, 0, 0, 0, 0, 0, 0, 0]);
3294
3295 let expected = polynomial([BFieldElement::P - 1, 0, 1]);
3296
3297 assert_eq!(expected, numerator.clone() / divisor_normalized.clone());
3299 assert_eq!(expected, numerator.clone() / divisor_not_normalized.clone());
3300 assert_eq!(expected, numerator / divisor_more_leading_zeros.clone());
3301
3302 let res_numerator_not_normalized_0 =
3304 numerator_with_leading_zero.clone() / divisor_normalized;
3305 let res_numerator_not_normalized_1 =
3306 numerator_with_leading_zero.clone() / divisor_not_normalized;
3307 let res_numerator_not_normalized_2 =
3308 numerator_with_leading_zero / divisor_more_leading_zeros;
3309 assert_eq!(expected, res_numerator_not_normalized_0);
3310 assert_eq!(expected, res_numerator_not_normalized_1);
3311 assert_eq!(expected, res_numerator_not_normalized_2);
3312 }
3313
3314 #[macro_rules_attr::apply(proptest)]
3315 fn leading_coefficient_of_truncated_polynomial_is_same_as_original_leading_coefficient(
3316 poly: BfePoly,
3317 #[strategy(..50_usize)] truncation_point: usize,
3318 ) {
3319 let Some(lc) = poly.leading_coefficient() else {
3320 let reason = "test is only sensible if polynomial has a leading coefficient";
3321 return Err(TestCaseError::Reject(reason.into()));
3322 };
3323 let truncated_poly = poly.truncate(truncation_point);
3324 let Some(trunc_lc) = truncated_poly.leading_coefficient() else {
3325 let reason = "test is only sensible if truncated polynomial has a leading coefficient";
3326 return Err(TestCaseError::Reject(reason.into()));
3327 };
3328 prop_assert_eq!(lc, trunc_lc);
3329 }
3330
3331 #[macro_rules_attr::apply(proptest)]
3332 fn truncated_polynomial_is_of_degree_min_of_truncation_point_and_poly_degree(
3333 poly: BfePoly,
3334 #[strategy(..50_usize)] truncation_point: usize,
3335 ) {
3336 let expected_degree = poly.degree().min(truncation_point.try_into().unwrap());
3337 prop_assert_eq!(expected_degree, poly.truncate(truncation_point).degree());
3338 }
3339
3340 #[macro_rules_attr::apply(proptest)]
3341 fn truncating_zero_polynomial_gives_zero_polynomial(
3342 #[strategy(..50_usize)] truncation_point: usize,
3343 ) {
3344 let poly = Polynomial::<BFieldElement>::zero().truncate(truncation_point);
3345 prop_assert!(poly.is_zero());
3346 }
3347
3348 #[macro_rules_attr::apply(proptest)]
3349 fn truncation_negates_degree_shifting(
3350 #[strategy(0_usize..30)] shift: usize,
3351 #[strategy(..50_usize)] truncation_point: usize,
3352 #[filter(#poly.degree() >= #truncation_point as isize)] poly: BfePoly,
3353 ) {
3354 prop_assert_eq!(
3355 poly.truncate(truncation_point),
3356 poly.shift_coefficients(shift).truncate(truncation_point)
3357 );
3358 }
3359
3360 #[macro_rules_attr::apply(proptest)]
3361 fn zero_polynomial_mod_any_power_of_x_is_zero_polynomial(power: usize) {
3362 let must_be_zero = Polynomial::<BFieldElement>::zero().mod_x_to_the_n(power);
3363 prop_assert!(must_be_zero.is_zero());
3364 }
3365
3366 #[macro_rules_attr::apply(proptest)]
3367 fn polynomial_mod_some_power_of_x_results_in_polynomial_of_degree_one_less_than_power(
3368 #[filter(!#poly.is_zero())] poly: BfePoly,
3369 #[strategy(..=usize::try_from(#poly.degree()).unwrap())] power: usize,
3370 ) {
3371 let remainder = poly.mod_x_to_the_n(power);
3372 prop_assert_eq!(isize::try_from(power).unwrap() - 1, remainder.degree());
3373 }
3374
3375 #[macro_rules_attr::apply(proptest)]
3376 fn polynomial_mod_some_power_of_x_shares_low_degree_terms_coefficients_with_original_polynomial(
3377 #[filter(!#poly.is_zero())] poly: BfePoly,
3378 power: usize,
3379 ) {
3380 let remainder = poly.mod_x_to_the_n(power);
3381 let min_num_coefficients = poly.coefficients.len().min(remainder.coefficients.len());
3382 prop_assert_eq!(
3383 &poly.coefficients[..min_num_coefficients],
3384 &remainder.coefficients[..min_num_coefficients]
3385 );
3386 }
3387
3388 #[macro_rules_attr::apply(proptest)]
3389 fn fast_multiplication_by_zero_gives_zero(poly: BfePoly) {
3390 let product = poly.fast_multiply(&Polynomial::<BFieldElement>::zero());
3391 prop_assert_eq!(Polynomial::zero(), product);
3392 }
3393
3394 #[macro_rules_attr::apply(proptest)]
3395 fn fast_multiplication_by_one_gives_self(poly: BfePoly) {
3396 let product = poly.fast_multiply(&Polynomial::<BFieldElement>::one());
3397 prop_assert_eq!(poly, product);
3398 }
3399
3400 #[macro_rules_attr::apply(proptest)]
3401 fn fast_multiplication_is_commutative(a: BfePoly, b: BfePoly) {
3402 prop_assert_eq!(a.fast_multiply(&b), b.fast_multiply(&a));
3403 }
3404
3405 #[macro_rules_attr::apply(proptest)]
3406 fn fast_multiplication_and_normal_multiplication_are_equivalent(a: BfePoly, b: BfePoly) {
3407 let product = a.fast_multiply(&b);
3408 prop_assert_eq!(a * b, product);
3409 }
3410
3411 #[macro_rules_attr::apply(proptest)]
3412 fn batch_multiply_agrees_with_iterative_multiply(a: Vec<BfePoly>) {
3413 let mut acc = Polynomial::one();
3414 for factor in &a {
3415 acc = acc.multiply(factor);
3416 }
3417 prop_assert_eq!(acc, Polynomial::batch_multiply(&a));
3418 }
3419
3420 #[macro_rules_attr::apply(proptest)]
3421 fn par_batch_multiply_agrees_with_batch_multiply(a: Vec<BfePoly>) {
3422 prop_assert_eq!(
3423 Polynomial::batch_multiply(&a),
3424 Polynomial::par_batch_multiply(&a)
3425 );
3426 }
3427
3428 #[macro_rules_attr::apply(proptest(cases = 50))]
3429 fn naive_zerofier_and_fast_zerofier_are_identical(
3430 #[any(size_range(..Polynomial::<BFieldElement>::FAST_ZEROFIER_CUTOFF_THRESHOLD * 2).lift())]
3431 roots: Vec<BFieldElement>,
3432 ) {
3433 let naive_zerofier = Polynomial::naive_zerofier(&roots);
3434 let fast_zerofier = Polynomial::fast_zerofier(&roots);
3435 prop_assert_eq!(naive_zerofier, fast_zerofier);
3436 }
3437
3438 #[macro_rules_attr::apply(proptest(cases = 50))]
3439 fn smart_zerofier_and_fast_zerofier_are_identical(
3440 #[any(size_range(..Polynomial::<BFieldElement>::FAST_ZEROFIER_CUTOFF_THRESHOLD * 2).lift())]
3441 roots: Vec<BFieldElement>,
3442 ) {
3443 let smart_zerofier = Polynomial::smart_zerofier(&roots);
3444 let fast_zerofier = Polynomial::fast_zerofier(&roots);
3445 prop_assert_eq!(smart_zerofier, fast_zerofier);
3446 }
3447
3448 #[macro_rules_attr::apply(proptest(cases = 50))]
3449 fn zerofier_and_naive_zerofier_are_identical(
3450 #[any(size_range(..Polynomial::<BFieldElement>::FAST_ZEROFIER_CUTOFF_THRESHOLD * 2).lift())]
3451 roots: Vec<BFieldElement>,
3452 ) {
3453 let zerofier = Polynomial::zerofier(&roots);
3454 let naive_zerofier = Polynomial::naive_zerofier(&roots);
3455 prop_assert_eq!(zerofier, naive_zerofier);
3456 }
3457
3458 #[macro_rules_attr::apply(proptest(cases = 50))]
3459 fn zerofier_is_zero_only_on_domain(
3460 #[any(size_range(..1024).lift())] domain: Vec<BFieldElement>,
3461 #[filter(#out_of_domain_points.iter().all(|p| !#domain.contains(p)))]
3462 out_of_domain_points: Vec<BFieldElement>,
3463 ) {
3464 let zerofier = Polynomial::zerofier(&domain);
3465 for point in domain {
3466 prop_assert_eq!(BFieldElement::ZERO, zerofier.evaluate(point));
3467 }
3468 for point in out_of_domain_points {
3469 prop_assert_ne!(BFieldElement::ZERO, zerofier.evaluate(point));
3470 }
3471 }
3472
3473 #[macro_rules_attr::apply(proptest)]
3474 fn zerofier_has_leading_coefficient_one(domain: Vec<BFieldElement>) {
3475 let zerofier = Polynomial::zerofier(&domain);
3476 prop_assert_eq!(BFieldElement::ONE, zerofier.leading_coefficient().unwrap());
3477 }
3478 #[macro_rules_attr::apply(proptest)]
3479 fn par_zerofier_agrees_with_zerofier(domain: Vec<BFieldElement>) {
3480 prop_assert_eq!(
3481 Polynomial::zerofier(&domain),
3482 Polynomial::par_zerofier(&domain)
3483 );
3484 }
3485
3486 #[macro_rules_attr::apply(test)]
3487 fn fast_evaluate_on_hardcoded_domain_and_polynomial() {
3488 let domain = bfe_array![6, 12];
3489 let x_to_the_5_plus_x_to_the_3 = Polynomial::new(bfe_vec![0, 0, 0, 1, 0, 1]);
3490
3491 let manual_evaluations = domain.map(|x| x.mod_pow(5) + x.mod_pow(3)).to_vec();
3492 let fast_evaluations = x_to_the_5_plus_x_to_the_3.batch_evaluate(&domain);
3493 assert_eq!(manual_evaluations, fast_evaluations);
3494 }
3495
3496 #[macro_rules_attr::apply(proptest)]
3497 fn slow_and_fast_polynomial_evaluation_are_equivalent(
3498 poly: BfePoly,
3499 #[any(size_range(..1024).lift())] domain: Vec<BFieldElement>,
3500 ) {
3501 let evaluations = domain
3502 .iter()
3503 .map(|&x| poly.evaluate_in_same_field(x))
3504 .collect_vec();
3505 let fast_evaluations = poly.batch_evaluate(&domain);
3506 prop_assert_eq!(evaluations, fast_evaluations);
3507 }
3508
3509 #[macro_rules_attr::apply(test)]
3510 #[should_panic(expected = "zero points")]
3511 fn interpolation_through_no_points_is_impossible() {
3512 let _ = Polynomial::<BFieldElement>::interpolate(&[], &[]);
3513 }
3514
3515 #[macro_rules_attr::apply(test)]
3516 #[should_panic(expected = "zero points")]
3517 fn lagrange_interpolation_through_no_points_is_impossible() {
3518 let _ = Polynomial::<BFieldElement>::lagrange_interpolate(&[], &[]);
3519 }
3520
3521 #[macro_rules_attr::apply(test)]
3522 #[should_panic(expected = "zero points")]
3523 fn zipped_lagrange_interpolation_through_no_points_is_impossible() {
3524 let _ = Polynomial::<BFieldElement>::lagrange_interpolate_zipped(&[]);
3525 }
3526
3527 #[macro_rules_attr::apply(test)]
3528 #[should_panic(expected = "zero points")]
3529 fn fast_interpolation_through_no_points_is_impossible() {
3530 let _ = Polynomial::<BFieldElement>::fast_interpolate(&[], &[]);
3531 }
3532
3533 #[macro_rules_attr::apply(test)]
3534 #[should_panic(expected = "equal length")]
3535 fn interpolation_with_domain_size_different_from_number_of_points_is_impossible() {
3536 let domain = bfe_array![1, 2, 3];
3537 let points = bfe_array![1, 2];
3538 let _ = Polynomial::interpolate(&domain, &points);
3539 }
3540
3541 #[macro_rules_attr::apply(test)]
3542 #[should_panic(expected = "Repeated")]
3543 fn zipped_lagrange_interpolate_using_repeated_domain_points_is_impossible() {
3544 let domain = bfe_array![1, 1, 2];
3545 let points = bfe_array![1, 2, 3];
3546 let zipped = domain.into_iter().zip(points).collect_vec();
3547 let _ = Polynomial::lagrange_interpolate_zipped(&zipped);
3548 }
3549
3550 #[macro_rules_attr::apply(proptest)]
3551 fn interpolating_through_one_point_gives_constant_polynomial(
3552 x: BFieldElement,
3553 y: BFieldElement,
3554 ) {
3555 let interpolant = Polynomial::lagrange_interpolate(&[x], &[y]);
3556 let polynomial = Polynomial::from_constant(y);
3557 prop_assert_eq!(polynomial, interpolant);
3558 }
3559
3560 #[macro_rules_attr::apply(proptest(cases = 10))]
3561 fn lagrange_and_fast_interpolation_are_identical(
3562 #[any(size_range(1..2048).lift())]
3563 #[filter(#domain.iter().all_unique())]
3564 domain: Vec<BFieldElement>,
3565 #[strategy(vec(arb(), #domain.len()))] values: Vec<BFieldElement>,
3566 ) {
3567 let lagrange_interpolant = Polynomial::lagrange_interpolate(&domain, &values);
3568 let fast_interpolant = Polynomial::fast_interpolate(&domain, &values);
3569 prop_assert_eq!(lagrange_interpolant, fast_interpolant);
3570 }
3571
3572 #[macro_rules_attr::apply(proptest(cases = 10))]
3573 fn par_fast_interpolate_and_fast_interpolation_are_identical(
3574 #[any(size_range(1..2048).lift())]
3575 #[filter(#domain.iter().all_unique())]
3576 domain: Vec<BFieldElement>,
3577 #[strategy(vec(arb(), #domain.len()))] values: Vec<BFieldElement>,
3578 ) {
3579 let par_fast_interpolant = Polynomial::par_fast_interpolate(&domain, &values);
3580 let fast_interpolant = Polynomial::fast_interpolate(&domain, &values);
3581 prop_assert_eq!(par_fast_interpolant, fast_interpolant);
3582 }
3583
3584 #[macro_rules_attr::apply(test)]
3585 fn fast_interpolation_through_a_single_point_succeeds() {
3586 let zero_arr = bfe_array![0];
3587 let _ = Polynomial::fast_interpolate(&zero_arr, &zero_arr);
3588 }
3589
3590 #[macro_rules_attr::apply(proptest(cases = 20))]
3591 fn interpolation_then_evaluation_is_identity(
3592 #[any(size_range(1..2048).lift())]
3593 #[filter(#domain.iter().all_unique())]
3594 domain: Vec<BFieldElement>,
3595 #[strategy(vec(arb(), #domain.len()))] values: Vec<BFieldElement>,
3596 ) {
3597 let interpolant = Polynomial::fast_interpolate(&domain, &values);
3598 let evaluations = interpolant.batch_evaluate(&domain);
3599 prop_assert_eq!(values, evaluations);
3600 }
3601
3602 #[macro_rules_attr::apply(proptest(cases = 1))]
3603 fn fast_batch_interpolation_is_equivalent_to_fast_interpolation(
3604 #[any(size_range(1..2048).lift())]
3605 #[filter(#domain.iter().all_unique())]
3606 domain: Vec<BFieldElement>,
3607 #[strategy(vec(vec(arb(), #domain.len()), 0..10))] value_vecs: Vec<Vec<BFieldElement>>,
3608 ) {
3609 let root_order = domain.len().next_power_of_two();
3610 let root_of_unity = BFieldElement::primitive_root_of_unity(root_order as u64).unwrap();
3611
3612 let interpolants = value_vecs
3613 .iter()
3614 .map(|values| Polynomial::fast_interpolate(&domain, values))
3615 .collect_vec();
3616
3617 let batched_interpolants =
3618 Polynomial::batch_fast_interpolate(&domain, &value_vecs, root_of_unity, root_order);
3619 prop_assert_eq!(interpolants, batched_interpolants);
3620 }
3621
3622 fn coset_domain_of_size_from_generator_with_offset(
3623 size: usize,
3624 generator: BFieldElement,
3625 offset: BFieldElement,
3626 ) -> Vec<BFieldElement> {
3627 let mut domain = vec![offset];
3628 for _ in 1..size {
3629 domain.push(domain.last().copied().unwrap() * generator);
3630 }
3631 domain
3632 }
3633
3634 #[macro_rules_attr::apply(proptest)]
3635 fn fast_coset_evaluation_and_fast_evaluation_on_coset_are_identical(
3636 polynomial: BfePoly,
3637 offset: BFieldElement,
3638 #[strategy(0..8usize)]
3639 #[map(|x: usize| 1 << x)]
3640 #[filter((#root_order as isize) > #polynomial.degree())]
3642 root_order: usize,
3643 ) {
3644 let root_of_unity = BFieldElement::primitive_root_of_unity(root_order as u64).unwrap();
3645 let domain =
3646 coset_domain_of_size_from_generator_with_offset(root_order, root_of_unity, offset);
3647
3648 let fast_values = polynomial.batch_evaluate(&domain);
3649 let fast_coset_values = polynomial.fast_coset_evaluate(offset, root_order);
3650 prop_assert_eq!(fast_values, fast_coset_values);
3651 }
3652
3653 #[macro_rules_attr::apply(proptest)]
3654 fn fast_coset_interpolation_and_and_fast_interpolation_on_coset_are_identical(
3655 #[filter(!#offset.is_zero())] offset: BFieldElement,
3656 #[strategy(1..8usize)]
3657 #[map(|x: usize| 1 << x)]
3658 root_order: usize,
3659 #[strategy(vec(arb(), #root_order))] values: Vec<BFieldElement>,
3660 ) {
3661 let root_of_unity = BFieldElement::primitive_root_of_unity(root_order as u64).unwrap();
3662 let domain =
3663 coset_domain_of_size_from_generator_with_offset(root_order, root_of_unity, offset);
3664
3665 let fast_interpolant = Polynomial::fast_interpolate(&domain, &values);
3666 let fast_coset_interpolant = Polynomial::fast_coset_interpolate(offset, &values);
3667 prop_assert_eq!(fast_interpolant, fast_coset_interpolant);
3668 }
3669
3670 #[macro_rules_attr::apply(proptest)]
3671 fn naive_division_gives_quotient_and_remainder_with_expected_properties(
3672 a: BfePoly,
3673 #[filter(!#b.is_zero())] b: BfePoly,
3674 ) {
3675 let (quot, rem) = a.naive_divide(&b);
3676 prop_assert!(rem.degree() < b.degree());
3677 prop_assert_eq!(a, quot * b + rem);
3678 }
3679
3680 #[macro_rules_attr::apply(proptest)]
3681 fn clean_naive_division_gives_quotient_and_remainder_with_expected_properties(
3682 #[filter(!#a_roots.is_empty())] a_roots: Vec<BFieldElement>,
3683 #[strategy(vec(0..#a_roots.len(), 1..=#a_roots.len()))]
3684 #[filter(#b_root_indices.iter().all_unique())]
3685 b_root_indices: Vec<usize>,
3686 ) {
3687 let b_roots = b_root_indices.into_iter().map(|i| a_roots[i]).collect_vec();
3688 let a = Polynomial::zerofier(&a_roots);
3689 let b = Polynomial::zerofier(&b_roots);
3690 let (quot, rem) = a.naive_divide(&b);
3691 prop_assert!(rem.is_zero());
3692 prop_assert_eq!(a, quot * b);
3693 }
3694
3695 #[macro_rules_attr::apply(proptest)]
3696 fn clean_division_agrees_with_divide_on_clean_division(
3697 #[strategy(arb())] a: BfePoly,
3698 #[strategy(arb())]
3699 #[filter(!#b.is_zero())]
3700 b: BfePoly,
3701 ) {
3702 let product = a.clone() * b.clone();
3703 let (naive_quotient, naive_remainder) = product.naive_divide(&b);
3704 let clean_quotient = product.clone().clean_divide(b.clone());
3705 let err = format!("{product} / {b} == {naive_quotient} != {clean_quotient}");
3706 prop_assert_eq!(naive_quotient, clean_quotient, "{}", err);
3707 prop_assert_eq!(Polynomial::<BFieldElement>::zero(), naive_remainder);
3708 }
3709
3710 #[macro_rules_attr::apply(proptest)]
3711 fn clean_division_agrees_with_division_if_divisor_has_only_0_as_root(
3712 #[strategy(arb())] mut dividend_roots: Vec<BFieldElement>,
3713 ) {
3714 dividend_roots.push(bfe!(0));
3715 let dividend = Polynomial::zerofier(÷nd_roots);
3716 let divisor = Polynomial::zerofier(&[bfe!(0)]);
3717
3718 let (naive_quotient, remainder) = dividend.naive_divide(&divisor);
3719 let clean_quotient = dividend.clean_divide(divisor);
3720 prop_assert_eq!(naive_quotient, clean_quotient);
3721 prop_assert_eq!(Polynomial::<BFieldElement>::zero(), remainder);
3722 }
3723
3724 #[macro_rules_attr::apply(proptest)]
3725 fn clean_division_agrees_with_division_if_divisor_has_only_0_as_multiple_root(
3726 #[strategy(arb())] mut dividend_roots: Vec<BFieldElement>,
3727 #[strategy(0_usize..300)] num_roots: usize,
3728 ) {
3729 let multiple_roots = bfe_vec![0; num_roots];
3730 let divisor = Polynomial::zerofier(&multiple_roots);
3731 dividend_roots.extend(multiple_roots);
3732 let dividend = Polynomial::zerofier(÷nd_roots);
3733
3734 let (naive_quotient, remainder) = dividend.naive_divide(&divisor);
3735 let clean_quotient = dividend.clean_divide(divisor);
3736 prop_assert_eq!(naive_quotient, clean_quotient);
3737 prop_assert_eq!(Polynomial::<BFieldElement>::zero(), remainder);
3738 }
3739
3740 #[macro_rules_attr::apply(proptest)]
3741 fn clean_division_agrees_with_division_if_divisor_has_0_as_root(
3742 #[strategy(arb())] mut dividend_roots: Vec<BFieldElement>,
3743 #[strategy(vec(0..#dividend_roots.len(), 0..=#dividend_roots.len()))]
3744 #[filter(#divisor_root_indices.iter().all_unique())]
3745 divisor_root_indices: Vec<usize>,
3746 ) {
3747 let mut divisor_roots = divisor_root_indices
3749 .into_iter()
3750 .map(|i| dividend_roots[i])
3751 .collect_vec();
3752
3753 dividend_roots.push(bfe!(0));
3755 divisor_roots.push(bfe!(0));
3756
3757 let dividend = Polynomial::zerofier(÷nd_roots);
3758 let divisor = Polynomial::zerofier(&divisor_roots);
3759 let quotient = dividend.clone().clean_divide(divisor.clone());
3760 prop_assert_eq!(dividend / divisor, quotient);
3761 }
3762
3763 #[macro_rules_attr::apply(proptest)]
3764 fn clean_division_agrees_with_division_if_divisor_has_0_through_9_as_roots(
3765 #[strategy(arb())] additional_dividend_roots: Vec<BFieldElement>,
3766 ) {
3767 let divisor_roots = (0..10).map(BFieldElement::new).collect_vec();
3768 let divisor = Polynomial::zerofier(&divisor_roots);
3769 let dividend_roots = [additional_dividend_roots, divisor_roots].concat();
3770 let dividend = Polynomial::zerofier(÷nd_roots);
3771 dbg!(dividend.to_string());
3772 dbg!(divisor.to_string());
3773 let quotient = dividend.clone().clean_divide(divisor.clone());
3774 prop_assert_eq!(dividend / divisor, quotient);
3775 }
3776
3777 #[macro_rules_attr::apply(proptest)]
3778 fn clean_division_gives_quotient_and_remainder_with_expected_properties(
3779 #[filter(!#a_roots.is_empty())] a_roots: Vec<BFieldElement>,
3780 #[strategy(vec(0..#a_roots.len(), 1..=#a_roots.len()))]
3781 #[filter(#b_root_indices.iter().all_unique())]
3782 b_root_indices: Vec<usize>,
3783 ) {
3784 let b_roots = b_root_indices.into_iter().map(|i| a_roots[i]).collect_vec();
3785 let a = Polynomial::zerofier(&a_roots);
3786 let b = Polynomial::zerofier(&b_roots);
3787 let quotient = a.clone().clean_divide(b.clone());
3788 prop_assert_eq!(a, quotient * b);
3789 }
3790
3791 #[macro_rules_attr::apply(proptest)]
3792 fn dividing_constant_polynomials_is_equivalent_to_dividing_constants(
3793 a: BFieldElement,
3794 #[filter(!#b.is_zero())] b: BFieldElement,
3795 ) {
3796 let a_poly = Polynomial::from_constant(a);
3797 let b_poly = Polynomial::from_constant(b);
3798 let expected_quotient = Polynomial::from_constant(a / b);
3799 prop_assert_eq!(expected_quotient, a_poly / b_poly);
3800 }
3801
3802 #[macro_rules_attr::apply(proptest)]
3803 fn dividing_any_polynomial_by_a_constant_polynomial_results_in_remainder_zero(
3804 a: BfePoly,
3805 #[filter(!#b.is_zero())] b: BFieldElement,
3806 ) {
3807 let b_poly = Polynomial::from_constant(b);
3808 let (_, remainder) = a.naive_divide(&b_poly);
3809 prop_assert_eq!(Polynomial::zero(), remainder);
3810 }
3811
3812 #[macro_rules_attr::apply(test)]
3813 fn polynomial_division_by_and_with_shah_polynomial() {
3814 fn polynomial<const N: usize>(coeffs: [u64; N]) -> BfePoly {
3815 Polynomial::new(coeffs.map(BFieldElement::new).to_vec())
3816 }
3817
3818 let shah = XFieldElement::shah_polynomial();
3819 let x_to_the_3 = polynomial([1]).shift_coefficients(3);
3820 let (shah_div_x_to_the_3, shah_mod_x_to_the_3) = shah.naive_divide(&x_to_the_3);
3821 assert_eq!(polynomial([1]), shah_div_x_to_the_3);
3822 assert_eq!(polynomial([1, BFieldElement::P - 1]), shah_mod_x_to_the_3);
3823
3824 let x_to_the_6 = polynomial([1]).shift_coefficients(6);
3825 let (x_to_the_6_div_shah, x_to_the_6_mod_shah) = x_to_the_6.naive_divide(&shah);
3826
3827 let expected_quot = polynomial([BFieldElement::P - 1, 1, 0, 1]);
3829 assert_eq!(expected_quot, x_to_the_6_div_shah);
3830
3831 let expected_rem = polynomial([1, BFieldElement::P - 2, 1]);
3833 assert_eq!(expected_rem, x_to_the_6_mod_shah);
3834 }
3835
3836 #[macro_rules_attr::apply(test)]
3837 fn xgcd_does_not_panic_on_input_zero() {
3838 let zero = Polynomial::<BFieldElement>::zero;
3839 let (gcd, a, b) = Polynomial::xgcd(zero(), zero());
3840 assert_eq!(zero(), gcd);
3841 println!("a = {a}");
3842 println!("b = {b}");
3843 }
3844
3845 #[macro_rules_attr::apply(proptest)]
3846 fn xgcd_b_field_pol_test(x: BfePoly, y: BfePoly) {
3847 let (gcd, a, b) = Polynomial::xgcd(x.clone(), y.clone());
3848 prop_assert_eq!(gcd, a * x + b * y);
3850 }
3851
3852 #[macro_rules_attr::apply(proptest)]
3853 fn xgcd_x_field_pol_test(x: XfePoly, y: XfePoly) {
3854 let (gcd, a, b) = Polynomial::xgcd(x.clone(), y.clone());
3855 prop_assert_eq!(gcd, a * x + b * y);
3857 }
3858
3859 #[macro_rules_attr::apply(proptest)]
3860 fn add_assign_is_equivalent_to_adding_and_assigning(a: BfePoly, b: BfePoly) {
3861 let mut c = a.clone();
3862 c += b.clone();
3863 prop_assert_eq!(a + b, c);
3864 }
3865
3866 #[macro_rules_attr::apply(test)]
3867 fn only_monic_polynomial_of_degree_1_is_x() {
3868 fn polynomial<const N: usize>(coeffs: [u64; N]) -> BfePoly {
3869 Polynomial::new(coeffs.map(BFieldElement::new).to_vec())
3870 }
3871
3872 assert!(polynomial([0, 1]).is_x());
3873 assert!(polynomial([0, 1, 0]).is_x());
3874 assert!(polynomial([0, 1, 0, 0]).is_x());
3875
3876 assert!(!polynomial([]).is_x());
3877 assert!(!polynomial([0]).is_x());
3878 assert!(!polynomial([1]).is_x());
3879 assert!(!polynomial([1, 0]).is_x());
3880 assert!(!polynomial([0, 2]).is_x());
3881 assert!(!polynomial([0, 0, 1]).is_x());
3882 }
3883
3884 #[macro_rules_attr::apply(test)]
3885 fn hardcoded_polynomial_squaring() {
3886 fn polynomial<const N: usize>(coeffs: [u64; N]) -> BfePoly {
3887 Polynomial::new(coeffs.map(BFieldElement::new).to_vec())
3888 }
3889
3890 assert_eq!(Polynomial::zero(), polynomial([]).square());
3891
3892 let x_plus_1 = polynomial([1, 1]);
3893 assert_eq!(polynomial([1, 2, 1]), x_plus_1.square());
3894
3895 let x_to_the_15 = polynomial([1]).shift_coefficients(15);
3896 let x_to_the_30 = polynomial([1]).shift_coefficients(30);
3897 assert_eq!(x_to_the_30, x_to_the_15.square());
3898
3899 let some_poly = polynomial([14, 1, 3, 4]);
3900 assert_eq!(
3901 polynomial([196, 28, 85, 118, 17, 24, 16]),
3902 some_poly.square()
3903 );
3904 }
3905
3906 #[macro_rules_attr::apply(proptest)]
3907 fn polynomial_squaring_is_equivalent_to_multiplication_with_self(poly: BfePoly) {
3908 prop_assert_eq!(poly.clone() * poly.clone(), poly.square());
3909 }
3910
3911 #[macro_rules_attr::apply(proptest)]
3912 fn slow_and_normal_squaring_are_equivalent(poly: BfePoly) {
3913 prop_assert_eq!(poly.slow_square(), poly.square());
3914 }
3915
3916 #[macro_rules_attr::apply(proptest)]
3917 fn normal_and_fast_squaring_are_equivalent(poly: BfePoly) {
3918 prop_assert_eq!(poly.square(), poly.fast_square());
3919 }
3920
3921 #[macro_rules_attr::apply(test)]
3922 fn constant_zero_eq_constant_zero() {
3923 let zero_polynomial1 = Polynomial::<BFieldElement>::zero();
3924 let zero_polynomial2 = Polynomial::<BFieldElement>::zero();
3925
3926 assert_eq!(zero_polynomial1, zero_polynomial2)
3927 }
3928
3929 #[macro_rules_attr::apply(test)]
3930 fn zero_polynomial_is_zero() {
3931 assert!(Polynomial::<BFieldElement>::zero().is_zero());
3932 assert!(Polynomial::<XFieldElement>::zero().is_zero());
3933 }
3934
3935 #[macro_rules_attr::apply(proptest)]
3936 fn zero_polynomial_is_zero_independent_of_spurious_leading_zeros(
3937 #[strategy(..500usize)] num_zeros: usize,
3938 ) {
3939 let coefficients = vec![0; num_zeros];
3940 prop_assert_eq!(
3941 Polynomial::zero(),
3942 Polynomial::<BFieldElement>::from(coefficients)
3943 );
3944 }
3945
3946 #[macro_rules_attr::apply(proptest)]
3947 fn no_constant_polynomial_with_non_zero_coefficient_is_zero(
3948 #[filter(!#constant.is_zero())] constant: BFieldElement,
3949 ) {
3950 let constant_polynomial = Polynomial::from_constant(constant);
3951 prop_assert!(!constant_polynomial.is_zero());
3952 }
3953
3954 #[macro_rules_attr::apply(test)]
3955 fn constant_one_eq_constant_one() {
3956 let one_polynomial1 = Polynomial::<BFieldElement>::one();
3957 let one_polynomial2 = Polynomial::<BFieldElement>::one();
3958
3959 assert_eq!(one_polynomial1, one_polynomial2)
3960 }
3961
3962 #[macro_rules_attr::apply(test)]
3963 fn one_polynomial_is_one() {
3964 assert!(Polynomial::<BFieldElement>::one().is_one());
3965 assert!(Polynomial::<XFieldElement>::one().is_one());
3966 }
3967
3968 #[macro_rules_attr::apply(proptest)]
3969 fn one_polynomial_is_one_independent_of_spurious_leading_zeros(
3970 #[strategy(..500usize)] num_leading_zeros: usize,
3971 ) {
3972 let spurious_leading_zeros = vec![0; num_leading_zeros];
3973 let mut coefficients = vec![1];
3974 coefficients.extend(spurious_leading_zeros);
3975 prop_assert_eq!(
3976 Polynomial::one(),
3977 Polynomial::<BFieldElement>::from(coefficients)
3978 );
3979 }
3980
3981 #[macro_rules_attr::apply(proptest)]
3982 fn no_constant_polynomial_with_non_one_coefficient_is_one(
3983 #[filter(!#constant.is_one())] constant: BFieldElement,
3984 ) {
3985 let constant_polynomial = Polynomial::from_constant(constant);
3986 prop_assert!(!constant_polynomial.is_one());
3987 }
3988
3989 #[macro_rules_attr::apply(test)]
3990 fn formal_derivative_of_zero_is_zero() {
3991 let bfe_0_poly = Polynomial::<BFieldElement>::zero();
3992 assert!(bfe_0_poly.formal_derivative().is_zero());
3993
3994 let xfe_0_poly = Polynomial::<XFieldElement>::zero();
3995 assert!(xfe_0_poly.formal_derivative().is_zero());
3996 }
3997
3998 #[macro_rules_attr::apply(proptest)]
3999 fn formal_derivative_of_constant_polynomial_is_zero(constant: BFieldElement) {
4000 let formal_derivative = Polynomial::from_constant(constant).formal_derivative();
4001 prop_assert!(formal_derivative.is_zero());
4002 }
4003
4004 #[macro_rules_attr::apply(proptest)]
4005 fn formal_derivative_of_non_zero_polynomial_is_of_degree_one_less_than_the_polynomial(
4006 #[filter(!#poly.is_zero())] poly: BfePoly,
4007 ) {
4008 prop_assert_eq!(poly.degree() - 1, poly.formal_derivative().degree());
4009 }
4010
4011 #[macro_rules_attr::apply(proptest)]
4012 fn formal_derivative_of_product_adheres_to_the_leibniz_product_rule(a: BfePoly, b: BfePoly) {
4013 let product_formal_derivative = (a.clone() * b.clone()).formal_derivative();
4014 let product_rule = a.formal_derivative() * b.clone() + a * b.formal_derivative();
4015 prop_assert_eq!(product_rule, product_formal_derivative);
4016 }
4017
4018 #[macro_rules_attr::apply(test)]
4019 fn zero_is_zero() {
4020 let f = Polynomial::new(vec![BFieldElement::new(0)]);
4021 assert!(f.is_zero());
4022 }
4023
4024 #[macro_rules_attr::apply(proptest)]
4025 fn formal_power_series_inverse_newton(
4026 #[strategy(2usize..20)] precision: usize,
4027 #[filter(!#f.coefficients.is_empty())]
4028 #[filter(!#f.coefficients[0].is_zero())]
4029 #[filter(#precision > 1 + #f.degree() as usize)]
4030 f: BfePoly,
4031 ) {
4032 let g = f.clone().formal_power_series_inverse_newton(precision);
4033 let mut coefficients = bfe_vec![0; precision + 1];
4034 coefficients[precision] = BFieldElement::ONE;
4035 let xn = Polynomial::new(coefficients);
4036 let (_quotient, remainder) = g.multiply(&f).divide(&xn);
4037 prop_assert!(remainder.is_one());
4038 }
4039
4040 #[macro_rules_attr::apply(test)]
4041 fn formal_power_series_inverse_newton_concrete() {
4042 let f = Polynomial::new(vec![
4043 BFieldElement::new(3618372803227210457),
4044 BFieldElement::new(14620511201754172786),
4045 BFieldElement::new(2577803283145951105),
4046 BFieldElement::new(1723541458268087404),
4047 BFieldElement::new(4119508755381840018),
4048 BFieldElement::new(8592072587377832596),
4049 BFieldElement::new(236223201225),
4050 ]);
4051 let precision = 8;
4052
4053 let g = f.clone().formal_power_series_inverse_newton(precision);
4054 let mut coefficients = vec![BFieldElement::ZERO; precision + 1];
4055 coefficients[precision] = BFieldElement::ONE;
4056 let xn = Polynomial::new(coefficients);
4057 let (_quotient, remainder) = g.multiply(&f).divide(&xn);
4058 assert!(remainder.is_one());
4059 }
4060
4061 #[macro_rules_attr::apply(proptest)]
4062 fn formal_power_series_inverse_minimal(
4063 #[strategy(2usize..20)] precision: usize,
4064 #[filter(!#f.coefficients.is_empty())]
4065 #[filter(!#f.coefficients[0].is_zero())]
4066 #[filter(#precision > 1 + #f.degree() as usize)]
4067 f: BfePoly,
4068 ) {
4069 let g = f.formal_power_series_inverse_minimal(precision);
4070 let mut coefficients = vec![BFieldElement::ZERO; precision + 1];
4071 coefficients[precision] = BFieldElement::ONE;
4072 let xn = Polynomial::new(coefficients);
4073 let (_quotient, remainder) = g.multiply(&f).divide(&xn);
4074
4075 prop_assert!(remainder.is_one());
4077
4078 prop_assert!(g.degree() <= precision as isize);
4080 }
4081
4082 #[macro_rules_attr::apply(proptest)]
4083 fn structured_multiple_is_multiple(
4084 #[filter(#coefficients.iter().any(|c|!c.is_zero()))]
4085 #[strategy(vec(arb(), 1..30))]
4086 coefficients: Vec<BFieldElement>,
4087 ) {
4088 let polynomial = Polynomial::new(coefficients);
4089 let multiple = polynomial.structured_multiple();
4090 let remainder = multiple.reduce_long_division(&polynomial);
4091 prop_assert!(remainder.is_zero());
4092 }
4093
4094 #[macro_rules_attr::apply(proptest)]
4095 fn structured_multiple_of_modulus_with_trailing_zeros_is_multiple(
4096 #[filter(!#raw_modulus.is_zero())] raw_modulus: BfePoly,
4097 #[strategy(0usize..100)] num_trailing_zeros: usize,
4098 ) {
4099 let modulus = raw_modulus.shift_coefficients(num_trailing_zeros);
4100 let multiple = modulus.structured_multiple();
4101 prop_assert!(multiple.reduce_long_division(&modulus).is_zero());
4102 }
4103
4104 #[macro_rules_attr::apply(proptest)]
4105 fn structured_multiple_generates_structure(
4106 #[filter(#coefficients.iter().filter(|c|!c.is_zero()).count() >= 3)]
4107 #[strategy(vec(arb(), 1..30))]
4108 coefficients: Vec<BFieldElement>,
4109 ) {
4110 let polynomial = Polynomial::new(coefficients);
4111 let n = polynomial.degree();
4112 let structured_multiple = polynomial.structured_multiple();
4113 assert!(structured_multiple.degree() <= 3 * n + 1);
4114
4115 let x3np1 = Polynomial::x_to_the((3 * n + 1) as usize);
4116 let remainder = structured_multiple.reduce_long_division(&x3np1);
4117 assert!(2 * n >= remainder.degree());
4118
4119 let structured_mul_minus_rem = structured_multiple - remainder;
4120 assert_eq!(0, structured_mul_minus_rem.clone().reverse().degree());
4121 assert_eq!(
4122 BFieldElement::ONE,
4123 *structured_mul_minus_rem.coefficients.last().unwrap(),
4124 );
4125 }
4126
4127 #[macro_rules_attr::apply(test)]
4128 fn structured_multiple_generates_structure_concrete() {
4129 let polynomial = Polynomial::new(
4130 [884763262770, 0, 51539607540, 14563891882495327437]
4131 .map(BFieldElement::new)
4132 .to_vec(),
4133 );
4134 let n = polynomial.degree();
4135 let structured_multiple = polynomial.structured_multiple();
4136 assert_eq!(3 * n + 1, structured_multiple.degree());
4137
4138 let x3np1 = Polynomial::x_to_the((3 * n + 1) as usize);
4139 let remainder = structured_multiple.reduce_long_division(&x3np1);
4140 assert!(2 * n >= remainder.degree());
4141
4142 let structured_mul_minus_rem = structured_multiple - remainder;
4143 assert_eq!(0, structured_mul_minus_rem.clone().reverse().degree());
4144 assert_eq!(
4145 BFieldElement::ONE,
4146 *structured_mul_minus_rem.coefficients.last().unwrap(),
4147 );
4148 }
4149
4150 #[macro_rules_attr::apply(proptest)]
4151 fn structured_multiple_of_degree_is_multiple(
4152 #[strategy(2usize..100)] n: usize,
4153 #[filter(#coefficients.iter().any(|c|!c.is_zero()))]
4154 #[strategy(vec(arb(), 1..usize::min(30, #n)))]
4155 coefficients: Vec<BFieldElement>,
4156 ) {
4157 let polynomial = Polynomial::new(coefficients);
4158 let multiple = polynomial.structured_multiple_of_degree(n);
4159 let remainder = multiple.reduce_long_division(&polynomial);
4160 prop_assert!(remainder.is_zero());
4161 }
4162
4163 #[macro_rules_attr::apply(proptest)]
4164 fn structured_multiple_of_degree_generates_structure(
4165 #[strategy(4usize..100)] n: usize,
4166 #[strategy(vec(arb(), 3..usize::min(30, #n)))] mut coefficients: Vec<BFieldElement>,
4167 ) {
4168 *coefficients.last_mut().unwrap() = BFieldElement::ONE;
4169 let polynomial = Polynomial::new(coefficients);
4170 let structured_multiple = polynomial.structured_multiple_of_degree(n);
4171
4172 let xn =
4173 Polynomial::new([vec![BFieldElement::ZERO; n], vec![BFieldElement::ONE; 1]].concat());
4174 let remainder = structured_multiple.reduce_long_division(&xn);
4175 assert_eq!(
4176 (structured_multiple.clone() - remainder.clone())
4177 .reverse()
4178 .degree() as usize,
4179 0
4180 );
4181 assert_eq!(
4182 BFieldElement::ONE,
4183 *(structured_multiple.clone() - remainder)
4184 .coefficients
4185 .last()
4186 .unwrap()
4187 );
4188 }
4189
4190 #[macro_rules_attr::apply(proptest)]
4191 fn structured_multiple_of_degree_has_given_degree(
4192 #[strategy(2usize..100)] n: usize,
4193 #[filter(#coefficients.iter().any(|c|!c.is_zero()))]
4194 #[strategy(vec(arb(), 1..usize::min(30, #n)))]
4195 coefficients: Vec<BFieldElement>,
4196 ) {
4197 let polynomial = Polynomial::new(coefficients);
4198 let multiple = polynomial.structured_multiple_of_degree(n);
4199 prop_assert_eq!(
4200 multiple.degree() as usize,
4201 n,
4202 "polynomial: {} whereas multiple {}",
4203 polynomial,
4204 multiple
4205 );
4206 }
4207
4208 #[macro_rules_attr::apply(proptest)]
4209 fn reverse_polynomial_with_nonzero_constant_term_twice_gives_original_back(f: BfePoly) {
4210 let fx_plus_1 = f.shift_coefficients(1) + Polynomial::from_constant(bfe!(1));
4211 prop_assert_eq!(fx_plus_1.clone(), fx_plus_1.reverse().reverse());
4212 }
4213
4214 #[macro_rules_attr::apply(proptest)]
4215 fn reverse_polynomial_with_zero_constant_term_twice_gives_shift_back(
4216 #[filter(!#f.is_zero())] f: BfePoly,
4217 ) {
4218 let fx_plus_1 = f.shift_coefficients(1);
4219 prop_assert_ne!(fx_plus_1.clone(), fx_plus_1.reverse().reverse());
4220 prop_assert_eq!(
4221 fx_plus_1.clone(),
4222 fx_plus_1.reverse().reverse().shift_coefficients(1)
4223 );
4224 }
4225
4226 #[macro_rules_attr::apply(proptest)]
4227 fn reduce_by_structured_modulus_and_reduce_long_division_agree(
4228 #[strategy(1usize..10)] n: usize,
4229 #[strategy(1usize..10)] m: usize,
4230 #[strategy(vec(arb(), #m))] b_coefficients: Vec<BFieldElement>,
4231 #[strategy(1usize..100)] _deg_a: usize,
4232 #[strategy(vec(arb(), #_deg_a + 1))] _a_coefficients: Vec<BFieldElement>,
4233 #[strategy(Just(Polynomial::new(#_a_coefficients)))] a: BfePoly,
4234 ) {
4235 let mut full_modulus_coefficients = b_coefficients.clone();
4236 full_modulus_coefficients.resize(m + n + 1, BFieldElement::from(0));
4237 *full_modulus_coefficients.last_mut().unwrap() = BFieldElement::from(1);
4238 let full_modulus = Polynomial::new(full_modulus_coefficients);
4239
4240 let long_remainder = a.reduce_long_division(&full_modulus);
4241 let structured_remainder = a.reduce_by_structured_modulus(&full_modulus);
4242
4243 prop_assert_eq!(long_remainder, structured_remainder);
4244 }
4245
4246 #[macro_rules_attr::apply(test)]
4247 fn reduce_by_structured_modulus_and_reduce_agree_long_division_concrete() {
4248 let a = Polynomial::new(
4249 [1, 0, 0, 3, 4, 3, 1, 5, 1, 0, 1, 2, 9, 2, 0, 3, 1]
4250 .into_iter()
4251 .map(BFieldElement::new)
4252 .collect_vec(),
4253 );
4254 let mut full_modulus_coefficients =
4255 [5, 6, 3].into_iter().map(BFieldElement::new).collect_vec();
4256 let m = full_modulus_coefficients.len();
4257 let n = 2;
4258 full_modulus_coefficients.resize(m + n + 1, BFieldElement::from(0));
4259 *full_modulus_coefficients.last_mut().unwrap() = BFieldElement::from(1);
4260 let full_modulus = Polynomial::new(full_modulus_coefficients);
4261
4262 let long_remainder = a.reduce_long_division(&full_modulus);
4263 let structured_remainder = a.reduce_by_structured_modulus(&full_modulus);
4264
4265 assert_eq!(
4266 long_remainder, structured_remainder,
4267 "naive: {long_remainder}\nstructured: {structured_remainder}",
4268 );
4269 }
4270
4271 #[macro_rules_attr::apply(proptest)]
4272 fn reduce_by_ntt_friendly_modulus_and_reduce_long_division_agree(
4273 #[strategy(1usize..10)] m: usize,
4274 #[strategy(vec(arb(), #m))] b_coefficients: Vec<BFieldElement>,
4275 #[strategy(1usize..100)] _deg_a: usize,
4276 #[strategy(vec(arb(), #_deg_a + 1))] _a_coefficients: Vec<BFieldElement>,
4277 #[strategy(Just(Polynomial::new(#_a_coefficients)))] a: BfePoly,
4278 ) {
4279 let b = Polynomial::new(b_coefficients.clone());
4280 if b.is_zero() {
4281 return Err(TestCaseError::Reject("some reason".into()));
4282 }
4283 let n = (b_coefficients.len() + 1).next_power_of_two();
4284 let mut full_modulus_coefficients = b_coefficients.clone();
4285 full_modulus_coefficients.resize(n + 1, BFieldElement::from(0));
4286 *full_modulus_coefficients.last_mut().unwrap() = BFieldElement::from(1);
4287 let full_modulus = Polynomial::new(full_modulus_coefficients);
4288
4289 let long_remainder = a.reduce_long_division(&full_modulus);
4290
4291 let mut shift_ntt = b_coefficients.clone();
4292 shift_ntt.resize(n, BFieldElement::from(0));
4293 ntt(&mut shift_ntt);
4294 let structured_remainder = a.reduce_by_ntt_friendly_modulus(&shift_ntt, m);
4295
4296 prop_assert_eq!(long_remainder, structured_remainder);
4297 }
4298
4299 #[macro_rules_attr::apply(test)]
4300 fn reduce_by_ntt_friendly_modulus_and_reduce_agree_concrete() {
4301 let m = 1;
4302 let a_coefficients = bfe_vec![0, 0, 75944580];
4303 let a = Polynomial::new(a_coefficients);
4304 let b_coefficients = vec![BFieldElement::new(944892804900)];
4305 let n = (b_coefficients.len() + 1).next_power_of_two();
4306 let mut full_modulus_coefficients = b_coefficients.clone();
4307 full_modulus_coefficients.resize(n + 1, BFieldElement::from(0));
4308 *full_modulus_coefficients.last_mut().unwrap() = BFieldElement::from(1);
4309 let full_modulus = Polynomial::new(full_modulus_coefficients);
4310
4311 let long_remainder = a.reduce_long_division(&full_modulus);
4312
4313 let mut shift_ntt = b_coefficients.clone();
4314 shift_ntt.resize(n, BFieldElement::from(0));
4315 ntt(&mut shift_ntt);
4316 let structured_remainder = a.reduce_by_ntt_friendly_modulus(&shift_ntt, m);
4317
4318 assert_eq!(
4319 long_remainder, structured_remainder,
4320 "full modulus: {full_modulus}",
4321 );
4322 }
4323
4324 #[macro_rules_attr::apply(proptest)]
4325 fn reduce_fast_and_reduce_long_division_agree(
4326 numerator: BfePoly,
4327 #[filter(!#modulus.is_zero())] modulus: BfePoly,
4328 ) {
4329 prop_assert_eq!(
4330 numerator.fast_reduce(&modulus),
4331 numerator.reduce_long_division(&modulus)
4332 );
4333 }
4334
4335 #[macro_rules_attr::apply(test)]
4336 fn reduce_and_fast_reduce_long_division_agree_on_fixed_input() {
4337 let mut failures = vec![];
4341 for i in 1..100 {
4342 let roots = (0..i).map(BFieldElement::new).collect_vec();
4346 let dividend = Polynomial::zerofier(&roots).formal_derivative();
4347
4348 let divisor_roots = &roots[..roots.len() / 5];
4353 let divisor = Polynomial::zerofier(divisor_roots);
4354
4355 let long_div_remainder = dividend.reduce_long_division(&divisor);
4356 let preprocessed_remainder = dividend.fast_reduce(&divisor);
4357
4358 if long_div_remainder != preprocessed_remainder {
4359 failures.push(i);
4360 }
4361 }
4362
4363 assert_eq!(0, failures.len(), "failures at indices: {failures:?}");
4364 }
4365
4366 #[macro_rules_attr::apply(test)]
4367 fn reduce_long_division_and_fast_reduce_agree_simple_fixed() {
4368 let roots = (0..10).map(BFieldElement::new).collect_vec();
4369 let numerator = Polynomial::zerofier(&roots).formal_derivative();
4370
4371 let divisor_roots = &roots[..roots.len() / 5];
4372 let denominator = Polynomial::zerofier(divisor_roots);
4373
4374 let (quotient, remainder) = numerator.divide(&denominator);
4375 assert_eq!(
4376 numerator,
4377 denominator.clone() * quotient + remainder.clone()
4378 );
4379
4380 let long_div_remainder = numerator.reduce_long_division(&denominator);
4381 assert_eq!(remainder, long_div_remainder);
4382
4383 let preprocessed_remainder = numerator.fast_reduce(&denominator);
4384
4385 assert_eq!(long_div_remainder, preprocessed_remainder);
4386 }
4387
4388 #[macro_rules_attr::apply(proptest(cases = 100))]
4389 fn batch_evaluate_methods_are_equivalent(
4390 #[strategy(vec(arb(), (1<<10)..(1<<11)))] coefficients: Vec<BFieldElement>,
4391 #[strategy(vec(arb(), (1<<5)..(1<<7)))] domain: Vec<BFieldElement>,
4392 ) {
4393 let polynomial = Polynomial::new(coefficients);
4394 let evaluations_iterative = polynomial.iterative_batch_evaluate(&domain);
4395 let zerofier_tree = ZerofierTree::new_from_domain(&domain);
4396 let evaluations_fast = polynomial.divide_and_conquer_batch_evaluate(&zerofier_tree);
4397 let evaluations_reduce_then = polynomial.reduce_then_batch_evaluate(&domain);
4398
4399 prop_assert_eq!(evaluations_iterative.clone(), evaluations_fast);
4400 prop_assert_eq!(evaluations_iterative, evaluations_reduce_then);
4401 }
4402
4403 #[macro_rules_attr::apply(proptest)]
4404 fn reduce_agrees_with_division(a: BfePoly, #[filter(!#b.is_zero())] b: BfePoly) {
4405 prop_assert_eq!(a.divide(&b).1, a.reduce(&b));
4406 }
4407
4408 #[macro_rules_attr::apply(proptest)]
4409 fn structured_multiple_of_monomial_term_is_actually_multiple_and_of_right_degree(
4410 #[strategy(1usize..1000)] degree: usize,
4411 #[filter(!#leading_coefficient.is_zero())] leading_coefficient: BFieldElement,
4412 #[strategy(#degree+1..#degree+200)] target_degree: usize,
4413 ) {
4414 let coefficients = [bfe_vec![0; degree], vec![leading_coefficient]].concat();
4415 let polynomial = Polynomial::new(coefficients);
4416 let multiple = polynomial.structured_multiple_of_degree(target_degree);
4417 prop_assert_eq!(Polynomial::zero(), multiple.reduce(&polynomial));
4418 prop_assert_eq!(multiple.degree() as usize, target_degree);
4419 }
4420
4421 #[macro_rules_attr::apply(proptest)]
4422 fn monomial_term_divided_by_smaller_monomial_term_gives_clean_division(
4423 #[strategy(100usize..102)] high_degree: usize,
4424 #[filter(!#high_lc.is_zero())] high_lc: BFieldElement,
4425 #[strategy(83..#high_degree)] low_degree: usize,
4426 #[filter(!#low_lc.is_zero())] low_lc: BFieldElement,
4427 ) {
4428 let numerator = Polynomial::new([bfe_vec![0; high_degree], vec![high_lc]].concat());
4429 let denominator = Polynomial::new([bfe_vec![0; low_degree], vec![low_lc]].concat());
4430 let (quotient, remainder) = numerator.divide(&denominator);
4431 prop_assert_eq!(
4432 quotient
4433 .coefficients
4434 .iter()
4435 .filter(|c| !c.is_zero())
4436 .count(),
4437 1
4438 );
4439 prop_assert_eq!(Polynomial::zero(), remainder);
4440 }
4441
4442 #[macro_rules_attr::apply(proptest)]
4443 fn fast_modular_coset_interpolate_agrees_with_interpolate_then_reduce_property(
4444 #[filter(!#modulus.is_zero())] modulus: BfePoly,
4445 #[strategy(0usize..10)] _logn: usize,
4446 #[strategy(Just(1 << #_logn))] n: usize,
4447 #[strategy(vec(arb(), #n))] values: Vec<BFieldElement>,
4448 #[strategy(arb())] offset: BFieldElement,
4449 ) {
4450 let omega = BFieldElement::primitive_root_of_unity(n as u64).unwrap();
4451 let domain = (0..n)
4452 .scan(offset, |acc: &mut BFieldElement, _| {
4453 let yld = *acc;
4454 *acc *= omega;
4455 Some(yld)
4456 })
4457 .collect_vec();
4458 prop_assert_eq!(
4459 Polynomial::fast_modular_coset_interpolate(&values, offset, &modulus),
4460 Polynomial::interpolate(&domain, &values).reduce(&modulus)
4461 )
4462 }
4463
4464 #[macro_rules_attr::apply(test)]
4465 fn fast_modular_coset_interpolate_agrees_with_interpolate_then_reduce_concrete() {
4466 let logn = 8;
4467 let n = 1u64 << logn;
4468 let modulus = Polynomial::new(bfe_vec![2, 3, 1]);
4469 let values = (0..n).map(|i| BFieldElement::new(i / 5)).collect_vec();
4470 let offset = BFieldElement::new(7);
4471
4472 let omega = BFieldElement::primitive_root_of_unity(n).unwrap();
4473 let mut domain = bfe_vec![0; n as usize];
4474 domain[0] = offset;
4475 for i in 1..n as usize {
4476 domain[i] = domain[i - 1] * omega;
4477 }
4478 assert_eq!(
4479 Polynomial::interpolate(&domain, &values).reduce(&modulus),
4480 Polynomial::fast_modular_coset_interpolate(&values, offset, &modulus),
4481 )
4482 }
4483
4484 #[macro_rules_attr::apply(proptest(cases = 100))]
4485 fn coset_extrapolation_methods_agree_with_interpolate_then_evaluate(
4486 #[strategy(0usize..10)] _logn: usize,
4487 #[strategy(Just(1 << #_logn))] n: usize,
4488 #[strategy(vec(arb(), #n))] values: Vec<BFieldElement>,
4489 #[strategy(arb())] offset: BFieldElement,
4490 #[strategy(vec(arb(), 1..1000))] points: Vec<BFieldElement>,
4491 ) {
4492 let omega = BFieldElement::primitive_root_of_unity(n as u64).unwrap();
4493 let domain = (0..n)
4494 .scan(offset, |acc: &mut BFieldElement, _| {
4495 let yld = *acc;
4496 *acc *= omega;
4497 Some(yld)
4498 })
4499 .collect_vec();
4500 let fast_coset_extrapolation = Polynomial::fast_coset_extrapolate(offset, &values, &points);
4501 let naive_coset_extrapolation =
4502 Polynomial::naive_coset_extrapolate(offset, &values, &points);
4503 let interpolation_then_evaluation =
4504 Polynomial::interpolate(&domain, &values).batch_evaluate(&points);
4505 prop_assert_eq!(fast_coset_extrapolation.clone(), naive_coset_extrapolation);
4506 prop_assert_eq!(fast_coset_extrapolation, interpolation_then_evaluation);
4507 }
4508
4509 #[macro_rules_attr::apply(proptest)]
4510 fn coset_extrapolate_and_batch_coset_extrapolate_agree(
4511 #[strategy(1usize..10)] _logn: usize,
4512 #[strategy(Just(1<<#_logn))] n: usize,
4513 #[strategy(0usize..5)] _m: usize,
4514 #[strategy(vec(arb(), #_m*#n))] codewords: Vec<BFieldElement>,
4515 #[strategy(vec(arb(), 0..20))] points: Vec<BFieldElement>,
4516 ) {
4517 let offset = BFieldElement::new(7);
4518
4519 let one_by_one_dispatch = codewords
4520 .chunks(n)
4521 .flat_map(|chunk| Polynomial::coset_extrapolate(offset, chunk, &points))
4522 .collect_vec();
4523 let batched_dispatch = Polynomial::batch_coset_extrapolate(offset, n, &codewords, &points);
4524 let par_batched_dispatch =
4525 Polynomial::par_batch_coset_extrapolate(offset, n, &codewords, &points);
4526 prop_assert_eq!(one_by_one_dispatch.clone(), batched_dispatch);
4527 prop_assert_eq!(one_by_one_dispatch, par_batched_dispatch);
4528
4529 let one_by_one_fast = codewords
4530 .chunks(n)
4531 .flat_map(|chunk| Polynomial::fast_coset_extrapolate(offset, chunk, &points))
4532 .collect_vec();
4533 let batched_fast = Polynomial::batch_fast_coset_extrapolate(offset, n, &codewords, &points);
4534 let par_batched_fast =
4535 Polynomial::par_batch_fast_coset_extrapolate(offset, n, &codewords, &points);
4536 prop_assert_eq!(one_by_one_fast.clone(), batched_fast);
4537 prop_assert_eq!(one_by_one_fast, par_batched_fast);
4538
4539 let one_by_one_naive = codewords
4540 .chunks(n)
4541 .flat_map(|chunk| Polynomial::naive_coset_extrapolate(offset, chunk, &points))
4542 .collect_vec();
4543 let batched_naive =
4544 Polynomial::batch_naive_coset_extrapolate(offset, n, &codewords, &points);
4545 let par_batched_naive =
4546 Polynomial::par_batch_naive_coset_extrapolate(offset, n, &codewords, &points);
4547 prop_assert_eq!(one_by_one_naive.clone(), batched_naive);
4548 prop_assert_eq!(one_by_one_naive, par_batched_naive);
4549 }
4550
4551 #[macro_rules_attr::apply(test)]
4552 fn fast_modular_coset_interpolate_thresholds_relate_properly() {
4553 type BfePoly = Polynomial<'static, BFieldElement>;
4554
4555 let intt = BfePoly::FAST_MODULAR_COSET_INTERPOLATE_CUTOFF_THRESHOLD_PREFER_INTT;
4556 let lagrange = BfePoly::FAST_MODULAR_COSET_INTERPOLATE_CUTOFF_THRESHOLD_PREFER_LAGRANGE;
4557 assert!(intt > lagrange);
4558 }
4559
4560 #[macro_rules_attr::apply(proptest)]
4561 fn interpolate_and_par_interpolate_agree(
4562 #[filter(!#points.is_empty())] points: Vec<BFieldElement>,
4563 #[strategy(vec(arb(), #points.len()))] domain: Vec<BFieldElement>,
4564 ) {
4565 let expected_interpolant = Polynomial::interpolate(&domain, &points);
4566 let observed_interpolant = Polynomial::par_interpolate(&domain, &points);
4567 prop_assert_eq!(expected_interpolant, observed_interpolant);
4568 }
4569
4570 #[macro_rules_attr::apply(proptest)]
4571 fn batch_evaluate_agrees_with_par_batch_evalaute(
4572 polynomial: BfePoly,
4573 points: Vec<BFieldElement>,
4574 ) {
4575 prop_assert_eq!(
4576 polynomial.batch_evaluate(&points),
4577 polynomial.par_batch_evaluate(&points)
4578 );
4579 }
4580
4581 #[macro_rules_attr::apply(proptest(cases = 20))]
4582 fn polynomial_evaluation_and_barycentric_evaluation_are_equivalent(
4583 #[strategy(1_usize..8)] _log_num_coefficients: usize,
4584 #[strategy(1_usize..6)] log_expansion_factor: usize,
4585 #[strategy(vec(arb(), 1 << #_log_num_coefficients))] coefficients: Vec<XFieldElement>,
4586 #[strategy(arb())] indeterminate: XFieldElement,
4587 ) {
4588 let domain_len = coefficients.len() * (1 << log_expansion_factor);
4589 let domain_gen = BFieldElement::primitive_root_of_unity(domain_len.try_into()?).unwrap();
4590 let domain = (0..domain_len)
4591 .scan(XFieldElement::ONE, |acc, _| {
4592 let current = *acc;
4593 *acc *= domain_gen;
4594 Some(current)
4595 })
4596 .collect_vec();
4597
4598 let polynomial = Polynomial::new(coefficients);
4599 let codeword = polynomial.batch_evaluate(&domain);
4600 prop_assert_eq!(
4601 polynomial.evaluate_in_same_field(indeterminate),
4602 barycentric_evaluate(&codeword, indeterminate)
4603 );
4604 }
4605
4606 #[macro_rules_attr::apply(test)]
4607 fn regular_evaluation_works_with_various_types() {
4608 let bfe_poly = Polynomial::new(bfe_vec![1]);
4609 let _: BFieldElement = bfe_poly.evaluate(bfe!(0));
4610 let _: XFieldElement = bfe_poly.evaluate(bfe!(0));
4611 let _: XFieldElement = bfe_poly.evaluate(xfe!(0));
4612
4613 let xfe_poly = Polynomial::new(xfe_vec![1]);
4614 let _: XFieldElement = xfe_poly.evaluate(bfe!(0));
4615 let _: XFieldElement = xfe_poly.evaluate(xfe!(0));
4616 }
4617
4618 #[macro_rules_attr::apply(test)]
4619 fn barycentric_evaluation_works_with_many_types() {
4620 let bfe_codeword = bfe_array![1];
4621 let _ = barycentric_evaluate(&bfe_codeword, bfe!(0));
4622 let _ = barycentric_evaluate(&bfe_codeword, xfe!(0));
4623
4624 let xfe_codeword = xfe_array![[1; 3]];
4625 let _ = barycentric_evaluate(&xfe_codeword, bfe!(0));
4626 let _ = barycentric_evaluate(&xfe_codeword, xfe!(0));
4627 }
4628
4629 #[macro_rules_attr::apply(test)]
4630 fn various_multiplications_work_with_various_types() {
4631 let b = Polynomial::<BFieldElement>::zero;
4632 let x = Polynomial::<XFieldElement>::zero;
4633
4634 let _ = b() * b();
4635 let _ = b() * x();
4636 let _ = x() * b();
4637 let _ = x() * x();
4638
4639 let _ = b().multiply(&b());
4640 let _ = b().multiply(&x());
4641 let _ = x().multiply(&b());
4642 let _ = x().multiply(&x());
4643
4644 let _ = b().naive_multiply(&b());
4645 let _ = b().naive_multiply(&x());
4646 let _ = x().naive_multiply(&b());
4647 let _ = x().naive_multiply(&x());
4648
4649 let _ = b().fast_multiply(&b());
4650 let _ = b().fast_multiply(&x());
4651 let _ = x().fast_multiply(&b());
4652 let _ = x().fast_multiply(&x());
4653 }
4654
4655 #[macro_rules_attr::apply(test)]
4656 fn evaluating_polynomial_with_borrowed_coefficients_leaves_coefficients_borrowed() {
4657 let coefficients = bfe_vec![4, 5, 6];
4658 let poly = Polynomial::new_borrowed(&coefficients);
4659 let _ = poly.evaluate_in_same_field(bfe!(0));
4660 let _ = poly.evaluate::<_, XFieldElement>(bfe!(0));
4661 let _ = poly.fast_coset_evaluate(bfe!(3), 128);
4662
4663 let Cow::Borrowed(_) = poly.coefficients else {
4664 panic!("evaluating must not clone the coefficient vector")
4665 };
4666
4667 drop(coefficients);
4669 }
4670}