designspace.Prop#

class designspace.Prop(operand: ArithExpr, name: str)#

Bases: ArithExpr, BoolExpr

ds.param(“c”).prop(name): a custom type’s declared scalar property (int/float/bool/str only). operand is the custom-typed param reference; name is checked against the type’s properties() at resolution. Dual-typed like ParamExpr itself: a bool-declared prop is usable directly as a condition (.require(x.prop (“ok”)), &/|/~), not just inside a Compare, matching the same “bare BoolExpr coerces via bool(value)” convention every param reference already gets. Bare boolean-position usage carries no type_kind or declared-type gate anywhere in the library, while the declared and scalar checks do still apply uniformly here.

Exported because it is .prop()’s return type and no other public type captures it: being both an ArithExpr and a BoolExpr is what lets a bool-declared property serve as a bare condition, which neither base alone expresses.

operand#

The custom-typed parameter reference being read.

Type:

ArithExpr

name#

The property name, checked against the type’s properties() at resolution.

Type:

str

property kind: str#

The node kind, always “prop”.

property children: tuple[Expr, ...]#

The operands, just the custom-typed parameter being read.

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 params: frozenset[str]#

Every parameter path this expression references.

What the dependency graph is built from, and how a constraint knows which parameters it belongs to.

Examples

>>> sorted((ds.param("x") + ds.param("y") < 3).params)
['x', 'y']