#[cfg(feature = "alloc")]
use alloc::vec;
use super::{DimensionMismatch, lu_partial_pivot};
use crate::scalar::Scalar;
use crate::storage::Storage;
struct Minor<'a, T> {
storage: &'a dyn Storage<Item = T>,
n: usize,
skip_row: usize,
skip_col: usize,
}
impl<T> Storage for Minor<'_, T> {
type Item = T;
fn len(&self) -> usize {
let m = self.n - 1;
m * m
}
fn get(&self, index: usize) -> Option<&Self::Item> {
let m = self.n - 1;
if index >= m * m {
return None;
}
let r = index / m;
let c = index % m;
let orig_r = if r < self.skip_row { r } else { r + 1 };
let orig_c = if c < self.skip_col { c } else { c + 1 };
self.storage.get(orig_r * self.n + orig_c)
}
}
fn cofactor_expansion<T: Scalar>(a: &dyn Storage<Item = T>, n: usize) -> T {
if n == 1 {
return match a.get(0) {
Some(&x) => x,
None => T::zero(),
};
}
let mut sum = T::zero();
for col in 0..n {
let Some(&entry) = a.get(col) else {
return sum;
};
let minor = Minor {
storage: a,
n,
skip_row: 0,
skip_col: col,
};
let term = entry.mul(cofactor_expansion(&minor, n - 1));
sum = if col % 2 == 0 {
sum.add(term)
} else {
sum.sub(term)
};
}
sum
}
pub fn determinant_cofactor<S, T>(a: &S, rows: usize, cols: usize) -> Result<T, DimensionMismatch>
where
S: Storage<Item = T>,
T: Scalar,
{
if rows != cols || a.len() != rows * cols {
return Err(DimensionMismatch);
}
Ok(cofactor_expansion(a, rows))
}
pub fn determinant_lu<S, T>(
a: &S,
rows: usize,
cols: usize,
scratch: &mut [T],
) -> Result<T, DimensionMismatch>
where
S: Storage<Item = T>,
T: Scalar + PartialEq,
{
if rows != cols || a.len() != rows * cols || scratch.len() != 2 * rows * cols {
return Err(DimensionMismatch);
}
let (l_buf, u_buf) = scratch.split_at_mut(rows * cols);
let swaps = lu_partial_pivot(a, rows, cols, l_buf, u_buf)?;
let mut det = T::one();
for i in 0..rows {
det = det.mul(u_buf[i * cols + i]);
}
if swaps % 2 == 1 {
det = T::zero().sub(det);
}
Ok(det)
}
pub fn determinant<S, T>(a: &S, rows: usize, cols: usize) -> Result<T, DimensionMismatch>
where
S: Storage<Item = T>,
T: Scalar + PartialEq,
{
#[cfg(feature = "alloc")]
if rows > 4 {
let mut scratch = vec![T::zero(); 2 * rows * cols];
return determinant_lu(a, rows, cols, &mut scratch);
}
determinant_cofactor(a, rows, cols)
}
#[cfg(test)]
mod tests {
use super::{DimensionMismatch, determinant, determinant_lu};
use crate::storage::StaticStorage;
#[test]
fn determinant_of_known_2x2_matrix() {
let a = StaticStorage::new([1.0, 2.0, 3.0, 4.0]);
assert_eq!(determinant(&a, 2, 2), Ok(-2.0));
}
#[test]
fn determinant_of_known_3x3_matrix() {
let a = StaticStorage::new([1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 10.0]);
assert_eq!(determinant(&a, 3, 3), Ok(-3.0));
}
#[test]
fn determinant_of_singular_matrix_with_a_zero_row_is_zero() {
let a = StaticStorage::new([1.0, 2.0, 3.0, 0.0, 0.0, 0.0, 7.0, 8.0, 9.0]);
assert_eq!(determinant(&a, 3, 3), Ok(0.0));
}
#[test]
fn determinant_of_non_square_matrix_is_an_error_not_a_panic() {
let a = StaticStorage::new([1.0, 2.0, 3.0, 4.0, 5.0, 6.0]);
assert_eq!(determinant(&a, 2, 3), Err(DimensionMismatch));
}
#[test]
fn determinant_of_5x5_matrix_matches_determinant_lu_directly() {
let a = StaticStorage::new([
5.0_f64, 1.0, 0.0, 0.0, 0.0, 0.0, 5.0, 1.0, 0.0, 0.0, 0.0, 0.0, 5.0, 1.0, 0.0, 0.0,
0.0, 0.0, 5.0, 1.0, 1.0, 0.0, 0.0, 0.0, 5.0,
]);
let mut scratch = [0.0; 50];
let via_lu = determinant_lu(&a, 5, 5, &mut scratch).unwrap();
let via_dispatch = determinant(&a, 5, 5).unwrap();
assert!((via_dispatch - via_lu).abs() < 1e-9);
}
}