Using the solver¶
The matrix you supply¶
Pass the sparse matrix A in the equation A x = b.
apxchol is designed for graph Laplacians and SDDM matrices: symmetric,
diagonally dominant matrices with nonpositive off-diagonal entries.
For a graph with nonnegative edge weights, the adjacency matrix stores those
weights. Its Laplacian is
L = D - Adj, where D contains the sums of incident weights.
Use apxchol.laplacian(Adj) in Python or apxchol_laplacian(Adj) in
MATLAB/Octave to form it. A Laplacian RHS must sum to zero on each connected
component; the solution is defined up to a constant on each component.
Python accepts SciPy sparse matrices and arrays. It converts them to CSC (compressed sparse column) format, sorts row indices and combines duplicate entries. Supply the full symmetric matrix, not just one triangle. Format conversion does not invent missing symmetric entries.
The CLI can detect adjacency input and construct its Laplacian. Use
--input-kind to choose explicitly. Library bindings treat the input as A
and do not guess whether it is an adjacency matrix.
Choosing CPU or GPU¶
The CPU build contains the CPU solver. CUDA and METAL builds add their
GPU backend and retain CPU support. Python wheels include CPU plus the GPU
backend for their platform; they work without a GPU.
Request |
Behavior |
|---|---|
|
Select a compatible available backend before setup; may prefer CPU for small inputs |
|
Run setup and solves on CPU |
|
Run setup and solves on the compiled GPU backend; fail if the device, input or options are unsupported |
Automatic selection is not a guarantee of the fastest backend for every matrix. Once selected, the backend stays fixed. An execution error is reported rather than silently retrying on another backend.
Use Python’s backend argument, C++ solve_options.backend, or CLI
--backend. Python’s solver.backend and native solve results report the
selected backend. The MATLAB/Octave binding currently uses CPU only.
Native GPU input and options¶
For direct C++ input, GPU setup needs compressed CSC storage: column offsets
must cover only stored entries, row indices within each column must be sorted
and unique, and every stored off-diagonal (i, j) needs its matching (j, i).
GPU setup also requires 32-bit vertex indices.
CPU setup supports custom partitioners and eliminators. CUDA setup supports
block_greedy, natural residual peeling, and GKS or trace-cycle sampling;
exact-clique, exact-core and double-cycle overrides require CPU construction.
Metal setup supports the default block_greedy/GKS options and FP32 graph
weights. See precision limits for Metal ranges.
These construction restrictions do not prevent using an explicitly supplied host factor with a GPU solver. See factor handoff.
Reuse, batches and initial guesses¶
Construct a solver once, then call solve for each new RHS. A matrix RHS
represents several independent systems with the same A; pass the columns
together to use native batching. This reuses the factor and shares work across
columns.
A warm start is an initial guess x0, often the solution of a related
system. Without one, the solver starts from zero. A useful guess can reduce
iterations; a poor guess need not help. C++, Python and MATLAB/Octave support
vector and matrix initial guesses. See the Python and
MATLAB/Octave examples.
For C++:
#include "apxchol.h"
apxchol::solver solver(A);
auto result = solver.solve(b, 1e-8);
// Negative tolerance/budget arguments keep this solver's defaults.
auto warm = solver.solve(b2, -1, -1, &result.x);
Eigen::MatrixXd X(B.rows(), B.cols());
auto batch = solver.solve(B, X);
One-shot apxchol::solve(A, b) uses the same backend selection. For explicit
CPU execution, set solve_options.backend = solve_backend::cpu. Use
cpu_solver only when you need its direct preconditioner() accessor;
see the C++ reference.
Binding capabilities¶
The bindings reuse the native solver, convergence checks and option defaults. Their language-specific helpers differ:
Capability |
Python |
MATLAB/Octave |
|---|---|---|
Reusable factor, scalar and batched solves/apply |
Yes |
Yes |
Initial guess, per-call tolerance and iteration budget |
Yes |
Yes |
GPU setup and solve |
CUDA or Metal when built and available |
CPU only |
Factor export |
FP32 values when retained |
Not exposed |
External iterative solver |
SciPy |
Function handle for |
Convergence and cost¶
Built-in solves verify the returned solution against the original matrix and
RHS. result.residual reports that relative residual, and result.converged
tells whether it meets the requested tolerance. Inspect this flag even when
the call returns without an exception.
This check is always part of the solve, including when timing collection is disabled. It usually costs one additional sparse matrix–vector product and norm reductions per RHS; trivial exact exits can avoid that work. Its cost is included in reported solve time.
A failed final check does not trigger more iterations or a different backend. The precision contract explains where each backend checks the residual and how rounding affects it.
Using another iterative solver¶
apxchol’s SciPy operator (solver.aslinearoperator()) and Eigen-compatible
preconditioner (apx_cholesky, CPU only) let another solver drive the
iteration. That solver controls its stopping test.
A GPU-backed SciPy operator exchanges host vectors with the device on each
application. Native solve keeps iteration vectors on the GPU, so prefer it
for GPU workloads unless you need another outer solver.
Keeping and exporting a factor¶
Exported factor values are FP32. Internal triangular-solve storage can be FP16 or FP32 independently; see storage choices.
C++ defaults to releasing factor values after preparation. Set
keep_factor_values=true when you need them later through solver.factor().
Python’s reusable solver instead defaults to keep_factor=True, enabling
chol(), L and D exports. Set it to False to save memory; the permutation
P remains available.
CPU-, CUDA- and Metal-built factors can be reused through
C++ factor handoff. Automatic adoption
uses CPU; request solve_backend::gpu to upload the factor to a GPU.
Portable solver archives save the original matrix and retained factor together, so loading can prepare the solver without repeating factorization.
CPU threads¶
Set Python num_threads, C++ solve_options.num_threads, or CLI --threads
to cap CPU parallelism. Zero inherits the caller’s OpenMP setting; a positive
value overrides it for the operation and is restored afterward. Small
workloads may use fewer threads.
The setting also covers CPU work in a GPU build; it does not control GPU threadgroups. More CPU threads need not be faster. To choose which cores run those CPU workers, see thread placement.