Skip to main content

FrozenTrial

Struct FrozenTrial 

pub struct FrozenTrial {
Show 18 fields pub id: TrialId, pub number: TrialNumber, pub study: StudyId, pub state: TrialState, pub params: Assignment, pub distributions: BTreeMap<String, Distribution>, pub values: Option<Vec<f64>>, pub intermediates: Vec<(u64, Vec<f64>)>, pub created_at_millis: u64, pub finished_at_millis: Option<u64>, pub heartbeat_millis: Option<u64>, pub retries: u32, pub error: Option<String>, pub worker: Option<WorkerId>, pub parent: Option<TrialId>, pub replicates: Vec<ReplicateResult>, pub constraints: Vec<f64>, pub user_attrs: UserAttrs,
}
Expand description

The immutable snapshot of a trial.

This is what storage hands to samplers, schedulers and the study view. It is a value: cloning one and holding it while other workers advance the study is safe and is the intended usage.

Objective values are a Vec<f64> from day one — multi-objective is not a later bolt-on — and neither are intermediate values, which are keyed by the study’s declared ResourceUnit.

Fields§

§id: TrialId

Storage-global identity.

§number: TrialNumber

Per-study ordinal; the determinism key (see crate::id).

§study: StudyId

The owning study.

§state: TrialState

Life-cycle state.

§params: Assignment

The sampled parameters, sorted by name.

§distributions: BTreeMap<String, Distribution>

The distribution each parameter was sampled from, as recorded at sampling time. Kept per trial because a define-by-run space may drift between trials.

§values: Option<Vec<f64>>

The final objective values, once known.

§intermediates: Vec<(u64, Vec<f64>)>

Intermediate reports as (step, values), in report order.

§created_at_millis: u64

Creation time, from the study’s Clock.

§finished_at_millis: Option<u64>

Completion time, once finished.

§heartbeat_millis: Option<u64>

Last heartbeat; used to detect a dead worker.

§retries: u32

How many times this trial has been retried.

Written by the fail-over path, not by the study loop. When a policy requeues a trial it took from a dead worker, the ReplacementSpec handed to the finalization outbox carries the failed trial’s count plus one, and the backend stamps that onto the replacement it creates. A trial nobody failed over therefore reads 0, and all backends agree.

This is only the count. Which trial replaced which, and why, is recorded by the study loop as typed state — see RetryRecord.

§error: Option<String>

Why the trial failed, if it did. Preserved for failed trials on purpose. Written through Storage::set_error.

§worker: Option<WorkerId>

The worker that owns (or last owned) the trial.

Ownership moves with the transition into Running. Whoever starts a trial — or restarts one after a fail-over — is stamped here, and a worker that merely adjudicates somebody else’s stale trial is not, which is what stops a sweeping process from being recorded as the owner of every trial it failed over. TrialLifecycleStorage::claim stamps the claimant for the same reason. A backend that has a worker identity also stamps it at creation, so a freshly created trial names the process that created it rather than reading None.

It is not a cross-backend invariant, because that identity belongs to the handle rather than to the history: JournalStorage takes it from JournalOptions::worker and atune::storage::SqliteStorage from SqliteOptions::worker, while MemStorage has no identity at all — every handle to it is the same process — so it stamps only the claim, and a trial it created but never leased stays None. One sequence of operations replayed against two backends may therefore differ here legitimately, which is why the differential suites blank the field before comparing records.

§parent: Option<TrialId>

The trial this one was forked from, copied verbatim from the TrialTemplate it was created with.

Purely descriptive in M1: nothing samples from it, filters on it or follows it, because no M1 scheduler produces a fork. It exists now because PBT-style lineage (M4) has to be recorded when the child is created, and the journal format (M2) would otherwise have to change to grow the field later.

§replicates: Vec<ReplicateResult>

The per-seed sub-results of a multi-seed fan.

A trial run under a SeedProtocol with a fan width above one evaluates the same configuration under several seeds; each replicate carries its index, its seed and its own objective value(s). The trial-level values is the Aggregate over these — the one number the sampler ranks — while the per-seed values are kept here and never lost to aggregation.

The authoritative fan lives in a per-trial state blob — the frozen Storage trait has no method to write it onto the record, so the study loop persists it with put_state on the independently keyed fan_scope (blob kind FAN_BLOB_KIND). This field is therefore empty on a snapshot read straight from storage — the sampler’s hot path pays nothing for it and needs only the aggregate — and is filled on demand by the fan-aware readers on Study (see Study::trial_fan). It is data, not the trait: it round-trips through serde and defaults to empty, so a trial written before the protocol existed reads back as a single implicit replicate (an empty fan).

§constraints: Vec<f64>

The typed constraint values the trial recorded, if any — see Constraints.

≤ 0 is satisfied, > 0 is violated — the convention the whole feasibility-first machinery rests on (pareto::constraint). An objective records them with TrialCtx::record_constraints; Nsga2 ranks with them through constrained_dominates, so a feasible trial outranks an infeasible one whatever their objectives say.

The authoritative values live in a per-trial state blob, exactly as the replicate fan does and for the same reason — the frozen Storage trait has no method to write them onto the record — so this field is empty on a snapshot read straight from storage. The study loop fills it on the finishing trial before after_trial, which is the sampler’s channel, and Study::trial_constraints / Study::pareto_front fill it on demand for a reader. It round-trips through serde and defaults to empty, so a trial written before constraints existed reads back as one that declared none.

§user_attrs: UserAttrs

User-owned JSON metadata, keyed deterministically by string.

The atune: namespace is reserved for generated interoperability metadata and cannot be written through public setters.

Implementations§

§

impl FrozenTrial

pub fn new( id: TrialId, number: TrialNumber, study: StudyId, created_at_millis: u64, ) -> Self

A freshly created, Waiting trial with no parameters.

pub fn last_intermediate(&self) -> Option<&(u64, Vec<f64>)>

The most recent intermediate report, if any.

pub fn objective_value(&self) -> Option<&[f64]>

The value a sampler should treat as this trial’s objective.

StateValue
Completethe recorded values
Prunedthe recorded values if present, else the last intermediate
anything elseNone

The pruned rule is Optuna’s, and it is what makes successive-halving schedulers correct: a trial stopped at rung k still contributes the performance it had reached.

use atune_core::id::{StudyId, TrialId, TrialNumber};
use atune_core::trial::{FrozenTrial, TrialState};

let mut trial = FrozenTrial::new(TrialId::new(1), TrialNumber::new(0), StudyId::new(0), 0);
trial.intermediates.push((10, vec![0.7]));
trial.intermediates.push((20, vec![0.4]));
trial.state = TrialState::Pruned;

assert_eq!(trial.objective_value(), Some(&[0.4][..]));

pub fn declared_constraints(&self) -> Option<&[f64]>

The trial’s constraint values as the pareto comparison layer wants them: None when the trial declared none.

An empty list is not a declaration — see Point::new.

pub fn single_objective_value(&self) -> Option<f64>

The single objective value, for single-objective studies.

None if the trial has no objective value yet, or if it has more than one (use objective_value).

pub const fn is_finished(&self) -> bool

true if the trial is in a terminal state.

pub fn meta(&self, study_seed: u64) -> TrialMeta

The lightweight handle a sampler sees, given the study’s seed.

Trait Implementations§

§

impl Clone for FrozenTrial

§

fn clone(&self) -> FrozenTrial

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
§

impl Debug for FrozenTrial

§

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

Formats the value using the given formatter. Read more
§

impl<'de> Deserialize<'de> for FrozenTrial

§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>
where __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
§

impl PartialEq for FrozenTrial

§

fn eq(&self, other: &FrozenTrial) -> bool

Equality operator ==. Read more
1.0.0 (const: unstable) · Source§

fn ne(&self, other: &Rhs) -> bool

Inequality operator !=. Read more
§

impl Serialize for FrozenTrial

§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>
where __S: Serializer,

Serialize this value into the given Serde serializer. Read more
§

impl StructuralPartialEq for FrozenTrial

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> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> DeserializeOwned for T
where T: for<'de> Deserialize<'de>,

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> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
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