designspace.ParamType#

class designspace.ParamType(*args, **kwargs)#

Bases: Protocol

The full .custom(param_type) protocol. Required: type_key, validate, to_json, from_json, describe.

Optional capabilities (checked via hasattr, not declared in this Protocol’s static shape; see the module docstring):

  • sample(self, rng) -> Any, generative iff present; absent, the param is non-generative: sampling raises SamplingError naming the param iff a value must be materialized, and .default()/freeze/slice/inactivity each satisfy that instead.

  • cardinality(self) -> int | None, contributing a finite factor to Space.cardinality() iff present; absent, the whole space’s cardinality is None whenever this param is included.

  • properties(self) -> dict[str, type] and extract(self, value, prop) -> Any, which present together enable .prop() in expressions. Reading an undeclared property, declaring a non-scalar property type, or comparing a property against a value of another type is a resolution error.

Examples

A complete implementation. The five required members are enough to declare, validate, and serialize; sample makes it generative, and properties/extract let constraints read into the value.

>>> class IntervalType:
...     type_key = "interval"
...
...     def validate(self, value):
...         return value["lo"] < value["hi"]
...
...     def to_json(self, value):
...         return value
...
...     def from_json(self, data):
...         return data
...
...     def describe(self):
...         return {"fields": ["lo", "hi"]}
...
...     def sample(self, rng):
...         lo = float(rng.random())
...         return {"lo": lo, "hi": lo + float(rng.random())}
...
...     def properties(self):
...         return {"width": float}
...
...     def extract(self, value, prop):
...         return value["hi"] - value["lo"]
>>> s = ds.space(ds.param("band").custom(IntervalType()))
>>> s.validate({"band": {"lo": 0.1, "hi": 0.4}}).valid
True
>>> s.validate({"band": {"lo": 0.9, "hi": 0.4}}).valid
False

Because it declares properties/extract, constraints can read into the value:

>>> narrow = s.require(ds.param("band").prop("width") <= 0.5)
>>> narrow.is_feasible({"band": {"lo": 0.1, "hi": 0.4}})
True
>>> narrow.is_feasible({"band": {"lo": 0.1, "hi": 0.9}})
False
property type_key: str#

A stable name for this type.

It identifies the type in a serialized document and is the key a consumer’s registry uses when rebuilding a space with Space.from_json(…, custom_types=…). Solver adapters key off it too. Choose something durable, since it is part of the wire format.

validate(value: Any) bool#

Whether value is a legal value of this type.

Receives the type’s native form. Called by Space.validate() and by the sampler after a draw.

Parameters:

value (Any) – A candidate value, in native form.

Returns:

Whether it is acceptable.

Return type:

bool

to_json(value: Any) Any#

Convert a native value to its JSON-safe form.

This is the bridge between the type’s internal representation and the form that appears in configuration dicts, .sample_one() results, hashes, and serialized documents, so it runs on every value leaving the type, not only when writing JSON.

Parameters:

value (Any) – A value in native form.

Returns:

A JSON-safe equivalent.

Return type:

Any

from_json(data: Any) Any#

Convert a JSON-safe value back to native form.

The inverse of to_json, called before validate or extract runs on a value that came from a configuration.

Parameters:

data (Any) – A value in JSON-safe form.

Returns:

The native equivalent.

Return type:

Any

describe() dict[str, Any]#

Describe the type itself, not any particular value.

What a consumer reads to learn the type’s shape: bounds, item counts, whatever a solver adapter or a documentation generator would want. Serialized with the space, and must be JSON-safe.

Returns:

A JSON-safe description of the type.

Return type:

dict[str, Any]