gam_solve/rho_optimizer/run.rs
1use super::*;
2use gam_problem::StationarityRung;
3
4use super::asymptote_certificate::{
5 AsymptoteSample, AsymptoteSide, AsymptoteTolerances, AsymptoteVerdict, AsymptoteWindow,
6 MIN_TAIL_SAMPLES, assess_coordinate,
7};
8use super::rail_face::{
9 RailFaceLimit, RailFaceLimitOutcome, RailFaceProof, RailFaceVerdict, certify_rail_face,
10};
11
12pub(crate) const OPERATOR_TRUST_RESTART_RADIUS_FLOOR: f64 = 1.0e-6;
13
14/// Inner coefficient state bound to one exact outer seed.
15///
16/// A cached coefficient vector is only a valid initialization for the outer
17/// coordinate that produced it. Keeping the coordinate beside the vector
18/// prevents a multi-start run from silently reusing one basin's coefficients
19/// at a different seed.
20#[derive(Clone, Debug)]
21pub(crate) struct BoundInnerSeed {
22 pub(crate) theta: Array1<f64>,
23 pub(crate) beta: Array1<f64>,
24}
25
26pub(crate) fn outer_theta_bitwise_eq(left: &Array1<f64>, right: &Array1<f64>) -> bool {
27 left.len() == right.len()
28 && left
29 .iter()
30 .zip(right.iter())
31 .all(|(left, right)| left.to_bits() == right.to_bits())
32}
33
34/// Install cached inner state only at the exact outer coordinate that owns it.
35///
36/// This function is called after a seed-attempt reset and immediately before
37/// the literal seed can be evaluated. Generated multistart candidates never
38/// inherit coefficients from another outer point.
39pub(crate) fn install_matching_initial_inner_seed(
40 obj: &mut dyn OuterObjective,
41 config: &OuterConfig,
42 seed: &Array1<f64>,
43 context: &str,
44) -> Result<(), EstimationError> {
45 let Some(bound) = config.initial_inner_seed.as_ref() else {
46 return Ok(());
47 };
48 if !outer_theta_bitwise_eq(&bound.theta, seed) {
49 return Ok(());
50 }
51 match obj.seed_inner_state(&bound.beta)? {
52 SeedOutcome::Installed => log::info!(
53 "[CACHE] beta-warm context={} theta_dim={} beta_dim={} action=installed",
54 context,
55 bound.theta.len(),
56 bound.beta.len(),
57 ),
58 SeedOutcome::NoSlot => log::warn!(
59 "[CACHE] beta-warm context={} theta_dim={} beta_dim={} action=skip \
60 reason=objective_has_no_inner_beta_slot",
61 context,
62 bound.theta.len(),
63 bound.beta.len(),
64 ),
65 SeedOutcome::Incompatible => log::info!(
66 "[CACHE] beta-warm context={} theta_dim={} beta_dim={} action=rho-only \
67 reason=seed_beta_incompatible_with_inner_state",
68 context,
69 bound.theta.len(),
70 bound.beta.len(),
71 ),
72 }
73 Ok(())
74}
75
76/// Hold terminal evidence at full inner-solve fidelity.
77///
78/// Search-time REML evaluations may deliberately cap P-IRLS. A terminal
79/// certificate or final state installation is a different operation: it must
80/// run with the cap lifted, then restore the scheduler's value after the
81/// operation completes. The objective reset performed by the callers clears
82/// any evaluation/P-IRLS entries created under the search state before the
83/// full-fidelity request is made.
84pub(crate) struct TerminalInnerCapGuard<'a> {
85 cap: &'a AtomicUsize,
86 previous: usize,
87}
88
89impl<'a> TerminalInnerCapGuard<'a> {
90 pub(crate) fn lift(feedback: &'a InnerProgressFeedback) -> Self {
91 let cap = feedback.cap.as_ref();
92 let previous = cap.swap(0, Ordering::Relaxed);
93 Self { cap, previous }
94 }
95}
96
97impl Drop for TerminalInnerCapGuard<'_> {
98 fn drop(&mut self) {
99 self.cap.store(self.previous, Ordering::Relaxed);
100 }
101}
102
103/// Configuration for the outer optimization runner.
104#[derive(Clone, Debug)]
105pub(crate) struct OuterConfig {
106 pub(crate) tolerance: f64,
107 /// Optional override for the *relative-cost-decrease* convergence stop,
108 /// decoupled from `tolerance`. `outer_gradient_tolerance` normally derives
109 /// BOTH the absolute projected-gradient floor
110 /// (`max(tolerance, scale·√ε_machine)`)
111 /// AND the relative-cost stop (`rel_cost = tolerance`) from the single
112 /// `tolerance`. That conflation forces a caller who needs a *tight absolute
113 /// floor* (to resolve λ to the genuine REML optimum at large `n`, where the
114 /// floor is `scale·√ε_machine`) to also accept a *tight rel-cost stop*,
115 /// which on a flat REML ridge never trips and grinds the optimizer to `max_iter` —
116 /// dozens of surplus O(D·p³) Laplace-derivative outer iterations (the #1082
117 /// multinomial smooth-by-factor wall-clock blow-up). When `Some(r)`, the
118 /// rel-cost stop uses `r` while the absolute floor keeps using `tolerance`
119 /// via `objective_scale`, so accuracy (absolute floor) and perf (loose
120 /// rel-cost) are selected independently. `None` preserves the legacy coupling
121 /// (`rel_cost = tolerance`) for every existing path byte-for-byte.
122 pub(crate) rel_cost_tolerance: Option<f64>,
123 pub(crate) max_iter: usize,
124 pub(crate) bounds: Option<(Array1<f64>, Array1<f64>)>,
125 pub(crate) seed_config: gam_problem::SeedConfig,
126 pub(crate) rho_bound: f64,
127 pub(crate) heuristic_lambdas: Option<Vec<f64>>,
128 pub(crate) initial_rho: Option<Array1<f64>>,
129 pub(crate) initial_inner_seed: Option<BoundInnerSeed>,
130 pub(crate) fallback_policy: FallbackPolicy,
131 pub(crate) screening_cap: Option<Arc<AtomicUsize>>,
132 pub(crate) screen_initial_rho: bool,
133 /// Outer-aware inner-PIRLS iteration cap (sibling of `screening_cap`).
134 /// When set, the BFGS bridge drives this atomic on every accepted
135 /// gradient eval to coarsen the inner Newton solve at early outer iters
136 /// (when ρ is far from converged) and lift it back to full as
137 /// convergence approaches. Distinct from `screening_cap` in that it
138 /// does NOT suppress cache writes / warm-start updates / KKT
139 /// enforcement; it is purely a budget. See
140 /// `RemlObjectiveState::outer_inner_cap` for dual-cap semantics.
141 pub(crate) outer_inner_cap: Option<InnerProgressFeedback>,
142 pub(crate) operator_initial_trust_radius: Option<f64>,
143 pub(crate) arc_initial_regularization: Option<f64>,
144 /// Optional scale factor for the objective's natural magnitude.
145 /// Used to widen the absolute gradient-norm floor on objectives whose
146 /// gradient lives on a non-unit scale (e.g. Gaussian-identity REML at
147 /// large `n`, whose ∂/∂logλ inherits the O(n) likelihood constant).
148 /// `None` falls back to the bare `tolerance` floor.
149 pub(crate) objective_scale: Option<f64>,
150 /// BFGS line-search infinity-norm cap applied to the leading `rho_dim`
151 /// outer parameters (log-λ axes). Documented natural step for
152 /// `log(lambda)` is ≈ 5 (`e^5 ≈ 148`-fold smoothing-parameter change
153 /// per accepted outer iter — matches typical quasi-Newton direction
154 /// magnitude on flat REML surfaces). Setting this `None` disables the
155 /// rho-axis cap entirely.
156 pub(crate) bfgs_step_cap: Option<f64>,
157 /// BFGS line-search infinity-norm cap applied to the trailing `psi_dim`
158 /// outer parameters (kappa / aniso-log-scale axes). Required because
159 /// the kernel scale axes need much tighter control (`e^1 ≈ 2.7`-fold
160 /// per iter is plenty) — using the rho-axis cap here lets the optimizer
161 /// jump kappa by orders of magnitude per step and oscillate. Setting
162 /// this `None` disables the psi-axis cap.
163 pub(crate) bfgs_step_cap_psi: Option<f64>,
164 /// Optional persistent-cache session. When `Some`, every finite objective
165 /// evaluation is written through to disk (rate-limited, atomic-rename)
166 /// and the best on-disk rho is prepended as a seed at the start of each
167 /// plan attempt. Defaulted off so test-only paths skip filesystem I/O.
168 pub(crate) cache_session: Option<Arc<CacheSession>>,
169 /// Optional mirror cache sessions. Checkpoints and successful finalize
170 /// writes are also written to each of these sessions (different keys,
171 /// shared store). Used for hierarchical broadcast: the current best ρ is
172 /// written to the exact-key (primary) AND the data-independent
173 /// seed-prefix key so the next fit with related structure can warm-start
174 /// from this one, even after an interrupted run.
175 pub(crate) cache_mirror_sessions: Vec<Arc<CacheSession>>,
176 pub(crate) rho_uncertainty_problem_size: crate::rho_uncertainty::RhoUncertaintyProblemSize,
177 /// Converged exact outer Hessian `H(θ̂)` transferred from a prior
178 /// structurally-matching fit via the persistent cache (a warm-start *hit*),
179 /// in the full θ layout. When present and SPD, the BFGS host path seeds its
180 /// iter-0 metric with `InitialMetric::DenseInverseHessian(H⁻¹)` so the first
181 /// outer step is quasi-Newton instead of unscaled steepest descent — the
182 /// dominant LOSO line-search-bracketing cost (each bracketing probe is a
183 /// full inner joint-Newton re-solve). Strictly stronger than the scalar
184 /// `1/‖g₀‖` metric: it carries the full anisotropic curvature, which across
185 /// folds (one held-out point) is nearly identical to this fold's. Never
186 /// changes the converged optimum — BFGS reaches `∇V=0` under any SPD initial
187 /// metric. `None` on every cold-start / no-cache / pre-Hessian-schema path,
188 /// which falls back to the scalar warm metric byte-for-byte.
189 pub(crate) warm_start_outer_hessian: Option<Array2<f64>>,
190 /// Per-ρ-coordinate structural keys, in the objective's NATIVE (formula)
191 /// coordinate order, used to make the outer smoothing-parameter search
192 /// invariant to the order the user wrote the smooth terms / tensor margins
193 /// (#1538/#1539).
194 ///
195 /// When `Some` and the keys induce a non-identity canonical permutation,
196 /// [`run_outer`] reorders the coordinate layout the optimizer sees into a
197 /// stable canonical order (derived purely from the keys, never from the
198 /// native position) before seeding/optimizing, and inverts the permutation
199 /// on the returned ρ / gradient / Hessian so the caller still receives the
200 /// native layout. Seeding, multistart and tie-breaking then all operate on
201 /// the identical canonical layout for every term order, so both orders
202 /// reach the same λ̂ and the same fitted surface. `None` (or an identity
203 /// permutation) leaves the legacy native-order path byte-for-byte unchanged.
204 pub(crate) rho_canonical_keys: Option<Vec<u64>>,
205}
206
207impl Default for OuterConfig {
208 fn default() -> Self {
209 Self {
210 tolerance: 1e-5,
211 rel_cost_tolerance: None,
212 max_iter: 200,
213 bounds: None,
214 seed_config: gam_problem::SeedConfig::default(),
215 rho_bound: 30.0,
216 heuristic_lambdas: None,
217 initial_rho: None,
218 initial_inner_seed: None,
219 fallback_policy: FallbackPolicy::Automatic,
220 screening_cap: None,
221 screen_initial_rho: false,
222 outer_inner_cap: None,
223 operator_initial_trust_radius: None,
224 arc_initial_regularization: None,
225 objective_scale: None,
226 bfgs_step_cap: None,
227 bfgs_step_cap_psi: None,
228 cache_session: None,
229 cache_mirror_sessions: Vec::new(),
230 rho_uncertainty_problem_size:
231 crate::rho_uncertainty::RhoUncertaintyProblemSize::default(),
232 warm_start_outer_hessian: None,
233 rho_canonical_keys: None,
234 }
235 }
236}
237
238// ─── OuterProblem builder ─────────────────────────────────────────────
239//
240// Declarative builder for outer optimization problems. Derives
241// OuterCapability flags from high-level inputs (gradient/hessian
242// availability, psi dimension, EFS eligibility) so call sites never
243// hand-copy capability flags.
244
245/// Declarative outer-problem builder. Produces both the
246/// [`OuterCapability`] (what the objective can provide) and the
247/// [`OuterConfig`] (how the runner should behave) from a small set
248/// of high-level declarations.
249pub struct OuterProblem {
250 n_params: usize,
251 gradient: Derivative,
252 hessian: DeclaredHessianForm,
253 prefer_gradient_only: bool,
254 disable_fixed_point: bool,
255 psi_dim: usize,
256 barrier_config: Option<BarrierConfig>,
257 tolerance: f64,
258 rel_cost_tolerance: Option<f64>,
259 max_iter: usize,
260 bounds: Option<(Array1<f64>, Array1<f64>)>,
261 rho_bound: f64,
262 seed_config: gam_problem::SeedConfig,
263 heuristic_lambdas: Option<Vec<f64>>,
264 initial_rho: Option<Array1<f64>>,
265 fallback_policy: FallbackPolicy,
266 screening_cap: Option<Arc<AtomicUsize>>,
267 screen_initial_rho: bool,
268 outer_inner_cap: Option<InnerProgressFeedback>,
269 operator_initial_trust_radius: Option<f64>,
270 arc_initial_regularization: Option<f64>,
271 objective_scale: Option<f64>,
272 bfgs_step_cap: Option<f64>,
273 bfgs_step_cap_psi: Option<f64>,
274 cache_session: Option<Arc<CacheSession>>,
275 cache_mirror_sessions: Vec<Arc<CacheSession>>,
276 rho_uncertainty_problem_size: crate::rho_uncertainty::RhoUncertaintyProblemSize,
277 rho_canonical_keys: Option<Vec<u64>>,
278}
279
280impl OuterProblem {
281 pub fn new(n_params: usize) -> Self {
282 Self {
283 n_params,
284 gradient: Derivative::Unavailable,
285 hessian: DeclaredHessianForm::Unavailable,
286 // Closed-form/test objectives preserve explicit ARC availability;
287 // production family-ladder entry points opt into #2359's
288 // optimize-3/certify-4 protocol with
289 // `with_prefer_gradient_only(true)`.
290 prefer_gradient_only: false,
291 disable_fixed_point: false,
292 psi_dim: 0,
293 barrier_config: None,
294 tolerance: 1e-5,
295 rel_cost_tolerance: None,
296 max_iter: 200,
297 bounds: None,
298 rho_bound: 30.0,
299 seed_config: gam_problem::SeedConfig::default(),
300 heuristic_lambdas: None,
301 initial_rho: None,
302 fallback_policy: FallbackPolicy::Automatic,
303 screening_cap: None,
304 screen_initial_rho: false,
305 outer_inner_cap: None,
306 operator_initial_trust_radius: None,
307 arc_initial_regularization: None,
308 objective_scale: None,
309 bfgs_step_cap: None,
310 bfgs_step_cap_psi: None,
311 cache_session: None,
312 cache_mirror_sessions: Vec::new(),
313 rho_uncertainty_problem_size:
314 crate::rho_uncertainty::RhoUncertaintyProblemSize::default(),
315 rho_canonical_keys: None,
316 }
317 }
318
319 /// Supply per-ρ-coordinate structural keys (native/formula order) so the
320 /// outer search is canonicalized to be invariant to the order the smooth
321 /// terms / tensor margins were written (#1538/#1539). See
322 /// [`OuterConfig::rho_canonical_keys`].
323 pub fn with_rho_canonical_keys(mut self, keys: Option<Vec<u64>>) -> Self {
324 self.rho_canonical_keys = keys;
325 self
326 }
327
328 pub fn with_gradient(mut self, d: Derivative) -> Self {
329 self.gradient = d;
330 self
331 }
332 pub fn with_hessian(mut self, form: DeclaredHessianForm) -> Self {
333 self.hessian = form;
334 self
335 }
336 /// Choose whether analytic Hessian work is reserved for terminal
337 /// certification (`true`, the generic derivative-ladder protocol) or may
338 /// also be consumed by the search (`false`, for closed-form objectives).
339 pub fn with_prefer_gradient_only(mut self, prefer_gradient_only: bool) -> Self {
340 self.prefer_gradient_only = prefer_gradient_only;
341 self
342 }
343 /// Forbid the planner from selecting EFS/HybridEfs, even when the
344 /// objective implements `eval_efs()` and the coordinate structure would
345 /// otherwise make pure/hybrid EFS eligible.
346 ///
347 /// Callers use this for families where the Wood-Fasiolo structural
348 /// property is known not to hold (e.g. GAMLSS/location-scale with
349 /// β-dependent joint Hessian), so EFS would stagnate and burn budget
350 /// before the automatic cascade falls back to gradient-based BFGS.
351 pub fn with_disable_fixed_point(mut self, disable: bool) -> Self {
352 self.disable_fixed_point = disable;
353 self
354 }
355 // MEASURE-JET ψ REGISTRATION: the engine below is already complete for a
356 // 3-coordinate measure-jet ψ group (s, α, ln τ) — `psi_dim` is generic,
357 // `with_bounds` carries the s ∈ (0, 2) box (the same convention matern κ
358 // uses for its log-κ window; no logistic reparameterization exists or is
359 // needed in-house), `with_bfgs_step_cap_psi` caps per-iteration ψ moves,
360 // and `DirectionalHyperParam::new_compact` (solver/reml/mod.rs) carries
361 // penalty-only first/second/cross jets with `is_penalty_like`
362 // auto-derived from the identically-zero design drift (∂X/∂ψ ≡ 0).
363 // Every remaining registration arm is formula-layer dispatch in
364 // src/terms/smooth.rs (eligibility in
365 // `spatial_term_supports_hyper_optimization`, dims in
366 // `spatial_dims_per_term`, seed/bounds/write-back on
367 // `SpatialLogKappaCoords`, the per-trial rebuild in
368 // `apply_log_kappa_to_term`, and the derivative bundle in
369 // `try_build_spatial_term_log_kappa_derivative`, which currently returns
370 // `Ok(None)` for `SmoothBasisSpec::MeasureJet`) plus the
371 // `build_measure_jet_basis_psi_derivatives` producer in
372 // src/terms/basis/measure_jet_smooth.rs; both are owned by the
373 // measure-jet terms actor. Registration stays gated on those arms — do
374 // NOT add measure-jet-specific branches to this engine.
375 pub fn with_psi_dim(mut self, dim: usize) -> Self {
376 self.psi_dim = dim;
377 self
378 }
379 pub fn with_barrier(mut self, cfg: Option<BarrierConfig>) -> Self {
380 self.barrier_config = cfg;
381 self
382 }
383 pub fn with_tolerance(mut self, tol: f64) -> Self {
384 self.tolerance = tol;
385 self
386 }
387 pub fn with_max_iter(mut self, n: usize) -> Self {
388 self.max_iter = n;
389 self
390 }
391 pub fn with_bounds(mut self, lo: Array1<f64>, hi: Array1<f64>) -> Self {
392 self.bounds = Some((lo, hi));
393 self
394 }
395 pub fn with_rho_bound(mut self, b: f64) -> Self {
396 self.rho_bound = b;
397 self
398 }
399 pub fn with_seed_config(mut self, sc: gam_problem::SeedConfig) -> Self {
400 self.seed_config = sc;
401 self
402 }
403 pub fn with_heuristic_lambdas(mut self, h: Vec<f64>) -> Self {
404 self.heuristic_lambdas = Some(h);
405 self
406 }
407 pub fn with_initial_rho(mut self, rho: Array1<f64>) -> Self {
408 self.initial_rho = Some(rho);
409 self
410 }
411 pub fn with_screening_cap(mut self, screening_cap: Arc<AtomicUsize>) -> Self {
412 self.screening_cap = Some(screening_cap);
413 self
414 }
415 /// Allow seed screening to rank the explicit initial rho against generated
416 /// candidates even when the effective seed budget is one. The default keeps
417 /// a user-provided initial point authoritative and avoids a separate
418 /// screening pass.
419 pub fn with_screen_initial_rho(mut self, screen_initial_rho: bool) -> Self {
420 self.screen_initial_rho = screen_initial_rho;
421 self
422 }
423 /// Wire the bidirectional inner-PIRLS feedback channel.
424 ///
425 /// The outer bridge writes a coarsened iteration cap into
426 /// `feedback.cap` on every accepted gradient/Hessian eval; the inner
427 /// solver writes back into `feedback.last_iters` /
428 /// `feedback.last_converged` after each non-screening solve so the
429 /// next outer iter's schedule can adapt to the inner solver's
430 /// actual convergence behavior. Typical caller passes
431 /// `InnerProgressFeedback {
432 /// cap: Arc::clone(&reml_state.outer_inner_cap),
433 /// last_iters: Arc::clone(&reml_state.last_inner_iters),
434 /// last_converged: Arc::clone(&reml_state.last_inner_converged),
435 /// }` so the inner and outer observe the same atomics.
436 pub fn with_outer_inner_cap(mut self, feedback: InnerProgressFeedback) -> Self {
437 self.outer_inner_cap = Some(feedback);
438 self
439 }
440
441 /// Wire a one-shot "re-evaluate the inner solve COLD" signal that the outer
442 /// cost-stall guard raises when it grants a STUCK-stall escape (#2349).
443 ///
444 /// A profiled objective whose inner solve is warm-started along the outer
445 /// trajectory can carry value HYSTERESIS on a near-flat inner ridge — the
446 /// multinomial simplex-boundary regime where the softmax Fisher weight
447 /// `diag(p) − ppᵀ` collapses is the motivating case: two warm starts
448 /// converge to different ridge points whose Laplace `½log|H(β)|`, hence the
449 /// profiled objective, differ by more than the outer descent resolution, so
450 /// the optimizer's step-acceptance cannot separate real descent from that
451 /// hysteresis and grinds to `max_iter` at a non-stationary point. Uncapping
452 /// the inner cycle budget does not cure it (a fully converged warm solve
453 /// still lands on the warm-biased ridge point); the objective must re-solve
454 /// COLD to see a consistent surface.
455 ///
456 /// The caller shares this `Arc<AtomicBool>` with its objective closure and
457 /// consults it there, re-solving the inner problem from a canonical seed
458 /// (dropping the warm cache) whenever the flag is raised. The signal rides
459 /// the internal inner-cap feedback channel, but its `cap` slot is a private
460 /// throwaway so wiring the signal never perturbs the caller's own inner-cap
461 /// scheduling (custom families hold their real inner cap separately).
462 /// Objectives that do not warm-start, or never near-separate, simply never
463 /// observe the flag raised.
464 pub fn with_stuck_stall_cold_reeval_signal(self, signal: Arc<AtomicBool>) -> Self {
465 self.with_outer_inner_cap(InnerProgressFeedback {
466 cap: Arc::new(AtomicUsize::new(0)),
467 accepted_iter: Arc::new(AtomicUsize::new(0)),
468 // `last_iters == 0` ⇒ `snapshot()` returns `None` ⇒ no cap-schedule
469 // adaptation is derived from this dummy; `last_converged == true`
470 // matches the `None` default of `inner_solve_converged`, so
471 // terminal-fidelity gating is byte-for-byte unchanged.
472 last_iters: Arc::new(AtomicUsize::new(0)),
473 last_converged: Arc::new(AtomicBool::new(true)),
474 ift_residual: Arc::new(AtomicU64::new(f64::NAN.to_bits())),
475 accept_rho: Arc::new(AtomicU64::new(f64::NAN.to_bits())),
476 force_cold: signal,
477 })
478 }
479 pub fn with_operator_initial_trust_radius(mut self, radius: Option<f64>) -> Self {
480 self.operator_initial_trust_radius = sanitized_operator_trust_restart_radius(radius);
481 self
482 }
483
484 /// Override the ARC initial cubic-regularization parameter sigma
485 /// (default in `opt`: 1.0). Smaller sigma → less cubic penalty on the
486 /// first step → larger first move on benign objectives. The matrix-
487 /// free Newton-TR analog is `with_operator_initial_trust_radius`.
488 ///
489 /// Used by Gaussian-identity REML at large-scale n: the objective is
490 /// quadratic-like in log-λ near the optimum (sigma is the right
491 /// scale), and log-λ moves of 2–4 units in the early iters
492 /// otherwise burn 4–8 iters of trust-region expansion before the
493 /// model trusts the analytic Hessian.
494 pub fn with_arc_initial_regularization(mut self, sigma: Option<f64>) -> Self {
495 self.arc_initial_regularization = sigma.filter(|v| v.is_finite() && *v > 0.0);
496 self
497 }
498
499 /// Set the objective's natural magnitude scale, used to derive an
500 /// `n`-aware absolute gradient-norm floor. When set to `Some(s)`,
501 /// the runner uses `abs_floor = max(tol, s * √ε_machine)` for the
502 /// projected-gradient convergence check.
503 ///
504 /// Rationale: a fixed `abs = tol` (e.g. 1e-6) is appropriate when the
505 /// objective and its gradient live on a unit scale, but Gaussian-
506 /// identity REML carries an O(n) likelihood constant that flows into
507 /// ∂/∂logλ. At large-scale n the floor becomes binding even when the
508 /// relative-from-seed component (`rel_initial_grad * ‖g0‖`) declared
509 /// convergence iters earlier — chasing sub-ULP changes in log-λ at
510 /// the cost of repeated k²·n·p² analytic-Hessian assemblies.
511 pub fn with_objective_scale(mut self, scale: Option<f64>) -> Self {
512 self.objective_scale = scale.filter(|v| v.is_finite() && *v > 0.0);
513 self
514 }
515
516 /// Decouple the *relative-cost-decrease* convergence stop from the
517 /// absolute projected-gradient floor. By default both are derived from the
518 /// single `with_tolerance` value (`abs = max(tol, scale·√ε_machine)`,
519 /// `rel_cost = tol`). Supplying `Some(r)` here makes the rel-cost stop use
520 /// `r` while the absolute floor keeps using `tolerance` (so a caller can
521 /// keep a tight absolute floor for accuracy at large `n` AND a loose
522 /// rel-cost stop for perf on a flat REML ridge — see #1082). `None` keeps
523 /// the legacy coupling.
524 pub fn with_rel_cost_tolerance(mut self, rel_cost: Option<f64>) -> Self {
525 self.rel_cost_tolerance = rel_cost.filter(|v| v.is_finite() && *v > 0.0);
526 self
527 }
528
529 /// Cap the infinity-norm displacement of BFGS cost-only line-search probes
530 /// on the **rho axes** (the first `n_params - psi_dim` outer parameters,
531 /// = log-λ). Also scales the initial inverse metric so the first trial
532 /// direction respects the same local budget coordinate-wise. Documented
533 /// natural step on log-λ is ≈ 5; tighter values throttle BFGS and starve
534 /// convergence on flat REML valleys.
535 pub fn with_bfgs_step_cap(mut self, cap: Option<f64>) -> Self {
536 self.bfgs_step_cap = cap.filter(|v| v.is_finite() && *v > 0.0);
537 self
538 }
539
540 /// Cap the infinity-norm displacement of BFGS cost-only line-search probes
541 /// on the **psi axes** (the trailing `psi_dim` outer parameters, = kappa
542 /// or anisotropic log-scales). Mirrors [`Self::with_bfgs_step_cap`] but
543 /// scoped to kernel-scale parameters whose natural step is much smaller
544 /// than log-λ (≈ ln 2 per iter keeps kappa from oscillating). Without
545 /// this split, a uniform rho-scale cap lets psi explode while a uniform
546 /// psi-scale cap throttles rho — both fail the survival-marginal-slope
547 /// path at large scale, where rho needs |d|≈5 while psi wants |d|≤1.
548 pub fn with_bfgs_step_cap_psi(mut self, cap: Option<f64>) -> Self {
549 self.bfgs_step_cap_psi = cap.filter(|v| v.is_finite() && *v > 0.0);
550 self
551 }
552
553 pub fn with_cache_session(mut self, session: Arc<CacheSession>) -> Self {
554 self.cache_session = Some(session);
555 self
556 }
557
558 /// Attach mirror cache sessions that receive a broadcast copy of
559 /// the final-result finalize write. See
560 /// [`OuterConfig::cache_mirror_sessions`].
561 pub fn with_cache_mirror_sessions(mut self, sessions: Vec<Arc<CacheSession>>) -> Self {
562 self.cache_mirror_sessions = sessions;
563 self
564 }
565
566 pub fn with_problem_size(mut self, n_obs: usize, p_coefficients: usize) -> Self {
567 self.rho_uncertainty_problem_size = crate::rho_uncertainty::RhoUncertaintyProblemSize {
568 n_obs: Some(n_obs),
569 p_coefficients: Some(p_coefficients),
570 };
571 self
572 }
573
574 /// Override the fallback policy. Default is [`FallbackPolicy::Automatic`].
575 ///
576 /// Set [`FallbackPolicy::Disabled`] when the caller requires the primary
577 /// plan to stand on its own. Exact-Hessian objectives use this to ensure
578 /// failures surface on the analytic geometry instead of being reinterpreted
579 /// by a different optimizer class.
580 pub fn with_fallback_policy(mut self, policy: FallbackPolicy) -> Self {
581 self.fallback_policy = policy;
582 self
583 }
584
585 /// Derive the capability flags from the builder state.
586 /// `fixed_point_available` is set to `false` here; `build_objective`
587 /// overrides it based on whether an EFS closure is actually provided.
588 fn capability(&self) -> OuterCapability {
589 OuterCapability {
590 gradient: self.gradient,
591 hessian: self.hessian,
592 prefer_gradient_only: self.prefer_gradient_only,
593 disable_fixed_point: self.disable_fixed_point,
594 n_params: self.n_params,
595 psi_dim: self.psi_dim,
596 fixed_point_available: false,
597 barrier_config: self.barrier_config.clone(),
598 }
599 }
600
601 /// Derive the runner configuration from the builder state.
602 pub(crate) fn config(&self) -> OuterConfig {
603 OuterConfig {
604 tolerance: self.tolerance,
605 rel_cost_tolerance: self.rel_cost_tolerance,
606 max_iter: self.max_iter,
607 bounds: self.bounds.clone(),
608 seed_config: self.seed_config,
609 rho_bound: self.rho_bound,
610 heuristic_lambdas: self.heuristic_lambdas.clone(),
611 initial_rho: self.initial_rho.clone(),
612 initial_inner_seed: None,
613 fallback_policy: self.fallback_policy,
614 screening_cap: self.screening_cap.clone(),
615 screen_initial_rho: self.screen_initial_rho,
616 outer_inner_cap: self.outer_inner_cap.clone(),
617 operator_initial_trust_radius: self.operator_initial_trust_radius,
618 arc_initial_regularization: self.arc_initial_regularization,
619 objective_scale: self.objective_scale,
620 bfgs_step_cap: self.bfgs_step_cap,
621 bfgs_step_cap_psi: self.bfgs_step_cap_psi,
622 cache_session: self.cache_session.clone(),
623 cache_mirror_sessions: self.cache_mirror_sessions.clone(),
624 rho_uncertainty_problem_size: self.rho_uncertainty_problem_size,
625 // Populated only by the persistent-cache resume path in `run` after
626 // a warm-start hit decodes a converged outer Hessian.
627 warm_start_outer_hessian: None,
628 rho_canonical_keys: self.rho_canonical_keys.clone(),
629 }
630 }
631
632 /// Construct a [`ClosureObjective`] with capability flags derived from the
633 /// builder state **and** the closures actually provided.
634 ///
635 /// `fixed_point_available` is set to `true` when `efs_fn` is `Some`,
636 /// regardless of whether `.with_efs()` was called. This is the canonical
637 /// way to create production objectives — it eliminates the drift risk of
638 /// manually entering capability flags.
639 pub fn build_objective<S, Fc, Fe, Fr, Fefs>(
640 &self,
641 state: S,
642 cost_fn: Fc,
643 eval_fn: Fe,
644 reset_fn: Option<Fr>,
645 efs_fn: Option<Fefs>,
646 ) -> ClosureObjective<S, Fc, Fe, Fr, Fefs>
647 where
648 Fc: FnMut(&mut S, &Array1<f64>) -> Result<f64, EstimationError>,
649 Fe: FnMut(&mut S, &Array1<f64>) -> Result<OuterEval, EstimationError>,
650 Fr: FnMut(&mut S),
651 Fefs: FnMut(&mut S, &Array1<f64>) -> Result<EfsEval, EstimationError>,
652 {
653 let mut cap = self.capability();
654 // Derive fixed_point_available from whether the caller actually
655 // provided an EFS hook, rather than relying on manual flags.
656 cap.fixed_point_available = efs_fn.is_some();
657 ClosureObjective {
658 state,
659 cap,
660 cost_fn,
661 eval_fn,
662 eval_order_fn: None,
663 reset_fn,
664 efs_fn,
665 fixed_point_certificate_fn: None,
666 exact_polish_fn: None,
667 rail_face_limit_fn: None,
668 screening_proxy_fn: None::<fn(&mut S, &Array1<f64>) -> Result<f64, EstimationError>>,
669 seed_fn: None::<fn(&mut S, &Array1<f64>) -> Result<SeedOutcome, EstimationError>>,
670 terminal_eval_order: None,
671 }
672 }
673
674 /// Construct a [`ClosureObjective`] with an order-aware evaluation hook.
675 ///
676 /// This lets the runner request first-order vs second-order work based on
677 /// the active outer plan while preserving the legacy eager `eval_fn`.
678 pub fn build_objective_with_eval_order<S, Fc, Fe, Feo, Fr, Fefs>(
679 &self,
680 state: S,
681 cost_fn: Fc,
682 eval_fn: Fe,
683 eval_order_fn: Feo,
684 reset_fn: Option<Fr>,
685 efs_fn: Option<Fefs>,
686 ) -> ClosureObjective<S, Fc, Fe, Fr, Fefs, Feo>
687 where
688 Fc: FnMut(&mut S, &Array1<f64>) -> Result<f64, EstimationError>,
689 Fe: FnMut(&mut S, &Array1<f64>) -> Result<OuterEval, EstimationError>,
690 Feo: FnMut(&mut S, &Array1<f64>, OuterEvalOrder) -> Result<OuterEval, EstimationError>,
691 Fr: FnMut(&mut S),
692 Fefs: FnMut(&mut S, &Array1<f64>) -> Result<EfsEval, EstimationError>,
693 {
694 let mut cap = self.capability();
695 cap.fixed_point_available = efs_fn.is_some();
696 ClosureObjective {
697 state,
698 cap,
699 cost_fn,
700 eval_fn,
701 eval_order_fn: Some(eval_order_fn),
702 reset_fn,
703 efs_fn,
704 fixed_point_certificate_fn: None,
705 exact_polish_fn: None,
706 rail_face_limit_fn: None,
707 screening_proxy_fn: None::<fn(&mut S, &Array1<f64>) -> Result<f64, EstimationError>>,
708 seed_fn: None::<fn(&mut S, &Array1<f64>) -> Result<SeedOutcome, EstimationError>>,
709 terminal_eval_order: None,
710 }
711 }
712
713 /// Construct a [`ClosureObjective`] with both an order-aware evaluation
714 /// hook and a custom seed-screening ranking proxy. The proxy fires only
715 /// when the cascade in `rank_seeds_with_screening` calls it; outside
716 /// screening the regular cost path is unaffected.
717 pub fn build_objective_with_screening_proxy<S, Fc, Fe, Feo, Fr, Fefs, Fsp>(
718 &self,
719 state: S,
720 cost_fn: Fc,
721 eval_fn: Fe,
722 eval_order_fn: Feo,
723 reset_fn: Option<Fr>,
724 efs_fn: Option<Fefs>,
725 screening_proxy_fn: Fsp,
726 ) -> ClosureObjective<S, Fc, Fe, Fr, Fefs, Feo, Fsp>
727 where
728 Fc: FnMut(&mut S, &Array1<f64>) -> Result<f64, EstimationError>,
729 Fe: FnMut(&mut S, &Array1<f64>) -> Result<OuterEval, EstimationError>,
730 Feo: FnMut(&mut S, &Array1<f64>, OuterEvalOrder) -> Result<OuterEval, EstimationError>,
731 Fr: FnMut(&mut S),
732 Fefs: FnMut(&mut S, &Array1<f64>) -> Result<EfsEval, EstimationError>,
733 Fsp: FnMut(&mut S, &Array1<f64>) -> Result<f64, EstimationError>,
734 {
735 let mut cap = self.capability();
736 cap.fixed_point_available = efs_fn.is_some();
737 ClosureObjective {
738 state,
739 cap,
740 cost_fn,
741 eval_fn,
742 eval_order_fn: Some(eval_order_fn),
743 reset_fn,
744 efs_fn,
745 fixed_point_certificate_fn: None,
746 exact_polish_fn: None,
747 rail_face_limit_fn: None,
748 screening_proxy_fn: Some(screening_proxy_fn),
749 seed_fn: None::<fn(&mut S, &Array1<f64>) -> Result<SeedOutcome, EstimationError>>,
750 terminal_eval_order: None,
751 }
752 }
753
754 /// Run the outer optimization with a given objective.
755 pub fn run(
756 &self,
757 obj: &mut dyn OuterObjective,
758 context: &str,
759 ) -> Result<OuterResult, EstimationError> {
760 let mut config = self.config();
761 let objective_lower = obj.outer_domain_lower_bound()?;
762 let objective_upper = obj.outer_domain_upper_bound()?;
763 if objective_lower.is_some() || objective_upper.is_some() {
764 install_objective_domain(&mut config, self.n_params, objective_lower, objective_upper)?;
765 }
766 let Some(session) = config.cache_session.clone() else {
767 return run_outer(obj, &config, context);
768 };
769 let key_hex = session.key().to_hex();
770 let short_key = &key_hex[..8.min(key_hex.len())];
771 let mut had_hit = false;
772 let mut cached_inner_seed: Option<BoundInnerSeed> = None;
773 if let Some(loaded) = session.try_load_with_source() {
774 match classify_cache_entry_for_outer(&loaded, self.n_params) {
775 CacheSeedDecision::ExactFinal {
776 rho,
777 beta,
778 iterations,
779 prior_obj_display,
780 } => {
781 log::info!(
782 "[CACHE] final-hit key={}.. context={} rho_dim={} prior_obj={:.6e} iter={} action=resume-and-recertify",
783 short_key,
784 context,
785 rho.len(),
786 prior_obj_display,
787 iterations,
788 );
789 config.initial_rho = Some(rho.clone());
790 config.screen_initial_rho = false;
791 if !beta.is_empty() {
792 cached_inner_seed = Some(BoundInnerSeed {
793 theta: rho,
794 beta: Array1::from_vec(beta),
795 });
796 }
797 had_hit = true;
798 }
799 CacheSeedDecision::Seed {
800 rho,
801 beta,
802 hessian,
803 prior_obj_display,
804 iteration,
805 } => {
806 let beta_len = beta.len();
807 let beta_arr = if beta.is_empty() {
808 None
809 } else {
810 Some(Array1::from_vec(beta))
811 };
812 // Adopt the transferred converged outer Hessian only when it
813 // matches this fit's full-θ dimension; a dimension drift
814 // (structural change the cache key did not capture) falls
815 // back to the scalar warm metric in run_plan.
816 config.warm_start_outer_hessian = if self.hessian.is_analytic() {
817 hessian.and_then(|(dim, flat)| {
818 if dim == self.n_params && flat.len() == dim * dim {
819 Array2::from_shape_vec((dim, dim), flat).ok()
820 } else {
821 None
822 }
823 })
824 } else {
825 None
826 };
827 if config
828 .initial_rho
829 .as_ref()
830 .is_none_or(|initial| initial != rho)
831 {
832 log::info!(
833 "[CACHE] hit key={}.. context={} rho_dim={} beta_dim={} prior_obj={:.6e} iter={}",
834 short_key,
835 context,
836 rho.len(),
837 beta_len,
838 prior_obj_display,
839 iteration,
840 );
841 config.initial_rho = Some(rho.clone());
842 config.screen_initial_rho = false;
843 had_hit = true;
844 } else {
845 log::info!(
846 "[CACHE] hit key={}.. context={} rho_dim={} beta_dim={} already-aligned prior_obj={:.6e}",
847 short_key,
848 context,
849 rho.len(),
850 beta_len,
851 prior_obj_display,
852 );
853 had_hit = true;
854 }
855 if let Some(beta) = beta_arr {
856 cached_inner_seed = Some(BoundInnerSeed { theta: rho, beta });
857 }
858 }
859 CacheSeedDecision::Discard {
860 reason: "payload-shape-mismatch",
861 ..
862 } => {
863 log::info!(
864 "[CACHE] skip key={}.. context={} reason=payload-shape-mismatch n_params={}",
865 short_key,
866 context,
867 self.n_params,
868 );
869 }
870 CacheSeedDecision::Discard {
871 reason,
872 prior_obj_display,
873 all_rho_finite,
874 } => {
875 log::info!(
876 "[CACHE] skip key={}.. context={} reason={} prior_obj={:.6e} all_rho_finite={}",
877 short_key,
878 context,
879 reason,
880 prior_obj_display,
881 all_rho_finite.unwrap_or(false),
882 );
883 }
884 }
885 } else {
886 log::info!(
887 "[CACHE] miss key={}.. context={} reason=fresh-fingerprint n_params={}",
888 short_key,
889 context,
890 self.n_params,
891 );
892 }
893 // Preserve the ownership relation between a cached coefficient vector
894 // and the exact outer coordinate that produced it. The runner installs
895 // this seed only after resetting for that bitwise-matching candidate;
896 // it is never replayed at another generated seed.
897 config.initial_inner_seed = cached_inner_seed;
898 let mut checkpointing = CheckpointingObjective::new(
899 obj,
900 Arc::clone(&session),
901 config.cache_mirror_sessions.clone(),
902 );
903 let result = run_outer(&mut checkpointing, &config, context);
904 // Pull the most-recent inner β surfaced by the inner solver so the
905 // finalize write encodes the (ρ, β) pair the BFGS optimum was
906 // actually fitted at, not a ρ-only seed that resumes at cold β.
907 let final_beta = checkpointing.last_inner_beta();
908 if let Ok(result) = result.as_ref()
909 && result.final_value.is_finite()
910 && result.converged
911 && result
912 .criterion_certificate
913 .as_ref()
914 .is_some_and(OuterCriterionCertificate::certifies)
915 && let Some(bytes) = encode_iterate(
916 &result.rho,
917 final_beta.as_ref(),
918 result.final_hessian.as_ref(),
919 result.final_value,
920 result.iterations as u64,
921 )
922 {
923 let saved = session.finalize(
924 &bytes,
925 Some(result.final_value),
926 Some(result.iterations as u64),
927 );
928 if saved {
929 log::info!(
930 "[CACHE] save key={}.. context={} final_obj={:.6e} iter={} resumed={}",
931 short_key,
932 context,
933 result.final_value,
934 result.iterations,
935 had_hit,
936 );
937 }
938 // Broadcast finalize to mirror keys. The seed-prefix mirror
939 // exists so future fits with related-but-not-identical
940 // structure can warm-start from this run via the dispatcher's
941 // prefix lookup.
942 for mirror in &config.cache_mirror_sessions {
943 let mirror_saved = mirror.finalize(
944 &bytes,
945 Some(result.final_value),
946 Some(result.iterations as u64),
947 );
948 if mirror_saved {
949 let mirror_hex = mirror.key().to_hex();
950 log::info!(
951 "[CACHE] save key={}.. context={} mirror final_obj={:.6e} iter={}",
952 &mirror_hex[..8.min(mirror_hex.len())],
953 context,
954 result.final_value,
955 result.iterations,
956 );
957 }
958 }
959 }
960 result
961 }
962
963 /// Run the outer optimization and return an unforgeable certified-result
964 /// carrier. Callers that only need checkpoints or diagnostics should use
965 /// [`Self::run`]; fit assembly after an optimized outer coordinate must use
966 /// this boundary so a caller-constructed [`OuterResult`] cannot mint
967 /// convergence provenance.
968 pub fn run_certified(
969 &self,
970 obj: &mut dyn OuterObjective,
971 context: &str,
972 ) -> Result<CertifiedOuterResult, EstimationError> {
973 let result = self.run(obj, context)?;
974 CertifiedOuterResult::from_optimizer_result(result).map_err(|reason| {
975 EstimationError::RemlOptimizationFailed(format!(
976 "{context}: outer result failed certified-fit validation: {reason}"
977 ))
978 })
979 }
980}
981
982/// Internal outcome of one planned solver/multistart attempt.
983///
984/// Exhausted checkpoints carry resumable work only. They never pass through
985/// finalization, cache promotion, uncertainty diagnostics, or fitted-model
986/// construction.
987pub(crate) enum PlanRunOutcome {
988 Converged(OuterResult),
989 Exhausted(OuterResult),
990}
991
992/// Which certificate concluded a CONVERGED outer run (#2235/#2241).
993///
994/// `OuterResult.converged == true` bundles genuinely different endings, each
995/// with its own certificate. Distinguishing them is pure evidence for the
996/// caller's termination report — every variant is a converged fit. There is
997/// deliberately no "budget/freeze" variant: exhaustion is a typed error
998/// carrying the resume checkpoint, never a minted fit (SPEC 20; the #2235
999/// forcing-function redesign deleted the freeze lanes).
1000#[derive(Clone, Copy, Debug, PartialEq)]
1001pub enum OuterConvergedVia {
1002 /// The bound-projected analytic gradient at the returned point cleared the
1003 /// solver's absolute/score-scaled stationarity tolerance.
1004 GradientStationary,
1005 /// Criterion-flat certificate (#2241/#2253): the criterion stalled over the
1006 /// cost-stall window and the residual projected gradient sits inside the
1007 /// flat certificate band — the score-relative stationarity bound
1008 /// (`flat_valley_converged_grad_bound`), the probe-noise-floor bound
1009 /// measured from the stall window's own value scatter, and/or the
1010 /// curvature-scaled Newton-decrement bound (`newton_predicted_decrease`),
1011 /// under which a residual above the gradient-magnitude bands is still
1012 /// stationary when the second-order-predicted improvement `½·gᵀH⁻¹g` is below
1013 /// the outer objective tolerance. `certificate_bound` is the operative
1014 /// (widened) bound the residual actually cleared.
1015 CriterionFlat {
1016 residual_grad_norm: f64,
1017 certificate_bound: f64,
1018 },
1019 /// Every optimized coordinate carried an explicit analytic fixed-point
1020 /// equation and the KKT-projected residual cleared the solver tolerance.
1021 FixedPointStationary {
1022 projected_residual_inf_norm: f64,
1023 certificate_bound: f64,
1024 },
1025 /// Fellner–Schall model-state fixed point (#2235 verdict 2): two
1026 /// consecutive outer evaluations restored the same banked incumbent, so a
1027 /// further outer update provably does not change the fitted state. The
1028 /// analytic first-order certificate is still taken at the incumbent.
1029 RecurrentIncumbent { consecutive_restores: usize },
1030 /// Stationary-at-asymptote (#2348 Inc 1 / #2299 layer 3): the interior
1031 /// (non-railed) coordinates are gradient-stationary, and every coordinate
1032 /// railed at the infinite-/zero-smoothing box bound is certified on a
1033 /// confirmed exponential tail (Thm 2.1) whose fitted model has reached the
1034 /// rail limit to within the estimand tolerance. The typed rail supersedes
1035 /// the generic gradient/criterion-flat verdict for a railed optimum.
1036 AsymptoteStationary { rails: usize },
1037}
1038
1039impl OuterConvergedVia {
1040 /// Stable wire name for termination reports; the enum owns the vocabulary
1041 /// so bindings marshal instead of mapping.
1042 pub fn as_str(&self) -> &'static str {
1043 match self {
1044 Self::GradientStationary => "converged_stationary",
1045 Self::CriterionFlat { .. } => "converged_criterion_flat",
1046 Self::FixedPointStationary { .. } => "converged_fixed_point",
1047 Self::RecurrentIncumbent { .. } => "incumbent_stationary",
1048 Self::AsymptoteStationary { .. } => "converged_asymptote_rail",
1049 }
1050 }
1051}
1052
1053/// Result of a completed outer optimization.
1054#[derive(Clone, Debug)]
1055pub struct OuterResult {
1056 /// Optimized log-smoothing parameters.
1057 pub rho: Array1<f64>,
1058 /// Final objective value.
1059 pub final_value: f64,
1060 /// Total outer iterations across all solver restarts.
1061 pub iterations: usize,
1062 /// Final gradient norm, when the solver computed an actual gradient.
1063 pub final_grad_norm: Option<f64>,
1064 /// Final gradient when the solver is gradient-based.
1065 pub final_gradient: Option<Array1<f64>>,
1066 /// Final Hessian when the solver tracks one.
1067 pub final_hessian: Option<Array2<f64>>,
1068 /// Whether the optimizer converged to a stationary point.
1069 pub converged: bool,
1070 /// Which plan was actually used (may differ from initial if fallback fired).
1071 pub plan_used: OuterPlan,
1072 /// Final trust radius for the internal operator trust-region solver.
1073 ///
1074 /// A non-converged operator-ARC attempt may be restarted by the budget
1075 /// ladder. Restarting only from the last θ but resetting the trust radius
1076 /// is not a warm start: it replays the same rejected large trial steps.
1077 /// Carry this globalization state so retries resume from the scale the
1078 /// previous attempt already learned.
1079 pub operator_trust_radius: Option<f64>,
1080 /// Why the internal operator trust-region solver stopped.
1081 pub operator_stop_reason: Option<OperatorTrustRegionStopReason>,
1082 /// First-order optimality self-audit at the returned point (#934).
1083 ///
1084 /// `None` when no analytic gradient was measured at termination
1085 /// (gradient-free solvers, cache-hit short-circuits, per-atom EFS) or
1086 /// when an audit probe failed to evaluate. Populated once by
1087 /// [`run_outer`] after the solver ladder returns, outside all hot loops.
1088 pub criterion_certificate: Option<OuterCriterionCertificate>,
1089 /// Which certificate concluded a converged run (#2235/#2241). Stamped by
1090 /// [`certify_outer_optimality`] on every certified result (the
1091 /// Fellner–Schall lane pre-stamps `RecurrentIncumbent`, which certification
1092 /// preserves); `None` exactly on non-converged resume checkpoints.
1093 pub converged_via: Option<OuterConvergedVia>,
1094 /// Probe-noise-floor gradient bound measured by the cost-stall guard at a
1095 /// halted stall (#2241): σ̂/Δ, the criterion's evaluation-noise floor over
1096 /// the stall window divided by the radius the accepted steps actually
1097 /// probed. Present only on results rebuilt from a cost-stall exit;
1098 /// [`certify_outer_optimality`] folds it into the stationarity bound so the
1099 /// final re-measured gradient is judged against the same flat certificate
1100 /// the guard granted.
1101 pub flat_noise_grad_bound: Option<f64>,
1102 /// Post-fit PSIS diagnostic for whether sampled smoothing-parameter weights
1103 /// show evidence that plug-in REML/LAML intervals are unreliable. Populated
1104 /// once by [`run_outer`] when the exact rho Hessian is cheap enough to use.
1105 pub rho_uncertainty_diagnostic: Option<crate::rho_uncertainty::RhoUncertaintyDiagnostic>,
1106 /// Reseed point minted by a refused certification whose tail snap CONFIRMED
1107 /// an exponential tail (#2348 Inc 2b). A snap is a waypoint, never a
1108 /// candidate optimum: even an interior coordinate stationary before the
1109 /// snap can move when it is coupled to the tail coordinate (#2358). The
1110 /// plan runner retries ONCE from this point, allowing every coordinate to
1111 /// re-descend or remain on the rail before the natural certificate judges
1112 /// the result.
1113 pub tail_snap_reseed: Option<Array1<f64>>,
1114 /// Saddle-escape reseed point minted by a refused certification whose
1115 /// interior reduced Hessian is a certified strict saddle — small projected
1116 /// gradient, `hessian_psd = Some(false)`, no railed coordinate (#2357). A
1117 /// gradient-only convergence gate (ARC's, or the cost-stall guard's) can
1118 /// ARRIVE at such a saddle with its gradient already below tolerance and
1119 /// stop, even though the certified negative-curvature eigendirection is a
1120 /// strict descent direction the optimizer never took. This point is
1121 /// `ρ + α·v` for the most-negative-curvature eigenvector `v`, stepped off
1122 /// the saddle ridge to a strictly-lower objective; the plan runner reseeds
1123 /// the outer search ONCE from it (reseed gate closed so it cannot recurse),
1124 /// which lets the optimizer descend to the true PSD minimum exactly as an
1125 /// identical warm-started resume does by hand.
1126 pub saddle_escape_reseed: Option<Array1<f64>>,
1127 /// Wrong-rail pull-back reseed point minted by a refused certification whose
1128 /// coordinate sits AT the ρ box bound but whose clean-band probes prove the
1129 /// objective DECREASES as the coordinate moves INWARD (#2392). The outer
1130 /// search drove the coordinate to the wrong bound — its terminal gradient is
1131 /// deep-λ instrument noise, so the trust region never proposed the large
1132 /// inward move — while a drift-band-clean, above-noise-floor run of probes a
1133 /// few e-folds inside carries a pencil constant of the sign OPPOSITE the rail
1134 /// (descent points away from the bound, `∂V/∂ρ > 0` at an upper rail). This
1135 /// point moves that coordinate to its clean-band interior scale, where the
1136 /// gradient is informative again; the plan runner reseeds ONCE (gate closed)
1137 /// and the optimizer descends to the true interior optimum. Gated strictly on
1138 /// the opposite-sign clean-tail proof, so a GENUINE rail (descent toward the
1139 /// bound) never mints it and no real λ→∞ optimum is pulled off its rail.
1140 pub wrong_rail_reseed: Option<Array1<f64>>,
1141 /// Active-set reduction reseed minted by a refused certification whose
1142 /// INTERIOR is not stationary while a coordinate is railed at the ρ box with
1143 /// a deep-λ noise-floor gradient (#2392). The railed coordinate's
1144 /// ill-conditioned Hessian row poisons the joint Newton/ARC steps, so the
1145 /// interior cannot polish; freezing that coordinate at its bound and
1146 /// re-running lets the optimizer converge the interior in the well-conditioned
1147 /// REDUCED space. The reseed carries the frozen box (`bounds`, with
1148 /// `lower[k]==upper[k]==rail` for each frozen coordinate); the plan runner's
1149 /// re-certification under the ORIGINAL bounds then judges every pinned
1150 /// coordinate's KKT sign at the reduced optimum (an inward-feasible-descent
1151 /// gradient unfreezes it — no silent clamping of a coordinate that stops
1152 /// wanting the rail).
1153 pub active_set_reseed: Option<ActiveSetReseed>,
1154}
1155
1156/// An active-set reduction reseed (#2392): re-run the outer search with a set of
1157/// railed coordinates FROZEN at their box bounds so the optimizer polishes the
1158/// interior in the reduced space.
1159#[derive(Clone, Debug)]
1160pub struct ActiveSetReseed {
1161 /// The reseed point: the refused checkpoint with the frozen coordinates
1162 /// pinned at their bounds (`rho[k] == bounds.0[k] == bounds.1[k]`).
1163 pub rho: Array1<f64>,
1164 /// The reduced-space box: `lower[k] == upper[k] == rail` for every frozen
1165 /// coordinate `k`, the original bounds elsewhere.
1166 pub bounds: (Array1<f64>, Array1<f64>),
1167}
1168
1169impl OuterResult {
1170 pub fn new(
1171 rho: Array1<f64>,
1172 final_value: f64,
1173 iterations: usize,
1174 converged: bool,
1175 plan_used: OuterPlan,
1176 ) -> Self {
1177 Self {
1178 rho,
1179 final_value,
1180 iterations,
1181 final_grad_norm: None,
1182 final_gradient: None,
1183 final_hessian: None,
1184 converged,
1185 plan_used,
1186 operator_trust_radius: None,
1187 operator_stop_reason: None,
1188 criterion_certificate: None,
1189 converged_via: None,
1190 flat_noise_grad_bound: None,
1191 rho_uncertainty_diagnostic: None,
1192 tail_snap_reseed: None,
1193 saddle_escape_reseed: None,
1194 wrong_rail_reseed: None,
1195 active_set_reseed: None,
1196 }
1197 }
1198
1199 /// Human-readable rendering of `final_grad_norm` for diagnostics. Returns
1200 /// `"n/a"` when no gradient was measured (gradient-free / cache-hit paths).
1201 pub fn final_grad_norm_report(&self) -> String {
1202 match self.final_grad_norm {
1203 Some(g) => format!("{g:.3e}"),
1204 None => "n/a".to_string(),
1205 }
1206 }
1207}
1208
1209/// Validated evidence that an outer optimization terminated at a finite,
1210/// analytically certified optimum.
1211///
1212/// The inner [`OuterResult`] is private so downstream fit assembly cannot turn
1213/// a status boolean into convergence provenance. Construction consumes the
1214/// optimizer result and revalidates the certificate at the ownership boundary.
1215#[derive(Clone, Debug)]
1216pub struct CertifiedOuterResult {
1217 result: OuterResult,
1218}
1219
1220impl CertifiedOuterResult {
1221 /// The sole constructor is reached from [`OuterProblem::run_certified`]
1222 /// after the optimizer has produced the result. Keeping this private is
1223 /// load-bearing: `OuterResult` is also a public diagnostic/checkpoint
1224 /// payload, so a public conversion would let downstream code fabricate a
1225 /// certificate-shaped result without ever running an objective.
1226 fn from_optimizer_result(result: OuterResult) -> Result<Self, String> {
1227 if !result.converged {
1228 return Err(format!(
1229 "outer optimization did not converge after {} iterations",
1230 result.iterations
1231 ));
1232 }
1233 if !result.final_value.is_finite() {
1234 return Err(format!(
1235 "outer optimization returned a non-finite objective: {}",
1236 result.final_value
1237 ));
1238 }
1239 if result.rho.iter().any(|value| !value.is_finite()) {
1240 return Err("outer optimization returned non-finite hyperparameters".to_string());
1241 }
1242 if result
1243 .final_grad_norm
1244 .is_some_and(|value| !value.is_finite() || value < 0.0)
1245 {
1246 return Err(format!(
1247 "outer optimization returned an invalid gradient norm: {:?}",
1248 result.final_grad_norm
1249 ));
1250 }
1251 let certificate = result
1252 .criterion_certificate
1253 .as_ref()
1254 .ok_or_else(|| "outer optimization returned no analytic certificate".to_string())?;
1255 if !certificate.certifies() {
1256 return Err(format!(
1257 "outer optimization certificate does not certify: {}",
1258 certificate.summary()
1259 ));
1260 }
1261 if result.converged_via.is_none() {
1262 return Err(
1263 "outer optimization did not retain optimizer-owned termination provenance"
1264 .to_string(),
1265 );
1266 }
1267 Ok(Self { result })
1268 }
1269
1270 /// Exact optimizer-owned hyperparameter vector covered by the certificate.
1271 pub fn rho(&self) -> &Array1<f64> {
1272 &self.result.rho
1273 }
1274
1275 pub fn iterations(&self) -> usize {
1276 self.result.iterations
1277 }
1278
1279 pub fn final_value(&self) -> f64 {
1280 self.result.final_value
1281 }
1282
1283 pub fn final_grad_norm(&self) -> Option<f64> {
1284 self.result.final_grad_norm
1285 }
1286
1287 /// Exact analytic gradient re-measured by the optimizer-owned terminal
1288 /// certificate. Downstream selected-profile finalizers use this to prove
1289 /// that a retained objective payload is the one certified at `rho()`.
1290 pub fn final_gradient(&self) -> Option<&Array1<f64>> {
1291 self.result.final_gradient.as_ref()
1292 }
1293
1294 pub fn criterion_certificate(&self) -> &OuterCriterionCertificate {
1295 self.result
1296 .criterion_certificate
1297 .as_ref()
1298 .expect("CertifiedOuterResult always owns a validated certificate")
1299 }
1300
1301 /// The analytic outer ρ-Hessian measured at the certified point, when the
1302 /// certification retained one. This is the curvature evidence behind the
1303 /// certificate's PSD verdict — and the `V_ρ = H_ρ⁻¹` input to first-order
1304 /// smoothing-correction inflation (#2346).
1305 pub fn final_hessian(&self) -> Option<&Array2<f64>> {
1306 self.result.final_hessian.as_ref()
1307 }
1308}
1309
1310#[cfg(test)]
1311mod certified_outer_result_tests {
1312 use super::*;
1313
1314 #[test]
1315 fn caller_boolean_and_zero_gradient_cannot_mint_outer_authority() {
1316 let mut fabricated = OuterResult::new(
1317 Array1::from_vec(vec![0.0]),
1318 1.0,
1319 3,
1320 true,
1321 OuterPlan {
1322 solver: Solver::Bfgs,
1323 hessian_source: HessianSource::BfgsApprox,
1324 },
1325 );
1326 fabricated.final_grad_norm = Some(0.0);
1327 fabricated.final_gradient = Some(Array1::from_vec(vec![0.0]));
1328 fabricated.converged_via = Some(OuterConvergedVia::GradientStationary);
1329
1330 let reason = CertifiedOuterResult::from_optimizer_result(fabricated)
1331 .expect_err("caller-written status and gradient must not mint a certificate");
1332 assert!(reason.contains("no analytic certificate"), "{reason}");
1333 }
1334}
1335
1336/// Typed refusal from [`audit_stationary_point`]. The rejected point and every
1337/// analytic certificate field measured before refusal remain available to the
1338/// caller; `source` records why those measurements did not certify.
1339#[derive(Debug)]
1340pub struct OuterStationaryPointRejection {
1341 pub result: OuterResult,
1342 pub source: EstimationError,
1343}
1344
1345impl std::fmt::Display for OuterStationaryPointRejection {
1346 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1347 std::fmt::Display::fmt(&self.source, f)
1348 }
1349}
1350
1351impl std::error::Error for OuterStationaryPointRejection {
1352 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
1353 Some(&self.source)
1354 }
1355}
1356
1357/// Apply the shared analytic outer-optimality authority to one caller-supplied
1358/// point without running an optimizer or taking a step.
1359///
1360/// The objective controls whether evaluating that point mutates its profiled
1361/// state. Callers auditing an already-installed inner state must put their
1362/// objective in a frozen evaluation mode before calling this function.
1363/// `iterations == 0` in the returned result is structural: no optimization loop
1364/// exists on this path.
1365pub fn audit_stationary_point(
1366 obj: &mut dyn OuterObjective,
1367 rho: Array1<f64>,
1368 context: &str,
1369) -> Result<OuterResult, OuterStationaryPointRejection> {
1370 let config = OuterConfig::default();
1371 let selected_plan = plan(&obj.capability());
1372 // There is intentionally no independent value-only probe. The analytic
1373 // sample is the authority being audited, and infinity records that no
1374 // optimizer-produced terminal value exists to compare against it.
1375 let mut result = OuterResult::new(rho, f64::INFINITY, 0, false, selected_plan);
1376 match certify_outer_optimality(obj, &config, context, &mut result) {
1377 Ok(certificate) => {
1378 result.criterion_certificate = Some(certificate);
1379 Ok(result)
1380 }
1381 Err(source) => Err(OuterStationaryPointRejection { result, source }),
1382 }
1383}
1384
1385// ─── First-order optimality certificate (#934) ────────────────────────
1386//
1387// The objective↔gradient desync bug genus (#748, #752, #808, #901, …) has a
1388// universal signature: at the returned "optimum" the optimizer claims
1389// convergence while the criterion is not actually stationary there (or the
1390// optimizer stalls and rails λ). The certificate makes the engine check
1391// itself, once, at θ̂, on every generic outer fit — purely from the ANALYTIC
1392// objective, per SPEC rule 2 (finite differences never run outside tests;
1393// the FD gradient oracle now lives in the test-only `fd_audit` module): the
1394// KKT-projected analytic gradient norm against the same score-relative
1395// stationarity bound the outer loop already uses to accept flat-valley
1396// stalls (#1690), a scaled PSD probe of the tracked outer Hessian, and the
1397// λ-rail facts every desync postmortem asks for. It is the runtime
1398// enforcement layer for the criterion-atom architecture (#931).
1399//
1400// A failed certificate REJECTS the fit as typed non-convergence — never a
1401// warn-and-continue diagnostic — so a nonstationary point can never be
1402// minted into a fit (SPEC rule 20).
1403
1404/// Cholesky positive-SEMIdefiniteness probe for the (small, outer-dim) final
1405/// Hessian, with a roundoff-scale diagonal shift. Returns `None` when the
1406/// matrix is empty, non-square, or non-finite; `Some(false)` when the shifted
1407/// matrix has a non-positive pivot — i.e. the curvature is genuinely
1408/// indefinite, not merely semidefinite-within-noise.
1409///
1410/// The shift is `√ε · max(1, max|H_ii|)`: eigenvalues assembled through
1411/// O(‖H‖)-scaled arithmetic carry O(ε·‖H‖) roundoff, so a `√ε`-relative
1412/// margin cleanly separates a true negative direction from accumulated
1413/// floating-point noise on a flat (near-semidefinite) valley.
1414pub(crate) fn certificate_hessian_is_psd(hessian: &Array2<f64>) -> Option<bool> {
1415 let n = hessian.nrows();
1416 if n == 0 || hessian.ncols() != n || hessian.iter().any(|v| !v.is_finite()) {
1417 return None;
1418 }
1419 let max_diag = (0..n).fold(0.0_f64, |acc, j| acc.max(hessian[[j, j]].abs()));
1420 let shift = f64::EPSILON.sqrt() * max_diag.max(1.0);
1421 let mut chol = hessian.clone();
1422 for j in 0..n {
1423 chol[[j, j]] += shift;
1424 }
1425 for j in 0..n {
1426 for k in 0..j {
1427 let l_jk = chol[[j, k]];
1428 for i in j..n {
1429 chol[[i, j]] -= chol[[i, k]] * l_jk;
1430 }
1431 }
1432 let pivot = chol[[j, j]];
1433 if !(pivot > 0.0) || !pivot.is_finite() {
1434 return Some(false);
1435 }
1436 let inv_sqrt = 1.0 / pivot.sqrt();
1437 for i in j..n {
1438 chol[[i, j]] *= inv_sqrt;
1439 }
1440 }
1441 Some(true)
1442}
1443
1444/// PSD verdict of the outer Hessian restricted to its UN-RAILED coordinates
1445/// (#2299 box-KKT reduced-Hessian / critical-cone gate).
1446///
1447/// A coordinate railed at ±`rho_bound` with an outward gradient is at the box-KKT
1448/// constrained optimum: its curvature direction is the flat/indefinite
1449/// infinite-smoothing plateau of a fully-saturated penalty (λ ~ 1e13), carrying
1450/// no feasible descent. Including it makes the FULL Hessian indefinite and used to
1451/// disable the very flatness certificate that exists to handle rails, so an
1452/// honest railed optimum ground to `max_iter` and refused. Judging PSD on the
1453/// INTERIOR (un-railed) sub-block is the standard reduced-Hessian condition: a
1454/// genuinely indefinite *interior* direction still keeps the sub-block non-PSD,
1455/// so this can never over-certify a real saddle. When every coordinate is railed
1456/// the interior is empty — there is no feasible curvature to certify and the rail
1457/// KKT signs are the whole certificate — so the empty sub-block is trivially PSD.
1458/// With no railed coordinate it is exactly [`certificate_hessian_is_psd`].
1459pub(crate) fn certificate_hessian_is_psd_off_railed(
1460 hessian: &Array2<f64>,
1461 railed: &[usize],
1462) -> Option<bool> {
1463 if railed.is_empty() {
1464 return certificate_hessian_is_psd(hessian);
1465 }
1466 let n = hessian.nrows();
1467 let railed_set: std::collections::BTreeSet<usize> = railed.iter().copied().collect();
1468 let interior: Vec<usize> = (0..n).filter(|k| !railed_set.contains(k)).collect();
1469 if interior.is_empty() {
1470 return Some(true);
1471 }
1472 let mut sub = Array2::<f64>::zeros((interior.len(), interior.len()));
1473 for (i, &ri) in interior.iter().enumerate() {
1474 for (j, &rj) in interior.iter().enumerate() {
1475 sub[[i, j]] = hessian[[ri, rj]];
1476 }
1477 }
1478 certificate_hessian_is_psd(&sub)
1479}
1480
1481/// Interior-PSD verdict judged ABOVE the per-coordinate gradient-residue noise
1482/// floor (#2349): PSD of `H + diag(|g|)` restricted to the un-excluded
1483/// coordinates.
1484///
1485/// The assembled ρ-Hessian's tail entries carry the #2298 trace-pair
1486/// cancellation residue: when the `λ²V_λλ` pair cancels to roundoff, the
1487/// surviving diagonal entry is `λV_λ = g_k` — gradient magnitude, corrupted
1488/// sign (the same tie signature the tail-snap candidate band keys on).
1489/// Measured on the #2349 multinomial checkpoint: the sole interior coordinate
1490/// had `g₁ = −1.0228e-3`, `H₁₁ = −1.0216e-3` (ratio 0.999), and that single
1491/// sub-resolution entry was the entire `interior Hessian sub-block not PSD`
1492/// refusal — the full 6×6 spectrum was `[−1.02e-3, 0.135, …, 0.904]`.
1493///
1494/// # Why the floor is safe — instrument resolution, not step economics
1495///
1496/// The residue is `O(|g_k|)`, and the sub-block is extracted AFTER flooring, so
1497/// only the *judged* (un-excluded) coordinates' gradients ever enter: a railed
1498/// coordinate's large `|g|` (measured 1.40 on the #2349 checkpoint) cannot
1499/// inflate the floor. Weyl bounds what remains exactly:
1500///
1501/// ```text
1502/// λ_min(H) + min|g| ≤ λ_min(H + diag|g|) ≤ λ_min(H) + max|g|
1503/// ```
1504///
1505/// so the floor absorbs **at most `max_k |g_k|` over the judged coordinates**.
1506/// Where this verdict can mint, those coordinates have passed gradient
1507/// stationarity, so that is at most the stationarity bound. A negative
1508/// eigenvalue smaller than that is not distinguishable from zero *by the
1509/// instrument that produced it* — the assembled tail entry IS `λV_λ = g`
1510/// (ratio 0.999 as measured) — while a genuine saddle survives untouched: the
1511/// #2357 trace's `λ_min ≈ −0.5` against `|g| ≈ 1e-3` floors to `−0.4973`, and a
1512/// `−1.50e-2` direction still refuses at a `1e-2` bound.
1513///
1514/// ## Do not widen this floor beyond `diag(|g|)`
1515///
1516/// Earlier revisions of this argument (and #2349 rounds 6/7) justified the
1517/// floor by an "exploitable improvement `≲ g²/2|H| ≈ 5e-7`". Both halves are
1518/// wrong and the pair is misleading in the permissive direction:
1519///
1520/// * the quoted number is `g²/2 = 5.23e-7`; the quoted formula evaluates to
1521/// `g²/(2|H|) = 5.12e-4` on the same measured `g = 1.0228e-3`,
1522/// `|H| = 1.0216e-3` — a 979× discrepancy. Since `|H| ≈ g` at exactly the
1523/// point this floor serves, that formula collapses to `≈ g/2`;
1524/// * and `g²/2|H|` is the Newton decrement, which bounds the improvement along
1525/// a direction of POSITIVE curvature. Along curvature `−ε` the model
1526/// `−g·t − ½εt²` is unbounded below, so the quantity it purports to bound
1527/// does not exist in the regime this floor exists for; the step is limited by
1528/// the trust radius, not the curvature.
1529///
1530/// The Weyl bound above is the correct and checkable statement. Anything that
1531/// widens the floor must re-derive against it, not against `g²/2|H|`.
1532pub(crate) fn certificate_hessian_is_psd_off_railed_above_gradient_floor(
1533 hessian: &Array2<f64>,
1534 excluded: &[usize],
1535 gradient: &Array1<f64>,
1536) -> Option<bool> {
1537 let n = hessian.nrows();
1538 if gradient.len() != n {
1539 return certificate_hessian_is_psd_off_railed(hessian, excluded);
1540 }
1541 let mut floored = hessian.clone();
1542 for k in 0..n {
1543 floored[[k, k]] += gradient[k].abs();
1544 }
1545 certificate_hessian_is_psd_off_railed(&floored, excluded)
1546}
1547
1548/// Measure the gradient-residue floor's clearance on the interior sub-block:
1549/// the sub-block's smallest eigenvalue as assembled, the floor it is judged
1550/// against (`max_k |g_k|` over exactly those coordinates), and whether
1551/// `H + diag(|g|)` is PSD there.
1552///
1553/// This RECORDS the verdict; it does not replace the raw measurement. See
1554/// [`certificate_hessian_is_psd_off_railed_above_gradient_floor`] for why the
1555/// Weyl bound makes the floor safe where it can mint, and
1556/// [`crate::model_types::CurvatureFloorClearance`] for why the two facts are
1557/// kept apart.
1558pub(crate) fn interior_curvature_floor_clearance(
1559 hessian: &Array2<f64>,
1560 excluded: &[usize],
1561 gradient: &Array1<f64>,
1562) -> Option<CurvatureFloorClearance> {
1563 use faer::Side;
1564 use gam_linalg::faer_ndarray::FaerEigh;
1565
1566 let n = hessian.nrows();
1567 if n == 0 || hessian.ncols() != n || gradient.len() != n {
1568 return None;
1569 }
1570 let excluded_set: std::collections::BTreeSet<usize> = excluded.iter().copied().collect();
1571 let interior: Vec<usize> = (0..n).filter(|k| !excluded_set.contains(k)).collect();
1572 if interior.is_empty() {
1573 return None;
1574 }
1575 let m = interior.len();
1576 let mut sub = Array2::<f64>::zeros((m, m));
1577 for (i, &ri) in interior.iter().enumerate() {
1578 for (j, &rj) in interior.iter().enumerate() {
1579 sub[[i, j]] = 0.5 * (hessian[[ri, rj]] + hessian[[rj, ri]]);
1580 }
1581 }
1582 if sub.iter().any(|v| !v.is_finite()) {
1583 return None;
1584 }
1585 let interior_min_eigenvalue = sub
1586 .eigh(Side::Lower)
1587 .ok()?
1588 .0
1589 .iter()
1590 .fold(f64::INFINITY, |acc, v| acc.min(*v));
1591 if !interior_min_eigenvalue.is_finite() {
1592 return None;
1593 }
1594 // The floor is the largest gradient among EXACTLY the judged coordinates —
1595 // the excluded ones never enter, so a railed coordinate's large |g| cannot
1596 // inflate it.
1597 let gradient_floor = interior
1598 .iter()
1599 .fold(0.0_f64, |acc, &k| acc.max(gradient[k].abs()));
1600 let cleared = certificate_hessian_is_psd_off_railed_above_gradient_floor(
1601 hessian, excluded, gradient,
1602 ) == Some(true);
1603 Some(CurvatureFloorClearance {
1604 interior_min_eigenvalue,
1605 gradient_floor,
1606 cleared,
1607 })
1608}
1609
1610/// Escape point off a certified strict saddle in the free (un-railed) subspace
1611/// (#2357, generalised to the box-constrained case in #2155).
1612///
1613/// A gradient-only outer convergence gate — ARC's own, or the cost-stall guard's
1614/// — can ARRIVE at a point that is first-order stationary (`‖Pg‖ ≤ bound`) yet
1615/// sits on genuinely indefinite curvature in its INTERIOR (un-railed) directions,
1616/// and stop there because its gradient already cleared tolerance. The mandatory
1617/// analytic certificate then refuses the point as `INDEFINITE CURVATURE AT
1618/// INTERIOR OPTIMUM` — a verdict `certificate_hessian_is_psd_off_railed` reaches
1619/// on the reduced Hessian restricted to the un-railed coordinates, so it fires
1620/// whether or not some other coordinate happens to be railed. Such a point is a
1621/// saddle, not a minimum: the most-negative-curvature eigenvector `v` of that
1622/// reduced Hessian is a strict, box-feasible descent direction the optimizer
1623/// never took. An
1624/// identical warm-started resume escapes it trivially (its fresh cubic step moves
1625/// off the ridge, which is why the resume converges where the cold run refuses);
1626/// this reproduces that escape deterministically by stepping `ρ ± α·v` to a
1627/// strictly-lower objective and handing the point back as a one-shot reseed.
1628///
1629/// Termination is guaranteed: along a direction of negative curvature
1630/// `vᵀHv = λ_min < 0` at a near-stationary gradient,
1631/// `f(ρ ± αv) = f(ρ) ± α(g·v) + ½α²λ_min + o(α²)` strictly decreases for small
1632/// enough `α` once the sign is chosen so the first-order term is non-positive, so
1633/// the finite backtracking below always finds a descending feasible point when
1634/// one exists inside the box.
1635///
1636/// Returns `None` (no reseed; the ordinary refusal proceeds) when the Hessian
1637/// carries no eigen-resolvable negative direction, or no bounded step along it
1638/// clears the box projection with a strict objective decrease. Restores the
1639/// objective's profiled inner state to `rho` before returning either way, so the
1640/// refusal path that follows measures the checkpoint rather than the last probe.
1641fn negative_curvature_escape_point(
1642 obj: &mut dyn OuterObjective,
1643 rho: &Array1<f64>,
1644 gradient: &Array1<f64>,
1645 hessian: &Array2<f64>,
1646 railed: &[usize],
1647 baseline_cost: f64,
1648 bounds: &(Array1<f64>, Array1<f64>),
1649 context: &str,
1650) -> Option<Array1<f64>> {
1651 use faer::Side;
1652 use gam_linalg::faer_ndarray::FaerEigh;
1653
1654 let n = hessian.nrows();
1655 if n == 0 || hessian.ncols() != n || hessian.iter().any(|v| !v.is_finite()) {
1656 return None;
1657 }
1658 // The escape direction lives in the INTERIOR (un-railed) subspace — the exact
1659 // reduced Hessian / critical cone that `certificate_hessian_is_psd_off_railed`
1660 // judges for the PSD verdict. A coordinate railed at a box bound with an
1661 // outward KKT gradient is already at its constrained optimum; its curvature is
1662 // the flat/indefinite infinite-smoothing plateau (λ ~ 1e13) and carries no
1663 // feasible descent. Including it would let the step chase that spurious
1664 // direction and simply re-rail. Restricting to the un-railed block yields a
1665 // feasible descent that holds every rail fixed, so the escape generalises from
1666 // the fully-interior saddle to a box-constrained one whose free-direction
1667 // reduced Hessian is indefinite (#2357 → #2155). With no rail this is exactly
1668 // the full-Hessian eigenproblem as before.
1669 let railed_set: std::collections::BTreeSet<usize> = railed.iter().copied().collect();
1670 let interior: Vec<usize> = (0..n).filter(|k| !railed_set.contains(k)).collect();
1671 if interior.is_empty() {
1672 // Every coordinate is railed: there is no feasible interior direction and
1673 // the rail KKT signs are the whole certificate.
1674 return None;
1675 }
1676 let m = interior.len();
1677 let mut sub = Array2::<f64>::zeros((m, m));
1678 for (i, &ri) in interior.iter().enumerate() {
1679 for (j, &rj) in interior.iter().enumerate() {
1680 sub[[i, j]] = hessian[[ri, rj]];
1681 }
1682 }
1683 let (eigenvalues, eigenvectors) = match sub.eigh(Side::Lower) {
1684 Ok(pair) => pair,
1685 Err(err) => {
1686 log::warn!(
1687 "[CERTIFICATE] {context}: saddle-escape eigendecomposition failed ({err}); \
1688 refusing at the checkpoint without a reseed"
1689 );
1690 return None;
1691 }
1692 };
1693 // The SAME √ε·‖H‖ margin `certificate_hessian_is_psd` uses to separate a
1694 // genuine negative eigenvalue from O(ε·‖H‖) assembly roundoff: only a truly
1695 // negative direction — not a flat / near-semidefinite one — carries a descent
1696 // the reseed can exploit. Measured on the interior sub-block's diagonal so the
1697 // threshold matches the reduced PSD verdict exactly.
1698 let max_diag = interior
1699 .iter()
1700 .fold(0.0_f64, |acc, &j| acc.max(hessian[[j, j]].abs()));
1701 let neg_margin = f64::EPSILON.sqrt() * max_diag.max(1.0);
1702 let mut min_idx = 0usize;
1703 for k in 1..eigenvalues.len() {
1704 if eigenvalues[k] < eigenvalues[min_idx] {
1705 min_idx = k;
1706 }
1707 }
1708 if !(eigenvalues[min_idx] < -neg_margin) {
1709 return None;
1710 }
1711 let v_sub = eigenvectors.column(min_idx);
1712 let dir_norm = v_sub.dot(&v_sub).sqrt();
1713 if !(dir_norm > 0.0) || !dir_norm.is_finite() {
1714 return None;
1715 }
1716 // Lift the interior eigenvector into the full ρ space, exactly zero on every
1717 // railed coordinate so the backtracking step below holds all rails fixed.
1718 let mut direction = Array1::<f64>::zeros(n);
1719 for (i, &ri) in interior.iter().enumerate() {
1720 direction[ri] = v_sub[i] / dir_norm;
1721 }
1722 // First-order-consistent sign: move against the (tiny) gradient's projection
1723 // onto `v` so the linear term never opposes the curvature descent. With a
1724 // stationary gradient the tie is arbitrary; the opposite sign is tried below
1725 // regardless, which also covers a `v` that projects straight out of the box.
1726 let primary_sign = if gradient.dot(&direction) > 0.0 {
1727 -1.0
1728 } else {
1729 1.0
1730 };
1731 // One e-fold in log-λ is a macroscopic step across the saddle ridge; ARC then
1732 // refines from wherever this lands, so the reseed only needs to leave the
1733 // ridge, not solve the problem. Backtrack so the box-projected point still
1734 // strictly descends.
1735 const ESCAPE_STEP_SCALES: [f64; 5] = [1.0, 0.5, 0.25, 0.125, 0.0625];
1736 // A strict-decrease floor at the objective's roundoff resolution: a reseed
1737 // that only matches the checkpoint to roundoff is not a real escape.
1738 let strict_floor = baseline_cost.abs().max(1.0) * (16.0 * f64::EPSILON);
1739 let mut best: Option<(f64, Array1<f64>)> = None;
1740 for sign in [primary_sign, -primary_sign] {
1741 for &alpha in ESCAPE_STEP_SCALES.iter() {
1742 let mut trial = rho.clone();
1743 for i in 0..n {
1744 trial[i] += sign * alpha * direction[i];
1745 }
1746 let trial = project_to_bounds(&trial, Some(bounds));
1747 // A fully box-clamped trial that lands back on ρ probes nothing.
1748 if outer_theta_bitwise_eq(&trial, rho) {
1749 continue;
1750 }
1751 if let Ok(cost) = obj.eval_cost(&trial)
1752 && cost.is_finite()
1753 && cost < baseline_cost - strict_floor
1754 && best.as_ref().is_none_or(|(c, _)| cost < *c)
1755 {
1756 best = Some((cost, trial));
1757 }
1758 }
1759 if best.is_some() {
1760 break;
1761 }
1762 }
1763 // Restore the profiled inner state to the checkpoint ρ so the refusal path
1764 // that follows measures the checkpoint, not the last probe.
1765 if let Err(err) = obj.eval_cost(rho) {
1766 log::warn!(
1767 "[CERTIFICATE] {context}: failed to restore the objective to the checkpoint \
1768 after saddle-escape probing: {err}"
1769 );
1770 }
1771 best.map(|(cost, point)| {
1772 log::info!(
1773 "[CERTIFICATE] {context}: interior strict saddle (λ_min={:.3e} < 0, |Pg| within \
1774 band); minting a negative-curvature escape reseed (objective {:.6e} → {:.6e}) for \
1775 one retry (#2357)",
1776 eigenvalues[min_idx],
1777 baseline_cost,
1778 cost,
1779 );
1780 point
1781 })
1782}
1783
1784/// Second-order predicted objective decrease of a safeguarded Newton step at a
1785/// flat-valley cost-stall exit (#2253/#2249/#2015).
1786///
1787/// On a flat-valley cost-stall exit the outer criterion has provably stopped
1788/// improving (the cost-stall window fired), yet the re-measured projected
1789/// gradient can sit modestly above the score-relative flat band on a
1790/// weakly-identified small-n fit (measured: |Pg| ≈ 0.072 vs a score-relative
1791/// band ≈ 0.053 on an n=84/p=64 K=1 circle). Whether that residual is genuine
1792/// available descent is a SECOND-ORDER question. The improvement a safeguarded
1793/// Newton step buys is the Newton decrement over two:
1794///
1795/// Δpred = ½ · gᵀ H⁻¹ g,
1796///
1797/// the textbook Newton stopping quantity (Boyd–Vandenberghe §9.5). When `Δpred`
1798/// is below the outer objective tolerance, no step can reduce the criterion by
1799/// more than that tolerance and the point is stationary at the resolution the
1800/// criterion can be optimized — the mathematically correct "no further descent
1801/// possible" criterion.
1802///
1803/// This is curvature-scaled, not a constant: because `H⁻¹` weights each gradient
1804/// component by the inverse eigenvalue, a residual aligned with a NEAR-FLAT
1805/// Hessian eigenvector (a linear ramp that DOES carry real descent) inflates
1806/// `gᵀ H⁻¹ g` toward the roundoff-regularized `|g_flat|² / shift` and is
1807/// REJECTED; only a residual that is small along the well-curved directions and
1808/// nearly orthogonal to the flat ones certifies. An indefinite Hessian never
1809/// reaches here — the certificate's curvature gate (`certificate_hessian_is_psd`)
1810/// rejects a genuinely indefinite point independently, and this factorization
1811/// returns `None` on a non-PSD shifted factor so the caller falls back to the
1812/// gradient-only bound.
1813///
1814/// `hessian` and `grad` are the analytic outer Hessian and the KKT-PROJECTED
1815/// gradient at the certified point. The shift `√ε · max|H_jj|` matches
1816/// [`certificate_hessian_is_psd`] so the definiteness verdict and this decrement
1817/// agree on the same regularized operator. Returns `None` when the shapes are
1818/// malformed, an entry is non-finite, the shifted factor is not PD, or the
1819/// resulting quadratic form is negative (which a PD factor rules out; retained
1820/// as a roundoff guard).
1821pub(crate) fn newton_predicted_decrease(hessian: &Array2<f64>, grad: &Array1<f64>) -> Option<f64> {
1822 let n = hessian.nrows();
1823 if n == 0 || hessian.ncols() != n || grad.len() != n {
1824 return None;
1825 }
1826 if hessian.iter().any(|v| !v.is_finite()) || grad.iter().any(|v| !v.is_finite()) {
1827 return None;
1828 }
1829 let max_diag = (0..n).fold(0.0_f64, |acc, j| acc.max(hessian[[j, j]].abs()));
1830 let shift = f64::EPSILON.sqrt() * max_diag.max(1.0);
1831 // Lower Cholesky factor L of H + shift·I (same regularization the PSD probe
1832 // uses), computed in place.
1833 let mut l = hessian.clone();
1834 for j in 0..n {
1835 l[[j, j]] += shift;
1836 }
1837 for j in 0..n {
1838 for k in 0..j {
1839 let l_jk = l[[j, k]];
1840 for i in j..n {
1841 l[[i, j]] -= l[[i, k]] * l_jk;
1842 }
1843 }
1844 let pivot = l[[j, j]];
1845 if !(pivot > 0.0) || !pivot.is_finite() {
1846 return None;
1847 }
1848 let inv_sqrt = 1.0 / pivot.sqrt();
1849 for i in j..n {
1850 l[[i, j]] *= inv_sqrt;
1851 }
1852 }
1853 // Solve (L Lᵀ) d = g for d = H_s⁻¹ g: forward-substitute L y = g, then
1854 // back-substitute Lᵀ d = y.
1855 let mut y = grad.clone();
1856 for j in 0..n {
1857 let mut s = y[j];
1858 for k in 0..j {
1859 s -= l[[j, k]] * y[k];
1860 }
1861 y[j] = s / l[[j, j]];
1862 }
1863 let mut d = y;
1864 for j in (0..n).rev() {
1865 let mut s = d[j];
1866 for k in (j + 1)..n {
1867 s -= l[[k, j]] * d[k];
1868 }
1869 d[j] = s / l[[j, j]];
1870 }
1871 let quad = grad.dot(&d); // gᵀ H_s⁻¹ g ≥ 0 for a PD factor.
1872 if !quad.is_finite() || quad < 0.0 {
1873 return None;
1874 }
1875 Some(0.5 * quad)
1876}
1877
1878/// Is outer coordinate `k` pinned within [`CERTIFICATE_RAIL_MARGIN`] of either
1879/// of its own box bounds?
1880///
1881/// Factored out so the λ-block REPORT and the θ-wide certificate FACE below
1882/// cannot drift apart about what "railed" means for the same coordinate: they
1883/// differ only in which coordinates they scan, never in the test.
1884fn outer_coordinate_is_railed(theta: &Array1<f64>, k: usize, config: &OuterConfig) -> bool {
1885 let (lo, hi) = match config.bounds.as_ref() {
1886 Some((lo, hi)) if k < lo.len() && k < hi.len() => (lo[k], hi[k]),
1887 Some(_) => return false,
1888 None => (-config.rho_bound, config.rho_bound),
1889 };
1890 (theta[k] - lo).abs() <= CERTIFICATE_RAIL_MARGIN
1891 || (hi - theta[k]).abs() <= CERTIFICATE_RAIL_MARGIN
1892}
1893
1894/// Smoothing coordinates (leading ρ block) railed against the outer box.
1895///
1896/// This is the **report**. `OuterCriterionCertificate::lambdas_railed` indexes
1897/// *smoothing parameters*, and every consumer reads it that way — gam-report's
1898/// "λ railed" warning, the pyffi certificate surface, `is_clean`. It is
1899/// deliberately NOT the set the certificate reasons with; see
1900/// [`certificate_railed_coordinates`].
1901pub(crate) fn certificate_railed_lambdas(
1902 rho: &Array1<f64>,
1903 rho_dim: usize,
1904 config: &OuterConfig,
1905) -> Vec<usize> {
1906 (0..rho_dim.min(rho.len()))
1907 .filter(|&k| outer_coordinate_is_railed(rho, k, config))
1908 .collect()
1909}
1910
1911/// **Every** outer coordinate railed against its own box bound — the ρ block
1912/// *and* the trailing non-ρ blocks that a joint search carries in the same θ
1913/// vector under the same box: the spatial log-κ ψ coordinates and the
1914/// auxiliary coordinates.
1915///
1916/// This is the set every *decision* in [`certify_outer_optimality`] must use,
1917/// and using the λ-only report there instead was a real defect. The active-set
1918/// reasoning at a box-constrained optimum is a statement about coordinates, not
1919/// about what a coordinate happens to parameterize:
1920///
1921/// * the second-order condition only has to hold on the **feasible tangent
1922/// subspace**, so `certificate_hessian_is_psd_off_railed` deletes the railed
1923/// rows and columns. `layout.rho_dim()` is `n_params − psi_dim`, so a ψ
1924/// coordinate pinned on its data-derived κ window stayed *inside* that
1925/// sub-block and its saturated (and, near a window edge, routinely negative)
1926/// curvature row decided `hessian_psd = false` for the whole fit. That is
1927/// precisely the failure the block's own comment says must not happen — "a
1928/// rail-caused indefiniteness would disable the very certificate that exists
1929/// to certify a railed optimum (#2299)" — it was simply never extended past
1930/// the ρ block;
1931/// * the same omission hid the coordinate from the asymptote-rail mint
1932/// (#2348 Inc 1), from tail-snap, from `interior_curvature_floor_clearance`,
1933/// from the #2155 saddle escape, and from the #2392 wrong-rail pull-back and
1934/// active-set reduction. A κ search whose rail is a ψ coordinate could
1935/// therefore never be certified *by construction*, no matter how correct its
1936/// gradient was;
1937/// * and it made the refusal message actively misleading, because
1938/// `project_gradient_vector` DOES project every coordinate against its own
1939/// bound. So `|g|` could collapse to a small `|Pg|` while `railed=[]`
1940/// reported nothing responsible for the collapse — the exact disagreement
1941/// #979 measured from the other side and could not explain.
1942///
1943/// Relaxing nothing: a coordinate near a bound whose gradient still points
1944/// *into* the box keeps its feasible-descent component in `|Pg|`
1945/// ([`project_gradient_vector`] zeros only the outward half), so a genuinely
1946/// unconverged interior direction is still refused.
1947pub(crate) fn certificate_railed_coordinates(
1948 theta: &Array1<f64>,
1949 config: &OuterConfig,
1950) -> Vec<usize> {
1951 (0..theta.len())
1952 .filter(|&k| outer_coordinate_is_railed(theta, k, config))
1953 .collect()
1954}
1955
1956/// Which term of the stationarity bound's `max` chain actually set it.
1957///
1958/// `certify_outer_optimality` does not compute *a* bound; it takes the maximum of
1959/// up to five independently-derived quantities, and until #2458 nothing recorded
1960/// which one won. That is why a `bound=1.000e0` and a `bound=5.68e-6` came out of
1961/// the same message shape and telling them apart required reading this file --
1962/// the emitted number is a DERIVATIVE of a construction-site value, computed
1963/// later, so grepping the construction sites cannot match an observed bound.
1964///
1965/// The rungs are not interchangeable calibrations of one quantity. Only
1966/// [`Self::CurvatureResolvability`] is derived from what a gradient of that size
1967/// does to the criterion: `|Pg|·√(τ/Δpred)` simplifies to `√(2·h·τ)` -- the `|Pg|`
1968/// cancels -- i.e. the gradient magnitude below which the criterion cannot resolve
1969/// descent at all. The others are gradient-magnitude tests, blind to how curvature
1970/// maps a gradient to an objective change, which is exactly what this file's own
1971/// comment at the curvature block already says. Recording the rung is the
1972/// prerequisite for holding every route to the derived one: it makes "which
1973/// machinery did this route happen to have" an observable rather than an
1974/// inference.
1975#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1976pub(crate) enum StationarityBoundSource {
1977 /// `outer_gradient_tolerance(config).threshold(cost, |g|)` -- the raw band.
1978 SolverBand,
1979 /// `flat_valley_converged_grad_bound(cost)`, gated on a `CostStallFlatValley`
1980 /// exit. Saturates at its `1.0` cap (#2456).
1981 FlatValleyScore,
1982 /// The cost-stall guard's measured probe-noise floor `σ̂/Δ` (#2241). Diverges
1983 /// as the step collapses, which is the regime it fires in.
1984 ProbeNoiseFloor,
1985 /// `|Pg|·√(τ/Δpred)` = `√(2·h·τ)` (#2253/#2249/#2015/#2091) -- the only rung
1986 /// with a derivation from the criterion's own resolution.
1987 CurvatureResolvability,
1988 /// Twice the same-ρ spread between the run-recorded and certificate-time
1989 /// gradients (#2299): the measuring instrument's demonstrated noise.
1990 GradientReproducibility,
1991}
1992
1993impl StationarityBoundSource {
1994 pub(crate) fn label(self) -> &'static str {
1995 match self {
1996 Self::SolverBand => "solver-band",
1997 Self::FlatValleyScore => "flat-valley-score",
1998 Self::ProbeNoiseFloor => "probe-noise-floor",
1999 Self::CurvatureResolvability => "curvature-resolvability",
2000 Self::GradientReproducibility => "gradient-reproducibility",
2001 }
2002 }
2003
2004 /// Whether this rung is the derived resolvability standard rather than a
2005 /// gradient-magnitude substitute adopted where that standard was unavailable.
2006 pub(crate) fn is_derived_standard(self) -> bool {
2007 matches!(self, Self::CurvatureResolvability)
2008 }
2009
2010 /// The neutral projection carried on the refusal, so a red states its own
2011 /// rung instead of leaving it to be inferred from the numbers.
2012 pub(crate) fn provenance(self) -> StationarityRung {
2013 StationarityRung {
2014 label: self.label(),
2015 derived_standard: self.is_derived_standard(),
2016 }
2017 }
2018}
2019
2020/// A stationarity bound bundled with the rung that produced it (#2458).
2021///
2022/// The bound and its provenance travelled separately before this: the bound was
2023/// a bare `f64` argument and the rung stayed a local in the certificate block,
2024/// so every refusal outside that block reported a number with no way to say
2025/// which standard it came from. Bundling them makes it impossible to pass one
2026/// without deciding the other.
2027#[derive(Debug, Clone, Copy)]
2028pub(crate) struct StationarityBound {
2029 value: f64,
2030 source: Option<StationarityBoundSource>,
2031}
2032
2033impl StationarityBound {
2034 /// A bound the rung ladder produced, carrying the rung it came from.
2035 pub(crate) fn from_ladder(value: f64, source: StationarityBoundSource) -> Self {
2036 Self {
2037 value,
2038 source: Some(source),
2039 }
2040 }
2041
2042 /// A bound from a route that does not select a ladder rung — a raw config
2043 /// tolerance, or a refusal reached before the ladder is evaluated.
2044 ///
2045 /// This is deliberately not defaulted to [`StationarityBoundSource::SolverBand`]:
2046 /// labelling an unclassified bound with a rung it did not come from would
2047 /// make the ladder look more uniformly applied than it is, which is the
2048 /// exact inference #2458 exists to stop. `rung=unrecorded` in a refusal is a
2049 /// finding — it names a route whose stationarity standard is still unstated.
2050 pub(crate) fn unrecorded(value: f64) -> Self {
2051 Self {
2052 value,
2053 source: None,
2054 }
2055 }
2056
2057 pub(crate) fn value(self) -> f64 {
2058 self.value
2059 }
2060
2061 pub(crate) fn rung(self) -> Option<StationarityRung> {
2062 self.source.map(StationarityBoundSource::provenance)
2063 }
2064}
2065
2066fn outer_nonconvergence_error(
2067 context: &str,
2068 reason: &str,
2069 result: &OuterResult,
2070 projected_grad_norm: Option<f64>,
2071 stationarity_bound: StationarityBound,
2072) -> EstimationError {
2073 // Solver provenance, appended to every outer non-convergence.
2074 //
2075 // The certificate answers "is this point stationary" — and when it is not,
2076 // the next question is always "then why did the search STOP here", which
2077 // the message did not answer. A binomial/logit P-spline (#1575/#1561)
2078 // refuses with `|Pg|=7.5e-1` against a `1.0e-2` bound, PSD Hessian, nothing
2079 // railed, all coordinates 18 e-folds inside the box, "after 7 outer
2080 // iteration(s)" — and the 7 is the whole mystery. `converged` distinguishes
2081 // a solver that CLAIMED convergence (a tolerance desync, and the only case
2082 // the #2273/#2374 resume will retry) from one that ran out of budget;
2083 // `operator_stop_reason` separates a trust-region reject floor from a
2084 // flat-valley cost stall from an iteration budget. All three are already on
2085 // the result and cost nothing to print.
2086 let reason = format!(
2087 "{reason}; solver provenance: claimed_converged={}{}{}",
2088 result.converged,
2089 result
2090 .operator_stop_reason
2091 .map(|stop| format!(", stop_reason={stop:?}"))
2092 .unwrap_or_default(),
2093 result
2094 .converged_via
2095 .map(|via| format!(", converged_via={via:?}"))
2096 .unwrap_or_default(),
2097 );
2098 EstimationError::RemlDidNotConverge {
2099 context: context.to_string(),
2100 reason,
2101 iterations: result.iterations,
2102 final_value: result.final_value,
2103 projected_grad_norm,
2104 stationarity_bound: stationarity_bound.value(),
2105 stationarity_bound_rung: stationarity_bound.rung(),
2106 rho_checkpoint: result.rho.to_vec(),
2107 }
2108}
2109
2110/// Roundoff envelope for two independently assembled values of the same outer
2111/// criterion at the same point.
2112///
2113/// Value-only and derivative-bearing evaluation lanes may use different
2114/// kernels and reduction trees, so bitwise identity is not a valid contract.
2115/// Their relative disagreement must nevertheless stay below the square root of
2116/// machine epsilon: beyond that scale the derivative sample is not evidence
2117/// about the scalar objective the optimizer's value lane ranks.
2118pub fn outer_value_agreement_bound(value_only: f64, derivative_sample: f64) -> f64 {
2119 f64::EPSILON.sqrt() * value_only.abs().max(derivative_sample.abs()).max(1.0)
2120}
2121
2122fn audit_outer_value_agreement(
2123 context: &str,
2124 value_only: f64,
2125 derivative_sample: f64,
2126 result: &mut OuterResult,
2127 projected_grad_norm: Option<f64>,
2128 stationarity_bound: StationarityBound,
2129) -> Result<(), EstimationError> {
2130 let bound = outer_value_agreement_bound(value_only, derivative_sample);
2131 let disagreement = (value_only - derivative_sample).abs();
2132 if disagreement <= bound {
2133 return Ok(());
2134 }
2135
2136 // The value-only lane is the scalar criterion authority. Preserve it on
2137 // the resumable checkpoint rather than the derivative lane's inconsistent
2138 // scalar; no certificate may be attached to this mixed evidence.
2139 result.final_value = value_only;
2140 result.converged = false;
2141 Err(outer_nonconvergence_error(
2142 context,
2143 &format!(
2144 "cost-only value disagrees with analytic-sample value at the same outer point: \
2145 value-only={value_only:.16e}, analytic-sample={derivative_sample:.16e}, \
2146 disagreement={disagreement:.3e}, roundoff bound={bound:.3e}"
2147 ),
2148 result,
2149 projected_grad_norm,
2150 stationarity_bound,
2151 ))
2152}
2153
2154fn certify_fixed_point_optimality(
2155 obj: &mut dyn OuterObjective,
2156 config: &OuterConfig,
2157 context: &str,
2158 result: &mut OuterResult,
2159 fidelity: CertificationFidelity,
2160) -> Result<OuterCriterionCertificate, EstimationError> {
2161 let layout = obj.capability().theta_layout();
2162 // A fixed-point residual is only a certificate for the scalar objective
2163 // when both lanes price that same objective at the same rho. Sample the
2164 // authoritative value lane first; the analytic fixed-point evaluator then
2165 // remains the installed terminal-state owner.
2166 //
2167 // MINT ONLY (#2359), for the same reason as on the analytic path: whether
2168 // the two lanes agree is terminal proof work, not a ranking signal, and on
2169 // this route it is the ONLY thing that distinguishes screening from mint —
2170 // there is no order-four ladder to reserve here.
2171 let value_only = match fidelity {
2172 CertificationFidelity::Screening => None,
2173 CertificationFidelity::Mint => {
2174 let sample = obj
2175 .eval_with_order(&result.rho, OuterEvalOrder::Value)
2176 .map_err(|err| {
2177 outer_nonconvergence_error(
2178 context,
2179 &format!("terminal value-only certificate evaluation failed: {err}"),
2180 result,
2181 None,
2182 StationarityBound::unrecorded(config.tolerance),
2183 )
2184 })?
2185 .cost;
2186 if !sample.is_finite() {
2187 return Err(outer_nonconvergence_error(
2188 context,
2189 "terminal value-only certificate evaluation returned a non-finite objective value",
2190 result,
2191 None,
2192 StationarityBound::unrecorded(config.tolerance),
2193 ));
2194 }
2195 Some(sample)
2196 }
2197 };
2198 let evaluation = obj
2199 .eval_fixed_point_certificate(&result.rho)
2200 .map_err(|err| {
2201 outer_nonconvergence_error(
2202 context,
2203 &format!("analytic fixed-point certificate evaluation failed: {err}"),
2204 result,
2205 None,
2206 StationarityBound::unrecorded(config.tolerance),
2207 )
2208 })?;
2209 if !inner_solve_converged(config.outer_inner_cap.as_ref()) {
2210 return Err(outer_nonconvergence_error(
2211 context,
2212 "terminal fixed-point evidence was evaluated at a non-converged inner state",
2213 result,
2214 None,
2215 StationarityBound::unrecorded(config.tolerance),
2216 ));
2217 }
2218 if evaluation.coordinates.len() != layout.n_params {
2219 return Err(outer_nonconvergence_error(
2220 context,
2221 &format!(
2222 "fixed-point certificate returned {} coordinates for an outer problem of dimension {}",
2223 evaluation.coordinates.len(),
2224 layout.n_params
2225 ),
2226 result,
2227 None,
2228 StationarityBound::unrecorded(config.tolerance),
2229 ));
2230 }
2231 if !evaluation.cost.is_finite() {
2232 return Err(outer_nonconvergence_error(
2233 context,
2234 "fixed-point certificate returned a non-finite objective value",
2235 result,
2236 None,
2237 StationarityBound::unrecorded(config.tolerance),
2238 ));
2239 }
2240 if let Some(value_only) = value_only {
2241 audit_outer_value_agreement(
2242 context,
2243 value_only,
2244 evaluation.cost,
2245 result,
2246 None,
2247 StationarityBound::unrecorded(config.tolerance),
2248 )?;
2249 }
2250
2251 let mut normalized_updates = Vec::with_capacity(layout.n_params);
2252 let mut uncovered = Vec::new();
2253 for (index, coordinate) in evaluation.coordinates.iter().enumerate() {
2254 match coordinate {
2255 FixedPointCoordinateCertificate::Covered { update, scale }
2256 if update.is_finite() && scale.is_finite() && *scale > 0.0 =>
2257 {
2258 normalized_updates.push(*update / *scale);
2259 }
2260 FixedPointCoordinateCertificate::Covered { update, scale } => {
2261 uncovered.push(format!(
2262 "coordinate {index} has invalid covered residual update={update} scale={scale}"
2263 ));
2264 normalized_updates.push(f64::NAN);
2265 }
2266 FixedPointCoordinateCertificate::Uncovered { reason } => {
2267 uncovered.push(format!("coordinate {index}: {reason}"));
2268 normalized_updates.push(f64::NAN);
2269 }
2270 }
2271 }
2272 if !uncovered.is_empty() {
2273 return Err(outer_nonconvergence_error(
2274 context,
2275 &format!(
2276 "fixed-point certificate lacks root-equivalent analytic coverage: {}",
2277 uncovered.join("; ")
2278 ),
2279 result,
2280 None,
2281 StationarityBound::unrecorded(config.tolerance),
2282 ));
2283 }
2284
2285 let (lower, upper) = outer_bounds_template(config, layout.n_params);
2286 let mut raw_inf = 0.0_f64;
2287 let mut projected_inf = 0.0_f64;
2288 for index in 0..layout.n_params {
2289 let update = normalized_updates[index];
2290 raw_inf = raw_inf.max(update.abs());
2291 // `update` is a signed descent/update direction, the negative of the
2292 // gradient convention used by `projected_gradient_norm`: at a lower
2293 // bound a negative update points out of the box, and at an upper bound
2294 // a positive update does. Only those infeasible multiplier components
2295 // are removed.
2296 let projected = if result.rho[index] <= lower[index] {
2297 update.max(0.0)
2298 } else {
2299 update
2300 };
2301 let projected = if result.rho[index] >= upper[index] {
2302 projected.min(0.0)
2303 } else {
2304 projected
2305 };
2306 projected_inf = projected_inf.max(projected.abs());
2307 }
2308
2309 result.final_value = evaluation.cost;
2310 result.final_grad_norm = None;
2311 result.final_gradient = None;
2312 result.final_hessian = None;
2313 result.converged = false;
2314
2315 let certificate = OuterCriterionCertificate {
2316 stationarity: OuterStationarityCertificate::FixedPoint {
2317 residual_inf_norm: raw_inf,
2318 projected_residual_inf_norm: projected_inf,
2319 bound: config.tolerance,
2320 covered_coordinates: layout.n_params,
2321 },
2322 hessian_psd: None,
2323 lambdas_railed: certificate_railed_lambdas(&result.rho, layout.rho_dim(), config),
2324 curvature_floor: None,
2325 };
2326 result.criterion_certificate = Some(certificate.clone());
2327 if !certificate.certifies() {
2328 return Err(outer_nonconvergence_error(
2329 context,
2330 &certificate.summary(),
2331 result,
2332 Some(projected_inf),
2333 StationarityBound::unrecorded(config.tolerance),
2334 ));
2335 }
2336
2337 result.converged = true;
2338 result.converged_via = match result.converged_via {
2339 Some(via @ OuterConvergedVia::RecurrentIncumbent { .. }) => Some(via),
2340 _ => Some(OuterConvergedVia::FixedPointStationary {
2341 projected_residual_inf_norm: projected_inf,
2342 certificate_bound: config.tolerance,
2343 }),
2344 };
2345 log::info!("[CERTIFICATE] {context}: {}", certificate.summary());
2346 Ok(certificate)
2347}
2348
2349/// Build the mandatory analytic optimality certificate at the returned point.
2350///
2351/// The objective is evaluated at the selected point through both its
2352/// authoritative value-only lane and its analytic derivative lane. The two
2353/// values must agree within their roundoff envelope before the derivative
2354/// sample can certify the scalar objective. Missing, malformed, non-finite, or
2355/// split-objective evidence is non-convergence: an optimizer status bit cannot
2356/// substitute for a stationarity certificate. Exact analytic curvature is
2357/// checked when the objective declares it and can materialize it; BFGS/EFS
2358/// solver geometry is never mistaken for objective curvature.
2359/// Which derivative fidelity a certification pass is allowed to spend.
2360///
2361/// #2359: the generic REML/LAML Hessian consumes the row-family derivative
2362/// ladder through order FOUR while its analytic gradient stops at order three,
2363/// so order four is a mint-time cost, not a per-candidate one. A multi-start
2364/// screens every seed it starts — that screening is a first-order gate
2365/// (stationarity, KKT projection, rail facts), and `curvature_admissible()`
2366/// reads `hessian_psd != Some(false)`, so a `None` curvature verdict certifies
2367/// on stationarity alone. The one order-four evaluation belongs to the winner,
2368/// once, and its verdict is the one that mints.
2369#[derive(Clone, Copy, Debug, PartialEq, Eq)]
2370pub(crate) enum CertificationFidelity {
2371 /// Per-candidate multi-start gate. Never spends order four.
2372 Screening,
2373 /// The single terminal mint audit. Spends order four when the objective
2374 /// declares an analytic Hessian.
2375 Mint,
2376}
2377
2378pub(crate) fn certify_outer_optimality(
2379 obj: &mut dyn OuterObjective,
2380 config: &OuterConfig,
2381 context: &str,
2382 result: &mut OuterResult,
2383) -> Result<OuterCriterionCertificate, EstimationError> {
2384 certify_outer_optimality_with_fidelity(obj, config, context, result, CertificationFidelity::Mint)
2385}
2386
2387pub(crate) fn certify_outer_optimality_with_fidelity(
2388 obj: &mut dyn OuterObjective,
2389 config: &OuterConfig,
2390 context: &str,
2391 result: &mut OuterResult,
2392 fidelity: CertificationFidelity,
2393) -> Result<OuterCriterionCertificate, EstimationError> {
2394 let terminal_cap_guard = config
2395 .outer_inner_cap
2396 .as_ref()
2397 .map(TerminalInnerCapGuard::lift);
2398 if terminal_cap_guard.is_some() || obj.owns_terminal_coefficient_mode() {
2399 // `reset` is deliberately conditional on the presence of the cap
2400 // contract. Those are the REML/mixture objectives whose search cache
2401 // can contain a coarse inner state; uncapped objectives retain their
2402 // ordinary stateful certification semantics.
2403 //
2404 // The `owns_terminal_coefficient_mode()` disjunct (#2334) closes the
2405 // gap for cap-less objectives that install an owned coefficient mode:
2406 // the certifying re-eval below must start from the same clean baseline
2407 // that `finalize_outer_result` used, so the mode's objective bitwise
2408 // matches the certified `final_value` even when the inner solve is
2409 // bimodal at `rho_star`.
2410 obj.reset();
2411 }
2412 let outcome =
2413 certify_outer_optimality_at_terminal_fidelity(obj, config, context, result, true, fidelity);
2414 drop(terminal_cap_guard);
2415 outcome
2416}
2417
2418fn certify_outer_optimality_at_terminal_fidelity(
2419 obj: &mut dyn OuterObjective,
2420 config: &OuterConfig,
2421 context: &str,
2422 result: &mut OuterResult,
2423 allow_tail_snap: bool,
2424 fidelity: CertificationFidelity,
2425) -> Result<OuterCriterionCertificate, EstimationError> {
2426 let capability = obj.capability();
2427 let layout = capability.theta_layout();
2428 layout
2429 .validate_point_len(&result.rho, "outer certificate point")
2430 .map_err(|err| {
2431 EstimationError::RemlOptimizationFailed(format!(
2432 "{context}: invalid outer certificate point: {err}"
2433 ))
2434 })?;
2435 if result.rho.iter().any(|value| !value.is_finite()) {
2436 return Err(outer_nonconvergence_error(
2437 context,
2438 "the selected checkpoint contains non-finite coordinates",
2439 result,
2440 None,
2441 StationarityBound::unrecorded(outer_gradient_tolerance(config).abs),
2442 ));
2443 }
2444 if layout.n_params == 0 {
2445 let value = obj.eval_cost(&result.rho).map_err(|err| {
2446 outer_nonconvergence_error(
2447 context,
2448 &format!("zero-dimensional final objective evaluation failed: {err}"),
2449 result,
2450 Some(0.0),
2451 StationarityBound::unrecorded(outer_gradient_tolerance(config).abs),
2452 )
2453 })?;
2454 if !value.is_finite() {
2455 return Err(outer_nonconvergence_error(
2456 context,
2457 "the zero-dimensional final objective is non-finite",
2458 result,
2459 Some(0.0),
2460 StationarityBound::unrecorded(outer_gradient_tolerance(config).abs),
2461 ));
2462 }
2463 let certificate = OuterCriterionCertificate {
2464 stationarity: OuterStationarityCertificate::AnalyticGradient {
2465 grad_norm: 0.0,
2466 projected_grad_norm: 0.0,
2467 bound: outer_gradient_tolerance(config).abs,
2468 },
2469 hessian_psd: None,
2470 lambdas_railed: Vec::new(),
2471 curvature_floor: None,
2472 };
2473 result.final_value = value;
2474 result.final_grad_norm = Some(0.0);
2475 result.final_gradient = Some(Array1::zeros(0));
2476 result.final_hessian = None;
2477 result.converged = true;
2478 result.converged_via = Some(OuterConvergedVia::GradientStationary);
2479 result.criterion_certificate = Some(certificate.clone());
2480 return Ok(certificate);
2481 }
2482 if matches!(result.plan_used.solver, Solver::Efs | Solver::HybridEfs)
2483 && capability.gradient != Derivative::Analytic
2484 {
2485 return certify_fixed_point_optimality(obj, config, context, result, fidelity);
2486 }
2487 if capability.gradient != Derivative::Analytic {
2488 return Err(outer_nonconvergence_error(
2489 context,
2490 "the objective exposes no analytic gradient for final certification",
2491 result,
2492 None,
2493 StationarityBound::unrecorded(outer_gradient_tolerance(config).abs),
2494 ));
2495 }
2496
2497 // Sample the scalar authority first and leave the derivative-bearing
2498 // evaluator as the terminal state owner. This is one same-rho audit, not a
2499 // finite-difference derivative: it catches a split value/gradient
2500 // implementation without violating the production no-FD contract.
2501 //
2502 // BOTH fidelities pay this, deliberately. It is tempting to reserve it for
2503 // the mint the way order four is reserved (#2359), but the two are not
2504 // alike: order four is evidence the filter does not consume, while the
2505 // value-agreement audit is a REFUSAL GATE on the very number the
2506 // multistart ranks by. `retain_best_outer_checkpoint` orders candidates on
2507 // `final_value`; admitting a candidate whose value lane disagrees with its
2508 // derivative lane lets a desynced candidate win the ranking on a number
2509 // nothing has validated, and mint then refuses the whole fit instead of a
2510 // runner-up carrying it. Pinned by
2511 // `analytic_hessian_candidate_screening_requires_only_first_order_evidence_2414`,
2512 // which asserts screening's orders are exactly [Value, ValueAndGradient].
2513 //
2514 // The EFS/fixed-point route is different and IS gated on fidelity — see
2515 // `certify_fixed_point_optimality`. It returns above this block, and there
2516 // screening and mint are otherwise identical work (no order-four ladder to
2517 // reserve), so running the audit twice buys nothing at all.
2518 let value_only = obj
2519 .eval_with_order(&result.rho, OuterEvalOrder::Value)
2520 .map_err(|err| {
2521 outer_nonconvergence_error(
2522 context,
2523 &format!("terminal value-only certificate evaluation failed: {err}"),
2524 result,
2525 result.final_grad_norm,
2526 StationarityBound::unrecorded(outer_gradient_tolerance(config).abs),
2527 )
2528 })?
2529 .cost;
2530 if !value_only.is_finite() {
2531 return Err(outer_nonconvergence_error(
2532 context,
2533 "terminal value-only certificate evaluation returned a non-finite objective value",
2534 result,
2535 result.final_grad_norm,
2536 StationarityBound::unrecorded(outer_gradient_tolerance(config).abs),
2537 ));
2538 }
2539
2540 // Order four is reserved for the mint audit (#2359). A screening pass takes
2541 // the same first-order evidence the no-analytic-Hessian path already
2542 // certifies on, so the multi-start keeps its filter without building the
2543 // order-four family tower once per seed.
2544 // One boolean drives BOTH the request below and the requirement at the
2545 // analytic-Hessian block: a pass that does not ask for curvature must not
2546 // then refuse the candidate for not supplying it. Splitting them made every
2547 // screened candidate of an analytic-Hessian objective fail certification
2548 // with "declared analytic curvature but returned none at the final point" —
2549 // a statement about this pass's own eval order, not about the candidate.
2550 let wants_analytic_hessian =
2551 capability.hessian.is_analytic() && matches!(fidelity, CertificationFidelity::Mint);
2552 let order = if wants_analytic_hessian {
2553 OuterEvalOrder::ValueGradientHessian
2554 } else {
2555 OuterEvalOrder::ValueAndGradient
2556 };
2557 let evaluation = obj.eval_with_order(&result.rho, order).map_err(|err| {
2558 outer_nonconvergence_error(
2559 context,
2560 &format!("analytic final-point evaluation failed: {err}"),
2561 result,
2562 result.final_grad_norm,
2563 StationarityBound::unrecorded(outer_gradient_tolerance(config).abs),
2564 )
2565 })?;
2566 if !inner_solve_converged(config.outer_inner_cap.as_ref()) {
2567 return Err(outer_nonconvergence_error(
2568 context,
2569 "terminal analytic evidence was evaluated at a non-converged inner state",
2570 result,
2571 None,
2572 StationarityBound::unrecorded(outer_gradient_tolerance(config).abs),
2573 ));
2574 }
2575 layout
2576 .validate_gradient_len(&evaluation.gradient, "outer certificate gradient")
2577 .map_err(|err| {
2578 outer_nonconvergence_error(
2579 context,
2580 &format!("malformed analytic final gradient: {err}"),
2581 result,
2582 None,
2583 StationarityBound::unrecorded(outer_gradient_tolerance(config).abs),
2584 )
2585 })?;
2586 if !evaluation.cost.is_finite() || evaluation.gradient.iter().any(|value| !value.is_finite()) {
2587 return Err(outer_nonconvergence_error(
2588 context,
2589 "the analytic final-point value or gradient is non-finite",
2590 result,
2591 None,
2592 StationarityBound::unrecorded(outer_gradient_tolerance(config).abs),
2593 ));
2594 }
2595
2596 let bounds = outer_bounds_template(config, layout.n_params);
2597 // A penalty creeping toward the ±rho_bound infinite-smoothing ceiling never reaches
2598 // it EXACTLY — each outer step only shrinks the gap, so it lands strictly inside the
2599 // box (the #2299 checkpoint sits at ρ=29.9938, not 30). `certificate_railed_lambdas`
2600 // then flags it railed via `CERTIFICATE_RAIL_MARGIN`, but the exact `x >= upper` /
2601 // `x <= lower` box-KKT projection treats it as INTERIOR and its outward pull inflates
2602 // |Pg| above the (tiny) stationarity bound — the fit refuses a genuine railed optimum.
2603 // Project the stationarity residual with the box endpoints relaxed inward by that SAME
2604 // rail margin, so "railed" means ONE thing to the detector AND the projector: a
2605 // within-tolerance coordinate whose gradient points OUT of the box has its KKT-multiplier
2606 // component removed rather than counted as a stationarity residual (#2299). The
2607 // projection only zeros the OUTWARD half (`.max(0.0)`/`.min(0.0)`), so a coordinate near
2608 // the bound that still has feasible-descent gradient keeps it and is never falsely
2609 // certified.
2610 let rail_projection_bounds = rail_relaxed_bounds(&bounds);
2611 let grad_norm = evaluation.gradient.dot(&evaluation.gradient).sqrt();
2612 // The terminal inner coefficients β(ρ̂), published by the REML bridge on
2613 // every eval (`inner_beta_hint`). Used to scale the estimand tolerance for
2614 // the asymptote-rail certificate (#2348 Inc 1).
2615 let terminal_beta = evaluation.inner_beta_hint.clone();
2616 // KKT-projected gradient VECTOR (not just its norm): the norm feeds the
2617 // stationarity certificate below, and the vector feeds the curvature-scaled
2618 // flat-valley Newton decrement (#2253/#2249/#2015) once the analytic Hessian
2619 // is in hand.
2620 let projected_gradient = project_gradient_vector(
2621 &result.rho,
2622 &evaluation.gradient,
2623 Some(&rail_projection_bounds),
2624 );
2625 let projected_grad_norm = projected_gradient.iter().map(|v| v * v).sum::<f64>().sqrt();
2626 let solver_bound = outer_gradient_tolerance(config).threshold(evaluation.cost, grad_norm);
2627 let mut bound_source = StationarityBoundSource::SolverBand;
2628 let mut stationarity_bound = if matches!(
2629 result.operator_stop_reason,
2630 Some(OperatorTrustRegionStopReason::CostStallFlatValley)
2631 ) {
2632 let flat_valley = flat_valley_converged_grad_bound(evaluation.cost);
2633 if flat_valley > solver_bound {
2634 bound_source = StationarityBoundSource::FlatValleyScore;
2635 flat_valley
2636 } else {
2637 solver_bound
2638 }
2639 } else {
2640 solver_bound
2641 };
2642 // #2241 — a cost-stall exit carries the guard's measured probe-noise-floor
2643 // gradient bound σ̂/Δ. The certificate must judge the re-measured final
2644 // gradient against the same flat band the guard certified, or the guard's
2645 // noise-scale convergence would be granted in the loop and revoked here.
2646 if let Some(noise_bound) = result.flat_noise_grad_bound
2647 && noise_bound.is_finite()
2648 {
2649 if noise_bound > stationarity_bound {
2650 bound_source = StationarityBoundSource::ProbeNoiseFloor;
2651 stationarity_bound = noise_bound;
2652 }
2653 }
2654 audit_outer_value_agreement(
2655 context,
2656 value_only,
2657 evaluation.cost,
2658 result,
2659 Some(projected_grad_norm),
2660 StationarityBound::from_ladder(stationarity_bound, bound_source),
2661 )?;
2662
2663 // The optimizer's own recorded best-iterate evidence, captured before the
2664 // fresh certificate-time measurement overwrites it below. Together with
2665 // `evaluation` this is a SECOND independent measurement of the objective
2666 // at the same ρ — the raw material for the gradient-reproducibility floor
2667 // further down, at zero additional objective evaluations.
2668 let run_recorded_gradient = result.final_gradient.take();
2669 let run_recorded_value = result.final_value;
2670
2671 // Install measured first-order evidence before any fallible curvature
2672 // processing. If curvature is malformed, the retained resume checkpoint
2673 // still carries the exact value/gradient that caused certification to stop.
2674 result.final_value = evaluation.cost;
2675 result.final_grad_norm = Some(projected_grad_norm);
2676 result.final_gradient = Some(evaluation.gradient);
2677 result.converged = false;
2678
2679 let analytic_hessian = if wants_analytic_hessian {
2680 match evaluation.hessian.materialize_dense() {
2681 Ok(Some(hessian)) => {
2682 layout
2683 .validate_hessian_shape(&hessian, "outer certificate Hessian")
2684 .map_err(|err| {
2685 outer_nonconvergence_error(
2686 context,
2687 &format!("malformed analytic final Hessian: {err}"),
2688 result,
2689 Some(projected_grad_norm),
2690 StationarityBound::from_ladder(stationarity_bound, bound_source),
2691 )
2692 })?;
2693 if hessian.iter().any(|value| !value.is_finite()) {
2694 return Err(outer_nonconvergence_error(
2695 context,
2696 "the analytic final Hessian contains non-finite entries",
2697 result,
2698 Some(projected_grad_norm),
2699 StationarityBound::from_ladder(stationarity_bound, bound_source),
2700 ));
2701 }
2702 Some(hessian)
2703 }
2704 Ok(None) => {
2705 return Err(outer_nonconvergence_error(
2706 context,
2707 "the objective declared analytic curvature but returned none at the final point",
2708 result,
2709 Some(projected_grad_norm),
2710 StationarityBound::from_ladder(stationarity_bound, bound_source),
2711 ));
2712 }
2713 Err(err) => {
2714 return Err(outer_nonconvergence_error(
2715 context,
2716 &format!("analytic final Hessian could not be certified: {err}"),
2717 result,
2718 Some(projected_grad_norm),
2719 StationarityBound::from_ladder(stationarity_bound, bound_source),
2720 ));
2721 }
2722 }
2723 } else {
2724 None
2725 };
2726
2727 // Curvature-scaled stationarity (#2253/#2249/#2015/#2091). The re-measured
2728 // projected gradient can sit modestly ABOVE the score-relative / probe-noise
2729 // bands even though NO step reduces the objective by more than the outer
2730 // tolerance — a weakly-identified small-n fit reaches this by a flat-valley
2731 // cost-stall, and an *already-stationary* fit reaches it at iteration 0 when
2732 // the plan search exhausts without stepping (a 2-parameter Gaussian-linear
2733 // REML lands λ→0 at a genuine interior optimum whose |Pg|≈1e-7 sits just above
2734 // an absolute score·1e-9 gradient floor tighter than the REML gradient's
2735 // matrix-factorization round-off). Whether that residual is genuine descent is
2736 // a second-order question the flat bands above cannot answer: they are
2737 // gradient-magnitude tests, blind to how the local curvature maps a gradient
2738 // to an objective change. The Newton decrement `½·gᵀH⁻¹g` (see
2739 // `newton_predicted_decrease`) IS that map — the exact predicted improvement of
2740 // a safeguarded second-order step. When it is below the outer objective
2741 // tolerance, the point is stationary at the resolution the criterion can be
2742 // optimized ("no further descent possible"), independent of HOW the solver
2743 // stopped.
2744 //
2745 // Applied whenever a PSD-along-gradient analytic Hessian is in hand (NOT gated
2746 // to a specific exit reason: the decrement test is the certificate, the exit
2747 // reason is not). It can NEVER wrongly certify a fit with real available
2748 // descent: it only widens when `curvature_grad_bound > stationarity_bound`, so
2749 // a well-identified fit that already clears `solver_bound` is untouched; a
2750 // gradient aligned with a near-flat Hessian direction inflates the decrement
2751 // and is rejected; a globally indefinite Hessian is rejected independently by
2752 // the `hessian_psd` gate inside `certifies()`. The derived widening is a
2753 // genuine, direction-aware curvature-scaled GRADIENT bound — the largest ‖Pg‖
2754 // that, in this gradient's direction under this curvature, still predicts a
2755 // decrease of exactly `objective_tol` — not a constant bump: because the
2756 // decrement scales quadratically with ‖g‖ at fixed direction, that bound is
2757 // `‖Pg‖·√(objective_tol/Δpred)`, which clears the actual ‖Pg‖ iff
2758 // `Δpred ≤ objective_tol`.
2759 if let Some(hessian) = analytic_hessian.as_ref()
2760 && let Some(predicted_decrease) = newton_predicted_decrease(hessian, &projected_gradient)
2761 && predicted_decrease.is_finite()
2762 && predicted_decrease > 0.0
2763 {
2764 // The SAME relative cost floor the cost-stall guard used to declare the
2765 // criterion stalled (run_plan.rs), so certification asserts nothing
2766 // tighter than the loop already proved about this surface.
2767 let objective_tol = outer_rel_cost_floor(config) * (1.0 + evaluation.cost.abs());
2768 let curvature_grad_bound =
2769 projected_grad_norm * (objective_tol / predicted_decrease).sqrt();
2770 if curvature_grad_bound.is_finite() && curvature_grad_bound > stationarity_bound {
2771 log::info!(
2772 "[CERTIFICATE] {context}: curvature-scaled flat-valley bound {curvature_grad_bound:.3e} \
2773 (|Pg|={projected_grad_norm:.3e}, Newton ½gᵀH⁻¹g={predicted_decrease:.3e} ≤ tol {objective_tol:.3e}) \
2774 widened from gradient-band {stationarity_bound:.3e}"
2775 );
2776 stationarity_bound = curvature_grad_bound;
2777 bound_source = StationarityBoundSource::CurvatureResolvability;
2778 }
2779 }
2780
2781 // Gradient-reproducibility floor (#2299 fully-saturated smooth). A
2782 // stationarity certificate cannot resolve below the reproducibility of its
2783 // own measuring instrument: at a rail-adjacent optimum (λ ~ 1e12, the term
2784 // collapsed onto its penalty null space, edf saturated) the analytic
2785 // gradient is a difference of enormous canceling log-det terms whose
2786 // evaluation drifts run to run, so |Pg| measures round-off, not slope —
2787 // observed as the SAME ρ returning |g| ∈ {2.5e-3 … 4.5e-2} across
2788 // consecutive evaluations while the objective stays flat to 1e-7.
2789 //
2790 // The certifier already holds TWO independent measurements at this ρ: the
2791 // optimizer's recorded best-iterate gradient (`run_recorded_gradient`) and
2792 // the fresh certificate-time `evaluation` — so the instrument's
2793 // demonstrated noise costs ZERO additional objective evaluations (scripted
2794 // test objectives keep their exact call counts). A REAL residual gradient
2795 // reproduces (spread ≈ 0, no widening — genuine descent can never be
2796 // masked, and a deterministic objective yields bit-identical pairs), while
2797 // cancellation noise decorrelates (spread ~ |Pg|). The widening is gated
2798 // on the two measurements' objective VALUES agreeing to the same relative
2799 // floor the cost-stall guard uses, and the PSD gate below is unchanged.
2800 if projected_grad_norm > stationarity_bound
2801 && let Some(prior_gradient) = run_recorded_gradient.as_ref()
2802 && layout
2803 .validate_gradient_len(prior_gradient, "outer run-recorded gradient")
2804 .is_ok()
2805 && prior_gradient.iter().all(|value| value.is_finite())
2806 && run_recorded_value.is_finite()
2807 {
2808 const GRADIENT_REPRODUCIBILITY_WIDENING: f64 = 2.0;
2809 let objective_tol = config
2810 .rel_cost_tolerance
2811 .unwrap_or(config.tolerance * 1.0e-2)
2812 .max(COST_STALL_REL_TOL_FLOOR)
2813 * (1.0 + evaluation.cost.abs());
2814 let cost_drift = (run_recorded_value - evaluation.cost).abs();
2815 let prior_projected =
2816 project_gradient_vector(&result.rho, prior_gradient, Some(&rail_projection_bounds));
2817 let spread = (&prior_projected - &projected_gradient)
2818 .iter()
2819 .map(|v| v * v)
2820 .sum::<f64>()
2821 .sqrt();
2822 let repro_bound = GRADIENT_REPRODUCIBILITY_WIDENING * spread;
2823 if cost_drift <= objective_tol
2824 && repro_bound.is_finite()
2825 && repro_bound > stationarity_bound
2826 && projected_grad_norm <= repro_bound
2827 {
2828 log::info!(
2829 "[CERTIFICATE] {context}: gradient-reproducibility floor widened the \
2830 stationarity bound to {repro_bound:.3e} (|Pg|={projected_grad_norm:.3e}, \
2831 same-ρ spread between the run-recorded and certificate-time gradients \
2832 {spread:.3e}, cost drift {cost_drift:.3e} ≤ tol {objective_tol:.3e})"
2833 );
2834 stationarity_bound = repro_bound;
2835 bound_source = StationarityBoundSource::GradientReproducibility;
2836 }
2837 }
2838
2839 // #2458 -- the bound's own provenance, emitted UNCONDITIONALLY rather than only
2840 // when a rung happens to widen. A certificate that does not carry which of its
2841 // five terms decided it can only be re-derived from source, never audited; the
2842 // same complaint this file's refusal messages make about the fits they refuse.
2843 // `derived_standard=false` is the actionable bit: it says this verdict rests on
2844 // a gradient-magnitude substitute because the resolvability form was
2845 // unavailable on this route, not because the problem called for it.
2846 log::info!(
2847 "[CERTIFICATE-BOUND] {context}: bound {stationarity_bound:.6e} set by {} \
2848 (derived_standard={}, |Pg|={projected_grad_norm:.6e}, |g|={grad_norm:.6e}, \
2849 solver_band={solver_bound:.6e}, cost={:.6e})",
2850 bound_source.label(),
2851 bound_source.is_derived_standard(),
2852 evaluation.cost,
2853 );
2854
2855 // Large-step flatness certificate (#2299 fully-saturated smooth). After the
2856 // reproducibility floor a coordinate that has collapsed EXACTLY onto its
2857 // penalty null space (λ ~ 1e12, edf saturated) can still carry a projected
2858 // gradient component that is DETERMINISTIC cancellation bias from the
2859 // 1e12-conditioned logdet derivative (≈ ε·κ·scale). Being deterministic it
2860 // reproduces run to run, so the spread-keyed reproducibility floor above
2861 // cannot see it; and the Newton decrement anti-rescues, because the near-null
2862 // Hessian direction inflates gᵀH⁻¹g by design. The decisive question is
2863 // second-order-independent: does the criterion actually MOVE along that
2864 // coordinate at MACROSCOPIC scale? This block answers it directly — it probes
2865 // the objective a full e-fold in λ to either side of a near-null-curvature
2866 // coordinate and, for coordinates whose value is provably flat there, removes
2867 // their measured gradient component (numerical bias, not slope) from the
2868 // projected gradient before the bound test. A coordinate whose large-step
2869 // value MOVES is left untouched, so a genuine pseudologdet ramp still refuses.
2870 //
2871 // Gated as narrowly as possible: it runs only when the certificate would
2872 // OTHERWISE refuse on |Pg|, only with an analytic Hessian that is PSD-within-
2873 // noise in hand, and only probes coordinates whose curvature row is below the
2874 // roundoff floor — so a well-conditioned objective (every scripted mock at its
2875 // certification point) probes nothing and pays zero extra evaluations.
2876 // The coordinates railed at ±rho_bound (the infinite-smoothing ceiling). Their
2877 // saturated curvature direction makes the FULL Hessian indefinite, so the
2878 // flatness certificate below — and the final curvature gate — judge PSD on the
2879 // interior (un-railed) sub-block instead, or a rail-caused indefiniteness would
2880 // disable the very certificate that exists to certify a railed optimum (#2299).
2881 // The FACE the certificate reasons on: every θ coordinate against its own
2882 // bound, ρ and non-ρ alike. See `certificate_railed_coordinates` for why
2883 // the λ-block report cannot be used here.
2884 let certificate_railed = certificate_railed_coordinates(&result.rho, config);
2885 // The λ-block REPORT that ships on the certificate. Same predicate,
2886 // narrower scan, because `lambdas_railed` indexes smoothing parameters.
2887 let railed_lambda_block = certificate_railed_lambdas(&result.rho, layout.rho_dim(), config);
2888
2889 // Typed stationary-at-asymptote rail certificate (#2348 Inc 1, #2299 layer 3,
2890 // #2337 Thm 2.1). Before falling through to the generic gradient/criterion-flat
2891 // verdict, POSITIVELY certify a railed optimum: the interior (non-railed)
2892 // coordinates are gradient-stationary, and each coordinate railed at the
2893 // infinite-/zero-smoothing box bound sits on a confirmed exponential tail whose
2894 // fitted model has already reached the rail limit to within the estimand
2895 // tolerance. This supersedes the untyped `lambdas_railed` flag with a proof that
2896 // the criterion improvement and coefficient travel still available by running to
2897 // the rail are both below tolerance.
2898 //
2899 // Computed in its own statement so the borrow of `analytic_hessian` ends before
2900 // the mint branch moves it onto the result. Gated to a genuinely railed optimum
2901 // with outward pull (`grad_norm` above the stationarity bound) and an analytic
2902 // Hessian: a well-conditioned interior fit, or a coordinate merely resting near a
2903 // bound with a vanishing gradient, probes nothing and keeps its ordinary verdict.
2904 let asymptote_objective_tol = config
2905 .rel_cost_tolerance
2906 .unwrap_or(config.tolerance * 1.0e-2)
2907 .max(COST_STALL_REL_TOL_FLOOR)
2908 * (1.0 + evaluation.cost.abs());
2909 let rail_outcome = match analytic_hessian.as_ref() {
2910 Some(hessian) if !certificate_railed.is_empty() && grad_norm > stationarity_bound => {
2911 Some(try_certify_asymptote_rail(
2912 obj,
2913 &AsymptoteRailInputs {
2914 rho: &result.rho,
2915 projected_gradient: &projected_gradient,
2916 railed: &certificate_railed,
2917 hessian,
2918 bounds: &bounds,
2919 terminal_beta: terminal_beta.as_ref(),
2920 stationarity_bound,
2921 objective_tol: asymptote_objective_tol,
2922 context,
2923 },
2924 )?)
2925 }
2926 _ => None,
2927 };
2928 // A refused railed mint carries its typed decline reason into the final
2929 // refusal summary (mirroring the tail-snap decline note), so a railed
2930 // non-mint names the gate that refused instead of failing silently.
2931 let mut asymptote_rail_note: Option<String> = None;
2932 let mut probes_ran = rail_outcome.is_some();
2933 if let Some(outcome) = rail_outcome {
2934 match outcome {
2935 Err(reason) => asymptote_rail_note = Some(reason),
2936 Ok(minted) => {
2937 let (interior_projected_grad_norm, effective_interior_bound, rails) = minted;
2938 // The tail probes were derivative-bearing evaluations at probe
2939 // ρ's, so the EVALUATOR-side terminal-mode carrier now owns the
2940 // last probe, not the checkpoint (#2155 regression: every
2941 // custom-family at-point mint then failed the bitwise terminal
2942 // theta identity at fit assembly). Re-evaluate at the minted
2943 // point and ship ITS numbers as the terminal facts: the same
2944 // evaluation sets the evaluator carrier, so the optimizer
2945 // certificate and the owned mode are bitwise-identical by
2946 // construction. The certified stationarity facts (interior
2947 // norms, rails) remain the judged ones.
2948 let restored = obj
2949 .eval_with_order(&result.rho, OuterEvalOrder::ValueAndGradient)
2950 .map_err(|err| {
2951 EstimationError::RemlOptimizationFailed(format!(
2952 "{context}: failed to re-own the certified point after \
2953 asymptote-rail probing: {err}"
2954 ))
2955 })?;
2956 result.final_value = restored.cost;
2957 let restored_projected = project_gradient_vector(
2958 &result.rho,
2959 &restored.gradient,
2960 Some(&rail_projection_bounds),
2961 );
2962 result.final_grad_norm = Some(
2963 restored_projected
2964 .iter()
2965 .map(|v| v * v)
2966 .sum::<f64>()
2967 .sqrt(),
2968 );
2969 result.final_gradient = Some(restored.gradient);
2970 let certificate = OuterCriterionCertificate {
2971 stationarity: OuterStationarityCertificate::AsymptoteRail {
2972 interior_projected_grad_norm,
2973 // The bound that admitted the interior: the raw stationarity
2974 // bound, or the curvature-scaled flat-valley widening when the
2975 // interior sub-block's Newton decrement is below the loop's
2976 // cost resolution (shared judgment with the Inc 2c mint).
2977 bound: effective_interior_bound,
2978 rails,
2979 },
2980 hessian_psd: Some(true),
2981 lambdas_railed: railed_lambda_block.clone(),
2982 curvature_floor: None,
2983 };
2984 // Move the certified curvature onto the result; the mint path returns
2985 // immediately, so the fall-through below never observes the move.
2986 result.final_hessian = analytic_hessian;
2987 result.criterion_certificate = Some(certificate.clone());
2988 if !certificate.certifies() {
2989 result.converged = false;
2990 return Err(outer_nonconvergence_error(
2991 context,
2992 &certificate.summary(),
2993 result,
2994 Some(interior_projected_grad_norm),
2995 StationarityBound::from_ladder(stationarity_bound, bound_source),
2996 ));
2997 }
2998 result.converged = true;
2999 result.converged_via = Some(OuterConvergedVia::AsymptoteStationary {
3000 rails: certificate.stationarity.rails().len(),
3001 });
3002 log::info!("[CERTIFICATE] {context}: {}", certificate.summary());
3003 return Ok(certificate);
3004 }
3005 }
3006 }
3007
3008 let mut certified_projected_grad_norm = projected_grad_norm;
3009 if projected_grad_norm > stationarity_bound
3010 && let Some(hessian) = analytic_hessian.as_ref()
3011 && certificate_hessian_is_psd_off_railed(hessian, &certificate_railed) == Some(true)
3012 {
3013 let n = layout.n_params;
3014 // Curvature scale of the analytic outer Hessian: its dominant diagonal,
3015 // the same ‖H‖ scale `certificate_hessian_is_psd` and
3016 // `newton_predicted_decrease` regularize against. A coordinate's curvature
3017 // ROW is indistinguishable from the assembly's roundoff — it has no
3018 // curvature the arithmetic can resolve and has collapsed onto the penalty
3019 // null space — when its largest entry falls below the SAME √ε·‖H‖ margin
3020 // those two probes use to separate a real curvature direction from
3021 // O(ε·‖H‖) accumulation noise. This is the derivation of the threshold:
3022 // NULL_CURVATURE_REL = √ε (machine epsilon's square root, the assembled
3023 // Hessian's relative resolution), scaled by the Hessian's own max-diagonal
3024 // magnitude, floored at 1 exactly as the PSD/Newton shift is.
3025 let max_diag = (0..n).fold(0.0_f64, |acc, j| acc.max(hessian[[j, j]].abs()));
3026 let null_curvature_threshold = f64::EPSILON.sqrt() * max_diag.max(1.0);
3027 // The SAME relative cost floor the cost-stall guard and both widenings
3028 // above use: certification asserts nothing tighter about this surface's
3029 // macroscopic flatness than the loop already proved.
3030 let objective_tol = config
3031 .rel_cost_tolerance
3032 .unwrap_or(config.tolerance * 1.0e-2)
3033 .max(COST_STALL_REL_TOL_FLOOR)
3034 * (1.0 + evaluation.cost.abs());
3035 // One e-fold in log-λ per coordinate (ρ IS log-λ): the +δ/−δ pair spans e²
3036 // in λ, a macroscopic move across which no genuine descent slope can hide.
3037 const LARGE_STEP_DELTA: f64 = 1.0;
3038 let mut saturated_flat: Vec<usize> = Vec::new();
3039 let mut probe_reports: Vec<String> = Vec::new();
3040 let mut probed_any = false;
3041 let mut probe_failed = false;
3042 for k in 0..n {
3043 let row_inf = (0..n).fold(0.0_f64, |acc, j| acc.max(hessian[[k, j]].abs()));
3044 // Only near-null-curvature coordinates the measured gradient actually
3045 // loads on can be responsible for |Pg| exceeding the band; skip every
3046 // other coordinate, so no probe fires on a well-conditioned surface.
3047 if row_inf > null_curvature_threshold || projected_gradient[k] == 0.0 {
3048 continue;
3049 }
3050 let mut plus = result.rho.clone();
3051 plus[k] += LARGE_STEP_DELTA;
3052 let mut minus = result.rho.clone();
3053 minus[k] -= LARGE_STEP_DELTA;
3054 probed_any = true;
3055 let (Ok(cost_plus), Ok(cost_minus)) = (obj.eval_cost(&plus), obj.eval_cost(&minus))
3056 else {
3057 // A failed probe is not evidence of flatness — refuse to classify
3058 // (conservative) and leave |Pg| intact for the bound test.
3059 probe_failed = true;
3060 break;
3061 };
3062 if !cost_plus.is_finite() || !cost_minus.is_finite() {
3063 probe_failed = true;
3064 break;
3065 }
3066 let up = (cost_plus - evaluation.cost).abs();
3067 let down = (cost_minus - evaluation.cost).abs();
3068 if up <= objective_tol && down <= objective_tol {
3069 saturated_flat.push(k);
3070 probe_reports.push(format!("k={k} |ΔV|+={up:.3e} |ΔV|-={down:.3e}"));
3071 }
3072 }
3073 if !probe_failed && !saturated_flat.is_empty() {
3074 // Recompute |Pg| with the provably macroscopically-flat coordinates
3075 // removed: their measured gradient is deterministic cancellation bias,
3076 // not slope. Coordinates that moved keep their component and still count
3077 // against the bound.
3078 let reduced_sq = (0..n)
3079 .filter(|k| !saturated_flat.contains(k))
3080 .map(|k| projected_gradient[k] * projected_gradient[k])
3081 .sum::<f64>();
3082 certified_projected_grad_norm = reduced_sq.sqrt();
3083 let flat_list = saturated_flat
3084 .iter()
3085 .map(usize::to_string)
3086 .collect::<Vec<_>>()
3087 .join(", ");
3088 let probe_summary = probe_reports.join("; ");
3089 log::info!(
3090 "[CERTIFICATE] {context}: large-step flatness certificate classified \
3091 coordinate(s) [{flat_list}] saturated-flat (curvature row ≤ \
3092 {null_curvature_threshold:.3e}, probed Δ=±{LARGE_STEP_DELTA} with \
3093 {probe_summary}, cost-flat to tol {objective_tol:.3e}); projected \
3094 gradient reduced from {projected_grad_norm:.3e} to \
3095 {certified_projected_grad_norm:.3e}"
3096 );
3097 }
3098 // `eval_cost` warm-starts the inner solve, so the probes moved the objective
3099 // off the certified point. Restore it to ρ̂ once iff we actually probed, so
3100 // the downstream state (and the rho-uncertainty diagnostic) sees the fitted
3101 // point. A failure to re-evaluate the same ρ that certified moments ago is a
3102 // genuinely broken objective and refuses conservatively.
3103 if probed_any {
3104 obj.eval_cost(&result.rho).map_err(|err| {
3105 outer_nonconvergence_error(
3106 context,
3107 &format!(
3108 "failed to restore the objective to the certified point after \
3109 flatness probing: {err}"
3110 ),
3111 result,
3112 Some(certified_projected_grad_norm),
3113 StationarityBound::from_ladder(stationarity_bound, bound_source),
3114 )
3115 })?;
3116 }
3117 }
3118
3119 let certificate = OuterCriterionCertificate {
3120 stationarity: OuterStationarityCertificate::AnalyticGradient {
3121 grad_norm,
3122 projected_grad_norm: certified_projected_grad_norm,
3123 bound: stationarity_bound,
3124 },
3125 // The RAW measurement — unchanged, and what every consumer that asks
3126 // for a genuine PSD certificate keeps receiving.
3127 hessian_psd: analytic_hessian.as_ref().and_then(|hessian| {
3128 certificate_hessian_is_psd_off_railed(hessian, &certificate_railed)
3129 }),
3130 lambdas_railed: railed_lambda_block.clone(),
3131 // The floor's verdict on that same curvature, recorded beside it.
3132 curvature_floor: analytic_hessian.as_ref().and_then(|hessian| {
3133 interior_curvature_floor_clearance(hessian, &certificate_railed, &projected_gradient)
3134 }),
3135 };
3136 // Certify-time tail snap (#2348 Inc 2). About to refuse a point whose
3137 // residual gradient is carried by un-railed coordinates crawling a
3138 // CONFIRMED exponential tail toward the ρ-box (the one-e-fold-per-step
3139 // grind: the loop budget can exhaust strictly inside the box, where the
3140 // Inc 1 railed mint can never fire), snap those coordinates to their box
3141 // bound and publish that point as a one-shot optimization reseed. The
3142 // resumed optimizer lets coupled interior coordinates move before the
3143 // FULL Inc 1 rail discipline judges the result — this path grants nothing
3144 // by itself.
3145 let mut tail_snap_note: Option<String> = None;
3146 if allow_tail_snap
3147 && !certificate.certifies()
3148 && grad_norm > stationarity_bound
3149 && let Some(hessian) = analytic_hessian.as_ref()
3150 {
3151 probes_ran = true;
3152 match try_tail_snap_to_rail(
3153 obj,
3154 &AsymptoteRailInputs {
3155 rho: &result.rho,
3156 projected_gradient: &projected_gradient,
3157 railed: &certificate_railed,
3158 hessian,
3159 bounds: &bounds,
3160 terminal_beta: terminal_beta.as_ref(),
3161 stationarity_bound,
3162 objective_tol: asymptote_objective_tol,
3163 context,
3164 },
3165 )? {
3166 TailSnapOutcome::TailStationaryAtPoint {
3167 rails,
3168 interior_projected_grad_norm,
3169 effective_interior_bound,
3170 } => {
3171 // #2348 Inc 2c: the confirmed tails extrapolate below the bound
3172 // AT the checkpoint — mint the typed asymptote certificate for
3173 // the point as it stands. `hessian_psd` is the interior
3174 // sub-block verdict established before probing (the full
3175 // matrix is expected non-PD from the noise-corrupted tail
3176 // entry); the stored bound is the one that actually certified
3177 // the interior (raw, or the sub-block curvature-scaled
3178 // flat-valley bound).
3179 //
3180 // Re-own the minted point first: the tail probes were
3181 // derivative-bearing evaluations at probe ρ's, so the
3182 // evaluator-side terminal-mode carrier owns the last probe —
3183 // shipping the pre-probe terminal numbers then fails the
3184 // bitwise terminal theta identity at custom-family fit
3185 // assembly (the #2155 all-links regression). One fresh
3186 // evaluation at the checkpoint sets the carrier AND supplies
3187 // the terminal facts, so both sides are bitwise-identical by
3188 // construction.
3189 let restored = obj
3190 .eval_with_order(&result.rho, OuterEvalOrder::ValueAndGradient)
3191 .map_err(|err| {
3192 EstimationError::RemlOptimizationFailed(format!(
3193 "{context}: failed to re-own the certified point after \
3194 tail-snap probing: {err}"
3195 ))
3196 })?;
3197 result.final_value = restored.cost;
3198 let restored_projected = project_gradient_vector(
3199 &result.rho,
3200 &restored.gradient,
3201 Some(&rail_projection_bounds),
3202 );
3203 result.final_grad_norm = Some(
3204 restored_projected
3205 .iter()
3206 .map(|v| v * v)
3207 .sum::<f64>()
3208 .sqrt(),
3209 );
3210 result.final_gradient = Some(restored.gradient);
3211 let certificate = OuterCriterionCertificate {
3212 stationarity: OuterStationarityCertificate::AsymptoteRail {
3213 interior_projected_grad_norm,
3214 bound: effective_interior_bound,
3215 rails,
3216 },
3217 hessian_psd: Some(true),
3218 lambdas_railed: railed_lambda_block.clone(),
3219 curvature_floor: None,
3220 };
3221 result.final_hessian = analytic_hessian;
3222 result.criterion_certificate = Some(certificate.clone());
3223 if !certificate.certifies() {
3224 result.converged = false;
3225 return Err(outer_nonconvergence_error(
3226 context,
3227 &certificate.summary(),
3228 result,
3229 Some(interior_projected_grad_norm),
3230 StationarityBound::unrecorded(effective_interior_bound),
3231 ));
3232 }
3233 result.converged = true;
3234 result.converged_via = Some(OuterConvergedVia::AsymptoteStationary {
3235 rails: certificate.stationarity.rails().len(),
3236 });
3237 log::info!(
3238 "[CERTIFICATE] {context}: tail-stationary at the checkpoint \
3239 (#2348 Inc 2c): {}",
3240 certificate.summary()
3241 );
3242 return Ok(certificate);
3243 }
3244 TailSnapOutcome::ConfirmedNeedsReseed(snapped) => {
3245 log::info!(
3246 "[CERTIFICATE] {context}: confirmed exponential tail on un-railed \
3247 coordinate(s); publishing the snapped point {snapped} as a waypoint \
3248 for one re-optimization retry (#2348 Inc 2b / #2358)"
3249 );
3250 tail_snap_note = Some(
3251 "tail confirmed; retry seeded at the snapped rail waypoint".to_string(),
3252 );
3253 result.tail_snap_reseed = Some(snapped);
3254 }
3255 TailSnapOutcome::Declined(reason) => {
3256 tail_snap_note = Some(reason);
3257 }
3258 }
3259 }
3260 // Install the measured evidence before deciding its verdict. A rejected
3261 // candidate is retained only as a resumable checkpoint, and that
3262 // checkpoint must carry the actual analytic residual/curvature evidence
3263 // that caused the rejection rather than the optimizer's stale terminal
3264 // status.
3265 result.final_hessian = analytic_hessian;
3266 result.criterion_certificate = Some(certificate.clone());
3267 if !certificate.certifies() {
3268 result.converged = false;
3269 // Mint the #2392 reseeds fresh for THIS refused point: clear any value a
3270 // prior (multistart / pre-polish) certification of a different ρ left on
3271 // the result so the resume loop never consumes a stale pull-back/freeze.
3272 result.wrong_rail_reseed = None;
3273 result.active_set_reseed = None;
3274 // #2357 — saddle escape. The point is first-order stationary
3275 // (`is_stationary`: ‖Pg‖ ≤ bound) yet its INTERIOR reduced Hessian is a
3276 // certified strict saddle (`!curvature_admissible`). A railed coordinate
3277 // no longer waives this: `curvature_admissible` already reads the
3278 // off-railed reduced Hessian, and the escape descends only the free
3279 // (un-railed) directions while holding every rail fixed (#2155). That is
3280 // exactly the case a gradient-only
3281 // convergence gate mis-accepts: it arrived with the gradient already
3282 // below tolerance and stopped, leaving the certified negative-curvature
3283 // eigendirection — a strict descent — untaken. Mint a one-shot reseed
3284 // stepped off the ridge to a strictly-lower objective so the plan runner
3285 // can re-descend to the true PSD minimum, exactly as an identical
3286 // warm-started resume does by hand. Gated by `allow_tail_snap` (the same
3287 // one-shot reseed gate the tail snap rides) so the retry pass — which
3288 // runs with it `false` — can never recurse.
3289 if allow_tail_snap
3290 && certificate.is_stationary()
3291 && !certificate.curvature_admissible()
3292 && let Some(hessian) = result.final_hessian.clone()
3293 && let Some(gradient) = result.final_gradient.clone()
3294 {
3295 let saddle_rho = result.rho.clone();
3296 let baseline_cost = result.final_value;
3297 // `curvature_admissible()` is `false` exactly when the REDUCED
3298 // (off-railed) Hessian is indefinite, so a railed coordinate no
3299 // longer waives the escape: it is passed through and held fixed while
3300 // the step descends the free-direction saddle (#2155). It must be
3301 // held fixed on the SAME face the reduction was taken on — the
3302 // certificate's λ-block report would leave a railed ψ free to be
3303 // stepped out of its box.
3304 result.saddle_escape_reseed = negative_curvature_escape_point(
3305 obj,
3306 &saddle_rho,
3307 &gradient,
3308 &hessian,
3309 &certificate_railed,
3310 baseline_cost,
3311 &bounds,
3312 context,
3313 );
3314 }
3315 // #2392 — wrong-rail pull-back and active-set reduction. A coordinate at
3316 // the ρ box whose deep-λ terminal gradient is instrument noise leaves the
3317 // outer search unable to move it: the trust region's local model is flat
3318 // there. Two evidence-gated one-shot reseeds recover the fit (both gated
3319 // by `allow_tail_snap` so the retry pass cannot recurse):
3320 // (1) WRONG-RAIL PULL-BACK: the coordinate's clean-band probes (a few
3321 // e-folds inside, above the noise floor) prove the objective
3322 // DECREASES inward — it was driven to the wrong bound. Reseed it at
3323 // its clean-band interior scale so the optimizer descends to the
3324 // true interior optimum. Fires ONLY on the opposite-sign clean-tail
3325 // proof, so a genuine λ→∞/λ→0 rail is never pulled off its bound.
3326 // (2) ACTIVE-SET REDUCTION: no wrong rail, but the INTERIOR is not
3327 // stationary while a rail is present — the railed coordinate's
3328 // ill-conditioned Hessian row poisons the joint step. Freeze the
3329 // rail(s) at their bound and re-run so the interior converges in the
3330 // reduced space; the plan runner re-certifies the polished point
3331 // under the ORIGINAL box, so a frozen coordinate whose gradient
3332 // turns inward there un-freezes (no silent clamping).
3333 // (1) takes precedence: a wrong rail must be pulled back, never frozen.
3334 if allow_tail_snap && !certificate_railed.is_empty() {
3335 let beta_norm = terminal_beta
3336 .as_ref()
3337 .map(|b| b.dot(b).sqrt())
3338 .filter(|v| v.is_finite())
3339 .unwrap_or(0.0);
3340 let mut rail_tol =
3341 AsymptoteTolerances::exp4_rail_bands(ASYMPTOTE_ESTIMAND_REL_TOL * (1.0 + beta_norm));
3342 rail_tol.tail_drift_rel = TAIL_SNAP_DRIFT_REL;
3343 let (lower, upper) = &bounds;
3344 let mut wrong_rail_point: Option<Array1<f64>> = None;
3345 for &k in certificate_railed.iter() {
3346 if k >= result.rho.len() || k >= lower.len() || k >= upper.len() {
3347 continue;
3348 }
3349 let side = if (upper[k] - result.rho[k]).abs() <= (result.rho[k] - lower[k]).abs() {
3350 AsymptoteSide::Upper
3351 } else {
3352 AsymptoteSide::Lower
3353 };
3354 if let Some(target) = detect_wrong_rail_pullback(
3355 obj,
3356 &result.rho,
3357 k,
3358 side,
3359 &rail_tol,
3360 (lower[k], upper[k]),
3361 )? {
3362 let mut reseed = result.rho.clone();
3363 reseed[k] = target;
3364 wrong_rail_point = Some(reseed);
3365 break;
3366 }
3367 }
3368 if let Some(reseed) = wrong_rail_point {
3369 result.wrong_rail_reseed = Some(reseed);
3370 } else if let Some(hessian) = result.final_hessian.as_ref() {
3371 // Active-set reduction needs curvature to prove that the free
3372 // subspace is genuinely unpolished. Wrong-rail pull-back above
3373 // is a first-order tail-sign proof and deliberately does NOT:
3374 // gradient-only BFGS objectives can rail incorrectly too, and
3375 // withholding a valid pull-back merely because they do not
3376 // materialize H would make recovery depend on solver class.
3377 let interior_indices: Vec<usize> = (0..projected_gradient.len())
3378 .filter(|k| !certificate_railed.contains(k))
3379 .collect();
3380 let interior_not_stationary = !interior_indices.is_empty()
3381 && certify_interior_stationarity(
3382 &projected_gradient,
3383 &hessian,
3384 &interior_indices,
3385 stationarity_bound,
3386 asymptote_objective_tol,
3387 )
3388 .is_err();
3389 if interior_not_stationary {
3390 let mut froz_lower = lower.clone();
3391 let mut froz_upper = upper.clone();
3392 let mut reseed = result.rho.clone();
3393 let mut froze_any = false;
3394 for &k in certificate_railed.iter() {
3395 if k >= reseed.len() {
3396 continue;
3397 }
3398 let rail = if (upper[k] - reseed[k]).abs() <= (reseed[k] - lower[k]).abs() {
3399 upper[k]
3400 } else {
3401 lower[k]
3402 };
3403 reseed[k] = rail;
3404 froz_lower[k] = rail;
3405 froz_upper[k] = rail;
3406 froze_any = true;
3407 }
3408 if froze_any {
3409 result.active_set_reseed = Some(ActiveSetReseed {
3410 rho: reseed,
3411 bounds: (froz_lower, froz_upper),
3412 });
3413 }
3414 }
3415 }
3416 }
3417 // Carry the railed-mint and tail-snap decline evidence into the
3418 // refusal so a railed or budget-exhausted crawl explains which
3419 // certificate gate refused instead of failing silently.
3420 let mut summary = certificate.summary();
3421 // `summary()` prints `lambdas_railed`, which is the λ-block report. When
3422 // the face the certificate actually reasoned on is wider — a joint
3423 // [ρ, ψ] search with a κ coordinate on its data-derived window — say so,
3424 // or the refusal reads as `railed=[]` while the projector has already
3425 // discarded that coordinate's outward pull and nothing explains where
3426 // `|g|` went (#979).
3427 if certificate_railed.len() != railed_lambda_block.len() {
3428 summary = format!(
3429 "{summary}; outer coordinates railed (theta-wide, incl. non-rho blocks): \
3430 {certificate_railed:?}"
3431 );
3432 }
3433 if let Some(note) = asymptote_rail_note {
3434 summary = format!("{summary}; asymptote-rail declined: {note}");
3435 }
3436 let summary = match tail_snap_note {
3437 Some(note) => format!("{summary}; tail-snap declined: {note}"),
3438 None => summary,
3439 };
3440 return Err(outer_nonconvergence_error(
3441 context,
3442 &summary,
3443 result,
3444 Some(certified_projected_grad_norm),
3445 StationarityBound::from_ladder(stationarity_bound, bound_source),
3446 ));
3447 }
3448
3449 // #2155 regression, the LAST carrier-stealing path: the rail-mint and
3450 // tail-snap attempts probe with derivative-bearing evaluations, and the
3451 // ORDINARY certificate can still certify after a declined attempt (e.g. a
3452 // KKT-railed projection whose raw gradient norm sits above the bound), so
3453 // this success would ship pre-probe terminal numbers while the evaluator's
3454 // terminal-mode carrier owns the last probe — refusing the bitwise theta
3455 // identity at custom-family fit assembly. Re-own the certified point with
3456 // one fresh evaluation and ship ITS numbers; the mint branches re-own for
3457 // themselves before their early returns, and the judged stationarity facts
3458 // above remain the measured pre-probe ones.
3459 if probes_ran {
3460 let restored = obj
3461 .eval_with_order(&result.rho, OuterEvalOrder::ValueAndGradient)
3462 .map_err(|err| {
3463 EstimationError::RemlOptimizationFailed(format!(
3464 "{context}: failed to re-own the certified point after rail/tail probing: {err}"
3465 ))
3466 })?;
3467 result.final_value = restored.cost;
3468 let restored_projected = project_gradient_vector(
3469 &result.rho,
3470 &restored.gradient,
3471 Some(&rail_projection_bounds),
3472 );
3473 result.final_grad_norm = Some(
3474 restored_projected
3475 .iter()
3476 .map(|v| v * v)
3477 .sum::<f64>()
3478 .sqrt(),
3479 );
3480 result.final_gradient = Some(restored.gradient);
3481 }
3482 result.converged = true;
3483 // #2235/#2241 — record WHICH certificate concluded this run. A
3484 // Fellner–Schall model-state fixed point was pre-stamped by the runner and
3485 // is preserved (this analytic certificate is its corroborating evidence);
3486 // otherwise the verdict is decided by which stationarity band the measured
3487 // projected gradient actually cleared: the solver's own tolerance
3488 // (gradient-stationary) or only the widened flat certificate band
3489 // (criterion-flat, #2241).
3490 result.converged_via = match result.converged_via {
3491 Some(via @ OuterConvergedVia::RecurrentIncumbent { .. }) => Some(via),
3492 _ if certified_projected_grad_norm <= solver_bound => {
3493 Some(OuterConvergedVia::GradientStationary)
3494 }
3495 _ => Some(OuterConvergedVia::CriterionFlat {
3496 residual_grad_norm: certified_projected_grad_norm,
3497 certificate_bound: stationarity_bound,
3498 }),
3499 };
3500 log::info!("[CERTIFICATE] {context}: {}", certificate.summary());
3501 Ok(certificate)
3502}
3503
3504/// Estimand tolerance relative to the fitted coefficient scale for the
3505/// asymptote-rail certificate (#2348 Inc 1): the remaining coefficient travel
3506/// to the rail limit must fall below `ASYMPTOTE_ESTIMAND_REL_TOL·(1 + ‖β‖)` for
3507/// the fitted model to be certified equal to the rail-limit fit.
3508const ASYMPTOTE_ESTIMAND_REL_TOL: f64 = 1.0e-4;
3509
3510/// Number of one-e-fold-in-`ρ` probes stepped back from a railed coordinate
3511/// toward the interior when reconstructing its exponential tail (#2348 Inc 1).
3512/// Enough to span both the finite-difference floor next to the rail (rejected)
3513/// and a confirmable-tail run further in.
3514// 18 e-folds: the window must REACH the finite-difference-clean constant-ĉ
3515// band from a coordinate railed AT the box ceiling. The fused-Hessian
3516// trajectory (#2348) rails fits at ρ=30 that previously stalled mid-box, and
3517// the #2299 fixture's clean band sits 13–16 e-folds inside — the old 12-probe
3518// window (sized for mid-box crawls) stopped one row short of it, so a fully
3519// confirmed tail declined with "no finite-difference-clean tail window". Six
3520// extra value+gradient evals, paid only at certification of railed fits.
3521const ASYMPTOTE_PROBE_COUNT: usize = 18;
3522
3523/// Local confirmation resolution used only when the one-e-fold ladder cannot
3524/// find a clean run. A finite smoothing box can intersect a perfectly regular
3525/// asymptote before three whole e-folds of the leading-order tail are visible;
3526/// half-e-fold probes resolve that band without relaxing any certificate gate.
3527///
3528/// The probes remain equally spaced, which is load-bearing: the estimand
3529/// certificate interprets consecutive coefficient moves as one geometric
3530/// sequence. Six samples cover three e-folds, so the fallback still observes
3531/// curvature over a material interval instead of manufacturing constancy from
3532/// an arbitrarily small neighborhood.
3533const ASYMPTOTE_LOCAL_PROBE_DELTA: f64 = 0.5;
3534const ASYMPTOTE_LOCAL_PROBE_COUNT: usize = 6;
3535
3536/// Read-only inputs to [`try_certify_asymptote_rail`], bundled so the certify
3537/// path passes one borrow rather than a long positional argument list.
3538struct AsymptoteRailInputs<'a> {
3539 rho: &'a Array1<f64>,
3540 projected_gradient: &'a Array1<f64>,
3541 railed: &'a [usize],
3542 hessian: &'a Array2<f64>,
3543 bounds: &'a (Array1<f64>, Array1<f64>),
3544 terminal_beta: Option<&'a Array1<f64>>,
3545 stationarity_bound: f64,
3546 /// The run's relative objective tolerance resolved at the certified cost —
3547 /// the same flat-valley floor the cost-stall guard and the curvature-scaled
3548 /// widening use. The tail-snap interior judgment applies the identical
3549 /// Newton-decrement criterion on the interior SUB-BLOCK (the full-Hessian
3550 /// widening is disabled exactly when a noise-corrupted tail entry makes the
3551 /// full matrix non-PD).
3552 objective_tol: f64,
3553 context: &'a str,
3554}
3555
3556/// Attempt the typed stationary-at-asymptote rail certificate (#2348 Inc 1).
3557///
3558/// Returns `Some((interior_projected_grad_norm, rails))` when the interior
3559/// (non-railed) coordinates are gradient-stationary, the interior Hessian
3560/// sub-block is PSD, and EVERY railed coordinate is certified on a confirmed
3561/// exponential tail whose fitted model has reached the rail limit to within the
3562/// estimand tolerance. Returns `None` (fall through to the generic verdict) on
3563/// any failure — a non-stationary interior, indefinite interior curvature, or
3564/// any railed coordinate whose tail is not confirmable. Never errors on a
3565/// refusal; the only `Err` is a genuinely broken objective that cannot restore
3566/// its inner state to the certified point after probing.
3567fn try_certify_asymptote_rail(
3568 obj: &mut dyn OuterObjective,
3569 inputs: &AsymptoteRailInputs<'_>,
3570) -> Result<Result<(f64, f64, Vec<RailCoordinate>), String>, EstimationError> {
3571 let rho = inputs.rho;
3572 let projected_gradient = inputs.projected_gradient;
3573 let railed = inputs.railed;
3574 // The interior (non-railed) coordinates must be stationary in their own
3575 // right: the asymptote certificate speaks only to the railed directions,
3576 // never rescues a still-descending interior. Judged by the SAME two-stage
3577 // criterion as the Inc 2c at-point mint: the raw bound first, then the
3578 // curvature-scaled flat-valley bound on the interior sub-block — a fit
3579 // whose remaining interior Newton step would improve the cost by less
3580 // than the loop's own cost resolution is at its interior optimum, and the
3581 // residual gradient is the deep-λ instrument noise floor (evaluations
3582 // beside a saturated rail share the rail's logdet noise).
3583 let interior_indices: Vec<usize> = (0..projected_gradient.len())
3584 .filter(|k| !railed.contains(k))
3585 .collect();
3586 let (interior_projected_grad_norm, effective_interior_bound) =
3587 match certify_interior_stationarity(
3588 projected_gradient,
3589 inputs.hessian,
3590 &interior_indices,
3591 inputs.stationarity_bound,
3592 inputs.objective_tol,
3593 ) {
3594 Ok(certified) => certified,
3595 Err(reason) => return Ok(Err(reason)),
3596 };
3597 // The interior sub-block (railed coordinates removed) must be admissible
3598 // curvature for a minimum. A rail-caused indefiniteness in the saturated
3599 // direction is expected and excluded; genuine interior negative curvature is
3600 // not, and refuses the certificate.
3601 if certificate_hessian_is_psd_off_railed_above_gradient_floor(
3602 inputs.hessian,
3603 railed,
3604 projected_gradient,
3605 ) != Some(true)
3606 {
3607 return Ok(Err("interior Hessian sub-block is not PSD".to_string()));
3608 }
3609 let beta_norm = inputs
3610 .terminal_beta
3611 .map(|b| b.dot(b).sqrt())
3612 .filter(|v| v.is_finite())
3613 .unwrap_or(0.0);
3614 let estimand_tol = ASYMPTOTE_ESTIMAND_REL_TOL * (1.0 + beta_norm);
3615 let mut tol = AsymptoteTolerances::exp4_rail_bands(estimand_tol);
3616 // Real REML tails hold ĉ to ~5e-3 relative, not the exp4 synthetic
3617 // characterization's 1e-3 (measured on the #2299 fixture during Inc 2c);
3618 // the tail-snap path already certifies against the widened band, and the
3619 // railed mint must judge the SAME physical tail by the same standard.
3620 tol.tail_drift_rel = TAIL_SNAP_DRIFT_REL;
3621 let (lower, upper) = inputs.bounds;
3622
3623 // #2348 Inc 5 — the ANALYTIC face proof, first resort.
3624 //
3625 // Measuring a tail beside the ρ box asks the criterion for derivative
3626 // information exactly where its logdet pair cancels; and even when it
3627 // succeeds it speaks for ONE coordinate along ONE ray. The analytic route
3628 // forms the λ=∞ limit itself — the null-space-restricted fit, and the
3629 // exact first-order form of the logdet and trace terms there — and its
3630 // positive definiteness proves the whole face against every way of coming
3631 // off it. When the objective cannot form that limit, or the proof does not
3632 // hold, the measured-tail path below is unchanged.
3633 match try_certify_face_analytically(obj, inputs, estimand_tol)? {
3634 Ok((rails, proof)) => {
3635 log::info!(
3636 "[CERTIFICATE] {}: analytic λ=∞ face proof on {} coordinate(s): λ_min(C)={:.6e} \
3637 > margin {:.3e}, joint pencil ĉ={:.6e}, remaining value gap {:.3e}, estimand \
3638 travel {:.3e}",
3639 inputs.context,
3640 rails.len(),
3641 proof.min_curvature,
3642 proof.curvature_margin,
3643 proof.joint_tail_constant,
3644 proof.value_gap,
3645 proof.estimand_travel,
3646 );
3647 return Ok(Ok((
3648 interior_projected_grad_norm,
3649 effective_interior_bound,
3650 rails,
3651 )));
3652 }
3653 Err(reason) => log::info!(
3654 "[CERTIFICATE] {}: analytic λ=∞ face proof declined ({reason}); measuring the tail \
3655 instead",
3656 inputs.context,
3657 ),
3658 }
3659
3660 let mut rails: Vec<RailCoordinate> = Vec::new();
3661 let mut decline: Option<String> = None;
3662 let mut probed_any = false;
3663 for &k in railed.iter() {
3664 if k >= rho.len() || k >= lower.len() || k >= upper.len() {
3665 decline = Some(format!("railed coordinate {k} outside the box layout"));
3666 break;
3667 }
3668 // Which rail: the box endpoint the coordinate sits nearest. `Upper`
3669 // (λ → ∞) probes step ρ downward into the tail; `Lower` (λ → 0) step up.
3670 let side = if (upper[k] - rho[k]).abs() <= (rho[k] - lower[k]).abs() {
3671 AsymptoteSide::Upper
3672 } else {
3673 AsymptoteSide::Lower
3674 };
3675 probed_any = true;
3676 match build_and_assess_rail_coordinate(obj, rho, k, side, &tol, (lower[k], upper[k]))? {
3677 Ok(rail) => rails.push(rail),
3678 Err(reason) => {
3679 decline = Some(reason);
3680 break;
3681 }
3682 }
3683 }
3684
3685 // The probes warm-started the inner solve away from ρ̂; restore it so the
3686 // shipped fitted state (and the ρ-uncertainty diagnostic) sees the certified
3687 // point. A failure here is a genuinely broken objective, not a refusal.
3688 if probed_any {
3689 obj.eval_cost(rho).map_err(|err| {
3690 EstimationError::RemlOptimizationFailed(format!(
3691 "{}: failed to restore the objective to the certified point after \
3692 asymptote-rail probing: {err}",
3693 inputs.context
3694 ))
3695 })?;
3696 }
3697
3698 if let Some(reason) = decline {
3699 return Ok(Err(reason));
3700 }
3701 if rails.is_empty() {
3702 return Ok(Err(
3703 "no railed coordinate produced a certifiable tail".to_string()
3704 ));
3705 }
3706 Ok(Ok((
3707 interior_projected_grad_norm,
3708 effective_interior_bound,
3709 rails,
3710 )))
3711}
3712
3713/// The analytic face law is falsified against production criterion VALUES, so
3714/// the admissible discrepancy is exactly the error sources the comparison is
3715/// made of: the run's own cost resolution (absolute, once per evaluation), the
3716/// law's `O(e^{−ρ})` second-order remainder (relative), and the digits the
3717/// Gram/Schur assembly loses. `FACE_LAW_ERROR_SLACK` widens that budget by the
3718/// factor a two-point difference accumulates — each evaluation carries its own
3719/// resolution error and the remainder enters both the predicted and the
3720/// measured side — so a CORRECT law is never refused by its own error bars,
3721/// while a wrong one (which misses by orders of magnitude, not by slack) still
3722/// is. The falsification only runs where the budget leaves a discriminating
3723/// band; where it does not, the analytic route declines rather than minting
3724/// unfalsifiable evidence.
3725const FACE_LAW_ERROR_SLACK: f64 = 4.0;
3726
3727/// Floor on the falsification band.
3728///
3729/// The derived numerical budget can land many orders below the honest fidelity
3730/// of a closed form that deliberately does not model the criterion's
3731/// stabilization ridge or its reparameterization; holding the law to that
3732/// budget would reject a CORRECT law over a difference that changes no
3733/// decision. What the falsification exists to catch is a structurally wrong law
3734/// — a missing Schur term, the wrong dispersion convention, a sign error — and
3735/// those miss by orders of magnitude, not by slack (measured on a fixture whose
3736/// released directions genuinely earned their cost: 100%). Half the predicted
3737/// drop is the coarsest band that still separates those two worlds.
3738const FACE_LAW_ORDER_BAND: f64 = 0.5;
3739
3740/// Prove the rail face analytically (#2348 Inc 5): ask the objective for the
3741/// exact λ→∞ limit, test the first-order form, and falsify the resulting law
3742/// against the production criterion before minting anything from it.
3743///
3744/// Returns the minted rail coordinates plus the proof, or a human-readable
3745/// decline that the caller logs before falling back to the measured tail.
3746fn try_certify_face_analytically(
3747 obj: &mut dyn OuterObjective,
3748 inputs: &AsymptoteRailInputs<'_>,
3749 estimand_tol: f64,
3750) -> Result<Result<(Vec<RailCoordinate>, RailFaceProof), String>, EstimationError> {
3751 let rho = inputs.rho;
3752 let (lower, upper) = inputs.bounds;
3753 // The analytic limit is the INFINITE-smoothing face. A coordinate railed at
3754 // the zero-smoothing bound is the opposite limit (the penalty leaves the
3755 // model rather than pinning it) and belongs to the measured-tail path.
3756 for &k in inputs.railed.iter() {
3757 if k >= rho.len() || k >= lower.len() || k >= upper.len() {
3758 return Ok(Err("railed coordinate outside the box layout".to_string()));
3759 }
3760 if (upper[k] - rho[k]).abs() > (rho[k] - lower[k]).abs() {
3761 return Ok(Err(format!(
3762 "coordinate {k} rails at the zero-smoothing bound; the analytic face limit \
3763 covers λ→∞ only"
3764 )));
3765 }
3766 }
3767 let limit = match obj.rail_face_limit(rho, inputs.railed)? {
3768 RailFaceLimitOutcome::Available(limit) => *limit,
3769 // The decline is typed, and the distinction is worth carrying into the
3770 // refusal: "outside this closed form" invites a different one, while
3771 // "the face is unavailable" is a statement about the face.
3772 RailFaceLimitOutcome::OutsideClosedForm { reason } => {
3773 return Ok(Err(format!("outside the analytic closed form: {reason}")));
3774 }
3775 RailFaceLimitOutcome::FaceUnavailable { reason } => {
3776 return Ok(Err(format!("the λ=∞ face is unavailable: {reason}")));
3777 }
3778 };
3779 let proof = match certify_rail_face(&limit) {
3780 RailFaceVerdict::Certified(proof) => proof,
3781 RailFaceVerdict::Refused { reason } => return Ok(Err(reason)),
3782 };
3783 // The face being optimal does not by itself mean the SHIPPED fit is the
3784 // limit fit; the estimand gate is the same one the measured path applies,
3785 // now answered by the exact first-order coefficient offset rather than a
3786 // geometric extrapolation of observed steps.
3787 if !(proof.estimand_travel <= estimand_tol) {
3788 return Ok(Err(format!(
3789 "λ=∞ face proven, but the shipped fit has not reached it: coefficient travel \
3790 {:.3e} > estimand tolerance {estimand_tol:.3e}",
3791 proof.estimand_travel
3792 )));
3793 }
3794 if let Err(reason) = falsify_face_law(obj, inputs, &limit, &proof)? {
3795 return Ok(Err(reason));
3796 }
3797 let rails: Vec<RailCoordinate> = limit
3798 .face
3799 .iter()
3800 .zip(limit.face_rho.iter())
3801 .zip(proof.tail_constants.iter())
3802 .map(|((&index, &rho_k), &tail_constant)| RailCoordinate {
3803 index,
3804 side: AsymptoteSide::Upper,
3805 tail_constant,
3806 // On the tail law the remaining value gap of one coordinate is
3807 // exactly `c_k·e^{−ρ_k}`; a coordinate the rest of the face has
3808 // already pinned reports `c_k = 0` because releasing it does not
3809 // move the criterion at all.
3810 value_gap: tail_constant * (-rho_k).exp(),
3811 estimand_travel_bound: proof.estimand_travel,
3812 // The analytic proof's floor is the eigen-backward-error margin it
3813 // cleared, not a finite-difference noise floor.
3814 noise_margin: proof.curvature_margin,
3815 })
3816 .collect();
3817 Ok(Ok((rails, proof)))
3818}
3819
3820/// Falsify the analytic face law against the production criterion.
3821///
3822/// The law predicts `V(ρ) − V_∞ = ½tr((Σλ_kQᵀS_kQ)⁻¹C)`, so pulling every face
3823/// coordinate back by `Δ` must raise the criterion by exactly
3824/// `gap·(e^{Δ} − 1)`. That is a VALUE comparison — no derivative, no
3825/// cancellation — and it costs two evaluations instead of a probe ladder.
3826///
3827/// `Δ` is not a knob: the measurement error is `resolution/(gap·(e^Δ−1))` and
3828/// the law's own remainder is `O(e^{Δ−ρ})`, so their sum is minimized at
3829/// `Δ* = ½(ln(resolution/gap) + ρ)`, where both equal `√(resolution·e^{−ρ}/gap)`.
3830fn falsify_face_law(
3831 obj: &mut dyn OuterObjective,
3832 inputs: &AsymptoteRailInputs<'_>,
3833 limit: &RailFaceLimit,
3834 proof: &RailFaceProof,
3835) -> Result<Result<(), String>, EstimationError> {
3836 const FACE_LAW_DOMAIN_MARGIN: f64 = 1.0e-6;
3837 let rho = inputs.rho;
3838 let (lower, _) = inputs.bounds;
3839 let gap = proof.value_gap;
3840 let resolution = inputs.objective_tol;
3841 if !(gap > 0.0) || !(resolution > 0.0) {
3842 return Ok(Err(format!(
3843 "the face law carries no resolvable value gap: gap={gap:.3e}, cost resolution \
3844 {resolution:.3e}"
3845 )));
3846 }
3847 let deepest = limit
3848 .face_rho
3849 .iter()
3850 .fold(f64::INFINITY, |acc, v| acc.min(*v));
3851 let ideal = 0.5 * ((resolution / gap).ln() + deepest);
3852 let room = limit
3853 .face
3854 .iter()
3855 .zip(limit.face_rho.iter())
3856 .map(|(&k, &rho_k)| rho_k - lower[k] - FACE_LAW_DOMAIN_MARGIN)
3857 .fold(f64::INFINITY, f64::min);
3858 let delta = ideal.min(room);
3859 // One e-fold is the natural unit of the law being tested; below that the
3860 // predicted change is not a statement about a tail.
3861 if !(delta >= 1.0) || !delta.is_finite() {
3862 return Ok(Err(format!(
3863 "no room inside the box to falsify the face law: Δ={delta:.3e} e-folds"
3864 )));
3865 }
3866 let predicted = gap * (delta.exp() - 1.0);
3867 let measurement_error = resolution / predicted;
3868 let remainder_error = (delta - deepest).exp();
3869 let assembly_error = f64::EPSILON.sqrt() * limit.form_conditioning.sqrt();
3870 let budget = measurement_error + remainder_error + assembly_error;
3871 let admissible = (FACE_LAW_ERROR_SLACK * budget).max(FACE_LAW_ORDER_BAND);
3872 if !(admissible < 1.0) {
3873 return Ok(Err(format!(
3874 "the face law cannot be falsified here: error budget {budget:.3e} (measurement \
3875 {measurement_error:.3e}, remainder {remainder_error:.3e}, assembly \
3876 {assembly_error:.3e}) leaves no discriminating band"
3877 )));
3878 }
3879 let baseline = obj.eval_cost(rho)?;
3880 let mut pulled_back = rho.clone();
3881 for &k in limit.face.iter() {
3882 pulled_back[k] -= delta;
3883 }
3884 let pulled_value = obj.eval_cost(&pulled_back)?;
3885 // Every exit below ships the certified point, so restore it before judging.
3886 obj.eval_cost(rho)?;
3887 if !baseline.is_finite() || !pulled_value.is_finite() {
3888 return Ok(Err(
3889 "the criterion is not finite at the falsification points".to_string()
3890 ));
3891 }
3892 let measured = pulled_value - baseline;
3893 let discrepancy = (predicted - measured).abs() / predicted;
3894 if discrepancy > admissible {
3895 return Ok(Err(format!(
3896 "the analytic face law does not reproduce the criterion: pulling the face back \
3897 {delta:.2} e-folds should raise V by {predicted:.6e}, measured {measured:.6e} \
3898 (relative {discrepancy:.3e} > admissible {admissible:.3e})"
3899 )));
3900 }
3901 Ok(Ok(()))
3902}
3903
3904/// Two-stage interior stationarity judgment shared by the Inc 1 railed mint
3905/// and the Inc 2c at-point mint (#2348): the raw stationarity bound first,
3906/// then the curvature-scaled flat-valley bound on the interior sub-block.
3907///
3908/// The second stage certifies a point whose full interior Newton step would
3909/// improve the objective by less than `objective_tol` — the loop's own cost
3910/// resolution — so the point is cost-indistinguishable from the interior
3911/// optimum and the measured gradient is resolution noise, not slope. Returns
3912/// `Ok((interior_grad_norm, effective_bound))` when certified (the bound
3913/// that admitted the norm: raw, or the curvature-scaled widening); `Err`
3914/// carries the measured evidence when real interior descent remains, so a
3915/// refused mint explains WHICH interior stage failed and by how much.
3916pub(crate) fn certify_interior_stationarity(
3917 gradient: &Array1<f64>,
3918 hessian: &Array2<f64>,
3919 interior_indices: &[usize],
3920 stationarity_bound: f64,
3921 objective_tol: f64,
3922) -> Result<(f64, f64), String> {
3923 let interior_grad_norm = interior_indices
3924 .iter()
3925 .map(|&k| gradient[k] * gradient[k])
3926 .sum::<f64>()
3927 .sqrt();
3928 if interior_grad_norm <= stationarity_bound {
3929 return Ok((interior_grad_norm, stationarity_bound));
3930 }
3931 let m = interior_indices.len();
3932 let mut sub_h = Array2::<f64>::zeros((m, m));
3933 let mut sub_g = Array1::<f64>::zeros(m);
3934 for (i, &ri) in interior_indices.iter().enumerate() {
3935 sub_g[i] = gradient[ri];
3936 for (j, &rj) in interior_indices.iter().enumerate() {
3937 sub_h[[i, j]] = hessian[[ri, rj]];
3938 }
3939 }
3940 match newton_predicted_decrease(&sub_h, &sub_g) {
3941 Some(predicted_decrease) if predicted_decrease.is_finite() && predicted_decrease > 0.0 => {
3942 if predicted_decrease <= objective_tol {
3943 let curvature_grad_bound =
3944 interior_grad_norm * (objective_tol / predicted_decrease).sqrt();
3945 if curvature_grad_bound.is_finite() && curvature_grad_bound >= interior_grad_norm {
3946 return Ok((interior_grad_norm, curvature_grad_bound));
3947 }
3948 }
3949 Err(format!(
3950 "interior not stationary: |Pg_int|={interior_grad_norm:.3e} > bound {stationarity_bound:.3e}, sub-block Newton decrement {predicted_decrease:.3e} > cost resolution {objective_tol:.3e}"
3951 ))
3952 }
3953 _ => Err(format!(
3954 "interior not stationary: |Pg_int|={interior_grad_norm:.3e} > bound {stationarity_bound:.3e} and the interior sub-block yields no PD Newton decrement"
3955 )),
3956 }
3957}
3958
3959/// Curvature-tie acceptance band for a certify-time tail-snap candidate
3960/// (#2348 Inc 2). On the #2337 Thm 2.1 exponential tail `V = V_∞ + c·e^{∓ρ}`
3961/// the coordinate's own curvature equals its gradient magnitude EXACTLY
3962/// (`H_kk = c·e^{∓ρ} = |g_k|`, unit decay rate in ρ = log λ), so `H_kk/|g_k| ≈ 1`
3963/// is a zero-cost analytic signature separating a live tail crawl from a
3964/// genuinely unconverged curved coordinate before any probe is spent. The band
3965/// tolerates the `O(e^{∓2ρ})` next-order term and assembly round-off; the
3966/// probing confirmation is the rigorous gate.
3967const TAIL_SNAP_CURVATURE_BAND: (f64, f64) = (0.25, 4.0);
3968
3969/// Certify-time tail snap (#2348 Inc 2): when certification is about to refuse
3970/// a point whose gradient residual is carried entirely by coordinates crawling
3971/// an exponential tail TOWARD the ρ-box (the one-e-fold-per-Newton-step grind
3972/// the asymptote certificate exists to kill — the loop can exhaust its budget
3973/// strictly inside the box, where the Inc 1 railed mint can never fire),
3974/// positively confirm each such coordinate's tail from the current point and
3975/// return the point with those coordinates snapped to their box bound as an
3976/// optimization waypoint. The caller re-runs the outer search from that point;
3977/// only the resulting point is eligible for the Inc 1 rail certificate.
3978///
3979/// Refusal semantics mirror [`try_certify_asymptote_rail`]: any gate failure
3980/// returns `Ok(None)` (fall through to the ordinary refusal); the only `Err` is
3981/// a genuinely broken objective that cannot restore its inner state after
3982/// probing. Gates, in order of cost:
3983/// 1. candidate coordinates = un-railed, `|g_k|` above the stationarity bound,
3984/// positive own-curvature within [`TAIL_SNAP_CURVATURE_BAND`] of `|g_k|`
3985/// (the tail-law tie), with the rail side read from the gradient sign;
3986/// 2. the interior Hessian sub-block (railed + candidates excluded) is PSD;
3987/// 3. every candidate's tail is confirmed by the same probing engine the rail
3988/// mint uses (`CertifiedAtAsymptote` or `OnTailNotYetEquivalent`).
3989/// Outcome of a certify-time tail-snap attempt: a point already stationary on
3990/// its confirmed tail, a confirmed-tail waypoint requiring one optimization
3991/// retry (#2348 Inc 2b / #2358), or a human-readable decline reason carried
3992/// into the refusal summary.
3993#[derive(Debug)]
3994enum TailSnapOutcome {
3995 /// Every candidate's confirmed tail EXTRAPOLATES to a gradient already
3996 /// below the stationarity bound at the CURRENT point (#2348 Inc 2c): the
3997 /// coordinate is tail-stationary where it stands, and the measured local
3998 /// gradient is instrument noise (observed on the #2299 fixture: measured
3999 /// |g|=1.04e-2 at ρ=26.56 vs the clean-band extrapolation ĉ·e^{−ρ} ≈
4000 /// 1.9e-8, 100× below the bound). Mint the AsymptoteRail at this point —
4001 /// no snap, no reseed. `interior_projected_grad_norm` and the
4002 /// `effective_interior_bound` that certified it (the raw stationarity
4003 /// bound, or the interior sub-block's curvature-scaled flat-valley bound —
4004 /// the full-Hessian widening is disabled precisely because the noisy tail
4005 /// entry makes the full matrix non-PD) ride along for the certificate.
4006 TailStationaryAtPoint {
4007 rails: Vec<RailCoordinate>,
4008 interior_projected_grad_norm: f64,
4009 effective_interior_bound: f64,
4010 },
4011 /// Tails confirmed, but the current point is not already
4012 /// tail-stationary. The snapped rail point is only a waypoint: the plan
4013 /// runner must retry ONCE from it so coupled coordinates can reoptimize
4014 /// before certification.
4015 ConfirmedNeedsReseed(Array1<f64>),
4016 Declined(String),
4017}
4018
4019/// Relative drift band for the tail-snap confirmation window, wider than the
4020/// exp4 characterization band (1e-3). The snap's evidentiary strength comes
4021/// from the EXTRAPOLATED-GAP margin, not the band tightness: a 1–2% spread in
4022/// `ĉ` across the clean run moves the extrapolated remaining gradient
4023/// `ĉ·e^{∓ρ}` by the same 1–2%, immaterial against the orders-of-magnitude
4024/// margin the at-point/stationarity decisions demand — while the true tail on
4025/// a REAL fixture still carries visible sub-percent curvature contamination at
4026/// probe depth (measured on #2299: ĉ ∈ {6544, 6565, 6574} over three e-folds,
4027/// drift 4.6e-3, against a wildly swinging noise region above).
4028const TAIL_SNAP_DRIFT_REL: f64 = 1.0e-2;
4029
4030fn try_tail_snap_to_rail(
4031 obj: &mut dyn OuterObjective,
4032 inputs: &AsymptoteRailInputs<'_>,
4033) -> Result<TailSnapOutcome, EstimationError> {
4034 let rho = inputs.rho;
4035 let gradient = inputs.projected_gradient;
4036 let hessian = inputs.hessian;
4037 let (lower, upper) = inputs.bounds;
4038 let n = gradient.len();
4039 if rho.len() != n
4040 || hessian.nrows() != n
4041 || hessian.ncols() != n
4042 || lower.len() < n
4043 || upper.len() < n
4044 {
4045 return Ok(TailSnapOutcome::Declined("shape mismatch".to_string()));
4046 }
4047
4048 let mut candidates: Vec<(usize, AsymptoteSide)> = Vec::new();
4049 let mut rejected: Vec<String> = Vec::new();
4050 for k in 0..n {
4051 if inputs.railed.contains(&k) {
4052 continue;
4053 }
4054 let g_k = gradient[k];
4055 let side = match AsymptoteSide::from_gradient(g_k, inputs.stationarity_bound) {
4056 Some(side) => side,
4057 None => continue,
4058 };
4059 // A tail candidate must be DEEP toward the bound its gradient points
4060 // at — within the probe span of the box. The tail law is an asymptotic
4061 // statement; a coordinate sitting many probe-spans inside the interior
4062 // (every scripted mock optimum, every ordinary unconverged fit) has no
4063 // asymptote to confirm there, and probing it would spend a dozen
4064 // objective evaluations per would-refuse certification for nothing
4065 // (breaking eval-count-asserting harnesses along the way).
4066 let probe_span = ASYMPTOTE_PROBE_COUNT as f64;
4067 let deep_enough = match side {
4068 AsymptoteSide::Upper => upper[k] - rho[k] <= probe_span,
4069 AsymptoteSide::Lower => rho[k] - lower[k] <= probe_span,
4070 };
4071 if !deep_enough {
4072 rejected.push(format!(
4073 "k={k}: ρ={:.2} more than {probe_span:.0} e-folds inside the box",
4074 rho[k]
4075 ));
4076 continue;
4077 }
4078 let h_kk = hessian[[k, k]];
4079 // The tie is judged on |H_kk|/|g_k| — MAGNITUDE only. On the exact
4080 // tail `H_kk = |g_k|` (positive), but the assembled ρ-Hessian's tail
4081 // entry is `λV_λ + λ²V_λλ`, and when the `λ²V_λλ` trace pair cancels
4082 // to roundoff in the deep-smoothing regime (the #2298 rail-cancellation
4083 // class), what survives is `λV_λ = g_k` — magnitude right, SIGN
4084 // flipped (measured on the #2299 fixture: g=-1.040e-2, H_kk=-1.018e-2,
4085 // ratio -0.979). The sign at the tail is exactly the corrupted datum,
4086 // so it cannot gate; the probing confirmation is the rigorous test.
4087 let ratio = h_kk.abs() / g_k.abs();
4088 if !(TAIL_SNAP_CURVATURE_BAND.0..=TAIL_SNAP_CURVATURE_BAND.1).contains(&ratio) {
4089 rejected.push(format!(
4090 "k={k}: g={g_k:.3e} H_kk={h_kk:.3e} |ratio|={ratio:.3e} outside tie band"
4091 ));
4092 continue;
4093 }
4094 candidates.push((k, side));
4095 }
4096 if candidates.is_empty() {
4097 return Ok(TailSnapOutcome::Declined(if rejected.is_empty() {
4098 "no super-bound coordinate".to_string()
4099 } else {
4100 format!(
4101 "no candidate passed the curvature tie ({})",
4102 rejected.join("; ")
4103 )
4104 }));
4105 }
4106
4107 // The curvature left after excluding the railed + candidate directions
4108 // must be admissible for a minimum; a genuinely indefinite interior
4109 // refuses before any probe is spent.
4110 let excluded: Vec<usize> = inputs
4111 .railed
4112 .iter()
4113 .copied()
4114 .chain(candidates.iter().map(|(k, _)| *k))
4115 .collect();
4116 if certificate_hessian_is_psd_off_railed_above_gradient_floor(hessian, &excluded, gradient)
4117 != Some(true)
4118 {
4119 return Ok(TailSnapOutcome::Declined(
4120 "interior Hessian sub-block not PSD".to_string(),
4121 ));
4122 }
4123
4124 let beta_norm = inputs
4125 .terminal_beta
4126 .map(|b| b.dot(b).sqrt())
4127 .filter(|v| v.is_finite())
4128 .unwrap_or(0.0);
4129 let mut tol =
4130 AsymptoteTolerances::exp4_rail_bands(ASYMPTOTE_ESTIMAND_REL_TOL * (1.0 + beta_norm));
4131 tol.tail_drift_rel = TAIL_SNAP_DRIFT_REL;
4132 let mut decline: Option<String> = None;
4133 // Rails for candidates whose confirmed tail extrapolates to an
4134 // already-below-bound gradient at the CURRENT point; when every candidate
4135 // qualifies, the point is minted where it stands (#2348 Inc 2c).
4136 let mut at_point_rails: Vec<RailCoordinate> = Vec::new();
4137 for (k, side) in &candidates {
4138 let verdict = match probe_tail_window(obj, rho, *k, *side, &tol, (lower[*k], upper[*k]))? {
4139 (Some(window), rows) => match assess_coordinate(&window, &tol) {
4140 AsymptoteVerdict::CertifiedAtAsymptote {
4141 side: assessed_side,
4142 tail_constant,
4143 estimand_travel_bound,
4144 ..
4145 } => {
4146 // Extrapolate the confirmed tail law to the current point:
4147 // the TRUE remaining gradient there, immune to the local
4148 // instrument noise the certificate measured.
4149 let extrapolated_gap = match assessed_side {
4150 AsymptoteSide::Upper => tail_constant * (-rho[*k]).exp(),
4151 AsymptoteSide::Lower => tail_constant * rho[*k].exp(),
4152 };
4153 if extrapolated_gap.is_finite() && extrapolated_gap <= inputs.stationarity_bound
4154 {
4155 at_point_rails.push(RailCoordinate {
4156 index: *k,
4157 side: assessed_side,
4158 tail_constant,
4159 value_gap: extrapolated_gap,
4160 estimand_travel_bound,
4161 noise_margin: tol.tail_noise_floor,
4162 });
4163 }
4164 None
4165 }
4166 AsymptoteVerdict::OnTailNotYetEquivalent { .. } => None,
4167 AsymptoteVerdict::NoAsymptote { reason } => {
4168 Some(format!("{reason}; probes: {rows}"))
4169 }
4170 },
4171 (None, rows) => Some(format!(
4172 "no finite-difference-clean tail run; probes: {rows}"
4173 )),
4174 };
4175 if let Some(reason) = verdict {
4176 decline = Some(format!("candidate k={k} tail unconfirmed: {reason}"));
4177 break;
4178 }
4179 }
4180 // #2349 round 7: a multi-coordinate rail face. When a candidate's OWN
4181 // one-dimensional tail law fails and several candidates ride out together,
4182 // the marginal law is the wrong object — overlapping penalties share range
4183 // space, so a lone coordinate's gradient saturates once the others
4184 // dominate the shared term (the measured #2349 ladder swept ĉ₀ across 8
4185 // orders of magnitude). The scalar section along the joint face direction
4186 // has the ordinary exponential tail; certify THAT with the same
4187 // discipline, and mint every face coordinate from the joint law.
4188 // A face confirmed through the JOINT fallback snaps as a WAYPOINT, never a
4189 // candidate optimum: the joint law certifies the direction of the optimum,
4190 // but individual face coordinates can hold interior optima once the others
4191 // sit railed (measured on the #2349 fixture: after snapping the 5-face,
4192 // coordinate 0's own gradient crossed zero near ρ₀ ≈ 7.5 — 4.5 e-folds
4193 // inside its snapped rail — while V dropped 3.86 from the checkpoint). The
4194 // reseed retry re-descends from the snapped point with the rails free to
4195 // hold or relax; a direct re-certification there would refuse exactly that
4196 // relaxation.
4197 if decline.is_some() && candidates.len() >= 2 {
4198 let (window, joint_rows) =
4199 probe_joint_tail_window(obj, rho, &candidates, &tol, (lower, upper))?;
4200 match window.as_ref().map(|w| assess_coordinate(w, &tol)) {
4201 Some(AsymptoteVerdict::CertifiedAtAsymptote {
4202 tail_constant,
4203 estimand_travel_bound,
4204 ..
4205 }) => {
4206 // Extrapolate the joint law back to the checkpoint: the true
4207 // remaining directional gradient there, immune to the local
4208 // instrument noise. All face gradients share one sign
4209 // structure along the face, so the joint gap bounds each
4210 // coordinate's own remaining gradient.
4211 let r0 = candidates
4212 .iter()
4213 .map(|(k, side)| match side {
4214 AsymptoteSide::Upper => rho[*k],
4215 AsymptoteSide::Lower => -rho[*k],
4216 })
4217 .sum::<f64>()
4218 / candidates.len() as f64;
4219 let joint_gap = tail_constant * (-r0).exp();
4220 if joint_gap.is_finite() && joint_gap <= inputs.stationarity_bound {
4221 at_point_rails = candidates
4222 .iter()
4223 .map(|(k, side)| RailCoordinate {
4224 index: *k,
4225 side: *side,
4226 tail_constant,
4227 value_gap: joint_gap,
4228 estimand_travel_bound,
4229 noise_margin: tol.tail_noise_floor,
4230 })
4231 .collect();
4232 }
4233 decline = None;
4234 }
4235 Some(AsymptoteVerdict::OnTailNotYetEquivalent { .. }) => {
4236 // Confirmed on the joint tail; travel not yet settled — the
4237 // face snaps/reseeds below exactly as a confirmed single
4238 // candidate would.
4239 decline = None;
4240 }
4241 Some(AsymptoteVerdict::NoAsymptote { reason }) => {
4242 // A returned window IS the law: it exists only when a
4243 // drift-band-clean, above-noise-floor, uniformly-positive
4244 // pencil-constant run of MIN_TAIL_SAMPLES was found, so the
4245 // only `NoAsymptote` reachable from it is the estimand
4246 // contraction gate — the β-steps in the retained (deep
4247 // interior) rows still move, i.e. the checkpoint is genuinely
4248 // NOT at the face limit yet (measured on the #2349 checkpoint:
4249 // ĉ settled to 34.2 over the last four probes while the crawl
4250 // was still travelling). That is the same state as
4251 // `OnTailNotYetEquivalent`: the law says WHERE the optimum is;
4252 // the snap below reoptimizes from the face, granting nothing
4253 // by itself.
4254 log::info!(
4255 "[CERTIFICATE] joint {}-coordinate face: pencil-constant run \
4256 confirmed but estimand not settled at the checkpoint \
4257 ({reason}); snapping the face for re-optimization",
4258 candidates.len(),
4259 );
4260 decline = None;
4261 }
4262 None => {
4263 decline = Some(format!(
4264 "{}; joint {}-coordinate face: no finite-difference-clean run; joint probes: {joint_rows}",
4265 decline.take().unwrap_or_default(),
4266 candidates.len(),
4267 ));
4268 }
4269 }
4270 }
4271 // The probes warm-started the inner solve away from the checkpoint; every
4272 // exit below leaves the CURRENT point as the shipped state, so restore it
4273 // before returning. A failure here is a genuinely broken objective.
4274 obj.eval_cost(rho).map_err(|err| {
4275 EstimationError::RemlOptimizationFailed(format!(
4276 "{}: failed to restore the objective to the certified point after \
4277 tail-snap probing: {err}",
4278 inputs.context
4279 ))
4280 })?;
4281 if let Some(reason) = decline {
4282 return Ok(TailSnapOutcome::Declined(reason));
4283 }
4284
4285 let interior_indices: Vec<usize> = (0..n)
4286 .filter(|k| !inputs.railed.contains(k) && !candidates.iter().any(|(c, _)| c == k))
4287 .collect();
4288 // #2348 Inc 2c: every candidate's confirmed tail already extrapolates
4289 // BELOW the stationarity bound at the current point — the fit is
4290 // tail-stationary where it stands and the measured local gradient is
4291 // instrument noise. Judge the interior with the shared two-stage
4292 // criterion (`certify_interior_stationarity`): the raw bound, then the
4293 // curvature-scaled flat-valley bound on the interior SUB-BLOCK (the
4294 // full-Hessian widening is unavailable here exactly because the
4295 // noise-corrupted tail entry makes the full matrix non-PD).
4296 if at_point_rails.len() == candidates.len() {
4297 if let Ok((interior_projected_grad_norm, effective_interior_bound)) =
4298 certify_interior_stationarity(
4299 gradient,
4300 hessian,
4301 &interior_indices,
4302 inputs.stationarity_bound,
4303 inputs.objective_tol,
4304 )
4305 {
4306 return Ok(TailSnapOutcome::TailStationaryAtPoint {
4307 rails: at_point_rails,
4308 interior_projected_grad_norm,
4309 effective_interior_bound,
4310 });
4311 }
4312 // Real interior descent remains: fall through to the reseed path so
4313 // one more optimizer pass polishes it.
4314 }
4315
4316 let mut snapped = rho.clone();
4317 for (k, side) in &candidates {
4318 snapped[*k] = match side {
4319 AsymptoteSide::Upper => upper[*k],
4320 AsymptoteSide::Lower => lower[*k],
4321 };
4322 }
4323
4324 // Tails confirmed, but a finite snap can change every coupled coordinate's
4325 // optimum even when its PRE-snap gradient was stationary (#2358 measured
4326 // the location-scale interior gradient jumping to 0.5). The snap proves a
4327 // direction and supplies a waypoint; only a resumed optimization and its
4328 // subsequent certificate can prove the endpoint.
4329 Ok(TailSnapOutcome::ConfirmedNeedsReseed(snapped))
4330}
4331
4332/// Reconstruct one railed coordinate's exponential tail by probing the analytic
4333/// gradient back from the rail at coarse and, when needed, local resolution;
4334/// locate the longest finite-difference-clean run (rejecting the noise floor
4335/// adjacent to the rail); and assess it against the tail law (#2348 Inc 1 /
4336/// #2337 Thm 2.1). Returns the certified [`RailCoordinate`] or `None` if no
4337/// confirmable tail is found.
4338fn build_and_assess_rail_coordinate(
4339 obj: &mut dyn OuterObjective,
4340 rho: &Array1<f64>,
4341 coord: usize,
4342 side: AsymptoteSide,
4343 tol: &AsymptoteTolerances,
4344 domain: (f64, f64),
4345) -> Result<Result<RailCoordinate, String>, EstimationError> {
4346 let window = match probe_tail_window(obj, rho, coord, side, tol, domain)? {
4347 (Some(window), _) => window,
4348 (None, rows) => {
4349 // #2450: name the floor instead of leaving the reader to find it.
4350 // The criterion ALWAYS carries the soft rho-guard barrier
4351 // (`soft_rho_guard_prior_atom`), and a `log cosh` barrier's gradient
4352 // SATURATES to `w*a` rather than decaying, so once the REML tail
4353 // falls below it, `c_hat = -e^rho * dV/drho` diverges and no tail is
4354 // observable THROUGH the guard at any box width or probe count. That
4355 // is a different failure from a noisy or truncated window, and
4356 // reporting both as "no finite-difference-clean tail window" cost
4357 // real time: it reads as a fixture/conditioning problem when it is a
4358 // property of the objective. Measured: the floor is exactly
4359 // `w*a*tanh(a*rho)` = 1.3324e-7 at rho=30 against a predicted
4360 // 1.3324e-7 (five significant figures, every coordinate).
4361 let guard_floor = crate::estimate::RHO_SOFT_PRIOR_WEIGHT
4362 * (crate::estimate::RHO_SOFT_PRIOR_SHARPNESS / crate::estimate::RHO_BOUND);
4363 return Ok(Err(format!(
4364 "k={coord}: no finite-difference-clean tail window; note the soft \
4365 rho-guard gradient saturates at w*a={guard_floor:.4e} (#2450), so any \
4366 tail whose |dV/drho| is at or below that is unobservable through the \
4367 guard rather than merely unclean; probes {rows}"
4368 )));
4369 }
4370 };
4371 match assess_coordinate(&window, tol) {
4372 AsymptoteVerdict::CertifiedAtAsymptote {
4373 side,
4374 tail_constant,
4375 value_gap,
4376 estimand_travel_bound,
4377 } => Ok(Ok(RailCoordinate {
4378 index: coord,
4379 side,
4380 tail_constant,
4381 value_gap,
4382 estimand_travel_bound,
4383 noise_margin: tol.tail_noise_floor,
4384 })),
4385 other => Ok(Err(format!("k={coord}: tail verdict {other:?}"))),
4386 }
4387}
4388
4389/// Detect a WRONG-RAIL coordinate (#2392): one sitting AT its ρ box bound whose
4390/// clean-band probes prove the objective strictly DECREASES as the coordinate
4391/// moves INWARD — the outer search drove it to the wrong bound. Returns the
4392/// interior ρ to reseed the coordinate at (the deepest drift-clean probe, where
4393/// `|g|` is largest and the descent is most informative) when the proof holds,
4394/// else `None`.
4395///
4396/// # Proof condition (evidence-gated; cannot launder a genuine λ→∞ / λ→0 optimum)
4397///
4398/// Probe [`ASYMPTOTE_PROBE_COUNT`] e-folds inward and require a contiguous run of
4399/// at least [`MIN_TAIL_SAMPLES`] probes that is, at once:
4400/// 1. above the gradient interior floor, `|g| > interior_grad_tol` (so a probe
4401/// whose gradient has decayed into finite-difference cancellation next to the
4402/// rail is excluded rather than read as a settled tail);
4403/// 2. above the pencil-constant noise floor, `|ĉ| > tail_noise_floor`, where
4404/// `ĉ = side.tail_constant(ρ, g)` uses the coordinate's ACTUAL rail side;
4405/// 3. drift-band-clean in `ĉ` within `tail_drift_rel` (the same constant-pencil
4406/// band the genuine tail uses — `run_drift_within_band` keys on `|mean|`, so a
4407/// uniformly-negative run is judged on its magnitude); AND
4408/// 4. `ĉ` uniformly of the sign OPPOSITE a genuine tail — `ĉ < 0` — i.e. the
4409/// descent direction points AWAY from the bound (`∂V/∂ρ > 0` at an upper rail,
4410/// `∂V/∂ρ < 0` at a lower rail).
4411///
4412/// A genuine rail (descent TOWARD the bound) has `ĉ > 0` across the clean run and
4413/// never satisfies (4), so it is never pulled off its rail. This shares every
4414/// tolerance with the genuine asymptote path ([`AsymptoteTolerances`]); the ONLY
4415/// difference is the sign gate in (4).
4416fn detect_wrong_rail_pullback(
4417 obj: &mut dyn OuterObjective,
4418 rho: &Array1<f64>,
4419 coord: usize,
4420 side: AsymptoteSide,
4421 tol: &AsymptoteTolerances,
4422 domain: (f64, f64),
4423) -> Result<Option<f64>, EstimationError> {
4424 const PROBE_DELTA: f64 = 1.0;
4425 const PROBE_DOMAIN_MARGIN: f64 = 1.0e-6;
4426 // Upper rail (ρ → +∞): step ρ DOWN into the interior. Lower rail: step UP.
4427 let sign = match side {
4428 AsymptoteSide::Upper => -1.0,
4429 AsymptoteSide::Lower => 1.0,
4430 };
4431 // rows[r] is probe j=r+1: r=0 is CLOSEST to the rail, increasing r steps
4432 // further into the interior (larger |grad| on a clean run).
4433 let mut rows: Vec<(f64, f64)> = Vec::new();
4434 for j in 1..=ASYMPTOTE_PROBE_COUNT {
4435 let stepped = rho[coord] + sign * (j as f64) * PROBE_DELTA;
4436 if stepped <= domain.0 + PROBE_DOMAIN_MARGIN || stepped >= domain.1 - PROBE_DOMAIN_MARGIN {
4437 break;
4438 }
4439 let mut probe = rho.clone();
4440 probe[coord] = stepped;
4441 let eval = match obj.eval_with_order(&probe, OuterEvalOrder::ValueAndGradient) {
4442 Ok(eval) => eval,
4443 Err(_) => break,
4444 };
4445 if !eval.cost.is_finite() || coord >= eval.gradient.len() || !eval.gradient[coord].is_finite()
4446 {
4447 break;
4448 }
4449 rows.push((stepped, eval.gradient[coord]));
4450 }
4451 if rows.len() < MIN_TAIL_SAMPLES {
4452 return Ok(None);
4453 }
4454 let constants: Vec<f64> = rows
4455 .iter()
4456 .map(|(r, g)| side.tail_constant(*r, *g))
4457 .collect();
4458 // Wrong-rail element-clean: gradient above the interior floor, pencil
4459 // constant above the noise floor IN MAGNITUDE, and of the descent-inward
4460 // (negative) sign. Genuine tails have ĉ > 0 here and are excluded.
4461 let element_clean: Vec<bool> = rows
4462 .iter()
4463 .zip(&constants)
4464 .map(|((_, g), c)| {
4465 c.is_finite() && *c < -tol.tail_noise_floor && g.abs() > tol.interior_grad_tol
4466 })
4467 .collect();
4468 // Longest contiguous element-clean, drift-band-clean run; reseed the
4469 // coordinate at the DEEPEST such probe (largest |g|, closest to the true
4470 // interior optimum the descent points toward).
4471 let mut best: Option<(usize, usize)> = None;
4472 for a in 0..rows.len() {
4473 if !element_clean[a] {
4474 continue;
4475 }
4476 for b in a..rows.len() {
4477 if !element_clean[b] {
4478 break;
4479 }
4480 if b - a + 1 < MIN_TAIL_SAMPLES {
4481 continue;
4482 }
4483 if !run_drift_within_band(&constants[a..=b], tol.tail_drift_rel) {
4484 continue;
4485 }
4486 match best {
4487 Some((ba, bb)) if bb - ba + 1 >= b - a + 1 => {}
4488 _ => best = Some((a, b)),
4489 }
4490 }
4491 }
4492 Ok(best.map(|(_, b)| rows[b].0))
4493}
4494
4495/// Probe one coordinate's tail toward the interior (the shared probing engine
4496/// of [`build_and_assess_rail_coordinate`] and the certify-time tail snap).
4497/// The one-e-fold ladder runs first; if it finds no clean run, a short
4498/// half-e-fold ladder resolves a narrower local band without mixing step sizes
4499/// in one estimand window. Returns the longest finite-difference-clean
4500/// constant-`ĉ` run (newest sample nearest `rho[coord]`), or `None` when neither
4501/// resolution contains at least [`MIN_TAIL_SAMPLES`] clean rows. The second
4502/// element dumps `(ρ, ∂V/∂ρ, ĉ)` evidence for every attempted resolution.
4503fn probe_tail_window(
4504 obj: &mut dyn OuterObjective,
4505 rho: &Array1<f64>,
4506 coord: usize,
4507 side: AsymptoteSide,
4508 tol: &AsymptoteTolerances,
4509 domain: (f64, f64),
4510) -> Result<(Option<AsymptoteWindow>, String), EstimationError> {
4511 let (coarse_window, coarse_rows) = probe_tail_window_at_resolution(
4512 obj,
4513 rho,
4514 coord,
4515 side,
4516 tol,
4517 domain,
4518 (1.0, ASYMPTOTE_PROBE_COUNT),
4519 )?;
4520 if coarse_window.is_some() {
4521 return Ok((coarse_window, coarse_rows));
4522 }
4523
4524 // The coarse ladder is deliberately retained as the first pass: railed
4525 // dense REML fits can have several e-folds of cancellation noise beside
4526 // the box followed by a clean band far inside it. The local pass addresses
4527 // the complementary geometry exposed by #2358, where a modest finite box
4528 // contains a narrow but regular tail and unit steps skip over it.
4529 let (local_window, local_rows) = probe_tail_window_at_resolution(
4530 obj,
4531 rho,
4532 coord,
4533 side,
4534 tol,
4535 domain,
4536 (
4537 ASYMPTOTE_LOCAL_PROBE_DELTA,
4538 ASYMPTOTE_LOCAL_PROBE_COUNT,
4539 ),
4540 )?;
4541 Ok((
4542 local_window,
4543 format!("coarse[{coarse_rows}] local[{local_rows}]"),
4544 ))
4545}
4546
4547/// Probe a single equally-spaced resolution of one coordinate's tail.
4548///
4549/// Keeping each returned window at one resolution is essential for
4550/// [`assess_coordinate`]: its coefficient-travel bound estimates a geometric
4551/// ratio from consecutive steps, which is only meaningful when their `Δρ`
4552/// values are identical.
4553fn probe_tail_window_at_resolution(
4554 obj: &mut dyn OuterObjective,
4555 rho: &Array1<f64>,
4556 coord: usize,
4557 side: AsymptoteSide,
4558 tol: &AsymptoteTolerances,
4559 domain: (f64, f64),
4560 resolution: (f64, usize),
4561) -> Result<(Option<AsymptoteWindow>, String), EstimationError> {
4562 let (probe_delta, probe_count) = resolution;
4563 // Strictly-inside guard for probes against the probed coordinate's own box
4564 // interval (#2388). The ρ-gradient assembly freezes any coordinate at (or
4565 // within 1e-8 of) its recorded upper bound to the #197 KKT projection — a
4566 // literal 0.0 — so a probe at or past a box bound samples the frozen-axis
4567 // convention, not the criterion's tail: a fabricated hard-zero tail that
4568 // the drift band can never confirm. Out-of-box points are outside the
4569 // λ-selection domain altogether; they are not evidence for or against a
4570 // tail, so the ladder stops at the last strictly-in-domain probe.
4571 const PROBE_DOMAIN_MARGIN: f64 = 1.0e-6;
4572 // Upper rail (ρ → +∞): step ρ DOWN into the tail. Lower rail: step UP.
4573 let sign = match side {
4574 AsymptoteSide::Upper => -1.0,
4575 AsymptoteSide::Lower => 1.0,
4576 };
4577 // rows[r] corresponds to probe j=r+1: r=0 is the point CLOSEST to the rail,
4578 // increasing r steps further into the interior (larger |grad|).
4579 let mut rows: Vec<(f64, f64, Option<Array1<f64>>)> = Vec::new();
4580 for j in 1..=probe_count {
4581 let stepped = rho[coord] + sign * (j as f64) * probe_delta;
4582 if stepped <= domain.0 + PROBE_DOMAIN_MARGIN || stepped >= domain.1 - PROBE_DOMAIN_MARGIN {
4583 break;
4584 }
4585 let mut probe = rho.clone();
4586 probe[coord] = stepped;
4587 let eval = match obj.eval_with_order(&probe, OuterEvalOrder::ValueAndGradient) {
4588 Ok(eval) => eval,
4589 // A failed probe is not evidence against a tail; stop probing and
4590 // assess whatever clean run the earlier probes established.
4591 Err(_) => break,
4592 };
4593 if !eval.cost.is_finite()
4594 || coord >= eval.gradient.len()
4595 || !eval.gradient[coord].is_finite()
4596 {
4597 break;
4598 }
4599 rows.push((probe[coord], eval.gradient[coord], eval.inner_beta_hint));
4600 }
4601 let rows_summary = rows
4602 .iter()
4603 .map(|(r, g, _)| {
4604 format!(
4605 "(ρ={r:.2}, g={g:.3e}, ĉ={:.3e})",
4606 side.tail_constant(*r, *g)
4607 )
4608 })
4609 .collect::<Vec<_>>()
4610 .join(" ");
4611 if rows.len() < MIN_TAIL_SAMPLES {
4612 return Ok((None, rows_summary));
4613 }
4614
4615 // Per-row pencil constant ĉ and the element-clean predicate: ĉ above the
4616 // noise floor AND the gradient above the interior floor (so a row adjacent to
4617 // the rail, whose gradient has decayed into finite-difference cancellation, is
4618 // excluded rather than mistaken for a settled tail).
4619 let constants: Vec<f64> = rows
4620 .iter()
4621 .map(|(r, g, _)| side.tail_constant(*r, *g))
4622 .collect();
4623 let element_clean: Vec<bool> = rows
4624 .iter()
4625 .zip(&constants)
4626 .map(|((_, g, _), c)| {
4627 c.is_finite() && *c > tol.tail_noise_floor && g.abs() > tol.interior_grad_tol
4628 })
4629 .collect();
4630
4631 // Longest contiguous run that is element-clean AND holds ĉ within the drift
4632 // band; ties broken toward the rail (smallest start) for the most settled
4633 // estimand.
4634 let mut best: Option<(usize, usize)> = None;
4635 for a in 0..rows.len() {
4636 if !element_clean[a] {
4637 continue;
4638 }
4639 for b in a..rows.len() {
4640 if !element_clean[b] {
4641 break;
4642 }
4643 if b - a + 1 < MIN_TAIL_SAMPLES {
4644 continue;
4645 }
4646 if !run_drift_within_band(&constants[a..=b], tol.tail_drift_rel) {
4647 continue;
4648 }
4649 let len = b - a + 1;
4650 match best {
4651 Some((ba, bb)) if bb - ba + 1 >= len => {}
4652 _ => best = Some((a, b)),
4653 }
4654 }
4655 }
4656 let (a, b) = match best {
4657 Some(run) => run,
4658 None => return Ok((None, rows_summary)),
4659 };
4660
4661 // Build the window oldest → newest: newest (window `latest`) is the row
4662 // CLOSEST to the rail (r=a). A sample's coefficient move is ‖β(r) − β(r+1)‖,
4663 // the step from the next-farther retained row toward the rail.
4664 let mut window = AsymptoteWindow::with_capacity(b - a + 1);
4665 for r in (a..=b).rev() {
4666 let (rho_r, grad_r, beta_r) = &rows[r];
4667 let coef_step_norm = match (beta_r, rows.get(r + 1).map(|row| &row.2)) {
4668 (Some(cur), Some(Some(farther))) if cur.len() == farther.len() => {
4669 (cur - farther).iter().map(|v| v * v).sum::<f64>().sqrt()
4670 }
4671 _ => 0.0,
4672 };
4673 window.push(AsymptoteSample {
4674 rho: *rho_r,
4675 grad: *grad_r,
4676 coef_step_norm,
4677 });
4678 }
4679
4680 Ok((Some(window), rows_summary))
4681}
4682
4683/// Probe a JOINT multi-coordinate rail face (#2349 round 7 / #2348): step every
4684/// face coordinate one e-fold toward the interior TOGETHER and assess the
4685/// directional gradient along the outward face direction against the same
4686/// exponential tail law, noise floor, and drift band as the single-coordinate
4687/// window.
4688///
4689/// Why a joint law exists where the per-coordinate laws fail: for OVERLAPPING
4690/// penalties (e.g. the multinomial per-class family's coalesced pseudo-logdet
4691/// `½log|Σ_s λ_s M_s|₊`) several λs ride to ∞ on one face and share range
4692/// space. Moving ONE coordinate down leaves the shared term dominated by the
4693/// others, so that coordinate's own gradient saturates and its per-probe pencil
4694/// constant `ĉ_k = |g_k|e^{ρ_k}` sweeps orders of magnitude — measured on the
4695/// #2349 checkpoint: ĉ₀ spanning 4.5e2 → 1.0e-6 over the ladder, an honest
4696/// refusal of a law that genuinely does not hold marginally. Along the face
4697/// direction `u` (`u_k = +1` toward an upper rail, `−1` toward a lower rail)
4698/// the shared term moves coherently and the scalar objective section
4699/// `t ↦ V(ρ + t·u)` has the ordinary one-dimensional exponential tail; its
4700/// pencil constant is assessed with the pseudo-coordinate `r = mean_k(u_k ρ_k)`
4701/// and the directional derivative `g_u = Σ_{k∈face} u_k g_k = dV/dt`.
4702///
4703/// The window it returns speaks the [`assess_coordinate`] conventions
4704/// verbatim: on a genuine face `g_u < 0` at every interior probe (descent runs
4705/// outward), so the verdict side is `Upper` in the pseudo-coordinate
4706/// regardless of the mix of physical sides, and `ĉ = −e^{r}·g_u` recovers the
4707/// joint tail constant. Per-coordinate rails minted from it keep their own
4708/// physical [`AsymptoteSide`].
4709fn probe_joint_tail_window(
4710 obj: &mut dyn OuterObjective,
4711 rho: &Array1<f64>,
4712 face: &[(usize, AsymptoteSide)],
4713 tol: &AsymptoteTolerances,
4714 bounds: (&Array1<f64>, &Array1<f64>),
4715) -> Result<(Option<AsymptoteWindow>, String), EstimationError> {
4716 const PROBE_DELTA: f64 = 1.0;
4717 const PROBE_DOMAIN_MARGIN: f64 = 1.0e-6;
4718 let (lower, upper) = bounds;
4719 // Outward unit direction of the face; probes step INWARD (−u).
4720 let direction: Vec<(usize, f64)> = face
4721 .iter()
4722 .map(|(k, side)| {
4723 (
4724 *k,
4725 match side {
4726 AsymptoteSide::Upper => 1.0,
4727 AsymptoteSide::Lower => -1.0,
4728 },
4729 )
4730 })
4731 .collect();
4732 let r0 = direction
4733 .iter()
4734 .map(|(k, u)| u * rho[*k])
4735 .sum::<f64>()
4736 / direction.len() as f64;
4737 let mut rows: Vec<(f64, f64, Option<Array1<f64>>)> = Vec::new();
4738 for j in 1..=ASYMPTOTE_PROBE_COUNT {
4739 let step = (j as f64) * PROBE_DELTA;
4740 let mut probe = rho.clone();
4741 let mut in_domain = true;
4742 for (k, u) in &direction {
4743 let stepped = rho[*k] - u * step;
4744 if stepped <= lower[*k] + PROBE_DOMAIN_MARGIN
4745 || stepped >= upper[*k] - PROBE_DOMAIN_MARGIN
4746 {
4747 in_domain = false;
4748 break;
4749 }
4750 probe[*k] = stepped;
4751 }
4752 if !in_domain {
4753 break;
4754 }
4755 let eval = match obj.eval_with_order(&probe, OuterEvalOrder::ValueAndGradient) {
4756 Ok(eval) => eval,
4757 Err(_) => break,
4758 };
4759 if !eval.cost.is_finite() {
4760 break;
4761 }
4762 let mut g_u = 0.0;
4763 let mut finite = true;
4764 for (k, u) in &direction {
4765 match eval.gradient.get(*k) {
4766 Some(g) if g.is_finite() => g_u += u * g,
4767 _ => {
4768 finite = false;
4769 break;
4770 }
4771 }
4772 }
4773 if !finite {
4774 break;
4775 }
4776 rows.push((r0 - step, g_u, eval.inner_beta_hint));
4777 }
4778 let rows_summary = rows
4779 .iter()
4780 .map(|(r, g, _)| {
4781 format!(
4782 "(r={r:.2}, dV/dt={g:.3e}, ĉ={:.3e})",
4783 AsymptoteSide::Upper.tail_constant(*r, *g)
4784 )
4785 })
4786 .collect::<Vec<_>>()
4787 .join(" ");
4788 if rows.len() < MIN_TAIL_SAMPLES {
4789 return Ok((None, rows_summary));
4790 }
4791 let constants: Vec<f64> = rows
4792 .iter()
4793 .map(|(r, g, _)| AsymptoteSide::Upper.tail_constant(*r, *g))
4794 .collect();
4795 let element_clean: Vec<bool> = rows
4796 .iter()
4797 .zip(&constants)
4798 .map(|((_, g, _), c)| {
4799 c.is_finite() && *c > tol.tail_noise_floor && g.abs() > tol.interior_grad_tol
4800 })
4801 .collect();
4802 let mut best: Option<(usize, usize)> = None;
4803 for a in 0..rows.len() {
4804 if !element_clean[a] {
4805 continue;
4806 }
4807 for b in a..rows.len() {
4808 if !element_clean[b] {
4809 break;
4810 }
4811 if b - a + 1 < MIN_TAIL_SAMPLES {
4812 continue;
4813 }
4814 if !run_drift_within_band(&constants[a..=b], tol.tail_drift_rel) {
4815 continue;
4816 }
4817 let len = b - a + 1;
4818 match best {
4819 Some((ba, bb)) if bb - ba + 1 >= len => {}
4820 _ => best = Some((a, b)),
4821 }
4822 }
4823 }
4824 let (a, b) = match best {
4825 Some(run) => run,
4826 None => return Ok((None, rows_summary)),
4827 };
4828 let mut window = AsymptoteWindow::with_capacity(b - a + 1);
4829 for r in (a..=b).rev() {
4830 let (rho_r, grad_r, beta_r) = &rows[r];
4831 let coef_step_norm = match (beta_r, rows.get(r + 1).map(|row| &row.2)) {
4832 (Some(cur), Some(Some(farther))) if cur.len() == farther.len() => {
4833 (cur - farther).iter().map(|v| v * v).sum::<f64>().sqrt()
4834 }
4835 _ => 0.0,
4836 };
4837 window.push(AsymptoteSample {
4838 rho: *rho_r,
4839 grad: *grad_r,
4840 coef_step_norm,
4841 });
4842 }
4843 Ok((Some(window), rows_summary))
4844}
4845
4846/// Whether a run of pencil constants holds constant within the relative drift
4847/// band `(max − min)/|mean| ≤ band` (deterministic, ordered).
4848fn run_drift_within_band(constants: &[f64], band: f64) -> bool {
4849 if constants.len() < MIN_TAIL_SAMPLES {
4850 return false;
4851 }
4852 let mut sum = 0.0_f64;
4853 let mut lo = f64::INFINITY;
4854 let mut hi = f64::NEG_INFINITY;
4855 for &c in constants {
4856 if !c.is_finite() {
4857 return false;
4858 }
4859 sum += c;
4860 lo = lo.min(c);
4861 hi = hi.max(c);
4862 }
4863 let mean = sum / constants.len() as f64;
4864 if !(mean.abs() > 0.0) {
4865 return false;
4866 }
4867 (hi - lo) / mean.abs() <= band
4868}
4869
4870pub(crate) fn compute_rho_uncertainty_diagnostic(
4871 obj: &mut dyn OuterObjective,
4872 config: &OuterConfig,
4873 context: &str,
4874 result: &mut OuterResult,
4875) -> crate::rho_uncertainty::RhoUncertaintyDiagnostic {
4876 let terminal_cap_guard = config
4877 .outer_inner_cap
4878 .as_ref()
4879 .map(TerminalInnerCapGuard::lift);
4880 // Do not reset here. The diagnostic intentionally runs before terminal
4881 // installation and certification; the certificate must remain the final
4882 // owner of objective state. Holding cap=0 ensures proposal evaluations use
4883 // terminal fidelity, and the selected point is reinstalled immediately
4884 // afterward before the mint audit.
4885 let diagnostic =
4886 compute_rho_uncertainty_diagnostic_at_terminal_fidelity(obj, config, context, result);
4887 drop(terminal_cap_guard);
4888 diagnostic
4889}
4890
4891fn compute_rho_uncertainty_diagnostic_at_terminal_fidelity(
4892 obj: &mut dyn OuterObjective,
4893 config: &OuterConfig,
4894 context: &str,
4895 result: &mut OuterResult,
4896) -> crate::rho_uncertainty::RhoUncertaintyDiagnostic {
4897 let cap = obj.capability();
4898 let layout = cap.theta_layout();
4899 let rho_dim = layout.rho_dim();
4900 let gate = crate::rho_uncertainty::RhoUncertaintyCostGate {
4901 sample_count: 32,
4902 problem_size: config.rho_uncertainty_problem_size,
4903 };
4904 if let Err(reason) = crate::rho_uncertainty::cost_gate_allows(rho_dim, gate) {
4905 return crate::rho_uncertainty::RhoUncertaintyDiagnostic::skipped(reason, 0);
4906 }
4907 if result.rho.len() != layout.n_params {
4908 return crate::rho_uncertainty::RhoUncertaintyDiagnostic::skipped(
4909 format!(
4910 "final outer point length {} does not match objective dimension {}",
4911 result.rho.len(),
4912 layout.n_params
4913 ),
4914 0,
4915 );
4916 }
4917 // The ρ-uncertainty diagnostic needs the EXACT outer Hessian, but it runs
4918 // BEFORE terminal certification so that the certificate remains the final
4919 // owner of objective state. Under the optimize-3/certify-4 protocol (#2359)
4920 // this diagnostic may therefore consume only curvature the SEARCH already
4921 // retained. It must never trigger its own `ValueGradientHessian` evaluation:
4922 // a gradient-only search has deliberately reserved that one order-four pass
4923 // for the mint gate. Such a search skips this optional diagnostic; the
4924 // terminal certificate still computes and persists exact curvature.
4925 if !cap.hessian.is_analytic() {
4926 return crate::rho_uncertainty::RhoUncertaintyDiagnostic::skipped(
4927 "outer Hessian is not analytic; rho-uncertainty diagnostic needs exact curvature",
4928 0,
4929 );
4930 }
4931 if result.plan_used.hessian_source != HessianSource::Analytic {
4932 return crate::rho_uncertainty::RhoUncertaintyDiagnostic::skipped(
4933 "search did not use exact outer curvature; order-four work is reserved for the terminal certificate",
4934 0,
4935 );
4936 }
4937
4938 let hessian = match result.final_hessian.as_ref() {
4939 Some(hessian) => hessian.clone(),
4940 None => {
4941 return crate::rho_uncertainty::RhoUncertaintyDiagnostic::skipped(
4942 "search retained no exact outer Hessian; order-four work is reserved for the terminal certificate",
4943 0,
4944 );
4945 }
4946 };
4947 if hessian.nrows() != layout.n_params || hessian.ncols() != layout.n_params {
4948 return crate::rho_uncertainty::RhoUncertaintyDiagnostic::skipped(
4949 format!(
4950 "exact outer Hessian shape {}x{} does not match objective dimension {}",
4951 hessian.nrows(),
4952 hessian.ncols(),
4953 layout.n_params
4954 ),
4955 1,
4956 );
4957 }
4958 let mut hessian_rho = Array2::<f64>::zeros((rho_dim, rho_dim));
4959 for row in 0..rho_dim {
4960 for col in 0..rho_dim {
4961 hessian_rho[[row, col]] = hessian[[row, col]];
4962 }
4963 }
4964 let rho_hat = result.rho.slice(ndarray::s![..rho_dim]).to_owned();
4965 let theta_hat = result.rho.clone();
4966 let cost_hat = result.final_value;
4967 let diagnostic = {
4968 let mut served_hat_cost = false;
4969 let mut criterion = |rho: &Array1<f64>| -> Option<f64> {
4970 let is_hat = rho.len() == rho_hat.len()
4971 && rho
4972 .iter()
4973 .zip(rho_hat.iter())
4974 .all(|(&left, &right)| left.to_bits() == right.to_bits());
4975 if is_hat && !served_hat_cost {
4976 served_hat_cost = true;
4977 return Some(cost_hat);
4978 }
4979 let mut theta = theta_hat.clone();
4980 for idx in 0..rho_dim {
4981 theta[idx] = rho[idx];
4982 }
4983 obj.eval_cost(&theta).ok()
4984 };
4985 crate::rho_uncertainty::rho_uncertainty_diagnostic(
4986 &rho_hat,
4987 &hessian_rho,
4988 gate,
4989 &mut criterion,
4990 )
4991 };
4992 match &diagnostic.status {
4993 crate::rho_uncertainty::RhoUncertaintyStatus::NoEvidenceOfHeavyTails => {
4994 log::info!(
4995 "[RHO uncertainty] {context}: no heavy-tail evidence at sampled rho proposals k_hat={:.3} evals={}",
4996 diagnostic.k_hat.unwrap_or(f64::NAN),
4997 diagnostic.n_evaluations,
4998 );
4999 }
5000 crate::rho_uncertainty::RhoUncertaintyStatus::HeavyTailsDetected { k_hat } => {
5001 log::warn!(
5002 "[RHO uncertainty] {context}: heavy rho-importance tail detected k_hat={:.3} evals={}",
5003 k_hat,
5004 diagnostic.n_evaluations,
5005 );
5006 }
5007 crate::rho_uncertainty::RhoUncertaintyStatus::Skipped { reason } => {
5008 log::info!("[RHO uncertainty] {context}: skipped ({reason})");
5009 }
5010 }
5011 diagnostic
5012}
5013
5014#[derive(Clone, Copy, Debug, PartialEq, Eq)]
5015pub enum OperatorTrustRegionStopReason {
5016 Converged,
5017 RejectFloor,
5018 IterationBudget,
5019 /// The objective stopped changing on a criterion-flat surface. The
5020 /// in-loop guard may already have certified the score-relative residual or
5021 /// may have returned a non-stationary floor; either way the final analytic
5022 /// certificate needs this provenance to reproduce the guard's derived
5023 /// stationarity band exactly.
5024 CostStallFlatValley,
5025 /// Family returned a non-operator Hessian mid-flight after routing into
5026 /// the operator path. Best-effort `x_k` returned with this reason; the
5027 /// caller should consider re-fitting under a different solver class
5028 /// (e.g. BFGS gradient-only) instead of trusting the partial result.
5029 RoutingMismatch,
5030}
5031
5032/// Run the outer smoothing-parameter optimization.
5033///
5034/// This is the single entry point that replaces the scattered optimizer wiring
5035/// across estimate.rs, joint.rs, and custom_family.rs. It:
5036///
5037/// 1. Queries and canonicalizes the objective's capability declaration.
5038/// 2. Calls `plan()` to select solver + hessian source.
5039/// 3. Logs the plan and the analytic derivative capabilities it will consume.
5040/// 4. Generates seed candidates.
5041/// 5. Runs the chosen solver on candidates in heuristic order up to budget.
5042/// 6. If the configured fallback policy allows it, re-plans with degraded
5043/// capabilities chosen centrally inside outer_strategy and retries.
5044/// 7. Returns the best result (including which plan was actually used).
5045///
5046/// Do not wrap `run_outer` calls in try/catch with ad-hoc solver recovery.
5047/// Callers should declare only the primary capability and, at most, whether
5048/// automatic fallback is enabled at all.
5049///
5050/// Bound on the certify-last checkpoint-resume loop (#2273/#2374). When a
5051/// solver CLAIMS convergence but the mandatory analytic certificate refuses,
5052/// the loop re-runs the outer search seeded AT the refused checkpoint (with a
5053/// fresh metric for gradient-only outers, since `final_hessian` is `None`)
5054/// while each resume strictly reduces the objective — real descent the claim
5055/// left unexploited. #2273 introduced this as a SINGLE retry for the
5056/// stale-tolerance desync (one reseed re-anchors the in-loop tolerance to the
5057/// terminal cost scale and certifies). #2374 generalized it to a
5058/// progress-bounded loop: a gradient-only `opt::Bfgs` outer in log-λ space can
5059/// exit its flat-valley `StallPolicy` at `‖g‖∞ ≤ tol·(1 + ‖ρ‖∞)` — a gate
5060/// inflated ~10× by a railed coordinate — reporting `Ok(converged)` at a
5061/// checkpoint whose projected gradient the un-inflated certificate correctly
5062/// rejects, and a single fresh-metric reseed rarely lands the optimum in one
5063/// hop (the transformation-survival LAML of #2373 needed two). This bound caps
5064/// how many such reseeds are attempted before the honest non-convergence is
5065/// surfaced; a fit that certifies on the first pass never enters the loop, and
5066/// a reseed that fails to reduce the objective (a genuine non-stationary floor
5067/// a fresh metric cannot escape) stops the loop immediately regardless of the
5068/// remaining budget.
5069const OUTER_CERTIFY_RESUME_BUDGET: usize = 16;
5070
5071/// Max interior strict-saddle escape resumes (#2357/#2155). A genuine saddle is
5072/// cleared in one escape; the small cap keeps a pathological non-convergent
5073/// objective (e.g. a bimodal inner solve, #2363) from re-escaping a family of
5074/// shallow saddles until the general resume budget is spent.
5075pub(crate) const OUTER_SADDLE_ESCAPE_BUDGET: usize = 3;
5076
5077/// Roundoff-relative scale below which a certify-last reseed's objective
5078/// reduction is numerical noise rather than exploited descent (#2374). A
5079/// fresh-metric BFGS restart seeded AT the refused checkpoint can only reduce
5080/// the objective from that checkpoint, so `retried == prior` (to roundoff)
5081/// means the restart found no descent — a genuine stationary floor — while a
5082/// false flat-valley stall yields a reduction orders of magnitude above this
5083/// scale. The progress gate MUST anchor on roundoff, not the much larger
5084/// cost-stall relative floor: a flat valley crawls out in per-reseed steps far
5085/// smaller than `rel_cost·(1 + |cost|)` (the transformation-survival LAML moves
5086/// ~4e-5 relative per reseed), and gating on that coarser floor stops the crawl
5087/// after a single hop and refuses a well-posed fit.
5088const CERTIFY_RESUME_PROGRESS_REL: f64 = 32.0 * f64::EPSILON;
5089
5090pub(crate) fn run_outer(
5091 obj: &mut dyn OuterObjective,
5092 config: &OuterConfig,
5093 context: &str,
5094) -> Result<OuterResult, EstimationError> {
5095 // Permutation-invariant outer search (#1538/#1539). When the caller has
5096 // supplied per-coordinate structural keys that induce a non-identity
5097 // canonical order, run the ENTIRE outer pipeline (seeding, multistart,
5098 // optimization, and the #934 certificate / uncertainty audits) in that
5099 // canonical layout against a permuting wrapper, then map the result back to
5100 // the native layout. Seeding/tie-breaking then see byte-identical
5101 // coordinates for every term order, so both orders select the same λ̂.
5102 if let Some(keys) = config.rho_canonical_keys.as_ref()
5103 && let Some(perm) = canonical_permutation(keys)
5104 {
5105 let canonical_config = canonicalize_outer_config(config, &perm);
5106 let mut canonical_obj = CanonicalizedObjective::new(obj, perm.clone());
5107 let result = run_outer(&mut canonical_obj, &canonical_config, context)?;
5108 return Ok(outer_result_to_native(result, &perm));
5109 }
5110 let mut result = run_outer_uncertified(obj, config, context)?;
5111 if obj.begin_exact_polish() {
5112 // A sampled outer-derivative pilot is an optimization stage, never a
5113 // certifiable objective. Continue from its best checkpoint on the
5114 // family's exact full-data measure before the mandatory analytic
5115 // certificate. This transition is unconditional whenever the family
5116 // reports that a sample actually ran, so convergence before a nominal
5117 // phase budget cannot strand the optimizer on the stochastic surface
5118 // (#979: matrix-free TR stopped after 6 evaluations while the family
5119 // waited for a 12-evaluation counter).
5120 let pilot_iterations = result.iterations;
5121 let mut exact_config = config.clone();
5122 exact_config.initial_rho = Some(result.rho.clone());
5123 exact_config.heuristic_lambdas = None;
5124 exact_config.seed_config.max_seeds = 1;
5125 exact_config.seed_config.seed_budget = 1;
5126 exact_config.screen_initial_rho = false;
5127 exact_config.operator_initial_trust_radius = result.operator_trust_radius;
5128 exact_config.warm_start_outer_hessian = result.final_hessian.clone();
5129 log::info!(
5130 "[OUTER] {context}: sampled derivative pilot completed after {} iteration(s); \
5131 continuing from its checkpoint on the exact full-data measure",
5132 pilot_iterations,
5133 );
5134 let mut polished = run_outer_uncertified(obj, &exact_config, context)?;
5135 polished.iterations = polished.iterations.saturating_add(pilot_iterations);
5136 result = polished;
5137 }
5138 // Mandatory analytic optimality certificate (#934): once at the selected
5139 // point, outside every hot loop, for every solver path and every iteration
5140 // budget. Missing or failed evidence is typed non-convergence; there is no
5141 // max-iteration or logging-level bypass.
5142 //
5143 // #2273 STALE-TOLERANCE DESYNC RETRY. The solver's in-loop convergence
5144 // threshold is resolved ONCE from the SEED's cost scale
5145 // (`rel_cost·(1+|seed_cost|)`), while this certificate re-derives the
5146 // same formula at the terminal point's own (often far smaller) cost — on
5147 // a perfectly-separated binomial the score plunges between the
5148 // oversmoothed heuristic seed and the first accepted step, so the solver
5149 // can declare victory against a bound orders of magnitude looser than
5150 // the one that then refuses it here (measured: |g|=8.1e-1 accepted
5151 // in-loop vs bound 8.3e-3 at certification, 'NOT STATIONARY after 1
5152 // outer iteration'; the pass/fail pattern was non-monotone in n because
5153 // it tracked the seed-to-terminus cost ratio, not identifiability). The
5154 // desync exists precisely because the tolerance anchor differs from the
5155 // terminus, so ONE re-run seeded AT the refused checkpoint removes it by
5156 // construction: the retry's seed cost IS the certificate's cost, its
5157 // in-loop bound equals the certificate bound, and the solver either
5158 // genuinely closes the remaining gradient gap or exhausts its budget and
5159 // takes the same typed refusal as before. Bounded to a single retry;
5160 // only fires when the solver CLAIMED convergence (a budget-exhausted
5161 // result is not a desync — its refusal is genuine).
5162 // CERTIFICATION-LAST FIT OWNERSHIP. The uncertainty diagnostic evaluates
5163 // proposal points after theta-hat and the terminal reinstallation
5164 // re-evaluates at `result.rho`, so any certificate measured BEFORE them
5165 // describes a state the caller never receives: on a nonconvex profile the
5166 // certificate-time inner mode and the finally-installed inner mode can sit
5167 // in different coefficient basins (measured on the cause-specific survival
5168 // gate as a stable bitwise mismatch, terminal 9.1931e2 vs certified
5169 // 9.1671e2, because the two paths prime the inner solve under different
5170 // eval orders). Running the diagnostic and the terminal installation
5171 // FIRST and certifying LAST makes the certificate's own evaluation the
5172 // final objective-state installer, so the sealed terminal identity fit
5173 // assembly binds against IS the certified evidence — bitwise, by
5174 // construction, independent of basin multiplicity.
5175 let certify_diagnose_and_install = |obj: &mut dyn OuterObjective,
5176 result: &mut OuterResult|
5177 -> Result<OuterCriterionCertificate, EstimationError> {
5178 result.rho_uncertainty_diagnostic = Some(compute_rho_uncertainty_diagnostic(
5179 obj, config, context, result,
5180 ));
5181 // Reinstall the selected point under cap=0 so the certificate below
5182 // measures the full-fidelity state belonging to `result.rho`, not
5183 // the diagnostic's final proposal (seeding beta alone does not
5184 // restore weights, factors, or link state). Reset forces a real
5185 // installation instead of an LRU value hit.
5186 let terminal_cap_guard = config
5187 .outer_inner_cap
5188 .as_ref()
5189 .map(TerminalInnerCapGuard::lift);
5190 // Reset is conditional on the cap contract, mirroring
5191 // `certify_outer_optimality`'s own doctrine: REML/mixture
5192 // objectives with a cap can hold a coarse search cache that must
5193 // not be installed as terminal state, while uncapped stateful
5194 // objectives (reactive-domain entries among them) retain the very
5195 // state their evaluation at `result.rho` depends on — an
5196 // unconditional reset here wiped it and made the certification
5197 // evaluation non-finite on the reactive fixture.
5198 //
5199 // OR-in the terminal-coefficient-mode ownership signal (#2334):
5200 // objectives that install an owned coefficient mode here but hold
5201 // their inner cap in a different field (custom families) leave
5202 // `outer_inner_cap` `None`, so the cap gate alone never fires and
5203 // `finalize` here could land in a different inner basin than the
5204 // certifying re-eval below — a spurious bitwise bind failure on a
5205 // bimodal inner solve. Forcing the reset for mode-owning objectives
5206 // makes both installations start from the same clean baseline.
5207 if terminal_cap_guard.is_some() || obj.owns_terminal_coefficient_mode() {
5208 obj.reset();
5209 }
5210 let terminal_installation = obj.finalize_outer_result(&result.rho, &result.plan_used);
5211 let terminal_inner_converged = inner_solve_converged(config.outer_inner_cap.as_ref());
5212 drop(terminal_cap_guard);
5213 terminal_installation?;
5214 if !terminal_inner_converged {
5215 return Err(outer_nonconvergence_error(
5216 context,
5217 "final outer state installation did not converge at full inner fidelity",
5218 result,
5219 result.final_grad_norm,
5220 StationarityBound::unrecorded(outer_gradient_tolerance(config).abs),
5221 ));
5222 }
5223 certify_outer_optimality(obj, config, context, result)
5224 };
5225 // Certify-last checkpoint-resume loop (#2273 stale-tolerance desync,
5226 // generalized by #2374). A solver that CLAIMS convergence but fails the
5227 // mandatory analytic certificate is re-run once per iteration seeded AT the
5228 // refused checkpoint — re-anchoring the in-loop tolerance to the terminal
5229 // cost scale and, for gradient-only outers (`final_hessian == None`),
5230 // restarting `opt::Bfgs` with a fresh inverse-Hessian metric that breaks the
5231 // flat-valley `StallPolicy` false stop the accumulated metric crawled into.
5232 // Looping (rather than the original single retry) matters because that stall
5233 // gate is inflated by `(1 + ‖ρ‖∞)` in log-λ space, so one fresh-metric
5234 // reseed rarely lands the optimum in a single hop. The loop stops the moment
5235 // certification passes; it also stops — regardless of remaining budget —
5236 // when a reseed fails to strictly reduce the objective, because a point that
5237 // a fresh-metric restart cannot improve is a genuine non-stationary floor
5238 // (or a true flat valley), not an exploitable false stall, and further
5239 // reseeds would only re-derive the same refusal. A result that never claimed
5240 // convergence (e.g. a budget-exhausted `MaxIterationsReached`) is refused
5241 // immediately with no reseed: its non-convergence is genuine.
5242 let mut resumes_remaining = OUTER_CERTIFY_RESUME_BUDGET;
5243 // Interior strict-saddle escapes are bounded separately and tightly: a real
5244 // saddle is cleared in one hop, so a handful of attempts is ample, while a
5245 // non-convergent bimodal-inner grind (#2155/#2363) is cut off well before it
5246 // exhausts the general resume budget (#2357).
5247 let mut saddle_escapes_remaining: usize = OUTER_SADDLE_ESCAPE_BUDGET;
5248 let certificate = loop {
5249 let claimed_converged = result.converged;
5250 match certify_diagnose_and_install(obj, &mut result) {
5251 Ok(certificate) => break certificate,
5252 Err(refusal) => {
5253 // #2357/#2155 — interior strict-saddle escape. When the refusal
5254 // is a first-order-stationary point whose reduced (off-railed)
5255 // Hessian is indefinite, the certificate publishes a
5256 // negative-curvature reseed stepped strictly BELOW the saddle
5257 // (`negative_curvature_escape_point`). Reseeding the resume at the
5258 // refused checkpoint itself would re-descend straight back to that
5259 // zero-gradient saddle — the #2273/#2374 stale-tolerance resume
5260 // anchors the tolerance and breaks flat-valley stalls, but it
5261 // cannot break a genuine saddle. Seed at the escape point instead,
5262 // off the ridge, and start from a FRESH outer metric so the
5263 // saddle's indefinite curvature is not transferred into the
5264 // restart. This is the run_outer-level consumer of the reseed that
5265 // the multistart-loop consumer (`run_outer_with_plan`) mints only
5266 // when a per-seed claim is already stationary; the terminal
5267 // certificate is where stationarity is reached for the binomial
5268 // link-wiggle families, so without this the reseed was minted and
5269 // then dropped.
5270 let saddle_escape_reseed = result.saddle_escape_reseed.take();
5271 let resume_from_saddle_escape = saddle_escape_reseed.is_some();
5272 // #2348 Inc 2b, completed (#2349 round 8): a confirmed-tail
5273 // snap that needs a re-descent publishes the snapped face as
5274 // `tail_snap_reseed` — previously minted and then DROPPED
5275 // (declared, set, never consumed), so every ConfirmedNeedsReseed
5276 // outcome fell through to the plain refusal. The joint tail law
5277 // is first-order evidence of WHERE the optimum is, so the retry
5278 // is warranted regardless of the solver's convergence claim,
5279 // exactly like the saddle-escape reseed (measured on the #2349
5280 // fixture: the face snap descends 3.86 with |Pg| dropping
5281 // 2.05 → 0.35; the retry lets over-snapped coordinates relax
5282 // back to their interior optima while the rest hold the rail).
5283 let tail_snap_reseed = if resume_from_saddle_escape {
5284 result.tail_snap_reseed.take();
5285 None
5286 } else {
5287 result.tail_snap_reseed.take()
5288 };
5289 let resume_from_tail_snap = tail_snap_reseed.is_some();
5290 // #2392 — wrong-rail pull-back and active-set reduction reseeds,
5291 // consumed with LOWER precedence than the saddle/tail-snap
5292 // reseeds. A higher-precedence reseed DROPS them (take-and-discard)
5293 // so no stale reseed leaks into a later iteration, exactly as the
5294 // saddle escape drops a co-minted tail snap above. Both are
5295 // first-order evidence (a proven inward descent / a poisoned-rail
5296 // interior), so — like the tail snap — they fire regardless of the
5297 // solver's convergence claim.
5298 let higher_precedence_reseed = resume_from_saddle_escape || resume_from_tail_snap;
5299 let wrong_rail_reseed = if higher_precedence_reseed {
5300 result.wrong_rail_reseed.take();
5301 None
5302 } else {
5303 result.wrong_rail_reseed.take()
5304 };
5305 let resume_from_wrong_rail = wrong_rail_reseed.is_some();
5306 let active_set_reseed = if higher_precedence_reseed || resume_from_wrong_rail {
5307 result.active_set_reseed.take();
5308 None
5309 } else {
5310 result.active_set_reseed.take()
5311 };
5312 let resume_from_active_set = active_set_reseed.is_some();
5313 let active_set_rho = active_set_reseed.as_ref().map(|a| a.rho.clone());
5314 let active_set_bounds = active_set_reseed.map(|a| a.bounds);
5315 // A published reseed means the refused point IS first-order
5316 // stationary (the escape mint gate requires `is_stationary`), so
5317 // it is a genuine saddle escapable regardless of whether the
5318 // solver "claimed" convergence: for exact-Hessian link-wiggle
5319 // families the terminal certificate — not the in-loop gate — is
5320 // where stationarity is first reached, so they arrive here with
5321 // `converged == false` yet stationary. The #2273/#2374
5322 // stale-tolerance resume, which reseeds AT the refused checkpoint,
5323 // still requires a genuine convergence claim (a budget-exhausted
5324 // non-stationary iterate has no desync to remove).
5325 if (!claimed_converged
5326 && !resume_from_saddle_escape
5327 && !resume_from_tail_snap
5328 && !resume_from_wrong_rail
5329 && !resume_from_active_set)
5330 || resumes_remaining == 0
5331 || (resume_from_saddle_escape && saddle_escapes_remaining == 0)
5332 {
5333 return Err(refusal);
5334 }
5335 resumes_remaining -= 1;
5336 if resume_from_saddle_escape {
5337 // Genuine strict saddles are cleared in one escape (the fresh
5338 // ARC step off the ridge descends straight to the PSD
5339 // minimum). A SMALL cap stops a pathological objective — e.g. a
5340 // bimodal inner solve whose warm re-descent keeps reporting a
5341 // phantom improvement that the cold certificate cannot
5342 // reproduce (#2155 / #2363) — from burning the whole resume
5343 // budget re-escaping a family of shallow saddles that never
5344 // certifies. Past the cap the honest refusal is taken.
5345 saddle_escapes_remaining -= 1;
5346 }
5347 let prior_iterations = result.iterations;
5348 let prior_value = result.final_value;
5349 log::info!(
5350 "[OUTER] {context}: analytic certification refused after \
5351 {prior_iterations} iteration(s) (final_value={prior_value:.6e}); re-running \
5352 seeded {} so the in-loop tolerance anchors to the terminal cost scale \
5353 ({resumes_remaining} resume(s) left after this one; #2273/#2374/#2155)",
5354 if resume_from_saddle_escape {
5355 "off the negative-curvature saddle ridge"
5356 } else if resume_from_tail_snap {
5357 "at the confirmed-tail snapped face"
5358 } else if resume_from_wrong_rail {
5359 "at the wrong-rail coordinate's clean-band interior scale"
5360 } else if resume_from_active_set {
5361 "with the poisoned rail frozen so the interior polishes in the reduced box"
5362 } else {
5363 "at the refused checkpoint"
5364 }
5365 );
5366 let mut retry_cfg = config.clone();
5367 retry_cfg.initial_rho = Some(
5368 saddle_escape_reseed
5369 .or(tail_snap_reseed)
5370 .or(wrong_rail_reseed)
5371 .or(active_set_rho)
5372 .unwrap_or_else(|| result.rho.clone()),
5373 );
5374 // Active-set reduction (#2392): the polish runs in the REDUCED
5375 // (frozen) box so the interior converges without the railed
5376 // coordinate's ill-conditioned Hessian row poisoning the step. The
5377 // loop re-certifies the polished point under the ORIGINAL box at
5378 // the top of the next iteration (`certify_diagnose_and_install`
5379 // captures the original `config`), so a frozen coordinate whose
5380 // gradient turns inward there un-freezes through the wrong-rail
5381 // path — no silent clamping — while a genuine rail certifies with
5382 // the interior now stationary. `config.clone()` reset each
5383 // iteration, so the frozen box never persists past this run.
5384 if let Some(frozen_bounds) = active_set_bounds {
5385 retry_cfg.bounds = Some(frozen_bounds);
5386 }
5387 retry_cfg.heuristic_lambdas = None;
5388 retry_cfg.seed_config.max_seeds = 1;
5389 retry_cfg.seed_config.seed_budget = 1;
5390 retry_cfg.screen_initial_rho = false;
5391 // Every reseed kind lands at a genuinely different point, so the
5392 // refused checkpoint's metric (trust radius, outer Hessian)
5393 // must not be transferred into the restart.
5394 let fresh_metric = resume_from_saddle_escape
5395 || resume_from_tail_snap
5396 || resume_from_wrong_rail
5397 || resume_from_active_set;
5398 retry_cfg.operator_initial_trust_radius = if fresh_metric {
5399 None
5400 } else {
5401 result.operator_trust_radius
5402 };
5403 retry_cfg.warm_start_outer_hessian = if fresh_metric {
5404 None
5405 } else {
5406 result.final_hessian.clone()
5407 };
5408 obj.reset();
5409 match run_outer_uncertified(obj, &retry_cfg, context) {
5410 Ok(mut retried) => {
5411 retried.iterations = retried.iterations.saturating_add(prior_iterations);
5412 // Progress gate. A fresh-metric reseed seeded AT the
5413 // checkpoint can only descend from it, so a reduction at
5414 // roundoff scale means it found no descent — a genuine
5415 // stationary floor — while a false flat-valley stall
5416 // yields a reduction orders of magnitude larger. Gate on
5417 // roundoff (NOT the coarser cost-stall floor) so a valley
5418 // that crawls out in tiny per-reseed steps is not cut off
5419 // after one hop; stop only when a reseed truly stalls, so
5420 // the next iteration certifies the best point once more
5421 // and takes the honest refusal.
5422 let improved = certify_resume_made_progress(
5423 prior_value,
5424 retried.final_value,
5425 CERTIFY_RESUME_PROGRESS_REL,
5426 );
5427 result = retried;
5428 if !improved {
5429 resumes_remaining = 0;
5430 }
5431 }
5432 // The reseed could not even run (e.g. the checkpoint is a
5433 // hard refusal wall for the objective): surface the
5434 // certification refusal from the point we started this
5435 // iteration at, which carries the checkpoint evidence.
5436 Err(_) => return Err(refusal),
5437 }
5438 }
5439 }
5440 };
5441 result.criterion_certificate = Some(certificate);
5442 Ok(result)
5443}
5444
5445/// Build a CANONICAL-order copy of an [`OuterConfig`] for the
5446/// permutation-invariant outer search (#1538/#1539).
5447///
5448/// `perm[c]` is the native coordinate at canonical slot `c`. Every
5449/// per-coordinate config field (initial ρ seed, heuristic-λ seed, per-axis
5450/// bounds, transferred warm Hessian) is reordered native→canonical so the
5451/// optimizer's seeding and multistart operate entirely in canonical space;
5452/// scalar fields are copied verbatim. `rho_canonical_keys` is cleared so the
5453/// recursive [`run_outer`] frame runs the normal (identity-order) pipeline on
5454/// the already-canonical objective.
5455fn canonicalize_outer_config(config: &OuterConfig, perm: &[usize]) -> OuterConfig {
5456 // Permute a per-coordinate slice native→canonical; pass through any length
5457 // that does not match the permutation (defensive — should not occur).
5458 let permute_vec = |v: &[f64]| -> Vec<f64> {
5459 if v.len() == perm.len() {
5460 perm.iter().map(|&i| v[i]).collect()
5461 } else {
5462 v.to_vec()
5463 }
5464 };
5465 let permute_arr = |a: &Array1<f64>| -> Array1<f64> {
5466 if a.len() == perm.len() {
5467 Array1::from_iter(perm.iter().map(|&i| a[i]))
5468 } else {
5469 a.clone()
5470 }
5471 };
5472 let mut canonical = config.clone();
5473 canonical.rho_canonical_keys = None;
5474 if let Some(initial) = config.initial_rho.as_ref() {
5475 canonical.initial_rho = Some(permute_arr(initial));
5476 }
5477 if let Some(bound) = config.initial_inner_seed.as_ref() {
5478 canonical.initial_inner_seed = Some(BoundInnerSeed {
5479 theta: permute_arr(&bound.theta),
5480 beta: bound.beta.clone(),
5481 });
5482 }
5483 if let Some(h) = config.heuristic_lambdas.as_ref() {
5484 canonical.heuristic_lambdas = Some(permute_vec(h));
5485 }
5486 if let Some((lower, upper)) = config.bounds.as_ref() {
5487 canonical.bounds = Some((permute_arr(lower), permute_arr(upper)));
5488 }
5489 // A transferred dense outer Hessian is in native coordinate order; permute
5490 // it into canonical order so the BFGS warm metric stays aligned. (None on
5491 // the cold-start canonicalized path, so this is usually a no-op.)
5492 if let Some(h) = config.warm_start_outer_hessian.as_ref()
5493 && h.nrows() == perm.len()
5494 && h.ncols() == perm.len()
5495 {
5496 let mut hc = Array2::<f64>::zeros((perm.len(), perm.len()));
5497 for (a, &ia) in perm.iter().enumerate() {
5498 for (b, &ib) in perm.iter().enumerate() {
5499 hc[[a, b]] = h[[ia, ib]];
5500 }
5501 }
5502 canonical.warm_start_outer_hessian = Some(hc);
5503 }
5504 canonical
5505}
5506
5507/// The solver ladder behind [`run_outer`], without the #934 self-audit.
5508pub(crate) fn run_outer_uncertified(
5509 obj: &mut dyn OuterObjective,
5510 config: &OuterConfig,
5511 context: &str,
5512) -> Result<OuterResult, EstimationError> {
5513 let cap = primary_capability_for_config(obj.capability(), config, context);
5514 cap.validate_layout(context)?;
5515 // #2370: reject a degenerate / inverted ρ-box up front, as a typed error.
5516 // Every downstream stage — the per-atom EFS path below and
5517 // `run_outer_with_plan` — projects seeds against these bounds with
5518 // `f64::clamp`, whose `min > max` (or NaN) precondition panics *inside the
5519 // Rust boundary* and surfaces as an opaque `GamError: ... panicked` across
5520 // the FFI, violating the fail-loudly contract. The configured box can invert
5521 // whenever an independently-derived upper bound drifts below the lower wall
5522 // (e.g. the custom-family effective-df ceiling vs. `rho_lower_bound`).
5523 // Validating the *effective* template here — the same one every consumer
5524 // reads — turns any such inversion into `EstimationError::InvalidInput`
5525 // regardless of how the bounds were constructed.
5526 {
5527 let (bound_lo, bound_hi) = outer_bounds_template(config, cap.n_params);
5528 for i in 0..bound_lo.len() {
5529 if !(bound_lo[i].is_finite() && bound_hi[i].is_finite()) {
5530 return Err(EstimationError::InvalidInput(format!(
5531 "{context}: outer rho bounds are non-finite at coordinate {i}: \
5532 lower={}, upper={}",
5533 bound_lo[i], bound_hi[i]
5534 )));
5535 }
5536
5537 // Report a collapsed interval with BOTH walls. `outer_bounds` below
5538 // is the backstop and rejects the same condition, but its message
5539 // names only the coordinate. The panic this guard replaced printed
5540 // `min = -10.0, max = -11.855421656441532`, and those two numbers
5541 // are what made #2370 diagnosable from a bug report alone: they
5542 // identify WHICH pair of independently-derived bounds drifted, and
5543 // by how much. A typed error must not be a weaker diagnostic than
5544 // the panic it replaced.
5545 //
5546 // Two tests constrain this string: `inverted_rho_box_is_a_typed_
5547 // error_not_a_clamp_panic_2370` greps for the word "bound", and
5548 // `the_inverted_box_refusal_carries_both_bound_values_2370` pins
5549 // both numeric walls. Keep both when rewording.
5550 if bound_lo[i] > bound_hi[i] {
5551 return Err(EstimationError::InvalidInput(format!(
5552 "{context}: outer rho bounds are inverted at coordinate {i}: \
5553 lower bound {} exceeds upper bound {}",
5554 bound_lo[i], bound_hi[i]
5555 )));
5556 }
5557 }
5558 outer_bounds(&bound_lo, &bound_hi)
5559 .map_err(|err| EstimationError::InvalidInput(format!("{context}: {err}")))?;
5560 }
5561 if let Some(initial_rho) = config.initial_rho.as_ref() {
5562 cap.theta_layout()
5563 .validate_point_len(initial_rho, "initial outer seed")
5564 .map_err(|err| match err {
5565 ObjectiveEvalError::Recoverable { message }
5566 | ObjectiveEvalError::Fatal { message } => {
5567 EstimationError::RemlOptimizationFailed(format!("{context}: {message}"))
5568 }
5569 })?;
5570 }
5571 // Frontier ρ-scaling auto-switch (#986): at per-atom-EFS-eligible frontier
5572 // rho dimension the decoupled per-atom fixed point is the primary outer
5573 // iteration; everything else falls through to the dense / standard path
5574 // below. Routed here so every entry point inherits it (magic by default).
5575 if let Some(result) = run_per_atom_efs_if_frontier(obj, config, context)? {
5576 if result.converged {
5577 return Ok(result);
5578 }
5579 return Err(outer_nonconvergence_error(
5580 context,
5581 "per-atom EFS exhausted its iteration budget before the fixed-point step converged",
5582 &result,
5583 None,
5584 StationarityBound::unrecorded(outer_gradient_tolerance(config).abs),
5585 ));
5586 }
5587
5588 if cap.n_params == 0 {
5589 let cost = obj.eval_cost(&Array1::zeros(0))?;
5590 let the_plan = plan(&cap);
5591 return Ok(outer_result_with_gradient_norm(
5592 Array1::zeros(0),
5593 cost,
5594 0,
5595 Some(0.0),
5596 true,
5597 the_plan,
5598 ));
5599 }
5600
5601 // Build the ordered list of capabilities to attempt: primary first, then
5602 // any centrally-derived degraded capabilities. Aux direct-search has no
5603 // degraded ladder — a single attempt either succeeds or the failure is
5604 // surfaced to the caller.
5605 let fallback_attempts = match config.fallback_policy {
5606 FallbackPolicy::Automatic => automatic_fallback_attempts(&cap),
5607 FallbackPolicy::Disabled => Vec::new(),
5608 };
5609 let mut attempts: Vec<OuterCapability> = Vec::with_capacity(1 + fallback_attempts.len());
5610 attempts.push(cap.clone());
5611 for degraded in fallback_attempts {
5612 attempts.push(degraded);
5613 }
5614
5615 let mut last_error: Option<EstimationError> = None;
5616 let mut best_checkpoint: Option<OuterResult> = None;
5617
5618 for (attempt_idx, attempt_cap) in attempts.iter().enumerate() {
5619 let the_plan = plan(attempt_cap);
5620 if attempt_idx > 0 {
5621 log::debug!("[OUTER] {context}: primary plan failed; falling back to {the_plan}");
5622 }
5623 log_plan(context, attempt_cap, &the_plan);
5624
5625 obj.reset();
5626
5627 // ARC budget-exhaustion retry: when an Arc attempt runs out of
5628 // outer iterations, reseed a fresh Arc run from the previous
5629 // attempt's last ρ and trust radius. Inner caches (PIRLS LRU,
5630 // eval bundle, warm-start predictor, adaptive signals) are wiped
5631 // by `obj.reset()`; the operator-TR's Cauchy/Newton/CG state has
5632 // no resume API and is not preserved. The lever that changes for
5633 // the resumed run is the inner-PIRLS cap (uncapped via the
5634 // feedback handle), not `max_iter` — empirically the prior stall
5635 // was an inner-tolerance / model-fidelity issue, not an outer
5636 // budget shortfall, and doubling `max_iter` only replays the
5637 // same trajectory byte-for-byte. The retry is gated on observed
5638 // `‖g‖` progress so trajectories that made no headway fall
5639 // through to the degraded plan instead of replaying.
5640 let mut arc_retries_left: u32 = if matches!(the_plan.solver, Solver::Arc) {
5641 2
5642 } else {
5643 0
5644 };
5645 let mut retry_config: Option<OuterConfig> = None;
5646 // Tracks the previous ARC attempt's terminal `‖g‖`. The retry
5647 // gate compares attempt-over-attempt: if a retry didn't move
5648 // the gradient norm, the trajectory replayed (same seed, same
5649 // trust radius, cold caches, deterministic optimizer) and
5650 // further retries cannot help. First retry is unconditional
5651 // (no prior attempt to compare against).
5652 let mut prev_attempt_grad_norm: Option<f64> = None;
5653
5654 let outcome = loop {
5655 // Bind the active config by cloning into a local owned value so
5656 // subsequent retry-config assignment does not collide with the
5657 // borrow used inside this iteration body.
5658 let active_config_owned: OuterConfig =
5659 retry_config.clone().unwrap_or_else(|| config.clone());
5660 let active_config: &OuterConfig = &active_config_owned;
5661 match run_outer_with_plan(obj, active_config, context, attempt_cap, &the_plan, true) {
5662 Ok(PlanRunOutcome::Converged(result)) => break Ok(result),
5663 Ok(PlanRunOutcome::Exhausted(result)) => {
5664 if arc_retries_left == 0
5665 || matches!(
5666 result.operator_stop_reason,
5667 Some(
5668 OperatorTrustRegionStopReason::RejectFloor
5669 // #1690: a flat-valley cost-stall is a CONVERGED
5670 // cost plateau over the whole stall window, not a
5671 // budget shortfall. The ARC retry only reseeds
5672 // from the same last ρ with a reset trust radius
5673 // and the same deterministic operator state, so it
5674 // replays the identical trajectory and re-halts at
5675 // the same valley floor with the same |g| (verified
5676 // on the #1690 Gamma repro: two retries, each
5677 // returning |g|=0.3646 byte-for-byte). Treat it
5678 // like `RejectFloor` and stop — the genuine
5679 // stationarity verdict is reconciled downstream
5680 // against the authoritative shipped-β gradient
5681 // (`optimizer.rs`), and a non-stationary floor is
5682 // still reported non-converged. This skips the
5683 // wasted full-trajectory replay that dominated the
5684 // count-family slowdown.
5685 | OperatorTrustRegionStopReason::CostStallFlatValley
5686 )
5687 )
5688 {
5689 break Ok(result);
5690 }
5691 // Gate the retry on attempt-over-attempt `‖g‖`
5692 // progress. The first retry is unconditional (no
5693 // prior attempt). Subsequent retries fall through
5694 // to the degraded plan when the gradient norm did
5695 // not materially shrink — the deterministic
5696 // optimizer with the same seed and trust radius
5697 // would replay the same trajectory.
5698 let Some(cur_grad_norm) = result.final_grad_norm else {
5699 log::info!(
5700 "[OUTER] {context}: ARC attempt exhausted budget at \
5701 iter={} cost={:.6e} without a final gradient norm; \
5702 falling through to degraded plan",
5703 result.iterations,
5704 result.final_value,
5705 );
5706 break Ok(result);
5707 };
5708 if let Some(prev_g) = prev_attempt_grad_norm {
5709 let progressed = cur_grad_norm.is_finite()
5710 && prev_g.is_finite()
5711 && cur_grad_norm < 0.5 * prev_g;
5712 if !progressed {
5713 log::info!(
5714 "[OUTER] {context}: ARC retry stalled at \
5715 iter={} cost={:.6e} |g|={:.6e} (prev |g|={:.6e}); \
5716 deterministic replay suspected, falling through \
5717 to degraded plan",
5718 result.iterations,
5719 result.final_value,
5720 cur_grad_norm,
5721 prev_g,
5722 );
5723 break Ok(result);
5724 }
5725 }
5726 let next_trust_radius =
5727 sanitized_operator_trust_restart_radius(result.operator_trust_radius);
5728 log::info!(
5729 "[OUTER] {context}: ARC attempt exhausted budget at \
5730 iter={} cost={:.6e} |g|={:.6e}; resuming from last \
5731 rho + trust_radius={:?}, inner-PIRLS uncapped \
5732 (objective caches wiped; operator-TR Cauchy/Newton \
5733 state is not resumable)",
5734 result.iterations,
5735 result.final_value,
5736 cur_grad_norm,
5737 next_trust_radius,
5738 );
5739 // Snapshot the cap-feedback handle before we
5740 // reassign `retry_config` (which currently backs
5741 // `active_config`'s borrow). `InnerProgressFeedback`
5742 // is an Arc-wrapper bundle, so the clone is cheap.
5743 let cap_feedback = active_config.outer_inner_cap.clone();
5744 let mut next = active_config.clone();
5745 prev_attempt_grad_norm = Some(cur_grad_norm);
5746 next.initial_rho = Some(result.rho.clone());
5747 next.operator_initial_trust_radius = next_trust_radius;
5748 retry_config = Some(next);
5749 arc_retries_left -= 1;
5750 obj.reset();
5751 // Lift any inner-PIRLS cap for the resumed run. The
5752 // schedule's cold-start ladder (3/5/10) would
5753 // re-coarsen exactly the inner solves whose tolerance
5754 // is suspected to have starved the prior trajectory.
5755 // The next outer iter consumes ρ near a near-stationary
5756 // point where exact β / gradient / Hessian is the
5757 // load-bearing input to the operator-TR geometry.
5758 if let Some(feedback) = cap_feedback.as_ref() {
5759 feedback.cap.store(0, Ordering::Relaxed);
5760 }
5761 }
5762 Err(e) => break Err(e),
5763 }
5764 };
5765
5766 match outcome {
5767 Ok(result) => {
5768 if result.converged {
5769 return Ok(result);
5770 }
5771
5772 let improves_checkpoint = result.final_value.is_finite()
5773 && best_checkpoint.as_ref().is_none_or(|checkpoint| {
5774 !checkpoint.final_value.is_finite()
5775 || result.final_value < checkpoint.final_value
5776 });
5777 if improves_checkpoint {
5778 best_checkpoint = Some(result);
5779 }
5780
5781 let message = format!(
5782 "{context}: attempt {} (plan={the_plan}) exhausted without convergence",
5783 attempt_idx + 1
5784 );
5785 log::debug!("[OUTER] {message}; trying degraded fallback plan");
5786 last_error = Some(EstimationError::RemlOptimizationFailed(message));
5787 }
5788 Err(e) => {
5789 if e.is_fatal_outer_evaluation() {
5790 return Err(e);
5791 }
5792 log::debug!(
5793 "[OUTER] {context}: attempt {} (plan={the_plan}) failed: {e}",
5794 attempt_idx + 1
5795 );
5796 last_error = Some(e);
5797 }
5798 }
5799 }
5800
5801 if let Some(checkpoint) = best_checkpoint {
5802 // The solver ladder produced no result that its OWN internal
5803 // (raw-gradient) convergence test accepted — but that test cannot see a
5804 // railed or already-stationary optimum. At a smoothing parameter railed to
5805 // the ρ box floor (λ→0, e.g. an exact linear fit or a separated smooth),
5806 // the RAW gradient stays large along the railed axis — it "wants" to push
5807 // past the boundary — so the solver reports non-convergence and can take
5808 // zero steps, even though the KKT-PROJECTED gradient (which zeroes
5809 // outward-railed axes) is stationary and no feasible step reduces the
5810 // objective. Only the mandatory analytic certificate in `run_outer`
5811 // computes that projected gradient AND the curvature-scaled flat-valley
5812 // bound (½·gᵀH⁻¹g ≤ objective_tol), so IT, not this raw-gradient ladder, is
5813 // the sole authority on stationarity. Hand it the best finite checkpoint:
5814 // `certify_outer_optimality` mints iff the point is genuinely stationary
5815 // (interior, railed, or flat-valley) and returns typed non-convergence
5816 // otherwise, so a truly divergent fit is still rejected there.
5817 return Ok(checkpoint);
5818 }
5819
5820 Err(last_error.unwrap_or_else(|| {
5821 EstimationError::RemlOptimizationFailed(format!("all plan attempts exhausted ({context})"))
5822 }))
5823}
5824
5825// ─── Frontier ρ-scaling auto-switch (issue #986) ─────────────────────────
5826//
5827// ARD-per-atom assigns one smoothing coordinate per dictionary atom, so the
5828// ρ-vector reaches 10^4–10^5 coordinates. A dense outer quasi-Newton over that
5829// materializes an O(K²) Hessian and is impossible at scale. When the ρ-dimension
5830// is frontier-scale AND every coordinate is penalty-like with a working
5831// fixed-point hook, route the PRIMARY outer iteration to the per-atom decoupled
5832// EFS path (`crate::estimate::reml::per_atom_efs`) instead of the dense
5833// ARC/BFGS lane. The decision is auto-derived from the coordinate count alone —
5834// there is no flag — and it is additive: the dense path is unchanged for small K
5835// and for any objective that is not per-atom-EFS-eligible.
5836
5837/// Whether this capability is in the frontier ρ-scaling regime where the
5838/// per-atom decoupled EFS primary should take over from the dense outer.
5839///
5840/// Delegates the eligibility decision to
5841/// [`crate::estimate::reml::per_atom_efs::per_atom_efs_eligible`], which
5842/// requires all-penalty-like coordinates, a working `eval_efs` hook,
5843/// fixed-point not disabled, and a frontier-scale ρ-dimension. This is the
5844/// single auto-switch predicate; `plan` keeps selecting the
5845/// dense or standard-EFS solver for everything below the frontier threshold.
5846pub fn is_per_atom_efs_frontier(cap: &OuterCapability) -> bool {
5847 crate::estimate::reml::per_atom_efs::per_atom_efs_eligible(cap)
5848}
5849
5850/// Auto-switch entry point: when `cap` is frontier-scale per-atom-EFS-eligible,
5851/// run the per-atom decoupled EFS primary and return its [`OuterResult`];
5852/// otherwise return `Ok(None)` so the caller falls through to the existing dense
5853/// / standard-EFS path via [`OuterProblem::run`] / [`run_outer`].
5854///
5855/// Builds the same bounded seed and tolerance/budget the standard plan path
5856/// uses, picks the seed (initial-ρ if supplied, else the first generated
5857/// candidate — the per-atom fixed point is a contraction near the optimum and
5858/// does not need the multi-seed cascade the dense path runs for its non-convex
5859/// quasi-Newton surface), then drives the per-atom EFS loop. The shared-border
5860/// topology defaults to disjoint (every atom owns a private penalty block — the
5861/// common ARD-per-atom case); callers with a known arrow-border overlap can run
5862/// the module's `run_per_atom_efs` directly with a populated
5863/// `SharedBorderTopology`.
5864///
5865/// Additive: this function neither mutates nor bypasses the dense path; it is
5866/// the pre-dispatch shortcut [`run_outer`] calls before the dense ladder.
5867pub(crate) fn run_per_atom_efs_if_frontier(
5868 obj: &mut dyn OuterObjective,
5869 config: &OuterConfig,
5870 context: &str,
5871) -> Result<Option<OuterResult>, EstimationError> {
5872 let cap = primary_capability_for_config(obj.capability(), config, context);
5873 cap.validate_layout(context)?;
5874 if !is_per_atom_efs_frontier(&cap) {
5875 return Ok(None);
5876 }
5877
5878 let the_plan = plan(&cap);
5879 let rho_dim = cap.theta_layout().rho_dim();
5880
5881 let (lower, upper) = outer_bounds_template(config, cap.n_params);
5882
5883 // Seed: cache/explicit initial ρ if present, otherwise the first generated
5884 // candidate. The per-atom multiplicative fixed point is locally
5885 // contractive, so a single seed suffices; the heavy multi-seed cascade
5886 // exists for the dense quasi-Newton's non-convex surface, not for EFS.
5887 let seed = match config.initial_rho.as_ref() {
5888 Some(initial) if initial.len() == cap.n_params => initial.clone(),
5889 _ => {
5890 let generated = crate::seeding::generate_rho_candidates(
5891 cap.n_params,
5892 config.heuristic_lambdas.as_deref(),
5893 &config.seed_config,
5894 )?;
5895 match generated.into_iter().next() {
5896 Some(first) => first,
5897 None => Array1::<f64>::zeros(cap.n_params),
5898 }
5899 }
5900 };
5901
5902 log::info!(
5903 "[OUTER] {context}: frontier ρ-scaling (rho_dim={rho_dim}) → per-atom decoupled EFS primary"
5904 );
5905
5906 let pa_cfg = crate::estimate::reml::per_atom_efs::PerAtomEfsConfig::new(
5907 config.tolerance,
5908 config.max_iter,
5909 lower,
5910 upper,
5911 );
5912 let topology = crate::estimate::reml::per_atom_efs::SharedBorderTopology::disjoint(rho_dim);
5913
5914 obj.reset();
5915 install_matching_initial_inner_seed(obj, config, &seed, context)?;
5916 let result =
5917 crate::estimate::reml::per_atom_efs::run_per_atom_efs(obj, &seed, &pa_cfg, &topology)?;
5918 Ok(Some(result.into_outer_result(the_plan)))
5919}
5920
5921#[cfg(test)]
5922#[path = "inverted_rho_box_tests.rs"]
5923mod inverted_rho_box_tests;
5924
5925pub(crate) fn outer_bounds(lo: &Array1<f64>, hi: &Array1<f64>) -> Result<Bounds, EstimationError> {
5926 Bounds::new(lo.clone(), hi.clone(), 1e-6).map_err(|err| {
5927 EstimationError::InvalidInput(format!("outer rho bounds are invalid: {err}"))
5928 })
5929}
5930
5931pub(crate) fn outer_bounds_template(config: &OuterConfig, n: usize) -> (Array1<f64>, Array1<f64>) {
5932 config.bounds.clone().unwrap_or_else(|| {
5933 (
5934 Array1::<f64>::from_elem(n, -config.rho_bound),
5935 Array1::<f64>::from_elem(n, config.rho_bound),
5936 )
5937 })
5938}
5939
5940/// Intersect typed objective-domain faces with the caller's configured search
5941/// box. The resulting box is stored back on `config`, making it the one source
5942/// consumed by seed projection, continuation entry, every solver, and terminal
5943/// projected-stationarity certification.
5944pub(super) fn install_objective_domain(
5945 config: &mut OuterConfig,
5946 n_params: usize,
5947 objective_lower: Option<Array1<f64>>,
5948 objective_upper: Option<Array1<f64>>,
5949) -> Result<(), EstimationError> {
5950 let (mut lower, mut upper) = outer_bounds_template(config, n_params);
5951 if lower.len() != n_params || upper.len() != n_params {
5952 return Err(EstimationError::InvalidInput(format!(
5953 "outer configured bounds dimension mismatch: parameters={n_params}, lower={}, upper={}",
5954 lower.len(),
5955 upper.len(),
5956 )));
5957 }
5958 if let Some(domain) = objective_lower.as_ref()
5959 && domain.len() != n_params
5960 {
5961 return Err(EstimationError::InvalidInput(format!(
5962 "outer objective-domain lower-bound dimension mismatch: parameters={n_params}, lower={}",
5963 domain.len()
5964 )));
5965 }
5966 if let Some(domain) = objective_upper.as_ref()
5967 && domain.len() != n_params
5968 {
5969 return Err(EstimationError::InvalidInput(format!(
5970 "outer objective-domain upper-bound dimension mismatch: parameters={n_params}, upper={}",
5971 domain.len()
5972 )));
5973 }
5974 for index in 0..n_params {
5975 if let Some(domain) = objective_lower.as_ref() {
5976 let value = domain[index];
5977 if !value.is_finite() {
5978 return Err(EstimationError::InvalidInput(format!(
5979 "outer objective-domain lower bound[{index}] must be finite; got {value}"
5980 )));
5981 }
5982 lower[index] = lower[index].max(value);
5983 }
5984 if let Some(domain) = objective_upper.as_ref() {
5985 let value = domain[index];
5986 if !value.is_finite() {
5987 return Err(EstimationError::InvalidInput(format!(
5988 "outer objective-domain upper bound[{index}] must be finite; got {value}"
5989 )));
5990 }
5991 upper[index] = upper[index].min(value);
5992 }
5993 if !(lower[index].is_finite() && upper[index].is_finite() && lower[index] < upper[index]) {
5994 return Err(EstimationError::InvalidInput(format!(
5995 "outer objective-domain intersection is empty or non-finite at coordinate {index}: lower={}, upper={}",
5996 lower[index], upper[index]
5997 )));
5998 }
5999 }
6000 config.bounds = Some((lower, upper));
6001 Ok(())
6002}
6003
6004pub(crate) fn outer_tolerance(value: f64) -> Result<Tolerance, EstimationError> {
6005 Tolerance::new(value)
6006 .map_err(|err| EstimationError::InvalidInput(format!("outer tolerance is invalid: {err}")))
6007}
6008
6009/// The relative cost floor shared by the cost-stall guard, the curvature-scaled
6010/// flat-valley certificate, and the certify-last resume progress gate: nothing
6011/// tighter than what the in-loop stall detector already proved about the
6012/// surface. `rel_cost_tolerance` when set, else a small fraction of the absolute
6013/// tolerance, never below `COST_STALL_REL_TOL_FLOOR`.
6014pub(crate) fn outer_rel_cost_floor(config: &OuterConfig) -> f64 {
6015 config
6016 .rel_cost_tolerance
6017 .unwrap_or(config.tolerance * 1.0e-2)
6018 .max(COST_STALL_REL_TOL_FLOOR)
6019}
6020
6021/// Whether a certify-last checkpoint reseed (#2273/#2374) exploited real descent.
6022///
6023/// A reseed that does not strictly reduce the outer objective past the shared
6024/// relative cost floor `rel_cost_floor·(1 + min(|prior|, |retried|))` is at a
6025/// genuine non-stationary floor (or a true flat valley) a fresh metric cannot
6026/// escape, so the resume loop must stop rather than spend its remaining budget
6027/// re-deriving the same refusal. Anchoring the floor on the SMALLER of the two
6028/// costs keeps a tiny uphill wobble from a metric restart from reading as
6029/// progress, and a non-finite retried value is never progress.
6030pub(crate) fn certify_resume_made_progress(
6031 prior_value: f64,
6032 retried_value: f64,
6033 rel_cost_floor: f64,
6034) -> bool {
6035 let floor = rel_cost_floor * (1.0 + prior_value.abs().min(retried_value.abs()));
6036 retried_value.is_finite() && retried_value < prior_value - floor
6037}
6038
6039pub(crate) fn outer_gradient_tolerance(config: &OuterConfig) -> GradientTolerance {
6040 let abs = config
6041 .objective_scale
6042 // A matrix-factorization REML/LAML score cannot resolve relative
6043 // perturbations below the forward-error scale √ε. Requiring a smaller
6044 // absolute residual made gradient-only / operator-curvature objectives
6045 // impossible to certify unless an unrelated Hessian or probe-noise
6046 // rescue happened to be available (#2269). This is the arithmetic
6047 // resolution of the declared objective scale, not a fitted tolerance.
6048 .map(|scale| config.tolerance.max(scale * f64::EPSILON.sqrt()))
6049 .unwrap_or(config.tolerance);
6050 GradientTolerance {
6051 abs,
6052 rel_initial_grad: None,
6053 rel_cost: Some(config.rel_cost_tolerance.unwrap_or(config.tolerance)),
6054 projected: true,
6055 }
6056}
6057
6058pub(crate) fn outer_max_iterations(value: usize) -> Result<MaxIterations, EstimationError> {
6059 MaxIterations::new(value)
6060 .map_err(|err| EstimationError::InvalidInput(format!("outer max_iter is invalid: {err}")))
6061}
6062
6063pub(crate) fn sanitized_operator_trust_restart_radius(radius: Option<f64>) -> Option<f64> {
6064 radius
6065 .filter(|value| value.is_finite() && *value > 0.0)
6066 .map(|value| value.max(OPERATOR_TRUST_RESTART_RADIUS_FLOOR))
6067}
6068
6069pub(crate) fn bfgs_axis_step_caps(
6070 config: &OuterConfig,
6071 layout: OuterThetaLayout,
6072) -> Option<Array1<f64>> {
6073 if config.bfgs_step_cap.is_none() && config.bfgs_step_cap_psi.is_none() {
6074 return None;
6075 }
6076 let mut caps = Array1::from_elem(layout.n_params, f64::INFINITY);
6077 if let Some(cap) = config.bfgs_step_cap {
6078 for i in 0..layout.rho_dim() {
6079 caps[i] = cap;
6080 }
6081 }
6082 if let Some(cap) = config.bfgs_step_cap_psi {
6083 for i in layout.rho_dim()..layout.n_params {
6084 caps[i] = cap;
6085 }
6086 }
6087 Some(caps)
6088}
6089
6090pub(crate) enum FixedPointOuterRunError {
6091 SeedRejected(EstimationError),
6092 ImmediateFallback(EstimationError),
6093 Failed(EstimationError),
6094}
6095
6096pub(crate) fn run_fixed_point_outer_solver(
6097 obj: &mut dyn OuterObjective,
6098 layout: OuterThetaLayout,
6099 barrier_config: Option<BarrierConfig>,
6100 config: &OuterConfig,
6101 context: &str,
6102 seed: &Array1<f64>,
6103 the_plan: OuterPlan,
6104 label: &str,
6105 failure_prefix: &str,
6106) -> Result<OuterResult, FixedPointOuterRunError> {
6107 // Shared publication slot for the recurrent-restored-incumbent stop
6108 // (#2235 verdict 2): the bridge is moved into the driver, so the streak
6109 // count comes back through this cell and is stamped onto the returned
6110 // `OuterResult` below.
6111 let recurrent_incumbent_exit = Arc::new(Mutex::new(None));
6112 let mut objective = OuterFixedPointBridge {
6113 obj,
6114 layout,
6115 barrier_config,
6116 fixed_point_tolerance: config.tolerance,
6117 consecutive_psi_zero_iters: 0,
6118 last_restored_incumbent_streak: None,
6119 recurrent_incumbent_exit: Arc::clone(&recurrent_incumbent_exit),
6120 };
6121 let seed_sample = match objective.eval_step(seed) {
6122 Ok(sample) => sample,
6123 Err(ObjectiveEvalError::Recoverable { message }) => {
6124 let err = EstimationError::RemlOptimizationFailed(message);
6125 if requests_immediate_first_order_fallback(&err.to_string()) {
6126 return Err(FixedPointOuterRunError::ImmediateFallback(err));
6127 }
6128 return Err(FixedPointOuterRunError::SeedRejected(err));
6129 }
6130 Err(ObjectiveEvalError::Fatal { message }) => {
6131 return Err(FixedPointOuterRunError::Failed(
6132 EstimationError::fatal_outer_evaluation(
6133 "outer fixed-point seed evaluation",
6134 EstimationError::RemlOptimizationFailed(message),
6135 ),
6136 ));
6137 }
6138 };
6139 let (lo, hi) = outer_bounds_template(config, layout.n_params);
6140 let bounds = outer_bounds(&lo, &hi).map_err(FixedPointOuterRunError::Failed)?;
6141 let tol = outer_tolerance(config.tolerance).map_err(FixedPointOuterRunError::Failed)?;
6142 let max_iter =
6143 outer_max_iterations(config.max_iter).map_err(FixedPointOuterRunError::Failed)?;
6144 let mut optimizer = FixedPoint::new(seed.clone(), objective)
6145 // Seed validation already paid the complete EFS inner solve. Reuse that
6146 // exact sample so iteration zero neither repeats the expensive solve nor
6147 // mistakes two evaluations at the identical rho for recurrent incumbent
6148 // evidence (#2241).
6149 .with_initial_sample(seed.clone(), seed_sample)
6150 .with_bounds(bounds)
6151 .with_tolerance(tol)
6152 .with_max_iterations(max_iter);
6153 match optimizer.run() {
6154 Ok(sol) => {
6155 let mut result = solution_into_outer_result(sol, true, the_plan);
6156 // Stamp the model-state fixed-point stop when the bridge published
6157 // one; `None` means the walk stopped through the ordinary
6158 // step-norm test instead.
6159 if let Some(consecutive_restores) =
6160 recurrent_incumbent_exit.lock().ok().and_then(|slot| *slot)
6161 {
6162 result.converged_via = Some(OuterConvergedVia::RecurrentIncumbent {
6163 consecutive_restores,
6164 });
6165 }
6166 Ok(result)
6167 }
6168 Err(FixedPointError::MaxIterationsReached { last_solution }) => {
6169 log::warn!(
6170 "[OUTER warning] {context}: {label} hit max_iter={} at final_value={:.6e} step_norm={:.3e}",
6171 config.max_iter,
6172 last_solution.final_value,
6173 last_solution.final_gradient_norm.unwrap_or(f64::NAN),
6174 );
6175 Ok(solution_into_outer_result(*last_solution, false, the_plan))
6176 }
6177 Err(FixedPointError::ObjectiveFailed { message })
6178 if requests_immediate_first_order_fallback(&message) =>
6179 {
6180 // The bridge raises `EFS_FIRST_ORDER_FALLBACK_MARKER` when the
6181 // fixed-point step is not a descent direction it can rescue — a
6182 // ψ-stagnation streak, or a step every halving rejected on both
6183 // the full vector and the ρ/τ-only fallback. That marker is a
6184 // ROUTING REQUEST ("abandon the fixed point, run the joint
6185 // gradient solver that enforces ∇_ψ V = 0"), not a defect: the
6186 // ladder `automatic_fallback_attempts` builds for an
6187 // analytic-gradient EFS/HybridEFS primary is exactly the
6188 // `disable_fixed_point` BFGS plan it is asking for.
6189 //
6190 // The seed evaluation above already honours it. Every LATER
6191 // iteration reached this arm instead, where the blanket
6192 // `fatal_outer_evaluation` classification short-circuits both
6193 // `run_outer_with_plan`'s own marker check and the attempt loop in
6194 // `run_outer_with_strategy` — so a HybridEFS search that descended
6195 // and then asked to hand over died fatally with the fallback plan
6196 // never attempted. Carry the same recoverable routing the seed
6197 // path uses, so the request is honoured wherever it is raised.
6198 Err(FixedPointOuterRunError::ImmediateFallback(
6199 EstimationError::RemlOptimizationFailed(message),
6200 ))
6201 }
6202 Err(FixedPointError::ObjectiveFailed { message }) => Err(FixedPointOuterRunError::Failed(
6203 EstimationError::fatal_outer_evaluation(
6204 "outer fixed-point evaluation",
6205 EstimationError::RemlOptimizationFailed(message),
6206 ),
6207 )),
6208 Err(e) => Err(FixedPointOuterRunError::Failed(
6209 EstimationError::RemlOptimizationFailed(format!("{failure_prefix}: {e:?}")),
6210 )),
6211 }
6212}
6213
6214#[cfg(test)]
6215mod asymptote_rail_certify_tests {
6216 use super::*;
6217 use ndarray::array;
6218
6219 /// Build a one-coordinate UPPER-rail tail-law objective: at ρ its gradient is
6220 /// `−c·e^{−ρ}` (so `ĉ = −e^{ρ}·grad = c` is constant) and its published inner
6221 /// β is `a·e^{−ρ}` (so consecutive-probe `‖Δβ‖` contracts geometrically).
6222 /// `drift_amp` ramps `ĉ` with ρ to model the finite-difference noise regime
6223 /// (a non-constant pencil constant that no drift band can confirm).
6224 fn upper_tail_objective(c: f64, a: f64, drift_amp: f64) -> impl OuterObjective {
6225 let problem = OuterProblem::new(1).with_gradient(Derivative::Analytic);
6226 problem.build_objective(
6227 (),
6228 move |_: &mut (), rho: &Array1<f64>| {
6229 let r = rho[0];
6230 let c_eff = c + drift_amp * r;
6231 Ok((c_eff * (-r).exp()).abs())
6232 },
6233 move |_: &mut (), rho: &Array1<f64>| {
6234 let r = rho[0];
6235 let c_eff = c + drift_amp * r;
6236 Ok(OuterEval {
6237 cost: (c_eff * (-r).exp()).abs(),
6238 gradient: array![-c_eff * (-r).exp()],
6239 hessian: HessianValue::Unavailable,
6240 inner_beta_hint: Some(array![a * (-r).exp()]),
6241 })
6242 },
6243 None::<fn(&mut ())>,
6244 None::<fn(&mut (), &Array1<f64>) -> Result<EfsEval, EstimationError>>,
6245 )
6246 }
6247
6248 /// An exact upper-rail exponential tail is certified: the reconstructed
6249 /// pencil constant is `c`, and the value-gap / estimand-travel are finite.
6250 #[test]
6251 fn asymptote_rail_mints_on_exact_tail_law() {
6252 let mut obj = upper_tail_objective(6723.0, 1.0, 0.0);
6253 let rho = array![29.9];
6254 let tol = AsymptoteTolerances::exp4_rail_bands(1.0e-2);
6255 let rail = build_and_assess_rail_coordinate(
6256 &mut obj,
6257 &rho,
6258 0,
6259 AsymptoteSide::Upper,
6260 &tol,
6261 (f64::NEG_INFINITY, f64::INFINITY),
6262 )
6263 .expect("probing the tail-law objective must not error")
6264 .expect("an exact exponential tail must certify a rail");
6265 assert_eq!(rail.index, 0);
6266 assert_eq!(rail.side, AsymptoteSide::Upper);
6267 assert!(
6268 (rail.tail_constant - 6723.0).abs() / 6723.0 < 1.0e-6,
6269 "recovered ĉ={} should equal c=6723",
6270 rail.tail_constant,
6271 );
6272 assert!(rail.value_gap.is_finite() && rail.value_gap >= 0.0);
6273 assert!(rail.estimand_travel_bound.is_finite() && rail.estimand_travel_bound >= 0.0);
6274 }
6275
6276 /// A drifting pencil constant (finite-difference noise regime) never
6277 /// certifies: no finite-difference-clean run of the required length exists.
6278 #[test]
6279 fn asymptote_rail_refuses_on_drifting_constant() {
6280 let mut obj = upper_tail_objective(6723.0, 1.0, 3000.0);
6281 let rho = array![29.9];
6282 let tol = AsymptoteTolerances::exp4_rail_bands(1.0e-2);
6283 let verdict = build_and_assess_rail_coordinate(
6284 &mut obj,
6285 &rho,
6286 0,
6287 AsymptoteSide::Upper,
6288 &tol,
6289 (f64::NEG_INFINITY, f64::INFINITY),
6290 )
6291 .expect("probing must not error");
6292 assert!(
6293 verdict.is_err(),
6294 "a drifting ĉ must not certify a tail, got {verdict:?}",
6295 );
6296 }
6297
6298 /// #2358: a finite smoothing box can expose fewer than three whole
6299 /// e-folds of the leading-order tail even though the compactified
6300 /// criterion is already regular there. For
6301 ///
6302 /// `V(ρ) = c·e⁻ρ + (d/2)·e⁻²ρ`,
6303 ///
6304 /// the pencil constant is `ĉ(ρ) = c + d·e⁻ρ`: the ordinary asymptotic law
6305 /// plus its first vanishing correction. Unit probes from ρ=10 step into
6306 /// enough correction curvature that no three-row clean run exists; the
6307 /// equal-spaced half-e-fold fallback resolves the local tail without
6308 /// changing the drift, estimand, sign, or noise gates.
6309 #[test]
6310 fn tail_probe_resolves_narrow_regular_band_before_finite_box_2358() {
6311 let (c, d, a) = (7.1_f64, 400.0_f64, 1.0e-3_f64);
6312 let problem = OuterProblem::new(1).with_gradient(Derivative::Analytic);
6313 let mut obj = problem.build_objective(
6314 (),
6315 move |_: &mut (), rho: &Array1<f64>| {
6316 let tau = (-rho[0]).exp();
6317 Ok(c * tau + 0.5 * d * tau * tau)
6318 },
6319 move |_: &mut (), rho: &Array1<f64>| {
6320 let tau = (-rho[0]).exp();
6321 Ok(OuterEval {
6322 cost: c * tau + 0.5 * d * tau * tau,
6323 gradient: array![-c * tau - d * tau * tau],
6324 hessian: HessianValue::Unavailable,
6325 inner_beta_hint: Some(array![a * tau]),
6326 })
6327 },
6328 None::<fn(&mut ())>,
6329 None::<fn(&mut (), &Array1<f64>) -> Result<EfsEval, EstimationError>>,
6330 );
6331 let rho = array![10.0];
6332 let mut tol = AsymptoteTolerances::exp4_rail_bands(1.0e-2);
6333 tol.tail_drift_rel = TAIL_SNAP_DRIFT_REL;
6334
6335 let (coarse, coarse_rows) = probe_tail_window_at_resolution(
6336 &mut obj,
6337 &rho,
6338 0,
6339 AsymptoteSide::Upper,
6340 &tol,
6341 (-10.0, 10.5),
6342 (1.0, ASYMPTOTE_PROBE_COUNT),
6343 )
6344 .expect("coarse probing must not error");
6345 assert!(
6346 coarse.is_none(),
6347 "unit probes must not manufacture a pure tail through the curved band: {coarse_rows}"
6348 );
6349
6350 let (window, rows) = probe_tail_window(
6351 &mut obj,
6352 &rho,
6353 0,
6354 AsymptoteSide::Upper,
6355 &tol,
6356 (-10.0, 10.5),
6357 )
6358 .expect("multi-resolution probing must not error");
6359 let window = window.unwrap_or_else(|| {
6360 panic!("the half-e-fold fallback must resolve the regular tail: {rows}")
6361 });
6362 assert!(
6363 matches!(
6364 assess_coordinate(&window, &tol),
6365 AsymptoteVerdict::CertifiedAtAsymptote { .. }
6366 ),
6367 "the resolved tail must pass the unchanged asymptote gates"
6368 );
6369 }
6370
6371 /// #2358: pre-snap interior stationarity cannot authorize direct
6372 /// certification at the rail. The second coordinate is stationary at the
6373 /// checkpoint but its mode depends on the tail coordinate, so moving the
6374 /// first coordinate changes the second coordinate's optimum. A confirmed
6375 /// snap must therefore publish a re-optimization waypoint even though the
6376 /// pre-snap interior gradient is exactly zero.
6377 #[test]
6378 fn coupled_coordinate_stationary_before_snap_still_reseeds_2358() {
6379 let c = 10.0_f64;
6380 let problem = OuterProblem::new(2).with_gradient(Derivative::Analytic);
6381 let mut obj = problem.build_objective(
6382 (),
6383 move |_: &mut (), rho: &Array1<f64>| {
6384 let tau = (-rho[0]).exp();
6385 let coupled = rho[1] - tau;
6386 Ok(c * tau + 0.5 * coupled * coupled)
6387 },
6388 move |_: &mut (), rho: &Array1<f64>| {
6389 let tau = (-rho[0]).exp();
6390 Ok(OuterEval {
6391 cost: c * tau + 0.5 * (rho[1] - tau).powi(2),
6392 gradient: array![
6393 -c * tau + (rho[1] - tau) * tau,
6394 rho[1] - tau
6395 ],
6396 hessian: HessianValue::Unavailable,
6397 inner_beta_hint: Some(array![1.0e-3 * tau]),
6398 })
6399 },
6400 None::<fn(&mut ())>,
6401 None::<fn(&mut (), &Array1<f64>) -> Result<EfsEval, EstimationError>>,
6402 );
6403 let rho = array![8.0, (-8.0_f64).exp()];
6404 let tail_gradient = -c * (-rho[0]).exp();
6405 let gradient = array![tail_gradient, 0.0];
6406 let hessian = array![[tail_gradient.abs(), 0.0], [0.0, 1.0]];
6407 let bounds = (Array1::from_elem(2, -12.0), Array1::from_elem(2, 12.0));
6408
6409 let outcome = try_tail_snap_to_rail(
6410 &mut obj,
6411 &AsymptoteRailInputs {
6412 rho: &rho,
6413 projected_gradient: &gradient,
6414 railed: &[],
6415 hessian: &hessian,
6416 bounds: &bounds,
6417 terminal_beta: None,
6418 stationarity_bound: 1.0e-3,
6419 objective_tol: 1.0e-8,
6420 context: "coupled pre-snap stationarity guard",
6421 },
6422 )
6423 .expect("tail snap must not error");
6424 match outcome {
6425 TailSnapOutcome::ConfirmedNeedsReseed(snapped) => {
6426 assert_eq!(snapped, array![12.0, (-8.0_f64).exp()]);
6427 }
6428 other => panic!(
6429 "a coupled coordinate stationary only before the snap must reoptimize, got {other:?}"
6430 ),
6431 }
6432 }
6433
6434 /// #2392 wrong-rail pull-back FIRES: a coordinate sitting at the UPPER bound
6435 /// whose clean-band probes carry a POSITIVE gradient (`∂V/∂ρ > 0`, so the
6436 /// pencil constant `ĉ = −e^{ρ}·g < 0` — descent points INWARD, away from the
6437 /// bound) was driven to the wrong rail. `detect_wrong_rail_pullback` returns
6438 /// an interior reseed target strictly below the coordinate's current ρ.
6439 #[test]
6440 fn wrong_rail_pullback_fires_on_inward_descent_2392() {
6441 // V(ρ) = −c·e^{−ρ} ⇒ ∂V/∂ρ = +c·e^{−ρ} > 0: the descent runs ρ DOWN, away
6442 // from the upper rail, and ĉ_upper = −e^{ρ}·(c·e^{−ρ}) = −c < 0 uniformly.
6443 let c = 6723.0;
6444 let problem = OuterProblem::new(1).with_gradient(Derivative::Analytic);
6445 let mut obj = problem.build_objective(
6446 (),
6447 move |_: &mut (), rho: &Array1<f64>| Ok(-c * (-rho[0]).exp()),
6448 move |_: &mut (), rho: &Array1<f64>| {
6449 Ok(OuterEval {
6450 cost: -c * (-rho[0]).exp(),
6451 gradient: array![c * (-rho[0]).exp()],
6452 hessian: HessianValue::Unavailable,
6453 inner_beta_hint: Some(array![(-rho[0]).exp()]),
6454 })
6455 },
6456 None::<fn(&mut ())>,
6457 None::<fn(&mut (), &Array1<f64>) -> Result<EfsEval, EstimationError>>,
6458 );
6459 let rho = array![29.9];
6460 let tol = AsymptoteTolerances::exp4_rail_bands(1.0e-2);
6461 let target = detect_wrong_rail_pullback(
6462 &mut obj,
6463 &rho,
6464 0,
6465 AsymptoteSide::Upper,
6466 &tol,
6467 (-30.0, 30.0),
6468 )
6469 .expect("probing the wrong-rail objective must not error")
6470 .expect("an inward-descent rail must publish a pull-back target");
6471 assert!(
6472 target < rho[0] && target.is_finite(),
6473 "the reseed must move the coordinate INWARD (ρ down), got {target}",
6474 );
6475 }
6476
6477 /// #2392 wrong-rail pull-back does NOT fire on a GENUINE upper-rail tail:
6478 /// `∂V/∂ρ < 0` ⇒ `ĉ > 0` ⇒ descent runs TOWARD the bound (a real λ→∞ optimum),
6479 /// which must never be pulled off its rail.
6480 #[test]
6481 fn wrong_rail_pullback_refuses_a_genuine_upper_tail_2392() {
6482 let mut obj = upper_tail_objective(6723.0, 1.0, 0.0);
6483 let rho = array![29.9];
6484 let tol = AsymptoteTolerances::exp4_rail_bands(1.0e-2);
6485 let verdict = detect_wrong_rail_pullback(
6486 &mut obj,
6487 &rho,
6488 0,
6489 AsymptoteSide::Upper,
6490 &tol,
6491 (-30.0, 30.0),
6492 )
6493 .expect("probing must not error");
6494 assert!(
6495 verdict.is_none(),
6496 "a genuine λ→∞ tail (ĉ>0) must not be pulled off its rail, got {verdict:?}",
6497 );
6498 }
6499
6500 /// #2349: the interior-PSD gate must judge curvature above the
6501 /// gradient-residue noise floor. Fixture = the measured multinomial
6502 /// checkpoint shape: excluded tail candidates {0}, interior coordinate 1
6503 /// gradient-stationary (|g| = 1.0228e-3) with the corrupted tie-signature
6504 /// diagonal H₁₁ = −1.0216e-3 ≈ −|g₁| (the #2298 trace-pair residue — the
6505 /// entire measured 6×6 spectrum was PSD except this one sub-resolution
6506 /// entry). The raw gate refuses on the residue; the floored gate
6507 /// certifies; a GENUINE interior saddle (λ_min = −0.5 against the same
6508 /// tiny gradient) still refuses under the floor.
6509 #[test]
6510 fn interior_psd_gate_floors_tail_residue_but_keeps_genuine_saddles_2349() {
6511 let hessian = array![[0.2828, 0.0004], [0.0004, -1.0216e-3]];
6512 let gradient = array![-1.057, -1.0228e-3];
6513 let excluded = [0usize];
6514 assert_eq!(
6515 certificate_hessian_is_psd_off_railed(&hessian, &excluded),
6516 Some(false),
6517 "raw gate must see the corrupted sub-resolution entry as indefinite"
6518 );
6519 assert_eq!(
6520 certificate_hessian_is_psd_off_railed_above_gradient_floor(
6521 &hessian, &excluded, &gradient
6522 ),
6523 Some(true),
6524 "the gradient floor must absorb the O(|g|) trace-pair residue"
6525 );
6526 let saddle = array![[0.2828, 0.0004], [0.0004, -0.5]];
6527 assert_eq!(
6528 certificate_hessian_is_psd_off_railed_above_gradient_floor(
6529 &saddle, &excluded, &gradient
6530 ),
6531 Some(false),
6532 "a genuine interior saddle dwarfs the bound-scale floor and refuses"
6533 );
6534 }
6535
6536 /// THE GUARANTEE, not an instance of it. Weyl bounds the floored spectrum by
6537 /// `λ_min(H) + min|g| ≤ λ_min(H + diag|g|) ≤ λ_min(H) + max|g|`, so the floor
6538 /// can absorb AT MOST `max_k |g_k|` over the JUDGED coordinates. Therefore
6539 /// any interior spectrum whose most negative direction exceeds that floor
6540 /// must still refuse — for every such spectrum, not merely for the one
6541 /// saddle that happened to be measured.
6542 ///
6543 /// Swept over curvatures spanning six orders and gradients spanning four,
6544 /// including the pair where they are within a factor of two of each other
6545 /// (the regime the floor exists to serve, and the only place the verdict is
6546 /// genuinely close). The excluded coordinate carries a deliberately huge
6547 /// gradient: the sub-block is extracted AFTER flooring, so it must never
6548 /// reach the floor.
6549 #[test]
6550 fn gradient_floor_absorbs_at_most_max_interior_gradient_weyl_bound() {
6551 let excluded = [0usize];
6552 for &lambda_min in &[-5.0e-1, -1.5e-2, -1.0e-3, -1.0e-5, -1.0e-7] {
6553 for &g_interior in &[1.0e-7, 1.0e-5, 1.0e-3, 1.0e-2] {
6554 // The railed coordinate's gradient is four orders above every
6555 // interior one; if it ever entered the floor the sweep would
6556 // certify everything.
6557 let gradient = array![-1.4017, g_interior];
6558 let hessian = array![[0.2828, 0.0004], [0.0004, lambda_min]];
6559 let floored = certificate_hessian_is_psd_off_railed_above_gradient_floor(
6560 &hessian, &excluded, &gradient,
6561 );
6562 if g_interior < lambda_min.abs() {
6563 assert_eq!(
6564 floored,
6565 Some(false),
6566 "Weyl: max|g_int|={g_interior:.1e} < |λ_min|={:.1e} means the floored \
6567 sub-block is still indefinite, so the gate MUST refuse",
6568 lambda_min.abs()
6569 );
6570 }
6571 // And the recorded clearance must report the same verdict
6572 // against the same floor, so the certificate's evidence and its
6573 // gate can never disagree.
6574 let clearance =
6575 interior_curvature_floor_clearance(&hessian, &excluded, &gradient)
6576 .expect("a finite 1×1 interior sub-block has a clearance");
6577 assert_eq!(
6578 clearance.gradient_floor, g_interior,
6579 "the floor must be the largest JUDGED gradient — the excluded \
6580 coordinate's 1.4017 must never enter it"
6581 );
6582 assert!(
6583 (clearance.interior_min_eigenvalue - lambda_min).abs()
6584 <= 1.0e-12 * lambda_min.abs(),
6585 "recorded λ_min {} should be the interior sub-block's own {lambda_min}",
6586 clearance.interior_min_eigenvalue
6587 );
6588 assert_eq!(
6589 Some(clearance.cleared),
6590 floored,
6591 "the recorded verdict and the gate must be the same judgment"
6592 );
6593 }
6594 }
6595 }
6596
6597 /// Joint-face objective `V(ρ) = c·e^{−(ρ₀+ρ₁)/2}` — the algebraic skeleton
6598 /// of an OVERLAPPING-penalty λ→∞ face (the coalesced pseudo-logdet's
6599 /// shared range space couples the two coordinates, so each marginal
6600 /// gradient `g_k = −(c/2)e^{−(ρ₀+ρ₁)/2}` decays in the JOINT coordinate
6601 /// only). Along either single coordinate the pencil constant
6602 /// `ĉ_k = |g_k|e^{ρ_k}` sweeps a factor `e^{1/2}` per e-fold — far outside
6603 /// any drift band — while along the face direction it is exactly constant.
6604 fn joint_face_objective(c: f64, a: f64) -> impl OuterObjective {
6605 let problem = OuterProblem::new(2).with_gradient(Derivative::Analytic);
6606 problem.build_objective(
6607 (),
6608 move |_: &mut (), rho: &Array1<f64>| Ok(c * (-(rho[0] + rho[1]) / 2.0).exp()),
6609 move |_: &mut (), rho: &Array1<f64>| {
6610 let v = c * (-(rho[0] + rho[1]) / 2.0).exp();
6611 Ok(OuterEval {
6612 cost: v,
6613 gradient: array![-0.5 * v, -0.5 * v],
6614 hessian: HessianValue::Unavailable,
6615 inner_beta_hint: Some(array![a * (-(rho[0] + rho[1]) / 2.0).exp()]),
6616 })
6617 },
6618 None::<fn(&mut ())>,
6619 None::<fn(&mut (), &Array1<f64>) -> Result<EfsEval, EstimationError>>,
6620 )
6621 }
6622
6623 /// #2349 round 7: the joint multi-coordinate rail face. The marginal
6624 /// single-coordinate tail law honestly fails on an overlapping-penalty
6625 /// face (measured on the multinomial checkpoint: ĉ₀ swept 8 orders of
6626 /// magnitude), so tail-snap must fall back to the joint face direction,
6627 /// certify the one-dimensional joint law there, and snap the whole face.
6628 #[test]
6629 fn joint_face_tail_certifies_where_single_coordinate_law_drifts_2349() {
6630 let c = 1.2 * (7.5_f64).exp();
6631 let rho = array![8.0, 7.0];
6632 let tol = {
6633 let mut t = AsymptoteTolerances::exp4_rail_bands(1.0e-2);
6634 t.tail_drift_rel = TAIL_SNAP_DRIFT_REL;
6635 t
6636 };
6637 let bounds = (Array1::from_elem(2, -12.0), Array1::from_elem(2, 12.0));
6638
6639 // The marginal law must genuinely fail first — otherwise this test
6640 // would pass vacuously with the joint path never exercised.
6641 let mut obj = joint_face_objective(c, 1.0e-9);
6642 let (single_window, _) = probe_tail_window(
6643 &mut obj,
6644 &rho,
6645 0,
6646 AsymptoteSide::Upper,
6647 &tol,
6648 (bounds.0[0], bounds.1[0]),
6649 )
6650 .expect("single-coordinate probing must not error");
6651 assert!(
6652 single_window.is_none(),
6653 "the marginal pencil constant drifts e^(1/2) per e-fold and must not \
6654 produce a finite-difference-clean run"
6655 );
6656
6657 // The joint window recovers the exact face constant.
6658 let (joint_window, _) = probe_joint_tail_window(
6659 &mut obj,
6660 &rho,
6661 &[(0, AsymptoteSide::Upper), (1, AsymptoteSide::Upper)],
6662 &tol,
6663 (&bounds.0, &bounds.1),
6664 )
6665 .expect("joint probing must not error");
6666 let window = joint_window.expect("the joint face law is exactly exponential");
6667 match assess_coordinate(&window, &tol) {
6668 AsymptoteVerdict::CertifiedAtAsymptote { tail_constant, .. } => {
6669 assert!(
6670 (tail_constant - c).abs() / c < 1.0e-6,
6671 "joint pencil constant must recover c: got {tail_constant}, want {c}"
6672 );
6673 }
6674 other => panic!("joint face must certify, got {other:?}"),
6675 }
6676
6677 // End-to-end: tail-snap declines the marginal law, falls back to the
6678 // joint face, and snaps BOTH coordinates to their upper rails.
6679 let g = -0.5 * c * (-7.5_f64).exp();
6680 let gradient = array![g, g];
6681 let hessian = array![[g.abs(), 0.0], [0.0, g.abs()]];
6682 let outcome = try_tail_snap_to_rail(
6683 &mut obj,
6684 &AsymptoteRailInputs {
6685 rho: &rho,
6686 projected_gradient: &gradient,
6687 railed: &[],
6688 hessian: &hessian,
6689 bounds: &bounds,
6690 terminal_beta: None,
6691 stationarity_bound: 1.0e-3,
6692 objective_tol: 1.0e-8,
6693 context: "joint-face guard test",
6694 },
6695 )
6696 .expect("tail snap must not error");
6697 match outcome {
6698 TailSnapOutcome::ConfirmedNeedsReseed(snapped) => {
6699 assert_eq!(
6700 snapped,
6701 array![12.0, 12.0],
6702 "both face coordinates must snap to their upper rails"
6703 );
6704 }
6705 other => panic!(
6706 "the joint face must confirm and publish a reseed waypoint, got {other:?}"
6707 ),
6708 }
6709 }
6710
6711 /// #2349 e2e shape: the joint pencil-constant run confirms (measured on
6712 /// the multinomial checkpoint: ĉ settling 62.7 → … → 34.2) while the
6713 /// coefficient steps in the retained deep-interior rows are NOT yet
6714 /// geometrically contracting — the crawl was cut mid-travel. A confirmed
6715 /// law with an unsettled estimand must SNAP the face for re-optimization
6716 /// (the single-coordinate `OnTailNotYetEquivalent` semantics), not
6717 /// decline.
6718 #[test]
6719 fn joint_face_with_unsettled_estimand_snaps_for_reoptimization_2349() {
6720 let c = 1.2 * (7.5_f64).exp();
6721 // Non-contracting coefficient hints: constant per-probe steps (β moves
6722 // linearly in r), so coef_step_ratio has q = 1 and the estimand gate
6723 // refuses while the pencil constant is exact.
6724 let problem = OuterProblem::new(2).with_gradient(Derivative::Analytic);
6725 let mut obj = problem.build_objective(
6726 (),
6727 move |_: &mut (), rho: &Array1<f64>| Ok(c * (-(rho[0] + rho[1]) / 2.0).exp()),
6728 move |_: &mut (), rho: &Array1<f64>| {
6729 let v = c * (-(rho[0] + rho[1]) / 2.0).exp();
6730 Ok(OuterEval {
6731 cost: v,
6732 gradient: array![-0.5 * v, -0.5 * v],
6733 hessian: HessianValue::Unavailable,
6734 inner_beta_hint: Some(array![0.1 * (rho[0] + rho[1])]),
6735 })
6736 },
6737 None::<fn(&mut ())>,
6738 None::<fn(&mut (), &Array1<f64>) -> Result<EfsEval, EstimationError>>,
6739 );
6740 let rho = array![8.0, 7.0];
6741 let g = -0.5 * c * (-7.5_f64).exp();
6742 let gradient = array![g, g];
6743 let hessian = array![[g.abs(), 0.0], [0.0, g.abs()]];
6744 let bounds = (Array1::from_elem(2, -12.0), Array1::from_elem(2, 12.0));
6745 let outcome = try_tail_snap_to_rail(
6746 &mut obj,
6747 &AsymptoteRailInputs {
6748 rho: &rho,
6749 projected_gradient: &gradient,
6750 railed: &[],
6751 hessian: &hessian,
6752 bounds: &bounds,
6753 terminal_beta: None,
6754 stationarity_bound: 1.0e-3,
6755 objective_tol: 1.0e-8,
6756 context: "joint-face unsettled-estimand guard test",
6757 },
6758 )
6759 .expect("tail snap must not error");
6760 match outcome {
6761 TailSnapOutcome::ConfirmedNeedsReseed(snapped) => {
6762 assert_eq!(
6763 snapped,
6764 array![12.0, 12.0],
6765 "a confirmed joint law with unsettled estimand must snap the face"
6766 );
6767 }
6768 other => panic!("expected a face snap, got {other:?}"),
6769 }
6770 }
6771
6772 /// A pair of super-bound coordinates that do NOT share a face (independent
6773 /// laws with strongly drifting joint section) must still decline — the
6774 /// joint fallback cannot manufacture a certificate where no joint law
6775 /// holds.
6776 #[test]
6777 fn joint_face_fallback_refuses_a_non_face_2349() {
6778 // V = c₀·e^{−ρ₀} + drift·ρ₀·e^{−ρ₀} + c₁·e^{−2ρ₁}: coordinate 0's own
6779 // law is corrupted by the drift term and coordinate 1 decays at a
6780 // DIFFERENT exponential rate, so neither the marginals nor the joint
6781 // direction carry a constant pencil.
6782 let problem = OuterProblem::new(2).with_gradient(Derivative::Analytic);
6783 let (c0, drift, c1) = (3.0e3, 2.0e3, 5.0e2);
6784 let mut obj = problem.build_objective(
6785 (),
6786 move |_: &mut (), rho: &Array1<f64>| {
6787 Ok((c0 + drift * rho[0]) * (-rho[0]).exp() + c1 * (-2.0 * rho[1]).exp())
6788 },
6789 move |_: &mut (), rho: &Array1<f64>| {
6790 let e0 = (-rho[0]).exp();
6791 let e1 = (-2.0 * rho[1]).exp();
6792 Ok(OuterEval {
6793 cost: (c0 + drift * rho[0]) * e0 + c1 * e1,
6794 gradient: array![
6795 drift * e0 - (c0 + drift * rho[0]) * e0,
6796 -2.0 * c1 * e1
6797 ],
6798 hessian: HessianValue::Unavailable,
6799 inner_beta_hint: Some(array![1.0e-9 * e0]),
6800 })
6801 },
6802 None::<fn(&mut ())>,
6803 None::<fn(&mut (), &Array1<f64>) -> Result<EfsEval, EstimationError>>,
6804 );
6805 let rho = array![8.0, 7.0];
6806 let g0 = drift * (-8.0_f64).exp() - (c0 + drift * 8.0) * (-8.0_f64).exp();
6807 let g1 = -2.0 * c1 * (-14.0_f64).exp();
6808 let gradient = array![g0, g1];
6809 let hessian = array![[g0.abs(), 0.0], [0.0, g1.abs()]];
6810 let bounds = (Array1::from_elem(2, -12.0), Array1::from_elem(2, 12.0));
6811 let outcome = try_tail_snap_to_rail(
6812 &mut obj,
6813 &AsymptoteRailInputs {
6814 rho: &rho,
6815 projected_gradient: &gradient,
6816 railed: &[],
6817 hessian: &hessian,
6818 bounds: &bounds,
6819 terminal_beta: None,
6820 stationarity_bound: 1.0e-9,
6821 objective_tol: 1.0e-8,
6822 context: "non-face guard test",
6823 },
6824 )
6825 .expect("tail snap must not error");
6826 match outcome {
6827 TailSnapOutcome::Declined(reason) => {
6828 assert!(
6829 reason.contains("joint 2-coordinate face"),
6830 "the decline must carry the joint-face evidence, got: {reason}"
6831 );
6832 }
6833 other => panic!("a non-face must decline, got {other:?}"),
6834 }
6835 }
6836
6837 /// #2388: the tail-probe ladder must never step the probed coordinate
6838 /// outside its own box interval. Past a box bound the ρ-gradient assembly
6839 /// reports the #197 frozen-axis projection — a literal `0.0` — so an
6840 /// out-of-box probe fabricates a hard-zero tail row (`1.531e0 → 0.000e0` in
6841 /// one e-fold in the #2388 evidence) that the drift band can never confirm,
6842 /// and the fit refuses. The ladder must stop at the last strictly-in-domain
6843 /// probe, and the in-domain rows alone must still confirm an exact tail.
6844 #[test]
6845 fn tail_probe_ladder_never_leaves_the_coordinate_box_2388() {
6846 let c = 6723.0_f64;
6847 // Deep enough that the in-domain ladder keeps a healthy-gradient run
6848 // (probes at |g| below the interior floor are rightly judged unclean),
6849 // shallow enough that the 18-probe ladder would cross it without the
6850 // domain clip.
6851 let box_lower = 12.0_f64;
6852 let probed = std::sync::Arc::new(std::sync::Mutex::new(Vec::<f64>::new()));
6853 let probed_in_eval = std::sync::Arc::clone(&probed);
6854 let problem = OuterProblem::new(1).with_gradient(Derivative::Analytic);
6855 let mut obj = problem.build_objective(
6856 (),
6857 move |_: &mut (), rho: &Array1<f64>| Ok((c * (-rho[0]).exp()).abs()),
6858 move |_: &mut (), rho: &Array1<f64>| {
6859 let r = rho[0];
6860 probed_in_eval.lock().expect("probe log").push(r);
6861 // Below the box the assembly's frozen-axis convention reports a
6862 // fabricated zero gradient — exactly the #2388 evidence shape.
6863 let grad = if r <= box_lower + 1.0e-8 {
6864 0.0
6865 } else {
6866 -c * (-r).exp()
6867 };
6868 Ok(OuterEval {
6869 cost: (c * (-r).exp()).abs(),
6870 gradient: array![grad],
6871 hessian: HessianValue::Unavailable,
6872 inner_beta_hint: Some(array![(-r).exp()]),
6873 })
6874 },
6875 None::<fn(&mut ())>,
6876 None::<fn(&mut (), &Array1<f64>) -> Result<EfsEval, EstimationError>>,
6877 );
6878 let rho = array![29.9];
6879 let tol = AsymptoteTolerances::exp4_rail_bands(1.0e-2);
6880 let rail = build_and_assess_rail_coordinate(
6881 &mut obj,
6882 &rho,
6883 0,
6884 AsymptoteSide::Upper,
6885 &tol,
6886 (box_lower, 30.0),
6887 )
6888 .expect("probing must not error")
6889 .expect("the in-domain rows alone must certify the exact tail");
6890 assert!(
6891 (rail.tail_constant - c).abs() / c < 1.0e-6,
6892 "recovered ĉ={} should equal c={c}",
6893 rail.tail_constant,
6894 );
6895 let seen = probed.lock().expect("probe log").clone();
6896 assert!(
6897 !seen.is_empty() && seen.iter().all(|&r| r > box_lower),
6898 "no probe may leave the λ-selection domain (lower bound {box_lower}): {seen:?}",
6899 );
6900 }
6901
6902 /// Build a two-coordinate objective: coordinate 0 follows the upper-rail tail
6903 /// law; coordinate 1 (interior) is gradient-flat.
6904 fn upper_tail_with_interior(c: f64, a: f64) -> impl OuterObjective {
6905 let problem = OuterProblem::new(2).with_gradient(Derivative::Analytic);
6906 problem.build_objective(
6907 (),
6908 move |_: &mut (), rho: &Array1<f64>| Ok((c * (-rho[0]).exp()).abs()),
6909 move |_: &mut (), rho: &Array1<f64>| {
6910 let r = rho[0];
6911 Ok(OuterEval {
6912 cost: (c * (-r).exp()).abs(),
6913 gradient: array![-c * (-r).exp(), 0.0],
6914 hessian: HessianValue::Unavailable,
6915 inner_beta_hint: Some(array![a * (-r).exp(), 0.0]),
6916 })
6917 },
6918 None::<fn(&mut ())>,
6919 None::<fn(&mut (), &Array1<f64>) -> Result<EfsEval, EstimationError>>,
6920 )
6921 }
6922
6923 /// The interior-PSD gate is load-bearing: with a positive-definite interior
6924 /// sub-block the confirmed tail mints, but a genuinely indefinite interior
6925 /// curvature refuses the rail certificate even though the tail is clean.
6926 #[test]
6927 fn asymptote_rail_requires_psd_interior_sub_block() {
6928 let rho = array![29.9, 0.0];
6929 let projected = array![0.0, 0.0];
6930 let bounds = (array![-30.0, -30.0], array![30.0, 30.0]);
6931 let railed = [0usize];
6932
6933 let mut obj = upper_tail_with_interior(6723.0, 1.0);
6934 let hessian_psd = array![[1.0, 0.0], [0.0, 2.0]];
6935 let inputs_psd = AsymptoteRailInputs {
6936 rho: &rho,
6937 projected_gradient: &projected,
6938 railed: &railed,
6939 hessian: &hessian_psd,
6940 bounds: &bounds,
6941 terminal_beta: None,
6942 stationarity_bound: 1.0e-6,
6943 objective_tol: 1.0e-5,
6944 context: "asymptote-rail psd test",
6945 };
6946 let minted = try_certify_asymptote_rail(&mut obj, &inputs_psd)
6947 .expect("certification must not error");
6948 let (interior_norm, effective_bound, rails) =
6949 minted.expect("PSD interior + confirmed tail must mint");
6950 assert!(interior_norm <= 1.0e-6);
6951 assert!(
6952 effective_bound >= interior_norm,
6953 "the admitting bound must cover the interior norm"
6954 );
6955 assert_eq!(rails.len(), 1);
6956 assert_eq!(rails[0].index, 0);
6957
6958 let hessian_indefinite = array![[1.0, 0.0], [0.0, -2.0]];
6959 let inputs_indefinite = AsymptoteRailInputs {
6960 hessian: &hessian_indefinite,
6961 ..inputs_psd
6962 };
6963 let refused = try_certify_asymptote_rail(&mut obj, &inputs_indefinite)
6964 .expect("certification must not error");
6965 assert!(
6966 refused.is_err(),
6967 "indefinite interior curvature must refuse the rail certificate, got {refused:?}",
6968 );
6969 let reason = refused.unwrap_err();
6970 assert!(
6971 reason.contains("not PSD") || reason.contains("interior"),
6972 "the decline must name the refusing gate, got: {reason}"
6973 );
6974 }
6975}
6976
6977#[cfg(test)]
6978mod certify_resume_progress_tests {
6979 //! Unit coverage for the certify-last checkpoint-resume progress gate
6980 //! (#2374). The generalized loop keeps reseeding at the refused checkpoint
6981 //! only while each reseed exploits real descent; `certify_resume_made_progress`
6982 //! is the exact predicate that decides "real descent" vs "genuine floor", so
6983 //! pinning it directly pins the loop's termination contract independent of the
6984 //! solver dynamics that produce the reseeds.
6985 use super::{
6986 CERTIFY_RESUME_PROGRESS_REL, OuterConfig, certify_resume_made_progress,
6987 outer_rel_cost_floor,
6988 };
6989
6990 fn config_with_rel_cost(rel_cost: Option<f64>, tolerance: f64) -> OuterConfig {
6991 OuterConfig {
6992 tolerance,
6993 rel_cost_tolerance: rel_cost,
6994 ..OuterConfig::default()
6995 }
6996 }
6997
6998 #[test]
6999 fn rel_cost_floor_prefers_explicit_then_scaled_tolerance_never_below_hard_floor() {
7000 // Explicit relative tolerance wins verbatim.
7001 let explicit = config_with_rel_cost(Some(1.0e-3), 1.0e-5);
7002 assert_eq!(outer_rel_cost_floor(&explicit), 1.0e-3);
7003 // Absent, it derives from a small fraction of the absolute tolerance.
7004 let derived = config_with_rel_cost(None, 1.0e-2);
7005 assert!((outer_rel_cost_floor(&derived) - 1.0e-4).abs() <= 1.0e-16);
7006 // But never below the shared hard floor, however tight the tolerances.
7007 let tiny = config_with_rel_cost(Some(1.0e-30), 1.0e-30);
7008 assert_eq!(outer_rel_cost_floor(&tiny), super::COST_STALL_REL_TOL_FLOOR);
7009 }
7010
7011 // ── Helper math (arbitrary floor) ────────────────────────────────────
7012
7013 #[test]
7014 fn strict_descent_past_the_floor_is_progress() {
7015 let floor = 1.0e-4;
7016 // A drop far larger than floor·(1+|cost|)≈0.034 at cost scale ~1e2.
7017 assert!(certify_resume_made_progress(342.0, 300.0, floor));
7018 // A drop of many orders is trivially progress.
7019 assert!(certify_resume_made_progress(1.0e6, 1.0e3, floor));
7020 }
7021
7022 #[test]
7023 fn flat_or_uphill_reseed_is_not_progress() {
7024 let floor = 1.0e-4;
7025 // Exactly equal: no descent.
7026 assert!(!certify_resume_made_progress(100.0, 100.0, floor));
7027 // Uphill: a metric restart that landed worse is never progress.
7028 assert!(!certify_resume_made_progress(100.0, 100.5, floor));
7029 // A reduction SMALLER than the passed floor is within the flat band.
7030 let cost = 1.0e4;
7031 let sub_floor = floor * (1.0 + cost) * 0.5;
7032 assert!(!certify_resume_made_progress(cost, cost - sub_floor, floor));
7033 }
7034
7035 #[test]
7036 fn floor_anchors_on_the_smaller_cost_magnitude() {
7037 // With prior≈0 and a large-magnitude retried, anchoring on the smaller
7038 // (prior) magnitude keeps the floor tight so a genuine tiny descent near a
7039 // small optimum still registers, rather than being swamped by |retried|.
7040 let floor = 1.0e-4;
7041 // prior tiny-positive, retried strictly below it by more than floor·(1+0):
7042 assert!(certify_resume_made_progress(1.0e-2, 1.0e-3, floor));
7043 }
7044
7045 #[test]
7046 fn non_finite_retried_is_never_progress() {
7047 let floor = 1.0e-4;
7048 assert!(!certify_resume_made_progress(100.0, f64::NAN, floor));
7049 assert!(!certify_resume_made_progress(100.0, f64::INFINITY, floor));
7050 assert!(!certify_resume_made_progress(100.0, f64::NEG_INFINITY, floor));
7051 }
7052
7053 // ── Production gate (roundoff floor) ─────────────────────────────────
7054 //
7055 // These pin the ACTUAL gate the loop runs (`CERTIFY_RESUME_PROGRESS_REL`),
7056 // which must admit the tiny per-reseed descent a flat valley crawls out in
7057 // and reject only numerical noise — the exact distinction the earlier
7058 // cost-stall-floor gate got wrong (#2374: it stopped the survival LAML crawl
7059 // after one hop and refused a well-posed fit).
7060
7061 #[test]
7062 fn roundoff_gate_admits_the_tiny_flat_valley_crawl_step() {
7063 let rel = CERTIFY_RESUME_PROGRESS_REL;
7064 // The transformation-survival LAML moves ~4e-5 relative per reseed:
7065 // cost 342.0730 → 342.0580 is ~4.4e-5 relative, far above roundoff.
7066 assert!(certify_resume_made_progress(342.0730, 342.0580, rel));
7067 // Even a 1e-6 relative step at cost ~455 (the two-smooth cohort scale)
7068 // is real descent under the roundoff gate — the coarse cost-stall floor
7069 // (~1e-6·456 ≈ 4.6e-4) would have wrongly rejected it.
7070 assert!(certify_resume_made_progress(455.40, 455.40 - 5.0e-4, rel));
7071 }
7072
7073 #[test]
7074 fn roundoff_gate_rejects_noise_and_non_descent() {
7075 let rel = CERTIFY_RESUME_PROGRESS_REL;
7076 // A bitwise-identical reseed (genuine floor: BFGS found no descent from
7077 // the checkpoint) is not progress.
7078 assert!(!certify_resume_made_progress(455.40, 455.40, rel));
7079 // A reduction at the roundoff scale is noise, not descent: a few ULPs at
7080 // cost ~455 sits below CERTIFY_RESUME_PROGRESS_REL·(1+455).
7081 let noise = 4.0 * f64::EPSILON * (1.0 + 455.40);
7082 assert!(!certify_resume_made_progress(455.40, 455.40 - noise, rel));
7083 // Uphill / non-finite are never progress.
7084 assert!(!certify_resume_made_progress(455.40, 455.41, rel));
7085 assert!(!certify_resume_made_progress(455.40, f64::NAN, rel));
7086 }
7087}