use oxiblas_core::scalar::Scalar;
#[derive(Copy, Clone)]
pub struct MatRef<'a, T: Scalar> {
ptr: *const T,
nrows: usize,
ncols: usize,
row_stride: usize,
col_stride: usize,
_marker: core::marker::PhantomData<&'a T>,
}
impl<'a, T: Scalar> MatRef<'a, T> {
#[inline]
pub unsafe fn new(ptr: *const T, nrows: usize, ncols: usize, row_stride: usize) -> Self {
MatRef {
ptr,
nrows,
ncols,
row_stride,
col_stride: 1,
_marker: core::marker::PhantomData,
}
}
#[inline]
pub unsafe fn new_strided(
ptr: *const T,
nrows: usize,
ncols: usize,
row_stride: usize,
col_stride: usize,
) -> Self {
MatRef {
ptr,
nrows,
ncols,
row_stride,
col_stride,
_marker: core::marker::PhantomData,
}
}
#[inline]
pub fn from_slice(slice: &'a [T]) -> Self {
Self::from_strided(slice, slice.len(), 1, 1, 1)
.expect("column-vector layout always fits its backing slice")
}
#[inline]
pub fn from_column_major(slice: &'a [T], nrows: usize, ncols: usize) -> Option<Self> {
Self::from_strided(slice, nrows, ncols, nrows, 1)
}
#[inline]
pub fn from_strided(
slice: &'a [T],
nrows: usize,
ncols: usize,
row_stride: usize,
col_stride: usize,
) -> Option<Self> {
if nrows == 0 || ncols == 0 {
return Some(unsafe {
Self::new_strided(slice.as_ptr(), nrows, ncols, row_stride, col_stride)
});
}
let max_row_offset = (nrows - 1).checked_mul(col_stride)?;
let max_col_offset = (ncols - 1).checked_mul(row_stride)?;
let max_offset = max_row_offset.checked_add(max_col_offset)?;
if max_offset < slice.len() {
Some(unsafe { Self::new_strided(slice.as_ptr(), nrows, ncols, row_stride, col_stride) })
} else {
None
}
}
#[inline]
pub fn nrows(&self) -> usize {
self.nrows
}
#[inline]
pub fn ncols(&self) -> usize {
self.ncols
}
#[inline]
pub fn shape(&self) -> (usize, usize) {
(self.nrows, self.ncols)
}
#[inline]
pub fn row_stride(&self) -> usize {
self.row_stride
}
#[inline]
pub fn col_stride(&self) -> usize {
self.col_stride
}
#[inline]
pub fn as_ptr(&self) -> *const T {
self.ptr
}
#[inline]
pub fn ptr_at(&self, row: usize, col: usize) -> *const T {
debug_assert!(row < self.nrows && col < self.ncols);
unsafe { self.ptr.add(row * self.col_stride + col * self.row_stride) }
}
#[inline]
pub fn get(&self, row: usize, col: usize) -> Option<&T> {
if row < self.nrows && col < self.ncols {
Some(unsafe { &*self.ptr_at(row, col) })
} else {
None
}
}
#[inline]
pub unsafe fn get_unchecked(&self, row: usize, col: usize) -> &T {
&*self.ptr_at(row, col)
}
#[inline]
pub fn submatrix(
&self,
row_start: usize,
col_start: usize,
nrows: usize,
ncols: usize,
) -> Self {
assert!(
nrows <= self.nrows
&& ncols <= self.ncols
&& row_start <= self.nrows - nrows
&& col_start <= self.ncols - ncols,
"Submatrix out of bounds"
);
let ptr = if nrows == 0 || ncols == 0 {
self.ptr
} else {
self.ptr_at(row_start, col_start)
};
unsafe { MatRef::new_strided(ptr, nrows, ncols, self.row_stride, self.col_stride) }
}
#[inline]
pub fn col(&self, j: usize) -> Self {
assert!(j < self.ncols, "Column index out of bounds");
self.submatrix(0, j, self.nrows, 1)
}
#[inline]
pub fn row(&self, i: usize) -> Self {
assert!(i < self.nrows, "Row index out of bounds");
self.submatrix(i, 0, 1, self.ncols)
}
#[inline]
pub fn diagonal(&self) -> DiagRef<'a, T> {
let len = self.nrows.min(self.ncols);
DiagRef {
ptr: self.ptr,
len,
stride: self.row_stride + self.col_stride,
_marker: core::marker::PhantomData,
}
}
#[inline]
pub fn transpose(&self) -> MatRef<'a, T> {
unsafe {
MatRef::new_strided(
self.ptr,
self.ncols,
self.nrows,
self.col_stride,
self.row_stride,
)
}
}
#[inline]
pub fn is_empty(&self) -> bool {
self.nrows == 0 || self.ncols == 0
}
#[inline]
pub fn is_column(&self) -> bool {
self.ncols == 1
}
#[inline]
pub fn is_row(&self) -> bool {
self.nrows == 1
}
#[inline]
pub fn is_square(&self) -> bool {
self.nrows == self.ncols
}
#[inline]
pub fn col_as_slice(&self, j: usize) -> Option<&'a [T]> {
if j >= self.ncols {
return None;
}
if self.col_stride != 1 {
return None;
}
let start = unsafe { self.ptr.add(j * self.row_stride) };
Some(unsafe { core::slice::from_raw_parts(start, self.nrows) })
}
#[inline]
pub fn cols(&self) -> impl Iterator<Item = MatRef<'a, T>> + '_ {
(0..self.ncols).map(move |j| self.col(j))
}
#[inline]
pub fn rows(&self) -> impl Iterator<Item = MatRef<'a, T>> + '_ {
(0..self.nrows).map(move |i| self.row(i))
}
#[inline]
pub fn split_cols(&self, mid: usize) -> (Self, Self) {
assert!(mid <= self.ncols, "Split point out of bounds");
(
self.submatrix(0, 0, self.nrows, mid),
self.submatrix(0, mid, self.nrows, self.ncols - mid),
)
}
#[inline]
pub fn split_rows(&self, mid: usize) -> (Self, Self) {
assert!(mid <= self.nrows, "Split point out of bounds");
(
self.submatrix(0, 0, mid, self.ncols),
self.submatrix(mid, 0, self.nrows - mid, self.ncols),
)
}
}
unsafe impl<'a, T: Scalar + Send> Send for MatRef<'a, T> {}
unsafe impl<'a, T: Scalar + Sync> Sync for MatRef<'a, T> {}
impl<'a, T: Scalar> core::ops::Index<(usize, usize)> for MatRef<'a, T> {
type Output = T;
#[inline]
fn index(&self, (row, col): (usize, usize)) -> &Self::Output {
assert!(row < self.nrows && col < self.ncols, "Index out of bounds");
unsafe { &*self.ptr_at(row, col) }
}
}
impl<'a, T: Scalar + core::fmt::Debug> core::fmt::Debug for MatRef<'a, T> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
writeln!(f, "MatRef {}x{} {{", self.nrows, self.ncols)?;
for i in 0..self.nrows.min(10) {
write!(f, " [")?;
for j in 0..self.ncols.min(10) {
if j > 0 {
write!(f, ", ")?;
}
write!(f, "{:?}", self[(i, j)])?;
}
if self.ncols > 10 {
write!(f, ", ...")?;
}
writeln!(f, "]")?;
}
if self.nrows > 10 {
writeln!(f, " ...")?;
}
write!(f, "}}")
}
}
#[derive(Copy, Clone)]
pub struct DiagRef<'a, T: Scalar> {
ptr: *const T,
len: usize,
stride: usize,
_marker: core::marker::PhantomData<&'a T>,
}
impl<'a, T: Scalar> DiagRef<'a, T> {
#[inline]
pub fn len(&self) -> usize {
self.len
}
#[inline]
pub fn is_empty(&self) -> bool {
self.len == 0
}
#[inline]
pub fn get(&self, i: usize) -> Option<&T> {
if i < self.len {
Some(unsafe { &*self.ptr.add(i * self.stride) })
} else {
None
}
}
}
impl<'a, T: Scalar> core::ops::Index<usize> for DiagRef<'a, T> {
type Output = T;
#[inline]
fn index(&self, i: usize) -> &Self::Output {
assert!(i < self.len, "Index out of bounds");
unsafe { &*self.ptr.add(i * self.stride) }
}
}
#[cfg(test)]
mod tests {
use super::MatRef;
use crate::Mat;
#[test]
fn test_mat_ref_basic() {
let m: Mat<f64> = Mat::from_rows(&[&[1.0, 2.0, 3.0], &[4.0, 5.0, 6.0]]);
let view = m.as_ref();
assert_eq!(view.shape(), (2, 3));
assert_eq!(view[(0, 0)], 1.0);
assert_eq!(view[(1, 2)], 6.0);
}
#[test]
fn test_mat_ref_submatrix() {
let m: Mat<f64> = Mat::from_rows(&[
&[1.0, 2.0, 3.0, 4.0],
&[5.0, 6.0, 7.0, 8.0],
&[9.0, 10.0, 11.0, 12.0],
]);
let sub = m.as_ref().submatrix(1, 1, 2, 2);
assert_eq!(sub.shape(), (2, 2));
assert_eq!(sub[(0, 0)], 6.0);
assert_eq!(sub[(1, 1)], 11.0);
}
#[test]
fn test_mat_ref_col_row() {
let m: Mat<f64> = Mat::from_rows(&[&[1.0, 2.0, 3.0], &[4.0, 5.0, 6.0]]);
let view = m.as_ref();
let col1 = view.col(1);
assert_eq!(col1.shape(), (2, 1));
assert_eq!(col1[(0, 0)], 2.0);
assert_eq!(col1[(1, 0)], 5.0);
let row0 = view.row(0);
assert_eq!(row0.shape(), (1, 3));
assert_eq!(row0[(0, 1)], 2.0);
}
#[test]
fn test_mat_ref_diagonal() {
let m: Mat<f64> = Mat::from_rows(&[&[1.0, 2.0, 3.0], &[4.0, 5.0, 6.0], &[7.0, 8.0, 9.0]]);
let diag = m.as_ref().diagonal();
assert_eq!(diag.len(), 3);
assert_eq!(diag[0], 1.0);
assert_eq!(diag[1], 5.0);
assert_eq!(diag[2], 9.0);
}
#[test]
fn test_mat_ref_transpose() {
let m: Mat<f64> = Mat::from_rows(&[&[1.0, 2.0, 3.0], &[4.0, 5.0, 6.0]]);
let t = m.as_ref().transpose();
assert_eq!(t.shape(), (3, 2));
assert_eq!(t[(0, 0)], 1.0);
assert_eq!(t[(1, 0)], 2.0);
assert_eq!(t[(0, 1)], 4.0);
assert_eq!(t[(2, 1)], 6.0);
}
#[test]
fn test_mat_ref_split() {
let m: Mat<f64> = Mat::from_rows(&[&[1.0, 2.0, 3.0, 4.0], &[5.0, 6.0, 7.0, 8.0]]);
let view = m.as_ref();
let (left, right) = view.split_cols(2);
assert_eq!(left.shape(), (2, 2));
assert_eq!(right.shape(), (2, 2));
assert_eq!(left[(0, 1)], 2.0);
assert_eq!(right[(0, 0)], 3.0);
let (top, bottom) = view.split_rows(1);
assert_eq!(top.shape(), (1, 4));
assert_eq!(bottom.shape(), (1, 4));
assert_eq!(top[(0, 2)], 3.0);
assert_eq!(bottom[(0, 2)], 7.0);
}
#[test]
fn test_submatrix_empty_boundary_no_panic() {
let m: Mat<f64> = Mat::from_rows(&[&[1.0, 2.0, 3.0], &[4.0, 5.0, 6.0]]);
let view = m.as_ref();
let (nr, nc) = view.shape();
let bottom = view.submatrix(nr, 0, 0, nc);
assert_eq!(bottom.shape(), (0, nc));
assert!(bottom.is_empty());
assert!(bottom.get(0, 0).is_none());
let right = view.submatrix(0, nc, nr, 0);
assert_eq!(right.shape(), (nr, 0));
assert!(right.is_empty());
let corner = view.submatrix(nr, nc, 0, 0);
assert_eq!(corner.shape(), (0, 0));
assert!(corner.is_empty());
}
#[test]
#[should_panic(expected = "Submatrix out of bounds")]
fn test_submatrix_wraparound_rejected() {
let m: Mat<f64> = Mat::zeros(4, 4);
let view = m.as_ref();
let _ = view.submatrix(usize::MAX - 3, 0, 4, 1);
}
#[test]
fn test_col_as_slice_contiguity() {
let data = [1.0_f64, 2.0, 3.0, 4.0, 5.0, 6.0]; let cm = MatRef::from_column_major(&data, 3, 2).expect("valid layout");
assert_eq!(cm.col_as_slice(0), Some(&data[0..3]));
assert_eq!(cm.col_as_slice(1), Some(&data[3..6]));
assert_eq!(cm.col_as_slice(2), None);
let t = cm.transpose();
assert_eq!(t.shape(), (2, 3));
assert!(t.col_stride() != 1);
assert_eq!(t.col_as_slice(0), None);
assert_eq!(t.col_as_slice(1), None);
}
#[test]
fn test_transpose_is_zero_copy() {
let m: Mat<f64> = Mat::from_rows(&[&[1.0, 2.0, 3.0], &[4.0, 5.0, 6.0]]);
let view = m.as_ref();
let t = view.transpose();
assert_eq!(t.as_ptr(), view.as_ptr());
assert_eq!(t.shape(), (view.ncols(), view.nrows()));
assert_eq!(t.row_stride(), view.col_stride());
assert_eq!(t.col_stride(), view.row_stride());
}
#[test]
fn test_transpose_indexing_matches_original() {
let m: Mat<f64> = Mat::from_rows(&[
&[1.0, 2.0, 3.0, 4.0],
&[5.0, 6.0, 7.0, 8.0],
&[9.0, 10.0, 11.0, 12.0],
]);
let view = m.as_ref();
let t = view.transpose();
for i in 0..view.nrows() {
for j in 0..view.ncols() {
assert_eq!(t[(j, i)], view[(i, j)]);
}
}
let tt = t.transpose();
assert_eq!(tt.shape(), view.shape());
assert_eq!(tt.as_ptr(), view.as_ptr());
assert_eq!(tt.row_stride(), view.row_stride());
assert_eq!(tt.col_stride(), view.col_stride());
}
#[test]
fn test_transpose_gemm_style_iteration() {
let m: Mat<f64> = Mat::from_rows(&[&[1.0, 2.0, 3.0], &[4.0, 5.0, 6.0]]);
let view = m.as_ref();
let t = view.transpose();
let expected_cols = [[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]];
for (j, tcol) in t.cols().enumerate() {
assert_eq!(tcol.shape(), (3, 1));
for i in 0..3 {
assert_eq!(tcol[(i, 0)], expected_cols[j][i]);
}
}
let sub = t.submatrix(1, 0, 2, 2); assert_eq!(sub.shape(), (2, 2));
assert_eq!(sub[(0, 0)], view[(0, 1)]);
assert_eq!(sub[(0, 1)], view[(1, 1)]);
assert_eq!(sub[(1, 0)], view[(0, 2)]);
assert_eq!(sub[(1, 1)], view[(1, 2)]);
}
#[test]
fn test_safe_constructors_validate() {
let data = [1.0_f64, 2.0, 3.0, 4.0, 5.0, 6.0];
assert!(MatRef::from_column_major(&data, 3, 2).is_some());
assert!(MatRef::from_column_major(&data, 3, 3).is_none());
assert!(MatRef::from_strided(&data, 2, 2, 3, 1).is_some());
assert!(MatRef::from_strided(&data, 2, 2, 4, 1).is_some());
assert!(MatRef::from_strided(&data, 2, 2, 5, 1).is_none());
assert!(MatRef::from_strided(&data, 0, 5, 999, 999).is_some());
assert!(MatRef::<f64>::from_strided(&[], 0, 0, 1, 1).is_some());
}
#[test]
fn test_from_slice_column_vector() {
let data = [10.0_f64, 20.0, 30.0];
let v = MatRef::from_slice(&data);
assert_eq!(v.shape(), (3, 1));
assert_eq!(v[(0, 0)], 10.0);
assert_eq!(v[(2, 0)], 30.0);
assert_eq!(v.col_as_slice(0), Some(&data[..]));
}
}