From 99baa284fdb19ad088cd7a9940b3e2e58e42db10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Garillot?= <4142+huitseeker@users.noreply.github.com> Date: Wed, 22 Nov 2023 09:40:10 -0500 Subject: [PATCH 1/3] feat: Implement and test multilinear polynomial evaluation (#128) - Expanded the test suite to include test cases asserting the equivalence of MLP evaluation and partial evaluation functions - Implemented partial evaluation functionality for MLPs in the `multilinear.rs` file - Enhanced the clarity of the MLP evaluation process by testing it on partial points. Test tooling: - Introduced new functions for generating and evaluating Multilinear Polynomials (MLPs) in the `multilinear.rs` file - Incorporated the use of `ChaCha20Rng`, `SeedableRng`, `RngCore`, and `CryptoRng` libraries for random number generation pertaining to MLP evaluation --- src/spartan/polys/multilinear.rs | 117 +++++++++++++++++++++++++++++++ 1 file changed, 117 insertions(+) diff --git a/src/spartan/polys/multilinear.rs b/src/spartan/polys/multilinear.rs index e21ec35e..d2df7438 100644 --- a/src/spartan/polys/multilinear.rs +++ b/src/spartan/polys/multilinear.rs @@ -184,6 +184,8 @@ mod tests { use super::*; use pasta_curves::Fp; + use rand_chacha::ChaCha20Rng; + use rand_core::{CryptoRng, RngCore, SeedableRng}; fn make_mlp(len: usize, value: F) -> MultilinearPolynomial { MultilinearPolynomial { @@ -287,4 +289,119 @@ mod tests { test_evaluation_with::(); test_evaluation_with::(); } + + /// Returns a random ML polynomial + fn random( + num_vars: usize, + mut rng: &mut R, + ) -> MultilinearPolynomial { + MultilinearPolynomial::new( + std::iter::from_fn(|| Some(Scalar::random(&mut rng))) + .take(1 << num_vars) + .collect(), + ) + } + + /// This evaluates a multilinear polynomial at a partial point in the evaluation domain, + /// which forces us to model how we pass coordinates to the evaluation function precisely. + fn partial_eval( + poly: &MultilinearPolynomial, + point: &[F], + ) -> MultilinearPolynomial { + // Get size of partial evaluation point u = (u_0,...,u_{m-1}) + let m = point.len(); + + // Assert that the size of the polynomial being evaluated is a power of 2 greater than (1 << m) + assert!(poly.Z.len().is_power_of_two()); + assert!(poly.Z.len() >= 1 << m); + let n = poly.Z.len().trailing_zeros() as usize; + + // Partial evaluation is done in m rounds l = 0,...,m-1. + + // Temporary buffer of half the size of the polynomial + let mut n_l = 1 << (n - 1); + let mut tmp = vec![F::ZERO; n_l]; + + let prev = &poly.Z; + // Evaluate variable X_{n-1} at u_{m-1} + let u_l = point[m - 1]; + for i in 0..n_l { + tmp[i] = prev[i] + u_l * (prev[i + n_l] - prev[i]); + } + + // Evaluate m-1 variables X_{n-l-1}, ..., X_{n-2} at m-1 remaining values u_0,...,u_{m-2}) + for l in 1..m { + n_l = 1 << (n - l - 1); + let u_l = point[m - l - 1]; + for i in 0..n_l { + tmp[i] = tmp[i] + u_l * (tmp[i + n_l] - tmp[i]); + } + } + tmp.truncate(1 << (poly.num_vars - m)); + + MultilinearPolynomial::new(tmp) + } + + fn partial_evaluate_mle_with() { + // Initialize a random polynomial + let n = 5; + let mut rng = ChaCha20Rng::from_seed([0u8; 32]); + let poly = random(n, &mut rng); + + // Define a random multivariate evaluation point u = (u_0, u_1, u_2, u_3, u_4) + let u_0 = F::random(&mut rng); + let u_1 = F::random(&mut rng); + let u_2 = F::random(&mut rng); + let u_3 = F::random(&mut rng); + let u_4 = F::random(&mut rng); + let u_challenge = [u_4, u_3, u_2, u_1, u_0]; + + // Directly computing v = p(u_0,...,u_4) and comparing it with the result of + // first computing the partial evaluation in the last 3 variables + // g(X_0,X_1) = p(X_0,X_1,u_2,u_3,u_4), then v = g(u_0,u_1) + + // Compute v = p(u_0,...,u_4) + let v_expected = poly.evaluate(&u_challenge[..]); + + // Compute g(X_0,X_1) = p(X_0,X_1,u_2,u_3,u_4), then v = g(u_0,u_1) + let u_part_1 = [u_1, u_0]; // note the endianness difference + let u_part_2 = [u_2, u_3, u_4]; + + // Note how we start with part 2, and continue with part 1 + let partial_evaluated_poly = partial_eval(&poly, &u_part_2); + let v_result = partial_evaluated_poly.evaluate(&u_part_1); + + assert_eq!(v_result, v_expected); + } + + #[test] + fn test_partial_evaluate_mle() { + partial_evaluate_mle_with::(); + partial_evaluate_mle_with::(); + partial_evaluate_mle_with::(); + } + + fn partial_and_evaluate_with() { + for i in 0..50 { + // Initialize a random polynomial + let n = 7; + let mut rng = ChaCha20Rng::from_seed([i as u8; 32]); + let poly = random(n, &mut rng); + + // draw a random point + let pt: Vec<_> = std::iter::from_fn(|| Some(F::random(&mut rng))) + .take(n) + .collect(); + // this shows the order in which coordinates are evaluated + let rev_pt: Vec<_> = pt.iter().cloned().rev().collect(); + assert_eq!(poly.evaluate(&pt), partial_eval(&poly, &rev_pt).Z[0]) + } + } + + #[test] + fn test_partial_and_evaluate() { + partial_and_evaluate_with::(); + partial_and_evaluate_with::(); + partial_and_evaluate_with::(); + } } From 44ff009d1e4fed47550e49c550da8147ecfb9725 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Garillot?= Date: Tue, 12 Dec 2023 13:46:05 -0500 Subject: [PATCH 2/3] chore: refactor test imports --- src/spartan/polys/multilinear.rs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/spartan/polys/multilinear.rs b/src/spartan/polys/multilinear.rs index d2df7438..9fb11f0d 100644 --- a/src/spartan/polys/multilinear.rs +++ b/src/spartan/polys/multilinear.rs @@ -183,7 +183,6 @@ mod tests { use crate::provider::{self, bn256_grumpkin::bn256, secp_secq::secp256k1}; use super::*; - use pasta_curves::Fp; use rand_chacha::ChaCha20Rng; use rand_core::{CryptoRng, RngCore, SeedableRng}; @@ -237,12 +236,12 @@ mod tests { #[test] fn test_multilinear_polynomial() { - test_multilinear_polynomial_with::(); + test_multilinear_polynomial_with::(); } #[test] fn test_sparse_polynomial() { - test_sparse_polynomial_with::(); + test_sparse_polynomial_with::(); } fn test_mlp_add_with() { @@ -256,7 +255,7 @@ mod tests { #[test] fn test_mlp_add() { - test_mlp_add_with::(); + test_mlp_add_with::(); test_mlp_add_with::(); test_mlp_add_with::(); } @@ -285,7 +284,7 @@ mod tests { #[test] fn test_evaluation() { - test_evaluation_with::(); + test_evaluation_with::(); test_evaluation_with::(); test_evaluation_with::(); } @@ -376,7 +375,7 @@ mod tests { #[test] fn test_partial_evaluate_mle() { - partial_evaluate_mle_with::(); + partial_evaluate_mle_with::(); partial_evaluate_mle_with::(); partial_evaluate_mle_with::(); } @@ -400,7 +399,7 @@ mod tests { #[test] fn test_partial_and_evaluate() { - partial_and_evaluate_with::(); + partial_and_evaluate_with::(); partial_and_evaluate_with::(); partial_and_evaluate_with::(); } From bdb6e37f8a739844bfbec7176af8499584c79246 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Garillot?= Date: Tue, 12 Dec 2023 15:39:20 -0500 Subject: [PATCH 3/3] refactor: simplify and reformulate evaluation testing --- src/spartan/polys/multilinear.rs | 100 +++++++------------------------ 1 file changed, 21 insertions(+), 79 deletions(-) diff --git a/src/spartan/polys/multilinear.rs b/src/spartan/polys/multilinear.rs index 9fb11f0d..385d8a34 100644 --- a/src/spartan/polys/multilinear.rs +++ b/src/spartan/polys/multilinear.rs @@ -301,86 +301,29 @@ mod tests { ) } - /// This evaluates a multilinear polynomial at a partial point in the evaluation domain, - /// which forces us to model how we pass coordinates to the evaluation function precisely. - fn partial_eval( + /// This binds the variables of a multilinear polynomial to a provided sequence + /// of values. + /// + /// Assuming `bind_poly_var_top` defines the "top" variable of the polynomial, + /// this aims to test whether variables should be provided to the + /// `evaluate` function in topmost-first (big endian) of topmost-last (lower endian) + /// order. + fn bind_sequence( poly: &MultilinearPolynomial, - point: &[F], + values: &[F], ) -> MultilinearPolynomial { - // Get size of partial evaluation point u = (u_0,...,u_{m-1}) - let m = point.len(); - - // Assert that the size of the polynomial being evaluated is a power of 2 greater than (1 << m) + // Assert that the size of the polynomial being evaluated is a power of 2 greater than (1 << values.len()) assert!(poly.Z.len().is_power_of_two()); - assert!(poly.Z.len() >= 1 << m); - let n = poly.Z.len().trailing_zeros() as usize; - - // Partial evaluation is done in m rounds l = 0,...,m-1. - - // Temporary buffer of half the size of the polynomial - let mut n_l = 1 << (n - 1); - let mut tmp = vec![F::ZERO; n_l]; - - let prev = &poly.Z; - // Evaluate variable X_{n-1} at u_{m-1} - let u_l = point[m - 1]; - for i in 0..n_l { - tmp[i] = prev[i] + u_l * (prev[i + n_l] - prev[i]); - } + assert!(poly.Z.len() >= 1 << values.len()); - // Evaluate m-1 variables X_{n-l-1}, ..., X_{n-2} at m-1 remaining values u_0,...,u_{m-2}) - for l in 1..m { - n_l = 1 << (n - l - 1); - let u_l = point[m - l - 1]; - for i in 0..n_l { - tmp[i] = tmp[i] + u_l * (tmp[i + n_l] - tmp[i]); - } + let mut tmp = poly.clone(); + for v in values.iter() { + tmp.bind_poly_var_top(v); } - tmp.truncate(1 << (poly.num_vars - m)); - - MultilinearPolynomial::new(tmp) - } - - fn partial_evaluate_mle_with() { - // Initialize a random polynomial - let n = 5; - let mut rng = ChaCha20Rng::from_seed([0u8; 32]); - let poly = random(n, &mut rng); - - // Define a random multivariate evaluation point u = (u_0, u_1, u_2, u_3, u_4) - let u_0 = F::random(&mut rng); - let u_1 = F::random(&mut rng); - let u_2 = F::random(&mut rng); - let u_3 = F::random(&mut rng); - let u_4 = F::random(&mut rng); - let u_challenge = [u_4, u_3, u_2, u_1, u_0]; - - // Directly computing v = p(u_0,...,u_4) and comparing it with the result of - // first computing the partial evaluation in the last 3 variables - // g(X_0,X_1) = p(X_0,X_1,u_2,u_3,u_4), then v = g(u_0,u_1) - - // Compute v = p(u_0,...,u_4) - let v_expected = poly.evaluate(&u_challenge[..]); - - // Compute g(X_0,X_1) = p(X_0,X_1,u_2,u_3,u_4), then v = g(u_0,u_1) - let u_part_1 = [u_1, u_0]; // note the endianness difference - let u_part_2 = [u_2, u_3, u_4]; - - // Note how we start with part 2, and continue with part 1 - let partial_evaluated_poly = partial_eval(&poly, &u_part_2); - let v_result = partial_evaluated_poly.evaluate(&u_part_1); - - assert_eq!(v_result, v_expected); - } - - #[test] - fn test_partial_evaluate_mle() { - partial_evaluate_mle_with::(); - partial_evaluate_mle_with::(); - partial_evaluate_mle_with::(); + tmp } - fn partial_and_evaluate_with() { + fn bind_and_evaluate_with() { for i in 0..50 { // Initialize a random polynomial let n = 7; @@ -392,15 +335,14 @@ mod tests { .take(n) .collect(); // this shows the order in which coordinates are evaluated - let rev_pt: Vec<_> = pt.iter().cloned().rev().collect(); - assert_eq!(poly.evaluate(&pt), partial_eval(&poly, &rev_pt).Z[0]) + assert_eq!(poly.evaluate(&pt), bind_sequence(&poly, &pt).Z[0]) } } #[test] - fn test_partial_and_evaluate() { - partial_and_evaluate_with::(); - partial_and_evaluate_with::(); - partial_and_evaluate_with::(); + fn test_bind_and_evaluate() { + bind_and_evaluate_with::(); + bind_and_evaluate_with::(); + bind_and_evaluate_with::(); } }