Preconditioners: exact factorized baseline#
Exact-factor preconditioners wrap FactorizedSolve, SparseLU, or
SparseCholesky as inverse-apply objects. They are mainly diagnostic baselines:
if exact preconditioning does not converge quickly, the issue is likely solver
plumbing, tolerance, or numerical breakdown rather than preconditioner quality.
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)
GMRES with exact LU#
This small nonsymmetric system uses exact LU as a left preconditioner for GMRES. The setup cost is a direct factorization; do not report it as a performance headline against ILU(0), Jacobi, or Chebyshev.
A_sp = scipy.sparse.csr_array(
np.array(
[
[5.0, -1.0, 0.0, 0.25],
[0.5, 4.0, -1.5, 0.0],
[0.0, 1.0, 3.0, -0.5],
[0.0, 0.25, -0.75, 2.5],
],
dtype=np.float32,
)
)
A = ms.from_scipy(A_sp)
b = mx.array([2.0, -1.0, 0.5, 1.0], dtype=mx.float32)
M = preconditioners.exact(A, method="lu")
x, info = linalg.gmres(A, b, M=M, rtol=1e-6, restart=2, maxiter=8, return_info=True)
print(M.kind, M.method, M.setup_device, M.apply_device)
print("GMRES status/iterations/residual:", info.status, info.iterations, f"{info.residual_norm:.3e}")
print("solution:", np.asarray(x).round(6))
exact lu accelerate_cpu accelerate_cpu
GMRES status/iterations/residual: 0 1 1.192e-07
solution: [ 0.3393 -0.176 0.3105 0.5107]
Accelerate boundary#
When linalg.factorized chooses an Accelerate-backed factorization, exact
preconditioner application remains an Apple CPU sparse solve. Native explicit
LU/Cholesky factors use mlx-sparse triangular-solve kernels. Both paths are
explicitly guarded; Accelerate is never a mandatory dependency.