use crate::fft::{EvaluationDomain, Evaluations, Polynomial};
use snarkvm_fields::{Field, PrimeField};
use snarkvm_utilities::{cfg_iter_mut, serialize::*};
use rand::Rng;
use std::{
fmt,
ops::{Add, AddAssign, Deref, DerefMut, Div, Mul, MulAssign, Neg, Sub, SubAssign},
};
#[cfg(feature = "serial")]
use itertools::Itertools;
#[cfg(not(feature = "serial"))]
use rayon::prelude::*;
use super::PolyMultiplier;
#[derive(Clone, PartialEq, Eq, Hash, Default, CanonicalSerialize, CanonicalDeserialize)]
#[must_use]
pub struct DensePolynomial<F: Field> {
pub coeffs: Vec<F>,
}
impl<F: Field> fmt::Debug for DensePolynomial<F> {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
for (i, coeff) in self.coeffs.iter().enumerate().filter(|(_, c)| !c.is_zero()) {
if i == 0 {
write!(f, "\n{coeff:?}",)?;
} else if i == 1 {
write!(f, " + \n{coeff:?} * x")?;
} else {
write!(f, " + \n{coeff:?} * x^{i}")?;
}
}
Ok(())
}
}
impl<F: Field> DensePolynomial<F> {
pub fn zero() -> Self {
Self { coeffs: Vec::new() }
}
pub fn is_zero(&self) -> bool {
self.coeffs.is_empty() || self.coeffs.iter().all(|coeff| coeff.is_zero())
}
pub fn from_coefficients_slice(coeffs: &[F]) -> Self {
Self::from_coefficients_vec(coeffs.to_vec())
}
pub fn from_coefficients_vec(mut coeffs: Vec<F>) -> Self {
while coeffs.last().map_or(false, |c| c.is_zero()) {
coeffs.pop();
}
assert!(coeffs.last().map_or(true, |coeff| !coeff.is_zero()));
Self { coeffs }
}
pub fn degree(&self) -> usize {
if self.is_zero() {
0
} else {
assert!(self.coeffs.last().map_or(false, |coeff| !coeff.is_zero()));
self.coeffs.len() - 1
}
}
pub fn evaluate(&self, point: F) -> F {
if self.is_zero() {
return F::zero();
} else if point.is_zero() {
return self.coeffs[0];
}
let mut powers_of_point = vec![F::one()];
let mut cur = point;
for _ in 0..self.degree() {
powers_of_point.push(cur);
cur *= point;
}
let zero = F::zero();
let mapping = crate::cfg_into_iter!(powers_of_point).zip_eq(&self.coeffs).map(|(power, coeff)| power * coeff);
crate::cfg_reduce!(mapping, || zero, |a, b| a + b)
}
pub fn rand<R: Rng>(d: usize, rng: &mut R) -> Self {
let random_coeffs = (0..(d + 1)).map(|_| F::rand(rng)).collect();
Self::from_coefficients_vec(random_coeffs)
}
pub fn coeffs(&self) -> &[F] {
&self.coeffs
}
#[cfg(test)]
fn naive_mul(&self, other: &Self) -> Self {
if self.is_zero() || other.is_zero() {
DensePolynomial::zero()
} else {
let mut result = vec![F::zero(); self.degree() + other.degree() + 1];
for (i, self_coeff) in self.coeffs.iter().enumerate() {
for (j, other_coeff) in other.coeffs.iter().enumerate() {
result[i + j] += *self_coeff * other_coeff;
}
}
DensePolynomial::from_coefficients_vec(result)
}
}
}
impl<F: PrimeField> DensePolynomial<F> {
pub fn mul_by_vanishing_poly(&self, domain: EvaluationDomain<F>) -> DensePolynomial<F> {
let mut shifted = vec![F::zero(); domain.size()];
shifted.extend_from_slice(&self.coeffs);
crate::cfg_iter_mut!(shifted[..self.coeffs.len()]).zip_eq(&self.coeffs).for_each(|(s, c)| *s -= c);
DensePolynomial::from_coefficients_vec(shifted)
}
pub fn divide_by_vanishing_poly(
&self,
domain: EvaluationDomain<F>,
) -> Option<(DensePolynomial<F>, DensePolynomial<F>)> {
let self_poly = Polynomial::from(self);
let vanishing_poly = Polynomial::from(domain.vanishing_polynomial());
self_poly.divide_with_q_and_r(&vanishing_poly)
}
pub fn evaluate_over_domain_by_ref(&self, domain: EvaluationDomain<F>) -> Evaluations<F> {
let poly: Polynomial<'_, F> = self.into();
Polynomial::<F>::evaluate_over_domain(poly, domain)
}
pub fn evaluate_over_domain(self, domain: EvaluationDomain<F>) -> Evaluations<F> {
let poly: Polynomial<'_, F> = self.into();
Polynomial::<F>::evaluate_over_domain(poly, domain)
}
}
impl<F: Field> From<super::SparsePolynomial<F>> for DensePolynomial<F> {
fn from(other: super::SparsePolynomial<F>) -> Self {
let mut result = vec![F::zero(); other.degree() + 1];
for (i, coeff) in other.coeffs() {
result[*i] = *coeff;
}
DensePolynomial::from_coefficients_vec(result)
}
}
impl<'a, 'b, F: Field> Add<&'a DensePolynomial<F>> for &'b DensePolynomial<F> {
type Output = DensePolynomial<F>;
fn add(self, other: &'a DensePolynomial<F>) -> DensePolynomial<F> {
if self.is_zero() {
other.clone()
} else if other.is_zero() {
self.clone()
} else if self.degree() >= other.degree() {
let mut result = self.clone();
cfg_iter_mut!(result.coeffs).zip(&other.coeffs).for_each(|(a, b)| *a += b);
result
} else {
let mut result = other.clone();
cfg_iter_mut!(result.coeffs).zip(&self.coeffs).for_each(|(a, b)| *a += b);
while result.coeffs.last().unwrap().is_zero() {
result.coeffs.pop();
}
result
}
}
}
impl<'a, F: Field> AddAssign<&'a DensePolynomial<F>> for DensePolynomial<F> {
fn add_assign(&mut self, other: &'a DensePolynomial<F>) {
if self.is_zero() {
self.coeffs.clear();
self.coeffs.extend_from_slice(&other.coeffs);
} else if other.is_zero() {
} else if self.degree() >= other.degree() {
cfg_iter_mut!(self.coeffs).zip(&other.coeffs).for_each(|(a, b)| *a += b);
} else {
self.coeffs.resize(other.coeffs.len(), F::zero());
cfg_iter_mut!(self.coeffs).zip(&other.coeffs).for_each(|(a, b)| *a += b);
while self.coeffs.last().unwrap().is_zero() {
self.coeffs.pop();
}
}
}
}
impl<'a, F: Field> AddAssign<&'a Polynomial<'a, F>> for DensePolynomial<F> {
fn add_assign(&mut self, other: &'a Polynomial<F>) {
match other {
Polynomial::Sparse(p) => *self += &Self::from(p.clone().into_owned()),
Polynomial::Dense(p) => *self += p.as_ref(),
}
}
}
impl<'a, F: Field> AddAssign<(F, &'a Polynomial<'a, F>)> for DensePolynomial<F> {
fn add_assign(&mut self, (f, other): (F, &'a Polynomial<F>)) {
match other {
Polynomial::Sparse(p) => *self += (f, &Self::from(p.clone().into_owned())),
Polynomial::Dense(p) => *self += (f, p.as_ref()),
}
}
}
impl<'a, F: Field> AddAssign<(F, &'a DensePolynomial<F>)> for DensePolynomial<F> {
#[allow(clippy::suspicious_op_assign_impl)]
fn add_assign(&mut self, (f, other): (F, &'a DensePolynomial<F>)) {
if self.is_zero() {
self.coeffs.clear();
self.coeffs.extend_from_slice(&other.coeffs);
self.coeffs.iter_mut().for_each(|c| *c *= &f);
} else if other.is_zero() {
} else if self.degree() >= other.degree() {
cfg_iter_mut!(self.coeffs).zip(&other.coeffs).for_each(|(a, b)| {
*a += f * b;
});
} else {
self.coeffs.resize(other.coeffs.len(), F::zero());
cfg_iter_mut!(self.coeffs).zip(&other.coeffs).for_each(|(a, b)| {
*a += f * b;
});
while self.coeffs.last().unwrap().is_zero() {
self.coeffs.pop();
}
}
}
}
impl<F: Field> Neg for DensePolynomial<F> {
type Output = DensePolynomial<F>;
#[inline]
fn neg(mut self) -> DensePolynomial<F> {
for coeff in &mut self.coeffs {
*coeff = -*coeff;
}
self
}
}
impl<'a, 'b, F: Field> Sub<&'a DensePolynomial<F>> for &'b DensePolynomial<F> {
type Output = DensePolynomial<F>;
#[inline]
fn sub(self, other: &'a DensePolynomial<F>) -> DensePolynomial<F> {
if self.is_zero() {
let mut result = other.clone();
for coeff in &mut result.coeffs {
*coeff = -(*coeff);
}
result
} else if other.is_zero() {
self.clone()
} else if self.degree() >= other.degree() {
let mut result = self.clone();
cfg_iter_mut!(result.coeffs).zip(&other.coeffs).for_each(|(a, b)| *a -= b);
result
} else {
let mut result = self.clone();
result.coeffs.resize(other.coeffs.len(), F::zero());
cfg_iter_mut!(result.coeffs).zip(&other.coeffs).for_each(|(a, b)| {
*a -= b;
});
if !result.is_zero() {
while result.coeffs.last().unwrap().is_zero() {
result.coeffs.pop();
}
}
result
}
}
}
impl<'a, F: Field> SubAssign<&'a DensePolynomial<F>> for DensePolynomial<F> {
#[inline]
fn sub_assign(&mut self, other: &'a DensePolynomial<F>) {
if self.is_zero() {
self.coeffs.resize(other.coeffs.len(), F::zero());
for (i, coeff) in other.coeffs.iter().enumerate() {
self.coeffs[i] -= coeff;
}
} else if other.is_zero() {
} else if self.degree() >= other.degree() {
cfg_iter_mut!(self.coeffs).zip(&other.coeffs).for_each(|(a, b)| *a -= b);
} else {
self.coeffs.resize(other.coeffs.len(), F::zero());
cfg_iter_mut!(self.coeffs).zip(&other.coeffs).for_each(|(a, b)| *a -= b);
while self.coeffs.last().unwrap().is_zero() {
self.coeffs.pop();
}
}
}
}
impl<'a, F: Field> AddAssign<&'a super::SparsePolynomial<F>> for DensePolynomial<F> {
#[inline]
fn add_assign(&mut self, other: &'a super::SparsePolynomial<F>) {
if self.degree() < other.degree() {
self.coeffs.resize(other.degree() + 1, F::zero());
}
for (i, b) in other.coeffs() {
self.coeffs[*i] += b;
}
while let Some(true) = self.coeffs.last().map(|c| c.is_zero()) {
self.coeffs.pop();
}
}
}
impl<'a, F: Field> Sub<&'a super::SparsePolynomial<F>> for DensePolynomial<F> {
type Output = Self;
#[inline]
fn sub(mut self, other: &'a super::SparsePolynomial<F>) -> Self::Output {
if self.degree() < other.degree() {
self.coeffs.resize(other.degree() + 1, F::zero());
}
for (i, b) in other.coeffs() {
self.coeffs[*i] -= b;
}
while let Some(true) = self.coeffs.last().map(|c| c.is_zero()) {
self.coeffs.pop();
}
self
}
}
impl<'a, 'b, F: Field> Div<&'a DensePolynomial<F>> for &'b DensePolynomial<F> {
type Output = DensePolynomial<F>;
#[inline]
fn div(self, divisor: &'a DensePolynomial<F>) -> DensePolynomial<F> {
let a: Polynomial<_> = self.into();
let b: Polynomial<_> = divisor.into();
a.divide_with_q_and_r(&b).expect("division failed").0
}
}
impl<'a, 'b, F: PrimeField> Mul<&'a DensePolynomial<F>> for &'b DensePolynomial<F> {
type Output = DensePolynomial<F>;
#[inline]
#[allow(clippy::suspicious_arithmetic_impl)]
fn mul(self, other: &'a DensePolynomial<F>) -> DensePolynomial<F> {
if self.is_zero() || other.is_zero() {
DensePolynomial::zero()
} else {
let mut m = PolyMultiplier::new();
m.add_polynomial_ref(self, "");
m.add_polynomial_ref(other, "");
m.multiply().unwrap()
}
}
}
impl<F: Field> Mul<F> for DensePolynomial<F> {
type Output = Self;
#[inline]
fn mul(mut self, other: F) -> Self {
self.iter_mut().for_each(|c| *c *= other);
self
}
}
impl<'a, F: Field> Mul<F> for &'a DensePolynomial<F> {
type Output = DensePolynomial<F>;
#[inline]
fn mul(self, other: F) -> Self::Output {
let result = self.clone();
result * other
}
}
impl<F: Field> MulAssign<F> for DensePolynomial<F> {
#[allow(clippy::suspicious_arithmetic_impl)]
fn mul_assign(&mut self, other: F) {
cfg_iter_mut!(self).for_each(|c| *c *= other);
}
}
impl<F: Field> std::iter::Sum for DensePolynomial<F> {
fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
iter.fold(DensePolynomial::zero(), |a, b| &a + &b)
}
}
impl<F: Field> Deref for DensePolynomial<F> {
type Target = [F];
fn deref(&self) -> &[F] {
&self.coeffs
}
}
impl<F: Field> DerefMut for DensePolynomial<F> {
fn deref_mut(&mut self) -> &mut [F] {
&mut self.coeffs
}
}
#[cfg(test)]
mod tests {
use crate::fft::polynomial::*;
use snarkvm_curves::bls12_377::Fr;
use snarkvm_fields::{Field, One, Zero};
use snarkvm_utilities::rand::{TestRng, Uniform};
use rand::RngCore;
#[test]
fn double_polynomials_random() {
let rng = &mut TestRng::default();
for degree in 0..70 {
let p = DensePolynomial::<Fr>::rand(degree, rng);
let p_double = &p + &p;
let p_quad = &p_double + &p_double;
assert_eq!(&(&(&p + &p) + &p) + &p, p_quad);
}
}
#[test]
fn add_polynomials() {
let rng = &mut TestRng::default();
for a_degree in 0..70 {
for b_degree in 0..70 {
let p1 = DensePolynomial::<Fr>::rand(a_degree, rng);
let p2 = DensePolynomial::<Fr>::rand(b_degree, rng);
let res1 = &p1 + &p2;
let res2 = &p2 + &p1;
assert_eq!(res1, res2);
}
}
}
#[test]
fn add_polynomials_with_mul() {
let rng = &mut TestRng::default();
for a_degree in 0..70 {
for b_degree in 0..70 {
let mut p1 = DensePolynomial::rand(a_degree, rng);
let p2 = DensePolynomial::rand(b_degree, rng);
let f = Fr::rand(rng);
let f_p2 = DensePolynomial::from_coefficients_vec(p2.coeffs.iter().map(|c| f * c).collect());
let res2 = &f_p2 + &p1;
p1 += (f, &p2);
let res1 = p1;
assert_eq!(res1, res2);
}
}
}
#[test]
fn sub_polynomials() {
let rng = &mut TestRng::default();
let p1 = DensePolynomial::<Fr>::rand(5, rng);
let p2 = DensePolynomial::<Fr>::rand(3, rng);
let res1 = &p1 - &p2;
let res2 = &p2 - &p1;
assert_eq!(&res1 + &p2, p1, "Subtraction should be inverse of addition!");
assert_eq!(res1, -res2, "p2 - p1 = -(p1 - p2)");
}
#[test]
fn divide_polynomials_fixed() {
let dividend = DensePolynomial::from_coefficients_slice(&[
"4".parse().unwrap(),
"8".parse().unwrap(),
"5".parse().unwrap(),
"1".parse().unwrap(),
]);
let divisor = DensePolynomial::from_coefficients_slice(&[Fr::one(), Fr::one()]); let result = ÷nd / &divisor;
let expected_result = DensePolynomial::from_coefficients_slice(&[
"4".parse().unwrap(),
"4".parse().unwrap(),
"1".parse().unwrap(),
]);
assert_eq!(expected_result, result);
}
#[test]
#[allow(clippy::needless_borrow)]
fn divide_polynomials_random() {
let rng = &mut TestRng::default();
for a_degree in 0..70 {
for b_degree in 0..70 {
let dividend = DensePolynomial::<Fr>::rand(a_degree, rng);
let divisor = DensePolynomial::<Fr>::rand(b_degree, rng);
if let Some((quotient, remainder)) =
Polynomial::divide_with_q_and_r(&(÷nd).into(), &(&divisor).into())
{
assert_eq!(dividend, &(&divisor * "ient) + &remainder)
}
}
}
}
#[test]
fn evaluate_polynomials() {
let rng = &mut TestRng::default();
for a_degree in 0..70 {
let p = DensePolynomial::rand(a_degree, rng);
let point: Fr = Fr::from(10u64);
let mut total = Fr::zero();
for (i, coeff) in p.coeffs.iter().enumerate() {
total += point.pow([i as u64]) * coeff;
}
assert_eq!(p.evaluate(point), total);
}
}
#[test]
fn mul_polynomials_random() {
let rng = &mut TestRng::default();
for a_degree in 0..70 {
for b_degree in 0..70 {
dbg!(a_degree);
dbg!(b_degree);
let a = DensePolynomial::<Fr>::rand(a_degree, rng);
let b = DensePolynomial::<Fr>::rand(b_degree, rng);
assert_eq!(&a * &b, a.naive_mul(&b))
}
}
}
#[test]
fn mul_polynomials_n_random() {
let rng = &mut TestRng::default();
let max_degree = 1 << 8;
for _ in 0..10 {
let mut multiplier = PolyMultiplier::new();
let a = DensePolynomial::<Fr>::rand(max_degree / 2, rng);
let mut mul_degree = a.degree() + 1;
multiplier.add_polynomial(a.clone(), "a");
let mut naive = a.clone();
let num_polys = (rng.next_u32() as usize) % 8;
let num_evals = (rng.next_u32() as usize) % 4;
println!("\nnum_polys {num_polys}, num_evals {num_evals}");
for _ in 1..num_polys {
let degree = (rng.next_u32() as usize) % max_degree;
mul_degree += degree + 1;
println!("poly degree {degree}");
let a = DensePolynomial::<Fr>::rand(degree, rng);
naive = naive.naive_mul(&a);
multiplier.add_polynomial(a.clone(), "a");
}
let mut eval_degree = mul_degree;
let domain = EvaluationDomain::new(mul_degree).unwrap();
println!("mul_degree {}, domain {}", mul_degree, domain.size());
for _ in 0..num_evals {
let a = DensePolynomial::<Fr>::rand(mul_degree / 8, rng);
eval_degree += a.len() + 1;
if eval_degree < domain.size() {
println!("eval degree {eval_degree}");
let mut a_evals = a.clone().coeffs;
domain.fft_in_place(&mut a_evals);
let a_evals = Evaluations::from_vec_and_domain(a_evals, domain);
naive = naive.naive_mul(&a);
multiplier.add_evaluation(a_evals, "a");
}
}
assert_eq!(multiplier.multiply().unwrap(), naive);
}
}
#[test]
fn mul_polynomials_corner_cases() {
let rng = &mut TestRng::default();
let a_degree = 70;
println!("Test single polynomial");
let a = DensePolynomial::<Fr>::rand(a_degree, rng);
let mut multiplier = PolyMultiplier::new();
multiplier.add_polynomial(a.clone(), "a");
assert_eq!(multiplier.multiply().unwrap(), a);
}
#[test]
fn mul_by_vanishing_poly() {
let rng = &mut TestRng::default();
for size in 1..10 {
let domain = EvaluationDomain::new(1 << size).unwrap();
for degree in 0..70 {
let p = DensePolynomial::<Fr>::rand(degree, rng);
let ans1 = p.mul_by_vanishing_poly(domain);
let ans2 = &p * &domain.vanishing_polynomial().into();
assert_eq!(ans1, ans2);
}
}
}
}