Struct Study
pub struct Study { /* private fields */ }Expand description
A study: storage, sampler, scheduler, clock and configuration, bound.
A handle is one worker. Several handles — threads, processes, machines — may drive the same study through the same storage at the same time; they coordinate through storage alone and never through each other (several workers, one study).
use atune_core::clock::ManualClock;
use atune_core::space::Scale;
use atune_core::study::{Budget, Study, StudyConfig};
use std::sync::Arc;
let study = Study::builder()
.clock(Arc::new(ManualClock::new(0)))
.budget(Budget::new_with_trials(16))
.create(StudyConfig::new("quadratic").with_seed(7))?;
study.optimize(|ctx| {
let x = ctx.suggest_f64("x", -5.0..=5.0, Scale::Linear)?;
Ok((x * x).into())
})?;
assert_eq!(study.trial_count()?, 16);
assert!(study.best_trial()?.is_some());Implementations§
§impl Study
impl Study
pub fn trial_fan(&self, trial: TrialId) -> Result<Option<ReplicateFan>>
pub fn trial_fan(&self, trial: TrialId) -> Result<Option<ReplicateFan>>
The per-seed replicate fan of a multi-seed trial, if it has one.
Ok(None) for a single-seed trial before optional re-evaluation (no fan
was stored). The fan carries both the tune replicates the sampler’s
objective aggregates over and — if the final
reevaluate stage has run — the disjoint test
replicates. A fan-free study may therefore have a test-only fan after
re-evaluation.
§Errors
Error::Conflict if a same-version fan violates the producer’s row
shape; Error::Storage if the blob cannot be decoded or the backend
faults.
pub fn trial_with_fan(&self, trial: TrialId) -> Result<FrozenTrial>
pub fn trial_with_fan(&self, trial: TrialId) -> Result<FrozenTrial>
A trial’s snapshot with its per-seed fan attached.
get_trial leaves
FrozenTrial::replicates empty —
storage is fan-agnostic and the sampler never needs it — so this reader
fills it from the trial’s fan blob for a caller that does want the
per-seed sub-results (a report, a dashboard, the Wilcoxon pruner). It
carries the tune replicates; the test replicates stay in
trial_fan.
§Errors
Error::NotFound if the trial does not exist; Error::Storage on a
backend fault.
§impl Study
impl Study
pub fn register_policy(&self, policy: &SpacePolicy) -> Result<()>
pub fn register_policy(&self, policy: &SpacePolicy) -> Result<()>
Persists policy into this study’s per-parameter slots — the
create-time half of declaring open parameters, shared by
StudyBuilder::policy and every surface that creates its study through
StudyLocator rather than
StudyBuilder::create (the CLI does).
Each Open is validated
(Open::validate), and a policy naming
a declared parameter must seed exactly the declared distribution — the
range has one home, not two. Writing is
per-parameter and replace-on-write, so re-registering an equal
policy is idempotent; refusing a disagreement with an already-stored
slot is the suggest-time and resume-time comparison’s job, not
this method’s.
An empty policy writes nothing at all — the property that keeps every existing journal golden byte-identical.
§Errors
Error::InvalidSpace/Error::Unsupported from validation, or
Error::Storage on a backend fault.
pub fn live_distribution(
&self,
name: &str,
seed: &Distribution,
) -> Result<Distribution>
pub fn live_distribution( &self, name: &str, seed: &Distribution, ) -> Result<Distribution>
The distribution name would be asked under right now: seed
widened by the replayed growth rule, or seed itself when no policy
names name (the scripted path — join_and_sample has no lease to
interpose on, so it asks the handle
directly and must record the widened range, not the caller’s seed).
Emits the same growth-decision events an ordinary ask would, so a scripted study’s decisions are observable too.
§Errors
As the ask-path widening: Error::Unsupported for a stateful
sampler or a multi-objective study under a policy, or a storage
fault from the on-demand policy reads.
pub fn policy_for(&self, name: &str) -> Result<Option<Open>>
pub fn policy_for(&self, name: &str) -> Result<Option<Open>>
Reads the stored policy slot for name, if one was ever declared —
the read half of register_policy, for the
CLI’s resume comparison and atune doctor’s policy
reporting.
§Errors
A foreign blob kind or a future version is Error::Incompatible;
Error::Storage on a backend fault or an undecodable payload.
§impl Study
impl Study
pub fn backing_paths(&self) -> Option<Vec<PathBuf>>
pub fn backing_paths(&self) -> Option<Vec<PathBuf>>
Local source files protected from export replacement.
See crate::storage::Storage::backing_paths for the unknown-provenance contract.
pub fn builder() -> StudyBuilder
pub fn builder() -> StudyBuilder
Starts building a study.
Storage is in-memory by default; call
storage for anything durable.
pub const fn config(&self) -> &StudyConfig
pub const fn config(&self) -> &StudyConfig
The configuration the study was created with.
Read from storage when the study was loaded, so a resumed study reproduces the original seed rather than a new one.
pub fn user_attrs(&self) -> Result<UserAttrs>
pub fn user_attrs(&self) -> Result<UserAttrs>
Returns the user-owned JSON metadata attached to this study.
The backing storage is authoritative, so the result is shared across handles and survives a reload.
§Errors
Returns the backing storage’s error if the study does not exist or its metadata cannot be read.
pub fn set_user_attr(&self, key: impl Into<String>, value: Value) -> Result<()>
pub fn set_user_attr(&self, key: impl Into<String>, value: Value) -> Result<()>
Inserts or replaces one user-owned JSON attribute on this study.
Reserved-key validation and persistence are delegated to the backing storage, which keeps this handle a coordination facade.
§Errors
Returns crate::error::Error::Conflict for a reserved key, or the backing
storage’s error if the study does not exist or the value cannot be
stored.
pub const fn worker(&self) -> WorkerId
pub const fn worker(&self) -> WorkerId
This handle’s worker identity.
Set by StudyBuilder::worker, and stamped onto
FrozenTrial::worker by every lease
this handle claims.
pub const fn retry_policy(&self) -> RetryPolicy
pub const fn retry_policy(&self) -> RetryPolicy
The retry policy failed trials are subject to.
pub const fn seed_protocol(&self) -> SeedProtocol
pub const fn seed_protocol(&self) -> SeedProtocol
The multi-seed protocol this study evaluates trials under.
The default is single-seed (SeedProtocol::new): no fan, and every
trial is one evaluation.
§impl Study
impl Study
pub fn ask(&self) -> Result<Option<TrialCtx<'_>>>
pub fn ask(&self) -> Result<Option<TrialCtx<'_>>>
Creates the next trial and samples it.
The sequence is fixed:
- check the budget — deadline against the injected clock, fidelity against the synced view, trial count against a local reservation;
- one storage sync folded into the handle’s view through its cursor,
with reads beside it only for the features in use: a full snapshot
when the study has a target, each newly reporting trial’s
atune.lineagerecord once under a fidelity budget, and constraint state for newly synced completed and pruned trials when the sampler reads constraints; - create the trial (storage assigns the contiguous, race-safe number);
- derive
sampler_seed = seed_for(study_seed, number, Stream::Sampler); - infer the relative space and sample it jointly;
- persist the whole sample as one batch write;
- transition
Waiting -> Runningand hand back the context.
Ok(None) means the budget is spent, the handle’s
StopHandle was stopped, or its
target_value is met — a
normal stop, not a failure.
§Budgets under parallelism
The handle first claims a local slot with an atomic compare-and-swap,
preventing its own threads from needlessly racing for the last slot.
The authoritative check and creation are one
TrialLifecycleStorage::reserve_and_create_if_budget operation, so
independently opened handles and processes cannot overshoot either.
max_trials counts durable trial records already in the study,
including records whose post-create start later fails.
§What the batch write costs a define-by-run study
Persisting the joint sample up front is what makes a declared-space
trial cost one parameter write instead of one per suggest. It has
one consequence worth stating: a parameter that the inferred relative
space contains but the objective does not go on to suggest — a
conditional branch not taken — is still recorded on the trial. For a
study with a declared SpaceSchema that is
exactly right (the space is the space). For a define-by-run study the
inferred space depends on what had completed when the trial was asked,
so under parallelism two runs may record different unsuggested extras
on the same trial number. The values actually suggested are unaffected:
a relative draw is used only when it was made under exactly the
distribution the objective asks for (step 4 of the chain on
TrialCtx), and otherwise the independent path — keyed on
(sampler seed, name, distribution) — answers. Determinism promise 2
therefore holds for everything the objective read.
§Errors
Error::SpaceExhausted when an exhaustive sampler has handed out
every configuration — a normal stop condition that
optimize treats as the end of the study. The trial
it was discovered on keeps its number, becomes durably Failed with the
reason recorded, and is invisible to samplers;
Error::LostRace if another worker claimed the freshly created trial
before this handle could start it; plus anything storage or the sampler
reports.
A failed ask does not consume a queued template. Every error path
below returns the template to the front of the queue, so an
enqueued configuration or a pending retry survives a
lost race, a backend fault or an exhausted sampler and is used by the
next successful ask.
§impl Study
impl Study
pub fn optimize<F>(&self, objective: F) -> Result<()>
pub fn optimize<F>(&self, objective: F) -> Result<()>
Runs trials until the budget is spent.
A convenience loop over ask/tell and nothing more — anything it can do,
a caller can do by
hand with ask and tell.
Sequential by default. With
parallelism(n) and n > 1
the trials run
on std::thread::scope, which needs the system feature; with
n == 1 the code path contains no thread spawn at all, which is
what keeps optimize usable on wasm32-unknown-unknown.
§What stops the loop
| Condition | Result |
|---|---|
| budget spent | Ok(()) |
Error::SpaceExhausted from the sampler | Ok(()) — a grid search ends this way |
| a failing objective | the trial is recorded Failed (and maybe retried); the loop continues |
| a panicking objective | unwinds sequentially; parallel joins return Err(..) |
| a storage or sampler fault | Err(..), after the trial in flight is recorded |
“Recorded” holds for successful terminal writes and for finalization
fallbacks that can commit the trial’s state without replacement effects.
If the backend refuses every terminal write, the trial remains Running
for stale-lease fail-over.
A terminal that overlapped another sampler or scheduler call commits
without draining its callbacks and effects. Once the workers join,
optimize recovers those finalizations; when that creates a retry or
fork child, the workers run again, under the same stop conditions, to
evaluate it.
§Panics in the objective
A sequential objective panic unwinds through optimize. In parallel,
it unwinds its worker; optimize joins every worker and returns an
error if any worker panicked. A panicking objective is not converted
into a trial outcome, so its trial remains Running for durable
stale-lease fail-over. A profile compiled with panic = "abort" still
aborts the process.
§Determinism
Trial-number-indexed
determinism
— trial n gets the same parameters however many workers ran
— holds for a stateless sampler with an empty template queue. Both
enqueue and a non-zero
RetryPolicy make the
number-to-template mapping order-dependent, because when a template is
queued is itself thread-scheduling dependent; such a study is
replayable rather than pre-determined. Under parallelism(1)
the mapping is fixed in both cases.
§Errors
The first fatal error any worker reports; the other workers finish the
trial they hold first and then stop, rather than running out the
remaining budget. A Error::LeaseLost while committing one trial is
local to that trial, so its worker moves on. Error::Conflict if
parallelism > 1 was requested from a build without the system
feature.
pub fn optimize_with(&self, objective: &dyn Objective) -> Result<()>
pub fn optimize_with(&self, objective: &dyn Objective) -> Result<()>
optimize for an Objective that is not a closure.
The general entry point, and the one both forms share: a hand-written
impl Objective, a boxed trait object, an objective chosen at run time.
optimize exists alongside it purely so that a
closure needs no type annotation — a bound of impl Objective gives the
compiler nothing to infer a closure’s argument type from, whereas a
function-trait bound does.
§Errors
As optimize.
§impl Study
impl Study
pub fn view(&self) -> Result<StudyView>
pub fn view(&self) -> Result<StudyView>
A fresh snapshot of the study.
Syncs once — incrementally, from the handle’s cursor — and returns the
folded view. This is the read path every convenience method below goes
through, and the one a caller should use too: re-reading the whole
study per question is exactly the chattiness the cursor exists to
kill. When the sampler
reads_constraints, each
newly synced completed or pruned trial also carries its recorded
constraint values.
§Errors
Error::NotFound if the study vanished from storage;
Error::Storage on a backend fault or a poisoned internal lock.
pub fn snapshot(&self) -> Result<StudySnapshot>
pub fn snapshot(&self) -> Result<StudySnapshot>
A canonical hydrated observation of the study.
Constraints, replicate fans, space policies, and pending terminal
finalizations are read with the trials through one storage snapshot
request. Built-in backends provide one read epoch; compatible custom
backends expose crate::storage::SnapshotConsistency::BestEffort
through the returned snapshot instead of making an atomicity claim.
Samplers and schedulers continue to use the lighter raw view.
§Errors
A storage read or side-state decoding error.
pub fn best_trial(&self) -> Result<Option<FrozenTrial>>
pub fn best_trial(&self) -> Result<Option<FrozenTrial>>
The best trial of a single-objective study, if there is one — feasibility first.
None for a multi-objective study, whose honest answer is
pareto_front — see StudyView::best.
Like pareto_front this attaches each visible
trial’s recorded constraint values before ranking (storage cannot carry
them on the record), so a trial that violated its constraints never wins
while a feasible one exists, whatever the objectives say. That is the same
relation the Pareto front uses, so the two answers cannot disagree about
feasibility. An unconstrained study is unaffected: with nothing recorded
the ranking is the plain objective comparison it always was.
§Cost
A reporting call through one storage snapshot request. It is not a per-suggest path.
§Errors
As view, plus Error::Storage if a constraint blob
cannot be decoded.
pub fn stop_handle(&self) -> StopHandle
pub fn stop_handle(&self) -> StopHandle
This handle’s cooperative stop flag; see crate::study::StopHandle.
Every clone shares the flag, so an objective, a signal handler or a
monitoring thread can stop the search while
optimize runs.
pub fn trial_count(&self) -> Result<usize>
pub fn trial_count(&self) -> Result<usize>
pub fn trial_checkpoints(
&self,
trial: TrialId,
) -> Result<Option<TrialCheckpoints>>
pub fn trial_checkpoints( &self, trial: TrialId, ) -> Result<Option<TrialCheckpoints>>
The checkpoint references a trial recorded, if any.
atune stores checkpoint references, never bytes
(population-based
training): each entry maps a resource
step to an opaque reference string that an
objective wrote through
TrialCtx::record_checkpoint.
Ok(None) when the trial recorded none.
§Errors
Error::Storage if the blob cannot be decoded or the backend faults.
pub fn latest_checkpoint(&self, trial: TrialId) -> Result<Option<String>>
pub fn latest_checkpoint(&self, trial: TrialId) -> Result<Option<String>>
pub fn trial_constraints(&self, trial: TrialId) -> Result<Option<Vec<f64>>>
pub fn trial_constraints(&self, trial: TrialId) -> Result<Option<Vec<f64>>>
The constraint values a trial recorded, if it recorded any.
≤ 0 is satisfied, > 0 is violated
(pareto::constraint). Ok(None) when the
trial declared none. Calling
TrialCtx::record_constraints
with an empty list is also undeclared: it is a no-op and creates no
canonical state. An undeclared trial is distinct from a declared trial
whose values are all satisfied.
§Errors
Error::Storage if the blob cannot be decoded or the backend faults.
pub fn pareto_front(&self) -> Result<Vec<FrozenTrial>>
pub fn pareto_front(&self) -> Result<Vec<FrozenTrial>>
The study’s Pareto front: the non-dominated set of completed trials.
The multi-objective analogue of best_trial, which
correctly answers None for a multi-objective study because a front has
no total order (read the front, not the
best). Trials are returned in
trial-number order, so the answer does not depend on completion
order, and a single-objective study gets the (possibly tied) best trials
— the degenerate case, not a special case.
Ranking is constrained_dominates:
if trials recorded constraint values, a feasible trial outranks an
infeasible one whatever their objectives say, and the front is the
feasible one whenever any trial is feasible.
§Cost
This is a reporting call through one storage snapshot request, not a
hot path. Ranking itself is O(M·N²).
§Errors
As view, plus Error::Storage if a constraint blob
cannot be decoded.
pub fn nadir_point(&self) -> Result<Option<Vec<f64>>>
pub fn nadir_point(&self) -> Result<Option<Vec<f64>>>
The nadir point of the study’s comparable trials: the componentwise worst objective value observed.
The conventional default reference for hypervolume
— see nadir_point for why a hypervolume
history must nonetheless be measured against one fixed reference
rather than a moving nadir. Ok(None) when no trial has a comparable
objective vector yet.
§Errors
As view.
pub fn hypervolume(&self, reference: &[f64]) -> Result<f64>
pub fn hypervolume(&self, reference: &[f64]) -> Result<f64>
The hypervolume the study’s Pareto front covers, bounded by
reference.
The standard scalar quality measure of a multi-objective run: it rewards a front for converging and for spreading out, which no single number derived from one objective can. Larger is better whatever the directions are. Only feasible front members count.
§Errors
As view, plus everything
hypervolume reports — notably
Error::InvalidSpace above
MAX_EXACT_HYPERVOLUME_DIM
objectives, where an exact computation would take exponential time and is
refused rather than silently attempted.
pub fn lineage(&self) -> Result<Vec<LineageEntry>>
pub fn lineage(&self) -> Result<Vec<LineageEntry>>
The study’s fork genealogy — the population tree a forking scheduler
(such as Pbt) builds.
Reads one hydrated snapshot and reconstructs, per trial, its parent and
the recovered explore perturbation (the parameter difference from its
parent).
This is the inspectable lineage the design calls the headline PBT
artifact and the tree the GUI renders (read the
genealogy); the pure
reconstruction is fork_lineage. Entries are
in trial-number order, so the answer does not depend on completion order.
§Errors
As snapshot.
pub fn recipe(&self, leaf: TrialId) -> Result<Recipe>
pub fn recipe(&self, leaf: TrialId) -> Result<Recipe>
The schedule that produced leaf, as a versioned Recipe.
Reads one hydrated snapshot and walks from leaf back through each
trial’s atune.lineage record while it inherited a checkpoint: the
segments run from the start of the leaf’s training (a root, a fresh
restart, or a child that started without a checkpoint) to the leaf,
each with its full typed configuration, the step range it trained, and
the fork or restart event that began it.
Recipe::to_json is the
deterministic atune.recipe version-1 export.
§Errors
Error::NotFound if leaf is not in the study; Error::Lineage
naming the trial when a trial on the path is a fork without an
atune.lineage record (written before the record existed) or names a
donor the study does not contain — nothing is inferred from reports;
otherwise as snapshot.
pub fn resource_ledger(&self) -> Result<ResourceLedger>
pub fn resource_ledger(&self) -> Result<ResourceLedger>
The study’s ResourceLedger: per
trial, the resource it was charged for, the resource its lineage
represents, its training age since initialization or the last restart,
and each stored evaluation stage’s replicate count, plus the study
total — the fidelity the budget gate has charged.
§Errors
As snapshot.
§impl Study
impl Study
pub fn reevaluate<F>(&self, objective: F, top_k: usize) -> Result<FinalReport>
pub fn reevaluate<F>(&self, objective: F, top_k: usize) -> Result<FinalReport>
The final re-evaluation stage: re-rank the top configurations on the held-out test seeds (re-evaluate on held-out seeds).
This is the guard against the ICML-2023 failure mode — configurations
that overfit their tuning seeds. After the optimization budget is spent,
it takes the top_k completed trials by the canonical constraint-aware
tune ranking (feasibility first, then aggregate), re-evaluates each
one’s configuration under the protocol’s disjoint test seeds, and
reports:
- the test-seed-ranked best (which is not necessarily the tune-ranked best — that is the whole point), ranked feasibility-first on the componentwise worst of the constraints its test replicates recorded, then by test value;
- per configuration, its tune value, its test value, and the signed
overfit_gapbetween them.
The test replicates are stored on each re-evaluated trial’s fan (readable
through trial_fan); the trial’s sampler-visible
objective — its values, and therefore the sampler’s observation — is
left untouched, so the tune aggregate remains the objective used by
the sampler. Study-owned ranking continues to apply the recorded
constraints first. In other words:
- before / without
reevaluate:best_trialis the canonical constraint-aware tune-ranked best. - after
reevaluate:best_trialis still the canonical constraint-aware tune-ranked best; the test-ranked best isFinalReport::best, which is the study’s honest answer.
Nothing else changes about a study, and calling it is optional — it runs only when the caller invokes it, and only if the protocol declares test seeds.
§A stored test set is reused
A candidate whose fan already holds a complete test set from an earlier call — one replicate per test seed, in index order, each under exactly the seed the protocol derives now, each with a finite objective vector of the study’s arity, and each with a recorded constraint vector when the trial declares tune-stage constraints — is ranked from that set without running the objective again, and its fan is not rewritten. Any other stored set (missing, partial, failed, or recorded under other seeds) is discarded and rerun in full, so a resumed study repeats only the held-out work an interruption lost.
§Errors
Error::Conflict if the study is multi-objective (there is no total
order to rank a test best by) or if the protocol declares no test seeds;
plus anything storage or the objective reports.
pub fn reevaluate_with(
&self,
objective: &dyn Objective,
top_k: usize,
) -> Result<FinalReport>
pub fn reevaluate_with( &self, objective: &dyn Objective, top_k: usize, ) -> Result<FinalReport>
reevaluate for an Objective that is not a
closure (the Subprocess executor, a boxed trait object).
§Errors
As reevaluate.
pub fn evaluate_checkpoints<F>(
&self,
objective: F,
top_k: usize,
) -> Result<FinalReport>
pub fn evaluate_checkpoints<F>( &self, objective: F, top_k: usize, ) -> Result<FinalReport>
The checkpoint estimand: evaluate each of the top_k tune-ranked
trials’ latest checkpoint under the held-out test seeds, without
training.
The candidates, test seeds, eligibility gate, ranking, and report are
exactly reevaluate’s. Each replicate’s context
reports Evaluation::Checkpoint from
TrialCtx::evaluation, carries the trial’s latest checkpoint as
parent_checkpoint and its step as
parent_step, and suggest returns the
trial’s recorded configuration; a Subprocess child
sees ATUNE_EVALUATE=checkpoint and ATUNE_CHECKPOINT. The objective
loads that checkpoint and evaluates it; it must not train. A trial that
recorded no checkpoint is an ineligible entry (test_value NaN) and the
objective does not run for it.
The replicates are stored in the trial’s atune.estimate record
(StudySnapshot::estimate) beside the checkpoint they evaluated; the
trial’s values and its fan’s test replicates are untouched. A stored
set is reused under the same conditions as
reevaluate’s, and only when it evaluated the
same checkpoint step and reference; otherwise it is rerun and this
estimand’s record replaced.
§Errors
As reevaluate.
pub fn evaluate_checkpoints_with(
&self,
objective: &dyn Objective,
top_k: usize,
) -> Result<FinalReport>
pub fn evaluate_checkpoints_with( &self, objective: &dyn Objective, top_k: usize, ) -> Result<FinalReport>
pub fn replay_recipes<F>(
&self,
objective: F,
top_k: usize,
) -> Result<FinalReport>
pub fn replay_recipes<F>( &self, objective: F, top_k: usize, ) -> Result<FinalReport>
The recipe estimand: train each of the top_k tune-ranked trials
afresh following its recipe under the held-out test seeds.
The candidates, test seeds, eligibility gate, ranking, and report are
exactly reevaluate’s. Each replicate’s context
reports Evaluation::Recipe from TrialCtx::evaluation and the
trial’s Recipe from TrialCtx::recipe; suggest returns the
recipe’s first-segment configuration, the one training starts with,
and there is no parent checkpoint. The objective trains from scratch
and switches configuration at each segment boundary
(Recipe::params_at). A Subprocess child sees
ATUNE_EVALUATE=recipe and the recipe as compact JSON in
ATUNE_RECIPE. The result is reported as measured; it is not assumed
to equal the checkpoint’s.
The replicates are stored in the trial’s atune.estimate record
beside the recipe’s exact JSON; a stored set is reused only for the
same recipe bytes and otherwise rerun, as for
evaluate_checkpoints.
§Errors
As reevaluate, plus the
recipe error (Error::Lineage or
Error::NotFound) of any candidate, which fails the whole call before
any objective runs.
pub fn replay_recipes_with(
&self,
objective: &dyn Objective,
top_k: usize,
) -> Result<FinalReport>
pub fn replay_recipes_with( &self, objective: &dyn Objective, top_k: usize, ) -> Result<FinalReport>
§impl Study
impl Study
pub fn retry_record(&self, trial: TrialId) -> Result<Option<RetryRecord>>
pub fn retry_record(&self, trial: TrialId) -> Result<Option<RetryRecord>>
What a trial records about being a retry, if it is one.
§Errors
Error::Storage if the blob cannot be
decoded or the backend faults.
pub fn enqueue(&self, template: TrialTemplate) -> Result<()>
pub fn enqueue(&self, template: TrialTemplate) -> Result<()>
Queues a trial to be created with something already decided.
The enqueued parameters outrank every sampler: they are step 2 of the
priority chain documented on TrialCtx. Templates are consumed in
FIFO order by ask; once the queue is empty, trials are
sampled normally again. An ask that takes a template but then fails
before the trial is running puts it back at the front of the queue,
so an explicitly requested configuration is never lost.
§Determinism: an enqueued study is replayable, not pre-determined
Trial-number-indexed
determinism
holds for a stateless sampler with an empty template
queue. Enqueuing makes the number-to-template mapping order-dependent:
which trial number a template lands on is decided by which
ask claims it, and under
parallelism(n) with n > 1
that is
thread scheduling. The values a template fixes are of course exactly
what was enqueued; it is the pairing with a trial number that floats.
Such a study is replayable (its history explains itself) rather than
pre-determined (computable from the seed before it runs).
§Errors
Error::Storage if the internal queue
lock is poisoned.
§impl Study
impl Study
pub fn tell(&self, ctx: TrialCtx<'_>, outcome: Outcome) -> Result<()>
pub fn tell(&self, ctx: TrialCtx<'_>, outcome: Outcome) -> Result<()>
Completes a trial with its objective values.
Values and the terminal state are committed atomically under the context’s owner/epoch fence. A stale worker therefore cannot complete a trial after another worker has reclaimed it.
This and the other terminal tell_* methods consume the context, so
the handle of a finished or suspended trial cannot stay usable.
§Errors
Error::Conflict if the value count does not match the study’s
directions or the trial has already finished; Error::Scheduler if the
scheduler asks to resume a trial that is not paused; plus anything storage
or a stateful seam reports.
pub fn tell_pruned(&self, ctx: TrialCtx<'_>) -> Result<()>
pub fn tell_pruned(&self, ctx: TrialCtx<'_>) -> Result<()>
Records a trial as pruned.
No values are written. A pruned trial’s objective value is its
last intermediate report
(FrozenTrial::objective_value),
and writing a copy of it would only create a second source of truth
that could disagree. A trial pruned before its first report therefore
has no objective value at all, which is the honest answer.
§Errors
As tell, minus the value-count conflict.
pub fn tell_failed(&self, ctx: TrialCtx<'_>, message: &str) -> Result<()>
pub fn tell_failed(&self, ctx: TrialCtx<'_>, message: &str) -> Result<()>
Records a trial as failed, keeping message.
The error text and terminal state are committed atomically under the
context’s owner/epoch fence. The record, its parameters and the message
all survive; only the sampler’s view of the trial does not
(StudyView::visible filters failures out).
§Errors
As tell.
pub fn tell_paused(&self, ctx: TrialCtx<'_>) -> Result<()>
pub fn tell_paused(&self, ctx: TrialCtx<'_>) -> Result<()>
Suspends a trial, keeping its checkpoint — the execution of
Decision::Pause.
The trial moves Running -> Paused, a non-terminal state: no values
are written, its recorded checkpoint reference is left in place, and it
can be woken later with
Command::ResumeTrial. Because
a paused trial is not finished, the sampler’s
after_trial and the scheduler’s
on_trial_end — both defined
as terminal-state callbacks — do not run here; the seam state that a
pause decision may have mutated (in
on_report) is persisted so a
resume across a reopen is exact.
§Errors
When another worker of this handle is inside a sampler or scheduler call, the pause commits without that snapshot: the seams are not touched, and the next owned finalization persists their current state.
Error::Storage on a backend fault; anything a stateful sampler’s or
scheduler’s state reports while its
blob is persisted.