use faer::sparse::SparseColMatRef;
use crate::{FreedomAnalysis, NonLinearSystemError, solver::Model};
impl Model<'_> {
pub(crate) fn freedom_analysis(&self) -> Result<FreedomAnalysis, NonLinearSystemError> {
let j_sparse = SparseColMatRef::new(self.jc.sym.as_ref(), &self.jc.vals);
let j_dense = j_sparse.to_dense();
let nvars = self.layout.num_variables;
debug_assert_eq!(
nvars,
j_dense.ncols(),
"Jacobian was malformed, Adam messed something up here."
);
let svd = j_dense.svd().map_err(NonLinearSystemError::FaerSvd)?;
let svd_s = svd.S();
let svd_v = svd.V();
let underconstrained = calculate(svd_s, svd_v, nvars)?;
Ok(FreedomAnalysis::new(underconstrained))
}
}
fn calculate(
svd_sigma: faer::diag::generic::Diag<faer::diag::Ref<'_, f64>>,
svd_v: faer::mat::generic::Mat<faer::mat::Ref<'_, f64>>,
nvars: usize,
) -> Result<Vec<crate::Id>, NonLinearSystemError> {
let sigma_col = svd_sigma.column_vector();
let largest_singular_value = sigma_col
.iter()
.copied()
.reduce(libm::fmax)
.ok_or(NonLinearSystemError::EmptySystemNotAllowed)?;
let tolerance = 1e-8 * largest_singular_value;
let rank = sigma_col.iter().filter(|&&s| s > tolerance).count();
let degrees_of_freedom: Vec<usize> = (rank..nvars).collect();
let participation: Vec<_> = (0..nvars)
.map(|j| {
let mut sum_sq = 0.0f64;
for &k in °rees_of_freedom {
let v_jk = svd_v.get(j, k);
sum_sq += v_jk * v_jk;
}
sum_sq.sqrt()
})
.collect();
let max_participation = participation.iter().copied().fold(0.0, libm::fmax);
let var_tol = 1e-3 * max_participation;
let underconstrained = (0..nvars)
.filter(|&j| participation[j] > var_tol)
.map(|x| x as u32)
.collect();
Ok(underconstrained)
}