#![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)]
pub enum MatrixError {
#[error("Matrix is singular!")]
SingularMatrix,
}
#[derive(Debug, Error)]
pub(crate) enum StackMatrixDispatchError {
#[error("unsupported stack matrix size: {k} (max {max})")]
UnsupportedDim {
k: usize,
max: usize,
},
#[error(transparent)]
La(#[from] LaError),
}
pub const SINGULARITY_TOLERANCE: f64 = 1e-12;
macro_rules! with_la_stack_matrix {
($k:expr, |$m:ident| $body:block) => {{
with_la_stack_matrix!(@dispatch $k, $m, $body,
0, 1, 2, 3, 4, 5, 6, 7)
}};
(@dispatch $k:expr, $m:ident, $body:block, $($n:literal),+) => {{
match $k {
$(
$n => {
#[allow(unused_mut)]
let mut $m = $crate::geometry::matrix::Matrix::<$n>::zero();
$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;
if k > $crate::geometry::matrix::MAX_STACK_MATRIX_DIM {
Err(
$crate::geometry::matrix::StackMatrixDispatchError::UnsupportedDim {
k,
max: $crate::geometry::matrix::MAX_STACK_MATRIX_DIM,
}
.into(),
)
} else {
with_la_stack_matrix!(k, |$m| $body)
}
}};
}
#[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>, r: usize, c: usize) -> f64 {
m.get(r, c)
.unwrap_or_else(|| unreachable!("matrix index out of bounds: ({r}, {c}) for {D}x{D}"))
}
#[inline]
pub(crate) fn matrix_set<const D: usize>(m: &mut Matrix<D>, r: usize, c: usize, value: f64) {
let ok = m.set(r, c, value);
assert!(ok, "matrix index out of bounds: ({r}, {c}) for {D}x{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 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);
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), 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), 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), 0.0);
assert_relative_eq!(matrix_get(&zero, k - 1, k - 1), 0.0);
});
}
}
}