#![forbid(unsafe_code)]
use la_stack::{LaError, Matrix as LaMatrix};
use thiserror::Error;
pub const MAX_STACK_MATRIX_DIM: usize = 7;
pub type Matrix<const D: usize> = LaMatrix<D>;
#[derive(Clone, Debug, Error, PartialEq, Eq)]
#[non_exhaustive]
pub enum MatrixError {
#[error("Matrix is singular!")]
SingularMatrix,
#[error("matrix index out of bounds: ({row}, {column}) for {dimension}x{dimension}")]
OutOfBounds {
row: usize,
column: usize,
dimension: usize,
},
}
#[derive(Clone, Debug, Error, PartialEq, Eq)]
#[non_exhaustive]
pub(crate) enum StackMatrixDispatchError {
#[error("unsupported stack matrix size: {k} (max {max})")]
UnsupportedDim {
k: usize,
max: usize,
},
#[error("active matrix block size {k} does not match concrete matrix dimension {dim}")]
ActiveBlockDimensionMismatch {
k: usize,
dim: usize,
},
#[error(transparent)]
La {
#[from]
source: LaError,
},
#[error(transparent)]
Matrix {
#[from]
source: MatrixError,
},
}
pub const SINGULARITY_TOLERANCE: f64 = 1e-12;
macro_rules! dispatch_la_stack_matrix {
($k:expr, |$m:ident| $body:block, $unsupported:expr) => {{
match $k {
0 => {
#[allow(unused_mut)]
let mut $m = $crate::geometry::matrix::Matrix::<0>::zero();
$body
}
1 => {
#[allow(unused_mut)]
let mut $m = $crate::geometry::matrix::Matrix::<1>::zero();
$body
}
2 => {
#[allow(unused_mut)]
let mut $m = $crate::geometry::matrix::Matrix::<2>::zero();
$body
}
3 => {
#[allow(unused_mut)]
let mut $m = $crate::geometry::matrix::Matrix::<3>::zero();
$body
}
4 => {
#[allow(unused_mut)]
let mut $m = $crate::geometry::matrix::Matrix::<4>::zero();
$body
}
5 => {
#[allow(unused_mut)]
let mut $m = $crate::geometry::matrix::Matrix::<5>::zero();
$body
}
6 => {
#[allow(unused_mut)]
let mut $m = $crate::geometry::matrix::Matrix::<6>::zero();
$body
}
7 => {
#[allow(unused_mut)]
let mut $m = $crate::geometry::matrix::Matrix::<7>::zero();
$body
}
_ => $unsupported,
}
}};
}
#[cfg(test)]
macro_rules! with_la_stack_matrix {
($k:expr, |$m:ident| $body:block) => {{
dispatch_la_stack_matrix!(
$k,
|$m| $body,
panic!(
"unsupported stack matrix size: {k} (max {max})",
k = $k,
max = $crate::geometry::matrix::MAX_STACK_MATRIX_DIM
)
)
}};
}
macro_rules! try_with_la_stack_matrix {
($k:expr, |$m:ident| $body:block) => {{
let k = $k;
dispatch_la_stack_matrix!(
k,
|$m| $body,
Err(
$crate::geometry::matrix::StackMatrixDispatchError::UnsupportedDim {
k,
max: $crate::geometry::matrix::MAX_STACK_MATRIX_DIM,
}
.into(),
)
)
}};
}
#[inline]
pub(crate) fn matrix_zero_like<const D: usize>(_template: &Matrix<D>) -> Matrix<D> {
Matrix::<D>::zero()
}
#[inline]
pub(crate) fn matrix_get<const D: usize>(
m: &Matrix<D>,
row: usize,
column: usize,
) -> Result<f64, MatrixError> {
m.get(row, column).ok_or(MatrixError::OutOfBounds {
row,
column,
dimension: D,
})
}
#[inline]
pub(crate) fn matrix_set<const D: usize>(
m: &mut Matrix<D>,
row: usize,
column: usize,
value: f64,
) -> Result<(), MatrixError> {
if m.set(row, column, value) {
return Ok(());
}
Err(MatrixError::OutOfBounds {
row,
column,
dimension: D,
})
}
#[inline]
#[must_use]
pub fn determinant<const D: usize>(m: &Matrix<D>) -> f64 {
match m.det(0.0) {
Ok(det) => det,
Err(LaError::Singular { .. }) => 0.0,
Err(_) => f64::NAN,
}
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_relative_eq;
#[test]
fn try_with_la_stack_matrix_returns_err_on_unsupported_dim() {
let k = MAX_STACK_MATRIX_DIM + 1;
let res: Result<(), StackMatrixDispatchError> =
try_with_la_stack_matrix!(k, |_m| { Ok(()) });
assert!(matches!(
res,
Err(StackMatrixDispatchError::UnsupportedDim { .. })
));
}
#[test]
fn stack_matrix_dispatch_error_clones_la_error_source() {
let source = LaError::Singular { pivot_col: 3 };
let error = StackMatrixDispatchError::La { source };
assert_eq!(error.clone(), error);
assert_eq!(
error.to_string(),
StackMatrixDispatchError::La { source }.to_string()
);
}
#[test]
fn matrix_zero_like_returns_zero_matrix_of_same_size() {
let k = 4;
with_la_stack_matrix!(k, |original| {
let mut val = 1.0_f64;
for i in 0..k {
for j in 0..k {
matrix_set(&mut original, i, j, val).unwrap();
val += 1.0;
}
}
let zero = matrix_zero_like(&original);
for i in 0..k {
for j in 0..k {
assert_relative_eq!(matrix_get(&zero, i, j).unwrap(), 0.0);
}
}
let mut expected = 1.0_f64;
for i in 0..k {
for j in 0..k {
assert_relative_eq!(matrix_get(&original, i, j).unwrap(), expected);
expected += 1.0;
}
}
});
}
#[test]
fn matrix_zero_like_works_across_dispatch_sizes() {
for &k in &[2_usize, 3, 6, MAX_STACK_MATRIX_DIM] {
with_la_stack_matrix!(k, |m| {
let zero = matrix_zero_like(&m);
assert_relative_eq!(matrix_get(&zero, 0, 0).unwrap(), 0.0);
assert_relative_eq!(matrix_get(&zero, k - 1, k - 1).unwrap(), 0.0);
});
}
}
#[test]
fn matrix_get_returns_error_on_out_of_bounds_index() {
let matrix = Matrix::<2>::zero();
let err = matrix_get(&matrix, 2, 0).unwrap_err();
assert_eq!(
err,
MatrixError::OutOfBounds {
row: 2,
column: 0,
dimension: 2,
}
);
}
#[test]
fn matrix_set_returns_error_on_out_of_bounds_index() {
let mut matrix = Matrix::<2>::zero();
let err = matrix_set(&mut matrix, 0, 2, 1.0).unwrap_err();
assert_eq!(
err,
MatrixError::OutOfBounds {
row: 0,
column: 2,
dimension: 2,
}
);
}
}