Skip to main content

gam_linalg/
types.rs

1use serde::{Deserialize, Serialize};
2
3/// How ridge-adjusted determinants should be evaluated for outer criteria.
4#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
5pub enum RidgeDeterminantMode {
6    /// Use exact full logdet.
7    Auto,
8    /// Use full log-determinant of the ridged matrix (requires SPD in practice).
9    Full,
10    /// Use positive-part pseudo-determinant (sum log ev for ev > floor).
11    PositivePart,
12}
13
14/// Global policy governing how a stabilization ridge participates in objectives.
15#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
16pub struct RidgePolicy {
17    /// Must remain independent of smoothing parameters (`rho`) for smooth outer derivatives.
18    pub rho_independent: bool,
19    /// Include ridge in quadratic penalty term: `0.5 * delta * ||beta||^2`.
20    pub include_quadratic_penalty: bool,
21    /// Include ridge in penalty determinant term (e.g. `log|S_lambda + delta I|`).
22    pub include_penalty_logdet: bool,
23    /// Include ridge in Hessian used by Laplace term / implicit differentiation.
24    pub include_laplacehessian: bool,
25    /// Determinant evaluation mode when ridge participates in logdet terms.
26    pub determinant_mode: RidgeDeterminantMode,
27}
28
29impl RidgePolicy {
30    /// Default policy used by PIRLS/REML path:
31    /// treat stabilization ridge as an explicit `delta I` prior contribution
32    /// with adaptive logdet evaluation.
33    pub const fn explicit_stabilization_full() -> Self {
34        Self {
35            rho_independent: true,
36            include_quadratic_penalty: true,
37            include_penalty_logdet: true,
38            include_laplacehessian: true,
39            determinant_mode: RidgeDeterminantMode::Auto,
40        }
41    }
42
43    pub const fn explicit_stabilization_full_exact() -> Self {
44        Self {
45            rho_independent: true,
46            include_quadratic_penalty: true,
47            include_penalty_logdet: true,
48            include_laplacehessian: true,
49            determinant_mode: RidgeDeterminantMode::Full,
50        }
51    }
52
53    /// Variant used when pseudo-determinants are required for indefinite matrices.
54    pub const fn explicit_stabilization_pospart() -> Self {
55        Self {
56            rho_independent: true,
57            include_quadratic_penalty: true,
58            include_penalty_logdet: true,
59            include_laplacehessian: true,
60            determinant_mode: RidgeDeterminantMode::PositivePart,
61        }
62    }
63
64    /// Solver-only stabilization: the ridge `δI` stabilizes the inner linear
65    /// solve (it bounds the Newton step `(H+δI)⁻¹∇`) but is **excluded** from
66    /// the REML/LAML objective — no `½·δ·‖β‖²` quadratic-penalty term, no
67    /// `δ`-shift of the penalty log-determinant, no `δ`-shift of the Laplace
68    /// Hessian. Use this when a numerical floor is needed purely to keep the
69    /// linear algebra finite during screening and must NOT bias the
70    /// smoothing-parameter selection or shrink identified coefficients off the
71    /// MLE. With every `include_*` false the optimized objective equals the
72    /// true penalized REML criterion, so the value surface and its analytic
73    /// gradient describe the same objective (gam#747/#748).
74    pub const fn solver_only() -> Self {
75        Self {
76            rho_independent: true,
77            include_quadratic_penalty: false,
78            include_penalty_logdet: false,
79            include_laplacehessian: false,
80            determinant_mode: RidgeDeterminantMode::PositivePart,
81        }
82    }
83}
84
85#[cfg(test)]
86mod ridge_policy_tests {
87    use super::*;
88
89    #[test]
90    fn explicit_stabilization_full_includes_all_terms() {
91        let p = RidgePolicy::explicit_stabilization_full();
92        assert!(p.rho_independent);
93        assert!(p.include_quadratic_penalty);
94        assert!(p.include_penalty_logdet);
95        assert!(p.include_laplacehessian);
96        assert_eq!(p.determinant_mode, RidgeDeterminantMode::Auto);
97    }
98
99    #[test]
100    fn explicit_stabilization_full_exact_uses_full_determinant() {
101        let p = RidgePolicy::explicit_stabilization_full_exact();
102        assert!(p.include_penalty_logdet);
103        assert_eq!(p.determinant_mode, RidgeDeterminantMode::Full);
104    }
105
106    #[test]
107    fn explicit_stabilization_pospart_uses_positive_part_determinant() {
108        let p = RidgePolicy::explicit_stabilization_pospart();
109        assert!(p.include_penalty_logdet);
110        assert_eq!(p.determinant_mode, RidgeDeterminantMode::PositivePart);
111    }
112
113    #[test]
114    fn solver_only_ridge_policy_stays_off_objective_accounting() {
115        let p = RidgePolicy::solver_only();
116        assert!(p.rho_independent);
117        assert!(!p.include_quadratic_penalty);
118        assert!(!p.include_penalty_logdet);
119        assert!(!p.include_laplacehessian);
120    }
121
122    #[test]
123    fn determinant_mode_variants_are_distinct() {
124        assert_ne!(RidgeDeterminantMode::Auto, RidgeDeterminantMode::Full);
125        assert_ne!(RidgeDeterminantMode::Full, RidgeDeterminantMode::PositivePart);
126        assert_ne!(RidgeDeterminantMode::Auto, RidgeDeterminantMode::PositivePart);
127    }
128}