p3_matrix/
lib.rs

1//! Matrix library.
2
3#![no_std]
4
5extern crate alloc;
6
7use alloc::vec::Vec;
8use core::fmt::{Debug, Display, Formatter};
9use core::ops::Deref;
10
11use itertools::{Itertools, izip};
12use p3_field::{
13    BasedVectorSpace, ExtensionField, Field, PackedValue, PrimeCharacteristicRing, dot_product,
14};
15use p3_maybe_rayon::prelude::*;
16use strided::{VerticallyStridedMatrixView, VerticallyStridedRowIndexMap};
17use tracing::instrument;
18
19use crate::dense::RowMajorMatrix;
20
21pub mod bitrev;
22pub mod dense;
23pub mod extension;
24pub mod horizontally_truncated;
25pub mod row_index_mapped;
26pub mod stack;
27pub mod strided;
28pub mod util;
29
30/// A simple struct representing the shape of a matrix.
31///
32/// The `Dimensions` type stores the number of columns (`width`) and rows (`height`)
33/// of a matrix. It is commonly used for querying and displaying matrix shapes.
34#[derive(Copy, Clone, PartialEq, Eq)]
35pub struct Dimensions {
36    /// Number of columns in the matrix.
37    pub width: usize,
38    /// Number of rows in the matrix.
39    pub height: usize,
40}
41
42impl Debug for Dimensions {
43    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
44        write!(f, "{}x{}", self.width, self.height)
45    }
46}
47
48impl Display for Dimensions {
49    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
50        write!(f, "{}x{}", self.width, self.height)
51    }
52}
53
54/// A generic trait for two-dimensional matrix-like data structures.
55///
56/// The `Matrix` trait provides a uniform interface for accessing rows, elements,
57/// and computing with matrices in both sequential and parallel contexts. It supports
58/// packing strategies for SIMD optimizations and interaction with extension fields.
59pub trait Matrix<T: Send + Sync + Clone>: Send + Sync {
60    /// Returns the number of columns in the matrix.
61    fn width(&self) -> usize;
62
63    /// Returns the number of rows in the matrix.
64    fn height(&self) -> usize;
65
66    /// Returns the dimensions (width, height) of the matrix.
67    fn dimensions(&self) -> Dimensions {
68        Dimensions {
69            width: self.width(),
70            height: self.height(),
71        }
72    }
73
74    // The methods:
75    // get, get_unchecked, row, row_unchecked, row_subseq_unchecked, row_slice, row_slice_unchecked, row_subslice_unchecked
76    // are all defined in a circular manner so you only need to implement a subset of them.
77    // In particular is is enough to implement just one of: row_unchecked, row_subseq_unchecked
78    //
79    // That being said, most implementations will want to implement several methods for performance reasons.
80
81    /// Returns the element at the given row and column.
82    ///
83    /// Returns `None` if either `r >= height()` or `c >= width()`.
84    #[inline]
85    fn get(&self, r: usize, c: usize) -> Option<T> {
86        (r < self.height() && c < self.width()).then(|| unsafe {
87            // Safety: Clearly `r < self.height()` and `c < self.width()`.
88            self.get_unchecked(r, c)
89        })
90    }
91
92    /// Returns the element at the given row and column.
93    ///
94    /// For a safe alternative, see [`get`].
95    ///
96    /// # Safety
97    /// The caller must ensure that `r < self.height()` and `c < self.width()`.
98    /// Breaking any of these assumptions is considered undefined behaviour.
99    #[inline]
100    unsafe fn get_unchecked(&self, r: usize, c: usize) -> T {
101        unsafe { self.row_slice_unchecked(r)[c].clone() }
102    }
103
104    /// Returns an iterator over the elements of the `r`-th row.
105    ///
106    /// The iterator will have `self.width()` elements.
107    ///
108    /// Returns `None` if `r >= height()`.
109    #[inline]
110    fn row(
111        &self,
112        r: usize,
113    ) -> Option<impl IntoIterator<Item = T, IntoIter = impl Iterator<Item = T> + Send + Sync>> {
114        (r < self.height()).then(|| unsafe {
115            // Safety: Clearly `r < self.height()`.
116            self.row_unchecked(r)
117        })
118    }
119
120    /// Returns an iterator over the elements of the `r`-th row.
121    ///
122    /// The iterator will have `self.width()` elements.
123    ///
124    /// For a safe alternative, see [`row`].
125    ///
126    /// # Safety
127    /// The caller must ensure that `r < self.height()`.
128    /// Breaking this assumption is considered undefined behaviour.
129    #[inline]
130    unsafe fn row_unchecked(
131        &self,
132        r: usize,
133    ) -> impl IntoIterator<Item = T, IntoIter = impl Iterator<Item = T> + Send + Sync> {
134        unsafe { self.row_subseq_unchecked(r, 0, self.width()) }
135    }
136
137    /// Returns an iterator over the elements of the `r`-th row from position `start` to `end`.
138    ///
139    /// When `start = 0` and `end = width()`, this is equivalent to [`row_unchecked`].
140    ///
141    /// For a safe alternative, use [`row`], along with the `skip` and `take` iterator methods.
142    ///
143    /// # Safety
144    /// The caller must ensure that `r < self.height()` and `start <= end <= self.width()`.
145    /// Breaking any of these assumptions is considered undefined behaviour.
146    #[inline]
147    unsafe fn row_subseq_unchecked(
148        &self,
149        r: usize,
150        start: usize,
151        end: usize,
152    ) -> impl IntoIterator<Item = T, IntoIter = impl Iterator<Item = T> + Send + Sync> {
153        unsafe {
154            self.row_unchecked(r)
155                .into_iter()
156                .skip(start)
157                .take(end - start)
158        }
159    }
160
161    /// Returns the elements of the `r`-th row as something which can be coerced to a slice.
162    ///
163    /// Returns `None` if `r >= height()`.
164    #[inline]
165    fn row_slice(&self, r: usize) -> Option<impl Deref<Target = [T]>> {
166        (r < self.height()).then(|| unsafe {
167            // Safety: Clearly `r < self.height()`.
168            self.row_slice_unchecked(r)
169        })
170    }
171
172    /// Returns the elements of the `r`-th row as something which can be coerced to a slice.
173    ///
174    /// For a safe alternative, see [`row_slice`].
175    ///
176    /// # Safety
177    /// The caller must ensure that `r < self.height()`.
178    /// Breaking this assumption is considered undefined behaviour.
179    #[inline]
180    unsafe fn row_slice_unchecked(&self, r: usize) -> impl Deref<Target = [T]> {
181        unsafe { self.row_subslice_unchecked(r, 0, self.width()) }
182    }
183
184    /// Returns a subset of elements of the `r`-th row as something which can be coerced to a slice.
185    ///
186    /// When `start = 0` and `end = width()`, this is equivalent to [`row_slice_unchecked`].
187    ///
188    /// For a safe alternative, see [`row_slice`].
189    ///
190    /// # Safety
191    /// The caller must ensure that `r < self.height()` and `start <= end <= self.width()`.
192    /// Breaking any of these assumptions is considered undefined behaviour.
193    #[inline]
194    unsafe fn row_subslice_unchecked(
195        &self,
196        r: usize,
197        start: usize,
198        end: usize,
199    ) -> impl Deref<Target = [T]> {
200        unsafe {
201            self.row_subseq_unchecked(r, start, end)
202                .into_iter()
203                .collect_vec()
204        }
205    }
206
207    /// Returns an iterator over all rows in the matrix.
208    #[inline]
209    fn rows(&self) -> impl Iterator<Item = impl Iterator<Item = T>> + Send + Sync {
210        unsafe {
211            // Safety: `r` always satisfies `r < self.height()`.
212            (0..self.height()).map(move |r| self.row_unchecked(r).into_iter())
213        }
214    }
215
216    /// Returns a parallel iterator over all rows in the matrix.
217    #[inline]
218    fn par_rows(
219        &self,
220    ) -> impl IndexedParallelIterator<Item = impl Iterator<Item = T>> + Send + Sync {
221        unsafe {
222            // Safety: `r` always satisfies `r < self.height()`.
223            (0..self.height())
224                .into_par_iter()
225                .map(move |r| self.row_unchecked(r).into_iter())
226        }
227    }
228
229    /// Collect the elements of the rows `r` through `r + c`. If anything is larger than `self.height()`
230    /// simply wrap around to the beginning of the matrix.
231    fn wrapping_row_slices(&self, r: usize, c: usize) -> Vec<impl Deref<Target = [T]>> {
232        unsafe {
233            // Safety: Thank to the `%`, the rows index is always less than `self.height()`.
234            (0..c)
235                .map(|i| self.row_slice_unchecked((r + i) % self.height()))
236                .collect_vec()
237        }
238    }
239
240    /// Returns an iterator over the first row of the matrix.
241    ///
242    /// Returns None if `height() == 0`.
243    #[inline]
244    fn first_row(
245        &self,
246    ) -> Option<impl IntoIterator<Item = T, IntoIter = impl Iterator<Item = T> + Send + Sync>> {
247        self.row(0)
248    }
249
250    /// Returns an iterator over the last row of the matrix.
251    ///
252    /// Returns None if `height() == 0`.
253    #[inline]
254    fn last_row(
255        &self,
256    ) -> Option<impl IntoIterator<Item = T, IntoIter = impl Iterator<Item = T> + Send + Sync>> {
257        if self.height() == 0 {
258            None
259        } else {
260            // Safety: Clearly `self.height() - 1 < self.height()`.
261            unsafe { Some(self.row_unchecked(self.height() - 1)) }
262        }
263    }
264
265    /// Converts the matrix into a `RowMajorMatrix` by collecting all rows into a single vector.
266    fn to_row_major_matrix(self) -> RowMajorMatrix<T>
267    where
268        Self: Sized,
269        T: Clone,
270    {
271        RowMajorMatrix::new(self.rows().flatten().collect(), self.width())
272    }
273
274    /// Get a packed iterator over the `r`-th row.
275    ///
276    /// If the row length is not divisible by the packing width, the final elements
277    /// are returned as a base iterator with length `<= P::WIDTH - 1`.
278    ///
279    /// # Panics
280    /// Panics if `r >= height()`.
281    fn horizontally_packed_row<'a, P>(
282        &'a self,
283        r: usize,
284    ) -> (
285        impl Iterator<Item = P> + Send + Sync,
286        impl Iterator<Item = T> + Send + Sync,
287    )
288    where
289        P: PackedValue<Value = T>,
290        T: Clone + 'a,
291    {
292        assert!(r < self.height(), "Row index out of bounds.");
293        let num_packed = self.width() / P::WIDTH;
294        unsafe {
295            // Safety: We have already checked that `r < height()`.
296            let mut iter = self
297                .row_subseq_unchecked(r, 0, num_packed * P::WIDTH)
298                .into_iter();
299
300            // array::from_fn is guaranteed to always call in order.
301            let packed =
302                (0..num_packed).map(move |_| P::from_fn(|_| iter.next().unwrap_unchecked()));
303
304            let sfx = self
305                .row_subseq_unchecked(r, num_packed * P::WIDTH, self.width())
306                .into_iter();
307            (packed, sfx)
308        }
309    }
310
311    /// Get a packed iterator over the `r`-th row.
312    ///
313    /// If the row length is not divisible by the packing width, the final entry will be zero-padded.
314    ///
315    /// # Panics
316    /// Panics if `r >= height()`.
317    fn padded_horizontally_packed_row<'a, P>(
318        &'a self,
319        r: usize,
320    ) -> impl Iterator<Item = P> + Send + Sync
321    where
322        P: PackedValue<Value = T>,
323        T: Clone + Default + 'a,
324    {
325        let mut row_iter = self.row(r).expect("Row index out of bounds.").into_iter();
326        let num_elems = self.width().div_ceil(P::WIDTH);
327        // array::from_fn is guaranteed to always call in order.
328        (0..num_elems).map(move |_| P::from_fn(|_| row_iter.next().unwrap_or_default()))
329    }
330
331    /// Get a parallel iterator over all packed rows of the matrix.
332    ///
333    /// If the matrix width is not divisible by the packing width, the final elements
334    /// of each row are returned as a base iterator with length `<= P::WIDTH - 1`.
335    fn par_horizontally_packed_rows<'a, P>(
336        &'a self,
337    ) -> impl IndexedParallelIterator<
338        Item = (
339            impl Iterator<Item = P> + Send + Sync,
340            impl Iterator<Item = T> + Send + Sync,
341        ),
342    >
343    where
344        P: PackedValue<Value = T>,
345        T: Clone + 'a,
346    {
347        (0..self.height())
348            .into_par_iter()
349            .map(|r| self.horizontally_packed_row(r))
350    }
351
352    /// Get a parallel iterator over all packed rows of the matrix.
353    ///
354    /// If the matrix width is not divisible by the packing width, the final entry of each row will be zero-padded.
355    fn par_padded_horizontally_packed_rows<'a, P>(
356        &'a self,
357    ) -> impl IndexedParallelIterator<Item = impl Iterator<Item = P> + Send + Sync>
358    where
359        P: PackedValue<Value = T>,
360        T: Clone + Default + 'a,
361    {
362        (0..self.height())
363            .into_par_iter()
364            .map(|r| self.padded_horizontally_packed_row(r))
365    }
366
367    /// Pack together a collection of adjacent rows from the matrix.
368    ///
369    /// Returns an iterator whose i'th element is packing of the i'th element of the
370    /// rows r through r + P::WIDTH - 1. If we exceed the height of the matrix,
371    /// wrap around and include initial rows.
372    #[inline]
373    fn vertically_packed_row<P>(&self, r: usize) -> impl Iterator<Item = P>
374    where
375        T: Copy,
376        P: PackedValue<Value = T>,
377    {
378        // Precompute row slices once to minimize redundant calls and improve performance.
379        let rows = self.wrapping_row_slices(r, P::WIDTH);
380
381        // Using precomputed rows avoids repeatedly calling `row_slice`, which is costly.
382        (0..self.width()).map(move |c| P::from_fn(|i| rows[i][c]))
383    }
384
385    /// Pack together a collection of rows and "next" rows from the matrix.
386    ///
387    /// Returns a vector corresponding to 2 packed rows. The i'th element of the first
388    /// row contains the packing of the i'th element of the rows r through r + P::WIDTH - 1.
389    /// The i'th element of the second row contains the packing of the i'th element of the
390    /// rows r + step through r + step + P::WIDTH - 1. If at some point we exceed the
391    /// height of the matrix, wrap around and include initial rows.
392    #[inline]
393    fn vertically_packed_row_pair<P>(&self, r: usize, step: usize) -> Vec<P>
394    where
395        T: Copy,
396        P: PackedValue<Value = T>,
397    {
398        // Whilst it would appear that this can be replaced by two calls to vertically_packed_row
399        // tests seem to indicate that combining them in the same function is slightly faster.
400        // It's probably allowing the compiler to make some optimizations on the fly.
401
402        let rows = self.wrapping_row_slices(r, P::WIDTH);
403        let next_rows = self.wrapping_row_slices(r + step, P::WIDTH);
404
405        (0..self.width())
406            .map(|c| P::from_fn(|i| rows[i][c]))
407            .chain((0..self.width()).map(|c| P::from_fn(|i| next_rows[i][c])))
408            .collect_vec()
409    }
410
411    /// Returns a view over a vertically strided submatrix.
412    ///
413    /// The view selects rows using `r = offset + i * stride` for each `i`.
414    fn vertically_strided(self, stride: usize, offset: usize) -> VerticallyStridedMatrixView<Self>
415    where
416        Self: Sized,
417    {
418        VerticallyStridedRowIndexMap::new_view(self, stride, offset)
419    }
420
421    /// Compute Mᵀv, aka premultiply this matrix by the given vector,
422    /// aka scale each row by the corresponding entry in `v` and take the sum across rows.
423    /// `v` can be a vector of extension elements.
424    #[instrument(level = "debug", skip_all, fields(dims = %self.dimensions()))]
425    fn columnwise_dot_product<EF>(&self, v: &[EF]) -> Vec<EF>
426    where
427        T: Field,
428        EF: ExtensionField<T>,
429    {
430        let packed_width = self.width().div_ceil(T::Packing::WIDTH);
431
432        let packed_result = self
433            .par_padded_horizontally_packed_rows::<T::Packing>()
434            .zip(v)
435            .par_fold_reduce(
436                || EF::ExtensionPacking::zero_vec(packed_width),
437                |mut acc, (row, &scale)| {
438                    let scale = EF::ExtensionPacking::from_basis_coefficients_fn(|i| {
439                        T::Packing::from(scale.as_basis_coefficients_slice()[i])
440                    });
441                    izip!(&mut acc, row).for_each(|(l, r)| *l += scale * r);
442                    acc
443                },
444                |mut acc_l, acc_r| {
445                    izip!(&mut acc_l, acc_r).for_each(|(l, r)| *l += r);
446                    acc_l
447                },
448            );
449
450        packed_result
451            .into_iter()
452            .flat_map(|p| {
453                (0..T::Packing::WIDTH).map(move |i| {
454                    EF::from_basis_coefficients_fn(|j| {
455                        p.as_basis_coefficients_slice()[j].as_slice()[i]
456                    })
457                })
458            })
459            .take(self.width())
460            .collect()
461    }
462
463    /// Compute the matrix vector product `M . vec`, aka take the dot product of each
464    /// row of `M` by `vec`. If the length of `vec` is longer than the width of `M`,
465    /// `vec` is truncated to the first `width()` elements.
466    ///
467    /// We make use of `PackedFieldExtension` to speed up computations. Thus `vec` is passed in as
468    /// a slice of `PackedFieldExtension` elements.
469    ///
470    /// # Panics
471    /// This function panics if the length of `vec` is less than `self.width().div_ceil(T::Packing::WIDTH)`.
472    fn rowwise_packed_dot_product<EF>(
473        &self,
474        vec: &[EF::ExtensionPacking],
475    ) -> impl IndexedParallelIterator<Item = EF>
476    where
477        T: Field,
478        EF: ExtensionField<T>,
479    {
480        // The length of a `padded_horizontally_packed_row` is `self.width().div_ceil(T::Packing::WIDTH)`.
481        assert!(vec.len() >= self.width().div_ceil(T::Packing::WIDTH));
482
483        // TODO: This is a base - extension dot product and so it should
484        // be possible to speed this up using ideas in `packed_linear_combination`.
485        // TODO: Perhaps we should be packing rows vertically not horizontally.
486        self.par_padded_horizontally_packed_rows::<T::Packing>()
487            .map(move |row_packed| {
488                let packed_sum_of_packed: EF::ExtensionPacking =
489                    dot_product(vec.iter().copied(), row_packed);
490                let sum_of_packed: EF = EF::from_basis_coefficients_fn(|i| {
491                    packed_sum_of_packed.as_basis_coefficients_slice()[i]
492                        .as_slice()
493                        .iter()
494                        .copied()
495                        .sum()
496                });
497                sum_of_packed
498            })
499    }
500}
501
502#[cfg(test)]
503mod tests {
504    use alloc::vec::Vec;
505    use alloc::{format, vec};
506
507    use itertools::izip;
508    use p3_baby_bear::BabyBear;
509    use p3_field::PrimeCharacteristicRing;
510    use p3_field::extension::BinomialExtensionField;
511    use rand::SeedableRng;
512    use rand::rngs::SmallRng;
513
514    use super::*;
515
516    #[test]
517    fn test_columnwise_dot_product() {
518        type F = BabyBear;
519        type EF = BinomialExtensionField<BabyBear, 4>;
520
521        let mut rng = SmallRng::seed_from_u64(1);
522        let m = RowMajorMatrix::<F>::rand(&mut rng, 1 << 8, 1 << 4);
523        let v = RowMajorMatrix::<EF>::rand(&mut rng, 1 << 8, 1).values;
524
525        let mut expected = vec![EF::ZERO; m.width()];
526        for (row, &scale) in izip!(m.rows(), &v) {
527            for (l, r) in izip!(&mut expected, row) {
528                *l += scale * r;
529            }
530        }
531
532        assert_eq!(m.columnwise_dot_product(&v), expected);
533    }
534
535    // Mock implementation for testing purposes
536    struct MockMatrix {
537        data: Vec<Vec<u32>>,
538        width: usize,
539        height: usize,
540    }
541
542    impl Matrix<u32> for MockMatrix {
543        fn width(&self) -> usize {
544            self.width
545        }
546
547        fn height(&self) -> usize {
548            self.height
549        }
550
551        unsafe fn row_unchecked(
552            &self,
553            r: usize,
554        ) -> impl IntoIterator<Item = u32, IntoIter = impl Iterator<Item = u32> + Send + Sync>
555        {
556            // Just a mock implementation so we just do the easy safe thing.
557            self.data[r].clone()
558        }
559    }
560
561    #[test]
562    fn test_dimensions() {
563        let dims = Dimensions {
564            width: 3,
565            height: 5,
566        };
567        assert_eq!(dims.width, 3);
568        assert_eq!(dims.height, 5);
569        assert_eq!(format!("{:?}", dims), "3x5");
570        assert_eq!(format!("{}", dims), "3x5");
571    }
572
573    #[test]
574    fn test_mock_matrix_dimensions() {
575        let matrix = MockMatrix {
576            data: vec![vec![1, 2, 3], vec![4, 5, 6], vec![7, 8, 9]],
577            width: 3,
578            height: 3,
579        };
580        assert_eq!(matrix.width(), 3);
581        assert_eq!(matrix.height(), 3);
582        assert_eq!(
583            matrix.dimensions(),
584            Dimensions {
585                width: 3,
586                height: 3
587            }
588        );
589    }
590
591    #[test]
592    fn test_first_row() {
593        let matrix = MockMatrix {
594            data: vec![vec![1, 2, 3], vec![4, 5, 6], vec![7, 8, 9]],
595            width: 3,
596            height: 3,
597        };
598        let mut first_row = matrix.first_row().unwrap().into_iter();
599        assert_eq!(first_row.next(), Some(1));
600        assert_eq!(first_row.next(), Some(2));
601        assert_eq!(first_row.next(), Some(3));
602    }
603
604    #[test]
605    fn test_last_row() {
606        let matrix = MockMatrix {
607            data: vec![vec![1, 2, 3], vec![4, 5, 6], vec![7, 8, 9]],
608            width: 3,
609            height: 3,
610        };
611        let mut last_row = matrix.last_row().unwrap().into_iter();
612        assert_eq!(last_row.next(), Some(7));
613        assert_eq!(last_row.next(), Some(8));
614        assert_eq!(last_row.next(), Some(9));
615    }
616
617    #[test]
618    fn test_first_last_row_empty_matrix() {
619        let matrix = MockMatrix {
620            data: vec![],
621            width: 3,
622            height: 0,
623        };
624        let first_row = matrix.first_row();
625        let last_row = matrix.last_row();
626        assert!(first_row.is_none());
627        assert!(last_row.is_none());
628    }
629
630    #[test]
631    fn test_to_row_major_matrix() {
632        let matrix = MockMatrix {
633            data: vec![vec![1, 2], vec![3, 4]],
634            width: 2,
635            height: 2,
636        };
637        let row_major = matrix.to_row_major_matrix();
638        assert_eq!(row_major.values, vec![1, 2, 3, 4]);
639        assert_eq!(row_major.width, 2);
640    }
641
642    #[test]
643    fn test_matrix_get_methods() {
644        let matrix = MockMatrix {
645            data: vec![vec![1, 2, 3], vec![4, 5, 6], vec![7, 8, 9]],
646            width: 3,
647            height: 3,
648        };
649        assert_eq!(matrix.get(0, 0), Some(1));
650        assert_eq!(matrix.get(1, 2), Some(6));
651        assert_eq!(matrix.get(2, 1), Some(8));
652
653        unsafe {
654            assert_eq!(matrix.get_unchecked(0, 1), 2);
655            assert_eq!(matrix.get_unchecked(1, 0), 4);
656            assert_eq!(matrix.get_unchecked(2, 2), 9);
657        }
658
659        assert_eq!(matrix.get(3, 0), None); // Height out of bounds
660        assert_eq!(matrix.get(0, 3), None); // Width out of bounds
661    }
662
663    #[test]
664    fn test_matrix_row_methods_iteration() {
665        let matrix = MockMatrix {
666            data: vec![vec![1, 2, 3], vec![4, 5, 6], vec![7, 8, 9]],
667            width: 3,
668            height: 3,
669        };
670
671        let mut row_iter = matrix.row(1).unwrap().into_iter();
672        assert_eq!(row_iter.next(), Some(4));
673        assert_eq!(row_iter.next(), Some(5));
674        assert_eq!(row_iter.next(), Some(6));
675        assert_eq!(row_iter.next(), None);
676
677        unsafe {
678            let mut row_iter_unchecked = matrix.row_unchecked(2).into_iter();
679            assert_eq!(row_iter_unchecked.next(), Some(7));
680            assert_eq!(row_iter_unchecked.next(), Some(8));
681            assert_eq!(row_iter_unchecked.next(), Some(9));
682            assert_eq!(row_iter_unchecked.next(), None);
683
684            let mut row_iter_subset = matrix.row_subseq_unchecked(0, 1, 3).into_iter();
685            assert_eq!(row_iter_subset.next(), Some(2));
686            assert_eq!(row_iter_subset.next(), Some(3));
687            assert_eq!(row_iter_subset.next(), None);
688        }
689
690        assert!(matrix.row(3).is_none()); // Height out of bounds
691    }
692
693    #[test]
694    fn test_row_slice_methods() {
695        let matrix = MockMatrix {
696            data: vec![vec![1, 2, 3], vec![4, 5, 6], vec![7, 8, 9]],
697            width: 3,
698            height: 3,
699        };
700        let row_slice = matrix.row_slice(1).unwrap();
701        assert_eq!(*row_slice, [4, 5, 6]);
702        unsafe {
703            let row_slice_unchecked = matrix.row_slice_unchecked(2);
704            assert_eq!(*row_slice_unchecked, [7, 8, 9]);
705
706            let row_subslice = matrix.row_subslice_unchecked(0, 1, 2);
707            assert_eq!(*row_subslice, [2]);
708        }
709
710        assert!(matrix.row_slice(3).is_none()); // Height out of bounds
711    }
712
713    #[test]
714    fn test_matrix_rows() {
715        let matrix = MockMatrix {
716            data: vec![vec![1, 2, 3], vec![4, 5, 6], vec![7, 8, 9]],
717            width: 3,
718            height: 3,
719        };
720
721        let all_rows: Vec<Vec<u32>> = matrix.rows().map(|row| row.collect()).collect();
722        assert_eq!(all_rows, vec![vec![1, 2, 3], vec![4, 5, 6], vec![7, 8, 9]]);
723    }
724}