Solvers¶
linear_solve ¶
linear_solve(
operator: AbstractLinearOperator,
vector: PyTree[Any],
solver: Any = None,
*,
options: dict[str, Any] | None = None,
state: PyTree[Any] = sentinel,
throw: bool = True,
) -> tuple[Solution, Any]
Solve operator @ x = vector, returning the solution and an updated state.
A wrapper over lineax.linear_solve for the stateful sparse API. It runs the
solver's init or update to fold the operator into a state, solves, then tracks the
solution against the state so a later release is ordered after it.
Unlike lineax.linear_solve, it returns a (solution, state) tuple:
With no state, a fresh one is built with solver.init. The default solver is
AutoSparseLinearSolver, which picks a backend for the platform and precision.
AutoSparseLinearSolver ¶
Bases: AbstractLinearSolver[Any]
Selects a sparse direct solver based on the JAX platform, precision, and what is installed, and by default refines its solution with iterative refinement.
On CPU with x64 enabled, dispatches to Pardiso (Intel oneMKL Pardiso, factorization
reuse) if the optional pardiso-mkl-jax dependency is installed, otherwise KLU
(SuiteSparse, factorization reuse). Both are double precision only, hence the x64
requirement. On any other backend, or on CPU when x64 is disabled, it dispatches to
Spsolve, which works in single or double precision and on any backend. It exposes
the same stateful API as Pardiso and KLU (update, init_symbolic), so it can
be substituted for either. When it dispatches to Spsolve, the reuse calls degrade
to no-ops.
pardiso_mkl_jax does not support complex matrices (see Pardiso's docstring), so
init falls back to KLU for a complex operator even when Pardiso was otherwise
selected, keeping Auto able to solve anything KLU can. init_symbolic cannot make
the same check, since a bare sparsity pattern carries no values to inspect, so it stays
on Pardiso. Construct KLU() directly for symbolic-pattern reuse on a complex
operator.
By default the chosen solver is wrapped in IterativeRefinement, which improves each
solution until its relative residual is within tolerance or a step cap is spent. Pass
an IterativeRefinementSettings to tune those, or iterative_refinement=False to
solve with the chosen direct solver alone.
platform
class-attribute
instance-attribute
¶
Platform to select for. If None, jax.default_backend() is used. Set to e.g.
"cpu", "gpu", or "tpu" to override the choice explicitly. Pardiso/KLU are chosen
only when this resolves to "cpu" and x64 is enabled, otherwise Spsolve is
chosen.
iterative_refinement
class-attribute
instance-attribute
¶
Whether to refine the direct solve, and with what settings. True refines with the
IterativeRefinementSettings defaults, False disables it, and an explicit
IterativeRefinementSettings sets the tolerance and step cap.
select_solver ¶
The exact solver AutoSparseLinearSolver will run, including any refinement.
Mirrors lineax.AutoLinearSolver.select_solver. With refinement on this is an
IterativeRefinement wrapping the chosen direct solver. The operator is accepted
for signature parity but selection depends only on the platform.
KLU ¶
Bases: AbstractLinearSolver[_KLUState]
Sparse direct solver wrapping the klujax (SuiteSparse KLU) library.
This solver keeps the operator in its native sparse (COO) storage rather than
densifying it, and so is intended for use with the sparse operators in this package
(BCOOLinearOperator and BCSRLinearOperator).
klujax is CPU and double-precision only: float32/complex64 inputs are
upcast to float64/complex128. It does not enable JAX's x64 mode or force the CPU
platform on import, so jax_enable_x64 must already be on before this solver runs.
klujax raises a clear error otherwise.
This solver can only handle square nonsingular operators.
init_symbolic ¶
Analyze a sparsity pattern into a symbolic-only state, no values yet.
Accepts a BCOO, BCSR, BCOOLinearOperator, BCSRLinearOperator,
SparseJacobianLinearOperator, SparseJacobianLinearOperatorColoring, or
JacobianColoring. update then folds in an operator sharing the pattern and
reuses this analysis. The pattern must be concrete here, not a traced value.
update ¶
update(
state: _KLUState,
operator: AbstractLinearOperator,
options: dict[str, Any] = {},
) -> _KLUState
Fold a new operator into state, reusing the analysis where the pattern holds.
Repeated calls with the same operator object are a no-op. When the operator shares the state's sparsity tag, the symbolic analysis is reused and only the numeric factorization is redone. Otherwise the operator is analyzed from scratch.
Pardiso ¶
Bases: AbstractLinearSolver[_PardisoState]
Sparse direct solver wrapping pardiso_mkl_jax (Intel oneMKL Pardiso).
This solver keeps the operator in its native sparse (CSR) storage rather than
densifying it, and so is intended for use with the sparse operators in this package
(BCOOLinearOperator and BCSRLinearOperator).
pardiso_mkl_jax is CPU, real-valued, and double-precision only: float32
inputs are upcast to float64, and complex operators raise TypeError (Pardiso's
complex matrix types are not supported by pardiso_mkl_jax yet). It does not enable
JAX's x64 mode or force the CPU platform on import, so jax_enable_x64 must already
be on before this solver runs.
This solver can only handle square nonsingular operators.
Requires the optional pardiso-mkl-jax dependency (pip install
splineax[pardiso]). Constructing Pardiso() raises ImportError if it is not
installed. AutoSparseLinearSolver prefers Pardiso over KLU on CPU with x64
enabled, falling back to KLU automatically when pardiso-mkl-jax is missing.
Arguments:
Nothing.
init_symbolic ¶
Record the pattern for reuse, deferring analysis to the first update.
Under Pardiso's default weighted matching the analysis depends on the matrix
values, so a values-independent symbolic phase is not sound. This keeps only the
shape and a pattern tag. The first update with real values runs analyze and
factor.
update ¶
update(
state: _PardisoState,
operator: AbstractLinearOperator,
options: dict[str, Any] = {},
) -> _PardisoState
Fold a new operator into state, reusing the analysis where the pattern holds.
Repeated calls with the same operator object are a no-op. When the operator shares the state's pattern and an analysis already exists, only the numeric factorization is redone. Otherwise the operator is analyzed from scratch.
Spsolve ¶
Bases: AbstractLinearSolver[_SpsolveState]
Sparse direct solver wrapping jax.experimental.sparse.linalg.spsolve.
This solver keeps the operator in its native sparse (CSR) storage rather than
densifying it, and so is intended for use with the sparse operators in this package
(BCOOLinearOperator and BCSRLinearOperator). Internally spsolve performs a
sparse QR factorization (CUDA native; on CPU it falls back to
scipy.sparse.linalg.spsolve).
It has no separate factorization phase, so the stateful reuse API (init_symbolic,
update, and the state's release) is a set of no-ops here, for parity with KLU and
Pardiso.
This solver can only handle square nonsingular operators.
init_symbolic ¶
No-op symbolic init, for parity with KLU.
Spsolve cannot pre-analyze a sparsity pattern, so this returns an empty state
that update fills with the first real operator.
update ¶
update(
state: _SpsolveState,
operator: AbstractLinearOperator,
options: dict[str, Any] = {},
) -> _SpsolveState
Rebuild the state from operator, since Spsolve reuses no factorization.
Repeated calls with the same operator object are a no-op.
IterativeRefinement ¶
Bases: AbstractLinearSolver[_IterativeRefinementState]
Wraps a stateful solver and refines each solve with iterative refinement.
The wrapped solver supplies the factorization and the per-step solves. compute
runs the refinement loop (see iterative_refinement), reusing that factorization for
both the initial solve and every correction. Every other method delegates to the
wrapped solver, so IterativeRefinement exposes the same stateful API (init,
init_symbolic, update, transpose, conj) and can stand in for the solver it
wraps. Its state releases through the inner state, so state.release() still frees it.
The wrapped solver must be square and nonsingular, since refinement assumes the correction solve returns a genuine approximate inverse.
init_symbolic ¶
Analyze a sparsity pattern, deferring to the wrapped solver's init_symbolic.
The resulting state has no operator yet, so update must fold one in before a
solve. Raises AttributeError if the wrapped solver has no symbolic phase.
update ¶
update(
state: _IterativeRefinementState,
operator: AbstractLinearOperator,
options: dict[str, Any] = {},
) -> _IterativeRefinementState
Fold a new operator into state through the wrapped solver.
Returns the same state object when the wrapped update did, so an update with an
unchanged operator stays a no-op.
IterativeRefinementSettings ¶
Bases: Module
The tol and max_steps of an iterative refinement, without a solver bound yet.
A solver that offers refinement as an option, such as AutoSparseLinearSolver, takes
one of these instead of separate tolerance and step-cap arguments, so the two settings
travel together. See IterativeRefinement for what each does.
ReorderingScheme ¶
Bases: IntEnum
Stateful solve transform¶
Threads a solver state through a function's lineax.linear_solve calls, so its solves reuse
a factorization. See Transforming existing Lineax code.
stateful_solve_transform ¶
stateful_solve_transform(
fn: Callable[..., _OutputT],
*,
filter_solver: _FilterSolver = StatefulSolver,
return_final_state: bool | None = None,
pass_through_custom_diff: bool = False,
) -> _WrappedFunction[_OutputT]
Thread a solver state through a function's lineax.linear_solve calls.
The wrapped function takes the original arguments plus a state keyword for an initial
state, defaulting to None, in which case init runs at the first solve. A function
whose own signature already has a state argument cannot be wrapped, since the keyword
is taken.
Arguments:
fn: the function to transform. It callslineax.linear_solveinternally.filter_solver: which solves to thread, as a solver class matched byisinstanceor a boolean predicate. The defaultStatefulSolverthreads only solvers that implement the stateful API, so a plain denselineax.LU()passes through.return_final_state: when true the wrapped function returns(output, final_state), when false it returns the output alone and releases the threaded state. The default is true when an initialstateis passed at call time, false otherwise.pass_through_custom_diff: by default a matched solve inside acustom_jvporcustom_vjpraises, since the state cannot cross the custom rule. Set this true to let such a solve run without threading, so it works but does not reuse a factorization.
Returns:
A function taking fn's arguments plus a state keyword for an initial state. It returns
fn's output, or the output paired with the final state when a state is kept.
Sparsity tags¶
sparsity_pattern_tag ¶
Create a tag marking an operator's structural sparsity pattern.
Attach the tag to operators through their tags argument. Two operators carrying
equal tags are asserted to have exactly the same index arrays, in the same order, so
a solver may reuse one operator's factorization for the other.
Given a concrete pattern, the tag is content-hashed, so independently tagged
operators with the same indices get equal tags. With no argument, or a pattern whose
indices are traced under jit, the tag instead carries a random id. Thread that one
tag object onto every operator sharing the pattern to mark them as equal.
sparse_indices_sorted
module-attribute
¶
One global assertion that an operator's indices are already row-major sorted, so
Pardiso and Spsolve may skip the sort they would otherwise do in init.
BCOOLinearOperator and BCSRLinearOperator add this automatically when the matrix they
wrap already carries indices_sorted.
Protocols¶
Solvers structurally satisfy the SparseLinearSolver protocol, which extends the
solver-agnostic StatefulSolver. The stateful reuse API is described in
Stateful solves.
SparseLinearSolver ¶
Bases: StatefulSolver[_StateT], Protocol[_StateT]
Structural type for the sparse stateful solvers in this package.
Extends the solver-agnostic StatefulSolver (init, update, compute, transpose, conj,
assume_full_rank) with init_symbolic, which analyzes a known
sparsity pattern into a reusable state before any values are available. KLU,
Pardiso, Spsolve, and AutoSparseLinearSolver all satisfy it structurally.
init_symbolic ¶
Analyze a sparsity pattern into a state, reused by a later update.
StatefulSolver ¶
Bases: Protocol[_StateT]
A solver that creates and updates a reusable state.
This is the part of the lineax AbstractLinearSolver interface we rely on, plus
update. A solver satisfies it structurally, so no base class is needed. update folds
new information about the operator into an existing state.
The states a solver produces from init, update, and a state's track should share
one pytree structure, so a state can be carried through a scan or while_loop, whose
carry has a fixed structure. The sparse init_symbolic state may differ, since it holds
only a symbolic analysis. Such a state must be updated before it is carried through a
loop, for example by unrolling the first iteration.
update ¶
update(
state: _StateT,
operator: AbstractLinearOperator,
options: dict[str, Any] = {},
) -> _StateT
Fold a new operator into state, reusing prior work where possible.
TrackingState ¶
Bases: Protocol
A solver state that records solves depending on it and frees its own memory.
A state may own memory that must outlive every solve made with it. track marks a
solution as a dependency, so a later release is ordered after that solve, and
release frees that memory once the state is done. Both live on the state, so a caller
releases without a reference to the solver. A state that owns nothing implements track
as a no-op returning self, and release as a no-op.
PerformanceWarning ¶
Bases: UserWarning
Raised when a sparse solver has to do work that a differently prepared input would have avoided.
Currently only used by Spsolve and Pardiso, when their init sorts an
unsorted BCOO or BCSR operator before solving. Both need row-major sorted
indices and will silently sort them for you, but doing so on every init is
wasted work if the same operator is solved more than once. Passing an
already-sorted matrix (for a BCOO, call .sort_indices() once yourself)
avoids the warning and the repeated cost.