1use 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
23fn 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
33impl 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 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
80impl 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 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
135impl 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 match self.global_optimality {
180 GlobalOptimalityVerdict::CertifiedGlobal { .. } => Verdict::Certified,
181 GlobalOptimalityVerdict::Uncertified { .. } => Verdict::Insufficient,
182 }
183 }
184}
185
186impl<'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
277impl<'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}