Skip to main content

OuterObjective

Trait OuterObjective 

Source
pub trait OuterObjective {
Show 24 methods // Required methods fn capability(&self) -> OuterCapability; fn eval_cost(&mut self, rho: &Array1<f64>) -> Result<f64, EstimationError>; fn eval(&mut self, rho: &Array1<f64>) -> Result<OuterEval, EstimationError>; fn reset(&mut self); fn seed_inner_state( &mut self, beta: &Array1<f64>, ) -> Result<SeedOutcome, EstimationError>; // Provided methods fn eval_screening_proxy( &mut self, rho: &Array1<f64>, ) -> Result<f64, EstimationError> { ... } fn eval_with_order( &mut self, rho: &Array1<f64>, order: OuterEvalOrder, ) -> Result<OuterEval, EstimationError> { ... } fn eval_efs( &mut self, rho: &Array1<f64>, ) -> Result<EfsEval, EstimationError> { ... } fn eval_fixed_point_certificate( &mut self, rho: &Array1<f64>, ) -> Result<FixedPointCertificateEval, EstimationError> { ... } fn rail_face_limit( &mut self, rho: &Array1<f64>, face: &[usize], ) -> Result<RailFaceLimitOutcome, EstimationError> { ... } fn owns_terminal_coefficient_mode(&self) -> bool { ... } fn begin_exact_polish(&mut self) -> bool { ... } fn outer_domain_upper_bound( &self, ) -> Result<Option<Array1<f64>>, EstimationError> { ... } fn outer_domain_lower_bound( &self, ) -> Result<Option<Array1<f64>>, EstimationError> { ... } fn outer_device_admission(&self) -> Option<RemlOuterAdmission> { ... } fn reactive_domain_scalar_contract( &self, ) -> Result<Option<ContinuationScalarContract>, EstimationError> { ... } fn install_reactive_domain_scalar_state( &mut self, state: &ContinuationScalarState, ) -> Result<(), EstimationError> { ... } fn begin_reactive_domain_waypoint(&mut self) -> Result<(), EstimationError> { ... } fn commit_reactive_domain_waypoint( &mut self, rho: &Array1<f64>, ) -> Result<(), EstimationError> { ... } fn rollback_reactive_domain_waypoint( &mut self, ) -> Result<(), EstimationError> { ... } fn curvature_homotopy_entry( &mut self, rho: &Array1<f64>, ) -> Option<Result<bool, EstimationError>> { ... } fn accept_seed_without_outer_iterations( &mut self, rho: &Array1<f64>, ) -> Result<Option<f64>, EstimationError> { ... } fn terminal_eval_order(&self) -> Option<OuterEvalOrder> { ... } fn finalize_outer_result( &mut self, rho: &Array1<f64>, plan: &OuterPlan, ) -> Result<(), EstimationError> { ... }
}
Expand description

Common interface for outer smoothing-parameter objectives.

Every model path that optimizes smoothing parameters implements this trait. The runner function consumes it and handles solver selection, multi-start, and logging while delegating derivative fallback policy to opt.

§Contract

  • capability() must be stable (same result across calls).
  • eval() may return HessianValue::Unavailable at individual trial points even when capability().hessian == Analytic; opt degrades that step to first-order behavior instead of requiring the objective to fake a stale or non-finite Hessian.
  • Use eval_cost() / OuterEval::infeasible() for infeasible trial points. Return Err(...) only when the evaluation artifact itself cannot be constructed. Such errors are fatal across screening, multistart, and solver plans; they are never reinterpreted as another numerical trial.
  • eval_cost() is used only for cost-based optimization paths.
  • eval() is the main evaluation path (cost + gradient + optional Hessian).
  • eval_efs() is used only by the EFS solver. It runs the inner solve, builds the InnerSolution, and computes the EFS step vector. The default implementation returns an error; only objectives that support EFS need to override it.
  • reset() restores state to a clean baseline (for multi-start).

Required Methods§

Source

fn capability(&self) -> OuterCapability

Declare what this objective can compute analytically.

Source

fn eval_cost(&mut self, rho: &Array1<f64>) -> Result<f64, EstimationError>

Evaluate cost only for cost-based optimization paths.

Source

fn eval(&mut self, rho: &Array1<f64>) -> Result<OuterEval, EstimationError>

Evaluate cost + gradient + (if capable) Hessian.

Source

fn reset(&mut self)

Restore to a clean baseline for the next multi-start candidate.

Source

fn seed_inner_state( &mut self, beta: &Array1<f64>, ) -> Result<SeedOutcome, EstimationError>

Seed the inner-solver iterate before the first eval, e.g. when the outer-iterate cache restored a (ρ, β) pair from a prior run, or when a typed reactive continuation path forwards OuterEval::inner_beta_hint from the previous step.

Objectives make an explicit choice via the SeedOutcome return: implementations with an inner β slot return SeedOutcome::Installed after storing β; implementations without one return SeedOutcome::NoSlot. Genuine seeding failures (wrong dimension when a slot exists, etc.) are reported via Err(EstimationError).

Callers that need to distinguish “no slot” from “installed” (the outer cache warm-start path, which logs cache provenance) branch on the variant. Callers that don’t care (the reactive continuation path, which only proceeds cold when the hint is unusable) ignore it and only propagate Err.

Provided Methods§

Source

fn eval_screening_proxy( &mut self, rho: &Array1<f64>, ) -> Result<f64, EstimationError>

Evaluate the seed-screening ranking proxy at this rho.

Used exclusively by the rank_seeds_with_screening cascade. The default delegates to OuterObjective::eval_cost, which preserves behavior for non-REML objectives.

Concrete REML-state objectives override this to return the per-seed minimum penalized deviance observed during the inner P-IRLS solve (a monotonically descending quantity that remains a meaningful quality signal even at a 3-iteration screening cap), instead of the V_LAML criterion (which is dominated by a poorly-conditioned 0.5·log|H| term at partial-fit β̂ and ranks seeds little better than random). The proxy fires only in screening mode; outside screening it must return the regular V_LAML cost so the optimization objective is unchanged.

§Why the eval_cost default is correct for everyone else (#969)

The partial-fit pathology is CAUSED by the screening cap: it is the 0.5·log|H| term evaluated at a β̂ whose inner solve was truncated by screening_max_inner_iterations. An objective only suffers it if it (a) consumes that cap atomic AND (b) ranks on a curvature-bearing criterion at the truncated iterate — which is exactly the REML/LAML state-objective family, all of which override this method (or are built via build_objective_with_screening_proxy). Objectives that never wire the cap pay the full inner solve during screening, so their screened cost IS the true criterion — slower, but a correct ranking by definition, and a proxy could only degrade it. Any future objective that starts honoring the screening cap on a curvature-bearing criterion must override this with its own monotonically-descending inner quantity (the penalized-deviance pattern above generalizes: rank on the best inner merit seen, never on a curvature term at a truncated iterate).

Source

fn eval_with_order( &mut self, rho: &Array1<f64>, order: OuterEvalOrder, ) -> Result<OuterEval, EstimationError>

Evaluate the outer objective at the order requested by the active plan.

The default preserves legacy behavior by delegating value-only requests to OuterObjective::eval_cost and derivative requests to OuterObjective::eval.

Source

fn eval_efs(&mut self, rho: &Array1<f64>) -> Result<EfsEval, EstimationError>

Evaluate cost + EFS step vector. Only needed when the plan selects Solver::Efs. The default returns an error indicating EFS is not supported by this objective.

Source

fn eval_fixed_point_certificate( &mut self, rho: &Array1<f64>, ) -> Result<FixedPointCertificateEval, EstimationError>

Re-evaluate the terminal fixed point and provide an explicit analytic residual for every optimized coordinate.

This is a proof surface, not an alias for Self::eval_efs: iteration steps may contain guarded or structurally unsupported zeros. The default refuses certification so an EFS-capable objective must deliberately describe complete, root-equivalent coordinate coverage before a fixed- point result can mint a fit.

Source

fn rail_face_limit( &mut self, rho: &Array1<f64>, face: &[usize], ) -> Result<RailFaceLimitOutcome, EstimationError>

Analytic λ→∞ limit data for a rail face (#2348 Inc 5).

face lists the ρ-coordinates sitting at their infinite-smoothing bound. An objective that can form the limit EXACTLY — the fit restricted to the railed penalties’ common null space, together with the analytic first-order form of the criterion’s logdet and trace terms there — returns it here, and the outer certificate proves the face from it instead of probing a tail at finite λ.

A decline is not a failure, and it is typed: OutsideClosedForm leaves room for a different closed form to apply, while FaceUnavailable is a statement about the face itself. Either way the caller keeps whatever evidence it already had. The default declines for every objective that has no analytic limit at all.

Source

fn owns_terminal_coefficient_mode(&self) -> bool

Whether this objective owns a terminal coefficient mode whose bitwise identity fit assembly will later bind against the certified outer value.

The certification sequence (run.rs) installs the terminal state twice at result.rho: once via Self::finalize_outer_result (which the mode-owning evaluator uses to install its coefficient mode) and once via the analytic re-evaluation inside certify_outer_optimality (which sets result.final_value). On a nonconvex profiled objective those two evaluations can settle in different coefficient basins unless each is forced to re-install from the same clean baseline through Self::reset — otherwise they prime the inner solve off whatever warm state the preceding diagnostic/finalize left behind, and the mode’s objective and the certified value disagree by a whole basin (measured: 9.1931e2 vs 9.1671e2 on the cause-specific survival gate).

That terminal reset is otherwise gated on config.outer_inner_cap, which the REML/mixture objectives wire but the custom-family (and any other terminal-mode-owning closure) objective does not — it holds its inner cap in a different field and leaves outer_inner_cap None, so the reset never fires and the bitwise bind can spuriously fail on a bimodal inner solve. Returning true here forces the terminal reset independently of the cap, so finalize and certify provably come from one fresh evaluation at rho_star. It deliberately does NOT touch the inner_solve_converged(config.outer_inner_cap) gate: an objective that owns a terminal mode but does not populate the cap’s convergence atomic keeps its own stateful convergence semantics.

The default is false: an objective that owns no terminal coefficient mode (the reactive-domain fixture among them) retains the very state its evaluation at result.rho depends on and must not be reset.

Source

fn begin_exact_polish(&mut self) -> bool

Transition an objective that actually used an approximate derivative pilot to its exact full-data measure.

The runner calls this once after the pilot solver returns a checkpoint. true means the objective changed measure and must be optimized again from that checkpoint before analytic certification. Exact objectives and pilots that never installed a sample return false.

Source

fn outer_domain_upper_bound( &self, ) -> Result<Option<Array1<f64>>, EstimationError>

Optional objective-owned hard upper domain for the outer coordinates.

The generic optimizer intersects this vector with its configured box before projecting seeds, constructing a solver, or opening reactive continuation. Consequently the exact same upper endpoint is both the solver’s legal box face and the continuation path’s literal rho entry. None means the objective has no domain narrower than the configured generic box. An advertised vector must have capability().n_params finite entries; malformed contracts are typed runner errors.

Source

fn outer_domain_lower_bound( &self, ) -> Result<Option<Array1<f64>>, EstimationError>

Optional objective-owned hard lower domain for the outer coordinates.

This is intersected with the caller’s configured box at the same single runner seam as Self::outer_domain_upper_bound, before any seed, continuation waypoint, solver evaluation, or stationarity certificate can observe an out-of-domain coordinate.

Source

fn outer_device_admission(&self) -> Option<RemlOuterAdmission>

Optional opt-in to the device-resident outer REML BFGS-over-ρ driver (crate::gpu::reml_outer::run_reml_outer_on_device). Returns Some(adm) when the objective is a REML evaluator whose (spec, n, p, num_rho) admission predicate accepts the device path, and None otherwise.

The default returns None so non-REML objectives (line-search-only inner bridges, screening proxies, the EFS / hybrid-EFS sub-objectives) keep the host BFGS branch unconditionally — only the concrete REML-state objectives override this to consult [crate::estimate::reml::outer_eval::outer_reml_device_admission].

Source

fn reactive_domain_scalar_contract( &self, ) -> Result<Option<ContinuationScalarContract>, EstimationError>

Typed scalar continuation contract for repairing a non-finite literal outer seed through crate::continuation_path::ContinuationPath.

This is a typed domain-entry capability, not a fallback objective. The objective supplies both the smoother entry state and its literal target state. None means this objective has no such domain homotopy. The runner always probes the real seed first, so merely supplying a contract performs no waypoint installation or heavy work on a finite seed.

Source

fn install_reactive_domain_scalar_state( &mut self, state: &ContinuationScalarState, ) -> Result<(), EstimationError>

Install one scalar waypoint before the continuation rho spine evaluates the objective. Objectives that return Some from Self::reactive_domain_scalar_contract must override this method; the default is a typed contract refusal, never a silent no-op.

Source

fn begin_reactive_domain_waypoint(&mut self) -> Result<(), EstimationError>

Snapshot the objective’s complete accepted inner state before a reactive coupled waypoint is installed. Contract-advertising objectives must make this transactional: a failed trial is restored by Self::rollback_reactive_domain_waypoint.

Source

fn commit_reactive_domain_waypoint( &mut self, rho: &Array1<f64>, ) -> Result<(), EstimationError>

Commit the converged full inner state produced by the value evaluation at rho. A coefficient-only handoff is insufficient: latent coordinates, routing logits, decoder frames, loss, and scalar state must advance as one accepted waypoint.

Source

fn rollback_reactive_domain_waypoint(&mut self) -> Result<(), EstimationError>

Restore the full accepted state saved by Self::begin_reactive_domain_waypoint after an errored or non-finite trial.

Source

fn curvature_homotopy_entry( &mut self, rho: &Array1<f64>, ) -> Option<Result<bool, EstimationError>>

Run the objective’s certified curvature-homotopy entry leg, if it has one, leaving the inner state warm at the real (η = 1) objective.

An objective with a certified anchor — a point known by construction to be the global optimum of a relaxed problem — can replace the blind multi-seed multistart with a single predictor-corrector walk from that anchor to the true objective (#1007). The SAE-manifold objective overrides this: its η = 0 base-topology relaxation is convex, and a genuine low-rank (Eckart-Young / SVD) residual ceiling is certified by linear_span_anchor — the η = 0 endpoint is NOT a linear/affine model (for curved bases its base columns still embed curvature); “Eckart-Young” names the rank ceiling, not the chart. The walk in η tracks the unique optimal branch to η = 1. The walk monitors the arrow-factor min-pivot and halves the η step when it shrinks; a pivot collapse below tolerance is a DETECTED bifurcation (recorded on the fit payload, never silent), at which point the objective falls back to the documented multi-seed cascade.

Returns:

  • None — no certified anchor; use the standard seed cascade (the default for every other objective).
  • Some(Ok(true)) — the walk arrived; the inner state is warm at the certified η = 1 solution and the seed cascade is bypassed.
  • Some(Ok(false)) — the anchor degenerated or the walk detected a bifurcation; fall back to the multi-seed cascade (the report is recorded on the objective for the fit payload).
  • Some(Err(_)) — a hard failure constructing the anchor.
Source

fn accept_seed_without_outer_iterations( &mut self, rho: &Array1<f64>, ) -> Result<Option<f64>, EstimationError>

Let an objective declare that a seed is already a terminal outer result. Used for objectives with a certified high-quality construction seed where the generic rho optimizer can only degrade the fitted state.

Source

fn terminal_eval_order(&self) -> Option<OuterEvalOrder>

Optional analytic evaluation order that must own the final installed objective state, independently of the solver plan that found rho.

The default follows the solver (EFS finalizes through eval_efs, BFGS through first order, ARC through second order). Stateful profiled objectives may override this when only one evaluator produces the ownership payload consumed by fit assembly.

Source

fn finalize_outer_result( &mut self, rho: &Array1<f64>, plan: &OuterPlan, ) -> Result<(), EstimationError>

Re-install the selected outer result into the mutable objective before callers consume objective-owned fitted state. Optimizers may evaluate rejected trial points after the best point was found; without this final synchronization, stateful objectives can report the last trial fit rather than the returned OuterResult::rho.

Dyn Compatibility§

This trait is dyn compatible.

In older versions of Rust, dyn compatibility was called "object safety".

Implementors§

Source§

impl<S, Fc, Fe, Fr, Fefs, Feo, Fsp, Fseed> OuterObjective for ClosureObjective<S, Fc, Fe, Fr, Fefs, Feo, Fsp, Fseed>