use core::cmp::min;
use crate::{DenseUVPolynomial, EvaluationDomain, Evaluations, Polynomial};
use ark_ff::{FftField, Field, Zero};
use ark_std::{borrow::Cow, cfg_iter_mut, vec, vec::*};
use DenseOrSparsePolynomial::{DPolynomial, SPolynomial};
mod dense;
mod sparse;
pub use dense::DensePolynomial;
pub use sparse::SparsePolynomial;
#[cfg(feature = "parallel")]
use rayon::prelude::*;
#[derive(Clone)]
pub enum DenseOrSparsePolynomial<'a, F: Field> {
SPolynomial(Cow<'a, SparsePolynomial<F>>),
DPolynomial(Cow<'a, DensePolynomial<F>>),
}
impl<'a, F: 'a + Field> From<DensePolynomial<F>> for DenseOrSparsePolynomial<'a, F> {
fn from(other: DensePolynomial<F>) -> Self {
DPolynomial(Cow::Owned(other))
}
}
impl<'a, F: 'a + Field> From<&'a DensePolynomial<F>> for DenseOrSparsePolynomial<'a, F> {
fn from(other: &'a DensePolynomial<F>) -> Self {
DPolynomial(Cow::Borrowed(other))
}
}
impl<'a, F: 'a + Field> From<SparsePolynomial<F>> for DenseOrSparsePolynomial<'a, F> {
fn from(other: SparsePolynomial<F>) -> Self {
SPolynomial(Cow::Owned(other))
}
}
impl<'a, F: Field> From<&'a SparsePolynomial<F>> for DenseOrSparsePolynomial<'a, F> {
fn from(other: &'a SparsePolynomial<F>) -> Self {
SPolynomial(Cow::Borrowed(other))
}
}
impl<'a, F: Field> From<DenseOrSparsePolynomial<'a, F>> for DensePolynomial<F> {
fn from(other: DenseOrSparsePolynomial<'a, F>) -> Self {
match other {
DPolynomial(p) => p.into_owned(),
SPolynomial(p) => p.into_owned().into(),
}
}
}
impl<'a, F: 'a + Field> TryInto<SparsePolynomial<F>> for DenseOrSparsePolynomial<'a, F> {
type Error = ();
fn try_into(self) -> Result<SparsePolynomial<F>, ()> {
match self {
SPolynomial(p) => Ok(p.into_owned()),
_ => Err(()),
}
}
}
impl<F: Field> DenseOrSparsePolynomial<'_, F> {
pub fn is_zero(&self) -> bool {
match self {
SPolynomial(s) => s.is_zero(),
DPolynomial(d) => d.is_zero(),
}
}
pub fn degree(&self) -> usize {
match self {
SPolynomial(s) => s.degree(),
DPolynomial(d) => d.degree(),
}
}
#[inline]
fn leading_coefficient(&self) -> Option<&F> {
match self {
SPolynomial(p) => p.last().map(|(_, c)| c),
DPolynomial(p) => p.last(),
}
}
#[inline]
fn iter_with_index(&self) -> Vec<(usize, F)> {
match self {
SPolynomial(p) => p.to_vec(),
DPolynomial(p) => p.iter().copied().enumerate().collect(),
}
}
fn naive_div(&self, divisor: &Self) -> Option<(DensePolynomial<F>, DensePolynomial<F>)> {
if self.is_zero() {
Some((DensePolynomial::zero(), DensePolynomial::zero()))
} else if divisor.is_zero() {
panic!("Dividing by zero polynomial")
} else if self.degree() < divisor.degree() {
Some((DensePolynomial::zero(), self.clone().into()))
} else {
let mut quotient = vec![F::zero(); self.degree() - divisor.degree() + 1];
let mut remainder: DensePolynomial<F> = self.clone().into();
let divisor_leading_inv = divisor.leading_coefficient().unwrap().inverse().unwrap();
while !remainder.is_zero() && remainder.degree() >= divisor.degree() {
let cur_q_coeff = *remainder.coeffs.last().unwrap() * divisor_leading_inv;
let cur_q_degree = remainder.degree() - divisor.degree();
quotient[cur_q_degree] = cur_q_coeff;
for (i, div_coeff) in divisor.iter_with_index() {
remainder[cur_q_degree + i] -= &(cur_q_coeff * div_coeff);
}
while remainder.coeffs.last().map(|c| c.is_zero()) == Some(true) {
remainder.coeffs.pop();
}
}
Some((DensePolynomial::from_coefficients_vec(quotient), remainder))
}
}
}
impl<'a, F: FftField> DenseOrSparsePolynomial<'a, F> {
const SWITCH_TO_HENSEL_DIV: usize = 1 << 8;
pub fn divide_with_q_and_r(
&self,
divisor: &Self,
) -> Option<(DensePolynomial<F>, DensePolynomial<F>)> {
match divisor {
DPolynomial(p) if p.degree() >= Self::SWITCH_TO_HENSEL_DIV => {
let q = self.hensel_div(divisor)?;
let r = &self.clone().into() - &(&q * &divisor.clone().into());
Some((q, r))
},
_ => self.naive_div(divisor),
}
}
pub fn divide(&self, divisor: &Self) -> Option<DensePolynomial<F>> {
match divisor {
DPolynomial(p) if p.degree() >= Self::SWITCH_TO_HENSEL_DIV => self.hensel_div(divisor),
_ => self.naive_div(divisor).map(|(q, _)| q),
}
}
fn hensel_div(&self, divisor: &Self) -> Option<DensePolynomial<F>> {
if self.is_zero() {
Some(DensePolynomial::zero())
} else if divisor.is_zero() {
panic!("Dividing by zero polynomial")
} else if self.degree() < divisor.degree() {
Some(DensePolynomial::zero())
} else {
let dividend_poly: DensePolynomial<F> = self.clone().into();
let divisor_poly: DensePolynomial<F> = divisor.clone().into();
let reverted_dividend = Self::reverse_coeffs(÷nd_poly, self.degree() + 1);
let reverted_divisor = Self::reverse_coeffs(&divisor_poly, divisor.degree() + 1);
let inversion_degree = self.degree() - divisor.degree() + 1;
let reverted_divisor_inverse = Self::inverse_mod(&reverted_divisor, inversion_degree);
let reverted_q = DensePolynomial::from_coefficients_slice(
&(&reverted_divisor_inverse * &reverted_dividend).coeffs[..inversion_degree],
);
let q = DensePolynomial::from_coefficients_slice(
&Self::reverse_coeffs(&reverted_q, inversion_degree)
.coeffs
.as_slice(),
);
Some(q)
}
}
fn inverse_mod(poly: &DensePolynomial<F>, degree: usize) -> DensePolynomial<F> {
if poly.coeffs[0].is_zero() {
panic!("Cannot compute inverse of 0");
}
let mut l = 1;
let mut current_inverse =
DensePolynomial::from_coefficients_slice(&[F::one() / poly.coeffs[0]]);
while l < degree {
current_inverse = Self::hensel_lift(poly, ¤t_inverse, l);
l *= 2;
}
let max_coeff = min(current_inverse.coeffs.len(), degree + 1);
DensePolynomial::from_coefficients_slice(¤t_inverse.coeffs[..max_coeff])
}
#[inline]
fn hensel_lift(
poly: &DensePolynomial<F>,
inverse: &DensePolynomial<F>,
base_degree: usize,
) -> DensePolynomial<F> {
let poly_mod_x2deg = DensePolynomial::from_coefficients_slice(
&poly.coeffs[..min(2 * base_degree, poly.coeffs.len())],
); let one_plus_cxdeg = &poly_mod_x2deg * inverse;
if one_plus_cxdeg.degree() < base_degree {
return inverse.clone();
}
let c = DensePolynomial::from_coefficients_slice(
&one_plus_cxdeg.coeffs[base_degree..min(2 * base_degree, one_plus_cxdeg.coeffs.len())],
);
let mut new_inverse_coeffs = vec![F::zero(); 2 * base_degree];
new_inverse_coeffs[..min(base_degree, inverse.coeffs.len())]
.clone_from_slice(&inverse.coeffs);
let above_deg_coeffs: Vec<F> = (inverse * &c)
.coeffs
.iter()
.take(min(base_degree, inverse.degree() + &c.degree() + 1))
.map(|&x| -x)
.collect();
new_inverse_coeffs[base_degree..min(2 * base_degree, base_degree + above_deg_coeffs.len())]
.clone_from_slice(above_deg_coeffs.as_slice());
DensePolynomial::from_coefficients_vec(new_inverse_coeffs)
}
fn reverse_coeffs(poly: &DensePolynomial<F>, max_degree: usize) -> DensePolynomial<F> {
if poly.coeffs.len() > max_degree {
panic!(
"Polynomial too big (size {}) to be reverted in size {}",
poly.coeffs.len(),
max_degree
);
}
let mut rev_coeffs = vec![F::zero(); max_degree];
rev_coeffs[..poly.coeffs.len()].clone_from_slice(
&poly
.coeffs
.clone()
.into_iter()
.rev()
.collect::<Vec<_>>()
.as_slice(),
);
DensePolynomial::from_coefficients_vec(rev_coeffs)
}
}
impl<'a, F: 'a + FftField> DenseOrSparsePolynomial<'a, F> {
pub fn evaluate_over_domain<D: EvaluationDomain<F>>(
poly: impl Into<Self>,
domain: D,
) -> Evaluations<F, D> {
let poly = poly.into();
poly.eval_over_domain_helper(domain)
}
fn eval_over_domain_helper<D: EvaluationDomain<F>>(self, domain: D) -> Evaluations<F, D> {
let eval_sparse_poly = |s: &SparsePolynomial<F>| {
let evals = domain.elements().map(|elem| s.evaluate(&elem)).collect();
Evaluations::from_vec_and_domain(evals, domain)
};
match self {
SPolynomial(Cow::Borrowed(s)) => eval_sparse_poly(s),
SPolynomial(Cow::Owned(s)) => eval_sparse_poly(&s),
DPolynomial(Cow::Borrowed(d)) => {
if d.is_zero() {
Evaluations::zero(domain)
} else {
let mut chunks = d.coeffs.chunks(domain.size());
let mut first = chunks.next().unwrap().to_vec();
let offset = domain.coset_offset();
for (i, chunk) in chunks.enumerate() {
if offset.is_one() {
cfg_iter_mut!(first).zip(chunk).for_each(|(x, y)| *x += y);
} else {
let offset_power = offset.pow([((i + 1) * domain.size()) as u64]);
cfg_iter_mut!(first)
.zip(chunk)
.for_each(|(x, y)| *x += offset_power * y);
}
}
domain.fft_in_place(&mut first);
Evaluations::from_vec_and_domain(first, domain)
}
},
DPolynomial(Cow::Owned(mut d)) => {
if d.is_zero() {
Evaluations::zero(domain)
} else {
let mut chunks = d.coeffs.chunks_mut(domain.size());
let first = chunks.next().unwrap();
let offset = domain.coset_offset();
for (i, chunk) in chunks.enumerate() {
if offset.is_one() {
cfg_iter_mut!(first).zip(chunk).for_each(|(x, y)| *x += y);
} else {
let offset_power = offset.pow([((i + 1) * domain.size()) as u64]);
cfg_iter_mut!(first)
.zip(chunk)
.for_each(|(x, y)| *x += offset_power * y);
}
}
domain.fft_in_place(&mut d.coeffs);
Evaluations::from_vec_and_domain(d.coeffs, domain)
}
},
}
}
}