use ark_ff::Field;
use ark_serialize::{CanonicalDeserialize, CanonicalSerialize};
use ark_std::{
cfg_into_iter,
cmp::Ordering,
fmt::{Debug, Error, Formatter},
hash::Hash,
ops::Deref,
vec::*,
};
#[cfg(feature = "parallel")]
use rayon::prelude::*;
mod sparse;
pub use sparse::SparsePolynomial;
pub trait Term:
Clone
+ PartialOrd
+ Ord
+ PartialEq
+ Eq
+ Hash
+ Default
+ Debug
+ Deref<Target = [(usize, usize)]>
+ Send
+ Sync
+ CanonicalSerialize
+ CanonicalDeserialize
{
fn new(term: Vec<(usize, usize)>) -> Self;
fn degree(&self) -> usize;
fn vars(&self) -> Vec<usize>;
fn powers(&self) -> Vec<usize>;
fn is_constant(&self) -> bool;
fn evaluate<F: Field>(&self, p: &[F]) -> F;
}
#[derive(Clone, PartialEq, Eq, Hash, Default, CanonicalSerialize, CanonicalDeserialize)]
pub struct SparseTerm(Vec<(usize, usize)>);
impl SparseTerm {
fn combine(term: &[(usize, usize)]) -> Vec<(usize, usize)> {
let mut term_dedup: Vec<(usize, usize)> = Vec::new();
for (var, pow) in term {
if let Some(prev) = term_dedup.last_mut() {
if prev.0 == *var {
prev.1 += pow;
continue;
}
}
term_dedup.push((*var, *pow));
}
term_dedup
}
}
impl Term for SparseTerm {
fn new(mut term: Vec<(usize, usize)>) -> Self {
term.retain(|(_, pow)| *pow != 0);
if term.len() > 1 {
term.sort_by(|(v1, _), (v2, _)| v1.cmp(v2));
term = Self::combine(&term);
}
Self(term)
}
fn degree(&self) -> usize {
self.iter().fold(0, |sum, acc| sum + acc.1)
}
fn vars(&self) -> Vec<usize> {
self.iter().map(|(v, _)| *v).collect()
}
fn powers(&self) -> Vec<usize> {
self.iter().map(|(_, p)| *p).collect()
}
fn is_constant(&self) -> bool {
self.is_empty() || self.degree() == 0
}
fn evaluate<F: Field>(&self, point: &[F]) -> F {
cfg_into_iter!(self)
.map(|(var, power)| point[*var].pow([*power as u64]))
.product()
}
}
impl Debug for SparseTerm {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
for variable in self.iter() {
if variable.1 == 1 {
write!(f, " * x_{}", variable.0)?;
} else {
write!(f, " * x_{}^{}", variable.0, variable.1)?;
}
}
Ok(())
}
}
impl Deref for SparseTerm {
type Target = [(usize, usize)];
fn deref(&self) -> &[(usize, usize)] {
&self.0
}
}
impl PartialOrd for SparseTerm {
#[allow(clippy::non_canonical_partial_ord_impl)]
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
if self.degree() == other.degree() {
for ((cur_variable, cur_power), (other_variable, other_power)) in
self.iter().zip(other.iter())
{
if other_variable == cur_variable {
if cur_power != other_power {
return Some(cur_power.cmp(other_power));
}
} else {
return Some(other_variable.cmp(cur_variable));
}
}
Some(Ordering::Equal)
} else {
Some(self.degree().cmp(&other.degree()))
}
}
}
impl Ord for SparseTerm {
fn cmp(&self, other: &Self) -> Ordering {
self.partial_cmp(other).unwrap()
}
}
#[cfg(test)]
mod tests {
use super::*;
use ark_ff::{Fp64, MontBackend, MontConfig};
use ark_std::vec;
#[derive(MontConfig)]
#[modulus = "5"]
#[generator = "2"]
pub(crate) struct F5Config;
pub(crate) type F5 = Fp64<MontBackend<F5Config, 1>>;
#[test]
fn test_sparse_term_combine() {
let term = vec![(1, 2), (1, 3), (2, 1)];
let combined = SparseTerm::combine(&term);
assert_eq!(combined, vec![(1, 5), (2, 1)]);
}
#[test]
fn test_sparse_term_new() {
let term = vec![(2, 1), (1, 2), (1, 3), (3, 0)];
let sparse_term = SparseTerm::new(term);
assert_eq!(sparse_term, SparseTerm(vec![(1, 5), (2, 1)]));
}
#[test]
fn test_sparse_term_degree() {
let term = SparseTerm::new(vec![(1, 2), (2, 3)]);
assert_eq!(term.degree(), 5); }
#[test]
fn test_sparse_term_vars() {
let term = SparseTerm::new(vec![(1, 1), (1, 2), (2, 3)]);
assert_eq!(term.vars(), vec![1, 2]);
}
#[test]
fn test_sparse_term_powers() {
let term = SparseTerm::new(vec![(1, 2), (1, 3), (2, 3)]);
assert_eq!(term.powers(), vec![5, 3]);
}
#[test]
fn test_sparse_term_is_constant() {
let constant_term = SparseTerm::new(vec![]);
assert!(constant_term.is_constant());
let non_constant_term = SparseTerm::new(vec![(1, 2)]);
assert!(!non_constant_term.is_constant());
}
#[test]
fn test_evaluate() {
let term = SparseTerm::new(vec![(0, 2), (1, 3)]);
let point = vec![F5::from(1u64), F5::from(2u64)];
let result = term.evaluate::<F5>(&point);
assert_eq!(result, F5::from(8u64)); }
#[test]
fn test_partial_cmp() {
let term1 = SparseTerm::new(vec![(1, 2), (2, 3)]);
let term2 = SparseTerm::new(vec![(1, 2), (2, 2)]);
let term3 = SparseTerm::new(vec![(2, 3), (1, 2)]);
let term4 = SparseTerm::new(vec![(1, 2)]);
let term5 = SparseTerm::new(vec![(2, 2)]);
let term6 = SparseTerm::new(vec![(1, 0), (2, 0)]);
let term7 = SparseTerm::new(vec![]);
assert_eq!(term1.partial_cmp(&term2), Some(Ordering::Greater)); assert_eq!(term1.partial_cmp(&term3), Some(Ordering::Equal)); assert_eq!(term2.partial_cmp(&term3), Some(Ordering::Less));
assert_eq!(term1.partial_cmp(&term4), Some(Ordering::Greater)); assert_eq!(term4.partial_cmp(&term5), Some(Ordering::Greater)); assert_eq!(term4.partial_cmp(&term6), Some(Ordering::Greater));
assert_eq!(term6.partial_cmp(&term7), Some(Ordering::Equal)); assert_eq!(term7.partial_cmp(&term1), Some(Ordering::Less)); }
#[test]
fn test_cmp() {
let term1 = SparseTerm::new(vec![(1, 2), (2, 3)]);
let term2 = SparseTerm::new(vec![(1, 2), (2, 2)]);
let term3 = SparseTerm::new(vec![(2, 3), (1, 2)]);
let term4 = SparseTerm::new(vec![(1, 2)]);
let term5 = SparseTerm::new(vec![(2, 2)]);
let term6 = SparseTerm::new(vec![(1, 0), (2, 0)]);
let term7 = SparseTerm::new(vec![]);
assert_eq!(term1.cmp(&term2), Ordering::Greater); assert_eq!(term1.cmp(&term3), Ordering::Equal); assert_eq!(term2.cmp(&term3), Ordering::Less);
assert_eq!(term1.cmp(&term4), Ordering::Greater); assert_eq!(term4.cmp(&term5), Ordering::Greater); assert_eq!(term4.cmp(&term6), Ordering::Greater);
assert_eq!(term6.cmp(&term7), Ordering::Equal); assert_eq!(term7.cmp(&term1), Ordering::Less); }
}