use alloc::vec::Vec;
use p3_binary_field::{BinaryField128, TowerLevel, poly_basis};
use p3_matrix::Matrix;
use p3_matrix::dense::RowMajorMatrix;
use p3_maybe_rayon::prelude::*;
use p3_util::log2_strict_usize;
use crate::domain::{domain_point, subspace_polynomial};
use crate::lch::{BUTTERFLY_GRAIN, LchNtt};
use crate::traits::AdditiveNtt;
#[derive(Clone, Debug, Default)]
pub struct PolyBasisNtt {
tower: LchNtt<BinaryField128>,
}
#[inline]
fn twiddle(base: BinaryField128, blk: usize) -> u128 {
poly_basis::from_tower(base + domain_point::<BinaryField128>(blk << 1))
}
impl AdditiveNtt<BinaryField128> for PolyBasisNtt {
fn shifted_ntt_batch(
&self,
mut mat: RowMajorMatrix<BinaryField128>,
shift: BinaryField128,
) -> RowMajorMatrix<BinaryField128> {
if !poly_basis::HAS_HARDWARE_CLMUL {
return self.tower.shifted_ntt_batch(mat, shift);
}
let width = mat.width();
let log_n = log2_strict_usize(mat.height());
let mut values: Vec<u128> = core::mem::take(&mut mat.values)
.into_iter()
.map(BinaryField128::to_repr)
.collect();
values
.par_iter_mut()
.for_each(|v| *v = poly_basis::from_tower(BinaryField128::from_repr(*v)));
for j in (0..log_n).rev() {
let half = (1 << j) * width;
let base = subspace_polynomial::<BinaryField128>(j, shift);
values
.par_chunks_mut(half << 1)
.enumerate()
.for_each(|(blk, block)| {
let t = twiddle(base, blk);
let zero = t == 0;
let (lo, hi) = block.split_at_mut(half);
let butterfly = |lo: &mut [u128], hi: &mut [u128]| {
if zero {
for (u, v) in lo.iter_mut().zip(hi) {
*v ^= *u;
}
} else {
for (u, v) in lo.iter_mut().zip(hi) {
*u ^= poly_basis::mul(t, *v);
*v ^= *u;
}
}
};
if half <= BUTTERFLY_GRAIN {
butterfly(lo, hi);
} else {
lo.par_chunks_mut(BUTTERFLY_GRAIN)
.zip(hi.par_chunks_mut(BUTTERFLY_GRAIN))
.for_each(|(lo, hi)| butterfly(lo, hi));
}
});
}
values
.par_iter_mut()
.for_each(|v| *v = poly_basis::to_tower(*v).to_repr());
mat.values = values.into_iter().map(BinaryField128::from_repr).collect();
mat
}
fn shifted_intt_batch(
&self,
mut mat: RowMajorMatrix<BinaryField128>,
shift: BinaryField128,
) -> RowMajorMatrix<BinaryField128> {
if !poly_basis::HAS_HARDWARE_CLMUL {
return self.tower.shifted_intt_batch(mat, shift);
}
let width = mat.width();
let log_n = log2_strict_usize(mat.height());
let mut values: Vec<u128> = core::mem::take(&mut mat.values)
.into_iter()
.map(BinaryField128::to_repr)
.collect();
values
.par_iter_mut()
.for_each(|v| *v = poly_basis::from_tower(BinaryField128::from_repr(*v)));
for j in 0..log_n {
let half = (1 << j) * width;
let base = subspace_polynomial::<BinaryField128>(j, shift);
values
.par_chunks_mut(half << 1)
.enumerate()
.for_each(|(blk, block)| {
let t = twiddle(base, blk);
let zero = t == 0;
let (lo, hi) = block.split_at_mut(half);
let butterfly = |lo: &mut [u128], hi: &mut [u128]| {
if zero {
for (u, v) in lo.iter_mut().zip(hi) {
*v ^= *u;
}
} else {
for (u, v) in lo.iter_mut().zip(hi) {
*v ^= *u;
*u ^= poly_basis::mul(t, *v);
}
}
};
if half <= BUTTERFLY_GRAIN {
butterfly(lo, hi);
} else {
lo.par_chunks_mut(BUTTERFLY_GRAIN)
.zip(hi.par_chunks_mut(BUTTERFLY_GRAIN))
.for_each(|(lo, hi)| butterfly(lo, hi));
}
});
}
values
.par_iter_mut()
.for_each(|v| *v = poly_basis::to_tower(*v).to_repr());
mat.values = values.into_iter().map(BinaryField128::from_repr).collect();
mat
}
}
#[cfg(test)]
mod tests {
use p3_binary_field::{BinaryField128, TowerLevel};
use p3_matrix::dense::RowMajorMatrix;
use proptest::prelude::*;
use super::PolyBasisNtt;
use crate::naive::NaiveAdditiveNtt;
use crate::traits::AdditiveNtt;
fn matrix(log_n: usize, width: usize, seed: u64) -> RowMajorMatrix<BinaryField128> {
RowMajorMatrix::new(
(0..(width << log_n))
.map(|i| {
let bits = seed
.wrapping_mul(0x9e37_79b9_7f4a_7c15)
.wrapping_add(i as u64);
BinaryField128::from_le_byte_iter(bits.to_le_bytes().into_iter().cycle())
})
.collect(),
width,
)
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(32))]
#[test]
fn poly_basis_matches_naive(
log_n in 0usize..=8,
width in 1usize..=5,
seed in any::<u64>(),
shift in any::<u64>(),
) {
let coeffs = matrix(log_n, width, seed);
let shift = BinaryField128::from_le_byte_iter(
shift.to_le_bytes().into_iter().cycle(),
);
let fast = PolyBasisNtt::default().shifted_ntt_batch(coeffs.clone(), shift);
let slow = NaiveAdditiveNtt::<BinaryField128>::default()
.shifted_ntt_batch(coeffs, shift);
prop_assert_eq!(fast, slow);
}
#[test]
fn poly_basis_round_trips(
log_n in 0usize..=8,
width in 1usize..=3,
seed in any::<u64>(),
shift in any::<u64>(),
) {
let coeffs = matrix(log_n, width, seed);
let shift = BinaryField128::from_le_byte_iter(
shift.to_le_bytes().into_iter().cycle(),
);
let ntt = PolyBasisNtt::default();
let evals = ntt.shifted_ntt_batch(coeffs.clone(), shift);
prop_assert_eq!(ntt.shifted_intt_batch(evals, shift), coeffs);
}
#[test]
fn poly_basis_lde_matches_naive(
log_n in 0usize..=6,
added in 0usize..=3,
width in 1usize..=3,
seed in any::<u64>(),
shift in any::<u64>(),
) {
let coeffs = matrix(log_n, width, seed);
let shift = BinaryField128::from_le_byte_iter(
shift.to_le_bytes().into_iter().cycle(),
);
let lde = PolyBasisNtt::default().shifted_lde_batch(coeffs.clone(), added, shift);
let naive = NaiveAdditiveNtt::<BinaryField128>::default()
.shifted_lde_batch(coeffs.clone(), added, shift);
prop_assert_eq!(&lde, &naive);
prop_assert_eq!(&lde.values[..coeffs.values.len()], &coeffs.values[..]);
}
}
#[test]
#[should_panic]
fn shifted_ntt_batch_rejects_a_non_power_of_two_height() {
let coeffs = RowMajorMatrix::new(matrix(0, 1, 0).values.repeat(3), 1);
let _ = PolyBasisNtt::default().ntt_batch(coeffs);
}
}