use alloc::vec::Vec;
use p3_field::{BasedVectorSpace, ExtensionField, Field};
use p3_maybe_rayon::prelude::*;
use p3_multilinear_util::point::Point;
use p3_multilinear_util::poly::Poly;
use p3_util::log2_strict_usize;
use super::tensor::TensorAlgebra;
pub fn packed_vars<F: Field, EF: ExtensionField<F>>() -> usize {
let d = <EF as BasedVectorSpace<F>>::DIMENSION;
assert!(
d.is_power_of_two(),
"ring switching needs a power-of-two extension degree, got {d}"
);
log2_strict_usize(d)
}
pub fn pack<F: Field, EF: ExtensionField<F>>(t: Poly<F>) -> Poly<EF> {
let kappa = packed_vars::<F, EF>();
assert!(
t.num_variables() >= kappa,
"packing needs at least the {kappa} variables one packed element absorbs, got {}",
t.num_variables()
);
Poly::new(<EF as BasedVectorSpace<F>>::reconstitute_from_base(
t.into_evals(),
))
}
#[cfg(not(test))]
const S_HAT_CHUNK: usize = 1 << 12;
#[cfg(test)]
const S_HAT_CHUNK: usize = 1 << 1;
pub fn compute_s_hat<F: Field, EF: ExtensionField<F>>(
packed: &Poly<EF>,
r_high: &Point<EF>,
) -> TensorAlgebra<F, EF> {
compute_s_hat_with_eq::<F, EF>(
packed,
&Poly::<EF>::new_from_point(r_high.as_slice(), EF::ONE),
)
}
pub(crate) fn compute_s_hat_with_eq<F: Field, EF: ExtensionField<F>>(
packed: &Poly<EF>,
eq: &Poly<EF>,
) -> TensorAlgebra<F, EF> {
assert_eq!(
eq.num_variables(),
packed.num_variables(),
"r_high must name exactly the packed polynomial's variables"
);
let partials: Vec<TensorAlgebra<F, EF>> = eq
.as_slice()
.par_chunks(S_HAT_CHUNK)
.zip(packed.as_slice().par_chunks(S_HAT_CHUNK))
.map(|(eq_chunk, packed_chunk)| {
let mut acc = TensorAlgebra::zero();
for (&e, &p) in eq_chunk.iter().zip(packed_chunk) {
acc.add_exterior_product(e, p);
}
acc
})
.collect();
partials
.into_iter()
.fold(TensorAlgebra::zero(), |mut acc, partial| {
acc += partial;
acc
})
}
#[cfg(test)]
mod tests {
use p3_binary_field::{BinaryField8, BinaryField16};
use p3_field::PrimeCharacteristicRing;
use rand::SeedableRng;
use rand::distr::{Distribution, StandardUniform};
use rand::rngs::SmallRng;
use super::*;
use crate::ring_switch::test_util::{EF, F, base_poly};
#[test]
fn packing_consumes_the_suffix_variables() {
let t = base_poly(6, 1);
let packed = pack::<F, EF>(t.clone());
assert_eq!(packed.num_variables(), 6 - 4);
for (w, e) in packed.as_slice().iter().enumerate() {
for j in 0..16 {
assert_eq!(
BasedVectorSpace::<F>::as_basis_coefficients_slice(e)[j],
t.as_slice()[16 * w + j]
);
}
}
}
fn check_columns_are_the_partial_evaluations<G, EG>(ell: usize, seed: u64)
where
G: Field,
EG: ExtensionField<G>,
StandardUniform: Distribution<G> + Distribution<EG>,
{
let kappa = packed_vars::<G, EG>();
let mut rng = SmallRng::seed_from_u64(seed);
let t = Poly::<G>::rand(&mut rng, ell);
let packed = pack::<G, EG>(t.clone());
let r = Point::<EG>::rand(&mut rng, ell);
let (r_high, r_low) = r.split_at(ell - kappa);
let cols = compute_s_hat::<G, EG>(&packed, &r_high).columns();
for (v, col) in cols.iter().enumerate() {
let mut point = r_high.clone();
point.extend(&Point::<EG>::hypercube(v, kappa));
assert_eq!(*col, t.eval_base(&point), "column {v}");
}
let combined: EG = cols
.iter()
.enumerate()
.map(|(v, col)| {
Point::eval_eq(
Point::<EG>::hypercube(v, kappa).as_slice(),
r_low.as_slice(),
) * *col
})
.sum();
assert_eq!(combined, t.eval_base(&r));
}
#[test]
fn s_hat_columns_are_the_partial_evaluations() {
check_columns_are_the_partial_evaluations::<F, EF>(6, 3);
}
#[test]
fn s_hat_columns_are_the_partial_evaluations_at_kappa_one() {
check_columns_are_the_partial_evaluations::<BinaryField8, BinaryField16>(4, 3);
}
#[test]
fn the_packed_evaluation_is_lossy() {
let ell = 6;
let t = base_poly(ell, 4);
let packed = pack::<F, EF>(t.clone());
let r = Point::<EF>::rand(&mut SmallRng::seed_from_u64(5), ell);
let (r_high, _) = r.split_at(ell - 4);
let cols = compute_s_hat::<F, EF>(&packed, &r_high).columns();
let beta_combination: EF = cols
.iter()
.enumerate()
.map(|(v, col)| *col * EF::from_basis_coefficients_fn(|i| F::from_bool(i == v)))
.sum();
assert_eq!(beta_combination, packed.eval_ext::<F>(&r_high));
assert_ne!(beta_combination, t.eval_base(&r));
}
#[test]
#[should_panic(expected = "packing needs at least the 4 variables")]
fn packing_rejects_a_polynomial_shorter_than_one_packed_element() {
let _ = pack::<F, EF>(base_poly(2, 6));
}
}