Linear Operators API#

Linear operators represent typed maps \(A : X \to Y\). apply evaluates \(A x\); rapply evaluates the metric adjoint \(A^\sharp y\), which matches the coordinate conjugate transpose only when both spaces are Euclidean.

Matrix-backed operators#

spacecore.linop.DenseLinOp

Represent a dense coordinate tensor-backed linear operator.

spacecore.linop.SparseLinOp

Represent a sparse coordinate matrix-backed linear operator.

spacecore.linop.DiagonalLinOp

Represent a coordinatewise diagonal linear operator.

  • DenseLinOp stores a dense matrix or tensor interpreted in domain and codomain coordinates.

  • SparseLinOp stores a backend sparse matrix for sparse forward and adjoint products.

  • DiagonalLinOp stores coordinate diagonal entries for a map X -> X.

Matrix-free and canonical operators#

spacecore.linop.LinOp

Represent a linear map between two spaces.

spacecore.linop.MatrixFreeLinOp

Linear operator defined by user-supplied forward and reverse callables.

spacecore.linop.IdentityLinOp

Lazy identity map on a space.

spacecore.linop.ZeroLinOp

Lazy zero map between two spaces.

  • LinOp is the abstract base contract.

  • MatrixFreeLinOp wraps forward and metric-adjoint callables exactly as supplied; it does not derive or Riesz-correct matrix-free adjoints.

  • IdentityLinOp represents I : X -> X.

  • ZeroLinOp represents 0 : X -> Y.

Algebraic operators#

spacecore.linop.SumLinOp

Lazy finite sum of linear operators with common spaces.

spacecore.linop.ComposedLinOp

Lazy composition of two linear operators.

spacecore.linop.ScaledLinOp

Lazy scalar multiple of a linear operator.

spacecore.linop.make_sum

Return a locally simplified lazy sum of linear operators.

spacecore.linop.make_composed

Return a locally simplified composition of two linear operators.

spacecore.linop.make_scaled

Return a locally simplified scalar multiple of a linear operator.

  • SumLinOp represents A + B for maps with the same domain and codomain.

  • ComposedLinOp represents A @ B where B.codomain == A.domain.

  • ScaledLinOp represents alpha * A.

  • Helper constructors perform the same simplifications used by Python operator overloads.

Tree and block operators#

spacecore.linop.TreeLinOp

Define a base class for operators assembled from component operators.

spacecore.linop.BlockDiagonalLinOp

Represent independent blocks over a finite direct-product tree.

spacecore.linop.BlockMatrixLinOp

Represent a rectangular matrix of blocks over direct products.

spacecore.linop.StackedLinOp

Represent operators from one domain as a tree-valued map.

spacecore.linop.SumToSingleLinOp

Represent a sum of leaf operators from a tree domain.

  • TreeLinOp is the base for operators with a TreeSpace domain or codomain.

  • BlockDiagonalLinOp(blocks) maps corresponding tree leaves independently and infers both TreeSpace endpoints from the block domains and codomains.

  • BlockMatrixLinOp(block_rows) computes row sums for a rectangular matrix of compatible blocks and infers tuple-structured TreeSpace endpoints.

  • StackedLinOp maps one domain into a tree codomain.

  • SumToSingleLinOp maps a tree domain into one codomain by summing leaf outputs.

Both block classes represent operators over finite direct products. They are not tensor-product or Kronecker-product operators. Their rapply methods use the metric adjoint of every block, which differs from a coordinate conjugate transpose when a leaf space has a non-Euclidean inner product.

Autodoc#

class spacecore.linop.LinOp(dom, cod, ctx=None)[source]#

Bases: ContextBound, Generic[Domain, Codomain]

Represent a linear map between two spaces.

This class is intentionally small. It defines no storage assumptions and requires subclasses to provide forward and adjoint actions.

The adjoint \(A^*\) satisfies \(\langle A x, y\rangle_Y = \langle x, A^* y\rangle_X\) for \(x \in X\) and \(y \in Y\). For complex operators this is the conjugate adjoint.

Parameters:
  • dom (Space) – Domain space X.

  • cod (Space) – Codomain space Y.

  • ctx (Context, str, or None, optional) – Backend context specification. Default is resolved from dom and cod.

dom#

Domain space converted to ctx.

Type:

Space

cod#

Codomain space converted to ctx.

Type:

Space

ctx#

Resolved backend context.

Type:

Context

Examples

Use a concrete dense operator as a LinOp.

>>> import numpy as np
>>> import spacecore as sc
>>> ctx = sc.Context(sc.NumpyOps(), dtype=np.float64)
>>> X = sc.DenseCoordinateSpace((2,), ctx)
>>> A = sc.DenseLinOp(ctx.asarray([[1.0, 0.0], [0.0, 2.0]]), X, X, ctx)
>>> A.apply(ctx.asarray([3.0, 4.0]))
array([3., 8.])
property domain: Domain#

Domain space of this linear operator.

property codomain: Codomain#

Codomain space of this linear operator.

abstractmethod apply(x)[source]#

Apply the forward map to an element of self.domain.

Parameters:

x (Any)

Return type:

Any

abstractmethod rapply(y)[source]#

Apply the adjoint map to an element of self.codomain.

Parameters:

y (Any)

Return type:

Any

adjoint_apply(y)[source]#

Apply the adjoint of this linear operator to y.

Parameters:

y (Any)

Return type:

Any

is_hermitian()[source]#

Return whether this operator is structurally Hermitian when known.

Returns:

True or False when the subclass can verify the structure cheaply, otherwise None for unknown or matrix-free operators.

Return type:

bool | None

vapply(xs)[source]#

Apply over a leading batch axis. Input must have shape (N,) + domain.shape; use moveaxis for other layouts.

Parameters:

xs (Any)

Return type:

Any

rvapply(ys)[source]#

Apply the adjoint over a leading batch axis. Input must have shape (N,) + codomain.shape; use moveaxis for other layouts.

Parameters:

ys (Any)

Return type:

Any

property H: LinOp#

Hermitian-adjoint view of this linear operator.

Returns:

Adjoint view satisfying \(\langle A x, y\rangle_Y = \langle x, A^* y\rangle_X\).

Return type:

LinOp

adjoint()[source]#

Return the Hermitian-adjoint view of this linear operator.

Return type:

LinOp

fuse(*, materialize=False)[source]#

Return an equivalent operator with fusible sub-expressions multiplied out.

Tier-2 lazy-algebra simplification ([ADR-021](021_lazy_operator_algebra_and_simplification.md)): collapse each maximal subtree of densely-fusible operators into a single materialized operator — for example, a composition of dense operators becomes one DenseLinOp holding the matrix product \(M_A M_B\) — while leaving matrix-free and other non-materializable leaves intact.

This is an explicit, opt-in materialization. The result is mathematically equal to self but only within floating-point rounding: fusing reassociates the arithmetic (multiplying matrices then applying differs from applying in sequence at the ulp level), so equality holds up to tolerance, not bit-for-bit. The fused operator preserves the domain, codomain, context, and scalar-field/dtype identity. A leaf operator returns itself.

Parameters:

materialize (bool, optional) – With the default False, a matrix-free operand ([ADR-008](008_linop_subclasses.md)) is never densified: it remains a lazy leaf and only breaks a fusible run. With True the caller explicitly accepts giving up the matrix-free contract: a matrix-free operand is densified into a DenseLinOp (via its to_dense basis probe, which may be expensive), allowing the enclosing expression to collapse to a single dense operator.

Returns:

A fused operator with the same action as self (up to rounding).

Return type:

LinOp

to_dense()[source]#

Materialize this operator as a dense backend array.

The returned array has shape self.codomain.shape + self.domain.shape. The default implementation is intended for small problems, debugging, and tests. It materializes the full coordinate matrix, so subclasses that already store a dense or sparse matrix should override this method for efficiency.

Return type:

Any

to_sparse()[source]#
to_matrix()[source]#

Materialize this operator as a 2D dense coordinate matrix.

The returned array has shape (prod(self.codomain.shape), prod(self.domain.shape)). The default implementation builds a batch of standard basis vectors and calls vapply() once. If a space cannot batch-flatten or batch-unflatten its representation, it falls back to a safe Python loop. This method is for small/testing use; concrete storage-backed subclasses should override it when they can expose a matrix directly.

Return type:

Any

assert_domain(x)[source]#

Raise if x is not in the domain.

Parameters:

x (Any)

Return type:

None

assert_codomain(y)[source]#

Raise if y is not in the codomain.

Parameters:

y (Any)

Return type:

None

property check_level: Literal['none', 'cheap', 'standard', 'strict']#

Return this object’s runtime validation level.

convert(new_ctx=None)#

Return this object represented in new_ctx.

Parameters:

new_ctx (Context | BackendFamily | str | None)

Return type:

Self

class spacecore.linop.DenseLinOp(A, dom, cod=None, ctx=None)[source]#

Bases: LinOp[Domain, Codomain]

Represent a dense coordinate tensor-backed linear operator.

DenseLinOp(A, dom, cod) represents a linear map \(A \colon X \to Y\) where the stored dense array has shape cod.shape + dom.shape. Forward application is the raw coordinate matrix action. Adjoint application is metric-aware: Euclidean spaces use the conjugate transpose fast path, while non-Euclidean spaces use their Riesz maps as R_X^{-1} A^dagger R_Y.

DenseLinOp does not copy or cast the input array. The caller is responsible for passing an array compatible with ctx. This avoids duplicating large dense operators in memory.

Parameters:
  • A (DenseArray) – Dense backend array with shape cod.shape + dom.shape.

  • dom (Space) – Domain space.

  • cod (Space or None, optional) – Codomain space. If omitted, it is inferred from the leading axes of A.

  • ctx (Context, str, or None, optional) – Backend context specification. Default is resolved from the spaces.

A#

Stored dense operator tensor.

Type:

DenseArray

Examples

>>> import numpy as np
>>> import spacecore as sc
>>> ctx = sc.Context(sc.NumpyOps(), dtype=np.float64)
>>> X = sc.DenseCoordinateSpace((2,), ctx)
>>> A = sc.DenseLinOp(ctx.asarray([[2.0, 0.0], [0.0, 3.0]]), X, X, ctx)
>>> A.apply(ctx.asarray([1.0, 2.0]))
array([2., 6.])
apply(x)[source]#

Apply the dense operator to x.

Parameters:

x (DenseArray)

Return type:

DenseArray

rapply(y)[source]#

Apply the adjoint dense operator to y.

Euclidean spaces use the conjugate transpose of the flattened matrix. Non-Euclidean spaces apply the codomain and domain Riesz maps around that Euclidean adjoint.

Parameters:

y (DenseArray)

Return type:

DenseArray

vapply(xs)[source]#

Apply over a leading batch axis. Input must have shape (N,) + domain.shape; use moveaxis for other layouts.

Parameters:

xs (DenseArray)

Return type:

DenseArray

rvapply(ys)[source]#

Apply the adjoint over a leading batch axis. Input must have shape (N,) + codomain.shape; use moveaxis for other layouts.

Parameters:

ys (DenseArray)

Return type:

DenseArray

to_dense()[source]#

Return the stored dense tensor representation of this operator.

The returned array has shape self.codomain.shape + self.domain.shape.

Return type:

DenseArray

to_matrix()[source]#

Return the flattened dense matrix representation.

The returned array has shape (prod(self.codomain.shape), prod(self.domain.shape)). It is a reshape/view of the stored dense tensor when the backend permits.

Return type:

DenseArray

is_hermitian()[source]#

Return whether this dense operator is structurally self-adjoint.

Returns:

True or False when the structure can be checked, otherwise None.

Return type:

bool or None

property H: LinOp#

Hermitian-adjoint view of this linear operator.

Returns:

Adjoint view satisfying \(\langle A x, y\rangle_Y = \langle x, A^* y\rangle_X\).

Return type:

LinOp

adjoint()#

Return the Hermitian-adjoint view of this linear operator.

Return type:

LinOp

adjoint_apply(y)#

Apply the adjoint of this linear operator to y.

Parameters:

y (Any)

Return type:

Any

assert_codomain(y)#

Raise if y is not in the codomain.

Parameters:

y (Any)

Return type:

None

assert_domain(x)#

Raise if x is not in the domain.

Parameters:

x (Any)

Return type:

None

property check_level: Literal['none', 'cheap', 'standard', 'strict']#

Return this object’s runtime validation level.

property codomain: Codomain#

Codomain space of this linear operator.

convert(new_ctx=None)#

Return this object represented in new_ctx.

Parameters:

new_ctx (Context | BackendFamily | str | None)

Return type:

Self

property domain: Domain#

Domain space of this linear operator.

fuse(*, materialize=False)#

Return an equivalent operator with fusible sub-expressions multiplied out.

Tier-2 lazy-algebra simplification ([ADR-021](021_lazy_operator_algebra_and_simplification.md)): collapse each maximal subtree of densely-fusible operators into a single materialized operator — for example, a composition of dense operators becomes one DenseLinOp holding the matrix product \(M_A M_B\) — while leaving matrix-free and other non-materializable leaves intact.

This is an explicit, opt-in materialization. The result is mathematically equal to self but only within floating-point rounding: fusing reassociates the arithmetic (multiplying matrices then applying differs from applying in sequence at the ulp level), so equality holds up to tolerance, not bit-for-bit. The fused operator preserves the domain, codomain, context, and scalar-field/dtype identity. A leaf operator returns itself.

Parameters:

materialize (bool, optional) – With the default False, a matrix-free operand ([ADR-008](008_linop_subclasses.md)) is never densified: it remains a lazy leaf and only breaks a fusible run. With True the caller explicitly accepts giving up the matrix-free contract: a matrix-free operand is densified into a DenseLinOp (via its to_dense basis probe, which may be expensive), allowing the enclosing expression to collapse to a single dense operator.

Returns:

A fused operator with the same action as self (up to rounding).

Return type:

LinOp

to_sparse()#
class spacecore.linop.SparseLinOp(A, dom, cod, ctx=None)[source]#

Bases: LinOp[CoordinateSpace, CoordinateSpace]

Represent a sparse coordinate matrix-backed linear operator.

SparseLinOp(A, dom, cod) represents a sparse coordinate matrix between vector spaces. Subclasses of VectorSpace are supported, but product spaces and other non-vector spaces are intentionally rejected. The conceptual operator tensor has shape cod.shape + dom.shape while storage uses a two-dimensional sparse matrix with shape (prod(cod.shape), prod(dom.shape)).

Forward application is the raw coordinate matrix action. Adjoint application is metric-aware: Euclidean spaces use the conjugate transpose fast path, while non-Euclidean spaces use their Riesz maps as R_X^{-1} A^dagger R_Y.

Parameters:
  • A (SparseArray) – Sparse backend matrix with shape (prod(cod.shape), prod(dom.shape)).

  • dom (CoordinateSpace) – Domain vector space, or a subclass of VectorSpace.

  • cod (CoordinateSpace) – Codomain vector space, or a subclass of VectorSpace.

  • ctx (Context, str, or None, optional) – Backend context specification. Default is resolved from the spaces.

A#

Stored sparse matrix representation. The constructor keeps this object without sparse conversion or copying; explicit conversion happens only through _convert().

Type:

SparseArray

Examples

>>> import numpy as np
>>> import scipy.sparse as sps
>>> import spacecore as sc
>>> ctx = sc.Context(sc.NumpyOps(), dtype=np.float64)
>>> X = sc.DenseCoordinateSpace((2,), ctx)
>>> A = sc.SparseLinOp(ctx.assparse(sps.eye(2)), X, X, ctx)
>>> A.apply(ctx.asarray([1.0, 2.0]))
array([1., 2.])
apply(x)[source]#

Forward action y = A @ x in Euclidean coordinates.

x must have shape dom.shape (dense).

Parameters:

x (DenseArray)

Return type:

DenseArray

rapply(y)[source]#

Metric-aware adjoint action.

y must have shape cod.shape (dense).

Parameters:

y (DenseArray)

Return type:

DenseArray

vapply(xs)[source]#

Apply over a leading batch axis. Input must have shape (N,) + domain.shape; use moveaxis for other layouts.

Parameters:

xs (DenseArray)

Return type:

DenseArray

rvapply(ys)[source]#

Apply the adjoint over a leading batch axis. Input must have shape (N,) + codomain.shape; use moveaxis for other layouts.

Parameters:

ys (DenseArray)

Return type:

DenseArray

to_sparse()[source]#

Return the stored sparse matrix representation without copying.

The returned object is exactly the sparse array supplied at construction.

Return type:

SparseArray

to_matrix()[source]#

Materialize the stored sparse matrix as a dense 2D coordinate matrix.

Use to_sparse() when sparse storage should be preserved.

Return type:

DenseArray

to_dense()[source]#

Materialize the stored sparse matrix as a dense operator tensor.

The returned array has shape self.codomain.shape + self.domain.shape.

Return type:

DenseArray

is_hermitian()[source]#

Return whether this sparse operator is structurally self-adjoint.

Returns:

True or False when the structure can be checked, otherwise None.

Return type:

bool or None

property H: LinOp#

Hermitian-adjoint view of this linear operator.

Returns:

Adjoint view satisfying \(\langle A x, y\rangle_Y = \langle x, A^* y\rangle_X\).

Return type:

LinOp

adjoint()#

Return the Hermitian-adjoint view of this linear operator.

Return type:

LinOp

adjoint_apply(y)#

Apply the adjoint of this linear operator to y.

Parameters:

y (Any)

Return type:

Any

assert_codomain(y)#

Raise if y is not in the codomain.

Parameters:

y (Any)

Return type:

None

assert_domain(x)#

Raise if x is not in the domain.

Parameters:

x (Any)

Return type:

None

property check_level: Literal['none', 'cheap', 'standard', 'strict']#

Return this object’s runtime validation level.

property codomain: Codomain#

Codomain space of this linear operator.

convert(new_ctx=None)#

Return this object represented in new_ctx.

Parameters:

new_ctx (Context | BackendFamily | str | None)

Return type:

Self

property domain: Domain#

Domain space of this linear operator.

fuse(*, materialize=False)#

Return an equivalent operator with fusible sub-expressions multiplied out.

Tier-2 lazy-algebra simplification ([ADR-021](021_lazy_operator_algebra_and_simplification.md)): collapse each maximal subtree of densely-fusible operators into a single materialized operator — for example, a composition of dense operators becomes one DenseLinOp holding the matrix product \(M_A M_B\) — while leaving matrix-free and other non-materializable leaves intact.

This is an explicit, opt-in materialization. The result is mathematically equal to self but only within floating-point rounding: fusing reassociates the arithmetic (multiplying matrices then applying differs from applying in sequence at the ulp level), so equality holds up to tolerance, not bit-for-bit. The fused operator preserves the domain, codomain, context, and scalar-field/dtype identity. A leaf operator returns itself.

Parameters:

materialize (bool, optional) – With the default False, a matrix-free operand ([ADR-008](008_linop_subclasses.md)) is never densified: it remains a lazy leaf and only breaks a fusible run. With True the caller explicitly accepts giving up the matrix-free contract: a matrix-free operand is densified into a DenseLinOp (via its to_dense basis probe, which may be expensive), allowing the enclosing expression to collapse to a single dense operator.

Returns:

A fused operator with the same action as self (up to rounding).

Return type:

LinOp

class spacecore.linop.DiagonalLinOp(diagonal, space=None, ctx=None)[source]#

Bases: LinOp[CoordinateSpace, CoordinateSpace]

Represent a coordinatewise diagonal linear operator.

DiagonalLinOp(diagonal, space) maps x to diagonal * x in coordinates. The adjoint is metric-aware: Euclidean spaces use the complex conjugate of the diagonal, while non-Euclidean spaces use their Riesz maps as R_X^{-1} D^dagger R_X.

Parameters:
  • diagonal (DenseArray) – Dense backend array with shape space.shape.

  • space (Space or None, optional) – Domain and codomain space. If omitted, a vector space is inferred from diagonal.shape.

  • ctx (Context, str, or None, optional) – Backend context specification. Default is resolved from space.

diagonal#

Stored diagonal values.

Type:

DenseArray

Examples

>>> import numpy as np
>>> import spacecore as sc
>>> ctx = sc.Context(sc.NumpyOps(), dtype=np.float64)
>>> X = sc.DenseCoordinateSpace((2,), ctx)
>>> D = sc.DiagonalLinOp(ctx.asarray([2.0, 3.0]), X, ctx)
>>> D.apply(ctx.asarray([4.0, 5.0]))
array([ 8., 15.])
apply(x)[source]#

Apply the diagonal operator to x.

Parameters:

x (DenseArray)

Return type:

DenseArray

rapply(y)[source]#

Apply the adjoint diagonal operator to y.

Parameters:

y (DenseArray)

Return type:

DenseArray

vapply(xs)[source]#

Apply over a leading batch axis. Input must have shape (N,) + domain.shape; use moveaxis for other layouts.

Parameters:

xs (DenseArray)

Return type:

DenseArray

rvapply(ys)[source]#

Apply the adjoint over a leading batch axis. Input must have shape (N,) + codomain.shape; use moveaxis for other layouts.

Parameters:

ys (DenseArray)

Return type:

DenseArray

to_matrix()[source]#

Return the flattened dense diagonal matrix representation.

Return type:

DenseArray

to_dense()[source]#

Return a dense tensor representation of this diagonal operator.

Return type:

DenseArray

is_hermitian()[source]#

Return whether this diagonal operator is structurally self-adjoint.

Returns:

True or False when the structure can be checked, otherwise None.

Return type:

bool or None

property H: LinOp#

Hermitian-adjoint view of this linear operator.

Returns:

Adjoint view satisfying \(\langle A x, y\rangle_Y = \langle x, A^* y\rangle_X\).

Return type:

LinOp

adjoint()#

Return the Hermitian-adjoint view of this linear operator.

Return type:

LinOp

adjoint_apply(y)#

Apply the adjoint of this linear operator to y.

Parameters:

y (Any)

Return type:

Any

assert_codomain(y)#

Raise if y is not in the codomain.

Parameters:

y (Any)

Return type:

None

assert_domain(x)#

Raise if x is not in the domain.

Parameters:

x (Any)

Return type:

None

property check_level: Literal['none', 'cheap', 'standard', 'strict']#

Return this object’s runtime validation level.

property codomain: Codomain#

Codomain space of this linear operator.

convert(new_ctx=None)#

Return this object represented in new_ctx.

Parameters:

new_ctx (Context | BackendFamily | str | None)

Return type:

Self

property domain: Domain#

Domain space of this linear operator.

fuse(*, materialize=False)#

Return an equivalent operator with fusible sub-expressions multiplied out.

Tier-2 lazy-algebra simplification ([ADR-021](021_lazy_operator_algebra_and_simplification.md)): collapse each maximal subtree of densely-fusible operators into a single materialized operator — for example, a composition of dense operators becomes one DenseLinOp holding the matrix product \(M_A M_B\) — while leaving matrix-free and other non-materializable leaves intact.

This is an explicit, opt-in materialization. The result is mathematically equal to self but only within floating-point rounding: fusing reassociates the arithmetic (multiplying matrices then applying differs from applying in sequence at the ulp level), so equality holds up to tolerance, not bit-for-bit. The fused operator preserves the domain, codomain, context, and scalar-field/dtype identity. A leaf operator returns itself.

Parameters:

materialize (bool, optional) – With the default False, a matrix-free operand ([ADR-008](008_linop_subclasses.md)) is never densified: it remains a lazy leaf and only breaks a fusible run. With True the caller explicitly accepts giving up the matrix-free contract: a matrix-free operand is densified into a DenseLinOp (via its to_dense basis probe, which may be expensive), allowing the enclosing expression to collapse to a single dense operator.

Returns:

A fused operator with the same action as self (up to rounding).

Return type:

LinOp

to_sparse()#
class spacecore.linop.MatrixFreeLinOp(apply, rapply, dom, cod, ctx=None, vapply=None, rvapply=None)[source]#

Bases: LinOp[Domain, Codomain]

Linear operator defined by user-supplied forward and reverse callables.

MatrixFreeLinOp(apply, rapply, X, Y) represents a matrix-free map A : X -> Y without storing or materializing a matrix. The context is resolved from the optional ctx argument and the spaces, then the spaces are converted to that context.

The forward action is apply(x) = apply_fn(x) for x in X. The reverse action is rapply(y) = rapply_fn(y) for y in Y. The supplied rapply callable must already be the true adjoint with respect to the declared domain and codomain inner products: <apply(x), y>_Y = <x, rapply(y)>_X. It is not automatically corrected with Riesz maps. If you only have a Euclidean coordinate adjoint in non-Euclidean spaces, compute the metric adjoint outside SpaceCore and pass that callable as rapply. When checks are enabled, inputs and callable outputs are validated against the corresponding domain and codomain, but construction does not run adjoint dot-tests.

Parameters:
  • apply (callable) – Callable with signature apply(x: Any) -> Any implementing the forward map from dom to cod.

  • rapply (callable) – Callable with signature rapply(y: Any) -> Any implementing the true space adjoint map from cod back to dom. For non-Euclidean spaces this is generally not the same as the Euclidean coordinate adjoint.

  • dom (Space) – Domain space containing valid inputs for apply and outputs from rapply.

  • cod (Space) – Codomain space containing outputs from apply and valid inputs for rapply.

  • ctx (Context, str, or None, optional) – Optional context specification. An explicit context wins over inferred contexts from dom and cod.

  • vapply (callable or None, optional) – Optional callable with signature vapply(xs: Any) -> Any for batched forward application. If omitted, backend vmap fallback is used.

  • rvapply (callable or None, optional) – Optional callable with signature rvapply(ys: Any) -> Any for batched adjoint application. If omitted, backend vmap fallback is used.

Returns:

Operator using the supplied callables for forward, adjoint, and optionally batched actions.

Return type:

MatrixFreeLinOp

Notes

See docs/dev/adr/009_metric_adjoint.md for the full design rationale for metric adjoints and the distinction between direct matrix-free adjoints and coordinate-adjoint wrapping.

__init__(apply, rapply, dom, cod, ctx=None, vapply=None, rvapply=None)[source]#

Initialize a matrix-free linear operator.

Parameters:
  • apply (Callable[[Any], Any]) – Callable apply(x) that accepts an element of dom and returns an element of cod.

  • rapply (Callable[[Any], Any]) – Callable rapply(y) that accepts an element of cod and returns an element of dom.

  • dom (Domain) – Domain space of the operator.

  • cod (Codomain) – Codomain space of the operator.

  • ctx (Context | str | None) – Optional context specification for the operator and converted spaces.

  • vapply (Callable[[Any], Any] | None) – Optional callable for batched forward application over dom batches.

  • rvapply (Callable[[Any], Any] | None) – Optional callable for batched adjoint application over cod batches.

Returns:

The initializer stores the callables and converted spaces on self.

Return type:

None

classmethod from_coordinate_adjoint(apply, coordinate_rapply, dom, cod, ctx=None, vapply=None, coordinate_rvapply=None)[source]#

Build a matrix-free operator from a Euclidean coordinate adjoint.

coordinate_rapply is interpreted as the Euclidean coordinate adjoint A^dagger of apply. The stored rapply callable is the metric adjoint

A^sharp(y) = R_X^-1 A^dagger R_Y y

for the declared domain X and codomain Y. Euclidean spaces have identity Riesz maps, so this degenerates to the supplied coordinate adjoint. Non-Euclidean spaces must expose usable Riesz maps at construction time; otherwise the constructor rejects the operator rather than storing an incoherent adjoint.

When coordinate_rvapply is provided, it is treated as the batched Euclidean coordinate adjoint and wrapped with batched Riesz maps. If batched Riesz application is unavailable, the public metric-adjoint helper falls back to vectorized scalar rapply consistently. When coordinate_rvapply is omitted, rvapply_fn remains None and the normal rvapply fallback vectorizes the wrapped scalar adjoint.

See docs/dev/adr/009_metric_adjoint.md for the full design rationale.

Parameters:
  • apply (callable) – Forward coordinate action from dom to cod.

  • coordinate_rapply (callable) – Euclidean coordinate adjoint from cod coordinates to dom coordinates.

  • dom (Space) – Domain and codomain spaces.

  • cod (Space) – Domain and codomain spaces.

  • ctx (Context, str, or None, optional) – Optional context specification.

  • vapply (callable or None, optional) – Optional batched forward application.

  • coordinate_rvapply (callable or None, optional) – Optional batched Euclidean coordinate adjoint. If omitted, batched adjoints use backend vmap over the wrapped scalar rapply.

Return type:

MatrixFreeLinOp

apply(x)[source]#

Apply the forward callable.

Parameters:

x (Any) – Element of self.domain passed to apply_fn.

Returns:

Element of self.codomain returned by apply_fn.

Return type:

Any

rapply(y)[source]#

Apply the adjoint callable.

Parameters:

y (Any) – Element of self.codomain passed to rapply_fn.

Returns:

Element of self.domain returned by rapply_fn.

Return type:

Any

vapply(xs)[source]#

Apply this operator to a batch of domain elements.

Parameters:

xs (Any) – Batched element of self.domain.

Returns:

Batched element of self.codomain produced by vapply_fn or by the fallback batching implementation.

Return type:

Any

rvapply(ys)[source]#

Apply the adjoint operator to a batch of codomain elements.

Parameters:

ys (Any) – Batched element of self.codomain.

Returns:

Batched element of self.domain produced by rvapply_fn or by the fallback batching implementation.

Return type:

Any

fuse(*, materialize=False)[source]#

Stay matrix-free unless materialize=True is requested (ADR-021/ADR-008).

By default a matrix-free operator is never densified, so it returns itself. With materialize=True the caller explicitly opts into densification: the operator is probed into a dense tensor (via to_dense, the basis sweep) and returned as a DenseLinOp, letting an enclosing expression collapse to a single dense operator.

Parameters:

materialize (bool)

Return type:

LinOp

property H: LinOp#

Hermitian-adjoint view of this linear operator.

Returns:

Adjoint view satisfying \(\langle A x, y\rangle_Y = \langle x, A^* y\rangle_X\).

Return type:

LinOp

adjoint()#

Return the Hermitian-adjoint view of this linear operator.

Return type:

LinOp

adjoint_apply(y)#

Apply the adjoint of this linear operator to y.

Parameters:

y (Any)

Return type:

Any

assert_codomain(y)#

Raise if y is not in the codomain.

Parameters:

y (Any)

Return type:

None

assert_domain(x)#

Raise if x is not in the domain.

Parameters:

x (Any)

Return type:

None

property check_level: Literal['none', 'cheap', 'standard', 'strict']#

Return this object’s runtime validation level.

property codomain: Codomain#

Codomain space of this linear operator.

convert(new_ctx=None)#

Return this object represented in new_ctx.

Parameters:

new_ctx (Context | BackendFamily | str | None)

Return type:

Self

property domain: Domain#

Domain space of this linear operator.

is_hermitian()#

Return whether this operator is structurally Hermitian when known.

Returns:

True or False when the subclass can verify the structure cheaply, otherwise None for unknown or matrix-free operators.

Return type:

bool | None

to_dense()#

Materialize this operator as a dense backend array.

The returned array has shape self.codomain.shape + self.domain.shape. The default implementation is intended for small problems, debugging, and tests. It materializes the full coordinate matrix, so subclasses that already store a dense or sparse matrix should override this method for efficiency.

Return type:

Any

to_matrix()#

Materialize this operator as a 2D dense coordinate matrix.

The returned array has shape (prod(self.codomain.shape), prod(self.domain.shape)). The default implementation builds a batch of standard basis vectors and calls vapply() once. If a space cannot batch-flatten or batch-unflatten its representation, it falls back to a safe Python loop. This method is for small/testing use; concrete storage-backed subclasses should override it when they can expose a matrix directly.

Return type:

Any

to_sparse()#
class spacecore.linop.IdentityLinOp(space, ctx=None)[source]#

Bases: LinOp[Domain, Domain]

Lazy identity map on a space.

IdentityLinOp(X) represents the identity operator I_X : X -> X. The context is resolved from the optional ctx argument and the space, and the resulting operator has domain and codomain equal to X in that context.

The forward action is apply(x) = x for x in X. The reverse action is rapply(x) = x for x in X.

Parameters:
  • space (Space) – Domain and codomain space.

  • ctx (Context, str, or None, optional) – Backend context specification. Default is resolved from space.

apply(x)[source]#

Return x after domain validation.

Parameters:

x (Any)

Return type:

Any

rapply(x)[source]#

Return x after codomain validation.

Parameters:

x (Any)

Return type:

Any

vapply(xs)[source]#

Return xs after batched domain validation.

Parameters:

xs (Any)

Return type:

Any

rvapply(xs)[source]#

Return xs after batched codomain validation.

Parameters:

xs (Any)

Return type:

Any

to_dense()[source]#

Return the dense tensor representation of this identity map.

The returned array has shape self.codomain.shape + self.domain.shape.

Return type:

Any

is_hermitian()[source]#

Return whether this identity operator is Hermitian.

Returns:

Always True.

Return type:

bool

property H: LinOp#

Hermitian-adjoint view of this linear operator.

Returns:

Adjoint view satisfying \(\langle A x, y\rangle_Y = \langle x, A^* y\rangle_X\).

Return type:

LinOp

adjoint()#

Return the Hermitian-adjoint view of this linear operator.

Return type:

LinOp

adjoint_apply(y)#

Apply the adjoint of this linear operator to y.

Parameters:

y (Any)

Return type:

Any

assert_codomain(y)#

Raise if y is not in the codomain.

Parameters:

y (Any)

Return type:

None

assert_domain(x)#

Raise if x is not in the domain.

Parameters:

x (Any)

Return type:

None

property check_level: Literal['none', 'cheap', 'standard', 'strict']#

Return this object’s runtime validation level.

property codomain: Codomain#

Codomain space of this linear operator.

convert(new_ctx=None)#

Return this object represented in new_ctx.

Parameters:

new_ctx (Context | BackendFamily | str | None)

Return type:

Self

property domain: Domain#

Domain space of this linear operator.

fuse(*, materialize=False)#

Return an equivalent operator with fusible sub-expressions multiplied out.

Tier-2 lazy-algebra simplification ([ADR-021](021_lazy_operator_algebra_and_simplification.md)): collapse each maximal subtree of densely-fusible operators into a single materialized operator — for example, a composition of dense operators becomes one DenseLinOp holding the matrix product \(M_A M_B\) — while leaving matrix-free and other non-materializable leaves intact.

This is an explicit, opt-in materialization. The result is mathematically equal to self but only within floating-point rounding: fusing reassociates the arithmetic (multiplying matrices then applying differs from applying in sequence at the ulp level), so equality holds up to tolerance, not bit-for-bit. The fused operator preserves the domain, codomain, context, and scalar-field/dtype identity. A leaf operator returns itself.

Parameters:

materialize (bool, optional) – With the default False, a matrix-free operand ([ADR-008](008_linop_subclasses.md)) is never densified: it remains a lazy leaf and only breaks a fusible run. With True the caller explicitly accepts giving up the matrix-free contract: a matrix-free operand is densified into a DenseLinOp (via its to_dense basis probe, which may be expensive), allowing the enclosing expression to collapse to a single dense operator.

Returns:

A fused operator with the same action as self (up to rounding).

Return type:

LinOp

to_matrix()#

Materialize this operator as a 2D dense coordinate matrix.

The returned array has shape (prod(self.codomain.shape), prod(self.domain.shape)). The default implementation builds a batch of standard basis vectors and calls vapply() once. If a space cannot batch-flatten or batch-unflatten its representation, it falls back to a safe Python loop. This method is for small/testing use; concrete storage-backed subclasses should override it when they can expose a matrix directly.

Return type:

Any

to_sparse()#
class spacecore.linop.ZeroLinOp(dom, cod, ctx=None)[source]#

Bases: LinOp[Domain, Codomain]

Lazy zero map between two spaces.

ZeroLinOp(X, Y) represents the linear map 0 : X -> Y. The context is resolved from the optional ctx argument and the two spaces, then both spaces are converted to that context. Its domain is X and its codomain is Y in the resolved context.

The forward action is apply(x) = 0_Y for x in X. The reverse action is rapply(y) = 0_X for y in Y.

Parameters:
  • dom (Space) – Domain space.

  • cod (Space) – Codomain space.

  • ctx (Context, str, or None, optional) – Backend context specification. Default is resolved from the spaces.

apply(x)[source]#

Return the zero element of the codomain.

Parameters:

x (Any)

Return type:

Any

rapply(y)[source]#

Return the zero element of the domain.

Parameters:

y (Any)

Return type:

Any

vapply(xs)[source]#

Return the batched zero element of the codomain.

Parameters:

xs (Any)

Return type:

Any

rvapply(ys)[source]#

Return the batched zero element of the domain.

Parameters:

ys (Any)

Return type:

Any

to_dense()[source]#

Return the dense tensor representation of the zero map.

The returned array has shape self.codomain.shape + self.domain.shape.

Return type:

Any

is_hermitian()[source]#

Return whether the zero map is Hermitian.

Returns:

True exactly when domain and codomain are the same space.

Return type:

bool

property H: LinOp#

Hermitian-adjoint view of this linear operator.

Returns:

Adjoint view satisfying \(\langle A x, y\rangle_Y = \langle x, A^* y\rangle_X\).

Return type:

LinOp

adjoint()#

Return the Hermitian-adjoint view of this linear operator.

Return type:

LinOp

adjoint_apply(y)#

Apply the adjoint of this linear operator to y.

Parameters:

y (Any)

Return type:

Any

assert_codomain(y)#

Raise if y is not in the codomain.

Parameters:

y (Any)

Return type:

None

assert_domain(x)#

Raise if x is not in the domain.

Parameters:

x (Any)

Return type:

None

property check_level: Literal['none', 'cheap', 'standard', 'strict']#

Return this object’s runtime validation level.

property codomain: Codomain#

Codomain space of this linear operator.

convert(new_ctx=None)#

Return this object represented in new_ctx.

Parameters:

new_ctx (Context | BackendFamily | str | None)

Return type:

Self

property domain: Domain#

Domain space of this linear operator.

fuse(*, materialize=False)#

Return an equivalent operator with fusible sub-expressions multiplied out.

Tier-2 lazy-algebra simplification ([ADR-021](021_lazy_operator_algebra_and_simplification.md)): collapse each maximal subtree of densely-fusible operators into a single materialized operator — for example, a composition of dense operators becomes one DenseLinOp holding the matrix product \(M_A M_B\) — while leaving matrix-free and other non-materializable leaves intact.

This is an explicit, opt-in materialization. The result is mathematically equal to self but only within floating-point rounding: fusing reassociates the arithmetic (multiplying matrices then applying differs from applying in sequence at the ulp level), so equality holds up to tolerance, not bit-for-bit. The fused operator preserves the domain, codomain, context, and scalar-field/dtype identity. A leaf operator returns itself.

Parameters:

materialize (bool, optional) – With the default False, a matrix-free operand ([ADR-008](008_linop_subclasses.md)) is never densified: it remains a lazy leaf and only breaks a fusible run. With True the caller explicitly accepts giving up the matrix-free contract: a matrix-free operand is densified into a DenseLinOp (via its to_dense basis probe, which may be expensive), allowing the enclosing expression to collapse to a single dense operator.

Returns:

A fused operator with the same action as self (up to rounding).

Return type:

LinOp

to_matrix()#

Materialize this operator as a 2D dense coordinate matrix.

The returned array has shape (prod(self.codomain.shape), prod(self.domain.shape)). The default implementation builds a batch of standard basis vectors and calls vapply() once. If a space cannot batch-flatten or batch-unflatten its representation, it falls back to a safe Python loop. This method is for small/testing use; concrete storage-backed subclasses should override it when they can expose a matrix directly.

Return type:

Any

to_sparse()#
class spacecore.linop.SumLinOp(ops)[source]#

Bases: LinOp[Domain, Codomain]

Lazy finite sum of linear operators with common spaces.

SumLinOp((A1, ..., Ak)) represents A1 + ... + Ak for a nonempty sequence of LinOp instances. All operands must have the same ctx, the same domain, and the same codomain before construction. The resulting operator has that shared context, domain, and codomain.

The forward action is apply(x) = sum_i Ai.apply(x) for the shared domain element x. The reverse action is rapply(y) = sum_i Ai.rapply(y) for the shared codomain element y.

Parameters:

ops (sequence of LinOp) – Nonempty sequence of operators with common context, domain, and codomain.

parts#

Stored operands in the lazy sum.

Type:

tuple of LinOp

apply(x)[source]#

Return sum_i ops[i].apply(x).

Parameters:

x (Any)

Return type:

Any

rapply(y)[source]#

Return sum_i ops[i].rapply(y).

Parameters:

y (Any)

Return type:

Any

vapply(xs)[source]#

Return sum_i ops[i].vapply(xs).

Parameters:

xs (Any)

Return type:

Any

rvapply(ys)[source]#

Return sum_i ops[i].rvapply(ys).

Parameters:

ys (Any)

Return type:

Any

fuse(*, materialize=False)[source]#

Fuse each term and combine the dense terms into one DenseLinOp (ADR-021).

Fuse every term, sum the matrices of the densely-fusible ones into a single DenseLinOp, and keep the remaining (matrix-free or structured) terms as lazy summands — so a matrix-free term is never densified. Adjoint-consistent and additive: (A + B)^* = A^* + B^*. Combining reassociates the term order, so equality holds up to rounding.

Parameters:

materialize (bool)

Return type:

LinOp

is_hermitian()[source]#

Return whether this lazy sum is structurally Hermitian.

The adjoint is additive, (A1 + ... + Ak)* = A1* + ... + Ak*, so a sum is self-adjoint when every term is. If all operands are provably Hermitian this returns True; otherwise the verdict is not cheaply decidable (a sum of non-Hermitian terms may still be Hermitian) and None is returned. False is never returned.

Returns:

True when every part is provably Hermitian, otherwise None.

Return type:

bool | None

property H: LinOp#

Hermitian-adjoint view of this linear operator.

Returns:

Adjoint view satisfying \(\langle A x, y\rangle_Y = \langle x, A^* y\rangle_X\).

Return type:

LinOp

adjoint()#

Return the Hermitian-adjoint view of this linear operator.

Return type:

LinOp

adjoint_apply(y)#

Apply the adjoint of this linear operator to y.

Parameters:

y (Any)

Return type:

Any

assert_codomain(y)#

Raise if y is not in the codomain.

Parameters:

y (Any)

Return type:

None

assert_domain(x)#

Raise if x is not in the domain.

Parameters:

x (Any)

Return type:

None

property check_level: Literal['none', 'cheap', 'standard', 'strict']#

Return this object’s runtime validation level.

property codomain: Codomain#

Codomain space of this linear operator.

convert(new_ctx=None)#

Return this object represented in new_ctx.

Parameters:

new_ctx (Context | BackendFamily | str | None)

Return type:

Self

property domain: Domain#

Domain space of this linear operator.

to_dense()#

Materialize this operator as a dense backend array.

The returned array has shape self.codomain.shape + self.domain.shape. The default implementation is intended for small problems, debugging, and tests. It materializes the full coordinate matrix, so subclasses that already store a dense or sparse matrix should override this method for efficiency.

Return type:

Any

to_matrix()#

Materialize this operator as a 2D dense coordinate matrix.

The returned array has shape (prod(self.codomain.shape), prod(self.domain.shape)). The default implementation builds a batch of standard basis vectors and calls vapply() once. If a space cannot batch-flatten or batch-unflatten its representation, it falls back to a safe Python loop. This method is for small/testing use; concrete storage-backed subclasses should override it when they can expose a matrix directly.

Return type:

Any

to_sparse()#
class spacecore.linop.ComposedLinOp(left, right)[source]#

Bases: LinOp[Domain, Codomain]

Lazy composition of two linear operators.

ComposedLinOp(A, B) represents A @ B = A circ B. The operands must have the same ctx before construction, and B.codomain must equal A.domain. The resulting operator has domain B.domain and codomain A.codomain.

The forward action is apply(x) = A.apply(B.apply(x)) for x in B.domain. The reverse action is rapply(z) = B.rapply(A.rapply(z)) for z in A.codomain.

Parameters:
  • left (LinOp) – Operator applied second.

  • right (LinOp) – Operator applied first.

left#

Left operand.

Type:

LinOp

right#

Right operand.

Type:

LinOp

apply(x)[source]#

Return left.apply(right.apply(x)).

Parameters:

x (Any)

Return type:

Any

rapply(z)[source]#

Return right.rapply(left.rapply(z)).

Parameters:

z (Any)

Return type:

Any

vapply(xs)[source]#

Return left.vapply(right.vapply(xs)).

Parameters:

xs (Any)

Return type:

Any

rvapply(zs)[source]#

Return right.rvapply(left.rvapply(zs)).

Parameters:

zs (Any)

Return type:

Any

fuse(*, materialize=False)[source]#

Fuse a composition of dense operators into one DenseLinOp (ADR-021).

Fuse each operand first, then — when both fused operands are dense — replace A @ B with a single DenseLinOp holding the matrix product \(M_A M_B\). Any operand that does not fuse to a dense operator (matrix-free leaves, sparse, structured) keeps the composition lazy, so a matrix-free operand is never densified. The fused matrix is adjoint-consistent on any geometry: the shared middle-space Riesz maps cancel, so the fused operator’s metric adjoint equals B* @ A* up to floating-point rounding.

Parameters:

materialize (bool)

Return type:

LinOp

is_hermitian()[source]#

Return whether this composition is structurally Hermitian.

A Gram product R* @ R (equivalently L @ L*) is self-adjoint in any geometry, since <R* R x, y> = <R x, R y> = <x, R* R y>. This is detected structurally when self.left == self.right.H (the adjoint view compares its wrapped operand, so this also matches L @ L*). Any other composition is not cheaply decidable and returns None; non-Hermiticity is never asserted.

Returns:

True for a Gram product, otherwise None.

Return type:

bool | None

property H: LinOp#

Hermitian-adjoint view of this linear operator.

Returns:

Adjoint view satisfying \(\langle A x, y\rangle_Y = \langle x, A^* y\rangle_X\).

Return type:

LinOp

adjoint()#

Return the Hermitian-adjoint view of this linear operator.

Return type:

LinOp

adjoint_apply(y)#

Apply the adjoint of this linear operator to y.

Parameters:

y (Any)

Return type:

Any

assert_codomain(y)#

Raise if y is not in the codomain.

Parameters:

y (Any)

Return type:

None

assert_domain(x)#

Raise if x is not in the domain.

Parameters:

x (Any)

Return type:

None

property check_level: Literal['none', 'cheap', 'standard', 'strict']#

Return this object’s runtime validation level.

property codomain: Codomain#

Codomain space of this linear operator.

convert(new_ctx=None)#

Return this object represented in new_ctx.

Parameters:

new_ctx (Context | BackendFamily | str | None)

Return type:

Self

property domain: Domain#

Domain space of this linear operator.

to_dense()#

Materialize this operator as a dense backend array.

The returned array has shape self.codomain.shape + self.domain.shape. The default implementation is intended for small problems, debugging, and tests. It materializes the full coordinate matrix, so subclasses that already store a dense or sparse matrix should override this method for efficiency.

Return type:

Any

to_matrix()#

Materialize this operator as a 2D dense coordinate matrix.

The returned array has shape (prod(self.codomain.shape), prod(self.domain.shape)). The default implementation builds a batch of standard basis vectors and calls vapply() once. If a space cannot batch-flatten or batch-unflatten its representation, it falls back to a safe Python loop. This method is for small/testing use; concrete storage-backed subclasses should override it when they can expose a matrix directly.

Return type:

Any

to_sparse()#
class spacecore.linop.ScaledLinOp(scalar, op)[source]#

Bases: LinOp[Domain, Codomain]

Lazy scalar multiple of a linear operator.

ScaledLinOp(alpha, A) represents the mathematical operator alpha * A. Its context is exactly A.ctx; its domain is A.domain and its codomain is A.codomain. No dense matrix representation is formed.

The forward action is apply(x) = alpha * A.apply(x) for x in A.domain. The reverse action is rapply(y) = conj(alpha) * A.rapply(y) for y in A.codomain, so complex scalars use the conjugated coefficient.

Parameters:
  • scalar (scalar-like) – Scalar multiplier.

  • op (LinOp) – Operator being scaled.

scalar#

Stored scalar multiplier.

Type:

scalar-like

op#

Stored operand.

Type:

LinOp

apply(x)[source]#

Return scalar * op.apply(x).

Parameters:

x (Any)

Return type:

Any

rapply(y)[source]#

Return conj(scalar) * op.rapply(y).

Parameters:

y (Any)

Return type:

Any

vapply(xs)[source]#

Return scalar * op.vapply(xs).

Parameters:

xs (Any)

Return type:

Any

rvapply(ys)[source]#

Return conj(scalar) * op.rvapply(ys).

Parameters:

ys (Any)

Return type:

Any

fuse(*, materialize=False)[source]#

Fuse the operand and fold the scalar into a dense matrix (ADR-021).

When the fused operand is dense, replace c · A with one DenseLinOp holding c · M_A; otherwise keep the scaling lazy, so a matrix-free operand is never densified. Adjoint-consistent: the fused operator’s adjoint is conj(c) · M_A^* (with the metric), exactly the lazy ScaledLinOp adjoint.

Parameters:

materialize (bool)

Return type:

LinOp

is_hermitian()[source]#

Return whether this scaled operator is structurally Hermitian.

For a real scalar s the adjoint satisfies (s A)* = s A*, so s A is self-adjoint exactly when A is; the operand’s verdict is propagated faithfully. For a non-real scalar the relation becomes (s A)* = conj(s) A* != s A in general, so Hermiticity cannot be decided cheaply and None is returned.

Returns:

self.op.is_hermitian() when self.scalar is real, otherwise None for unknown.

Return type:

bool | None

property H: LinOp#

Hermitian-adjoint view of this linear operator.

Returns:

Adjoint view satisfying \(\langle A x, y\rangle_Y = \langle x, A^* y\rangle_X\).

Return type:

LinOp

adjoint()#

Return the Hermitian-adjoint view of this linear operator.

Return type:

LinOp

adjoint_apply(y)#

Apply the adjoint of this linear operator to y.

Parameters:

y (Any)

Return type:

Any

assert_codomain(y)#

Raise if y is not in the codomain.

Parameters:

y (Any)

Return type:

None

assert_domain(x)#

Raise if x is not in the domain.

Parameters:

x (Any)

Return type:

None

property check_level: Literal['none', 'cheap', 'standard', 'strict']#

Return this object’s runtime validation level.

property codomain: Codomain#

Codomain space of this linear operator.

convert(new_ctx=None)#

Return this object represented in new_ctx.

Parameters:

new_ctx (Context | BackendFamily | str | None)

Return type:

Self

property domain: Domain#

Domain space of this linear operator.

to_dense()#

Materialize this operator as a dense backend array.

The returned array has shape self.codomain.shape + self.domain.shape. The default implementation is intended for small problems, debugging, and tests. It materializes the full coordinate matrix, so subclasses that already store a dense or sparse matrix should override this method for efficiency.

Return type:

Any

to_matrix()#

Materialize this operator as a 2D dense coordinate matrix.

The returned array has shape (prod(self.codomain.shape), prod(self.domain.shape)). The default implementation builds a batch of standard basis vectors and calls vapply() once. If a space cannot batch-flatten or batch-unflatten its representation, it falls back to a safe Python loop. This method is for small/testing use; concrete storage-backed subclasses should override it when they can expose a matrix directly.

Return type:

Any

to_sparse()#
spacecore.linop.make_sum(ops)[source]#

Return a locally simplified lazy sum of linear operators.

This factory performs only local algebraic canonicalization: nested SumLinOp nodes are flattened and ZeroLinOp terms are removed. It does not collect like terms, reorder operands, or attempt full symbolic optimization. All operands must have the same context, domain, and codomain before a simplified operator is returned.

Parameters:

ops (sequence of LinOp) – Nonempty sequence of operators with common domain and codomain.

Returns:

Simplified lazy sum, a single operand, or a zero operator.

Return type:

LinOp

spacecore.linop.make_composed(left, right)[source]#

Return a locally simplified composition of two linear operators.

This factory performs only local algebraic canonicalization: identity factors are removed and compositions with zero maps become zero maps. It preserves the binary ComposedLinOp representation and does not flatten multi-factor chains or attempt full symbolic optimization. Operands must have the same context and compatible middle spaces before a simplified operator is returned.

Parameters:
  • left (LinOp) – Operator applied second.

  • right (LinOp) – Operator applied first.

Returns:

Simplified lazy composition representing left @ right.

Return type:

LinOp

spacecore.linop.make_scaled(scalar, op)[source]#

Return a locally simplified scalar multiple of a linear operator.

This factory performs only local algebraic canonicalization: zero and unit scalars are simplified, and nested ScaledLinOp nodes are folded into one scalar. It does not distribute scaling over sums or perform full symbolic optimization. Complex scalars retain the usual conjugated coefficient in rapply through ScaledLinOp.

Parameters:
  • scalar (scalar-like) – Scalar coefficient multiplying op.

  • op (LinOp) – Operator to scale.

Returns:

Simplified scalar multiple.

Return type:

LinOp

class spacecore.linop.TreeLinOp(dom, cod, parts, ctx=None)[source]#

Bases: 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.

abstractmethod classmethod from_operators(parts)[source]#

Build a tree-structured operator from component operators.

Parameters:

parts (Tuple[LinOp, ...])

Return type:

TreeLinOp

property H: LinOp#

Hermitian-adjoint view of this linear operator.

Returns:

Adjoint view satisfying \(\langle A x, y\rangle_Y = \langle x, A^* y\rangle_X\).

Return type:

LinOp

adjoint()#

Return the Hermitian-adjoint view of this linear operator.

Return type:

LinOp

adjoint_apply(y)#

Apply the adjoint of this linear operator to y.

Parameters:

y (Any)

Return type:

Any

abstractmethod apply(x)#

Apply the forward map to an element of self.domain.

Parameters:

x (Any)

Return type:

Any

assert_codomain(y)#

Raise if y is not in the codomain.

Parameters:

y (Any)

Return type:

None

assert_domain(x)#

Raise if x is not in the domain.

Parameters:

x (Any)

Return type:

None

property check_level: Literal['none', 'cheap', 'standard', 'strict']#

Return this object’s runtime validation level.

property codomain: Codomain#

Codomain space of this linear operator.

convert(new_ctx=None)#

Return this object represented in new_ctx.

Parameters:

new_ctx (Context | BackendFamily | str | None)

Return type:

Self

property domain: Domain#

Domain space of this linear operator.

fuse(*, materialize=False)#

Return an equivalent operator with fusible sub-expressions multiplied out.

Tier-2 lazy-algebra simplification ([ADR-021](021_lazy_operator_algebra_and_simplification.md)): collapse each maximal subtree of densely-fusible operators into a single materialized operator — for example, a composition of dense operators becomes one DenseLinOp holding the matrix product \(M_A M_B\) — while leaving matrix-free and other non-materializable leaves intact.

This is an explicit, opt-in materialization. The result is mathematically equal to self but only within floating-point rounding: fusing reassociates the arithmetic (multiplying matrices then applying differs from applying in sequence at the ulp level), so equality holds up to tolerance, not bit-for-bit. The fused operator preserves the domain, codomain, context, and scalar-field/dtype identity. A leaf operator returns itself.

Parameters:

materialize (bool, optional) – With the default False, a matrix-free operand ([ADR-008](008_linop_subclasses.md)) is never densified: it remains a lazy leaf and only breaks a fusible run. With True the caller explicitly accepts giving up the matrix-free contract: a matrix-free operand is densified into a DenseLinOp (via its to_dense basis probe, which may be expensive), allowing the enclosing expression to collapse to a single dense operator.

Returns:

A fused operator with the same action as self (up to rounding).

Return type:

LinOp

is_hermitian()#

Return whether this operator is structurally Hermitian when known.

Returns:

True or False when the subclass can verify the structure cheaply, otherwise None for unknown or matrix-free operators.

Return type:

bool | None

abstractmethod rapply(y)#

Apply the adjoint map to an element of self.codomain.

Parameters:

y (Any)

Return type:

Any

rvapply(ys)#

Apply the adjoint over a leading batch axis. Input must have shape (N,) + codomain.shape; use moveaxis for other layouts.

Parameters:

ys (Any)

Return type:

Any

to_dense()#

Materialize this operator as a dense backend array.

The returned array has shape self.codomain.shape + self.domain.shape. The default implementation is intended for small problems, debugging, and tests. It materializes the full coordinate matrix, so subclasses that already store a dense or sparse matrix should override this method for efficiency.

Return type:

Any

to_matrix()#

Materialize this operator as a 2D dense coordinate matrix.

The returned array has shape (prod(self.codomain.shape), prod(self.domain.shape)). The default implementation builds a batch of standard basis vectors and calls vapply() once. If a space cannot batch-flatten or batch-unflatten its representation, it falls back to a safe Python loop. This method is for small/testing use; concrete storage-backed subclasses should override it when they can expose a matrix directly.

Return type:

Any

to_sparse()#
vapply(xs)#

Apply over a leading batch axis. Input must have shape (N,) + domain.shape; use moveaxis for other layouts.

Parameters:

xs (Any)

Return type:

Any

class spacecore.linop.BlockDiagonalLinOp(blocks, cod=None, parts=None, ctx=None)[source]#

Bases: TreeLinOp[TreeSpace, TreeSpace]

Represent independent blocks over a finite direct-product tree.

BlockDiagonalLinOp(blocks) infers matching domain and codomain TreeSpace objects from the block domains and codomains. The Python tree structure of blocks is also the element structure on both sides. This is a direct-product operator, not a tensor or Kronecker product.

Parameters:
  • blocks (tree of LinOp or TreeSpace) – Nonempty block tree (one-argument form). Each leaf A_i maps the corresponding domain leaf X_i to codomain leaf Y_i. In the legacy four-argument form this is instead the domain TreeSpace.

  • cod (TreeSpace or None, optional) – Codomain tree for the legacy (dom, cod, parts, ctx) form; inferred from the blocks otherwise.

  • parts (sequence of LinOp or None, optional) – Block operators for the legacy form; inferred from blocks otherwise.

  • ctx (Context, str, or None, optional) – Backend context specification. Default is resolved from the blocks.

Notes

The legacy BlockDiagonalLinOp(dom, cod, blocks, ctx) form remains accepted so callers can provide distinct custom domain and codomain tree structures. New code should use the inferred one-argument form.

apply(x)[source]#

Apply each block to the matching direct-product component.

Parameters:

x (Any)

Return type:

Any

rapply(y)[source]#

Apply each block’s metric adjoint to the matching component.

Parameters:

y (Any)

Return type:

Any

vapply(x)[source]#

Apply each block over a tree of leading-axis batches.

Parameters:

x (Any)

Return type:

Any

rvapply(y)[source]#

Apply each metric adjoint over a tree of leading-axis batches.

Parameters:

y (Any)

Return type:

Any

property H: BlockDiagonalLinOp#

Return a block-diagonal adjoint with every block replaced by A_i.H.

fuse(*, materialize=False)[source]#

Fuse each block (ADR-021), preserving the tree layout and context.

Parameters:

materialize (bool)

Return type:

BlockDiagonalLinOp

classmethod from_operators(parts)[source]#

Build a tuple-structured block-diagonal operator.

Parameters:

parts (Sequence[LinOp])

Return type:

BlockDiagonalLinOp

adjoint()#

Return the Hermitian-adjoint view of this linear operator.

Return type:

LinOp

adjoint_apply(y)#

Apply the adjoint of this linear operator to y.

Parameters:

y (Any)

Return type:

Any

assert_codomain(y)#

Raise if y is not in the codomain.

Parameters:

y (Any)

Return type:

None

assert_domain(x)#

Raise if x is not in the domain.

Parameters:

x (Any)

Return type:

None

property check_level: Literal['none', 'cheap', 'standard', 'strict']#

Return this object’s runtime validation level.

property codomain: Codomain#

Codomain space of this linear operator.

convert(new_ctx=None)#

Return this object represented in new_ctx.

Parameters:

new_ctx (Context | BackendFamily | str | None)

Return type:

Self

property domain: Domain#

Domain space of this linear operator.

is_hermitian()#

Return whether this operator is structurally Hermitian when known.

Returns:

True or False when the subclass can verify the structure cheaply, otherwise None for unknown or matrix-free operators.

Return type:

bool | None

to_dense()#

Materialize this operator as a dense backend array.

The returned array has shape self.codomain.shape + self.domain.shape. The default implementation is intended for small problems, debugging, and tests. It materializes the full coordinate matrix, so subclasses that already store a dense or sparse matrix should override this method for efficiency.

Return type:

Any

to_matrix()#

Materialize this operator as a 2D dense coordinate matrix.

The returned array has shape (prod(self.codomain.shape), prod(self.domain.shape)). The default implementation builds a batch of standard basis vectors and calls vapply() once. If a space cannot batch-flatten or batch-unflatten its representation, it falls back to a safe Python loop. This method is for small/testing use; concrete storage-backed subclasses should override it when they can expose a matrix directly.

Return type:

Any

to_sparse()#
class spacecore.linop.BlockMatrixLinOp(block_rows)[source]#

Bases: TreeLinOp[TreeSpace, TreeSpace]

Represent a rectangular matrix of blocks over direct products.

For blocks A_ij : X_j -> Y_i, the operator maps X_0 x ... x X_n to Y_0 x ... x Y_m and computes y_i = sum_j A_ij x_j. These are direct-product blocks, not tensor or Kronecker products.

Parameters:

block_rows (sequence of sequences of LinOp) – Nonempty rectangular block matrix. Blocks in one row must have compatible codomains, and blocks in one column must have compatible domains.

apply(x)[source]#

Apply the block matrix and sum each output row.

Parameters:

x (Any)

Return type:

Any

rapply(y)[source]#

Apply the metric-adjoint blocks and sum each transposed column.

Parameters:

y (Any)

Return type:

Any

vapply(x)[source]#

Apply the block matrix over a tuple of leading-axis batches.

Parameters:

x (Any)

Return type:

Any

rvapply(y)[source]#

Apply the block metric adjoint over leading-axis batches.

Parameters:

y (Any)

Return type:

Any

property H: BlockMatrixLinOp#

Transpose the block layout and replace every block by its adjoint.

fuse(*, materialize=False)[source]#

Fuse each block (ADR-021), preserving the rectangular block layout.

Parameters:

materialize (bool)

Return type:

BlockMatrixLinOp

classmethod from_operators(parts)[source]#

Build a one-row block matrix from a sequence of operators.

Parameters:

parts (Sequence[LinOp])

Return type:

BlockMatrixLinOp

adjoint()#

Return the Hermitian-adjoint view of this linear operator.

Return type:

LinOp

adjoint_apply(y)#

Apply the adjoint of this linear operator to y.

Parameters:

y (Any)

Return type:

Any

assert_codomain(y)#

Raise if y is not in the codomain.

Parameters:

y (Any)

Return type:

None

assert_domain(x)#

Raise if x is not in the domain.

Parameters:

x (Any)

Return type:

None

property check_level: Literal['none', 'cheap', 'standard', 'strict']#

Return this object’s runtime validation level.

property codomain: Codomain#

Codomain space of this linear operator.

convert(new_ctx=None)#

Return this object represented in new_ctx.

Parameters:

new_ctx (Context | BackendFamily | str | None)

Return type:

Self

property domain: Domain#

Domain space of this linear operator.

is_hermitian()#

Return whether this operator is structurally Hermitian when known.

Returns:

True or False when the subclass can verify the structure cheaply, otherwise None for unknown or matrix-free operators.

Return type:

bool | None

to_dense()#

Materialize this operator as a dense backend array.

The returned array has shape self.codomain.shape + self.domain.shape. The default implementation is intended for small problems, debugging, and tests. It materializes the full coordinate matrix, so subclasses that already store a dense or sparse matrix should override this method for efficiency.

Return type:

Any

to_matrix()#

Materialize this operator as a 2D dense coordinate matrix.

The returned array has shape (prod(self.codomain.shape), prod(self.domain.shape)). The default implementation builds a batch of standard basis vectors and calls vapply() once. If a space cannot batch-flatten or batch-unflatten its representation, it falls back to a safe Python loop. This method is for small/testing use; concrete storage-backed subclasses should override it when they can expose a matrix directly.

Return type:

Any

to_sparse()#
class spacecore.linop.StackedLinOp(dom, cod, parts, ctx=None)[source]#

Bases: TreeLinOp[Domain, TreeSpace]

Represent operators from one domain as a tree-valued map.

If dom = X and cod = Y1 x ... x Yk, component parts[i] maps X to Yi. Forward application returns a value with cod.treedef; adjoint application sums component adjoints in X.

Parameters:
  • dom (Space) – Shared component domain.

  • cod (TreeSpace) – Tree-structured codomain.

  • parts (sequence of LinOp) – Operators from dom to each component of cod.

  • ctx (Context, str, or None, optional) – Backend context specification.

apply(x)[source]#

Apply each component operator and return a codomain product element.

Parameters:

x (Any)

Return type:

Any

rapply(y)[source]#

Apply component adjoints from a codomain product element and sum them.

Parameters:

y (Any)

Return type:

Any

vapply(x)[source]#

Apply this stacked operator over a batch and preserve codomain structure.

Parameters:

x (Any)

Return type:

Any

rvapply(y)[source]#

Apply the adjoint stacked operator over a structured product batch.

Parameters:

y (Any)

Return type:

Any

fuse(*, materialize=False)[source]#

Fuse each component operator (ADR-021), preserving dom/cod and context.

Parameters:

materialize (bool)

Return type:

StackedLinOp

classmethod from_operators(parts)[source]#

Build a stacked operator from component operators.

Parameters:

parts (Tuple[LinOp, ...])

Return type:

StackedLinOp

property H: LinOp#

Hermitian-adjoint view of this linear operator.

Returns:

Adjoint view satisfying \(\langle A x, y\rangle_Y = \langle x, A^* y\rangle_X\).

Return type:

LinOp

adjoint()#

Return the Hermitian-adjoint view of this linear operator.

Return type:

LinOp

adjoint_apply(y)#

Apply the adjoint of this linear operator to y.

Parameters:

y (Any)

Return type:

Any

assert_codomain(y)#

Raise if y is not in the codomain.

Parameters:

y (Any)

Return type:

None

assert_domain(x)#

Raise if x is not in the domain.

Parameters:

x (Any)

Return type:

None

property check_level: Literal['none', 'cheap', 'standard', 'strict']#

Return this object’s runtime validation level.

property codomain: Codomain#

Codomain space of this linear operator.

convert(new_ctx=None)#

Return this object represented in new_ctx.

Parameters:

new_ctx (Context | BackendFamily | str | None)

Return type:

Self

property domain: Domain#

Domain space of this linear operator.

is_hermitian()#

Return whether this operator is structurally Hermitian when known.

Returns:

True or False when the subclass can verify the structure cheaply, otherwise None for unknown or matrix-free operators.

Return type:

bool | None

to_dense()#

Materialize this operator as a dense backend array.

The returned array has shape self.codomain.shape + self.domain.shape. The default implementation is intended for small problems, debugging, and tests. It materializes the full coordinate matrix, so subclasses that already store a dense or sparse matrix should override this method for efficiency.

Return type:

Any

to_matrix()#

Materialize this operator as a 2D dense coordinate matrix.

The returned array has shape (prod(self.codomain.shape), prod(self.domain.shape)). The default implementation builds a batch of standard basis vectors and calls vapply() once. If a space cannot batch-flatten or batch-unflatten its representation, it falls back to a safe Python loop. This method is for small/testing use; concrete storage-backed subclasses should override it when they can expose a matrix directly.

Return type:

Any

to_sparse()#
class spacecore.linop.SumToSingleLinOp(dom, cod, parts, ctx=None)[source]#

Bases: TreeLinOp[TreeSpace, Codomain]

Represent a sum of leaf operators from a tree domain.

If dom = X1 x ... x Xk and cod = Y, component parts[i] maps Xi to Y. Forward application sums component outputs in Y; adjoint application returns a value with dom.treedef.

Parameters:
  • dom (TreeSpace) – Tree-structured domain.

  • cod (Space) – Shared codomain.

  • parts (sequence of LinOp) – Operators from each product component to cod.

  • ctx (Context, str, or None, optional) – Backend context specification.

apply(x)[source]#

Apply operators to components of a domain product element and sum.

Parameters:

x (Any)

Return type:

Any

rapply(y)[source]#

Apply component adjoints and return a domain product element.

Parameters:

y (Any)

Return type:

Any

vapply(x)[source]#

Apply this sum-to-single operator over a structured product batch.

Parameters:

x (Any)

Return type:

Any

rvapply(y)[source]#

Apply the adjoint over a codomain batch and preserve domain structure.

Parameters:

y (Any)

Return type:

Any

fuse(*, materialize=False)[source]#

Fuse each component operator (ADR-021), preserving dom/cod and context.

Parameters:

materialize (bool)

Return type:

SumToSingleLinOp

classmethod from_operators(parts)[source]#

Build a sum-to-single operator from component operators.

Parameters:

parts (Tuple[LinOp, ...])

Return type:

SumToSingleLinOp

property H: LinOp#

Hermitian-adjoint view of this linear operator.

Returns:

Adjoint view satisfying \(\langle A x, y\rangle_Y = \langle x, A^* y\rangle_X\).

Return type:

LinOp

adjoint()#

Return the Hermitian-adjoint view of this linear operator.

Return type:

LinOp

adjoint_apply(y)#

Apply the adjoint of this linear operator to y.

Parameters:

y (Any)

Return type:

Any

assert_codomain(y)#

Raise if y is not in the codomain.

Parameters:

y (Any)

Return type:

None

assert_domain(x)#

Raise if x is not in the domain.

Parameters:

x (Any)

Return type:

None

property check_level: Literal['none', 'cheap', 'standard', 'strict']#

Return this object’s runtime validation level.

property codomain: Codomain#

Codomain space of this linear operator.

convert(new_ctx=None)#

Return this object represented in new_ctx.

Parameters:

new_ctx (Context | BackendFamily | str | None)

Return type:

Self

property domain: Domain#

Domain space of this linear operator.

is_hermitian()#

Return whether this operator is structurally Hermitian when known.

Returns:

True or False when the subclass can verify the structure cheaply, otherwise None for unknown or matrix-free operators.

Return type:

bool | None

to_dense()#

Materialize this operator as a dense backend array.

The returned array has shape self.codomain.shape + self.domain.shape. The default implementation is intended for small problems, debugging, and tests. It materializes the full coordinate matrix, so subclasses that already store a dense or sparse matrix should override this method for efficiency.

Return type:

Any

to_matrix()#

Materialize this operator as a 2D dense coordinate matrix.

The returned array has shape (prod(self.codomain.shape), prod(self.domain.shape)). The default implementation builds a batch of standard basis vectors and calls vapply() once. If a space cannot batch-flatten or batch-unflatten its representation, it falls back to a safe Python loop. This method is for small/testing use; concrete storage-backed subclasses should override it when they can expose a matrix directly.

Return type:

Any

to_sparse()#