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::{
19    CertificateInputs, CoordinateFidelityCertificate, GlobalOptimalityVerdict,
20    TopologyPersistenceCertificate,
21};
22
23/// Helper: insert a scalar only when finite, else record it as text "n/a" so the
24/// evidence is explicit about a missing quantity (never a silent 0.0).
25fn put_finite(evidence: &mut Evidence, key: &'static str, value: f64) {
26    if value.is_finite() {
27        evidence.insert(key, value.into());
28    } else {
29        evidence.insert(key, "n/a".into());
30    }
31}
32
33// ── 4. Kantorovich encode atlas (#1010) ──────────────────────────────────────
34
35impl Certificate for EncodeResult {
36    fn claim(&self) -> Claim {
37        Claim::new(
38            "encode-atlas",
39            "each encoded row carries a per-row Newton–Kantorovich certificate \
40             (h = β·η·L ≤ ½ at the start point); certified rows converge \
41             quadratically into the unique root, and uncertified rows are flagged \
42             for the exact multi-start fallback — never silently encoded wrong",
43        )
44    }
45
46    fn evidence(&self) -> Evidence {
47        let mut e = Evidence::new();
48        let n = self.certified.len();
49        let certified = n - self.encode_uncertified_count;
50        e.insert("rows", n.into());
51        e.insert("certified_rows", certified.into());
52        e.insert(
53            "encode_uncertified_count",
54            self.encode_uncertified_count.into(),
55        );
56        let frac = if n > 0 {
57            certified as f64 / n as f64
58        } else {
59            f64::NAN
60        };
61        put_finite(&mut e, "certified_fraction", frac);
62        e
63    }
64
65    fn verdict(&self) -> Verdict {
66        // Conservative batch roll-up: the whole encode certifies only when EVERY
67        // row certified. One flagged row makes the batch `Insufficient` (the
68        // flagged rows must route to the exact fallback). An empty batch
69        // certifies nothing → `Unavailable`.
70        if self.certified.is_empty() {
71            Verdict::Unavailable
72        } else if self.encode_uncertified_count == 0 {
73            Verdict::Certified
74        } else {
75            Verdict::Insufficient
76        }
77    }
78}
79
80// ── 5. Exact-orbit residual-gauge report (#980/#998/#1008) ───────────────────
81
82impl Certificate for ResidualGaugeReport {
83    fn claim(&self) -> Claim {
84        Claim::new(
85            "residual-gauge",
86            "the fit is identified up to a named residual gauge group: every \
87             generator was curvature-tested in the fit's own metric, the pinning \
88             span rank is reported, and any surviving (unpinned) freedom is \
89             enumerated rather than silently absorbed",
90        )
91    }
92
93    fn evidence(&self) -> Evidence {
94        let mut e = Evidence::new();
95        e.insert(
96            "metric_provenance",
97            format!("{:?}", self.metric_provenance).into(),
98        );
99        e.insert("group_signature", self.group_signature().into());
100        e.insert("pinning_rank", self.pinning_rank.into());
101        e.insert("residual_gauge_dim", self.residual_gauge_dim.into());
102        e.insert(
103            "diffeomorphism_unpinned",
104            self.diffeomorphism_unpinned.into(),
105        );
106        e.insert("generator_count", self.generators.len().into());
107        match self.sym_f_trivial_under_output_fisher {
108            Some(t) => e.insert("sym_f_trivial_under_output_fisher", t.into()),
109            None => e.insert("sym_f_trivial_under_output_fisher", "n/a".into()),
110        };
111        e.insert("summary", self.summary.clone().into());
112        e
113    }
114
115    fn verdict(&self) -> Verdict {
116        // The report ALWAYS makes a claim once computed (every generator is
117        // tested), so it is never `Unavailable` here. The conservative reading:
118        // the identifiability claim is `Certified` when the model is pinned down
119        // to a discrete (zero-dimensional) gauge group and the diffeomorphism
120        // pin is active; a positive residual gauge dimension or an inactive
121        // diffeomorphism pin is the escalation flag → `Insufficient`. Under
122        // OutputFisher provenance a surviving atom-permutation is a certificate
123        // violation → also `Insufficient`.
124        let pinned = self.residual_gauge_dim == 0
125            && !self.diffeomorphism_unpinned
126            && self.sym_f_trivial_under_output_fisher != Some(false);
127        if pinned {
128            Verdict::Certified
129        } else {
130            Verdict::Insufficient
131        }
132    }
133}
134
135// ── 6. Dictionary incoherence / global optimality (#1008) ────────────────────
136
137impl Certificate for CertificateInputs {
138    fn claim(&self) -> Claim {
139        Claim::new(
140            "global-optimality",
141            "the fitted dictionary's basin stationary point is the unique global \
142             optimum up to the residual gauge group: a conservative sufficient \
143             condition on mutual coherence, per-atom curvature, activity floors, \
144             and reconstruction SNR holds with positive margin",
145        )
146    }
147
148    fn evidence(&self) -> Evidence {
149        let mut e = Evidence::new();
150        put_finite(&mut e, "mu_hat", self.mu_hat);
151        put_finite(&mut e, "mean_activity_floor", self.mean_activity_floor);
152        put_finite(&mut e, "peak_activity_floor", self.peak_activity_floor);
153        put_finite(&mut e, "snr_proxy", self.snr_proxy);
154        put_finite(&mut e, "dispersion", self.dispersion);
155        put_finite(
156            &mut e,
157            "global_optimality_margin",
158            self.global_optimality.margin(),
159        );
160        e.insert(
161            "global_optimality",
162            if self.global_optimality.is_certified() {
163                "certified_global"
164            } else {
165                "uncertified"
166            }
167            .into(),
168        );
169        e.insert("atom_count", self.per_atom_mean_activity.len().into());
170        e.insert("note", self.note.clone().into());
171        e
172    }
173
174    fn verdict(&self) -> Verdict {
175        // The unchanged decision rule is `GlobalOptimalityVerdict::is_certified`:
176        // a `CertifiedGlobal { margin > 0 }` is never wrong (conservative
177        // sufficient condition), an `Uncertified` is "cannot decide" — not
178        // "non-unique" — so it maps to `Insufficient`, never a false pass.
179        match self.global_optimality {
180            GlobalOptimalityVerdict::CertifiedGlobal { .. } => Verdict::Certified,
181            GlobalOptimalityVerdict::Uncertified { .. } => Verdict::Insufficient,
182        }
183    }
184}
185
186// ── 7. Chart coordinate fidelity (#2081) ─────────────────────────────────────
187
188impl<'a> Certificate for CoordinateFidelityCertificate<'a> {
189    fn claim(&self) -> Claim {
190        Claim::new(
191            "coordinate-fidelity",
192            "every eligible d=1 SAE chart reports a faithful coordinate reading: \
193             either the raw chart is already arc-length or the payload supplies \
194             the pure-read arc-length coordinate; degenerate charts are exposed \
195             as refusals rather than silently treated as angles",
196        )
197    }
198
199    fn evidence(&self) -> Evidence {
200        let mut e = Evidence::new();
201        let mut eligible = 0_usize;
202        let mut certified = 0_usize;
203        let mut degenerate = 0_usize;
204        let mut max_arclength = f64::NEG_INFINITY;
205        let mut max_raw_rms = f64::NEG_INFINITY;
206        let mut max_raw_max = f64::NEG_INFINITY;
207        let mut max_uniformity = f64::NEG_INFINITY;
208        let mut min_p = f64::INFINITY;
209        let mut support_masses = Vec::new();
210        let mut effective_ns = Vec::new();
211        let mut support_esses = Vec::new();
212        let mut worst = "unavailable";
213
214        for atom in self.atoms.iter().flatten() {
215            eligible += 1;
216            if atom.certified {
217                certified += 1;
218                if worst == "unavailable" {
219                    worst = atom.verdict.label();
220                }
221            } else {
222                degenerate += 1;
223                worst = atom.verdict.label();
224            }
225            if atom.arclength_defect.is_finite() {
226                max_arclength = max_arclength.max(atom.arclength_defect);
227            }
228            if atom.raw_arclength_defect_rms.is_finite() {
229                max_raw_rms = max_raw_rms.max(atom.raw_arclength_defect_rms);
230            }
231            if atom.raw_arclength_defect_max.is_finite() {
232                max_raw_max = max_raw_max.max(atom.raw_arclength_defect_max);
233            }
234            if atom.uniformity_statistic.is_finite() {
235                max_uniformity = max_uniformity.max(atom.uniformity_statistic);
236            }
237            if atom.uniformity_p_value.is_finite() {
238                min_p = min_p.min(atom.uniformity_p_value);
239            }
240            support_masses.push(atom.support_mass);
241            effective_ns.push(atom.effective_n);
242            support_esses.push(atom.support_ess);
243        }
244
245        e.insert("atom_count", self.atoms.len().into());
246        e.insert("eligible_d1_atoms", eligible.into());
247        e.insert("certified_d1_atoms", certified.into());
248        e.insert("degenerate_d1_atoms", degenerate.into());
249        e.insert("worst_coordinate_verdict", worst.into());
250        put_finite(&mut e, "max_arclength_defect", max_arclength);
251        put_finite(&mut e, "max_raw_arclength_defect_rms", max_raw_rms);
252        put_finite(&mut e, "max_raw_arclength_defect_max", max_raw_max);
253        put_finite(&mut e, "max_uniformity_statistic", max_uniformity);
254        put_finite(&mut e, "min_uniformity_p_value", min_p);
255        e.insert("support_mass", support_masses.into());
256        e.insert("effective_n", effective_ns.into());
257        e.insert("support_ess", support_esses.into());
258        e
259    }
260
261    fn verdict(&self) -> Verdict {
262        let mut saw_eligible = false;
263        for atom in self.atoms.iter().flatten() {
264            saw_eligible = true;
265            if !atom.certified {
266                return Verdict::Insufficient;
267            }
268        }
269        if saw_eligible {
270            Verdict::Certified
271        } else {
272            Verdict::Unavailable
273        }
274    }
275}
276
277// ── 8. Persistent-homology topology audit (reviewer F3) ───────────────────────
278
279impl<'a> Certificate for TopologyPersistenceCertificate<'a> {
280    fn claim(&self) -> Claim {
281        Claim::new(
282            "topology-persistence",
283            "each audited SAE atom's assigned-row image has persistent homology \
284             consistent with the raced topology: measured Betti numbers and loop \
285             evidence are exposed, and any disagreement is marked contested rather \
286             than trusted silently",
287        )
288    }
289
290    fn evidence(&self) -> Evidence {
291        let mut e = Evidence::new();
292        let mut audited = 0_usize;
293        let mut contested = 0_usize;
294        let mut max_h1 = f64::NEG_INFINITY;
295        let mut max_h2 = f64::NEG_INFINITY;
296        let mut betti0 = Vec::new();
297        let mut betti1 = Vec::new();
298        let mut betti2 = Vec::new();
299        let mut expected_betti0 = Vec::new();
300        let mut expected_betti1 = Vec::new();
301        let mut expected_betti2 = Vec::new();
302        let mut support_sizes = Vec::new();
303        let mut support_masses = Vec::new();
304        let mut effective_ns = Vec::new();
305        let mut support_esses = Vec::new();
306        let mut null_pvalues = Vec::new();
307        let mut spikein_powers = Vec::new();
308
309        for atom in self.atoms.iter().flatten() {
310            audited += 1;
311            if atom.contested {
312                contested += 1;
313            }
314            if atom.dominant_h1_persistence.is_finite() {
315                max_h1 = max_h1.max(atom.dominant_h1_persistence);
316            }
317            if atom.dominant_h2_persistence.is_finite() {
318                max_h2 = max_h2.max(atom.dominant_h2_persistence);
319            }
320            betti0.push(atom.measured_betti.b0 as f64);
321            betti1.push(atom.measured_betti.b1 as f64);
322            betti2.push(atom.measured_betti.b2.unwrap_or(0) as f64);
323            expected_betti0.push(atom.expected_betti.b0 as f64);
324            expected_betti1.push(atom.expected_betti.b1 as f64);
325            expected_betti2.push(atom.expected_betti.b2.unwrap_or(0) as f64);
326            support_sizes.push(atom.support_size as f64);
327            support_masses.push(atom.support_mass);
328            effective_ns.push(atom.effective_n);
329            support_esses.push(atom.support_ess);
330            if let Some(calibration) = &atom.null_calibration {
331                null_pvalues.push(calibration.null_pvalue);
332                spikein_powers.push(calibration.spikein_power);
333            }
334        }
335
336        e.insert("atom_count", self.atoms.len().into());
337        e.insert("audited_atoms", audited.into());
338        e.insert("contested_atoms", contested.into());
339        e.insert("measured_betti0", betti0.into());
340        e.insert("measured_betti1", betti1.into());
341        e.insert("measured_betti2", betti2.into());
342        e.insert("expected_betti0", expected_betti0.into());
343        e.insert("expected_betti1", expected_betti1.into());
344        e.insert("expected_betti2", expected_betti2.into());
345        e.insert("support_size", support_sizes.into());
346        e.insert("support_mass", support_masses.into());
347        e.insert("effective_n", effective_ns.into());
348        e.insert("support_ess", support_esses.into());
349        e.insert("null_pvalue", null_pvalues.into());
350        e.insert("spikein_power", spikein_powers.into());
351        put_finite(&mut e, "max_dominant_h1_persistence", max_h1);
352        put_finite(&mut e, "max_dominant_h2_persistence", max_h2);
353        e
354    }
355
356    fn verdict(&self) -> Verdict {
357        let mut saw_audited = false;
358        for atom in self.atoms.iter().flatten() {
359            saw_audited = true;
360            if atom.contested {
361                return Verdict::Insufficient;
362            }
363        }
364        if saw_audited {
365            Verdict::Certified
366        } else {
367            Verdict::Unavailable
368        }
369    }
370}