Skip to main content

gam_sae/
certificate_impls.rs

1//! [`Certificate`] implementations for the gam-sae certificate zoo (task #16;
2//! descended #1521).
3//!
4//! These `impl Certificate for …` blocks were relocated out of the monolith
5//! root (`gam::inference::certificate_impls`) to satisfy the coherence orphan
6//! rule: the [`Certificate`] trait now lives in the neutral `gam-problem` crate
7//! and the implemented types ([`EncodeResult`], [`ResidualGaugeReport`],
8//! [`CertificateInputs`]) are owned here in `gam-sae`, so the impls must be
9//! defined in the type's home crate. The bodies are byte-identical to the
10//! monolith originals: each [`Certificate::verdict`] is still defined in terms
11//! of the type's own (unchanged) decision rule, so there remains exactly one
12//! source of truth for each verdict.
13
14use gam_problem::topology_certificates::{Certificate, Claim, Evidence, Verdict};
15
16use crate::encode::EncodeResult;
17use crate::identifiability::ResidualGaugeReport;
18use crate::manifold::{CertificateInputs, GlobalOptimalityVerdict};
19
20/// Helper: insert a scalar only when finite, else record it as text "n/a" so the
21/// evidence is explicit about a missing quantity (never a silent 0.0).
22fn put_finite(evidence: &mut Evidence, key: &'static str, value: f64) {
23    if value.is_finite() {
24        evidence.insert(key, value.into());
25    } else {
26        evidence.insert(key, "n/a".into());
27    }
28}
29
30// ── 4. Kantorovich encode atlas (#1010) ──────────────────────────────────────
31
32impl Certificate for EncodeResult {
33    fn claim(&self) -> Claim {
34        Claim::new(
35            "encode-atlas",
36            "each encoded row carries a per-row Newton–Kantorovich certificate \
37             (h = β·η·L ≤ ½ at the start point); certified rows converge \
38             quadratically into the unique root, and uncertified rows are flagged \
39             for the exact multi-start fallback — never silently encoded wrong",
40        )
41    }
42
43    fn evidence(&self) -> Evidence {
44        let mut e = Evidence::new();
45        let n = self.certified.len();
46        let certified = n - self.encode_uncertified_count;
47        e.insert("rows", n.into());
48        e.insert("certified_rows", certified.into());
49        e.insert(
50            "encode_uncertified_count",
51            self.encode_uncertified_count.into(),
52        );
53        let frac = if n > 0 {
54            certified as f64 / n as f64
55        } else {
56            f64::NAN
57        };
58        put_finite(&mut e, "certified_fraction", frac);
59        e
60    }
61
62    fn verdict(&self) -> Verdict {
63        // Conservative batch roll-up: the whole encode certifies only when EVERY
64        // row certified. One flagged row makes the batch `Insufficient` (the
65        // flagged rows must route to the exact fallback). An empty batch
66        // certifies nothing → `Unavailable`.
67        if self.certified.is_empty() {
68            Verdict::Unavailable
69        } else if self.encode_uncertified_count == 0 {
70            Verdict::Certified
71        } else {
72            Verdict::Insufficient
73        }
74    }
75}
76
77// ── 5. Exact-orbit residual-gauge report (#980/#998/#1008) ───────────────────
78
79impl Certificate for ResidualGaugeReport {
80    fn claim(&self) -> Claim {
81        Claim::new(
82            "residual-gauge",
83            "the fit is identified up to a named residual gauge group: every \
84             generator was curvature-tested in the fit's own metric, the pinning \
85             span rank is reported, and any surviving (unpinned) freedom is \
86             enumerated rather than silently absorbed",
87        )
88    }
89
90    fn evidence(&self) -> Evidence {
91        let mut e = Evidence::new();
92        e.insert(
93            "metric_provenance",
94            format!("{:?}", self.metric_provenance).into(),
95        );
96        e.insert("group_signature", self.group_signature().into());
97        e.insert("pinning_rank", self.pinning_rank.into());
98        e.insert("residual_gauge_dim", self.residual_gauge_dim.into());
99        e.insert(
100            "diffeomorphism_unpinned",
101            self.diffeomorphism_unpinned.into(),
102        );
103        e.insert("generator_count", self.generators.len().into());
104        match self.sym_f_trivial_under_output_fisher {
105            Some(t) => e.insert("sym_f_trivial_under_output_fisher", t.into()),
106            None => e.insert("sym_f_trivial_under_output_fisher", "n/a".into()),
107        };
108        e.insert("summary", self.summary.clone().into());
109        e
110    }
111
112    fn verdict(&self) -> Verdict {
113        // The report ALWAYS makes a claim once computed (every generator is
114        // tested), so it is never `Unavailable` here. The conservative reading:
115        // the identifiability claim is `Certified` when the model is pinned down
116        // to a discrete (zero-dimensional) gauge group and the diffeomorphism
117        // pin is active; a positive residual gauge dimension or an inactive
118        // diffeomorphism pin is the escalation flag → `Insufficient`. Under
119        // OutputFisher provenance a surviving atom-permutation is a certificate
120        // violation → also `Insufficient`.
121        let pinned = self.residual_gauge_dim == 0
122            && !self.diffeomorphism_unpinned
123            && self.sym_f_trivial_under_output_fisher != Some(false);
124        if pinned {
125            Verdict::Certified
126        } else {
127            Verdict::Insufficient
128        }
129    }
130}
131
132// ── 6. Dictionary incoherence / global optimality (#1008) ────────────────────
133
134impl Certificate for CertificateInputs {
135    fn claim(&self) -> Claim {
136        Claim::new(
137            "global-optimality",
138            "the fitted dictionary's basin stationary point is the unique global \
139             optimum up to the residual gauge group: a conservative sufficient \
140             condition on mutual coherence, per-atom curvature, activity floors, \
141             and reconstruction SNR holds with positive margin",
142        )
143    }
144
145    fn evidence(&self) -> Evidence {
146        let mut e = Evidence::new();
147        put_finite(&mut e, "mu_hat", self.mu_hat);
148        put_finite(&mut e, "mean_activity_floor", self.mean_activity_floor);
149        put_finite(&mut e, "peak_activity_floor", self.peak_activity_floor);
150        put_finite(&mut e, "snr_proxy", self.snr_proxy);
151        put_finite(&mut e, "dispersion", self.dispersion);
152        put_finite(
153            &mut e,
154            "global_optimality_margin",
155            self.global_optimality.margin(),
156        );
157        e.insert(
158            "global_optimality",
159            if self.global_optimality.is_certified() {
160                "certified_global"
161            } else {
162                "uncertified"
163            }
164            .into(),
165        );
166        e.insert("atom_count", self.per_atom_mean_activity.len().into());
167        e.insert("note", self.note.clone().into());
168        e
169    }
170
171    fn verdict(&self) -> Verdict {
172        // The unchanged decision rule is `GlobalOptimalityVerdict::is_certified`:
173        // a `CertifiedGlobal { margin > 0 }` is never wrong (conservative
174        // sufficient condition), an `Uncertified` is "cannot decide" — not
175        // "non-unique" — so it maps to `Insufficient`, never a false pass.
176        match self.global_optimality {
177            GlobalOptimalityVerdict::CertifiedGlobal { .. } => Verdict::Certified,
178            GlobalOptimalityVerdict::Uncertified { .. } => Verdict::Insufficient,
179        }
180    }
181}