diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml new file mode 100644 index 0000000..06b5427 --- /dev/null +++ b/.github/workflows/rust.yml @@ -0,0 +1,33 @@ +name: Rust + +on: + push: + branches: + - main + pull_request: + branches: + - main + +jobs: + test: + name: Test + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@stable + + - name: Cache Cargo dependencies + uses: Swatinem/rust-cache@v2 + + - name: Check formatting + run: cargo fmt --all -- --check + + - name: Run Clippy + run: cargo clippy --all-targets --all-features -- -D warnings + + - name: Run tests + run: cargo test --all-features \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index f3f35bf..70a0253 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3,7 +3,7 @@ version = 4 [[package]] -name = "NumRS" +name = "num_rs" version = "0.1.0" dependencies = [ "thiserror", diff --git a/Cargo.toml b/Cargo.toml index eccb7a2..fc254c6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "NumRS" +name = "num_rs" version = "0.1.0" edition = "2024" diff --git a/src/error.rs b/src/error.rs index c5c5a97..dca2103 100644 --- a/src/error.rs +++ b/src/error.rs @@ -6,23 +6,23 @@ pub enum MatrixError { InvalidDataLength { rows: usize, cols: usize, - data_len: usize + data_len: usize, }, #[error("Invalid index")] OutOfBounds { rows: usize, cols: usize, - data_len: usize + data_len: usize, }, #[error("Shape mismatch")] ShapeMismatch { rows: usize, cols: usize, - data_len: usize + data_len: usize, }, #[error("Internal unknown error: {0}")] Other(#[from] std::io::Error), -} \ No newline at end of file +} diff --git a/src/lib.rs b/src/lib.rs index 617b010..8e06a13 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,10 +10,7 @@ mod tests { #[test] fn creates_matrix_with_valid_data() { - let matrix = Matrix::new(2, 3, vec![ - 1.0, 2.0, 3.0, - 4.0, 5.0, 6.0, - ]).unwrap(); + let matrix = Matrix::new(2, 3, vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0]).unwrap(); assert_eq!(matrix.rows(), 2); assert_eq!(matrix.cols(), 3); @@ -58,11 +55,8 @@ mod tests { fn creates_identity_matrix() { let matrix = Matrix::identity(3); - let expected = Matrix::new(3, 3, vec![ - 1.0, 0.0, 0.0, - 0.0, 1.0, 0.0, - 0.0, 0.0, 1.0, - ]).unwrap(); + let expected = + Matrix::new(3, 3, vec![1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0]).unwrap(); assert_eq!(matrix, expected); assert!(matrix.is_square()); @@ -70,24 +64,16 @@ mod tests { #[test] fn creates_matrix_from_function() { - let matrix = Matrix::from_fn(2, 3, |row, col| { - (row * 10 + col) as f64 - }); + let matrix = Matrix::from_fn(2, 3, |row, col| (row * 10 + col) as f64); - let expected = Matrix::new(2, 3, vec![ - 0.0, 1.0, 2.0, - 10.0, 11.0, 12.0, - ]).unwrap(); + let expected = Matrix::new(2, 3, vec![0.0, 1.0, 2.0, 10.0, 11.0, 12.0]).unwrap(); assert_eq!(matrix, expected); } #[test] fn gets_matrix_value() { - let matrix = Matrix::new(2, 3, vec![ - 1.0, 2.0, 3.0, - 4.0, 5.0, 6.0, - ]).unwrap(); + let matrix = Matrix::new(2, 3, vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0]).unwrap(); assert_eq!(matrix.get(0, 0).unwrap(), 1.0); assert_eq!(matrix.get(0, 2).unwrap(), 3.0); @@ -149,83 +135,50 @@ mod tests { #[test] fn adds_matrices() { - let a = Matrix::new(2, 2, vec![ - 1.0, 2.0, - 3.0, 4.0, - ]).unwrap(); + let a = Matrix::new(2, 2, vec![1.0, 2.0, 3.0, 4.0]).unwrap(); - let b = Matrix::new(2, 2, vec![ - 10.0, 20.0, - 30.0, 40.0, - ]).unwrap(); + let b = Matrix::new(2, 2, vec![10.0, 20.0, 30.0, 40.0]).unwrap(); let result = a.add(&b).unwrap(); - let expected = Matrix::new(2, 2, vec![ - 11.0, 22.0, - 33.0, 44.0, - ]).unwrap(); + let expected = Matrix::new(2, 2, vec![11.0, 22.0, 33.0, 44.0]).unwrap(); assert_eq!(result, expected); } #[test] fn subtracts_matrices() { - let a = Matrix::new(2, 2, vec![ - 10.0, 20.0, - 30.0, 40.0, - ]).unwrap(); + let a = Matrix::new(2, 2, vec![10.0, 20.0, 30.0, 40.0]).unwrap(); - let b = Matrix::new(2, 2, vec![ - 1.0, 2.0, - 3.0, 4.0, - ]).unwrap(); + let b = Matrix::new(2, 2, vec![1.0, 2.0, 3.0, 4.0]).unwrap(); let result = a.sub(&b).unwrap(); - let expected = Matrix::new(2, 2, vec![ - 9.0, 18.0, - 27.0, 36.0, - ]).unwrap(); + let expected = Matrix::new(2, 2, vec![9.0, 18.0, 27.0, 36.0]).unwrap(); assert_eq!(result, expected); } #[test] fn scales_matrix() { - let matrix = Matrix::new(2, 2, vec![ - 1.0, 2.0, - 3.0, 4.0, - ]).unwrap(); + let matrix = Matrix::new(2, 2, vec![1.0, 2.0, 3.0, 4.0]).unwrap(); let result = matrix.scale(2.0); - let expected = Matrix::new(2, 2, vec![ - 2.0, 4.0, - 6.0, 8.0, - ]).unwrap(); + let expected = Matrix::new(2, 2, vec![2.0, 4.0, 6.0, 8.0]).unwrap(); assert_eq!(result, expected); } #[test] fn calculates_hadamard_product() { - let a = Matrix::new(2, 2, vec![ - 1.0, 2.0, - 3.0, 4.0, - ]).unwrap(); + let a = Matrix::new(2, 2, vec![1.0, 2.0, 3.0, 4.0]).unwrap(); - let b = Matrix::new(2, 2, vec![ - 10.0, 20.0, - 30.0, 40.0, - ]).unwrap(); + let b = Matrix::new(2, 2, vec![10.0, 20.0, 30.0, 40.0]).unwrap(); let result = a.hadamard(&b).unwrap(); - let expected = Matrix::new(2, 2, vec![ - 10.0, 40.0, - 90.0, 160.0, - ]).unwrap(); + let expected = Matrix::new(2, 2, vec![10.0, 40.0, 90.0, 160.0]).unwrap(); assert_eq!(result, expected); } @@ -237,46 +190,32 @@ mod tests { assert!(matches!(a.add(&b), Err(MatrixError::ShapeMismatch { .. }))); assert!(matches!(a.sub(&b), Err(MatrixError::ShapeMismatch { .. }))); - assert!(matches!(a.hadamard(&b), Err(MatrixError::ShapeMismatch { .. }))); + assert!(matches!( + a.hadamard(&b), + Err(MatrixError::ShapeMismatch { .. }) + )); } #[test] fn transposes_matrix() { - let matrix = Matrix::new(2, 3, vec![ - 1.0, 2.0, 3.0, - 4.0, 5.0, 6.0, - ]).unwrap(); + let matrix = Matrix::new(2, 3, vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0]).unwrap(); let result = matrix.transpose(); - let expected = Matrix::new(3, 2, vec![ - 1.0, 4.0, - 2.0, 5.0, - 3.0, 6.0, - ]).unwrap(); + let expected = Matrix::new(3, 2, vec![1.0, 4.0, 2.0, 5.0, 3.0, 6.0]).unwrap(); assert_eq!(result, expected); } #[test] fn multiplies_matrices() { - let a = Matrix::new(2, 3, vec![ - 1.0, 2.0, 3.0, - 4.0, 5.0, 6.0, - ]).unwrap(); + let a = Matrix::new(2, 3, vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0]).unwrap(); - let b = Matrix::new(3, 2, vec![ - 7.0, 8.0, - 9.0, 10.0, - 11.0, 12.0, - ]).unwrap(); + let b = Matrix::new(3, 2, vec![7.0, 8.0, 9.0, 10.0, 11.0, 12.0]).unwrap(); let result = a.matmul(&b).unwrap(); - let expected = Matrix::new(2, 2, vec![ - 58.0, 64.0, - 139.0, 154.0, - ]).unwrap(); + let expected = Matrix::new(2, 2, vec![58.0, 64.0, 139.0, 154.0]).unwrap(); assert_eq!(result, expected); } @@ -293,17 +232,9 @@ mod tests { #[test] fn compares_matrices_with_approximate_equality() { - let a = Matrix::new(1, 3, vec![ - 0.1 + 0.2, - 1.00000000001, - 3.0, - ]).unwrap(); - - let b = Matrix::new(1, 3, vec![ - 0.3, - 1.0, - 3.0, - ]).unwrap(); + let a = Matrix::new(1, 3, vec![0.1 + 0.2, 1.00000000001, 3.0]).unwrap(); + + let b = Matrix::new(1, 3, vec![0.3, 1.0, 3.0]).unwrap(); assert!(a.approx_eq(&b, 1e-10)); } @@ -327,27 +258,14 @@ mod tests { #[test] fn display_formats_matrix() { - let matrix = Matrix::new( - 2, - 2, - vec![1.0, 2.0, 3.0, 4.0], - ) - .unwrap(); - - assert_eq!( - format!("{matrix}"), - "[1.0000, 2.0000]\n[3.0000, 4.0000]" - ); + let matrix = Matrix::new(2, 2, vec![1.0, 2.0, 3.0, 4.0]).unwrap(); + + assert_eq!(format!("{matrix}"), "[1.0000, 2.0000]\n[3.0000, 4.0000]"); } #[test] fn index_reads_element() { - let matrix = Matrix::new( - 2, - 2, - vec![1.0, 2.0, 3.0, 4.0], - ) - .unwrap(); + let matrix = Matrix::new(2, 2, vec![1.0, 2.0, 3.0, 4.0]).unwrap(); assert_eq!(matrix[(1, 0)], 3.0); } @@ -375,12 +293,7 @@ mod tests { let b = Matrix::ones(2, 2); let result = (&a + &b).unwrap(); - let expected = Matrix::new( - 2, - 2, - vec![2.0, 2.0, 2.0, 2.0], - ) - .unwrap(); + let expected = Matrix::new(2, 2, vec![2.0, 2.0, 2.0, 2.0]).unwrap(); assert_eq!(result, expected); } @@ -390,13 +303,8 @@ mod tests { let matrix = Matrix::ones(2, 2); let result = &matrix * 3.0; - let expected = Matrix::new( - 2, - 2, - vec![3.0, 3.0, 3.0, 3.0], - ) - .unwrap(); + let expected = Matrix::new(2, 2, vec![3.0, 3.0, 3.0, 3.0]).unwrap(); assert_eq!(result, expected); } -} \ No newline at end of file +} diff --git a/src/matrix.rs b/src/matrix.rs index 6028cfd..8bd55e0 100644 --- a/src/matrix.rs +++ b/src/matrix.rs @@ -1,6 +1,6 @@ use crate::MatrixError; use std::fmt; -use std::ops::{Index, IndexMut, Add, Sub, Mul}; +use std::ops::{Add, Index, IndexMut, Mul, Sub}; // use assert!(a.approx_eq(&b, 1e-10)); instead of: a == b @@ -8,31 +8,22 @@ use std::ops::{Index, IndexMut, Add, Sub, Mul}; pub struct Matrix { rows: usize, cols: usize, - data: Vec + data: Vec, } impl Matrix { - pub fn new( - rows: usize, - cols: usize, - data: Vec - ) -> Result { + pub fn new(rows: usize, cols: usize, data: Vec) -> Result { if rows * cols != data.len() { return Err(MatrixError::InvalidDataLength { rows, cols, - data_len: data.len() + data_len: data.len(), }); } - Ok(Matrix { - rows, - cols, - data - }) + Ok(Matrix { rows, cols, data }) } - pub fn zeros(rows: usize, cols: usize) -> Self { Matrix::new(rows, cols, vec![0.0; rows * cols]).unwrap() } @@ -49,24 +40,27 @@ impl Matrix { matrix } - pub fn from_fn( - rows: usize, - cols: usize, - function: F, - ) -> Self + pub fn from_fn(rows: usize, cols: usize, function: F) -> Self where F: FnMut(usize, usize) -> f64, { use std::sync::{Arc, Mutex}; let function = Arc::new(Mutex::new(function)); - Matrix::new(rows, cols, (0..rows).flat_map(|i| { - let function = Arc::clone(&function); - (0..cols).map(move |j| { - let mut function = function.lock().unwrap(); - function(i, j) - }) - }).collect()).unwrap() + Matrix::new( + rows, + cols, + (0..rows) + .flat_map(|i| { + let function = Arc::clone(&function); + (0..cols).map(move |j| { + let mut function = function.lock().unwrap(); + function(i, j) + }) + }) + .collect(), + ) + .unwrap() } pub fn rows(&self) -> usize { @@ -93,21 +87,12 @@ impl Matrix { self.rows == self.cols } - pub fn get( - &self, - row: usize, - col: usize, - ) -> Result { + pub fn get(&self, row: usize, col: usize) -> Result { self.validate_indices(row, col)?; Ok(self.get_unchecked(row, col)) } - pub fn set( - &mut self, - row: usize, - col: usize, - value: f64, - ) -> Result<(), MatrixError> { + pub fn set(&mut self, row: usize, col: usize, value: f64) -> Result<(), MatrixError> { self.validate_indices(row, col)?; self.set_unchecked(row, col, value); Ok(()) @@ -121,10 +106,7 @@ impl Matrix { &mut self.data } - pub fn add( - &self, - other: &Self, - ) -> Result { + pub fn add(&self, other: &Self) -> Result { self.shape_mismatch(other)?; let mut result = Matrix::zeros(self.rows, self.cols); @@ -135,10 +117,7 @@ impl Matrix { Ok(result) } - pub fn sub( - &self, - other: &Self, - ) -> Result { + pub fn sub(&self, other: &Self) -> Result { self.shape_mismatch(other)?; let mut result = Matrix::zeros(self.rows, self.cols); @@ -158,10 +137,7 @@ impl Matrix { result } - pub fn hadamard( - &self, - other: &Self, - ) -> Result { + pub fn hadamard(&self, other: &Self) -> Result { self.shape_mismatch(other)?; let mut result = Matrix::zeros(self.rows, self.cols); @@ -183,10 +159,7 @@ impl Matrix { result } - pub fn matmul( - &self, - other: &Self, - ) -> Result { + pub fn matmul(&self, other: &Self) -> Result { self.matmul_shape_mismatch(other)?; let mut result = Matrix::zeros(self.rows, other.cols); @@ -213,40 +186,23 @@ impl Matrix { return false; } - self.data - .iter() - .zip(&other.data) - .all(|(a, b)| { - let difference = (a - b).abs(); - let scale = a.abs().max(b.abs()).max(1.0); + self.data.iter().zip(&other.data).all(|(a, b)| { + let difference = (a - b).abs(); + let scale = a.abs().max(b.abs()).max(1.0); - difference <= tolerance * scale - }) + difference <= tolerance * scale + }) } - - - fn get_unchecked( - &self, - row: usize, - col: usize, - ) -> f64 { + fn get_unchecked(&self, row: usize, col: usize) -> f64 { self.data[row * self.cols + col] } - fn set_unchecked( - &mut self, - row: usize, - col: usize, - value: f64, - ) { + fn set_unchecked(&mut self, row: usize, col: usize, value: f64) { self.data[row * self.cols + col] = value; } - fn shape_mismatch( - &self, - other: &Self, - ) -> Result<(), MatrixError> { + fn shape_mismatch(&self, other: &Self) -> Result<(), MatrixError> { if self.rows != other.rows || self.cols != other.cols { Err(MatrixError::ShapeMismatch { rows: self.rows, @@ -258,10 +214,7 @@ impl Matrix { } } - fn matmul_shape_mismatch( - &self, - other: &Self, - ) -> Result<(), MatrixError> { + fn matmul_shape_mismatch(&self, other: &Self) -> Result<(), MatrixError> { if self.cols != other.rows { Err(MatrixError::ShapeMismatch { rows: self.rows, @@ -273,11 +226,7 @@ impl Matrix { } } - fn validate_indices( - &self, - rows: usize, - cols: usize, - ) -> Result<(), MatrixError> { + fn validate_indices(&self, rows: usize, cols: usize) -> Result<(), MatrixError> { if rows >= self.rows || cols >= self.cols { return Err(MatrixError::OutOfBounds { rows: self.rows, @@ -288,14 +237,12 @@ impl Matrix { Ok(()) } - } impl Index<(usize, usize)> for Matrix { type Output = f64; fn index(&self, (row, col): (usize, usize)) -> &Self::Output { - assert!( row < self.rows && col < self.cols, "matrix index out of bounds: index ({row}, {col}), shape ({}, {})", @@ -311,7 +258,6 @@ impl Index<(usize, usize)> for Matrix { impl IndexMut<(usize, usize)> for Matrix { fn index_mut(&mut self, (row, col): (usize, usize)) -> &mut Self::Output { - assert!( row < self.rows && col < self.cols, "matrix index out of bounds: index ({row}, {col}), shape ({}, {})", @@ -321,7 +267,6 @@ impl IndexMut<(usize, usize)> for Matrix { let flat_index = row * self.cols + col; - &mut self.data[flat_index] } } @@ -388,4 +333,4 @@ impl Mul for &Matrix { fn mul(self, rhs: Self) -> Self::Output { self.matmul(rhs) } -} \ No newline at end of file +}