use alloc::vec::Vec;
use p3_challenger::fs::TranscriptField;
use p3_challenger::{FieldChallenger, GrindingChallenger};
use p3_field::{Algebra, ExtensionField, Field};
use p3_maybe_rayon::prelude::*;
use p3_multilinear_util::point::Point;
use p3_multilinear_util::poly::Poly;
use super::banked::BankedColumn;
use super::suffix::weighted_sum;
use crate::SumcheckData;
use crate::layout::witness::{Table, TablePlacement, column_slots};
use crate::strategy::{Basis, IntoTranscriptField, ReprSumcheckProver, VariableOrder};
use crate::transcript::{ProverTranscript, SumcheckShape};
const AGGREGATE_BLOCK: usize = 1 << 12;
#[derive(Debug, Clone)]
pub struct SuffixResidualProver<F: Field, EF: ExtensionField<F>, R: Field> {
prover: ReprSumcheckProver<F, EF, R>,
columns: Option<ColumnHandoff<F, EF, R>>,
banked: Option<BankedColumn<F, R>>,
}
#[derive(Debug, Clone)]
struct ColumnHandoff<F: Field, EF, R> {
tables: Vec<Table<F>>,
placements: Vec<TablePlacement>,
scales: Vec<Vec<R>>,
num_variables: usize,
challenges: Vec<EF>,
}
impl<F, EF, R> SuffixResidualProver<F, EF, R>
where
F: Field,
EF: ExtensionField<F>,
R: IntoTranscriptField<EF> + Algebra<F>,
{
pub(crate) const fn dense(prover: ReprSumcheckProver<F, EF, R>) -> Self {
Self {
prover,
columns: None,
banked: None,
}
}
#[tracing::instrument(skip_all)]
pub(crate) fn banked(
table: Table<F>,
claims: &[(Point<EF>, EF)],
sum: EF,
stage_rounds: usize,
) -> Self {
let (prover, column) = BankedColumn::new(table, claims, sum, stage_rounds);
Self {
prover,
columns: None,
banked: Some(column),
}
}
#[tracing::instrument(skip_all)]
pub(crate) fn row_first(
tables: Vec<Table<F>>,
placements: Vec<TablePlacement>,
scales: Vec<Vec<EF>>,
row_weights: Vec<R>,
sum: EF,
num_variables: usize,
) -> Self {
let scales: Vec<Vec<R>> = scales
.into_iter()
.map(|scales| scales.into_iter().map(R::from).collect())
.collect();
let aggregate = aggregate_columns(&tables, &scales, row_weights.len());
let prover = ReprSumcheckProver::from_repr_tables(
VariableOrder::Suffix,
Poly::new(aggregate),
Poly::new(row_weights),
sum,
);
Self {
prover,
columns: Some(ColumnHandoff {
tables,
placements,
scales,
num_variables,
challenges: Vec::new(),
}),
banked: None,
}
}
#[cfg(test)]
pub(crate) const fn is_row_first(&self) -> bool {
self.columns.is_some()
}
#[cfg(test)]
pub(crate) const fn is_banked(&self) -> bool {
self.banked.is_some()
}
pub fn claimed_sum(&self) -> EF {
self.prover.claimed_sum()
}
pub fn num_variables(&self) -> usize {
let slot_variables = self
.columns
.as_ref()
.map_or(0, ColumnHandoff::num_slot_variables);
let played = self.banked.as_ref().map_or_else(
|| self.prover.num_variables(),
|column| column.num_variables(&self.prover),
);
played + slot_variables
}
pub fn settle(&mut self) {
self.prover.settle();
}
pub fn bound_column(&mut self) -> Option<&[R]> {
match &mut self.banked {
Some(column) if column.stage_played(&self.prover) => {
Some(column.bound(&mut self.prover))
}
_ => None,
}
}
#[tracing::instrument(skip_all, level = "debug")]
pub fn compute_sumcheck_polynomials<Challenger>(
&mut self,
sumcheck_data: &mut SumcheckData<F, EF>,
challenger: &mut Challenger,
folding_factor: usize,
pow_bits: usize,
) -> Point<EF>
where
F: TranscriptField,
Challenger: FieldChallenger<F> + GrindingChallenger<Witness = F>,
{
let shape = SumcheckShape::new(folding_factor, pow_bits, Basis::Evaluation);
let mut transcript = ProverTranscript::<Challenger, F, EF>::new(challenger, shape);
let mut challenges = Vec::with_capacity(folding_factor);
for _ in 0..folding_factor {
if self.prover.num_variables() == 0
&& let Some(columns) = self.columns.take()
{
self.prover = columns.into_prover(&mut self.prover);
}
if let Some(column) = &mut self.banked
&& column.stage_played(&self.prover)
{
self.prover = column.next_stage(&mut self.prover);
}
let r = self.prover.round(sumcheck_data, &mut transcript);
if let Some(columns) = &mut self.columns {
columns.challenges.push(r);
}
if let Some(column) = &mut self.banked {
column.push_challenge(R::from(r));
}
challenges.push(r);
}
transcript.finish();
Point::new(challenges)
}
}
impl<F: Field, EF: ExtensionField<F>, R> ColumnHandoff<F, EF, R> {
fn num_slot_variables(&self) -> usize {
self.num_variables - self.tables[0].num_variables()
}
#[tracing::instrument(skip_all)]
fn into_prover(self, rows: &mut ReprSumcheckProver<F, EF, R>) -> ReprSumcheckProver<F, EF, R>
where
R: IntoTranscriptField<EF> + Algebra<F>,
{
let row_variables = self.tables[0].num_variables();
let num_slot_variables = self.num_slot_variables();
let row_weight = rows.weights().as_slice()[0];
let point = Point::new(self.challenges).reversed();
let eq = R::from_table(Poly::new_from_point(point.as_slice(), EF::ONE).into_evals());
let mut evals = Poly::<R>::zero(num_slot_variables);
column_slots(
&self.placements,
&self.tables,
row_variables,
evals.as_mut_slice(),
)
.into_par_iter()
.for_each(|(slot, table_idx, poly_idx)| {
let column = self.tables[table_idx].poly(poly_idx);
slot[0] = weighted_sum(&eq, column.as_slice());
});
let mut weights = Poly::<R>::zero(num_slot_variables);
for (slot, table_idx, poly_idx) in column_slots(
&self.placements,
&self.tables,
row_variables,
weights.as_mut_slice(),
) {
slot[0] = self.scales[table_idx][poly_idx] * row_weight;
}
ReprSumcheckProver::from_repr_tables(
VariableOrder::Suffix,
evals,
weights,
rows.claimed_sum(),
)
}
}
fn aggregate_columns<F: Field, R: Field + Algebra<F>>(
tables: &[Table<F>],
scales: &[Vec<R>],
len: usize,
) -> Vec<R> {
let columns: Vec<(&[F], R)> = tables
.iter()
.zip(scales)
.flat_map(|(table, scales)| table.iter_polys().zip(scales.iter().copied()))
.filter(|&(_, scale)| scale != R::ZERO)
.collect();
let mut aggregate = R::zero_vec(len);
if len.div_ceil(AGGREGATE_BLOCK) >= current_num_threads() {
aggregate
.par_chunks_mut(AGGREGATE_BLOCK)
.enumerate()
.for_each(|(block, out)| {
let rows = block * AGGREGATE_BLOCK..block * AGGREGATE_BLOCK + out.len();
for &(column, scale) in &columns {
add_scaled_column(out, &column[rows.clone()], scale);
}
});
return aggregate;
}
aggregate
.par_chunks_mut(AGGREGATE_BLOCK)
.enumerate()
.for_each(|(block, out)| {
let len = out.len();
let rows = block * AGGREGATE_BLOCK..block * AGGREGATE_BLOCK + len;
let sum = columns.par_iter().par_fold_reduce(
|| R::zero_vec(len),
|mut acc, &(column, scale)| {
add_scaled_column(&mut acc, &column[rows.clone()], scale);
acc
},
|mut acc, other| {
acc.iter_mut()
.zip(other)
.for_each(|(acc, other)| *acc += other);
acc
},
);
out.copy_from_slice(&sum);
});
aggregate
}
fn add_scaled_column<F: Field, R: Field + Algebra<F>>(acc: &mut [R], column: &[F], scale: R) {
for (acc, &value) in acc.iter_mut().zip(column) {
if value == F::ONE {
*acc += scale;
} else if value != F::ZERO {
*acc += scale * value;
}
}
}
#[cfg(test)]
mod tests {
use alloc::vec::Vec;
use p3_baby_bear::BabyBear;
use p3_binary_field::{BinaryField128, Ghash128};
use p3_field::extension::BinomialExtensionField;
use p3_matrix::dense::RowMajorMatrix;
use rand::distr::{Distribution, StandardUniform};
use rand::rngs::SmallRng;
use rand::{RngExt, SeedableRng};
use super::*;
fn assert_aggregate_matches_row_sums<F, R>(seed: u64)
where
F: Field,
R: Field + Algebra<F>,
StandardUniform: Distribution<F> + Distribution<R>,
{
let mut rng = SmallRng::seed_from_u64(seed);
for (row_variables, width) in [(0, 3), (3, 5), (13, 1), (13, 6), (16, 2)] {
let len = 1 << row_variables;
let tables: Vec<Table<F>> = (0..2)
.map(|_| {
let values = (0..width * len)
.map(|cell| {
if cell % 3 == 0 {
rng.random()
} else {
F::from_bool(rng.random_bool(0.5))
}
})
.collect();
Table::new(RowMajorMatrix::new(values, len))
})
.collect();
let scales: Vec<Vec<R>> = (0..2)
.map(|_| {
(0..width)
.map(|column| if column == 0 { R::ZERO } else { rng.random() })
.collect()
})
.collect();
let expected: Vec<R> = (0..len)
.map(|row| {
tables
.iter()
.zip(&scales)
.flat_map(|(table, scales)| {
table
.iter_polys()
.zip(scales)
.map(move |(column, &scale)| scale * column[row])
})
.sum()
})
.collect();
assert_eq!(
aggregate_columns(&tables, &scales, len),
expected,
"row_variables={row_variables}, width={width}"
);
}
}
#[test]
fn aggregate_matches_row_sums_over_binary_field() {
assert_aggregate_matches_row_sums::<BinaryField128, Ghash128>(1);
}
#[test]
fn aggregate_matches_row_sums_over_extension_of_prime_field() {
assert_aggregate_matches_row_sums::<BabyBear, BinomialExtensionField<BabyBear, 4>>(2);
}
}