use super::qr::QrResult;
#[derive(Debug, Clone, Copy)]
pub struct DofAnalysis {
pub dof: usize,
pub rank: usize,
pub num_params: usize,
pub num_equations: usize,
}
pub fn analyze(jacobian: &[f64], m: usize, n: usize) -> DofAnalysis {
let rank = if m == 0 || n == 0 {
0
} else {
let mut data = jacobian.to_vec();
let qr = QrResult::factorize(&mut data, m, n);
qr.rank(1e-10)
};
DofAnalysis {
dof: n.saturating_sub(rank),
rank,
num_params: n,
num_equations: m,
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used)]
mod tests {
use super::*;
#[test]
fn fully_constrained() {
let j = vec![1.0, 0.0, 0.0, 1.0];
let result = analyze(&j, 2, 2);
assert_eq!(result.dof, 0);
assert_eq!(result.rank, 2);
}
#[test]
fn under_constrained() {
let j = vec![1.0, 1.0];
let result = analyze(&j, 1, 2);
assert_eq!(result.dof, 1);
assert_eq!(result.rank, 1);
}
#[test]
fn over_constrained_redundant() {
let j = vec![1.0, 0.0, 0.0, 1.0, 1.0, 0.0];
let result = analyze(&j, 3, 2);
assert_eq!(result.dof, 0);
assert_eq!(result.rank, 2);
}
#[test]
fn empty_system() {
let result = analyze(&[], 0, 0);
assert_eq!(result.dof, 0);
assert_eq!(result.rank, 0);
}
#[test]
fn free_point_has_two_dof() {
let result = analyze(&[], 0, 2);
assert_eq!(result.dof, 2);
}
}