use alloc::vec::Vec;
use p3_binary_field::{BitCoordinates, TowerLevel};
use p3_challenger::FieldChallenger;
use p3_challenger::fs::TranscriptField;
use p3_field::{ExtensionField, Field};
use p3_multilinear_util::point::Point;
use p3_sumcheck::layout::{Selector, Table, TablePlacement, plan_stacked_layout};
use p3_sumcheck::{OpeningEvals, OpeningPointMismatch, OpeningProtocol, TableShape};
use super::{BooleanTraceCommitment, BooleanTraceCommitmentError};
use crate::boolean::{BitOpening, BitReadings, BooleanBackend};
use crate::boolean_trace_transcript::ColumnBatchShape;
impl<EF, B> BooleanTraceCommitment<EF, B>
where
EF: BitCoordinates + ExtensionField<B::Val>,
B: BooleanBackend<EF>,
B::Val: TranscriptField + TowerLevel,
{
pub(super) fn placements(
&self,
shapes: &[TableShape],
) -> Result<Vec<TablePlacement>, BooleanTraceCommitmentError<B::Error>> {
let (arity, placements) = plan_stacked_layout(shapes);
if arity != self.num_variables() {
return Err(BooleanTraceCommitmentError::StackedArity {
expected: self.num_variables(),
actual: arity,
});
}
Ok(placements)
}
pub(super) fn bit_openings(
protocol: &OpeningProtocol,
claims: &[ColumnClaim],
points: &[Point<EF>],
placements: &[TablePlacement],
) -> Vec<BitOpening<EF>> {
let shapes = protocol.table_shapes();
let mut by_table = alloc::vec![None; shapes.len()];
for placement in placements {
by_table[placement.idx()] = Some(placement);
}
claims
.iter()
.map(|claim| {
let placement =
by_table[claim.table].expect("the planner places every supplied shape");
BitOpening {
point: placement.selectors()[claim.column].lift_prefix(&points[claim.opening]),
row_variables: shapes[claim.table].num_variables(),
current: claim.current_at.is_some(),
next: claim.next_at.is_some(),
}
})
.collect()
}
pub(super) fn validate_opening(
&self,
protocol: &OpeningProtocol,
points: &[Point<EF>],
) -> Result<Vec<TablePlacement>, BooleanTraceCommitmentError<B::Error>> {
let placements = self.placements(&protocol.table_shapes())?;
protocol
.check_points(points)
.map_err(|mismatch| match mismatch {
OpeningPointMismatch::Count { expected, actual } => {
BooleanTraceCommitmentError::PointCount { expected, actual }
}
OpeningPointMismatch::Arity {
table,
expected,
actual,
} => BooleanTraceCommitmentError::PointArity {
table,
expected,
actual,
},
})?;
Ok(placements)
}
pub(super) fn validate_source_shapes(
tables: &[Table<B::Val>],
protocol: &OpeningProtocol,
) -> Result<(), BooleanTraceCommitmentError<B::Error>> {
let expected = protocol.table_shapes();
if tables.len() != expected.len() {
return Err(BooleanTraceCommitmentError::TableCountMismatch {
expected: expected.len(),
actual: tables.len(),
});
}
for (table, expected) in expected.iter().copied().enumerate() {
let actual = tables[table].shape();
if actual != expected {
return Err(BooleanTraceCommitmentError::TableShapeMismatch {
table,
expected,
actual,
});
}
}
Ok(())
}
pub(super) fn validate_known(
protocol: &OpeningProtocol,
known: &[Option<OpeningEvals<EF>>],
) -> Result<(), BooleanTraceCommitmentError<B::Error>> {
if known.len() != protocol.num_openings() {
return Err(BooleanTraceCommitmentError::KnownValueCount {
expected: protocol.num_openings(),
actual: known.len(),
});
}
for (batch, ((_, request), known)) in protocol.iter_openings().zip(known).enumerate() {
if known
.as_ref()
.is_some_and(|known| !request.has_same_shape(known))
{
return Err(BooleanTraceCommitmentError::KnownValueShape { batch });
}
}
Ok(())
}
}
#[derive(Clone, Debug)]
pub(super) enum OpeningRoute {
Batched(Vec<TableRun>),
PerColumn(ClaimPlan),
}
#[derive(Clone, Debug)]
pub(super) struct TableRun {
pub(super) table: usize,
pub(super) shape: ColumnBatchShape,
pub(super) blocks: Vec<ColumnBlock>,
}
#[derive(Clone, Copy, Debug)]
pub(super) struct ColumnBlock {
pub(super) prefix: Selector,
pub(super) first: usize,
pub(super) variables: usize,
}
impl OpeningRoute {
pub(super) fn new(protocol: &OpeningProtocol) -> Self {
batched_runs(protocol)
.map_or_else(|| Self::PerColumn(ClaimPlan::of(protocol)), Self::Batched)
}
pub(super) fn num_reductions(&self) -> usize {
match self {
Self::Batched(runs) => runs
.iter()
.map(|run| run.shape.num_batches * run.blocks.len())
.sum(),
Self::PerColumn(plan) => plan.claims().len(),
}
}
pub(super) fn successor_tensors(&self, shapes: &[TableShape], absorbed: usize) -> bool {
match self {
Self::Batched(runs) => runs
.iter()
.any(|run| run.shape.next && run.shape.table_variables > absorbed),
Self::PerColumn(plan) => plan.claims().iter().any(|claim| {
claim.next_at.is_some() && shapes[claim.table].num_variables() > absorbed
}),
}
}
}
fn batched_runs(protocol: &OpeningProtocol) -> Option<Vec<TableRun>> {
let shapes = protocol.table_shapes();
if protocol.num_openings() == 0 {
return None;
}
let (arity, placements) = plan_stacked_layout(&shapes);
let used = shapes
.iter()
.map(|shape| shape.width() << shape.num_variables())
.sum::<usize>();
let mut by_table = alloc::vec![None; shapes.len()];
for placement in &placements {
by_table[placement.idx()] = Some(placement);
}
let mut runs: Vec<TableRun> = Vec::new();
for (table, batch) in protocol.iter_openings() {
let width = shapes[table].width();
let complete = |read: &[usize]| read.iter().copied().eq(0..width);
let next = !batch.next().is_empty();
if !complete(batch.current()) || (next && !complete(batch.next())) {
return None;
}
match runs.last_mut() {
Some(run) if run.table == table => {
if run.shape.next != next {
return None;
}
run.shape.num_batches += 1;
}
_ => {
let placement = by_table[table]?;
runs.push(TableRun {
table,
shape: ColumnBatchShape {
table_variables: shapes[table].num_variables(),
width,
num_batches: 1,
next,
},
blocks: column_blocks(placement, shapes[table], arity, used)?,
});
}
}
}
Some(runs)
}
fn column_blocks(
placement: &TablePlacement,
shape: TableShape,
arity: usize,
used: usize,
) -> Option<Vec<ColumnBlock>> {
let rows = shape.num_variables();
let selectors = placement.selectors();
let start = selectors.first()?.index();
if !selectors
.iter()
.enumerate()
.all(|(column, selector)| selector.index() == start + column)
{
return None;
}
let slots = 1usize << (arity - rows);
let end = start + shape.width();
let free_tail = end << rows >= used;
let column_variables = shape.width().next_power_of_two().trailing_zeros() as usize;
let mut blocks = Vec::new();
let mut position = start;
while position < end {
let mut variables = column_variables.min(position.trailing_zeros() as usize);
loop {
let stop = position + (1 << variables);
if stop <= end || (free_tail && stop <= slots) || variables == 0 {
break;
}
variables -= 1;
}
blocks.push(ColumnBlock {
prefix: Selector::new(arity - rows - variables, position >> variables),
first: position - start,
variables,
});
position += 1 << variables;
}
Some(blocks)
}
#[derive(Clone, Copy, Debug)]
pub(super) struct ColumnClaim {
pub(super) table: usize,
pub(super) column: usize,
pub(super) opening: usize,
pub(super) current_at: Option<usize>,
pub(super) next_at: Option<usize>,
}
pub(super) fn column_claims(protocol: &OpeningProtocol) -> Vec<ColumnClaim> {
let mut claims = Vec::new();
let mut cursor = 0;
for (opening, (table, batch)) in protocol.iter_openings().enumerate() {
let next_cursor = cursor + batch.current().len();
let mut answered = alloc::vec![false; batch.next().len()];
for (offset, &column) in batch.current().iter().enumerate() {
let at = batch
.next()
.iter()
.zip(&answered)
.position(|(&other, &taken)| other == column && !taken);
if let Some(at) = at {
answered[at] = true;
}
claims.push(ColumnClaim {
table,
column,
opening,
current_at: Some(cursor + offset),
next_at: at.map(|at| next_cursor + at),
});
}
for (at, &column) in batch.next().iter().enumerate() {
if !answered[at] {
claims.push(ColumnClaim {
table,
column,
opening,
current_at: None,
next_at: Some(next_cursor + at),
});
}
}
cursor = next_cursor + batch.next().len();
}
claims
}
pub(super) fn value_count(protocol: &OpeningProtocol) -> usize {
protocol.iter_openings().map(|(_, batch)| batch.len()).sum()
}
#[derive(Clone, Debug)]
pub(super) struct ClaimPlan {
claims: Vec<ColumnClaim>,
len: usize,
}
impl ClaimPlan {
pub(super) fn of(protocol: &OpeningProtocol) -> Self {
Self::new(column_claims(protocol), value_count(protocol))
.expect("the per-column claims write every value position exactly once")
}
pub(super) fn new(claims: Vec<ColumnClaim>, len: usize) -> Option<Self> {
let mut written = alloc::vec![false; len];
for claim in &claims {
for at in [claim.current_at, claim.next_at].into_iter().flatten() {
if at >= len || core::mem::replace(&mut written[at], true) {
return None;
}
}
}
written
.into_iter()
.all(|written| written)
.then_some(Self { claims, len })
}
pub(super) const fn claims(&self) -> &[ColumnClaim] {
self.claims.as_slice()
}
pub(super) fn values<EF: Field>(&self, readings: &[BitReadings<EF>]) -> Vec<EF> {
let mut values = alloc::vec![EF::ZERO; self.len];
for (claim, reading) in self.claims.iter().zip(readings) {
if let Some(at) = claim.current_at {
values[at] = reading
.current
.expect("a claim asking for the reading at the point carries it");
}
if let Some(at) = claim.next_at {
values[at] = reading
.next
.expect("a claim asking for the reading one row ahead carries it");
}
}
values
}
pub(super) fn readings<EF: Field>(&self, values: &[EF]) -> Vec<BitReadings<EF>> {
self.claims
.iter()
.map(|claim| BitReadings {
current: claim.current_at.map(|at| values[at]),
next: claim.next_at.map(|at| values[at]),
})
.collect()
}
}
pub(super) fn opening_evals<EF: Field>(
protocol: &OpeningProtocol,
values: &[EF],
) -> Vec<OpeningEvals<EF>> {
let mut evals = Vec::with_capacity(protocol.num_openings());
let mut cursor = 0;
for (_, batch) in protocol.iter_openings() {
let next_cursor = cursor + batch.current().len();
let end = next_cursor + batch.next().len();
evals.push(OpeningEvals::new(
values[cursor..next_cursor].to_vec(),
values[next_cursor..end].to_vec(),
));
cursor = end;
}
evals
}
pub(super) fn sample_points<F, EF, Challenger>(
protocol: &OpeningProtocol,
challenger: &mut Challenger,
) -> Vec<Point<EF>>
where
F: Field,
EF: ExtensionField<F>,
Challenger: FieldChallenger<F>,
{
let shapes = protocol.table_shapes();
protocol
.iter_openings()
.map(|(table, _)| {
let num_variables = shapes[table].num_variables();
Point::new(
(0..num_variables)
.map(|_| challenger.sample_algebra_element())
.collect(),
)
})
.collect()
}