#[allow(unused_imports)]
use crate::prelude::*;
use core::fmt;
use num_rational::Rational64;
use num_traits::Signed;
#[derive(Debug, Clone, PartialEq)]
pub struct Matrix {
rows: usize,
cols: usize,
data: Vec<Rational64>,
}
impl Matrix {
pub fn zeros(rows: usize, cols: usize) -> Self {
Self {
rows,
cols,
data: vec![Rational64::from(0); rows * cols],
}
}
pub fn identity(size: usize) -> Self {
let mut matrix = Self::zeros(size, size);
for i in 0..size {
matrix.set(i, i, Rational64::from(1));
}
matrix
}
pub fn from_rows(rows: Vec<Vec<Rational64>>) -> Self {
if rows.is_empty() {
return Self::zeros(0, 0);
}
let nrows = rows.len();
let ncols = rows[0].len();
let mut data = Vec::with_capacity(nrows * ncols);
for row in rows {
assert_eq!(row.len(), ncols, "All rows must have the same length");
data.extend(row);
}
Self {
rows: nrows,
cols: ncols,
data,
}
}
pub fn from_vec(rows: usize, cols: usize, data: Vec<Rational64>) -> Self {
assert_eq!(data.len(), rows * cols, "Data size must match dimensions");
Self { rows, cols, data }
}
pub fn nrows(&self) -> usize {
self.rows
}
pub fn ncols(&self) -> usize {
self.cols
}
pub fn get(&self, row: usize, col: usize) -> Rational64 {
assert!(row < self.rows && col < self.cols);
self.data[row * self.cols + col]
}
pub fn set(&mut self, row: usize, col: usize, value: Rational64) {
assert!(row < self.rows && col < self.cols);
self.data[row * self.cols + col] = value;
}
pub fn row(&self, row: usize) -> &[Rational64] {
assert!(row < self.rows);
let start = row * self.cols;
&self.data[start..start + self.cols]
}
pub fn swap_rows(&mut self, i: usize, j: usize) {
assert!(i < self.rows && j < self.rows);
if i == j {
return;
}
for k in 0..self.cols {
let idx_i = i * self.cols + k;
let idx_j = j * self.cols + k;
self.data.swap(idx_i, idx_j);
}
}
pub fn add_row_multiple(&mut self, i: usize, j: usize, scalar: Rational64) {
assert!(i < self.rows && j < self.rows);
if scalar == Rational64::from(0) {
return;
}
for k in 0..self.cols {
let idx_i = i * self.cols + k;
let idx_j = j * self.cols + k;
let val_j = self.data[idx_j];
self.data[idx_i] += scalar * val_j;
}
}
pub fn scale_row(&mut self, row: usize, scalar: Rational64) {
assert!(row < self.rows);
for k in 0..self.cols {
let idx = row * self.cols + k;
self.data[idx] *= scalar;
}
}
#[allow(clippy::needless_range_loop)]
pub fn mul_vec(&self, v: &[Rational64]) -> Vec<Rational64> {
assert_eq!(
v.len(),
self.cols,
"Vector dimension must match matrix columns"
);
let mut result = vec![Rational64::from(0); self.rows];
for i in 0..self.rows {
for j in 0..self.cols {
result[i] += self.get(i, j) * v[j];
}
}
result
}
pub fn mul(&self, other: &Matrix) -> Matrix {
assert_eq!(
self.cols, other.rows,
"Matrix dimensions must be compatible for multiplication"
);
let mut result = Matrix::zeros(self.rows, other.cols);
for i in 0..self.rows {
for j in 0..other.cols {
let mut sum = Rational64::from(0);
for k in 0..self.cols {
sum += self.get(i, k) * other.get(k, j);
}
result.set(i, j, sum);
}
}
result
}
pub fn transpose(&self) -> Matrix {
let mut result = Matrix::zeros(self.cols, self.rows);
for i in 0..self.rows {
for j in 0..self.cols {
result.set(j, i, self.get(i, j));
}
}
result
}
pub fn gaussian_elimination(&self) -> (Matrix, usize) {
let mut result = self.clone();
let mut rank = 0;
for col in 0..self.cols.min(self.rows) {
let mut pivot_row = None;
for row in rank..self.rows {
if result.get(row, col) != Rational64::from(0) {
pivot_row = Some(row);
break;
}
}
let pivot_row = match pivot_row {
Some(r) => r,
None => continue, };
if pivot_row != rank {
result.swap_rows(rank, pivot_row);
}
let pivot = result.get(rank, col);
for row in rank + 1..self.rows {
let factor = result.get(row, col) / pivot;
result.add_row_multiple(row, rank, -factor);
}
rank += 1;
}
(result, rank)
}
pub fn lu_decomposition(&self) -> Option<(Matrix, Matrix, Vec<usize>)> {
if self.rows != self.cols {
return None; }
let n = self.rows;
let mut l = Matrix::zeros(n, n);
let mut u = self.clone();
let mut p: Vec<usize> = (0..n).collect();
for i in 0..n {
let mut max_row = i;
let mut max_val = u.get(i, i).abs();
for k in i + 1..n {
let val = u.get(k, i).abs();
if val > max_val {
max_val = val;
max_row = k;
}
}
if max_row != i {
u.swap_rows(i, max_row);
p.swap(i, max_row);
for k in 0..i {
let tmp = l.get(i, k);
l.set(i, k, l.get(max_row, k));
l.set(max_row, k, tmp);
}
}
let pivot = u.get(i, i);
if pivot == Rational64::from(0) {
return None; }
l.set(i, i, Rational64::from(1));
for k in i + 1..n {
let factor = u.get(k, i) / pivot;
l.set(k, i, factor);
for j in i..n {
let val = u.get(k, j) - factor * u.get(i, j);
u.set(k, j, val);
}
}
}
Some((l, u, p))
}
#[allow(clippy::needless_range_loop)]
pub fn solve(&self, b: &[Rational64]) -> Option<Vec<Rational64>> {
assert_eq!(
b.len(),
self.rows,
"Right-hand side dimension must match matrix rows"
);
if self.rows != self.cols {
return None; }
let mut aug = Matrix::zeros(self.rows, self.cols + 1);
for i in 0..self.rows {
for j in 0..self.cols {
aug.set(i, j, self.get(i, j));
}
aug.set(i, self.cols, b[i]);
}
for col in 0..self.cols {
let mut pivot_row = None;
for row in col..self.rows {
if aug.get(row, col) != Rational64::from(0) {
pivot_row = Some(row);
break;
}
}
let pivot_row = pivot_row?;
if pivot_row != col {
aug.swap_rows(col, pivot_row);
}
let pivot = aug.get(col, col);
for row in col + 1..self.rows {
let factor = aug.get(row, col) / pivot;
aug.add_row_multiple(row, col, -factor);
}
}
let mut x = vec![Rational64::from(0); self.cols];
for i in (0..self.rows).rev() {
let mut sum = aug.get(i, self.cols);
for j in i + 1..self.cols {
sum -= aug.get(i, j) * x[j];
}
let pivot = aug.get(i, i);
if pivot == Rational64::from(0) {
return None; }
x[i] = sum / pivot;
}
Some(x)
}
pub fn determinant(&self) -> Option<Rational64> {
if self.rows != self.cols {
return None;
}
let (_, u, p) = self.lu_decomposition()?;
let mut det = Rational64::from(1);
for i in 0..self.rows {
det *= u.get(i, i);
}
let mut swaps = 0;
let mut visited = vec![false; p.len()];
for i in 0..p.len() {
if visited[i] {
continue;
}
let mut j = i;
let mut cycle_len = 0;
while !visited[j] {
visited[j] = true;
j = p[j];
cycle_len += 1;
}
if cycle_len > 1 {
swaps += cycle_len - 1;
}
}
if swaps % 2 == 1 {
det = -det;
}
Some(det)
}
pub fn qr_decomposition(&self) -> Option<(Matrix, Matrix)> {
let m = self.rows;
let n = self.cols;
if m < n {
return None; }
let mut q = Matrix::zeros(m, n);
let mut r = Matrix::zeros(n, n);
for j in 0..n {
for i in 0..m {
q.set(i, j, self.get(i, j));
}
for k in 0..j {
let mut dot_qk_aj = Rational64::from(0);
let mut dot_qk_qk = Rational64::from(0);
for i in 0..m {
dot_qk_aj += q.get(i, k) * self.get(i, j);
dot_qk_qk += q.get(i, k) * q.get(i, k);
}
if dot_qk_qk == Rational64::from(0) {
return None;
}
let proj_coeff = dot_qk_aj / dot_qk_qk;
r.set(k, j, proj_coeff);
for i in 0..m {
let new_val = q.get(i, j) - proj_coeff * q.get(i, k);
q.set(i, j, new_val);
}
}
let mut norm_sq = Rational64::from(0);
for i in 0..m {
let val = q.get(i, j);
norm_sq += val * val;
}
if norm_sq == Rational64::from(0) {
return None;
}
r.set(j, j, Rational64::from(1));
}
Some((q, r))
}
pub fn cholesky_decomposition(&self) -> Option<Matrix> {
if self.rows != self.cols {
return None; }
let n = self.rows;
for i in 0..n {
for j in i + 1..n {
if self.get(i, j) != self.get(j, i) {
return None; }
}
}
let mut l = Matrix::zeros(n, n);
for i in 0..n {
for j in 0..=i {
let mut sum = self.get(i, j);
for k in 0..j {
sum -= l.get(i, k) * l.get(j, k);
}
if i == j {
if sum <= Rational64::from(0) {
return None; }
let sqrt_val = rational_sqrt(sum)?;
l.set(i, i, sqrt_val);
} else {
let l_jj = l.get(j, j);
if l_jj == Rational64::from(0) {
return None;
}
l.set(i, j, sum / l_jj);
}
}
}
Some(l)
}
pub fn inverse(&self) -> Option<Matrix> {
if self.rows != self.cols {
return None; }
let n = self.rows;
let (l, u, p) = self.lu_decomposition()?;
let mut inv = Matrix::zeros(n, n);
for col in 0..n {
let mut b = vec![Rational64::from(0); n];
b[col] = Rational64::from(1);
let mut pb = vec![Rational64::from(0); n];
for i in 0..n {
pb[i] = b[p[i]];
}
let mut y = vec![Rational64::from(0); n];
for i in 0..n {
let mut sum = pb[i];
for (j, &y_j) in y.iter().enumerate().take(i) {
sum -= l.get(i, j) * y_j;
}
y[i] = sum; }
let mut x = vec![Rational64::from(0); n];
for i in (0..n).rev() {
let mut sum = y[i];
for (j, &x_j) in x.iter().enumerate().skip(i + 1).take(n - i - 1) {
sum -= u.get(i, j) * x_j;
}
let u_ii = u.get(i, i);
if u_ii == Rational64::from(0) {
return None; }
x[i] = sum / u_ii;
}
for (i, &x_i) in x.iter().enumerate() {
inv.set(i, col, x_i);
}
}
Some(inv)
}
pub fn is_identity(&self) -> bool {
if self.rows != self.cols {
return false;
}
for i in 0..self.rows {
for j in 0..self.cols {
let expected = if i == j {
Rational64::from(1)
} else {
Rational64::from(0)
};
if self.get(i, j) != expected {
return false;
}
}
}
true
}
}
fn rational_sqrt(r: Rational64) -> Option<Rational64> {
use num_integer::Roots;
use num_traits::Zero;
if r < Rational64::zero() {
return None;
}
if r == Rational64::zero() {
return Some(Rational64::zero());
}
let numer = r.numer();
let denom = r.denom();
let sqrt_numer = numer.sqrt();
let sqrt_denom = denom.sqrt();
if sqrt_numer * sqrt_numer == *numer && sqrt_denom * sqrt_denom == *denom {
Some(Rational64::new(sqrt_numer, sqrt_denom))
} else {
None
}
}
impl fmt::Display for Matrix {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "[")?;
for i in 0..self.rows {
write!(f, " [")?;
for j in 0..self.cols {
if j > 0 {
write!(f, ", ")?;
}
write!(f, "{}", self.get(i, j))?;
}
writeln!(f, "]")?;
}
write!(f, "]")
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct SparseMatrix {
rows: usize,
cols: usize,
row_ptr: Vec<usize>,
col_indices: Vec<usize>,
values: Vec<Rational64>,
}
impl SparseMatrix {
pub fn new(rows: usize, cols: usize) -> Self {
Self {
rows,
cols,
row_ptr: vec![0; rows + 1],
col_indices: Vec::new(),
values: Vec::new(),
}
}
pub fn identity(size: usize) -> Self {
let mut matrix = Self::new(size, size);
matrix.row_ptr = (0..=size).collect();
matrix.col_indices = (0..size).collect();
matrix.values = vec![Rational64::from(1); size];
matrix
}
pub fn from_dense(dense: &Matrix) -> Self {
let mut matrix = Self::new(dense.nrows(), dense.ncols());
let mut col_indices = Vec::new();
let mut values = Vec::new();
for i in 0..dense.nrows() {
matrix.row_ptr[i] = values.len();
for j in 0..dense.ncols() {
let val = dense.get(i, j);
if val != Rational64::from(0) {
col_indices.push(j);
values.push(val);
}
}
}
matrix.row_ptr[dense.nrows()] = values.len();
matrix.col_indices = col_indices;
matrix.values = values;
matrix
}
pub fn nrows(&self) -> usize {
self.rows
}
pub fn ncols(&self) -> usize {
self.cols
}
pub fn nnz(&self) -> usize {
self.values.len()
}
pub fn get(&self, row: usize, col: usize) -> Rational64 {
assert!(row < self.rows && col < self.cols);
let start = self.row_ptr[row];
let end = self.row_ptr[row + 1];
for i in start..end {
if self.col_indices[i] == col {
return self.values[i];
} else if self.col_indices[i] > col {
break;
}
}
Rational64::from(0)
}
pub fn set(&mut self, row: usize, col: usize, value: Rational64) {
assert!(row < self.rows && col < self.cols);
if value == Rational64::from(0) {
let start = self.row_ptr[row];
let end = self.row_ptr[row + 1];
for i in start..end {
if self.col_indices[i] == col {
self.col_indices.remove(i);
self.values.remove(i);
for r in row + 1..=self.rows {
self.row_ptr[r] -= 1;
}
return;
}
}
return;
}
let start = self.row_ptr[row];
let end = self.row_ptr[row + 1];
for i in start..end {
if self.col_indices[i] == col {
self.values[i] = value;
return;
} else if self.col_indices[i] > col {
self.col_indices.insert(i, col);
self.values.insert(i, value);
for r in row + 1..=self.rows {
self.row_ptr[r] += 1;
}
return;
}
}
self.col_indices.insert(end, col);
self.values.insert(end, value);
for r in row + 1..=self.rows {
self.row_ptr[r] += 1;
}
}
#[allow(clippy::needless_range_loop)]
pub fn mul_vec(&self, v: &[Rational64]) -> Vec<Rational64> {
assert_eq!(
v.len(),
self.cols,
"Vector dimension must match matrix columns"
);
let mut result = vec![Rational64::from(0); self.rows];
for i in 0..self.rows {
let start = self.row_ptr[i];
let end = self.row_ptr[i + 1];
let mut sum = Rational64::from(0);
for k in start..end {
sum += self.values[k] * v[self.col_indices[k]];
}
result[i] = sum;
}
result
}
pub fn mul(&self, other: &SparseMatrix) -> SparseMatrix {
assert_eq!(
self.cols, other.rows,
"Matrix dimensions must be compatible for multiplication"
);
let mut result = SparseMatrix::new(self.rows, other.cols);
let mut row_data: Vec<Rational64> = vec![Rational64::from(0); other.cols];
let mut col_indices = Vec::new();
let mut values = Vec::new();
for i in 0..self.rows {
result.row_ptr[i] = values.len();
row_data.fill(Rational64::from(0));
let start = self.row_ptr[i];
let end = self.row_ptr[i + 1];
for k in start..end {
let a_val = &self.values[k];
let k_col = self.col_indices[k];
let b_start = other.row_ptr[k_col];
let b_end = other.row_ptr[k_col + 1];
for j in b_start..b_end {
let b_col = other.col_indices[j];
let b_val = &other.values[j];
row_data[b_col] += a_val * b_val;
}
}
for (j, val) in row_data.iter().enumerate() {
if val != &Rational64::from(0) {
col_indices.push(j);
values.push(*val);
}
}
}
result.row_ptr[self.rows] = values.len();
result.col_indices = col_indices;
result.values = values;
result
}
pub fn transpose(&self) -> SparseMatrix {
let mut result = SparseMatrix::new(self.cols, self.rows);
let mut col_counts = vec![0; self.cols];
for &col in &self.col_indices {
col_counts[col] += 1;
}
result.row_ptr[0] = 0;
#[allow(clippy::needless_range_loop)]
for i in 0..self.cols {
result.row_ptr[i + 1] = result.row_ptr[i] + col_counts[i];
}
result.col_indices = vec![0; self.nnz()];
result.values = vec![Rational64::from(0); self.nnz()];
let mut current_pos = result.row_ptr.clone();
for i in 0..self.rows {
let start = self.row_ptr[i];
let end = self.row_ptr[i + 1];
for k in start..end {
let col = self.col_indices[k];
let pos = current_pos[col];
result.col_indices[pos] = i;
result.values[pos] = self.values[k];
current_pos[col] += 1;
}
}
result
}
pub fn to_dense(&self) -> Matrix {
let mut dense = Matrix::zeros(self.rows, self.cols);
for i in 0..self.rows {
let start = self.row_ptr[i];
let end = self.row_ptr[i + 1];
for k in start..end {
dense.set(i, self.col_indices[k], self.values[k]);
}
}
dense
}
}
impl fmt::Display for SparseMatrix {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(
f,
"SparseMatrix({}x{}, {} non-zeros):",
self.rows,
self.cols,
self.nnz()
)?;
for i in 0..self.rows {
write!(f, " Row {}: ", i)?;
let start = self.row_ptr[i];
let end = self.row_ptr[i + 1];
for k in start..end {
write!(f, "({}, {}) ", self.col_indices[k], self.values[k])?;
}
writeln!(f)?;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_matrix_zeros() {
let m = Matrix::zeros(2, 3);
assert_eq!(m.nrows(), 2);
assert_eq!(m.ncols(), 3);
assert_eq!(m.get(0, 0), Rational64::from(0));
assert_eq!(m.get(1, 2), Rational64::from(0));
}
#[test]
fn test_matrix_identity() {
let m = Matrix::identity(3);
assert_eq!(m.get(0, 0), Rational64::from(1));
assert_eq!(m.get(1, 1), Rational64::from(1));
assert_eq!(m.get(2, 2), Rational64::from(1));
assert_eq!(m.get(0, 1), Rational64::from(0));
assert_eq!(m.get(1, 2), Rational64::from(0));
}
#[test]
fn test_matrix_from_rows() {
let m = Matrix::from_rows(vec![
vec![Rational64::from(1), Rational64::from(2)],
vec![Rational64::from(3), Rational64::from(4)],
]);
assert_eq!(m.get(0, 0), Rational64::from(1));
assert_eq!(m.get(0, 1), Rational64::from(2));
assert_eq!(m.get(1, 0), Rational64::from(3));
assert_eq!(m.get(1, 1), Rational64::from(4));
}
#[test]
fn test_matrix_swap_rows() {
let mut m = Matrix::from_rows(vec![
vec![Rational64::from(1), Rational64::from(2)],
vec![Rational64::from(3), Rational64::from(4)],
]);
m.swap_rows(0, 1);
assert_eq!(m.get(0, 0), Rational64::from(3));
assert_eq!(m.get(0, 1), Rational64::from(4));
assert_eq!(m.get(1, 0), Rational64::from(1));
assert_eq!(m.get(1, 1), Rational64::from(2));
}
#[test]
fn test_matrix_add_row_multiple() {
let mut m = Matrix::from_rows(vec![
vec![Rational64::from(1), Rational64::from(2)],
vec![Rational64::from(3), Rational64::from(4)],
]);
m.add_row_multiple(1, 0, Rational64::from(2));
assert_eq!(m.get(0, 0), Rational64::from(1));
assert_eq!(m.get(0, 1), Rational64::from(2));
assert_eq!(m.get(1, 0), Rational64::from(5)); assert_eq!(m.get(1, 1), Rational64::from(8)); }
#[test]
fn test_matrix_mul_vec() {
let m = Matrix::from_rows(vec![
vec![Rational64::from(1), Rational64::from(2)],
vec![Rational64::from(3), Rational64::from(4)],
]);
let v = vec![Rational64::from(5), Rational64::from(6)];
let result = m.mul_vec(&v);
assert_eq!(result[0], Rational64::from(17)); assert_eq!(result[1], Rational64::from(39)); }
#[test]
fn test_matrix_mul() {
let a = Matrix::from_rows(vec![
vec![Rational64::from(1), Rational64::from(2)],
vec![Rational64::from(3), Rational64::from(4)],
]);
let b = Matrix::from_rows(vec![
vec![Rational64::from(5), Rational64::from(6)],
vec![Rational64::from(7), Rational64::from(8)],
]);
let c = a.mul(&b);
assert_eq!(c.get(0, 0), Rational64::from(19)); assert_eq!(c.get(0, 1), Rational64::from(22)); assert_eq!(c.get(1, 0), Rational64::from(43)); assert_eq!(c.get(1, 1), Rational64::from(50)); }
#[test]
fn test_matrix_transpose() {
let m = Matrix::from_rows(vec![
vec![
Rational64::from(1),
Rational64::from(2),
Rational64::from(3),
],
vec![
Rational64::from(4),
Rational64::from(5),
Rational64::from(6),
],
]);
let t = m.transpose();
assert_eq!(t.nrows(), 3);
assert_eq!(t.ncols(), 2);
assert_eq!(t.get(0, 0), Rational64::from(1));
assert_eq!(t.get(1, 0), Rational64::from(2));
assert_eq!(t.get(2, 0), Rational64::from(3));
assert_eq!(t.get(0, 1), Rational64::from(4));
assert_eq!(t.get(1, 1), Rational64::from(5));
assert_eq!(t.get(2, 1), Rational64::from(6));
}
#[test]
fn test_gaussian_elimination() {
let m = Matrix::from_rows(vec![
vec![
Rational64::from(2),
Rational64::from(1),
Rational64::from(-1),
],
vec![
Rational64::from(-3),
Rational64::from(-1),
Rational64::from(2),
],
vec![
Rational64::from(-2),
Rational64::from(1),
Rational64::from(2),
],
]);
let (ref_form, rank) = m.gaussian_elimination();
assert_eq!(rank, 3);
assert_ne!(ref_form.get(0, 0), Rational64::from(0));
assert_eq!(ref_form.get(1, 0), Rational64::from(0));
assert_eq!(ref_form.get(2, 0), Rational64::from(0));
}
#[test]
fn test_solve_linear_system() {
let a = Matrix::from_rows(vec![
vec![
Rational64::from(2),
Rational64::from(1),
Rational64::from(-1),
],
vec![
Rational64::from(-3),
Rational64::from(-1),
Rational64::from(2),
],
vec![
Rational64::from(-2),
Rational64::from(1),
Rational64::from(2),
],
]);
let b = vec![
Rational64::from(8),
Rational64::from(-11),
Rational64::from(-3),
];
let x = a.solve(&b).expect("test operation should succeed");
assert_eq!(x[0], Rational64::from(2));
assert_eq!(x[1], Rational64::from(3));
assert_eq!(x[2], Rational64::from(-1));
}
#[test]
fn test_lu_decomposition() {
let m = Matrix::from_rows(vec![
vec![Rational64::from(4), Rational64::from(3)],
vec![Rational64::from(6), Rational64::from(3)],
]);
let (l, u, _p) = m.lu_decomposition().expect("test operation should succeed");
assert_eq!(l.get(0, 0), Rational64::from(1));
assert_eq!(l.get(1, 1), Rational64::from(1));
assert_eq!(l.get(0, 1), Rational64::from(0));
assert_eq!(u.get(1, 0), Rational64::from(0));
}
#[test]
fn test_determinant() {
let m = Matrix::from_rows(vec![
vec![Rational64::from(1), Rational64::from(2)],
vec![Rational64::from(3), Rational64::from(4)],
]);
let det = m.determinant().expect("test operation should succeed");
assert_eq!(det, Rational64::from(-2)); }
#[test]
fn test_determinant_3x3() {
let m = Matrix::from_rows(vec![
vec![
Rational64::from(6),
Rational64::from(1),
Rational64::from(1),
],
vec![
Rational64::from(4),
Rational64::from(-2),
Rational64::from(5),
],
vec![
Rational64::from(2),
Rational64::from(8),
Rational64::from(7),
],
]);
let det = m.determinant().expect("test operation should succeed");
assert_eq!(det, Rational64::from(-306));
}
#[test]
fn test_sparse_matrix_identity() {
let m = SparseMatrix::identity(3);
assert_eq!(m.nrows(), 3);
assert_eq!(m.ncols(), 3);
assert_eq!(m.nnz(), 3);
assert_eq!(m.get(0, 0), Rational64::from(1));
assert_eq!(m.get(1, 1), Rational64::from(1));
assert_eq!(m.get(2, 2), Rational64::from(1));
assert_eq!(m.get(0, 1), Rational64::from(0));
assert_eq!(m.get(1, 2), Rational64::from(0));
}
#[test]
fn test_sparse_from_dense() {
let dense = Matrix::from_rows(vec![
vec![
Rational64::from(1),
Rational64::from(0),
Rational64::from(2),
],
vec![
Rational64::from(0),
Rational64::from(3),
Rational64::from(0),
],
vec![
Rational64::from(4),
Rational64::from(0),
Rational64::from(5),
],
]);
let sparse = SparseMatrix::from_dense(&dense);
assert_eq!(sparse.nnz(), 5); assert_eq!(sparse.get(0, 0), Rational64::from(1));
assert_eq!(sparse.get(0, 2), Rational64::from(2));
assert_eq!(sparse.get(1, 1), Rational64::from(3));
assert_eq!(sparse.get(2, 0), Rational64::from(4));
assert_eq!(sparse.get(2, 2), Rational64::from(5));
assert_eq!(sparse.get(0, 1), Rational64::from(0));
}
#[test]
fn test_sparse_to_dense() {
let sparse = SparseMatrix::identity(3);
let dense = sparse.to_dense();
assert_eq!(dense.get(0, 0), Rational64::from(1));
assert_eq!(dense.get(1, 1), Rational64::from(1));
assert_eq!(dense.get(2, 2), Rational64::from(1));
assert_eq!(dense.get(0, 1), Rational64::from(0));
}
#[test]
fn test_sparse_mul_vec() {
let dense = Matrix::from_rows(vec![
vec![
Rational64::from(1),
Rational64::from(0),
Rational64::from(2),
],
vec![
Rational64::from(0),
Rational64::from(3),
Rational64::from(0),
],
]);
let sparse = SparseMatrix::from_dense(&dense);
let v = vec![
Rational64::from(1),
Rational64::from(2),
Rational64::from(3),
];
let result = sparse.mul_vec(&v);
assert_eq!(result[0], Rational64::from(7)); assert_eq!(result[1], Rational64::from(6)); }
#[test]
fn test_sparse_transpose() {
let dense = Matrix::from_rows(vec![
vec![Rational64::from(1), Rational64::from(2)],
vec![Rational64::from(3), Rational64::from(4)],
vec![Rational64::from(5), Rational64::from(6)],
]);
let sparse = SparseMatrix::from_dense(&dense);
let transposed = sparse.transpose();
assert_eq!(transposed.nrows(), 2);
assert_eq!(transposed.ncols(), 3);
assert_eq!(transposed.get(0, 0), Rational64::from(1));
assert_eq!(transposed.get(0, 1), Rational64::from(3));
assert_eq!(transposed.get(0, 2), Rational64::from(5));
assert_eq!(transposed.get(1, 0), Rational64::from(2));
assert_eq!(transposed.get(1, 1), Rational64::from(4));
assert_eq!(transposed.get(1, 2), Rational64::from(6));
}
#[test]
fn test_sparse_mul() {
let a_dense = Matrix::from_rows(vec![
vec![Rational64::from(1), Rational64::from(2)],
vec![Rational64::from(3), Rational64::from(4)],
]);
let b_dense = Matrix::from_rows(vec![
vec![Rational64::from(5), Rational64::from(6)],
vec![Rational64::from(7), Rational64::from(8)],
]);
let a = SparseMatrix::from_dense(&a_dense);
let b = SparseMatrix::from_dense(&b_dense);
let c = a.mul(&b);
let c_dense = c.to_dense();
assert_eq!(c_dense.get(0, 0), Rational64::from(19)); assert_eq!(c_dense.get(0, 1), Rational64::from(22)); assert_eq!(c_dense.get(1, 0), Rational64::from(43)); assert_eq!(c_dense.get(1, 1), Rational64::from(50)); }
#[test]
fn test_qr_decomposition_simple() {
let m = Matrix::from_rows(vec![
vec![Rational64::from(1), Rational64::from(1)],
vec![Rational64::from(0), Rational64::from(1)],
]);
let (q, r) = m.qr_decomposition().expect("test operation should succeed");
let reconstructed = q.mul(&r);
for i in 0..m.nrows() {
for j in 0..m.ncols() {
assert_eq!(
m.get(i, j),
reconstructed.get(i, j),
"QR reconstruction failed at ({}, {})",
i,
j
);
}
}
}
#[test]
fn test_qr_decomposition_3x2() {
let m = Matrix::from_rows(vec![
vec![Rational64::from(1), Rational64::from(0)],
vec![Rational64::from(1), Rational64::from(1)],
vec![Rational64::from(0), Rational64::from(1)],
]);
let (q, r) = m.qr_decomposition().expect("test operation should succeed");
assert_eq!(q.nrows(), 3);
assert_eq!(q.ncols(), 2);
assert_eq!(r.nrows(), 2);
assert_eq!(r.ncols(), 2);
let reconstructed = q.mul(&r);
for i in 0..m.nrows() {
for j in 0..m.ncols() {
assert_eq!(m.get(i, j), reconstructed.get(i, j));
}
}
}
#[test]
fn test_cholesky_decomposition_simple() {
let m = Matrix::from_rows(vec![
vec![Rational64::from(4), Rational64::from(2)],
vec![Rational64::from(2), Rational64::from(2)],
]);
let l = m
.cholesky_decomposition()
.expect("test operation should succeed");
let lt = l.transpose();
let reconstructed = l.mul(<);
for i in 0..m.nrows() {
for j in 0..m.ncols() {
assert_eq!(m.get(i, j), reconstructed.get(i, j));
}
}
assert_eq!(l.get(0, 1), Rational64::from(0));
}
#[test]
fn test_cholesky_decomposition_identity() {
let m = Matrix::identity(3);
let l = m
.cholesky_decomposition()
.expect("test operation should succeed");
for i in 0..3 {
for j in 0..3 {
assert_eq!(l.get(i, j), m.get(i, j));
}
}
}
#[test]
fn test_cholesky_decomposition_3x3() {
let m = Matrix::from_rows(vec![
vec![
Rational64::from(9),
Rational64::from(3),
Rational64::from(3),
],
vec![
Rational64::from(3),
Rational64::from(5),
Rational64::from(1),
],
vec![
Rational64::from(3),
Rational64::from(1),
Rational64::from(5),
],
]);
let l = m
.cholesky_decomposition()
.expect("test operation should succeed");
let lt = l.transpose();
let reconstructed = l.mul(<);
for i in 0..m.nrows() {
for j in 0..m.ncols() {
assert_eq!(
m.get(i, j),
reconstructed.get(i, j),
"Cholesky reconstruction failed at ({}, {})",
i,
j
);
}
}
for i in 0..3 {
for j in i + 1..3 {
assert_eq!(l.get(i, j), Rational64::from(0));
}
}
}
#[test]
fn test_cholesky_decomposition_not_symmetric() {
let m = Matrix::from_rows(vec![
vec![Rational64::from(4), Rational64::from(1)],
vec![Rational64::from(2), Rational64::from(3)],
]);
assert!(m.cholesky_decomposition().is_none());
}
#[test]
fn test_cholesky_decomposition_not_positive_definite() {
let m = Matrix::from_rows(vec![
vec![Rational64::from(1), Rational64::from(2)],
vec![Rational64::from(2), Rational64::from(1)],
]);
assert!(m.cholesky_decomposition().is_none());
}
#[test]
fn test_rational_sqrt_perfect_squares() {
assert_eq!(
rational_sqrt(Rational64::from(0)),
Some(Rational64::from(0))
);
assert_eq!(
rational_sqrt(Rational64::from(1)),
Some(Rational64::from(1))
);
assert_eq!(
rational_sqrt(Rational64::from(4)),
Some(Rational64::from(2))
);
assert_eq!(
rational_sqrt(Rational64::from(9)),
Some(Rational64::from(3))
);
assert_eq!(
rational_sqrt(Rational64::from(16)),
Some(Rational64::from(4))
);
assert_eq!(
rational_sqrt(Rational64::from(25)),
Some(Rational64::from(5))
);
assert_eq!(
rational_sqrt(Rational64::new(1, 4)),
Some(Rational64::new(1, 2))
);
assert_eq!(
rational_sqrt(Rational64::new(9, 16)),
Some(Rational64::new(3, 4))
);
}
#[test]
fn test_rational_sqrt_not_perfect_squares() {
assert!(rational_sqrt(Rational64::from(2)).is_none());
assert!(rational_sqrt(Rational64::from(3)).is_none());
assert!(rational_sqrt(Rational64::from(5)).is_none());
assert!(rational_sqrt(Rational64::new(1, 2)).is_none());
assert!(rational_sqrt(Rational64::new(2, 3)).is_none());
assert!(rational_sqrt(Rational64::from(-1)).is_none()); }
#[test]
fn test_matrix_inverse_2x2() {
let a = Matrix::from_rows(vec![
vec![Rational64::from(1), Rational64::from(2)],
vec![Rational64::from(3), Rational64::from(4)],
]);
let a_inv = a.inverse().expect("test operation should succeed");
let product = a.mul(&a_inv);
assert!(product.is_identity(), "A * A^-1 should be identity");
let product2 = a_inv.mul(&a);
assert!(product2.is_identity(), "A^-1 * A should be identity");
}
#[test]
fn test_matrix_inverse_identity() {
let i = Matrix::identity(3);
let i_inv = i.inverse().expect("test operation should succeed");
assert!(i_inv.is_identity());
}
#[test]
fn test_matrix_inverse_3x3() {
let a = Matrix::from_rows(vec![
vec![
Rational64::from(2),
Rational64::from(1),
Rational64::from(1),
],
vec![
Rational64::from(1),
Rational64::from(2),
Rational64::from(1),
],
vec![
Rational64::from(1),
Rational64::from(1),
Rational64::from(2),
],
]);
let a_inv = a.inverse().expect("test operation should succeed");
let product = a.mul(&a_inv);
for i in 0..3 {
for j in 0..3 {
let expected = if i == j {
Rational64::from(1)
} else {
Rational64::from(0)
};
assert_eq!(
product.get(i, j),
expected,
"A * A^-1 failed at ({}, {})",
i,
j
);
}
}
}
#[test]
fn test_matrix_inverse_singular() {
let a = Matrix::from_rows(vec![
vec![Rational64::from(1), Rational64::from(2)],
vec![Rational64::from(2), Rational64::from(4)], ]);
assert!(
a.inverse().is_none(),
"Singular matrix should not have inverse"
);
}
#[test]
fn test_matrix_inverse_non_square() {
let a = Matrix::from_rows(vec![
vec![
Rational64::from(1),
Rational64::from(2),
Rational64::from(3),
],
vec![
Rational64::from(4),
Rational64::from(5),
Rational64::from(6),
],
]);
assert!(
a.inverse().is_none(),
"Non-square matrix should not have inverse"
);
}
#[test]
fn test_is_identity() {
let i = Matrix::identity(3);
assert!(i.is_identity());
let not_i = Matrix::from_rows(vec![
vec![Rational64::from(1), Rational64::from(0)],
vec![Rational64::from(0), Rational64::from(2)],
]);
assert!(!not_i.is_identity());
let also_not_i = Matrix::from_rows(vec![
vec![Rational64::from(1), Rational64::from(1)],
vec![Rational64::from(0), Rational64::from(1)],
]);
assert!(!also_not_i.is_identity());
}
}