Struct StudyView
pub struct StudyView { /* private fields */ }Expand description
A consistent, incrementally maintained view of a study.
One storage sync per decision point, incremental afterwards: the study loop
calls Storage::sync once, folds the
deltas in with apply, and hands the result to the
sampler and the scheduler. That is what removes Optuna’s per-suggest
chattiness and its N+1 re-reads by construction.
§Visibility rules (architecture invariants, not preferences)
visible— what a sampler may learn from:CompleteandPrunedtrials. Failed trials are invisible. Their records survive (with parameters and error text) but a crashed run must not teach the sampler that its parameters were bad.running—RunningandPausedtrials. Pending-trial awareness is a loop-level guarantee, so constant-liar and qEI-style repulsion work for every sampler, not just the ones that remembered to ask.- Iteration is always by trial number, so two workers folding the same deltas in a different order still see the same sequence.
Implementations§
§impl StudyView
impl StudyView
pub fn new(
study: StudyId,
directions: Vec<Direction>,
space: Option<SpaceSchema>,
) -> Self
pub fn new( study: StudyId, directions: Vec<Direction>, space: Option<SpaceSchema>, ) -> Self
An empty view positioned at Cursor::BEGIN.
pub fn directions(&self) -> &[Direction]
pub fn directions(&self) -> &[Direction]
The objective directions.
pub const fn space(&self) -> Option<&SpaceSchema>
pub const fn space(&self) -> Option<&SpaceSchema>
The statically declared search space, if the study has one.
pub fn all(&self) -> impl Iterator<Item = &FrozenTrial>
pub fn all(&self) -> impl Iterator<Item = &FrozenTrial>
Every trial the view knows about, ordered by trial number.
pub fn get(&self, number: TrialNumber) -> Option<&FrozenTrial>
pub fn get(&self, number: TrialNumber) -> Option<&FrozenTrial>
The trial with this number, if the view has it.
pub fn visible(&self) -> impl Iterator<Item = &FrozenTrial>
pub fn visible(&self) -> impl Iterator<Item = &FrozenTrial>
The trials a sampler may learn from: Complete and Pruned.
Failed trials are deliberately excluded.
pub fn completed(&self) -> impl Iterator<Item = &FrozenTrial>
pub fn completed(&self) -> impl Iterator<Item = &FrozenTrial>
The successfully completed trials only.
pub fn running(&self) -> impl Iterator<Item = &FrozenTrial>
pub fn running(&self) -> impl Iterator<Item = &FrozenTrial>
The in-flight trials: Running and Paused.
A paused trial still occupies a slot in the population and still holds a checkpoint, so parallel-repulsion samplers must see it.
pub fn failed(&self) -> impl Iterator<Item = &FrozenTrial>
pub fn failed(&self) -> impl Iterator<Item = &FrozenTrial>
The failed trials.
Not visible to samplers; exposed here because reports, retry policies and humans all want them.
pub fn best(&self) -> Option<&FrozenTrial>
pub fn best(&self) -> Option<&FrozenTrial>
The best trial of a single-objective study, feasibility first.
Returns None for a multi-objective study — there is no total order on
a Pareto front, and silently picking the first objective would be a
lie. Pruned trials compete on their last intermediate value
(FrozenTrial::objective_value). Ties go to the lower trial number,
so the answer does not depend on completion order.
§Constraints outrank the objective
Ranking is constrained_dominates,
the same relation pareto_front uses, so a
trial that recorded constraints
(TrialCtx::record_constraints)
and satisfies them beats one that violates them whatever their objectives
say, and two infeasible trials are ranked by total violation. That is what
“typed constraints, feasibility-first best” means
(Constraints):
a constrained study whose best was an
illegal configuration would be reporting an answer the user cannot use.
A study where no trial recorded constraints is ranked by the objective
alone, exactly as before — the relation degenerates to
Direction::is_better for one
objective.
§A view synced from storage carries no constraints
The same caveat pareto_front carries, and for
the same reason: FrozenTrial::constraints is empty on a snapshot read
straight from storage (the frozen Storage
seam cannot write it), so this method is constraint-blind unless the
caller has attached them.
Study::best_trial is the
constrained-aware entry point.
pub fn pareto_front(&self) -> Vec<&FrozenTrial>
pub fn pareto_front(&self) -> Vec<&FrozenTrial>
The Pareto front: the non-dominated set of trials a sampler may learn from.
The multi-objective answer to best, which returns
None for a multi-objective study on purpose — there is no total order
on a front, so the honest answer is a set (read the front, not the
best). Trials come back in trial-number
order, so the answer does not depend on completion order.
Candidates are visible trials with an objective
vector of the right arity (a pruned trial competes on its last
intermediate, as everywhere else); a trial with a NaN objective is
dominated by every well-formed one and can never reach the front
(dominates). Ranking is
constrained_dominates over each
trial’s constraints, so a feasible trial
outranks an infeasible one.
§A view synced from storage carries no constraints
FrozenTrial::constraints is empty on a snapshot read straight from
storage (the frozen Storage seam cannot
write it), so this method is constraint-blind unless the caller has
attached them. Study::pareto_front
is the constrained-aware entry point: it attaches each trial’s recorded
values first. For an unconstrained study the two agree exactly.
§Complexity
O(M·N²) in the number of comparable trials — a reporting call, not a
per-suggest one.
pub fn nadir_point(&self) -> Option<Vec<f64>>
pub fn nadir_point(&self) -> Option<Vec<f64>>
The nadir point of the comparable trials: the componentwise worst objective value observed.
The conventional default reference for hypervolume.
It is taken over every comparable trial rather than over the front
alone, so it is the more stable of the two choices; a hypervolume history
must still be measured against one fixed reference (see
nadir_point). None when nothing is
comparable yet.
pub fn hypervolume(&self, reference: &[f64]) -> Result<f64>
pub fn hypervolume(&self, reference: &[f64]) -> Result<f64>
The hypervolume the front covers, bounded by reference.
Computed over the pareto_front, keeping only
its feasible members: an infeasible solution has not earned the
objective space it appears to cover. (It reaches the front at all only
when nothing feasible exists.)
§Errors
Everything hypervolume reports — notably
Error::InvalidSpace above
MAX_EXACT_HYPERVOLUME_DIM
objectives.
pub fn apply(&mut self, deltas: Vec<TrialDelta>, cursor: Cursor) -> Result<()>
pub fn apply(&mut self, deltas: Vec<TrialDelta>, cursor: Cursor) -> Result<()>
Folds storage deltas in and advances the cursor.
Each delta carries a whole-trial snapshot, so merging is a replace keyed by trial number (which is a stable bijection with the trial id inside one study).
§Errors
Error::Conflict if cursor moves backwards. A cursor is
monotone by contract; a backwards move means the caller mixed up two
storages or two studies, and silently accepting it would corrupt the
view. Applying the same cursor twice is allowed (a no-op resync).
pub fn upsert(&mut self, trial: FrozenTrial)
pub fn upsert(&mut self, trial: FrozenTrial)
Inserts or replaces a trial without touching the cursor.
The escape hatch for building a view by hand — tests, and the study loop’s own just-created trial, which it already holds and need not wait for a sync to see.