use crate::{error::SemiflowError, float::SemiflowFloat};
pub(crate) fn mat_inv_dispatch<F: SemiflowFloat, const M: usize>(
a: &[[F; M]; M],
) -> Result<[[F; M]; M], SemiflowError> {
match M {
1 => mat_inv_m1(a),
2 => mat_inv_m2(a),
3 => mat_inv_m3(a),
4 => mat_inv_m4(a),
_ => mat_inv_lu::<F, M>(a),
}
}
pub(crate) fn mat_inv_lu<F: SemiflowFloat, const M: usize>(
mat_a: &[[F; M]; M],
) -> Result<[[F; M]; M], SemiflowError> {
let (lu, piv) = lu_factor(mat_a)?;
let mut inv = [[F::zero(); M]; M];
for cidx in 0..M {
let xcol = lu_solve_col(&lu, &piv, cidx);
write_column(&mut inv, cidx, &xcol);
}
Ok(inv)
}
fn write_column<F: SemiflowFloat, const M: usize>(
mat: &mut [[F; M]; M],
cidx: usize,
src: &[F; M],
) {
for (row, &val) in mat.iter_mut().zip(src.iter()) {
row[cidx] = val;
}
}
fn lu_factor<F: SemiflowFloat, const M: usize>(
mat_a: &[[F; M]; M],
) -> Result<([[F; M]; M], [usize; M]), SemiflowError> {
let mut lu = *mat_a;
let mut piv = [0usize; M];
for (idx, slot) in piv.iter_mut().enumerate() {
*slot = idx;
}
let thresh = lu_inf_norm_eps::<F, M>(&lu);
for pkc in 0..M {
let max_row = find_pivot_row(&lu, pkc);
if lu[max_row][pkc].abs() < thresh {
return Err(SemiflowError::DomainViolation {
what: "mat_inv_lu: near-singular matrix",
value: 0.0,
});
}
if max_row != pkc {
lu.swap(pkc, max_row);
piv.swap(pkc, max_row);
}
apply_lu_column(&mut lu, pkc);
}
Ok((lu, piv))
}
fn find_pivot_row<F: SemiflowFloat, const M: usize>(lu: &[[F; M]; M], pkc: usize) -> usize {
lu[pkc..M]
.iter()
.enumerate()
.max_by(|(_, ra), (_, rb)| {
ra[pkc]
.abs()
.partial_cmp(&rb[pkc].abs())
.unwrap_or(core::cmp::Ordering::Equal)
})
.map_or(pkc, |(offset, _)| pkc + offset)
}
fn apply_lu_column<F: SemiflowFloat, const M: usize>(lu: &mut [[F; M]; M], pkc: usize) {
let inv_pivot = F::one() / lu[pkc][pkc];
let mut pivot_row = [F::zero(); M];
pivot_row[..M].copy_from_slice(&lu[pkc]);
for row in lu.iter_mut().skip(pkc + 1) {
row[pkc] *= inv_pivot;
let mult = row[pkc];
for (ec, &pivot_val) in pivot_row.iter().enumerate().skip(pkc + 1) {
row[ec] -= mult * pivot_val;
}
}
}
fn lu_solve_col<F: SemiflowFloat, const M: usize>(
lu: &[[F; M]; M],
piv: &[usize; M],
rhs_col: usize,
) -> [F; M] {
let mut yvec = [F::zero(); M];
for fwd in 0..M {
yvec[fwd] = if piv[fwd] == rhs_col {
F::one()
} else {
F::zero()
};
for sub in 0..fwd {
yvec[fwd] -= lu[fwd][sub] * yvec[sub];
}
}
let mut xvec = [F::zero(); M];
for bwd in (0..M).rev() {
xvec[bwd] = yvec[bwd];
for sub in (bwd + 1)..M {
xvec[bwd] -= lu[bwd][sub] * xvec[sub];
}
xvec[bwd] /= lu[bwd][bwd];
}
xvec
}
fn lu_inf_norm_eps<F: SemiflowFloat, const M: usize>(lu: &[[F; M]; M]) -> F {
let a_norm: F = lu.iter().fold(F::zero(), |acc, row| {
let row_sum = row.iter().fold(F::zero(), |rs, &val| rs + val.abs());
if row_sum > acc {
row_sum
} else {
acc
}
});
F::epsilon() * a_norm * F::from(64.0_f64).unwrap_or(F::one())
}
fn mat_inv_m1<F: SemiflowFloat, const M: usize>(
a: &[[F; M]; M],
) -> Result<[[F; M]; M], SemiflowError> {
let d = a[0][0];
if d == F::zero() {
return Err(SemiflowError::DomainViolation {
what: "mat_inv_m1: singular matrix (det=0)",
value: 0.0,
});
}
let mut out = [[F::zero(); M]; M];
out[0][0] = F::one() / d;
Ok(out)
}
fn mat_inv_m2<F: SemiflowFloat, const M: usize>(
a: &[[F; M]; M],
) -> Result<[[F; M]; M], SemiflowError> {
let det = a[0][0] * a[1][1] - a[0][1] * a[1][0];
if det == F::zero() {
return Err(SemiflowError::DomainViolation {
what: "mat_inv_m2: singular matrix (det=0)",
value: 0.0,
});
}
let inv_det = F::one() / det;
let mut out = [[F::zero(); M]; M];
out[0][0] = a[1][1] * inv_det;
out[0][1] = -(a[0][1] * inv_det);
out[1][0] = -(a[1][0] * inv_det);
out[1][1] = a[0][0] * inv_det;
Ok(out)
}
fn mat_inv_m3<F: SemiflowFloat, const M: usize>(
a: &[[F; M]; M],
) -> Result<[[F; M]; M], SemiflowError> {
let c00 = a[1][1] * a[2][2] - a[1][2] * a[2][1];
let c01 = -(a[1][0] * a[2][2] - a[1][2] * a[2][0]);
let c02 = a[1][0] * a[2][1] - a[1][1] * a[2][0];
let det = a[0][0] * c00 + a[0][1] * c01 + a[0][2] * c02;
if det == F::zero() {
return Err(SemiflowError::DomainViolation {
what: "mat_inv_m3: singular matrix (det=0)",
value: 0.0,
});
}
let inv_det = F::one() / det;
let c10 = -(a[0][1] * a[2][2] - a[0][2] * a[2][1]);
let c11 = a[0][0] * a[2][2] - a[0][2] * a[2][0];
let c12 = -(a[0][0] * a[2][1] - a[0][1] * a[2][0]);
let c20 = a[0][1] * a[1][2] - a[0][2] * a[1][1];
let c21 = -(a[0][0] * a[1][2] - a[0][2] * a[1][0]);
let c22 = a[0][0] * a[1][1] - a[0][1] * a[1][0];
let mut out = [[F::zero(); M]; M];
out[0][0] = c00 * inv_det;
out[0][1] = c10 * inv_det;
out[0][2] = c20 * inv_det;
out[1][0] = c01 * inv_det;
out[1][1] = c11 * inv_det;
out[1][2] = c21 * inv_det;
out[2][0] = c02 * inv_det;
out[2][1] = c12 * inv_det;
out[2][2] = c22 * inv_det;
Ok(out)
}
#[inline]
fn complement3(skip: usize) -> [usize; 3] {
let mut out = [0usize; 3];
let mut idx = 0;
for k in 0..4usize {
if k != skip {
out[idx] = k;
idx += 1;
}
}
out
}
fn mat_inv_m4<F: SemiflowFloat, const M: usize>(
a: &[[F; M]; M],
) -> Result<[[F; M]; M], SemiflowError> {
let det2 = |r0: usize, r1: usize, c0: usize, c1: usize| -> F {
a[r0][c0] * a[r1][c1] - a[r0][c1] * a[r1][c0]
};
let det3 = |r0: usize, r1: usize, r2: usize, c0: usize, c1: usize, c2: usize| -> F {
a[r0][c0] * det2(r1, r2, c1, c2) - a[r0][c1] * det2(r1, r2, c0, c2)
+ a[r0][c2] * det2(r1, r2, c0, c1)
};
let cf = |row: usize, col: usize| -> F {
let rows = complement3(row);
let cols = complement3(col);
let minor = det3(rows[0], rows[1], rows[2], cols[0], cols[1], cols[2]);
if (row + col) % 2 == 0 {
minor
} else {
-minor
}
};
let det = (0..4).fold(F::zero(), |acc, j| acc + a[0][j] * cf(0, j));
if det == F::zero() {
return Err(SemiflowError::DomainViolation {
what: "mat_inv_m4: singular matrix (det=0)",
value: 0.0,
});
}
let inv_det = F::one() / det;
let mut out = [[F::zero(); M]; M];
#[allow(clippy::needless_range_loop)]
for i in 0..4 {
for j in 0..4 {
out[i][j] = cf(j, i) * inv_det;
}
}
Ok(out)
}
#[cfg(test)]
#[allow(clippy::needless_range_loop)]
mod tests {
use super::*;
use crate::matrix_strang::block_mat_mul;
#[test]
fn mat_inv_m1_identity_check() {
const M: usize = 1;
let a: [[f64; M]; M] = [[3.7]];
let inv = mat_inv_dispatch::<f64, M>(&a).unwrap();
let prod = block_mat_mul::<f64, M>(&a, &inv);
assert!(
(prod[0][0] - 1.0).abs() < 1e-14,
"mat_inv_m1 A*A^-1 != I: {prod:?}"
);
}
#[test]
fn mat_inv_m2_identity_check() {
const M: usize = 2;
let a: [[f64; M]; M] = [[4.0, 1.5], [1.5, 3.0]];
let inv = mat_inv_dispatch::<f64, M>(&a).unwrap();
let prod = block_mat_mul::<f64, M>(&a, &inv);
for i in 0..M {
for j in 0..M {
let expected = if i == j { 1.0 } else { 0.0 };
assert!(
(prod[i][j] - expected).abs() < 1e-12,
"mat_inv_m2 A*A^-1 != I at ({i},{j}): got {val:.4e}",
val = prod[i][j]
);
}
}
}
#[test]
fn mat_inv_m3_identity_check() {
const M: usize = 3;
let a: [[f64; M]; M] = [[4.0, 1.0, 0.5], [1.0, 3.0, 0.8], [0.5, 0.8, 2.5]];
let inv = mat_inv_dispatch::<f64, M>(&a).unwrap();
let prod = block_mat_mul::<f64, M>(&a, &inv);
for i in 0..M {
for j in 0..M {
let expected = if i == j { 1.0 } else { 0.0 };
assert!(
(prod[i][j] - expected).abs() < 1e-11,
"mat_inv_m3 A*A^-1 != I at ({i},{j}): got {val:.4e}",
val = prod[i][j]
);
}
}
}
#[test]
fn mat_inv_m4_identity_check() {
const M: usize = 4;
let a: [[f64; M]; M] = [
[5.0, 1.0, 0.5, 0.2],
[1.0, 4.0, 0.8, 0.3],
[0.5, 0.8, 3.5, 0.6],
[0.2, 0.3, 0.6, 2.5],
];
let inv = mat_inv_dispatch::<f64, M>(&a).unwrap();
let prod = block_mat_mul::<f64, M>(&a, &inv);
for i in 0..M {
for j in 0..M {
let expected = if i == j { 1.0 } else { 0.0 };
assert!(
(prod[i][j] - expected).abs() < 1e-10,
"mat_inv_m4 A*A^-1 != I at ({i},{j}): got {val:.4e}",
val = prod[i][j]
);
}
}
}
}