use itertools::{multizip, Itertools};
use num_traits::Zero;
#[cfg(feature = "parallel")]
use rayon::prelude::*;
use stwo::core::fields::m31::BaseField;
use stwo::core::fields::qm31::{SecureField, SECURE_EXTENSION_DEGREE};
use stwo::core::poly::circle::CanonicCoset;
use stwo::core::utils::uninit_vec;
use stwo::core::ColumnVec;
use stwo::prover::backend::simd::column::SecureColumn;
use stwo::prover::backend::simd::m31::{PackedBaseField, LOG_N_LANES, N_LANES};
use stwo::prover::backend::simd::prefix_sum::inclusive_prefix_sum;
use stwo::prover::backend::simd::qm31::{batch_inverse_packed_qm31, PackedSecureField};
use stwo::prover::backend::simd::SimdBackend;
use stwo::prover::backend::Column;
use stwo::prover::poly::circle::CircleEvaluation;
use stwo::prover::poly::BitReversedOrder;
use stwo::prover::secure_column::SecureColumnByCoords;
pub struct LogupTraceGenerator {
log_size: u32,
trace: Vec<SecureColumnByCoords<SimdBackend>>,
denom: SecureColumn,
batch_inverse_buffer: Vec<PackedSecureField>,
}
impl LogupTraceGenerator {
pub fn new(log_size: u32) -> Self {
let trace = vec![];
let denom = SecureColumn::zeros(1 << log_size);
let batch_inverse_buffer = unsafe { uninit_vec(1 << (log_size - LOG_N_LANES)) };
Self {
log_size,
trace,
denom,
batch_inverse_buffer,
}
}
pub unsafe fn uninitialized(log_size: u32) -> Self {
let trace = vec![];
let denom = SecureColumn::uninitialized(1 << log_size);
let batch_inverse_buffer = unsafe { uninit_vec(1 << (log_size - LOG_N_LANES)) };
Self {
log_size,
trace,
denom,
batch_inverse_buffer,
}
}
pub fn new_col(&mut self) -> LogupColGenerator<'_> {
let log_size = self.log_size;
LogupColGenerator {
gen: self,
numerator: SecureColumnByCoords::<SimdBackend>::zeros(1 << log_size),
}
}
pub fn col_from_iter(
&mut self,
iter: impl ExactSizeIterator<Item = (PackedSecureField, PackedSecureField)>,
) {
let length = 1 << self.log_size;
assert_eq!(iter.len() * N_LANES, length);
let mut col_gen = self.new_col();
for (vec_row, (numerator, denom)) in iter.enumerate() {
col_gen.write_frac(vec_row, numerator, denom);
}
col_gen.finalize_col();
}
#[cfg(feature = "parallel")]
pub fn col_from_par_iter(
&mut self,
iter: impl IndexedParallelIterator<Item = (PackedSecureField, PackedSecureField)>,
) {
use stwo::prover::backend::simd::column::BaseColumn;
let length = 1 << self.log_size;
assert_eq!(iter.len() * N_LANES, length);
let (((c0, c1), c2), c3) = (self.denom.data.par_iter_mut())
.zip(iter)
.map(|(dst_denom, (numerator, denom))| {
*dst_denom = denom;
let [c0, c1, c2, c3] = numerator.into_packed_m31s();
(((c0, c1), c2), c3)
})
.unzip();
let columns = [c0, c1, c2, c3].map(BaseColumn::from_simd);
let numerator = SecureColumnByCoords::<SimdBackend> { columns };
LogupColGenerator {
gen: self,
numerator,
}
.finalize_col();
}
pub fn finalize_last(
mut self,
) -> (
ColumnVec<CircleEvaluation<SimdBackend, BaseField, BitReversedOrder>>,
SecureField,
) {
let mut last_col_coords = self.trace.pop().unwrap().columns;
let coordinate_sums = last_col_coords.each_ref().map(|c| {
c.data
.iter()
.copied()
.sum::<PackedBaseField>()
.pointwise_sum()
});
let claimed_sum = SecureField::from_m31_array(coordinate_sums);
let cumsum_shift = claimed_sum / BaseField::from_u32_unchecked(1 << self.log_size);
let packed_cumsum_shift = PackedSecureField::broadcast(cumsum_shift);
last_col_coords.iter_mut().enumerate().for_each(|(i, c)| {
c.data
.iter_mut()
.for_each(|x| *x -= packed_cumsum_shift.into_packed_m31s()[i])
});
let coord_prefix_sum = last_col_coords.map(inclusive_prefix_sum);
let secure_prefix_sum = SecureColumnByCoords {
columns: coord_prefix_sum,
};
self.trace.push(secure_prefix_sum);
let trace = self
.trace
.into_iter()
.flat_map(|eval| {
eval.columns.map(|col| {
CircleEvaluation::new(CanonicCoset::new(self.log_size).circle_domain(), col)
})
})
.collect_vec();
(trace, claimed_sum)
}
}
pub struct LogupColGenerator<'a> {
gen: &'a mut LogupTraceGenerator,
numerator: SecureColumnByCoords<SimdBackend>,
}
impl LogupColGenerator<'_> {
pub fn write_frac(
&mut self,
vec_row: usize,
numerator: PackedSecureField,
denom: PackedSecureField,
) {
debug_assert!(
denom.to_array().iter().all(|x| *x != SecureField::zero()),
"{:?}",
("denom at vec_row {} is zero {}", denom, vec_row)
);
unsafe {
self.numerator.set_packed(vec_row, numerator);
*self.gen.denom.data.get_unchecked_mut(vec_row) = denom;
}
}
pub fn finalize_col(mut self) {
let chunk_size = std::cmp::min(4, self.gen.denom.data.len());
batch_inverse_packed_qm31(&self.gen.denom.data, &mut self.gen.batch_inverse_buffer);
#[cfg(feature = "parallel")]
let chunks_iter = {
let denom_inv_chunks = self.gen.batch_inverse_buffer.par_chunks(chunk_size);
let numerator_chunks = self.numerator.par_chunks_mut(chunk_size);
(numerator_chunks, denom_inv_chunks).into_par_iter()
};
#[cfg(not(feature = "parallel"))]
let chunks_iter = {
let denom_inv_chunks = self.gen.batch_inverse_buffer.chunks(chunk_size);
let numerator_chunks = self.numerator.chunks_mut(chunk_size);
numerator_chunks.zip(denom_inv_chunks)
};
chunks_iter
.enumerate()
.for_each(|(chunk_idx, (mut numerator_chunk, denom_inv_chunk))| {
for (idx_in_chunk, denom_item) in denom_inv_chunk.iter().enumerate() {
unsafe {
let vec_row = chunk_idx * chunk_size + idx_in_chunk;
let value = numerator_chunk.packed_at(idx_in_chunk) * *denom_item;
let prev_value = self
.gen
.trace
.last()
.map(|col| col.packed_at(vec_row))
.unwrap_or_else(PackedSecureField::zero);
numerator_chunk.set_packed(idx_in_chunk, value + prev_value)
}
}
});
self.gen.trace.push(self.numerator)
}
pub fn iter_mut(&mut self) -> impl Iterator<Item = FractionWriter<'_>> {
let denom = self.gen.denom.data.iter_mut();
let [coord0, coord1, coord2, coord3] =
self.numerator.columns.each_mut().map(|s| &mut s.data);
multizip((coord0, coord1, coord2, coord3, denom)).map(|(n0, n1, n2, n3, d)| {
FractionWriter {
numerator: [n0, n1, n2, n3],
denom: d,
}
})
}
#[cfg(feature = "parallel")]
pub fn par_iter_mut(&mut self) -> impl IndexedParallelIterator<Item = FractionWriter<'_>> {
let [coord0, coord1, coord2, coord3] =
self.numerator.columns.each_mut().map(|s| &mut s.data);
(coord0, coord1, coord2, coord3, &mut self.gen.denom.data)
.into_par_iter()
.map(|(n0, n1, n2, n3, d)| FractionWriter {
numerator: [n0, n1, n2, n3],
denom: d,
})
}
}
pub struct FractionWriter<'a> {
numerator: [&'a mut PackedBaseField; SECURE_EXTENSION_DEGREE],
denom: &'a mut PackedSecureField,
}
impl FractionWriter<'_> {
pub fn write_frac(self, numerator: PackedSecureField, denom: PackedSecureField) {
debug_assert!(
denom.to_array().iter().all(|x| *x != SecureField::zero()),
"denom is zero {denom:?}"
);
let [c0, c1, c2, c3] = numerator.into_packed_m31s();
*self.numerator[0] = c0;
*self.numerator[1] = c1;
*self.numerator[2] = c2;
*self.numerator[3] = c3;
*self.denom = denom;
}
}
#[cfg(test)]
mod tests {
use stwo::core::fields::FieldExpOps;
use stwo::prover::backend::simd::m31::LOG_N_LANES;
use stwo::prover::backend::simd::qm31::PackedSecureField;
use crate::prover::logup::LogupTraceGenerator;
use crate::{m31, qm31};
#[test]
fn test_frac_writer() {
let expected_sum = (qm31!(1, 2, 3, 4) * qm31!(5, 6, 7, 8).inverse()) * m31!(1 << 6);
let mut log_gen = LogupTraceGenerator::new(6);
let mut col_gen = log_gen.new_col();
for writer in col_gen.iter_mut() {
let num = PackedSecureField::broadcast(qm31!(1, 2, 3, 4));
let den = PackedSecureField::broadcast(qm31!(5, 6, 7, 8));
writer.write_frac(num, den);
}
col_gen.finalize_col();
let (_, sum) = log_gen.finalize_last();
assert_eq!(sum, expected_sum);
}
#[test]
fn test_col_from_iter() {
let log_size = 8;
let expected_sum = (qm31!(1, 2, 3, 4) * qm31!(5, 6, 7, 8).inverse()) * m31!(1 << log_size);
let mut log_gen = LogupTraceGenerator::new(log_size);
let col_iter = (0..1 << (log_size - LOG_N_LANES)).map(|_| {
let num = PackedSecureField::broadcast(qm31!(1, 2, 3, 4));
let den = PackedSecureField::broadcast(qm31!(5, 6, 7, 8));
(num, den)
});
log_gen.col_from_iter(col_iter);
let (_, sum) = log_gen.finalize_last();
assert_eq!(sum, expected_sum);
}
#[cfg(feature = "parallel")]
#[test]
fn test_col_from_par_iter() {
use rayon::iter::{IntoParallelIterator, ParallelIterator};
let log_size = 8;
let expected_sum = (qm31!(1, 2, 3, 4) * qm31!(5, 6, 7, 8).inverse()) * m31!(1 << log_size);
let mut log_gen = LogupTraceGenerator::new(log_size);
let col_iter = (0..1 << (log_size - LOG_N_LANES)).into_par_iter().map(|_| {
let num = PackedSecureField::broadcast(qm31!(1, 2, 3, 4));
let den = PackedSecureField::broadcast(qm31!(5, 6, 7, 8));
(num, den)
});
log_gen.col_from_par_iter(col_iter);
let (_, sum) = log_gen.finalize_last();
assert_eq!(sum, expected_sum);
}
#[cfg(feature = "parallel")]
#[test]
fn test_parallel_frac_writer() {
use std::array;
use rayon::prelude::*;
let mut log_gen_seq = LogupTraceGenerator::new(6);
let mut col_gen_seq = log_gen_seq.new_col();
col_gen_seq.iter_mut().enumerate().for_each(|(i, writer)| {
let num = array::from_fn(|j| qm31!(i as u32, j as u32, 0, 1));
let den = array::from_fn(|j| qm31!(i as u32, j as u32, 2, 3));
let [num, den] = [num, den].map(PackedSecureField::from_array);
writer.write_frac(num, den);
});
col_gen_seq.finalize_col();
let (_, sum_seq) = log_gen_seq.finalize_last();
let mut log_gen_par = LogupTraceGenerator::new(6);
let mut col_gen_par = log_gen_par.new_col();
col_gen_par
.par_iter_mut()
.enumerate()
.for_each(|(i, writer)| {
let num = array::from_fn(|j| qm31!(i as u32, j as u32, 0, 1));
let den = array::from_fn(|j| qm31!(i as u32, j as u32, 2, 3));
let [num, den] = [num, den].map(PackedSecureField::from_array);
writer.write_frac(num, den);
});
col_gen_par.finalize_col();
let (_, sum_par) = log_gen_par.finalize_last();
assert_eq!(sum_seq, sum_par);
}
}