1use super::PolyMultiplier;
19use crate::fft::{EvaluationDomain, Evaluations, Polynomial};
20use snarkvm_fields::{Field, PrimeField};
21use snarkvm_utilities::{cfg_iter_mut, serialize::*};
22
23use anyhow::Result;
24use num_traits::CheckedDiv;
25use rand::RngExt;
26use std::{
27 fmt,
28 ops::{Add, AddAssign, Deref, DerefMut, Div, Mul, MulAssign, Neg, Sub, SubAssign},
29};
30
31use itertools::Itertools;
32
33#[cfg(not(feature = "serial"))]
34use rayon::prelude::*;
35
36#[derive(Clone, PartialEq, Eq, Hash, Default, CanonicalSerialize, CanonicalDeserialize)]
38#[must_use]
39pub struct DensePolynomial<F: Field> {
40 pub coeffs: Vec<F>,
42}
43
44impl<F: Field> fmt::Debug for DensePolynomial<F> {
45 fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
46 for (i, coeff) in self.coeffs.iter().enumerate().filter(|(_, c)| !c.is_zero()) {
47 if i == 0 {
48 write!(f, "\n{coeff:?}",)?;
49 } else if i == 1 {
50 write!(f, " + \n{coeff:?} * x")?;
51 } else {
52 write!(f, " + \n{coeff:?} * x^{i}")?;
53 }
54 }
55 Ok(())
56 }
57}
58
59impl<F: Field> DensePolynomial<F> {
60 pub(crate) fn trim_trailing_zeros(&mut self) {
71 while let Some(true) = self.coeffs.last().map(|c| c.is_zero()) {
72 self.coeffs.pop();
73 }
74 }
75
76 pub fn zero() -> Self {
78 Self { coeffs: Vec::new() }
79 }
80
81 pub fn is_zero(&self) -> bool {
83 self.coeffs.is_empty() || self.coeffs.iter().all(|coeff| coeff.is_zero())
84 }
85
86 pub fn from_coefficients_slice(coeffs: &[F]) -> Self {
88 Self::from_coefficients_vec(coeffs.to_vec())
89 }
90
91 pub fn from_coefficients_vec(mut coeffs: Vec<F>) -> Self {
93 while let Some(true) = coeffs.last().map(|c| c.is_zero()) {
95 coeffs.pop();
96 }
97 assert!(coeffs.last().is_none_or(|coeff| !coeff.is_zero()));
100
101 Self { coeffs }
102 }
103
104 pub fn degree(&self) -> usize {
106 if self.is_zero() {
107 0
108 } else {
109 assert!(self.coeffs.last().is_some_and(|coeff| !coeff.is_zero()));
110 self.coeffs.len() - 1
111 }
112 }
113
114 pub fn evaluate(&self, point: F) -> F {
116 if self.is_zero() {
117 return F::zero();
118 } else if point.is_zero() {
119 return self.coeffs[0];
120 }
121
122 let mut acc = F::zero();
127 for coeff in self.coeffs.iter().rev() {
128 acc = acc * point + *coeff;
129 }
130 acc
131 }
132
133 #[cfg(test)]
136 fn evaluate_by_powers(&self, point: F) -> F {
137 if self.is_zero() {
138 return F::zero();
139 } else if point.is_zero() {
140 return self.coeffs[0];
141 }
142 let mut powers_of_point = Vec::with_capacity(1 + self.degree());
143 powers_of_point.push(F::one());
144 let mut cur = point;
145 for _ in 0..self.degree() {
146 powers_of_point.push(cur);
147 cur *= point;
148 }
149 let zero = F::zero();
150 let mapping = crate::cfg_into_iter!(powers_of_point).zip_eq(&self.coeffs).map(|(power, coeff)| power * coeff);
151 crate::cfg_reduce!(mapping, || zero, |a, b| a + b)
152 }
153
154 pub fn rand<R: RngExt>(d: usize, rng: &mut R) -> Self {
159 let mut random_coeffs = (0..(d + 1)).map(|_| F::rand(rng)).collect_vec();
160 while random_coeffs[d].is_zero() {
161 random_coeffs[d] = F::rand(rng);
163 }
164 Self::from_coefficients_vec(random_coeffs)
165 }
166
167 pub fn coeffs(&self) -> &[F] {
169 &self.coeffs
170 }
171
172 #[cfg(test)]
174 fn naive_mul(&self, other: &Self) -> Self {
175 if self.is_zero() || other.is_zero() {
176 DensePolynomial::zero()
177 } else {
178 let mut result = vec![F::zero(); self.degree() + other.degree() + 1];
179 for (i, self_coeff) in self.coeffs.iter().enumerate() {
180 for (j, other_coeff) in other.coeffs.iter().enumerate() {
181 result[i + j] += *self_coeff * other_coeff;
182 }
183 }
184 DensePolynomial::from_coefficients_vec(result)
185 }
186 }
187}
188
189impl<F: PrimeField> DensePolynomial<F> {
190 pub fn mul_by_vanishing_poly(&self, domain: EvaluationDomain<F>) -> DensePolynomial<F> {
192 let mut shifted = vec![F::zero(); domain.size()];
193 shifted.extend_from_slice(&self.coeffs);
194 shifted[..self.coeffs.len()].iter_mut().zip_eq(&self.coeffs).for_each(|(s, c)| *s -= c);
195 DensePolynomial::from_coefficients_vec(shifted)
196 }
197
198 pub fn divide_by_monic_linear(&self, z: F) -> (Self, F) {
201 match self.coeffs.len() {
202 0 => (Self::zero(), F::zero()),
203 1 => (Self::zero(), self.coeffs[0]),
204 n => {
205 let mut quotient = vec![F::zero(); n - 1];
206 quotient[n - 2] = self.coeffs[n - 1];
207 for i in (1..n - 1).rev() {
208 quotient[i - 1] = self.coeffs[i] + z * quotient[i];
209 }
210 let remainder = self.coeffs[0] + z * quotient[0];
211 (Self::from_coefficients_vec(quotient), remainder)
212 }
213 }
214 }
215
216 pub fn divide_by_vanishing_poly(
230 &self,
231 domain: EvaluationDomain<F>,
232 ) -> Result<(DensePolynomial<F>, DensePolynomial<F>)> {
233 let n = domain.size();
234 anyhow::ensure!(n > 0, "Dividing by the vanishing polynomial of an empty domain");
235
236 let p = &self.coeffs;
237 if p.len() <= n {
239 return Ok((DensePolynomial::zero(), Self::from_coefficients_slice(p)));
240 }
241
242 let q_len = p.len() - n;
243 let q = if q_len <= n {
244 p[n..].to_vec()
246 } else {
247 let mut q = vec![F::zero(); q_len];
249 for j in (0..q_len).rev() {
250 let carry = if j + n < q_len { q[j + n] } else { F::zero() };
251 q[j] = p[j + n] + carry;
252 }
253 q
254 };
255
256 let mut r = p[..n].to_vec();
257 let overlap = q_len.min(n);
258 cfg_iter_mut!(r[..overlap]).enumerate().for_each(|(i, r_i)| *r_i += q[i]);
259
260 Ok((DensePolynomial::from_coefficients_vec(q), DensePolynomial::from_coefficients_vec(r)))
261 }
262
263 pub fn evaluate_over_domain_by_ref(&self, domain: EvaluationDomain<F>) -> Evaluations<F> {
265 let poly: Polynomial<'_, F> = self.into();
266 Polynomial::<F>::evaluate_over_domain(poly, domain)
267 }
268
269 pub fn evaluate_over_domain(self, domain: EvaluationDomain<F>) -> Evaluations<F> {
271 let poly: Polynomial<'_, F> = self.into();
272 Polynomial::<F>::evaluate_over_domain(poly, domain)
273 }
274}
275
276impl<F: Field> From<super::SparsePolynomial<F>> for DensePolynomial<F> {
277 fn from(other: super::SparsePolynomial<F>) -> Self {
278 let mut result = vec![F::zero(); other.degree() + 1];
279 for (i, coeff) in other.coeffs() {
280 result[*i] = *coeff;
281 }
282 DensePolynomial::from_coefficients_vec(result)
283 }
284}
285
286impl<'a, F: Field> Add<&'a DensePolynomial<F>> for &'_ DensePolynomial<F> {
287 type Output = DensePolynomial<F>;
288
289 fn add(self, other: &'a DensePolynomial<F>) -> DensePolynomial<F> {
290 let mut result = if self.is_zero() {
291 other.clone()
292 } else if other.is_zero() {
293 self.clone()
294 } else if self.degree() >= other.degree() {
295 let mut result = self.clone();
296 cfg_iter_mut!(result.coeffs).zip(&other.coeffs).for_each(|(a, b)| *a += b);
298 result
299 } else {
300 let mut result = other.clone();
301 cfg_iter_mut!(result.coeffs).zip(&self.coeffs).for_each(|(a, b)| *a += b);
303 result
304 };
305 result.trim_trailing_zeros();
306 result
307 }
308}
309
310impl<'a, F: Field> AddAssign<&'a DensePolynomial<F>> for DensePolynomial<F> {
311 fn add_assign(&mut self, other: &'a DensePolynomial<F>) {
312 if self.is_zero() {
313 self.coeffs.clear();
314 self.coeffs.extend_from_slice(&other.coeffs);
315 } else if other.is_zero() {
316 } else if self.degree() >= other.degree() {
318 cfg_iter_mut!(self.coeffs, 1_000).zip(&other.coeffs).for_each(|(a, b)| *a += b);
320 } else {
321 self.coeffs.resize(other.coeffs.len(), F::zero());
323 cfg_iter_mut!(self.coeffs, 1_000).zip(&other.coeffs).for_each(|(a, b)| *a += b);
325 }
326 self.trim_trailing_zeros();
327 }
328}
329
330impl<'a, F: Field> AddAssign<&'a Polynomial<'a, F>> for DensePolynomial<F> {
331 fn add_assign(&mut self, other: &'a Polynomial<F>) {
332 match other {
333 Polynomial::Sparse(p) => *self += &Self::from(p.clone().into_owned()),
334 Polynomial::Dense(p) => *self += p.as_ref(),
335 }
336 }
337}
338
339impl<'a, F: Field> AddAssign<(F, &'a Polynomial<'a, F>)> for DensePolynomial<F> {
340 fn add_assign(&mut self, (f, other): (F, &'a Polynomial<F>)) {
341 match other {
342 Polynomial::Sparse(p) => *self += (f, &Self::from(p.clone().into_owned())),
343 Polynomial::Dense(p) => *self += (f, p.as_ref()),
344 }
345 }
346}
347
348impl<'a, F: Field> AddAssign<(F, &'a DensePolynomial<F>)> for DensePolynomial<F> {
349 #[allow(clippy::suspicious_op_assign_impl)]
350 fn add_assign(&mut self, (f, other): (F, &'a DensePolynomial<F>)) {
351 if self.is_zero() {
352 self.coeffs.clear();
360 self.coeffs.extend(other.coeffs.iter().map(|c| *c * f));
361 } else if other.is_zero() {
362 } else if self.degree() >= other.degree() {
364 cfg_iter_mut!(self.coeffs, 1_000).zip(&other.coeffs).for_each(|(a, b)| {
366 *a += f * b;
367 });
368 } else {
369 self.coeffs.resize(other.coeffs.len(), F::zero());
371 cfg_iter_mut!(self.coeffs, 1_000).zip(&other.coeffs).for_each(|(a, b)| {
373 *a += f * b;
374 });
375 }
376 self.trim_trailing_zeros();
377 }
378}
379
380impl<F: Field> Neg for DensePolynomial<F> {
381 type Output = DensePolynomial<F>;
382
383 #[inline]
384 fn neg(mut self) -> DensePolynomial<F> {
385 for coeff in &mut self.coeffs {
386 *coeff = -*coeff;
387 }
388 self
389 }
390}
391
392impl<'a, F: Field> Sub<&'a DensePolynomial<F>> for &'_ DensePolynomial<F> {
393 type Output = DensePolynomial<F>;
394
395 #[inline]
396 fn sub(self, other: &'a DensePolynomial<F>) -> DensePolynomial<F> {
397 let mut result = if self.is_zero() {
398 let mut result = other.clone();
399 for coeff in &mut result.coeffs {
400 *coeff = -(*coeff);
401 }
402 result
403 } else if other.is_zero() {
404 self.clone()
405 } else if self.degree() >= other.degree() {
406 let mut result = self.clone();
407 cfg_iter_mut!(result.coeffs, 1_000).zip(&other.coeffs).for_each(|(a, b)| *a -= b);
409 result
410 } else {
411 let mut result = self.clone();
412 result.coeffs.resize(other.coeffs.len(), F::zero());
413 cfg_iter_mut!(result.coeffs, 1_000).zip(&other.coeffs).for_each(|(a, b)| {
415 *a -= b;
416 });
417 result
418 };
419 result.trim_trailing_zeros();
420 result
421 }
422}
423
424impl<'a, F: Field> SubAssign<&'a DensePolynomial<F>> for DensePolynomial<F> {
425 #[inline]
426 fn sub_assign(&mut self, other: &'a DensePolynomial<F>) {
427 if self.is_zero() {
428 self.coeffs.resize(other.coeffs.len(), F::zero());
429 for (i, coeff) in other.coeffs.iter().enumerate() {
430 self.coeffs[i] -= coeff;
431 }
432 } else if other.is_zero() {
433 } else if self.degree() >= other.degree() {
435 cfg_iter_mut!(self.coeffs).zip(&other.coeffs).for_each(|(a, b)| *a -= b);
437 } else {
438 self.coeffs.resize(other.coeffs.len(), F::zero());
440 cfg_iter_mut!(self.coeffs).zip(&other.coeffs).for_each(|(a, b)| *a -= b);
442 }
443 self.trim_trailing_zeros();
444 }
445}
446
447impl<'a, F: Field> AddAssign<&'a super::SparsePolynomial<F>> for DensePolynomial<F> {
448 #[inline]
449 fn add_assign(&mut self, other: &'a super::SparsePolynomial<F>) {
450 if self.degree() < other.degree() {
451 self.coeffs.resize(other.degree() + 1, F::zero());
452 }
453 for (i, b) in other.coeffs() {
454 self.coeffs[*i] += b;
455 }
456 self.trim_trailing_zeros();
457 }
458}
459
460impl<'a, F: Field> Sub<&'a super::SparsePolynomial<F>> for DensePolynomial<F> {
461 type Output = Self;
462
463 #[inline]
464 fn sub(mut self, other: &'a super::SparsePolynomial<F>) -> Self::Output {
465 if self.degree() < other.degree() {
466 self.coeffs.resize(other.degree() + 1, F::zero());
467 }
468 for (i, b) in other.coeffs() {
469 self.coeffs[*i] -= b;
470 }
471 self.trim_trailing_zeros();
472 self
473 }
474}
475
476impl<'a, F: Field> Div<&'a DensePolynomial<F>> for &'_ DensePolynomial<F> {
477 type Output = DensePolynomial<F>;
478
479 #[inline]
481 fn div(self, divisor: &'a DensePolynomial<F>) -> DensePolynomial<F> {
482 let a: Polynomial<_> = self.into();
483 let b: Polynomial<_> = divisor.into();
484 a.divide_with_q_and_r(&b).expect("division failed").0
485 }
486}
487
488impl<F: Field> Div<DensePolynomial<F>> for DensePolynomial<F> {
489 type Output = DensePolynomial<F>;
490
491 #[inline]
493 fn div(self, divisor: DensePolynomial<F>) -> DensePolynomial<F> {
494 let a: Polynomial<_> = self.into();
495 let b: Polynomial<_> = divisor.into();
496 a.divide_with_q_and_r(&b).expect("division failed").0
497 }
498}
499
500impl<F: Field> CheckedDiv for DensePolynomial<F> {
501 #[inline]
502 fn checked_div(&self, divisor: &DensePolynomial<F>) -> Option<DensePolynomial<F>> {
503 let a: Polynomial<_> = self.into();
504 let b: Polynomial<_> = divisor.into();
505 match a.divide_with_q_and_r(&b) {
506 Ok((divisor, remainder)) => {
507 if remainder.is_zero() {
508 Some(divisor)
509 } else {
510 None
511 }
512 }
513 Err(_) => None,
514 }
515 }
516}
517
518impl<'a, F: PrimeField> Mul<&'a DensePolynomial<F>> for &'_ DensePolynomial<F> {
520 type Output = DensePolynomial<F>;
521
522 #[inline]
523 #[allow(clippy::suspicious_arithmetic_impl)]
524 fn mul(self, other: &'a DensePolynomial<F>) -> DensePolynomial<F> {
525 if self.is_zero() || other.is_zero() {
526 DensePolynomial::zero()
527 } else {
528 let mut m = PolyMultiplier::new();
529 m.add_polynomial_ref(self, "");
530 m.add_polynomial_ref(other, "");
531 m.multiply().unwrap()
532 }
533 }
534}
535
536impl<F: Field> Mul<F> for DensePolynomial<F> {
538 type Output = Self;
539
540 #[inline]
541 fn mul(mut self, other: F) -> Self {
542 self.iter_mut().for_each(|c| *c *= other);
543 self
544 }
545}
546
547impl<F: Field> Mul<F> for &'_ DensePolynomial<F> {
549 type Output = DensePolynomial<F>;
550
551 #[inline]
552 fn mul(self, other: F) -> Self::Output {
553 let result = self.clone();
554 result * other
555 }
556}
557
558impl<F: Field> MulAssign<F> for DensePolynomial<F> {
560 #[allow(clippy::suspicious_arithmetic_impl)]
561 fn mul_assign(&mut self, other: F) {
562 self.iter_mut().for_each(|c| *c *= other);
563 }
564}
565
566impl<F: Field> std::iter::Sum for DensePolynomial<F> {
568 fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
569 iter.fold(DensePolynomial::zero(), |a, b| &a + &b)
570 }
571}
572
573impl<F: Field> Deref for DensePolynomial<F> {
574 type Target = [F];
575
576 fn deref(&self) -> &[F] {
577 &self.coeffs
578 }
579}
580
581impl<F: Field> DerefMut for DensePolynomial<F> {
582 fn deref_mut(&mut self) -> &mut [F] {
583 &mut self.coeffs
584 }
585}
586
587#[cfg(test)]
588mod tests {
589 use crate::fft::polynomial::*;
590 use num_traits::CheckedDiv;
591 use snarkvm_curves::bls12_377::Fr;
592 use snarkvm_fields::{Field, One, Zero};
593 use snarkvm_utilities::rand::{TestRng, Uniform};
594
595 use rand::Rng;
596
597 #[test]
598 fn double_polynomials_random() {
599 let rng = &mut TestRng::default();
600 for degree in 0..70 {
601 let p = DensePolynomial::<Fr>::rand(degree, rng);
602 let p_double = &p + &p;
603 let p_quad = &p_double + &p_double;
604 assert_eq!(&(&(&p + &p) + &p) + &p, p_quad);
605 }
606 }
607
608 #[test]
609 fn add_polynomials() {
610 let rng = &mut TestRng::default();
611 for a_degree in 0..70 {
612 for b_degree in 0..70 {
613 let p1 = DensePolynomial::<Fr>::rand(a_degree, rng);
614 let p2 = DensePolynomial::<Fr>::rand(b_degree, rng);
615 let res1 = &p1 + &p2;
616 let res2 = &p2 + &p1;
617 assert_eq!(res1, res2);
618 }
619 }
620 }
621
622 #[test]
623 fn add_polynomials_with_mul() {
624 let rng = &mut TestRng::default();
625 for a_degree in 0..70 {
626 for b_degree in 0..70 {
627 let mut p1 = DensePolynomial::rand(a_degree, rng);
628 let p2 = DensePolynomial::rand(b_degree, rng);
629 let f = Fr::rand(rng);
630 let f_p2 = DensePolynomial::from_coefficients_vec(p2.coeffs.iter().map(|c| f * c).collect());
631 let res2 = &f_p2 + &p1;
632 p1 += (f, &p2);
633 let res1 = p1;
634 assert_eq!(res1, res2);
635 }
636 }
637 }
638
639 #[test]
640 fn add_scaled_polynomial_to_zero() {
641 let rng = &mut TestRng::default();
647 for degree in 0..70 {
648 let q = DensePolynomial::rand(degree, rng);
649 let f = Fr::rand(rng);
650 let expected = DensePolynomial::from_coefficients_vec(q.coeffs.iter().map(|c| f * c).collect());
651
652 let mut empty = DensePolynomial::zero();
653 empty += (f, &q);
654 assert_eq!(empty, expected, "scaled add onto an empty polynomial");
655
656 let mut zeroed = DensePolynomial::zero();
660 zeroed.coeffs = vec![Fr::zero(); degree + 3];
661 assert!(zeroed.is_zero() && !zeroed.coeffs.is_empty());
662 zeroed += (f, &q);
663 assert_eq!(zeroed, expected, "scaled add onto a non-empty all-zero polynomial");
664 }
665
666 let q = DensePolynomial::rand(16, rng);
668 let mut p = DensePolynomial::zero();
669 p += (Fr::zero(), &q);
670 assert!(p.is_zero());
671 assert!(p.coeffs.is_empty());
672 }
673
674 #[test]
675 fn sub_polynomials() {
676 let rng = &mut TestRng::default();
677 let p1 = DensePolynomial::<Fr>::rand(5, rng);
678 let p2 = DensePolynomial::<Fr>::rand(3, rng);
679 let res1 = &p1 - &p2;
680 let res2 = &p2 - &p1;
681 assert_eq!(&res1 + &p2, p1, "Subtraction should be inverse of addition!");
682 assert_eq!(res1, -res2, "p2 - p1 = -(p1 - p2)");
683 }
684
685 #[test]
686 fn divide_polynomials_fixed() {
687 let dividend = DensePolynomial::from_coefficients_slice(&[
688 "4".parse().unwrap(),
689 "8".parse().unwrap(),
690 "5".parse().unwrap(),
691 "1".parse().unwrap(),
692 ]);
693 let divisor = DensePolynomial::from_coefficients_slice(&[Fr::one(), Fr::one()]); let result = dividend.checked_div(&divisor).unwrap();
695 let expected_result = DensePolynomial::from_coefficients_slice(&[
696 "4".parse().unwrap(),
697 "4".parse().unwrap(),
698 "1".parse().unwrap(),
699 ]);
700 assert_eq!(expected_result, result);
701 }
702
703 #[test]
704 #[allow(clippy::needless_borrow)]
705 fn divide_polynomials_random() {
706 let rng = &mut TestRng::default();
707
708 for a_degree in 0..70 {
709 for b_degree in 0..70 {
710 let dividend = DensePolynomial::<Fr>::rand(a_degree, rng);
711 let divisor = DensePolynomial::<Fr>::rand(b_degree, rng);
712 let (quotient, remainder) =
713 Polynomial::divide_with_q_and_r(&(÷nd).into(), &(&divisor).into()).unwrap();
714 assert_eq!(dividend, &(&divisor * "ient) + &remainder)
715 }
716 }
717 }
718
719 #[test]
720 fn evaluate_polynomials() {
721 let rng = &mut TestRng::default();
722 for a_degree in 0..70 {
723 let p = DensePolynomial::rand(a_degree, rng);
724 let point: Fr = Fr::from(10u64);
725 let mut total = Fr::zero();
726 for (i, coeff) in p.coeffs.iter().enumerate() {
727 total += point.pow([i as u64]) * coeff;
728 }
729 assert_eq!(p.evaluate(point), total);
730 }
731 }
732
733 #[test]
734 fn divide_poly_by_zero() {
735 let a = Polynomial::<Fr>::zero();
736 let b = Polynomial::<Fr>::zero();
737 assert!(a.divide_with_q_and_r(&b).is_err());
738 }
739
740 #[test]
741 fn mul_polynomials_random() {
742 let rng = &mut TestRng::default();
743 for a_degree in 0..70 {
744 for b_degree in 0..70 {
745 dbg!(a_degree);
746 dbg!(b_degree);
747 let a = DensePolynomial::<Fr>::rand(a_degree, rng);
748 let b = DensePolynomial::<Fr>::rand(b_degree, rng);
749 assert_eq!(&a * &b, a.naive_mul(&b))
750 }
751 }
752 }
753
754 #[test]
755 fn mul_polynomials_n_random() {
756 let rng = &mut TestRng::default();
757
758 let max_degree = 1 << 8;
759
760 for _ in 0..10 {
761 let mut multiplier = PolyMultiplier::new();
762 let a = DensePolynomial::<Fr>::rand(max_degree / 2, rng);
763 let mut mul_degree = a.degree() + 1;
764 multiplier.add_polynomial(a.clone(), "a");
765 let mut naive = a.clone();
766
767 let num_polys = (rng.next_u32() as usize) % 8;
769 let num_evals = (rng.next_u32() as usize) % 4;
770 println!("\nnum_polys {num_polys}, num_evals {num_evals}");
771
772 for _ in 1..num_polys {
773 let degree = (rng.next_u32() as usize) % max_degree;
774 mul_degree += degree + 1;
775 println!("poly degree {degree}");
776 let a = DensePolynomial::<Fr>::rand(degree, rng);
777 naive = naive.naive_mul(&a);
778 multiplier.add_polynomial(a.clone(), "a");
779 }
780
781 let mut eval_degree = mul_degree;
783 let domain = EvaluationDomain::new(mul_degree).unwrap();
784 println!("mul_degree {}, domain {}", mul_degree, domain.size());
785 for _ in 0..num_evals {
786 let a = DensePolynomial::<Fr>::rand(mul_degree / 8, rng);
787 eval_degree += a.len() + 1;
788 if eval_degree < domain.size() {
789 println!("eval degree {eval_degree}");
790 let mut a_evals = a.clone().coeffs;
791 domain.fft_in_place(&mut a_evals);
792 let a_evals = Evaluations::from_vec_and_domain(a_evals, domain);
793
794 naive = naive.naive_mul(&a);
795 multiplier.add_evaluation(a_evals, "a");
796 }
797 }
798
799 assert_eq!(multiplier.multiply().unwrap(), naive);
800 }
801 }
802
803 #[test]
804 fn mul_polynomials_corner_cases() {
805 let rng = &mut TestRng::default();
806
807 let a_degree = 70;
808
809 println!("Test single polynomial");
811 let a = DensePolynomial::<Fr>::rand(a_degree, rng);
812 let mut multiplier = PolyMultiplier::new();
813 multiplier.add_polynomial(a.clone(), "a");
814 assert_eq!(multiplier.multiply().unwrap(), a);
815
816 }
818
819 #[test]
820 fn mul_by_vanishing_poly() {
821 let rng = &mut TestRng::default();
822 for size in 1..10 {
823 let domain = EvaluationDomain::new(1 << size).unwrap();
824 for degree in 0..70 {
825 let p = DensePolynomial::<Fr>::rand(degree, rng);
826 let ans1 = p.mul_by_vanishing_poly(domain);
827 let ans2 = &p * &domain.vanishing_polynomial().into();
828 assert_eq!(ans1, ans2);
829 }
830 }
831 }
832}
833
834#[cfg(test)]
835mod vanishing_divide_tests {
836 use crate::fft::{DensePolynomial, EvaluationDomain, Polynomial};
837 use snarkvm_curves::bls12_377::Fr;
838 use snarkvm_fields::Zero;
839 use snarkvm_utilities::rand::{TestRng, Uniform};
840
841 pub(super) fn rand_poly(len: usize, rng: &mut TestRng) -> DensePolynomial<Fr> {
842 DensePolynomial::from_coefficients_vec((0..len).map(|_| Fr::rand(rng)).collect())
843 }
844
845 fn check_identity(p: &DensePolynomial<Fr>, domain: EvaluationDomain<Fr>, label: &str) {
851 let n = domain.size();
852 let (q, r) = p.divide_by_vanishing_poly(domain).unwrap();
853 let v = DensePolynomial::from(domain.vanishing_polynomial());
854 let canonical = DensePolynomial::from_coefficients_vec(p.coeffs.clone());
855 assert_eq!(&(&q * &v) + &r, canonical, "q*v + r != p [{label}]");
856 assert!(r.is_zero() || r.degree() < n, "deg(r) >= n [{label}]");
857 }
858
859 #[test]
863 fn agrees_with_generic_long_division() {
864 let rng = &mut TestRng::default();
865 for log_n in [3usize, 5, 8] {
866 let domain = EvaluationDomain::<Fr>::new(1 << log_n).unwrap();
867 let n = domain.size();
868 for len in [0, 1, n / 2, n, n + 1, 2 * n - 1, 2 * n, 2 * n + 3, 4 * n + 5] {
869 let p = rand_poly(len, rng);
870 let (q, r) = p.divide_by_vanishing_poly(domain).unwrap();
871
872 let expect =
873 Polynomial::from(&p).divide_with_q_and_r(&Polynomial::from(domain.vanishing_polynomial())).unwrap();
874 assert_eq!(q, expect.0, "quotient differs at n={n} len={len}");
875 assert_eq!(r, expect.1, "remainder differs at n={n} len={len}");
876 check_identity(&p, domain, &format!("n={n} len={len}"));
877 }
878 }
879 }
880
881 #[test]
884 fn identity_holds_for_random_inputs() {
885 let rng = &mut TestRng::default();
886 for _ in 0..200 {
887 let log_n = 2 + (u64::rand(rng) % 7) as usize;
888 let domain = EvaluationDomain::<Fr>::new(1 << log_n).unwrap();
889 let len = (u64::rand(rng) % (6 * domain.size() as u64 + 2)) as usize;
890 let p = rand_poly(len, rng);
891 check_identity(&p, domain, &format!("n={} len={len}", domain.size()));
892 }
893 }
894
895 #[test]
899 fn tolerates_trailing_zeros() {
900 let rng = &mut TestRng::default();
901 let domain = EvaluationDomain::<Fr>::new(16).unwrap();
902 for len in [0usize, 5, 16, 17, 33, 70] {
903 for pad in [1usize, 4, 20] {
904 let mut p = rand_poly(len, rng);
905 p.coeffs.resize(p.coeffs.len() + pad, Fr::zero());
906 check_identity(&p, domain, &format!("len={len} pad={pad}"));
907 }
908 }
909 }
910
911 #[test]
914 fn exact_multiples_leave_no_remainder() {
915 let rng = &mut TestRng::default();
916 for log_n in [3usize, 6] {
917 let domain = EvaluationDomain::<Fr>::new(1 << log_n).unwrap();
918 let v = DensePolynomial::from(domain.vanishing_polynomial());
919 for len in [1usize, 5, domain.size(), 3 * domain.size()] {
920 let p = &rand_poly(len, rng) * &v;
921 let (q, r) = p.divide_by_vanishing_poly(domain).unwrap();
922 assert!(r.is_zero(), "expected no remainder [n={} len={len}]", domain.size());
923 assert_eq!(&q * &v, p, "q*v != p [n={} len={len}]", domain.size());
924 }
925 }
926 }
927
928 #[test]
930 fn zero_divides_to_zero() {
931 let domain = EvaluationDomain::<Fr>::new(8).unwrap();
932 let (q, r) = DensePolynomial::<Fr>::zero().divide_by_vanishing_poly(domain).unwrap();
933 assert!(q.is_zero());
934 assert!(r.is_zero());
935 }
936}
937
938#[cfg(test)]
939mod monic_linear_divide_tests {
940 use crate::fft::{DensePolynomial, Polynomial};
941 use snarkvm_curves::bls12_377::Fr;
942 use snarkvm_fields::{One, Zero};
943 use snarkvm_utilities::{TestRng, Uniform};
944
945 fn generic(p: &DensePolynomial<Fr>, z: Fr) -> (DensePolynomial<Fr>, DensePolynomial<Fr>) {
947 let divisor = DensePolynomial::from_coefficients_vec(vec![-z, Fr::one()]);
948 Polynomial::from(p.clone()).divide_with_q_and_r(&Polynomial::from(divisor)).unwrap()
949 }
950
951 #[test]
952 fn agrees_with_generic_long_division() {
953 let rng = &mut TestRng::default();
954 for degree in [0usize, 1, 2, 3, 7, 64, 255, 1024] {
955 let p = DensePolynomial::<Fr>::rand(degree, rng);
956 let z = Fr::rand(rng);
957 let (q, r) = p.divide_by_monic_linear(z);
958 let (gq, gr) = generic(&p, z);
959 assert_eq!(q, gq, "quotient differs at degree {degree}");
960 assert_eq!(DensePolynomial::from_coefficients_vec(vec![r]), gr, "remainder differs at degree {degree}");
961 }
962 }
963
964 #[test]
967 fn satisfies_the_defining_identity() {
968 let rng = &mut TestRng::default();
969 for _ in 0..200 {
970 let degree = (u64::rand(rng) % 128) as usize;
971 let p = DensePolynomial::<Fr>::rand(degree, rng);
972 let z = Fr::rand(rng);
973 let (q, r) = p.divide_by_monic_linear(z);
974 let divisor = DensePolynomial::from_coefficients_vec(vec![-z, Fr::one()]);
975 let reconstructed = &(&divisor * &q) + &DensePolynomial::from_coefficients_vec(vec![r]);
976 assert_eq!(reconstructed, p);
977 }
978 }
979
980 #[test]
982 fn the_remainder_is_the_evaluation() {
983 let rng = &mut TestRng::default();
984 for degree in [0usize, 1, 5, 100, 513] {
985 let p = DensePolynomial::<Fr>::rand(degree, rng);
986 let z = Fr::rand(rng);
987 let (_, r) = p.divide_by_monic_linear(z);
988 assert_eq!(r, p.evaluate(z), "at degree {degree}");
989 }
990 }
991
992 #[test]
995 fn a_root_divides_exactly() {
996 let rng = &mut TestRng::default();
997 for degree in [1usize, 2, 9, 200] {
998 let q = DensePolynomial::<Fr>::rand(degree, rng);
999 let z = Fr::rand(rng);
1000 let divisor = DensePolynomial::from_coefficients_vec(vec![-z, Fr::one()]);
1001 let p = &divisor * &q;
1002 let (quotient, r) = p.divide_by_monic_linear(z);
1003 assert!(r.is_zero(), "remainder at degree {degree}");
1004 assert_eq!(quotient, q);
1005 assert!(quotient.coeffs.last().is_none_or(|c| !c.is_zero()), "non-canonical quotient");
1006 }
1007 }
1008
1009 #[test]
1011 fn tolerates_non_canonical_input() {
1012 let rng = &mut TestRng::default();
1013 let p = DensePolynomial::<Fr>::rand(30, rng);
1014 let z = Fr::rand(rng);
1015 let mut padded = p.clone();
1016 padded.coeffs.extend(std::iter::repeat_n(Fr::zero(), 5));
1017 let (q1, r1) = p.divide_by_monic_linear(z);
1018 let (q2, r2) = padded.divide_by_monic_linear(z);
1019 assert_eq!(q1, q2);
1020 assert_eq!(r1, r2);
1021 }
1022
1023 #[test]
1024 fn zero_and_constant_polynomials() {
1025 let rng = &mut TestRng::default();
1026 let z = Fr::rand(rng);
1027
1028 let (q, r) = DensePolynomial::<Fr>::zero().divide_by_monic_linear(z);
1029 assert!(q.is_zero() && r.is_zero());
1030
1031 let c = Fr::rand(rng);
1032 let (q, r) = DensePolynomial::from_coefficients_vec(vec![c]).divide_by_monic_linear(z);
1033 assert!(q.is_zero());
1034 assert_eq!(r, c);
1035 }
1036}
1037
1038#[cfg(test)]
1039mod evaluate_tests {
1040 use super::vanishing_divide_tests::rand_poly;
1041 use snarkvm_curves::bls12_377::Fr;
1042 use snarkvm_fields::{One, Zero};
1043 use snarkvm_utilities::rand::{TestRng, Uniform};
1044
1045 #[test]
1052 fn evaluate_agrees_with_the_powers_form() {
1053 let rng = &mut TestRng::default();
1054 for len in [0usize, 1, 2, 5, 16, 17, 33, 70] {
1055 let p = rand_poly(len, rng);
1056 for point in [Fr::zero(), Fr::one(), Fr::rand(rng), Fr::rand(rng)] {
1057 assert_eq!(p.evaluate(point), p.evaluate_by_powers(point), "len={len}");
1058 }
1059 }
1060 }
1061
1062 #[test]
1066 fn evaluate_tolerates_trailing_zeros() {
1067 let rng = &mut TestRng::default();
1068 for len in [1usize, 5, 17] {
1069 for pad in [1usize, 4, 20] {
1070 let mut p = rand_poly(len, rng);
1071 let point = Fr::rand(rng);
1072 let expected = p.evaluate(point);
1073 p.coeffs.resize(p.coeffs.len() + pad, Fr::zero());
1074 assert_eq!(p.evaluate(point), expected, "len={len} pad={pad}");
1075 }
1076 }
1077 }
1078}