designspace.FreshParamExpr#

class designspace.FreshParamExpr(path: str, domain: RealDomain | IntegerDomain | CategoricalDomain | OrdinalDomain | BoolDomain | SubsetDomain | PermutationDomain | ChoiceDomain | StructDomain | CustomDomain | SymbolicDomain | CodeDomain | ListDomain | None = None, periodic: bool = False, prior_spec: Any = None, quantized_spec: QuantizedSpec | None = None, default_value: Any = None, condition: BoolExpr | None = None, tags: frozenset[str] = frozenset({}), meta_map: MappingProxyType = <factory>, choice_payloads: MappingProxyType = <factory>, struct_space: Any = None, lift: _ElementSnapshot | None = None)#

Bases: ParamExpr

What ds.param(name) returns: a parameter with no type chosen yet.

The nine type methods here are the fork in the road. Calling one says what kind of value the parameter holds and narrows the builder to the matching view, which offers only the modifiers valid for that type. Calling a second one is an error, caught statically by the type checker and at resolution either way.

Examples

>>> ds.param("lr").real(1e-4, 1e-1)
RealParamExpr(path='lr'...)
>>> ds.param("algo").categorical("greedy", "exact")
CategoricalParamExpr(path='algo'...)
real(lo: float | ArithExpr, hi: float | ArithExpr, periodic: bool = False) RealParamExpr#

Declare a continuous parameter on [lo, hi].

Bounds may be expressions over other parameters, which makes the domain depend on what has already been chosen.

Parameters:
  • lo (float | ArithExpr) – Lower bound, inclusive.

  • hi (float | ArithExpr) – Upper bound, inclusive.

  • periodic (bool) – Treat the domain as a circle, so lo and hi are the same point. Use it for angles and phases.

Returns:

The narrowed builder.

Return type:

RealParamExpr

Examples

>>> s = ds.space(ds.param("lr").real(1e-4, 1e-1).log_scale())
>>> round(s.sample_one(seed=0)["lr"], 6)
0.008145

A bound can reference another parameter:

>>> s = ds.space(
...     ds.param("lo").real(0.0, 1.0),
...     ds.param("hi").real(ds.param("lo"), 1.0),
... )
>>> c = s.sample_one(seed=0)
>>> c["lo"] <= c["hi"]
True
integer(lo: int | ArithExpr, hi: int | ArithExpr) IntegerParamExpr#

Declare an integer parameter on [lo, hi], both inclusive.

Parameters:
Returns:

The narrowed builder.

Return type:

IntegerParamExpr

Examples

>>> s = ds.space(ds.param("depth").integer(1, 4))
>>> s.cardinality()
4
>>> s.sample_one(seed=0)
{'depth': 3}
categorical(*values: Any) CategoricalParamExpr#

Declare an unordered choice among values.

Use this when the values have no meaningful order: solver names, kernel types, strategies. If they are ordered, use .ordinal(), which lets comparisons work. If a value needs parameters of its own, use .choice().

Values are compared with type-tagged equality, so 1 and 1.0 are distinct.

Parameters:

*values (Any) – The allowed values. At least one; duplicates are an error.

Returns:

The narrowed builder.

Return type:

CategoricalParamExpr

Examples

>>> s = ds.space(ds.param("algo").categorical("greedy", "exact"))
>>> s.sample_one(seed=0)
{'algo': 'exact'}
ordinal(*values: Any) OrdinalParamExpr#

Declare an ordered choice among values.

Order is declaration position, not the values’ natural order, so “low”, “medium”, “high” compare as you would want them to. That is what distinguishes this from .categorical(): comparison operators work.

Parameters:

*values (Any) – The allowed values, in increasing order.

Returns:

The narrowed builder.

Return type:

OrdinalParamExpr

Examples

>>> s = ds.space(ds.param("effort").ordinal("low", "medium", "high"))
>>> s = s.require(ds.param("effort") >= "medium")
>>> s.is_feasible({"effort": "high"})
True
>>> s.is_feasible({"effort": "low"})
False
bool() BoolParamExpr#

Declare a boolean parameter.

A bool parameter doubles as a condition, so it can be passed straight to .when() without comparing it to True.

Returns:

The narrowed builder, which is also a boolean expression.

Return type:

BoolParamExpr

Examples

>>> s = ds.space(
...     ds.param("use_cache").bool(),
...     ds.param("cache_mb").integer(64, 512).when(ds.param("use_cache")),
... )
>>> s.sample_one(seed=0)
{'use_cache': False}
subset(items: Sequence[Any], min_size: int = 0, max_size: int | None = None) SubsetParamExpr#

Declare a selection of any number of items.

Set semantics: order does not matter and there are no duplicates. Query it with .contains(), .size(), and .sum_over().

Parameters:
  • items (Sequence[Any]) – The items available for selection.

  • min_size (int) – Smallest allowed selection.

  • max_size (int | None) – Largest allowed selection; None means all of items.

Returns:

The narrowed builder.

Return type:

SubsetParamExpr

Examples

>>> s = ds.space(ds.param("passes").subset(["inline", "unroll", "vectorize"]))
>>> s.sample_one(seed=0)
{'passes': ['unroll', 'vectorize']}
>>> s.cardinality()
8
permutation(items: Sequence[Any]) PermutationParamExpr#

Declare an ordering of all of items.

Every item appears exactly once; only the order varies. Query a position with .position_of().

Parameters:

items (Sequence[Any]) – The items to order.

Returns:

The narrowed builder.

Return type:

PermutationParamExpr

Examples

>>> s = ds.space(ds.param("order").permutation(["x", "y", "z"]))
>>> s.sample_one(seed=0)
{'order': ['z', 'x', 'y']}
>>> s.cardinality()
6
choice(*variants: str | tuple[str, Any], **keyword_variants: Any) ChoiceParamExpr#

Declare alternatives, each optionally carrying its own parameters.

This is how a design space branches structurally: pick a variant, and that variant’s parameters become active while the others’ vanish from the config. Use it over .categorical() when the alternatives are not interchangeable, when each brings its own knobs.

Three spellings, mixable in one call: a bare string for a variant with no payload, a (name, space) tuple, or name=space as a keyword. Use the tuple form when the name is not a valid Python identifier.

Parameters:
  • *variants (str | tuple[str, Any]) – Bare variant names, or (name, payload_space) pairs.

  • **keyword_variants (Any) – Variants as name=payload_space.

Returns:

The narrowed builder.

Return type:

ChoiceParamExpr

Examples

>>> s = ds.space(
...     ds.param("opt").choice(
...         "adagrad",
...         sgd=ds.space(ds.param("momentum").real(0.0, 1.0)),
...     ),
... )
>>> list(s.params)
['opt', 'opt.sgd.momentum']

A bare variant is just its name; a variant with a payload nests:

>>> s.sample_one(seed=2)
{'opt': 'adagrad'}
>>> s.sample_one(seed=0)
{'opt': {'sgd': {'momentum': 0.2697867137638703}}}

Note that .params uses flat definition paths while a config nests. ds.flatten() and ds.unflatten() convert between the two.

custom(param_type: ParamType | None = None, sampler: Callable[[Any], Any] | None = None, validator: Callable[[Any], bool] | None = None) CustomParamExpr#

Declare a parameter whose values are of your own type.

The extension point for structure the built-in types cannot express: a graph, a topology, a schedule with a global invariant. Reach for it when an invariant is genuinely global (connectivity, pairwise spacing) or when expressing the structure with primitives would leave rejection sampling doing all the work.

Two forms, and exactly one may be used. Passing param_type gives the full protocol: the type can serialize, describe itself, expose properties to .prop(), and optionally sample. Passing sampler and validator is a callback shorthand for quick work, and it cannot be serialized or fingerprinted.

Parameters:
  • param_type (ParamType | None) – An object implementing the ParamType protocol. Mutually exclusive with sampler/validator.

  • sampler (Callable[[Any], Any] | None) – Shorthand form: called with a numpy generator, returns a value. Must be given together with validator.

  • validator (Callable[[Any], bool] | None) – Shorthand form: returns whether a value is acceptable.

Returns:

The narrowed builder.

Return type:

CustomParamExpr

Raises:

ResolutionError – If both forms are given, neither is, or the shorthand form is missing one of its two callbacks.

Examples

>>> class Weekday:
...     type_key = "weekday"
...     names = ["mon", "tue", "wed"]
...     def validate(self, v): return v in self.names
...     def to_json(self, v): return v
...     def from_json(self, d): return d
...     def describe(self): return {"names": self.names}
...     def sample(self, rng): return self.names[int(rng.integers(0, 3))]
>>> s = ds.space(ds.param("day").custom(Weekday()))
>>> s.sample_one(seed=0)
{'day': 'wed'}
>>> s.validate({"day": "sun"}).valid
False

The shorthand, for throwaway work:

>>> quick = ds.param("u").custom(
...     sampler=lambda rng: float(rng.random()),
...     validator=lambda v: 0.0 <= v <= 1.0,
... )
>>> ds.space(quick).validate({"u": 0.5}).valid
True
symbolic(signature: Signature, primitives: Sequence[str | Primitive | FloatLiteral | IntLiteral], max_depth: int, validators: Sequence[Callable[[Any], bool]] | None = None, sampler: Callable[[Any], Any] | None = None) SymbolicParamExpr#

Declare a parameter holding a symbolic expression tree.

For design spaces whose subject is a formula: an acquisition function, a cooling schedule, a heuristic. The library declares and validates the tree’s shape but neither generates nor evaluates it: tree search is a solver’s job, and evaluation is your interpreter’s. Primitive names are declared metadata; nothing here calls them.

Values are dicts of the form {“ast”: …, “source”: …}, where the AST is built from {“op”, “args”}, {“var”}, and {“const”} nodes. Variables come from signature.args.

The parameter is non-generative unless sampler is given.

Parameters:
  • signature (Signature) – Argument names and types, and the return type. The argument names become the usable variables.

  • primitives (Sequence[str | Primitive | FloatLiteral | IntLiteral]) – The vocabulary. A bare string names an operator with unchecked arity; a ds.Primitive declares arity so it can be checked; a ds.FloatLiteral/ds.IntLiteral admits constants in a range.

  • max_depth (int) – Maximum tree depth.

  • validators (Sequence[Callable[[Any], bool]] | None) – Extra checks run against the tree. Not serializable.

  • sampler (Callable[[Any], Any] | None) – Makes the parameter generative. Not serializable.

Returns:

The narrowed builder.

Return type:

SymbolicParamExpr

Examples

>>> sig = ds.Signature(args={"x": float}, returns=float)
>>> f = ds.param("f").symbolic(
...     sig,
...     primitives=["add", "mul", ds.FloatLiteral(-1.0, 1.0)],
...     max_depth=3,
... )
>>> s = ds.space(f)
>>> tree = {"ast": {"op": "add", "args": [{"var": "x"}, {"const": 0.5}]}}
>>> s.validate({"f": tree}).valid
True

An operator outside the declared vocabulary is rejected:

>>> s.validate({"f": {"ast": {"op": "cos", "args": [{"var": "x"}]}}}).valid
False
>>> s.has_nongenerative_params
True
code(signature: Signature, description: str = '', constraints: Sequence[str] | None = None, examples: Sequence[Any] | None = None, validators: Sequence[Callable[[str], bool]] | None = None) CodeParamExpr#

Declare a parameter holding freeform source code.

For a design space with a slot an external process fills: a human, a code-generating model, a library of hand-written implementations. The library carries and validates the source; it never writes or runs it, so this parameter is always non-generative and a space containing one cannot be sampled unless the parameter is defaulted, frozen, or inactive.

description, constraints, and examples are declared metadata: serialized and fingerprinted, never interpreted. They are there for whatever backend does the filling.

Parameters:
  • signature (Signature) – The interface the source must implement.

  • description (str) – What the code should do, in prose.

  • constraints (Sequence[str] | None) – Additional requirements, in prose.

  • examples (Sequence[Any] | None) – Example implementations or input/output pairs.

  • validators (Sequence[Callable[[str], bool]] | None) – Checks run against the source text. Not serializable.

Returns:

The narrowed builder.

Return type:

CodeParamExpr

Examples

>>> sig = ds.Signature(args={"x": float}, returns=float)
>>> impl = ds.param("impl").code(sig, description="a fitness function")
>>> s = ds.space(impl)
>>> s.validate({"impl": {"source": "def f(x): return x * x"}}).valid
True
>>> s.has_nongenerative_params
True
space(*exprs: Any) StructParamExpr#

Declare a struct: a named group of parameters, always active together.

Use this for pure grouping, when several parameters belong to one another and you want them namespaced. Unlike .choice(), a struct picks nothing: every field is always present.

Pass a prebuilt Space instead of loose parameters when the group needs its own constraints, which is the only way to attach per-element constraints to a repeated struct.

Parameters:

*exprs (Any) – The field builders, or a single prebuilt Space.

Returns:

The narrowed builder.

Return type:

StructParamExpr

Examples

>>> s = ds.space(
...     ds.param("pid").space(
...         ds.param("kp").real(0.0, 1.0),
...         ds.param("ki").real(0.0, 1.0),
...     ),
... )
>>> list(s.params)
['pid', 'pid.kp', 'pid.ki']
>>> s.sample_one(seed=0)
{'pid': {'kp': 0.6369616873214543, 'ki': 0.2697867137638703}}

With its own constraint, via a prebuilt space:

>>> inner = ds.space(
...     ds.param("lo").real(0.0, 1.0),
...     ds.param("hi").real(0.0, 1.0),
... ).require(ds.param("lo") < ds.param("hi"))
>>> s = ds.space(ds.param("band").space(inner))
>>> s.is_feasible({"band": {"lo": 0.2, "hi": 0.8}})
True
>>> s.is_feasible({"band": {"lo": 0.8, "hi": 0.2}})
False
property children: tuple[Expr, ...]#

The node’s operands, always empty, a reference being a leaf.

Examples

>>> ds.param("x").children
()
>>> [c.path for c in (ds.param("x") < ds.param("y")).children]
['x', 'y']
contains(item: Any) BoolExpr#

Whether a subset parameter includes item, as an expression.

Parameters:

item (Any) – One of the subset’s declared items.

Returns:

A condition usable in .require(), .forbid(), or .when().

Return type:

BoolExpr

Examples

>>> s = ds.space(ds.param("items").subset(["a", "b", "c"]))
>>> s = s.require(ds.param("items").contains("a"))
>>> s.is_feasible({"items": ["a", "b"]})
True
>>> s.is_feasible({"items": ["b"]})
False
default(value: Any) Self#

Set the value used to fill this parameter in when it is unset.

Defaults are for completing a configuration, not for repairing one: Space.apply_defaults() fills only what is missing and only where the parameter is active, and it never clamps a value into range.

Position matters around .repeat(). Called before, it sets the default for each element; called after, it sets the default for the list as a whole.

Parameters:

value (Any) – The fill value. It must be valid for the parameter’s domain.

Returns:

A new builder with the default set. Last call wins.

Return type:

Self

Examples

>>> s = ds.space(ds.param("depth").integer(1, 8).default(3))
>>> s.apply_defaults({})
{'depth': 3}
>>> s.apply_defaults({"depth": 7})
{'depth': 7}

Element default versus list default:

>>> element = ds.space(ds.param("w").real(0, 1).default(0.5).repeat(3))
>>> element.apply_defaults({})
{'w': [0.5, 0.5, 0.5]}
>>> whole = ds.space(ds.param("w").real(0, 1).repeat(3).default([0.1, 0.2, 0.3]))
>>> whole.apply_defaults({})
{'w': [0.1, 0.2, 0.3]}
if_inactive(fallback: object) ArithExpr#

Substitute fallback when this expression has no value.

An expression over an inactive parameter, or an aggregate over a list that is switched off, evaluates to unknown, and a constraint that cannot be decided is treated as inapplicable rather than violated. That is usually right, but sometimes the intended reading is “absent means zero”. This says so.

It substitutes only for inactivity. An expression that is unknown because a value has not been chosen yet stays unknown, and an aggregate over an active but empty list keeps its own empty value, which the fallback would otherwise mask.

Parameters:

fallback (object) – The value to use when the expression is inactive.

Returns:

An expression that is never unknown for want of activity.

Return type:

ArithExpr

Examples

Without a fallback the budget cannot be decided, so it does not constrain anything:

>>> s = ds.space(
...     ds.param("use_cache").bool(),
...     ds.param("cache_mb").integer(64, 512).when(ds.param("use_cache")),
...     ds.param("heap_mb").integer(64, 512),
... )
>>> total = ds.param("cache_mb") + ds.param("heap_mb")
>>> loose = s.require(total <= 512)
>>> loose.is_feasible({"use_cache": False, "heap_mb": 512})
True

With one, an absent cache counts as zero and the rule applies:

>>> guarded = s.require(ds.param("cache_mb").if_inactive(0) + ds.param("heap_mb") <= 400)
>>> guarded.is_feasible({"use_cache": False, "heap_mb": 512})
False
>>> guarded.is_feasible({"use_cache": False, "heap_mb": 256})
True
implies(other: BoolExpr) BoolExpr#

Material implication: if this holds, other must too.

The natural shape for a conditional rule such as “if we are on GPU, the batch must be at least 32”, and much clearer than the equivalent ~a | b, which it is exactly (down to the fingerprint).

Parameters:

other (BoolExpr) – The consequent.

Returns:

A condition, false only when this holds and other does not.

Return type:

BoolExpr

Raises:

TypeError – If other is not a boolean expression.

Examples

>>> s = ds.space(
...     ds.param("gpu").bool(),
...     ds.param("batch").integer(1, 64),
... ).require(ds.param("gpu").implies(ds.param("batch") >= 32))
>>> s.is_feasible({"gpu": True, "batch": 64})
True
>>> s.is_feasible({"gpu": True, "batch": 8})
False

The rule says nothing when the antecedent is false:

>>> s.is_feasible({"gpu": False, "batch": 8})
True
is_active() BoolExpr#

Whether the referenced parameter is active, as a condition.

Lets a constraint ask about presence rather than value: “if the cache is switched on at all, then …”. Distinct from reading the value, which would be unknown for an inactive parameter.

Returns:

A condition, true when the parameter is present.

Return type:

BoolExpr

Examples

>>> s = ds.space(
...     ds.param("use_cache").bool(),
...     ds.param("cache_mb").integer(64, 512).when(ds.param("use_cache")),
...     ds.param("workers").integer(1, 8),
... )
>>> s = s.require(
...     ds.param("cache_mb").is_active().implies(ds.param("workers") <= 4)
... )
>>> s.is_feasible({"use_cache": True, "cache_mb": 128, "workers": 2})
True
>>> s.is_feasible({"use_cache": True, "cache_mb": 128, "workers": 8})
False
>>> s.is_feasible({"use_cache": False, "workers": 8})
True
is_in(*values: Any) BoolExpr#

Whether the value is one of values.

The replacement for Python’s in, which cannot be used on an expression: in coerces its result to a bool and would collapse the tree.

Parameters:

*values (Any) – The values to test membership against.

Returns:

A condition.

Return type:

BoolExpr

Examples

>>> s = ds.space(ds.param("algo").categorical("a", "b", "c"))
>>> s = s.require(ds.param("algo").is_in("a", "b"))
>>> s.is_feasible({"algo": "a"})
True
>>> s.is_feasible({"algo": "c"})
False
property kind: str#

The expression node kind, always “ref” for a parameter reference.

Every expression node reports a kind, which is how a consumer walks a constraint tree without isinstance chains.

Examples

>>> ds.param("x").kind
'ref'
length() ArithExpr#

How many elements a .repeat() list holds, as an expression.

Useful when the count is itself a parameter and you want to constrain the realized length.

Returns:

An integer-valued expression.

Return type:

ArithExpr

Examples

>>> s = ds.space(
...     ds.param("n").integer(1, 3),
...     ds.param("w").real(0, 1).repeat(ds.param("n")),
... ).require(ds.param("w").length() >= 2)
>>> s.is_feasible({"n": 2, "w": [0.1, 0.2]})
True
>>> s.is_feasible({"n": 1, "w": [0.1]})
False
meta(mapping: dict[str, Any] | None = None, **kwargs: Any) Self#

Attach arbitrary metadata to the parameter.

Metadata is carried through serialization and the fingerprint but never interpreted: units, help text, a UI hint, provenance.

Parameters:
  • mapping (dict[str, Any] | None) – Metadata as a dict, for keys that are not valid identifiers.

  • **kwargs (Any) – The same, as keyword arguments.

Returns:

A new builder carrying the metadata, merged over any already set.

Return type:

Self

Examples

>>> p = ds.param("timeout").real(0.1, 60.0).meta(unit="seconds")
>>> dict(ds.space(p).params["timeout"].meta)
{'unit': 'seconds'}
property params: frozenset[str]#

The parameter paths this expression references.

On a bare reference that is just its own path; on a compound expression it is every path underneath, which is what the dependency graph is built from.

Examples

>>> ds.param("x").params
frozenset({'x'})
>>> sorted((ds.param("x") + ds.param("y")).params)
['x', 'y']
position_of(item: Any) ArithExpr#

Where item sits in a permutation, as a zero-based expression.

Parameters:

item (Any) – One of the permutation’s declared items.

Returns:

The item’s index.

Return type:

ArithExpr

Examples

>>> s = ds.space(ds.param("order").permutation(["x", "y", "z"]))
>>> s = s.require(ds.param("order").position_of("x") == 0)
>>> s.is_feasible({"order": ["x", "y", "z"]})
True
>>> s.is_feasible({"order": ["y", "x", "z"]})
False
prior(dist: Any = None, *, weights: Sequence[float] | None = None) Self#

Set the parameter’s prior, the measure it is sampled from.

A prior is not a hint: it is the coordinate system the parameter lives in. It determines both how the reference sampler draws and how a solver perturbs, which is why there is no separate “transform” concept. Pass a distribution for a numeric parameter, or weights= for a categorical or choice.

Parameters:
  • dist (Any) – A prior for a numeric parameter: ds.Log(), ds.Logit(), ds.Power(p), or any object implementing the external-prior protocol (a ppf, optionally a cdf). Mutually exclusive with weights.

  • weights (Sequence[float] | None) – Relative weights, one per declared value or variant, for a categorical, ordinal, or choice parameter. Need not sum to 1.

Returns:

A new builder with the prior set. Last call wins.

Return type:

Self

Raises:

ResolutionError – If neither or both of dist and weights are given.

Examples

>>> s = ds.space(
...     ds.param("algo").categorical("greedy", "exact").prior(weights=[9, 1]),
... )
>>> s.sample_dicts(4, seed=0)
[{'algo': 'greedy'}, {'algo': 'greedy'}, {'algo': 'greedy'}, {'algo': 'greedy'}]

.log_scale() is shorthand for the equivalent Log prior:

>>> a = ds.space(ds.param("lr").real(1e-4, 1.0).log_scale())
>>> b = ds.space(ds.param("lr").real(1e-4, 1.0).prior(ds.Log()))
>>> a.fingerprint() == b.fingerprint()
True
prop(name: str) Prop#

Read a named property of a .custom() value, as an expression.

A custom type’s values are opaque to the library, so this is the window into them: the type’s properties() supplies the named quantities, and constraints can then be written over those. The result is dual-typed, usable as a number or as a condition, depending on what the property returns.

Parameters:

name (str) – A property name the custom type reports.

Returns:

An expression reading that property.

Return type:

Prop

Examples

>>> class GridType:
...     type_key = "grid"
...     def validate(self, v): return v["n"] >= 1
...     def to_json(self, v): return v
...     def from_json(self, d): return d
...     def describe(self): return {"kind": "grid"}
...     def properties(self): return {"cells": int}
...     def extract(self, v, prop): return v["n"] * v["n"]
>>> s = ds.space(ds.param("g").custom(GridType()))
>>> s = s.require(ds.param("g").prop("cells") <= 4)
>>> s.is_feasible({"g": {"n": 2}})
True
>>> s.is_feasible({"g": {"n": 3}})
False
size() ArithExpr#

How many items a subset holds, as an expression.

Returns:

An integer-valued expression.

Return type:

ArithExpr

Examples

>>> s = ds.space(ds.param("items").subset(["a", "b", "c"]))
>>> s = s.require(ds.param("items").size() >= 2)
>>> s.is_feasible({"items": ["a", "b"]})
True
>>> s.is_feasible({"items": ["a"]})
False
sum_over(mapping: dict[Any, float]) ArithExpr#

Total a per-item weight over a subset’s members, as an expression.

The natural way to write a budget: give each item a cost, then constrain the total of whichever items are selected.

Parameters:

mapping (dict[Any, float]) – Weight per declared item.

Returns:

The sum of mapping[i] over the selected items i.

Return type:

ArithExpr

Examples

>>> s = ds.space(ds.param("items").subset(["a", "b", "c"]))
>>> cost = ds.param("items").sum_over({"a": 1.0, "b": 2.0, "c": 3.0})
>>> s = s.require(cost <= 3.0)
>>> s.is_feasible({"items": ["a", "b"]})
True
>>> s.is_feasible({"items": ["b", "c"]})
False
tag(*tags: str) Self#

Attach labels to the parameter.

Tags are how you address groups of parameters later, with .filter() selects by them. They carry no meaning to the library.

Parameters:

*tags (str) – Labels to add. Repeated calls accumulate.

Returns:

A new builder carrying the tags.

Return type:

Self

Examples

>>> s = ds.space(
...     ds.param("lr").real(1e-4, 1.0).tag("optimizer"),
...     ds.param("batch").integer(8, 64),
... )
>>> list(s.filter(("optimizer",)).params)
['lr']
when(condition: BoolExpr) Self#

Make the parameter active only when condition holds.

This is how a design space branches. An inactive parameter is absent from the configuration dict entirely, not None and not a placeholder, so a config always says exactly what applies to it.

Calling .when() more than once accumulates: the conditions are combined with and, in call order.

Parameters:

condition (BoolExpr) – A boolean expression over other parameters. A bool parameter can be used directly, without comparing it to True.

Returns:

A new builder carrying the condition.

Return type:

Self

Raises:

TypeError – If condition is not a boolean expression. In particular Python’s and/or/in cannot be used, since they would coerce the expression to a bool. Use &, |, ~, and .is_in() instead.

Examples

>>> s = ds.space(
...     ds.param("use_cache").bool(),
...     ds.param("cache_mb").integer(64, 512).when(ds.param("use_cache")),
... )
>>> s.sample_one(seed=0)
{'use_cache': False}
>>> s.sample_one(seed=2)
{'use_cache': True, 'cache_mb': 198}