Skip to content

Basic usage

splineax follows the standard Lineax workflow: build a linear operator, then call lineax.linear_solve with a solver. The only difference is that the operator wraps a sparse array and the solver is a sparse direct solver.

Solving a system

import jax.numpy as jnp
import lineax as lx
from jax.experimental.sparse import BCOO

import splineax

# A sparse matrix A and a right-hand side b.
dense = jnp.array(
    [
        [10.0, 2.0, 0.0, 0.0],
        [3.0, 14.0, 5.0, 0.0],
        [0.0, 6.0, 18.0, 9.0],
        [0.0, 0.0, 1.0, 12.0],
    ]
)
operator = splineax.BCOOLinearOperator(BCOO.fromdense(dense))
b = jnp.array([1.0, 2.0, 3.0, 4.0])

solution = lx.linear_solve(operator, b, solver=splineax.AutoSparseLinearSolver())
x = solution.value

solution is a lineax.Solution; the answer is solution.value and solution.result reports success or failure, exactly as in plain lineax.

You can swap the solver for any of the three: splineax.Spsolve(), splineax.KLU(), or splineax.AutoSparseLinearSolver() (see Solvers).

Note

These solvers only handle square, nonsingular systems. Passing a non-square operator raises a ValueError from init.

Transposes, batching, and autodiff

Because the operators and solvers are ordinary Lineax components, the usual JAX transformations work:

import jax

# KLU solver requires 64-bit mode:
jax.config.update("jax_enable_x64", True)

# Solve the transposed system.
solution_T = lx.linear_solve(operator.T, b, solver=splineax.KLU())

# Solve under jit.
solve = jax.jit(lambda v: lx.linear_solve(operator, v, solver=splineax.KLU()).value)
x = solve(b)

# Batch over many right-hand sides with vmap.
bs = jnp.stack([b, b[::-1]])
xs = jax.vmap(solve)(bs)

# Differentiate through the solve (w.r.t. the right-hand side or the matrix entries).
jacobian = jax.jacrev(solve)(b)

Reusing work across right-hand sides

It is common to solve Ax = b for the same A and many different b. Lineax lets you compute the operator-only part once with init, then reuse the resulting state:

solver = splineax.KLU()
state = solver.init(operator, options={})

x1 = lx.linear_solve(operator, b, solver=solver, state=state).value
x2 = lx.linear_solve(operator, b[::-1], solver=solver, state=state).value

For KLU this avoids redoing the matrix bookkeeping on each solve. To go further and reuse an actual matrix factorization across solves, see Advanced usage.