Skip to content

Commit

Permalink
PointEvaluationAccumulator
Browse files Browse the repository at this point in the history
  • Loading branch information
spapinistarkware committed Feb 13, 2024
1 parent f68d6d1 commit bab9f78
Show file tree
Hide file tree
Showing 3 changed files with 117 additions and 8 deletions.
103 changes: 102 additions & 1 deletion src/core/air/evaluation.rs
Original file line number Diff line number Diff line change
@@ -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<QM31>,
// Number of accumulated evaluations for each log_size.
n_accumulated: Vec<usize>,
}
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;
use crate::m31;

#[test]
fn test_point_evaluation_accumulator() {
// Generate a vector of random sizes with a constant seed.
let rng = &mut StdRng::seed_from_u64(0);

Check warning on line 71 in src/core/air/evaluation.rs

View check run for this annotation

OX Security (Read Only) / ox-security/scan

Usage of non-cryptographic random number generator

Always use cryptographic random number generators.
const MAX_LOG_SIZE: u32 = 10;
const MASK: u32 = (1 << 30) - 1;
let log_sizes = (0..100)
.map(|_| rng.gen_range(4..MAX_LOG_SIZE))
.collect::<Vec<_>>();

// Generate random evaluations.
let evaluations = log_sizes
.iter()
.map(|_| M31::from_u32_unchecked(rng.gen::<u32>() & MASK))
.collect::<Vec<_>>();
let alpha = m31!(2).into();

// 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::<Vec<_>>();
pairs.sort_by_key(|(log_size, _)| *log_size);
for (_, evaluation) in pairs.iter() {
res = res * alpha + *evaluation;
}

assert_eq!(accumulator_res, res);
}
}
19 changes: 13 additions & 6 deletions src/core/poly/circle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -321,7 +321,7 @@ pub struct CirclePoly<F: ExtensionOf<BaseField>> {
/// 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<F>,
pub coeffs: Vec<F>,
/// The number of coefficients stored as `log2(len(coeffs))`.
log_size: u32,
}
Expand Down Expand Up @@ -352,6 +352,17 @@ impl<F: ExtensionOf<BaseField>> CirclePoly<F> {
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<F> {
// Use CFFT to evaluate.
Expand All @@ -360,11 +371,7 @@ impl<F: ExtensionOf<BaseField>> CirclePoly<F> {

// 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);
Expand Down
3 changes: 2 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
is_sorted,
new_uninit,
slice_group_by,
stdsimd
stdsimd,
get_many_mut
)]
pub mod commitment_scheme;
pub mod core;
Expand Down

0 comments on commit bab9f78

Please sign in to comment.