diff --git a/src/core/air/evaluation.rs b/src/core/air/evaluation.rs index 0a3012d1f..f617cc7cb 100644 --- a/src/core/air/evaluation.rs +++ b/src/core/air/evaluation.rs @@ -1,2 +1,103 @@ -pub struct PointEvaluationAccumulator; +//! Accumulators for a random linear combination of circle polynomials. +//! Given N polynomials, sort them by size: u_0(P), ... u_{N-1}(P). +//! Given a random alpha, the combined polynomial is defined as +//! f(p) = sum_i alpha^{N-1-i} u_i (P). + +use crate::core::fields::qm31::QM31; +use crate::core::fields::Field; + +/// Accumulates evaluations of u_i(P0) at a single point. +/// Computes f(P0), the combined polynomial at that point. +pub struct PointEvaluationAccumulator { + random_coeff: QM31, + // Accumulated evaluations for each log_size. + // Each `sub_accumulation` holds `sum_{i=0}^{n-1} evaluation_i * alpha^(n-1-i)`, + // where `n` is the number of accumulated evaluations for this log_size. + sub_accumulations: Vec, + // Number of accumulated evaluations for each log_size. + n_accumulated: Vec, +} +impl PointEvaluationAccumulator { + /// Creates a new accumulator. + /// `random_coeff` should be a secure random field element, drawn from the channel. + /// `max_log_size` is the maximum log_size of the accumulated evaluations. + pub fn new(random_coeff: QM31, max_log_size: u32) -> Self { + // TODO(spapini): Consider making all log_sizes usize. + let max_log_size = max_log_size as usize; + Self { + random_coeff, + sub_accumulations: vec![QM31::default(); max_log_size + 1], + n_accumulated: vec![0; max_log_size + 1], + } + } + + /// Accumulates u_i(P0), a polynomial evaluation at a P0. + pub fn accumulate(&mut self, log_size: u32, evaluation: QM31) { + let sub_accumulation = &mut self.sub_accumulations[log_size as usize]; + *sub_accumulation = *sub_accumulation * self.random_coeff + evaluation; + + self.n_accumulated[log_size as usize] += 1; + } + + /// Computes f(P0), the evaluation of the combined polynomial at P0. + pub fn finalize(self) -> QM31 { + // Each `sub_accumulation` holds a linear combination of a consecutive slice of + // u_0(P0), ... u_{N-1}(P0): + // alpha^n_k u_i(P0) + alpha^{n_k-1} u_{i+1}(P0) + ... + alpha^0 u_{i+n_k-1}(P0). + // To combine all these slices, multiply an accumulator by alpha^k, and add the next slice. + self.sub_accumulations + .iter() + .zip(self.n_accumulated.iter()) + .fold(QM31::default(), |total, (sub_accumulation, n_i)| { + total * self.random_coeff.pow(*n_i as u128) + *sub_accumulation + }) + } +} + pub struct DomainEvaluationAccumulator; + +#[cfg(test)] +mod tests { + use rand::rngs::StdRng; + use rand::{Rng, SeedableRng}; + + use super::*; + use crate::core::fields::m31::{M31, P}; + use crate::qm31; + + #[test] + fn test_point_evaluation_accumulator() { + // Generate a vector of random sizes with a constant seed. + let rng = &mut StdRng::seed_from_u64(0); + const MAX_LOG_SIZE: u32 = 10; + const MASK: u32 = P; + let log_sizes = (0..100) + .map(|_| rng.gen_range(4..MAX_LOG_SIZE)) + .collect::>(); + + // Generate random evaluations. + let evaluations = log_sizes + .iter() + .map(|_| M31::from_u32_unchecked(rng.gen::() & MASK)) + .collect::>(); + let alpha = qm31!(2, 3, 4, 5); + + // Use accumulator. + let mut accumulator = PointEvaluationAccumulator::new(alpha, MAX_LOG_SIZE); + for (log_size, evaluation) in log_sizes.iter().zip(evaluations.iter()) { + accumulator.accumulate(*log_size, (*evaluation).into()); + } + let accumulator_res = accumulator.finalize(); + + // Use direct computation. + let mut res = QM31::default(); + // Sort evaluations by log_size. + let mut pairs = log_sizes.into_iter().zip(evaluations).collect::>(); + pairs.sort_by_key(|(log_size, _)| *log_size); + for (_, evaluation) in pairs.iter() { + res = res * alpha + *evaluation; + } + + assert_eq!(accumulator_res, res); + } +} diff --git a/src/core/poly/circle.rs b/src/core/poly/circle.rs index 9fc042f4d..4919a4e9c 100644 --- a/src/core/poly/circle.rs +++ b/src/core/poly/circle.rs @@ -321,7 +321,7 @@ pub struct CirclePoly> { /// monomial basis. The FFT basis is a tensor product of the twiddles: /// y, x, pi(x), pi^2(x), ..., pi^{log_size-2}(x). /// pi(x) := 2x^2 - 1. - coeffs: Vec, + pub coeffs: Vec, /// The number of coefficients stored as `log2(len(coeffs))`. log_size: u32, } @@ -352,6 +352,17 @@ impl> CirclePoly { fold(&self.coeffs, &mappings) } + /// Extends the polynomial to a larger degree bound. + pub fn extend(self, log_size: u32) -> Self { + assert!(log_size >= self.log_size); + let mut coeffs = vec![F::zero(); 1 << log_size]; + let log_jump = log_size - self.log_size; + for (i, val) in self.coeffs.iter().enumerate() { + coeffs[i << log_jump] = *val; + } + Self { coeffs, log_size } + } + /// Evaluates the polynomial at all points in the domain. pub fn evaluate(&self, domain: CircleDomain) -> CircleEvaluation { // Use CFFT to evaluate. @@ -360,11 +371,7 @@ impl> CirclePoly { // TODO(spapini): extend better. assert!(domain.log_size() >= self.log_size); - let mut values = vec![F::zero(); domain.size()]; - let log_jump = domain.log_size() - self.log_size; - for (i, val) in self.coeffs.iter().enumerate() { - values[i << log_jump] = *val; - } + let mut values = self.clone().extend(domain.log_size()).coeffs; while coset.size() > 1 { cosets.push(coset); @@ -435,10 +442,11 @@ impl> PointMapping { #[cfg(test)] mod tests { use super::{CanonicCoset, CircleDomain, CircleEvaluation, Coset}; - use crate::core::circle::CirclePointIndex; + use crate::core::circle::{CirclePoint, CirclePointIndex}; use crate::core::constraints::{EvalByEvaluation, PolyOracle}; use crate::core::fields::m31::{BaseField, M31}; use crate::core::fields::Field; + use crate::core::poly::circle::CirclePoly; use crate::core::poly::NaturalOrder; use crate::core::utils::bit_reverse_index; use crate::m31; @@ -598,4 +606,16 @@ mod tests { ); } } + + #[test] + fn test_circle_poly_extend() { + let poly = CirclePoly::new((0..16).map(BaseField::from_u32_unchecked).collect()); + let extended = poly.clone().extend(8); + let random_point = CirclePoint::get_point(21903); + + assert_eq!( + poly.eval_at_point(random_point), + extended.eval_at_point(random_point) + ); + } }