1use 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
28fn 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
38impl 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 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 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 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 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
119impl 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 if self.race_transfer_margin().is_finite() {
151 Verdict::Insufficient
152 } else {
153 Verdict::Unavailable
154 }
155 }
156}
157
158pub fn coreset_race_verdict(verdict: CoresetMarginVerdict) -> Verdict {
165 match verdict {
166 CoresetMarginVerdict::Certified { .. } => Verdict::Certified,
167 CoresetMarginVerdict::InsufficientMargin { .. } => Verdict::Insufficient,
168 }
169}
170
171pub 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
180impl 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 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
222impl 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 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 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 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}