Skip to main content

Random

Struct Random 

pub struct Random;
Expand description

Samples every parameter independently and uniformly over its support.

The baseline sampler, and the one every other sampler is compared against. It is stateless: it holds no fields, persists nothing, and learns nothing from history.

§Determinism

Each value is a pure function of (trial.sampler_seed, parameter name, distribution) — the determinism contract. Concretely, one draw is

rng   = ChaCha8Rng::seed_from_u64(seed_for_name(trial.sampler_seed, name))
value = distribution.from_unit(<one uniform coordinate from rng>)

and trial.sampler_seed is itself seed_for(study_seed, trial_number, Stream::Sampler). Three properties follow, and all three are asserted by this module’s tests:

  1. The assignment of trial number n depends only on (study seed, n, space) — not on wall clock, thread interleaving, completion order or worker count. A 16-thread study evaluates exactly the configuration set a single-threaded one does.
  2. A parameter’s value is stable under space edits. Because nothing is drawn sequentially, adding, removing or reordering a parameter leaves every other parameter of every trial untouched.
  3. sample_relative and sample_independent agree. They run the identical per-name derivation, so a parameter that migrates between the relative space and the independent fallback keeps its value.

§Uniformity

Sampling happens in unit space and goes through Distribution::from_unit, so log scaling, step snapping and categorical mapping have exactly one implementation. What “uniform” means depends on how from_unit reads the coordinate, so there are two draws, chosen by whether that map is affine in u:

DistributionDrawUniform over
Cat, Boolcell index k, coordinate (k + 0.5) / nthe choices
Int { log: false, .. }cell indexthe integers of the grid
Float { step: Some(_), log: false }cell indexthe grid points
Int { log: true, .. }raw coordinate in [0, 1)ln(value), not the integers
Float { step: None, .. } (linear or log)raw coordinate in [0, 1)the value / ln(value)

The cell-index draw is the better choice wherever it is correct: a raw coordinate would give the two endpoints half the weight of every interior point, because from_unit rounds to the nearest cell. It is only correct when the coordinate maps affinely onto the grid, which is exactly the non-logarithmic case. A logarithmic Int reports a cardinality — it counts the linear step grid — but from_unit maps the coordinate exponentially, so cell centres do not land on value cells: the draw would be neither uniform in the integers nor uniform in the log, and the top of the range would be unreachable. It therefore takes the raw coordinate, which is what a log-scaled distribution means and what reaches both declared bounds.

Consequently a log-scaled draw is not uniform over its integers: small values are deliberately favoured, which is the entire point of asking for a log scale. Both declared bounds are reachable in every case.

Either way the result satisfies Distribution::contains, which the tests sweep over every distribution kind.

use atune_core::id::{TrialId, TrialNumber};
use atune_core::sampler::{Random, Sampler};
use atune_core::space::{Distribution, ParamSpec, SpaceSchema};
use atune_core::study::StudyView;
use atune_core::trial::TrialMeta;

let space = SpaceSchema::new([
    ParamSpec::new("lr", Distribution::float_log(1e-5, 1e-1).unwrap()).unwrap(),
    ParamSpec::new("layers", Distribution::int(1, 8).unwrap()).unwrap(),
])
.unwrap();

let sampler = Random::new();
let view = StudyView::new(atune_core::id::StudyId::new(0), vec![], Some(space.clone()));
let trial = TrialMeta::derive(TrialId::new(1), TrialNumber::new(7), 42);

let params = sampler.sample_relative(&view, &trial, &space).unwrap();
let lr = params.get("lr").unwrap();

// The independent fallback reproduces the joint draw exactly.
let alone = sampler
    .sample_independent(&view, &trial, "lr", space.distribution("lr").unwrap())
    .unwrap();
assert_eq!(&alone, lr);

Implementations§

§

impl Random

pub const fn new() -> Self

Creates the sampler.

pub fn draw( &self, base: u64, name: &str, dist: &Distribution, ) -> Result<ParamValue>

Draws one parameter from base — the whole sampler in one function.

base is the trial’s sampler_seed. Exposed because it is the contract: any sampler that wants “the uniform draw this parameter would have got” (a fallback, a restart, a comparison) must produce the same bits, and the only way to guarantee that is to call this.

§Errors

Error::InvalidSpace if the distribution is malformed — propagated verbatim from Distribution::from_unit.

Trait Implementations§

§

impl Clone for Random

§

fn clone(&self) -> Random

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 Copy for Random

§

impl Debug for Random

§

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

Formats the value using the given formatter. Read more
§

impl Default for Random

§

fn default() -> Random

Returns the “default value” for a type. Read more
§

impl Eq for Random

§

impl PartialEq for Random

§

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

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

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

Inequality operator !=. Read more
§

impl Sampler for Random

§

fn infer_relative_space(&self, study: &StudyView) -> Result<SpaceSchema>

The study’s declared space when it has one; otherwise the intersection of the visible trials’ parameter sets.

§Define-by-run semantics

This is Optuna’s IntersectionSearchSpace, made explicit:

  • only visible trials count (Complete and Pruned) — a crashed trial must not teach the sampler anything, not even which parameters exist;
  • a name survives only if every visible trial recorded it and every recording is compatible with the first one. Numeric bounds may therefore drift between trials; a changed categorical choice set, log flag or kind drops the parameter from the joint space, and it is then sampled through sample_independent instead;
  • the surviving distribution is the first visible trial’s;
  • with no visible trials the result is the empty schema, which means “sample everything independently”.

The result is ordered by name: an inferred space has no declaration order to preserve, and a name order is the only order that does not depend on which trial happened to complete first.

§Errors

Error::InvalidSpace if a recorded parameter name is empty, which no writer of this crate produces.

§

fn sample_relative( &self, _study: &StudyView, trial: &TrialMeta, space: &SpaceSchema, ) -> Result<Assignment>

Draws every parameter of space, each independently of the others.

The returned assignment binds exactly the names space declares.

§Errors

Error::InvalidSpace if a declared distribution is malformed.

§

fn sample_independent( &self, _study: &StudyView, trial: &TrialMeta, name: &str, dist: &Distribution, ) -> Result<ParamValue>

Draws exactly one parameter.

Returns bit-for-bit what sample_relative would have bound to name, because both call Random::draw.

§Errors

Error::InvalidSpace if dist is malformed.

§

fn reseed(&self, _seed: u64)

A no-op — and deliberately so.

Sampler::reseed exists for samplers that carry auxiliary randomness which two parallel workers must not draw in lockstep. Random has none: every value it produces is a pure function of (trial.sampler_seed, name, distribution), and sampler_seed is derived by the framework from the study seed and the trial number.

What this call therefore does not affect:

  • any value sampled for any trial, before or after the call;
  • the reproducibility of a study — which is the point. If reseeding moved the draws, a worker calling it would silently break promise 2 and two workers would disagree about what trial n is.

To change what a study samples, change the study seed (StudyConfig::with_seed); it is configuration, not runtime state.

§

fn after_trial(&self, study: &StudyView, trial: &FrozenTrial) -> Result<()>

Called once a trial reaches a terminal state. Read more
§

fn state(&self) -> Result<Option<SamplerState>>

The sampler’s persistable state, if it has any. Read more
§

fn snapshots_space(&self) -> bool

Whether this sampler enumerates a snapshot of the space it was constructed with, rather than the space each ask presents. Read more
§

fn restore_state(&self, blob: &SamplerState) -> Result<()>

Restores a sampler from a previously persisted blob. Read more
§

impl StructuralPartialEq for Random

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> 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