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#
Represent a dense coordinate tensor-backed linear operator. |
|
Represent a sparse coordinate matrix-backed linear operator. |
|
Represent a coordinatewise diagonal linear operator. |
DenseLinOpstores a dense matrix or tensor interpreted in domain and codomain coordinates.SparseLinOpstores a backend sparse matrix for sparse forward and adjoint products.DiagonalLinOpstores coordinate diagonal entries for a mapX -> X.
Matrix-free and canonical operators#
Represent a linear map between two spaces. |
|
Linear operator defined by user-supplied forward and reverse callables. |
|
Lazy identity map on a space. |
|
Lazy zero map between two spaces. |
LinOpis the abstract base contract.MatrixFreeLinOpwraps forward and metric-adjoint callables exactly as supplied; it does not derive or Riesz-correct matrix-free adjoints.IdentityLinOprepresentsI : X -> X.ZeroLinOprepresents0 : X -> Y.
Algebraic operators#
Lazy finite sum of linear operators with common spaces. |
|
Lazy composition of two linear operators. |
|
Lazy scalar multiple of a linear operator. |
|
Return a locally simplified lazy sum of linear operators. |
|
Return a locally simplified composition of two linear operators. |
|
Return a locally simplified scalar multiple of a linear operator. |
SumLinOprepresentsA + Bfor maps with the same domain and codomain.ComposedLinOprepresentsA @ BwhereB.codomain == A.domain.ScaledLinOprepresentsalpha * A.Helper constructors perform the same simplifications used by Python operator overloads.
Tree and block operators#
Define a base class for operators assembled from component operators. |
|
Represent independent blocks over a finite direct-product tree. |
|
Represent a rectangular matrix of blocks over direct products. |
|
Represent operators from one domain as a tree-valued map. |
|
Represent a sum of leaf operators from a tree domain. |
TreeLinOpis the base for operators with aTreeSpacedomain or codomain.BlockDiagonalLinOp(blocks)maps corresponding tree leaves independently and infers bothTreeSpaceendpoints from the block domains and codomains.BlockMatrixLinOp(block_rows)computes row sums for a rectangular matrix of compatible blocks and infers tuple-structuredTreeSpaceendpoints.StackedLinOpmaps one domain into a tree codomain.SumToSingleLinOpmaps 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:
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:
TrueorFalsewhen the subclass can verify the structure cheaply, otherwiseNonefor 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; usemoveaxisfor 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; usemoveaxisfor 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:
- 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
DenseLinOpholding 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
selfbut 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. WithTruethe caller explicitly accepts giving up the matrix-free contract: a matrix-free operand is densified into aDenseLinOp(via itsto_densebasis 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:
- 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_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 callsvapply()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_codomain(y)[source]#
Raise if
yis 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.
- 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 shapecod.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 asR_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.])
- 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; usemoveaxisfor 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; usemoveaxisfor 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:
TrueorFalsewhen the structure can be checked, otherwiseNone.- 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:
- adjoint_apply(y)#
Apply the adjoint of this linear operator to
y.- Parameters:
y (Any)
- Return type:
Any
- assert_codomain(y)#
Raise if
yis not in the codomain.- Parameters:
y (Any)
- Return type:
None
- assert_domain(x)#
Raise if
xis 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
DenseLinOpholding 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
selfbut 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. WithTruethe caller explicitly accepts giving up the matrix-free contract: a matrix-free operand is densified into aDenseLinOp(via itsto_densebasis 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:
- 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 ofVectorSpaceare supported, but product spaces and other non-vector spaces are intentionally rejected. The conceptual operator tensor has shapecod.shape + dom.shapewhile 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 @ xin 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; usemoveaxisfor 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; usemoveaxisfor 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:
TrueorFalsewhen the structure can be checked, otherwiseNone.- 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:
- adjoint_apply(y)#
Apply the adjoint of this linear operator to
y.- Parameters:
y (Any)
- Return type:
Any
- assert_codomain(y)#
Raise if
yis not in the codomain.- Parameters:
y (Any)
- Return type:
None
- assert_domain(x)#
Raise if
xis 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
DenseLinOpholding 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
selfbut 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. WithTruethe caller explicitly accepts giving up the matrix-free contract: a matrix-free operand is densified into aDenseLinOp(via itsto_densebasis 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:
- class spacecore.linop.DiagonalLinOp(diagonal, space=None, ctx=None)[source]#
Bases:
LinOp[CoordinateSpace,CoordinateSpace]Represent a coordinatewise diagonal linear operator.
DiagonalLinOp(diagonal, space)mapsxtodiagonal * xin coordinates. The adjoint is metric-aware: Euclidean spaces use the complex conjugate of the diagonal, while non-Euclidean spaces use their Riesz maps asR_X^{-1} D^dagger R_X.- Parameters:
- 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; usemoveaxisfor 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; usemoveaxisfor 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:
TrueorFalsewhen the structure can be checked, otherwiseNone.- 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:
- adjoint_apply(y)#
Apply the adjoint of this linear operator to
y.- Parameters:
y (Any)
- Return type:
Any
- assert_codomain(y)#
Raise if
yis not in the codomain.- Parameters:
y (Any)
- Return type:
None
- assert_domain(x)#
Raise if
xis 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
DenseLinOpholding 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
selfbut 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. WithTruethe caller explicitly accepts giving up the matrix-free contract: a matrix-free operand is densified into aDenseLinOp(via itsto_densebasis 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:
- 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 mapA : X -> Ywithout storing or materializing a matrix. The context is resolved from the optionalctxargument and the spaces, then the spaces are converted to that context.The forward action is
apply(x) = apply_fn(x)forx in X. The reverse action israpply(y) = rapply_fn(y)fory in Y. The suppliedrapplycallable 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 asrapply. 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) -> Anyimplementing the forward map fromdomtocod.rapply (callable) – Callable with signature
rapply(y: Any) -> Anyimplementing the true space adjoint map fromcodback todom. For non-Euclidean spaces this is generally not the same as the Euclidean coordinate adjoint.dom (Space) – Domain space containing valid inputs for
applyand outputs fromrapply.cod (Space) – Codomain space containing outputs from
applyand valid inputs forrapply.ctx (Context, str, or None, optional) – Optional context specification. An explicit context wins over inferred contexts from
domandcod.vapply (callable or None, optional) – Optional callable with signature
vapply(xs: Any) -> Anyfor batched forward application. If omitted, backendvmapfallback is used.rvapply (callable or None, optional) – Optional callable with signature
rvapply(ys: Any) -> Anyfor batched adjoint application. If omitted, backendvmapfallback is used.
- Returns:
Operator using the supplied callables for forward, adjoint, and optionally batched actions.
- Return type:
Notes
See
docs/dev/adr/009_metric_adjoint.mdfor 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 ofdomand returns an element ofcod.rapply (Callable[[Any], Any]) – Callable
rapply(y)that accepts an element ofcodand returns an element ofdom.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
dombatches.rvapply (Callable[[Any], Any] | None) – Optional callable for batched adjoint application over
codbatches.
- 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_rapplyis interpreted as the Euclidean coordinate adjointA^daggerofapply. The storedrapplycallable is the metric adjointA^sharp(y) = R_X^-1 A^dagger R_Y yfor the declared domain
Xand codomainY. 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_rvapplyis 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 scalarrapplyconsistently. Whencoordinate_rvapplyis omitted,rvapply_fnremainsNoneand the normalrvapplyfallback vectorizes the wrapped scalar adjoint.See
docs/dev/adr/009_metric_adjoint.mdfor the full design rationale.- Parameters:
apply (callable) – Forward coordinate action from
domtocod.coordinate_rapply (callable) – Euclidean coordinate adjoint from
codcoordinates todomcoordinates.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
vmapover the wrapped scalarrapply.
- Return type:
- apply(x)[source]#
Apply the forward callable.
- Parameters:
x (Any) – Element of
self.domainpassed toapply_fn.- Returns:
Element of
self.codomainreturned byapply_fn.- Return type:
Any
- rapply(y)[source]#
Apply the adjoint callable.
- Parameters:
y (Any) – Element of
self.codomainpassed torapply_fn.- Returns:
Element of
self.domainreturned byrapply_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.codomainproduced byvapply_fnor 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.domainproduced byrvapply_fnor by the fallback batching implementation.- Return type:
Any
- fuse(*, materialize=False)[source]#
Stay matrix-free unless
materialize=Trueis requested (ADR-021/ADR-008).By default a matrix-free operator is never densified, so it returns itself. With
materialize=Truethe caller explicitly opts into densification: the operator is probed into a dense tensor (viato_dense, the basis sweep) and returned as aDenseLinOp, letting an enclosing expression collapse to a single dense operator.- Parameters:
materialize (bool)
- Return type:
- 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:
- adjoint_apply(y)#
Apply the adjoint of this linear operator to
y.- Parameters:
y (Any)
- Return type:
Any
- assert_codomain(y)#
Raise if
yis not in the codomain.- Parameters:
y (Any)
- Return type:
None
- assert_domain(x)#
Raise if
xis 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:
TrueorFalsewhen the subclass can verify the structure cheaply, otherwiseNonefor 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 callsvapply()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 operatorI_X : X -> X. The context is resolved from the optionalctxargument and the space, and the resulting operator has domain and codomain equal toXin that context.The forward action is
apply(x) = xforx in X. The reverse action israpply(x) = xforx in X.- Parameters:
- vapply(xs)[source]#
Return
xsafter batched domain validation.- Parameters:
xs (Any)
- Return type:
Any
- rvapply(xs)[source]#
Return
xsafter 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:
- adjoint_apply(y)#
Apply the adjoint of this linear operator to
y.- Parameters:
y (Any)
- Return type:
Any
- assert_codomain(y)#
Raise if
yis not in the codomain.- Parameters:
y (Any)
- Return type:
None
- assert_domain(x)#
Raise if
xis 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
DenseLinOpholding 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
selfbut 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. WithTruethe caller explicitly accepts giving up the matrix-free contract: a matrix-free operand is densified into aDenseLinOp(via itsto_densebasis 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:
- 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 callsvapply()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 map0 : X -> Y. The context is resolved from the optionalctxargument and the two spaces, then both spaces are converted to that context. Its domain isXand its codomain isYin the resolved context.The forward action is
apply(x) = 0_Yforx in X. The reverse action israpply(y) = 0_Xfory in Y.- Parameters:
- 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:
Trueexactly 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:
- adjoint_apply(y)#
Apply the adjoint of this linear operator to
y.- Parameters:
y (Any)
- Return type:
Any
- assert_codomain(y)#
Raise if
yis not in the codomain.- Parameters:
y (Any)
- Return type:
None
- assert_domain(x)#
Raise if
xis 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
DenseLinOpholding 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
selfbut 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. WithTruethe caller explicitly accepts giving up the matrix-free contract: a matrix-free operand is densified into aDenseLinOp(via itsto_densebasis 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:
- 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 callsvapply()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))representsA1 + ... + Akfor a nonempty sequence ofLinOpinstances. All operands must have the samectx, 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 elementx. The reverse action israpply(y) = sum_i Ai.rapply(y)for the shared codomain elementy.- Parameters:
ops (sequence of LinOp) – Nonempty sequence of operators with common context, domain, and codomain.
- 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:
- 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 returnsTrue; otherwise the verdict is not cheaply decidable (a sum of non-Hermitian terms may still be Hermitian) andNoneis returned.Falseis never returned.- Returns:
Truewhen every part is provably Hermitian, otherwiseNone.- 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:
- adjoint_apply(y)#
Apply the adjoint of this linear operator to
y.- Parameters:
y (Any)
- Return type:
Any
- assert_codomain(y)#
Raise if
yis not in the codomain.- Parameters:
y (Any)
- Return type:
None
- assert_domain(x)#
Raise if
xis 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 callsvapply()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)representsA @ B = A circ B. The operands must have the samectxbefore construction, andB.codomainmust equalA.domain. The resulting operator has domainB.domainand codomainA.codomain.The forward action is
apply(x) = A.apply(B.apply(x))forx in B.domain. The reverse action israpply(z) = B.rapply(A.rapply(z))forz in A.codomain.- 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 @ Bwith a singleDenseLinOpholding 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 equalsB* @ A*up to floating-point rounding.- Parameters:
materialize (bool)
- Return type:
- is_hermitian()[source]#
Return whether this composition is structurally Hermitian.
A Gram product
R* @ R(equivalentlyL @ L*) is self-adjoint in any geometry, since<R* R x, y> = <R x, R y> = <x, R* R y>. This is detected structurally whenself.left == self.right.H(the adjoint view compares its wrapped operand, so this also matchesL @ L*). Any other composition is not cheaply decidable and returnsNone; non-Hermiticity is never asserted.- Returns:
Truefor a Gram product, otherwiseNone.- 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:
- adjoint_apply(y)#
Apply the adjoint of this linear operator to
y.- Parameters:
y (Any)
- Return type:
Any
- assert_codomain(y)#
Raise if
yis not in the codomain.- Parameters:
y (Any)
- Return type:
None
- assert_domain(x)#
Raise if
xis 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 callsvapply()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 operatoralpha * A. Its context is exactlyA.ctx; its domain isA.domainand its codomain isA.codomain. No dense matrix representation is formed.The forward action is
apply(x) = alpha * A.apply(x)forx in A.domain. The reverse action israpply(y) = conj(alpha) * A.rapply(y)fory 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
- 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 · Awith oneDenseLinOpholdingc · M_A; otherwise keep the scaling lazy, so a matrix-free operand is never densified. Adjoint-consistent: the fused operator’s adjoint isconj(c) · M_A^*(with the metric), exactly the lazyScaledLinOpadjoint.- Parameters:
materialize (bool)
- Return type:
- is_hermitian()[source]#
Return whether this scaled operator is structurally Hermitian.
For a real scalar
sthe adjoint satisfies(s A)* = s A*, sos Ais self-adjoint exactly whenAis; the operand’s verdict is propagated faithfully. For a non-real scalar the relation becomes(s A)* = conj(s) A* != s Ain general, so Hermiticity cannot be decided cheaply andNoneis returned.- Returns:
self.op.is_hermitian()whenself.scalaris real, otherwiseNonefor 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:
- adjoint_apply(y)#
Apply the adjoint of this linear operator to
y.- Parameters:
y (Any)
- Return type:
Any
- assert_codomain(y)#
Raise if
yis not in the codomain.- Parameters:
y (Any)
- Return type:
None
- assert_domain(x)#
Raise if
xis 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 callsvapply()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
SumLinOpnodes are flattened andZeroLinOpterms 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.
- 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
ComposedLinOprepresentation 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.
- 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
ScaledLinOpnodes are folded into one scalar. It does not distribute scaling over sums or perform full symbolic optimization. Complex scalars retain the usual conjugated coefficient inrapplythroughScaledLinOp.
- 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:
- abstractmethod classmethod from_operators(parts)[source]#
Build a tree-structured operator from component operators.
- 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:
- 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
yis not in the codomain.- Parameters:
y (Any)
- Return type:
None
- assert_domain(x)#
Raise if
xis 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
DenseLinOpholding 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
selfbut 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. WithTruethe caller explicitly accepts giving up the matrix-free contract: a matrix-free operand is densified into aDenseLinOp(via itsto_densebasis 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:
- is_hermitian()#
Return whether this operator is structurally Hermitian when known.
- Returns:
TrueorFalsewhen the subclass can verify the structure cheaply, otherwiseNonefor 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; usemoveaxisfor 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 callsvapply()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; usemoveaxisfor 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 codomainTreeSpaceobjects from the block domains and codomains. The Python tree structure ofblocksis 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_imaps the corresponding domain leafX_ito codomain leafY_i. In the legacy four-argument form this is instead the domainTreeSpace.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
blocksotherwise.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:
- classmethod from_operators(parts)[source]#
Build a tuple-structured block-diagonal operator.
- Parameters:
parts (Sequence[LinOp])
- Return type:
- adjoint_apply(y)#
Apply the adjoint of this linear operator to
y.- Parameters:
y (Any)
- Return type:
Any
- assert_codomain(y)#
Raise if
yis not in the codomain.- Parameters:
y (Any)
- Return type:
None
- assert_domain(x)#
Raise if
xis 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:
TrueorFalsewhen the subclass can verify the structure cheaply, otherwiseNonefor 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 callsvapply()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 mapsX_0 x ... x X_ntoY_0 x ... x Y_mand computesy_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:
- classmethod from_operators(parts)[source]#
Build a one-row block matrix from a sequence of operators.
- Parameters:
parts (Sequence[LinOp])
- Return type:
- adjoint_apply(y)#
Apply the adjoint of this linear operator to
y.- Parameters:
y (Any)
- Return type:
Any
- assert_codomain(y)#
Raise if
yis not in the codomain.- Parameters:
y (Any)
- Return type:
None
- assert_domain(x)#
Raise if
xis 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:
TrueorFalsewhen the subclass can verify the structure cheaply, otherwiseNonefor 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 callsvapply()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 = Xandcod = Y1 x ... x Yk, componentparts[i]mapsXtoYi. Forward application returns a value withcod.treedef; adjoint application sums component adjoints inX.- Parameters:
- 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:
- classmethod from_operators(parts)[source]#
Build a stacked operator from component operators.
- Parameters:
parts (Tuple[LinOp, ...])
- Return type:
- 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:
- adjoint_apply(y)#
Apply the adjoint of this linear operator to
y.- Parameters:
y (Any)
- Return type:
Any
- assert_codomain(y)#
Raise if
yis not in the codomain.- Parameters:
y (Any)
- Return type:
None
- assert_domain(x)#
Raise if
xis 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:
TrueorFalsewhen the subclass can verify the structure cheaply, otherwiseNonefor 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 callsvapply()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 Xkandcod = Y, componentparts[i]mapsXitoY. Forward application sums component outputs inY; adjoint application returns a value withdom.treedef.- Parameters:
- 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:
- classmethod from_operators(parts)[source]#
Build a sum-to-single operator from component operators.
- Parameters:
parts (Tuple[LinOp, ...])
- Return type:
- 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:
- adjoint_apply(y)#
Apply the adjoint of this linear operator to
y.- Parameters:
y (Any)
- Return type:
Any
- assert_codomain(y)#
Raise if
yis not in the codomain.- Parameters:
y (Any)
- Return type:
None
- assert_domain(x)#
Raise if
xis 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:
TrueorFalsewhen the subclass can verify the structure cheaply, otherwiseNonefor 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 callsvapply()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()#