Source code for spacecore.linop.tree._base
from __future__ import annotations
from abc import abstractmethod
from typing import Tuple, Sequence
from .._base import LinOp, Domain, Codomain
from ...contextual import Context
from ..._checks import CheckLevel
[docs]
class TreeLinOp(LinOp[Domain, Codomain]):
"""
Define a base class for operators assembled from component operators.
Parameters
----------
dom : Space
Domain space of the assembled operator.
cod : Space
Codomain space of the assembled operator.
parts : sequence of LinOp
Nonempty sequence of component operators.
ctx : Context, str, or None, optional
Backend context specification. Default is resolved from ``dom`` and
``cod``.
check_level : {"none", "cheap", "standard", "strict"}, optional
Validation level; the ambient default when omitted.
"""
_fields = ("dom", "cod", "parts", "ctx")
_dynamic = ("parts",)
parts: Tuple[LinOp, ...]
def __init__(
self,
dom: Domain,
cod: Codomain,
parts: Sequence[LinOp],
ctx: Context | str | None = None,
check_level: CheckLevel | None = None,
) -> None:
if not parts:
raise ValueError("Parts must be non-empty.")
super().__init__(dom, cod, ctx, check_level=check_level)
# Bind the parts, then cache each part's check-free apply and adjoint.
self.parts = tuple(op.convert(self.ctx) for op in parts)
self._apply_parts = tuple(op._apply_core for op in self.parts)
self._rapply_parts = tuple(op._rapply_core for op in self.parts)
self._check_layout()
@abstractmethod
def _check_layout(self) -> None:
"""Check incidence compatibility between parts and endpoint spaces."""
raise NotImplementedError
[docs]
@classmethod
@abstractmethod
def from_operators(cls, parts: Tuple[LinOp, ...]) -> "TreeLinOp":
"""Build a tree-structured operator from component operators."""
...