From d4bc985891c72b744cdff3a4f710ef695cc18853 Mon Sep 17 00:00:00 2001 From: "Joey@macstudio" Date: Mon, 13 Jul 2026 02:37:41 +0800 Subject: [PATCH] feat: add DenseMatrix ndarray constructor --- src/linalg/ndarray/matrix.rs | 88 ++++++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) diff --git a/src/linalg/ndarray/matrix.rs b/src/linalg/ndarray/matrix.rs index ac39547e..5c94bb55 100644 --- a/src/linalg/ndarray/matrix.rs +++ b/src/linalg/ndarray/matrix.rs @@ -4,6 +4,7 @@ use std::ops::Range; use crate::linalg::basic::arrays::{ Array as BaseArray, Array2, ArrayView1, ArrayView2, MutArray, MutArrayView2, }; +use crate::linalg::basic::matrix::DenseMatrix; use crate::linalg::traits::cholesky::CholeskyDecomposable; use crate::linalg::traits::evd::EVDDecomposable; @@ -19,6 +20,46 @@ use ndarray::{s, Array, ArrayBase, ArrayView, ArrayViewMut, Axis, Ix2, Order, Ow // ArrayBase, Ix2> (owned 2-D array) // --------------------------------------------------------------------------- +const ROW_MAJOR_AXIS: u8 = 0; + +impl DenseMatrix { + /// Copies an owned two-dimensional ndarray into a [`DenseMatrix`]. + /// + /// The resulting matrix uses row-major (C) storage regardless of the + /// memory layout of the source array. + /// + /// # Notes + /// + /// [`ndarray::Array2::iter`] always yields elements in logical row-major + /// order, independent of whether the source is C- or Fortran-ordered. This + /// invariant makes transposed-layout conversion correct. + /// + /// # Panics + /// + /// Panics if `nrows * ncols` overflows `usize`. An empty array (zero + /// rows or zero columns) does not panic. + /// + /// # Examples + /// + /// ``` + /// use ndarray::Array2; + /// use smartcore::linalg::basic::arrays::Array; + /// use smartcore::linalg::basic::matrix::DenseMatrix; + /// + /// let array = Array2::from_shape_vec( + /// (3, 4), + /// (0..12).map(|value| value as f64).collect(), + /// ).unwrap(); + /// let matrix = DenseMatrix::from_ndarray2(&array); + /// assert_eq!(matrix.shape(), (3, 4)); + /// assert_eq!(*matrix.get((1, 2)), 6.0); + /// ``` + pub fn from_ndarray2(a: &ndarray::Array2) -> Self { + // iter() yields logical row-major order regardless of memory layout. + Self::from_iterator(a.iter().copied(), a.nrows(), a.ncols(), ROW_MAJOR_AXIS) + } +} + impl BaseArray for ArrayBase, Ix2> { @@ -224,6 +265,53 @@ mod tests { use super::*; use ndarray::arr2; + #[test] + fn test_dense_matrix_from_ndarray2() { + let input = arr2(&[[1, 2, 3], [4, 5, 6]]); + let matrix = DenseMatrix::from_ndarray2(&input); + let expected = DenseMatrix::from_2d_array(&[&[1, 2, 3], &[4, 5, 6]]).unwrap(); + assert_eq!(matrix, expected); + + let transposed = input.reversed_axes(); + let matrix = DenseMatrix::from_ndarray2(&transposed); + let expected = DenseMatrix::from_2d_array(&[&[1, 4], &[2, 5], &[3, 6]]).unwrap(); + assert_eq!(matrix, expected); + } + + #[test] + fn test_dense_matrix_from_ndarray2_square() { + let input = arr2(&[[1, 2], [3, 4]]); + let matrix = DenseMatrix::from_ndarray2(&input); + let expected = DenseMatrix::from_2d_array(&[&[1, 2], &[3, 4]]).unwrap(); + assert_eq!(matrix, expected); + } + + #[test] + fn test_dense_matrix_from_ndarray2_row_vector() { + let input = arr2(&[[10, 20, 30, 40]]); + let matrix = DenseMatrix::from_ndarray2(&input); + let expected = DenseMatrix::from_2d_array(&[&[10, 20, 30, 40]]).unwrap(); + assert_eq!(matrix, expected); + assert_eq!(matrix.shape(), (1, 4)); + } + + #[test] + fn test_dense_matrix_from_ndarray2_col_vector() { + let input = arr2(&[[10], [20], [30], [40]]); + let matrix = DenseMatrix::from_ndarray2(&input); + let expected = DenseMatrix::from_2d_array(&[&[10], &[20], &[30], &[40]]).unwrap(); + assert_eq!(matrix, expected); + assert_eq!(matrix.shape(), (4, 1)); + } + + #[test] + fn test_dense_matrix_from_ndarray2_empty() { + let input = ndarray::Array2::::zeros((0, 0)); + let matrix = DenseMatrix::from_ndarray2(&input); + assert!(matrix.is_empty()); + assert_eq!(matrix.shape(), (0, 0)); + } + #[test] fn test_get_row() { let m = arr2(&[[1, 2, 3], [4, 5, 6]]);