Skip to main content

gam_linalg/
types.rs

1use serde::{Deserialize, Serialize};
2
3/// Determinant semantics of an objective-accounted ridge.
4///
5/// There is deliberately no `Auto`: callers must decide whether they are
6/// evaluating the exact SPD determinant or a named positive-part
7/// approximation before constructing the policy.
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
9pub enum RidgeDeterminantMode {
10    /// Exact full log-determinant of the ridged SPD matrix.
11    Full,
12    /// Smooth positive-part spectral determinant approximation. This changes
13    /// the estimand and is therefore explicitly named as an approximation.
14    PositivePartApproximation,
15}
16
17/// Structurally valid ways a diagonal ridge may participate in a computation.
18///
19/// The former public boolean matrix admitted contradictory states such as a
20/// quadratic penalty without the corresponding Hessian. This enum has only
21/// the three coherent inhabitants used by the engine.
22#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
23pub enum RidgePolicy {
24    /// Ridge is an explicit part of the exact objective: quadratic, penalty
25    /// normalizer, and Laplace Hessian all include it, using a full SPD logdet.
26    ExactFullObjective,
27    /// Ridge participates in every objective term, but determinant evaluation
28    /// is the explicitly named positive-part approximation.
29    PositivePartApproximateObjective,
30    /// Ridge changes only an inner linear solve and never the fitted objective,
31    /// exported Hessian, determinant, covariance, or serialized model.
32    SolverOnly,
33}
34
35impl RidgePolicy {
36    pub const fn exact_full_objective() -> Self {
37        Self::ExactFullObjective
38    }
39
40    pub const fn positive_part_approximate_objective() -> Self {
41        Self::PositivePartApproximateObjective
42    }
43
44    pub const fn solver_only() -> Self {
45        Self::SolverOnly
46    }
47
48    #[inline]
49    pub const fn accounts_for_objective(self) -> bool {
50        !matches!(self, Self::SolverOnly)
51    }
52
53    #[inline]
54    pub const fn determinant_mode(self) -> RidgeDeterminantMode {
55        match self {
56            Self::ExactFullObjective | Self::SolverOnly => RidgeDeterminantMode::Full,
57            Self::PositivePartApproximateObjective => {
58                RidgeDeterminantMode::PositivePartApproximation
59            }
60        }
61    }
62
63    #[inline]
64    pub const fn is_approximation(self) -> bool {
65        matches!(self, Self::PositivePartApproximateObjective)
66    }
67}
68
69#[cfg(test)]
70mod ridge_policy_tests {
71    use super::*;
72
73    #[test]
74    fn exact_policy_is_homogeneous_and_full() {
75        let policy = RidgePolicy::exact_full_objective();
76        assert!(policy.accounts_for_objective());
77        assert_eq!(policy.determinant_mode(), RidgeDeterminantMode::Full);
78        assert!(!policy.is_approximation());
79    }
80
81    #[test]
82    fn positive_part_policy_is_explicitly_approximate() {
83        let policy = RidgePolicy::positive_part_approximate_objective();
84        assert!(policy.accounts_for_objective());
85        assert_eq!(
86            policy.determinant_mode(),
87            RidgeDeterminantMode::PositivePartApproximation
88        );
89        assert!(policy.is_approximation());
90    }
91
92    #[test]
93    fn solver_only_policy_cannot_enter_objective_accounting() {
94        let policy = RidgePolicy::solver_only();
95        assert!(!policy.accounts_for_objective());
96        assert_eq!(policy.determinant_mode(), RidgeDeterminantMode::Full);
97    }
98}