plonky2/src/util/partial_products.rs
Daniel Lubarov 7185c2d7d2
Fix & cleanup partial products (#355)
My previous change introduced a bug -- when `num_routed_wires` was a multiple of 8, the partial products "consumed" all `num_routed_wires` terms, whereas we actually want to leave 8 terms for the final product.

This also changes `check_partial_products` to include the final product constraint, and merges `vanishing_v_shift_terms` into `vanishing_partial_products_terms`. I think this is natural since `Z(x)`, partial products, and `Z(g x)` are all part of the product accumulator chain.
2021-11-14 11:58:44 -08:00

149 lines
5.9 KiB
Rust

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<F: Field>(
quotient_values: &[F],
max_degree: usize,
) -> Vec<F> {
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<F: Field>(z_x: F, quotient_chunk_products: &[F]) -> Vec<F> {
assert!(quotient_chunk_products.len() > 0);
let mut res = Vec::new();
let mut acc = z_x;
for &quotient_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<F: Field>(
numerators: &[F],
denominators: &[F],
partials: &[F],
z_x: F,
z_gx: F,
max_degree: usize,
) -> Vec<F> {
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<F: RichField + Extendable<D>, const D: usize>(
builder: &mut CircuitBuilder<F, D>,
numerators: &[ExtensionTarget<D>],
denominators: &[ExtensionTarget<D>],
partials: &[ExtensionTarget<D>],
z_x: ExtensionTarget<D>,
z_gx: ExtensionTarget<D>,
max_degree: usize,
) -> Vec<ExtensionTarget<D>> {
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, &quotient_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, &quotient_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<F: Field>(xs: &[usize]) -> Vec<F> {
xs.iter().map(|&x| F::from_canonical_usize(x)).collect()
}
}