@@ -8,8 +8,7 @@ use ff::PrimeField;
8
8
use itertools:: Itertools as _;
9
9
use rand_core:: { CryptoRng , RngCore } ;
10
10
use rayon:: prelude:: {
11
- IndexedParallelIterator , IntoParallelIterator , IntoParallelRefIterator ,
12
- IntoParallelRefMutIterator , ParallelIterator ,
11
+ IndexedParallelIterator , IntoParallelRefIterator , IntoParallelRefMutIterator , ParallelIterator ,
13
12
} ;
14
13
use serde:: { Deserialize , Serialize } ;
15
14
@@ -130,47 +129,37 @@ impl<Scalar: PrimeField> Index<usize> for MultilinearPolynomial<Scalar> {
130
129
}
131
130
132
131
/// Sparse multilinear polynomial, which means the $Z(\cdot)$ is zero at most points.
133
- /// So we do not have to store every evaluations of $Z(\cdot)$, only store the non-zero points.
134
- ///
135
- /// For example, the evaluations are [0, 0, 0, 1, 0, 1, 0, 2].
136
- /// The sparse polynomial only store the non-zero values, [(3, 1), (5, 1), (7, 2)].
137
- /// In the tuple, the first is index, the second is value.
132
+ /// In our context, sparse polynomials are non-zeros over the hypercube at locations that map to "small" integers
133
+ /// We exploit this property to implement a time-optimal algorithm
138
134
pub ( crate ) struct SparsePolynomial < Scalar > {
139
135
num_vars : usize ,
140
- Z : Vec < ( usize , Scalar ) > ,
136
+ Z : Vec < Scalar > ,
141
137
}
142
138
143
139
impl < Scalar : PrimeField > SparsePolynomial < Scalar > {
144
- pub fn new ( num_vars : usize , Z : Vec < ( usize , Scalar ) > ) -> Self {
140
+ pub fn new ( num_vars : usize , Z : Vec < Scalar > ) -> Self {
145
141
Self { num_vars, Z }
146
142
}
147
143
148
- /// Computes the $\tilde{eq}$ extension polynomial.
149
- /// return 1 when a == r, otherwise return 0.
150
- fn compute_chi ( a : & [ bool ] , r : & [ Scalar ] ) -> Scalar {
151
- assert_eq ! ( a. len( ) , r. len( ) ) ;
152
- let mut chi_i = Scalar :: ONE ;
153
- for j in 0 ..r. len ( ) {
154
- if a[ j] {
155
- chi_i *= r[ j] ;
156
- } else {
157
- chi_i *= Scalar :: ONE - r[ j] ;
158
- }
159
- }
160
- chi_i
161
- }
162
-
163
- // Takes O(m log n) where m is the number of non-zero evaluations and n is the number of variables.
144
+ // a time-optimal algorithm to evaluate sparse polynomials
164
145
pub fn evaluate ( & self , r : & [ Scalar ] ) -> Scalar {
165
146
assert_eq ! ( self . num_vars, r. len( ) ) ;
166
147
167
- ( 0 ..self . Z . len ( ) )
168
- . into_par_iter ( )
169
- . map ( |i| {
170
- let bits = ( self . Z [ i] . 0 ) . get_bits ( r. len ( ) ) ;
171
- Self :: compute_chi ( & bits, r) * self . Z [ i] . 1
172
- } )
173
- . sum ( )
148
+ let num_vars_z = self . Z . len ( ) . next_power_of_two ( ) . log_2 ( ) ;
149
+ let chis = EqPolynomial :: evals_from_points ( & r[ self . num_vars - 1 - num_vars_z..] ) ;
150
+ #[ allow( clippy:: disallowed_methods) ]
151
+ let eval_partial: Scalar = self
152
+ . Z
153
+ . iter ( )
154
+ . zip ( chis. iter ( ) )
155
+ . map ( |( z, chi) | * z * * chi)
156
+ . sum ( ) ;
157
+
158
+ let common = ( 0 ..self . num_vars - 1 - num_vars_z)
159
+ . map ( |i| ( Scalar :: ONE - r[ i] ) )
160
+ . product :: < Scalar > ( ) ;
161
+
162
+ common * eval_partial
174
163
}
175
164
}
176
165
@@ -192,7 +181,7 @@ impl<Scalar: PrimeField> Add for MultilinearPolynomial<Scalar> {
192
181
193
182
#[ cfg( test) ]
194
183
mod tests {
195
- use crate :: provider:: { self , bn256_grumpkin:: bn256, secp_secq:: secp256k1} ;
184
+ use crate :: provider:: { bn256_grumpkin:: bn256, secp_secq:: secp256k1} ;
196
185
197
186
use super :: * ;
198
187
use rand_chacha:: ChaCha20Rng ;
@@ -232,18 +221,21 @@ mod tests {
232
221
}
233
222
234
223
fn test_sparse_polynomial_with < F : PrimeField > ( ) {
235
- // Let the polynomial have 3 variables, p(x_1, x_2, x_3) = (x_1 + x_2) * x_3
236
- // Evaluations of the polynomial at boolean cube are [0, 0, 0, 1, 0, 1, 0, 2].
224
+ // Let the polynomial have 4 variables, but is non-zero at only 3 locations (out of 2^4 = 16) over the hypercube
225
+ let mut Z = vec ! [ F :: ONE , F :: ONE , F :: from( 2 ) ] ;
226
+ let m_poly = SparsePolynomial :: < F > :: new ( 4 , Z . clone ( ) ) ;
237
227
238
- let TWO = F :: from ( 2 ) ;
239
- let Z = vec ! [ ( 3 , F :: ONE ) , ( 5 , F :: ONE ) , ( 7 , TWO ) ] ;
240
- let m_poly = SparsePolynomial :: < F > :: new ( 3 , Z ) ;
228
+ Z . resize ( 16 , F :: ZERO ) ; // append with zeros to make it a dense polynomial
229
+ let m_poly_dense = MultilinearPolynomial :: new ( Z ) ;
241
230
242
- let x = vec ! [ F :: ONE , F :: ONE , F :: ONE ] ;
243
- assert_eq ! ( m_poly . evaluate ( x . as_slice ( ) ) , TWO ) ;
231
+ // evaluation point
232
+ let x = vec ! [ F :: from ( 5 ) , F :: from ( 8 ) , F :: from ( 5 ) , F :: from ( 3 ) ] ;
244
233
245
- let x = vec ! [ F :: ONE , F :: ZERO , F :: ONE ] ;
246
- assert_eq ! ( m_poly. evaluate( x. as_slice( ) ) , F :: ONE ) ;
234
+ // check evaluations
235
+ assert_eq ! (
236
+ m_poly. evaluate( x. as_slice( ) ) ,
237
+ m_poly_dense. evaluate( x. as_slice( ) )
238
+ ) ;
247
239
}
248
240
249
241
#[ test]
@@ -301,8 +293,8 @@ mod tests {
301
293
#[ test]
302
294
fn test_evaluation ( ) {
303
295
test_evaluation_with :: < pasta_curves:: Fp > ( ) ;
304
- test_evaluation_with :: < provider :: bn256_grumpkin :: bn256:: Scalar > ( ) ;
305
- test_evaluation_with :: < provider :: secp_secq :: secp256k1:: Scalar > ( ) ;
296
+ test_evaluation_with :: < bn256:: Scalar > ( ) ;
297
+ test_evaluation_with :: < secp256k1:: Scalar > ( ) ;
306
298
}
307
299
308
300
/// This binds the variables of a multilinear polynomial to a provided sequence
0 commit comments