1#![doc = include_str!("../README.md")]
2#![no_std]
3
4extern crate alloc;
5
6use alloc::vec::Vec;
7use core::fmt::{Debug, Display, Formatter};
8use core::ops::Deref;
9
10use itertools::Itertools;
11use p3_field::{
12 BasedVectorSpace, ExtensionField, Field, FieldArray, PackedField, PackedFieldExtension,
13 PackedValue, PrimeCharacteristicRing,
14};
15use p3_maybe_rayon::PARALLEL_ENABLED;
16use p3_maybe_rayon::prelude::*;
17use strided::{VerticallyStridedMatrixView, VerticallyStridedRowIndexMap};
18use tracing::instrument;
19
20use crate::dense::RowMajorMatrix;
21
22pub mod bitrev;
23pub mod dense;
24pub mod extension;
25pub mod horizontally_truncated;
26pub mod interpolation;
27pub mod row_index_mapped;
28pub mod stack;
29pub mod strided;
30pub mod util;
31
32#[derive(Copy, Clone, PartialEq, Eq)]
37pub struct Dimensions {
38 pub width: usize,
40 pub height: usize,
42}
43
44impl Debug for Dimensions {
45 fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
46 write!(f, "{}x{}", self.width, self.height)
47 }
48}
49
50impl Display for Dimensions {
51 fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
52 write!(f, "{}x{}", self.width, self.height)
53 }
54}
55
56pub trait Matrix<T: Send + Sync + Clone>: Send + Sync {
62 fn width(&self) -> usize;
64
65 fn height(&self) -> usize;
67
68 fn dimensions(&self) -> Dimensions {
70 Dimensions {
71 width: self.width(),
72 height: self.height(),
73 }
74 }
75
76 #[inline]
87 fn get(&self, r: usize, c: usize) -> Option<T> {
88 (r < self.height() && c < self.width()).then(|| unsafe {
89 self.get_unchecked(r, c)
91 })
92 }
93
94 #[inline]
102 unsafe fn get_unchecked(&self, r: usize, c: usize) -> T {
103 unsafe { self.row_slice_unchecked(r)[c].clone() }
104 }
105
106 #[inline]
112 fn row(
113 &self,
114 r: usize,
115 ) -> Option<impl IntoIterator<Item = T, IntoIter = impl Iterator<Item = T> + Send + Sync>> {
116 (r < self.height()).then(|| unsafe {
117 self.row_unchecked(r)
119 })
120 }
121
122 #[inline]
132 unsafe fn row_unchecked(
133 &self,
134 r: usize,
135 ) -> impl IntoIterator<Item = T, IntoIter = impl Iterator<Item = T> + Send + Sync> {
136 unsafe { self.row_subseq_unchecked(r, 0, self.width()) }
137 }
138
139 #[inline]
149 unsafe fn row_subseq_unchecked(
150 &self,
151 r: usize,
152 start: usize,
153 end: usize,
154 ) -> impl IntoIterator<Item = T, IntoIter = impl Iterator<Item = T> + Send + Sync> {
155 unsafe {
156 self.row_unchecked(r)
157 .into_iter()
158 .skip(start)
159 .take(end - start)
160 }
161 }
162
163 #[inline]
167 fn row_slice(&self, r: usize) -> Option<impl Deref<Target = [T]>> {
168 (r < self.height()).then(|| unsafe {
169 self.row_slice_unchecked(r)
171 })
172 }
173
174 #[inline]
182 unsafe fn row_slice_unchecked(&self, r: usize) -> impl Deref<Target = [T]> {
183 unsafe { self.row_subslice_unchecked(r, 0, self.width()) }
184 }
185
186 #[inline]
196 unsafe fn row_subslice_unchecked(
197 &self,
198 r: usize,
199 start: usize,
200 end: usize,
201 ) -> impl Deref<Target = [T]> {
202 unsafe {
203 self.row_subseq_unchecked(r, start, end)
204 .into_iter()
205 .collect_vec()
206 }
207 }
208
209 #[inline]
211 fn rows(&self) -> impl Iterator<Item = impl Iterator<Item = T>> + Send + Sync {
212 unsafe {
213 (0..self.height()).map(move |r| self.row_unchecked(r).into_iter())
215 }
216 }
217
218 #[inline]
220 fn par_rows(
221 &self,
222 ) -> impl IndexedParallelIterator<Item = impl Iterator<Item = T>> + Send + Sync {
223 unsafe {
224 (0..self.height())
226 .into_par_iter()
227 .map(move |r| self.row_unchecked(r).into_iter())
228 }
229 }
230
231 fn wrapping_row_slices(&self, r: usize, c: usize) -> Vec<impl Deref<Target = [T]>> {
234 unsafe {
235 (0..c)
237 .map(|i| self.row_slice_unchecked((r + i) % self.height()))
238 .collect_vec()
239 }
240 }
241
242 #[inline]
246 fn first_row(
247 &self,
248 ) -> Option<impl IntoIterator<Item = T, IntoIter = impl Iterator<Item = T> + Send + Sync>> {
249 self.row(0)
250 }
251
252 #[inline]
256 fn last_row(
257 &self,
258 ) -> Option<impl IntoIterator<Item = T, IntoIter = impl Iterator<Item = T> + Send + Sync>> {
259 if self.height() == 0 {
260 None
261 } else {
262 unsafe { Some(self.row_unchecked(self.height() - 1)) }
264 }
265 }
266
267 fn to_row_major_matrix(self) -> RowMajorMatrix<T>
269 where
270 Self: Sized,
271 T: Clone,
272 {
273 RowMajorMatrix::new(self.rows().flatten().collect(), self.width())
274 }
275
276 fn horizontally_packed_row<'a, P>(
284 &'a self,
285 r: usize,
286 ) -> (
287 impl Iterator<Item = P> + Send + Sync,
288 impl Iterator<Item = T> + Send + Sync,
289 )
290 where
291 P: PackedValue<Value = T>,
292 T: Clone + 'a,
293 {
294 assert!(r < self.height(), "Row index out of bounds.");
295 let num_packed = self.width() / P::WIDTH;
296 unsafe {
297 let mut iter = self
299 .row_subseq_unchecked(r, 0, num_packed * P::WIDTH)
300 .into_iter();
301
302 let packed =
304 (0..num_packed).map(move |_| P::from_fn(|_| iter.next().unwrap_unchecked()));
305
306 let sfx = self
307 .row_subseq_unchecked(r, num_packed * P::WIDTH, self.width())
308 .into_iter();
309 (packed, sfx)
310 }
311 }
312
313 fn padded_horizontally_packed_row<'a, P>(
320 &'a self,
321 r: usize,
322 ) -> impl Iterator<Item = P> + Send + Sync
323 where
324 P: PackedValue<Value = T>,
325 T: Clone + Default + 'a,
326 {
327 let mut row_iter = self.row(r).expect("Row index out of bounds.").into_iter();
328 let num_elems = self.width().div_ceil(P::WIDTH);
329 (0..num_elems).map(move |_| P::from_fn(|_| row_iter.next().unwrap_or_default()))
331 }
332
333 fn par_horizontally_packed_rows<'a, P>(
338 &'a self,
339 ) -> impl IndexedParallelIterator<
340 Item = (
341 impl Iterator<Item = P> + Send + Sync,
342 impl Iterator<Item = T> + Send + Sync,
343 ),
344 >
345 where
346 P: PackedValue<Value = T>,
347 T: Clone + 'a,
348 {
349 (0..self.height())
350 .into_par_iter()
351 .map(|r| self.horizontally_packed_row(r))
352 }
353
354 fn par_padded_horizontally_packed_rows<'a, P>(
358 &'a self,
359 ) -> impl IndexedParallelIterator<Item = impl Iterator<Item = P> + Send + Sync>
360 where
361 P: PackedValue<Value = T>,
362 T: Clone + Default + 'a,
363 {
364 (0..self.height())
365 .into_par_iter()
366 .map(|r| self.padded_horizontally_packed_row(r))
367 }
368
369 #[inline]
375 fn vertically_packed_row<P>(&self, r: usize) -> impl Iterator<Item = P>
376 where
377 T: Copy,
378 P: PackedValue<Value = T>,
379 {
380 let rows = self.wrapping_row_slices(r, P::WIDTH);
382
383 (0..self.width()).map(move |c| P::from_fn(|i| rows[i][c]))
385 }
386
387 #[inline]
395 fn vertically_packed_row_pair<P>(&self, r: usize, step: usize) -> Vec<P>
396 where
397 T: Copy,
398 P: PackedValue<Value = T>,
399 {
400 let rows = self.wrapping_row_slices(r, P::WIDTH);
405 let next_rows = self.wrapping_row_slices(r + step, P::WIDTH);
406
407 (0..self.width())
408 .map(|c| P::from_fn(|i| rows[i][c]))
409 .chain((0..self.width()).map(|c| P::from_fn(|i| next_rows[i][c])))
410 .collect_vec()
411 }
412
413 fn vertically_strided(self, stride: usize, offset: usize) -> VerticallyStridedMatrixView<Self>
417 where
418 Self: Sized,
419 {
420 VerticallyStridedRowIndexMap::new_view(self, stride, offset)
421 }
422
423 #[instrument(level = "debug", skip_all, fields(dims = %self.dimensions()))]
427 fn columnwise_dot_product<EF>(&self, v: &[EF]) -> Vec<EF>
428 where
429 T: Field,
430 EF: ExtensionField<T>,
431 {
432 assert_eq!(
433 v.len(),
434 self.height(),
435 "weight count must match matrix height"
436 );
437
438 const SMALL_ELEMS: usize = 256;
443 if self.height().saturating_mul(self.width()) <= SMALL_ELEMS {
444 let mut acc = EF::zero_vec(self.width());
445 for (row, &scale) in self.rows().zip(v) {
446 for (l, r) in acc.iter_mut().zip(row) {
447 *l += scale * r;
448 }
449 }
450 return acc;
451 }
452
453 let packed_width = self.width().div_ceil(T::Packing::WIDTH);
454
455 const SERIAL_PACKED_ELEMS: usize = 4096;
467 const SERIAL_PACKED_COEFF_OPS: usize = 512;
468 if T::Packing::WIDTH > 1
469 && self.height() > 1
470 && self.height().saturating_mul(self.width()) <= SERIAL_PACKED_ELEMS
471 && self
472 .height()
473 .saturating_mul(packed_width)
474 .saturating_mul(EF::DIMENSION)
475 <= SERIAL_PACKED_COEFF_OPS
476 && PARALLEL_ENABLED
477 && current_num_threads() > 1
478 {
479 return serial_packed_columnwise_dot_product(self, v);
480 }
481
482 let packed_result = self
483 .par_padded_horizontally_packed_rows::<T::Packing>()
484 .zip(v)
485 .par_fold_reduce(
486 || EF::ExtensionPacking::zero_vec(packed_width),
487 |mut acc, (row, &scale)| {
488 let scale: EF::ExtensionPacking = scale.into();
489 acc.iter_mut().zip(row).for_each(|(l, r)| *l += scale * r);
490 acc
491 },
492 |mut acc_l, acc_r| {
493 acc_l.iter_mut().zip(&acc_r).for_each(|(l, r)| *l += *r);
494 acc_l
495 },
496 );
497
498 EF::ExtensionPacking::to_ext_iter(packed_result)
499 .take(self.width())
500 .collect()
501 }
502
503 #[instrument(level = "debug", skip_all, fields(dims = %self.dimensions()))]
510 fn columnwise_dot_product_batched<EF, const N: usize>(
511 &self,
512 vs: &[FieldArray<EF, N>],
513 ) -> Vec<FieldArray<EF, N>>
514 where
515 T: Field,
516 EF: ExtensionField<T>,
517 {
518 assert_eq!(vs.len(), self.height());
519
520 let packed_width = self.width().div_ceil(T::Packing::WIDTH);
521 let height = self.height();
522
523 let row_bytes = columnwise_row_bytes::<T, EF>(self.width(), N);
531 let chunk_rows = height
533 .div_ceil((4 * current_num_threads()).clamp(1, height.max(1)))
534 .max(min_task_len(height, row_bytes));
535 let num_chunks = height.div_ceil(chunk_rows);
536
537 let packed_results: Vec<EF::ExtensionPacking> =
538 (0..num_chunks).into_par_iter().par_fold_reduce(
539 || EF::ExtensionPacking::zero_vec(packed_width * N),
540 |mut acc, chunk| {
541 let rows = chunk * chunk_rows..((chunk + 1) * chunk_rows).min(height);
542 T::batched_columnwise_dot_product::<EF, _, _, N>(
543 &mut acc,
544 rows.map(|r| {
545 (
546 self.padded_horizontally_packed_row::<T::Packing>(r),
547 vs[r].0,
548 )
549 }),
550 );
551 acc
552 },
553 |mut acc_l, acc_r| {
554 acc_l.iter_mut().zip(&acc_r).for_each(|(lj, rj)| *lj += *rj);
555 acc_l
556 },
557 );
558
559 packed_results
561 .chunks(N)
562 .flat_map(|chunk| {
563 (0..T::Packing::WIDTH)
564 .map(move |lane| FieldArray::from_fn(|j| chunk[j].extract(lane)))
565 })
566 .take(self.width())
567 .collect()
568 }
569
570 fn rowwise_packed_dot_product<EF>(
580 &self,
581 vec: &[EF::ExtensionPacking],
582 ) -> impl IndexedParallelIterator<Item = EF>
583 where
584 T: Field,
585 EF: ExtensionField<T>,
586 {
587 assert!(vec.len() >= self.width().div_ceil(T::Packing::WIDTH));
589
590 self.par_padded_horizontally_packed_rows::<T::Packing>()
593 .map(move |row_packed| {
594 let d = <EF::ExtensionPacking as BasedVectorSpace<T::Packing>>::DIMENSION;
596
597 let coeff_accs = T::Packing::coeffwise_dot_product(
599 d,
600 vec.iter()
601 .zip(row_packed)
602 .map(|(v, r)| (v.as_basis_coefficients_slice(), r)),
603 );
604
605 let packed_result =
607 EF::ExtensionPacking::from_basis_coefficients_fn(|i| coeff_accs[i]);
608 EF::ExtensionPacking::to_ext_iter([packed_result]).sum()
609 })
610 }
611}
612
613const COLUMNWISE_MAC_WIDTHS: usize = 7;
625
626const COLUMNWISE_MAC_LANES: usize = 16;
635
636const fn columnwise_row_bytes<T, EF>(width: usize, weight_vectors: usize) -> usize
646where
647 T: Field,
648 EF: ExtensionField<T>,
649{
650 let columns = width.div_ceil(T::Packing::WIDTH) * T::Packing::WIDTH;
654
655 columns * size_of::<T>()
656 + weight_vectors * size_of::<EF>()
657 + (COLUMNWISE_MAC_WIDTHS * weight_vectors * columns * size_of::<EF>())
658 .div_ceil(COLUMNWISE_MAC_LANES)
659}
660
661#[inline(never)]
662fn serial_packed_columnwise_dot_product<F, EF, M>(matrix: &M, weights: &[EF]) -> Vec<EF>
663where
664 F: Field,
665 EF: ExtensionField<F>,
666 M: Matrix<F> + ?Sized,
667{
668 let mut acc = EF::ExtensionPacking::zero_vec(matrix.width().div_ceil(F::Packing::WIDTH));
669 F::batched_columnwise_dot_product::<EF, _, _, 1>(
670 &mut acc,
671 (0..matrix.height()).map(|r| {
672 (
673 matrix.padded_horizontally_packed_row::<F::Packing>(r),
674 [weights[r]],
675 )
676 }),
677 );
678 EF::ExtensionPacking::to_ext_iter(acc)
679 .take(matrix.width())
680 .collect()
681}
682
683#[cfg(test)]
684mod tests {
685 use alloc::vec::Vec;
686 use alloc::{format, vec};
687
688 use itertools::izip;
689 use p3_baby_bear::BabyBear;
690 use p3_field::PrimeCharacteristicRing;
691 use p3_field::extension::{BinomialExtensionField, CubicTrinomialExtensionField};
692 use p3_goldilocks::Goldilocks;
693 use p3_mersenne_31::{Mersenne31, QM31};
694 use rand::SeedableRng;
695 use rand::rngs::SmallRng;
696
697 use super::*;
698 use crate::bitrev::BitReversibleMatrix;
699 use crate::extension::FlatMatrixView;
700
701 fn patterned_matrix<F: Field>(height: usize, width: usize) -> RowMajorMatrix<F> {
702 RowMajorMatrix::new(
703 (0..height * width)
704 .map(|i| F::from_usize((i * 17 + 3) % 127))
705 .collect(),
706 width,
707 )
708 }
709
710 fn patterned_extension_matrix<F, EF>(height: usize, width: usize) -> RowMajorMatrix<EF>
711 where
712 F: Field,
713 EF: ExtensionField<F>,
714 {
715 RowMajorMatrix::new(
716 (0..height * width)
717 .map(|i| {
718 EF::from_basis_coefficients_fn(|d| {
719 F::from_usize((i * EF::DIMENSION + d + 1) % 127)
720 })
721 })
722 .collect(),
723 width,
724 )
725 }
726
727 fn assert_columnwise_dot_product_matches_scalar<F, EF, M>(mat: &M)
728 where
729 F: Field,
730 EF: ExtensionField<F>,
731 M: Matrix<F>,
732 {
733 let weights: Vec<EF> = (0..mat.height())
734 .map(|r| {
735 EF::from_basis_coefficients_fn(|d| F::from_usize((r * EF::DIMENSION + d + 5) % 127))
736 })
737 .collect();
738 let expected: Vec<EF> = (0..mat.width())
739 .map(|c| {
740 (0..mat.height())
741 .map(|r| weights[r] * mat.get(r, c).unwrap())
742 .sum()
743 })
744 .collect();
745
746 assert_eq!(mat.columnwise_dot_product(&weights), expected);
747 }
748
749 fn assert_columnwise_dot_product_grid<F, EF>()
750 where
751 F: Field,
752 EF: ExtensionField<F>,
753 {
754 for height in [0, 1, 17, 32, 128, 1024] {
755 for width in [1, 3, 8, 17, 65] {
756 let mat = patterned_matrix::<F>(height, width);
757 assert_columnwise_dot_product_matches_scalar::<F, EF, _>(&mat);
758 }
759 }
760 }
761
762 #[test]
763 fn test_columnwise_dot_product() {
764 type F = BabyBear;
765 type EF = BinomialExtensionField<BabyBear, 4>;
766
767 let mut rng = SmallRng::seed_from_u64(1);
768 let m = RowMajorMatrix::<F>::rand(&mut rng, 1 << 8, 1 << 4);
769 let v = RowMajorMatrix::<EF>::rand(&mut rng, 1 << 8, 1).values;
770
771 let mut expected = EF::zero_vec(m.width());
772 for (row, &scale) in izip!(m.rows(), &v) {
773 for (l, r) in izip!(&mut expected, row) {
774 *l += scale * r;
775 }
776 }
777
778 assert_eq!(m.columnwise_dot_product(&v), expected);
779 }
780
781 #[test]
782 fn test_columnwise_dot_product_small_height() {
783 type F = BabyBear;
784 type EF = BinomialExtensionField<BabyBear, 4>;
785
786 let mut rng = SmallRng::seed_from_u64(2);
787
788 for height in [0, 1, 3, 16, 17] {
790 let m = RowMajorMatrix::<F>::rand(&mut rng, height, 1 << 4);
791 let v = RowMajorMatrix::<EF>::rand(&mut rng, height, 1).values;
792
793 let mut expected = EF::zero_vec(m.width());
794 for (row, &scale) in izip!(m.rows(), &v) {
795 for (l, r) in izip!(&mut expected, row) {
796 *l += scale * r;
797 }
798 }
799
800 assert_eq!(m.columnwise_dot_product(&v), expected, "height = {height}");
801 }
802 }
803
804 #[test]
805 fn test_columnwise_dot_product_matches_scalar_across_extension_fields() {
806 type BabyBear4 = BinomialExtensionField<BabyBear, 4>;
807 type BabyBear5 = BinomialExtensionField<BabyBear, 5>;
808 type Goldilocks2 = BinomialExtensionField<Goldilocks, 2>;
809 type Goldilocks3 = CubicTrinomialExtensionField<Goldilocks>;
810 type Mersenne31_3 = BinomialExtensionField<Mersenne31, 3>;
811
812 assert_columnwise_dot_product_grid::<BabyBear, BabyBear4>();
813 assert_columnwise_dot_product_grid::<BabyBear, BabyBear5>();
814 assert_columnwise_dot_product_grid::<Goldilocks, Goldilocks2>();
815 assert_columnwise_dot_product_grid::<Goldilocks, Goldilocks3>();
816 assert_columnwise_dot_product_grid::<Mersenne31, QM31>();
817 assert_columnwise_dot_product_grid::<Mersenne31, Mersenne31_3>();
818 }
819
820 #[test]
821 fn test_columnwise_dot_product_matches_scalar_for_matrix_views() {
822 type BabyBear4 = BinomialExtensionField<BabyBear, 4>;
823 type Goldilocks3 = CubicTrinomialExtensionField<Goldilocks>;
824 type Mersenne31_3 = BinomialExtensionField<Mersenne31, 3>;
825
826 let mapped = patterned_matrix::<BabyBear>(32, 17).bit_reverse_rows();
827 assert_columnwise_dot_product_matches_scalar::<BabyBear, BabyBear4, _>(&mapped);
828
829 let flat = FlatMatrixView::<Goldilocks, Goldilocks3, _>::new(patterned_extension_matrix::<
830 Goldilocks,
831 Goldilocks3,
832 >(32, 3));
833 assert_columnwise_dot_product_matches_scalar::<Goldilocks, Goldilocks3, _>(&flat);
834
835 let mapped_flat = FlatMatrixView::<Mersenne31, Mersenne31_3, _>::new(
836 patterned_extension_matrix::<Mersenne31, Mersenne31_3>(128, 3).bit_reverse_rows(),
837 );
838 assert_columnwise_dot_product_matches_scalar::<Mersenne31, Mersenne31_3, _>(&mapped_flat);
839 }
840
841 #[test]
842 #[should_panic(expected = "weight count must match matrix height")]
843 fn test_columnwise_dot_product_rejects_short_weights() {
844 let mat = patterned_matrix::<BabyBear>(17, 17);
845 let weights = BinomialExtensionField::<BabyBear, 4>::zero_vec(16);
846 let _ = mat.columnwise_dot_product(&weights);
847 }
848
849 #[test]
850 #[should_panic(expected = "weight count must match matrix height")]
851 fn test_columnwise_dot_product_rejects_long_weights() {
852 let mat = patterned_matrix::<BabyBear>(17, 17);
853 let weights = BinomialExtensionField::<BabyBear, 4>::zero_vec(18);
854 let _ = mat.columnwise_dot_product(&weights);
855 }
856
857 #[test]
858 fn test_columnwise_dot_product_batched() {
859 type F = BabyBear;
860 type EF = BinomialExtensionField<BabyBear, 4>;
861
862 let mut rng = SmallRng::seed_from_u64(1);
863 let m = RowMajorMatrix::<F>::rand(&mut rng, 1 << 8, 1 << 4);
864 let v1 = RowMajorMatrix::<EF>::rand(&mut rng, 1 << 8, 1).values;
865 let v2 = RowMajorMatrix::<EF>::rand(&mut rng, 1 << 8, 1).values;
866
867 let expected1 = m.columnwise_dot_product(&v1);
869 let expected2 = m.columnwise_dot_product(&v2);
870
871 let vs: Vec<FieldArray<EF, 2>> = v1
873 .into_iter()
874 .zip(v2)
875 .map(|(a, b)| FieldArray([a, b]))
876 .collect();
877 let results = m.columnwise_dot_product_batched::<EF, 2>(&vs);
878
879 let result1: Vec<EF> = results.iter().map(|r| r[0]).collect();
881 let result2: Vec<EF> = results.iter().map(|r| r[1]).collect();
882
883 assert_eq!(result1, expected1);
884 assert_eq!(result2, expected2);
885 }
886
887 struct MockMatrix {
889 data: Vec<Vec<u32>>,
890 width: usize,
891 height: usize,
892 }
893
894 impl Matrix<u32> for MockMatrix {
895 fn width(&self) -> usize {
896 self.width
897 }
898
899 fn height(&self) -> usize {
900 self.height
901 }
902
903 unsafe fn row_unchecked(
904 &self,
905 r: usize,
906 ) -> impl IntoIterator<Item = u32, IntoIter = impl Iterator<Item = u32> + Send + Sync>
907 {
908 self.data[r].clone()
910 }
911 }
912
913 #[test]
914 fn test_dimensions() {
915 let dims = Dimensions {
916 width: 3,
917 height: 5,
918 };
919 assert_eq!(dims.width, 3);
920 assert_eq!(dims.height, 5);
921 assert_eq!(format!("{dims:?}"), "3x5");
922 assert_eq!(format!("{dims}"), "3x5");
923 }
924
925 #[test]
926 fn test_mock_matrix_dimensions() {
927 let matrix = MockMatrix {
928 data: vec![vec![1, 2, 3], vec![4, 5, 6], vec![7, 8, 9]],
929 width: 3,
930 height: 3,
931 };
932 assert_eq!(matrix.width(), 3);
933 assert_eq!(matrix.height(), 3);
934 assert_eq!(
935 matrix.dimensions(),
936 Dimensions {
937 width: 3,
938 height: 3
939 }
940 );
941 }
942
943 #[test]
944 fn test_first_row() {
945 let matrix = MockMatrix {
946 data: vec![vec![1, 2, 3], vec![4, 5, 6], vec![7, 8, 9]],
947 width: 3,
948 height: 3,
949 };
950 let mut first_row = matrix.first_row().unwrap().into_iter();
951 assert_eq!(first_row.next(), Some(1));
952 assert_eq!(first_row.next(), Some(2));
953 assert_eq!(first_row.next(), Some(3));
954 }
955
956 #[test]
957 fn test_last_row() {
958 let matrix = MockMatrix {
959 data: vec![vec![1, 2, 3], vec![4, 5, 6], vec![7, 8, 9]],
960 width: 3,
961 height: 3,
962 };
963 let mut last_row = matrix.last_row().unwrap().into_iter();
964 assert_eq!(last_row.next(), Some(7));
965 assert_eq!(last_row.next(), Some(8));
966 assert_eq!(last_row.next(), Some(9));
967 }
968
969 #[test]
970 fn test_first_last_row_empty_matrix() {
971 let matrix = MockMatrix {
972 data: vec![],
973 width: 3,
974 height: 0,
975 };
976 let first_row = matrix.first_row();
977 let last_row = matrix.last_row();
978 assert!(first_row.is_none());
979 assert!(last_row.is_none());
980 }
981
982 #[test]
983 fn test_to_row_major_matrix() {
984 let matrix = MockMatrix {
985 data: vec![vec![1, 2], vec![3, 4]],
986 width: 2,
987 height: 2,
988 };
989 let row_major = matrix.to_row_major_matrix();
990 assert_eq!(row_major.values, vec![1, 2, 3, 4]);
991 assert_eq!(row_major.width, 2);
992 }
993
994 #[test]
995 fn test_matrix_get_methods() {
996 let matrix = MockMatrix {
997 data: vec![vec![1, 2, 3], vec![4, 5, 6], vec![7, 8, 9]],
998 width: 3,
999 height: 3,
1000 };
1001 assert_eq!(matrix.get(0, 0), Some(1));
1002 assert_eq!(matrix.get(1, 2), Some(6));
1003 assert_eq!(matrix.get(2, 1), Some(8));
1004
1005 unsafe {
1006 assert_eq!(matrix.get_unchecked(0, 1), 2);
1007 assert_eq!(matrix.get_unchecked(1, 0), 4);
1008 assert_eq!(matrix.get_unchecked(2, 2), 9);
1009 }
1010
1011 assert_eq!(matrix.get(3, 0), None); assert_eq!(matrix.get(0, 3), None); }
1014
1015 #[test]
1016 fn test_matrix_row_methods_iteration() {
1017 let matrix = MockMatrix {
1018 data: vec![vec![1, 2, 3], vec![4, 5, 6], vec![7, 8, 9]],
1019 width: 3,
1020 height: 3,
1021 };
1022
1023 let mut row_iter = matrix.row(1).unwrap().into_iter();
1024 assert_eq!(row_iter.next(), Some(4));
1025 assert_eq!(row_iter.next(), Some(5));
1026 assert_eq!(row_iter.next(), Some(6));
1027 assert_eq!(row_iter.next(), None);
1028
1029 unsafe {
1030 let mut row_iter_unchecked = matrix.row_unchecked(2).into_iter();
1031 assert_eq!(row_iter_unchecked.next(), Some(7));
1032 assert_eq!(row_iter_unchecked.next(), Some(8));
1033 assert_eq!(row_iter_unchecked.next(), Some(9));
1034 assert_eq!(row_iter_unchecked.next(), None);
1035
1036 let mut row_iter_subset = matrix.row_subseq_unchecked(0, 1, 3).into_iter();
1037 assert_eq!(row_iter_subset.next(), Some(2));
1038 assert_eq!(row_iter_subset.next(), Some(3));
1039 assert_eq!(row_iter_subset.next(), None);
1040 }
1041
1042 assert!(matrix.row(3).is_none()); }
1044
1045 #[test]
1046 fn test_row_slice_methods() {
1047 let matrix = MockMatrix {
1048 data: vec![vec![1, 2, 3], vec![4, 5, 6], vec![7, 8, 9]],
1049 width: 3,
1050 height: 3,
1051 };
1052 let row_slice = matrix.row_slice(1).unwrap();
1053 assert_eq!(*row_slice, [4, 5, 6]);
1054 unsafe {
1055 let row_slice_unchecked = matrix.row_slice_unchecked(2);
1056 assert_eq!(*row_slice_unchecked, [7, 8, 9]);
1057
1058 let row_subslice = matrix.row_subslice_unchecked(0, 1, 2);
1059 assert_eq!(*row_subslice, [2]);
1060 }
1061
1062 assert!(matrix.row_slice(3).is_none()); }
1064
1065 #[test]
1066 fn test_matrix_rows() {
1067 let matrix = MockMatrix {
1068 data: vec![vec![1, 2, 3], vec![4, 5, 6], vec![7, 8, 9]],
1069 width: 3,
1070 height: 3,
1071 };
1072
1073 let all_rows: Vec<Vec<u32>> = matrix.rows().map(|row| row.collect()).collect();
1074 assert_eq!(all_rows, vec![vec![1, 2, 3], vec![4, 5, 6], vec![7, 8, 9]]);
1075 }
1076
1077 #[test]
1078 fn test_rowwise_packed_dot_product() {
1079 use p3_field::PackedFieldExtension;
1080
1081 type F = BabyBear;
1082 type EF = BinomialExtensionField<BabyBear, 4>;
1083 type PF = <F as p3_field::Field>::Packing;
1084 type EFPacked = <EF as p3_field::ExtensionField<F>>::ExtensionPacking;
1085
1086 let mut rng = SmallRng::seed_from_u64(42);
1087
1088 for (height, width) in [(32, 16), (64, 128), (128, 17), (256, 255)] {
1090 let m = RowMajorMatrix::<F>::rand(&mut rng, height, width);
1091 let v = RowMajorMatrix::<EF>::rand(&mut rng, width, 1).values;
1092
1093 let expected: Vec<EF> = m
1095 .rows()
1096 .map(|row| {
1097 row.into_iter()
1098 .zip(v.iter())
1099 .map(|(r, &ve)| ve * r)
1100 .sum::<EF>()
1101 })
1102 .collect();
1103
1104 let packed_v: Vec<EFPacked> = v
1106 .chunks(<PF as PackedValue>::WIDTH)
1107 .map(|chunk| {
1108 let mut padded = EF::zero_vec(<PF as PackedValue>::WIDTH);
1109 padded[..chunk.len()].copy_from_slice(chunk);
1110 EFPacked::from_ext_slice(&padded)
1111 })
1112 .collect();
1113
1114 let result: Vec<EF> = m.rowwise_packed_dot_product::<EF>(&packed_v).collect();
1116
1117 assert_eq!(result, expected, "Mismatch for matrix {}x{}", height, width);
1118 }
1119 }
1120}