Struct StudyBuilder
pub struct StudyBuilder { /* private fields */ }Expand description
Builds a Study, injecting the handles it drives.
Every seam is injected and every default is stated: a study never constructs a clock, reaches for the system time, or picks a storage backend on the caller’s behalf.
| Knob | Default |
|---|---|
| storage | MemStorage — in memory, nothing survives the process |
| sampler | Random |
| scheduler | NopScheduler — nothing is ever pruned |
| clock | SystemClock with the system feature, otherwise a ManualClock frozen at 0 |
| observer | none (structured tracing events are still emitted) |
| worker | WorkerId(0) |
| budget | Budget::new |
| parallelism | 1 |
| retry policy | RetryPolicy::new |
use atune_core::clock::ManualClock;
use atune_core::sampler::Grid;
use atune_core::space::{Distribution, ParamSpec, SpaceSchema};
use atune_core::study::{Study, StudyConfig};
use std::sync::Arc;
let space = SpaceSchema::try_new([
ParamSpec::try_new("opt", Distribution::try_new_with_categorical_labels(["adam", "sgd"])?)?,
])?;
let study = Study::builder()
.sampler(Arc::new(Grid::try_new(space.clone())?))
.clock(Arc::new(ManualClock::new(0)))
.create(StudyConfig::new("grid").with_space(space))?;
assert_eq!(study.trial_count()?, 0);Implementations§
§impl StudyBuilder
impl StudyBuilder
pub const fn new() -> Self
pub const fn new() -> Self
Starts a builder with every seam at its default.
pub const fn target_value(self, value: f64) -> Self
pub const fn target_value(self, value: f64) -> Self
Stops this handle’s search once the study’s best trial reaches
value.
Before each ask the handle reads the canonical
constraint-aware best_trial; when that trial is
feasible (or declares no constraints) and its value is at least as good
as value in the study’s direction, ask returns Ok(None) and
optimize ends normally. A resumed study whose best
already meets the target therefore starts no trial. The check costs one
extra snapshot read per ask, and only when a target is set.
The target belongs to this handle and is not persisted. It needs a
single-objective study and a finite value;
create and load refuse
anything else.
pub fn fan_sync_steps(self, steps: impl IntoIterator<Item = u64>) -> Self
pub fn fan_sync_steps(self, steps: impl IntoIterator<Item = u64>) -> Self
Makes a multi-seed fan rung-synchronous at steps.
A fan normally runs each replicate to completion before the scheduler
sees any aggregate, so a pruner can only cut a fan after all of its
replicates have finished. With sync steps, the study runs every
replicate up to the first step, hands the scheduler the aggregate of
each aligned step reported so far, and only then runs the replicates on
to the next step. A Decision::Prune
or Decision::Pause at a sync step
therefore stops every replicate there. Pass a scheduler’s rung steps,
such as RungLadder::rungs.
A replicate yields on its first report at or past
a sync step: that call returns Error::TrialPaused, and
should_stop does too. The objective must be
resumable: record a checkpoint with
record_checkpoint before reporting,
return the error, and on its next run continue from
parent_checkpoint and
parent_step, which hold that replicate’s own
latest reference. An objective that keeps no checkpoint restarts and
reaches the same reports, at the cost of repeated work.
The steps belong to this handle and are not persisted. Duplicates are
ignored and order does not matter. create and
load refuse a zero step, and any step on a study
whose fan is not wider than one seed.
pub fn storage(self, storage: Arc<dyn Storage>) -> Self
pub fn storage(self, storage: Arc<dyn Storage>) -> Self
Uses storage as the study’s backend.
In-memory by default: a study built without this call runs against a
fresh MemStorage, which is right for a
first program, a test and a study whose results are read before the
process exits — and wrong for anything durable, resumable or shared
between processes. Call this for those.
A backend used for Study-driven trial execution must expose
TrialLifecycleStorage through Storage::trial_lifecycle. The base
Storage trait alone supports direct CRUD and observation, but
Study::ask and Study::optimize return Error::Unsupported when
the atomic lifecycle capability is absent.
Shares template-queue ownership with source for a compatible load.
The queue is the only source state retained. A subsequent
load must name the same study and use the exact same
storage Arc; otherwise it fails before reading or recovering the
backend. This method is intentionally incompatible with
create, which rejects it before creating anything.
Shares finalization admission with source for a compatible load.
This is an explicit companion to share_queue_from
for adapters that rebuild a handle while reusing the same underlying
mutable sampler and scheduler state. Transparent wrappers may delegate
to those seams; this method does not select or verify the seam objects.
It shares only the local finalization runtime, and queue ownership
remains independent unless share_queue_from
is also used. A subsequent load must name the same study
and use the exact same storage Arc; otherwise it fails before reading
or recovering the backend. This method is intentionally incompatible
with create, which rejects it before creating anything.
pub fn scheduler(self, scheduler: Arc<dyn Scheduler>) -> Self
pub fn scheduler(self, scheduler: Arc<dyn Scheduler>) -> Self
Uses scheduler instead of NopScheduler.
pub fn observer(self, observer: Arc<dyn StudyObserver>) -> Self
pub fn observer(self, observer: Arc<dyn StudyObserver>) -> Self
Sends typed lifecycle events to observer.
Delivery is synchronous with each emitting operation; parallel trials may invoke the observer concurrently. Observer failures never become study errors. Implementations should enqueue lightweight notifications and perform blocking work elsewhere.
pub const fn worker(self, worker: WorkerId) -> Self
pub const fn worker(self, worker: WorkerId) -> Self
Names this handle among the study’s workers.
Two workers sharing a study must not share an id: it is what a lease
names as its owner, what
FrozenTrial::worker records, and
therefore what tells a fail-over sweep — and a human reading the study —
whose trial a stale heartbeat belongs to. The identity is readable back
through Study::worker, and every lease this handle claims stamps it
onto the trial record.
It names the handle, not the Storage behind it. A backend that
carries its own worker option
(JournalOptions::worker,
SqliteOptions::worker) stamps that one on the writes it performs
itself — see
FrozenTrial::worker for the full
rule.
pub const fn parallelism(self, n: usize) -> Self
pub const fn parallelism(self, n: usize) -> Self
Evaluates n trials at once.
n <= 1 is sequential and spawns nothing, which is the only form
available on wasm32-unknown-unknown; n > 1 needs the system
feature and is rejected by Study::optimize without it.
pub const fn retry_policy(self, policy: RetryPolicy) -> Self
pub const fn retry_policy(self, policy: RetryPolicy) -> Self
Re-enqueues failed configurations according to policy.
pub const fn seed_protocol(self, protocol: SeedProtocol) -> Self
pub const fn seed_protocol(self, protocol: SeedProtocol) -> Self
Evaluates each trial as a multi-seed fan under protocol.
The default is SeedProtocol::new (single-seed, no fan), which takes
the ordinary one-evaluation-per-trial path. A protocol with a fan width
above one turns a trial into k replicates of the same configuration
under k seeds, aggregated into the sampler-visible objective
(multi-seed
protocol). See
Study::reevaluate for the final held-out-seed stage.
§This applies only when create-ing
The protocol is a persisted property of the study
(StudyConfig::seed_protocol): create writes what you set here into
the config it stores, and the study evaluates trials under it. On
load this setting is ignored — a resumed study
takes the protocol from its stored config, so it can never silently mix a
fanned study’s aggregate objective with fan-free raw draws. Passing a
protocol here and then load-ing is therefore a no-op, not an override.
pub fn create(self, cfg: StudyConfig) -> Result<Study>
pub fn create(self, cfg: StudyConfig) -> Result<Study>
Creates a new study in storage.
§Errors
Error::InvalidSpace if the configuration does not validate;
Error::Conflict if the backend enforces unique names and the name
is taken; Error::Storage on a backend fault.
pub fn load(self, id: StudyId) -> Result<Study>
pub fn load(self, id: StudyId) -> Result<Study>
Loads an existing study — the resume path.
The configuration (and therefore the seed and the
SeedProtocol) comes from
storage, so a second handle reproduces the first one’s sampling and its
per-trial fan: a study created with a k-seed fan resumes as a k-seed
fan even from a plain builder that set no protocol. Any protocol passed to
this builder via seed_protocol is
ignored. Trial numbering continues where storage left it, and the trial
budget counts the trials already in the study.
Writable load also recovers durable finalization outboxes before it
returns. If another owner holds the study-wide finalization claim, a
system build reclaims it only after the complete claim observation
stays unchanged for three seconds of local monotonic time, using
nominal 100 ms polling. Without system, held ownership fails immediately
with backpressure. This recovery grace is independent of the Journal’s
append-lock policy.
§Errors
Error::NotFound if the study does not exist; Error::Backpressure
for same-handle admission or an active durable finalization owner;
Error::Unsupported if the backend lacks finalization ownership or
bounded outbox paging;
Error::ResourceLimit if recovery cannot traverse its outbox within
the backend’s supported bounds; Error::Conflict or
Error::Incompatible for invalid persisted state; Error::LeaseLost
if the exact acquisition observation loses a race; or Error::Storage
on a backend fault.
§impl StudyBuilder
impl StudyBuilder
pub fn policy(self, policy: SpacePolicy) -> Self
pub fn policy(self, policy: SpacePolicy) -> Self
Declares this study’s growth policy — which parameters are open, and how.
Create-time plumbing, exactly as sampler is:
create persists one
Scope::Named slot per named
parameter, and load ignores this field — a
resumed handle reads the stored slots on demand, so the storage is the
single source of truth. StudyConfig is
untouched.
Every Open is validated at
create, and a policy naming a declared
parameter must seed exactly the declared distribution — the range has
one home, not two.