use std::iter; use itertools::Itertools; use crate::field::extension_field::target::ExtensionTarget; use crate::field::extension_field::Extendable; use crate::field::field_types::{Field, RichField}; use crate::plonk::circuit_builder::CircuitBuilder; use crate::util::ceil_div_usize; pub(crate) fn quotient_chunk_products( quotient_values: &[F], max_degree: usize, ) -> Vec { debug_assert!(max_degree > 1); assert!(quotient_values.len() > 0); let chunk_size = max_degree; quotient_values .chunks(chunk_size) .map(|chunk| chunk.iter().copied().product()) .collect() } /// Compute partial products of the original vector `v` such that all products consist of `max_degree` /// or less elements. This is done until we've computed the product `P` of all elements in the vector. pub(crate) fn partial_products_and_z_gx(z_x: F, quotient_chunk_products: &[F]) -> Vec { assert!(quotient_chunk_products.len() > 0); let mut res = Vec::new(); let mut acc = z_x; for "ient_chunk_product in quotient_chunk_products { acc *= quotient_chunk_product; res.push(acc); } res } /// Returns a tuple `(a,b)`, where `a` is the length of the output of `partial_products()` on a /// vector of length `n`, and `b` is the number of original elements consumed in `partial_products()`. pub(crate) fn num_partial_products(n: usize, max_degree: usize) -> (usize, usize) { debug_assert!(max_degree > 1); let chunk_size = max_degree; // We'll split the product into `ceil_div_usize(n, chunk_size)` chunks, but the last chunk will // be associated with Z(gx) itself. Thus we subtract one to get the chunks associated with // partial products. let num_chunks = ceil_div_usize(n, chunk_size) - 1; (num_chunks, num_chunks * chunk_size) } /// Checks the relationship between each pair of partial product accumulators. In particular, this /// sequence of accumulators starts with `Z(x)`, then contains each partial product polynomials /// `p_i(x)`, and finally `Z(g x)`. See the partial products section of the Plonky2 paper. pub(crate) fn check_partial_products( numerators: &[F], denominators: &[F], partials: &[F], z_x: F, z_gx: F, max_degree: usize, ) -> Vec { debug_assert!(max_degree > 1); let product_accs = iter::once(&z_x) .chain(partials.iter()) .chain(iter::once(&z_gx)); let chunk_size = max_degree; numerators .chunks(chunk_size) .zip_eq(denominators.chunks(chunk_size)) .zip_eq(product_accs.tuple_windows()) .map(|((nume_chunk, deno_chunk), (&prev_acc, &next_acc))| { let num_chunk_product = nume_chunk.iter().copied().product(); let den_chunk_product = deno_chunk.iter().copied().product(); // Assert that next_acc * deno_product = prev_acc * nume_product. prev_acc * num_chunk_product - next_acc * den_chunk_product }) .collect() } /// Checks the relationship between each pair of partial product accumulators. In particular, this /// sequence of accumulators starts with `Z(x)`, then contains each partial product polynomials /// `p_i(x)`, and finally `Z(g x)`. See the partial products section of the Plonky2 paper. pub(crate) fn check_partial_products_recursively, const D: usize>( builder: &mut CircuitBuilder, numerators: &[ExtensionTarget], denominators: &[ExtensionTarget], partials: &[ExtensionTarget], z_x: ExtensionTarget, z_gx: ExtensionTarget, max_degree: usize, ) -> Vec> { debug_assert!(max_degree > 1); let product_accs = iter::once(&z_x) .chain(partials.iter()) .chain(iter::once(&z_gx)); let chunk_size = max_degree; numerators .chunks(chunk_size) .zip_eq(denominators.chunks(chunk_size)) .zip_eq(product_accs.tuple_windows()) .map(|((nume_chunk, deno_chunk), (&prev_acc, &next_acc))| { let nume_product = builder.mul_many_extension(nume_chunk); let deno_product = builder.mul_many_extension(deno_chunk); let next_acc_deno = builder.mul_extension(next_acc, deno_product); // Assert that next_acc * deno_product = prev_acc * nume_product. builder.mul_sub_extension(prev_acc, nume_product, next_acc_deno) }) .collect() } #[cfg(test)] mod tests { use super::*; use crate::field::goldilocks_field::GoldilocksField; #[test] fn test_partial_products() { type F = GoldilocksField; let denominators = vec![F::ONE; 6]; let z_x = F::ONE; let v = field_vec(&[1, 2, 3, 4, 5, 6]); let z_gx = F::from_canonical_u64(720); let quotient_chunks_prods = quotient_chunk_products(&v, 2); assert_eq!(quotient_chunks_prods, field_vec(&[2, 12, 30])); let pps_and_z_gx = partial_products_and_z_gx(z_x, "ient_chunks_prods); let pps = &pps_and_z_gx[..pps_and_z_gx.len() - 1]; assert_eq!(pps_and_z_gx, field_vec(&[2, 24, 720])); let nums = num_partial_products(v.len(), 2); assert_eq!(pps.len(), nums.0); assert!(check_partial_products(&v, &denominators, pps, z_x, z_gx, 2) .iter() .all(|x| x.is_zero())); let quotient_chunks_prods = quotient_chunk_products(&v, 3); assert_eq!(quotient_chunks_prods, field_vec(&[6, 120])); let pps_and_z_gx = partial_products_and_z_gx(z_x, "ient_chunks_prods); let pps = &pps_and_z_gx[..pps_and_z_gx.len() - 1]; assert_eq!(pps_and_z_gx, field_vec(&[6, 720])); let nums = num_partial_products(v.len(), 3); assert_eq!(pps.len(), nums.0); assert!(check_partial_products(&v, &denominators, pps, z_x, z_gx, 3) .iter() .all(|x| x.is_zero())); } fn field_vec(xs: &[usize]) -> Vec { xs.iter().map(|&x| F::from_canonical_usize(x)).collect() } }