use alloc::vec::Vec;
use core::ops::AddAssign;
use p3_binary_field::TowerLevel;
use serde::{Deserialize, Serialize};
use super::basis::Coefficients;
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(
into = "Vec<EF>",
try_from = "Vec<EF>",
bound(serialize = "EF: TowerLevel", deserialize = "EF: TowerLevel")
)]
pub struct BitTensor<EF> {
rows: Vec<EF>,
}
impl<EF: TowerLevel> BitTensor<EF> {
pub const DIMENSION: usize = Coefficients::<EF>::DIMENSION;
#[must_use]
pub fn zero() -> Self {
Self {
rows: alloc::vec![EF::ZERO; Self::DIMENSION],
}
}
#[must_use]
pub fn one() -> Self {
Self::exterior_product(EF::ONE, EF::ONE)
}
pub fn add_exterior_product(&mut self, a: EF, b: EF) {
for u in Coefficients::of(a).iter_set() {
self.rows[u] += b;
}
}
#[must_use]
pub fn exterior_product(a: EF, b: EF) -> Self {
let mut out = Self::zero();
out.add_exterior_product(a, b);
out
}
#[must_use]
pub const fn is_well_formed(&self) -> bool {
self.rows.len() == Self::DIMENSION
}
#[must_use]
pub fn rows(&self) -> &[EF] {
&self.rows
}
#[must_use]
pub fn columns(&self) -> Vec<EF> {
let d = Self::DIMENSION;
let source = self
.rows
.iter()
.map(|&row| Coefficients::of(row))
.collect::<Vec<_>>();
(0..d)
.map(|v| {
let mut column = Coefficients::<EF>::zero();
for (u, row) in source.iter().enumerate() {
if row.get(v) {
column.set(u);
}
}
column.element()
})
.collect()
}
pub fn scale_rows(&mut self, b: EF) {
for row in &mut self.rows {
*row *= b;
}
}
pub fn scale_columns(&mut self, a: EF) {
let mut scaled = Self::zero();
scaled.add_scaled_columns(self, a);
*self = scaled;
}
pub fn add_scaled_columns(&mut self, other: &Self, a: EF) {
for (u, &row) in other.rows.iter().enumerate() {
if row != EF::ZERO {
let mut basis = Coefficients::<EF>::zero();
basis.set(u);
self.add_exterior_product(a * basis.element(), row);
}
}
}
pub fn mul_equality_factor(&mut self, a: EF, b: EF) {
let mut first = self.clone();
first.scale_columns(a);
let mut second = self.clone();
second.scale_rows(b);
*self += first;
*self += second;
}
#[must_use]
pub fn successor_element(point: &[EF], other: &[EF]) -> Self {
assert_eq!(
point.len(),
other.len(),
"the successor element pairs one coordinate of each point"
);
let (mut carry_a, mut carry_b) = (EF::ONE, EF::ONE);
let mut done = Self::zero();
for (&a, &b) in point.iter().zip(other).rev() {
done.mul_equality_factor(a, b);
done.add_exterior_product(carry_a * (EF::ONE + a), carry_b * b);
carry_a *= a;
carry_b *= EF::ONE + b;
}
done
}
#[must_use]
pub fn column(&self, v: usize) -> EF {
let mut column = Coefficients::<EF>::zero();
for (u, &row) in self.rows.iter().enumerate() {
if Coefficients::of(row).get(v) {
column.set(u);
}
}
column.element()
}
}
#[derive(Clone, Debug)]
pub(crate) struct BitTensorBuckets<EF> {
buckets: Vec<[EF; 256]>,
}
impl<EF: TowerLevel> BitTensorBuckets<EF> {
pub(crate) fn zero() -> Self {
Self {
buckets: alloc::vec![[EF::ZERO; 256]; EF::NUM_BYTES],
}
}
#[inline]
pub(crate) fn add_exterior_product(&mut self, a: EF, b: EF) {
for (bucket, byte) in self.buckets.iter_mut().zip(a.into_bytes()) {
bucket[usize::from(byte)] += b;
}
}
pub(crate) fn clear(&mut self) {
for bucket in &mut self.buckets {
bucket.fill(EF::ZERO);
}
}
pub(crate) fn tensor(&self) -> BitTensor<EF> {
let mut tensor = BitTensor::zero();
for (position, bucket) in self.buckets.iter().enumerate() {
for (value, &sum) in bucket.iter().enumerate() {
let mut bits = value as u8;
while bits != 0 {
let bit = bits.trailing_zeros() as usize;
bits &= bits - 1;
if let Some(row) = tensor.rows.get_mut(position * 8 + bit) {
*row += sum;
}
}
}
}
tensor
}
}
impl<EF: TowerLevel> AddAssign<&Self> for BitTensor<EF> {
fn add_assign(&mut self, rhs: &Self) {
for (row, &other) in self.rows.iter_mut().zip(&rhs.rows) {
*row += other;
}
}
}
impl<EF: TowerLevel> AddAssign for BitTensor<EF> {
fn add_assign(&mut self, rhs: Self) {
*self += &rhs;
}
}
impl<EF> From<BitTensor<EF>> for Vec<EF> {
fn from(value: BitTensor<EF>) -> Self {
value.rows
}
}
impl<EF: TowerLevel> TryFrom<Vec<EF>> for BitTensor<EF> {
type Error = MalformedBitTensor;
fn try_from(rows: Vec<EF>) -> Result<Self, Self::Error> {
if rows.len() == Self::DIMENSION {
Ok(Self { rows })
} else {
Err(MalformedBitTensor {
expected: Self::DIMENSION,
actual: rows.len(),
})
}
}
}
#[derive(Debug, thiserror::Error, PartialEq, Eq)]
#[error("a bit tensor element carries {actual} rows, expected {expected}")]
pub struct MalformedBitTensor {
pub expected: usize,
pub actual: usize,
}
#[cfg(test)]
mod tests {
use p3_binary_field::{BinaryField16, BinaryField128, Gf2};
use p3_field::PrimeCharacteristicRing;
use p3_multilinear_util::point::Point;
use p3_multilinear_util::poly::Poly;
use proptest::prelude::*;
use rand::rngs::SmallRng;
use rand::{RngExt, SeedableRng};
use super::*;
type EF = BinaryField16;
#[test]
fn the_buckets_hold_the_sum_of_the_exterior_products() {
let mut rng = SmallRng::seed_from_u64(0xB0C5);
let terms = (0..32)
.map(|_| (rng.random::<EF>(), rng.random::<EF>()))
.collect::<Vec<_>>();
let mut buckets = BitTensorBuckets::<EF>::zero();
let mut tensor = BitTensor::<EF>::zero();
for &(a, b) in &terms {
buckets.add_exterior_product(a, b);
tensor.add_exterior_product(a, b);
}
assert_eq!(buckets.tensor(), tensor);
}
#[test]
fn a_level_narrower_than_its_byte_reads_its_own_rows_back() {
let mut buckets = BitTensorBuckets::<Gf2>::zero();
buckets.add_exterior_product(Gf2::ONE, Gf2::ONE);
assert_eq!(
buckets.tensor(),
BitTensor::exterior_product(Gf2::ONE, Gf2::ONE)
);
}
fn element(seed: u64) -> BitTensor<EF> {
let mut rng = SmallRng::seed_from_u64(seed);
let mut out = BitTensor::zero();
for _ in 0..8 {
out.add_exterior_product(rng.random(), rng.random());
}
out
}
fn transpose(element: &BitTensor<EF>) -> BitTensor<EF> {
BitTensor::try_from(element.columns()).unwrap()
}
#[test]
fn the_matrix_is_square_in_the_fields_dimension() {
assert_eq!(BitTensor::<BinaryField16>::DIMENSION, 16);
assert_eq!(BitTensor::<BinaryField128>::DIMENSION, 128);
assert_eq!(BitTensor::<BinaryField16>::zero().rows().len(), 16);
}
#[test]
fn an_exterior_product_is_the_outer_product_of_the_coordinates() {
let mut rng = SmallRng::seed_from_u64(0x0117);
for _ in 0..32 {
let (a, b) = (rng.random::<EF>(), rng.random::<EF>());
let product = BitTensor::exterior_product(a, b);
let (left, right) = (Coefficients::of(a), Coefficients::of(b));
for (u, row) in product.rows().iter().enumerate() {
for (v, entry) in Coefficients::of(*row).iter().enumerate() {
assert_eq!(entry, left.get(u) && right.get(v), "entry ({u}, {v})");
}
}
}
}
#[test]
fn the_identity_is_one_tensor_one() {
assert_eq!(
BitTensor::<EF>::one(),
BitTensor::exterior_product(EF::ONE, EF::ONE)
);
}
#[test]
fn transposing_twice_is_the_identity() {
let original = element(0x7A5);
assert_eq!(transpose(&transpose(&original)), original);
}
#[test]
fn the_column_reading_transposes_the_row_reading() {
let original = element(0xC01);
let columns = original.columns();
for (u, row) in original.rows().iter().enumerate() {
let row_bits = Coefficients::of(*row);
for (v, column) in columns.iter().enumerate() {
assert_eq!(
Coefficients::of(*column).get(u),
row_bits.get(v),
"entry ({u}, {v})"
);
}
}
}
#[test]
fn scaling_the_rows_scales_every_row_reading() {
let mut scaled = element(0x505);
let before = scaled.rows().to_vec();
let b = SmallRng::seed_from_u64(0x506).random::<EF>();
scaled.scale_rows(b);
for (after, &original) in scaled.rows().iter().zip(&before) {
assert_eq!(*after, original * b);
}
}
fn the_columns_scale_at<F: TowerLevel>(seed: u64)
where
rand::distr::StandardUniform: rand::distr::Distribution<F>,
{
let mut rng = SmallRng::seed_from_u64(seed);
let mut scaled = BitTensor::<F>::zero();
for _ in 0..8 {
scaled.add_exterior_product(rng.random(), rng.random());
}
let before = scaled.columns();
let a = rng.random::<F>();
scaled.scale_columns(a);
for (after, &original) in scaled.columns().iter().zip(&before) {
assert_eq!(*after, original * a);
}
}
#[test]
fn scaling_the_columns_scales_every_column_reading() {
the_columns_scale_at::<BinaryField16>(0xC015);
the_columns_scale_at::<BinaryField128>(0xC017);
}
#[test]
fn adding_a_scaled_element_scales_its_column_reading() {
let mut rng = SmallRng::seed_from_u64(0x5CA7);
let a = rng.random::<EF>();
let mut expected = element(0x5CA8);
let mut scaled = element(0x5CA9);
scaled.scale_columns(a);
expected += scaled;
let mut accumulated = element(0x5CA8);
accumulated.add_scaled_columns(&element(0x5CA9), a);
assert_eq!(accumulated, expected);
}
#[test]
fn the_two_scalings_commute() {
let original = element(0xC0119);
let mut rng = SmallRng::seed_from_u64(0xC011A);
let (a, b) = (rng.random::<EF>(), rng.random::<EF>());
let mut rows_first = original.clone();
rows_first.scale_rows(b);
rows_first.scale_columns(a);
let mut columns_first = original;
columns_first.scale_columns(a);
columns_first.scale_rows(b);
assert_eq!(rows_first, columns_first);
}
#[test]
fn the_equality_factor_is_agree_plus_disagree() {
let mut rng = SmallRng::seed_from_u64(0xE0F);
for _ in 0..8 {
let original = element(rng.random());
let (a, b) = (rng.random::<EF>(), rng.random::<EF>());
let mut agree = original.clone();
agree.scale_columns(a);
agree.scale_rows(b);
let mut disagree = original.clone();
disagree.scale_columns(EF::ONE + a);
disagree.scale_rows(EF::ONE + b);
agree += disagree;
let mut factored = original;
factored.mul_equality_factor(a, b);
assert_eq!(factored, agree);
}
}
#[test]
fn the_successor_element_matches_its_hypercube_definition() {
let mut rng = SmallRng::seed_from_u64(0x5CC);
for num_variables in 0..6 {
let point = Point::<EF>::rand(&mut rng, num_variables);
let other = Point::<EF>::rand(&mut rng, num_variables);
let eq_point = Poly::<EF>::new_from_point(point.as_slice(), EF::ONE);
let eq_other = Poly::<EF>::new_from_point(other.as_slice(), EF::ONE);
let mut expected = BitTensor::zero();
for x in 0..1usize << num_variables {
let row = Point::<EF>::hypercube(x, num_variables);
let (_, done, _) = Point::eval_next(point.as_slice(), row.as_slice());
let closed = if x == 0 {
EF::ZERO
} else {
eq_point.as_slice()[x - 1]
};
assert_eq!(done, closed, "{num_variables} variables, row {x}");
expected.add_exterior_product(done, eq_other.as_slice()[x]);
}
assert_eq!(
BitTensor::successor_element(point.as_slice(), other.as_slice()),
expected,
"{num_variables} variables"
);
}
}
#[test]
fn one_column_is_that_column_of_the_transpose() {
let original = element(0xC07);
let columns = original.columns();
for (v, &column) in columns.iter().enumerate() {
assert_eq!(original.column(v), column, "column {v}");
}
}
#[test]
fn a_wrong_row_count_is_refused() {
assert_eq!(
BitTensor::<EF>::try_from(alloc::vec![EF::ZERO; 15]).unwrap_err(),
MalformedBitTensor {
expected: 16,
actual: 15,
}
);
assert!(BitTensor::<EF>::try_from(alloc::vec![EF::ZERO; 16]).is_ok());
assert!(BitTensor::<EF>::zero().is_well_formed());
}
#[test]
fn the_wire_form_round_trips() {
let original = element(0x5E4);
let encoded = serde_json::to_string(&original).unwrap();
let decoded: BitTensor<EF> = serde_json::from_str(&encoded).unwrap();
assert_eq!(decoded, original);
}
proptest! {
#[test]
fn addition_is_entrywise(a: u16, b: u16, c: u16, d: u16) {
let mut left = BitTensor::exterior_product(EF::from_repr(a), EF::from_repr(b));
let right = BitTensor::exterior_product(EF::from_repr(c), EF::from_repr(d));
let expected: Vec<EF> = left
.rows()
.iter()
.zip(right.rows())
.map(|(&x, &y)| x + y)
.collect();
left += &right;
prop_assert_eq!(left.rows(), expected.as_slice());
}
}
}