use alloc::vec::Vec;
use p3_binary_dft::EncodableLevel;
use p3_binary_field::TowerLevel;
use p3_challenger::fs::TranscriptField;
use p3_field::Field;
use p3_matrix::dense::RowMajorMatrix;
use p3_maybe_rayon::prelude::*;
use p3_multilinear_util::point::Point;
use p3_multilinear_util::poly::Poly;
use p3_multilinear_util::split_eq::SplitEq;
use p3_sumcheck::layout::{ColumnView, Table};
use super::{BooleanTraceCommitment, WORD_BITS};
use crate::boolean::BooleanBackend;
use crate::fold::{ChallengeField, FoldAlphabet};
use crate::packing::Coordinates;
const ROW_GRAIN: usize = 1 << 12;
const BYTES_PER_WORD: usize = WORD_BITS / 8;
pub(super) const BLOCKS_PER_TASK: usize = 16;
impl<EF, B> BooleanTraceCommitment<EF, B>
where
EF: ChallengeField<EF>
+ EncodableLevel
+ TranscriptField
+ TowerLevel
+ FoldAlphabet<EF>
+ Coordinates,
B: BooleanBackend<EF>,
{
pub(super) fn evaluate_views(
table: &Table<EF>,
point: &Point<EF>,
next: bool,
) -> (Vec<EF>, Vec<EF>) {
let mut weights = SplitEq::<EF, EF>::new_packed(point, EF::ONE).materialize();
let current = Self::weighted_columns(table, &weights);
if !next {
return (current, Vec::new());
}
let rows = weights.as_mut_slice();
let last = rows.len() - 1;
let repeated = rows[last];
rows.copy_within(..last, 1);
rows[0] = EF::ZERO;
rows[last] += repeated;
let successor = Self::weighted_columns(table, &weights);
(current, successor)
}
fn weighted_columns(table: &Table<EF>, weights: &Poly<EF>) -> Vec<EF> {
let rows = weights.as_slice();
if let Some(words) = table.packed_bits() {
return packed_column_sums(words, rows);
}
table
.par_columns()
.map(|column| Self::weighted_column(column, rows))
.collect()
}
fn weighted_column(column: ColumnView<'_, EF>, rows: &[EF]) -> EF {
let cells = column
.as_dense()
.expect("a packed table is summed word by word");
cells
.par_chunks(ROW_GRAIN)
.zip(rows.par_chunks(ROW_GRAIN))
.map(|(cells, rows)| {
cells
.iter()
.zip(rows)
.filter(|&(&cell, _)| cell == EF::ONE)
.map(|(_, &weight)| weight)
.sum::<EF>()
})
.sum()
}
}
pub(super) fn packed_column_sums<EF: Field>(
words: &RowMajorMatrix<u64>,
row_weights: &[EF],
) -> Vec<EF> {
let width = words.width;
debug_assert!(
row_weights.len() > (words.values.len() / width).saturating_sub(1) * WORD_BITS,
"every row block needs at least one weight"
);
words
.values
.par_chunks(width * BLOCKS_PER_TASK)
.enumerate()
.par_fold_reduce(
|| EF::zero_vec(width),
|mut sums, (task, blocks)| {
let mut tables = EF::zero_vec(BYTES_PER_WORD << 8);
let (tables, _) = tables.as_chunks_mut::<256>();
for (offset, block) in blocks.chunks_exact(width).enumerate() {
let first_row = (task * BLOCKS_PER_TASK + offset) * WORD_BITS;
for (group, table) in tables.iter_mut().enumerate() {
let weight = |row: usize| {
row_weights
.get(first_row + 8 * group + row)
.copied()
.unwrap_or(EF::ZERO)
};
for subset in 1..256usize {
table[subset] = table[subset & (subset - 1)]
+ weight(subset.trailing_zeros() as usize);
}
}
for (sum, &word) in sums.iter_mut().zip(block) {
*sum += tables
.iter()
.zip(word.to_le_bytes())
.map(|(table, byte)| table[usize::from(byte)])
.sum::<EF>();
}
}
sums
},
|mut left, right| {
left.iter_mut()
.zip(right)
.for_each(|(left, right)| *left += right);
left
},
)
}