use nalgebra::{DMatrix, Matrix3};
pub(crate) fn tikhonov_scale(diag_abs_sum: f64, n: usize) -> f64 {
(1e-6 * diag_abs_sum / n.max(1) as f64).max(1e-8)
}
pub(crate) fn invert_with_retry_1(v: f64) -> Option<f64> {
if v.abs() > f64::EPSILON {
return Some(1.0 / v);
}
let shifted = v + tikhonov_scale(v.abs(), 1);
if shifted.abs() > f64::EPSILON {
Some(1.0 / shifted)
} else {
None
}
}
pub(crate) fn invert_with_retry_3(m: &Matrix3<f64>) -> Option<Matrix3<f64>> {
if let Some(inv) = m.try_inverse() {
return Some(inv);
}
let reg = tikhonov_scale(m.diagonal().iter().sum::<f64>().abs(), 3);
(m + Matrix3::identity() * reg).try_inverse()
}
pub(crate) fn invert_with_retry_dyn(m: &DMatrix<f64>) -> Option<DMatrix<f64>> {
if let Some(inv) = m.clone().try_inverse() {
return Some(inv);
}
let n = m.nrows();
let reg = tikhonov_scale(m.diagonal().iter().sum::<f64>().abs(), n);
(m + DMatrix::identity(n, n) * reg).try_inverse()
}
#[cfg(test)]
mod tests {
use super::*;
type TestResult = Result<(), Box<dyn std::error::Error>>;
#[test]
fn tikhonov_scale_floors_and_scales() -> TestResult {
assert_eq!(tikhonov_scale(0.0, 3), 1e-8);
assert!((tikhonov_scale(300.0, 3) - 1e-4).abs() < 1e-12);
assert_eq!(tikhonov_scale(0.0, 0), 1e-8);
Ok(())
}
#[test]
fn retry_helpers_agree_with_direct_inverse_when_healthy() -> TestResult {
assert!((invert_with_retry_1(4.0).ok_or("scalar")? - 0.25).abs() < 1e-15);
let m3 = Matrix3::new(4.0, 1.0, 0.0, 1.0, 3.0, 0.0, 0.0, 0.0, 2.0);
let inv3 = invert_with_retry_3(&m3).ok_or("3x3")?;
let identity = m3 * inv3;
for r in 0..3 {
for c in 0..3 {
let want = if r == c { 1.0 } else { 0.0 };
assert!((identity[(r, c)] - want).abs() < 1e-12);
}
}
let md = DMatrix::from_row_slice(2, 2, &[4.0, 1.0, 1.0, 3.0]);
let invd = invert_with_retry_dyn(&md).ok_or("dyn")?;
let identity = md * invd;
for r in 0..2 {
for c in 0..2 {
let want = if r == c { 1.0 } else { 0.0 };
assert!((identity[(r, c)] - want).abs() < 1e-12);
}
}
Ok(())
}
#[test]
fn retry_helpers_rescue_singular_blocks_without_nan() -> TestResult {
let inv0 = invert_with_retry_1(0.0).ok_or("zero scalar must take the shift")?;
assert!(inv0.is_finite() && inv0 > 0.0);
let z3 = Matrix3::<f64>::zeros();
let invz = invert_with_retry_3(&z3).ok_or("zero 3x3 must take the shift")?;
assert!(invz.iter().all(|v| v.is_finite()));
let zd = DMatrix::<f64>::zeros(2, 2);
let invd = invert_with_retry_dyn(&zd).ok_or("zero dyn must take the shift")?;
assert!(invd.iter().all(|v| v.is_finite()));
Ok(())
}
}