Skip to main content

Grid

Struct Grid 

pub struct Grid { /* private fields */ }
Expand description

Walks every point of a finite space, once each, in a fixed order.

The sampler for “try all of these”: a small, exhaustively enumerable space where a random search would waste evaluations re-drawing points it has already seen. It is stateless — no visited set, no cursor, nothing persisted — because the grid point is computed from the trial number.

§Which point a trial gets

Trial number n gets grid point n, decomposed in mixed radix over the parameters in schema declaration order, with the last-declared parameter varying fastest (the usual positional-numeral convention, where the first parameter is the most significant digit):

index(p_i) = (n / (card(p_{i+1}) * … * card(p_{m-1}))) % card(p_i)

So a space [a: 3 values, b: 2 values] is walked (a0,b0), (a0,b1), (a1,b0), (a1,b1), (a2,b0), (a2,b1).

Nothing about this depends on stored state, wall clock, worker count or the order trials complete in, so the grid is covered exactly once even when 16 workers run concurrently — and a resumed study picks up precisely where the trial numbering left off.

§Exhaustion is the stop condition

Once n reaches the number of grid points, every sampling call returns Error::SpaceExhausted. The study loop treats that as a normal stop, not as a failure — it is how a grid search ends. A loop that maps it onto a failed trial is wrong.

§Construction is fallible

Grid::new refuses, rather than allocating, when the space cannot be enumerated:

SpaceResult
contains a continuous parameter (cardinality() == None)Error::InvalidSpace
product of cardinalities exceeds MAX_GRID_POINTSError::InvalidSpace
empty schemathe single empty point

The ceiling is MAX_GRID_POINTS — the same 2^20 that bounds Distribution::grid_values, reused rather than reinvented, so “a distribution can be enumerated” and “a space can be enumerated” mean the same thing at the same size.

use atune_core::sampler::Grid;
use atune_core::space::{Distribution, ParamSpec, ParamValue, SpaceSchema};

let space = SpaceSchema::new([
    ParamSpec::new("opt", Distribution::cat_labels(["adam", "sgd"]).unwrap()).unwrap(),
    ParamSpec::new("amp", Distribution::boolean()).unwrap(),
])
.unwrap();

let grid = Grid::new(space).unwrap();
assert_eq!(grid.points(), 4);

// `amp` is declared last, so it varies fastest.
let first = grid.point_at(0).unwrap();
assert_eq!(first.get("opt"), Some(&ParamValue::Cat(0)));
assert_eq!(first.get("amp"), Some(&ParamValue::Bool(false)));
let second = grid.point_at(1).unwrap();
assert_eq!(second.get("opt"), Some(&ParamValue::Cat(0)));
assert_eq!(second.get("amp"), Some(&ParamValue::Bool(true)));

// And the fifth trial has nothing left to evaluate.
assert!(grid.point_at(4).is_err());

Implementations§

§

impl Grid

pub fn new(space: SpaceSchema) -> Result<Self>

Builds the grid of space.

§Errors

Error::InvalidSpace if any parameter is continuous (a float without a step), if any single parameter’s grid exceeds MAX_GRID_POINTS, or if the product of the cardinalities does. Nothing is allocated before those checks pass, so an accidentally unbounded space costs an error and not the process.

pub const fn space(&self) -> &SpaceSchema

The space this grid enumerates.

pub const fn points(&self) -> u64

The number of grid points — the number of trials the grid can serve.

An empty schema has exactly one point (the empty assignment), matching SpaceSchema::cardinality.

pub fn point_at(&self, index: u64) -> Result<Assignment>

The assignment at grid point index.

See the type documentation for the mixed-radix decomposition. This is a pure function: the same index always yields the same assignment.

§Errors

Error::SpaceExhausted if index is at or beyond points.

Trait Implementations§

§

impl Clone for Grid

§

fn clone(&self) -> Grid

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 Grid

§

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

Formats the value using the given formatter. Read more
§

impl Sampler for Grid

§

fn snapshots_space(&self) -> bool

true: the grid is an enumeration of the space snapshotted in Grid::new, so a bound that grows mid-study would change the grid’s cardinality underneath the trial-number-to-point mapping (open-search-spaces §12.2, clause 1).

§

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

The space the grid was constructed with.

Deliberately not the study’s declared space: a Grid enumerates what it was built to enumerate, and silently switching to a space with different cardinalities would break the trial-number-to-grid-point mapping mid-study. The two are normally the same schema; when they are not, parameters the grid does not cover still get values, through the uniform fallback documented on sample_independent.

§Errors

Never; the signature is fallible because inference in general is.

§

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

The grid point of this trial’s number.

Binds exactly the parameters of space: those the grid covers take their grid value, and any others fall back to the uniform per-name draw (see sample_independent).

§Errors

Error::SpaceExhausted once the trial number reaches points — the grid search is over, and the study loop stops rather than returning the error to the caller.

The ask that discovers this has already created its trial record durably, because creation and the lifecycle reservation are one atomic step, so the record cannot be withdrawn: it is finished Failed with this error as its reason. That trailing record is expected, not a fault — it carries no parameters, is invisible to samplers and to pending repulsion, and is asserted by space_exhaustion_is_a_normal_stop. It does count against max_trials, and a caller listing every trial will see it. Error::InvalidSpace if a distribution outside the grid is malformed.

§

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

One parameter of this trial’s grid point.

§Parameters outside the grid

A define-by-run objective may suggest a parameter the grid was never built for, and a caller may ask for a covered parameter with a distribution whose support no longer holds the grid value. Both cases fall back to the uniform draw Random would have made — from_unit of a coordinate drawn from ChaCha8Rng::seed_from_u64(seed_for_name(trial.sampler_seed, name)).

That keeps three things true: the value is in the caller’s declared support; it is a pure function of (study seed, trial number, name), so it is as reproducible as the grid itself; and the grid’s own enumeration is untouched, because an off-grid parameter never consumes a grid coordinate.

§Errors

Error::SpaceExhausted once the trial number reaches points; Error::InvalidSpace if dist is malformed.

§

fn reseed(&self, _seed: u64)

A no-op: the grid is a pure function of the trial number.

Reseeding cannot move a grid point without breaking the visit-every-point-once guarantee, so it does not. It does not move the uniform fallback either — that is keyed on the trial’s sampler_seed, exactly as Random::reseed is. See Random’s reseed documentation for the full argument.

§

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 restore_state(&self, blob: &SamplerState) -> Result<()>

Restores a sampler from a previously persisted blob. Read more

Auto Trait Implementations§

§

impl Freeze for Grid

§

impl RefUnwindSafe for Grid

§

impl Send for Grid

§

impl Sync for Grid

§

impl Unpin for Grid

§

impl UnsafeUnpin for Grid

§

impl UnwindSafe for Grid

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