hyperplonk/poly-iop/src/virtual_poly.rs
poly-iop/src/virtual_poly.rsRaw
1//! This module defines our main mathematical object `VirtualPolynomial`; and
2//! various functions associated with it.
3
4use crate::errors::PolyIOPErrors;
5use ark_ff::PrimeField;
6use ark_poly::{DenseMultilinearExtension, MultilinearExtension};
7use ark_std::{
8 end_timer,
9 rand::{Rng, RngCore},
10 start_timer,
11};
12use std::{cmp::max, collections::HashMap, marker::PhantomData, ops::Add, rc::Rc};
13
14#[rustfmt::skip]
15/// A virtual polynomial is a sum of products of multilinear polynomials;
16/// where the multilinear polynomials are stored via their multilinear
17/// extensions: `(coefficient, DenseMultilinearExtension)`
18///
19/// * Number of products n = `polynomial.products.len()`,
20/// * Number of multiplicands of ith product m_i =
21/// `polynomial.products[i].1.len()`,
22/// * Coefficient of ith product c_i = `polynomial.products[i].0`
23///
24/// The resulting polynomial is
25///
26/// $$\sum_{i=0}^{n} c_i \cdot \prod_{j=0}^{m_i} P_{ij} $$
27///
28/// Example:
29/// f = c0 * f0 * f1 * f2 + c1 * f3 * f4
30/// where f0 ... f4 are multilinear polynomials
31///
32/// - flattened_ml_extensions stores the multilinear extension representation of
33/// f0, f1, f2, f3 and f4
34/// - products is
35/// \[
36/// (c0, \[0, 1, 2\]),
37/// (c1, \[3, 4\])
38/// \]
39/// - raw_pointers_lookup_table maps fi to i
40///
41#[derive(Clone, Debug, Default, PartialEq)]
42pub struct VirtualPolynomial<F: PrimeField> {
43 /// Aux information about the multilinear polynomial
44 pub aux_info: VPAuxInfo<F>,
45 /// list of reference to products (as usize) of multilinear extension
46 pub products: Vec<(F, Vec<usize>)>,
47 /// Stores multilinear extensions in which product multiplicand can refer
48 /// to.
49 pub flattened_ml_extensions: Vec<Rc<DenseMultilinearExtension<F>>>,
50 /// Pointers to the above poly extensions
51 raw_pointers_lookup_table: HashMap<*const DenseMultilinearExtension<F>, usize>,
52}
53
54#[derive(Clone, Debug, Default, PartialEq)]
55/// Auxiliary information about the multilinear polynomial
56pub struct VPAuxInfo<F: PrimeField> {
57 /// max number of multiplicands in each product
58 pub max_degree: usize,
59 /// number of variables of the polynomial
60 pub num_variables: usize,
61 /// Associated field
62 #[doc(hidden)]
63 pub(crate) phantom: PhantomData<F>,
64}
65
66impl<F: PrimeField> Add for &VirtualPolynomial<F> {
67 type Output = VirtualPolynomial<F>;
68 fn add(self, other: &VirtualPolynomial<F>) -> Self::Output {
69 let start = start_timer!(|| "virtual poly add");
70 let mut res = self.clone();
71 for products in other.products.iter() {
72 let cur: Vec<Rc<DenseMultilinearExtension<F>>> = products
73 .1
74 .iter()
75 .map(|&x| other.flattened_ml_extensions[x].clone())
76 .collect();
77
78 res.add_mle_list(cur, products.0)
79 .expect("add product failed");
80 }
81 end_timer!(start);
82 res
83 }
84}
85
86impl<F: PrimeField> VirtualPolynomial<F> {
87 /// Creates an empty virtual polynomial with `num_variables`.
88 pub fn new(num_variables: usize) -> Self {
89 VirtualPolynomial {
90 aux_info: VPAuxInfo {
91 max_degree: 0,
92 num_variables,
93 phantom: PhantomData::default(),
94 },
95 products: Vec::new(),
96 flattened_ml_extensions: Vec::new(),
97 raw_pointers_lookup_table: HashMap::new(),
98 }
99 }
100
101 /// Creates an new virtual polynomial from a MLE and its coefficient.
102 pub fn new_from_mle(mle: Rc<DenseMultilinearExtension<F>>, coefficient: F) -> Self {
103 let mle_ptr: *const DenseMultilinearExtension<F> = Rc::as_ptr(&mle);
104 let mut hm = HashMap::new();
105 hm.insert(mle_ptr, 0);
106
107 VirtualPolynomial {
108 aux_info: VPAuxInfo {
109 // The max degree is the max degree of any individual variable
110 max_degree: 1,
111 num_variables: mle.num_vars,
112 phantom: PhantomData::default(),
113 },
114 // here `0` points to the first polynomial of `flattened_ml_extensions`
115 products: vec![(coefficient, vec![0])],
116 flattened_ml_extensions: vec![mle],
117 raw_pointers_lookup_table: hm,
118 }
119 }
120
121 /// Add a product of list of multilinear extensions to self
122 /// Returns an error if the list is empty, or the MLE has a different
123 /// `num_vars` from self.
124 ///
125 /// The MLEs will be multiplied together, and then multiplied by the scalar
126 /// `coefficient`.
127 pub fn add_mle_list(
128 &mut self,
129 mle_list: impl IntoIterator<Item = Rc<DenseMultilinearExtension<F>>>,
130 coefficient: F,
131 ) -> Result<(), PolyIOPErrors> {
132 let mle_list: Vec<Rc<DenseMultilinearExtension<F>>> = mle_list.into_iter().collect();
133 let mut indexed_product = Vec::with_capacity(mle_list.len());
134
135 if mle_list.is_empty() {
136 return Err(PolyIOPErrors::InvalidParameters(
137 "input mle_list is empty".to_string(),
138 ));
139 }
140
141 self.aux_info.max_degree = max(self.aux_info.max_degree, mle_list.len());
142
143 for mle in mle_list {
144 if mle.num_vars != self.aux_info.num_variables {
145 return Err(PolyIOPErrors::InvalidParameters(format!(
146 "product has a multiplicand with wrong number of variables {} vs {}",
147 mle.num_vars, self.aux_info.num_variables
148 )));
149 }
150
151 let mle_ptr: *const DenseMultilinearExtension<F> = Rc::as_ptr(&mle);
152 if let Some(index) = self.raw_pointers_lookup_table.get(&mle_ptr) {
153 indexed_product.push(*index)
154 } else {
155 let curr_index = self.flattened_ml_extensions.len();
156 self.flattened_ml_extensions.push(mle.clone());
157 self.raw_pointers_lookup_table.insert(mle_ptr, curr_index);
158 indexed_product.push(curr_index);
159 }
160 }
161 self.products.push((coefficient, indexed_product));
162 Ok(())
163 }
164
165 /// Multiple the current VirtualPolynomial by an MLE:
166 /// - add the MLE to the MLE list;
167 /// - multiple each product by MLE and its coefficient.
168 /// Returns an error if the MLE has a different `num_vars` from self.
169 pub fn mul_by_mle(
170 &mut self,
171 mle: Rc<DenseMultilinearExtension<F>>,
172 coefficient: F,
173 ) -> Result<(), PolyIOPErrors> {
174 let start = start_timer!(|| "mul by mle");
175
176 if mle.num_vars != self.aux_info.num_variables {
177 return Err(PolyIOPErrors::InvalidParameters(format!(
178 "product has a multiplicand with wrong number of variables {} vs {}",
179 mle.num_vars, self.aux_info.num_variables
180 )));
181 }
182
183 let mle_ptr: *const DenseMultilinearExtension<F> = Rc::as_ptr(&mle);
184
185 // check if this mle already exists in the virtual polynomial
186 let mle_index = match self.raw_pointers_lookup_table.get(&mle_ptr) {
187 Some(&p) => p,
188 None => {
189 self.raw_pointers_lookup_table
190 .insert(mle_ptr, self.flattened_ml_extensions.len());
191 self.flattened_ml_extensions.push(mle);
192 self.flattened_ml_extensions.len() - 1
193 },
194 };
195
196 for (prod_coef, indices) in self.products.iter_mut() {
197 // - add the MLE to the MLE list;
198 // - multiple each product by MLE and its coefficient.
199 indices.push(mle_index);
200 *prod_coef *= coefficient;
201 }
202
203 // increase the max degree by one as the MLE has degree 1.
204 self.aux_info.max_degree += 1;
205 end_timer!(start);
206 Ok(())
207 }
208
209 /// Evaluate the virtual polynomial at point `point`.
210 /// Returns an error is point.len() does not match `num_variables`.
211 pub fn evaluate(&self, point: &[F]) -> Result<F, PolyIOPErrors> {
212 let start = start_timer!(|| "evaluation");
213
214 if self.aux_info.num_variables != point.len() {
215 return Err(PolyIOPErrors::InvalidParameters(format!(
216 "wrong number of variables {} vs {}",
217 self.aux_info.num_variables,
218 point.len()
219 )));
220 }
221
222 let evals: Vec<F> = self
223 .flattened_ml_extensions
224 .iter()
225 .map(|x| {
226 x.evaluate(point).unwrap() // safe unwrap here since we have
227 // already checked that num_var
228 // matches
229 })
230 .collect();
231
232 let res = self
233 .products
234 .iter()
235 .map(|(c, p)| *c * p.iter().map(|&i| evals[i]).product::<F>())
236 .sum();
237
238 end_timer!(start);
239 Ok(res)
240 }
241
242 /// Sample a random virtual polynomial, return the polynomial and its sum.
243 pub fn rand<R: RngCore>(
244 nv: usize,
245 num_multiplicands_range: (usize, usize),
246 num_products: usize,
247 rng: &mut R,
248 ) -> Result<(Self, F), PolyIOPErrors> {
249 let start = start_timer!(|| "sample random virtual polynomial");
250
251 let mut sum = F::zero();
252 let mut poly = VirtualPolynomial::new(nv);
253 for _ in 0..num_products {
254 let num_multiplicands =
255 rng.gen_range(num_multiplicands_range.0..num_multiplicands_range.1);
256 let (product, product_sum) = random_mle_list(nv, num_multiplicands, rng);
257 let coefficient = F::rand(rng);
258 poly.add_mle_list(product.into_iter(), coefficient)?;
259 sum += product_sum * coefficient;
260 }
261
262 end_timer!(start);
263 Ok((poly, sum))
264 }
265
266 /// Sample a random virtual polynomial that evaluates to zero everywhere
267 /// over the boolean hypercube.
268 pub fn rand_zero<R: RngCore>(
269 nv: usize,
270 num_multiplicands_range: (usize, usize),
271 num_products: usize,
272 rng: &mut R,
273 ) -> Result<Self, PolyIOPErrors> {
274 let mut poly = VirtualPolynomial::new(nv);
275 for _ in 0..num_products {
276 let num_multiplicands =
277 rng.gen_range(num_multiplicands_range.0..num_multiplicands_range.1);
278 let product = random_zero_mle_list(nv, num_multiplicands, rng);
279 let coefficient = F::rand(rng);
280 poly.add_mle_list(product.into_iter(), coefficient)?;
281 }
282
283 Ok(poly)
284 }
285
286 // Input poly f(x) and a random vector r, output
287 // \hat f(x) = \sum_{x_i \in eval_x} f(x_i) eq(x, r)
288 // where
289 // eq(x,y) = \prod_i=1^num_var (x_i * y_i + (1-x_i)*(1-y_i))
290 //
291 // This function is used in ZeroCheck.
292 pub(crate) fn build_f_hat(&self, r: &[F]) -> Result<Self, PolyIOPErrors> {
293 let start = start_timer!(|| "zero check build hat f");
294
295 if self.aux_info.num_variables != r.len() {
296 return Err(PolyIOPErrors::InvalidParameters(format!(
297 "r.len() is different from number of variables: {} vs {}",
298 r.len(),
299 self.aux_info.num_variables
300 )));
301 }
302
303 let eq_x_r = build_eq_x_r(r)?;
304 let mut res = self.clone();
305 res.mul_by_mle(eq_x_r, F::one())?;
306
307 end_timer!(start);
308 Ok(res)
309 }
310}
311
312/// Sample a random list of multilinear polynomials.
313/// Returns
314/// - the list of polynomials,
315/// - its sum of polynomial evaluations over the boolean hypercube.
316fn random_mle_list<F: PrimeField, R: RngCore>(
317 nv: usize,
318 degree: usize,
319 rng: &mut R,
320) -> (Vec<Rc<DenseMultilinearExtension<F>>>, F) {
321 let start = start_timer!(|| "sample random mle list");
322 let mut multiplicands = Vec::with_capacity(degree);
323 for _ in 0..degree {
324 multiplicands.push(Vec::with_capacity(1 << nv))
325 }
326 let mut sum = F::zero();
327
328 for _ in 0..(1 << nv) {
329 let mut product = F::one();
330
331 for e in multiplicands.iter_mut() {
332 let val = F::rand(rng);
333 e.push(val);
334 product *= val;
335 }
336 sum += product;
337 }
338
339 let list = multiplicands
340 .into_iter()
341 .map(|x| Rc::new(DenseMultilinearExtension::from_evaluations_vec(nv, x)))
342 .collect();
343
344 end_timer!(start);
345 (list, sum)
346}
347
348// Build a randomize list of mle-s whose sum is zero.
349pub fn random_zero_mle_list<F: PrimeField, R: RngCore>(
350 nv: usize,
351 degree: usize,
352 rng: &mut R,
353) -> Vec<Rc<DenseMultilinearExtension<F>>> {
354 let start = start_timer!(|| "sample random zero mle list");
355
356 let mut multiplicands = Vec::with_capacity(degree);
357 for _ in 0..degree {
358 multiplicands.push(Vec::with_capacity(1 << nv))
359 }
360 for _ in 0..(1 << nv) {
361 multiplicands[0].push(F::zero());
362 for e in multiplicands.iter_mut().skip(1) {
363 e.push(F::rand(rng));
364 }
365 }
366
367 let list = multiplicands
368 .into_iter()
369 .map(|x| Rc::new(DenseMultilinearExtension::from_evaluations_vec(nv, x)))
370 .collect();
371
372 end_timer!(start);
373 list
374}
375
376// This function build the eq(x, r) polynomial for any given r.
377//
378// Evaluate
379// eq(x,y) = \prod_i=1^num_var (x_i * y_i + (1-x_i)*(1-y_i))
380// over r, which is
381// eq(x,y) = \prod_i=1^num_var (x_i * r_i + (1-x_i)*(1-r_i))
382fn build_eq_x_r<F: PrimeField>(r: &[F]) -> Result<Rc<DenseMultilinearExtension<F>>, PolyIOPErrors> {
383 let start = start_timer!(|| "zero check build eq_x_r");
384
385 // we build eq(x,r) from its evaluations
386 // we want to evaluate eq(x,r) over x \in {0, 1}^num_vars
387 // for example, with num_vars = 4, x is a binary vector of 4, then
388 // 0 0 0 0 -> (1-r0) * (1-r1) * (1-r2) * (1-r3)
389 // 1 0 0 0 -> r0 * (1-r1) * (1-r2) * (1-r3)
390 // 0 1 0 0 -> (1-r0) * r1 * (1-r2) * (1-r3)
391 // 1 1 0 0 -> r0 * r1 * (1-r2) * (1-r3)
392 // ....
393 // 1 1 1 1 -> r0 * r1 * r2 * r3
394 // we will need 2^num_var evaluations
395
396 let mut eval = Vec::new();
397 build_eq_x_r_helper(r, &mut eval)?;
398
399 let mle = DenseMultilinearExtension::from_evaluations_vec(r.len(), eval);
400
401 let res = Rc::new(mle);
402 end_timer!(start);
403 Ok(res)
404}
405
406/// A helper function to build eq(x, r) recursively.
407/// This function takes `r.len()` steps, and for each step it requires a maximum
408/// `r.len()-1` multiplications.
409fn build_eq_x_r_helper<F: PrimeField>(r: &[F], buf: &mut Vec<F>) -> Result<(), PolyIOPErrors> {
410 if r.is_empty() {
411 return Err(PolyIOPErrors::InvalidParameters(
412 "r length is 0".to_string(),
413 ));
414 } else if r.len() == 1 {
415 // initializing the buffer with [1-r_0, r_0]
416 buf.push(F::one() - r[0]);
417 buf.push(r[0]);
418 } else {
419 build_eq_x_r_helper(&r[1..], buf)?;
420
421 // suppose at the previous step we received [b_1, ..., b_k]
422 // for the current step we will need
423 // if x_0 = 0: (1-r0) * [b_1, ..., b_k]
424 // if x_0 = 1: r0 * [b_1, ..., b_k]
425
426 let mut res = vec![];
427 for &b_i in buf.iter() {
428 let tmp = r[0] * b_i;
429 res.push(b_i - tmp);
430 res.push(tmp);
431 }
432 *buf = res;
433 }
434
435 Ok(())
436}
437
438#[cfg(test)]
439mod test {
440 use super::*;
441 use crate::utils::bit_decompose;
442 use ark_bls12_381::Fr;
443 use ark_ff::UniformRand;
444 use ark_std::test_rng;
445
446 #[test]
447 fn test_virtual_polynomial_additions() -> Result<(), PolyIOPErrors> {
448 let mut rng = test_rng();
449 for nv in 2..5 {
450 for num_products in 2..5 {
451 let base: Vec<Fr> = (0..nv).map(|_| Fr::rand(&mut rng)).collect();
452
453 let (a, _a_sum) =
454 VirtualPolynomial::<Fr>::rand(nv, (2, 3), num_products, &mut rng)?;
455 let (b, _b_sum) =
456 VirtualPolynomial::<Fr>::rand(nv, (2, 3), num_products, &mut rng)?;
457 let c = &a + &b;
458
459 assert_eq!(
460 a.evaluate(base.as_ref())? + b.evaluate(base.as_ref())?,
461 c.evaluate(base.as_ref())?
462 );
463 }
464 }
465
466 Ok(())
467 }
468
469 #[test]
470 fn test_virtual_polynomial_mul_by_mle() -> Result<(), PolyIOPErrors> {
471 let mut rng = test_rng();
472 for nv in 2..5 {
473 for num_products in 2..5 {
474 let base: Vec<Fr> = (0..nv).map(|_| Fr::rand(&mut rng)).collect();
475
476 let (a, _a_sum) =
477 VirtualPolynomial::<Fr>::rand(nv, (2, 3), num_products, &mut rng)?;
478 let (b, _b_sum) = random_mle_list(nv, 1, &mut rng);
479 let b_mle = b[0].clone();
480 let coeff = Fr::rand(&mut rng);
481 let b_vp = VirtualPolynomial::new_from_mle(b_mle.clone(), coeff);
482
483 let mut c = a.clone();
484
485 c.mul_by_mle(b_mle, coeff)?;
486
487 assert_eq!(
488 a.evaluate(base.as_ref())? * b_vp.evaluate(base.as_ref())?,
489 c.evaluate(base.as_ref())?
490 );
491 }
492 }
493
494 Ok(())
495 }
496
497 #[test]
498 fn test_eq_xr() {
499 let mut rng = test_rng();
500 for nv in 4..10 {
501 let r: Vec<Fr> = (0..nv).map(|_| Fr::rand(&mut rng)).collect();
502 let eq_x_r = build_eq_x_r(r.as_ref()).unwrap();
503 let eq_x_r2 = build_eq_x_r_for_test(r.as_ref());
504 assert_eq!(eq_x_r, eq_x_r2);
505 }
506 }
507
508 /// Naive method to build eq(x, r).
509 /// Only used for testing purpose.
510 // Evaluate
511 // eq(x,y) = \prod_i=1^num_var (x_i * y_i + (1-x_i)*(1-y_i))
512 // over r, which is
513 // eq(x,y) = \prod_i=1^num_var (x_i * r_i + (1-x_i)*(1-r_i))
514 fn build_eq_x_r_for_test<F: PrimeField>(r: &[F]) -> Rc<DenseMultilinearExtension<F>> {
515 let start = start_timer!(|| "zero check naive build eq_x_r");
516
517 // we build eq(x,r) from its evaluations
518 // we want to evaluate eq(x,r) over x \in {0, 1}^num_vars
519 // for example, with num_vars = 4, x is a binary vector of 4, then
520 // 0 0 0 0 -> (1-r0) * (1-r1) * (1-r2) * (1-r3)
521 // 1 0 0 0 -> r0 * (1-r1) * (1-r2) * (1-r3)
522 // 0 1 0 0 -> (1-r0) * r1 * (1-r2) * (1-r3)
523 // 1 1 0 0 -> r0 * r1 * (1-r2) * (1-r3)
524 // ....
525 // 1 1 1 1 -> r0 * r1 * r2 * r3
526 // we will need 2^num_var evaluations
527
528 // First, we build array for {1 - r_i}
529 let one_minus_r: Vec<F> = r.iter().map(|ri| F::one() - ri).collect();
530
531 let num_var = r.len();
532 let mut eval = vec![];
533
534 for i in 0..1 << num_var {
535 let mut current_eval = F::one();
536 let bit_sequence = bit_decompose(i, num_var);
537
538 for (&bit, (ri, one_minus_ri)) in
539 bit_sequence.iter().zip(r.iter().zip(one_minus_r.iter()))
540 {
541 current_eval *= if bit { *ri } else { *one_minus_ri };
542 }
543 eval.push(current_eval);
544 }
545
546 let mle = DenseMultilinearExtension::from_evaluations_vec(num_var, eval);
547
548 let res = Rc::new(mle);
549 end_timer!(start);
550 res
551 }
552}