xrtoolz.linalg — API¶
Proposed public surface.
Imports¶
import xrtoolz.linalg as xla # Function API + NamedOperator
from xrtoolz.linalg import ( # Operator API
Solve, Logdet, Cholesky, Diag, Trace, Inv, Sqrt,
Sample, LogProb,
NamedOperator, MultivariateNormal,
)
Importing xrtoolz.linalg does not import JAX or gaussx; the first
call into a primitive triggers the lazy import.
NamedOperator¶
@dataclass(frozen=True)
class NamedOperator:
operator: lineax.AbstractLinearOperator
dims: tuple[str, ...]
coords: Mapping[str, xr.DataArray] | None = None
# operator algebra
def __add__(self, other: NamedOperator) -> NamedOperator: ...
def __sub__(self, other: NamedOperator) -> NamedOperator: ...
def __mul__(self, scalar: float | jax.Array) -> NamedOperator: ...
def __matmul__(
self, other: NamedOperator | xr.DataArray,
) -> NamedOperator | xr.DataArray: ...
@property
def T(self) -> NamedOperator: ...
# materialisation
def materialize(self) -> jax.Array:
"""Return the dense (N, N) matrix. Loses structure."""
def to_dataarray(
self, *, row_suffix: str = "_row", col_suffix: str = "_col",
) -> xr.DataArray:
"""Return a 2D labeled view of the operator.
Row and column dims are renamed by appending ``row_suffix``
and ``col_suffix`` to the original ``dims`` so the rendered
matrix has distinct row/column labels.
"""
# constructors
@classmethod
def from_dense(cls, da: xr.DataArray, *, row_dim: str, col_dim: str) -> NamedOperator: ...
@classmethod
def diagonal(cls, da: xr.DataArray) -> NamedOperator: ...
@classmethod
def kronecker(cls, *factors: NamedOperator) -> NamedOperator: ...
@classmethod
def block_diag(cls, *blocks: NamedOperator, dim: str) -> NamedOperator: ...
@classmethod
def low_rank(
cls,
U: xr.DataArray, *,
rank_dim: str,
D: NamedOperator | None = None,
V: xr.DataArray | None = None,
) -> NamedOperator: ...
The class methods are convenience builders that map onto gaussx
constructors (gaussx.Kronecker, gaussx.BlockDiag,
gaussx.LowRankUpdate, ...) and assemble the right dims tuple.
Layer-0 functions¶
Solving and factorising¶
def solve(
K: NamedOperator, y: xr.DataArray, *,
strategy: gaussx.AbstractSolveStrategy | None = None,
batch_dims: tuple[str, ...] | None = None,
) -> xr.DataArray: ...
def logdet(
K: NamedOperator, *,
strategy: gaussx.AbstractLogdetStrategy | None = None,
) -> jax.Array: ...
def cholesky(K: NamedOperator) -> NamedOperator:
"""Return the lower-triangular factor as a NamedOperator."""
def sqrt(K: NamedOperator) -> NamedOperator:
"""Symmetric square root (matrix square root)."""
def inv(K: NamedOperator) -> NamedOperator:
"""Inverse operator. Prefer ``solve`` over materialising inv when possible."""
Scalar / diagonal extractions¶
def diag(K: NamedOperator) -> xr.DataArray:
"""Diagonal of ``K`` as a DataArray with ``K.dims``."""
def trace(K: NamedOperator) -> jax.Array: ...
def quadratic_form(K: NamedOperator, x: xr.DataArray) -> jax.Array:
"""x.T @ K @ x — scalar even for batched x."""
Matrix-vector and matrix-matrix products¶
def matvec(K: NamedOperator, y: xr.DataArray) -> xr.DataArray:
"""Apply ``K`` to a labeled vector. Same as ``K @ y``."""
def matmul(K: NamedOperator, M: NamedOperator | xr.DataArray) -> ...
Spectral¶
def eig(K: NamedOperator) -> tuple[xr.DataArray, NamedOperator]:
"""Returns (eigenvalues, eigenvectors-as-NamedOperator)."""
def eigvals(K: NamedOperator) -> xr.DataArray: ...
def svd(K: NamedOperator) -> tuple[NamedOperator, xr.DataArray, NamedOperator]: ...
Sampling and log-prob¶
def sample(
K: NamedOperator, key: jax.Array, *,
sample_shape: tuple[str, ...] = (),
mean: xr.DataArray | None = None,
) -> xr.DataArray:
"""Draw from N(mean, K). New dims listed in ``sample_shape``
(each a ``"name=size"`` token) prepend the output.
"""
def log_prob(
K: NamedOperator, x: xr.DataArray, *,
mean: xr.DataArray | None = None,
) -> jax.Array: ...
MultivariateNormal¶
class MultivariateNormal:
def __init__(
self,
loc: xr.DataArray,
cov: NamedOperator,
*,
solver: gaussx.AbstractSolveStrategy | None = None,
) -> None: ...
def sample(
self, key: jax.Array, *,
sample_shape: tuple[str, ...] = (),
) -> xr.DataArray: ...
def log_prob(self, x: xr.DataArray) -> jax.Array: ...
def entropy(self) -> jax.Array: ...
def mean(self) -> xr.DataArray: ...
@property
def covariance(self) -> NamedOperator: ...
sample_shape follows the same "name=size" convention used by
sample() above. NumPyro callers can pass a plain int tuple
(sample_shape=(3,)) — the bridge auto-names the resulting dim
"sample_0", "sample_1", ... and warns once per call site.
Layer-1 operators¶
| Function | Operator | Notes |
|---|---|---|
solve |
Solve |
Solve(operator, rhs=..., strategy=...). rhs may be a string (var name on input Dataset) or a callable. |
logdet |
Logdet |
Returns a 0-d DataArray for pipeline composition. |
cholesky |
Cholesky |
Returns a NamedOperator wrapped in a single-variable Dataset for thread-through. |
diag |
Diag |
|
trace |
Trace |
0-d DataArray output. |
sqrt / inv |
Sqrt, Inv |
|
sample |
Sample |
Sample(operator=..., key=..., sample_shape=("draw=10",)). |
log_prob |
LogProb |
|
| MVN.sample | MVNSample |
Convenience wrapper. |
Operators all accept operator as either a NamedOperator instance
or a Callable[[xr.Dataset], NamedOperator] so the operator can be
data-dependent (e.g. a learned kernel re-evaluated on each call).
Exception hierarchy¶
class LinAlgBridgeError(Exception):
"""Base for xrtoolz.linalg errors."""
class CoordMismatch(LinAlgBridgeError):
"""Operator coords disagree with input DataArray coords."""
class DimMismatch(LinAlgBridgeError, ValueError):
"""Operator dim names or sizes don't match the input."""
class BackendError(LinAlgBridgeError, TypeError):
"""Input DataArray wraps a non-JAX array."""
Upstream gaussx / lineax exceptions propagate unchanged.
Pack / unpack helpers (publicly exposed)¶
def pack(da: xr.DataArray, dims: tuple[str, ...]) -> tuple[jax.Array, Callable]: ...
def unpack(flat: jax.Array, restore: Callable) -> xr.DataArray: ...
Exposed so users can drop to gaussx directly when they need something not in the bridge surface, without re-implementing the flatten/reshape loop.
What this API explicitly does not include¶
- Recipe wrappers (Kalman, RTS, Matheron, etc.) — deferred until the primitive surface is stable.
- A
Gaussiandistribution mixin that flows through bothxrtoolz.linalgandxrtoolz.prob. Two distribution surfaces is one too many;xrtoolz.prob.Normal(numpyro-backed) andxrtoolz.linalg.MultivariateNormal(gaussx-backed) coexist with documented boundaries (seeprob/decisions.mdD4 — "Two MVN classes coexist"). - Automatic strategy selection. gaussx has
AutoSolver; users who want auto-dispatch pass it explicitly. The bridge does not guess. - Backwards-compat with non-JAX arrays. The error message
points users at
jnp.asarray; no silent conversion.