use ndarray::prelude::*;
use ndarray_linalg::SVD;
use ndarray_stats::QuantileExt;
use crate::types::EPSILON;
pub trait PseudoInverse {
fn pinv(&self) -> Self;
}
impl PseudoInverse for Array2<f64> {
fn pinv(&self) -> Self {
let a = *self.abs().max().unwrap_or(&1.);
let m = self / a;
let (u, s, vt) = m.svd(true, true).unwrap_or_else(|e| {
panic!(
"Failed to compute the SVD \n\
\t for the matrix: \n\
\t {m:?} \n\
\t with error: \n\
\t {e:?}."
)
});
let u = u.expect("Failed to get U from the SVD.");
let vt = vt.expect("Failed to get VT from the SVD.");
let s_max = s.max().unwrap_or(&0.);
let r_tol = f64::max(EPSILON, s.len() as f64 * s_max * EPSILON);
let s_inv = Array2::from_diag(&s.mapv(|x| if x > r_tol { 1. / x } else { 0. }));
vt.t().dot(&s_inv).dot(&u.t()) / a
}
}