#![doc = include_str!("../README.md")]
#![no_std]
extern crate alloc;
use alloc::vec::Vec;
use core::fmt::{Debug, Display, Formatter};
use core::ops::Deref;
use itertools::Itertools;
use p3_field::{
BasedVectorSpace, ExtensionField, Field, FieldArray, PackedField, PackedFieldExtension,
PackedValue, PrimeCharacteristicRing,
};
use p3_maybe_rayon::PARALLEL_ENABLED;
use p3_maybe_rayon::prelude::*;
use strided::{VerticallyStridedMatrixView, VerticallyStridedRowIndexMap};
use tracing::instrument;
use crate::dense::RowMajorMatrix;
pub mod bitrev;
pub mod dense;
pub mod extension;
pub mod horizontally_truncated;
pub mod interpolation;
pub mod row_index_mapped;
pub mod stack;
pub mod strided;
pub mod util;
#[derive(Copy, Clone, PartialEq, Eq)]
pub struct Dimensions {
pub width: usize,
pub height: usize,
}
impl Debug for Dimensions {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
write!(f, "{}x{}", self.width, self.height)
}
}
impl Display for Dimensions {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
write!(f, "{}x{}", self.width, self.height)
}
}
pub trait Matrix<T: Send + Sync + Clone>: Send + Sync {
fn width(&self) -> usize;
fn height(&self) -> usize;
fn dimensions(&self) -> Dimensions {
Dimensions {
width: self.width(),
height: self.height(),
}
}
#[inline]
fn get(&self, r: usize, c: usize) -> Option<T> {
(r < self.height() && c < self.width()).then(|| unsafe {
self.get_unchecked(r, c)
})
}
#[inline]
unsafe fn get_unchecked(&self, r: usize, c: usize) -> T {
unsafe { self.row_slice_unchecked(r)[c].clone() }
}
#[inline]
fn row(
&self,
r: usize,
) -> Option<impl IntoIterator<Item = T, IntoIter = impl Iterator<Item = T> + Send + Sync>> {
(r < self.height()).then(|| unsafe {
self.row_unchecked(r)
})
}
#[inline]
unsafe fn row_unchecked(
&self,
r: usize,
) -> impl IntoIterator<Item = T, IntoIter = impl Iterator<Item = T> + Send + Sync> {
unsafe { self.row_subseq_unchecked(r, 0, self.width()) }
}
#[inline]
unsafe fn row_subseq_unchecked(
&self,
r: usize,
start: usize,
end: usize,
) -> impl IntoIterator<Item = T, IntoIter = impl Iterator<Item = T> + Send + Sync> {
unsafe {
self.row_unchecked(r)
.into_iter()
.skip(start)
.take(end - start)
}
}
#[inline]
fn row_slice(&self, r: usize) -> Option<impl Deref<Target = [T]>> {
(r < self.height()).then(|| unsafe {
self.row_slice_unchecked(r)
})
}
#[inline]
unsafe fn row_slice_unchecked(&self, r: usize) -> impl Deref<Target = [T]> {
unsafe { self.row_subslice_unchecked(r, 0, self.width()) }
}
#[inline]
unsafe fn row_subslice_unchecked(
&self,
r: usize,
start: usize,
end: usize,
) -> impl Deref<Target = [T]> {
unsafe {
self.row_subseq_unchecked(r, start, end)
.into_iter()
.collect_vec()
}
}
#[inline]
fn rows(&self) -> impl Iterator<Item = impl Iterator<Item = T>> + Send + Sync {
unsafe {
(0..self.height()).map(move |r| self.row_unchecked(r).into_iter())
}
}
#[inline]
fn par_rows(
&self,
) -> impl IndexedParallelIterator<Item = impl Iterator<Item = T>> + Send + Sync {
unsafe {
(0..self.height())
.into_par_iter()
.map(move |r| self.row_unchecked(r).into_iter())
}
}
fn wrapping_row_slices(&self, r: usize, c: usize) -> Vec<impl Deref<Target = [T]>> {
unsafe {
(0..c)
.map(|i| self.row_slice_unchecked((r + i) % self.height()))
.collect_vec()
}
}
#[inline]
fn first_row(
&self,
) -> Option<impl IntoIterator<Item = T, IntoIter = impl Iterator<Item = T> + Send + Sync>> {
self.row(0)
}
#[inline]
fn last_row(
&self,
) -> Option<impl IntoIterator<Item = T, IntoIter = impl Iterator<Item = T> + Send + Sync>> {
if self.height() == 0 {
None
} else {
unsafe { Some(self.row_unchecked(self.height() - 1)) }
}
}
fn to_row_major_matrix(self) -> RowMajorMatrix<T>
where
Self: Sized,
T: Clone,
{
RowMajorMatrix::new(self.rows().flatten().collect(), self.width())
}
fn horizontally_packed_row<'a, P>(
&'a self,
r: usize,
) -> (
impl Iterator<Item = P> + Send + Sync,
impl Iterator<Item = T> + Send + Sync,
)
where
P: PackedValue<Value = T>,
T: Clone + 'a,
{
assert!(r < self.height(), "Row index out of bounds.");
let num_packed = self.width() / P::WIDTH;
unsafe {
let mut iter = self
.row_subseq_unchecked(r, 0, num_packed * P::WIDTH)
.into_iter();
let packed =
(0..num_packed).map(move |_| P::from_fn(|_| iter.next().unwrap_unchecked()));
let sfx = self
.row_subseq_unchecked(r, num_packed * P::WIDTH, self.width())
.into_iter();
(packed, sfx)
}
}
fn padded_horizontally_packed_row<'a, P>(
&'a self,
r: usize,
) -> impl Iterator<Item = P> + Send + Sync
where
P: PackedValue<Value = T>,
T: Clone + Default + 'a,
{
let mut row_iter = self.row(r).expect("Row index out of bounds.").into_iter();
let num_elems = self.width().div_ceil(P::WIDTH);
(0..num_elems).map(move |_| P::from_fn(|_| row_iter.next().unwrap_or_default()))
}
fn par_horizontally_packed_rows<'a, P>(
&'a self,
) -> impl IndexedParallelIterator<
Item = (
impl Iterator<Item = P> + Send + Sync,
impl Iterator<Item = T> + Send + Sync,
),
>
where
P: PackedValue<Value = T>,
T: Clone + 'a,
{
(0..self.height())
.into_par_iter()
.map(|r| self.horizontally_packed_row(r))
}
fn par_padded_horizontally_packed_rows<'a, P>(
&'a self,
) -> impl IndexedParallelIterator<Item = impl Iterator<Item = P> + Send + Sync>
where
P: PackedValue<Value = T>,
T: Clone + Default + 'a,
{
(0..self.height())
.into_par_iter()
.map(|r| self.padded_horizontally_packed_row(r))
}
#[inline]
fn vertically_packed_row<P>(&self, r: usize) -> impl Iterator<Item = P>
where
T: Copy,
P: PackedValue<Value = T>,
{
let rows = self.wrapping_row_slices(r, P::WIDTH);
(0..self.width()).map(move |c| P::from_fn(|i| rows[i][c]))
}
#[inline]
fn vertically_packed_row_pair<P>(&self, r: usize, step: usize) -> Vec<P>
where
T: Copy,
P: PackedValue<Value = T>,
{
let rows = self.wrapping_row_slices(r, P::WIDTH);
let next_rows = self.wrapping_row_slices(r + step, P::WIDTH);
(0..self.width())
.map(|c| P::from_fn(|i| rows[i][c]))
.chain((0..self.width()).map(|c| P::from_fn(|i| next_rows[i][c])))
.collect_vec()
}
fn vertically_strided(self, stride: usize, offset: usize) -> VerticallyStridedMatrixView<Self>
where
Self: Sized,
{
VerticallyStridedRowIndexMap::new_view(self, stride, offset)
}
#[instrument(level = "debug", skip_all, fields(dims = %self.dimensions()))]
fn columnwise_dot_product<EF>(&self, v: &[EF]) -> Vec<EF>
where
T: Field,
EF: ExtensionField<T>,
{
assert_eq!(
v.len(),
self.height(),
"weight count must match matrix height"
);
const SMALL_ELEMS: usize = 256;
if self.height().saturating_mul(self.width()) <= SMALL_ELEMS {
let mut acc = EF::zero_vec(self.width());
for (row, &scale) in self.rows().zip(v) {
for (l, r) in acc.iter_mut().zip(row) {
*l += scale * r;
}
}
return acc;
}
let packed_width = self.width().div_ceil(T::Packing::WIDTH);
const SERIAL_PACKED_ELEMS: usize = 4096;
const SERIAL_PACKED_COEFF_OPS: usize = 512;
if T::Packing::WIDTH > 1
&& self.height() > 1
&& self.height().saturating_mul(self.width()) <= SERIAL_PACKED_ELEMS
&& self
.height()
.saturating_mul(packed_width)
.saturating_mul(EF::DIMENSION)
<= SERIAL_PACKED_COEFF_OPS
&& PARALLEL_ENABLED
&& current_num_threads() > 1
{
return serial_packed_columnwise_dot_product(self, v);
}
let packed_result = self
.par_padded_horizontally_packed_rows::<T::Packing>()
.zip(v)
.par_fold_reduce(
|| EF::ExtensionPacking::zero_vec(packed_width),
|mut acc, (row, &scale)| {
let scale: EF::ExtensionPacking = scale.into();
acc.iter_mut().zip(row).for_each(|(l, r)| *l += scale * r);
acc
},
|mut acc_l, acc_r| {
acc_l.iter_mut().zip(&acc_r).for_each(|(l, r)| *l += *r);
acc_l
},
);
EF::ExtensionPacking::to_ext_iter(packed_result)
.take(self.width())
.collect()
}
#[instrument(level = "debug", skip_all, fields(dims = %self.dimensions()))]
fn columnwise_dot_product_batched<EF, const N: usize>(
&self,
vs: &[FieldArray<EF, N>],
) -> Vec<FieldArray<EF, N>>
where
T: Field,
EF: ExtensionField<T>,
{
assert_eq!(vs.len(), self.height());
let packed_width = self.width().div_ceil(T::Packing::WIDTH);
let height = self.height();
let row_bytes = columnwise_row_bytes::<T, EF>(self.width(), N);
let chunk_rows = height
.div_ceil((4 * current_num_threads()).clamp(1, height.max(1)))
.max(min_task_len(height, row_bytes));
let num_chunks = height.div_ceil(chunk_rows);
let packed_results: Vec<EF::ExtensionPacking> =
(0..num_chunks).into_par_iter().par_fold_reduce(
|| EF::ExtensionPacking::zero_vec(packed_width * N),
|mut acc, chunk| {
let rows = chunk * chunk_rows..((chunk + 1) * chunk_rows).min(height);
T::batched_columnwise_dot_product::<EF, _, _, N>(
&mut acc,
rows.map(|r| {
(
self.padded_horizontally_packed_row::<T::Packing>(r),
vs[r].0,
)
}),
);
acc
},
|mut acc_l, acc_r| {
acc_l.iter_mut().zip(&acc_r).for_each(|(lj, rj)| *lj += *rj);
acc_l
},
);
packed_results
.chunks(N)
.flat_map(|chunk| {
(0..T::Packing::WIDTH)
.map(move |lane| FieldArray::from_fn(|j| chunk[j].extract(lane)))
})
.take(self.width())
.collect()
}
fn rowwise_packed_dot_product<EF>(
&self,
vec: &[EF::ExtensionPacking],
) -> impl IndexedParallelIterator<Item = EF>
where
T: Field,
EF: ExtensionField<T>,
{
assert!(vec.len() >= self.width().div_ceil(T::Packing::WIDTH));
self.par_padded_horizontally_packed_rows::<T::Packing>()
.map(move |row_packed| {
let d = <EF::ExtensionPacking as BasedVectorSpace<T::Packing>>::DIMENSION;
let coeff_accs = T::Packing::coeffwise_dot_product(
d,
vec.iter()
.zip(row_packed)
.map(|(v, r)| (v.as_basis_coefficients_slice(), r)),
);
let packed_result =
EF::ExtensionPacking::from_basis_coefficients_fn(|i| coeff_accs[i]);
EF::ExtensionPacking::to_ext_iter([packed_result]).sum()
})
}
}
const COLUMNWISE_MAC_WIDTHS: usize = 7;
const COLUMNWISE_MAC_LANES: usize = 16;
const fn columnwise_row_bytes<T, EF>(width: usize, weight_vectors: usize) -> usize
where
T: Field,
EF: ExtensionField<T>,
{
let columns = width.div_ceil(T::Packing::WIDTH) * T::Packing::WIDTH;
columns * size_of::<T>()
+ weight_vectors * size_of::<EF>()
+ (COLUMNWISE_MAC_WIDTHS * weight_vectors * columns * size_of::<EF>())
.div_ceil(COLUMNWISE_MAC_LANES)
}
#[inline(never)]
fn serial_packed_columnwise_dot_product<F, EF, M>(matrix: &M, weights: &[EF]) -> Vec<EF>
where
F: Field,
EF: ExtensionField<F>,
M: Matrix<F> + ?Sized,
{
let mut acc = EF::ExtensionPacking::zero_vec(matrix.width().div_ceil(F::Packing::WIDTH));
F::batched_columnwise_dot_product::<EF, _, _, 1>(
&mut acc,
(0..matrix.height()).map(|r| {
(
matrix.padded_horizontally_packed_row::<F::Packing>(r),
[weights[r]],
)
}),
);
EF::ExtensionPacking::to_ext_iter(acc)
.take(matrix.width())
.collect()
}
#[cfg(test)]
mod tests {
use alloc::vec::Vec;
use alloc::{format, vec};
use itertools::izip;
use p3_baby_bear::BabyBear;
use p3_field::PrimeCharacteristicRing;
use p3_field::extension::{BinomialExtensionField, CubicTrinomialExtensionField};
use p3_goldilocks::Goldilocks;
use p3_mersenne_31::{Mersenne31, QM31};
use rand::SeedableRng;
use rand::rngs::SmallRng;
use super::*;
use crate::bitrev::BitReversibleMatrix;
use crate::extension::FlatMatrixView;
fn patterned_matrix<F: Field>(height: usize, width: usize) -> RowMajorMatrix<F> {
RowMajorMatrix::new(
(0..height * width)
.map(|i| F::from_usize((i * 17 + 3) % 127))
.collect(),
width,
)
}
fn patterned_extension_matrix<F, EF>(height: usize, width: usize) -> RowMajorMatrix<EF>
where
F: Field,
EF: ExtensionField<F>,
{
RowMajorMatrix::new(
(0..height * width)
.map(|i| {
EF::from_basis_coefficients_fn(|d| {
F::from_usize((i * EF::DIMENSION + d + 1) % 127)
})
})
.collect(),
width,
)
}
fn assert_columnwise_dot_product_matches_scalar<F, EF, M>(mat: &M)
where
F: Field,
EF: ExtensionField<F>,
M: Matrix<F>,
{
let weights: Vec<EF> = (0..mat.height())
.map(|r| {
EF::from_basis_coefficients_fn(|d| F::from_usize((r * EF::DIMENSION + d + 5) % 127))
})
.collect();
let expected: Vec<EF> = (0..mat.width())
.map(|c| {
(0..mat.height())
.map(|r| weights[r] * mat.get(r, c).unwrap())
.sum()
})
.collect();
assert_eq!(mat.columnwise_dot_product(&weights), expected);
}
fn assert_columnwise_dot_product_grid<F, EF>()
where
F: Field,
EF: ExtensionField<F>,
{
for height in [0, 1, 17, 32, 128, 1024] {
for width in [1, 3, 8, 17, 65] {
let mat = patterned_matrix::<F>(height, width);
assert_columnwise_dot_product_matches_scalar::<F, EF, _>(&mat);
}
}
}
#[test]
fn test_columnwise_dot_product() {
type F = BabyBear;
type EF = BinomialExtensionField<BabyBear, 4>;
let mut rng = SmallRng::seed_from_u64(1);
let m = RowMajorMatrix::<F>::rand(&mut rng, 1 << 8, 1 << 4);
let v = RowMajorMatrix::<EF>::rand(&mut rng, 1 << 8, 1).values;
let mut expected = EF::zero_vec(m.width());
for (row, &scale) in izip!(m.rows(), &v) {
for (l, r) in izip!(&mut expected, row) {
*l += scale * r;
}
}
assert_eq!(m.columnwise_dot_product(&v), expected);
}
#[test]
fn test_columnwise_dot_product_small_height() {
type F = BabyBear;
type EF = BinomialExtensionField<BabyBear, 4>;
let mut rng = SmallRng::seed_from_u64(2);
for height in [0, 1, 3, 16, 17] {
let m = RowMajorMatrix::<F>::rand(&mut rng, height, 1 << 4);
let v = RowMajorMatrix::<EF>::rand(&mut rng, height, 1).values;
let mut expected = EF::zero_vec(m.width());
for (row, &scale) in izip!(m.rows(), &v) {
for (l, r) in izip!(&mut expected, row) {
*l += scale * r;
}
}
assert_eq!(m.columnwise_dot_product(&v), expected, "height = {height}");
}
}
#[test]
fn test_columnwise_dot_product_matches_scalar_across_extension_fields() {
type BabyBear4 = BinomialExtensionField<BabyBear, 4>;
type BabyBear5 = BinomialExtensionField<BabyBear, 5>;
type Goldilocks2 = BinomialExtensionField<Goldilocks, 2>;
type Goldilocks3 = CubicTrinomialExtensionField<Goldilocks>;
type Mersenne31_3 = BinomialExtensionField<Mersenne31, 3>;
assert_columnwise_dot_product_grid::<BabyBear, BabyBear4>();
assert_columnwise_dot_product_grid::<BabyBear, BabyBear5>();
assert_columnwise_dot_product_grid::<Goldilocks, Goldilocks2>();
assert_columnwise_dot_product_grid::<Goldilocks, Goldilocks3>();
assert_columnwise_dot_product_grid::<Mersenne31, QM31>();
assert_columnwise_dot_product_grid::<Mersenne31, Mersenne31_3>();
}
#[test]
fn test_columnwise_dot_product_matches_scalar_for_matrix_views() {
type BabyBear4 = BinomialExtensionField<BabyBear, 4>;
type Goldilocks3 = CubicTrinomialExtensionField<Goldilocks>;
type Mersenne31_3 = BinomialExtensionField<Mersenne31, 3>;
let mapped = patterned_matrix::<BabyBear>(32, 17).bit_reverse_rows();
assert_columnwise_dot_product_matches_scalar::<BabyBear, BabyBear4, _>(&mapped);
let flat = FlatMatrixView::<Goldilocks, Goldilocks3, _>::new(patterned_extension_matrix::<
Goldilocks,
Goldilocks3,
>(32, 3));
assert_columnwise_dot_product_matches_scalar::<Goldilocks, Goldilocks3, _>(&flat);
let mapped_flat = FlatMatrixView::<Mersenne31, Mersenne31_3, _>::new(
patterned_extension_matrix::<Mersenne31, Mersenne31_3>(128, 3).bit_reverse_rows(),
);
assert_columnwise_dot_product_matches_scalar::<Mersenne31, Mersenne31_3, _>(&mapped_flat);
}
#[test]
#[should_panic(expected = "weight count must match matrix height")]
fn test_columnwise_dot_product_rejects_short_weights() {
let mat = patterned_matrix::<BabyBear>(17, 17);
let weights = BinomialExtensionField::<BabyBear, 4>::zero_vec(16);
let _ = mat.columnwise_dot_product(&weights);
}
#[test]
#[should_panic(expected = "weight count must match matrix height")]
fn test_columnwise_dot_product_rejects_long_weights() {
let mat = patterned_matrix::<BabyBear>(17, 17);
let weights = BinomialExtensionField::<BabyBear, 4>::zero_vec(18);
let _ = mat.columnwise_dot_product(&weights);
}
#[test]
fn test_columnwise_dot_product_batched() {
type F = BabyBear;
type EF = BinomialExtensionField<BabyBear, 4>;
let mut rng = SmallRng::seed_from_u64(1);
let m = RowMajorMatrix::<F>::rand(&mut rng, 1 << 8, 1 << 4);
let v1 = RowMajorMatrix::<EF>::rand(&mut rng, 1 << 8, 1).values;
let v2 = RowMajorMatrix::<EF>::rand(&mut rng, 1 << 8, 1).values;
let expected1 = m.columnwise_dot_product(&v1);
let expected2 = m.columnwise_dot_product(&v2);
let vs: Vec<FieldArray<EF, 2>> = v1
.into_iter()
.zip(v2)
.map(|(a, b)| FieldArray([a, b]))
.collect();
let results = m.columnwise_dot_product_batched::<EF, 2>(&vs);
let result1: Vec<EF> = results.iter().map(|r| r[0]).collect();
let result2: Vec<EF> = results.iter().map(|r| r[1]).collect();
assert_eq!(result1, expected1);
assert_eq!(result2, expected2);
}
struct MockMatrix {
data: Vec<Vec<u32>>,
width: usize,
height: usize,
}
impl Matrix<u32> for MockMatrix {
fn width(&self) -> usize {
self.width
}
fn height(&self) -> usize {
self.height
}
unsafe fn row_unchecked(
&self,
r: usize,
) -> impl IntoIterator<Item = u32, IntoIter = impl Iterator<Item = u32> + Send + Sync>
{
self.data[r].clone()
}
}
#[test]
fn test_dimensions() {
let dims = Dimensions {
width: 3,
height: 5,
};
assert_eq!(dims.width, 3);
assert_eq!(dims.height, 5);
assert_eq!(format!("{dims:?}"), "3x5");
assert_eq!(format!("{dims}"), "3x5");
}
#[test]
fn test_mock_matrix_dimensions() {
let matrix = MockMatrix {
data: vec![vec![1, 2, 3], vec![4, 5, 6], vec![7, 8, 9]],
width: 3,
height: 3,
};
assert_eq!(matrix.width(), 3);
assert_eq!(matrix.height(), 3);
assert_eq!(
matrix.dimensions(),
Dimensions {
width: 3,
height: 3
}
);
}
#[test]
fn test_first_row() {
let matrix = MockMatrix {
data: vec![vec![1, 2, 3], vec![4, 5, 6], vec![7, 8, 9]],
width: 3,
height: 3,
};
let mut first_row = matrix.first_row().unwrap().into_iter();
assert_eq!(first_row.next(), Some(1));
assert_eq!(first_row.next(), Some(2));
assert_eq!(first_row.next(), Some(3));
}
#[test]
fn test_last_row() {
let matrix = MockMatrix {
data: vec![vec![1, 2, 3], vec![4, 5, 6], vec![7, 8, 9]],
width: 3,
height: 3,
};
let mut last_row = matrix.last_row().unwrap().into_iter();
assert_eq!(last_row.next(), Some(7));
assert_eq!(last_row.next(), Some(8));
assert_eq!(last_row.next(), Some(9));
}
#[test]
fn test_first_last_row_empty_matrix() {
let matrix = MockMatrix {
data: vec![],
width: 3,
height: 0,
};
let first_row = matrix.first_row();
let last_row = matrix.last_row();
assert!(first_row.is_none());
assert!(last_row.is_none());
}
#[test]
fn test_to_row_major_matrix() {
let matrix = MockMatrix {
data: vec![vec![1, 2], vec![3, 4]],
width: 2,
height: 2,
};
let row_major = matrix.to_row_major_matrix();
assert_eq!(row_major.values, vec![1, 2, 3, 4]);
assert_eq!(row_major.width, 2);
}
#[test]
fn test_matrix_get_methods() {
let matrix = MockMatrix {
data: vec![vec![1, 2, 3], vec![4, 5, 6], vec![7, 8, 9]],
width: 3,
height: 3,
};
assert_eq!(matrix.get(0, 0), Some(1));
assert_eq!(matrix.get(1, 2), Some(6));
assert_eq!(matrix.get(2, 1), Some(8));
unsafe {
assert_eq!(matrix.get_unchecked(0, 1), 2);
assert_eq!(matrix.get_unchecked(1, 0), 4);
assert_eq!(matrix.get_unchecked(2, 2), 9);
}
assert_eq!(matrix.get(3, 0), None); assert_eq!(matrix.get(0, 3), None); }
#[test]
fn test_matrix_row_methods_iteration() {
let matrix = MockMatrix {
data: vec![vec![1, 2, 3], vec![4, 5, 6], vec![7, 8, 9]],
width: 3,
height: 3,
};
let mut row_iter = matrix.row(1).unwrap().into_iter();
assert_eq!(row_iter.next(), Some(4));
assert_eq!(row_iter.next(), Some(5));
assert_eq!(row_iter.next(), Some(6));
assert_eq!(row_iter.next(), None);
unsafe {
let mut row_iter_unchecked = matrix.row_unchecked(2).into_iter();
assert_eq!(row_iter_unchecked.next(), Some(7));
assert_eq!(row_iter_unchecked.next(), Some(8));
assert_eq!(row_iter_unchecked.next(), Some(9));
assert_eq!(row_iter_unchecked.next(), None);
let mut row_iter_subset = matrix.row_subseq_unchecked(0, 1, 3).into_iter();
assert_eq!(row_iter_subset.next(), Some(2));
assert_eq!(row_iter_subset.next(), Some(3));
assert_eq!(row_iter_subset.next(), None);
}
assert!(matrix.row(3).is_none()); }
#[test]
fn test_row_slice_methods() {
let matrix = MockMatrix {
data: vec![vec![1, 2, 3], vec![4, 5, 6], vec![7, 8, 9]],
width: 3,
height: 3,
};
let row_slice = matrix.row_slice(1).unwrap();
assert_eq!(*row_slice, [4, 5, 6]);
unsafe {
let row_slice_unchecked = matrix.row_slice_unchecked(2);
assert_eq!(*row_slice_unchecked, [7, 8, 9]);
let row_subslice = matrix.row_subslice_unchecked(0, 1, 2);
assert_eq!(*row_subslice, [2]);
}
assert!(matrix.row_slice(3).is_none()); }
#[test]
fn test_matrix_rows() {
let matrix = MockMatrix {
data: vec![vec![1, 2, 3], vec![4, 5, 6], vec![7, 8, 9]],
width: 3,
height: 3,
};
let all_rows: Vec<Vec<u32>> = matrix.rows().map(|row| row.collect()).collect();
assert_eq!(all_rows, vec![vec![1, 2, 3], vec![4, 5, 6], vec![7, 8, 9]]);
}
#[test]
fn test_rowwise_packed_dot_product() {
use p3_field::PackedFieldExtension;
type F = BabyBear;
type EF = BinomialExtensionField<BabyBear, 4>;
type PF = <F as p3_field::Field>::Packing;
type EFPacked = <EF as p3_field::ExtensionField<F>>::ExtensionPacking;
let mut rng = SmallRng::seed_from_u64(42);
for (height, width) in [(32, 16), (64, 128), (128, 17), (256, 255)] {
let m = RowMajorMatrix::<F>::rand(&mut rng, height, width);
let v = RowMajorMatrix::<EF>::rand(&mut rng, width, 1).values;
let expected: Vec<EF> = m
.rows()
.map(|row| {
row.into_iter()
.zip(v.iter())
.map(|(r, &ve)| ve * r)
.sum::<EF>()
})
.collect();
let packed_v: Vec<EFPacked> = v
.chunks(<PF as PackedValue>::WIDTH)
.map(|chunk| {
let mut padded = EF::zero_vec(<PF as PackedValue>::WIDTH);
padded[..chunk.len()].copy_from_slice(chunk);
EFPacked::from_ext_slice(&padded)
})
.collect();
let result: Vec<EF> = m.rowwise_packed_dot_product::<EF>(&packed_v).collect();
assert_eq!(result, expected, "Mismatch for matrix {}x{}", height, width);
}
}
}