Skip to main content

Distribution

Enum Distribution 

pub enum Distribution {
    Float {
        low: f64,
        high: f64,
        log: bool,
        step: Option<f64>,
    },
    Int {
        low: i64,
        high: i64,
        log: bool,
        step: i64,
    },
    Cat {
        choices: CatChoices,
    },
    Bool,
}
Expand description

The support of one parameter.

§Validity

The variants carry public fields so that the shape matches the frozen seam and pattern matching stays natural. That means a caller can build a nonsensical distribution by hand. Every method on this type therefore tolerates an invalid value: transforms return Error::InvalidSpace, and the predicate-style methods return false / None. Nothing panics. Prefer the fallible constructors (Distribution::float, Distribution::int, …), which validate up front; deserialization validates too.

§Stepped ranges are normalized

The constructors and the deserializer snap a stepped range’s high down onto the step grid, because a high that is not reachable from low in whole steps has no consistent meaning: contains, cardinality, grid_values and from_unit would each have to guess. So Distribution::float_step(0.0, 11.0, 3.0) stores high == 9.0, and the effective bounds are always the ones the distribution reports. A high that reconstructs to the nearest grid point within the bounded float-rounding allowance is kept bit-for-bit: Distribution::float_step(0.0, 0.7, 0.1) keeps 0.7 and has eight grid points, even though 0.7 / 0.1 is not exact in binary.

A hand-built variant skips that normalization, so its high may sit off the grid. The methods stay consistent regardless — they derive the grid from low and step, never from an unreachable high.

Variants§

§

Float

A floating-point range.

Fields

§low: f64

Inclusive lower bound.

§high: f64

Inclusive upper bound. When step is set, the constructors snap this down to the last grid point (see the type-level Stepped ranges are normalized); it is then always attainable.

§log: bool

Sample uniformly in ln(value) instead of value.

§step: Option<f64>

Discretization step, measured from low. None is continuous.

§

Int

An integer range.

Fields

§low: i64

Inclusive lower bound.

§high: i64

Inclusive upper bound. When step exceeds 1, the constructors snap this down to the last grid point, exactly as for Distribution::Float.

§log: bool

Sample uniformly in ln(value) instead of value.

§step: i64

Discretization step, measured from low; at least 1, and exactly 1 when log is set.

§

Cat

A categorical choice.

Fields

§choices: CatChoices

The stable labels; ParamValue::Cat indexes them.

§

Bool

A boolean, treated throughout as a two-choice categorical (false = index 0, true = index 1).

Implementations§

§

impl Distribution

pub fn float(low: f64, high: f64) -> Result<Self>

A continuous linear float range.

§Errors

Error::InvalidSpace if a bound is not finite or low > high.

pub fn float_log(low: f64, high: f64) -> Result<Self>

A continuous logarithmic float range.

§Errors

As Distribution::float, plus low must be strictly positive.

pub fn float_step(low: f64, high: f64, step: f64) -> Result<Self>

A linear float range discretized to a step grid anchored at low.

⚠️ high is snapped down onto the grid when it is not reachable from low in whole steps — see Distribution::new_float.

use atune_core::space::Distribution;

// 11.0 is not 0.0 + k * 3.0 for any whole k: the bound moves to 9.0.
let d = Distribution::float_step(0.0, 11.0, 3.0).unwrap();
assert!(matches!(d, Distribution::Float { high, .. } if high == 9.0));
assert_eq!(d.cardinality(), Some(4)); // 0, 3, 6, 9

// 0.7 reconstructs to the nearest grid point, so it is kept.
let e = Distribution::float_step(0.0, 0.7, 0.1).unwrap();
assert!(matches!(e, Distribution::Float { high, .. } if high == 0.7));
assert_eq!(e.cardinality(), Some(8));
§Errors

As Distribution::float, plus step must be finite and positive.

pub fn new_float( low: f64, high: f64, log: bool, step: Option<f64>, ) -> Result<Self>

The general float constructor.

§Normalization

With a step, the stored high is the last grid point rather than the declared bound. (high - low) / step is computed; if the declared high matches the reconstructed nearest grid point within the bounded float-rounding allowance used by contains, high is kept verbatim. Otherwise the cell count is floored and high is rewritten to low + n * step. This matches Optuna, which likewise adjusts high for stepped distributions, and it is what makes contains, cardinality, grid_values and from_unit agree: the support has exactly n + 1 points, the last of which is the reported high.

§Errors

Error::InvalidSpace if a bound or the step is not finite, low > high, step <= 0, log with low <= 0, or log combined with a step (a logarithmic and uniformly stepped grid is ill-defined; Optuna rejects it too).

pub fn int(low: i64, high: i64) -> Result<Self>

A linear integer range with unit step.

§Errors

Error::InvalidSpace if low > high.

pub fn int_log(low: i64, high: i64) -> Result<Self>

A logarithmic integer range (implies unit step).

§Errors

As Distribution::int, plus low must be strictly positive.

pub fn int_step(low: i64, high: i64, step: i64) -> Result<Self>

A linear integer range discretized to a step grid anchored at low.

⚠️ high is snapped down onto the grid when it is not reachable from low in whole steps — see Distribution::new_int.

use atune_core::space::Distribution;

let d = Distribution::int_step(0, 10, 3).unwrap();
assert!(matches!(d, Distribution::Int { high, .. } if high == 9));
assert_eq!(d.cardinality(), Some(4)); // 0, 3, 6, 9
§Errors

As Distribution::int, plus step must be at least 1.

pub fn new_int(low: i64, high: i64, log: bool, step: i64) -> Result<Self>

The general integer constructor.

§Normalization

As Distribution::new_float: with step > 1 the stored high becomes low + n * step where n = (high - low) / step (integer division), so the reported upper bound is always attainable. Integer arithmetic is exact, so no tolerance is involved.

§Errors

Error::InvalidSpace if low > high, step < 1, log with low <= 0, or log with step != 1.

pub const fn cat(choices: CatChoices) -> Self

A categorical distribution over an already-validated choice set.

pub fn cat_labels<I, S>(labels: I) -> Result<Self>
where I: IntoIterator<Item = S>, S: Into<String>,

A categorical distribution over labels.

§Errors

Error::InvalidSpace if the labels are empty or contain duplicates.

pub const fn boolean() -> Self

A boolean distribution.

pub const fn value_kind(&self) -> ValueKind

The kind of ParamValue this distribution produces.

pub fn is_expansion_of(&self, seed: &Self) -> bool

true when self could have been produced by growing seed — same kind, same log flag, same step/choice structure, bounds a superset, and (for a stepped support) a grid still anchored so that every point seed supported remains supported (§8.7’s invariance).

A predicate, deliberately not on the suggest hot path (§11.3 deleted it from there): the monotonicity property test and atune doctor are its consumers.

pub const fn is_log(&self) -> bool

true if log scaling is in effect.

pub fn is_single_valued(&self) -> bool

true if the support holds exactly one value.

Single-valued parameters skip sampling entirely: the study loop assigns the only legal value — the enqueued-fixed → single-valued → relative → independent priority.

pub fn validate(&self) -> Result<()>

Checks the distribution’s own invariants.

§Errors

Error::InvalidSpace describing the first violated invariant. See the constructors for the full list.

pub fn contains(&self, value: &ParamValue) -> bool

true if value lies in this distribution’s support.

For a stepped distribution the value must also sit on the grid (within the bounded reconstructed float-rounding allowance). An invalid distribution contains nothing.

One float grid cannot be indexed at all: when (v - low) / step overflows to infinity — a subnormal step — the grid is finer than f64 can address, and every in-range value counts as on it. That is the same reading cardinality (which saturates) and from_unit (which skips the snap) already take, so the four views keep describing one grid.

pub fn cardinality(&self) -> Option<u64>

The number of distinct values, or None for a continuous float.

None means “unbounded resolution”, nothing else: every other distribution — including a stepped float — is finite and reports its count. An invalid distribution reports None. Counts saturate at u64::MAX: a grid so fine that its cell count overflows is reported as the largest representable count, never as the smallest.

pub fn grid_values(&self) -> Option<Vec<ParamValue>>

Every value of a finite distribution, in ascending order.

None when cardinality is None (a continuous float, or an invalid distribution), or when the grid exceeds MAX_GRID_POINTS. This is the primitive the Grid sampler enumerates.

The last element is bit-for-bit the reported high (for a stepped range that is the normalized, attainable bound — see the type-level Stepped ranges are normalized), so a caller may compare against the bound exactly rather than within a tolerance.

§Allocation

The returned vector has cardinality elements, which is why the MAX_GRID_POINTS ceiling exists: an unbounded range such as Distribution::int(0, i64::MAX) returns None rather than attempting — and failing — to allocate its grid. Nothing here can panic or exhaust memory.

pub fn is_compatible_with(&self, other: &Self) -> bool

Whether two distributions may describe the same parameter of one study.

This is Optuna’s compatibility gate, made explicit (see when a space is wrong):

  • the kind must match — a parameter never changes from float to categorical;
  • the log flag must match — the meaning of a stored unit coordinate would otherwise change;
  • categorical choice sets must be identical, because ParamValue::Cat stores an index and reordering the labels would silently rewrite history;
  • numeric bounds (and steps) may drift, because a define-by-run objective is allowed to widen or narrow a range between trials. Old values stay valid; they simply may fall outside the new support.
use atune_core::space::Distribution;

let a = Distribution::float(0.0, 1.0).unwrap();
let b = Distribution::float(0.0, 2.0).unwrap();
assert!(a.is_compatible_with(&b));                       // bounds may drift
assert!(!a.is_compatible_with(&Distribution::float_log(1.0, 2.0).unwrap()));

let x = Distribution::cat_labels(["adam", "sgd"]).unwrap();
let y = Distribution::cat_labels(["adam", "sgd", "rmsprop"]).unwrap();
assert!(!x.is_compatible_with(&y));                      // choice sets may not

pub fn to_unit(&self, value: &ParamValue) -> Result<f64>

Maps a value to its unit coordinate in [0, 1].

KindFormula
linear float / int(v - low) / (high - low)
logarithmic float / int(ln v - ln low) / (ln high - ln low)
categorical (n choices)(i + 0.5) / n — the cell centre
booleanas a two-choice categorical: 0.25 / 0.75

A degenerate numeric support (low == high) maps to 0.0 instead of dividing by zero. Categoricals have no such special case: a one-choice categorical (and likewise a one-choice Cat-shaped support) maps to its cell centre, 0.5. Step grids do not affect this direction: an in-range value that is off the grid still gets its exact coordinate.

§Errors

pub fn from_unit(&self, u: f64) -> Result<ParamValue>

Maps a unit coordinate back to a value in the support.

The inverse of to_unit, and total by construction: u is clamped to [0, 1], the result is snapped to the nearest step-grid cell with the cell index clamped to the grid, and integers are rounded to nearest. It never panics, and d.contains(&d.from_unit(u).unwrap()) holds for every u — the result is always a point of the support, not merely a value inside the bounds.

Categorical coordinates use floor(u * n), clamped to n - 1, which is the exact inverse of the cell-centre mapping used by to_unit.

§Errors

Trait Implementations§

§

impl Clone for Distribution

§

fn clone(&self) -> Distribution

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 Distribution

§

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

Formats the value using the given formatter. Read more
§

impl<'de> Deserialize<'de> for Distribution

§

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

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

impl Display for Distribution

§

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

Renders the distribution in the string DSL.

The output re-parses to an equal distribution for every valid distribution (see the module documentation). A hand-built invalid distribution renders best-effort and is not promised to re-parse.

§

impl PartialEq for Distribution

§

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

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

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

Inequality operator !=. Read more
§

impl Serialize for Distribution

§

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 Distribution

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> ToString for T
where T: Display + ?Sized,

Source§

fn to_string(&self) -> String

Converts the given value to a String. 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