Skip to main content

OuterObjective

Trait OuterObjective 

Source
pub trait OuterObjective {
Show 14 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 allow_continuation_prewarm(&self) -> bool { ... } fn outer_device_admission(&self) -> Option<RemlOuterAdmission> { ... } fn requires_continuation_path_entry(&self) -> bool { ... } 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 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 HessianResult::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(...) for genuine evaluation breakdowns so the runner can mark the step as a recoverable solver failure and escalate to the next declared fallback plan if the full attempt still fails.
  • 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 the continuation walk 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 continuation walk, 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 allow_continuation_prewarm(&self) -> bool

Whether the objective can benefit from continuation pre-warm before the first solver eval at a candidate seed.

Pre-warm is only correct for objectives with a real writable inner state slot: it evaluates an oversmoothed rho path before the seed and forwards non-empty inner_beta_hints between steps. Generic synthetic objectives and rho-only cache probes must start at the chosen seed directly, otherwise the pre-warm becomes an observable extra eval and can clobber seed-dispatch bookkeeping with empty beta seeds.

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 requires_continuation_path_entry(&self) -> bool

Whether every joint fit of this objective must ENTER through the crate::continuation_path::ContinuationPath (heavy-smoothing entry) rather than being solved cold at the seed ρ*.

The SAE-manifold joint objective overrides this to true: its joint (logits, t, β) block has a combinatorial active-set component that a cold solve can collapse, so it is entered at a heavy-smoothing regime and annealed down. Crucially, this flips the seed cascade’s structural failure handling from REJECT to DEMOTE-WITH-REASON: a “cold” structural defect (rank/alias/active-set diagnosis from the seed pre-warm or the uniform-structural early-exit) is not a disqualification but a signal to RE-ENTER the same seed at a heavier ContinuationPath regime. The candidate set therefore never empties on a structural diagnosis — every demotion is recorded with its reason and routed to a heavier regime.

The default false preserves the existing contract for every other objective: pre-warm stays an optimization (never a feasibility gate), and a uniform structural rejection still short-circuits the cascade.

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 Eckart-Young linear relaxation is convex and its optimum is certified by linear_span_anchor, so 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 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>