use p3_binary_field::TowerLevel;
use p3_field::Field;
use p3_multilinear_util::poly::Poly;
use super::basis::{Coefficients, CoordinateSums};
#[derive(Clone, Debug)]
pub(crate) struct FactoredEquality<F> {
outer: Poly<F>,
inner: Poly<F>,
}
impl<F: Field> FactoredEquality<F> {
#[must_use]
pub(crate) fn new(point: &[F], log_block: usize) -> Self {
let inner_variables = log_block.min(point.len());
let (outer, inner) = point.split_at(point.len() - inner_variables);
Self {
outer: Poly::new_from_point(outer, F::ONE),
inner: Poly::new_from_point(inner, F::ONE),
}
}
#[must_use]
pub(crate) fn num_variables(&self) -> usize {
self.outer.num_variables() + self.inner.num_variables()
}
#[must_use]
pub(crate) fn num_evals(&self) -> usize {
self.outer.num_evals() * self.inner.num_evals()
}
#[must_use]
pub(crate) fn block_len(&self) -> usize {
self.inner.num_evals()
}
#[must_use]
pub(crate) fn outer(&self) -> &[F] {
self.outer.as_slice()
}
#[must_use]
pub(crate) fn inner(&self) -> &[F] {
self.inner.as_slice()
}
#[must_use]
pub(crate) fn before_block(&self, index: usize) -> F {
index.checked_sub(1).map_or(F::ZERO, |previous| {
self.outer()[previous] * self.inner()[self.block_len() - 1]
})
}
#[cfg(test)]
pub(crate) fn materialize(&self) -> Poly<F> {
let mut table = Poly::zero(self.num_variables());
for (block, &weight) in table
.as_mut_slice()
.chunks_mut(self.block_len())
.zip(self.outer())
{
for (slot, &inner) in block.iter_mut().zip(self.inner()) {
*slot = weight * inner;
}
}
table
}
}
pub(crate) fn scaled_sums_into<EF: TowerLevel, A: Field>(
out: &mut CoordinateSums<EF, A>,
sums: &CoordinateSums<EF, A>,
scale: EF,
) {
out.overwrite(|coordinate| {
let mut basis = Coefficients::<EF>::zero();
basis.set(coordinate);
sums.sum(scale * basis.element())
});
}
#[cfg(test)]
mod tests {
use alloc::vec::Vec;
use p3_binary_field::BinaryField128;
use p3_field::PrimeCharacteristicRing;
use p3_multilinear_util::point::Point;
use rand::rngs::SmallRng;
use rand::{RngExt, SeedableRng};
use super::*;
type F = BinaryField128;
#[test]
fn the_factors_multiply_to_the_dense_table() {
let mut rng = SmallRng::seed_from_u64(0xFAC7);
for num_variables in 0..10 {
let point = Point::<F>::rand(&mut rng, num_variables);
let dense = Poly::new_from_point(point.as_slice(), F::ONE);
for log_block in 0..=num_variables {
let factored = FactoredEquality::new(point.as_slice(), log_block);
assert_eq!(factored.num_variables(), num_variables);
assert_eq!(factored.num_evals(), dense.num_evals());
assert_eq!(
factored.materialize().as_slice(),
dense.as_slice(),
"{num_variables} variables in blocks of 2^{log_block}"
);
}
}
}
#[test]
fn a_block_wider_than_the_table_is_the_whole_table() {
let mut rng = SmallRng::seed_from_u64(0xB10C);
let point = Point::<F>::rand(&mut rng, 3);
let factored = FactoredEquality::new(point.as_slice(), 9);
assert_eq!(factored.outer(), &[F::ONE]);
assert_eq!(factored.block_len(), 8);
assert_eq!(
factored.inner(),
Poly::new_from_point(point.as_slice(), F::ONE).as_slice()
);
}
#[test]
fn the_entry_before_a_block_is_the_last_entry_of_the_one_before() {
let mut rng = SmallRng::seed_from_u64(0xBEF0);
let point = Point::<F>::rand(&mut rng, 7);
let dense = Poly::new_from_point(point.as_slice(), F::ONE);
let factored = FactoredEquality::new(point.as_slice(), 3);
assert_eq!(factored.before_block(0), F::ZERO);
for index in 1..factored.outer().len() {
assert_eq!(
factored.before_block(index),
dense.as_slice()[index * factored.block_len() - 1],
"block {index}"
);
}
}
#[test]
fn scaled_sums_read_the_scaled_value() {
let mut rng = SmallRng::seed_from_u64(0x5CA1);
let weights = (0..Coefficients::<F>::DIMENSION)
.map(|_| rng.random())
.collect::<Vec<F>>();
let sums = CoordinateSums::new(&weights);
let mut scaled = CoordinateSums::<F, F>::new(&weights);
for scale in [F::ZERO, F::ONE, rng.random(), rng.random()] {
scaled_sums_into(&mut scaled, &sums, scale);
for value in [F::ZERO, F::ONE]
.into_iter()
.chain((0..32).map(|_| rng.random()))
{
assert_eq!(scaled.sum(value), sums.sum(scale * value));
}
}
}
#[test]
fn a_point_of_no_variables_is_the_single_unit_entry() {
let factored = FactoredEquality::<F>::new(&[], 4);
assert_eq!(factored.num_variables(), 0);
assert_eq!(factored.num_evals(), 1);
assert_eq!(factored.materialize().as_slice(), &[F::ONE]);
}
#[test]
fn every_block_reads_the_dense_run_it_covers() {
let mut rng = SmallRng::seed_from_u64(0x8100);
let point = Point::<F>::rand(&mut rng, 8);
let dense = Poly::new_from_point(point.as_slice(), F::ONE);
let factored = FactoredEquality::new(point.as_slice(), 4);
let rebuilt = factored
.outer()
.iter()
.flat_map(|&weight| factored.inner().iter().map(move |&inner| weight * inner))
.collect::<Vec<_>>();
assert_eq!(rebuilt, dense.as_slice());
}
}