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 population_report_mode(&self) -> PopulationReportMode { ... }
    fn on_population_report(
        &self,
        study: &StudyView,
        reports: &[PopulationReport],
    ) -> Result<Vec<PopulationDecision>> { ... }
    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 crate::scheduler::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 population_report_mode(&self) -> PopulationReportMode

Declares whether this scheduler accepts one shared report batch from several population members. This is distinct from fanned replicate reports. The default is fail-closed.

fn on_population_report( &self, study: &StudyView, reports: &[PopulationReport], ) -> Result<Vec<PopulationDecision>>

Decides all members at one population boundary from the same snapshot.

Implementations must return one result per input report in input order. The default refuses the operation, so an external runner cannot silently substitute per-member decisions for a batched population decision.

§Errors

Returns Unsupported by default. An opted-in scheduler may return its configuration or batch validation error without producing decisions.

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.

This runs inside the study’s finalization. It must not block on a Study::optimize of the same study (for example by joining the thread running it): that loop’s ask is refused with backpressure until this returns, and it retries rather than fails, so neither ever finishes.

§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 (crate::scheduler::NopScheduler, the pruners, crate::scheduler::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 rejects a blob: stateless schedulers persist nothing, so a blob here belongs to a different scheduler or an incompatible prior implementation. Silently accepting it would make an algorithm switch look like a successful resume. Built-in stateless schedulers (crate::scheduler::NopScheduler, crate::scheduler::MedianPruner, crate::scheduler::AshaPruner, crate::scheduler::HyperbandPruner, crate::scheduler::Patient, crate::scheduler::WilcoxonPruner) recompute their decisions from the StudyView and are unaffected when no blob is present.

§Errors

Error::Incompatible if the default restore receives a blob; Error::Scheduler if an overriding implementation cannot decode it.

Dyn Compatibility§

This trait is dyn compatible.

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

Implementors§