Compare commits

..

No commits in common. "main" and "0.3.0" have entirely different histories.
main ... 0.3.0

5 changed files with 43 additions and 209 deletions

View file

@ -1,6 +1,6 @@
[package] [package]
name = "matrix-basic" name = "matrix-basic"
version = "0.5.0" version = "0.3.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,11 +1,10 @@
[![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, multiplication, This is a crate for very basic matrix operations with any type that supports addition, substraction,
negation, has a zero defined, and implements the Copy trait. Additional properties (e.g. division, existence of one etc.) and multiplication. Additional properties might be needed for certain operations.
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.

View file

@ -1,29 +0,0 @@
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,13 +2,11 @@
//! 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,
@ -19,7 +17,6 @@ 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
@ -34,7 +31,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>
@ -54,7 +51,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
/// ``` /// ```
@ -64,7 +61,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>, MatrixError> { pub fn from(entries: Vec<Vec<T>>) -> Result<Matrix<T>, &'static str> {
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 {
@ -76,7 +73,7 @@ impl<T: ToMatrix> Matrix<T> {
if equal_rows { if equal_rows {
Ok(Matrix { entries }) Ok(Matrix { entries })
} else { } else {
Err(MatrixError::UnequalRows) Err("Unequal rows.")
} }
} }
@ -154,7 +151,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, MatrixError> { pub fn det(&self) -> Result<T, &'static str> {
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.
@ -175,7 +172,7 @@ impl<T: ToMatrix> Matrix<T> {
}; };
Ok(out) Ok(out)
} else { } else {
Err(MatrixError::NotSquare) Err("Provided matrix isn't square.")
} }
} }
@ -187,9 +184,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_in_field(), Ok(-2.0)); /// assert_eq!(m.det(), Ok(-2.0));
/// ``` /// ```
pub fn det_in_field(&self) -> Result<T, MatrixError> pub fn det_in_field(&self) -> Result<T, &'static str>
where where
T: One, T: One,
T: PartialEq, T: PartialEq,
@ -201,14 +198,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 - 1) { for i in 0..h {
// 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 = -multiplier; multiplier = T::zero() - multiplier;
zero_column = false; zero_column = false;
break; break;
} }
@ -229,7 +226,7 @@ impl<T: ToMatrix> Matrix<T> {
} }
Ok(multiplier) Ok(multiplier)
} else { } else {
Err(MatrixError::NotSquare) Err("Provided matrix isn't square.")
} }
} }
@ -251,7 +248,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 - 1) { for i in 0..h {
// 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;
@ -353,7 +350,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, MatrixError> { pub fn trace(self) -> Result<T, &'static str> {
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() {
@ -361,7 +358,7 @@ impl<T: ToMatrix> Matrix<T> {
} }
Ok(out) Ok(out)
} else { } else {
Err(MatrixError::NotSquare) Err("Provided matrix isn't square.")
} }
} }
@ -402,87 +399,6 @@ 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.
} }
@ -498,7 +414,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 != column length of second matrix"); panic!("Row length of first matrix must be same as 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() {
@ -529,7 +445,7 @@ impl<T: Mul<Output = T> + ToMatrix> Add for Matrix<T> {
} }
Matrix { entries: out } Matrix { entries: out }
} else { } else {
panic!("provided matrices have different dimensions"); panic!("Both matrices must be of same dimensions.");
} }
} }
} }
@ -553,41 +469,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!("provided matrices have different dimensions"); panic!("Both matrices must be of same dimensions.");
} }
} }
} }
/// Trait for conversion between matrices of different types. /// Trait for conversion between matrices of different types.
/// It only has a [`matrix_from()`](Self::matrix_from()) method. /// It only has a `convert_to()` 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 MatrixFrom<T: ToMatrix> { pub trait MatrixInto<T: ToMatrix> {
/// Method for getting a matrix of a new type from a matrix of type [`Matrix<T>`]. /// Method for converting a matrix into a matrix of type `Matrix<T>`
/// # Example fn matrix_into(self) -> Matrix<T>;
/// ```
/// 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 [`MatrixFrom<T>`] for converting [`Matrix<S>`] to [`Matrix<T>`] whenever /// Blanket implementation of MatrixInto for converting `Matrix<S>` to `Matrix<T>` whenever
/// `S` implements [`From(T)`]. Look at [`matrix_into`](Self::matrix_into()). /// `S` implements `Into<T>`.
impl<T: ToMatrix, S: ToMatrix + From<T>> MatrixFrom<T> for Matrix<S> { /// # Example
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 input.entries { for row in self.entries {
let mut new_row: Vec<S> = Vec::new(); let mut new_row: Vec<T> = Vec::new();
for entry in row { for entry in row {
new_row.push(entry.into()); new_row.push(entry.into());
} }
@ -596,30 +512,3 @@ impl<T: ToMatrix, S: ToMatrix + From<T>> MatrixFrom<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,30 +72,5 @@ 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!(b, a.clone().matrix_into()); assert_eq!(a.matrix_into(), b);
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));
} }