sparse.rs
1 // Copyright (c) 2025-2026 ACDC Network 2 // This file is part of the alphavm library. 3 // 4 // Alpha Chain | Delta Chain Protocol 5 // International Monetary Graphite. 6 // 7 // Derived from Aleo (https://aleo.org) and ProvableHQ (https://provable.com). 8 // They built world-class ZK infrastructure. We installed the EASY button. 9 // Their cryptography: elegant. Our modifications: bureaucracy-compatible. 10 // Original brilliance: theirs. Robert's Rules: ours. Bugs: definitely ours. 11 // 12 // Original Aleo/ProvableHQ code subject to Apache 2.0 https://www.apache.org/licenses/LICENSE-2.0 13 // All modifications and new work: CC0 1.0 Universal Public Domain Dedication. 14 // No rights reserved. No permission required. No warranty. No refunds. 15 // 16 // https://creativecommons.org/publicdomain/zero/1.0/ 17 // SPDX-License-Identifier: CC0-1.0 18 19 //! A sparse polynomial represented in coefficient form. 20 21 use crate::fft::{EvaluationDomain, Evaluations, Polynomial}; 22 use alphavm_fields::{Field, PrimeField}; 23 use alphavm_utilities::serialize::*; 24 25 use std::{collections::BTreeMap, fmt}; 26 27 /// Stores a sparse polynomial in coefficient form. 28 #[derive(Clone, PartialEq, Eq, Hash, Default, CanonicalSerialize, CanonicalDeserialize)] 29 #[must_use] 30 pub struct SparsePolynomial<F: Field> { 31 /// The coefficient a_i of `x^i` is stored as (i, a_i) in `self.coeffs`. 32 /// the entries in `self.coeffs` are sorted in increasing order of `i`. 33 coeffs: BTreeMap<usize, F>, 34 } 35 36 impl<F: Field> fmt::Debug for SparsePolynomial<F> { 37 fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { 38 for (i, coeff) in self.coeffs.iter().filter(|(_, c)| !c.is_zero()) { 39 if *i == 0 { 40 write!(f, "\n{coeff:?}")?; 41 } else if *i == 1 { 42 write!(f, " + \n{coeff:?} * x")?; 43 } else { 44 write!(f, " + \n{coeff:?} * x^{i}")?; 45 } 46 } 47 Ok(()) 48 } 49 } 50 51 impl<F: Field> SparsePolynomial<F> { 52 /// Returns the zero polynomial. 53 pub fn zero() -> Self { 54 Self { coeffs: BTreeMap::new() } 55 } 56 57 /// Checks if the given polynomial is zero. 58 pub fn is_zero(&self) -> bool { 59 self.coeffs.is_empty() || self.coeffs.iter().all(|(_, c)| c.is_zero()) 60 } 61 62 /// Constructs a new polynomial from a list of coefficients. 63 pub fn from_coefficients_slice(coeffs: &[(usize, F)]) -> Self { 64 Self::from_coefficients(coeffs.iter().copied()) 65 } 66 67 /// Constructs a new polynomial from a list of coefficients. 68 pub fn from_coefficients(coeffs: impl IntoIterator<Item = (usize, F)>) -> Self { 69 let coeffs: BTreeMap<_, _> = coeffs.into_iter().filter(|(_, c)| !c.is_zero()).collect(); 70 Self { coeffs } 71 } 72 73 pub fn coeffs(&self) -> impl Iterator<Item = (&usize, &F)> { 74 self.coeffs.iter() 75 } 76 77 /// Returns the degree of the polynomial. 78 pub fn degree(&self) -> usize { 79 if self.is_zero() { 80 0 81 } else { 82 let last = self.coeffs.iter().max(); 83 assert!(last.is_some_and(|(_, c)| !c.is_zero())); 84 *last.unwrap().0 85 } 86 } 87 88 /// Evaluates `self` at the given `point` in the field. 89 pub fn evaluate(&self, point: F) -> F { 90 if self.is_zero() { 91 return F::zero(); 92 } 93 let mut total = F::zero(); 94 for (i, c) in &self.coeffs { 95 total += *c * point.pow([*i as u64]); 96 } 97 total 98 } 99 100 /// Perform a naive n^2 multiplication of `self` by `other`. 101 pub fn mul(&self, other: &Self) -> Self { 102 if self.is_zero() || other.is_zero() { 103 SparsePolynomial::zero() 104 } else { 105 let mut result = std::collections::BTreeMap::new(); 106 for (i, self_coeff) in self.coeffs.iter() { 107 for (j, other_coeff) in other.coeffs.iter() { 108 let cur_coeff = result.entry(i + j).or_insert_with(F::zero); 109 *cur_coeff += *self_coeff * other_coeff; 110 } 111 } 112 SparsePolynomial::from_coefficients(result) 113 } 114 } 115 } 116 117 impl<F: PrimeField> SparsePolynomial<F> { 118 /// Evaluate `self` over `domain`. 119 pub fn evaluate_over_domain_by_ref(&self, domain: EvaluationDomain<F>) -> Evaluations<F> { 120 let poly: Polynomial<'_, F> = self.into(); 121 Polynomial::<F>::evaluate_over_domain(poly, domain) 122 // unimplemented!("current implementation does not produce evals in 123 // correct order") 124 } 125 126 /// Evaluate `self` over `domain`. 127 pub fn evaluate_over_domain(self, domain: EvaluationDomain<F>) -> Evaluations<F> { 128 let poly: Polynomial<'_, F> = self.into(); 129 Polynomial::<F>::evaluate_over_domain(poly, domain) 130 // unimplemented!("current implementation does not produce evals in 131 // correct order") 132 } 133 } 134 impl<F: PrimeField> core::ops::MulAssign<F> for SparsePolynomial<F> { 135 fn mul_assign(&mut self, other: F) { 136 if other.is_zero() { 137 *self = Self::zero() 138 } else { 139 for coeff in self.coeffs.values_mut() { 140 *coeff *= other; 141 } 142 } 143 } 144 } 145 146 impl<F: PrimeField> core::ops::Mul<F> for &'_ SparsePolynomial<F> { 147 type Output = SparsePolynomial<F>; 148 149 fn mul(self, other: F) -> Self::Output { 150 let mut result = self.clone(); 151 result *= other; 152 result 153 } 154 } 155 156 impl<'a, F: PrimeField> core::ops::AddAssign<&'a Self> for SparsePolynomial<F> { 157 fn add_assign(&mut self, other: &'a Self) { 158 let mut result = other.clone(); 159 for (i, coeff) in self.coeffs.iter() { 160 let cur_coeff = result.coeffs.entry(*i).or_insert_with(F::zero); 161 *cur_coeff += coeff; 162 } 163 *self = Self::from_coefficients(result.coeffs.into_iter().filter(|(_, f)| !f.is_zero())); 164 } 165 } 166 167 impl<'a, F: PrimeField> core::ops::AddAssign<(F, &'a Self)> for SparsePolynomial<F> { 168 fn add_assign(&mut self, (f, other): (F, &'a Self)) { 169 let mut result = other.clone(); 170 for (i, coeff) in self.coeffs.iter() { 171 let cur_coeff = result.coeffs.entry(*i).or_insert_with(F::zero); 172 *cur_coeff += f * coeff; 173 } 174 *self = Self::from_coefficients(result.coeffs.into_iter().filter(|(_, f)| !f.is_zero())) 175 } 176 } 177 178 #[cfg(test)] 179 mod tests { 180 use crate::fft::{DensePolynomial, EvaluationDomain, SparsePolynomial}; 181 use alphavm_curves::bls12_377::Fr; 182 use alphavm_fields::One; 183 184 #[test] 185 fn evaluate_over_domain() { 186 for size in 2..10 { 187 let domain_size = 1 << size; 188 let domain = EvaluationDomain::new(domain_size).unwrap(); 189 let two = Fr::one() + Fr::one(); 190 let sparse_poly = SparsePolynomial::from_coefficients(vec![(0, two), (1, two)]); 191 let evals1 = sparse_poly.evaluate_over_domain_by_ref(domain); 192 193 let dense_poly: DensePolynomial<Fr> = sparse_poly.into(); 194 let evals2 = dense_poly.clone().evaluate_over_domain(domain); 195 assert_eq!(evals1.clone().interpolate(), evals2.clone().interpolate()); 196 assert_eq!(evals1.interpolate(), dense_poly); 197 assert_eq!(evals2.interpolate(), dense_poly); 198 } 199 } 200 }