Compare commits

..

10 commits
0.3.0 ... main

5 changed files with 209 additions and 43 deletions

View file

@ -1,6 +1,6 @@
[package] [package]
name = "matrix-basic" name = "matrix-basic"
version = "0.3.0" version = "0.5.0"
edition = "2021" edition = "2021"
authors = ["Sayantan Santra <sayantan[dot]santra689[at]gmail[dot]com"] authors = ["Sayantan Santra <sayantan[dot]santra689[at]gmail[dot]com"]
license = "GPL-3.0" license = "GPL-3.0"

View file

@ -1,10 +1,11 @@
[![crate.io badge](https://img.shields.io/crates/d/matrix-basic)](https://crates.io/crates/matrix-basic) [![crate.io badge](https://img.shields.io/crates/d/matrix-basic)](https://crates.io/crates/matrix-basic)
# `matrix-basic` # `matrix-basic`
### A Rust crate for very basic matrix operations ### A Rust crate for very basic matrix operations.
This is a crate for very basic matrix operations with any type that supports addition, substraction, This is a crate for very basic matrix operations with any type that supports addition, substraction, multiplication,
and multiplication. Additional properties might be needed for certain operations. negation, has a zero defined, and implements the Copy trait. Additional properties (e.g. division, existence of one etc.)
might be needed for certain operations.
I created it mostly to learn how to use generic types and traits. I created it mostly to learn how to use generic types and traits.

29
src/errors.rs Normal file
View file

@ -0,0 +1,29 @@
use std::{
error::Error,
fmt::{self, Display, Formatter},
};
/// Error type for using in this crate. Mostly to reduce writing
/// error description every time.
#[derive(Debug, PartialEq)]
pub enum MatrixError {
/// Provided matrix isn't square.
NotSquare,
/// provided matrix is singular.
Singular,
/// Provided array has unequal rows.
UnequalRows,
}
impl Display for MatrixError {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
let out = match *self {
Self::NotSquare => "provided matrix isn't square",
Self::Singular => "provided matrix is singular",
Self::UnequalRows => "provided array has unequal rows",
};
write!(f, "{out}")
}
}
impl Error for MatrixError {}

View file

@ -2,11 +2,13 @@
//! with any type that implement [`Add`], [`Sub`], [`Mul`], //! with any type that implement [`Add`], [`Sub`], [`Mul`],
//! [`Zero`], [`Neg`] and [`Copy`]. Additional properties might be //! [`Zero`], [`Neg`] and [`Copy`]. Additional properties might be
//! needed for certain operations. //! needed for certain operations.
//!
//! I created it mostly to learn using generic types //! I created it mostly to learn using generic types
//! and traits. //! and traits.
//! //!
//! Sayantan Santra (2023) //! Sayantan Santra (2023)
use errors::MatrixError;
use num::{ use num::{
traits::{One, Zero}, traits::{One, Zero},
Integer, Integer,
@ -17,6 +19,7 @@ use std::{
result::Result, result::Result,
}; };
pub mod errors;
mod tests; mod tests;
/// Trait a type must satisfy to be element of a matrix. This is /// Trait a type must satisfy to be element of a matrix. This is
@ -31,7 +34,7 @@ pub trait ToMatrix:
{ {
} }
/// Blanket implementation for ToMatrix for any type that satisfies its bounds /// Blanket implementation for [`ToMatrix`] for any type that satisfies its bounds.
impl<T> ToMatrix for T where impl<T> ToMatrix for T where
T: Mul<Output = T> T: Mul<Output = T>
+ Add<Output = T> + Add<Output = T>
@ -51,7 +54,7 @@ pub struct Matrix<T: ToMatrix> {
} }
impl<T: ToMatrix> Matrix<T> { impl<T: ToMatrix> Matrix<T> {
/// Creates a matrix from given 2D "array" in a `Vec<Vec<T>>` form. /// Creates a matrix from given 2D "array" in a [`Vec<Vec<T>>`] form.
/// It'll throw an error if all the given rows aren't of the same size. /// It'll throw an error if all the given rows aren't of the same size.
/// # Example /// # Example
/// ``` /// ```
@ -61,7 +64,7 @@ impl<T: ToMatrix> Matrix<T> {
/// will create the following matrix: /// will create the following matrix:
/// ⌈1, 2, 3⌉ /// ⌈1, 2, 3⌉
/// ⌊4, 5, 6⌋ /// ⌊4, 5, 6⌋
pub fn from(entries: Vec<Vec<T>>) -> Result<Matrix<T>, &'static str> { pub fn from(entries: Vec<Vec<T>>) -> Result<Matrix<T>, MatrixError> {
let mut equal_rows = true; let mut equal_rows = true;
let row_len = entries[0].len(); let row_len = entries[0].len();
for row in &entries { for row in &entries {
@ -73,7 +76,7 @@ impl<T: ToMatrix> Matrix<T> {
if equal_rows { if equal_rows {
Ok(Matrix { entries }) Ok(Matrix { entries })
} else { } else {
Err("Unequal rows.") Err(MatrixError::UnequalRows)
} }
} }
@ -151,7 +154,7 @@ impl<T: ToMatrix> Matrix<T> {
/// let m = Matrix::from(vec![vec![1, 2], vec![3, 4]]).unwrap(); /// let m = Matrix::from(vec![vec![1, 2], vec![3, 4]]).unwrap();
/// assert_eq!(m.det(), Ok(-2)); /// assert_eq!(m.det(), Ok(-2));
/// ``` /// ```
pub fn det(&self) -> Result<T, &'static str> { pub fn det(&self) -> Result<T, MatrixError> {
if self.is_square() { if self.is_square() {
// It's a recursive algorithm using minors. // It's a recursive algorithm using minors.
// TODO: Implement a faster algorithm. // TODO: Implement a faster algorithm.
@ -172,7 +175,7 @@ impl<T: ToMatrix> Matrix<T> {
}; };
Ok(out) Ok(out)
} else { } else {
Err("Provided matrix isn't square.") Err(MatrixError::NotSquare)
} }
} }
@ -184,9 +187,9 @@ impl<T: ToMatrix> Matrix<T> {
/// ``` /// ```
/// use matrix_basic::Matrix; /// use matrix_basic::Matrix;
/// let m = Matrix::from(vec![vec![1.0, 2.0], vec![3.0, 4.0]]).unwrap(); /// let m = Matrix::from(vec![vec![1.0, 2.0], vec![3.0, 4.0]]).unwrap();
/// assert_eq!(m.det(), Ok(-2.0)); /// assert_eq!(m.det_in_field(), Ok(-2.0));
/// ``` /// ```
pub fn det_in_field(&self) -> Result<T, &'static str> pub fn det_in_field(&self) -> Result<T, MatrixError>
where where
T: One, T: One,
T: PartialEq, T: PartialEq,
@ -198,14 +201,14 @@ impl<T: ToMatrix> Matrix<T> {
let mut multiplier = T::one(); let mut multiplier = T::one();
let h = self.height(); let h = self.height();
let w = self.width(); let w = self.width();
for i in 0..h { for i in 0..(h - 1) {
// First check if the row has diagonal element 0, if yes, then swap. // First check if the row has diagonal element 0, if yes, then swap.
if rows[i][i] == T::zero() { if rows[i][i] == T::zero() {
let mut zero_column = true; let mut zero_column = true;
for j in (i + 1)..h { for j in (i + 1)..h {
if rows[j][i] != T::zero() { if rows[j][i] != T::zero() {
rows.swap(i, j); rows.swap(i, j);
multiplier = T::zero() - multiplier; multiplier = -multiplier;
zero_column = false; zero_column = false;
break; break;
} }
@ -226,7 +229,7 @@ impl<T: ToMatrix> Matrix<T> {
} }
Ok(multiplier) Ok(multiplier)
} else { } else {
Err("Provided matrix isn't square.") Err(MatrixError::NotSquare)
} }
} }
@ -248,7 +251,7 @@ impl<T: ToMatrix> Matrix<T> {
let mut offset = 0; let mut offset = 0;
let h = self.height(); let h = self.height();
let w = self.width(); let w = self.width();
for i in 0..h { for i in 0..(h - 1) {
// Check if all the rows below are 0 // Check if all the rows below are 0
if i + offset >= self.width() { if i + offset >= self.width() {
break; break;
@ -350,7 +353,7 @@ impl<T: ToMatrix> Matrix<T> {
/// let m = Matrix::from(vec![vec![1, 2], vec![3, 4]]).unwrap(); /// let m = Matrix::from(vec![vec![1, 2], vec![3, 4]]).unwrap();
/// assert_eq!(m.trace(), Ok(5)); /// assert_eq!(m.trace(), Ok(5));
/// ``` /// ```
pub fn trace(self) -> Result<T, &'static str> { pub fn trace(self) -> Result<T, MatrixError> {
if self.is_square() { if self.is_square() {
let mut out = self.entries[0][0]; let mut out = self.entries[0][0];
for i in 1..self.height() { for i in 1..self.height() {
@ -358,7 +361,7 @@ impl<T: ToMatrix> Matrix<T> {
} }
Ok(out) Ok(out)
} else { } else {
Err("Provided matrix isn't square.") Err(MatrixError::NotSquare)
} }
} }
@ -399,6 +402,87 @@ impl<T: ToMatrix> Matrix<T> {
} }
} }
/// Returns the inverse of a square matrix. Throws an error if the matrix isn't square.
/// /// # Example
/// ```
/// use matrix_basic::Matrix;
/// let m = Matrix::from(vec![vec![1.0, 2.0], vec![3.0, 4.0]]).unwrap();
/// let n = Matrix::from(vec![vec![-2.0, 1.0], vec![1.5, -0.5]]).unwrap();
/// assert_eq!(m.inverse(), Ok(n));
/// ```
pub fn inverse(&self) -> Result<Self, MatrixError>
where
T: Div<Output = T>,
T: One,
T: PartialEq,
{
if self.is_square() {
// We'll use the basic technique of using an augmented matrix (in essence)
// Cloning is necessary as we'll be doing row operations on it.
let mut rows = self.entries.clone();
let h = self.height();
let w = self.width();
let mut out = Self::identity(h).entries;
// First we get row echelon form
for i in 0..(h - 1) {
// First check if the row has diagonal element 0, if yes, then swap.
if rows[i][i] == T::zero() {
let mut zero_column = true;
for j in (i + 1)..h {
if rows[j][i] != T::zero() {
rows.swap(i, j);
out.swap(i, j);
zero_column = false;
break;
}
}
if zero_column {
return Err(MatrixError::Singular);
}
}
for j in (i + 1)..h {
let ratio = rows[j][i] / rows[i][i];
for k in i..w {
rows[j][k] = rows[j][k] - rows[i][k] * ratio;
}
// We cannot skip entries here as they might not be 0
for k in 0..w {
out[j][k] = out[j][k] - out[i][k] * ratio;
}
}
}
// Then we reduce the rows
for i in 0..h {
if rows[i][i] == T::zero() {
return Err(MatrixError::Singular);
}
let divisor = rows[i][i];
for entry in rows[i].iter_mut().skip(i) {
*entry = *entry / divisor;
}
for entry in out[i].iter_mut() {
*entry = *entry / divisor;
}
}
// Finally, we do upside down row reduction
for i in (1..h).rev() {
for j in (0..i).rev() {
let ratio = rows[j][i];
for k in 0..w {
out[j][k] = out[j][k] - out[i][k] * ratio;
}
}
}
Ok(Matrix { entries: out })
} else {
Err(MatrixError::NotSquare)
}
}
// TODO: Canonical forms, eigenvalues, eigenvectors etc. // TODO: Canonical forms, eigenvalues, eigenvectors etc.
} }
@ -414,7 +498,7 @@ impl<T: Mul<Output = T> + ToMatrix> Mul for Matrix<T> {
fn mul(self, other: Self) -> Self::Output { fn mul(self, other: Self) -> Self::Output {
let width = self.width(); let width = self.width();
if width != other.height() { if width != other.height() {
panic!("Row length of first matrix must be same as column length of second matrix."); panic!("row length of first matrix != column length of second matrix");
} else { } else {
let mut out = Vec::new(); let mut out = Vec::new();
for row in self.rows() { for row in self.rows() {
@ -445,7 +529,7 @@ impl<T: Mul<Output = T> + ToMatrix> Add for Matrix<T> {
} }
Matrix { entries: out } Matrix { entries: out }
} else { } else {
panic!("Both matrices must be of same dimensions."); panic!("provided matrices have different dimensions");
} }
} }
} }
@ -469,41 +553,41 @@ impl<T: ToMatrix> Sub for Matrix<T> {
if self.height() == other.height() && self.width() == other.width() { if self.height() == other.height() && self.width() == other.width() {
self + -other self + -other
} else { } else {
panic!("Both matrices must be of same dimensions."); panic!("provided matrices have different dimensions");
} }
} }
} }
/// Trait for conversion between matrices of different types. /// Trait for conversion between matrices of different types.
/// It only has a `convert_to()` method. /// It only has a [`matrix_from()`](Self::matrix_from()) method.
/// This is needed since negative trait bound are not supported in stable Rust /// This is needed since negative trait bound are not supported in stable Rust
/// yet, so we'll have a conflict trying to implement [`From`]. /// yet, so we'll have a conflict trying to implement [`From`].
/// I plan to change this to the default From trait as soon as some sort /// I plan to change this to the default From trait as soon as some sort
/// of specialization system is implemented. /// of specialization system is implemented.
/// You can track this issue [here](https://github.com/rust-lang/rust/issues/42721). /// You can track this issue [here](https://github.com/rust-lang/rust/issues/42721).
pub trait MatrixInto<T: ToMatrix> { pub trait MatrixFrom<T: ToMatrix> {
/// Method for converting a matrix into a matrix of type `Matrix<T>` /// Method for getting a matrix of a new type from a matrix of type [`Matrix<T>`].
fn matrix_into(self) -> Matrix<T>; /// # Example
/// ```
/// use matrix_basic::Matrix;
/// use matrix_basic::MatrixFrom;
///
/// let a = Matrix::from(vec![vec![1, 2, 3], vec![0, 1, 2]]).unwrap();
/// let b = Matrix::from(vec![vec![1.0, 2.0, 3.0], vec![0.0, 1.0, 2.0]]).unwrap();
/// let c = Matrix::<f64>::matrix_from(a); // Type annotation is needed here
///
/// assert_eq!(c, b);
/// ```
fn matrix_from(input: Matrix<T>) -> Self;
} }
/// Blanket implementation of MatrixInto for converting `Matrix<S>` to `Matrix<T>` whenever /// Blanket implementation of [`MatrixFrom<T>`] for converting [`Matrix<S>`] to [`Matrix<T>`] whenever
/// `S` implements `Into<T>`. /// `S` implements [`From(T)`]. Look at [`matrix_into`](Self::matrix_into()).
/// # Example impl<T: ToMatrix, S: ToMatrix + From<T>> MatrixFrom<T> for Matrix<S> {
/// ``` fn matrix_from(input: Matrix<T>) -> Self {
/// use matrix_basic::Matrix;
/// use matrix_basic::MatrixInto;
///
/// let a = Matrix::from(vec![vec![1, 2, 3], vec![0, 1, 2]]).unwrap();
/// let b = Matrix::from(vec![vec![1.0, 2.0, 3.0], vec![0.0, 1.0, 2.0]]).unwrap();
/// let c: Matrix<f64> = a.matrix_into();
///
/// assert_eq!(c, b);
/// ```
impl<T: ToMatrix, S: ToMatrix + Into<T>> MatrixInto<T> for Matrix<S> {
fn matrix_into(self) -> Matrix<T> {
let mut out = Vec::new(); let mut out = Vec::new();
for row in self.entries { for row in input.entries {
let mut new_row: Vec<T> = Vec::new(); let mut new_row: Vec<S> = Vec::new();
for entry in row { for entry in row {
new_row.push(entry.into()); new_row.push(entry.into());
} }
@ -512,3 +596,30 @@ impl<T: ToMatrix, S: ToMatrix + Into<T>> MatrixInto<T> for Matrix<S> {
Matrix { entries: out } Matrix { entries: out }
} }
} }
/// Sister trait of [`MatrixFrom`]. Basically does the same thing, just with a
/// different syntax.
pub trait MatrixInto<T> {
/// Method for converting a matrix [`Matrix<T>`] to another type.
/// # Example
/// ```
/// use matrix_basic::Matrix;
/// use matrix_basic::MatrixInto;
///
/// let a = Matrix::from(vec![vec![1, 2, 3], vec![0, 1, 2]]).unwrap();
/// let b = Matrix::from(vec![vec![1.0, 2.0, 3.0], vec![0.0, 1.0, 2.0]]).unwrap();
/// let c: Matrix<f64> = a.matrix_into(); // Type annotation is needed here
///
///
/// assert_eq!(c, b);
/// ```
fn matrix_into(self) -> T;
}
/// Blanket implementation of [`MatrixInto<T>`] for [`Matrix<S>`] whenever `T`
/// (which is actually some)[`Matrix<U>`] implements [`MatrixFrom<S>`].
impl<T: MatrixFrom<S>, S: ToMatrix> MatrixInto<T> for Matrix<S> {
fn matrix_into(self) -> T {
T::matrix_from(self)
}
}

View file

@ -72,5 +72,30 @@ fn conversion_test() {
let b = Matrix::from(vec![vec![1.0, 2.0, 3.0], vec![0.0, 1.0, 2.0]]).unwrap(); let b = Matrix::from(vec![vec![1.0, 2.0, 3.0], vec![0.0, 1.0, 2.0]]).unwrap();
use crate::MatrixInto; use crate::MatrixInto;
assert_eq!(a.matrix_into(), b); assert_eq!(b, a.clone().matrix_into());
use crate::MatrixFrom;
let c = Matrix::<f64>::matrix_from(a);
assert_eq!(c, b);
}
#[test]
fn inverse_test() {
let a = Matrix::from(vec![vec![1.0, 2.0], vec![1.0, 2.0]]).unwrap();
let b = Matrix::from(vec![
vec![1.0, 2.0, 3.0],
vec![0.0, 1.0, 4.0],
vec![5.0, 6.0, 0.0],
])
.unwrap();
let c = Matrix::from(vec![
vec![-24.0, 18.0, 5.0],
vec![20.0, -15.0, -4.0],
vec![-5.0, 4.0, 1.0],
])
.unwrap();
println!("{:?}", a.inverse());
assert!(a.inverse().is_err());
assert_eq!(b.inverse(), Ok(c));
} }