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