bayes_estimate/linalg/rcond.rs
1//! Numerical approximation of matrix reciprocal condition numbers 'rcond'.
2//!
3//! Matrices are well-conditioned if the reciprocal condition number is near 1 and ill-conditioned if it is near zero.
4//!
5//! For positive (semi-)definite matrices a simple definition is the minimum diagonal element / maximum diagonal element.
6//! This is best applied to Cholesky factorisation by using the square of the factors diagonal or for a UdU' by using 'd'
7//! directly.
8//!
9//! A rcond = 0 implies the matrix is semi definite, < 0 implies the matrix is negative.
10//!
11//! Required for all linear algebra in models and filters.
12
13use nalgebra::{allocator::Allocator, DefaultAllocator, Dim, OMatrix, RealField};
14
15/// Determine the reciprocal condition number of a symmetric matrix.
16///
17/// The 'rcond' is simply the minimum diagonal element / maximum diagonal element. A NaN result a negative result.
18pub fn rcond_symmetric<N: Copy + RealField, R: Dim, C: Dim>(sm: &OMatrix<N, R, C>) -> N
19where
20 DefaultAllocator: Allocator<R, C>,
21{
22 // Special case an empty matrix
23 let n = sm.nrows();
24 if n == 0 {
25 N::zero()
26 } else {
27 let mut mind = sm[(0, 0)];
28 let mut maxd = mind;
29
30 for i in 0..n {
31 let d = sm[(i, i)];
32 if d != d {
33 // NaN
34 mind = N::one().neg();
35 break;
36 }
37 if d < mind {
38 mind = d;
39 }
40 if d > maxd {
41 maxd = d;
42 }
43 }
44
45 rcond_min_max(mind, maxd)
46 }
47}
48
49/// Determine the 'rcond' from minimum and maximum diagonal elements.
50fn rcond_min_max<N: RealField>(mind: N, maxd: N) -> N {
51 if mind < N::zero() {
52 // matrix is negative
53 mind // mind < 0 but does not represent a rcond
54 } else {
55 assert!(mind <= maxd); // check sanity
56
57 // NOTE mind -0 will be handled here and propagate into rcond
58 let rcond = mind / maxd; // rcond from min/max norm
59 if !rcond.is_finite() {
60 // NaN, singular due to (mind == maxd) == (zero or infinity)
61 N::zero()
62 } else {
63 assert!(rcond <= N::one());
64 rcond
65 }
66 }
67}
68
69#[cfg(test)]
70mod test {
71 use super::*;
72
73 #[test]
74 fn test_simple_rconds() {
75 assert_eq!(rcond_min_max(1., 1.), 1.);
76 assert_eq!(rcond_min_max(1., 2.), 0.5);
77 }
78
79 #[test]
80 fn test_special_rconds() {
81 assert_eq!(rcond_min_max(-1., 55.), -1.);
82 assert_eq!(rcond_min_max(-2.,55.), -2.);
83 assert_eq!(rcond_min_max(-0.,55.), -0.);
84 assert_eq!(rcond_min_max(-0., 0.), 0.);
85 assert_eq!(rcond_min_max(-0., -0.), 0.);
86 }
87
88 #[should_panic(expected = "mind <= maxd")]
89 #[test]
90 fn test_greater_one_panics() {
91 rcond_min_max(2., 1.);
92 }
93
94}
95
96/// Checks the reciprocal condition number is > 0 .
97///
98/// IEC 559 NaN values are never true
99pub fn check_positive<N: RealField>(rcond: N, message: &str) -> Result<(), &str> {
100 if rcond > N::zero() {
101 Ok(())
102 } else {
103 Err(message)
104 }
105}
106
107/// Checks the reciprocal condition number is >= 0 .
108///
109/// IEC 559 NaN values are never true
110pub fn check_non_negative<N: RealField>(rcond: N, message: &str) -> Result<(), &str> {
111 if rcond >= N::zero() {
112 Ok(())
113 } else {
114 Err(message)
115 }
116}
117
118