Trait Scheduler
pub trait Scheduler: Send + Sync {
// Required method
fn on_report(
&self,
study: &StudyView,
trial: &TrialMeta,
step: u64,
values: &[f64],
) -> Result<Decision, Error>;
// 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>, Error> { ... }
fn on_trial_end(
&self,
study: &StudyView,
trial: &FrozenTrial,
) -> Result<Vec<Command>, Error> { ... }
fn resume_candidates(
&self,
study: &StudyView,
) -> Result<Vec<Command>, Error> { ... }
fn state(&self) -> Result<Option<StateBlob>, Error> { ... }
fn restore_state(&self, blob: &StateBlob) -> Result<(), Error> { ... }
}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, Error>
fn on_report( &self, study: &StudyView, trial: &TrialMeta, step: u64, values: &[f64], ) -> Result<Decision, Error>
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
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
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
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>, Error>
fn on_population_report( &self, study: &StudyView, reports: &[PopulationReport], ) -> Result<Vec<PopulationDecision>, Error>
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>, Error>
fn on_trial_end( &self, study: &StudyView, trial: &FrozenTrial, ) -> Result<Vec<Command>, Error>
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>, Error>
fn resume_candidates(&self, study: &StudyView) -> Result<Vec<Command>, Error>
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<StateBlob>, Error>
fn state(&self) -> Result<Option<StateBlob>, Error>
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: &StateBlob) -> Result<(), Error>
fn restore_state(&self, blob: &StateBlob) -> Result<(), Error>
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".