Preconditioners: custom callable inverse apply#

Custom callables are for experimentation and interop. The contract is explicit: the callable or object with solve(x) must apply an approximation to A^{-1} @ x. mlx-sparse does not treat an arbitrary sparse matrix passed as M as something to invert. Custom callables use the host GMRES fallback and are therefore slower than native preconditioners.

import mlx.core as mx
import numpy as np
import scipy.sparse
import mlx_sparse as ms
from mlx_sparse import linalg
from mlx_sparse.linalg import preconditioners

# Use CPU execution throughout.
ms.use_cpu(require_available=False)
np.set_printoptions(precision=4, suppress=True)

A user-supplied inverse diagonal#

This example wraps a function with aspreconditioner. The shape is validated against A, and finite outputs are checked before the solver accepts the result.

A_sp = scipy.sparse.csr_array(
    np.array(
        [
            [6.0, -1.0, 0.0],
            [0.5, 5.0, -1.0],
            [0.0, 1.0, 4.0],
        ],
        dtype=np.float32,
    )
)
A = ms.from_scipy(A_sp)
b = mx.array([1.0, -2.0, 0.5], dtype=mx.float32)
inv_diag = mx.array(1.0 / A_sp.diagonal().astype(np.float32), dtype=mx.float32)

def inverse_apply(rhs):
    return inv_diag * rhs

M = preconditioners.aspreconditioner(inverse_apply, A)
x, info = linalg.gmres(A, b, M=M, rtol=1e-6, restart=3, maxiter=24, return_info=True)
print(M.kind, M.setup_device, M.apply_device)
print("GMRES status/iterations/residual:", info.status, info.iterations, f"{info.residual_norm:.3e}")
callable python_host python_host
GMRES status/iterations/residual: 0 3 9.127e-08

Production guidance#

Use a native preconditioner when one matches the matrix class. Use custom callables for prototypes, external solver interop, or one-off diagnostics where Python inverse-apply cost is acceptable.