Skip to main content

Scheduler

Trait Scheduler 

pub trait Scheduler: Send + Sync {
    // Required method
    fn on_report(
        &self,
        study: &StudyView,
        trial: &TrialMeta,
        step: u64,
        values: &[f64],
    ) -> Result<Decision>;

    // Provided methods
    fn scripted_capability(&self) -> ScriptedSchedulerCapability { ... }
    fn fan_report_mode(&self) -> FanReportMode { ... }
    fn on_trial_end(
        &self,
        study: &StudyView,
        trial: &FrozenTrial,
    ) -> Result<Vec<Command>> { ... }
    fn resume_candidates(&self, study: &StudyView) -> Result<Vec<Command>> { ... }
    fn state(&self) -> Result<Option<SchedulerState>> { ... }
    fn restore_state(&self, blob: &SchedulerState) -> Result<()> { ... }
}
Expand description

Decides the fate of running trials.

Object-safe and Send + Sync: the study holds Arc<dyn Scheduler> and calls it from every worker.

Required Methods§

fn on_report( &self, study: &StudyView, trial: &TrialMeta, step: u64, values: &[f64], ) -> Result<Decision>

Called on every intermediate report.

values is a slice: a multi-objective study reports every objective at every step, and a scheduler is entitled to look at all of them. step is in the study’s declared ResourceUnit.

Implementations must be cheap — this runs inside the objective’s inner loop — and must not block on storage beyond the view they were handed.

§Errors

Error::Scheduler on failure. The study loop treats an error as “no decision”, not as a trial failure.

Provided Methods§

fn scripted_capability(&self) -> ScriptedSchedulerCapability

Declares whether scripted ask/tell can recreate this scheduler.

External and custom schedulers fail closed by default. A scheduler may opt into the narrow Nop marker only when its behavior is exactly the built-in NopScheduler; the marker is persisted with the study at creation so a later CLI process can validate it before loading work.

fn fan_report_mode(&self) -> FanReportMode

Declares whether this scheduler accepts aligned aggregate fan reports.

The default is fail-closed so an external scheduler cannot silently lose every report when a study’s fan width is greater than one.

fn on_trial_end( &self, study: &StudyView, trial: &FrozenTrial, ) -> Result<Vec<Command>>

Called once a trial reaches a terminal state.

The place where rung tables are updated and where a promotion decides to wake a paused trial (by returning Command::ResumeTrial). The default does nothing.

§What is visible for a fanned trial

For a study running a multi-seed SeedProtocol, the trial handed here carries its per-seed fan in FrozenTrial::replicates — the study loop fills it before this call. So a scheduler can read the just-finished trial’s per-seed samples (values and intermediates) at trial end, which is what a fan-aware decision needs. Two limits are deliberate and unchanged by this: the other trials reachable through study still have an empty replicates (the StudyView stays fan-agnostic so the sampler hot path pays nothing for fans), and per-replicate reports are captured and only aligned aggregate reports are sent to on_report. A between-trials fan comparison (this trial’s fan against an incumbent’s) therefore still needs the incumbent’s fan, read caller-side through Study::trial_fan. For a single-seed study replicates is empty, exactly as before.

§Errors

Error::Scheduler on failure.

fn resume_candidates(&self, study: &StudyView) -> Result<Vec<Command>>

Paused trials worth waking on a freed work slot, in priority order.

The slot-driven companion to on_trial_end’s resume: the study loop consults this in ask each time it is about to start fresh work, before creating a new trial. on_trial_end only fires on a terminal completion, so a scheduler that pauses trials which no later completion ever thaws (freeze-thaw’s exact regime — a slot is freed by a pause, not a finish) would otherwise strand its frozen winners: the paused pool is never drained. Returning the paused trial it would thaw here drains it on the next freed slot.

A returned Command::ResumeTrial whose target is no longer Paused — another worker won the wake under parallelism — is a safe no-op in the loop, so this may be called concurrently and need not re-check liveness itself.

The default returns nothing, correct for every scheduler that never emits Decision::Pause (NopScheduler, the pruners, Pbt): with no paused trials to drain there is nothing to wake on a freed slot, and the loop’s fresh-trial path is unchanged.

§Errors

Error::Scheduler on failure.

fn state(&self) -> Result<Option<SchedulerState>>

The scheduler’s persistable state, if it has any.

Written to Scope::Scheduler. ASHA’s rung tables live here as typed data — not as attribute-string hacks.

§Errors

Error::Scheduler if the state cannot be serialized.

fn restore_state(&self, blob: &SchedulerState) -> Result<()>

Restores a scheduler from a previously persisted blob.

The framework calls this once, when a study is constructed or resumed, before any decision: it reads Scope::Scheduler and, if a blob is present, hands it here so a stateful scheduler (a PBT population it carries, a promotion ladder it must not recompute) continues from where the last handle left off. State lives behind interior mutability, so &self suffices.

The default is a no-op, correct for every built-in scheduler (NopScheduler, MedianPruner, AshaPruner, HyperbandPruner, Patient, WilcoxonPruner): they are stateless-by-recomputation (rung tables are rebuilt from the StudyView), persist nothing (state is None, so this is never called with a blob of theirs), and would be broken by a restore.

§Errors

Error::Scheduler if the blob cannot be decoded.

Dyn Compatibility§

This trait is dyn compatible.

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

Implementors§