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

Linear operator stored as a dense array of shape cod.shape + dom.shape.

spacecore.linop.SparseLinOp

Linear operator stored as a sparse matrix of shape (cod.size, dom.size).

spacecore.linop.DiagonalLinOp

Coordinatewise map x -> diagonal * x on one space.

Matrix-free and canonical operators#

spacecore.linop.LinOp

A linear map from dom to cod; subclasses supply apply and rapply.

spacecore.linop.MatrixFreeLinOp

Linear operator given by forward and adjoint callables; no matrix is stored.

spacecore.linop.IdentityLinOp

Lazy identity map on space.

spacecore.linop.ZeroLinOp

Lazy zero map from dom to cod.

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

Algebraic operators#

spacecore.linop.SumLinOp

Lazy sum of operators sharing context, domain and codomain.

spacecore.linop.ComposedLinOp

Lazy composition left @ right; the adjoint is right.H @ left.H.

spacecore.linop.ScaledLinOp

Lazy scalar * op; its adjoint is conj(scalar) * op.H.

spacecore.linop.make_sum

Return the lazy sum of operators, with nested sums flattened and zero terms dropped.

spacecore.linop.make_composed

Return left @ right, dropping identity factors and collapsing zero factors.

spacecore.linop.make_scaled

Return scalar * op: 0 gives zero, 1 gives op, nested scalars fold.

  • 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

Independent blocks acting on the leaves of a direct-product tree.

spacecore.linop.BlockMatrixLinOp

Block matrix over direct products: y_i = sum_j A_ij x_j for blocks A_ij : X_j -> Y_i.

  • 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. The container of rows is the codomain tree and each row’s container the domain tree; a side written as a plain one-element list or tuple is a single space. So BlockMatrixLinOp.stack((A1, A2)) maps one domain into a tree, x -> (A1 x, A2 x), and BlockMatrixLinOp.from_operators((A1, A2)) sums a tree into one codomain, (x1, x2) -> A1 x1 + A2 x2; each is the adjoint of the other built from the blocks’ adjoints.

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(*args, **kwargs)[source]#

Bases: Mapping, Generic[Domain, Codomain]

A linear map from dom to cod; subclasses supply apply and rapply.

rapply is the Hilbert-space adjoint in each space’s own inner product, \(\langle A x, y\rangle_Y = \langle x, A^* y\rangle_X\), not the coordinate transpose. In orthonormal bases it is the conjugate transpose (Trefethen & Bau, Numerical Linear Algebra, Lecture 2, pp. 11-12); in general coordinates it is \(R_X^{-1} A^\dagger R_Y\), derived in metric_rapply(). Adjoint products reverse order (Trefethen & Bau, Numerical Linear Algebra, Eq. (2.4)); inserting Gram factors cancels the intermediate metric.

Parameters:
  • dom (Space) – Domain X.

  • cod (Space) – Codomain Y.

  • ctx (Context, str, or None, optional) – Backend context; resolved from dom and cod when omitted.

  • check_level ({"none", "cheap", "standard", "strict"}, optional) – Validation level; the ambient default when omitted.

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.

apply(x)[source]#

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

Parameters:

x (Any)

Return type:

Any

rapply(y)[source]#

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

This is the Hilbert-space adjoint A^#, defined by <Ax, y>_Y = <x, A^# y>_X. Trefethen & Bau, Numerical Linear Algebra, Lecture 2, pp. 11-12, gives the conjugate transpose in orthonormal coordinates; spacecore.linop._metric.metric_rapply() derives the Gram-matrix form. The dual map sends a linear functional phi to phi o A; writing phi(y) = b^T y gives dual coordinates A^T b, even over the complex field.

Parameters:

y (Any)

Return type:

Any

adjoint_apply(y)[source]#

Apply the adjoint of this linear operator to y.

Spelled-out alias for the inner-product adjoint rapply().

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 dense sub-expressions multiplied out (ADR-021).

Each maximal run of dense operators becomes one DenseLinOp; other leaves stay lazy. The result equals self up to rounding. A leaf returns itself.

Parameters:

materialize (bool, optional) – Also densify matrix-free operands, which may be costly; by default they stay lazy.

Returns:

The fused operator.

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.

The level is stored on the bound object (_check_level), seeded at construction. The fallback covers any construction path that assigns _ctx without going through __init__/_bind_context.

convert(new_ctx=None)#

Return this object represented in new_ctx.

The object’s check_level is a property of the object, not of the backend/dtype context, so it is preserved across conversion rather than re-seeded from the ambient default.

Parameters:

new_ctx (Context | BackendFamily | str | None)

Return type:

Self

same_math(other)#

Tier-1 equality gate: same concrete type and same math context.

Combines a runtime-type identity check with Context.same_math() (equal backend ops and dtype, ignoring check_level — a validation policy, not a mathematical or backend property). This is the mandatory first check in every __eq__: the type gate guarantees other exposes the same attributes this object’s __eq__ will read, and same_math guarantees any later ops.allclose runs only on same-backend, same-dtype arrays, making cross-backend objects compare unequal.

Callers that fail this gate should return NotImplemented so Python can try the reflected comparison and fall back to identity symmetrically.

Parameters:

other (Any)

Return type:

bool

class spacecore.linop.DenseLinOp(*args, **kwargs)[source]#

Bases: LinOp[Domain, Codomain]

Linear operator stored as a dense array of shape cod.shape + dom.shape.

The adjoint is the conjugate transpose on Euclidean spaces and R_X^{-1} A^dagger R_Y otherwise. The array is used as given, not copied or cast.

Parameters:
  • A (DenseArray) – Operator tensor of shape cod.shape + dom.shape.

  • dom (Space) – Domain.

  • cod (Space or None, optional) – Codomain. When omitted it is a Euclidean space of A’s leading shape, so for an endomorphism of a weighted X pass cod=X explicitly.

  • ctx (Context, str, or None, optional) – Backend context; resolved from the spaces when omitted.

  • check_level ({"none", "cheap", "standard", "strict"}, optional) – Validation level; the ambient default when omitted.

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.])
rvapply(ys)#

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

Parameters:
Return type:

Any

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.

Spelled-out alias for the inner-product adjoint rapply().

Parameters:

y (Any)

Return type:

Any

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.

The level is stored on the bound object (_check_level), seeded at construction. The fallback covers any construction path that assigns _ctx without going through __init__/_bind_context.

property codomain: Codomain#

Codomain space of this linear operator.

convert(new_ctx=None)#

Return this object represented in new_ctx.

The object’s check_level is a property of the object, not of the backend/dtype context, so it is preserved across conversion rather than re-seeded from the ambient default.

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 dense sub-expressions multiplied out (ADR-021).

Each maximal run of dense operators becomes one DenseLinOp; other leaves stay lazy. The result equals self up to rounding. A leaf returns itself.

Parameters:

materialize (bool, optional) – Also densify matrix-free operands, which may be costly; by default they stay lazy.

Returns:

The fused operator.

Return type:

LinOp

rapply(y)#

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

This is the Hilbert-space adjoint A^#, defined by <Ax, y>_Y = <x, A^# y>_X. Trefethen & Bau, Numerical Linear Algebra, Lecture 2, pp. 11-12, gives the conjugate transpose in orthonormal coordinates; spacecore.linop._metric.metric_rapply() derives the Gram-matrix form. The dual map sends a linear functional phi to phi o A; writing phi(y) = b^T y gives dual coordinates A^T b, even over the complex field.

Parameters:

y (Any)

Return type:

Any

same_math(other)#

Tier-1 equality gate: same concrete type and same math context.

Combines a runtime-type identity check with Context.same_math() (equal backend ops and dtype, ignoring check_level — a validation policy, not a mathematical or backend property). This is the mandatory first check in every __eq__: the type gate guarantees other exposes the same attributes this object’s __eq__ will read, and same_math guarantees any later ops.allclose runs only on same-backend, same-dtype arrays, making cross-backend objects compare unequal.

Callers that fail this gate should return NotImplemented so Python can try the reflected comparison and fall back to identity symmetrically.

Parameters:

other (Any)

Return type:

bool

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.SparseLinOp(*args, **kwargs)[source]#

Bases: LinOp[CoordinateSpace, CoordinateSpace]

Linear operator stored as a sparse matrix of shape (cod.size, dom.size).

Domain and codomain must be vector spaces. The adjoint is the conjugate transpose on Euclidean spaces and R_X^{-1} A^dagger R_Y otherwise. The matrix is used as given.

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

  • dom (CoordinateSpace) – Domain.

  • cod (CoordinateSpace) – Codomain.

  • ctx (Context, str, or None, optional) – Backend context; resolved from the spaces when omitted.

  • check_level ({"none", "cheap", "standard", "strict"}, optional) – Validation level; the ambient default when omitted.

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.])
rvapply(ys)#

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

Parameters:
Return type:

Any

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.

Spelled-out alias for the inner-product adjoint rapply().

Parameters:

y (Any)

Return type:

Any

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.

The level is stored on the bound object (_check_level), seeded at construction. The fallback covers any construction path that assigns _ctx without going through __init__/_bind_context.

property codomain: Codomain#

Codomain space of this linear operator.

convert(new_ctx=None)#

Return this object represented in new_ctx.

The object’s check_level is a property of the object, not of the backend/dtype context, so it is preserved across conversion rather than re-seeded from the ambient default.

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 dense sub-expressions multiplied out (ADR-021).

Each maximal run of dense operators becomes one DenseLinOp; other leaves stay lazy. The result equals self up to rounding. A leaf returns itself.

Parameters:

materialize (bool, optional) – Also densify matrix-free operands, which may be costly; by default they stay lazy.

Returns:

The fused operator.

Return type:

LinOp

rapply(y)#

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

This is the Hilbert-space adjoint A^#, defined by <Ax, y>_Y = <x, A^# y>_X. Trefethen & Bau, Numerical Linear Algebra, Lecture 2, pp. 11-12, gives the conjugate transpose in orthonormal coordinates; spacecore.linop._metric.metric_rapply() derives the Gram-matrix form. The dual map sends a linear functional phi to phi o A; writing phi(y) = b^T y gives dual coordinates A^T b, even over the complex field.

Parameters:

y (Any)

Return type:

Any

same_math(other)#

Tier-1 equality gate: same concrete type and same math context.

Combines a runtime-type identity check with Context.same_math() (equal backend ops and dtype, ignoring check_level — a validation policy, not a mathematical or backend property). This is the mandatory first check in every __eq__: the type gate guarantees other exposes the same attributes this object’s __eq__ will read, and same_math guarantees any later ops.allclose runs only on same-backend, same-dtype arrays, making cross-backend objects compare unequal.

Callers that fail this gate should return NotImplemented so Python can try the reflected comparison and fall back to identity symmetrically.

Parameters:

other (Any)

Return type:

bool

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.DiagonalLinOp(*args, **kwargs)[source]#

Bases: LinOp[CoordinateSpace, CoordinateSpace]

Coordinatewise map x -> diagonal * x on one space.

The adjoint conjugates the diagonal on Euclidean spaces and is R_X^{-1} D^dagger R_X otherwise.

Parameters:
  • diagonal (DenseArray) – Diagonal of shape space.shape.

  • space (Space or None, optional) – Domain and codomain; a vector space of diagonal.shape when omitted.

  • ctx (Context, str, or None, optional) – Backend context; resolved from space when omitted.

  • check_level ({"none", "cheap", "standard", "strict"}, optional) – Validation level; the ambient default when omitted.

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.])
rvapply(ys)#

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

Parameters:
Return type:

Any

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.

Spelled-out alias for the inner-product adjoint rapply().

Parameters:

y (Any)

Return type:

Any

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.

The level is stored on the bound object (_check_level), seeded at construction. The fallback covers any construction path that assigns _ctx without going through __init__/_bind_context.

property codomain: Codomain#

Codomain space of this linear operator.

convert(new_ctx=None)#

Return this object represented in new_ctx.

The object’s check_level is a property of the object, not of the backend/dtype context, so it is preserved across conversion rather than re-seeded from the ambient default.

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 dense sub-expressions multiplied out (ADR-021).

Each maximal run of dense operators becomes one DenseLinOp; other leaves stay lazy. The result equals self up to rounding. A leaf returns itself.

Parameters:

materialize (bool, optional) – Also densify matrix-free operands, which may be costly; by default they stay lazy.

Returns:

The fused operator.

Return type:

LinOp

rapply(y)#

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

This is the Hilbert-space adjoint A^#, defined by <Ax, y>_Y = <x, A^# y>_X. Trefethen & Bau, Numerical Linear Algebra, Lecture 2, pp. 11-12, gives the conjugate transpose in orthonormal coordinates; spacecore.linop._metric.metric_rapply() derives the Gram-matrix form. The dual map sends a linear functional phi to phi o A; writing phi(y) = b^T y gives dual coordinates A^T b, even over the complex field.

Parameters:

y (Any)

Return type:

Any

same_math(other)#

Tier-1 equality gate: same concrete type and same math context.

Combines a runtime-type identity check with Context.same_math() (equal backend ops and dtype, ignoring check_level — a validation policy, not a mathematical or backend property). This is the mandatory first check in every __eq__: the type gate guarantees other exposes the same attributes this object’s __eq__ will read, and same_math guarantees any later ops.allclose runs only on same-backend, same-dtype arrays, making cross-backend objects compare unequal.

Callers that fail this gate should return NotImplemented so Python can try the reflected comparison and fall back to identity symmetrically.

Parameters:

other (Any)

Return type:

bool

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.MatrixFreeLinOp(*args, **kwargs)[source]#

Bases: LinOp[Domain, Codomain]

Linear operator given by forward and adjoint callables; no matrix is stored.

rapply must be the adjoint in the declared inner products, <apply(x), y>_Y = <x, rapply(y)>_X. It is used as given unless euclidean_adjoint=True, and no adjoint test runs at construction.

Parameters:
  • apply (callable) – Forward map apply(x) from dom to cod.

  • rapply (callable) – Adjoint map rapply(y) from cod to dom.

  • dom (Space) – Domain.

  • cod (Space) – Codomain.

  • ctx (Context, str, or None, optional) – Backend context; inferred from dom and cod when omitted.

  • vapply (callable or None, optional) – Batched forward map; vmap of apply by default.

  • rvapply (callable or None, optional) – Batched adjoint; vmap of rapply by default.

  • check_level ({"none", "cheap", "standard", "strict"}, optional) – Validation level; the ambient default when omitted.

  • euclidean_adjoint (bool, optional) – True: rapply is the coordinate adjoint and is wrapped into R_X^-1 A^T R_Y (see from_coordinate_adjoint()). False: it is the metric adjoint. Omitting it on a non-Euclidean space warns, because a coordinate adjoint there is silently wrong.

  • hermitian (bool or None, optional) – Declared self-adjointness, returned by is_hermitian() so solvers need no probe; None (the default) means unknown. At strict checks a declared True is probed once against rapply.

is_hermitian()[source]#

Return the declared self-adjointness (None when not declared).

Return type:

bool | 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.

The stored adjoint is the metric one, R_X^-1 A^T R_Y, which is the given callable itself on Euclidean spaces. A non-Euclidean space without usable Riesz maps is rejected.

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

  • coordinate_rapply (callable) – Euclidean coordinate adjoint of apply.

  • dom (Space) – Domain and codomain.

  • cod (Space) – Domain and codomain.

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

  • vapply (callable or None, optional) – Batched forward map.

  • coordinate_rvapply (callable or None, optional) – Batched coordinate adjoint, wrapped the same way; vmap of the wrapped adjoint by default.

Return type:

MatrixFreeLinOp

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.

Spelled-out alias for the inner-product adjoint rapply().

Parameters:

y (Any)

Return type:

Any

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.

The level is stored on the bound object (_check_level), seeded at construction. The fallback covers any construction path that assigns _ctx without going through __init__/_bind_context.

property codomain: Codomain#

Codomain space of this linear operator.

convert(new_ctx=None)#

Return this object represented in new_ctx.

The object’s check_level is a property of the object, not of the backend/dtype context, so it is preserved across conversion rather than re-seeded from the ambient default.

Parameters:

new_ctx (Context | BackendFamily | str | None)

Return type:

Self

property domain: Domain#

Domain space of this linear operator.

rapply(y)#

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

This is the Hilbert-space adjoint A^#, defined by <Ax, y>_Y = <x, A^# y>_X. Trefethen & Bau, Numerical Linear Algebra, Lecture 2, pp. 11-12, gives the conjugate transpose in orthonormal coordinates; spacecore.linop._metric.metric_rapply() derives the Gram-matrix form. The dual map sends a linear functional phi to phi o A; writing phi(y) = b^T y gives dual coordinates A^T b, even over the complex field.

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

same_math(other)#

Tier-1 equality gate: same concrete type and same math context.

Combines a runtime-type identity check with Context.same_math() (equal backend ops and dtype, ignoring check_level — a validation policy, not a mathematical or backend property). This is the mandatory first check in every __eq__: the type gate guarantees other exposes the same attributes this object’s __eq__ will read, and same_math guarantees any later ops.allclose runs only on same-backend, same-dtype arrays, making cross-backend objects compare unequal.

Callers that fail this gate should return NotImplemented so Python can try the reflected comparison and fall back to identity symmetrically.

Parameters:

other (Any)

Return type:

bool

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.IdentityLinOp(*args, **kwargs)[source]#

Bases: LinOp[Domain, Domain]

Lazy identity map on space.

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

  • ctx (Context, str, or None, optional) – Backend context; resolved from space when omitted.

  • check_level ({"none", "cheap", "standard", "strict"}, optional) – Validation level; the ambient default when omitted.

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.

Spelled-out alias for the inner-product adjoint rapply().

Parameters:

y (Any)

Return type:

Any

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.

The level is stored on the bound object (_check_level), seeded at construction. The fallback covers any construction path that assigns _ctx without going through __init__/_bind_context.

property codomain: Codomain#

Codomain space of this linear operator.

convert(new_ctx=None)#

Return this object represented in new_ctx.

The object’s check_level is a property of the object, not of the backend/dtype context, so it is preserved across conversion rather than re-seeded from the ambient default.

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 dense sub-expressions multiplied out (ADR-021).

Each maximal run of dense operators becomes one DenseLinOp; other leaves stay lazy. The result equals self up to rounding. A leaf returns itself.

Parameters:

materialize (bool, optional) – Also densify matrix-free operands, which may be costly; by default they stay lazy.

Returns:

The fused operator.

Return type:

LinOp

rapply(y)#

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

This is the Hilbert-space adjoint A^#, defined by <Ax, y>_Y = <x, A^# y>_X. Trefethen & Bau, Numerical Linear Algebra, Lecture 2, pp. 11-12, gives the conjugate transpose in orthonormal coordinates; spacecore.linop._metric.metric_rapply() derives the Gram-matrix form. The dual map sends a linear functional phi to phi o A; writing phi(y) = b^T y gives dual coordinates A^T b, even over the complex field.

Parameters:

y (Any)

Return type:

Any

same_math(other)#

Tier-1 equality gate: same concrete type and same math context.

Combines a runtime-type identity check with Context.same_math() (equal backend ops and dtype, ignoring check_level — a validation policy, not a mathematical or backend property). This is the mandatory first check in every __eq__: the type gate guarantees other exposes the same attributes this object’s __eq__ will read, and same_math guarantees any later ops.allclose runs only on same-backend, same-dtype arrays, making cross-backend objects compare unequal.

Callers that fail this gate should return NotImplemented so Python can try the reflected comparison and fall back to identity symmetrically.

Parameters:

other (Any)

Return type:

bool

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(*args, **kwargs)[source]#

Bases: ZeroNode, LinOp[Domain, Codomain]

Lazy zero map from dom to cod.

Parameters:
  • dom (Space) – Domain.

  • cod (Space) – Codomain.

  • ctx (Context, str, or None, optional) – Backend context; resolved from the spaces when omitted.

  • check_level ({"none", "cheap", "standard", "strict"}, optional) – Validation level; the ambient default when omitted.

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.

Spelled-out alias for the inner-product adjoint rapply().

Parameters:

y (Any)

Return type:

Any

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.

The level is stored on the bound object (_check_level), seeded at construction. The fallback covers any construction path that assigns _ctx without going through __init__/_bind_context.

property codomain: Codomain#

Codomain space of this linear operator.

convert(new_ctx=None)#

Return this object represented in new_ctx.

The object’s check_level is a property of the object, not of the backend/dtype context, so it is preserved across conversion rather than re-seeded from the ambient default.

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 dense sub-expressions multiplied out (ADR-021).

Each maximal run of dense operators becomes one DenseLinOp; other leaves stay lazy. The result equals self up to rounding. A leaf returns itself.

Parameters:

materialize (bool, optional) – Also densify matrix-free operands, which may be costly; by default they stay lazy.

Returns:

The fused operator.

Return type:

LinOp

rapply(y)#

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

This is the Hilbert-space adjoint A^#, defined by <Ax, y>_Y = <x, A^# y>_X. Trefethen & Bau, Numerical Linear Algebra, Lecture 2, pp. 11-12, gives the conjugate transpose in orthonormal coordinates; spacecore.linop._metric.metric_rapply() derives the Gram-matrix form. The dual map sends a linear functional phi to phi o A; writing phi(y) = b^T y gives dual coordinates A^T b, even over the complex field.

Parameters:

y (Any)

Return type:

Any

same_math(other)#

Tier-1 equality gate: same concrete type and same math context.

Combines a runtime-type identity check with Context.same_math() (equal backend ops and dtype, ignoring check_level — a validation policy, not a mathematical or backend property). This is the mandatory first check in every __eq__: the type gate guarantees other exposes the same attributes this object’s __eq__ will read, and same_math guarantees any later ops.allclose runs only on same-backend, same-dtype arrays, making cross-backend objects compare unequal.

Callers that fail this gate should return NotImplemented so Python can try the reflected comparison and fall back to identity symmetrically.

Parameters:

other (Any)

Return type:

bool

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(*args, **kwargs)[source]#

Bases: SumNode, LinOp[Domain, Codomain]

Lazy sum of operators sharing context, domain and codomain.

Parameters:
  • ops (sequence of LinOp) – The summands.

  • check_level ({"none", "cheap", "standard", "strict"}, optional) – Validation level; the ambient default when omitted.

property ops_tuple: tuple[LinOp[Domain, Codomain], ...]#

The summed operators (alias of parts).

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.

Spelled-out alias for the inner-product adjoint rapply().

Parameters:

y (Any)

Return type:

Any

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.

The level is stored on the bound object (_check_level), seeded at construction. The fallback covers any construction path that assigns _ctx without going through __init__/_bind_context.

property codomain: Codomain#

Codomain space of this linear operator.

convert(new_ctx=None)#

Return this object represented in new_ctx.

The object’s check_level is a property of the object, not of the backend/dtype context, so it is preserved across conversion rather than re-seeded from the ambient default.

Parameters:

new_ctx (Context | BackendFamily | str | None)

Return type:

Self

property domain: Domain#

Domain space of this linear operator.

rapply(y)#

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

This is the Hilbert-space adjoint A^#, defined by <Ax, y>_Y = <x, A^# y>_X. Trefethen & Bau, Numerical Linear Algebra, Lecture 2, pp. 11-12, gives the conjugate transpose in orthonormal coordinates; spacecore.linop._metric.metric_rapply() derives the Gram-matrix form. The dual map sends a linear functional phi to phi o A; writing phi(y) = b^T y gives dual coordinates A^T b, even over the complex field.

Parameters:

y (Any)

Return type:

Any

same_math(other)#

Tier-1 equality gate: same concrete type and same math context.

Combines a runtime-type identity check with Context.same_math() (equal backend ops and dtype, ignoring check_level — a validation policy, not a mathematical or backend property). This is the mandatory first check in every __eq__: the type gate guarantees other exposes the same attributes this object’s __eq__ will read, and same_math guarantees any later ops.allclose runs only on same-backend, same-dtype arrays, making cross-backend objects compare unequal.

Callers that fail this gate should return NotImplemented so Python can try the reflected comparison and fall back to identity symmetrically.

Parameters:

other (Any)

Return type:

bool

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(*args, **kwargs)[source]#

Bases: LinOp[Domain, Codomain]

Lazy composition left @ right; the adjoint is right.H @ left.H.

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

  • right (LinOp) – Operator applied first; right.codomain must equal left.domain.

  • check_level ({"none", "cheap", "standard", "strict"}, optional) – Validation level; the ambient default when omitted.

vapply(xs)[source]#

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

Parameters:

xs (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

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 True for an adjoint-palindromic chain (R.H @ R, L @ V @ L.H with V self-adjoint), else None.

Returns:

True when structurally Hermitian, None when 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.

Spelled-out alias for the inner-product adjoint rapply().

Parameters:

y (Any)

Return type:

Any

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.

The level is stored on the bound object (_check_level), seeded at construction. The fallback covers any construction path that assigns _ctx without going through __init__/_bind_context.

property codomain: Codomain#

Codomain space of this linear operator.

convert(new_ctx=None)#

Return this object represented in new_ctx.

The object’s check_level is a property of the object, not of the backend/dtype context, so it is preserved across conversion rather than re-seeded from the ambient default.

Parameters:

new_ctx (Context | BackendFamily | str | None)

Return type:

Self

property domain: Domain#

Domain space of this linear operator.

rapply(y)#

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

This is the Hilbert-space adjoint A^#, defined by <Ax, y>_Y = <x, A^# y>_X. Trefethen & Bau, Numerical Linear Algebra, Lecture 2, pp. 11-12, gives the conjugate transpose in orthonormal coordinates; spacecore.linop._metric.metric_rapply() derives the Gram-matrix form. The dual map sends a linear functional phi to phi o A; writing phi(y) = b^T y gives dual coordinates A^T b, even over the complex field.

Parameters:

y (Any)

Return type:

Any

same_math(other)#

Tier-1 equality gate: same concrete type and same math context.

Combines a runtime-type identity check with Context.same_math() (equal backend ops and dtype, ignoring check_level — a validation policy, not a mathematical or backend property). This is the mandatory first check in every __eq__: the type gate guarantees other exposes the same attributes this object’s __eq__ will read, and same_math guarantees any later ops.allclose runs only on same-backend, same-dtype arrays, making cross-backend objects compare unequal.

Callers that fail this gate should return NotImplemented so Python can try the reflected comparison and fall back to identity symmetrically.

Parameters:

other (Any)

Return type:

bool

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(*args, **kwargs)[source]#

Bases: ScaledNode, LinOp[Domain, Codomain]

Lazy scalar * op; its adjoint is conj(scalar) * op.H.

Parameters:
  • scalar (scalar-like) – Coefficient.

  • op (LinOp) – Operator to scale.

  • check_level ({"none", "cheap", "standard", "strict"}, optional) – Validation level; the ambient default when omitted.

property op: LinOp[Domain, Codomain]#

The scaled operand.

vapply(xs)[source]#

Return scalar * op.vapply(xs).

Parameters:

xs (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

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 op.is_hermitian() for a real scalar, else None (undecided).

Returns:

Structural Hermiticity, or None when 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.

Spelled-out alias for the inner-product adjoint rapply().

Parameters:

y (Any)

Return type:

Any

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.

The level is stored on the bound object (_check_level), seeded at construction. The fallback covers any construction path that assigns _ctx without going through __init__/_bind_context.

property codomain: Codomain#

Codomain space of this linear operator.

convert(new_ctx=None)#

Return this object represented in new_ctx.

The object’s check_level is a property of the object, not of the backend/dtype context, so it is preserved across conversion rather than re-seeded from the ambient default.

Parameters:

new_ctx (Context | BackendFamily | str | None)

Return type:

Self

property domain: Domain#

Domain space of this linear operator.

rapply(y)#

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

This is the Hilbert-space adjoint A^#, defined by <Ax, y>_Y = <x, A^# y>_X. Trefethen & Bau, Numerical Linear Algebra, Lecture 2, pp. 11-12, gives the conjugate transpose in orthonormal coordinates; spacecore.linop._metric.metric_rapply() derives the Gram-matrix form. The dual map sends a linear functional phi to phi o A; writing phi(y) = b^T y gives dual coordinates A^T b, even over the complex field.

Parameters:

y (Any)

Return type:

Any

same_math(other)#

Tier-1 equality gate: same concrete type and same math context.

Combines a runtime-type identity check with Context.same_math() (equal backend ops and dtype, ignoring check_level — a validation policy, not a mathematical or backend property). This is the mandatory first check in every __eq__: the type gate guarantees other exposes the same attributes this object’s __eq__ will read, and same_math guarantees any later ops.allclose runs only on same-backend, same-dtype arrays, making cross-backend objects compare unequal.

Callers that fail this gate should return NotImplemented so Python can try the reflected comparison and fall back to identity symmetrically.

Parameters:

other (Any)

Return type:

bool

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 the lazy sum of operators, with nested sums flattened and zero terms dropped.

Parameters:

ops (sequence of LinOp) – Operators sharing context, domain and codomain.

Returns:

The sum, the single remaining operand, or a zero operator.

Return type:

LinOp

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

Return left @ right, dropping identity factors and collapsing zero factors.

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

  • right (LinOp) – Operator applied first; right.codomain must equal left.domain.

Returns:

The composition.

Return type:

LinOp

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

Return scalar * op: 0 gives zero, 1 gives op, nested scalars fold.

Parameters:
  • scalar (scalar-like) – Coefficient.

  • op (LinOp) – Operator to scale.

Returns:

The scaled operator.

Return type:

LinOp

class spacecore.linop.TreeLinOp(*args, **kwargs)[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.

  • check_level ({"none", "cheap", "standard", "strict"}, optional) – Validation level; the ambient default when omitted.

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.

Spelled-out alias for the inner-product adjoint rapply().

Parameters:

y (Any)

Return type:

Any

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.

The level is stored on the bound object (_check_level), seeded at construction. The fallback covers any construction path that assigns _ctx without going through __init__/_bind_context.

property codomain: Codomain#

Codomain space of this linear operator.

convert(new_ctx=None)#

Return this object represented in new_ctx.

The object’s check_level is a property of the object, not of the backend/dtype context, so it is preserved across conversion rather than re-seeded from the ambient default.

Parameters:

new_ctx (Context | BackendFamily | str | None)

Return type:

Self

property domain: Domain#

Domain space of this linear operator.

abstractmethod classmethod from_operators(parts)[source]#

Build a tree-structured operator from component operators.

Parameters:

parts (Tuple[LinOp, ...])

Return type:

TreeLinOp

fuse(*, materialize=False)#

Return an equivalent operator with dense sub-expressions multiplied out (ADR-021).

Each maximal run of dense operators becomes one DenseLinOp; other leaves stay lazy. The result equals self up to rounding. A leaf returns itself.

Parameters:

materialize (bool, optional) – Also densify matrix-free operands, which may be costly; by default they stay lazy.

Returns:

The fused operator.

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

rapply(y)#

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

This is the Hilbert-space adjoint A^#, defined by <Ax, y>_Y = <x, A^# y>_X. Trefethen & Bau, Numerical Linear Algebra, Lecture 2, pp. 11-12, gives the conjugate transpose in orthonormal coordinates; spacecore.linop._metric.metric_rapply() derives the Gram-matrix form. The dual map sends a linear functional phi to phi o A; writing phi(y) = b^T y gives dual coordinates A^T b, even over the complex field.

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

same_math(other)#

Tier-1 equality gate: same concrete type and same math context.

Combines a runtime-type identity check with Context.same_math() (equal backend ops and dtype, ignoring check_level — a validation policy, not a mathematical or backend property). This is the mandatory first check in every __eq__: the type gate guarantees other exposes the same attributes this object’s __eq__ will read, and same_math guarantees any later ops.allclose runs only on same-backend, same-dtype arrays, making cross-backend objects compare unequal.

Callers that fail this gate should return NotImplemented so Python can try the reflected comparison and fall back to identity symmetrically.

Parameters:

other (Any)

Return type:

bool

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(*args, **kwargs)[source]#

Bases: TreeLinOp[TreeSpace, TreeSpace]

Independent blocks acting on the leaves of a direct-product tree.

BlockDiagonalLinOp(blocks) infers both tree spaces from the blocks; the tree of blocks is the element structure. This is a direct product, not a Kronecker product.

Parameters:
  • blocks (tree of LinOp) – Block tree, each leaf mapping X_i to Y_i.

  • ctx (Context, str, or None, optional) – Backend context; resolved from the blocks when omitted.

  • check_level ({"none", "cheap", "standard", "strict"}, optional) – Validation level; the ambient default when omitted.

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.

Spelled-out alias for the inner-product adjoint rapply().

Parameters:

y (Any)

Return type:

Any

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.

The level is stored on the bound object (_check_level), seeded at construction. The fallback covers any construction path that assigns _ctx without going through __init__/_bind_context.

property codomain: Codomain#

Codomain space of this linear operator.

convert(new_ctx=None)#

Return this object represented in new_ctx.

The object’s check_level is a property of the object, not of the backend/dtype context, so it is preserved across conversion rather than re-seeded from the ambient default.

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

rapply(y)#

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

This is the Hilbert-space adjoint A^#, defined by <Ax, y>_Y = <x, A^# y>_X. Trefethen & Bau, Numerical Linear Algebra, Lecture 2, pp. 11-12, gives the conjugate transpose in orthonormal coordinates; spacecore.linop._metric.metric_rapply() derives the Gram-matrix form. The dual map sends a linear functional phi to phi o A; writing phi(y) = b^T y gives dual coordinates A^T b, even over the complex field.

Parameters:

y (Any)

Return type:

Any

same_math(other)#

Tier-1 equality gate: same concrete type and same math context.

Combines a runtime-type identity check with Context.same_math() (equal backend ops and dtype, ignoring check_level — a validation policy, not a mathematical or backend property). This is the mandatory first check in every __eq__: the type gate guarantees other exposes the same attributes this object’s __eq__ will read, and same_math guarantees any later ops.allclose runs only on same-backend, same-dtype arrays, making cross-backend objects compare unequal.

Callers that fail this gate should return NotImplemented so Python can try the reflected comparison and fall back to identity symmetrically.

Parameters:

other (Any)

Return type:

bool

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(*args, **kwargs)[source]#

Bases: TreeLinOp[Any, Any]

Block matrix over direct products: y_i = sum_j A_ij x_j for blocks A_ij : X_j -> Y_i.

The container of rows is the codomain’s tree and the container of each row is the domain’s tree, so a NamedTuple of rows gives NamedTuple codomain elements. A side given as a plain one-element list or tuple is a single space, not a one-leaf tree: BlockMatrixLinOp([[A1], [A2]]) stacks x -> (A1 x, A2 x) and BlockMatrixLinOp([[A1, A2]]) sums (x1, x2) -> A1 x1 + A2 x2; each is the other’s adjoint when the blocks are replaced by theirs.

Parameters:
  • block_rows (sequence of sequences of LinOp) – Rectangular block matrix; rows share codomains and columns share domains.

  • check_level ({"none", "cheap", "standard", "strict"}, optional) – Validation level; the ambient default when omitted.

is_hermitian()[source]#

Return True when the block layout is square, A_ij# == A_ji and every diagonal block is self-adjoint.

Return type:

bool | None

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 block layout.

Parameters:

materialize (bool)

Return type:

BlockMatrixLinOp

classmethod from_operators(parts)[source]#

Build a one-row block matrix, (x_1, ..., x_k) -> sum_j A_j x_j.

Parameters:

parts (Sequence[LinOp])

Return type:

BlockMatrixLinOp

classmethod stack(parts)[source]#

Build a one-column block matrix, x -> (A_1 x, ..., A_k x).

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.

Spelled-out alias for the inner-product adjoint rapply().

Parameters:

y (Any)

Return type:

Any

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.

The level is stored on the bound object (_check_level), seeded at construction. The fallback covers any construction path that assigns _ctx without going through __init__/_bind_context.

property codomain: Codomain#

Codomain space of this linear operator.

convert(new_ctx=None)#

Return this object represented in new_ctx.

The object’s check_level is a property of the object, not of the backend/dtype context, so it is preserved across conversion rather than re-seeded from the ambient default.

Parameters:

new_ctx (Context | BackendFamily | str | None)

Return type:

Self

property domain: Domain#

Domain space of this linear operator.

rapply(y)#

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

This is the Hilbert-space adjoint A^#, defined by <Ax, y>_Y = <x, A^# y>_X. Trefethen & Bau, Numerical Linear Algebra, Lecture 2, pp. 11-12, gives the conjugate transpose in orthonormal coordinates; spacecore.linop._metric.metric_rapply() derives the Gram-matrix form. The dual map sends a linear functional phi to phi o A; writing phi(y) = b^T y gives dual coordinates A^T b, even over the complex field.

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

same_math(other)#

Tier-1 equality gate: same concrete type and same math context.

Combines a runtime-type identity check with Context.same_math() (equal backend ops and dtype, ignoring check_level — a validation policy, not a mathematical or backend property). This is the mandatory first check in every __eq__: the type gate guarantees other exposes the same attributes this object’s __eq__ will read, and same_math guarantees any later ops.allclose runs only on same-backend, same-dtype arrays, making cross-backend objects compare unequal.

Callers that fail this gate should return NotImplemented so Python can try the reflected comparison and fall back to identity symmetrically.

Parameters:

other (Any)

Return type:

bool

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