Source code for spacecore.contextual._context

from __future__ import annotations

from dataclasses import dataclass
from typing import Any, Generic, overload

from ..backend import BackendOps
from ..types import DenseArray, SparseArray, DType, ArrayLike
from ..types._array import DenseT


[docs] @dataclass(frozen=True, slots=True, init=False) class Context(Generic[DenseT]): """ Backend operations plus the default dtype for new arrays. A context owns no arrays; it creates and converts them. It is frozen, and ``check_level`` is deliberately not part of it (see :class:`spacecore.contextual.ContextBound`). Parameters ---------- ops : BackendOps Backend operations, e.g. :class:`spacecore.backend.NumpyOps`. dtype : dtype-like or None, optional Dtype for :meth:`asarray` and :meth:`assparse`; normalized by ``ops.sanitize_dtype``. It does not fix a space's scalar field. Examples -------- Create a NumPy context and convert a Python list to a backend array. >>> import numpy as np >>> import spacecore as sc >>> ctx = sc.Context(sc.NumpyOps(), dtype=np.float64) >>> x = ctx.asarray([1.0, 2.0]) >>> x.dtype == np.dtype("float64") True """ ops: BackendOps[DenseT] dtype: DType | None @overload def __init__( self, ops: BackendOps[DenseT] | type[BackendOps[DenseT]] | Context[DenseT], dtype: DType | None = None, ) -> None: ... @overload def __init__( self: Context[DenseArray], ops: str, dtype: DType | None = None, ) -> None: ...
[docs] def __init__( self, ops: BackendOps[DenseT] | type[BackendOps[DenseT]] | Context[DenseT] | str, dtype: DType | None = None, ) -> None: """ Validate and normalize the context after dataclass initialization. Raises ------ TypeError If ``ops`` is not a :class:`BackendOps` instance. """ from ._state import normalize_ops # Accept anything normalize_ops understands; the frozen dataclass is set via object.__setattr__. try: resolved_ops = normalize_ops(ops) except TypeError: raise TypeError("Unknown ops type.") object.__setattr__(self, "ops", resolved_ops) object.__setattr__(self, "dtype", self.ops.sanitize_dtype(dtype))
[docs] def assert_dense(self, x: Any) -> DenseT: """Return ``x`` if it is a dense array of this backend; raise ``TypeError`` otherwise.""" if not self.ops.is_dense(x): raise TypeError(f"Expected dense array for {self.ops.family}, got {type(x).__name__}") return x
[docs] def assert_sparse(self, x: Any) -> SparseArray: """Return ``x`` if it is a sparse array of this backend; raise ``TypeError`` otherwise.""" if not self.ops.allow_sparse: raise TypeError("Sparse objects are disallowed by this backend.") if not self.ops.is_sparse(x): raise TypeError(f"Expected sparse array for {self.ops.family}, got {type(x).__name__}") return x
[docs] def asarray(self, x: Any) -> DenseT: """ Convert ``x`` to a dense backend array of dtype ``self.dtype``. Raises ``TypeError`` rather than silently dropping an imaginary part. """ return self.ops.asarray(x, dtype=self.dtype)
[docs] def assparse(self, x: Any) -> SparseArray: """ Convert ``x`` to a sparse backend array of dtype ``self.dtype``. Raises ``TypeError`` if the backend has no sparse support, or if the conversion would drop an imaginary part. """ return self.ops.assparse(x, dtype=self.dtype)
[docs] def convert(self, x: Any) -> ArrayLike: """Convert a dense or sparse backend array to this context; raise ``NotImplementedError`` for anything else.""" # Dense and sparse arrays convert through their own constructors. if self.ops.is_dense(x): return self.asarray(x) elif self.ops.is_sparse(x): return self.assparse(x) else: raise NotImplementedError
[docs] def same_math(self, other: Any) -> bool: """ Return whether ``other`` has equal backend ops and dtype. The first-tier gate of every context-bound ``__eq__``: it guarantees later value comparisons run on same-backend, same-dtype arrays. Not policy-configurable. Parameters ---------- other : Any Object to compare against. Returns ------- bool ``True`` when ``other`` is a ``Context`` with equal ops and dtype. """ if isinstance(other, Context): return self.ops == other.ops and self.dtype == other.dtype return False
[docs] def same_backend(self, other: Any) -> bool: """ Return whether ``other`` is a ``Context`` with equal ops, ignoring dtype. The loosest context relation, used by context resolution: operands on one backend combine, and their dtypes are reconciled by conversion. """ return isinstance(other, Context) and self.ops == other.ops
def __eq__(self, other: Any) -> bool: """Return whether ``other`` is a ``Context`` with equal ops and dtype.""" return isinstance(other, Context) and self.ops == other.ops and self.dtype == other.dtype