Compare commits

..

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

5 changed files with 91 additions and 543 deletions

View file

@ -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"

View file

@ -1,13 +1,9 @@
[![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 using generic types and traits.
## Usage
Documentation is available here: [docs.rs](https://docs.rs/matrix-basic).

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

@ -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)
} }
} }

View file

@ -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));
} }