Skip to main content

gam_solve/inference/
certificate_impls.rs

1//! `Certificate` implementations and margin-resolved `Verdict` mappings for
2//! the gam-solve-tier certificate zoo (task #16; descended #1521).
3//!
4//! Two concerns live here, both gam-solve-tier: (a) the `impl Certificate for …`
5//! blocks for the gam-solve-owned certificate types (`OuterCriterionCertificate`,
6//! [`CoresetCertificate`](crate::row_sampling_measure::CoresetCertificate),
7//! `LogdetEnclosure`, [`CollapseEvent`](crate::structure_search::CollapseEvent)),
8//! and (b) the two pure margin-resolution helpers, whose only inputs are
9//! gam-solve-tier types (`LogdetEnclosure`/`MarginVerdict` and
10//! [`CoresetMarginVerdict`](crate::row_sampling_measure::CoresetMarginVerdict))
11//! plus the contracted-down `Verdict` ladder. Both were relocated out of the
12//! monolith root (`gam::inference::certificate_impls`) to satisfy the coherence
13//! orphan rule: the `Certificate` trait now lives in the neutral `gam-problem`
14//! crate and these types are owned here in `gam-solve`, so the impls must be
15//! defined in the type's home crate. The bodies are byte-identical to the
16//! monolith originals, so there remains exactly one decision rule per verdict.
17//! (The gam-sae-owned certificate types — `EncodeResult`, `ResidualGaugeReport`,
18//! `CertificateInputs` — carry their own impls in `gam_sae::certificate_impls`.)
19
20use crate::logdet_bounds::LogdetEnclosure;
21use crate::model_types::OuterCriterionCertificate;
22use crate::row_sampling_measure::CoresetCertificate;
23use crate::structure_search::{CollapseAction, CollapseEvent};
24use gam_problem::topology_certificates::{Certificate, Claim, Evidence, Verdict};
25use crate::row_sampling_measure::CoresetMarginVerdict;
26use crate::logdet_bounds::MarginVerdict;
27
28/// Helper: insert a scalar only when finite, else record it as text "n/a" so the
29/// evidence is explicit about a missing quantity (never a silent 0.0).
30fn put_finite(evidence: &mut Evidence, key: &'static str, value: f64) {
31    if value.is_finite() {
32        evidence.insert(key, value.into());
33    } else {
34        evidence.insert(key, "n/a".into());
35    }
36}
37
38// ── 1. Outer-optimum first-order self-audit (#931/#934) ──────────────────────
39
40impl Certificate for OuterCriterionCertificate {
41    fn claim(&self) -> Claim {
42        Claim::new(
43            "outer-optimality",
44            concat!(
45                "the returned outer optimum is analytically KKT-stationary and ",
46                "its available exact curvature is not indefinite",
47            ),
48        )
49    }
50
51    fn evidence(&self) -> Evidence {
52        let mut e = Evidence::new();
53        put_finite(
54            &mut e,
55            "stationarity_raw_norm",
56            self.stationarity.raw_norm(),
57        );
58        put_finite(
59            &mut e,
60            "stationarity_projected_norm",
61            self.stationarity.projected_norm(),
62        );
63        put_finite(&mut e, "stationarity_bound", self.stationarity.bound());
64        // The bound never appears without the standard that produced it: across
65        // this subsystem the bound alone spans nine orders and names nothing
66        // (#2458/#2530).
67        let rung = self.stationarity.rung();
68        e.insert("stationarity_rung", rung.label.clone().into());
69        e.insert("stationarity_rung_derived", rung.derived_standard.into());
70        // `kind_label` rather than `is_fixed_point()` + else: the old two-way
71        // test reported an AsymptoteRail certificate as "analytic_gradient" —
72        // a route wearing another route's name, in the map a reader consults
73        // precisely to find out which route ran.
74        e.insert("stationarity_kind", self.stationarity.kind_label().into());
75        e.insert(
76            "hessian_psd",
77            match self.hessian_psd() {
78                Some(psd) => psd.into(),
79                None => "n/a".into(),
80            },
81        );
82        // The floor's verdict rides BESIDE the raw measurement, never over it,
83        // so a reader can see both which question was asked and how much
84        // negative curvature the floor was able to absorb.
85        if let Some(clearance) = self.curvature_floor {
86            e.insert("curvature_floor_cleared", clearance.cleared.into());
87            put_finite(
88                &mut e,
89                "curvature_interior_min_eigenvalue",
90                clearance.interior_min_eigenvalue,
91            );
92            put_finite(&mut e, "curvature_gradient_floor", clearance.gradient_floor);
93        }
94        e.insert("lambdas_railed_count", self.lambdas_railed.len().into());
95        e.insert("stationary", self.is_stationary().into());
96        // Both, deliberately (#2578): the boolean is the published contract and
97        // stays byte-compatible, and the verdict beside it says WHICH of the
98        // three states produced it — so a reader can tell "measured and
99        // admissible" from "nothing was measured", which the boolean alone
100        // cannot express.
101        e.insert("curvature_admissible", self.curvature_not_refused().into());
102        e.insert(
103            "curvature_verdict",
104            self.curvature_verdict().to_string().into(),
105        );
106        e.insert("summary", self.summary().into());
107        e
108    }
109
110    fn verdict(&self) -> Verdict {
111        if self.certifies() {
112            Verdict::Certified
113        } else {
114            Verdict::Insufficient
115        }
116    }
117}
118
119// ── 2. Sensitivity-coreset error budget ──────────────────────────────────────
120
121impl Certificate for CoresetCertificate {
122    fn claim(&self) -> Claim {
123        Claim::new(
124            "coreset-budget",
125            "the selected row coreset reproduces the full-corpus evidence within \
126             a certified spectral + likelihood error budget; a race decision \
127             inherits the full-corpus verdict only when its margin clears this \
128             budget",
129        )
130    }
131
132    fn evidence(&self) -> Evidence {
133        let mut e = Evidence::new();
134        put_finite(&mut e, "eps_spectral", self.eps_spectral);
135        put_finite(&mut e, "eps_likelihood", self.eps_likelihood);
136        e.insert("dim_effective", self.dim_effective.into());
137        e.insert("n_selected", self.n_selected.into());
138        put_finite(&mut e, "logdet_error_bound", self.logdet_error_bound());
139        put_finite(&mut e, "race_transfer_margin", self.race_transfer_margin());
140        e
141    }
142
143    fn verdict(&self) -> Verdict {
144        // A coreset certificate is a transfer BUDGET, not a standalone decision:
145        // it certifies a race verdict only once a consumer supplies a decision
146        // margin that clears `race_transfer_margin`. With no consumer margin in
147        // hand, the conservative standalone verdict is `Insufficient` (the
148        // budget is present, but nothing has been decided by it yet) when the
149        // budget is finite, and `Unavailable` when it is not.
150        if self.race_transfer_margin().is_finite() {
151            Verdict::Insufficient
152        } else {
153            Verdict::Unavailable
154        }
155    }
156}
157
158/// Map a coreset race outcome (the certificate's own
159/// `CoresetCertificate::certify_margin`
160/// rule, evaluated against a consumer's
161/// `decision_margin`) onto the shared [`Verdict`] ladder. This is the
162/// margin-resolved entry point a race consumer uses to obtain a unified verdict
163/// without re-deriving the mapping.
164pub fn coreset_race_verdict(verdict: CoresetMarginVerdict) -> Verdict {
165    match verdict {
166        CoresetMarginVerdict::Certified { .. } => Verdict::Certified,
167        CoresetMarginVerdict::InsufficientMargin { .. } => Verdict::Insufficient,
168    }
169}
170
171/// Verdict for an enclosure resolved against a concrete consumer
172/// `decision_margin`, reusing [`LogdetEnclosure::decide_within_margin`].
173pub fn enclosure_margin_verdict(enclosure: &LogdetEnclosure, decision_margin: f64) -> Verdict {
174    match enclosure.decide_within_margin(decision_margin) {
175        MarginVerdict::Decided { .. } => Verdict::Certified,
176        MarginVerdict::InsufficientMargin { .. } => Verdict::Insufficient,
177    }
178}
179
180// ── 3. Log-det enclosure ─────────────────────────────────────────────────────
181
182impl Certificate for LogdetEnclosure {
183    fn claim(&self) -> Claim {
184        Claim::new(
185            "logdet-enclosure",
186            "the log-determinant is enclosed in a certified [lower, upper] \
187             interval whose midpoint is interchangeable with the exact value for \
188             any decision whose margin exceeds the enclosure gap",
189        )
190    }
191
192    fn evidence(&self) -> Evidence {
193        let mut e = Evidence::new();
194        put_finite(&mut e, "block_diag_logdet", self.block_diag_logdet);
195        put_finite(&mut e, "lower", self.lower);
196        put_finite(&mut e, "upper", self.upper);
197        put_finite(&mut e, "gap", self.gap());
198        put_finite(&mut e, "rho", self.rho);
199        put_finite(&mut e, "p2", self.p2);
200        match self.p3 {
201            Some(p3) => put_finite(&mut e, "p3", p3),
202            None => {
203                e.insert("p3", "n/a".into());
204            }
205        }
206        e
207    }
208
209    fn verdict(&self) -> Verdict {
210        // An enclosure on its own does not certify a decision — only a consumer
211        // margin does (via `decide_within_margin`). The standalone verdict is
212        // `Insufficient` when the enclosure is finite (evidence present, no
213        // decision yet) and `Unavailable` when the bounds are non-finite.
214        if self.lower.is_finite() && self.upper.is_finite() && self.gap().is_finite() {
215            Verdict::Insufficient
216        } else {
217            Verdict::Unavailable
218        }
219    }
220}
221
222// ── 7. Structure-search collapse event ───────────────────────────────────────
223
224impl Certificate for CollapseEvent {
225    fn claim(&self) -> Claim {
226        Claim::new(
227            "structure-collapse",
228            "an atom's active mass fell below the collapse floor during the joint \
229             fit; the guard either reseeded it from a fresh basin or, once the \
230             reseed budget was exhausted, recorded the collapse as the objective's \
231             terminal verdict",
232        )
233    }
234
235    fn evidence(&self) -> Evidence {
236        let mut e = Evidence::new();
237        e.insert("iteration", self.iteration.into());
238        e.insert("atom", self.atom.into());
239        put_finite(&mut e, "max_active_mass", self.max_active_mass);
240        put_finite(&mut e, "floor", self.floor);
241        e.insert(
242            "action",
243            match self.action {
244                CollapseAction::Reseeded => "reseeded",
245                CollapseAction::Terminal => "terminal",
246            }
247            .into(),
248        );
249        e
250    }
251
252    fn verdict(&self) -> Verdict {
253        // A collapse event is, by definition, a guard FIRING — it never certifies
254        // health. A `Reseeded` event is a recovered breach (`Insufficient`: the
255        // breach happened but the fit continued); a `Terminal` event is the
256        // objective's verdict that the collapse stands (`Unavailable`: the claim
257        // of a healthy non-collapsed dictionary cannot be made at all).
258        match self.action {
259            CollapseAction::Reseeded => Verdict::Insufficient,
260            CollapseAction::Terminal => Verdict::Unavailable,
261        }
262    }
263}
264
265#[cfg(test)]
266mod tests {
267    use super::*;
268
269    #[test]
270    fn criterion_stationarity_and_curvature_control_verdict() {
271        let clean = OuterCriterionCertificate {
272            stationarity: crate::model_types::OuterStationarityCertificate::AnalyticGradient {
273                grad_norm: 1e-8,
274                projected_grad_norm: 1e-8,
275                bound: 1e-6,
276                rung: gam_problem::StationarityRung {
277                    label: "solver-band",
278                    derived_standard: false,
279                }
280                .into(),
281            },
282            curvature: crate::model_types::CurvatureEvidence::Measured { psd: true },
283            lambdas_railed: Vec::new(),
284            railed_facts: Vec::new(),
285            curvature_floor: None,
286        };
287        assert_eq!(clean.verdict(), Verdict::Certified);
288        assert!(clean.verdict().is_certified());
289
290        let nonstationary = OuterCriterionCertificate {
291            stationarity: crate::model_types::OuterStationarityCertificate::AnalyticGradient {
292                grad_norm: 1e-2,
293                projected_grad_norm: 1e-2,
294                bound: 1e-6,
295                rung: gam_problem::StationarityRung {
296                    label: "solver-band",
297                    derived_standard: false,
298                }
299                .into(),
300            },
301            ..clean
302        };
303        assert_eq!(nonstationary.verdict(), Verdict::Insufficient);
304        assert!(!nonstationary.verdict().is_certified());
305        // The claim id is stable and the summary rides the evidence.
306        assert_eq!(nonstationary.claim().id, "outer-optimality");
307        assert!(nonstationary.evidence().contains_key("summary"));
308    }
309
310    #[test]
311    fn enclosure_certifies_only_when_margin_clears_gap() {
312        let enc = LogdetEnclosure {
313            block_diag_logdet: 10.0,
314            lower: 9.9,
315            upper: 10.1,
316            rho: 0.3,
317            p2: 0.01,
318            p3: None,
319        };
320        assert_eq!(enc.verdict(), Verdict::Insufficient);
321        // gap = 0.2; a margin of 0.5 > gap certifies; 0.1 < gap does not.
322        assert_eq!(enclosure_margin_verdict(&enc, 0.5), Verdict::Certified);
323        assert_eq!(enclosure_margin_verdict(&enc, 0.1), Verdict::Insufficient);
324    }
325
326    #[test]
327    fn collapse_terminal_is_unavailable_reseeded_is_insufficient() {
328        let reseeded = CollapseEvent {
329            iteration: 3,
330            atom: 1,
331            max_active_mass: 1e-4,
332            floor: 1e-3,
333            action: CollapseAction::Reseeded,
334        };
335        assert_eq!(reseeded.verdict(), Verdict::Insufficient);
336        let terminal = CollapseEvent {
337            action: CollapseAction::Terminal,
338            ..reseeded
339        };
340        assert_eq!(terminal.verdict(), Verdict::Unavailable);
341    }
342
343}