designspace.ArithExpr#

class designspace.ArithExpr#

Bases: Expr

An expression node that evaluates to a scalar (numeric or otherwise) value.

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
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
property children: tuple[Expr, ...]#

The node’s operands, in order.

Together with .kind this is enough to walk or rebuild any expression tree. A leaf has none.

Examples

>>> [c.kind for c in (ds.param("x") < 3).children]
['ref', 'literal']
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
property kind: str#

A short string naming the node type.

Examples

>>> ds.param("x").kind
'ref'
>>> (ds.param("x") < 3).kind
'lt'
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']