designspace.Representation#

class designspace.Representation(source: Space, target: Space, decode: Callable[[dict[str, Any]], dict[str, Any]], encoded: tuple[str, ...] = (), excluded_by_prop: tuple[str, ...] = (), opaque_conditions: tuple[str, ...] = (), opaque_constraints: tuple[Constraint, ...] = (), dropped_defaults: tuple[str, ...] = (), dropped_anchors: tuple[str, ...] = (), encode: Callable[[dict[str, Any]], dict[str, Any]] | None = None, measure_preserving: bool = False)#

Bases: object

A Space → Space morphism carrying a value-level decode/encode pair. Two tiers construct one: derived, from Space.represent(*rules), and supplied, from this constructor called directly. A derived representation is a supplied one; both compose through then and are checked through check().

source is the phenotype and target the genotype, an ordinary Space, so a solver asks it the same questions it would ask any space. Never enters the IR, to_json, or the fingerprint preimage; target serializes as an ordinary Space in its own right.

source#

The phenotype space, the one you declared.

Type:

Space

target#

The genotype space, the one a solver works in. An ordinary Space, so all the usual introspection applies to it.

Type:

Space

decode#

Genotype configuration to phenotype configuration. Total: every configuration valid for target decodes to one valid for source.

Type:

Callable[[Config], Config]

encoded#

Paths that an encoding actually re-expressed.

Type:

tuple[str, …]

excluded_by_prop#

Paths left alone because a .repeat() count or a .prop() reads them.

Type:

tuple[str, …]

opaque_conditions#

Conditions carried across as opaque callables rather than rewritten structurally.

Type:

tuple[str, …]

opaque_constraints#

Constraints carried across the same way.

Type:

tuple[Constraint, …]

dropped_defaults#

Phenotype defaults that encode could not carry over.

Type:

tuple[str, …]

dropped_anchors#

Anchor names likewise dropped. An anchor drops as a whole.

Type:

tuple[str, …]

encode#

Phenotype to genotype, when the morphism is invertible. Raises if it is not.

Type:

Callable[[Config], Config] | None

measure_preserving#

Whether every applied encoding declared that it preserves the declared measure. Never assumed: an encoding that says nothing counts as False.

Type:

bool

invertible#

Whether encode is usable. Derived from whether one was supplied.

Type:

bool

then(other: Representation) Representation#

Compose self (source → target) with other (target → its own target), producing a single morphism from self.source all the way to other.target. Requires other.source to fingerprint-equal self.target (a TypeError otherwise, which is misuse rather than resolution). decode composes right-to-left (self.decode(other.decode(g)), other first, since it is closer to the composed target); encode the reverse (other.encode(self.encode(x))), and only when both sides are invertible.

Parameters:

other (Representation) – A morphism whose source is this one’s target.

Returns:

The composite, from self.source to other.target.

Return type:

Representation

Raises:

TypeError – If other.source does not fingerprint-equal self.target.

Examples

The identity of composition is a representation onto the same space, so composing with one changes nothing observable:

>>> s = ds.space(ds.param("depth").integer(1, 8))
>>> rep = s.represent()
>>> identity = ds.Representation(
...     source=rep.target, target=rep.target, decode=lambda g: g
... )
>>> composed = rep.then(identity)
>>> composed.source.fingerprint() == s.fingerprint()
True
>>> composed.decode({"depth": 0.5}) == rep.decode({"depth": 0.5})
True

Composing morphisms that do not meet is refused:

>>> other = ds.space(ds.param("width").integer(1, 8))
>>> rep.then(other.represent())
Traceback (most recent call last):
    ...
TypeError: then(): other.source does not fingerprint-equal self.target ...
check(n: int = 200, seed: int | Generator | None = None) RepresentationCheck#

Sample n draws of target, decode each, and assert the conformance laws a Representation owes regardless of tier: decode totality (source.validate(decode(g)).param_errors == ()), feasibility agreement (target.is_feasible(g) == source.is_feasible(decode(g))), and, when invertible, the one-directional round-trip decode(encode(x)) == x for x = decode(g). Never raises on a law violation: the suite as a tool, since a supplied morphism has no other way to be shown sound. Structural laws (path/arity) are guaranteed by construction for the derived tier and asserted directly in the conformance suite, since a supplied morphism has no such law to check, so check() does not re-derive them here.

Failures dedupe by (law, detail), accumulating a count rather than one row per draw.

Parameters:
Returns:

With .ok and the deduplicated .failures.

Return type:

RepresentationCheck

Examples

>>> s = ds.space(
...     ds.param("lr").real(1e-4, 1e-1).log_scale(),
...     ds.param("depth").integer(1, 8),
... )
>>> report = s.represent().check(n=50, seed=0)
>>> report.ok, report.failures
(True, ())