Skip to main content

snarkvm_algorithms/fft/polynomial/
dense.rs

1// Copyright (c) 2019-2026 Provable Inc.
2// This file is part of the snarkVM library.
3
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at:
7
8// http://www.apache.org/licenses/LICENSE-2.0
9
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15
16//! A polynomial represented in coefficient form.
17
18use 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/// Stores a polynomial in coefficient form.
37#[derive(Clone, PartialEq, Eq, Hash, Default, CanonicalSerialize, CanonicalDeserialize)]
38#[must_use]
39pub struct DensePolynomial<F: Field> {
40    /// The coefficient of `x^i` is stored at location `i` in `self.coeffs`.
41    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    /// Drops trailing zero coefficients, so that `degree()` is the real degree.
61    ///
62    /// `degree()` asserts that the last coefficient is non-zero, and
63    /// `apply_randomized_selector` divides by degree, so a polynomial that
64    /// skips this is a live panic rather than an untidy one. The loop was
65    /// written out seven times in this file and once in `second.rs`; naming it
66    /// means the next person changing one changes all of them.
67    ///
68    /// `from_coefficients_vec` still writes the loop out, and has to: it trims
69    /// a bare `Vec<F>` before there is a `Self` to call this on.
70    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    /// Returns the zero polynomial.
77    pub fn zero() -> Self {
78        Self { coeffs: Vec::new() }
79    }
80
81    /// Checks if the given polynomial is zero.
82    pub fn is_zero(&self) -> bool {
83        self.coeffs.is_empty() || self.coeffs.iter().all(|coeff| coeff.is_zero())
84    }
85
86    /// Constructs a new polynomial from a list of coefficients.
87    pub fn from_coefficients_slice(coeffs: &[F]) -> Self {
88        Self::from_coefficients_vec(coeffs.to_vec())
89    }
90
91    /// Constructs a new polynomial from a list of coefficients.
92    pub fn from_coefficients_vec(mut coeffs: Vec<F>) -> Self {
93        // While there are zeros at the end of the coefficient vector, pop them off.
94        while let Some(true) = coeffs.last().map(|c| c.is_zero()) {
95            coeffs.pop();
96        }
97        // Check that either the coefficients vec are empty or that the last coeff is
98        // non-zero.
99        assert!(coeffs.last().is_none_or(|coeff| !coeff.is_zero()));
100
101        Self { coeffs }
102    }
103
104    /// Returns the degree of the polynomial.
105    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    /// Evaluates `self` at the given `point` in the field.
115    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        // Horner's rule. It never calls `degree()`, so unlike the powers form it
123        // replaced it tolerates trailing zero coefficients, which reach it
124        // because `coeffs` is public. `evaluate_tolerates_trailing_zeros` pins
125        // that.
126        let mut acc = F::zero();
127        for coeff in self.coeffs.iter().rev() {
128            acc = acc * point + *coeff;
129        }
130        acc
131    }
132
133    /// The powers form that `evaluate` replaced, kept so the equivalence test
134    /// has something to compare against.
135    #[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    /// Outputs a univariate polynomial of degree `d` where each non-leading
155    /// coefficient is sampled uniformly at random from R and the leading
156    /// coefficient is sampled uniformly at random from among the non-zero
157    /// elements of R.
158    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            // In the extremely unlikely event, sample again.
162            random_coeffs[d] = F::rand(rng);
163        }
164        Self::from_coefficients_vec(random_coeffs)
165    }
166
167    /// Returns the coefficients of `self`.
168    pub fn coeffs(&self) -> &[F] {
169        &self.coeffs
170    }
171
172    /// Perform a naive n^2 multiplication of `self` by `other`.
173    #[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    /// Multiply `self` by the vanishing polynomial for the domain `domain`.
191    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    /// Divides by the monic linear polynomial `X - z` using Ruffini's rule,
199    /// returning the quotient and the remainder `self(z)`.
200    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    /// Divides by the vanishing polynomial `X^n - 1` of `domain`, returning
217    /// `(quotient, remainder)`.
218    ///
219    /// Matching coefficients on `p = q(X^n - 1) + r`, with `deg(r) < n`:
220    ///
221    /// ```text
222    ///     q[j] = p[j+n] + q[j+n]      (q[k] = 0 for k >= len(q))
223    ///     r[i] = p[i]   + q[i]
224    /// ```
225    ///
226    /// The recurrence is `n` independent chains, one per residue class mod `n`.
227    /// For `deg(p) < 2n` each chain is a single step and the quotient is
228    /// `p[n..]`.
229    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        // deg(p) < n: the quotient is zero and the remainder is `p`.
238        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            // Single-step chains.
245            p[n..].to_vec()
246        } else {
247            // Chains span several steps, so each carries into the next.
248            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    /// Evaluate `self` over `domain`.
264    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    /// Evaluate `self` over `domain`.
270    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            // Zip safety: `result` and `other` could have different lengths.
297            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            // Zip safety: `result` and `other` could have different lengths.
302            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            // return
317        } else if self.degree() >= other.degree() {
318            // Zip safety: `self` and `other` could have different lengths.
319            cfg_iter_mut!(self.coeffs, 1_000).zip(&other.coeffs).for_each(|(a, b)| *a += b);
320        } else {
321            // Add the necessary number of zero coefficients.
322            self.coeffs.resize(other.coeffs.len(), F::zero());
323            // Zip safety: `self` and `other` have the same length.
324            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            // One pass, not two. The scale has to happen either way; the copy
353            // does not have to be a separate walk over the coefficients.
354            //
355            // `clear()` rather than assigning a fresh vector, because `self`
356            // being zero does not mean it is empty -- a polynomial whose
357            // coefficients are all zero reaches here too, and its allocation is
358            // worth keeping.
359            self.coeffs.clear();
360            self.coeffs.extend(other.coeffs.iter().map(|c| *c * f));
361        } else if other.is_zero() {
362            // return
363        } else if self.degree() >= other.degree() {
364            // Zip safety: `self` and `other` could have different lengths.
365            cfg_iter_mut!(self.coeffs, 1_000).zip(&other.coeffs).for_each(|(a, b)| {
366                *a += f * b;
367            });
368        } else {
369            // Add the necessary number of zero coefficients.
370            self.coeffs.resize(other.coeffs.len(), F::zero());
371            // Zip safety: `self` and `other` have the same length after the resize.
372            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            // Zip safety: `result` and `other` could have different degrees.
408            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            // Zip safety: `result` and `other` have the same length after the resize.
414            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            // return
434        } else if self.degree() >= other.degree() {
435            // Zip safety: self and other could have different lengths.
436            cfg_iter_mut!(self.coeffs).zip(&other.coeffs).for_each(|(a, b)| *a -= b);
437        } else {
438            // Add the necessary number of zero coefficients.
439            self.coeffs.resize(other.coeffs.len(), F::zero());
440            // Zip safety: self and other have the same length after the resize.
441            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    /// This division can panic and ignores remainders
480    #[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    /// This division can panic and ignores remainders
492    #[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
518/// Performs O(nlogn) multiplication of polynomials if F is smooth.
519impl<'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
536/// Multiplies `self` by `other: F`.
537impl<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
547/// Multiplies `self` by `other: F`.
548impl<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
558/// Multiplies `self` by `other: F`.
559impl<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
566/// Multiplies `self` by `other: F`.
567impl<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        // `DensePolynomial::rand` resamples until the leading coefficient is
642        // non-zero, so `add_polynomials_with_mul` never enters `add_assign`'s
643        // `self.is_zero()` branch. These do, both ways it can be reached: an
644        // empty polynomial, and a non-empty one whose coefficients are all zero
645        // -- which is why that branch still has to `clear()`.
646        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            // Built through the field, not `from_coefficients_vec`, which strips
657            // trailing zeros and would hand back the empty representation --
658            // making this the same case as `empty` above and testing nothing.
659            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        // A zero scalar still normalises to the empty representation.
667        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()]); // Construct a monic linear polynomial.
694        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(&(&dividend).into(), &(&divisor).into()).unwrap();
714                assert_eq!(dividend, &(&divisor * &quotient) + &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            // Include polynomials and evaluations
768            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            // Add evaluations but don't overflow the domain
782            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        // Single polynomial
810        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        // Note PolyMultiplier doesn't support evaluations with no polynomials
817    }
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    /// `q * (X^n - 1) + r == p`, and `deg(r) < n`.
846    ///
847    /// Compared against the canonical form of `p`: results are trimmed on
848    /// construction, so an input carrying trailing zeros would otherwise differ
849    /// from an equal output by representation alone.
850    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    /// Agrees with generic long division across inputs shorter than the
860    /// divisor, on the `deg(p) < 2n` path, and long enough for a chain to
861    /// carry.
862    #[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    /// Random lengths spanning every branch, including chains long enough to
882    /// carry several times.
883    #[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    /// `coeffs` is public, so a caller can hand over a polynomial carrying
896    /// trailing zeros. The quotient's own leading zeros are trimmed on
897    /// construction, so the identity still holds.
898    #[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    /// A polynomial that is exactly a multiple of the vanishing polynomial
912    /// divides with no remainder, which is what the selector paths assert.
913    #[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    /// The zero polynomial divides to zero and zero.
929    #[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    /// What generic long division would have produced, for comparison.
946    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    /// p = (X - z) q + r, over enough random inputs to catch an off-by-one that
965    /// hand-picked shapes would step over.
966    #[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    /// The remainder of dividing by `X - z` is the evaluation at `z`.
981    #[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    /// A root divides exactly, and the quotient must come back canonical --
993    /// this is the shape that carried a real defect in the vanishing-poly case.
994    #[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    /// Trailing zeros in the coefficient vector must not change the answer.
1010    #[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    /// Horner agrees with the powers form it replaced, on well-formed input.
1046    ///
1047    /// Field addition is associative, so the tree reduction the powers form
1048    /// used and Horner's serial accumulation agree exactly rather than
1049    /// approximately -- `assert_eq!` is the right strength here, not an
1050    /// epsilon.
1051    #[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    /// `evaluate` tolerates trailing zero coefficients. The powers form it
1063    /// replaced panicked in `degree()` on exactly this input, so the tolerance
1064    /// is a deliberate widening rather than an accident.
1065    #[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}