use alloc::vec::Vec;
use core::marker::PhantomData;
use p3_binary_field::TowerLevel;
use p3_field::Field;
use crate::strategy::FromTable;
const MAX_BYTES: usize = 16;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Coefficients<EF> {
bytes: [u8; MAX_BYTES],
_ef: PhantomData<EF>,
}
impl<EF: TowerLevel> Coefficients<EF> {
pub const DIMENSION: usize = 1 << EF::LOG_BITS;
pub const LOG_DIMENSION: usize = EF::LOG_BITS;
#[must_use]
pub const fn zero() -> Self {
Self {
bytes: [0; MAX_BYTES],
_ef: PhantomData,
}
}
#[must_use]
pub fn of(value: EF) -> Self {
assert!(
EF::NUM_BYTES <= MAX_BYTES,
"the tower level is wider than these coordinates hold"
);
let mut out = Self::zero();
for (slot, byte) in out.bytes.iter_mut().zip(value.into_bytes()) {
*slot = byte;
}
out
}
#[must_use]
pub const fn get(&self, index: usize) -> bool {
(self.bytes[index / 8] >> (index % 8)) & 1 == 1
}
pub const fn set(&mut self, index: usize) {
self.bytes[index / 8] |= 1 << (index % 8);
}
pub fn iter_set(&self) -> impl Iterator<Item = usize> + '_ {
self.bytes[..EF::NUM_BYTES]
.iter()
.enumerate()
.flat_map(|(position, &byte)| SetBits {
rest: byte,
base: position * 8,
})
}
pub fn iter(&self) -> impl Iterator<Item = bool> + '_ {
(0..Self::DIMENSION).map(|index| self.get(index))
}
#[must_use]
pub fn element(&self) -> EF {
EF::from_le_byte_iter(self.bytes.iter().copied())
}
}
#[derive(Clone, Debug)]
pub(crate) struct CoordinateSums<EF, A> {
tables: Vec<[A; 256]>,
_ef: PhantomData<EF>,
}
impl<EF: TowerLevel, A: Field> CoordinateSums<EF, A> {
#[must_use]
pub(crate) fn new(weights: &[A]) -> Self {
assert_eq!(
weights.len(),
Coefficients::<EF>::DIMENSION,
"one weight per coordinate"
);
let mut sums = Self {
tables: alloc::vec![[A::ZERO; 256]; EF::NUM_BYTES],
_ef: PhantomData,
};
sums.overwrite(|coordinate| weights[coordinate]);
sums
}
pub(crate) fn overwrite(&mut self, mut weight: impl FnMut(usize) -> A) {
for (position, slot) in self.tables.iter_mut().enumerate() {
let coordinates: [A; 8] = core::array::from_fn(|bit| {
let coordinate = position * 8 + bit;
if coordinate < Coefficients::<EF>::DIMENSION {
weight(coordinate)
} else {
A::ZERO
}
});
let mut table = [A::ZERO; 256];
for subset in 1..256usize {
let lowest = subset.trailing_zeros() as usize;
table[subset] = table[subset & (subset - 1)] + coordinates[lowest];
}
*slot = table;
}
}
#[inline]
#[must_use]
pub(crate) fn sum(&self, value: EF) -> A {
self.tables
.iter()
.zip(value.into_bytes())
.map(|(table, byte)| table[usize::from(byte)])
.sum()
}
pub(crate) fn apply_into(&self, input: &[EF], output: &mut [A])
where
A: FromTable<EF>,
{
assert_eq!(
input.len(),
output.len(),
"input and output must have equal lengths"
);
#[cfg(all(
target_arch = "x86_64",
target_feature = "gfni",
target_feature = "avx512f",
target_feature = "avx512bw"
))]
{
if Coefficients::<EF>::DIMENSION == 128 {
let images = core::array::from_fn(|coordinate| {
self.tables[coordinate / 8][1 << (coordinate % 8)]
});
if A::try_map_coordinates_into(&images, input, output) {
return;
}
}
}
for (destination, &value) in output.iter_mut().zip(input) {
*destination = self.sum(value);
}
}
}
struct SetBits {
rest: u8,
base: usize,
}
impl Iterator for SetBits {
type Item = usize;
fn next(&mut self) -> Option<usize> {
(self.rest != 0).then(|| {
let bit = self.rest.trailing_zeros() as usize;
self.rest &= self.rest - 1;
self.base + bit
})
}
}
#[cfg(test)]
mod tests {
use alloc::vec;
use alloc::vec::Vec;
use p3_binary_field::{BinaryField16, BinaryField128, Ghash128};
use p3_field::PrimeCharacteristicRing;
use proptest::prelude::*;
use rand::rngs::SmallRng;
use rand::{RngExt, SeedableRng};
use super::*;
fn walked_sum<EF: TowerLevel>(weights: &[EF], value: EF) -> EF {
Coefficients::of(value).iter_set().map(|u| weights[u]).sum()
}
fn coordinate_sums_match_the_walk<EF: TowerLevel>(seed: u64)
where
rand::distr::StandardUniform: rand::distr::Distribution<EF>,
{
let mut rng = SmallRng::seed_from_u64(seed);
let weights = (0..Coefficients::<EF>::DIMENSION)
.map(|_| rng.random())
.collect::<Vec<EF>>();
let sums = CoordinateSums::new(&weights);
for value in [EF::ZERO, EF::ONE]
.into_iter()
.chain((0..64).map(|_| rng.random()))
{
assert_eq!(sums.sum(value), walked_sum(&weights, value));
}
}
#[test]
fn coordinate_sums_are_the_weights_over_the_set_coordinates() {
coordinate_sums_match_the_walk::<BinaryField128>(1);
coordinate_sums_match_the_walk::<BinaryField16>(2);
}
#[test]
fn coordinate_sums_apply_into_matches_the_scalar_sum_for_a_large_batch() {
let mut rng = SmallRng::seed_from_u64(0xA991_5EED);
let weights = (0..Coefficients::<BinaryField128>::DIMENSION)
.map(|_| rng.random::<Ghash128>())
.collect::<Vec<_>>();
let sums = CoordinateSums::<BinaryField128, Ghash128>::new(&weights);
let input = (0..4096 + 63)
.map(|_| rng.random::<BinaryField128>())
.collect::<Vec<_>>();
let mut output = vec![Ghash128::from_repr(u128::MAX); input.len()];
sums.apply_into(&input, &mut output);
let expected = input
.iter()
.map(|&value| sums.sum(value))
.collect::<Vec<_>>();
assert_eq!(output, expected);
}
#[test]
fn coordinate_sums_apply_into_survives_repeated_rescaling() {
use super::super::equality::scaled_sums_into;
let mut rng = SmallRng::seed_from_u64(0x51A1_5CA1);
let weights = (0..Coefficients::<BinaryField128>::DIMENSION)
.map(|_| rng.random::<Ghash128>())
.collect::<Vec<_>>();
let sums = CoordinateSums::<BinaryField128, Ghash128>::new(&weights);
let input = (0..4096 + 17)
.map(|_| rng.random::<BinaryField128>())
.collect::<Vec<_>>();
let mut scaled = sums.clone();
for scale in [
BinaryField128::ZERO,
BinaryField128::ONE,
rng.random(),
rng.random(),
] {
scaled_sums_into(&mut scaled, &sums, scale);
let mut output = vec![Ghash128::ZERO; input.len()];
scaled.apply_into(&input, &mut output);
let expected = input
.iter()
.map(|&value| sums.sum(scale * value))
.collect::<Vec<_>>();
assert_eq!(output, expected);
}
}
#[test]
#[should_panic(expected = "equal lengths")]
fn coordinate_sums_apply_into_rejects_mismatched_shapes() {
let sums = CoordinateSums::<BinaryField128, Ghash128>::new(&[Ghash128::ZERO; 128]);
let input = [BinaryField128::ZERO; 1];
let mut output = [Ghash128::ZERO; 0];
sums.apply_into(&input, &mut output);
}
#[test]
fn a_level_has_one_coordinate_per_bit() {
assert_eq!(Coefficients::<BinaryField128>::DIMENSION, 128);
assert_eq!(Coefficients::<BinaryField128>::LOG_DIMENSION, 7);
assert_eq!(Coefficients::<BinaryField16>::DIMENSION, 16);
assert_eq!(Coefficients::<BinaryField16>::LOG_DIMENSION, 4);
}
#[test]
fn reading_coordinates_and_rebuilding_is_the_identity() {
let mut rng = SmallRng::seed_from_u64(0xB17E);
for _ in 0..64 {
let value = rng.random::<BinaryField128>();
assert_eq!(Coefficients::of(value).element(), value);
}
}
#[test]
fn the_first_coordinate_is_the_coefficient_of_one() {
for coefficients in [
Coefficients::of(BinaryField128::ONE)
.iter()
.collect::<Vec<_>>(),
Coefficients::of(BinaryField16::ONE)
.iter()
.collect::<Vec<_>>(),
] {
assert!(coefficients[0]);
assert!(coefficients[1..].iter().all(|&bit| !bit));
}
}
#[test]
fn the_coordinates_are_a_basis_decomposition() {
let mut rng = SmallRng::seed_from_u64(0xBA515);
for _ in 0..64 {
let value = rng.random::<BinaryField128>();
let mut rebuilt = BinaryField128::ZERO;
for index in Coefficients::of(value).iter_set() {
let mut basis = Coefficients::<BinaryField128>::zero();
basis.set(index);
rebuilt += basis.element();
}
assert_eq!(rebuilt, value);
}
}
#[test]
fn a_narrow_level_reads_none_of_its_padding() {
let mut rng = SmallRng::seed_from_u64(0x9A44);
for _ in 0..64 {
let coefficients = Coefficients::of(rng.random::<BinaryField16>());
assert!(coefficients.iter_set().all(|index| index < 16));
}
}
proptest! {
#[test]
fn the_set_walk_finds_exactly_the_set_coordinates(raw: u128) {
let coefficients = Coefficients::of(BinaryField128::from_repr(raw));
let walked = coefficients.iter_set().collect::<Vec<_>>();
let expected = coefficients
.iter()
.enumerate()
.filter_map(|(index, bit)| bit.then_some(index))
.collect::<Vec<_>>();
prop_assert_eq!(walked, expected);
}
#[test]
fn setting_a_coordinate_is_what_reading_it_back_reports(index in 0usize..16) {
let mut coefficients = Coefficients::<BinaryField16>::zero();
prop_assert!(!coefficients.get(index));
coefficients.set(index);
prop_assert!(coefficients.get(index));
prop_assert_eq!(coefficients.iter_set().collect::<Vec<_>>(), alloc::vec![index]);
}
}
}