use alloc::vec::Vec;
use p3_field::{ExtensionField, Field, dot_product};
use crate::Claim;
use crate::lagrange::lagrange_weights_01inf_multi;
use crate::layout::opening::{EqSvoPartials, NextSvoPartials, Opening};
use crate::layout::witness::{Table, TablePlacement};
use crate::layout::{ProverMultiClaim, ProverVirtualClaim};
use crate::svo::{SvoAccumulators, SvoPoint, calculate_accumulators_batch};
use crate::table::{OpeningEvals, OpeningRequest, TableShape};
#[derive(Debug, Clone)]
pub struct StackedClaims<F: Field, EF: ExtensionField<F>> {
pub(crate) tables: Vec<Table<F>>,
pub(crate) placements: Vec<TablePlacement>,
pub(crate) num_variables: usize,
pub(crate) folding: usize,
pub(crate) claim_map: Vec<Vec<ProverMultiClaim<F, EF>>>,
pub(crate) virtual_claims: Vec<ProverVirtualClaim<EF>>,
}
impl<F: Field, EF: ExtensionField<F>> StackedClaims<F, EF> {
pub(crate) fn new(
tables: Vec<Table<F>>,
placements: Vec<TablePlacement>,
num_variables: usize,
folding: usize,
) -> Self {
let claim_map = (0..tables.len()).map(|_| Vec::new()).collect();
Self {
tables,
placements,
num_variables,
folding,
claim_map,
virtual_claims: Vec::new(),
}
}
pub(crate) const fn folding(&self) -> usize {
self.folding
}
pub(crate) const fn num_variables(&self) -> usize {
self.num_variables
}
pub(crate) fn num_variables_table(&self, id: usize) -> usize {
self.tables[id].num_variables()
}
pub(crate) fn table_shapes(&self) -> Vec<TableShape> {
self.tables.iter().map(Table::shape).collect()
}
pub fn table(&self, id: usize) -> &Table<F> {
&self.tables[id]
}
pub(crate) fn num_claims(&self) -> usize {
self.claim_map
.iter()
.flat_map(|claims| claims.iter().map(ProverMultiClaim::len))
.sum()
}
pub(crate) const fn num_virtual_claims(&self) -> usize {
self.virtual_claims.len()
}
pub(crate) fn record_known(
&mut self,
table_idx: usize,
batch: &OpeningRequest,
point: SvoPoint<F, EF>,
evals: &OpeningEvals<EF>,
) {
assert_eq!(
self.folding, 0,
"a supplied evaluation carries no preprocessing residual for a round to read"
);
debug_assert!(batch.has_same_shape(evals));
let current = batch
.current()
.iter()
.zip(evals.current())
.map(|(&poly_idx, &eval)| {
Opening::new_with_data(poly_idx, eval, EqSvoPartials::new(Vec::new()))
})
.collect();
let next = batch
.next()
.iter()
.zip(evals.next())
.map(|(&poly_idx, &eval)| {
Opening::new_with_data(poly_idx, eval, NextSvoPartials::new(Vec::new()))
})
.collect();
self.claim_map[table_idx].push(ProverMultiClaim::new(point, current, next));
}
pub(crate) fn concrete_claims(&self) -> impl Iterator<Item = &ProverMultiClaim<F, EF>> {
self.placements
.iter()
.flat_map(|placement| self.claim_map[placement.idx()].iter())
}
pub(crate) fn sum(&self, alpha: EF) -> EF {
let mut sum = EF::ZERO;
let mut alphas = alpha.powers();
for placement in &self.placements {
for claim in &self.claim_map[placement.idx()] {
for opening in claim.current_openings() {
sum += opening.eval() * alphas.next().unwrap();
}
for opening in claim.next_openings() {
sum += opening.eval() * alphas.next().unwrap();
}
}
}
sum += dot_product::<EF, _, _>(
self.virtual_claims.iter().map(Claim::eval),
alpha.shifted_powers(alpha.exp_u64(self.num_claims() as u64)),
);
sum
}
pub(crate) fn batched_accumulators(&self, alpha: EF) -> BatchedAccumulators<'_, EF> {
let mut alphas = alpha.powers();
let concrete = self
.concrete_claims()
.map(|claim| {
let per_claim: Vec<EF> = alphas.by_ref().take(claim.len()).collect();
calculate_accumulators_batch(claim, &per_claim)
})
.collect();
let virtual_alphas = alpha
.shifted_powers(alpha.exp_u64(self.num_claims() as u64))
.take(self.virtual_claims.len())
.collect();
BatchedAccumulators {
concrete,
virtual_claims: &self.virtual_claims,
virtual_alphas,
}
}
}
pub(crate) struct BatchedAccumulators<'a, EF: Field> {
concrete: Vec<SvoAccumulators<EF>>,
virtual_claims: &'a [ProverVirtualClaim<EF>],
virtual_alphas: Vec<EF>,
}
impl<EF: Field> BatchedAccumulators<'_, EF> {
pub(crate) fn round_coefficients(&self, rs: &[EF]) -> (EF, EF) {
let weights = lagrange_weights_01inf_multi(rs);
let round_idx = rs.len();
let mut c0 = EF::ZERO;
let mut c_inf = EF::ZERO;
for accs in &self.concrete {
c0 += dot_product::<EF, _, _>(
accs[round_idx][0].iter().copied(),
weights.iter().copied(),
);
c_inf += dot_product::<EF, _, _>(
accs[round_idx][1].iter().copied(),
weights.iter().copied(),
);
}
for (vc, &alpha_i) in self.virtual_claims.iter().zip(self.virtual_alphas.iter()) {
let vc_accs = &vc.data;
c0 += alpha_i
* dot_product::<EF, _, _>(
vc_accs[round_idx][0].iter().copied(),
weights.iter().copied(),
);
c_inf += alpha_i
* dot_product::<EF, _, _>(
vc_accs[round_idx][1].iter().copied(),
weights.iter().copied(),
);
}
(c0, c_inf)
}
}
#[cfg(test)]
mod tests {
use alloc::vec;
use p3_baby_bear::BabyBear;
use p3_field::PrimeCharacteristicRing;
use p3_field::extension::BinomialExtensionField;
use p3_multilinear_util::point::Point;
use super::*;
use crate::layout::opening::{EqSvoPartials, NextSvoPartials, Opening};
use crate::strategy::VariableOrder;
use crate::svo::SvoPoint;
type F = BabyBear;
type EF = BinomialExtensionField<F, 4>;
fn table(arity: usize) -> Table<F> {
Table::zero(1, arity)
}
fn placement(idx: usize) -> TablePlacement {
TablePlacement::new(idx, vec![])
}
fn claim(current: &[u64], next: &[u64]) -> ProverMultiClaim<F, EF> {
let point = SvoPoint::<F, EF>::new_unpacked(0, &Point::new(vec![]), VariableOrder::Prefix);
let current_openings = current
.iter()
.map(|&e| Opening::new_with_data(0, EF::from_u64(e), EqSvoPartials::new(vec![])))
.collect();
let next_openings = next
.iter()
.map(|&e| Opening::new_with_data(0, EF::from_u64(e), NextSvoPartials::new(vec![])))
.collect();
ProverMultiClaim::new(point, current_openings, next_openings)
}
#[test]
fn new_starts_empty_and_records_shape() {
let sc = StackedClaims::<F, EF>::new(
vec![table(3), table(5)],
vec![placement(1), placement(0)],
6,
2,
);
assert_eq!(sc.folding(), 2);
assert_eq!(sc.num_variables(), 6);
assert_eq!(sc.num_variables_table(0), 3);
assert_eq!(sc.num_variables_table(1), 5);
assert_eq!(sc.num_claims(), 0);
assert!(sc.concrete_claims().next().is_none());
assert_eq!(sc.claim_map.len(), 2);
assert!(sc.virtual_claims.is_empty());
}
#[test]
fn num_claims_counts_openings_in_both_groups() {
let mut sc = StackedClaims::<F, EF>::new(vec![table(2)], vec![placement(0)], 2, 0);
sc.claim_map[0].push(claim(&[3, 5], &[7]));
sc.claim_map[0].push(claim(&[11], &[]));
assert_eq!(sc.num_claims(), 4);
}
#[test]
fn concrete_claims_walks_in_placement_order() {
let mut sc = StackedClaims::<F, EF>::new(
vec![table(2), table(2)],
vec![placement(1), placement(0)],
3,
0,
);
sc.claim_map[0].push(claim(&[100], &[]));
sc.claim_map[1].push(claim(&[200], &[]));
let evals: Vec<EF> = sc
.concrete_claims()
.map(|c| c.current_openings()[0].eval())
.collect();
assert_eq!(evals, vec![EF::from_u64(200), EF::from_u64(100)]);
}
#[test]
fn sum_weights_openings_by_alpha_powers() {
let mut sc = StackedClaims::<F, EF>::new(vec![table(2)], vec![placement(0)], 2, 0);
sc.claim_map[0].push(claim(&[3, 5], &[7]));
sc.claim_map[0].push(claim(&[11], &[]));
sc.virtual_claims.push(Claim {
point: Point::new(vec![]),
eval: EF::from_u64(13),
data: Vec::new(),
});
assert_eq!(sc.num_claims(), 4);
assert_eq!(sc.sum(EF::from_u64(2)), EF::from_u64(337));
}
}