use alloc::vec;
use alloc::vec::Vec;
use core::fmt;
use crate::algorithm::matrix::{
self as algorithm, CholeskyError, ConditionNumberError, DeterminantError, DimensionMismatch,
};
use crate::scalar::{FloatTolerance, Scalar};
use crate::storage::{DynamicStorage, Storage};
use crate::vector::DynamicVector;
pub struct DynamicMatrix<T> {
storage: DynamicStorage<T>,
rows: usize,
cols: usize,
}
impl<T: Scalar> DynamicMatrix<T> {
pub fn new(rows: usize, cols: usize, data: Vec<T>) -> Result<Self, DimensionMismatch> {
if data.len() != rows * cols {
return Err(DimensionMismatch);
}
Ok(Self::from_parts(DynamicStorage::new(data), rows, cols))
}
fn from_parts(storage: DynamicStorage<T>, rows: usize, cols: usize) -> Self {
Self {
storage,
rows,
cols,
}
}
pub fn rows(&self) -> usize {
self.rows
}
pub fn cols(&self) -> usize {
self.cols
}
pub fn add(&self, other: &Self) -> Result<Self, DimensionMismatch> {
let mut data = vec![T::zero(); self.rows * self.cols];
algorithm::add(
&self.storage,
self.rows,
self.cols,
&other.storage,
other.rows,
other.cols,
&mut data,
)?;
Ok(Self::from_parts(
DynamicStorage::new(data),
self.rows,
self.cols,
))
}
pub fn sub(&self, other: &Self) -> Result<Self, DimensionMismatch> {
let mut data = vec![T::zero(); self.rows * self.cols];
algorithm::sub(
&self.storage,
self.rows,
self.cols,
&other.storage,
other.rows,
other.cols,
&mut data,
)?;
Ok(Self::from_parts(
DynamicStorage::new(data),
self.rows,
self.cols,
))
}
pub fn mul_scalar(&self, factor: T) -> Self {
let mut data = vec![T::zero(); self.rows * self.cols];
match algorithm::mul_scalar(&self.storage, self.rows, self.cols, factor, &mut data) {
Ok(()) | Err(DimensionMismatch) => {}
}
Self::from_parts(DynamicStorage::new(data), self.rows, self.cols)
}
pub fn mul_vector(&self, v: &DynamicVector<T>) -> Result<DynamicVector<T>, DimensionMismatch> {
let mut out = vec![T::zero(); self.rows];
algorithm::mul_vector(&self.storage, self.rows, self.cols, v, &mut out)?;
Ok(DynamicVector::new(out))
}
pub fn mul_matrix(&self, other: &Self) -> Result<Self, DimensionMismatch> {
let mut data = vec![T::zero(); self.rows * other.cols];
algorithm::mul_matrix(
&self.storage,
self.rows,
self.cols,
&other.storage,
other.rows,
other.cols,
&mut data,
)?;
Ok(Self::from_parts(
DynamicStorage::new(data),
self.rows,
other.cols,
))
}
pub fn transpose(&self) -> Self {
let mut data = vec![T::zero(); self.rows * self.cols];
match algorithm::transpose(&self.storage, self.rows, self.cols, &mut data) {
Ok(()) | Err(DimensionMismatch) => {}
}
Self::from_parts(DynamicStorage::new(data), self.cols, self.rows)
}
pub fn determinant(&self) -> Result<T, DeterminantError>
where
T: PartialOrd,
{
algorithm::determinant(&self.storage, self.rows, self.cols)
}
pub fn rank(&self) -> usize
where
T: FloatTolerance + PartialOrd,
{
let mut scratch = vec![T::zero(); self.rows * self.cols];
algorithm::rank(&self.storage, self.rows, self.cols, &mut scratch).unwrap_or(0)
}
pub fn lu(&self) -> Result<(Self, Self, usize), DimensionMismatch>
where
T: PartialOrd,
{
let mut l = vec![T::zero(); self.rows * self.cols];
let mut u = vec![T::zero(); self.rows * self.cols];
let swap_count = algorithm::lu(&self.storage, self.rows, self.cols, &mut l, &mut u)?;
Ok((
Self::from_parts(DynamicStorage::new(l), self.rows, self.cols),
Self::from_parts(DynamicStorage::new(u), self.rows, self.cols),
swap_count,
))
}
pub fn qr(&self) -> Result<(Self, Self), DimensionMismatch>
where
T: PartialOrd,
{
let mut q = vec![T::zero(); self.rows * self.rows];
let mut r = vec![T::zero(); self.rows * self.cols];
let mut scratch = vec![T::zero(); self.rows];
algorithm::qr(
&self.storage,
self.rows,
self.cols,
&mut q,
&mut r,
&mut scratch,
)?;
Ok((
Self::from_parts(DynamicStorage::new(q), self.rows, self.rows),
Self::from_parts(DynamicStorage::new(r), self.rows, self.cols),
))
}
pub fn cholesky(&self) -> Result<Self, CholeskyError>
where
T: FloatTolerance + PartialOrd,
{
let mut l = vec![T::zero(); self.rows * self.cols];
algorithm::cholesky(&self.storage, self.rows, self.cols, &mut l)?;
Ok(Self::from_parts(
DynamicStorage::new(l),
self.rows,
self.cols,
))
}
pub fn svd(&self) -> Result<(Self, DynamicVector<T>, Self), DimensionMismatch>
where
T: FloatTolerance + PartialOrd,
{
let mut u = vec![T::zero(); self.rows * self.cols];
let mut sigma = vec![T::zero(); self.cols];
let mut v = vec![T::zero(); self.cols * self.cols];
let mut scratch = vec![T::zero(); 5 * self.cols * self.cols + self.cols + self.rows];
algorithm::svd(
&self.storage,
self.rows,
self.cols,
&mut u,
&mut sigma,
&mut v,
&mut scratch,
)?;
Ok((
Self::from_parts(DynamicStorage::new(u), self.rows, self.cols),
DynamicVector::new(sigma),
Self::from_parts(DynamicStorage::new(v), self.cols, self.cols),
))
}
pub fn condition_number(&self) -> Result<T, ConditionNumberError>
where
T: FloatTolerance + PartialOrd,
{
let n = self.rows;
let mut scratch = vec![T::zero(); 7 * n * n + 3 * n];
algorithm::condition_number(&self.storage, self.rows, self.cols, &mut scratch)
}
}
impl<T> PartialEq for DynamicMatrix<T>
where
T: Scalar + PartialEq,
{
fn eq(&self, other: &Self) -> bool {
self.rows == other.rows
&& self.cols == other.cols
&& (0..self.storage.len()).all(|i| self.storage.get(i) == other.storage.get(i))
}
}
impl<T> fmt::Debug for DynamicMatrix<T>
where
T: Scalar + fmt::Debug,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_list()
.entries((0..self.storage.len()).filter_map(|i| self.storage.get(i)))
.finish()
}
}
#[cfg(test)]
mod tests {
use super::DynamicMatrix;
use crate::algorithm::matrix::{DeterminantError, DimensionMismatch};
use crate::storage::Storage;
use crate::vector::DynamicVector;
#[test]
fn constructs_from_flat_row_major_data() {
let m = DynamicMatrix::new(2, 2, vec![1.0, 2.0, 3.0, 4.0]).unwrap();
assert_eq!(
m,
DynamicMatrix::new(2, 2, vec![1.0, 2.0, 3.0, 4.0]).unwrap()
);
assert_eq!(m.rows(), 2);
assert_eq!(m.cols(), 2);
}
#[test]
fn constructs_mismatched_data_length_is_an_error_not_a_panic() {
assert_eq!(
DynamicMatrix::new(2, 2, vec![1.0, 2.0, 3.0]),
Err(DimensionMismatch)
);
}
#[test]
fn add_is_wired_to_the_algorithm_layer() {
let a = DynamicMatrix::new(2, 2, vec![1.0, 2.0, 3.0, 4.0]).unwrap();
let b = DynamicMatrix::new(2, 2, vec![5.0, 6.0, 7.0, 8.0]).unwrap();
assert_eq!(
a.add(&b),
Ok(DynamicMatrix::new(2, 2, vec![6.0, 8.0, 10.0, 12.0]).unwrap())
);
}
#[test]
fn add_mismatched_shape_is_an_error_not_a_panic() {
let a = DynamicMatrix::new(2, 2, vec![1.0, 2.0, 3.0, 4.0]).unwrap();
let b = DynamicMatrix::new(2, 3, vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0]).unwrap();
assert_eq!(a.add(&b), Err(DimensionMismatch));
}
#[test]
fn sub_is_wired_to_the_algorithm_layer() {
let a = DynamicMatrix::new(2, 2, vec![5.0, 6.0, 7.0, 8.0]).unwrap();
let b = DynamicMatrix::new(2, 2, vec![1.0, 2.0, 3.0, 4.0]).unwrap();
assert_eq!(
a.sub(&b),
Ok(DynamicMatrix::new(2, 2, vec![4.0, 4.0, 4.0, 4.0]).unwrap())
);
}
#[test]
fn sub_mismatched_shape_is_an_error_not_a_panic() {
let a = DynamicMatrix::new(2, 2, vec![1.0, 2.0, 3.0, 4.0]).unwrap();
let b = DynamicMatrix::new(3, 2, vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0]).unwrap();
assert_eq!(a.sub(&b), Err(DimensionMismatch));
}
#[test]
fn mul_scalar_is_wired_to_the_algorithm_layer() {
let m = DynamicMatrix::new(2, 2, vec![1.0, 2.0, 3.0, 4.0]).unwrap();
assert_eq!(
m.mul_scalar(2.0),
DynamicMatrix::new(2, 2, vec![2.0, 4.0, 6.0, 8.0]).unwrap()
);
}
#[test]
fn mul_vector_is_wired_to_the_algorithm_layer() {
let m = DynamicMatrix::new(2, 2, vec![1.0, 2.0, 3.0, 4.0]).unwrap();
let v = DynamicVector::new(vec![1.0, 1.0]);
assert_eq!(m.mul_vector(&v), Ok(DynamicVector::new(vec![3.0, 7.0])));
}
#[test]
fn mul_vector_mismatched_inner_dimension_is_an_error_not_a_panic() {
let m = DynamicMatrix::new(2, 2, vec![1.0, 2.0, 3.0, 4.0]).unwrap();
let v = DynamicVector::new(vec![1.0, 1.0, 1.0]);
assert_eq!(m.mul_vector(&v), Err(DimensionMismatch));
}
#[test]
fn mul_matrix_is_wired_to_the_algorithm_layer() {
let a = DynamicMatrix::new(2, 2, vec![1.0, 2.0, 3.0, 4.0]).unwrap();
let b = DynamicMatrix::new(2, 2, vec![5.0, 6.0, 7.0, 8.0]).unwrap();
assert_eq!(
a.mul_matrix(&b),
Ok(DynamicMatrix::new(2, 2, vec![19.0, 22.0, 43.0, 50.0]).unwrap())
);
}
#[test]
fn mul_matrix_mismatched_inner_dimension_is_an_error_not_a_panic() {
let a = DynamicMatrix::new(2, 3, vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0]).unwrap();
let b = DynamicMatrix::new(2, 2, vec![1.0, 2.0, 3.0, 4.0]).unwrap();
assert_eq!(a.mul_matrix(&b), Err(DimensionMismatch));
}
#[test]
fn transpose_is_wired_to_the_algorithm_layer() {
let m = DynamicMatrix::new(2, 3, vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0]).unwrap();
assert_eq!(
m.transpose(),
DynamicMatrix::new(3, 2, vec![1.0, 4.0, 2.0, 5.0, 3.0, 6.0]).unwrap()
);
}
#[test]
fn determinant_is_wired_to_the_algorithm_layer() {
let m = DynamicMatrix::new(2, 2, vec![1.0, 2.0, 3.0, 4.0]).unwrap();
assert_eq!(m.determinant(), Ok(-2.0));
}
#[test]
fn determinant_of_non_square_matrix_is_an_error_not_a_panic() {
let m = DynamicMatrix::new(2, 3, vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0]).unwrap();
assert_eq!(m.determinant(), Err(DeterminantError::DimensionMismatch));
}
#[test]
fn rank_is_wired_to_the_algorithm_layer() {
let m = DynamicMatrix::new(2, 2, vec![1.0, 2.0, 2.0, 4.0]).unwrap();
assert_eq!(m.rank(), 1);
}
#[test]
fn lu_is_wired_to_the_algorithm_layer() {
let m = DynamicMatrix::new(2, 2, vec![6.0, 3.0, 4.0, 3.0]).unwrap();
let (l, u, swap_count) = m.lu().unwrap();
assert_eq!(swap_count, 0);
assert_eq!(
l,
DynamicMatrix::new(2, 2, vec![1.0, 0.0, 4.0 / 6.0, 1.0]).unwrap()
);
assert_eq!(
u,
DynamicMatrix::new(2, 2, vec![6.0, 3.0, 0.0, 1.0]).unwrap()
);
}
#[test]
fn lu_of_non_square_matrix_is_an_error_not_a_panic() {
let m = DynamicMatrix::new(2, 3, vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0]).unwrap();
assert_eq!(m.lu(), Err(DimensionMismatch));
}
#[test]
fn qr_is_wired_to_the_algorithm_layer() {
let m = DynamicMatrix::new(2, 2, vec![3.0_f64, 5.0, 4.0, 0.0]).unwrap();
let (q, r) = m.qr().unwrap();
let reconstructed = q.mul_matrix(&r).unwrap();
for (actual, expected) in (0..4)
.map(|i| *reconstructed.storage.get(i).unwrap())
.zip([3.0, 5.0, 4.0, 0.0])
{
assert!((actual - expected).abs() < 1e-9);
}
}
#[test]
fn qr_of_matrix_with_more_columns_than_rows_is_an_error_not_a_panic() {
let m = DynamicMatrix::new(2, 3, vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0]).unwrap();
assert_eq!(m.qr(), Err(DimensionMismatch));
}
#[test]
fn cholesky_is_wired_to_the_algorithm_layer() {
let m = DynamicMatrix::new(2, 2, vec![4.0, 2.0, 2.0, 2.0]).unwrap();
assert_eq!(
m.cholesky(),
Ok(DynamicMatrix::new(2, 2, vec![2.0, 0.0, 1.0, 1.0]).unwrap())
);
}
#[test]
fn cholesky_of_non_positive_definite_matrix_is_an_error_not_a_panic() {
let m = DynamicMatrix::new(2, 2, vec![1.0, 2.0, 2.0, 1.0]).unwrap();
assert_eq!(
m.cholesky(),
Err(crate::algorithm::matrix::CholeskyError::NotPositiveDefinite)
);
}
#[test]
fn cholesky_of_non_square_matrix_is_an_error_not_a_panic() {
let m = DynamicMatrix::new(2, 3, vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0]).unwrap();
assert_eq!(
m.cholesky(),
Err(crate::algorithm::matrix::CholeskyError::DimensionMismatch)
);
}
#[test]
fn svd_is_wired_to_the_algorithm_layer() {
let m = DynamicMatrix::new(2, 2, vec![1.0_f64, 1.0, 0.0, 1.0]).unwrap();
let (u, sigma, v) = m.svd().unwrap();
assert!(sigma.get(0) >= sigma.get(1));
assert!(*sigma.get(1).unwrap() >= 0.0);
assert_eq!((u.rows(), u.cols()), (2, 2));
assert_eq!((v.rows(), v.cols()), (2, 2));
}
#[test]
fn condition_number_is_wired_to_the_algorithm_layer() {
let m = DynamicMatrix::new(2, 2, vec![100.0_f64, 0.0, 0.0, 1.0]).unwrap();
let kappa = m.condition_number().unwrap();
assert!((kappa - 100.0).abs() < 1e-6);
}
#[test]
fn condition_number_of_singular_matrix_is_an_error() {
let m = DynamicMatrix::new(2, 2, vec![1.0_f64, 2.0, 2.0, 4.0]).unwrap();
assert_eq!(
m.condition_number(),
Err(crate::algorithm::matrix::ConditionNumberError::Singular)
);
}
#[test]
fn condition_number_of_non_square_matrix_is_an_error_not_a_panic() {
let m = DynamicMatrix::new(2, 3, vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0]).unwrap();
assert_eq!(
m.condition_number(),
Err(crate::algorithm::matrix::ConditionNumberError::DimensionMismatch)
);
}
}