Source code for spacecore.space.base._space

from __future__ import annotations

from typing import Any, ClassVar, Literal

from ..._check_policy import CheckLevel, check_level_at_least, normalize_check_level
from ..._contextual import ContextBound
from ..._repr import field_symbol
from ...backend import Context
from ..checks import SpaceCheck, SpaceValidationError, _run_checks


[docs] class Space(ContextBound): """ General space capability: context ownership and membership checks. Parameters ---------- ctx : Context, str, or None, optional Context specification used for elements and validation checks. """ checks: ClassVar[tuple[SpaceCheck, ...]] = () def __init__(self, ctx: Context | str | None = None) -> None: super().__init__(ctx) # Lazy caches. Populated on first call to member_checks() / # _checks_for_level() and reused for every subsequent membership # validation. Spaces are immutable after construction (`convert` # returns a fresh instance), so caching the MRO-walked check list # is safe. self._cached_member_checks: tuple[SpaceCheck, ...] | None = None self._cached_checks_by_level: dict[CheckLevel, tuple[SpaceCheck, ...]] = {} @property def field(self) -> Literal["real", "complex"]: """Return the mathematical scalar field derived from the context dtype. ``Context.dtype`` controls array representation. This property records only whether the space is over the real or complex scalar field. """ return "complex" if self.ops.is_complex_dtype(self.dtype) else "real" def __eq__(self, other: Any) -> bool: # Tier 1: backend compatibility (type + ops family + dtype, ignoring # check_level). Tier 2/3: per-subclass algebraic comparison. if not self._eq_backend_compatible(other): return NotImplemented return self._eq_algebra(other) def _eq_algebra(self, other: Any) -> bool: """Algebraic-equality comparison, run only after the backend gate passes. Subclasses extend this via ``super()._eq_algebra(other) and ...`` so each adds its own structural/numerical checks. The base contributes the scalar field (real vs complex): two spaces over different fields are never equal. """ return self.field == other.field def _field_symbol(self) -> str: """Return the scalar-field glyph (``ℝ``/``ℂ``) for this space.""" try: return field_symbol(self.field) except Exception: return "?" def _space_descriptor(self) -> str: """Return a compact math descriptor used in reprs and operator arrows. The base descriptor is just the scalar-field glyph; coordinate, Hermitian, stacked, and tree spaces refine it with shape/structure. """ return self._field_symbol() def _repr_body(self) -> str: return self._space_descriptor()
[docs] def member_checks(self) -> tuple[SpaceCheck, ...]: """Return every ``SpaceCheck`` this instance must satisfy. Walks the MRO and collects ``checks`` class attributes plus any instance-state-driven ``_local_checks`` factories. The result is cached on first access because spaces are immutable post-init. Subclasses that depend on mutable state (none in 0.4.0) must clear ``self._cached_member_checks`` themselves. """ cached = self._cached_member_checks if cached is not None: return cached checks: list[SpaceCheck] = [] for klass in reversed(type(self).__mro__): checks.extend(klass.__dict__.get("checks", ())) local_checks = klass.__dict__.get("_local_checks") if local_checks is not None: checks.extend(local_checks(self)) result = tuple(checks) self._cached_member_checks = result return result
def _checks_for_level(self, level: CheckLevel) -> tuple[SpaceCheck, ...]: """Return the subset of ``member_checks`` that apply at ``level``. Cached per level. This is the hot-path validator used by ``_check_member`` and ``_run_checks``; precomputing it removes per-call MRO walks and per-check ``minimum_level`` comparisons. """ cached = self._cached_checks_by_level.get(level) if cached is not None: return cached result = tuple( check for check in self.member_checks() if check_level_at_least(level, check.minimum_level) ) self._cached_checks_by_level[level] = result return result def _check_member(self, x: Any) -> None: """Raise if ``x`` is not a valid element of this space.""" # Hot path: use the per-level cached check list and run inline, # avoiding the generic ``_run_checks`` MRO walk. level = normalize_check_level(getattr(self, "check_level", "standard")) for check in self._checks_for_level(level): if not check.validate(self, x, allow_leading=False): raise SpaceValidationError( check.validation_message(self, x, allow_leading=False) )
[docs] def check_member(self, x: Any) -> None: if self.check_level != "none": self._check_member(x)
def _convert(self, new_ctx: Context) -> Space: raise NotImplementedError() # ``_run_checks`` is kept importable for backwards compatibility but # callers inside SpaceCore go through ``_check_member`` (single # element) or ``_check_batched_member`` (batched) above. _legacy_run_checks = staticmethod(_run_checks)