Skip to main content

Crate atune_derive

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 at least one #[space(...)] attribute (a field with no attribute is a compile error, so nothing is silently dropped). Its options may be split across several attributes, but an option given twice is an error. The forms:

AttributeField typesLowers to
#[space(range = LOW..=HIGH)]f32/f64, any integerFloat / Int
#[space(log, range = LOW..=HIGH)]dittologarithmic Float/Int
#[space(range = LOW..=HIGH, step = S)]dittostepped Float/Int
#[space(range = LOW..=HIGH, open)]numericopen on both sides
#[space(range = LOW..=HIGH, open = up)]numericopen above the seed
#[space(range = LOW..=HIGH, open = down)]numericopen below the seed
#[space(range = LOW..=HIGH, open, limit = LOW..=HIGH)]numericopen with inclusive growth limits
#[space(around = CENTRE, times = FACTOR)]f32/f64multiplicative open seed
#[space(around = CENTRE, plus = DELTA)]f32/f64additive open seed
#[space(choices = [A, B, C])]any integer, an enum, …Cat (value ↔ index)
#[space]bool onlyBool
#[space(fixed)]anyexcluded from the schema
#[space(fixed = EXPR)]anyexcluded from the schema
  • range is 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.
  • log and step cannot be combined (a logarithmic and uniformly stepped grid is ill-defined — Optuna rejects it too).
  • choices maps 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])]. encode finds a value’s index with ==, so a choice field’s type must be PartialEq. If the field contains a value outside the listed choices, encode uses choice index 0; decode rejects an index outside the list. Automatic enumeration of a bare enum field’s variants (the companion #[derive(Categorical)]) is not implemented.
  • fixed is 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. On decode a bare #[space(fixed)] takes the field from the struct’s Default (so the struct must implement Default), and #[space(fixed = EXPR)] takes the given expression.
  • open opts a numeric range into growth. Bare open grows both sides; open = up or open = down grows only that side. It may accompany step. limit optionally caps either growing side and accepts low.., ..=high, or low..=high; any upper bound must be inclusive. A limit on a side closed by open = up/down is rejected, as is limit without open.
  • around declares a floating-point seed by centre and exactly one spread: times = FACTOR for multiplicative growth or plus = DELTA for additive growth. Bare open is implied; open = up/down and limit refine it. times implies a logarithmic seed, while plus implies a linear seed. around cannot be combined with range, log, or step. times and plus are only valid with around, and cannot both be given.

§Type mapping

Rust field typeDistribution kind
f32, f64Float
i8…i64, u8…u64, isize, usizeInt
boolBool
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.

An f32 field narrows the stored f64 on decode. For a range field, a stored value inside LOW..=HIGH that would round past a bound becomes the adjacent f32 inside it, so the decoded value stays inside the declared range; a value an open range reached by growing past a declared bound narrows unchanged.

§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)]);
  • choices on a floating-point field, or range on a bool field;
  • range on a non-numeric field, or a bare non-bool field with no range/choices/fixed;
  • log and step together, or fixed combined with any of range/choices/log/step;
  • open on a bool, choices, or fixed field; limit without open, or a limit on a closed side; an upper limit bound written exclusively;
  • around on a non-float field, without exactly one of times/plus, or combined with range/log/step; times/plus without around;
  • any option repeated, including across multiple field attributes;
  • 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.

Derive Macros§

Space
Derives Space for a flat configuration struct.