mod errors;
pub mod transform;
pub use errors::{MatrixError, MatrixResult, EPSILON};
use std::fmt;
use std::ops::{Add, Index, IndexMut, Mul, Neg, Sub};
use self::transform::{is_near_zero, mult};
#[derive(Debug, Clone)]
pub struct Matrix<const R: usize, const C: usize> {
data: Vec<Vec<f32>>,
}
impl<const R: usize, const C: usize> Matrix<R, C> {
pub fn new(arr: [[f32; C]; R]) -> Self {
let data: Vec<Vec<f32>> = arr.iter().map(|row| row.to_vec()).collect();
Self { data }
}
pub fn from_vec(v: Vec<Vec<f32>>) -> Self {
let mut data = Vec::with_capacity(R);
for row_idx in 0..v.len().min(R) {
let mut row = Vec::with_capacity(C);
for col_idx in 0..v[row_idx].len().min(C) {
row.push(v[row_idx][col_idx]);
}
while row.len() < C {
row.push(0.0);
}
data.push(row);
}
while data.len() < R {
data.push(vec![0.0; C]);
}
Self { data }
}
pub fn zeros() -> Self {
Self {
data: vec![vec![0.0; C]; R],
}
}
pub fn identity() -> Self {
let mut data = vec![vec![0.0; C]; R];
for i in 0..R.min(C) {
data[i][i] = 1.0;
}
Self { data }
}
pub fn diagonal(values: &[f32]) -> Self {
let mut data = vec![vec![0.0; C]; R];
for (i, &val) in values.iter().enumerate().take(R.min(C)) {
data[i][i] = val;
}
Self { data }
}
pub fn fill(value: f32) -> Self {
Self {
data: vec![vec![value; C]; R],
}
}
}
impl<const R: usize, const C: usize> Matrix<R, C> {
#[inline]
pub const fn rows(&self) -> usize {
R
}
#[inline]
pub const fn cols(&self) -> usize {
C
}
#[inline]
pub const fn shape(&self) -> (usize, usize) {
(R, C)
}
#[inline]
pub fn get(&self, row: usize, col: usize) -> f32 {
self.data[row][col]
}
#[inline]
pub fn set(&mut self, row: usize, col: usize, value: f32) {
self.data[row][col] = value;
}
pub fn as_slice(&self) -> &Vec<Vec<f32>> {
&self.data
}
pub fn as_mut_slice(&mut self) -> &mut Vec<Vec<f32>> {
&mut self.data
}
pub fn row(&self, idx: usize) -> &[f32] {
&self.data[idx]
}
pub fn col(&self, idx: usize) -> Vec<f32> {
self.data.iter().map(|row| row[idx]).collect()
}
}
impl<const R: usize, const C: usize> Matrix<R, C> {
pub fn transpose(&self) -> Matrix<C, R> {
let mut data = vec![vec![0.0; R]; C];
for i in 0..R {
for j in 0..C {
data[j][i] = self.data[i][j];
}
}
Matrix::<C, R>::from_vec(data)
}
pub fn print(&self) {
for row in &self.data {
for el in row {
if *el < 0.0 {
print!("{:8.4}", el);
} else {
print!("{:8.4}", el);
}
}
println!();
}
}
pub fn trace(&self) -> f32 {
let mut sum = 0.0;
for i in 0..R.min(C) {
sum += self.data[i][i];
}
sum
}
pub fn frobenius_norm(&self) -> f32 {
let mut sum = 0.0;
for row in &self.data {
for &val in row {
sum += val * val;
}
}
sum.sqrt()
}
pub fn is_symmetric(&self) -> bool {
if R != C {
return false;
}
for i in 0..R {
for j in (i + 1)..C {
if (self.data[i][j] - self.data[j][i]).abs() > EPSILON {
return false;
}
}
}
true
}
pub fn is_orthogonal(&self) -> bool {
if R != C {
return false;
}
let transpose = self.transpose();
if let Ok(product) = mult(&self.data, &transpose.data) {
for i in 0..R {
for j in 0..C {
let expected = if i == j { 1.0 } else { 0.0 };
if (product[i][j] - expected).abs() > 1e-5 {
return false;
}
}
}
true
} else {
false
}
}
}
impl<const R: usize, const C: usize> Matrix<R, C> {
pub fn determinant(&self) -> MatrixResult<f32> {
if C != R {
return Err(MatrixError::NonSquare { rows: R, cols: C });
}
let mut upper = self.data.clone();
let n = R;
let mut sign = 1.0;
for i in 0..n {
let mut max_row = i;
for k in (i + 1)..n {
if upper[k][i].abs() > upper[max_row][i].abs() {
max_row = k;
}
}
if max_row != i {
upper.swap(i, max_row);
sign *= -1.0;
}
let pivot = upper[i][i];
if is_near_zero(pivot) {
return Ok(0.0);
}
for j in (i + 1)..n {
let factor = upper[j][i] / pivot;
for k in i..n {
upper[j][k] -= factor * upper[i][k];
}
}
}
let mut det = sign;
for i in 0..n {
det *= upper[i][i];
}
Ok(det)
}
pub fn inverse(&self) -> MatrixResult<Matrix<R, C>> {
if C != R {
return Err(MatrixError::NonSquare { rows: R, cols: C });
}
let n = R;
let mut augmented = self.data.clone();
for i in 0..n {
for j in 0..n {
augmented[i].push(if i == j { 1.0 } else { 0.0 });
}
}
for i in 0..n {
let mut max_row = i;
for k in (i + 1)..n {
if augmented[k][i].abs() > augmented[max_row][i].abs() {
max_row = k;
}
}
if max_row != i {
augmented.swap(i, max_row);
}
let pivot = augmented[i][i];
if is_near_zero(pivot) {
return Err(MatrixError::NotInvertible);
}
for j in 0..(2 * n) {
augmented[i][j] /= pivot;
}
for j in 0..n {
if j != i {
let factor = augmented[j][i];
for k in 0..(2 * n) {
augmented[j][k] -= factor * augmented[i][k];
}
}
}
}
let mut result = Vec::with_capacity(n);
for i in 0..n {
result.push(augmented[i][n..].to_vec());
}
Ok(Matrix::<R, C>::from_vec(result))
}
pub fn solve(&self, b: &Matrix<R, 1>) -> MatrixResult<Matrix<R, 1>> {
if R != C {
return Err(MatrixError::NonSquare { rows: R, cols: C });
}
let inv = self.inverse()?;
let result = mult(&inv.data, &b.data)?;
Ok(Matrix::<R, 1>::from_vec(result))
}
}
impl<const R: usize, const C: usize> Matrix<R, C> {
#[allow(non_snake_case)]
pub fn qr(&self) -> MatrixResult<(Matrix<R, C>, Matrix<C, C>)> {
let m = R;
let n = C;
let mut q: Vec<Vec<f32>> = vec![vec![0.0; n]; m];
let mut r: Vec<Vec<f32>> = vec![vec![0.0; n]; n];
for j in 0..n {
for i in 0..m {
q[i][j] = self.data[i][j];
}
for k in 0..j {
let dot: f32 = (0..m).map(|i| q[i][j] * q[i][k]).sum();
r[k][j] = dot;
for i in 0..m {
q[i][j] -= dot * q[i][k];
}
}
let norm: f32 = (0..m).map(|i| q[i][j] * q[i][j]).sum::<f32>().sqrt();
if is_near_zero(norm) {
return Err(MatrixError::NumericalInstability);
}
r[j][j] = norm;
for i in 0..m {
q[i][j] /= norm;
}
}
Ok((Matrix::<R, C>::from_vec(q), Matrix::<C, C>::from_vec(r)))
}
#[allow(non_snake_case)]
pub fn lu(&self) -> MatrixResult<(Matrix<R, C>, Matrix<R, C>)> {
if R != C {
return Err(MatrixError::NonSquare { rows: R, cols: C });
}
let n = R;
let mut l = vec![vec![0.0; n]; n];
let mut u = vec![vec![0.0; n]; n];
for i in 0..n {
for k in i..n {
let sum: f32 = (0..i).map(|j| l[i][j] * u[j][k]).sum();
u[i][k] = self.data[i][k] - sum;
}
for k in i..n {
if i == k {
l[i][i] = 1.0;
} else {
let sum: f32 = (0..i).map(|j| l[k][j] * u[j][i]).sum();
if is_near_zero(u[i][i]) {
return Err(MatrixError::NumericalInstability);
}
l[k][i] = (self.data[k][i] - sum) / u[i][i];
}
}
}
Ok((Matrix::<R, C>::from_vec(l), Matrix::<R, C>::from_vec(u)))
}
#[allow(non_snake_case)]
pub fn ldu(&self) -> MatrixResult<(Matrix<R, C>, Matrix<R, C>, Matrix<R, C>)> {
if R != C {
return Err(MatrixError::NonSquare { rows: R, cols: C });
}
let n = R;
let mut work = self.data.clone();
let mut l = vec![vec![0.0; n]; n];
let mut d = vec![vec![0.0; n]; n];
let mut u = vec![vec![0.0; n]; n];
for i in 0..n {
l[i][i] = 1.0;
u[i][i] = 1.0;
}
for k in 0..n {
d[k][k] = work[k][k];
if is_near_zero(d[k][k]) {
return Err(MatrixError::NumericalInstability);
}
for i in (k + 1)..n {
l[i][k] = work[i][k] / d[k][k];
u[k][i] = work[k][i] / d[k][k];
}
for i in (k + 1)..n {
for j in (k + 1)..n {
work[i][j] -= l[i][k] * d[k][k] * u[k][j];
}
}
}
Ok((
Matrix::<R, C>::from_vec(l),
Matrix::<R, C>::from_vec(d),
Matrix::<R, C>::from_vec(u),
))
}
#[allow(non_snake_case)]
pub fn plu(&self) -> MatrixResult<(Matrix<R, C>, Matrix<R, C>, Matrix<R, C>)> {
if R != C {
return Err(MatrixError::NonSquare { rows: R, cols: C });
}
let n = R;
let mut a = self.data.clone();
let mut p = vec![vec![0.0; n]; n];
let mut l = vec![vec![0.0; n]; n];
for i in 0..n {
p[i][i] = 1.0;
}
for k in 0..n {
let mut max_val = a[k][k].abs();
let mut max_row = k;
for i in (k + 1)..n {
if a[i][k].abs() > max_val {
max_val = a[i][k].abs();
max_row = i;
}
}
if is_near_zero(max_val) {
return Err(MatrixError::NotInvertible);
}
if max_row != k {
a.swap(k, max_row);
p.swap(k, max_row);
for j in 0..k {
let tmp = l[k][j];
l[k][j] = l[max_row][j];
l[max_row][j] = tmp;
}
}
l[k][k] = 1.0;
for i in (k + 1)..n {
l[i][k] = a[i][k] / a[k][k];
for j in k..n {
a[i][j] -= l[i][k] * a[k][j];
}
}
}
Ok((
Matrix::<R, C>::from_vec(p),
Matrix::<R, C>::from_vec(l),
Matrix::<R, C>::from_vec(a),
))
}
pub fn cholesky(&self) -> MatrixResult<Matrix<R, C>> {
if R != C {
return Err(MatrixError::NonSquare { rows: R, cols: C });
}
if !self.is_symmetric() {
return Err(MatrixError::NotInvertible); }
let n = R;
let mut l = vec![vec![0.0; n]; n];
for i in 0..n {
for j in 0..=i {
let mut sum = 0.0;
if j == i {
for k in 0..j {
sum += l[j][k] * l[j][k];
}
let val = self.data[j][j] - sum;
if val <= 0.0 {
return Err(MatrixError::NotInvertible);
}
l[j][j] = val.sqrt();
} else {
for k in 0..j {
sum += l[i][k] * l[j][k];
}
if is_near_zero(l[j][j]) {
return Err(MatrixError::NumericalInstability);
}
l[i][j] = (self.data[i][j] - sum) / l[j][j];
}
}
}
Ok(Matrix::<R, C>::from_vec(l))
}
}
impl<const R: usize> Matrix<R, 1> {
pub fn norm(&self) -> f32 {
self.data
.iter()
.map(|row| row[0] * row[0])
.sum::<f32>()
.sqrt()
}
pub fn normalize(&self) -> MatrixResult<Matrix<R, 1>> {
let n = self.norm();
if is_near_zero(n) {
return Err(MatrixError::NumericalInstability);
}
let data: Vec<Vec<f32>> = self.data.iter().map(|row| vec![row[0] / n]).collect();
Ok(Matrix::<R, 1>::from_vec(data))
}
pub fn dot(&self, other: &Matrix<R, 1>) -> f32 {
(0..R).map(|i| self.data[i][0] * other.data[i][0]).sum()
}
}
impl Matrix<3, 1> {
pub fn cross(&self, other: &Matrix<3, 1>) -> Matrix<3, 1> {
let a = &self.data;
let b = &other.data;
Matrix::<3, 1>::new([
[a[1][0] * b[2][0] - a[2][0] * b[1][0]],
[a[2][0] * b[0][0] - a[0][0] * b[2][0]],
[a[0][0] * b[1][0] - a[1][0] * b[0][0]],
])
}
}
impl<const R: usize, const C: usize> Matrix<R, C> {
pub fn is_projection(&self) -> bool {
if R != C {
return false;
}
if let Ok(p_squared) = mult(&self.data, &self.data) {
for i in 0..R {
for j in 0..C {
if (p_squared[i][j] - self.data[i][j]).abs() > EPSILON * 100.0 {
return false;
}
}
}
true
} else {
false
}
}
pub fn pow(&self, exp: u32) -> MatrixResult<Matrix<R, C>> {
if R != C {
return Err(MatrixError::NonSquare { rows: R, cols: C });
}
if exp == 0 {
return Ok(Matrix::<R, C>::identity());
}
let mut result = Matrix::<R, C>::identity();
let mut base = self.clone();
let mut n = exp;
while n > 0 {
if n % 2 == 1 {
result = Matrix::<R, C>::from_vec(mult(&result.data, &base.data)?);
}
base = Matrix::<R, C>::from_vec(mult(&base.data, &base.data)?);
n /= 2;
}
Ok(result)
}
pub fn rank(&self) -> usize {
let mut work = self.data.clone();
let mut rank = 0;
for col in 0..C {
let mut pivot_row = None;
for row in rank..R {
if !is_near_zero(work[row][col]) {
pivot_row = Some(row);
break;
}
}
if let Some(pr) = pivot_row {
work.swap(rank, pr);
let pivot = work[rank][col];
for j in col..C {
work[rank][j] /= pivot;
}
for row in 0..R {
if row != rank && !is_near_zero(work[row][col]) {
let factor = work[row][col];
for j in col..C {
work[row][j] -= factor * work[rank][j];
}
}
}
rank += 1;
}
}
rank
}
}
impl<const R: usize, const C: usize> Matrix<R, C> {
pub fn scale(&self, scalar: f32) -> Matrix<R, C> {
let data: Vec<Vec<f32>> = self
.data
.iter()
.map(|row| row.iter().map(|&x| x * scalar).collect())
.collect();
Matrix::<R, C>::from_vec(data)
}
pub fn add_scalar(&self, scalar: f32) -> Matrix<R, C> {
let data: Vec<Vec<f32>> = self
.data
.iter()
.map(|row| row.iter().map(|&x| x + scalar).collect())
.collect();
Matrix::<R, C>::from_vec(data)
}
}
impl<const R: usize, const C: usize> Index<(usize, usize)> for Matrix<R, C> {
type Output = f32;
fn index(&self, (row, col): (usize, usize)) -> &Self::Output {
&self.data[row][col]
}
}
impl<const R: usize, const C: usize> IndexMut<(usize, usize)> for Matrix<R, C> {
fn index_mut(&mut self, (row, col): (usize, usize)) -> &mut Self::Output {
&mut self.data[row][col]
}
}
impl<const R: usize, const C: usize> Add for &Matrix<R, C> {
type Output = MatrixResult<Matrix<R, C>>;
fn add(self, rhs: Self) -> Self::Output {
let data: Vec<Vec<f32>> = self
.data
.iter()
.zip(rhs.data.iter())
.map(|(row_a, row_b)| {
row_a
.iter()
.zip(row_b.iter())
.map(|(&a, &b)| a + b)
.collect()
})
.collect();
Ok(Matrix::<R, C>::from_vec(data))
}
}
impl<const R: usize, const C: usize> Sub for &Matrix<R, C> {
type Output = MatrixResult<Matrix<R, C>>;
fn sub(self, rhs: Self) -> Self::Output {
let data: Vec<Vec<f32>> = self
.data
.iter()
.zip(rhs.data.iter())
.map(|(row_a, row_b)| {
row_a
.iter()
.zip(row_b.iter())
.map(|(&a, &b)| a - b)
.collect()
})
.collect();
Ok(Matrix::<R, C>::from_vec(data))
}
}
impl<const R: usize, const C: usize, const K: usize> Mul<&Matrix<C, K>> for &Matrix<R, C> {
type Output = MatrixResult<Matrix<R, K>>;
fn mul(self, rhs: &Matrix<C, K>) -> Self::Output {
let result = mult(&self.data, &rhs.data)?;
Ok(Matrix::<R, K>::from_vec(result))
}
}
impl<const R: usize, const C: usize> Neg for &Matrix<R, C> {
type Output = Matrix<R, C>;
fn neg(self) -> Self::Output {
self.scale(-1.0)
}
}
impl<const R: usize, const C: usize> Mul<f32> for &Matrix<R, C> {
type Output = Matrix<R, C>;
fn mul(self, rhs: f32) -> Self::Output {
self.scale(rhs)
}
}
impl<const R: usize, const C: usize> PartialEq for Matrix<R, C> {
fn eq(&self, other: &Self) -> bool {
for i in 0..R {
for j in 0..C {
if (self.data[i][j] - other.data[i][j]).abs() > EPSILON {
return false;
}
}
}
true
}
}
impl<const R: usize, const C: usize> Default for Matrix<R, C> {
fn default() -> Self {
Self::zeros()
}
}
impl<const R: usize, const C: usize> fmt::Display for Matrix<R, C> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "Matrix<{}, {}> [", R, C)?;
for row in &self.data {
write!(f, " [")?;
for (i, &val) in row.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{:8.4}", val)?;
}
writeln!(f, "]")?;
}
write!(f, "]")
}
}
impl<const R: usize, const C: usize> From<[[f32; C]; R]> for Matrix<R, C> {
fn from(arr: [[f32; C]; R]) -> Self {
Self::new(arr)
}
}
impl<const R: usize, const C: usize> From<Vec<Vec<f32>>> for Matrix<R, C> {
fn from(v: Vec<Vec<f32>>) -> Self {
Self::from_vec(v)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_new() {
let m = Matrix::<2, 2>::new([[1.0, 2.0], [3.0, 4.0]]);
assert_eq!(m[(0, 0)], 1.0);
assert_eq!(m[(1, 1)], 4.0);
}
#[test]
fn test_zeros() {
let m = Matrix::<3, 3>::zeros();
for i in 0..3 {
for j in 0..3 {
assert_eq!(m[(i, j)], 0.0);
}
}
}
#[test]
fn test_identity() {
let m = Matrix::<3, 3>::identity();
for i in 0..3 {
for j in 0..3 {
assert_eq!(m[(i, j)], if i == j { 1.0 } else { 0.0 });
}
}
}
#[test]
fn test_diagonal() {
let m = Matrix::<3, 3>::diagonal(&[1.0, 2.0, 3.0]);
assert_eq!(m[(0, 0)], 1.0);
assert_eq!(m[(1, 1)], 2.0);
assert_eq!(m[(2, 2)], 3.0);
assert_eq!(m[(0, 1)], 0.0);
}
#[test]
fn test_from_vec_non_square() {
let v = vec![vec![1.0, 2.0, 3.0], vec![4.0, 5.0, 6.0]];
let m = Matrix::<2, 3>::from_vec(v);
assert_eq!(m[(0, 2)], 3.0);
assert_eq!(m[(1, 0)], 4.0);
}
#[test]
fn test_transpose() {
let m = Matrix::<2, 3>::new([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]]);
let t = m.transpose();
assert_eq!(t.shape(), (3, 2));
assert_eq!(t[(0, 1)], 4.0);
assert_eq!(t[(2, 0)], 3.0);
}
#[test]
fn test_trace() {
let m = Matrix::<3, 3>::new([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [7.0, 8.0, 9.0]]);
assert_eq!(m.trace(), 15.0);
}
#[test]
fn test_frobenius_norm() {
let m = Matrix::<2, 2>::new([[3.0, 0.0], [4.0, 0.0]]);
assert!((m.frobenius_norm() - 5.0).abs() < 1e-6);
}
#[test]
fn test_determinant_2x2() {
let m = Matrix::<2, 2>::new([[1.0, 2.0], [3.0, 4.0]]);
assert!((m.determinant().unwrap() - (-2.0)).abs() < 1e-6);
}
#[test]
fn test_determinant_3x3() {
let m = Matrix::<3, 3>::new([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [7.0, 8.0, 10.0]]);
assert!((m.determinant().unwrap() - (-3.0)).abs() < 1e-4);
}
#[test]
fn test_determinant_singular() {
let m = Matrix::<2, 2>::new([[1.0, 2.0], [2.0, 4.0]]);
assert!((m.determinant().unwrap()).abs() < 1e-6);
}
#[test]
fn test_determinant_non_square() {
let m = Matrix::<2, 3>::new([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]]);
assert!(m.determinant().is_err());
}
#[test]
fn test_inverse_identity() {
let m = Matrix::<2, 2>::identity();
let inv = m.inverse().unwrap();
assert!((inv[(0, 0)] - 1.0).abs() < 1e-6);
assert!((inv[(1, 1)] - 1.0).abs() < 1e-6);
}
#[test]
fn test_inverse_2x2() {
let m = Matrix::<2, 2>::new([[4.0, 7.0], [2.0, 6.0]]);
let inv = m.inverse().unwrap();
assert!((inv[(0, 0)] - 0.6).abs() < 1e-6);
assert!((inv[(0, 1)] - (-0.7)).abs() < 1e-6);
}
#[test]
fn test_inverse_singular() {
let m = Matrix::<2, 2>::new([[1.0, 2.0], [2.0, 4.0]]);
assert!(m.inverse().is_err());
}
#[test]
fn test_pow_zero() {
let m = Matrix::<2, 2>::new([[2.0, 3.0], [1.0, 4.0]]);
let p = m.pow(0).unwrap();
assert_eq!(p, Matrix::<2, 2>::identity());
}
#[test]
fn test_pow_one() {
let m = Matrix::<2, 2>::new([[2.0, 3.0], [1.0, 4.0]]);
let p = m.pow(1).unwrap();
assert_eq!(p, m);
}
#[test]
fn test_pow_squared() {
let m = Matrix::<2, 2>::new([[1.0, 2.0], [3.0, 4.0]]);
let p = m.pow(2).unwrap();
assert!((p[(0, 0)] - 7.0).abs() < 1e-6);
assert!((p[(0, 1)] - 10.0).abs() < 1e-6);
assert!((p[(1, 0)] - 15.0).abs() < 1e-6);
assert!((p[(1, 1)] - 22.0).abs() < 1e-6);
}
#[test]
fn test_add() {
let a = Matrix::<2, 2>::new([[1.0, 2.0], [3.0, 4.0]]);
let b = Matrix::<2, 2>::new([[5.0, 6.0], [7.0, 8.0]]);
let c = (&a + &b).unwrap();
assert_eq!(c[(0, 0)], 6.0);
assert_eq!(c[(1, 1)], 12.0);
}
#[test]
fn test_sub() {
let a = Matrix::<2, 2>::new([[5.0, 6.0], [7.0, 8.0]]);
let b = Matrix::<2, 2>::new([[1.0, 2.0], [3.0, 4.0]]);
let c = (&a - &b).unwrap();
assert_eq!(c[(0, 0)], 4.0);
assert_eq!(c[(1, 1)], 4.0);
}
#[test]
fn test_mul() {
let a = Matrix::<2, 2>::new([[1.0, 2.0], [3.0, 4.0]]);
let b = Matrix::<2, 2>::new([[5.0, 6.0], [7.0, 8.0]]);
let c = (&a * &b).unwrap();
assert_eq!(c[(0, 0)], 19.0);
assert_eq!(c[(0, 1)], 22.0);
assert_eq!(c[(1, 0)], 43.0);
assert_eq!(c[(1, 1)], 50.0);
}
#[test]
fn test_neg() {
let m = Matrix::<2, 2>::new([[1.0, 2.0], [3.0, 4.0]]);
let n = -&m;
assert_eq!(n[(0, 0)], -1.0);
assert_eq!(n[(1, 1)], -4.0);
}
#[test]
fn test_scalar_mul() {
let m = Matrix::<2, 2>::new([[1.0, 2.0], [3.0, 4.0]]);
let n = &m * 2.0;
assert_eq!(n[(0, 0)], 2.0);
assert_eq!(n[(1, 1)], 8.0);
}
#[test]
fn test_vector_norm() {
let v = Matrix::<3, 1>::new([[3.0], [4.0], [0.0]]);
assert!((v.norm() - 5.0).abs() < 1e-6);
}
#[test]
fn test_vector_dot() {
let a = Matrix::<3, 1>::new([[1.0], [2.0], [3.0]]);
let b = Matrix::<3, 1>::new([[4.0], [5.0], [6.0]]);
assert_eq!(a.dot(&b), 32.0);
}
#[test]
fn test_vector_cross() {
let a = Matrix::<3, 1>::new([[1.0], [0.0], [0.0]]);
let b = Matrix::<3, 1>::new([[0.0], [1.0], [0.0]]);
let c = a.cross(&b);
assert!((c[(0, 0)] - 0.0).abs() < 1e-6);
assert!((c[(1, 0)] - 0.0).abs() < 1e-6);
assert!((c[(2, 0)] - 1.0).abs() < 1e-6);
}
#[test]
fn test_qr() {
let m = Matrix::<3, 3>::new([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [7.0, 8.0, 10.0]]);
let (q, r) = m.qr().unwrap();
assert!(q.is_orthogonal());
let reconstructed = (&q * &r).unwrap();
for i in 0..3 {
for j in 0..3 {
assert!((reconstructed[(i, j)] - m[(i, j)]).abs() < 1e-4);
}
}
}
#[test]
fn test_lu() {
let m = Matrix::<3, 3>::new([[2.0, 1.0, 1.0], [4.0, 3.0, 3.0], [8.0, 7.0, 9.0]]);
let (l, u) = m.lu().unwrap();
let reconstructed = (&l * &u).unwrap();
for i in 0..3 {
for j in 0..3 {
assert!((reconstructed[(i, j)] - m[(i, j)]).abs() < 1e-4);
}
}
}
#[test]
fn test_is_symmetric() {
let m = Matrix::<3, 3>::new([[1.0, 2.0, 3.0], [2.0, 4.0, 5.0], [3.0, 5.0, 6.0]]);
assert!(m.is_symmetric());
let n = Matrix::<2, 2>::new([[1.0, 2.0], [3.0, 4.0]]);
assert!(!n.is_symmetric());
}
#[test]
fn test_is_projection() {
let identity = Matrix::<2, 2>::identity();
assert!(identity.is_projection());
let zero = Matrix::<2, 2>::zeros();
assert!(zero.is_projection());
}
#[test]
fn test_rank() {
let m = Matrix::<3, 3>::new([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [7.0, 8.0, 9.0]]);
assert_eq!(m.rank(), 2);
let n = Matrix::<2, 2>::identity();
assert_eq!(n.rank(), 2);
}
#[test]
fn test_1x1_matrix() {
let m = Matrix::<1, 1>::new([[5.0]]);
assert_eq!(m.determinant().unwrap(), 5.0);
assert!((m.inverse().unwrap()[(0, 0)] - 0.2).abs() < 1e-6);
}
#[test]
fn test_display() {
let m = Matrix::<2, 2>::new([[1.0, 2.0], [3.0, 4.0]]);
let s = format!("{}", m);
assert!(s.contains("Matrix<2, 2>"));
}
#[test]
fn test_equality() {
let a = Matrix::<2, 2>::new([[1.0, 2.0], [3.0, 4.0]]);
let b = Matrix::<2, 2>::new([[1.0, 2.0], [3.0, 4.0]]);
let c = Matrix::<2, 2>::new([[1.0, 2.0], [3.0, 5.0]]);
assert_eq!(a, b);
assert_ne!(a, c);
}
#[test]
fn test_plu() {
let m = Matrix::<3, 3>::new([[0.0, 1.0, 2.0], [1.0, 2.0, 3.0], [2.0, 3.0, 5.0]]);
let (p, l, u) = m.plu().unwrap();
let pa = (&p * &m).unwrap();
let lu = (&l * &u).unwrap();
for i in 0..3 {
for j in 0..3 {
assert!((pa[(i, j)] - lu[(i, j)]).abs() < 1e-5);
}
}
}
#[test]
fn test_plu_identity() {
let m = Matrix::<3, 3>::identity();
let (_p, l, u) = m.plu().unwrap();
for i in 0..3 {
assert!((l[(i, i)] - 1.0).abs() < 1e-6);
assert!((u[(i, i)] - 1.0).abs() < 1e-6);
}
}
#[test]
fn test_cholesky() {
let m = Matrix::<3, 3>::new([[4.0, 2.0, 2.0], [2.0, 5.0, 1.0], [2.0, 1.0, 6.0]]);
let l = m.cholesky().unwrap();
let lt = l.transpose();
let result = (&l * <).unwrap();
for i in 0..3 {
for j in 0..3 {
assert!((result[(i, j)] - m[(i, j)]).abs() < 1e-5);
}
}
}
#[test]
fn test_cholesky_not_positive_definite() {
let m = Matrix::<2, 2>::new([[1.0, 2.0], [2.0, 1.0]]);
assert!(m.cholesky().is_err());
}
#[test]
fn test_cholesky_not_symmetric() {
let m = Matrix::<2, 2>::new([[1.0, 2.0], [3.0, 4.0]]);
assert!(m.cholesky().is_err());
}
#[test]
fn test_large_matrix_identity() {
let m = Matrix::<10, 10>::identity();
assert_eq!(m.determinant().unwrap(), 1.0);
assert_eq!(m.rank(), 10);
assert!(m.is_symmetric());
assert!(m.is_orthogonal());
}
#[test]
fn test_solve_singular_system() {
let a = Matrix::<2, 2>::new([[1.0, 2.0], [2.0, 4.0]]);
let b = Matrix::<2, 1>::new([[1.0], [2.0]]);
assert!(a.solve(&b).is_err());
}
#[test]
fn test_inverse_times_original_is_identity() {
let m = Matrix::<3, 3>::new([[1.0, 2.0, 3.0], [0.0, 1.0, 4.0], [5.0, 6.0, 0.0]]);
let inv = m.inverse().unwrap();
let product = (&m * &inv).unwrap();
for i in 0..3 {
for j in 0..3 {
let expected = if i == j { 1.0 } else { 0.0 };
assert!((product[(i, j)] - expected).abs() < 1e-4);
}
}
}
#[test]
fn test_transpose_twice_is_original() {
let m = Matrix::<2, 3>::new([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]]);
let tt = m.transpose().transpose();
assert_eq!(m, tt);
}
#[test]
fn test_zero_matrix_properties() {
let z = Matrix::<3, 3>::zeros();
assert_eq!(z.determinant().unwrap(), 0.0);
assert_eq!(z.trace(), 0.0);
assert_eq!(z.rank(), 0);
assert!(z.is_symmetric());
assert!(z.is_projection()); }
#[test]
fn test_scalar_multiplication_properties() {
let m = Matrix::<2, 2>::new([[1.0, 2.0], [3.0, 4.0]]);
let scaled = m.scale(2.0);
assert_eq!(scaled.transpose(), m.transpose().scale(2.0));
let det_scaled = scaled.determinant().unwrap();
let det_original = m.determinant().unwrap();
assert!((det_scaled - 4.0 * det_original).abs() < 1e-6);
}
#[test]
fn test_matrix_addition_commutativity() {
let a = Matrix::<2, 2>::new([[1.0, 2.0], [3.0, 4.0]]);
let b = Matrix::<2, 2>::new([[5.0, 6.0], [7.0, 8.0]]);
assert_eq!((&a + &b).unwrap(), (&b + &a).unwrap());
}
#[test]
fn test_matrix_multiplication_associativity() {
let a = Matrix::<2, 2>::new([[1.0, 2.0], [3.0, 4.0]]);
let b = Matrix::<2, 2>::new([[5.0, 6.0], [7.0, 8.0]]);
let c = Matrix::<2, 2>::new([[9.0, 10.0], [11.0, 12.0]]);
let ab_c = (&(&a * &b).unwrap() * &c).unwrap();
let a_bc = (&a * &(&b * &c).unwrap()).unwrap();
for i in 0..2 {
for j in 0..2 {
assert!((ab_c[(i, j)] - a_bc[(i, j)]).abs() < 1e-4);
}
}
}
#[test]
fn test_frobenius_norm_properties() {
let m = Matrix::<2, 2>::new([[3.0, 4.0], [0.0, 0.0]]);
assert!((m.frobenius_norm() - 5.0).abs() < 1e-6);
let scaled = m.scale(2.0);
assert!((scaled.frobenius_norm() - 10.0).abs() < 1e-6);
}
#[test]
fn test_pow_larger_exponent() {
let m = Matrix::<2, 2>::new([[1.0, 1.0], [0.0, 1.0]]);
let p5 = m.pow(5).unwrap();
assert!((p5[(0, 1)] - 5.0).abs() < 1e-6);
}
#[test]
fn test_diagonal_matrix_properties() {
let d = Matrix::<3, 3>::diagonal(&[2.0, 3.0, 4.0]);
assert_eq!(d.determinant().unwrap(), 24.0); assert_eq!(d.trace(), 9.0); assert!(d.is_symmetric());
}
#[test]
fn test_indexing() {
let mut m = Matrix::<2, 2>::new([[1.0, 2.0], [3.0, 4.0]]);
assert_eq!(m[(0, 1)], 2.0);
m[(0, 1)] = 10.0;
assert_eq!(m[(0, 1)], 10.0);
}
#[test]
fn test_default_is_zeros() {
let m: Matrix<3, 3> = Matrix::default();
assert_eq!(m, Matrix::<3, 3>::zeros());
}
#[test]
fn test_from_vec_of_vecs() {
let v = vec![vec![1.0, 2.0], vec![3.0, 4.0]];
let m: Matrix<2, 2> = v.into();
assert_eq!(m[(0, 0)], 1.0);
assert_eq!(m[(1, 1)], 4.0);
}
}