Skip to main content

scirs2_sparse/cholesky_update/
types.rs

1//! Types for sparse Cholesky modifications (rank-1 updates/downdates).
2//!
3//! This module defines the configuration, result, and classification types
4//! used by the Cholesky update and downdate algorithms.
5
6use std::fmt;
7
8/// Configuration for Cholesky update/downdate operations.
9///
10/// Controls numerical checks and tolerances used during the modification
11/// of a Cholesky factorization.
12///
13/// # Example
14///
15/// ```
16/// use scirs2_sparse::cholesky_update::CholUpdateConfig;
17///
18/// let config = CholUpdateConfig {
19///     check_positive_definite: true,
20///     tolerance: 1e-12,
21/// };
22/// ```
23#[derive(Debug, Clone, Copy)]
24pub struct CholUpdateConfig {
25    /// Whether to verify positive definiteness of the result.
26    ///
27    /// When `true`, the algorithm checks that all diagonal elements of the
28    /// updated factor remain positive, returning an error if the result
29    /// would be indefinite (particularly relevant for downdates).
30    pub check_positive_definite: bool,
31
32    /// Numerical tolerance for zero-checks and stability decisions.
33    ///
34    /// Diagonal elements below this threshold are considered numerically
35    /// zero, triggering a positive-definiteness failure when checking is
36    /// enabled.
37    pub tolerance: f64,
38}
39
40impl Default for CholUpdateConfig {
41    fn default() -> Self {
42        Self {
43            check_positive_definite: true,
44            tolerance: 1e-14,
45        }
46    }
47}
48
49/// Result of a Cholesky update or downdate operation.
50///
51/// Contains the modified Cholesky factor along with diagnostic information
52/// about the success and numerical conditioning of the operation.
53///
54/// # Representation
55///
56/// The factor is stored as a dense lower-triangular matrix in row-major
57/// order: `factor[i][j]` is the element at row `i`, column `j`, with
58/// `factor[i][j] == 0` for `j > i`.
59#[derive(Debug, Clone)]
60pub struct CholUpdateResult {
61    /// The modified lower-triangular Cholesky factor L'.
62    ///
63    /// Satisfies L' L'^T = A' where A' is the modified matrix.
64    pub factor: Vec<Vec<f64>>,
65
66    /// Whether the update completed successfully.
67    ///
68    /// For updates this is almost always `true`; for downdates it may be
69    /// `false` if the result would be indefinite (only when
70    /// `check_positive_definite` is enabled in the config).
71    pub successful: bool,
72
73    /// Rough estimate of the condition number of the modified factor.
74    ///
75    /// Computed as `max(diag) / min(diag)` of the factor, giving a cheap
76    /// (but often useful) lower bound on the true condition number.
77    pub condition_estimate: f64,
78}
79
80impl fmt::Display for CholUpdateResult {
81    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
82        write!(
83            f,
84            "CholUpdateResult {{ n={}, successful={}, cond_est={:.6e} }}",
85            self.factor.len(),
86            self.successful,
87            self.condition_estimate,
88        )
89    }
90}
91
92/// Classification of Cholesky modification types.
93///
94/// Describes the kind of rank-structured perturbation applied to the
95/// original matrix A = L L^T.
96#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
97#[non_exhaustive]
98pub enum UpdateType {
99    /// Rank-1 update: A' = A + α u u^T  (α > 0).
100    RankOneUpdate,
101    /// Rank-1 downdate: A' = A − α u u^T  (α > 0).
102    RankOneDowndate,
103    /// Low-rank update: A' = A + W D W^T where D is diagonal.
104    LowRankUpdate,
105}
106
107impl fmt::Display for UpdateType {
108    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
109        match self {
110            UpdateType::RankOneUpdate => write!(f, "Rank-1 Update"),
111            UpdateType::RankOneDowndate => write!(f, "Rank-1 Downdate"),
112            UpdateType::LowRankUpdate => write!(f, "Low-Rank Update"),
113            #[allow(unreachable_patterns)]
114            _ => write!(f, "Unknown Update Type"),
115        }
116    }
117}
118
119/// Estimate the condition number of a lower-triangular matrix from its diagonal.
120///
121/// Returns `max(|diag|) / min(|diag|)`.  If any diagonal element is zero
122/// (or below `tol`), returns `f64::INFINITY`.
123pub(crate) fn estimate_condition(l: &[Vec<f64>], tol: f64) -> f64 {
124    let n = l.len();
125    if n == 0 {
126        return 1.0;
127    }
128
129    let mut min_diag = f64::INFINITY;
130    let mut max_diag = 0.0_f64;
131
132    for i in 0..n {
133        let d = l[i][i].abs();
134        if d < tol {
135            return f64::INFINITY;
136        }
137        if d < min_diag {
138            min_diag = d;
139        }
140        if d > max_diag {
141            max_diag = d;
142        }
143    }
144
145    if min_diag <= 0.0 {
146        f64::INFINITY
147    } else {
148        max_diag / min_diag
149    }
150}
151
152#[cfg(test)]
153mod tests {
154    use super::*;
155
156    #[test]
157    fn test_default_config() {
158        let cfg = CholUpdateConfig::default();
159        assert!(cfg.check_positive_definite);
160        assert!(cfg.tolerance > 0.0);
161        assert!(cfg.tolerance < 1e-10);
162    }
163
164    #[test]
165    fn test_update_type_display() {
166        assert_eq!(format!("{}", UpdateType::RankOneUpdate), "Rank-1 Update");
167        assert_eq!(
168            format!("{}", UpdateType::RankOneDowndate),
169            "Rank-1 Downdate"
170        );
171        assert_eq!(format!("{}", UpdateType::LowRankUpdate), "Low-Rank Update");
172    }
173
174    #[test]
175    fn test_estimate_condition_identity() {
176        let l = vec![vec![1.0, 0.0], vec![0.0, 1.0]];
177        let cond = estimate_condition(&l, 1e-14);
178        assert!((cond - 1.0).abs() < 1e-14);
179    }
180
181    #[test]
182    fn test_estimate_condition_singular() {
183        let l = vec![vec![1.0, 0.0], vec![0.5, 0.0]];
184        let cond = estimate_condition(&l, 1e-14);
185        assert!(cond.is_infinite());
186    }
187
188    #[test]
189    fn test_result_display() {
190        let res = CholUpdateResult {
191            factor: vec![vec![1.0]],
192            successful: true,
193            condition_estimate: 1.0,
194        };
195        let s = format!("{}", res);
196        assert!(s.contains("successful=true"));
197    }
198}