snarkvm_algorithms/fft/polynomial/
sparse.rs1use crate::fft::{EvaluationDomain, Evaluations, Polynomial};
19use snarkvm_fields::{Field, PrimeField};
20use snarkvm_utilities::serialize::*;
21
22use std::{collections::BTreeMap, fmt};
23
24#[derive(Clone, PartialEq, Eq, Hash, Default, CanonicalSerialize, CanonicalDeserialize)]
26#[must_use]
27pub struct SparsePolynomial<F: Field> {
28 coeffs: BTreeMap<usize, F>,
31}
32
33impl<F: Field> fmt::Debug for SparsePolynomial<F> {
34 fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
35 for (i, coeff) in self.coeffs.iter().filter(|(_, c)| !c.is_zero()) {
36 if *i == 0 {
37 write!(f, "\n{coeff:?}")?;
38 } else if *i == 1 {
39 write!(f, " + \n{coeff:?} * x")?;
40 } else {
41 write!(f, " + \n{coeff:?} * x^{i}")?;
42 }
43 }
44 Ok(())
45 }
46}
47
48impl<F: Field> SparsePolynomial<F> {
49 pub fn zero() -> Self {
51 Self { coeffs: BTreeMap::new() }
52 }
53
54 pub fn is_zero(&self) -> bool {
56 self.coeffs.is_empty() || self.coeffs.iter().all(|(_, c)| c.is_zero())
57 }
58
59 pub fn from_coefficients_slice(coeffs: &[(usize, F)]) -> Self {
61 Self::from_coefficients(coeffs.iter().copied())
62 }
63
64 pub fn from_coefficients(coeffs: impl IntoIterator<Item = (usize, F)>) -> Self {
66 let coeffs: BTreeMap<_, _> = coeffs.into_iter().filter(|(_, c)| !c.is_zero()).collect();
67 Self { coeffs }
68 }
69
70 pub fn coeffs(&self) -> impl Iterator<Item = (&usize, &F)> {
71 self.coeffs.iter()
72 }
73
74 pub fn degree(&self) -> usize {
76 if self.is_zero() {
77 0
78 } else {
79 let last = self.coeffs.iter().max();
80 assert!(last.map_or(false, |(_, c)| !c.is_zero()));
81 *last.unwrap().0
82 }
83 }
84
85 pub fn evaluate(&self, point: F) -> F {
87 if self.is_zero() {
88 return F::zero();
89 }
90 let mut total = F::zero();
91 for (i, c) in &self.coeffs {
92 total += *c * point.pow([*i as u64]);
93 }
94 total
95 }
96
97 pub fn mul(&self, other: &Self) -> Self {
99 if self.is_zero() || other.is_zero() {
100 SparsePolynomial::zero()
101 } else {
102 let mut result = std::collections::BTreeMap::new();
103 for (i, self_coeff) in self.coeffs.iter() {
104 for (j, other_coeff) in other.coeffs.iter() {
105 let cur_coeff = result.entry(i + j).or_insert_with(F::zero);
106 *cur_coeff += *self_coeff * other_coeff;
107 }
108 }
109 SparsePolynomial::from_coefficients(result)
110 }
111 }
112}
113
114impl<F: PrimeField> SparsePolynomial<F> {
115 pub fn evaluate_over_domain_by_ref(&self, domain: EvaluationDomain<F>) -> Evaluations<F> {
117 let poly: Polynomial<'_, F> = self.into();
118 Polynomial::<F>::evaluate_over_domain(poly, domain)
119 }
122
123 pub fn evaluate_over_domain(self, domain: EvaluationDomain<F>) -> Evaluations<F> {
125 let poly: Polynomial<'_, F> = self.into();
126 Polynomial::<F>::evaluate_over_domain(poly, domain)
127 }
130}
131impl<F: PrimeField> core::ops::MulAssign<F> for SparsePolynomial<F> {
132 fn mul_assign(&mut self, other: F) {
133 if other.is_zero() {
134 *self = Self::zero()
135 } else {
136 for coeff in self.coeffs.values_mut() {
137 *coeff *= other;
138 }
139 }
140 }
141}
142
143impl<F: PrimeField> core::ops::Mul<F> for &'_ SparsePolynomial<F> {
144 type Output = SparsePolynomial<F>;
145
146 fn mul(self, other: F) -> Self::Output {
147 let mut result = self.clone();
148 result *= other;
149 result
150 }
151}
152
153impl<'a, F: PrimeField> core::ops::AddAssign<&'a Self> for SparsePolynomial<F> {
154 fn add_assign(&mut self, other: &'a Self) {
155 let mut result = other.clone();
156 for (i, coeff) in self.coeffs.iter() {
157 let cur_coeff = result.coeffs.entry(*i).or_insert_with(F::zero);
158 *cur_coeff += coeff;
159 }
160 *self = Self::from_coefficients(result.coeffs.into_iter().filter(|(_, f)| !f.is_zero()));
161 }
162}
163
164impl<'a, F: PrimeField> core::ops::AddAssign<(F, &'a Self)> for SparsePolynomial<F> {
165 fn add_assign(&mut self, (f, other): (F, &'a Self)) {
166 let mut result = other.clone();
167 for (i, coeff) in self.coeffs.iter() {
168 let cur_coeff = result.coeffs.entry(*i).or_insert_with(F::zero);
169 *cur_coeff += f * coeff;
170 }
171 *self = Self::from_coefficients(result.coeffs.into_iter().filter(|(_, f)| !f.is_zero()))
172 }
173}
174
175#[cfg(test)]
176mod tests {
177 use crate::fft::{DensePolynomial, EvaluationDomain, SparsePolynomial};
178 use snarkvm_curves::bls12_377::Fr;
179 use snarkvm_fields::One;
180
181 #[test]
182 fn evaluate_over_domain() {
183 for size in 2..10 {
184 let domain_size = 1 << size;
185 let domain = EvaluationDomain::new(domain_size).unwrap();
186 let two = Fr::one() + Fr::one();
187 let sparse_poly = SparsePolynomial::from_coefficients(vec![(0, two), (1, two)]);
188 let evals1 = sparse_poly.evaluate_over_domain_by_ref(domain);
189
190 let dense_poly: DensePolynomial<Fr> = sparse_poly.into();
191 let evals2 = dense_poly.clone().evaluate_over_domain(domain);
192 assert_eq!(evals1.clone().interpolate(), evals2.clone().interpolate());
193 assert_eq!(evals1.interpolate(), dense_poly);
194 assert_eq!(evals2.interpolate(), dense_poly);
195 }
196 }
197}