use alloc::vec::Vec;
use core::borrow::Borrow;
use p3_binary_field::TowerLevel;
use p3_maybe_rayon::prelude::*;
use p3_multilinear_util::poly::Poly;
use super::basis::Coefficients;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct BitPacking<EF, S = Vec<EF>> {
packed: Poly<EF, S>,
}
pub type BitPackingView<'a, EF> = BitPacking<EF, &'a [EF]>;
impl<EF: TowerLevel> BitPacking<EF> {
pub fn new(bits: &[u8]) -> Result<Self, BitPackingError> {
let stride = EF::NUM_BYTES;
if 8 * stride != Coefficients::<EF>::DIMENSION {
return Err(BitPackingError::SubByteLevel {
bits: Coefficients::<EF>::DIMENSION,
});
}
if !(bits.len() * 8).is_power_of_two() {
return Err(BitPackingError::NotAHypercube {
cells: bits.len() * 8,
});
}
if bits.len() < stride {
return Err(BitPackingError::TooShort {
needed: stride,
actual: bits.len(),
});
}
Ok(Self {
packed: Poly::new(
bits.par_chunks_exact(stride)
.map(|chunk| EF::from_le_byte_iter(chunk.iter().copied()))
.collect::<Vec<_>>(),
),
})
}
pub fn into_poly(self) -> Poly<EF> {
self.packed
}
}
impl<EF: TowerLevel, S: Borrow<[EF]>> BitPacking<EF, S> {
pub fn from_packed(packed: Poly<EF, S>) -> Result<Self, BitPackingError> {
if 8 * EF::NUM_BYTES != Coefficients::<EF>::DIMENSION {
return Err(BitPackingError::SubByteLevel {
bits: Coefficients::<EF>::DIMENSION,
});
}
if !packed.num_evals().is_power_of_two() {
return Err(BitPackingError::NotAHypercube {
cells: packed.num_evals() * Coefficients::<EF>::DIMENSION,
});
}
Ok(Self { packed })
}
pub const fn poly(&self) -> &Poly<EF, S> {
&self.packed
}
pub fn num_variables(&self) -> usize {
self.packed.num_variables()
}
pub fn len(&self) -> usize {
self.packed.num_evals()
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn coefficients(&self, index: usize) -> Coefficients<EF> {
Coefficients::of(self.packed.as_slice()[index])
}
}
#[derive(Debug, thiserror::Error, PartialEq, Eq)]
pub enum BitPackingError {
#[error("a {bits}-bit level cannot pack a byte's worth of cells")]
SubByteLevel {
bits: usize,
},
#[error("a bit witness covers {cells} cells, which is no hypercube")]
NotAHypercube {
cells: usize,
},
#[error("packing needs at least {needed} bytes, got {actual}")]
TooShort {
needed: usize,
actual: usize,
},
}
#[cfg(test)]
mod tests {
use p3_binary_field::{BinaryField4, BinaryField16, BinaryField128, Gf2};
use p3_field::PrimeCharacteristicRing;
use p3_multilinear_util::point::Point;
use rand::rngs::SmallRng;
use rand::{RngExt, SeedableRng};
use super::*;
use crate::univariate_skip::embed_bits;
type EF = BinaryField16;
fn bits(seed: u64, bytes: usize) -> Vec<u8> {
let mut rng = SmallRng::seed_from_u64(seed);
(0..bytes).map(|_| rng.random::<u8>()).collect()
}
#[test]
fn the_coordinates_are_the_cells_in_order() {
let witness = bits(0x9AC, 8);
let packing = BitPacking::<EF>::new(&witness).unwrap();
assert_eq!(packing.len(), 4);
for element in 0..packing.len() {
for (j, bit) in packing.coefficients(element).iter().enumerate() {
let cell = element * Coefficients::<EF>::DIMENSION + j;
let expected = (witness[cell / 8] >> (cell % 8)) & 1 == 1;
assert_eq!(bit, expected, "element={element} coordinate={j}");
}
}
}
#[test]
fn packing_agrees_with_the_one_element_per_bit_embedding() {
let witness = bits(0xE3B, 32);
let embedded = embed_bits::<BinaryField128>(&witness);
let packing = BitPacking::<BinaryField128>::new(&witness).unwrap();
for element in 0..packing.len() {
for (j, bit) in packing.coefficients(element).iter().enumerate() {
let cell = element * Coefficients::<BinaryField128>::DIMENSION + j;
let expected = if bit {
BinaryField128::ONE
} else {
BinaryField128::ZERO
};
assert_eq!(embedded.as_slice()[cell], expected, "cell={cell}");
}
}
}
#[test]
fn the_packing_loses_exactly_the_absorbed_variables() {
let packing = BitPacking::<EF>::new(&[0u8; 32]).unwrap();
assert_eq!(
packing.num_variables(),
8 - Coefficients::<EF>::LOG_DIMENSION
);
}
#[test]
fn the_packing_is_the_bit_planes_read_together() {
let mut rng = SmallRng::seed_from_u64(0x91A2);
let witness = bits(0x91A3, 16);
let packing = BitPacking::<EF>::new(&witness).unwrap();
let point = Point::new(
(0..packing.num_variables())
.map(|_| rng.random::<EF>())
.collect(),
);
let d = Coefficients::<EF>::DIMENSION;
let mut expected = EF::ZERO;
for j in 0..d {
let plane = Poly::new(
(0..packing.len())
.map(|w| {
if packing.coefficients(w).get(j) {
EF::ONE
} else {
EF::ZERO
}
})
.collect::<Vec<_>>(),
);
let mut basis = Coefficients::<EF>::zero();
basis.set(j);
expected += plane.eval_base(&point) * basis.element();
}
assert_eq!(packing.poly().eval_base(&point), expected);
}
#[test]
fn a_level_narrower_than_a_byte_is_refused() {
assert_eq!(
BitPacking::<BinaryField4>::new(&[0x0F, 0x0F]).unwrap_err(),
BitPackingError::SubByteLevel { bits: 4 }
);
assert_eq!(
BitPacking::<Gf2>::new(&[0xFE]).unwrap_err(),
BitPackingError::SubByteLevel { bits: 1 }
);
assert!(BitPacking::<EF>::new(&[0u8; 2]).is_ok());
}
#[test]
fn a_witness_that_is_no_hypercube_is_refused() {
assert_eq!(
BitPacking::<EF>::new(&[0u8; 3]).unwrap_err(),
BitPackingError::NotAHypercube { cells: 24 }
);
}
#[test]
fn a_witness_too_short_for_one_element_is_refused() {
assert_eq!(
BitPacking::<EF>::new(&[0u8; 1]).unwrap_err(),
BitPackingError::TooShort {
needed: 2,
actual: 1
}
);
}
}