use alloc::vec;
use alloc::vec::Vec;
use core::marker::PhantomData;
use core::ops::AddAssign;
use p3_field::{BasedVectorSpace, ExtensionField, Field};
use serde::{Deserialize, Serialize};
use super::RingSwitchError;
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(
into = "Vec<F>",
try_from = "Vec<F>",
bound(
serialize = "F: Field, EF: ExtensionField<F>",
deserialize = "F: Field, EF: ExtensionField<F>"
)
)]
pub struct TensorAlgebra<F, EF> {
coeffs: Vec<F>,
_marker: PhantomData<EF>,
}
impl<F: Field, EF: ExtensionField<F>> TensorAlgebra<F, EF> {
pub const DIMENSION: usize = <EF as BasedVectorSpace<F>>::DIMENSION;
pub fn zero() -> Self {
Self {
coeffs: vec![F::ZERO; Self::DIMENSION * Self::DIMENSION],
_marker: PhantomData,
}
}
pub fn one() -> Self {
Self::exterior_product(EF::ONE, EF::ONE)
}
pub fn add_exterior_product(&mut self, a: EF, b: EF) {
let d = Self::DIMENSION;
let a_coeffs = a.as_basis_coefficients_slice();
let b_coeffs = b.as_basis_coefficients_slice();
for (u, &au) in a_coeffs.iter().enumerate() {
for (v, &bv) in b_coeffs.iter().enumerate() {
self.coeffs[u * d + v] += au * bv;
}
}
}
pub fn exterior_product(a: EF, b: EF) -> Self {
let d = Self::DIMENSION;
let a_coeffs = a.as_basis_coefficients_slice();
let b_coeffs = b.as_basis_coefficients_slice();
let mut coeffs = vec![F::ZERO; d * d];
for u in 0..d {
for v in 0..d {
coeffs[u * d + v] = a_coeffs[u] * b_coeffs[v];
}
}
Self {
coeffs,
_marker: PhantomData,
}
}
pub const fn is_well_formed(&self) -> bool {
self.coeffs.len() == Self::DIMENSION * Self::DIMENSION
}
pub fn coefficients(&self) -> &[F] {
&self.coeffs
}
#[cfg(test)]
pub(crate) fn perturb_for_test(&mut self, index: usize, delta: F) {
self.coeffs[index] += delta;
}
#[cfg(test)]
pub(crate) fn truncate_for_test(&mut self, len: usize) {
self.coeffs.truncate(len);
}
pub fn columns(&self) -> Vec<EF> {
let d = Self::DIMENSION;
(0..d)
.map(|v| EF::from_basis_coefficients_fn(|u| self.coeffs[u * d + v]))
.collect()
}
pub fn rows(&self) -> Vec<EF> {
self.rows_iter().collect()
}
pub fn rows_iter(&self) -> impl Iterator<Item = EF> + '_ {
let d = Self::DIMENSION;
(0..d).map(move |u| EF::from_basis_coefficients_fn(|v| self.coeffs[u * d + v]))
}
pub fn scale_columns(&mut self, a: EF) {
let d = Self::DIMENSION;
for v in 0..d {
let scaled = EF::from_basis_coefficients_fn(|u| self.coeffs[u * d + v]) * a;
for (u, coeff) in scaled.as_basis_coefficients_slice().iter().enumerate() {
self.coeffs[u * d + v] = *coeff;
}
}
}
pub fn scale_rows(&mut self, a: EF) {
let d = Self::DIMENSION;
for u in 0..d {
let scaled = EF::from_basis_coefficients_fn(|v| self.coeffs[u * d + v]) * a;
for (v, coeff) in scaled.as_basis_coefficients_slice().iter().enumerate() {
self.coeffs[u * d + v] = *coeff;
}
}
}
}
impl<F: Field, EF: ExtensionField<F>> TryFrom<Vec<F>> for TensorAlgebra<F, EF> {
type Error = RingSwitchError;
fn try_from(coeffs: Vec<F>) -> Result<Self, Self::Error> {
let expected = Self::DIMENSION * Self::DIMENSION;
if coeffs.len() == expected {
Ok(Self {
coeffs,
_marker: PhantomData,
})
} else {
Err(RingSwitchError::MalformedTensor {
expected,
actual: coeffs.len(),
})
}
}
}
impl<F: Field, EF: ExtensionField<F>> From<TensorAlgebra<F, EF>> for Vec<F> {
fn from(tensor: TensorAlgebra<F, EF>) -> Self {
tensor.coeffs
}
}
impl<F: Field, EF> AddAssign for TensorAlgebra<F, EF> {
fn add_assign(&mut self, rhs: Self) {
debug_assert_eq!(
self.coeffs.len(),
rhs.coeffs.len(),
"adding tensor elements of different shapes would truncate to the shorter one"
);
for (c, r) in self.coeffs.iter_mut().zip(rhs.coeffs) {
*c += r;
}
}
}
#[cfg(test)]
mod tests {
use alloc::vec;
use p3_binary_field::TowerLevel;
use p3_field::{BasedVectorSpace, PrimeCharacteristicRing};
use super::TensorAlgebra;
use crate::ring_switch::RingSwitchError;
use crate::ring_switch::test_util::{EF, F};
#[test]
fn exterior_product_readings_are_the_scaled_operands() {
let a = EF::from_repr(0x0123_4567_89ab_cdef_fedc_ba98_7654_3210);
let b = EF::from_repr(0x1111_2222_3333_4444_5555_6666_7777_8888);
let t = TensorAlgebra::<F, EF>::exterior_product(a, b);
for (v, col) in t.columns().iter().enumerate() {
assert_eq!(
*col,
a * EF::from(BasedVectorSpace::<F>::as_basis_coefficients_slice(&b)[v])
);
}
for (u, row) in t.rows().iter().enumerate() {
assert_eq!(
*row,
b * EF::from(BasedVectorSpace::<F>::as_basis_coefficients_slice(&a)[u])
);
}
}
#[test]
fn column_and_row_scaling_commute() {
let a = EF::from_repr(0xdead_beef_0000_0000_0000_0000_cafe_babe);
let b = EF::from_repr(0x0f0e_0d0c_0b0a_0908_0706_0504_0302_0100);
let (x, y) = (EF::from_repr(7), EF::from_repr(11));
let mut lhs = TensorAlgebra::<F, EF>::exterior_product(a, b);
lhs.scale_columns(x);
lhs.scale_rows(y);
let mut rhs = TensorAlgebra::<F, EF>::exterior_product(a, b);
rhs.scale_rows(y);
rhs.scale_columns(x);
assert_eq!(lhs, rhs);
assert_eq!(lhs, TensorAlgebra::<F, EF>::exterior_product(a * x, b * y));
}
#[test]
fn one_is_the_exterior_product_of_ones() {
let t = TensorAlgebra::<F, EF>::one();
assert_eq!(
t,
TensorAlgebra::<F, EF>::exterior_product(EF::ONE, EF::ONE)
);
assert_eq!(t.columns()[0], EF::ONE);
assert!(t.columns()[1..].iter().all(|c| *c == EF::ZERO));
assert_eq!(t.rows()[0], EF::ONE);
assert!(t.rows()[1..].iter().all(|r| *r == EF::ZERO));
}
#[test]
fn a_wrong_length_coefficient_vector_is_rejected() {
let dimension = TensorAlgebra::<F, EF>::DIMENSION;
let expected = dimension * dimension;
let well_formed = TensorAlgebra::<F, EF>::try_from(vec![F::ZERO; expected]).unwrap();
assert!(well_formed.is_well_formed());
assert_eq!(
TensorAlgebra::<F, EF>::try_from(vec![F::ZERO; expected - 1]),
Err(RingSwitchError::MalformedTensor {
expected,
actual: expected - 1,
})
);
}
#[test]
fn serialization_round_trips() {
let t = TensorAlgebra::<F, EF>::exterior_product(EF::from_repr(3), EF::from_repr(5));
let bytes = serde_json::to_vec(&t).unwrap();
let decoded: TensorAlgebra<F, EF> = serde_json::from_slice(&bytes).unwrap();
assert_eq!(t, decoded);
}
#[test]
fn deserializing_a_short_coefficient_vector_fails() {
let bytes = serde_json::to_vec(&vec![F::ZERO; 1]).unwrap();
assert!(serde_json::from_slice::<TensorAlgebra<F, EF>>(&bytes).is_err());
}
}