ruvector-solver

February 27, 2026 ยท View on GitHub

Crates.io docs.rs License: MIT Tests

Sublinear-time sparse solvers -- O(log n) PageRank, spectral methods, and linear systems in Rust and WASM.

Most numerical libraries use dense solvers that slow down dramatically as data grows. ruvector-solver provides seven specialized sparse algorithms that run in O(log n) to O(sqrt(n)) time, automatically picks the best one for your problem, and works in native Rust or in the browser via WebAssembly. It powers the graph analytics and AI coherence layers inside RuVector.

Dense Solvers (e.g. nalgebra)ruvector-solver
Speed at scaleO(n^3) -- slows fastO(nnz * log n) to O(log n) -- stays fast
MemoryStores full n*n matrixOnly stores non-zero entries (CSR)
SIMD accelerationPartialAVX2 8-wide + fused kernels
Algorithm selectionManualAutomatic router picks the best of 7
PageRankNot available3 sublinear algorithms built in
Browser / WASMNoFull wasm-bindgen bindings

Quick Start

[dependencies]
ruvector-solver = "0.1"
use ruvector_solver::types::CsrMatrix;
use ruvector_solver::neumann::NeumannSolver;

// Build a sparse 3x3 system and solve it
let a = CsrMatrix::<f32>::from_coo(3, 3, vec![
    (0, 0, 2.0_f32), (0, 1, -0.5),
    (1, 0, -0.5),    (1, 1, 2.0),  (1, 2, -0.5),
    (2, 1, -0.5),    (2, 2, 2.0),
]);
let b = vec![1.0_f32, 0.0, 1.0];

let solver = NeumannSolver::new(1e-6, 500);
let result = solver.solve(&a, &b).unwrap();

println!("solution: {:?}", result.solution);
println!("iterations: {}", result.iterations);
println!("residual:   {:.2e}", result.residual_norm);

Or let the router pick the best algorithm automatically:

use ruvector_solver::router::{SolverRouter, QueryType};
use ruvector_solver::types::{CsrMatrix, ComputeBudget};

let router = SolverRouter::new();
let (algo, result) = router.solve(&matrix, &rhs, &ComputeBudget::default(), QueryType::LinearSystem).unwrap();
println!("Router selected: {:?}", algo);

Key Features

FeatureWhat It DoesWhy It Matters
7 specialized algorithmsNeumann, CG, Forward Push, Backward Push, Random Walk, TRUE, BMSSPEach tuned for a specific problem class
Automatic routingSolverRouter analyzes matrix structure and picks the optimal algorithmNo need to be a numerical methods expert
Fallback chainIf the selected algorithm fails, tries CG, then denseRobust convergence in production
AVX2 SIMD SpMV8-wide vectorized sparse matrix-vector multiplyMaximizes throughput on x86_64
Fused residual + normComputes residual and norm in one pass instead of three3x less memory traffic per iteration
Arena allocatorBump allocation for scratch buffers, O(1) resetZero heap allocation inside solve loops
WASM supportFull wasm-bindgen bindingsRun solvers in the browser
ComputeBudgetSet max time, iterations, and tolerancePredictable resource usage

Algorithms

AlgorithmModuleComplexityApplicable to
Jacobi-preconditioned Neumann seriesneumannO(nnz * log(1/eps))Diagonally dominant Ax = b
Conjugate Gradient (Hestenes-Stiefel)cgO(nnz * sqrt(kappa))Symmetric positive-definite Ax = b
Forward Push (Andersen-Chung-Lang)forward_pushO(1/epsilon)Single-source Personalized PageRank
Backward Pushbackward_pushO(1/epsilon)Reverse relevance / target-centric PPR
Hybrid Random Walkrandom_walkO(sqrt(n)/epsilon)Large-graph PPR with push initialisation
TRUE (JL + sparsification + Neumann)true_solverO(nnz * log n)Batch linear systems with shared A
BMSSP Multigrid (V-cycle + Jacobi)bmsspO(n log n)Ill-conditioned / graph Laplacian systems

Feature Flags

FeatureDefaultDescription
neumannYesJacobi-preconditioned Neumann series solver
cgYesConjugate Gradient (Hestenes-Stiefel) solver
forward-pushYesForward push for single-source PPR
backward-pushNoBackward push for reverse relevance computation
hybrid-random-walkNoHybrid random walk with push initialisation (enables getrandom)
true-solverNoTRUE batch solver (implies neumann)
bmsspNoBMSSP multigrid solver (V-cycle with Jacobi smoothing)
all-algorithmsNoEnables every algorithm above
simdNoAVX2 SIMD-accelerated SpMV (x86_64 only)
wasmNoWebAssembly target support
parallelNoMulti-threaded SpMV and solver loops (enables rayon, crossbeam)
fullNoAll algorithms + parallel + nalgebra-backend

Enable all algorithms:

[dependencies]
ruvector-solver = { version = "0.1", features = ["all-algorithms"] }

Performance Optimisations

Bounds-check-free SpMV (spmv_unchecked)

The inner SpMV loop is the single hottest path in every iterative solver. The spmv_unchecked method on CsrMatrix<f32> and CsrMatrix<f64> uses raw pointers to eliminate per-element bounds checks, relying on a one-time CSR structural validation (validation::validate_csr_matrix) performed before entering the solve loop.

Fused residual + norm computation (fused_residual_norm_sq)

Standard implementations compute the residual r = b - Ax and its squared norm ||r||^2 in separate passes (SpMV, vector subtraction, dot product -- three full memory traversals). fused_residual_norm_sq computes both in a single pass, reducing memory traffic by roughly 3x per iteration.

AVX2 8-wide SIMD SpMV

When the simd feature is enabled on x86_64, spmv_simd dispatches to an AVX2 kernel that processes 8 f32 values per instruction using _mm256 intrinsics with a horizontal sum reduction at the end of each row. Falls back to a portable scalar loop on other architectures.

4-wide unrolled Jacobi update

The Neumann iteration's update step x[j] += d_inv[j] * r[j] is manually unrolled 4-wide for instruction-level parallelism, with a scalar remainder loop for dimensions not divisible by 4.

Arena allocator

SolverArena provides bump allocation for per-solve scratch buffers. All temporary vectors are allocated from a single contiguous backing buffer and reclaimed in O(1) via arena.reset(), eliminating heap allocation overhead inside the iteration loop.

Architecture

                          +-------------------+
                          |   SolverRouter    |
                          | (algorithm select)|
                          +--------+----------+
                                   |
            +----------+-----------+-----------+----------+
            |          |           |           |          |
       +----v---+ +----v---+ +----v------+ +--v----+ +---v----+
       |Neumann | |   CG   | |ForwardPush| | TRUE  | | BMSSP  |
       |Solver  | | Solver | |  Solver   | |Solver | |Solver  |
       +----+---+ +----+---+ +-----+-----+ +--+----+ +---+----+
            |          |            |          |          |
            +-----+----+-----+-----+-----+----+-----+---+
                  |          |           |           |
             +----v---+ +---v----+ +----v----+ +----v-----+
             |types.rs| |simd.rs | |arena.rs | |budget.rs |
             |CsrMatrix| |AVX2   | |Bump     | |ComputeBudget|
             +--------+ |SpMV   | |Alloc    | |enforcement|
                         +-------+ +--------+ +----------+
                  |          |           |
             +----v---+ +---v------+ +--v---------+
             |traits.rs| |validate.rs| |error.rs    |
             |SolverEngine| |CSR check| |SolverError |
             +--------+ +---------+ +-----------+

The SolverRouter analyses the matrix SparsityProfile and QueryType to select the optimal algorithm. When the selected algorithm fails, SolverOrchestrator::solve_with_fallback tries a deterministic fallback chain: selected -> CG -> Dense.

API Overview

Core types (types.rs)

TypeDescription
CsrMatrix<T>Compressed Sparse Row matrix with spmv, spmv_unchecked, from_coo, transpose
SolverResultSolution vector, iteration count, residual norm, wall time, convergence history
ComputeBudgetMaximum time, max iterations, target tolerance
AlgorithmEnum of all solver algorithms (Neumann, CG, ForwardPush, ...)
SparsityProfileMatrix structural analysis (density, diagonal dominance, spectral radius estimate)
QueryTypeWhat the caller wants to solve (LinearSystem, PageRankSingle, Batch, ...)
ComplexityEstimatePredicted flops, iterations, memory, and complexity class

Traits (traits.rs)

TraitDescription
SolverEngineCore trait: solve(matrix, rhs, budget) -> SolverResult
SparseLaplacianSolverExtension for graph Laplacian systems and effective resistance
SublinearPageRankExtension for sublinear PPR: ppr(matrix, source, alpha, epsilon)

Error hierarchy (error.rs)

ErrorCause
SolverError::NonConvergenceIteration budget exhausted without reaching tolerance
SolverError::NumericalInstabilityNaN/Inf or residual growth > 2x detected
SolverError::SpectralRadiusExceededSpectral radius >= 1.0 (Neumann series would diverge)
SolverError::BudgetExhaustedWall-clock time limit exceeded
SolverError::InvalidInputDimension mismatch, non-finite values, index out of bounds
SolverError::BackendErrorBackend-specific failure (nalgebra, BLAS)

Infrastructure modules

ModuleDescription
router.rsSolverRouter for automatic algorithm selection; SolverOrchestrator with fallback
simd.rsAVX2-accelerated SpMV with runtime feature detection
validation.rsCSR structural validation (index bounds, monotonic row_ptr, NaN/Inf)
arena.rsSolverArena bump allocator for zero per-iteration heap allocation
budget.rsComputeBudget enforcement during solve
audit.rsAudit logging for solver invocations
events.rsEvent system for solver lifecycle hooks

Testing

The crate includes 177 tests (138 unit tests + 39 integration/doctests):

# Run all tests
cargo test -p ruvector-solver

# Run tests with all algorithms enabled
cargo test -p ruvector-solver --features all-algorithms

# Run a specific test module
cargo test -p ruvector-solver -- neumann::tests

Benchmarks

Five Criterion benchmark groups are provided:

# Run all benchmarks
cargo bench -p ruvector-solver

# Run a specific benchmark
cargo bench -p ruvector-solver --bench solver_neumann
BenchmarkDescription
solver_baselineBaseline SpMV and vector operations
solver_neumannNeumann solver convergence on tridiagonal systems
solver_cgConjugate Gradient on SPD matrices
solver_pushForward/backward push on graph adjacency matrices
solver_e2eEnd-to-end solve through the router with algorithm selection
Tutorial: Solving a Sparse Linear System

Step 1: Build a CSR matrix

use ruvector_solver::types::CsrMatrix;

// 4x4 tridiagonal system (diagonally dominant)
let a = CsrMatrix::<f32>::from_coo(4, 4, vec![
    (0, 0, 3.0), (0, 1, -1.0),
    (1, 0, -1.0), (1, 1, 3.0), (1, 2, -1.0),
    (2, 1, -1.0), (2, 2, 3.0), (2, 3, -1.0),
    (3, 2, -1.0), (3, 3, 3.0),
]);
let b = vec![2.0f32, 1.0, 1.0, 2.0];

Step 2: Choose a solver

use ruvector_solver::neumann::NeumannSolver;

let solver = NeumannSolver::new(1e-6, 500);
let result = solver.solve(&a, &b).unwrap();

println!("Solution:   {:?}", result.solution);
println!("Iterations: {}", result.iterations);
println!("Residual:   {:.2e}", result.residual_norm);

Step 3: Use the automatic router

use ruvector_solver::router::{SolverRouter, QueryType};
use ruvector_solver::types::{CsrMatrix, ComputeBudget};

let a64 = CsrMatrix::<f64>::from_coo(4, 4, vec![/* ... */]);
let b64 = vec![2.0, 1.0, 1.0, 2.0];
let budget = ComputeBudget::default();

let router = SolverRouter::new();
let (algo, result) = router.solve(&a64, &b64, &budget, QueryType::LinearSystem).unwrap();
println!("Router selected: {:?}", algo);

Step 4: Validate input

use ruvector_solver::validation::validate_csr_matrix;

let errors = validate_csr_matrix(&a);
assert!(errors.is_empty(), "CSR validation failed: {:?}", errors);

Step 5: Benchmark

cargo bench -p ruvector-solver --bench solver_neumann
cargo bench -p ruvector-solver --bench solver_e2e
Tutorial: PageRank with Forward Push
use ruvector_solver::forward_push::ForwardPushSolver;
use ruvector_solver::types::CsrMatrix;

// Build adjacency matrix for a small graph
let adj = CsrMatrix::<f32>::from_coo(4, 4, vec![
    (0, 1, 1.0), (1, 0, 1.0),
    (1, 2, 1.0), (2, 1, 1.0),
    (2, 3, 1.0), (3, 2, 1.0),
    (0, 3, 1.0), (3, 0, 1.0),
]);

let solver = ForwardPushSolver::new(0.85, 1e-6);  // alpha=0.85
let ppr = solver.ppr(&adj, 0);  // PPR from node 0

println!("PPR scores: {:?}", ppr);
CrateRole
ruvector-attn-mincutMin-cut gating using graph solvers
ruvector-coherenceCoherence metrics for attention comparison
ruvector-profilerBenchmarking memory, power, latency

Minimum Supported Rust Version

Rust 1.77 or later.

License

Licensed under the MIT License.


Part of RuVector -- the self-learning vector database.