mixle.evolve.space module

A small typed search space: the categorical/integer/box gap-filler over the numeric BO backend.

mixle.doe optimizes over a continuous numeric box (a sequence of (low, high) bounds). Real configuration search needs categorical and integer knobs too, and the evolutionary/bandit backends want to sample and walk neighbors natively. Space is the encoding layer that gives both:

  • Space.to_bounds() – a continuous (low, high) box for the BO backend, with categoricals encoded as integer indices ([-0.5, k - 0.5] so a round lands on each level with equal width).

  • Space.encode() / Space.decode() – the lossy round-trip between a config dict and the numeric vector the BO backend proposes (decode rounds integers / categorical indices, clips to range).

  • Space.sample() / Space.neighbors() – native draws and local moves for the evolutionary/bandit backends that handle categoricals without going through the numeric box.

The space lives in evolve (not doe) on purpose: doe stays a pure numeric-box optimizer, and the encoding policy that bridges categoricals into it is an evolve concern.

class Space(dims)[source]

Bases: object

A typed, named search space over Real / Integer / Categorical dims.

Construct from a dict mapping each parameter name to its dimension:

space = Space({"mu": Real(-5, 5), "k": Integer(1, 4), "family": Categorical(["a", "b"])})

Dimension order is the insertion order of the dict; encode() / decode() and to_bounds() all use that same fixed order so the numeric vector and the box align.

Parameters:

dims (dict[str, Dimension])

property dims: MappingProxyType

The named dimensions, read-only.

This was a plain public dict. __init__ validates that a space is non-empty and that every dimension has a supported type, and then the caller could space.dims.clear() and get an object reporting ndim == 0 and sample() == {} – a space that the constructor would have refused to build, reached by mutating one it had just approved. Checks that run once only hold if the thing they checked cannot change afterwards. Build a new Space to change the dimensions.

property names: tuple[str, ...]

The fixed dimension order, always read straight from dims.

This was a tuple snapshotted in __init__ while dims stayed a public mutable dict, so adding or removing a dimension afterwards left the two disagreeing: ndim and every encode/decode kept using the old order and silently ignored the new dimension, while dims reported it as present. Deriving the order removes the possibility rather than documenting it.

property ndim: int

Number of dimensions in the fixed search-space order.

to_bounds()[source]

The continuous (low, high) box for the BO backend (categoricals as integer indices).

Return type:

list[tuple[float, float]]

sample(rng)[source]

Draw a random config dict, each dimension sampled natively.

Parameters:

rng (RandomState)

Return type:

dict[str, Any]

encode(config)[source]

Encode a config dict into the numeric vector (in the fixed dimension order).

Parameters:

config (dict[str, Any])

Return type:

ndarray

decode(vector)[source]

Decode a numeric vector (BO proposal) back into a config dict (rounding / clipping).

Parameters:

vector (Sequence[float])

Return type:

dict[str, Any]

neighbors(point)[source]

All configs one local move away in exactly one dimension (the evolutionary mutation set).

Parameters:

point (dict[str, Any])

Return type:

list[dict[str, Any]]

class Real(lo, hi)[source]

Bases: object

A continuous dimension over [lo, hi].

Both bounds must be finite. An infinite (or NaN) bound would construct fine but then silently poison downstream sample / neighbors draws with inf / nan (or crash deep inside NumPy’s RNG with an opaque OverflowError), far from this actual root cause – so it is rejected here instead.

Parameters:
bounds()[source]

Return the numeric bounds used by continuous optimizers.

Return type:

tuple[float, float]

sample(rng)[source]

Draw a uniformly distributed value from the interval.

Parameters:

rng (RandomState)

Return type:

float

encode(value)[source]

Clip and encode value as a floating-point coordinate.

Parameters:

value (Any)

Return type:

float

decode(x)[source]

Clip a numeric optimizer coordinate back into the interval.

Parameters:

x (float)

Return type:

float

neighbors(value)[source]

A coarse local move: +/- 10% of the range, clipped to bounds.

Parameters:

value (Any)

Return type:

list[float]

class Integer(lo, hi)[source]

Bases: object

An integer dimension over the inclusive range [lo, hi].

Parameters:
bounds()[source]

Return widened numeric bounds so rounding covers each integer level.

Return type:

tuple[float, float]

sample(rng)[source]

Draw an integer uniformly from the inclusive range.

Parameters:

rng (RandomState)

Return type:

int

encode(value)[source]

Round, clip, and encode value as a numeric coordinate.

Parameters:

value (Any)

Return type:

float

decode(x)[source]

Round and clip a numeric optimizer coordinate to an integer value.

Parameters:

x (float)

Return type:

int

neighbors(value)[source]

Return adjacent integer values within the range.

Parameters:

value (Any)

Return type:

list[int]

class Categorical(choices)[source]

Bases: object

An unordered categorical dimension over a finite list of choices.

choices must be unique under == (the same equality _index_of() uses to look a value up) – a duplicate raises ValueError at construction. Without this check, encode would silently collapse a duplicate to its first occurrence’s index while sample / bounds / the dimension’s own length would still count it as a distinct, independently-selectable level, so the dimension’s notion of “how many choices” would disagree with itself depending which path asked.

Parameters:

choices (tuple[Any, ...])

bounds()[source]

Return widened index bounds so rounding covers each categorical choice.

Return type:

tuple[float, float]

sample(rng)[source]

Draw one choice uniformly at random.

Parameters:

rng (RandomState)

Return type:

Any

encode(value)[source]

Encode a choice as its floating-point index.

Parameters:

value (Any)

Return type:

float

decode(x)[source]

Round and clip an optimizer coordinate back to a choice.

Parameters:

x (float)

Return type:

Any

neighbors(value)[source]

Return all choices except value.

Parameters:

value (Any)

Return type:

list[Any]