Skip to main content

scirs2_sparse/ml_preconditioner/
types.rs

1//! Types for ML-guided preconditioner selection.
2
3/// The type of preconditioner to apply.
4#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
5#[non_exhaustive]
6pub enum PreconditionerType {
7    /// Jacobi (diagonal) preconditioner.
8    Jacobi,
9    /// Symmetric Successive Over-Relaxation.
10    SSOR,
11    /// Incomplete LU with zero fill-in.
12    ILU0,
13    /// Incomplete Cholesky with zero fill-in.
14    IC0,
15    /// Algebraic Multigrid.
16    AMG,
17    /// Sparse Approximate Inverse.
18    SPAI,
19    /// Polynomial preconditioner.
20    Polynomial,
21    /// No preconditioner (identity / direct solve).
22    None,
23}
24
25impl std::fmt::Display for PreconditionerType {
26    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
27        match self {
28            Self::Jacobi => write!(f, "Jacobi"),
29            Self::SSOR => write!(f, "SSOR"),
30            Self::ILU0 => write!(f, "ILU(0)"),
31            Self::IC0 => write!(f, "IC(0)"),
32            Self::AMG => write!(f, "AMG"),
33            Self::SPAI => write!(f, "SPAI"),
34            Self::Polynomial => write!(f, "Polynomial"),
35            Self::None => write!(f, "None"),
36            #[allow(unreachable_patterns)]
37            _ => write!(f, "Unknown"),
38        }
39    }
40}
41
42/// Numerical features extracted from a sparse matrix for classification.
43#[derive(Debug, Clone)]
44pub struct MatrixFeatures {
45    /// Matrix dimension (rows = cols for square matrices).
46    pub n: usize,
47    /// Number of non-zero entries.
48    pub nnz: usize,
49    /// Density = nnz / (n * n).
50    pub density: f64,
51    /// Maximum number of non-zeros in any single row.
52    pub max_row_nnz: usize,
53    /// Mean number of non-zeros per row.
54    pub mean_row_nnz: f64,
55    /// Half-bandwidth: max |i - j| over all stored (i, j).
56    pub bandwidth: usize,
57    /// Bandwidth ratio: bandwidth / n.
58    pub bandwidth_ratio: f64,
59    /// Cheap condition number estimate (max diag / min diag).
60    pub cond_estimate: f64,
61    /// Spectral radius estimate via Gershgorin bound.
62    pub spectral_radius: f64,
63    /// Diagonal dominance ratio: min_i |a_ii| / sum_{j!=i} |a_ij|.
64    pub diag_dominance: f64,
65    /// Symmetry measure: fraction of (i,j) entries that have a matching (j,i).
66    pub symmetry_measure: f64,
67    /// Whether all diagonal entries are positive.
68    pub has_positive_diagonal: bool,
69}
70
71/// Configuration for preconditioner selection.
72#[derive(Debug, Clone)]
73pub struct SelectionConfig {
74    /// Whether to incorporate the cost model when ranking candidates.
75    pub use_cost_model: bool,
76    /// Maximum number of features used by the classifier.
77    pub max_features: usize,
78}
79
80impl Default for SelectionConfig {
81    fn default() -> Self {
82        Self {
83            use_cost_model: true,
84            max_features: 12,
85        }
86    }
87}
88
89/// Result of preconditioner selection.
90#[derive(Debug, Clone)]
91pub struct SelectionResult {
92    /// The recommended preconditioner type.
93    pub recommended: PreconditionerType,
94    /// Confidence in the recommendation (0.0 – 1.0).
95    pub confidence: f64,
96    /// All candidate scores, sorted descending by score.
97    pub all_scores: Vec<(PreconditionerType, f64)>,
98    /// The matrix features that drove the decision.
99    pub features: MatrixFeatures,
100}
101
102/// Cost estimate for applying a given preconditioner.
103#[derive(Debug, Clone)]
104pub struct CostEstimate {
105    /// Estimated setup cost (e.g. factorization), in FLOPs.
106    pub setup_cost: f64,
107    /// Estimated cost per Krylov iteration, in FLOPs.
108    pub per_iteration_cost: f64,
109    /// Estimated number of iterations to convergence.
110    pub estimated_iterations: usize,
111    /// Total estimated cost: setup + iterations * per_iteration.
112    pub total_cost: f64,
113}
114
115#[cfg(test)]
116mod tests {
117    use super::*;
118
119    #[test]
120    fn test_preconditioner_type_display() {
121        assert_eq!(format!("{}", PreconditionerType::Jacobi), "Jacobi");
122        assert_eq!(format!("{}", PreconditionerType::ILU0), "ILU(0)");
123        assert_eq!(format!("{}", PreconditionerType::None), "None");
124    }
125
126    #[test]
127    fn test_selection_config_default() {
128        let cfg = SelectionConfig::default();
129        assert!(cfg.use_cost_model);
130        assert_eq!(cfg.max_features, 12);
131    }
132
133    #[test]
134    fn test_preconditioner_type_eq() {
135        assert_eq!(PreconditionerType::AMG, PreconditionerType::AMG);
136        assert_ne!(PreconditionerType::Jacobi, PreconditionerType::SSOR);
137    }
138}