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