diff --git a/src/frame/ops.rs b/src/frame/ops.rs index f52ee9d..c35625e 100644 --- a/src/frame/ops.rs +++ b/src/frame/ops.rs @@ -96,9 +96,10 @@ impl BoolOps for Frame { // } // } +#[cfg(test)] mod tests { - use crate::frame::*; - use crate::matrix::*; + use super::*; + use crate::matrix::Matrix; #[test] fn test_series_ops() { diff --git a/src/matrix/mat.rs b/src/matrix/mat.rs index b3d93b6..4798ccd 100644 --- a/src/matrix/mat.rs +++ b/src/matrix/mat.rs @@ -1,4 +1,6 @@ -use std::ops::{Index, IndexMut, Not}; +//! A simple column-major Matrix implementation with element-wise operations. + +use std::ops::{Add, BitAnd, BitOr, BitXor, Div, Index, IndexMut, Mul, Not, Sub}; /// A column‑major 2D matrix of `T` #[derive(Debug, Clone, PartialEq, Eq)] @@ -13,9 +15,15 @@ impl Matrix { pub fn from_cols(cols_data: Vec>) -> Self { let cols = cols_data.len(); assert!(cols > 0, "need at least one column"); - let rows = cols_data[0].len(); - assert!(rows > 0, "need at least one row"); - for (i, col) in cols_data.iter().enumerate().skip(1) { + // Handle empty cols_data + let rows = cols_data.get(0).map_or(0, |c| c.len()); + // Allow 0-row matrices if columns are empty, but not 0-col matrices if rows > 0 + assert!( + rows > 0 || cols == 0, + "need at least one row if columns exist" + ); + + for (i, col) in cols_data.iter().enumerate() { assert!( col.len() == rows, "col {} has len {}, expected {}", @@ -24,17 +32,34 @@ impl Matrix { rows ); } - let mut data = Vec::with_capacity(rows * cols); - for col in cols_data { - data.extend(col); - } + // Flatten column data directly + let data = cols_data.into_iter().flatten().collect(); Matrix { rows, cols, data } } + /// Build from a flat Vec, assuming column-major order. pub fn from_vec(data: Vec, rows: usize, cols: usize) -> Self { - assert!(rows > 0, "need at least one row"); - assert!(cols > 0, "need at least one column"); - assert_eq!(data.len(), rows * cols, "data length mismatch"); + assert!( + rows > 0 || cols == 0, + "need at least one row if columns exist" + ); + assert!( + cols > 0 || rows == 0, + "need at least one column if rows exist" + ); + if rows * cols != 0 { + // Only assert length if matrix is non-empty + assert_eq!( + data.len(), + rows * cols, + "data length mismatch: expected {}, got {}", + rows * cols, + data.len() + ); + } else { + assert!(data.is_empty(), "data must be empty for 0-sized matrix"); + } + Matrix { rows, cols, data } } @@ -46,7 +71,13 @@ impl Matrix { &mut self.data } - pub fn as_vec(&self) -> Vec { + /// Consumes the Matrix and returns its underlying data Vec. + pub fn into_vec(self) -> Vec { + self.data + } + + /// Creates a new Vec containing the matrix data (cloned). + pub fn to_vec(&self) -> Vec { self.data.clone() } @@ -58,20 +89,36 @@ impl Matrix { self.cols } + /// Get element reference (immutable). Panics on out-of-bounds. pub fn get(&self, r: usize, c: usize) -> &T { &self[(r, c)] } + + /// Get element reference (mutable). Panics on out-of-bounds. pub fn get_mut(&mut self, r: usize, c: usize) -> &mut T { &mut self[(r, c)] } #[inline] pub fn column(&self, c: usize) -> &[T] { + assert!( + c < self.cols, + "column index {} out of bounds for {} columns", + c, + self.cols + ); let start = c * self.rows; &self.data[start..start + self.rows] } + #[inline] pub fn column_mut(&mut self, c: usize) -> &mut [T] { + assert!( + c < self.cols, + "column index {} out of bounds for {} columns", + c, + self.cols + ); let start = c * self.rows; &mut self.data[start..start + self.rows] } @@ -87,11 +134,19 @@ impl Matrix { }) } - /// Swaps two columns in the matrix. + /// Swaps two columns in the matrix. Panics on out-of-bounds. pub fn swap_columns(&mut self, c1: usize, c2: usize) { assert!( - c1 < self.cols && c2 < self.cols, - "column index out of bounds" + c1 < self.cols, + "column index c1={} out of bounds for {} columns", + c1, + self.cols + ); + assert!( + c2 < self.cols, + "column index c2={} out of bounds for {} columns", + c2, + self.cols ); if c1 == c2 { // Indices are equal; no operation required @@ -132,7 +187,7 @@ impl Matrix { impl Matrix { /// Adds a column to the matrix at the specified index. pub fn add_column(&mut self, index: usize, column: Vec) { - assert!(index <= self.cols, "column index out of bounds"); + assert!(index <= self.cols,"add_column index {} out of bounds for {} columns",index,self.cols); assert_eq!(column.len(), self.rows, "column length mismatch"); for (r, value) in column.into_iter().enumerate() { @@ -143,8 +198,8 @@ impl Matrix { /// Adds a row to the matrix at the specified index. pub fn add_row(&mut self, index: usize, row: Vec) { - assert!(index <= self.rows, "row index out of bounds"); - assert_eq!(row.len(), self.cols, "row length mismatch"); + assert!(index <= self.rows,"add_row index {} out of bounds for {} rows",index,self.rows); + assert_eq!(row.len(),self.cols,"row length mismatch: expected {} (cols), got {}",self.cols,row.len()); for (c, value) in row.into_iter().enumerate() { self.data.insert(c * (self.rows + 1) + index, value); @@ -159,7 +214,14 @@ impl Index<(usize, usize)> for Matrix { #[inline] fn index(&self, (r, c): (usize, usize)) -> &T { // Validate that the requested indices are within bounds - assert!(r < self.rows && c < self.cols, "index out of bounds"); + assert!( + r < self.rows && c < self.cols, + "index out of bounds: ({}, {}) vs {}x{}", + r, + c, + self.rows, + self.cols + ); // Compute column-major offset and return reference &self.data[c * self.rows + r] } @@ -169,7 +231,14 @@ impl IndexMut<(usize, usize)> for Matrix { #[inline] fn index_mut(&mut self, (r, c): (usize, usize)) -> &mut T { // Validate that the requested indices are within bounds - assert!(r < self.rows && c < self.cols, "index out of bounds"); + assert!( + r < self.rows && c < self.cols, + "index out of bounds: ({}, {}) vs {}x{}", + r, + c, + self.rows, + self.cols + ); // Compute column-major offset and return mutable reference &mut self.data[c * self.rows + r] } @@ -196,14 +265,16 @@ impl<'a, T> MatrixRow<'a, T> { /// Specifies the axis along which to perform a reduction operation. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Axis { - /// Apply reduction along columns (vertical axis). + /// Apply reduction along columns (vertical axis). Result has 1 row. Col, - /// Apply reduction along rows (horizontal axis). + /// Apply reduction along rows (horizontal axis). Result has 1 column. Row, } +// --- Broadcasting --- + /// A trait to turn either a `Matrix` or a scalar T into a `Vec` of -/// length `rows*cols` (broadcasting the scalar). +/// length `rows*cols` (broadcasting the scalar). Used for comparisons. pub trait Broadcastable { fn to_vec(&self, rows: usize, cols: usize) -> Vec; }