use alloc::vec::Vec;
use p3_field::Field;
use p3_maybe_rayon::prelude::*;
use p3_util::log2_strict_usize;
pub(super) fn evals_01inf_grid_into<F: Field>(
boolean_evals: &[F],
output: &mut [F],
scratch: &mut [F],
) {
let num_variables = log2_strict_usize(boolean_evals.len());
let output_len = 3usize.pow(num_variables as u32);
assert_eq!(output.len(), output_len);
assert_eq!(scratch.len(), output_len);
if num_variables == 0 {
output[0] = boolean_evals[0];
return;
}
let (mut cur, mut next) = if num_variables % 2 == 1 {
scratch[..boolean_evals.len()].copy_from_slice(boolean_evals);
(&mut scratch[..], &mut output[..])
} else {
output[..boolean_evals.len()].copy_from_slice(boolean_evals);
(&mut output[..], &mut scratch[..])
};
const PARALLEL_STRIDE_THRESHOLD: usize = 256;
for stage in 0..num_variables {
let in_stride = 3usize.pow(stage as u32);
let blocks = 1usize << (num_variables - stage - 1);
let cur_slice = &cur[..blocks * 2 * in_stride];
let next_slice = &mut next[..blocks * 3 * in_stride];
if in_stride < PARALLEL_STRIDE_THRESHOLD {
cur_slice
.par_chunks(2 * in_stride)
.zip(next_slice.par_chunks_mut(3 * in_stride))
.for_each(|(c_chunk, n_chunk)| {
for j in 0..in_stride {
let f0 = c_chunk[j];
let f1 = c_chunk[in_stride + j];
n_chunk[3 * j] = f0;
n_chunk[3 * j + 1] = f1;
n_chunk[3 * j + 2] = f1 - f0;
}
});
} else {
cur_slice
.chunks(2 * in_stride)
.zip(next_slice.chunks_mut(3 * in_stride))
.for_each(|(c_chunk, n_chunk)| {
let (c_left, c_right) = c_chunk.split_at(in_stride);
c_left
.par_iter()
.zip(c_right.par_iter())
.zip(n_chunk.par_chunks_mut(3))
.for_each(|((&f0, &f1), out)| {
out[0] = f0;
out[1] = f1;
out[2] = f1 - f0;
});
});
}
core::mem::swap(&mut cur, &mut next);
}
}
pub(crate) fn evals_01inf_grid_prefix<F: Field>(evals: &[F]) -> Vec<F> {
fn reverse_ternary_digits(mut idx: usize, l: usize) -> usize {
let mut rev = 0usize;
for _ in 0..l {
rev = 3 * rev + (idx % 3);
idx /= 3;
}
rev
}
let l = log2_strict_usize(evals.len());
let grid_len = 3usize.pow(l as u32);
let mut prefix = F::zero_vec(grid_len);
let mut scratch = F::zero_vec(grid_len);
evals_01inf_grid_into(evals, &mut prefix, &mut scratch);
let mut out = F::zero_vec(grid_len);
for (src_idx, value) in prefix.into_iter().enumerate() {
out[reverse_ternary_digits(src_idx, l)] = value;
}
out
}
#[cfg(test)]
mod test {
use alloc::vec;
use alloc::vec::Vec;
use p3_field::PrimeCharacteristicRing;
use p3_field::extension::BinomialExtensionField;
use p3_koala_bear::KoalaBear;
use proptest::prelude::*;
use rand::rngs::SmallRng;
use rand::{RngExt, SeedableRng};
use super::*;
type F = KoalaBear;
type EF = BinomialExtensionField<F, 4>;
fn evals_01inf_grid(boolean_evals: &[EF]) -> Vec<EF> {
let num_variables = log2_strict_usize(boolean_evals.len());
let output_len = 3usize.pow(num_variables as u32);
let mut output = EF::zero_vec(output_len);
let mut scratch = EF::zero_vec(output_len);
evals_01inf_grid_into(boolean_evals, &mut output, &mut scratch);
output
}
fn assert_evals_01inf_grid_correct(boolean_evals: &[EF]) {
let num_variables = log2_strict_usize(boolean_evals.len());
let grid = evals_01inf_grid(boolean_evals);
let inner_groups = grid.len() / 3;
for g in 0..inner_groups {
let v0 = grid[3 * g];
let v1 = grid[3 * g + 1];
let v_inf = grid[3 * g + 2];
assert_eq!(
v_inf,
v1 - v0,
"f(inf) != f(1)-f(0) at group {g}, num_variables={num_variables}"
);
}
}
#[test]
fn test_evals_01inf_grid_into_zero_vars() {
let c = EF::from_u32(42);
let input = [c];
let mut output = [EF::ZERO];
let mut scratch = [EF::ZERO];
evals_01inf_grid_into(&input, &mut output, &mut scratch);
assert_eq!(output, [c]);
}
#[test]
fn test_evals_01inf_grid_into_one_var() {
let f0 = EF::from_u32(3);
let f1 = EF::from_u32(7);
let input = [f0, f1];
let mut output = [EF::ZERO; 3];
let mut scratch = [EF::ZERO; 3];
evals_01inf_grid_into(&input, &mut output, &mut scratch);
assert_eq!(output[0], f0);
assert_eq!(output[1], f1);
assert_eq!(output[2], EF::from_u32(4));
}
#[test]
fn test_evals_01inf_grid_into_two_vars_hand_computed() {
let input = [1, 3, 5, 11].map(EF::from_u32);
let mut output = [EF::ZERO; 9];
let mut scratch = [EF::ZERO; 9];
evals_01inf_grid_into(&input, &mut output, &mut scratch);
let expected = [1, 5, 4, 3, 11, 8, 2, 6, 4].map(EF::from_u32);
assert_eq!(output, expected);
}
#[test]
fn test_evals_01inf_grid_into_output_size() {
for num_variables in 1..=5 {
let input_len = 1 << num_variables;
let output_len = 3usize.pow(num_variables as u32);
let input = EF::zero_vec(input_len);
let mut output = EF::zero_vec(output_len);
let mut scratch = EF::zero_vec(output_len);
evals_01inf_grid_into(&input, &mut output, &mut scratch);
}
}
#[test]
fn test_evals_01inf_grid_into_result_lands_in_output() {
let mut rng = SmallRng::seed_from_u64(123);
for num_variables in 1..=4 {
let input: Vec<EF> = (0..1 << num_variables).map(|_| rng.random()).collect();
let output_len = 3usize.pow(num_variables as u32);
let mut output = EF::zero_vec(output_len);
let mut scratch = EF::zero_vec(output_len);
evals_01inf_grid_into(&input, &mut output, &mut scratch);
let reference = evals_01inf_grid(&input);
assert_eq!(
output, reference,
"ping-pong mismatch for num_variables={num_variables}"
);
}
}
#[test]
fn test_evals_01inf_grid_into_preserves_boolean_points() {
let mut rng = SmallRng::seed_from_u64(77);
for num_variables in 1..=4 {
let input: Vec<EF> = (0..1 << num_variables).map(|_| rng.random()).collect();
let grid = evals_01inf_grid(&input);
for (bool_idx, &input_val) in input.iter().enumerate() {
let mut bits = Vec::with_capacity(num_variables);
let mut tmp = bool_idx;
for _ in 0..num_variables {
bits.push(tmp & 1);
tmp >>= 1;
}
let mut ternary_idx = 0;
let mut power_of_3 = 1;
for &b in bits.iter().rev() {
ternary_idx += b * power_of_3;
power_of_3 *= 3;
}
assert_eq!(
grid[ternary_idx], input_val,
"Boolean point mismatch at bool_idx={bool_idx}, num_variables={num_variables}"
);
}
}
}
#[test]
fn test_evals_01inf_grid_into_constant_polynomial() {
let c = EF::from_u32(99);
for num_variables in 0..=4 {
let input = vec![c; 1 << num_variables];
let output_len = 3usize.pow(num_variables as u32);
let mut output = EF::zero_vec(output_len);
let mut scratch = EF::zero_vec(output_len);
evals_01inf_grid_into(&input, &mut output, &mut scratch);
for (idx, &val) in output.iter().enumerate() {
let has_inf = {
let mut tmp = idx;
let mut found = false;
for _ in 0..num_variables {
if tmp % 3 == 2 {
found = true;
}
tmp /= 3;
}
found
};
let expected = if has_inf { EF::ZERO } else { c };
assert_eq!(
val, expected,
"constant polynomial mismatch at idx={idx}, num_variables={num_variables}"
);
}
}
}
#[test]
fn test_evals_01inf_grid_into_linearity() {
let mut rng = SmallRng::seed_from_u64(55);
let num_variables = 3;
let n = 1 << num_variables;
let f: Vec<EF> = (0..n).map(|_| rng.random()).collect();
let g: Vec<EF> = (0..n).map(|_| rng.random()).collect();
let a: EF = rng.random();
let b: EF = rng.random();
let combined: Vec<EF> = f
.iter()
.zip(g.iter())
.map(|(&fi, &gi)| a * fi + b * gi)
.collect();
let grid_combined = evals_01inf_grid(&combined);
let grid_f = evals_01inf_grid(&f);
let grid_g = evals_01inf_grid(&g);
let linear_combined: Vec<EF> = grid_f
.iter()
.zip(grid_g.iter())
.map(|(&fi, &gi)| a * fi + b * gi)
.collect();
assert_eq!(grid_combined, linear_combined);
}
#[test]
fn test_evals_01inf_grid_into_large_stride_branch_matches_naive() {
let num_variables = 7;
let mut rng = SmallRng::seed_from_u64(2025);
let evals: Vec<EF> = (0..1 << num_variables).map(|_| rng.random()).collect();
assert_evals_01inf_grid_correct(evals.as_slice());
}
#[test]
#[should_panic(expected = "Not a power of two")]
fn test_evals_01inf_grid_into_panics_on_non_power_of_two_input() {
let input = [EF::ZERO; 3];
let mut output = [EF::ZERO; 3];
let mut scratch = [EF::ZERO; 3];
evals_01inf_grid_into(&input, &mut output, &mut scratch);
}
#[test]
#[should_panic(expected = "assertion `left == right` failed")]
fn test_evals_01inf_grid_into_panics_on_wrong_output_len() {
let input = [EF::ZERO; 4];
let mut output = [EF::ZERO; 8];
let mut scratch = [EF::ZERO; 9];
evals_01inf_grid_into(&input, &mut output, &mut scratch);
}
#[test]
#[should_panic(expected = "assertion `left == right` failed")]
fn test_evals_01inf_grid_into_panics_on_wrong_scratch_len() {
let input = [EF::ZERO; 4];
let mut output = [EF::ZERO; 9];
let mut scratch = [EF::ZERO; 8];
evals_01inf_grid_into(&input, &mut output, &mut scratch);
}
proptest! {
#[test]
fn prop_evals_01inf_grid_matches_naive(num_variables in 1usize..=5) {
let mut rng = SmallRng::seed_from_u64(num_variables as u64);
let evals: Vec<EF> = (0..1 << num_variables).map(|_| rng.random()).collect();
assert_evals_01inf_grid_correct(evals.as_slice());
}
#[test]
fn prop_evals_01inf_grid_preserves_boolean_points(num_variables in 1usize..=6) {
let mut rng = SmallRng::seed_from_u64(num_variables as u64 + 1000);
let input: Vec<EF> = (0..1 << num_variables).map(|_| rng.random()).collect();
let grid = evals_01inf_grid(&input);
for (bool_idx, &input_val) in input.iter().enumerate() {
let mut bits = Vec::with_capacity(num_variables);
let mut tmp = bool_idx;
for _ in 0..num_variables {
bits.push(tmp & 1);
tmp >>= 1;
}
let mut ternary_idx = 0;
let mut power_of_3 = 1;
for &b in bits.iter().rev() {
ternary_idx += b * power_of_3;
power_of_3 *= 3;
}
prop_assert_eq!(grid[ternary_idx], input_val);
}
}
}
}