use crate::algorithm::matrix::{
self as algorithm, CholeskyError, ConditionNumberError, DeterminantError, DimensionMismatch,
};
use crate::scalar::{FloatTolerance, Scalar};
use crate::storage::Storage;
use crate::vector::StaticVector;
#[derive(Debug, PartialEq)]
pub struct StaticMatrix<T, const R: usize, const C: usize> {
data: [[T; C]; R],
}
type SvdResult<T, const R: usize, const C: usize> = (
StaticMatrix<T, R, C>,
StaticVector<T, C>,
StaticMatrix<T, C, C>,
);
impl<T, const R: usize, const C: usize> Storage for StaticMatrix<T, R, C> {
type Item = T;
fn len(&self) -> usize {
R * C
}
fn get(&self, index: usize) -> Option<&T> {
self.data.as_flattened().get(index)
}
}
impl<T: Scalar, const R: usize, const C: usize> StaticMatrix<T, R, C> {
pub fn new(data: [[T; C]; R]) -> Self {
Self { data }
}
pub fn add(&self, other: &Self) -> Self {
let mut data = [[T::zero(); C]; R];
match algorithm::add(self, R, C, other, R, C, data.as_flattened_mut()) {
Ok(()) | Err(DimensionMismatch) => {}
}
Self::new(data)
}
pub fn sub(&self, other: &Self) -> Self {
let mut data = [[T::zero(); C]; R];
match algorithm::sub(self, R, C, other, R, C, data.as_flattened_mut()) {
Ok(()) | Err(DimensionMismatch) => {}
}
Self::new(data)
}
pub fn mul_scalar(&self, factor: T) -> Self {
let mut data = [[T::zero(); C]; R];
match algorithm::mul_scalar(self, R, C, factor, data.as_flattened_mut()) {
Ok(()) | Err(DimensionMismatch) => {}
}
Self::new(data)
}
pub fn mul_vector(&self, v: &StaticVector<T, C>) -> StaticVector<T, R> {
let mut out = [T::zero(); R];
match algorithm::mul_vector(self, R, C, v, &mut out) {
Ok(()) | Err(DimensionMismatch) => {}
}
StaticVector::new(out)
}
pub fn mul_matrix<const C2: usize>(
&self,
other: &StaticMatrix<T, C, C2>,
) -> StaticMatrix<T, R, C2> {
let mut data = [[T::zero(); C2]; R];
match algorithm::mul_matrix(self, R, C, other, C, C2, data.as_flattened_mut()) {
Ok(()) | Err(DimensionMismatch) => {}
}
StaticMatrix::new(data)
}
pub fn transpose(&self) -> StaticMatrix<T, C, R> {
let mut data = [[T::zero(); R]; C];
match algorithm::transpose(self, R, C, data.as_flattened_mut()) {
Ok(()) | Err(DimensionMismatch) => {}
}
StaticMatrix::new(data)
}
pub fn rank(&self) -> usize
where
T: FloatTolerance + PartialOrd,
{
let mut scratch = [[T::zero(); C]; R];
algorithm::rank(self, R, C, scratch.as_flattened_mut()).unwrap_or(0)
}
pub fn qr(&self) -> Result<(StaticMatrix<T, R, R>, StaticMatrix<T, R, C>), DimensionMismatch>
where
T: PartialOrd,
{
let mut q = [[T::zero(); R]; R];
let mut r = [[T::zero(); C]; R];
let mut scratch = [T::zero(); R];
algorithm::qr(
self,
R,
C,
q.as_flattened_mut(),
r.as_flattened_mut(),
&mut scratch,
)?;
Ok((StaticMatrix::new(q), StaticMatrix::new(r)))
}
pub fn svd(&self, scratch: &mut [T]) -> Result<SvdResult<T, R, C>, DimensionMismatch>
where
T: FloatTolerance + PartialOrd,
{
let mut u = [[T::zero(); C]; R];
let mut sigma = [T::zero(); C];
let mut v = [[T::zero(); C]; C];
algorithm::svd(
self,
R,
C,
u.as_flattened_mut(),
&mut sigma,
v.as_flattened_mut(),
scratch,
)?;
Ok((
StaticMatrix::new(u),
StaticVector::new(sigma),
StaticMatrix::new(v),
))
}
}
impl<T: Scalar, const N: usize> StaticMatrix<T, N, N> {
pub fn determinant(&self) -> Result<T, DeterminantError>
where
T: PartialOrd,
{
algorithm::determinant(self, N, N)
}
pub fn lu(&self) -> (StaticMatrix<T, N, N>, StaticMatrix<T, N, N>, usize)
where
T: PartialOrd,
{
let mut l = [[T::zero(); N]; N];
let mut u = [[T::zero(); N]; N];
let swap_count = algorithm::lu(self, N, N, l.as_flattened_mut(), u.as_flattened_mut())
.unwrap_or_default();
(StaticMatrix::new(l), StaticMatrix::new(u), swap_count)
}
pub fn cholesky(&self) -> Result<StaticMatrix<T, N, N>, CholeskyError>
where
T: FloatTolerance + PartialOrd,
{
let mut l = [[T::zero(); N]; N];
algorithm::cholesky(self, N, N, l.as_flattened_mut())?;
Ok(StaticMatrix::new(l))
}
pub fn condition_number(&self, scratch: &mut [T]) -> Result<T, ConditionNumberError>
where
T: FloatTolerance + PartialOrd,
{
algorithm::condition_number(self, N, N, scratch)
}
}
#[cfg(test)]
mod tests {
use super::StaticMatrix;
use crate::storage::Storage;
use crate::vector::StaticVector;
#[test]
fn constructs_from_rows() {
let m = StaticMatrix::new([[1.0, 2.0], [3.0, 4.0]]);
assert_eq!(m, StaticMatrix::new([[1.0, 2.0], [3.0, 4.0]]));
}
#[test]
fn add_is_wired_to_the_algorithm_layer() {
let a = StaticMatrix::new([[1.0, 2.0], [3.0, 4.0]]);
let b = StaticMatrix::new([[5.0, 6.0], [7.0, 8.0]]);
assert_eq!(a.add(&b), StaticMatrix::new([[6.0, 8.0], [10.0, 12.0]]));
}
#[test]
fn sub_is_wired_to_the_algorithm_layer() {
let a = StaticMatrix::new([[5.0, 6.0], [7.0, 8.0]]);
let b = StaticMatrix::new([[1.0, 2.0], [3.0, 4.0]]);
assert_eq!(a.sub(&b), StaticMatrix::new([[4.0, 4.0], [4.0, 4.0]]));
}
#[test]
fn mul_scalar_is_wired_to_the_algorithm_layer() {
let m = StaticMatrix::new([[1.0, 2.0], [3.0, 4.0]]);
assert_eq!(
m.mul_scalar(2.0),
StaticMatrix::new([[2.0, 4.0], [6.0, 8.0]])
);
}
#[test]
fn mul_vector_is_wired_to_the_algorithm_layer() {
let m = StaticMatrix::new([[1.0, 2.0], [3.0, 4.0]]);
let v = StaticVector::new([1.0, 1.0]);
assert_eq!(m.mul_vector(&v), StaticVector::new([3.0, 7.0]));
}
#[test]
fn mul_matrix_is_wired_to_the_algorithm_layer() {
let a = StaticMatrix::new([[1.0, 2.0], [3.0, 4.0]]);
let b = StaticMatrix::new([[5.0, 6.0], [7.0, 8.0]]);
assert_eq!(
a.mul_matrix(&b),
StaticMatrix::new([[19.0, 22.0], [43.0, 50.0]])
);
}
#[test]
fn transpose_is_wired_to_the_algorithm_layer() {
let m = StaticMatrix::new([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]]);
assert_eq!(
m.transpose(),
StaticMatrix::new([[1.0, 4.0], [2.0, 5.0], [3.0, 6.0]])
);
}
#[test]
fn determinant_is_wired_to_the_algorithm_layer() {
let m = StaticMatrix::new([[1.0, 2.0], [3.0, 4.0]]);
assert_eq!(m.determinant(), Ok(-2.0));
}
#[test]
fn rank_is_wired_to_the_algorithm_layer() {
let m = StaticMatrix::new([[1.0, 2.0], [2.0, 4.0]]);
assert_eq!(m.rank(), 1);
}
#[test]
fn lu_is_wired_to_the_algorithm_layer() {
let m = StaticMatrix::new([[6.0, 3.0], [4.0, 3.0]]);
let (l, u, swap_count) = m.lu();
assert_eq!(swap_count, 0);
assert_eq!(l, StaticMatrix::new([[1.0, 0.0], [4.0 / 6.0, 1.0]]));
assert_eq!(u, StaticMatrix::new([[6.0, 3.0], [0.0, 1.0]]));
}
#[test]
fn qr_is_wired_to_the_algorithm_layer() {
let m = StaticMatrix::new([[3.0_f64, 5.0], [4.0, 0.0]]);
let (q, r) = m.qr().unwrap();
let reconstructed = q.mul_matrix(&r);
for (actual, expected) in [
reconstructed.data[0][0],
reconstructed.data[0][1],
reconstructed.data[1][0],
reconstructed.data[1][1],
]
.into_iter()
.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 = StaticMatrix::new([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]]);
assert_eq!(m.qr(), Err(crate::algorithm::matrix::DimensionMismatch));
}
#[test]
fn cholesky_is_wired_to_the_algorithm_layer() {
let m = StaticMatrix::new([[4.0, 2.0], [2.0, 2.0]]);
assert_eq!(
m.cholesky(),
Ok(StaticMatrix::new([[2.0, 0.0], [1.0, 1.0]]))
);
}
#[test]
fn cholesky_of_non_positive_definite_matrix_is_an_error_not_a_panic() {
let m = StaticMatrix::new([[1.0, 2.0], [2.0, 1.0]]);
assert_eq!(
m.cholesky(),
Err(crate::algorithm::matrix::CholeskyError::NotPositiveDefinite)
);
}
#[test]
fn svd_is_wired_to_the_algorithm_layer() {
let m = StaticMatrix::new([[1.0_f64, 1.0], [0.0, 1.0]]);
let mut scratch = [0.0; 5 * 2 * 2 + 2 + 2];
let (_, sigma, _) = m.svd(&mut scratch).unwrap();
assert!(sigma.get(0) >= sigma.get(1));
assert!(*sigma.get(1).unwrap() >= 0.0);
}
#[test]
fn svd_mismatched_scratch_length_is_an_error_not_a_panic() {
let m = StaticMatrix::new([[1.0, 1.0], [0.0, 1.0]]);
let mut scratch = [0.0; 4];
assert_eq!(
m.svd(&mut scratch),
Err(crate::algorithm::matrix::DimensionMismatch)
);
}
#[test]
fn condition_number_is_wired_to_the_algorithm_layer() {
let m = StaticMatrix::new([[100.0_f64, 0.0], [0.0, 1.0]]);
let mut scratch = [0.0; 7 * 2 * 2 + 3 * 2];
let kappa = m.condition_number(&mut scratch).unwrap();
assert!((kappa - 100.0).abs() < 1e-6);
}
#[test]
fn condition_number_of_singular_matrix_is_an_error() {
let m = StaticMatrix::new([[1.0_f64, 2.0], [2.0, 4.0]]);
let mut scratch = [0.0; 7 * 2 * 2 + 3 * 2];
assert_eq!(
m.condition_number(&mut scratch),
Err(crate::algorithm::matrix::ConditionNumberError::Singular)
);
}
}