Skip to main content

StudyBuilder

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.

KnobDefault
storageMemStorage — in memory, nothing survives the process
samplerRandom
schedulerNopScheduler — nothing is ever pruned
clockSystemClock with the system feature, otherwise a ManualClock frozen at 0
observernone (structured tracing events are still emitted)
workerWorkerId(0)
budgetBudget::unbounded
parallelism1
retry policyRetryPolicy::none
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::new([
    ParamSpec::new("opt", Distribution::cat_labels(["adam", "sgd"])?)?,
])?;
let study = Study::builder()
    .sampler(Arc::new(Grid::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

pub const fn new() -> Self

Starts a builder with every seam at its default.

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.

pub fn share_queue_from(self, source: &Study) -> Self

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.

pub fn share_finalization_runtime_from(self, source: &Study) -> Self

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 sampler(self, sampler: Arc<dyn Sampler>) -> Self

Uses sampler instead of Random.

pub fn scheduler(self, scheduler: Arc<dyn Scheduler>) -> Self

Uses scheduler instead of NopScheduler.

pub fn clock(self, clock: Arc<dyn Clock>) -> Self

Uses clock as the study’s only source of time.

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

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 budget(self, budget: Budget) -> Self

Limits what the study may spend.

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

Re-enqueues failed configurations according to policy.

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 policy(self, policy: SpacePolicy) -> Self

Declares this study’s growth policy — which parameters are open, and how (§6.4 of the open-search-spaces plan).

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 (§7.1). StudyConfig is untouched.

Every Open is validated at create (§7.3, §7.4, §9.6), and a policy naming a declared parameter must seed exactly the declared distribution — the range has one home, not two.

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>

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.

Trait Implementations§

§

impl Debug for StudyBuilder

§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

The knobs; the seam handles are opaque by design.

§

impl Default for StudyBuilder

§

fn default() -> Self

As new.

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
§

impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
where ST: ?Sized, DT: ?Sized,

§

impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self> ⓘ

Instruments this type with the provided [Span], returning an Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self> ⓘ

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

§

impl<T> Read<Exclusive, BecauseExclusive> for T
where T: ?Sized,

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self> ⓘ
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a [WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self> ⓘ

Attaches the current default Subscriber to this type, returning a [WithDispatch] wrapper. Read more