Skip to main content

Module dsl

Module dsl 

Expand description

The string DSL: a compact text notation for a Distribution.

This is the zero-code path (two ways to write a space, and a third for configuration files): any TOML-configured or CLI-driven program becomes tunable by writing a distribution as text, without a Rust type. The same notation is shared by the CLI (atune run --param lr=loguniform(1e-5,1e-2)) and the TOML overlay (the oniro path), so it is total — every byte sequence maps to either a Distribution or a descriptive Error, and nothing here can panic.

Everything lowers to the existing Distribution constructors; the DSL invents no parallel types and duplicates none of their validation — it surfaces the constructors’ own errors.

§Grammar

Whitespace (ASCII or Unicode) is insignificant everywhere. Function names and the keywords bool, log, step, open, limit, times, plus, up and down are lower-case and case-sensitive.

FormLowers toNotes
uniform(low, high)Distribution::floatcontinuous linear float
uniform(low, high, step)Distribution::float_stepthird arg is the step; step=<v> is also accepted
loguniform(low, high)Distribution::float_loga log range cannot be stepped
int(low, high)Distribution::intunit-step integer
int(low, high, log)Distribution::int_logthe bare keyword log
int(low, high, step=<n>)Distribution::int_stepthe step keyword is required for ints
choice(a, b, c)Distribution::cat_labelslabels in order, stable indices
boolDistribution::booleanalso written bool()
<number>a fixed value (see below)e.g. 0.001, 42, 1e-5

§Numeric literals

A literal is read as an integer when it is written with no decimal point and no exponent (42, -7), and as a float otherwise (0.1, 1e-5, -2.5E3). This is exactly the “integer vs float inferred from the literal” rule: it decides the type of a bare fixed value and of an int bound, and it is why int(2.5, 3) is rejected (an int bound must be an integer literal) while uniform(0, 1) is fine (an integer literal is accepted wherever a float is wanted, and widened). Negative numbers and scientific notation are accepted; NaN and inf literals are rejected (they can only ever name an invalid distribution).

§The step syntax

The step is written differently for the two numeric kinds, on purpose:

  • uniform takes the step as its third positional argument (uniform(0, 1, 0.25)); the explicit uniform(0, 1, step=0.25) is accepted too and means the same thing.
  • int requires the step=<n> keyword (int(2, 512, step=4)), because an int’s third slot is shared with the bare log keyword and a bare number there would read ambiguously.

§Fixed values

A bare number is a parameter that is present but not tuned — it always takes that one value. It lowers to the degenerate range of the matching numeric kind:

  • an integer literal n → Distribution::int(n, n), which is single-valued (the study loop assigns it without sampling);
  • a float literal x → Distribution::float(x, x), a degenerate continuous range. This is not is_single_valued (a continuous float reports no cardinality), but its transform pins every unit coordinate to x, so the independent-sampling path returns the constant just the same. A single-valued float would require a fabricated step, which would then have to be invented on every round trip; the degenerate continuous range avoids that and still yields the constant.

§Growing declarations

parse_growing, parse_schema_growing and parse_schema_lines_growing are a parallel set of entry points – parse and its own three callers are untouched – that accept everything above plus zero or more trailing keyword arguments declaring an Open growth policy. render_growing is the counterpart that renders a distribution back through them, since Open is deliberately not part of Distribution and Display cannot carry it.

FormAddsNotes
<form>, openan Open with Sides::Bothany form above, plus a bare open
<form>, open=upan Open with Sides::Uponly the upper bound may grow
<form>, open=downan Open with Sides::Downonly the lower bound may grow
<form>, limit=..=<n>a high Open::limitinclusive; ..<n> (exclusive) is rejected
<form>, limit=<n>..a low Open::limitinclusive; the two limit= forms combine
around(c, times=<n>)Distribution::float_log seed [c/n, c*n], plus an Openmultiplicative “no idea” declaration
around(c, plus=<n>)Distribution::float seed [c-n, c+n], plus an Openadditive “no idea” declaration

open, open=up, open=down and limit=<range> may follow any call form above (uniform, loguniform, int) or an around(...), in any combination and any order, after that form’s own arguments – which is why loguniform(1e-5, 1e-2, open=up, limit=..=1.0) has four arguments where parse accepts exactly two. around has no closed-form meaning (there is no plain Distribution for “no idea yet”), so it is recognized only by parse_growing and always produces a policy, with or without additional open/limit keywords. limit is independently optional per side, exactly as Open::limit is; setting the same side twice keeps the later value. around ... times lowers to a logarithmic seed, which cannot be stepped (compare Distribution::float_log), so a step= keyword after around is rejected outright rather than silently accepted.

What reaches text is only the seed, the sides and the limit – factor and max_expansions keep their defaults in text and are reachable only from the Open builder in Rust.

§Round trip

Distribution implements Display, and parse(&d.to_string()) reconstructs an equal distribution for every distribution parse can produce (a property test in this module asserts it across every kind). The canonical text a valid distribution renders to is one of the grammar forms above; floats are rendered in the shortest form that round-trips and always carry float syntax where a bare integer would otherwise be re-read as an int. Display of a hand-built invalid distribution is best-effort and is not promised to re-parse.

render_growing extends the same guarantee to a growth policy: parse_growing(&render_growing(&d, open.as_ref())) reconstructs an equal (Distribution, Option<Open>) pair for every pair parse_growing can produce. render_growing(&d, None) renders identically to d.to_string(), since Open – not Distribution – is the only thing it adds.

§Whole spaces

parse_schema turns an ordered sequence of (name, spec) pairs (a TOML table, a map, CLI --param pairs) into a SpaceSchema, preserving declaration order; parse_named parses a single name = spec line; and parse_schema_lines parses a block of such lines (blank and #-comment lines are skipped).

parse_schema_growing and parse_schema_lines_growing are the growing counterparts: each returns a SpacePolicy alongside the SpaceSchema, holding one Open per parameter whose spec carried a growth keyword. A spec with none contributes nothing to the policy, so the returned SpacePolicy only ever names parameters that actually declared themselves open.

Functions§

parse
Parses a DSL spec into a Distribution.
parse_growing
Parses a growing DSL spec into a distribution and its optional growth policy – the parallel entry point that adds open, open=up, open=down, limit=<range> and around(...) on top of everything parse already accepts – see the module documentation for the full grammar. parse itself is untouched: a spec with no growth keyword parses to the identical Distribution, with None in the second slot.
parse_named
Parses a single name = spec line into a name and its Distribution.
parse_schema
Parses an ordered sequence of (name, spec) pairs into a SpaceSchema.
parse_schema_growing
Parses an ordered sequence of (name, spec) pairs into a SpaceSchema and the SpacePolicy its growth keywords declare – the growing counterpart of parse_schema. A parameter whose spec carries no growth keyword contributes nothing to the returned policy.
parse_schema_lines
Parses a block of name = spec lines into a SpaceSchema.
parse_schema_lines_growing
Parses a block of name = spec lines into a SpaceSchema and the SpacePolicy its growth keywords declare – the growing counterpart of parse_schema_lines. Blank lines and #-comments are skipped, exactly as parse_schema_lines skips them.
render_growing
Renders a distribution and its optional growth policy in the string DSL – the counterpart of parse_growing, and what makes its round trip expressible at all: Open is deliberately not part of Distribution, so Display alone cannot carry a policy.