Skip to content

The linear-algebra layer

The IPM iteration depends only on two protocols; everything scale- and sparsity-related lives behind them.

LinearOperator

A backend-agnostic matvec (+ optional rmatvec/matmat). Subclasses: Dense, Diagonal, Identity, LowRank, LBFGSOperator, MatrixFreeJacobian, SparseOperator, Composite. Every Problem Jacobian/Hessian normalizes into one of these.

LinearSolver

solve(K, rhs) -> step for the augmented or condensed KKT operator.

Solver Use Purity
DenseSolver ≲ 1e4 vars; Cholesky/solve pure Array API
KrylovSolver default at scale; matrix-free CG/MINRES/GMRES pure Array API
SparseDirectSolver sparse Jacobians; per-backend factorization adapter

Selection is automatic (size + density + namespace capabilities) and user-overridable via Options.linsolve. Adding a solver never touches ipm/driver.py (invariant #3).

Sparsity as an adapter concern

The standard has no sparse type. The core emits structure as Array-API integer/float (row, col, value) vectors; thin per-backend wrappers in backend/sparse/ build and factor the actual sparse matrix. The IPM never sees a backend-specific sparse object.

Two backends own a concrete factorization: SciPy (CPU — Feral LDLᵀ / SuperLU) and CuPy (CUDA — NVIDIA cuDSS via nvmath-python, with a spsolve fallback). Torch and JAX have no native sparse-direct path of their own, so they reuse those two: the DeviceRoutingSparseAdapter reads the COO buffer's __dlpack_device__ and reinterprets it — zero-copy via DLPack where the libraries allow — onto the SciPy adapter for host arrays and the CuPy/cuDSS adapter for CUDA arrays. So Torch-CPU and JAX-CPU factor through Feral/SuperLU, and Torch-CUDA and JAX-GPU factor through cuDSS, with results handed back in the caller's namespace. Routing is by device, not by library name.