Crate atune_derive
Expand description
Procedural macros for atune.
This crate provides #[derive(Space)], which turns a flat configuration
struct into a typed search space (two ways to write a
space). It is the
static, well-lit counterpart of the string DSL: both lower to the same
atune_core::space::{SpaceSchema, Distribution, Assignment} engine — the
derive invents no parallel model.
Do not depend on this crate directly. The generated code names the atune
facade (::atune::…), which re-exports both the Space trait and this
derive, so a crate using #[derive(Space)] must have atune in scope.
If it is in scope under another name — a dependency renamed in Cargo.toml,
tuner = { package = "atune" } — say so once on the struct:
#[space(crate = ::tuner)]. That is the only #[space(...)] key a struct
takes; every key in the table below belongs on a field.
§The attribute grammar
Every field carries exactly one #[space(...)] attribute (a field with no
attribute is a compile error, so nothing is silently dropped). The forms:
| Attribute | Field types | Lowers to |
|---|---|---|
#[space(range = LOW..=HIGH)] | f32/f64, any integer | Float / Int |
#[space(log, range = LOW..=HIGH)] | ditto | logarithmic Float/Int |
#[space(range = LOW..=HIGH, step = S)] | ditto | stepped Float/Int |
#[space(choices = [A, B, C])] | any integer, an enum, … | Cat (value ↔ index) |
#[space] | bool only | Bool |
#[space(fixed)] | any | excluded from the schema |
#[space(fixed = EXPR)] | any | excluded from the schema |
rangeis always inclusive (low..=high). Float ranges take float literals (1e-5..=1e-2), integer ranges take integer literals (1..=16); a mismatched literal is a plain type error from the generated constructor call.logandstepcannot be combined (a logarithmic and uniformly stepped grid is ill-defined — Optuna rejects it too).choicesmaps the listed values to categorical indices in order: the labels stored in the schema are the source text of each choice (64,Optimizer::Adam, …), so an enum is expressed as#[space(choices = [Enum::A, Enum::B])].encodefinds a value’s index with==, so a choice field’s type must bePartialEq. Automatic enumeration of a bare enum field’s variants (the companion#[derive(Categorical)]) is a later slice.fixedis a field that is present on the decoded value but not tuned: it is excluded from the schema and never appears in an encoded point. Ondecodea bare#[space(fixed)]takes the field from the struct’sDefault(so the struct must implementDefault), and#[space(fixed = EXPR)]takes the given expression.
§Type mapping
| Rust field type | Distribution kind |
|---|---|
f32, f64 | Float |
i8…i64, u8…u64, isize, usize | Int |
bool | Bool |
a choices = [...] list (integers, enum variants) | Cat |
An integer that does not fit its field type on decode (for example a
stored -1 for a u32 field) is a clean Err, never a panic. encode
is total: an integer field wider than i64 (only u64/usize) saturates
rather than wrapping — a value HPO ranges never reach.
§What is a compile error
Malformed attributes are rejected at compile time with a spanned
compile_error!, never a panic in the macro:
- an unknown key (
#[space(foo = 1)]); - a non-inclusive range (
#[space(range = 0..1)]); choiceson a floating-point field, orrangeon aboolfield;rangeon a non-numeric field, or a bare non-boolfield with norange/choices/fixed;logandsteptogether, orfixedcombined with any ofrange/choices/log/step;- a field with no
#[space(...)]attribute at all; - a tuple struct, unit struct, enum or union (only named-field structs).
Malformed values (low > high, a logarithmic range with a non-positive
lower bound, a non-positive step, …) are surfaced at run time as an Err
from Space::schema, because they flow through the fallible
Distribution constructors and a library path must not panic.