1use crate::logdet_bounds::{LogdetEnclosure, MarginVerdict};
21use crate::model_types::OuterCriterionCertificate;
22use crate::row_sampling_measure::{CoresetCertificate, CoresetMarginVerdict};
23use crate::structure_search::{CollapseAction, CollapseEvent};
24use gam_problem::topology_certificates::{Certificate, Claim, Evidence, Verdict};
25
26fn put_finite(evidence: &mut Evidence, key: &'static str, value: f64) {
29 if value.is_finite() {
30 evidence.insert(key, value.into());
31 } else {
32 evidence.insert(key, "n/a".into());
33 }
34}
35
36impl Certificate for OuterCriterionCertificate {
39 fn claim(&self) -> Claim {
40 Claim::new(
41 "outer-optimality",
42 concat!(
43 "the returned outer optimum is analytically KKT-stationary and ",
44 "its available exact curvature is not indefinite",
45 ),
46 )
47 }
48
49 fn evidence(&self) -> Evidence {
50 let mut e = Evidence::new();
51 put_finite(
52 &mut e,
53 "stationarity_raw_norm",
54 self.stationarity.raw_norm(),
55 );
56 put_finite(
57 &mut e,
58 "stationarity_projected_norm",
59 self.stationarity.projected_norm(),
60 );
61 put_finite(&mut e, "stationarity_bound", self.stationarity.bound());
62 let rung = self.stationarity.rung();
66 e.insert("stationarity_rung", rung.label.clone().into());
67 e.insert("stationarity_rung_derived", rung.derived_standard.into());
68 e.insert("stationarity_kind", self.stationarity.kind_label().into());
73 e.insert(
74 "hessian_psd",
75 match self.hessian_psd() {
76 Some(psd) => psd.into(),
77 None => "n/a".into(),
78 },
79 );
80 if let Some(clearance) = self.curvature_floor {
84 e.insert("curvature_floor_cleared", clearance.cleared.into());
85 put_finite(
86 &mut e,
87 "curvature_interior_min_eigenvalue",
88 clearance.interior_min_eigenvalue,
89 );
90 put_finite(&mut e, "curvature_gradient_floor", clearance.gradient_floor);
91 }
92 e.insert("lambdas_railed_count", self.lambdas_railed.len().into());
93 e.insert("stationary", self.is_stationary().into());
94 e.insert("curvature_admissible", self.curvature_not_refused().into());
100 e.insert(
101 "curvature_verdict",
102 self.curvature_verdict().to_string().into(),
103 );
104 e.insert("summary", self.summary().into());
105 e
106 }
107
108 fn verdict(&self) -> Verdict {
109 if self.certifies() {
110 Verdict::Certified
111 } else {
112 Verdict::Insufficient
113 }
114 }
115}
116
117impl Certificate for CoresetCertificate {
120 fn claim(&self) -> Claim {
121 Claim::new(
122 "coreset-budget",
123 "the selected row coreset reproduces the full-corpus evidence within \
124 a certified spectral + likelihood error budget; a race decision \
125 inherits the full-corpus verdict only when its margin clears this \
126 budget",
127 )
128 }
129
130 fn evidence(&self) -> Evidence {
131 let mut e = Evidence::new();
132 put_finite(&mut e, "eps_spectral", self.eps_spectral);
133 put_finite(&mut e, "eps_likelihood", self.eps_likelihood);
134 e.insert("dim_effective", self.dim_effective.into());
135 e.insert("n_selected", self.n_selected.into());
136 put_finite(&mut e, "logdet_error_bound", self.logdet_error_bound());
137 put_finite(&mut e, "race_transfer_margin", self.race_transfer_margin());
138 e
139 }
140
141 fn verdict(&self) -> Verdict {
142 if self.race_transfer_margin().is_finite() {
149 Verdict::Insufficient
150 } else {
151 Verdict::Unavailable
152 }
153 }
154}
155
156pub fn coreset_race_verdict(verdict: CoresetMarginVerdict) -> Verdict {
163 match verdict {
164 CoresetMarginVerdict::Certified { .. } => Verdict::Certified,
165 CoresetMarginVerdict::InsufficientMargin { .. } => Verdict::Insufficient,
166 }
167}
168
169pub fn enclosure_margin_verdict(enclosure: &LogdetEnclosure, decision_margin: f64) -> Verdict {
172 match enclosure.decide_within_margin(decision_margin) {
173 MarginVerdict::Decided { .. } => Verdict::Certified,
174 MarginVerdict::InsufficientMargin { .. } => Verdict::Insufficient,
175 }
176}
177
178impl Certificate for LogdetEnclosure {
181 fn claim(&self) -> Claim {
182 Claim::new(
183 "logdet-enclosure",
184 "the log-determinant is enclosed in a certified [lower, upper] \
185 interval whose midpoint is interchangeable with the exact value for \
186 any decision whose margin exceeds the enclosure gap",
187 )
188 }
189
190 fn evidence(&self) -> Evidence {
191 let mut e = Evidence::new();
192 put_finite(&mut e, "block_diag_logdet", self.block_diag_logdet);
193 put_finite(&mut e, "lower", self.lower);
194 put_finite(&mut e, "upper", self.upper);
195 put_finite(&mut e, "gap", self.gap());
196 put_finite(&mut e, "rho", self.rho);
197 put_finite(&mut e, "p2", self.p2);
198 match self.p3 {
199 Some(p3) => put_finite(&mut e, "p3", p3),
200 None => {
201 e.insert("p3", "n/a".into());
202 }
203 }
204 e
205 }
206
207 fn verdict(&self) -> Verdict {
208 if self.lower.is_finite() && self.upper.is_finite() && self.gap().is_finite() {
213 Verdict::Insufficient
214 } else {
215 Verdict::Unavailable
216 }
217 }
218}
219
220impl Certificate for CollapseEvent {
223 fn claim(&self) -> Claim {
224 Claim::new(
225 "structure-collapse",
226 "an atom's active mass fell below the collapse floor during the joint \
227 fit; the guard either reseeded it from a fresh basin or, once the \
228 reseed budget was exhausted, recorded the collapse as the objective's \
229 terminal verdict",
230 )
231 }
232
233 fn evidence(&self) -> Evidence {
234 let mut e = Evidence::new();
235 e.insert("iteration", self.iteration.into());
236 e.insert("atom", self.atom.into());
237 put_finite(&mut e, "max_active_mass", self.max_active_mass);
238 put_finite(&mut e, "floor", self.floor);
239 e.insert(
240 "action",
241 match self.action {
242 CollapseAction::Reseeded => "reseeded",
243 CollapseAction::Terminal => "terminal",
244 }
245 .into(),
246 );
247 e
248 }
249
250 fn verdict(&self) -> Verdict {
251 match self.action {
257 CollapseAction::Reseeded => Verdict::Insufficient,
258 CollapseAction::Terminal => Verdict::Unavailable,
259 }
260 }
261}
262
263#[cfg(test)]
264mod tests {
265 use super::*;
266 use gam_problem::topology_certificates::CertificateLedger;
267
268 #[test]
269 fn criterion_stationarity_and_curvature_control_verdict() {
270 let clean = OuterCriterionCertificate {
271 stationarity: crate::model_types::OuterStationarityCertificate::AnalyticGradient {
272 grad_norm: 1e-8,
273 projected_grad_norm: 1e-8,
274 bound: 1e-6,
275 rung: gam_problem::StationarityRung {
276 label: "solver-band",
277 derived_standard: false,
278 }
279 .into(),
280 },
281 curvature: crate::model_types::CurvatureEvidence::Measured { psd: true },
282 lambdas_railed: Vec::new(),
283 railed_facts: Vec::new(),
284 curvature_floor: None,
285 };
286 assert_eq!(clean.verdict(), Verdict::Certified);
287 assert!(clean.verdict().is_certified());
288
289 let nonstationary = OuterCriterionCertificate {
290 stationarity: crate::model_types::OuterStationarityCertificate::AnalyticGradient {
291 grad_norm: 1e-2,
292 projected_grad_norm: 1e-2,
293 bound: 1e-6,
294 rung: gam_problem::StationarityRung {
295 label: "solver-band",
296 derived_standard: false,
297 }
298 .into(),
299 },
300 ..clean
301 };
302 assert_eq!(nonstationary.verdict(), Verdict::Insufficient);
303 assert!(!nonstationary.verdict().is_certified());
304 assert_eq!(nonstationary.claim().id, "outer-optimality");
306 assert!(nonstationary.evidence().contains_key("summary"));
307 }
308
309 #[test]
310 fn coreset_budget_alone_is_insufficient_but_decides_with_margin() {
311 let cert = CoresetCertificate::new(0.1, 0.0, 4, 32).expect("coreset cert");
312 assert_eq!(cert.verdict(), Verdict::Insufficient);
313 let req = cert.race_transfer_margin();
315 assert_eq!(
316 coreset_race_verdict(cert.certify_margin(req * 0.5)),
317 Verdict::Insufficient
318 );
319 assert_eq!(
320 coreset_race_verdict(cert.certify_margin(req * 2.0 + 1.0)),
321 Verdict::Certified
322 );
323 }
324
325 #[test]
326 fn enclosure_certifies_only_when_margin_clears_gap() {
327 let enc = LogdetEnclosure {
328 block_diag_logdet: 10.0,
329 lower: 9.9,
330 upper: 10.1,
331 rho: 0.3,
332 p2: 0.01,
333 p3: None,
334 };
335 assert_eq!(enc.verdict(), Verdict::Insufficient);
336 assert_eq!(enclosure_margin_verdict(&enc, 0.5), Verdict::Certified);
338 assert_eq!(enclosure_margin_verdict(&enc, 0.1), Verdict::Insufficient);
339 }
340
341 #[test]
342 fn collapse_terminal_is_unavailable_reseeded_is_insufficient() {
343 let reseeded = CollapseEvent {
344 iteration: 3,
345 atom: 1,
346 max_active_mass: 1e-4,
347 floor: 1e-3,
348 action: CollapseAction::Reseeded,
349 };
350 assert_eq!(reseeded.verdict(), Verdict::Insufficient);
351 let terminal = CollapseEvent {
352 action: CollapseAction::Terminal,
353 ..reseeded
354 };
355 assert_eq!(terminal.verdict(), Verdict::Unavailable);
356 }
357
358 #[test]
359 fn ledger_rolls_up_to_weakest_member() {
360 let mut ledger = CertificateLedger::new();
361 let clean = OuterCriterionCertificate {
362 stationarity: crate::model_types::OuterStationarityCertificate::AnalyticGradient {
363 grad_norm: 1e-8,
364 projected_grad_norm: 1e-8,
365 bound: 1e-6,
366 rung: gam_problem::StationarityRung {
367 label: "solver-band",
368 derived_standard: false,
369 }
370 .into(),
371 },
372 curvature: crate::model_types::CurvatureEvidence::Measured { psd: true },
373 lambdas_railed: Vec::new(),
374 railed_facts: Vec::new(),
375 curvature_floor: None,
376 };
377 let cert = CoresetCertificate::new(0.1, 0.0, 4, 32).expect("coreset");
378 ledger.record(&clean); ledger.record(&cert); assert_eq!(ledger.overall(), Verdict::Insufficient);
381 assert_eq!(ledger.verdict_of("outer-optimality"), Verdict::Certified);
382 assert_eq!(ledger.verdict_of("coreset-budget"), Verdict::Insufficient);
383 }
384}