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
Int
An integer range.
Fields
high: i64Inclusive upper bound. When step exceeds 1, the constructors
snap this down to the last grid point, exactly as for
Distribution::Float.
Cat
A categorical choice.
Fields
choices: CatChoicesThe 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
impl Distribution
pub fn float(low: f64, high: f64) -> Result<Self>
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>
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>
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>
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_log(low: i64, high: i64) -> Result<Self>
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>
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>
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
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>
pub fn cat_labels<I, S>(labels: I) -> Result<Self>
A categorical distribution over labels.
§Errors
Error::InvalidSpace if the labels are empty or contain duplicates.
pub const fn boolean() -> Self
pub const fn boolean() -> Self
A boolean distribution.
pub const fn value_kind(&self) -> ValueKind
pub const fn value_kind(&self) -> ValueKind
The kind of ParamValue this distribution produces.
pub fn is_expansion_of(&self, seed: &Self) -> bool
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 fn is_single_valued(&self) -> bool
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<()>
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
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>
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>>
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
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::Catstores 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 notpub fn to_unit(&self, value: &ParamValue) -> Result<f64>
pub fn to_unit(&self, value: &ParamValue) -> Result<f64>
Maps a value to its unit coordinate in [0, 1].
| Kind | Formula |
|---|---|
| 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 |
| boolean | as 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
Error::InvalidSpaceif the distribution is malformed;Error::OutOfRangeif the value has the wrong kind or lies outside the bounds.
pub fn from_unit(&self, u: f64) -> Result<ParamValue>
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
Error::InvalidSpaceif the distribution is malformed;Error::OutOfRangeifuis NaN (there is nothing to clamp it to).
Trait Implementations§
§impl Clone for Distribution
impl Clone for Distribution
§fn clone(&self) -> Distribution
fn clone(&self) -> Distribution
1.0.0 (const: unstable) · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source. Read more§impl Debug for Distribution
impl Debug for Distribution
§impl<'de> Deserialize<'de> for Distribution
impl<'de> Deserialize<'de> for Distribution
§fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where
__D: Deserializer<'de>,
fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where
__D: Deserializer<'de>,
§impl Display for Distribution
impl Display for Distribution
§fn fmt(&self, f: &mut Formatter<'_>) -> Result
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.