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::try_new_with_float, Distribution::try_new_with_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::try_new_with_stepped_float(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::try_new_with_stepped_float(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 try_new_with_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 try_new_with_log_float(low: f64, high: f64) -> Result<Self>

A continuous logarithmic float range.

§Errors

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

pub fn try_new_with_stepped_float( 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::try_new_with_float_options.

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::try_new_with_stepped_float(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::try_new_with_stepped_float(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::try_new_with_float, plus step must be finite and positive.

pub fn try_new_with_float_options( low: f64, high: f64, scale: Scale, 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, Scale::Log with low <= 0, or logarithmic scale combined with a step (a logarithmic and uniformly stepped grid is ill-defined; Optuna rejects it too).

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

A linear integer range with unit step.

§Errors

Error::InvalidSpace if low > high.

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

A logarithmic integer range (implies unit step).

§Errors

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

pub fn try_new_with_stepped_int(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::try_new_with_int_options.

use atune_core::space::Distribution;

let d = Distribution::try_new_with_stepped_int(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::try_new_with_int, plus step must be at least 1.

pub fn try_new_with_int_options( low: i64, high: i64, scale: Scale, step: i64, ) -> Result<Self>

The general integer constructor.

§Normalization

As Distribution::try_new_with_float_options: 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, logarithmic scale with low <= 0, or logarithmic scale with step != 1.

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

A categorical distribution over an already-validated choice set.

pub fn try_new_with_categorical_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 new_with_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.

A predicate, deliberately kept off the suggest hot path: the growth tests are its consumers, the monotonicity property test growth_keeps_every_seed_value_supported (space/growth/tests.rs, N65) among them.

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::try_new_with_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::try_new_with_float(0.0, 1.0).unwrap();
let b = Distribution::try_new_with_float(0.0, 2.0).unwrap();
assert!(a.is_compatible_with(&b));                       // bounds may drift
assert!(!a.is_compatible_with(&Distribution::try_new_with_log_float(1.0, 2.0).unwrap()));

let x = Distribution::try_new_with_categorical_labels(["adam", "sgd"]).unwrap();
let y = Distribution::try_new_with_categorical_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.

Numeric bounds come back exactly: u = 0 gives low and u = 1 gives high (or the last grid point below it), on a logarithmic scale too, where exp(ln x) is not the identity in f64. Interior round trips, from_unit(to_unit(v)), are exact up to f64 rounding, which for a logarithmic integer is about v · ln v · 2^-52. Once that passes half a step, from about 2^46 for unit steps, the round trip can return a neighbouring integer; the result is still a point of the support.

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 a human-readable representation in the string DSL.

For distributions produced by parse, the output re-parses to an equal distribution (see the module documentation). This is not lossless serialization for arbitrary hand-built distributions: choice syntax cannot represent empty labels, commas, or leading or trailing whitespace, and omits typed choice payloads. Quotes and backslashes are ordinary label characters, not escape syntax. Use serialization that preserves payloads when those must be retained.

§

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