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 returnHessianResult::Unavailableat individual trial points even whencapability().hessian == Analytic;optdegrades 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. ReturnErr(...)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 theInnerSolution, 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§
Sourcefn capability(&self) -> OuterCapability
fn capability(&self) -> OuterCapability
Declare what this objective can compute analytically.
Sourcefn eval_cost(&mut self, rho: &Array1<f64>) -> Result<f64, EstimationError>
fn eval_cost(&mut self, rho: &Array1<f64>) -> Result<f64, EstimationError>
Evaluate cost only for cost-based optimization paths.
Sourcefn eval(&mut self, rho: &Array1<f64>) -> Result<OuterEval, EstimationError>
fn eval(&mut self, rho: &Array1<f64>) -> Result<OuterEval, EstimationError>
Evaluate cost + gradient + (if capable) Hessian.
Sourcefn seed_inner_state(
&mut self,
beta: &Array1<f64>,
) -> Result<SeedOutcome, EstimationError>
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§
Sourcefn eval_screening_proxy(
&mut self,
rho: &Array1<f64>,
) -> Result<f64, EstimationError>
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).
Sourcefn eval_with_order(
&mut self,
rho: &Array1<f64>,
order: OuterEvalOrder,
) -> Result<OuterEval, EstimationError>
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.
Sourcefn eval_efs(&mut self, rho: &Array1<f64>) -> Result<EfsEval, EstimationError>
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.
Sourcefn allow_continuation_prewarm(&self) -> bool
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.
Sourcefn outer_device_admission(&self) -> Option<RemlOuterAdmission>
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].
Sourcefn requires_continuation_path_entry(&self) -> bool
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.
Sourcefn curvature_homotopy_entry(
&mut self,
rho: &Array1<f64>,
) -> Option<Result<bool, EstimationError>>
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η = 1solution 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.
Sourcefn accept_seed_without_outer_iterations(
&mut self,
rho: &Array1<f64>,
) -> Result<Option<f64>, EstimationError>
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.
Sourcefn finalize_outer_result(
&mut self,
rho: &Array1<f64>,
plan: &OuterPlan,
) -> Result<(), EstimationError>
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".