mirror of
https://github.com/SinTan1729/matrix-basic.git
synced 2025-04-19 17:20:00 -05:00
Compare commits
No commits in common. "main" and "0.1.0" have entirely different histories.
5 changed files with 91 additions and 543 deletions
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "matrix-basic"
|
name = "matrix-basic"
|
||||||
version = "0.5.0"
|
version = "0.1.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"
|
||||||
|
|
12
README.md
12
README.md
|
@ -1,13 +1,9 @@
|
||||||
[](https://crates.io/crates/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 using generic types and traits.
|
||||||
|
|
||||||
## Usage
|
|
||||||
Documentation is available here: [docs.rs](https://docs.rs/matrix-basic).
|
|
|
@ -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 {}
|
|
515
src/lib.rs
515
src/lib.rs
|
@ -1,70 +1,44 @@
|
||||||
//! This is a crate for very basic matrix operations
|
//! This is a crate for very basic matrix operations
|
||||||
//! with any type that implement [`Add`], [`Sub`], [`Mul`],
|
//! with any type that supports addition, substraction,
|
||||||
//! [`Zero`], [`Neg`] and [`Copy`]. Additional properties might be
|
//! and multiplication. 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,
|
||||||
};
|
};
|
||||||
use std::{
|
use std::{
|
||||||
fmt::{self, Debug, Display, Formatter},
|
fmt::{self, Debug, Display, Formatter},
|
||||||
ops::{Add, Div, Mul, Neg, Sub},
|
ops::{Add, Mul, Sub},
|
||||||
result::Result,
|
result::Result,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub mod errors;
|
|
||||||
mod tests;
|
mod tests;
|
||||||
|
|
||||||
/// Trait a type must satisfy to be element of a matrix. This is
|
/// A generic matrix struct (over any type with addition, substraction
|
||||||
/// mostly to reduce writing trait bounds afterwards.
|
/// and multiplication defined on it).
|
||||||
pub trait ToMatrix:
|
|
||||||
Mul<Output = Self>
|
|
||||||
+ Add<Output = Self>
|
|
||||||
+ Sub<Output = Self>
|
|
||||||
+ Zero<Output = Self>
|
|
||||||
+ Neg<Output = Self>
|
|
||||||
+ Copy
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Blanket implementation for [`ToMatrix`] for any type that satisfies its bounds.
|
|
||||||
impl<T> ToMatrix for T where
|
|
||||||
T: Mul<Output = T>
|
|
||||||
+ Add<Output = T>
|
|
||||||
+ Sub<Output = T>
|
|
||||||
+ Zero<Output = T>
|
|
||||||
+ Neg<Output = T>
|
|
||||||
+ Copy
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A generic matrix struct (over any type with [`Add`], [`Sub`], [`Mul`],
|
|
||||||
/// [`Zero`], [`Neg`] and [`Copy`] implemented).
|
|
||||||
/// Look at [`from`](Self::from()) to see examples.
|
/// Look at [`from`](Self::from()) to see examples.
|
||||||
#[derive(PartialEq, Debug, Clone)]
|
#[derive(PartialEq, Debug, Clone)]
|
||||||
pub struct Matrix<T: ToMatrix> {
|
pub struct Matrix<T: Mul + Add + Sub> {
|
||||||
entries: Vec<Vec<T>>,
|
entries: Vec<Vec<T>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: ToMatrix> Matrix<T> {
|
impl<T: Mul + Add + Sub> 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
|
||||||
/// ```
|
/// ```
|
||||||
/// use matrix_basic::Matrix;
|
/// use matrix::Matrix;
|
||||||
/// let m = Matrix::from(vec![vec![1, 2, 3], vec![4, 5, 6]]);
|
/// let m = Matrix::from(vec![vec![1,2,3], vec![4,5,6]]);
|
||||||
/// ```
|
/// ```
|
||||||
/// 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,22 +50,25 @@ 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.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the height of a matrix.
|
/// Return the height of a matrix.
|
||||||
pub fn height(&self) -> usize {
|
pub fn height(&self) -> usize {
|
||||||
self.entries.len()
|
self.entries.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the width of a matrix.
|
/// Return the width of a matrix.
|
||||||
pub fn width(&self) -> usize {
|
pub fn width(&self) -> usize {
|
||||||
self.entries[0].len()
|
self.entries[0].len()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the transpose of a matrix.
|
/// Return the transpose of a matrix.
|
||||||
pub fn transpose(&self) -> Self {
|
pub fn transpose(&self) -> Self
|
||||||
|
where
|
||||||
|
T: Copy,
|
||||||
|
{
|
||||||
let mut out = Vec::new();
|
let mut out = Vec::new();
|
||||||
for i in 0..self.width() {
|
for i in 0..self.width() {
|
||||||
let mut column = Vec::new();
|
let mut column = Vec::new();
|
||||||
|
@ -103,13 +80,16 @@ impl<T: ToMatrix> Matrix<T> {
|
||||||
Matrix { entries: out }
|
Matrix { entries: out }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a reference to the rows of a matrix as `&Vec<Vec<T>>`.
|
/// Return a reference to the rows of a matrix as `&Vec<Vec<T>>`.
|
||||||
pub fn rows(&self) -> &Vec<Vec<T>> {
|
pub fn rows(&self) -> &Vec<Vec<T>> {
|
||||||
&self.entries
|
&self.entries
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return the columns of a matrix as `Vec<Vec<T>>`.
|
/// Return the columns of a matrix as `Vec<Vec<T>>`.
|
||||||
pub fn columns(&self) -> Vec<Vec<T>> {
|
pub fn columns(&self) -> Vec<Vec<T>>
|
||||||
|
where
|
||||||
|
T: Copy,
|
||||||
|
{
|
||||||
self.transpose().entries
|
self.transpose().entries
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -118,16 +98,19 @@ impl<T: ToMatrix> Matrix<T> {
|
||||||
self.height() == self.width()
|
self.height() == self.width()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a matrix after removing the provided row and column from it.
|
/// Return a matrix after removing the provided row and column from it.
|
||||||
/// Note: Row and column numbers are 0-indexed.
|
/// Note: Row and column numbers are 0-indexed.
|
||||||
/// # Example
|
/// # Example
|
||||||
/// ```
|
/// ```
|
||||||
/// use matrix_basic::Matrix;
|
/// use matrix::Matrix;
|
||||||
/// let m = Matrix::from(vec![vec![1, 2, 3], vec![4, 5, 6]]).unwrap();
|
/// let m = Matrix::from(vec![vec![1,2,3],vec![4,5,6]]).unwrap();
|
||||||
/// let n = Matrix::from(vec![vec![5, 6]]).unwrap();
|
/// let n = Matrix::from(vec![vec![5,6]]).unwrap();
|
||||||
/// assert_eq!(m.submatrix(0, 0), n);
|
/// assert_eq!(m.submatrix(0,0),n);
|
||||||
/// ```
|
/// ```
|
||||||
pub fn submatrix(&self, row: usize, col: usize) -> Self {
|
pub fn submatrix(&self, row: usize, col: usize) -> Self
|
||||||
|
where
|
||||||
|
T: Copy,
|
||||||
|
{
|
||||||
let mut out = Vec::new();
|
let mut out = Vec::new();
|
||||||
for (m, row_iter) in self.entries.iter().enumerate() {
|
for (m, row_iter) in self.entries.iter().enumerate() {
|
||||||
if m == row {
|
if m == row {
|
||||||
|
@ -144,20 +127,26 @@ impl<T: ToMatrix> Matrix<T> {
|
||||||
Matrix { entries: out }
|
Matrix { entries: out }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the determinant of a square matrix.
|
/// Return the determinant of a square matrix. This method additionally requires [`Zero`],
|
||||||
/// This uses basic recursive algorithm using cofactor-minor.
|
/// [`One`] and [`Copy`] traits. Also, we need that the [`Mul`] and [`Add`] operations
|
||||||
/// See [`det_in_field`](Self::det_in_field()) for faster determinant calculation in fields.
|
/// return the same type `T`.
|
||||||
/// It'll throw an error if the provided matrix isn't square.
|
/// It'll throw an error if the provided matrix isn't square.
|
||||||
/// # Example
|
/// # Example
|
||||||
/// ```
|
/// ```
|
||||||
/// use matrix_basic::Matrix;
|
/// use matrix::Matrix;
|
||||||
/// 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>
|
||||||
|
where
|
||||||
|
T: Copy,
|
||||||
|
T: Mul<Output = T>,
|
||||||
|
T: Sub<Output = T>,
|
||||||
|
T: Zero,
|
||||||
|
{
|
||||||
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. Maybe use row reduction for fields.
|
||||||
let out = if self.width() == 1 {
|
let out = if self.width() == 1 {
|
||||||
self.entries[0][0]
|
self.entries[0][0]
|
||||||
} else {
|
} else {
|
||||||
|
@ -175,152 +164,15 @@ impl<T: ToMatrix> Matrix<T> {
|
||||||
};
|
};
|
||||||
Ok(out)
|
Ok(out)
|
||||||
} else {
|
} else {
|
||||||
Err(MatrixError::NotSquare)
|
Err("Provided matrix isn't square.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the determinant of a square matrix over a field i.e. needs [`One`] and [`Div`] traits.
|
|
||||||
/// See [`det`](Self::det()) for determinants in rings.
|
|
||||||
/// This method uses row reduction as is much faster.
|
|
||||||
/// It'll throw an error if the provided 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();
|
|
||||||
/// assert_eq!(m.det_in_field(), Ok(-2.0));
|
|
||||||
/// ```
|
|
||||||
pub fn det_in_field(&self) -> Result<T, MatrixError>
|
|
||||||
where
|
|
||||||
T: One,
|
|
||||||
T: PartialEq,
|
|
||||||
T: Div<Output = T>,
|
|
||||||
{
|
|
||||||
if self.is_square() {
|
|
||||||
// Cloning is necessary as we'll be doing row operations on it.
|
|
||||||
let mut rows = self.entries.clone();
|
|
||||||
let mut multiplier = T::one();
|
|
||||||
let h = self.height();
|
|
||||||
let w = self.width();
|
|
||||||
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);
|
|
||||||
multiplier = -multiplier;
|
|
||||||
zero_column = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if zero_column {
|
|
||||||
return Ok(T::zero());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (i, row) in rows.iter().enumerate() {
|
|
||||||
multiplier = multiplier * row[i];
|
|
||||||
}
|
|
||||||
Ok(multiplier)
|
|
||||||
} else {
|
|
||||||
Err(MatrixError::NotSquare)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the row echelon form of a matrix over a field i.e. needs the [`Div`] trait.
|
|
||||||
/// # Example
|
|
||||||
/// ```
|
|
||||||
/// use matrix_basic::Matrix;
|
|
||||||
/// let m = Matrix::from(vec![vec![1.0, 2.0, 3.0], vec![3.0, 4.0, 5.0]]).unwrap();
|
|
||||||
/// let n = Matrix::from(vec![vec![1.0, 2.0, 3.0], vec![0.0, -2.0, -4.0]]).unwrap();
|
|
||||||
/// assert_eq!(m.row_echelon(), n);
|
|
||||||
/// ```
|
|
||||||
pub fn row_echelon(&self) -> Self
|
|
||||||
where
|
|
||||||
T: PartialEq,
|
|
||||||
T: Div<Output = T>,
|
|
||||||
{
|
|
||||||
// Cloning is necessary as we'll be doing row operations on it.
|
|
||||||
let mut rows = self.entries.clone();
|
|
||||||
let mut offset = 0;
|
|
||||||
let h = self.height();
|
|
||||||
let w = self.width();
|
|
||||||
for i in 0..(h - 1) {
|
|
||||||
// Check if all the rows below are 0
|
|
||||||
if i + offset >= self.width() {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
// First check if the row has diagonal element 0, if yes, then swap.
|
|
||||||
if rows[i][i + offset] == T::zero() {
|
|
||||||
let mut zero_column = true;
|
|
||||||
for j in (i + 1)..h {
|
|
||||||
if rows[j][i + offset] != T::zero() {
|
|
||||||
rows.swap(i, j);
|
|
||||||
zero_column = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if zero_column {
|
|
||||||
offset += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for j in (i + 1)..h {
|
|
||||||
let ratio = rows[j][i + offset] / rows[i][i + offset];
|
|
||||||
for k in (i + offset)..w {
|
|
||||||
rows[j][k] = rows[j][k] - rows[i][k] * ratio;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Matrix { entries: rows }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the column echelon form of a matrix over a field i.e. needs the [`Div`] trait.
|
|
||||||
/// It's just the transpose of the row echelon form of the transpose.
|
|
||||||
/// See [`row_echelon`](Self::row_echelon()) and [`transpose`](Self::transpose()).
|
|
||||||
pub fn column_echelon(&self) -> Self
|
|
||||||
where
|
|
||||||
T: PartialEq,
|
|
||||||
T: Div<Output = T>,
|
|
||||||
{
|
|
||||||
self.transpose().row_echelon().transpose()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the reduced row echelon form of a matrix over a field i.e. needs the `Div`] trait.
|
|
||||||
/// # Example
|
|
||||||
/// ```
|
|
||||||
/// use matrix_basic::Matrix;
|
|
||||||
/// let m = Matrix::from(vec![vec![1.0, 2.0, 3.0], vec![3.0, 4.0, 5.0]]).unwrap();
|
|
||||||
/// let n = Matrix::from(vec![vec![1.0, 2.0, 3.0], vec![0.0, 1.0, 2.0]]).unwrap();
|
|
||||||
/// assert_eq!(m.reduced_row_echelon(), n);
|
|
||||||
/// ```
|
|
||||||
pub fn reduced_row_echelon(&self) -> Self
|
|
||||||
where
|
|
||||||
T: PartialEq,
|
|
||||||
T: Div<Output = T>,
|
|
||||||
{
|
|
||||||
let mut echelon = self.row_echelon();
|
|
||||||
let mut offset = 0;
|
|
||||||
for row in &mut echelon.entries {
|
|
||||||
while row[offset] == T::zero() {
|
|
||||||
offset += 1;
|
|
||||||
}
|
|
||||||
let divisor = row[offset];
|
|
||||||
for entry in row.iter_mut().skip(offset) {
|
|
||||||
*entry = *entry / divisor;
|
|
||||||
}
|
|
||||||
offset += 1;
|
|
||||||
}
|
|
||||||
echelon
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates a zero matrix of a given size.
|
/// Creates a zero matrix of a given size.
|
||||||
pub fn zero(height: usize, width: usize) -> Self {
|
pub fn zero(height: usize, width: usize) -> Self
|
||||||
|
where
|
||||||
|
T: Zero,
|
||||||
|
{
|
||||||
let mut out = Vec::new();
|
let mut out = Vec::new();
|
||||||
for _ in 0..height {
|
for _ in 0..height {
|
||||||
let mut new_row = Vec::new();
|
let mut new_row = Vec::new();
|
||||||
|
@ -333,172 +185,40 @@ impl<T: ToMatrix> Matrix<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates an identity matrix of a given size.
|
/// Creates an identity matrix of a given size.
|
||||||
/// It needs the [`One`] trait.
|
|
||||||
pub fn identity(size: usize) -> Self
|
pub fn identity(size: usize) -> Self
|
||||||
where
|
where
|
||||||
|
T: Zero,
|
||||||
T: One,
|
T: One,
|
||||||
{
|
{
|
||||||
let mut out = Matrix::zero(size, size);
|
let mut out = Vec::new();
|
||||||
for (i, row) in out.entries.iter_mut().enumerate() {
|
for i in 0..size {
|
||||||
row[i] = T::one();
|
let mut new_row = Vec::new();
|
||||||
}
|
for j in 0..size {
|
||||||
out
|
if i == j {
|
||||||
}
|
new_row.push(T::one());
|
||||||
|
|
||||||
/// Returns the trace of a square matrix.
|
|
||||||
/// It'll throw an error if the provided matrix isn't square.
|
|
||||||
/// # Example
|
|
||||||
/// ```
|
|
||||||
/// use matrix_basic::Matrix;
|
|
||||||
/// let m = Matrix::from(vec![vec![1, 2], vec![3, 4]]).unwrap();
|
|
||||||
/// assert_eq!(m.trace(), Ok(5));
|
|
||||||
/// ```
|
|
||||||
pub fn trace(self) -> Result<T, MatrixError> {
|
|
||||||
if self.is_square() {
|
|
||||||
let mut out = self.entries[0][0];
|
|
||||||
for i in 1..self.height() {
|
|
||||||
out = out + self.entries[i][i];
|
|
||||||
}
|
|
||||||
Ok(out)
|
|
||||||
} else {
|
} else {
|
||||||
Err(MatrixError::NotSquare)
|
new_row.push(T::zero());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
out.push(new_row);
|
||||||
/// Returns a diagonal matrix with a given diagonal.
|
|
||||||
/// # Example
|
|
||||||
/// ```
|
|
||||||
/// use matrix_basic::Matrix;
|
|
||||||
/// let m = Matrix::diagonal_matrix(vec![1, 2, 3]);
|
|
||||||
/// let n = Matrix::from(vec![vec![1, 0, 0], vec![0, 2, 0], vec![0, 0, 3]]).unwrap();
|
|
||||||
///
|
|
||||||
/// assert_eq!(m, n);
|
|
||||||
/// ```
|
|
||||||
pub fn diagonal_matrix(diag: Vec<T>) -> Self {
|
|
||||||
let size = diag.len();
|
|
||||||
let mut out = Matrix::zero(size, size);
|
|
||||||
for (i, row) in out.entries.iter_mut().enumerate() {
|
|
||||||
row[i] = diag[i];
|
|
||||||
}
|
}
|
||||||
out
|
Matrix { entries: out }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Multiplies all entries of a matrix by a scalar.
|
|
||||||
/// Note that it modifies the supplied matrix.
|
|
||||||
/// # Example
|
|
||||||
/// ```
|
|
||||||
/// use matrix_basic::Matrix;
|
|
||||||
/// let mut m = Matrix::from(vec![vec![1, 2, 0], vec![0, 2, 5], vec![0, 0, 3]]).unwrap();
|
|
||||||
/// let n = Matrix::from(vec![vec![2, 4, 0], vec![0, 4, 10], vec![0, 0, 6]]).unwrap();
|
|
||||||
/// m.mul_scalar(2);
|
|
||||||
///
|
|
||||||
/// assert_eq!(m, n);
|
|
||||||
/// ```
|
|
||||||
pub fn mul_scalar(&mut self, scalar: T) {
|
|
||||||
for row in &mut self.entries {
|
|
||||||
for entry in row {
|
|
||||||
*entry = *entry * scalar;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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.
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Debug + ToMatrix> Display for Matrix<T> {
|
impl<T: Debug + Mul + Add + Sub> Display for Matrix<T> {
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
write!(f, "{:?}", self.entries)
|
write!(f, "{:?}", self.entries)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Mul<Output = T> + ToMatrix> Mul for Matrix<T> {
|
impl<T: Mul<Output = T> + Add + Sub + Copy + Zero> Mul for Matrix<T> {
|
||||||
// TODO: Implement a faster algorithm.
|
// TODO: Implement a faster algorithm. Maybe use row reduction for fields.
|
||||||
type Output = Self;
|
type Output = Self;
|
||||||
fn mul(self, other: Self) -> Self::Output {
|
fn mul(self, other: Self) -> Self {
|
||||||
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() {
|
||||||
|
@ -517,9 +237,9 @@ impl<T: Mul<Output = T> + ToMatrix> Mul for Matrix<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Mul<Output = T> + ToMatrix> Add for Matrix<T> {
|
impl<T: Add<Output = T> + Sub + Mul + Copy + Zero> Add for Matrix<T> {
|
||||||
type Output = Self;
|
type Output = Self;
|
||||||
fn add(self, other: Self) -> Self::Output {
|
fn add(self, other: Self) -> Self {
|
||||||
if self.height() == other.height() && self.width() == other.width() {
|
if self.height() == other.height() && self.width() == other.width() {
|
||||||
let mut out = self.entries.clone();
|
let mut out = self.entries.clone();
|
||||||
for (i, row) in self.rows().iter().enumerate() {
|
for (i, row) in self.rows().iter().enumerate() {
|
||||||
|
@ -529,97 +249,24 @@ 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.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: ToMatrix> Neg for Matrix<T> {
|
impl<T: Add + Sub<Output = T> + Mul + Copy + Zero> Sub for Matrix<T> {
|
||||||
type Output = Self;
|
type Output = Self;
|
||||||
fn neg(self) -> Self::Output {
|
fn sub(self, other: Self) -> Self {
|
||||||
let mut out = self;
|
|
||||||
for row in &mut out.entries {
|
|
||||||
for entry in row {
|
|
||||||
*entry = -*entry;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
out
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: ToMatrix> Sub for Matrix<T> {
|
|
||||||
type Output = Self;
|
|
||||||
fn sub(self, other: Self) -> Self::Output {
|
|
||||||
if self.height() == other.height() && self.width() == other.width() {
|
if self.height() == other.height() && self.width() == other.width() {
|
||||||
self + -other
|
let mut out = self.entries.clone();
|
||||||
} else {
|
for (i, row) in self.rows().iter().enumerate() {
|
||||||
panic!("provided matrices have different dimensions");
|
for (j, entry) in other.rows()[i].iter().enumerate() {
|
||||||
|
out[i][j] = row[j] - *entry;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/// Trait for conversion between matrices of different types.
|
|
||||||
/// It only has a [`matrix_from()`](Self::matrix_from()) method.
|
|
||||||
/// This is needed since negative trait bound are not supported in stable Rust
|
|
||||||
/// 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
|
|
||||||
/// of specialization system is implemented.
|
|
||||||
/// You can track this issue [here](https://github.com/rust-lang/rust/issues/42721).
|
|
||||||
pub trait MatrixFrom<T: ToMatrix> {
|
|
||||||
/// Method for getting a matrix of a new type from a matrix of type [`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 [`MatrixFrom<T>`] for converting [`Matrix<S>`] to [`Matrix<T>`] whenever
|
|
||||||
/// `S` implements [`From(T)`]. Look at [`matrix_into`](Self::matrix_into()).
|
|
||||||
impl<T: ToMatrix, S: ToMatrix + From<T>> MatrixFrom<T> for Matrix<S> {
|
|
||||||
fn matrix_from(input: Matrix<T>) -> Self {
|
|
||||||
let mut out = Vec::new();
|
|
||||||
for row in input.entries {
|
|
||||||
let mut new_row: Vec<S> = Vec::new();
|
|
||||||
for entry in row {
|
|
||||||
new_row.push(entry.into());
|
|
||||||
}
|
|
||||||
out.push(new_row)
|
|
||||||
}
|
|
||||||
Matrix { entries: out }
|
Matrix { entries: out }
|
||||||
}
|
} else {
|
||||||
}
|
panic!("Both matrices must be of same dimensions.");
|
||||||
|
}
|
||||||
/// 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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
72
src/tests.rs
72
src/tests.rs
|
@ -1,17 +1,11 @@
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
use crate::Matrix;
|
use crate::Matrix;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn mul_test() {
|
fn mul_test() {
|
||||||
let a = Matrix::from(vec![vec![1, 2, 4], vec![3, 4, 9]]).unwrap();
|
let a = Matrix::from(vec![vec![1, 2, 4], vec![3, 4, 9]]).unwrap();
|
||||||
let b = Matrix::from(vec![vec![1, 2], vec![2, 3], vec![5, 1]]).unwrap();
|
let b = Matrix::from(vec![vec![1, 2], vec![2, 3], vec![5, 1]]).unwrap();
|
||||||
let mut c = Matrix::from(vec![vec![25, 12], vec![56, 27]]).unwrap();
|
let c = Matrix::from(vec![vec![25, 12], vec![56, 27]]).unwrap();
|
||||||
let d = Matrix::from(vec![vec![75, 36], vec![168, 81]]).unwrap();
|
|
||||||
|
|
||||||
assert_eq!(a * b, c);
|
assert_eq!(a * b, c);
|
||||||
|
|
||||||
c.mul_scalar(3);
|
|
||||||
assert_eq!(c, d);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -20,82 +14,22 @@ fn add_sub_test() {
|
||||||
let b = Matrix::from(vec![vec![0, 0, 1], vec![2, 1, 3]]).unwrap();
|
let b = Matrix::from(vec![vec![0, 0, 1], vec![2, 1, 3]]).unwrap();
|
||||||
let c = Matrix::from(vec![vec![1, 2, 4], vec![2, 2, 5]]).unwrap();
|
let c = Matrix::from(vec![vec![1, 2, 4], vec![2, 2, 5]]).unwrap();
|
||||||
let d = Matrix::from(vec![vec![1, 2, 2], vec![-2, 0, -1]]).unwrap();
|
let d = Matrix::from(vec![vec![1, 2, 2], vec![-2, 0, -1]]).unwrap();
|
||||||
let e = Matrix::from(vec![vec![-1, -2, -4], vec![-2, -2, -5]]).unwrap();
|
|
||||||
|
|
||||||
assert_eq!(a.clone() + b.clone(), c);
|
assert_eq!(a.clone() + b.clone(), c);
|
||||||
assert_eq!(a - b, d);
|
assert_eq!(a - b, d);
|
||||||
assert_eq!(-c, e);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn det_trace_test() {
|
fn det_test() {
|
||||||
let a = Matrix::from(vec![vec![1, 2, 0], vec![0, 3, 5], vec![0, 0, 10]]).unwrap();
|
let a = Matrix::from(vec![vec![1, 2, 0], vec![0, 3, 5], vec![0, 0, 10]]).unwrap();
|
||||||
let b = Matrix::from(vec![vec![1, 2, 0], vec![0, 3, 5]]).unwrap();
|
let b = Matrix::from(vec![vec![1, 2, 0], vec![0, 3, 5]]).unwrap();
|
||||||
let c = Matrix::from(vec![
|
|
||||||
vec![0.0, 0.0, 10.0],
|
|
||||||
vec![0.0, 3.0, 5.0],
|
|
||||||
vec![1.0, 2.0, 0.0],
|
|
||||||
])
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
assert_eq!(a.det(), Ok(30));
|
assert_eq!(a.det(), Ok(30));
|
||||||
assert_eq!(c.det_in_field(), Ok(-30.0));
|
|
||||||
assert!(b.det().is_err());
|
assert!(b.det().is_err());
|
||||||
assert_eq!(a.trace(), Ok(14));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn zero_one_diag_test() {
|
fn zero_one_test() {
|
||||||
let a = Matrix::from(vec![vec![0, 0, 0], vec![0, 0, 0]]).unwrap();
|
let a = Matrix::from(vec![vec![0, 0, 0], vec![0, 0, 0]]).unwrap();
|
||||||
let b = Matrix::from(vec![vec![1, 0], vec![0, 1]]).unwrap();
|
let b = Matrix::from(vec![vec![1, 0], vec![0, 1]]).unwrap();
|
||||||
|
|
||||||
assert_eq!(Matrix::<i32>::zero(2, 3), a);
|
assert_eq!(Matrix::<i32>::zero(2, 3), a);
|
||||||
assert_eq!(Matrix::<i32>::identity(2), b);
|
assert_eq!(Matrix::<i32>::identity(2), b);
|
||||||
assert_eq!(Matrix::diagonal_matrix(vec![1, 1]), b);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn echelon_test() {
|
|
||||||
let m = Matrix::from(vec![vec![1.0, 2.0, 3.0], vec![1.0, 0.0, 1.0]]).unwrap();
|
|
||||||
let a = Matrix::from(vec![vec![1.0, 2.0, 3.0], vec![0.0, -2.0, -2.0]]).unwrap();
|
|
||||||
let b = Matrix::from(vec![vec![1.0, 0.0, 0.0], vec![1.0, -2.0, 0.0]]).unwrap();
|
|
||||||
let c = Matrix::from(vec![vec![1.0, 2.0, 3.0], vec![0.0, 1.0, 1.0]]).unwrap();
|
|
||||||
|
|
||||||
assert_eq!(m.row_echelon(), a);
|
|
||||||
assert_eq!(m.column_echelon(), b);
|
|
||||||
assert_eq!(m.reduced_row_echelon(), c);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn conversion_test() {
|
|
||||||
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();
|
|
||||||
|
|
||||||
use crate::MatrixInto;
|
|
||||||
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));
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue