1use std::borrow::Cow;
2use std::collections::HashMap;
3use std::fmt::Debug;
4use std::fmt::Display;
5use std::fmt::Formatter;
6use std::hash::Hash;
7use std::ops::Add;
8use std::ops::AddAssign;
9use std::ops::Div;
10use std::ops::Mul;
11use std::ops::MulAssign;
12use std::ops::Neg;
13use std::ops::Rem;
14use std::ops::Sub;
15use std::thread::available_parallelism;
16
17use arbitrary::Arbitrary;
18use arbitrary::Unstructured;
19use itertools::EitherOrBoth;
20use itertools::Itertools;
21use num_traits::ConstOne;
22use num_traits::ConstZero;
23use num_traits::One;
24use num_traits::Zero;
25use rayon::prelude::*;
26
27use super::traits::PrimitiveRootOfUnity;
28use super::zerofier_tree::ZerofierTree;
29use crate::math::ntt::intt;
30use crate::math::ntt::ntt;
31use crate::math::traits::FiniteField;
32use crate::math::traits::ModPowU32;
33use crate::prelude::BFieldElement;
34use crate::prelude::Inverse;
35use crate::prelude::XFieldElement;
36
37impl<FF: FiniteField> Zero for Polynomial<'static, FF> {
38 fn zero() -> Self {
39 Self::new(vec![])
40 }
41
42 fn is_zero(&self) -> bool {
43 *self == Self::zero()
44 }
45}
46
47impl<FF: FiniteField> One for Polynomial<'static, FF> {
48 fn one() -> Self {
49 Self::new(vec![FF::ONE])
50 }
51
52 fn is_one(&self) -> bool {
53 self.degree() == 0 && self.coefficients[0].is_one()
54 }
55}
56
57#[doc(hidden)]
60#[derive(Debug, Clone)]
61pub struct ModularInterpolationPreprocessingData<'coeffs, FF: FiniteField> {
62 pub even_zerofiers: Vec<Polynomial<'coeffs, FF>>,
63 pub odd_zerofiers: Vec<Polynomial<'coeffs, FF>>,
64 pub shift_coefficients: Vec<FF>,
65 pub tail_length: usize,
66}
67
68#[derive(Clone)]
70pub struct Polynomial<'coeffs, FF: FiniteField> {
71 coefficients: Cow<'coeffs, [FF]>,
75}
76
77impl<'a, FF> Arbitrary<'a> for Polynomial<'static, FF>
78where
79 FF: FiniteField + Arbitrary<'a>,
80{
81 fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
82 Ok(Self::new(u.arbitrary()?))
83 }
84}
85
86impl<FF: FiniteField> Debug for Polynomial<'_, FF> {
87 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
88 f.debug_struct("Polynomial")
89 .field("coefficients", &self.coefficients)
90 .finish()
91 }
92}
93
94impl<FF: FiniteField> Hash for Polynomial<'_, FF> {
96 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
97 self.coefficients.hash(state);
98 }
99}
100
101impl<FF: FiniteField> Display for Polynomial<'_, FF> {
102 fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
103 let degree = match self.degree() {
104 -1 => return write!(f, "0"),
105 d => d as usize,
106 };
107
108 for pow in (0..=degree).rev() {
109 let coeff = self.coefficients[pow];
110 if coeff.is_zero() {
111 continue;
112 }
113
114 if pow != degree {
115 write!(f, " + ")?;
116 }
117 if !coeff.is_one() || pow == 0 {
118 write!(f, "{coeff}")?;
119 }
120 match pow {
121 0 => (),
122 1 => write!(f, "x")?,
123 _ => write!(f, "x^{pow}")?,
124 }
125 }
126
127 Ok(())
128 }
129}
130
131impl<FF: FiniteField> PartialEq<Polynomial<'_, FF>> for Polynomial<'_, FF> {
133 fn eq(&self, other: &Polynomial<'_, FF>) -> bool {
134 if self.degree() != other.degree() {
135 return false;
136 }
137
138 self.coefficients
139 .iter()
140 .zip(other.coefficients.iter())
141 .all(|(x, y)| x == y)
142 }
143}
144
145impl<FF: FiniteField> Eq for Polynomial<'_, FF> {}
146
147impl<FF> Polynomial<'_, FF>
148where
149 FF: FiniteField,
150{
151 pub fn degree(&self) -> isize {
152 let mut deg = self.coefficients.len() as isize - 1;
153 while deg >= 0 && self.coefficients[deg as usize].is_zero() {
154 deg -= 1;
155 }
156
157 deg }
159
160 pub fn coefficients(&self) -> &[FF] {
168 let coefficients = self.coefficients.as_ref();
169
170 let Some(leading_coeff_idx) = coefficients.iter().rposition(|&c| !c.is_zero()) else {
171 return &[];
173 };
174
175 &coefficients[0..=leading_coeff_idx]
176 }
177
178 pub fn into_coefficients(mut self) -> Vec<FF> {
182 self.normalize();
183 self.coefficients.into_owned()
184 }
185
186 fn normalize(&mut self) {
190 while self.coefficients.last().is_some_and(Zero::is_zero) {
191 self.coefficients.to_mut().pop();
192 }
193 }
194
195 pub fn leading_coefficient(&self) -> Option<FF> {
210 match self.degree() {
211 -1 => None,
212 n => Some(self.coefficients[n as usize]),
213 }
214 }
215
216 pub fn is_x(&self) -> bool {
217 self.degree() == 1 && self.coefficients[0].is_zero() && self.coefficients[1].is_one()
218 }
219
220 pub fn formal_derivative(&self) -> Polynomial<'static, FF> {
221 let coefficients = (0..)
223 .zip(self.coefficients.iter())
224 .map(|(i, &coefficient)| FF::from(i) * coefficient)
225 .skip(1)
226 .collect();
227
228 Polynomial::new(coefficients)
229 }
230
231 pub fn evaluate<Ind, Eval>(&self, x: Ind) -> Eval
236 where
237 Ind: Clone,
238 Eval: Mul<Ind, Output = Eval> + Add<FF, Output = Eval> + Zero,
239 {
240 let mut acc = Eval::zero();
241 for &c in self.coefficients.iter().rev() {
242 acc = acc * x.clone() + c;
243 }
244
245 acc
246 }
247 pub fn evaluate_in_same_field(&self, x: FF) -> FF {
254 self.evaluate::<FF, FF>(x)
255 }
256
257 pub fn are_colinear_3(p0: (FF, FF), p1: (FF, FF), p2: (FF, FF)) -> bool {
258 if p0.0 == p1.0 || p1.0 == p2.0 || p2.0 == p0.0 {
259 return false;
260 }
261
262 let dy = p0.1 - p1.1;
263 let dx = p0.0 - p1.0;
264
265 dx * (p2.1 - p0.1) == dy * (p2.0 - p0.0)
266 }
267
268 pub fn are_colinear(points: &[(FF, FF)]) -> bool {
269 if points.len() < 3 {
270 return false;
271 }
272
273 if !points.iter().map(|(x, _)| x).all_unique() {
274 return false;
275 }
276
277 let (p0_x, p0_y) = points[0];
279 let (p1_x, p1_y) = points[1];
280 let a = (p0_y - p1_y) / (p0_x - p1_x);
281 let b = p0_y - a * p0_x;
282
283 points.iter().skip(2).all(|&(x, y)| a * x + b == y)
284 }
285
286 pub fn get_colinear_y(p0: (FF, FF), p1: (FF, FF), p2_x: FF) -> FF {
287 assert_ne!(p0.0, p1.0, "Line must not be parallel to y-axis");
288 let dy = p0.1 - p1.1;
289 let dx = p0.0 - p1.0;
290 let p2_y_times_dx = dy * (p2_x - p0.0) + dx * p0.1;
291
292 p2_y_times_dx / dx
294 }
295
296 #[must_use]
298 pub fn slow_square(&self) -> Polynomial<'static, FF> {
299 let degree = self.degree();
300 if degree == -1 {
301 return Polynomial::zero();
302 }
303
304 let squared_coefficient_len = self.degree() as usize * 2 + 1;
305 let zero = FF::ZERO;
306 let one = FF::ONE;
307 let two = one + one;
308 let mut squared_coefficients = vec![zero; squared_coefficient_len];
309
310 for i in 0..self.coefficients.len() {
311 let ci = self.coefficients[i];
312 squared_coefficients[2 * i] += ci * ci;
313
314 for j in i + 1..self.coefficients.len() {
316 let cj = self.coefficients[j];
317 squared_coefficients[i + j] += two * ci * cj;
318 }
319 }
320
321 Polynomial::new(squared_coefficients)
322 }
323
324 #[doc(hidden)]
326 pub fn naive_multiply<FF2>(
327 &self,
328 other: &Polynomial<FF2>,
329 ) -> Polynomial<'static, <FF as Mul<FF2>>::Output>
330 where
331 FF: Mul<FF2>,
332 FF2: FiniteField,
333 <FF as Mul<FF2>>::Output: FiniteField,
334 {
335 let Ok(degree_lhs) = usize::try_from(self.degree()) else {
336 return Polynomial::zero();
337 };
338 let Ok(degree_rhs) = usize::try_from(other.degree()) else {
339 return Polynomial::zero();
340 };
341
342 let mut product = vec![<FF as Mul<FF2>>::Output::ZERO; degree_lhs + degree_rhs + 1];
343 for i in 0..=degree_lhs {
344 for j in 0..=degree_rhs {
345 product[i + j] += self.coefficients[i] * other.coefficients[j];
346 }
347 }
348
349 Polynomial::new(product)
350 }
351
352 #[must_use]
356 pub fn pow(&self, pow: u32) -> Polynomial<'static, FF> {
357 let Some(bit_length) = pow.checked_ilog2() else {
359 return Polynomial::one();
360 };
361
362 if self.degree() < 0 {
363 return Polynomial::zero();
364 }
365
366 let mut acc = Polynomial::one();
368 for i in 0..=bit_length {
369 acc = acc.slow_square();
370 let bit_is_set = (pow >> (bit_length - i)) & 1 == 1;
371 if bit_is_set {
372 acc = acc * self.clone();
373 }
374 }
375
376 acc
377 }
378
379 #[must_use]
381 pub fn shift_coefficients(self, power: usize) -> Polynomial<'static, FF> {
382 let mut coefficients = self.coefficients.into_owned();
383 coefficients.splice(0..0, vec![FF::ZERO; power]);
384 Polynomial::new(coefficients)
385 }
386
387 pub fn scalar_mul_mut<S>(&mut self, scalar: S)
400 where
401 S: Clone,
402 FF: MulAssign<S>,
403 {
404 let mut coefficients = std::mem::take(&mut self.coefficients).into_owned();
405 for coefficient in &mut coefficients {
406 *coefficient *= scalar.clone();
407 }
408 self.coefficients = Cow::Owned(coefficients);
409 }
410
411 #[must_use]
424 pub fn scalar_mul<S, FF2>(&self, scalar: S) -> Polynomial<'static, FF2>
425 where
426 S: Clone,
427 FF: Mul<S, Output = FF2>,
428 FF2: FiniteField,
429 {
430 let coeff_iter = self.coefficients.iter();
431 let new_coeffs = coeff_iter.map(|&c| c * scalar.clone()).collect();
432 Polynomial::new(new_coeffs)
433 }
434
435 pub fn divide(
441 &self,
442 divisor: &Polynomial<'_, FF>,
443 ) -> (Polynomial<'static, FF>, Polynomial<'static, FF>) {
444 self.naive_divide(divisor)
447 }
448
449 #[doc(hidden)]
453 pub fn naive_divide(
454 &self,
455 divisor: &Polynomial<'_, FF>,
456 ) -> (Polynomial<'static, FF>, Polynomial<'static, FF>) {
457 let divisor_lc_inv = divisor
458 .leading_coefficient()
459 .expect("divisor should be non-zero")
460 .inverse();
461
462 let Ok(quotient_degree) = usize::try_from(self.degree() - divisor.degree()) else {
463 return (Polynomial::zero(), self.clone().into_owned());
465 };
466 debug_assert!(self.degree() >= 0);
467
468 let mut rev_quotient = Vec::with_capacity(quotient_degree + 1);
470 let mut remainder = self.clone();
471 remainder.normalize();
472
473 let rev_divisor = divisor.coefficients.iter().rev();
476 let normal_rev_divisor = rev_divisor.skip_while(|c| c.is_zero());
477
478 let mut remainder_coefficients = remainder.coefficients.into_owned();
479 for _ in 0..=quotient_degree {
480 let remainder_lc = remainder_coefficients.pop().unwrap();
481 let quotient_coeff = remainder_lc * divisor_lc_inv;
482 rev_quotient.push(quotient_coeff);
483
484 if quotient_coeff.is_zero() {
485 continue;
486 }
487
488 let remainder_degree = remainder_coefficients.len().saturating_sub(1);
489
490 for (i, &divisor_coeff) in normal_rev_divisor.clone().skip(1).enumerate() {
492 remainder_coefficients[remainder_degree - i] -= quotient_coeff * divisor_coeff;
493 }
494 }
495
496 rev_quotient.reverse();
497
498 let quot = Polynomial::new(rev_quotient);
499 let rem = Polynomial::new(remainder_coefficients);
500 (quot, rem)
501 }
502
503 pub fn xgcd(
518 x: Self,
519 y: Polynomial<'_, FF>,
520 ) -> (
521 Polynomial<'static, FF>,
522 Polynomial<'static, FF>,
523 Polynomial<'static, FF>,
524 ) {
525 let mut x = x.into_owned();
526 let mut y = y.into_owned();
527 let (mut a_factor, mut a1) = (Polynomial::one(), Polynomial::zero());
528 let (mut b_factor, mut b1) = (Polynomial::zero(), Polynomial::one());
529
530 while !y.is_zero() {
531 let (quotient, remainder) = x.naive_divide(&y);
532 let c = a_factor - quotient.clone() * a1.clone();
533 let d = b_factor - quotient * b1.clone();
534
535 x = y;
536 y = remainder;
537 a_factor = a1;
538 a1 = c;
539 b_factor = b1;
540 b1 = d;
541 }
542
543 let lc = x.leading_coefficient().unwrap_or(FF::ONE);
545 let normalize = |poly: Self| poly.scalar_mul(lc.inverse());
546
547 let [x, a, b] = [x, a_factor, b_factor].map(normalize);
548 (x, a, b)
549 }
550
551 fn formal_power_series_inverse_minimal(&self, precision: usize) -> Polynomial<'static, FF> {
558 let lc_inv = self.coefficients.first().unwrap().inverse();
559 let mut g = vec![lc_inv];
560
561 for _ in 1..(precision + 1) {
563 let inner_product = self
564 .coefficients
565 .iter()
566 .skip(1)
567 .take(g.len())
568 .zip(g.iter().rev())
569 .map(|(l, r)| *l * *r)
570 .fold(FF::ZERO, |l, r| l + r);
571 g.push(-inner_product * lc_inv);
572 }
573
574 Polynomial::new(g)
575 }
576
577 pub(crate) fn reverse(&self) -> Polynomial<'static, FF> {
578 let degree = self.degree();
579 let new_coefficients = self
580 .coefficients
581 .iter()
582 .take((degree + 1) as usize)
583 .copied()
584 .rev()
585 .collect_vec();
586 Polynomial::new(new_coefficients)
587 }
588
589 pub fn into_owned(self) -> Polynomial<'static, FF> {
592 Polynomial::new(self.coefficients.into_owned())
593 }
594}
595
596impl<FF> Polynomial<'_, FF>
597where
598 FF: FiniteField + MulAssign<BFieldElement>,
599{
600 const FAST_MULTIPLY_CUTOFF_THRESHOLD: isize = 1 << 8;
605
606 const FAST_INTERPOLATE_CUTOFF_THRESHOLD_SEQUENTIAL: usize = 1 << 12;
611
612 const FAST_INTERPOLATE_CUTOFF_THRESHOLD_PARALLEL: usize = 1 << 8;
617
618 const FAST_MODULAR_COSET_INTERPOLATE_CUTOFF_THRESHOLD_PREFER_LAGRANGE: usize = 1 << 8;
623
624 const FAST_MODULAR_COSET_INTERPOLATE_CUTOFF_THRESHOLD_PREFER_INTT: usize = 1 << 17;
628
629 const FAST_COSET_EXTRAPOLATE_THRESHOLD: usize = 100;
633
634 const FORMAL_POWER_SERIES_INVERSE_CUTOFF: isize = 1 << 8;
639
640 const FAST_REDUCE_CUTOFF_THRESHOLD: usize = 1 << 8;
647
648 const REDUCE_BEFORE_EVALUATE_THRESHOLD_RATIO: isize = 4;
652
653 #[must_use]
658 pub fn scale<S, XF>(&self, alpha: S) -> Polynomial<'static, XF>
659 where
660 S: Clone + One,
661 FF: Mul<S, Output = XF>,
662 XF: FiniteField,
663 {
664 let mut power_of_alpha = S::one();
665 let mut return_coefficients = Vec::with_capacity(self.coefficients.len());
666 for &coefficient in self.coefficients.iter() {
667 return_coefficients.push(coefficient * power_of_alpha.clone());
668 power_of_alpha = power_of_alpha * alpha.clone();
669 }
670 Polynomial::new(return_coefficients)
671 }
672
673 #[must_use]
676 pub fn fast_square(&self) -> Polynomial<'static, FF> {
677 let degree = self.degree();
678 if degree == -1 {
679 return Polynomial::zero();
680 }
681 if degree == 0 {
682 return Polynomial::from_constant(self.coefficients[0] * self.coefficients[0]);
683 }
684
685 let result_degree: u64 = 2 * self.degree() as u64;
686 let order = (result_degree + 1).next_power_of_two();
687
688 let mut coefficients = self.coefficients.to_vec();
689 coefficients.resize(order as usize, FF::ZERO);
690 ntt::<FF>(&mut coefficients);
691
692 for element in coefficients.iter_mut() {
693 *element = element.to_owned() * element.to_owned();
694 }
695
696 intt::<FF>(&mut coefficients);
697 coefficients.truncate(result_degree as usize + 1);
698
699 Polynomial::new(coefficients)
700 }
701
702 #[must_use]
703 pub fn square(&self) -> Polynomial<'static, FF> {
704 let degree = self.degree();
705 if degree == -1 {
706 return Polynomial::zero();
707 }
708
709 let squared_coefficient_len = self.degree() as usize * 2 + 1;
712 if squared_coefficient_len > 64 {
713 return self.fast_square();
714 }
715
716 let zero = FF::ZERO;
717 let one = FF::ONE;
718 let two = one + one;
719 let mut squared_coefficients = vec![zero; squared_coefficient_len];
720
721 for i in 0..self.coefficients.len() {
723 let ci = self.coefficients[i];
724 squared_coefficients[2 * i] += ci * ci;
725
726 for j in i + 1..self.coefficients.len() {
727 let cj = self.coefficients[j];
728 squared_coefficients[i + j] += two * ci * cj;
729 }
730 }
731
732 Polynomial::new(squared_coefficients)
733 }
734
735 #[must_use]
739 pub fn fast_pow(&self, pow: u32) -> Polynomial<'static, FF> {
740 let Some(bit_length) = pow.checked_ilog2() else {
742 return Polynomial::one();
743 };
744
745 if self.degree() < 0 {
746 return Polynomial::zero();
747 }
748
749 let mut acc = Polynomial::one();
751 for i in 0..=bit_length {
752 acc = acc.square();
753 let bit_is_set = (pow >> (bit_length - i)) & 1 == 1;
754 if bit_is_set {
755 acc = self.multiply(&acc);
756 }
757 }
758
759 acc
760 }
761
762 #[must_use]
767 pub fn multiply<FF2>(
768 &self,
769 other: &Polynomial<'_, FF2>,
770 ) -> Polynomial<'static, <FF as Mul<FF2>>::Output>
771 where
772 FF: Mul<FF2>,
773 FF2: FiniteField + MulAssign<BFieldElement>,
774 <FF as Mul<FF2>>::Output: FiniteField + MulAssign<BFieldElement>,
775 {
776 if self.degree() + other.degree() < Self::FAST_MULTIPLY_CUTOFF_THRESHOLD {
777 self.naive_multiply(other)
778 } else {
779 self.fast_multiply(other)
780 }
781 }
782
783 #[doc(hidden)]
792 pub fn fast_multiply<FF2>(
793 &self,
794 other: &Polynomial<FF2>,
795 ) -> Polynomial<'static, <FF as Mul<FF2>>::Output>
796 where
797 FF: Mul<FF2>,
798 FF2: FiniteField + MulAssign<BFieldElement>,
799 <FF as Mul<FF2>>::Output: FiniteField + MulAssign<BFieldElement>,
800 {
801 let Ok(degree) = usize::try_from(self.degree() + other.degree()) else {
802 return Polynomial::zero();
803 };
804 let order = (degree + 1).next_power_of_two();
805
806 let mut lhs_coefficients = self.coefficients.to_vec();
807 let mut rhs_coefficients = other.coefficients.to_vec();
808
809 lhs_coefficients.resize(order, FF::ZERO);
810 rhs_coefficients.resize(order, FF2::ZERO);
811
812 ntt(&mut lhs_coefficients);
813 ntt(&mut rhs_coefficients);
814
815 let mut hadamard_product = lhs_coefficients
816 .into_iter()
817 .zip(rhs_coefficients)
818 .map(|(l, r)| l * r)
819 .collect_vec();
820
821 intt(&mut hadamard_product);
822 hadamard_product.truncate(degree + 1);
823 Polynomial::new(hadamard_product)
824 }
825
826 pub fn batch_multiply(factors: &[Self]) -> Polynomial<'static, FF> {
828 if factors.is_empty() {
836 return Polynomial::one();
837 }
838 let mut products = factors.to_vec();
839 while products.len() != 1 {
840 products = products
841 .chunks(2)
842 .map(|chunk| match chunk.len() {
843 2 => chunk[0].multiply(&chunk[1]),
844 1 => chunk[0].clone(),
845 _ => unreachable!(),
846 })
847 .collect();
848 }
849
850 let product_coeffs = products.pop().unwrap().coefficients.into_owned();
855 Polynomial::new(product_coeffs)
856 }
857
858 pub fn par_batch_multiply(factors: &[Self]) -> Polynomial<'static, FF> {
860 if factors.is_empty() {
861 return Polynomial::one();
862 }
863 let num_threads = available_parallelism()
864 .map(|non_zero_usize| non_zero_usize.get())
865 .unwrap_or(1);
866 let mut products = factors.to_vec();
867 while products.len() != 1 {
868 let chunk_size = usize::max(2, products.len() / num_threads);
869 products = products
870 .par_chunks(chunk_size)
871 .map(Self::batch_multiply)
872 .collect();
873 }
874
875 let product_coeffs = products.pop().unwrap().coefficients.into_owned();
876 Polynomial::new(product_coeffs)
877 }
878
879 pub fn reduce(&self, modulus: &Polynomial<'_, FF>) -> Polynomial<'static, FF> {
883 const FAST_REDUCE_MAKES_SENSE_MULTIPLE: isize = 4;
884 if modulus.degree() < 0 {
885 panic!("Cannot divide by zero; needed for reduce.");
886 } else if modulus.degree() == 0 {
887 Polynomial::zero()
888 } else if self.degree() < modulus.degree() {
889 self.clone().into_owned()
890 } else if self.degree() > FAST_REDUCE_MAKES_SENSE_MULTIPLE * modulus.degree() {
891 self.fast_reduce(modulus)
892 } else {
893 self.reduce_long_division(modulus)
894 }
895 }
896
897 pub fn fast_reduce(&self, modulus: &Self) -> Polynomial<'static, FF> {
904 if modulus.degree() == 0 {
905 return Polynomial::zero();
906 }
907 if self.degree() < modulus.degree() {
908 return self.clone().into_owned();
909 }
910
911 let (shift_factor_ntt, tail_size) = modulus.shift_factor_ntt_with_tail_length();
922 let mut intermediate_remainder =
923 self.reduce_by_ntt_friendly_modulus(&shift_factor_ntt, tail_size);
924
925 if intermediate_remainder.degree() > 4 * modulus.degree() {
932 let structured_multiple = modulus.structured_multiple();
933 intermediate_remainder =
934 intermediate_remainder.reduce_by_structured_modulus(&structured_multiple);
935 }
936
937 intermediate_remainder.reduce_long_division(modulus)
939 }
940
941 #[doc(hidden)]
944 pub fn shift_factor_ntt_with_tail_length(&self) -> (Vec<FF>, usize)
945 where
946 FF: 'static,
947 {
948 let n = usize::max(
949 Self::FAST_REDUCE_CUTOFF_THRESHOLD,
950 self.degree() as usize * 2,
951 )
952 .next_power_of_two();
953 let ntt_friendly_multiple = self.structured_multiple_of_degree(n);
954
955 let m = 1 + ntt_friendly_multiple
957 .coefficients
958 .iter()
959 .enumerate()
960 .rev()
961 .skip(1)
962 .find_map(|(i, c)| if !c.is_zero() { Some(i) } else { None })
963 .unwrap_or(0);
964 let mut shift_factor_ntt = ntt_friendly_multiple.coefficients[..n].to_vec();
965 ntt(&mut shift_factor_ntt);
966 (shift_factor_ntt, m)
967 }
968
969 #[doc(hidden)]
980 pub fn reduce_by_ntt_friendly_modulus(
981 &self,
982 shift_ntt: &[FF],
983 tail_length: usize,
984 ) -> Polynomial<'static, FF> {
985 let domain_length = shift_ntt.len();
986 assert!(domain_length.is_power_of_two());
987 let chunk_size = domain_length - tail_length;
988
989 if self.coefficients.len() < chunk_size + tail_length {
990 return self.clone().into_owned();
991 }
992 let num_reducible_chunks =
993 (self.coefficients.len() - (tail_length + chunk_size)).div_ceil(chunk_size);
994
995 let range_start = num_reducible_chunks * chunk_size;
996 let mut working_window = if range_start >= self.coefficients.len() {
997 vec![FF::ZERO; chunk_size + tail_length]
998 } else {
999 self.coefficients[range_start..].to_vec()
1000 };
1001 working_window.resize(chunk_size + tail_length, FF::ZERO);
1002
1003 for chunk_index in (0..num_reducible_chunks).rev() {
1004 let mut product = [
1005 working_window[tail_length..].to_vec(),
1006 vec![FF::ZERO; tail_length],
1007 ]
1008 .concat();
1009 ntt(&mut product);
1010 product
1011 .iter_mut()
1012 .zip(shift_ntt.iter())
1013 .for_each(|(l, r)| *l *= *r);
1014 intt(&mut product);
1015
1016 working_window = [
1017 vec![FF::ZERO; chunk_size],
1018 working_window[0..tail_length].to_vec(),
1019 ]
1020 .concat();
1021 for (i, wwi) in working_window.iter_mut().enumerate().take(chunk_size) {
1022 *wwi = self.coefficients[chunk_index * chunk_size + i];
1023 }
1024
1025 for (i, wwi) in working_window
1026 .iter_mut()
1027 .enumerate()
1028 .take(chunk_size + tail_length)
1029 {
1030 *wwi -= product[i];
1031 }
1032 }
1033
1034 Polynomial::new(working_window)
1035 }
1036
1037 fn structured_multiple(&self) -> Polynomial<'static, FF> {
1044 let n = usize::try_from(self.degree()).expect("cannot compute multiple of zero");
1045 self.structured_multiple_of_degree(3 * n + 1)
1046 }
1047
1048 pub fn structured_multiple_of_degree(&self, n: usize) -> Polynomial<'static, FF> {
1055 let Ok(degree) = usize::try_from(self.degree()) else {
1056 panic!("cannot compute multiples of zero");
1057 };
1058 assert!(degree <= n, "cannot compute multiple of smaller degree.");
1059 if degree == 0 {
1060 return Polynomial::new(
1061 [vec![FF::ZERO; n], vec![self.coefficients[0].inverse()]].concat(),
1062 );
1063 }
1064
1065 let reverse = self.reverse();
1066
1067 let inverse_reverse = reverse.formal_power_series_inverse_minimal(n - degree);
1073 let product_reverse = reverse.multiply(&inverse_reverse);
1074 let product = product_reverse.reverse();
1075
1076 let product_degree = product.degree() as usize;
1078 product.shift_coefficients(n - product_degree)
1079 }
1080
1081 fn reduce_by_structured_modulus(&self, multiple: &Self) -> Polynomial<'static, FF> {
1092 assert_ne!(0, multiple.degree());
1093 let multiple_degree = usize::try_from(multiple.degree()).expect("cannot reduce by zero");
1094 assert_eq!(
1095 Some(FF::ONE),
1096 multiple.leading_coefficient(),
1097 "multiple must be monic"
1098 );
1099 let leading_term = Polynomial::x_to_the(multiple_degree);
1100 let shift_polynomial = multiple.clone() - leading_term.clone();
1101 assert!(shift_polynomial.degree() < multiple.degree());
1102
1103 let tail_length = usize::try_from(shift_polynomial.degree())
1104 .map(|unsigned_degree| unsigned_degree + 1)
1105 .unwrap_or(0);
1106 let window_length = multiple_degree;
1107 let chunk_size = window_length - tail_length;
1108 if self.coefficients.len() < chunk_size + tail_length {
1109 return self.clone().into_owned();
1110 }
1111 let num_reducible_chunks =
1112 (self.coefficients.len() - (tail_length + chunk_size)).div_ceil(chunk_size);
1113
1114 let window_stop = (tail_length + chunk_size) + num_reducible_chunks * chunk_size;
1115 let mut window_start = window_stop - window_length;
1116 let mut working_window = self.coefficients[window_start..].to_vec();
1117 working_window.resize(chunk_size + tail_length, FF::ZERO);
1118
1119 for _ in (0..num_reducible_chunks).rev() {
1120 let overflow = Polynomial::new(working_window[tail_length..].to_vec());
1121 let product = overflow.multiply(&shift_polynomial);
1122
1123 window_start -= chunk_size;
1124 working_window = [
1125 self.coefficients[window_start..window_start + chunk_size].to_vec(),
1126 working_window[0..tail_length].to_vec(),
1127 ]
1128 .concat();
1129
1130 for (i, wwi) in working_window
1131 .iter_mut()
1132 .enumerate()
1133 .take(chunk_size + tail_length)
1134 {
1135 *wwi -= *product.coefficients.get(i).unwrap_or(&FF::ZERO);
1136 }
1137 }
1138
1139 Polynomial::new(working_window)
1140 }
1141
1142 fn reduce_long_division(&self, modulus: &Polynomial<'_, FF>) -> Polynomial<'static, FF> {
1143 let (_quotient, remainder) = self.divide(modulus);
1144 remainder
1145 }
1146
1147 pub fn formal_power_series_inverse_newton(self, precision: usize) -> Polynomial<'static, FF> {
1175 let self_degree = self.degree();
1177 if self_degree == 0 {
1178 return Polynomial::from_constant(self.coefficients[0].inverse());
1179 }
1180
1181 let num_rounds = precision.next_power_of_two().ilog2();
1183
1184 let switch_point = if Self::FORMAL_POWER_SERIES_INVERSE_CUTOFF < self_degree {
1187 0
1188 } else {
1189 (Self::FORMAL_POWER_SERIES_INVERSE_CUTOFF / self_degree).ilog2()
1190 };
1191
1192 let cc = self.coefficients[0];
1193
1194 let mut f = Polynomial::from_constant(cc.inverse());
1196 for _ in 0..u32::min(num_rounds, switch_point) {
1197 let sub = f.multiply(&f).multiply(&self);
1198 f.scalar_mul_mut(FF::from(2));
1199 f = f - sub;
1200 }
1201
1202 if switch_point >= num_rounds {
1204 return f;
1205 }
1206
1207 let full_domain_length =
1211 ((1 << (num_rounds + 1)) * self_degree as usize).next_power_of_two();
1212
1213 let mut self_ntt = self.coefficients.into_owned();
1214 self_ntt.resize(full_domain_length, FF::ZERO);
1215 ntt(&mut self_ntt);
1216
1217 let mut current_domain_length = f.coefficients.len().next_power_of_two();
1219
1220 let lde = |v: &mut [FF], old_domain_length: usize, new_domain_length: usize| {
1222 intt(&mut v[..old_domain_length]);
1223 ntt(&mut v[..new_domain_length]);
1224 };
1225
1226 let mut f_degree = f.degree();
1228
1229 let mut f_ntt = f.coefficients.into_owned();
1231 f_ntt.resize(full_domain_length, FF::ZERO);
1232 ntt(&mut f_ntt[..current_domain_length]);
1233
1234 for _ in switch_point..num_rounds {
1235 f_degree = 2 * f_degree + self_degree;
1236 if f_degree as usize >= current_domain_length {
1237 let next_domain_length = (1 + f_degree as usize).next_power_of_two();
1238 lde(&mut f_ntt, current_domain_length, next_domain_length);
1239 current_domain_length = next_domain_length;
1240 }
1241 f_ntt
1242 .iter_mut()
1243 .zip(
1244 self_ntt
1245 .iter()
1246 .step_by(full_domain_length / current_domain_length),
1247 )
1248 .for_each(|(ff, dd)| *ff = FF::from(2) * *ff - *ff * *ff * *dd);
1249 }
1250
1251 intt(&mut f_ntt[..current_domain_length]);
1252 Polynomial::new(f_ntt)
1253 }
1254
1255 pub fn fast_coset_evaluate<S>(&self, offset: S, order: usize) -> Vec<FF>
1266 where
1267 S: Clone + One,
1268 FF: Mul<S, Output = FF> + 'static,
1269 {
1270 assert!(
1278 (order as isize) > self.degree(),
1279 "`Polynomial::fast_coset_evaluate` is currently limited to domains of order \
1280 greater than the degree of the polynomial."
1281 );
1282
1283 let mut coefficients = self.scale(offset).coefficients.into_owned();
1284 coefficients.resize(order, FF::ZERO);
1285 ntt(&mut coefficients);
1286
1287 coefficients
1288 }
1289}
1290
1291impl<FF> Polynomial<'static, FF>
1292where
1293 FF: FiniteField + MulAssign<BFieldElement>,
1294{
1295 const FAST_ZEROFIER_CUTOFF_THRESHOLD: usize = 100;
1305
1306 pub fn zerofier(roots: &[FF]) -> Self {
1324 if roots.len() < Self::FAST_ZEROFIER_CUTOFF_THRESHOLD {
1325 Self::smart_zerofier(roots)
1326 } else {
1327 Self::fast_zerofier(roots)
1328 }
1329 }
1330
1331 pub fn par_zerofier(roots: &[FF]) -> Self {
1333 if roots.is_empty() {
1334 return Polynomial::one();
1335 }
1336 let num_threads = available_parallelism()
1337 .map(|non_zero_usize| non_zero_usize.get())
1338 .unwrap_or(1);
1339 let chunk_size = roots
1340 .len()
1341 .div_ceil(num_threads)
1342 .max(Self::FAST_ZEROFIER_CUTOFF_THRESHOLD);
1343 let factors = roots
1344 .par_chunks(chunk_size)
1345 .map(|chunk| Self::zerofier(chunk))
1346 .collect::<Vec<_>>();
1347 Polynomial::par_batch_multiply(&factors)
1348 }
1349
1350 #[doc(hidden)]
1352 pub fn smart_zerofier(roots: &[FF]) -> Self {
1353 let mut zerofier = vec![FF::ZERO; roots.len() + 1];
1354 zerofier[0] = FF::ONE;
1355 let mut num_coeffs = 1;
1356 for &root in roots {
1357 for k in (1..=num_coeffs).rev() {
1358 zerofier[k] = zerofier[k - 1] - root * zerofier[k];
1359 }
1360 zerofier[0] = -root * zerofier[0];
1361 num_coeffs += 1;
1362 }
1363 Self::new(zerofier)
1364 }
1365
1366 #[doc(hidden)]
1368 pub fn fast_zerofier(roots: &[FF]) -> Self {
1369 let mid_point = roots.len() / 2;
1370 let left = Self::zerofier(&roots[..mid_point]);
1371 let right = Self::zerofier(&roots[mid_point..]);
1372
1373 left.multiply(&right)
1374 }
1375
1376 pub fn interpolate(domain: &[FF], values: &[FF]) -> Self {
1393 assert!(
1394 !domain.is_empty(),
1395 "interpolation must happen through more than zero points"
1396 );
1397 assert_eq!(
1398 domain.len(),
1399 values.len(),
1400 "The domain and values lists have to be of equal length."
1401 );
1402
1403 if domain.len() <= Self::FAST_INTERPOLATE_CUTOFF_THRESHOLD_SEQUENTIAL {
1404 Self::lagrange_interpolate(domain, values)
1405 } else {
1406 Self::fast_interpolate(domain, values)
1407 }
1408 }
1409
1410 pub fn par_interpolate(domain: &[FF], values: &[FF]) -> Self {
1416 assert!(
1417 !domain.is_empty(),
1418 "interpolation must happen through more than zero points"
1419 );
1420 assert_eq!(
1421 domain.len(),
1422 values.len(),
1423 "The domain and values lists have to be of equal length."
1424 );
1425
1426 if domain.len() <= Self::FAST_INTERPOLATE_CUTOFF_THRESHOLD_PARALLEL {
1429 Self::lagrange_interpolate(domain, values)
1430 } else {
1431 Self::par_fast_interpolate(domain, values)
1432 }
1433 }
1434
1435 #[doc(hidden)]
1439 pub fn lagrange_interpolate_zipped(points: &[(FF, FF)]) -> Self {
1440 assert!(
1441 !points.is_empty(),
1442 "interpolation must happen through more than zero points"
1443 );
1444 assert!(
1445 points.iter().map(|x| x.0).all_unique(),
1446 "Repeated x values received. Got: {points:?}",
1447 );
1448
1449 let xs: Vec<FF> = points.iter().map(|x| x.0.to_owned()).collect();
1450 let ys: Vec<FF> = points.iter().map(|x| x.1.to_owned()).collect();
1451 Self::lagrange_interpolate(&xs, &ys)
1452 }
1453
1454 #[doc(hidden)]
1455 pub fn lagrange_interpolate(domain: &[FF], values: &[FF]) -> Self {
1456 debug_assert!(
1457 !domain.is_empty(),
1458 "interpolation domain cannot have zero points"
1459 );
1460 debug_assert_eq!(domain.len(), values.len());
1461
1462 let zero = FF::ZERO;
1463 let zerofier = Self::zerofier(domain).coefficients;
1464
1465 let mut lagrange_sum_array = vec![zero; domain.len()];
1469 let mut summand_array = vec![zero; domain.len()];
1470 for (i, &abscis) in values.iter().enumerate() {
1471 let mut leading_coefficient = zerofier[domain.len()];
1473 let mut supporting_coefficient = zerofier[domain.len() - 1];
1474 let mut summand_eval = zero;
1475 for j in (1..domain.len()).rev() {
1476 summand_array[j] = leading_coefficient;
1477 summand_eval = summand_eval * domain[i] + leading_coefficient;
1478 leading_coefficient = supporting_coefficient + leading_coefficient * domain[i];
1479 supporting_coefficient = zerofier[j - 1];
1480 }
1481
1482 summand_array[0] = leading_coefficient;
1484 summand_eval = summand_eval * domain[i] + leading_coefficient;
1485
1486 let corrected_abscis = abscis / summand_eval;
1488
1489 for j in 0..domain.len() {
1491 lagrange_sum_array[j] += corrected_abscis * summand_array[j];
1492 }
1493 }
1494
1495 Self::new(lagrange_sum_array)
1496 }
1497
1498 #[doc(hidden)]
1500 pub fn fast_interpolate(domain: &[FF], values: &[FF]) -> Self {
1501 debug_assert!(
1502 !domain.is_empty(),
1503 "interpolation domain cannot have zero points"
1504 );
1505 debug_assert_eq!(domain.len(), values.len());
1506
1507 if domain.len() == 1 {
1509 return Self::from_constant(values[0]);
1510 }
1511
1512 let mid_point = domain.len() / 2;
1513 let left_domain_half = &domain[..mid_point];
1514 let left_values_half = &values[..mid_point];
1515 let right_domain_half = &domain[mid_point..];
1516 let right_values_half = &values[mid_point..];
1517
1518 let left_zerofier = Self::zerofier(left_domain_half);
1519 let right_zerofier = Self::zerofier(right_domain_half);
1520
1521 let left_offset = right_zerofier.batch_evaluate(left_domain_half);
1522 let right_offset = left_zerofier.batch_evaluate(right_domain_half);
1523
1524 let hadamard_mul = |x: &[_], y: Vec<_>| x.iter().zip(y).map(|(&n, d)| n * d).collect_vec();
1525 let interpolate_half = |offset, domain_half, values_half| {
1526 let offset_inverse = FF::batch_inversion(offset);
1527 let targets = hadamard_mul(values_half, offset_inverse);
1528 Self::interpolate(domain_half, &targets)
1529 };
1530 let (left_interpolant, right_interpolant) = (
1531 interpolate_half(left_offset, left_domain_half, left_values_half),
1532 interpolate_half(right_offset, right_domain_half, right_values_half),
1533 );
1534
1535 let (left_term, right_term) = (
1536 left_interpolant.multiply(&right_zerofier),
1537 right_interpolant.multiply(&left_zerofier),
1538 );
1539
1540 left_term + right_term
1541 }
1542
1543 #[doc(hidden)]
1545 pub fn par_fast_interpolate(domain: &[FF], values: &[FF]) -> Self {
1546 debug_assert!(
1547 !domain.is_empty(),
1548 "interpolation domain cannot have zero points"
1549 );
1550 debug_assert_eq!(domain.len(), values.len());
1551
1552 if domain.len() == 1 {
1554 return Self::from_constant(values[0]);
1555 }
1556
1557 let mid_point = domain.len() / 2;
1558 let left_domain_half = &domain[..mid_point];
1559 let left_values_half = &values[..mid_point];
1560 let right_domain_half = &domain[mid_point..];
1561 let right_values_half = &values[mid_point..];
1562
1563 let (left_zerofier, right_zerofier) = rayon::join(
1564 || Self::zerofier(left_domain_half),
1565 || Self::zerofier(right_domain_half),
1566 );
1567
1568 let (left_offset, right_offset) = rayon::join(
1569 || right_zerofier.par_batch_evaluate(left_domain_half),
1570 || left_zerofier.par_batch_evaluate(right_domain_half),
1571 );
1572
1573 let hadamard_mul = |x: &[_], y: Vec<_>| x.iter().zip(y).map(|(&n, d)| n * d).collect_vec();
1574 let interpolate_half = |offset, domain_half, values_half| {
1575 let offset_inverse = FF::batch_inversion(offset);
1576 let targets = hadamard_mul(values_half, offset_inverse);
1577 Self::par_interpolate(domain_half, &targets)
1578 };
1579 let (left_interpolant, right_interpolant) = rayon::join(
1580 || interpolate_half(left_offset, left_domain_half, left_values_half),
1581 || interpolate_half(right_offset, right_domain_half, right_values_half),
1582 );
1583
1584 let (left_term, right_term) = rayon::join(
1585 || left_interpolant.multiply(&right_zerofier),
1586 || right_interpolant.multiply(&left_zerofier),
1587 );
1588
1589 left_term + right_term
1590 }
1591
1592 pub fn batch_fast_interpolate(
1593 domain: &[FF],
1594 values_matrix: &[Vec<FF>],
1595 primitive_root: BFieldElement,
1596 root_order: usize,
1597 ) -> Vec<Self> {
1598 debug_assert_eq!(
1599 primitive_root.mod_pow_u32(root_order as u32),
1600 BFieldElement::ONE,
1601 "Supplied element โprimitive_rootโ must have supplied order.\
1602 Supplied element was: {primitive_root:?}\
1603 Supplied order was: {root_order:?}"
1604 );
1605
1606 assert!(
1607 !domain.is_empty(),
1608 "Cannot fast interpolate through zero points.",
1609 );
1610
1611 let mut zerofier_dictionary: HashMap<(FF, FF), Polynomial<FF>> = HashMap::default();
1612 let mut offset_inverse_dictionary: HashMap<(FF, FF), Vec<FF>> = HashMap::default();
1613
1614 Self::batch_fast_interpolate_with_memoization(
1615 domain,
1616 values_matrix,
1617 &mut zerofier_dictionary,
1618 &mut offset_inverse_dictionary,
1619 )
1620 }
1621
1622 fn batch_fast_interpolate_with_memoization(
1623 domain: &[FF],
1624 values_matrix: &[Vec<FF>],
1625 zerofier_dictionary: &mut HashMap<(FF, FF), Polynomial<'static, FF>>,
1626 offset_inverse_dictionary: &mut HashMap<(FF, FF), Vec<FF>>,
1627 ) -> Vec<Self> {
1628 const OPTIMAL_CUTOFF_POINT_FOR_BATCHED_INTERPOLATION: usize = 16;
1631 if domain.len() < OPTIMAL_CUTOFF_POINT_FOR_BATCHED_INTERPOLATION {
1632 return values_matrix
1633 .iter()
1634 .map(|values| Self::lagrange_interpolate(domain, values))
1635 .collect();
1636 }
1637
1638 let half = domain.len() / 2;
1640
1641 let left_key = (domain[0], domain[half - 1]);
1642 let left_zerofier = match zerofier_dictionary.get(&left_key) {
1643 Some(z) => z.to_owned(),
1644 None => {
1645 let left_zerofier = Self::zerofier(&domain[..half]);
1646 zerofier_dictionary.insert(left_key, left_zerofier.clone());
1647 left_zerofier
1648 }
1649 };
1650 let right_key = (domain[half], *domain.last().unwrap());
1651 let right_zerofier = match zerofier_dictionary.get(&right_key) {
1652 Some(z) => z.to_owned(),
1653 None => {
1654 let right_zerofier = Self::zerofier(&domain[half..]);
1655 zerofier_dictionary.insert(right_key, right_zerofier.clone());
1656 right_zerofier
1657 }
1658 };
1659
1660 let left_offset_inverse = match offset_inverse_dictionary.get(&left_key) {
1661 Some(vector) => vector.to_owned(),
1662 None => {
1663 let left_offset: Vec<FF> = Self::batch_evaluate(&right_zerofier, &domain[..half]);
1664 let left_offset_inverse = FF::batch_inversion(left_offset);
1665 offset_inverse_dictionary.insert(left_key, left_offset_inverse.clone());
1666 left_offset_inverse
1667 }
1668 };
1669 let right_offset_inverse = match offset_inverse_dictionary.get(&right_key) {
1670 Some(vector) => vector.to_owned(),
1671 None => {
1672 let right_offset: Vec<FF> = Self::batch_evaluate(&left_zerofier, &domain[half..]);
1673 let right_offset_inverse = FF::batch_inversion(right_offset);
1674 offset_inverse_dictionary.insert(right_key, right_offset_inverse.clone());
1675 right_offset_inverse
1676 }
1677 };
1678
1679 let all_left_targets: Vec<_> = values_matrix
1681 .par_iter()
1682 .map(|values| {
1683 values[..half]
1684 .iter()
1685 .zip(left_offset_inverse.iter())
1686 .map(|(n, d)| n.to_owned() * *d)
1687 .collect()
1688 })
1689 .collect();
1690 let all_right_targets: Vec<_> = values_matrix
1691 .par_iter()
1692 .map(|values| {
1693 values[half..]
1694 .par_iter()
1695 .zip(right_offset_inverse.par_iter())
1696 .map(|(n, d)| n.to_owned() * *d)
1697 .collect()
1698 })
1699 .collect();
1700
1701 let left_interpolants = Self::batch_fast_interpolate_with_memoization(
1703 &domain[..half],
1704 &all_left_targets,
1705 zerofier_dictionary,
1706 offset_inverse_dictionary,
1707 );
1708 let right_interpolants = Self::batch_fast_interpolate_with_memoization(
1709 &domain[half..],
1710 &all_right_targets,
1711 zerofier_dictionary,
1712 offset_inverse_dictionary,
1713 );
1714
1715 let interpolants = left_interpolants
1717 .par_iter()
1718 .zip(right_interpolants.par_iter())
1719 .map(|(left_interpolant, right_interpolant)| {
1720 let left_term = left_interpolant.multiply(&right_zerofier);
1721 let right_term = right_interpolant.multiply(&left_zerofier);
1722
1723 left_term + right_term
1724 })
1725 .collect();
1726
1727 interpolants
1728 }
1729
1730 pub fn batch_evaluate(&self, domain: &[FF]) -> Vec<FF> {
1732 if self.is_zero() {
1733 vec![FF::ZERO; domain.len()]
1734 } else if self.degree()
1735 >= Self::REDUCE_BEFORE_EVALUATE_THRESHOLD_RATIO * (domain.len() as isize)
1736 {
1737 self.reduce_then_batch_evaluate(domain)
1738 } else {
1739 let zerofier_tree = ZerofierTree::new_from_domain(domain);
1740 self.divide_and_conquer_batch_evaluate(&zerofier_tree)
1741 }
1742 }
1743
1744 fn reduce_then_batch_evaluate(&self, domain: &[FF]) -> Vec<FF> {
1745 let zerofier_tree = ZerofierTree::new_from_domain(domain);
1746 let zerofier = zerofier_tree.zerofier();
1747 let remainder = self.fast_reduce(&zerofier);
1748 remainder.divide_and_conquer_batch_evaluate(&zerofier_tree)
1749 }
1750
1751 pub fn par_batch_evaluate(&self, domain: &[FF]) -> Vec<FF> {
1753 if domain.is_empty() || self.is_zero() {
1754 return vec![FF::ZERO; domain.len()];
1755 }
1756 let num_threads = available_parallelism()
1757 .map(|non_zero_usize| non_zero_usize.get())
1758 .unwrap_or(1);
1759 let chunk_size = domain.len().div_ceil(num_threads);
1760 domain
1761 .par_chunks(chunk_size)
1762 .flat_map(|ch| self.batch_evaluate(ch))
1763 .collect()
1764 }
1765
1766 #[doc(hidden)]
1768 pub fn iterative_batch_evaluate(&self, domain: &[FF]) -> Vec<FF> {
1769 domain.iter().map(|&p| self.evaluate(p)).collect()
1770 }
1771
1772 #[doc(hidden)]
1774 pub fn divide_and_conquer_batch_evaluate(&self, zerofier_tree: &ZerofierTree<FF>) -> Vec<FF> {
1775 match zerofier_tree {
1776 ZerofierTree::Leaf(leaf) => self
1777 .reduce(&zerofier_tree.zerofier())
1778 .iterative_batch_evaluate(&leaf.points),
1779 ZerofierTree::Branch(branch) => [
1780 self.divide_and_conquer_batch_evaluate(&branch.left),
1781 self.divide_and_conquer_batch_evaluate(&branch.right),
1782 ]
1783 .concat(),
1784 ZerofierTree::Padding => vec![],
1785 }
1786 }
1787
1788 pub fn fast_coset_interpolate<S>(offset: S, values: &[FF]) -> Self
1800 where
1801 S: Clone + One + Inverse,
1802 FF: Mul<S, Output = FF>,
1803 {
1804 let mut mut_values = values.to_vec();
1805
1806 intt(&mut mut_values);
1807 let poly = Polynomial::new(mut_values);
1808
1809 poly.scale(offset.inverse())
1810 }
1811
1812 pub fn truncate(&self, k: usize) -> Self {
1826 let coefficients = self.coefficients.iter().copied();
1827 let coefficients = coefficients.rev().take(k + 1).rev().collect();
1828 Self::new(coefficients)
1829 }
1830
1831 pub fn mod_x_to_the_n(&self, n: usize) -> Self {
1844 let num_coefficients_to_retain = n.min(self.coefficients.len());
1845 Self::new(self.coefficients[..num_coefficients_to_retain].into())
1846 }
1847
1848 #[doc(hidden)]
1852 pub fn fast_modular_coset_interpolate_preprocess(
1853 n: usize,
1854 offset: BFieldElement,
1855 modulus: &Polynomial<FF>,
1856 ) -> ModularInterpolationPreprocessingData<'static, FF> {
1857 let omega = BFieldElement::primitive_root_of_unity(n as u64).unwrap();
1858 let modular_squares = (0..n.ilog2())
1860 .scan(Polynomial::<FF>::x_to_the(1), |acc, _| {
1861 let yld = acc.clone();
1862 *acc = acc.multiply(acc).reduce(modulus);
1863 Some(yld)
1864 })
1865 .collect_vec();
1866 let even_zerofiers = (0..n.ilog2())
1867 .map(|i| offset.inverse().mod_pow(1u64 << i))
1868 .zip(modular_squares.iter())
1869 .map(|(lc, sq)| sq.scalar_mul(FF::from(lc.value())) - Polynomial::one())
1870 .collect_vec();
1871 let odd_zerofiers = (0..n.ilog2())
1872 .map(|i| (offset * omega).inverse().mod_pow(1u64 << i))
1873 .zip(modular_squares.iter())
1874 .map(|(lc, sq)| sq.scalar_mul(FF::from(lc.value())) - Polynomial::one())
1875 .collect_vec();
1876
1877 let (shift_coefficients, tail_length) = modulus.shift_factor_ntt_with_tail_length();
1879
1880 ModularInterpolationPreprocessingData {
1881 even_zerofiers,
1882 odd_zerofiers,
1883 shift_coefficients,
1884 tail_length,
1885 }
1886 }
1887
1888 fn fast_modular_coset_interpolate(
1892 values: &[FF],
1893 offset: BFieldElement,
1894 modulus: &Polynomial<FF>,
1895 ) -> Self {
1896 let preprocessing_data =
1897 Self::fast_modular_coset_interpolate_preprocess(values.len(), offset, modulus);
1898 Self::fast_modular_coset_interpolate_with_zerofiers_and_ntt_friendly_multiple(
1899 values,
1900 offset,
1901 modulus,
1902 &preprocessing_data,
1903 )
1904 }
1905
1906 #[doc(hidden)]
1909 pub fn fast_modular_coset_interpolate_with_zerofiers_and_ntt_friendly_multiple(
1910 values: &[FF],
1911 offset: BFieldElement,
1912 modulus: &Polynomial<FF>,
1913 preprocessed: &ModularInterpolationPreprocessingData<FF>,
1914 ) -> Self {
1915 if modulus.degree() < 0 {
1916 panic!("cannot reduce modulo zero")
1917 };
1918 let n = values.len();
1919 let omega = BFieldElement::primitive_root_of_unity(n as u64).unwrap();
1920
1921 if n < Self::FAST_MODULAR_COSET_INTERPOLATE_CUTOFF_THRESHOLD_PREFER_LAGRANGE {
1922 let domain = (0..n)
1923 .scan(FF::from(offset.value()), |acc: &mut FF, _| {
1924 let yld = *acc;
1925 *acc *= omega;
1926 Some(yld)
1927 })
1928 .collect::<Vec<FF>>();
1929 let interpolant = Self::lagrange_interpolate(&domain, values);
1930 return interpolant.reduce(modulus);
1931 } else if n <= Self::FAST_MODULAR_COSET_INTERPOLATE_CUTOFF_THRESHOLD_PREFER_INTT {
1932 let mut coefficients = values.to_vec();
1933 intt(&mut coefficients);
1934 let interpolant = Polynomial::new(coefficients);
1935
1936 return interpolant
1937 .scale(FF::from(offset.inverse().value()))
1938 .reduce_by_ntt_friendly_modulus(
1939 &preprocessed.shift_coefficients,
1940 preprocessed.tail_length,
1941 )
1942 .reduce(modulus);
1943 }
1944
1945 const MINUS_TWO_INVERSE: BFieldElement = BFieldElement::MINUS_TWO_INVERSE;
1978 let even_zerofier_on_odd_domain_inverted = vec![FF::from(MINUS_TWO_INVERSE.value()); n / 2];
1979 let odd_zerofier_on_even_domain_inverted = vec![FF::from(MINUS_TWO_INVERSE.value()); n / 2];
1980
1981 let mut odd_domain_targets = even_zerofier_on_odd_domain_inverted;
1983 let mut even_domain_targets = odd_zerofier_on_even_domain_inverted;
1984 for i in 0..(n / 2) {
1985 even_domain_targets[i] *= values[2 * i];
1986 odd_domain_targets[i] *= values[2 * i + 1];
1987 }
1988
1989 let even_interpolant =
1991 Self::fast_modular_coset_interpolate(&even_domain_targets, offset, modulus);
1992 let odd_interpolant =
1993 Self::fast_modular_coset_interpolate(&odd_domain_targets, offset * omega, modulus);
1994
1995 let interpolant = even_interpolant
1997 .multiply(&preprocessed.odd_zerofiers[(n / 2).ilog2() as usize])
1998 + odd_interpolant.multiply(&preprocessed.even_zerofiers[(n / 2).ilog2() as usize]);
1999
2000 interpolant.reduce(modulus)
2002 }
2003
2004 pub fn coset_extrapolate(
2007 domain_offset: BFieldElement,
2008 codeword: &[FF],
2009 points: &[FF],
2010 ) -> Vec<FF> {
2011 if points.len() < Self::FAST_COSET_EXTRAPOLATE_THRESHOLD {
2012 Self::fast_coset_extrapolate(domain_offset, codeword, points)
2013 } else {
2014 Self::naive_coset_extrapolate(domain_offset, codeword, points)
2015 }
2016 }
2017
2018 fn naive_coset_extrapolate_preprocessing(points: &[FF]) -> (ZerofierTree<FF>, Vec<FF>, usize) {
2019 let zerofier_tree = ZerofierTree::new_from_domain(points);
2020 let (shift_coefficients, tail_length) =
2021 Self::shift_factor_ntt_with_tail_length(&zerofier_tree.zerofier());
2022 (zerofier_tree, shift_coefficients, tail_length)
2023 }
2024
2025 fn naive_coset_extrapolate(
2026 domain_offset: BFieldElement,
2027 codeword: &[FF],
2028 points: &[FF],
2029 ) -> Vec<FF> {
2030 let mut coefficients = codeword.to_vec();
2031 intt(&mut coefficients);
2032 let interpolant =
2033 Polynomial::new(coefficients).scale(FF::from(domain_offset.inverse().value()));
2034 interpolant.batch_evaluate(points)
2035 }
2036
2037 fn fast_coset_extrapolate(
2038 domain_offset: BFieldElement,
2039 codeword: &[FF],
2040 points: &[FF],
2041 ) -> Vec<FF> {
2042 let zerofier_tree = ZerofierTree::new_from_domain(points);
2043 let minimal_interpolant = Self::fast_modular_coset_interpolate(
2044 codeword,
2045 domain_offset,
2046 &zerofier_tree.zerofier(),
2047 );
2048 minimal_interpolant.divide_and_conquer_batch_evaluate(&zerofier_tree)
2049 }
2050
2051 pub fn batch_coset_extrapolate(
2076 domain_offset: BFieldElement,
2077 codeword_length: usize,
2078 codewords: &[FF],
2079 points: &[FF],
2080 ) -> Vec<FF> {
2081 if points.len() < Self::FAST_COSET_EXTRAPOLATE_THRESHOLD {
2082 Self::batch_fast_coset_extrapolate(domain_offset, codeword_length, codewords, points)
2083 } else {
2084 Self::batch_naive_coset_extrapolate(domain_offset, codeword_length, codewords, points)
2085 }
2086 }
2087
2088 fn batch_fast_coset_extrapolate(
2089 domain_offset: BFieldElement,
2090 codeword_length: usize,
2091 codewords: &[FF],
2092 points: &[FF],
2093 ) -> Vec<FF> {
2094 let n = codeword_length;
2095
2096 let zerofier_tree = ZerofierTree::new_from_domain(points);
2097 let modulus = zerofier_tree.zerofier();
2098 let preprocessing_data = Self::fast_modular_coset_interpolate_preprocess(
2099 codeword_length,
2100 domain_offset,
2101 &modulus,
2102 );
2103
2104 (0..codewords.len() / n)
2105 .flat_map(|i| {
2106 let codeword = &codewords[i * n..(i + 1) * n];
2107 let minimal_interpolant =
2108 Self::fast_modular_coset_interpolate_with_zerofiers_and_ntt_friendly_multiple(
2109 codeword,
2110 domain_offset,
2111 &modulus,
2112 &preprocessing_data,
2113 );
2114 minimal_interpolant.divide_and_conquer_batch_evaluate(&zerofier_tree)
2115 })
2116 .collect()
2117 }
2118
2119 fn batch_naive_coset_extrapolate(
2120 domain_offset: BFieldElement,
2121 codeword_length: usize,
2122 codewords: &[FF],
2123 points: &[FF],
2124 ) -> Vec<FF> {
2125 let (zerofier_tree, shift_coefficients, tail_length) =
2126 Self::naive_coset_extrapolate_preprocessing(points);
2127 let n = codeword_length;
2128
2129 (0..codewords.len() / n)
2130 .flat_map(|i| {
2131 let mut coefficients = codewords[i * n..(i + 1) * n].to_vec();
2132 intt(&mut coefficients);
2133 Polynomial::new(coefficients)
2134 .scale(FF::from(domain_offset.inverse().value()))
2135 .reduce_by_ntt_friendly_modulus(&shift_coefficients, tail_length)
2136 .divide_and_conquer_batch_evaluate(&zerofier_tree)
2137 })
2138 .collect()
2139 }
2140
2141 pub fn par_batch_coset_extrapolate(
2143 domain_offset: BFieldElement,
2144 codeword_length: usize,
2145 codewords: &[FF],
2146 points: &[FF],
2147 ) -> Vec<FF> {
2148 if points.len() < Self::FAST_COSET_EXTRAPOLATE_THRESHOLD {
2149 Self::par_batch_fast_coset_extrapolate(
2150 domain_offset,
2151 codeword_length,
2152 codewords,
2153 points,
2154 )
2155 } else {
2156 Self::par_batch_naive_coset_extrapolate(
2157 domain_offset,
2158 codeword_length,
2159 codewords,
2160 points,
2161 )
2162 }
2163 }
2164
2165 fn par_batch_fast_coset_extrapolate(
2166 domain_offset: BFieldElement,
2167 codeword_length: usize,
2168 codewords: &[FF],
2169 points: &[FF],
2170 ) -> Vec<FF> {
2171 let n = codeword_length;
2172
2173 let zerofier_tree = ZerofierTree::new_from_domain(points);
2174 let modulus = zerofier_tree.zerofier();
2175 let preprocessing_data = Self::fast_modular_coset_interpolate_preprocess(
2176 codeword_length,
2177 domain_offset,
2178 &modulus,
2179 );
2180
2181 (0..codewords.len() / n)
2182 .into_par_iter()
2183 .flat_map(|i| {
2184 let codeword = &codewords[i * n..(i + 1) * n];
2185 let minimal_interpolant =
2186 Self::fast_modular_coset_interpolate_with_zerofiers_and_ntt_friendly_multiple(
2187 codeword,
2188 domain_offset,
2189 &modulus,
2190 &preprocessing_data,
2191 );
2192 minimal_interpolant.divide_and_conquer_batch_evaluate(&zerofier_tree)
2193 })
2194 .collect()
2195 }
2196
2197 fn par_batch_naive_coset_extrapolate(
2198 domain_offset: BFieldElement,
2199 codeword_length: usize,
2200 codewords: &[FF],
2201 points: &[FF],
2202 ) -> Vec<FF> {
2203 let (zerofier_tree, shift_coefficients, tail_length) =
2204 Self::naive_coset_extrapolate_preprocessing(points);
2205 let n = codeword_length;
2206
2207 (0..codewords.len() / n)
2208 .into_par_iter()
2209 .flat_map(|i| {
2210 let mut coefficients = codewords[i * n..(i + 1) * n].to_vec();
2211 intt(&mut coefficients);
2212 Polynomial::new(coefficients)
2213 .scale(FF::from(domain_offset.inverse().value()))
2214 .reduce_by_ntt_friendly_modulus(&shift_coefficients, tail_length)
2215 .divide_and_conquer_batch_evaluate(&zerofier_tree)
2216 })
2217 .collect()
2218 }
2219}
2220
2221impl Polynomial<'_, BFieldElement> {
2222 const CLEAN_DIVIDE_CUTOFF_THRESHOLD: isize = {
2227 if cfg!(test) {
2228 0
2229 } else {
2230 1 << 9
2231 }
2232 };
2233
2234 #[must_use]
2247 #[expect(clippy::shadow_unrelated)]
2248 pub fn clean_divide(self, divisor: Self) -> Polynomial<'static, BFieldElement> {
2249 let dividend = self;
2250 if divisor.degree() < Self::CLEAN_DIVIDE_CUTOFF_THRESHOLD {
2251 let (quotient, remainder) = dividend.divide(&divisor);
2252 debug_assert!(remainder.is_zero());
2253 return quotient;
2254 }
2255
2256 let mut dividend_coefficients = dividend.coefficients.into_owned();
2259 let mut divisor_coefficients = divisor.coefficients.into_owned();
2260 if divisor_coefficients.first().is_some_and(Zero::is_zero) {
2261 assert!(dividend_coefficients[0].is_zero());
2263 dividend_coefficients.remove(0);
2264 divisor_coefficients.remove(0);
2265 }
2266 let dividend = Polynomial::new(dividend_coefficients);
2267 let divisor = Polynomial::new(divisor_coefficients);
2268
2269 let offset = XFieldElement::from([0, 1, 0]);
2271 let mut dividend_coefficients = dividend.scale(offset).coefficients.into_owned();
2272 let mut divisor_coefficients = divisor.scale(offset).coefficients.into_owned();
2273
2274 let dividend_deg_plus_1 = usize::try_from(dividend.degree() + 1).unwrap();
2276 let order = dividend_deg_plus_1.next_power_of_two();
2277
2278 dividend_coefficients.resize(order, XFieldElement::ZERO);
2279 divisor_coefficients.resize(order, XFieldElement::ZERO);
2280
2281 ntt(&mut dividend_coefficients);
2282 ntt(&mut divisor_coefficients);
2283
2284 let divisor_inverses = XFieldElement::batch_inversion(divisor_coefficients);
2285 let mut quotient_codeword = dividend_coefficients
2286 .into_iter()
2287 .zip(divisor_inverses)
2288 .map(|(l, r)| l * r)
2289 .collect_vec();
2290
2291 intt(&mut quotient_codeword);
2292 let quotient = Polynomial::new(quotient_codeword);
2293
2294 let Cow::Owned(coeffs) = quotient.scale(offset.inverse()).coefficients else {
2296 unreachable!();
2297 };
2298
2299 Polynomial::new(coeffs.into_iter().map(|c| c.unlift().unwrap()).collect())
2300 }
2301}
2302
2303impl<const N: usize, FF, E> From<[E; N]> for Polynomial<'static, FF>
2304where
2305 FF: FiniteField,
2306 E: Into<FF>,
2307{
2308 fn from(coefficients: [E; N]) -> Self {
2309 Self::new(coefficients.into_iter().map(|x| x.into()).collect())
2310 }
2311}
2312
2313impl<'c, FF> From<&'c [FF]> for Polynomial<'c, FF>
2314where
2315 FF: FiniteField,
2316{
2317 fn from(coefficients: &'c [FF]) -> Self {
2318 Self::new_borrowed(coefficients)
2319 }
2320}
2321
2322impl<FF, E> From<Vec<E>> for Polynomial<'static, FF>
2323where
2324 FF: FiniteField,
2325 E: Into<FF>,
2326{
2327 fn from(coefficients: Vec<E>) -> Self {
2328 Self::new(coefficients.into_iter().map(|c| c.into()).collect())
2329 }
2330}
2331
2332impl From<XFieldElement> for Polynomial<'static, BFieldElement> {
2333 fn from(xfe: XFieldElement) -> Self {
2334 Self::new(xfe.coefficients.to_vec())
2335 }
2336}
2337
2338impl<FF> Polynomial<'static, FF>
2339where
2340 FF: FiniteField,
2341{
2342 pub fn new(coefficients: Vec<FF>) -> Self {
2347 let coefficients = Cow::Owned(coefficients);
2348 Self { coefficients }
2349 }
2350
2351 pub fn x_to_the(n: usize) -> Self {
2353 let mut coefficients = vec![FF::ZERO; n + 1];
2354 coefficients[n] = FF::ONE;
2355 Self::new(coefficients)
2356 }
2357
2358 pub fn from_constant(constant: FF) -> Self {
2359 Self::new(vec![constant])
2360 }
2361
2362 #[doc(hidden)]
2364 pub fn naive_zerofier(domain: &[FF]) -> Self {
2365 domain
2366 .iter()
2367 .map(|&r| Self::new(vec![-r, FF::ONE]))
2368 .reduce(|accumulator, linear_poly| accumulator * linear_poly)
2369 .unwrap_or_else(Self::one)
2370 }
2371}
2372
2373impl<'coeffs, FF> Polynomial<'coeffs, FF>
2374where
2375 FF: FiniteField,
2376{
2377 pub fn new_borrowed(coefficients: &'coeffs [FF]) -> Self {
2379 let coefficients = Cow::Borrowed(coefficients);
2380 Self { coefficients }
2381 }
2382}
2383
2384impl<FF> Div<Polynomial<'_, FF>> for Polynomial<'_, FF>
2385where
2386 FF: FiniteField + 'static,
2387{
2388 type Output = Polynomial<'static, FF>;
2389
2390 fn div(self, other: Polynomial<'_, FF>) -> Self::Output {
2391 let (quotient, _) = self.naive_divide(&other);
2392 quotient
2393 }
2394}
2395
2396impl<FF> Rem<Polynomial<'_, FF>> for Polynomial<'_, FF>
2397where
2398 FF: FiniteField + 'static,
2399{
2400 type Output = Polynomial<'static, FF>;
2401
2402 fn rem(self, other: Polynomial<'_, FF>) -> Self::Output {
2403 let (_, remainder) = self.naive_divide(&other);
2404 remainder
2405 }
2406}
2407
2408impl<FF> Add<Polynomial<'_, FF>> for Polynomial<'_, FF>
2409where
2410 FF: FiniteField + 'static,
2411{
2412 type Output = Polynomial<'static, FF>;
2413
2414 fn add(self, other: Polynomial<'_, FF>) -> Self::Output {
2415 let summed = self
2416 .coefficients
2417 .iter()
2418 .zip_longest(other.coefficients.iter())
2419 .map(|a| match a {
2420 EitherOrBoth::Both(&l, &r) => l + r,
2421 EitherOrBoth::Left(&c) | EitherOrBoth::Right(&c) => c,
2422 })
2423 .collect();
2424
2425 Polynomial::new(summed)
2426 }
2427}
2428
2429impl<FF: FiniteField> AddAssign<Polynomial<'_, FF>> for Polynomial<'_, FF> {
2430 fn add_assign(&mut self, rhs: Polynomial<'_, FF>) {
2431 let rhs_len = rhs.coefficients.len();
2432 let self_len = self.coefficients.len();
2433 let mut self_coefficients = std::mem::take(&mut self.coefficients).into_owned();
2434
2435 for (l, &r) in self_coefficients.iter_mut().zip(rhs.coefficients.iter()) {
2436 *l += r;
2437 }
2438
2439 if rhs_len > self_len {
2440 self_coefficients.extend(&rhs.coefficients[self_len..]);
2441 }
2442
2443 self.coefficients = Cow::Owned(self_coefficients);
2444 }
2445}
2446
2447impl<FF> Sub<Polynomial<'_, FF>> for Polynomial<'_, FF>
2448where
2449 FF: FiniteField + 'static,
2450{
2451 type Output = Polynomial<'static, FF>;
2452
2453 fn sub(self, other: Polynomial<'_, FF>) -> Self::Output {
2454 let coefficients = self
2455 .coefficients
2456 .iter()
2457 .zip_longest(other.coefficients.iter())
2458 .map(|a| match a {
2459 EitherOrBoth::Both(&l, &r) => l - r,
2460 EitherOrBoth::Left(&l) => l,
2461 EitherOrBoth::Right(&r) => FF::ZERO - r,
2462 })
2463 .collect();
2464
2465 Polynomial::new(coefficients)
2466 }
2467}
2468
2469pub fn barycentric_evaluate<Ind, Coeff, Eval>(
2492 codeword: &[Coeff],
2493 indeterminate: Ind,
2494) -> <Eval as Mul<Ind>>::Output
2495where
2496 Ind: FiniteField + Mul<BFieldElement, Output = Ind> + Sub<BFieldElement, Output = Ind>,
2497 Coeff: FiniteField + Mul<Ind, Output = Eval>,
2498 Eval: FiniteField + Mul<Ind>,
2499{
2500 let root_order = codeword.len().try_into().unwrap();
2501 let generator = BFieldElement::primitive_root_of_unity(root_order).unwrap();
2502 let domain_iter = (0..root_order).scan(BFieldElement::ONE, |acc, _| {
2503 let to_yield = Some(*acc);
2504 *acc *= generator;
2505 to_yield
2506 });
2507
2508 let domain_shift = domain_iter.clone().map(|d| indeterminate - d).collect();
2509 let domain_shift_inverses = Ind::batch_inversion(domain_shift);
2510 let domain_over_domain_shift = domain_iter
2511 .zip(domain_shift_inverses)
2512 .map(|(d, inv)| inv * d);
2513 let denominator = domain_over_domain_shift.clone().fold(Ind::ZERO, Ind::add);
2514 let numerator = domain_over_domain_shift
2515 .zip(codeword)
2516 .map(|(dsi, &abscis)| abscis * dsi)
2517 .fold(Eval::ZERO, Eval::add);
2518
2519 numerator * denominator.inverse()
2520}
2521
2522impl<FF, FF2> Mul<Polynomial<'_, FF>> for BFieldElement
2533where
2534 FF: FiniteField + Mul<BFieldElement, Output = FF2>,
2535 FF2: 'static + FiniteField,
2536{
2537 type Output = Polynomial<'static, FF2>;
2538
2539 fn mul(self, other: Polynomial<FF>) -> Self::Output {
2540 other.scalar_mul(self)
2541 }
2542}
2543
2544impl<FF, FF2> Mul<Polynomial<'_, FF>> for XFieldElement
2545where
2546 FF: FiniteField + Mul<XFieldElement, Output = FF2>,
2547 FF2: 'static + FiniteField,
2548{
2549 type Output = Polynomial<'static, FF2>;
2550
2551 fn mul(self, other: Polynomial<FF>) -> Self::Output {
2552 other.scalar_mul(self)
2553 }
2554}
2555
2556impl<S, FF, FF2> Mul<S> for Polynomial<'_, FF>
2557where
2558 S: FiniteField,
2559 FF: FiniteField + Mul<S, Output = FF2>,
2560 FF2: 'static + FiniteField,
2561{
2562 type Output = Polynomial<'static, FF2>;
2563
2564 fn mul(self, other: S) -> Self::Output {
2565 self.scalar_mul(other)
2566 }
2567}
2568
2569impl<FF, FF2> Mul<Polynomial<'_, FF2>> for Polynomial<'_, FF>
2570where
2571 FF: FiniteField + Mul<FF2>,
2572 FF2: FiniteField,
2573 <FF as Mul<FF2>>::Output: 'static + FiniteField,
2574{
2575 type Output = Polynomial<'static, <FF as Mul<FF2>>::Output>;
2576
2577 fn mul(self, other: Polynomial<'_, FF2>) -> Polynomial<'static, <FF as Mul<FF2>>::Output> {
2578 self.naive_multiply(&other)
2579 }
2580}
2581
2582impl<FF> Neg for Polynomial<'_, FF>
2583where
2584 FF: FiniteField + 'static,
2585{
2586 type Output = Polynomial<'static, FF>;
2587
2588 fn neg(mut self) -> Self::Output {
2589 self.scalar_mul_mut(-FF::ONE);
2590
2591 self.into_owned()
2593 }
2594}
2595
2596#[cfg(test)]
2597mod test_polynomials {
2598 use num_traits::ConstZero;
2599 use proptest::collection::size_range;
2600 use proptest::collection::vec;
2601 use proptest::prelude::*;
2602 use proptest_arbitrary_interop::arb;
2603 use test_strategy::proptest;
2604
2605 use super::*;
2606 use crate::prelude::*;
2607
2608 type BfePoly = Polynomial<'static, BFieldElement>;
2610
2611 type XfePoly = Polynomial<'static, XFieldElement>;
2613
2614 impl proptest::arbitrary::Arbitrary for BfePoly {
2615 type Parameters = ();
2616
2617 fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {
2618 arb().boxed()
2619 }
2620
2621 type Strategy = BoxedStrategy<Self>;
2622 }
2623
2624 impl proptest::arbitrary::Arbitrary for XfePoly {
2625 type Parameters = ();
2626
2627 fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {
2628 arb().boxed()
2629 }
2630
2631 type Strategy = BoxedStrategy<Self>;
2632 }
2633
2634 #[test]
2635 fn polynomial_can_be_debug_printed() {
2636 let polynomial = Polynomial::new(bfe_vec![1, 2, 3]);
2637 println!("{polynomial:?}");
2638 }
2639
2640 #[proptest]
2641 fn unequal_hash_implies_unequal_polynomials(poly_0: BfePoly, poly_1: BfePoly) {
2642 let hash = |poly: &Polynomial<_>| {
2643 let mut hasher = std::hash::DefaultHasher::new();
2644 poly.hash(&mut hasher);
2645 std::hash::Hasher::finish(&hasher)
2646 };
2647
2648 if hash(&poly_0) != hash(&poly_1) {
2654 prop_assert_ne!(poly_0, poly_1);
2655 }
2656 }
2657
2658 #[test]
2659 fn polynomial_display_test() {
2660 fn polynomial<const N: usize>(coeffs: [u64; N]) -> BfePoly {
2661 Polynomial::new(coeffs.map(BFieldElement::new).to_vec())
2662 }
2663
2664 assert_eq!("0", polynomial([]).to_string());
2665 assert_eq!("0", polynomial([0]).to_string());
2666 assert_eq!("0", polynomial([0, 0]).to_string());
2667
2668 assert_eq!("1", polynomial([1]).to_string());
2669 assert_eq!("2", polynomial([2, 0]).to_string());
2670 assert_eq!("3", polynomial([3, 0, 0]).to_string());
2671
2672 assert_eq!("x", polynomial([0, 1]).to_string());
2673 assert_eq!("2x", polynomial([0, 2]).to_string());
2674 assert_eq!("3x", polynomial([0, 3]).to_string());
2675
2676 assert_eq!("5x + 2", polynomial([2, 5]).to_string());
2677 assert_eq!("9x + 7", polynomial([7, 9, 0, 0, 0]).to_string());
2678
2679 assert_eq!("4x^4 + 3x^3", polynomial([0, 0, 0, 3, 4]).to_string());
2680 assert_eq!("2x^4 + 1", polynomial([1, 0, 0, 0, 2]).to_string());
2681 }
2682
2683 #[proptest]
2684 fn leading_coefficient_of_zero_polynomial_is_none(#[strategy(0usize..30)] num_zeros: usize) {
2685 let coefficients = vec![BFieldElement::ZERO; num_zeros];
2686 let polynomial = Polynomial::new(coefficients);
2687 prop_assert!(polynomial.leading_coefficient().is_none());
2688 }
2689
2690 #[proptest]
2691 fn leading_coefficient_of_non_zero_polynomial_is_some(
2692 polynomial: BfePoly,
2693 leading_coefficient: BFieldElement,
2694 #[strategy(0usize..30)] num_leading_zeros: usize,
2695 ) {
2696 let mut coefficients = polynomial.coefficients.into_owned();
2697 coefficients.push(leading_coefficient);
2698 coefficients.extend(vec![BFieldElement::ZERO; num_leading_zeros]);
2699 let polynomial_with_leading_zeros = Polynomial::new(coefficients);
2700 prop_assert_eq!(
2701 leading_coefficient,
2702 polynomial_with_leading_zeros.leading_coefficient().unwrap()
2703 );
2704 }
2705
2706 #[test]
2707 fn normalizing_canonical_zero_polynomial_has_no_effect() {
2708 let mut zero_polynomial = Polynomial::<BFieldElement>::zero();
2709 zero_polynomial.normalize();
2710 assert_eq!(Polynomial::zero(), zero_polynomial);
2711 }
2712
2713 #[proptest]
2714 fn spurious_leading_zeros_dont_affect_equality(
2715 polynomial: BfePoly,
2716 #[strategy(0usize..30)] num_leading_zeros: usize,
2717 ) {
2718 let mut coefficients = polynomial.clone().coefficients.into_owned();
2719 coefficients.extend(vec![BFieldElement::ZERO; num_leading_zeros]);
2720 let polynomial_with_leading_zeros = Polynomial::new(coefficients);
2721
2722 prop_assert_eq!(polynomial, polynomial_with_leading_zeros);
2723 }
2724
2725 #[proptest]
2726 fn normalizing_removes_spurious_leading_zeros(
2727 polynomial: BfePoly,
2728 #[filter(!#leading_coefficient.is_zero())] leading_coefficient: BFieldElement,
2729 #[strategy(0usize..30)] num_leading_zeros: usize,
2730 ) {
2731 let mut coefficients = polynomial.clone().coefficients.into_owned();
2732 coefficients.push(leading_coefficient);
2733 coefficients.extend(vec![BFieldElement::ZERO; num_leading_zeros]);
2734 let mut polynomial_with_leading_zeros = Polynomial::new(coefficients);
2735 polynomial_with_leading_zeros.normalize();
2736
2737 let num_inserted_coefficients = 1;
2738 let expected_num_coefficients = polynomial.coefficients.len() + num_inserted_coefficients;
2739 let num_coefficients = polynomial_with_leading_zeros.coefficients.len();
2740
2741 prop_assert_eq!(expected_num_coefficients, num_coefficients);
2742 }
2743
2744 #[test]
2745 fn accessing_coefficients_of_empty_polynomial_gives_empty_slice() {
2746 let poly = BfePoly::new(vec![]);
2747 assert!(poly.coefficients().is_empty());
2748 assert!(poly.into_coefficients().is_empty());
2749 }
2750
2751 #[proptest]
2752 fn accessing_coefficients_of_polynomial_with_only_zero_coefficients_gives_empty_slice(
2753 #[strategy(0_usize..30)] num_zeros: usize,
2754 ) {
2755 let poly = Polynomial::new(vec![BFieldElement::ZERO; num_zeros]);
2756 prop_assert!(poly.coefficients().is_empty());
2757 prop_assert!(poly.into_coefficients().is_empty());
2758 }
2759
2760 #[proptest]
2761 fn accessing_the_coefficients_is_equivalent_to_normalizing_then_raw_access(
2762 mut coefficients: Vec<BFieldElement>,
2763 #[strategy(0_usize..30)] num_leading_zeros: usize,
2764 ) {
2765 coefficients.extend(vec![BFieldElement::ZERO; num_leading_zeros]);
2766 let mut polynomial = Polynomial::new(coefficients);
2767
2768 let accessed_coefficients_borrow = polynomial.coefficients().to_vec();
2769 let accessed_coefficients_owned = polynomial.clone().into_coefficients();
2770
2771 polynomial.normalize();
2772 let raw_coefficients = polynomial.coefficients.into_owned();
2773
2774 prop_assert_eq!(&raw_coefficients, &accessed_coefficients_borrow);
2775 prop_assert_eq!(&raw_coefficients, &accessed_coefficients_owned);
2776 }
2777
2778 #[test]
2779 fn x_to_the_0_is_constant_1() {
2780 assert!(Polynomial::<BFieldElement>::x_to_the(0).is_one());
2781 assert!(Polynomial::<XFieldElement>::x_to_the(0).is_one());
2782 }
2783
2784 #[test]
2785 fn x_to_the_1_is_x() {
2786 assert!(Polynomial::<BFieldElement>::x_to_the(1).is_x());
2787 assert!(Polynomial::<XFieldElement>::x_to_the(1).is_x());
2788 }
2789
2790 #[proptest]
2791 fn x_to_the_n_to_the_m_is_homomorphic(
2792 #[strategy(0_usize..50)] n: usize,
2793 #[strategy(0_usize..50)] m: usize,
2794 ) {
2795 let to_the_n_times_m = Polynomial::<BFieldElement>::x_to_the(n * m);
2796 let to_the_n_then_to_the_m = Polynomial::x_to_the(n).pow(m as u32);
2797 prop_assert_eq!(to_the_n_times_m, to_the_n_then_to_the_m);
2798 }
2799
2800 #[test]
2801 fn scaling_a_polynomial_works_with_different_fields_as_the_offset() {
2802 let bfe_poly = Polynomial::new(bfe_vec![0, 1, 2]);
2803 let _ = bfe_poly.scale(bfe!(42));
2804 let _ = bfe_poly.scale(xfe!(42));
2805
2806 let xfe_poly = Polynomial::new(xfe_vec![0, 1, 2]);
2807 let _ = xfe_poly.scale(bfe!(42));
2808 let _ = xfe_poly.scale(xfe!(42));
2809 }
2810
2811 #[proptest]
2812 fn polynomial_scaling_is_equivalent_in_extension_field(
2813 bfe_polynomial: BfePoly,
2814 alpha: BFieldElement,
2815 ) {
2816 let bfe_coefficients = bfe_polynomial.coefficients.iter();
2817 let xfe_coefficients = bfe_coefficients.map(|bfe| bfe.lift()).collect();
2818 let xfe_polynomial = Polynomial::<XFieldElement>::new(xfe_coefficients);
2819
2820 let xfe_poly_bfe_scalar = xfe_polynomial.scale(alpha);
2821 let bfe_poly_xfe_scalar = bfe_polynomial.scale(alpha.lift());
2822 prop_assert_eq!(xfe_poly_bfe_scalar, bfe_poly_xfe_scalar);
2823 }
2824
2825 #[proptest]
2826 fn evaluating_scaled_polynomial_is_equivalent_to_evaluating_original_in_offset_point(
2827 polynomial: BfePoly,
2828 alpha: BFieldElement,
2829 x: BFieldElement,
2830 ) {
2831 let scaled_polynomial = polynomial.scale(alpha);
2832 prop_assert_eq!(
2833 polynomial.evaluate_in_same_field(alpha * x),
2834 scaled_polynomial.evaluate_in_same_field(x)
2835 );
2836 }
2837
2838 #[proptest]
2839 fn polynomial_multiplication_with_scalar_is_equivalent_for_the_two_methods(
2840 mut polynomial: BfePoly,
2841 scalar: BFieldElement,
2842 ) {
2843 let new_polynomial = polynomial.scalar_mul(scalar);
2844 polynomial.scalar_mul_mut(scalar);
2845 prop_assert_eq!(polynomial, new_polynomial);
2846 }
2847
2848 #[proptest]
2849 fn polynomial_multiplication_with_scalar_is_equivalent_for_all_mul_traits(
2850 polynomial: BfePoly,
2851 scalar: BFieldElement,
2852 ) {
2853 let bfe_rhs = polynomial.clone() * scalar;
2854 let xfe_rhs = polynomial.clone() * scalar.lift();
2855 let bfe_lhs = scalar * polynomial.clone();
2856 let xfe_lhs = scalar.lift() * polynomial;
2857
2858 prop_assert_eq!(bfe_lhs.clone(), bfe_rhs);
2859 prop_assert_eq!(xfe_lhs.clone(), xfe_rhs);
2860
2861 prop_assert_eq!(bfe_lhs * XFieldElement::ONE, xfe_lhs);
2862 }
2863
2864 #[test]
2865 fn polynomial_multiplication_with_scalar_works_for_various_types() {
2866 let bfe_poly = Polynomial::new(bfe_vec![0, 1, 2]);
2867 let _: Polynomial<BFieldElement> = bfe_poly.scalar_mul(bfe!(42));
2868 let _: Polynomial<XFieldElement> = bfe_poly.scalar_mul(xfe!(42));
2869
2870 let xfe_poly = Polynomial::new(xfe_vec![0, 1, 2]);
2871 let _: Polynomial<XFieldElement> = xfe_poly.scalar_mul(bfe!(42));
2872 let _: Polynomial<XFieldElement> = xfe_poly.scalar_mul(xfe!(42));
2873
2874 let mut bfe_poly = bfe_poly;
2875 bfe_poly.scalar_mul_mut(bfe!(42));
2876
2877 let mut xfe_poly = xfe_poly;
2878 xfe_poly.scalar_mul_mut(bfe!(42));
2879 xfe_poly.scalar_mul_mut(xfe!(42));
2880 }
2881
2882 #[proptest]
2883 fn slow_lagrange_interpolation(
2884 polynomial: BfePoly,
2885 #[strategy(Just(#polynomial.coefficients.len().max(1)))] _min_points: usize,
2886 #[any(size_range(#_min_points..8 * #_min_points).lift())] points: Vec<BFieldElement>,
2887 ) {
2888 let evaluations = points
2889 .into_iter()
2890 .map(|x| (x, polynomial.evaluate(x)))
2891 .collect_vec();
2892 let interpolation_polynomial = Polynomial::lagrange_interpolate_zipped(&evaluations);
2893 prop_assert_eq!(polynomial, interpolation_polynomial);
2894 }
2895
2896 #[proptest]
2897 fn three_colinear_points_are_colinear(
2898 p0: (BFieldElement, BFieldElement),
2899 #[filter(#p0.0 != #p1.0)] p1: (BFieldElement, BFieldElement),
2900 #[filter(#p0.0 != #p2_x && #p1.0 != #p2_x)] p2_x: BFieldElement,
2901 ) {
2902 let line = Polynomial::lagrange_interpolate_zipped(&[p0, p1]);
2903 let p2 = (p2_x, line.evaluate(p2_x));
2904 prop_assert!(Polynomial::are_colinear_3(p0, p1, p2));
2905 }
2906
2907 #[proptest]
2908 fn three_non_colinear_points_are_not_colinear(
2909 p0: (BFieldElement, BFieldElement),
2910 #[filter(#p0.0 != #p1.0)] p1: (BFieldElement, BFieldElement),
2911 #[filter(#p0.0 != #p2_x && #p1.0 != #p2_x)] p2_x: BFieldElement,
2912 #[filter(!#disturbance.is_zero())] disturbance: BFieldElement,
2913 ) {
2914 let line = Polynomial::lagrange_interpolate_zipped(&[p0, p1]);
2915 let p2 = (p2_x, line.evaluate_in_same_field(p2_x) + disturbance);
2916 prop_assert!(!Polynomial::are_colinear_3(p0, p1, p2));
2917 }
2918
2919 #[proptest]
2920 fn colinearity_check_needs_at_least_three_points(
2921 p0: (BFieldElement, BFieldElement),
2922 #[filter(#p0.0 != #p1.0)] p1: (BFieldElement, BFieldElement),
2923 ) {
2924 prop_assert!(!Polynomial::<BFieldElement>::are_colinear(&[]));
2925 prop_assert!(!Polynomial::are_colinear(&[p0]));
2926 prop_assert!(!Polynomial::are_colinear(&[p0, p1]));
2927 }
2928
2929 #[proptest]
2930 fn colinearity_check_with_repeated_points_fails(
2931 p0: (BFieldElement, BFieldElement),
2932 #[filter(#p0.0 != #p1.0)] p1: (BFieldElement, BFieldElement),
2933 ) {
2934 prop_assert!(!Polynomial::are_colinear(&[p0, p1, p1]));
2935 }
2936
2937 #[proptest]
2938 fn colinear_points_are_colinear(
2939 p0: (BFieldElement, BFieldElement),
2940 #[filter(#p0.0 != #p1.0)] p1: (BFieldElement, BFieldElement),
2941 #[filter(!#additional_points_xs.contains(&#p0.0))]
2942 #[filter(!#additional_points_xs.contains(&#p1.0))]
2943 #[filter(#additional_points_xs.iter().all_unique())]
2944 #[any(size_range(1..100).lift())]
2945 additional_points_xs: Vec<BFieldElement>,
2946 ) {
2947 let line = Polynomial::lagrange_interpolate_zipped(&[p0, p1]);
2948 let additional_points = additional_points_xs
2949 .into_iter()
2950 .map(|x| (x, line.evaluate(x)))
2951 .collect_vec();
2952 let all_points = [p0, p1].into_iter().chain(additional_points).collect_vec();
2953 prop_assert!(Polynomial::are_colinear(&all_points));
2954 }
2955
2956 #[test]
2957 #[should_panic(expected = "Line must not be parallel to y-axis")]
2958 fn getting_point_on_invalid_line_fails() {
2959 let one = BFieldElement::ONE;
2960 let two = one + one;
2961 let three = two + one;
2962 Polynomial::<BFieldElement>::get_colinear_y((one, one), (one, three), two);
2963 }
2964
2965 #[proptest]
2966 fn point_on_line_and_colinear_point_are_identical(
2967 p0: (BFieldElement, BFieldElement),
2968 #[filter(#p0.0 != #p1.0)] p1: (BFieldElement, BFieldElement),
2969 x: BFieldElement,
2970 ) {
2971 let line = Polynomial::lagrange_interpolate_zipped(&[p0, p1]);
2972 let y = line.evaluate_in_same_field(x);
2973 let y_from_get_point_on_line = Polynomial::get_colinear_y(p0, p1, x);
2974 prop_assert_eq!(y, y_from_get_point_on_line);
2975 }
2976
2977 #[proptest]
2978 fn point_on_line_and_colinear_point_are_identical_in_extension_field(
2979 p0: (XFieldElement, XFieldElement),
2980 #[filter(#p0.0 != #p1.0)] p1: (XFieldElement, XFieldElement),
2981 x: XFieldElement,
2982 ) {
2983 let line = Polynomial::lagrange_interpolate_zipped(&[p0, p1]);
2984 let y = line.evaluate_in_same_field(x);
2985 let y_from_get_point_on_line = Polynomial::get_colinear_y(p0, p1, x);
2986 prop_assert_eq!(y, y_from_get_point_on_line);
2987 }
2988
2989 #[proptest]
2990 fn shifting_polynomial_coefficients_by_zero_is_the_same_as_not_shifting_it(poly: BfePoly) {
2991 prop_assert_eq!(poly.clone(), poly.shift_coefficients(0));
2992 }
2993
2994 #[proptest]
2995 fn shifting_polynomial_one_is_equivalent_to_raising_polynomial_x_to_the_power_of_the_shift(
2996 #[strategy(0usize..30)] shift: usize,
2997 ) {
2998 let shifted_one = Polynomial::one().shift_coefficients(shift);
2999 let x_to_the_shift = Polynomial::<BFieldElement>::from([0, 1]).pow(shift as u32);
3000 prop_assert_eq!(shifted_one, x_to_the_shift);
3001 }
3002
3003 #[test]
3004 fn polynomial_shift_test() {
3005 fn polynomial<const N: usize>(coeffs: [u64; N]) -> BfePoly {
3006 Polynomial::new(coeffs.map(BFieldElement::new).to_vec())
3007 }
3008
3009 assert_eq!(
3010 polynomial([17, 14]),
3011 polynomial([17, 14]).shift_coefficients(0)
3012 );
3013 assert_eq!(
3014 polynomial([0, 17, 14]),
3015 polynomial([17, 14]).shift_coefficients(1)
3016 );
3017 assert_eq!(
3018 polynomial([0, 0, 0, 0, 17, 14]),
3019 polynomial([17, 14]).shift_coefficients(4)
3020 );
3021
3022 let poly = polynomial([17, 14]);
3023 let poly_shift_0 = poly.clone().shift_coefficients(0);
3024 assert_eq!(polynomial([17, 14]), poly_shift_0);
3025
3026 let poly_shift_1 = poly.clone().shift_coefficients(1);
3027 assert_eq!(polynomial([0, 17, 14]), poly_shift_1);
3028
3029 let poly_shift_4 = poly.clone().shift_coefficients(4);
3030 assert_eq!(polynomial([0, 0, 0, 0, 17, 14]), poly_shift_4);
3031 }
3032
3033 #[proptest]
3034 fn shifting_a_polynomial_means_prepending_zeros_to_its_coefficients(
3035 poly: BfePoly,
3036 #[strategy(0usize..30)] shift: usize,
3037 ) {
3038 let shifted_poly = poly.clone().shift_coefficients(shift);
3039 let mut expected_coefficients = vec![BFieldElement::ZERO; shift];
3040 expected_coefficients.extend(poly.coefficients.to_vec());
3041 prop_assert_eq!(expected_coefficients, shifted_poly.coefficients.to_vec());
3042 }
3043
3044 #[proptest]
3045 fn any_polynomial_to_the_power_of_zero_is_one(poly: BfePoly) {
3046 let poly_to_the_zero = poly.pow(0);
3047 prop_assert_eq!(Polynomial::one(), poly_to_the_zero);
3048 }
3049
3050 #[proptest]
3051 fn any_polynomial_to_the_power_one_is_itself(poly: BfePoly) {
3052 let poly_to_the_one = poly.pow(1);
3053 prop_assert_eq!(poly, poly_to_the_one);
3054 }
3055
3056 #[proptest]
3057 fn polynomial_one_to_any_power_is_one(#[strategy(0u32..30)] exponent: u32) {
3058 let one_to_the_exponent = Polynomial::<BFieldElement>::one().pow(exponent);
3059 prop_assert_eq!(Polynomial::one(), one_to_the_exponent);
3060 }
3061
3062 #[test]
3063 fn pow_test() {
3064 fn polynomial<const N: usize>(coeffs: [u64; N]) -> BfePoly {
3065 Polynomial::new(coeffs.map(BFieldElement::new).to_vec())
3066 }
3067
3068 let pol = polynomial([0, 14, 0, 4, 0, 8, 0, 3]);
3069 let pol_squared = polynomial([0, 0, 196, 0, 112, 0, 240, 0, 148, 0, 88, 0, 48, 0, 9]);
3070 let pol_cubed = polynomial([
3071 0, 0, 0, 2744, 0, 2352, 0, 5376, 0, 4516, 0, 4080, 0, 2928, 0, 1466, 0, 684, 0, 216, 0,
3072 27,
3073 ]);
3074
3075 assert_eq!(pol_squared, pol.pow(2));
3076 assert_eq!(pol_cubed, pol.pow(3));
3077
3078 let parabola = polynomial([5, 41, 19]);
3079 let parabola_squared = polynomial([25, 410, 1871, 1558, 361]);
3080 assert_eq!(parabola_squared, parabola.pow(2));
3081 }
3082
3083 #[proptest]
3084 fn pow_arbitrary_test(poly: BfePoly, #[strategy(0u32..15)] exponent: u32) {
3085 let actual = poly.pow(exponent);
3086 let fast_actual = poly.fast_pow(exponent);
3087 let mut expected = Polynomial::one();
3088 for _ in 0..exponent {
3089 expected = expected.clone() * poly.clone();
3090 }
3091
3092 prop_assert_eq!(expected.clone(), actual);
3093 prop_assert_eq!(expected, fast_actual);
3094 }
3095
3096 #[proptest]
3097 fn polynomial_zero_is_neutral_element_for_addition(a: BfePoly) {
3098 prop_assert_eq!(a.clone() + Polynomial::zero(), a.clone());
3099 prop_assert_eq!(Polynomial::zero() + a.clone(), a);
3100 }
3101
3102 #[proptest]
3103 fn polynomial_one_is_neutral_element_for_multiplication(a: BfePoly) {
3104 prop_assert_eq!(a.clone() * Polynomial::<BFieldElement>::one(), a.clone());
3105 prop_assert_eq!(Polynomial::<BFieldElement>::one() * a.clone(), a);
3106 }
3107
3108 #[proptest]
3109 fn multiplication_by_zero_is_zero(a: BfePoly) {
3110 let zero = Polynomial::<BFieldElement>::zero();
3111
3112 prop_assert_eq!(Polynomial::zero(), a.clone() * zero.clone());
3113 prop_assert_eq!(Polynomial::zero(), zero * a);
3114 }
3115
3116 #[proptest]
3117 fn polynomial_addition_is_commutative(a: BfePoly, b: BfePoly) {
3118 prop_assert_eq!(a.clone() + b.clone(), b + a);
3119 }
3120
3121 #[proptest]
3122 fn polynomial_multiplication_is_commutative(a: BfePoly, b: BfePoly) {
3123 prop_assert_eq!(a.clone() * b.clone(), b * a);
3124 }
3125
3126 #[proptest]
3127 fn polynomial_addition_is_associative(a: BfePoly, b: BfePoly, c: BfePoly) {
3128 prop_assert_eq!((a.clone() + b.clone()) + c.clone(), a + (b + c));
3129 }
3130
3131 #[proptest]
3132 fn polynomial_multiplication_is_associative(a: BfePoly, b: BfePoly, c: BfePoly) {
3133 prop_assert_eq!((a.clone() * b.clone()) * c.clone(), a * (b * c));
3134 }
3135
3136 #[proptest]
3137 fn polynomial_multiplication_is_distributive(a: BfePoly, b: BfePoly, c: BfePoly) {
3138 prop_assert_eq!(
3139 (a.clone() + b.clone()) * c.clone(),
3140 (a * c.clone()) + (b * c)
3141 );
3142 }
3143
3144 #[proptest]
3145 fn polynomial_subtraction_of_self_is_zero(a: BfePoly) {
3146 prop_assert_eq!(Polynomial::zero(), a.clone() - a);
3147 }
3148
3149 #[proptest]
3150 fn polynomial_division_by_self_is_one(#[filter(!#a.is_zero())] a: BfePoly) {
3151 prop_assert_eq!(Polynomial::one(), a.clone() / a);
3152 }
3153
3154 #[proptest]
3155 fn polynomial_division_removes_common_factors(a: BfePoly, #[filter(!#b.is_zero())] b: BfePoly) {
3156 prop_assert_eq!(a.clone(), a * b.clone() / b);
3157 }
3158
3159 #[proptest]
3160 fn polynomial_multiplication_raises_degree_at_maximum_to_sum_of_degrees(
3161 a: BfePoly,
3162 b: BfePoly,
3163 ) {
3164 let sum_of_degrees = (a.degree() + b.degree()).max(-1);
3165 prop_assert!((a * b).degree() <= sum_of_degrees);
3166 }
3167
3168 #[test]
3169 fn leading_zeros_dont_affect_polynomial_division() {
3170 fn polynomial<const N: usize>(coeffs: [u64; N]) -> BfePoly {
3174 Polynomial::new(coeffs.map(BFieldElement::new).to_vec())
3175 }
3176
3177 let numerator = polynomial([1, BFieldElement::P - 1, 0, 1]);
3179 let numerator_with_leading_zero = polynomial([1, BFieldElement::P - 1, 0, 1, 0]);
3180
3181 let divisor_normalized = polynomial([0, 1]);
3182 let divisor_not_normalized = polynomial([0, 1, 0]);
3183 let divisor_more_leading_zeros = polynomial([0, 1, 0, 0, 0, 0, 0, 0, 0]);
3184
3185 let expected = polynomial([BFieldElement::P - 1, 0, 1]);
3186
3187 assert_eq!(expected, numerator.clone() / divisor_normalized.clone());
3189 assert_eq!(expected, numerator.clone() / divisor_not_normalized.clone());
3190 assert_eq!(expected, numerator / divisor_more_leading_zeros.clone());
3191
3192 let res_numerator_not_normalized_0 =
3194 numerator_with_leading_zero.clone() / divisor_normalized;
3195 let res_numerator_not_normalized_1 =
3196 numerator_with_leading_zero.clone() / divisor_not_normalized;
3197 let res_numerator_not_normalized_2 =
3198 numerator_with_leading_zero / divisor_more_leading_zeros;
3199 assert_eq!(expected, res_numerator_not_normalized_0);
3200 assert_eq!(expected, res_numerator_not_normalized_1);
3201 assert_eq!(expected, res_numerator_not_normalized_2);
3202 }
3203
3204 #[proptest]
3205 fn leading_coefficient_of_truncated_polynomial_is_same_as_original_leading_coefficient(
3206 poly: BfePoly,
3207 #[strategy(..50_usize)] truncation_point: usize,
3208 ) {
3209 let Some(lc) = poly.leading_coefficient() else {
3210 let reason = "test is only sensible if polynomial has a leading coefficient";
3211 return Err(TestCaseError::Reject(reason.into()));
3212 };
3213 let truncated_poly = poly.truncate(truncation_point);
3214 let Some(trunc_lc) = truncated_poly.leading_coefficient() else {
3215 let reason = "test is only sensible if truncated polynomial has a leading coefficient";
3216 return Err(TestCaseError::Reject(reason.into()));
3217 };
3218 prop_assert_eq!(lc, trunc_lc);
3219 }
3220
3221 #[proptest]
3222 fn truncated_polynomial_is_of_degree_min_of_truncation_point_and_poly_degree(
3223 poly: BfePoly,
3224 #[strategy(..50_usize)] truncation_point: usize,
3225 ) {
3226 let expected_degree = poly.degree().min(truncation_point.try_into().unwrap());
3227 prop_assert_eq!(expected_degree, poly.truncate(truncation_point).degree());
3228 }
3229
3230 #[proptest]
3231 fn truncating_zero_polynomial_gives_zero_polynomial(
3232 #[strategy(..50_usize)] truncation_point: usize,
3233 ) {
3234 let poly = Polynomial::<BFieldElement>::zero().truncate(truncation_point);
3235 prop_assert!(poly.is_zero());
3236 }
3237
3238 #[proptest]
3239 fn truncation_negates_degree_shifting(
3240 #[strategy(0_usize..30)] shift: usize,
3241 #[strategy(..50_usize)] truncation_point: usize,
3242 #[filter(#poly.degree() >= #truncation_point as isize)] poly: BfePoly,
3243 ) {
3244 prop_assert_eq!(
3245 poly.truncate(truncation_point),
3246 poly.shift_coefficients(shift).truncate(truncation_point)
3247 );
3248 }
3249
3250 #[proptest]
3251 fn zero_polynomial_mod_any_power_of_x_is_zero_polynomial(power: usize) {
3252 let must_be_zero = Polynomial::<BFieldElement>::zero().mod_x_to_the_n(power);
3253 prop_assert!(must_be_zero.is_zero());
3254 }
3255
3256 #[proptest]
3257 fn polynomial_mod_some_power_of_x_results_in_polynomial_of_degree_one_less_than_power(
3258 #[filter(!#poly.is_zero())] poly: BfePoly,
3259 #[strategy(..=usize::try_from(#poly.degree()).unwrap())] power: usize,
3260 ) {
3261 let remainder = poly.mod_x_to_the_n(power);
3262 prop_assert_eq!(isize::try_from(power).unwrap() - 1, remainder.degree());
3263 }
3264
3265 #[proptest]
3266 fn polynomial_mod_some_power_of_x_shares_low_degree_terms_coefficients_with_original_polynomial(
3267 #[filter(!#poly.is_zero())] poly: BfePoly,
3268 power: usize,
3269 ) {
3270 let remainder = poly.mod_x_to_the_n(power);
3271 let min_num_coefficients = poly.coefficients.len().min(remainder.coefficients.len());
3272 prop_assert_eq!(
3273 &poly.coefficients[..min_num_coefficients],
3274 &remainder.coefficients[..min_num_coefficients]
3275 );
3276 }
3277
3278 #[proptest]
3279 fn fast_multiplication_by_zero_gives_zero(poly: BfePoly) {
3280 let product = poly.fast_multiply(&Polynomial::<BFieldElement>::zero());
3281 prop_assert_eq!(Polynomial::zero(), product);
3282 }
3283
3284 #[proptest]
3285 fn fast_multiplication_by_one_gives_self(poly: BfePoly) {
3286 let product = poly.fast_multiply(&Polynomial::<BFieldElement>::one());
3287 prop_assert_eq!(poly, product);
3288 }
3289
3290 #[proptest]
3291 fn fast_multiplication_is_commutative(a: BfePoly, b: BfePoly) {
3292 prop_assert_eq!(a.fast_multiply(&b), b.fast_multiply(&a));
3293 }
3294
3295 #[proptest]
3296 fn fast_multiplication_and_normal_multiplication_are_equivalent(a: BfePoly, b: BfePoly) {
3297 let product = a.fast_multiply(&b);
3298 prop_assert_eq!(a * b, product);
3299 }
3300
3301 #[proptest]
3302 fn batch_multiply_agrees_with_iterative_multiply(a: Vec<BfePoly>) {
3303 let mut acc = Polynomial::one();
3304 for factor in &a {
3305 acc = acc.multiply(factor);
3306 }
3307 prop_assert_eq!(acc, Polynomial::batch_multiply(&a));
3308 }
3309
3310 #[proptest]
3311 fn par_batch_multiply_agrees_with_batch_multiply(a: Vec<BfePoly>) {
3312 prop_assert_eq!(
3313 Polynomial::batch_multiply(&a),
3314 Polynomial::par_batch_multiply(&a)
3315 );
3316 }
3317
3318 #[proptest(cases = 50)]
3319 fn naive_zerofier_and_fast_zerofier_are_identical(
3320 #[any(size_range(..Polynomial::<BFieldElement>::FAST_ZEROFIER_CUTOFF_THRESHOLD * 2).lift())]
3321 roots: Vec<BFieldElement>,
3322 ) {
3323 let naive_zerofier = Polynomial::naive_zerofier(&roots);
3324 let fast_zerofier = Polynomial::fast_zerofier(&roots);
3325 prop_assert_eq!(naive_zerofier, fast_zerofier);
3326 }
3327
3328 #[proptest(cases = 50)]
3329 fn smart_zerofier_and_fast_zerofier_are_identical(
3330 #[any(size_range(..Polynomial::<BFieldElement>::FAST_ZEROFIER_CUTOFF_THRESHOLD * 2).lift())]
3331 roots: Vec<BFieldElement>,
3332 ) {
3333 let smart_zerofier = Polynomial::smart_zerofier(&roots);
3334 let fast_zerofier = Polynomial::fast_zerofier(&roots);
3335 prop_assert_eq!(smart_zerofier, fast_zerofier);
3336 }
3337
3338 #[proptest(cases = 50)]
3339 fn zerofier_and_naive_zerofier_are_identical(
3340 #[any(size_range(..Polynomial::<BFieldElement>::FAST_ZEROFIER_CUTOFF_THRESHOLD * 2).lift())]
3341 roots: Vec<BFieldElement>,
3342 ) {
3343 let zerofier = Polynomial::zerofier(&roots);
3344 let naive_zerofier = Polynomial::naive_zerofier(&roots);
3345 prop_assert_eq!(zerofier, naive_zerofier);
3346 }
3347
3348 #[proptest(cases = 50)]
3349 fn zerofier_is_zero_only_on_domain(
3350 #[any(size_range(..1024).lift())] domain: Vec<BFieldElement>,
3351 #[filter(#out_of_domain_points.iter().all(|p| !#domain.contains(p)))]
3352 out_of_domain_points: Vec<BFieldElement>,
3353 ) {
3354 let zerofier = Polynomial::zerofier(&domain);
3355 for point in domain {
3356 prop_assert_eq!(BFieldElement::ZERO, zerofier.evaluate(point));
3357 }
3358 for point in out_of_domain_points {
3359 prop_assert_ne!(BFieldElement::ZERO, zerofier.evaluate(point));
3360 }
3361 }
3362
3363 #[proptest]
3364 fn zerofier_has_leading_coefficient_one(domain: Vec<BFieldElement>) {
3365 let zerofier = Polynomial::zerofier(&domain);
3366 prop_assert_eq!(BFieldElement::ONE, zerofier.leading_coefficient().unwrap());
3367 }
3368 #[proptest]
3369 fn par_zerofier_agrees_with_zerofier(domain: Vec<BFieldElement>) {
3370 prop_assert_eq!(
3371 Polynomial::zerofier(&domain),
3372 Polynomial::par_zerofier(&domain)
3373 );
3374 }
3375
3376 #[test]
3377 fn fast_evaluate_on_hardcoded_domain_and_polynomial() {
3378 let domain = bfe_array![6, 12];
3379 let x_to_the_5_plus_x_to_the_3 = Polynomial::new(bfe_vec![0, 0, 0, 1, 0, 1]);
3380
3381 let manual_evaluations = domain.map(|x| x.mod_pow(5) + x.mod_pow(3)).to_vec();
3382 let fast_evaluations = x_to_the_5_plus_x_to_the_3.batch_evaluate(&domain);
3383 assert_eq!(manual_evaluations, fast_evaluations);
3384 }
3385
3386 #[proptest]
3387 fn slow_and_fast_polynomial_evaluation_are_equivalent(
3388 poly: BfePoly,
3389 #[any(size_range(..1024).lift())] domain: Vec<BFieldElement>,
3390 ) {
3391 let evaluations = domain
3392 .iter()
3393 .map(|&x| poly.evaluate_in_same_field(x))
3394 .collect_vec();
3395 let fast_evaluations = poly.batch_evaluate(&domain);
3396 prop_assert_eq!(evaluations, fast_evaluations);
3397 }
3398
3399 #[test]
3400 #[should_panic(expected = "zero points")]
3401 fn interpolation_through_no_points_is_impossible() {
3402 let _ = Polynomial::<BFieldElement>::interpolate(&[], &[]);
3403 }
3404
3405 #[test]
3406 #[should_panic(expected = "zero points")]
3407 fn lagrange_interpolation_through_no_points_is_impossible() {
3408 let _ = Polynomial::<BFieldElement>::lagrange_interpolate(&[], &[]);
3409 }
3410
3411 #[test]
3412 #[should_panic(expected = "zero points")]
3413 fn zipped_lagrange_interpolation_through_no_points_is_impossible() {
3414 let _ = Polynomial::<BFieldElement>::lagrange_interpolate_zipped(&[]);
3415 }
3416
3417 #[test]
3418 #[should_panic(expected = "zero points")]
3419 fn fast_interpolation_through_no_points_is_impossible() {
3420 let _ = Polynomial::<BFieldElement>::fast_interpolate(&[], &[]);
3421 }
3422
3423 #[test]
3424 #[should_panic(expected = "equal length")]
3425 fn interpolation_with_domain_size_different_from_number_of_points_is_impossible() {
3426 let domain = bfe_array![1, 2, 3];
3427 let points = bfe_array![1, 2];
3428 let _ = Polynomial::interpolate(&domain, &points);
3429 }
3430
3431 #[test]
3432 #[should_panic(expected = "Repeated")]
3433 fn zipped_lagrange_interpolate_using_repeated_domain_points_is_impossible() {
3434 let domain = bfe_array![1, 1, 2];
3435 let points = bfe_array![1, 2, 3];
3436 let zipped = domain.into_iter().zip(points).collect_vec();
3437 let _ = Polynomial::lagrange_interpolate_zipped(&zipped);
3438 }
3439
3440 #[proptest]
3441 fn interpolating_through_one_point_gives_constant_polynomial(
3442 x: BFieldElement,
3443 y: BFieldElement,
3444 ) {
3445 let interpolant = Polynomial::lagrange_interpolate(&[x], &[y]);
3446 let polynomial = Polynomial::from_constant(y);
3447 prop_assert_eq!(polynomial, interpolant);
3448 }
3449
3450 #[proptest(cases = 10)]
3451 fn lagrange_and_fast_interpolation_are_identical(
3452 #[any(size_range(1..2048).lift())]
3453 #[filter(#domain.iter().all_unique())]
3454 domain: Vec<BFieldElement>,
3455 #[strategy(vec(arb(), #domain.len()))] values: Vec<BFieldElement>,
3456 ) {
3457 let lagrange_interpolant = Polynomial::lagrange_interpolate(&domain, &values);
3458 let fast_interpolant = Polynomial::fast_interpolate(&domain, &values);
3459 prop_assert_eq!(lagrange_interpolant, fast_interpolant);
3460 }
3461
3462 #[proptest(cases = 10)]
3463 fn par_fast_interpolate_and_fast_interpolation_are_identical(
3464 #[any(size_range(1..2048).lift())]
3465 #[filter(#domain.iter().all_unique())]
3466 domain: Vec<BFieldElement>,
3467 #[strategy(vec(arb(), #domain.len()))] values: Vec<BFieldElement>,
3468 ) {
3469 let par_fast_interpolant = Polynomial::par_fast_interpolate(&domain, &values);
3470 let fast_interpolant = Polynomial::fast_interpolate(&domain, &values);
3471 prop_assert_eq!(par_fast_interpolant, fast_interpolant);
3472 }
3473
3474 #[test]
3475 fn fast_interpolation_through_a_single_point_succeeds() {
3476 let zero_arr = bfe_array![0];
3477 let _ = Polynomial::fast_interpolate(&zero_arr, &zero_arr);
3478 }
3479
3480 #[proptest(cases = 20)]
3481 fn interpolation_then_evaluation_is_identity(
3482 #[any(size_range(1..2048).lift())]
3483 #[filter(#domain.iter().all_unique())]
3484 domain: Vec<BFieldElement>,
3485 #[strategy(vec(arb(), #domain.len()))] values: Vec<BFieldElement>,
3486 ) {
3487 let interpolant = Polynomial::fast_interpolate(&domain, &values);
3488 let evaluations = interpolant.batch_evaluate(&domain);
3489 prop_assert_eq!(values, evaluations);
3490 }
3491
3492 #[proptest(cases = 1)]
3493 fn fast_batch_interpolation_is_equivalent_to_fast_interpolation(
3494 #[any(size_range(1..2048).lift())]
3495 #[filter(#domain.iter().all_unique())]
3496 domain: Vec<BFieldElement>,
3497 #[strategy(vec(vec(arb(), #domain.len()), 0..10))] value_vecs: Vec<Vec<BFieldElement>>,
3498 ) {
3499 let root_order = domain.len().next_power_of_two();
3500 let root_of_unity = BFieldElement::primitive_root_of_unity(root_order as u64).unwrap();
3501
3502 let interpolants = value_vecs
3503 .iter()
3504 .map(|values| Polynomial::fast_interpolate(&domain, values))
3505 .collect_vec();
3506
3507 let batched_interpolants =
3508 Polynomial::batch_fast_interpolate(&domain, &value_vecs, root_of_unity, root_order);
3509 prop_assert_eq!(interpolants, batched_interpolants);
3510 }
3511
3512 fn coset_domain_of_size_from_generator_with_offset(
3513 size: usize,
3514 generator: BFieldElement,
3515 offset: BFieldElement,
3516 ) -> Vec<BFieldElement> {
3517 let mut domain = vec![offset];
3518 for _ in 1..size {
3519 domain.push(domain.last().copied().unwrap() * generator);
3520 }
3521 domain
3522 }
3523
3524 #[proptest]
3525 fn fast_coset_evaluation_and_fast_evaluation_on_coset_are_identical(
3526 polynomial: BfePoly,
3527 offset: BFieldElement,
3528 #[strategy(0..8usize)]
3529 #[map(|x: usize| 1 << x)]
3530 #[filter((#root_order as isize) > #polynomial.degree())]
3532 root_order: usize,
3533 ) {
3534 let root_of_unity = BFieldElement::primitive_root_of_unity(root_order as u64).unwrap();
3535 let domain =
3536 coset_domain_of_size_from_generator_with_offset(root_order, root_of_unity, offset);
3537
3538 let fast_values = polynomial.batch_evaluate(&domain);
3539 let fast_coset_values = polynomial.fast_coset_evaluate(offset, root_order);
3540 prop_assert_eq!(fast_values, fast_coset_values);
3541 }
3542
3543 #[proptest]
3544 fn fast_coset_interpolation_and_and_fast_interpolation_on_coset_are_identical(
3545 #[filter(!#offset.is_zero())] offset: BFieldElement,
3546 #[strategy(1..8usize)]
3547 #[map(|x: usize| 1 << x)]
3548 root_order: usize,
3549 #[strategy(vec(arb(), #root_order))] values: Vec<BFieldElement>,
3550 ) {
3551 let root_of_unity = BFieldElement::primitive_root_of_unity(root_order as u64).unwrap();
3552 let domain =
3553 coset_domain_of_size_from_generator_with_offset(root_order, root_of_unity, offset);
3554
3555 let fast_interpolant = Polynomial::fast_interpolate(&domain, &values);
3556 let fast_coset_interpolant = Polynomial::fast_coset_interpolate(offset, &values);
3557 prop_assert_eq!(fast_interpolant, fast_coset_interpolant);
3558 }
3559
3560 #[proptest]
3561 fn naive_division_gives_quotient_and_remainder_with_expected_properties(
3562 a: BfePoly,
3563 #[filter(!#b.is_zero())] b: BfePoly,
3564 ) {
3565 let (quot, rem) = a.naive_divide(&b);
3566 prop_assert!(rem.degree() < b.degree());
3567 prop_assert_eq!(a, quot * b + rem);
3568 }
3569
3570 #[proptest]
3571 fn clean_naive_division_gives_quotient_and_remainder_with_expected_properties(
3572 #[filter(!#a_roots.is_empty())] a_roots: Vec<BFieldElement>,
3573 #[strategy(vec(0..#a_roots.len(), 1..=#a_roots.len()))]
3574 #[filter(#b_root_indices.iter().all_unique())]
3575 b_root_indices: Vec<usize>,
3576 ) {
3577 let b_roots = b_root_indices.into_iter().map(|i| a_roots[i]).collect_vec();
3578 let a = Polynomial::zerofier(&a_roots);
3579 let b = Polynomial::zerofier(&b_roots);
3580 let (quot, rem) = a.naive_divide(&b);
3581 prop_assert!(rem.is_zero());
3582 prop_assert_eq!(a, quot * b);
3583 }
3584
3585 #[proptest]
3586 fn clean_division_agrees_with_divide_on_clean_division(
3587 #[strategy(arb())] a: BfePoly,
3588 #[strategy(arb())]
3589 #[filter(!#b.is_zero())]
3590 b: BfePoly,
3591 ) {
3592 let product = a.clone() * b.clone();
3593 let (naive_quotient, naive_remainder) = product.naive_divide(&b);
3594 let clean_quotient = product.clone().clean_divide(b.clone());
3595 let err = format!("{product} / {b} == {naive_quotient} != {clean_quotient}");
3596 prop_assert_eq!(naive_quotient, clean_quotient, "{}", err);
3597 prop_assert_eq!(Polynomial::<BFieldElement>::zero(), naive_remainder);
3598 }
3599
3600 #[proptest]
3601 fn clean_division_agrees_with_division_if_divisor_has_only_0_as_root(
3602 #[strategy(arb())] mut dividend_roots: Vec<BFieldElement>,
3603 ) {
3604 dividend_roots.push(bfe!(0));
3605 let dividend = Polynomial::zerofier(÷nd_roots);
3606 let divisor = Polynomial::zerofier(&[bfe!(0)]);
3607
3608 let (naive_quotient, remainder) = dividend.naive_divide(&divisor);
3609 let clean_quotient = dividend.clean_divide(divisor);
3610 prop_assert_eq!(naive_quotient, clean_quotient);
3611 prop_assert_eq!(Polynomial::<BFieldElement>::zero(), remainder);
3612 }
3613
3614 #[proptest]
3615 fn clean_division_agrees_with_division_if_divisor_has_only_0_as_multiple_root(
3616 #[strategy(arb())] mut dividend_roots: Vec<BFieldElement>,
3617 #[strategy(0_usize..300)] num_roots: usize,
3618 ) {
3619 let multiple_roots = bfe_vec![0; num_roots];
3620 let divisor = Polynomial::zerofier(&multiple_roots);
3621 dividend_roots.extend(multiple_roots);
3622 let dividend = Polynomial::zerofier(÷nd_roots);
3623
3624 let (naive_quotient, remainder) = dividend.naive_divide(&divisor);
3625 let clean_quotient = dividend.clean_divide(divisor);
3626 prop_assert_eq!(naive_quotient, clean_quotient);
3627 prop_assert_eq!(Polynomial::<BFieldElement>::zero(), remainder);
3628 }
3629
3630 #[proptest]
3631 fn clean_division_agrees_with_division_if_divisor_has_0_as_root(
3632 #[strategy(arb())] mut dividend_roots: Vec<BFieldElement>,
3633 #[strategy(vec(0..#dividend_roots.len(), 0..=#dividend_roots.len()))]
3634 #[filter(#divisor_root_indices.iter().all_unique())]
3635 divisor_root_indices: Vec<usize>,
3636 ) {
3637 let mut divisor_roots = divisor_root_indices
3639 .into_iter()
3640 .map(|i| dividend_roots[i])
3641 .collect_vec();
3642
3643 dividend_roots.push(bfe!(0));
3645 divisor_roots.push(bfe!(0));
3646
3647 let dividend = Polynomial::zerofier(÷nd_roots);
3648 let divisor = Polynomial::zerofier(&divisor_roots);
3649 let quotient = dividend.clone().clean_divide(divisor.clone());
3650 prop_assert_eq!(dividend / divisor, quotient);
3651 }
3652
3653 #[proptest]
3654 fn clean_division_agrees_with_division_if_divisor_has_0_through_9_as_roots(
3655 #[strategy(arb())] additional_dividend_roots: Vec<BFieldElement>,
3656 ) {
3657 let divisor_roots = (0..10).map(BFieldElement::new).collect_vec();
3658 let divisor = Polynomial::zerofier(&divisor_roots);
3659 let dividend_roots = [additional_dividend_roots, divisor_roots].concat();
3660 let dividend = Polynomial::zerofier(÷nd_roots);
3661 dbg!(dividend.to_string());
3662 dbg!(divisor.to_string());
3663 let quotient = dividend.clone().clean_divide(divisor.clone());
3664 prop_assert_eq!(dividend / divisor, quotient);
3665 }
3666
3667 #[proptest]
3668 fn clean_division_gives_quotient_and_remainder_with_expected_properties(
3669 #[filter(!#a_roots.is_empty())] a_roots: Vec<BFieldElement>,
3670 #[strategy(vec(0..#a_roots.len(), 1..=#a_roots.len()))]
3671 #[filter(#b_root_indices.iter().all_unique())]
3672 b_root_indices: Vec<usize>,
3673 ) {
3674 let b_roots = b_root_indices.into_iter().map(|i| a_roots[i]).collect_vec();
3675 let a = Polynomial::zerofier(&a_roots);
3676 let b = Polynomial::zerofier(&b_roots);
3677 let quotient = a.clone().clean_divide(b.clone());
3678 prop_assert_eq!(a, quotient * b);
3679 }
3680
3681 #[proptest]
3682 fn dividing_constant_polynomials_is_equivalent_to_dividing_constants(
3683 a: BFieldElement,
3684 #[filter(!#b.is_zero())] b: BFieldElement,
3685 ) {
3686 let a_poly = Polynomial::from_constant(a);
3687 let b_poly = Polynomial::from_constant(b);
3688 let expected_quotient = Polynomial::from_constant(a / b);
3689 prop_assert_eq!(expected_quotient, a_poly / b_poly);
3690 }
3691
3692 #[proptest]
3693 fn dividing_any_polynomial_by_a_constant_polynomial_results_in_remainder_zero(
3694 a: BfePoly,
3695 #[filter(!#b.is_zero())] b: BFieldElement,
3696 ) {
3697 let b_poly = Polynomial::from_constant(b);
3698 let (_, remainder) = a.naive_divide(&b_poly);
3699 prop_assert_eq!(Polynomial::zero(), remainder);
3700 }
3701
3702 #[test]
3703 fn polynomial_division_by_and_with_shah_polynomial() {
3704 fn polynomial<const N: usize>(coeffs: [u64; N]) -> BfePoly {
3705 Polynomial::new(coeffs.map(BFieldElement::new).to_vec())
3706 }
3707
3708 let shah = XFieldElement::shah_polynomial();
3709 let x_to_the_3 = polynomial([1]).shift_coefficients(3);
3710 let (shah_div_x_to_the_3, shah_mod_x_to_the_3) = shah.naive_divide(&x_to_the_3);
3711 assert_eq!(polynomial([1]), shah_div_x_to_the_3);
3712 assert_eq!(polynomial([1, BFieldElement::P - 1]), shah_mod_x_to_the_3);
3713
3714 let x_to_the_6 = polynomial([1]).shift_coefficients(6);
3715 let (x_to_the_6_div_shah, x_to_the_6_mod_shah) = x_to_the_6.naive_divide(&shah);
3716
3717 let expected_quot = polynomial([BFieldElement::P - 1, 1, 0, 1]);
3719 assert_eq!(expected_quot, x_to_the_6_div_shah);
3720
3721 let expected_rem = polynomial([1, BFieldElement::P - 2, 1]);
3723 assert_eq!(expected_rem, x_to_the_6_mod_shah);
3724 }
3725
3726 #[test]
3727 fn xgcd_does_not_panic_on_input_zero() {
3728 let zero = Polynomial::<BFieldElement>::zero;
3729 let (gcd, a, b) = Polynomial::xgcd(zero(), zero());
3730 assert_eq!(zero(), gcd);
3731 println!("a = {a}");
3732 println!("b = {b}");
3733 }
3734
3735 #[proptest]
3736 fn xgcd_b_field_pol_test(x: BfePoly, y: BfePoly) {
3737 let (gcd, a, b) = Polynomial::xgcd(x.clone(), y.clone());
3738 prop_assert_eq!(gcd, a * x + b * y);
3740 }
3741
3742 #[proptest]
3743 fn xgcd_x_field_pol_test(x: XfePoly, y: XfePoly) {
3744 let (gcd, a, b) = Polynomial::xgcd(x.clone(), y.clone());
3745 prop_assert_eq!(gcd, a * x + b * y);
3747 }
3748
3749 #[proptest]
3750 fn add_assign_is_equivalent_to_adding_and_assigning(a: BfePoly, b: BfePoly) {
3751 let mut c = a.clone();
3752 c += b.clone();
3753 prop_assert_eq!(a + b, c);
3754 }
3755
3756 #[test]
3757 fn only_monic_polynomial_of_degree_1_is_x() {
3758 fn polynomial<const N: usize>(coeffs: [u64; N]) -> BfePoly {
3759 Polynomial::new(coeffs.map(BFieldElement::new).to_vec())
3760 }
3761
3762 assert!(polynomial([0, 1]).is_x());
3763 assert!(polynomial([0, 1, 0]).is_x());
3764 assert!(polynomial([0, 1, 0, 0]).is_x());
3765
3766 assert!(!polynomial([]).is_x());
3767 assert!(!polynomial([0]).is_x());
3768 assert!(!polynomial([1]).is_x());
3769 assert!(!polynomial([1, 0]).is_x());
3770 assert!(!polynomial([0, 2]).is_x());
3771 assert!(!polynomial([0, 0, 1]).is_x());
3772 }
3773
3774 #[test]
3775 fn hardcoded_polynomial_squaring() {
3776 fn polynomial<const N: usize>(coeffs: [u64; N]) -> BfePoly {
3777 Polynomial::new(coeffs.map(BFieldElement::new).to_vec())
3778 }
3779
3780 assert_eq!(Polynomial::zero(), polynomial([]).square());
3781
3782 let x_plus_1 = polynomial([1, 1]);
3783 assert_eq!(polynomial([1, 2, 1]), x_plus_1.square());
3784
3785 let x_to_the_15 = polynomial([1]).shift_coefficients(15);
3786 let x_to_the_30 = polynomial([1]).shift_coefficients(30);
3787 assert_eq!(x_to_the_30, x_to_the_15.square());
3788
3789 let some_poly = polynomial([14, 1, 3, 4]);
3790 assert_eq!(
3791 polynomial([196, 28, 85, 118, 17, 24, 16]),
3792 some_poly.square()
3793 );
3794 }
3795
3796 #[proptest]
3797 fn polynomial_squaring_is_equivalent_to_multiplication_with_self(poly: BfePoly) {
3798 prop_assert_eq!(poly.clone() * poly.clone(), poly.square());
3799 }
3800
3801 #[proptest]
3802 fn slow_and_normal_squaring_are_equivalent(poly: BfePoly) {
3803 prop_assert_eq!(poly.slow_square(), poly.square());
3804 }
3805
3806 #[proptest]
3807 fn normal_and_fast_squaring_are_equivalent(poly: BfePoly) {
3808 prop_assert_eq!(poly.square(), poly.fast_square());
3809 }
3810
3811 #[test]
3812 fn constant_zero_eq_constant_zero() {
3813 let zero_polynomial1 = Polynomial::<BFieldElement>::zero();
3814 let zero_polynomial2 = Polynomial::<BFieldElement>::zero();
3815
3816 assert_eq!(zero_polynomial1, zero_polynomial2)
3817 }
3818
3819 #[test]
3820 fn zero_polynomial_is_zero() {
3821 assert!(Polynomial::<BFieldElement>::zero().is_zero());
3822 assert!(Polynomial::<XFieldElement>::zero().is_zero());
3823 }
3824
3825 #[proptest]
3826 fn zero_polynomial_is_zero_independent_of_spurious_leading_zeros(
3827 #[strategy(..500usize)] num_zeros: usize,
3828 ) {
3829 let coefficients = vec![0; num_zeros];
3830 prop_assert_eq!(
3831 Polynomial::zero(),
3832 Polynomial::<BFieldElement>::from(coefficients)
3833 );
3834 }
3835
3836 #[proptest]
3837 fn no_constant_polynomial_with_non_zero_coefficient_is_zero(
3838 #[filter(!#constant.is_zero())] constant: BFieldElement,
3839 ) {
3840 let constant_polynomial = Polynomial::from_constant(constant);
3841 prop_assert!(!constant_polynomial.is_zero());
3842 }
3843
3844 #[test]
3845 fn constant_one_eq_constant_one() {
3846 let one_polynomial1 = Polynomial::<BFieldElement>::one();
3847 let one_polynomial2 = Polynomial::<BFieldElement>::one();
3848
3849 assert_eq!(one_polynomial1, one_polynomial2)
3850 }
3851
3852 #[test]
3853 fn one_polynomial_is_one() {
3854 assert!(Polynomial::<BFieldElement>::one().is_one());
3855 assert!(Polynomial::<XFieldElement>::one().is_one());
3856 }
3857
3858 #[proptest]
3859 fn one_polynomial_is_one_independent_of_spurious_leading_zeros(
3860 #[strategy(..500usize)] num_leading_zeros: usize,
3861 ) {
3862 let spurious_leading_zeros = vec![0; num_leading_zeros];
3863 let mut coefficients = vec![1];
3864 coefficients.extend(spurious_leading_zeros);
3865 prop_assert_eq!(
3866 Polynomial::one(),
3867 Polynomial::<BFieldElement>::from(coefficients)
3868 );
3869 }
3870
3871 #[proptest]
3872 fn no_constant_polynomial_with_non_one_coefficient_is_one(
3873 #[filter(!#constant.is_one())] constant: BFieldElement,
3874 ) {
3875 let constant_polynomial = Polynomial::from_constant(constant);
3876 prop_assert!(!constant_polynomial.is_one());
3877 }
3878
3879 #[test]
3880 fn formal_derivative_of_zero_is_zero() {
3881 assert!(Polynomial::<BFieldElement>::zero()
3882 .formal_derivative()
3883 .is_zero());
3884 assert!(Polynomial::<XFieldElement>::zero()
3885 .formal_derivative()
3886 .is_zero());
3887 }
3888
3889 #[proptest]
3890 fn formal_derivative_of_constant_polynomial_is_zero(constant: BFieldElement) {
3891 let formal_derivative = Polynomial::from_constant(constant).formal_derivative();
3892 prop_assert!(formal_derivative.is_zero());
3893 }
3894
3895 #[proptest]
3896 fn formal_derivative_of_non_zero_polynomial_is_of_degree_one_less_than_the_polynomial(
3897 #[filter(!#poly.is_zero())] poly: BfePoly,
3898 ) {
3899 prop_assert_eq!(poly.degree() - 1, poly.formal_derivative().degree());
3900 }
3901
3902 #[proptest]
3903 fn formal_derivative_of_product_adheres_to_the_leibniz_product_rule(a: BfePoly, b: BfePoly) {
3904 let product_formal_derivative = (a.clone() * b.clone()).formal_derivative();
3905 let product_rule = a.formal_derivative() * b.clone() + a * b.formal_derivative();
3906 prop_assert_eq!(product_rule, product_formal_derivative);
3907 }
3908
3909 #[test]
3910 fn zero_is_zero() {
3911 let f = Polynomial::new(vec![BFieldElement::new(0)]);
3912 assert!(f.is_zero());
3913 }
3914
3915 #[proptest]
3916 fn formal_power_series_inverse_newton(
3917 #[strategy(2usize..20)] precision: usize,
3918 #[filter(!#f.coefficients.is_empty())]
3919 #[filter(!#f.coefficients[0].is_zero())]
3920 #[filter(#precision > 1 + #f.degree() as usize)]
3921 f: BfePoly,
3922 ) {
3923 let g = f.clone().formal_power_series_inverse_newton(precision);
3924 let mut coefficients = bfe_vec![0; precision + 1];
3925 coefficients[precision] = BFieldElement::ONE;
3926 let xn = Polynomial::new(coefficients);
3927 let (_quotient, remainder) = g.multiply(&f).divide(&xn);
3928 prop_assert!(remainder.is_one());
3929 }
3930
3931 #[test]
3932 fn formal_power_series_inverse_newton_concrete() {
3933 let f = Polynomial::new(vec![
3934 BFieldElement::new(3618372803227210457),
3935 BFieldElement::new(14620511201754172786),
3936 BFieldElement::new(2577803283145951105),
3937 BFieldElement::new(1723541458268087404),
3938 BFieldElement::new(4119508755381840018),
3939 BFieldElement::new(8592072587377832596),
3940 BFieldElement::new(236223201225),
3941 ]);
3942 let precision = 8;
3943
3944 let g = f.clone().formal_power_series_inverse_newton(precision);
3945 let mut coefficients = vec![BFieldElement::ZERO; precision + 1];
3946 coefficients[precision] = BFieldElement::ONE;
3947 let xn = Polynomial::new(coefficients);
3948 let (_quotient, remainder) = g.multiply(&f).divide(&xn);
3949 assert!(remainder.is_one());
3950 }
3951
3952 #[proptest]
3953 fn formal_power_series_inverse_minimal(
3954 #[strategy(2usize..20)] precision: usize,
3955 #[filter(!#f.coefficients.is_empty())]
3956 #[filter(!#f.coefficients[0].is_zero())]
3957 #[filter(#precision > 1 + #f.degree() as usize)]
3958 f: BfePoly,
3959 ) {
3960 let g = f.formal_power_series_inverse_minimal(precision);
3961 let mut coefficients = vec![BFieldElement::ZERO; precision + 1];
3962 coefficients[precision] = BFieldElement::ONE;
3963 let xn = Polynomial::new(coefficients);
3964 let (_quotient, remainder) = g.multiply(&f).divide(&xn);
3965
3966 prop_assert!(remainder.is_one());
3968
3969 prop_assert!(g.degree() <= precision as isize);
3971 }
3972
3973 #[proptest]
3974 fn structured_multiple_is_multiple(
3975 #[filter(#coefficients.iter().any(|c|!c.is_zero()))]
3976 #[strategy(vec(arb(), 1..30))]
3977 coefficients: Vec<BFieldElement>,
3978 ) {
3979 let polynomial = Polynomial::new(coefficients);
3980 let multiple = polynomial.structured_multiple();
3981 let remainder = multiple.reduce_long_division(&polynomial);
3982 prop_assert!(remainder.is_zero());
3983 }
3984
3985 #[proptest]
3986 fn structured_multiple_of_modulus_with_trailing_zeros_is_multiple(
3987 #[filter(!#raw_modulus.is_zero())] raw_modulus: BfePoly,
3988 #[strategy(0usize..100)] num_trailing_zeros: usize,
3989 ) {
3990 let modulus = raw_modulus.shift_coefficients(num_trailing_zeros);
3991 let multiple = modulus.structured_multiple();
3992 prop_assert!(multiple.reduce_long_division(&modulus).is_zero());
3993 }
3994
3995 #[proptest]
3996 fn structured_multiple_generates_structure(
3997 #[filter(#coefficients.iter().filter(|c|!c.is_zero()).count() >= 3)]
3998 #[strategy(vec(arb(), 1..30))]
3999 coefficients: Vec<BFieldElement>,
4000 ) {
4001 let polynomial = Polynomial::new(coefficients);
4002 let n = polynomial.degree();
4003 let structured_multiple = polynomial.structured_multiple();
4004 assert!(structured_multiple.degree() <= 3 * n + 1);
4005
4006 let x3np1 = Polynomial::x_to_the((3 * n + 1) as usize);
4007 let remainder = structured_multiple.reduce_long_division(&x3np1);
4008 assert!(2 * n >= remainder.degree());
4009
4010 let structured_mul_minus_rem = structured_multiple - remainder;
4011 assert_eq!(0, structured_mul_minus_rem.clone().reverse().degree());
4012 assert_eq!(
4013 BFieldElement::ONE,
4014 *structured_mul_minus_rem.coefficients.last().unwrap(),
4015 );
4016 }
4017
4018 #[test]
4019 fn structured_multiple_generates_structure_concrete() {
4020 let polynomial = Polynomial::new(
4021 [884763262770, 0, 51539607540, 14563891882495327437]
4022 .map(BFieldElement::new)
4023 .to_vec(),
4024 );
4025 let n = polynomial.degree();
4026 let structured_multiple = polynomial.structured_multiple();
4027 assert_eq!(3 * n + 1, structured_multiple.degree());
4028
4029 let x3np1 = Polynomial::x_to_the((3 * n + 1) as usize);
4030 let remainder = structured_multiple.reduce_long_division(&x3np1);
4031 assert!(2 * n >= remainder.degree());
4032
4033 let structured_mul_minus_rem = structured_multiple - remainder;
4034 assert_eq!(0, structured_mul_minus_rem.clone().reverse().degree());
4035 assert_eq!(
4036 BFieldElement::ONE,
4037 *structured_mul_minus_rem.coefficients.last().unwrap(),
4038 );
4039 }
4040
4041 #[proptest]
4042 fn structured_multiple_of_degree_is_multiple(
4043 #[strategy(2usize..100)] n: usize,
4044 #[filter(#coefficients.iter().any(|c|!c.is_zero()))]
4045 #[strategy(vec(arb(), 1..usize::min(30, #n)))]
4046 coefficients: Vec<BFieldElement>,
4047 ) {
4048 let polynomial = Polynomial::new(coefficients);
4049 let multiple = polynomial.structured_multiple_of_degree(n);
4050 let remainder = multiple.reduce_long_division(&polynomial);
4051 prop_assert!(remainder.is_zero());
4052 }
4053
4054 #[proptest]
4055 fn structured_multiple_of_degree_generates_structure(
4056 #[strategy(4usize..100)] n: usize,
4057 #[strategy(vec(arb(), 3..usize::min(30, #n)))] mut coefficients: Vec<BFieldElement>,
4058 ) {
4059 *coefficients.last_mut().unwrap() = BFieldElement::ONE;
4060 let polynomial = Polynomial::new(coefficients);
4061 let structured_multiple = polynomial.structured_multiple_of_degree(n);
4062
4063 let xn =
4064 Polynomial::new([vec![BFieldElement::ZERO; n], vec![BFieldElement::ONE; 1]].concat());
4065 let remainder = structured_multiple.reduce_long_division(&xn);
4066 assert_eq!(
4067 (structured_multiple.clone() - remainder.clone())
4068 .reverse()
4069 .degree() as usize,
4070 0
4071 );
4072 assert_eq!(
4073 BFieldElement::ONE,
4074 *(structured_multiple.clone() - remainder)
4075 .coefficients
4076 .last()
4077 .unwrap()
4078 );
4079 }
4080
4081 #[proptest]
4082 fn structured_multiple_of_degree_has_given_degree(
4083 #[strategy(2usize..100)] n: usize,
4084 #[filter(#coefficients.iter().any(|c|!c.is_zero()))]
4085 #[strategy(vec(arb(), 1..usize::min(30, #n)))]
4086 coefficients: Vec<BFieldElement>,
4087 ) {
4088 let polynomial = Polynomial::new(coefficients);
4089 let multiple = polynomial.structured_multiple_of_degree(n);
4090 prop_assert_eq!(
4091 multiple.degree() as usize,
4092 n,
4093 "polynomial: {} whereas multiple {}",
4094 polynomial,
4095 multiple
4096 );
4097 }
4098
4099 #[proptest]
4100 fn reverse_polynomial_with_nonzero_constant_term_twice_gives_original_back(f: BfePoly) {
4101 let fx_plus_1 = f.shift_coefficients(1) + Polynomial::from_constant(bfe!(1));
4102 prop_assert_eq!(fx_plus_1.clone(), fx_plus_1.reverse().reverse());
4103 }
4104
4105 #[proptest]
4106 fn reverse_polynomial_with_zero_constant_term_twice_gives_shift_back(
4107 #[filter(!#f.is_zero())] f: BfePoly,
4108 ) {
4109 let fx_plus_1 = f.shift_coefficients(1);
4110 prop_assert_ne!(fx_plus_1.clone(), fx_plus_1.reverse().reverse());
4111 prop_assert_eq!(
4112 fx_plus_1.clone(),
4113 fx_plus_1.reverse().reverse().shift_coefficients(1)
4114 );
4115 }
4116
4117 #[proptest]
4118 fn reduce_by_structured_modulus_and_reduce_long_division_agree(
4119 #[strategy(1usize..10)] n: usize,
4120 #[strategy(1usize..10)] m: usize,
4121 #[strategy(vec(arb(), #m))] b_coefficients: Vec<BFieldElement>,
4122 #[strategy(1usize..100)] _deg_a: usize,
4123 #[strategy(vec(arb(), #_deg_a + 1))] _a_coefficients: Vec<BFieldElement>,
4124 #[strategy(Just(Polynomial::new(#_a_coefficients)))] a: BfePoly,
4125 ) {
4126 let mut full_modulus_coefficients = b_coefficients.clone();
4127 full_modulus_coefficients.resize(m + n + 1, BFieldElement::from(0));
4128 *full_modulus_coefficients.last_mut().unwrap() = BFieldElement::from(1);
4129 let full_modulus = Polynomial::new(full_modulus_coefficients);
4130
4131 let long_remainder = a.reduce_long_division(&full_modulus);
4132 let structured_remainder = a.reduce_by_structured_modulus(&full_modulus);
4133
4134 prop_assert_eq!(long_remainder, structured_remainder);
4135 }
4136
4137 #[test]
4138 fn reduce_by_structured_modulus_and_reduce_agree_long_division_concrete() {
4139 let a = Polynomial::new(
4140 [1, 0, 0, 3, 4, 3, 1, 5, 1, 0, 1, 2, 9, 2, 0, 3, 1]
4141 .into_iter()
4142 .map(BFieldElement::new)
4143 .collect_vec(),
4144 );
4145 let mut full_modulus_coefficients =
4146 [5, 6, 3].into_iter().map(BFieldElement::new).collect_vec();
4147 let m = full_modulus_coefficients.len();
4148 let n = 2;
4149 full_modulus_coefficients.resize(m + n + 1, BFieldElement::from(0));
4150 *full_modulus_coefficients.last_mut().unwrap() = BFieldElement::from(1);
4151 let full_modulus = Polynomial::new(full_modulus_coefficients);
4152
4153 let long_remainder = a.reduce_long_division(&full_modulus);
4154 let structured_remainder = a.reduce_by_structured_modulus(&full_modulus);
4155
4156 assert_eq!(
4157 long_remainder, structured_remainder,
4158 "naive: {}\nstructured: {}",
4159 long_remainder, structured_remainder
4160 );
4161 }
4162
4163 #[proptest]
4164 fn reduce_by_ntt_friendly_modulus_and_reduce_long_division_agree(
4165 #[strategy(1usize..10)] m: usize,
4166 #[strategy(vec(arb(), #m))] b_coefficients: Vec<BFieldElement>,
4167 #[strategy(1usize..100)] _deg_a: usize,
4168 #[strategy(vec(arb(), #_deg_a + 1))] _a_coefficients: Vec<BFieldElement>,
4169 #[strategy(Just(Polynomial::new(#_a_coefficients)))] a: BfePoly,
4170 ) {
4171 let b = Polynomial::new(b_coefficients.clone());
4172 if b.is_zero() {
4173 return Err(TestCaseError::Reject("some reason".into()));
4174 }
4175 let n = (b_coefficients.len() + 1).next_power_of_two();
4176 let mut full_modulus_coefficients = b_coefficients.clone();
4177 full_modulus_coefficients.resize(n + 1, BFieldElement::from(0));
4178 *full_modulus_coefficients.last_mut().unwrap() = BFieldElement::from(1);
4179 let full_modulus = Polynomial::new(full_modulus_coefficients);
4180
4181 let long_remainder = a.reduce_long_division(&full_modulus);
4182
4183 let mut shift_ntt = b_coefficients.clone();
4184 shift_ntt.resize(n, BFieldElement::from(0));
4185 ntt(&mut shift_ntt);
4186 let structured_remainder = a.reduce_by_ntt_friendly_modulus(&shift_ntt, m);
4187
4188 prop_assert_eq!(long_remainder, structured_remainder);
4189 }
4190
4191 #[test]
4192 fn reduce_by_ntt_friendly_modulus_and_reduce_agree_concrete() {
4193 let m = 1;
4194 let a_coefficients = bfe_vec![0, 0, 75944580];
4195 let a = Polynomial::new(a_coefficients);
4196 let b_coefficients = vec![BFieldElement::new(944892804900)];
4197 let n = (b_coefficients.len() + 1).next_power_of_two();
4198 let mut full_modulus_coefficients = b_coefficients.clone();
4199 full_modulus_coefficients.resize(n + 1, BFieldElement::from(0));
4200 *full_modulus_coefficients.last_mut().unwrap() = BFieldElement::from(1);
4201 let full_modulus = Polynomial::new(full_modulus_coefficients);
4202
4203 let long_remainder = a.reduce_long_division(&full_modulus);
4204
4205 let mut shift_ntt = b_coefficients.clone();
4206 shift_ntt.resize(n, BFieldElement::from(0));
4207 ntt(&mut shift_ntt);
4208 let structured_remainder = a.reduce_by_ntt_friendly_modulus(&shift_ntt, m);
4209
4210 assert_eq!(
4211 long_remainder, structured_remainder,
4212 "full modulus: {}",
4213 full_modulus
4214 );
4215 }
4216
4217 #[proptest]
4218 fn reduce_fast_and_reduce_long_division_agree(
4219 numerator: BfePoly,
4220 #[filter(!#modulus.is_zero())] modulus: BfePoly,
4221 ) {
4222 prop_assert_eq!(
4223 numerator.fast_reduce(&modulus),
4224 numerator.reduce_long_division(&modulus)
4225 );
4226 }
4227
4228 #[test]
4229 fn reduce_and_fast_reduce_long_division_agree_on_fixed_input() {
4230 let mut failures = vec![];
4234 for i in 1..100 {
4235 let roots = (0..i).map(BFieldElement::new).collect_vec();
4239 let dividend = Polynomial::zerofier(&roots).formal_derivative();
4240
4241 let divisor_roots = &roots[..roots.len() / 5];
4245 let divisor = Polynomial::zerofier(divisor_roots);
4246
4247 let long_div_remainder = dividend.reduce_long_division(&divisor);
4248 let preprocessed_remainder = dividend.fast_reduce(&divisor);
4249
4250 if long_div_remainder != preprocessed_remainder {
4251 failures.push(i);
4252 }
4253 }
4254
4255 assert_eq!(0, failures.len(), "failures at indices: {failures:?}");
4256 }
4257
4258 #[test]
4259 fn reduce_long_division_and_fast_reduce_agree_simple_fixed() {
4260 let roots = (0..10).map(BFieldElement::new).collect_vec();
4261 let numerator = Polynomial::zerofier(&roots).formal_derivative();
4262 println!("numerator: {}", numerator);
4263
4264 let divisor_roots = &roots[..roots.len() / 5];
4265 let denominator = Polynomial::zerofier(divisor_roots);
4266 println!("modulus: {}", denominator);
4267
4268 let (quotient, remainder) = numerator.divide(&denominator);
4269 assert_eq!(
4270 numerator,
4271 denominator.clone() * quotient + remainder.clone()
4272 );
4273
4274 let long_div_remainder = numerator.reduce_long_division(&denominator);
4275 println!("long div remainder: {}", long_div_remainder);
4276 assert_eq!(remainder, long_div_remainder);
4277
4278 let preprocessed_remainder = numerator.fast_reduce(&denominator);
4279 println!("fast remainder: {}", preprocessed_remainder);
4280
4281 assert_eq!(long_div_remainder, preprocessed_remainder);
4282 }
4283
4284 #[proptest(cases = 100)]
4285 fn batch_evaluate_methods_are_equivalent(
4286 #[strategy(vec(arb(), (1<<10)..(1<<11)))] coefficients: Vec<BFieldElement>,
4287 #[strategy(vec(arb(), (1<<5)..(1<<7)))] domain: Vec<BFieldElement>,
4288 ) {
4289 let polynomial = Polynomial::new(coefficients);
4290 let evaluations_iterative = polynomial.iterative_batch_evaluate(&domain);
4291 let zerofier_tree = ZerofierTree::new_from_domain(&domain);
4292 let evaluations_fast = polynomial.divide_and_conquer_batch_evaluate(&zerofier_tree);
4293 let evaluations_reduce_then = polynomial.reduce_then_batch_evaluate(&domain);
4294
4295 prop_assert_eq!(evaluations_iterative.clone(), evaluations_fast);
4296 prop_assert_eq!(evaluations_iterative, evaluations_reduce_then);
4297 }
4298
4299 #[proptest]
4300 fn reduce_agrees_with_division(a: BfePoly, #[filter(!#b.is_zero())] b: BfePoly) {
4301 prop_assert_eq!(a.divide(&b).1, a.reduce(&b));
4302 }
4303
4304 #[proptest]
4305 fn structured_multiple_of_monomial_term_is_actually_multiple_and_of_right_degree(
4306 #[strategy(1usize..1000)] degree: usize,
4307 #[filter(!#leading_coefficient.is_zero())] leading_coefficient: BFieldElement,
4308 #[strategy(#degree+1..#degree+200)] target_degree: usize,
4309 ) {
4310 let coefficients = [bfe_vec![0; degree], vec![leading_coefficient]].concat();
4311 let polynomial = Polynomial::new(coefficients);
4312 let multiple = polynomial.structured_multiple_of_degree(target_degree);
4313 prop_assert_eq!(Polynomial::zero(), multiple.reduce(&polynomial));
4314 prop_assert_eq!(multiple.degree() as usize, target_degree);
4315 }
4316
4317 #[proptest]
4318 fn monomial_term_divided_by_smaller_monomial_term_gives_clean_division(
4319 #[strategy(100usize..102)] high_degree: usize,
4320 #[filter(!#high_lc.is_zero())] high_lc: BFieldElement,
4321 #[strategy(83..#high_degree)] low_degree: usize,
4322 #[filter(!#low_lc.is_zero())] low_lc: BFieldElement,
4323 ) {
4324 let numerator = Polynomial::new([bfe_vec![0; high_degree], vec![high_lc]].concat());
4325 let denominator = Polynomial::new([bfe_vec![0; low_degree], vec![low_lc]].concat());
4326 let (quotient, remainder) = numerator.divide(&denominator);
4327 prop_assert_eq!(
4328 quotient
4329 .coefficients
4330 .iter()
4331 .filter(|c| !c.is_zero())
4332 .count(),
4333 1
4334 );
4335 prop_assert_eq!(Polynomial::zero(), remainder);
4336 }
4337
4338 #[proptest]
4339 fn fast_modular_coset_interpolate_agrees_with_interpolate_then_reduce_property(
4340 #[filter(!#modulus.is_zero())] modulus: BfePoly,
4341 #[strategy(0usize..10)] _logn: usize,
4342 #[strategy(Just(1 << #_logn))] n: usize,
4343 #[strategy(vec(arb(), #n))] values: Vec<BFieldElement>,
4344 #[strategy(arb())] offset: BFieldElement,
4345 ) {
4346 let omega = BFieldElement::primitive_root_of_unity(n as u64).unwrap();
4347 let domain = (0..n)
4348 .scan(offset, |acc: &mut BFieldElement, _| {
4349 let yld = *acc;
4350 *acc *= omega;
4351 Some(yld)
4352 })
4353 .collect_vec();
4354 prop_assert_eq!(
4355 Polynomial::fast_modular_coset_interpolate(&values, offset, &modulus),
4356 Polynomial::interpolate(&domain, &values).reduce(&modulus)
4357 )
4358 }
4359
4360 #[test]
4361 fn fast_modular_coset_interpolate_agrees_with_interpolate_then_reduce_concrete() {
4362 let logn = 8;
4363 let n = 1u64 << logn;
4364 let modulus = Polynomial::new(bfe_vec![2, 3, 1]);
4365 let values = (0..n).map(|i| BFieldElement::new(i / 5)).collect_vec();
4366 let offset = BFieldElement::new(7);
4367
4368 let omega = BFieldElement::primitive_root_of_unity(n).unwrap();
4369 let mut domain = bfe_vec![0; n as usize];
4370 domain[0] = offset;
4371 for i in 1..n as usize {
4372 domain[i] = domain[i - 1] * omega;
4373 }
4374 assert_eq!(
4375 Polynomial::interpolate(&domain, &values).reduce(&modulus),
4376 Polynomial::fast_modular_coset_interpolate(&values, offset, &modulus),
4377 )
4378 }
4379
4380 #[proptest(cases = 100)]
4381 fn coset_extrapolation_methods_agree_with_interpolate_then_evaluate(
4382 #[strategy(0usize..10)] _logn: usize,
4383 #[strategy(Just(1 << #_logn))] n: usize,
4384 #[strategy(vec(arb(), #n))] values: Vec<BFieldElement>,
4385 #[strategy(arb())] offset: BFieldElement,
4386 #[strategy(vec(arb(), 1..1000))] points: Vec<BFieldElement>,
4387 ) {
4388 let omega = BFieldElement::primitive_root_of_unity(n as u64).unwrap();
4389 let domain = (0..n)
4390 .scan(offset, |acc: &mut BFieldElement, _| {
4391 let yld = *acc;
4392 *acc *= omega;
4393 Some(yld)
4394 })
4395 .collect_vec();
4396 let fast_coset_extrapolation = Polynomial::fast_coset_extrapolate(offset, &values, &points);
4397 let naive_coset_extrapolation =
4398 Polynomial::naive_coset_extrapolate(offset, &values, &points);
4399 let interpolation_then_evaluation =
4400 Polynomial::interpolate(&domain, &values).batch_evaluate(&points);
4401 prop_assert_eq!(fast_coset_extrapolation.clone(), naive_coset_extrapolation);
4402 prop_assert_eq!(fast_coset_extrapolation, interpolation_then_evaluation);
4403 }
4404
4405 #[proptest]
4406 fn coset_extrapolate_and_batch_coset_extrapolate_agree(
4407 #[strategy(1usize..10)] _logn: usize,
4408 #[strategy(Just(1<<#_logn))] n: usize,
4409 #[strategy(0usize..5)] _m: usize,
4410 #[strategy(vec(arb(), #_m*#n))] codewords: Vec<BFieldElement>,
4411 #[strategy(vec(arb(), 0..20))] points: Vec<BFieldElement>,
4412 ) {
4413 let offset = BFieldElement::new(7);
4414
4415 let one_by_one_dispatch = codewords
4416 .chunks(n)
4417 .flat_map(|chunk| Polynomial::coset_extrapolate(offset, chunk, &points))
4418 .collect_vec();
4419 let batched_dispatch = Polynomial::batch_coset_extrapolate(offset, n, &codewords, &points);
4420 let par_batched_dispatch =
4421 Polynomial::par_batch_coset_extrapolate(offset, n, &codewords, &points);
4422 prop_assert_eq!(one_by_one_dispatch.clone(), batched_dispatch);
4423 prop_assert_eq!(one_by_one_dispatch, par_batched_dispatch);
4424
4425 let one_by_one_fast = codewords
4426 .chunks(n)
4427 .flat_map(|chunk| Polynomial::fast_coset_extrapolate(offset, chunk, &points))
4428 .collect_vec();
4429 let batched_fast = Polynomial::batch_fast_coset_extrapolate(offset, n, &codewords, &points);
4430 let par_batched_fast =
4431 Polynomial::par_batch_fast_coset_extrapolate(offset, n, &codewords, &points);
4432 prop_assert_eq!(one_by_one_fast.clone(), batched_fast);
4433 prop_assert_eq!(one_by_one_fast, par_batched_fast);
4434
4435 let one_by_one_naive = codewords
4436 .chunks(n)
4437 .flat_map(|chunk| Polynomial::naive_coset_extrapolate(offset, chunk, &points))
4438 .collect_vec();
4439 let batched_naive =
4440 Polynomial::batch_naive_coset_extrapolate(offset, n, &codewords, &points);
4441 let par_batched_naive =
4442 Polynomial::par_batch_naive_coset_extrapolate(offset, n, &codewords, &points);
4443 prop_assert_eq!(one_by_one_naive.clone(), batched_naive);
4444 prop_assert_eq!(one_by_one_naive, par_batched_naive);
4445 }
4446
4447 #[test]
4448 fn fast_modular_coset_interpolate_thresholds_relate_properly() {
4449 type BfePoly = Polynomial<'static, BFieldElement>;
4450
4451 let intt = BfePoly::FAST_MODULAR_COSET_INTERPOLATE_CUTOFF_THRESHOLD_PREFER_INTT;
4452 let lagrange = BfePoly::FAST_MODULAR_COSET_INTERPOLATE_CUTOFF_THRESHOLD_PREFER_LAGRANGE;
4453 assert!(intt > lagrange);
4454 }
4455
4456 #[proptest]
4457 fn interpolate_and_par_interpolate_agree(
4458 #[filter(!#points.is_empty())] points: Vec<BFieldElement>,
4459 #[strategy(vec(arb(), #points.len()))] domain: Vec<BFieldElement>,
4460 ) {
4461 let expected_interpolant = Polynomial::interpolate(&domain, &points);
4462 let observed_interpolant = Polynomial::par_interpolate(&domain, &points);
4463 prop_assert_eq!(expected_interpolant, observed_interpolant);
4464 }
4465
4466 #[proptest]
4467 fn batch_evaluate_agrees_with_par_batch_evalaute(
4468 polynomial: BfePoly,
4469 points: Vec<BFieldElement>,
4470 ) {
4471 prop_assert_eq!(
4472 polynomial.batch_evaluate(&points),
4473 polynomial.par_batch_evaluate(&points)
4474 );
4475 }
4476
4477 #[proptest(cases = 20)]
4478 fn polynomial_evaluation_and_barycentric_evaluation_are_equivalent(
4479 #[strategy(1_usize..8)] _log_num_coefficients: usize,
4480 #[strategy(1_usize..6)] log_expansion_factor: usize,
4481 #[strategy(vec(arb(), 1 << #_log_num_coefficients))] coefficients: Vec<XFieldElement>,
4482 #[strategy(arb())] indeterminate: XFieldElement,
4483 ) {
4484 let domain_len = coefficients.len() * (1 << log_expansion_factor);
4485 let domain_gen = BFieldElement::primitive_root_of_unity(domain_len.try_into()?).unwrap();
4486 let domain = (0..domain_len)
4487 .scan(XFieldElement::ONE, |acc, _| {
4488 let current = *acc;
4489 *acc *= domain_gen;
4490 Some(current)
4491 })
4492 .collect_vec();
4493
4494 let polynomial = Polynomial::new(coefficients);
4495 let codeword = polynomial.batch_evaluate(&domain);
4496 prop_assert_eq!(
4497 polynomial.evaluate_in_same_field(indeterminate),
4498 barycentric_evaluate(&codeword, indeterminate)
4499 );
4500 }
4501
4502 #[test]
4503 fn regular_evaluation_works_with_various_types() {
4504 let bfe_poly = Polynomial::new(bfe_vec![1]);
4505 let _: BFieldElement = bfe_poly.evaluate(bfe!(0));
4506 let _: XFieldElement = bfe_poly.evaluate(bfe!(0));
4507 let _: XFieldElement = bfe_poly.evaluate(xfe!(0));
4508
4509 let xfe_poly = Polynomial::new(xfe_vec![1]);
4510 let _: XFieldElement = xfe_poly.evaluate(bfe!(0));
4511 let _: XFieldElement = xfe_poly.evaluate(xfe!(0));
4512 }
4513
4514 #[test]
4515 fn barycentric_evaluation_works_with_many_types() {
4516 let bfe_codeword = bfe_array![1];
4517 let _ = barycentric_evaluate(&bfe_codeword, bfe!(0));
4518 let _ = barycentric_evaluate(&bfe_codeword, xfe!(0));
4519
4520 let xfe_codeword = xfe_array![[1; 3]];
4521 let _ = barycentric_evaluate(&xfe_codeword, bfe!(0));
4522 let _ = barycentric_evaluate(&xfe_codeword, xfe!(0));
4523 }
4524
4525 #[test]
4526 fn various_multiplications_work_with_various_types() {
4527 let b = Polynomial::<BFieldElement>::zero;
4528 let x = Polynomial::<XFieldElement>::zero;
4529
4530 let _ = b() * b();
4531 let _ = b() * x();
4532 let _ = x() * b();
4533 let _ = x() * x();
4534
4535 let _ = b().multiply(&b());
4536 let _ = b().multiply(&x());
4537 let _ = x().multiply(&b());
4538 let _ = x().multiply(&x());
4539
4540 let _ = b().naive_multiply(&b());
4541 let _ = b().naive_multiply(&x());
4542 let _ = x().naive_multiply(&b());
4543 let _ = x().naive_multiply(&x());
4544
4545 let _ = b().fast_multiply(&b());
4546 let _ = b().fast_multiply(&x());
4547 let _ = x().fast_multiply(&b());
4548 let _ = x().fast_multiply(&x());
4549 }
4550
4551 #[test]
4552 fn evaluating_polynomial_with_borrowed_coefficients_leaves_coefficients_borrowed() {
4553 let coefficients = bfe_vec![4, 5, 6];
4554 let poly = Polynomial::new_borrowed(&coefficients);
4555 let _ = poly.evaluate_in_same_field(bfe!(0));
4556 let _ = poly.evaluate::<_, XFieldElement>(bfe!(0));
4557 let _ = poly.fast_coset_evaluate(bfe!(3), 128);
4558
4559 let Cow::Borrowed(_) = poly.coefficients else {
4560 panic!("evaluating must not clone the coefficient vector")
4561 };
4562
4563 drop(coefficients);
4565 }
4566}