use crate::{errors::PolyIOPErrors, structs::DomainInfo}; use ark_ff::PrimeField; use ark_poly::{DenseMultilinearExtension, MultilinearExtension}; use ark_std::{ end_timer, rand::{Rng, RngCore}, start_timer, }; use std::{cmp::max, collections::HashMap, marker::PhantomData, ops::Add, rc::Rc}; #[rustfmt::skip] /// A virtual polynomial is a sum of products of multilinear polynomials; /// where the multilinear polynomials are stored via their multilinear /// extensions: `(coefficient, DenseMultilinearExtension)` /// /// * Number of products n = `polynomial.products.len()`, /// * Number of multiplicands of ith product m_i = /// `polynomial.products[i].1.len()`, /// * Coefficient of ith product c_i = `polynomial.products[i].0` /// /// The resulting polynomial is /// /// $$\sum_{i=0}^{n} c_i \cdot \prod_{j=0}^{m_i} P_{ij} $$ /// /// Example: /// f = c0 * f0 * f1 * f2 + c1 * f3 * f4 /// where f0 ... f4 are multilinear polynomials /// /// - flattened_ml_extensions stores the multilinear extension representation of /// f0, f1, f2, f3 and f4 /// - products is /// \[ /// (c0, \[0, 1, 2\]), /// (c1, \[3, 4\]) /// \] /// - raw_pointers_lookup_table maps fi to i /// #[derive(Clone, Debug, Default, PartialEq)] pub struct VirtualPolynomial { /// Aux information about the multilinear polynomial pub domain_info: DomainInfo, /// list of reference to products (as usize) of multilinear extension pub products: Vec<(F, Vec)>, /// Stores multilinear extensions in which product multiplicand can refer /// to. pub flattened_ml_extensions: Vec>>, /// Pointers to the above poly extensions raw_pointers_lookup_table: HashMap<*const DenseMultilinearExtension, usize>, } impl Add for &VirtualPolynomial { type Output = VirtualPolynomial; fn add(self, other: &VirtualPolynomial) -> Self::Output { let start = start_timer!(|| "virtual poly add"); let mut res = self.clone(); for products in other.products.iter() { let cur: Vec>> = products .1 .iter() .map(|&x| other.flattened_ml_extensions[x].clone()) .collect(); res.add_mle_list(cur, products.0) .expect("add product failed"); } end_timer!(start); res } } impl VirtualPolynomial { /// Returns an empty polynomial pub fn new(num_variables: usize) -> Self { VirtualPolynomial { domain_info: DomainInfo { max_degree: 0, num_variables, phantom: PhantomData::default(), }, products: Vec::new(), flattened_ml_extensions: Vec::new(), raw_pointers_lookup_table: HashMap::new(), } } /// Returns an new virtual polynomial from a MLE pub fn new_from_mle(mle: Rc>, coefficient: F) -> Self { let mle_ptr: *const DenseMultilinearExtension = Rc::as_ptr(&mle); let mut hm = HashMap::new(); hm.insert(mle_ptr, 0); VirtualPolynomial { domain_info: DomainInfo { // The max degree is the max degree of any individual variable max_degree: 1, num_variables: mle.num_vars, phantom: PhantomData::default(), }, products: vec![(coefficient, vec![0])], flattened_ml_extensions: vec![mle], raw_pointers_lookup_table: hm, } } /// Add a list of multilinear extensions that is meant to be multiplied /// together. The resulting polynomial will be multiplied by the scalar /// `coefficient`. pub fn add_mle_list( &mut self, mle_list: impl IntoIterator>>, coefficient: F, ) -> Result<(), PolyIOPErrors> { let mle_list: Vec>> = mle_list.into_iter().collect(); let mut indexed_product = Vec::with_capacity(mle_list.len()); assert!(!mle_list.is_empty()); self.domain_info.max_degree = max(self.domain_info.max_degree, mle_list.len()); for mle in mle_list { if mle.num_vars != self.domain_info.num_variables { return Err(PolyIOPErrors::InvalidParameters(format!( "product has a multiplicand with wrong number of variables {} vs {}", mle.num_vars, self.domain_info.num_variables ))); } let mle_ptr: *const DenseMultilinearExtension = Rc::as_ptr(&mle); if let Some(index) = self.raw_pointers_lookup_table.get(&mle_ptr) { indexed_product.push(*index) } else { let curr_index = self.flattened_ml_extensions.len(); self.flattened_ml_extensions.push(mle.clone()); self.raw_pointers_lookup_table.insert(mle_ptr, curr_index); indexed_product.push(curr_index); } } self.products.push((coefficient, indexed_product)); Ok(()) } /// Multiple the current VirtualPolynomial by an MLE: /// - add the MLE to the MLE list /// - multiple each product by MLE and its coefficient pub fn mul_by_mle( &mut self, mle: Rc>, coefficient: F, ) -> Result<(), PolyIOPErrors> { let start = start_timer!(|| "mul by mle"); let mle_ptr: *const DenseMultilinearExtension = Rc::as_ptr(&mle); let mle_index = match self.raw_pointers_lookup_table.get(&mle_ptr) { Some(&p) => p, None => { self.raw_pointers_lookup_table .insert(mle_ptr, self.flattened_ml_extensions.len()); self.flattened_ml_extensions.push(mle); self.flattened_ml_extensions.len() - 1 }, }; for (prod_coef, indices) in self.products.iter_mut() { indices.push(mle_index); *prod_coef *= coefficient; } self.domain_info.max_degree += 1; end_timer!(start); Ok(()) } /// Evaluate the polynomial at point `point` pub fn evaluate(&self, point: &[F]) -> Result { let start = start_timer!(|| "evaluation"); if self.domain_info.num_variables != point.len() { return Err(PolyIOPErrors::InvalidParameters(format!( "wrong number of variables {} vs {}", self.domain_info.num_variables, point.len() ))); } let evals: Vec = self .flattened_ml_extensions .iter() .map(|x| { x.evaluate(point).unwrap() // safe unwrap here since we have // already checked that num_var // matches }) .collect(); let res = self .products .iter() .map(|(c, p)| *c * p.iter().map(|&i| evals[i]).product::()) .sum(); end_timer!(start); Ok(res) } /// Sample a random virtual polynomial, return the polynomial and its sum. pub fn rand( nv: usize, num_multiplicands_range: (usize, usize), num_products: usize, rng: &mut R, ) -> Result<(Self, F), PolyIOPErrors> { let start = start_timer!(|| "sample random virtual polynomial"); let mut sum = F::zero(); let mut poly = VirtualPolynomial::new(nv); for _ in 0..num_products { let num_multiplicands = rng.gen_range(num_multiplicands_range.0..num_multiplicands_range.1); let (product, product_sum) = random_mle_list(nv, num_multiplicands, rng); let coefficient = F::rand(rng); poly.add_mle_list(product.into_iter(), coefficient)?; sum += product_sum * coefficient; } end_timer!(start); Ok((poly, sum)) } /// Sample a random virtual polynomial that evaluates to zero everywhere on /// the boolean hypercube. pub fn rand_zero( nv: usize, num_multiplicands_range: (usize, usize), num_products: usize, rng: &mut R, ) -> Result { let mut poly = VirtualPolynomial::new(nv); for _ in 0..num_products { let num_multiplicands = rng.gen_range(num_multiplicands_range.0..num_multiplicands_range.1); let product = random_zero_mle_list(nv, num_multiplicands, rng); let coefficient = F::rand(rng); poly.add_mle_list(product.into_iter(), coefficient).unwrap(); } Ok(poly) } } /// Sample a random list of multilinear polynomials. /// Returns /// - the list of polynomials, /// - its sum of polynomial evaluations over the boolean hypercube. fn random_mle_list( nv: usize, degree: usize, rng: &mut R, ) -> (Vec>>, F) { let start = start_timer!(|| "sample random mle list"); let mut multiplicands = Vec::with_capacity(degree); for _ in 0..degree { multiplicands.push(Vec::with_capacity(1 << nv)) } let mut sum = F::zero(); for _ in 0..(1 << nv) { let mut product = F::one(); for e in multiplicands.iter_mut() { let val = F::rand(rng); e.push(val); product *= val; } sum += product; } let list = multiplicands .into_iter() .map(|x| Rc::new(DenseMultilinearExtension::from_evaluations_vec(nv, x))) .collect(); end_timer!(start); (list, sum) } // Build a randomize list of mle-s whose sum is zero. pub fn random_zero_mle_list( nv: usize, degree: usize, rng: &mut R, ) -> Vec>> { let start = start_timer!(|| "sample random zero mle list"); let mut multiplicands = Vec::with_capacity(degree); for _ in 0..degree { multiplicands.push(Vec::with_capacity(1 << nv)) } for _ in 0..(1 << nv) { multiplicands[0].push(F::zero()); for e in multiplicands.iter_mut().skip(1) { e.push(F::rand(rng)); } } let list = multiplicands .into_iter() .map(|x| Rc::new(DenseMultilinearExtension::from_evaluations_vec(nv, x))) .collect(); end_timer!(start); list } #[cfg(test)] pub(crate) mod test { use super::*; use ark_bls12_381::Fr; use ark_ff::UniformRand; use ark_std::test_rng; #[test] fn test_virtual_polynomial_additions() -> Result<(), PolyIOPErrors> { let mut rng = test_rng(); for nv in 2..5 { for num_products in 2..5 { let base: Vec = (0..nv).map(|_| Fr::rand(&mut rng)).collect(); let (a, _a_sum) = VirtualPolynomial::::rand(nv, (2, 3), num_products, &mut rng)?; let (b, _b_sum) = VirtualPolynomial::::rand(nv, (2, 3), num_products, &mut rng)?; let c = &a + &b; assert_eq!( a.evaluate(base.as_ref())? + b.evaluate(base.as_ref())?, c.evaluate(base.as_ref())? ); } } Ok(()) } #[test] fn test_virtual_polynomial_mul_by_mle() -> Result<(), PolyIOPErrors> { let mut rng = test_rng(); for nv in 2..5 { for num_products in 2..5 { let base: Vec = (0..nv).map(|_| Fr::rand(&mut rng)).collect(); let (a, _a_sum) = VirtualPolynomial::::rand(nv, (2, 3), num_products, &mut rng)?; let (b, _b_sum) = random_mle_list(nv, 1, &mut rng); let b_mle = b[0].clone(); let coeff = Fr::rand(&mut rng); let b_vp = VirtualPolynomial::new_from_mle(b_mle.clone(), coeff); let mut c = a.clone(); c.mul_by_mle(b_mle, coeff)?; assert_eq!( a.evaluate(base.as_ref())? * b_vp.evaluate(base.as_ref())?, c.evaluate(base.as_ref())? ); } } Ok(()) } }