sparse-ldlt
A sparse LDLᵀ factorization for symmetric matrices, including indefinite ones, in pure Rust with no dependencies.
It factors A = L D Lᵀ, where L is unit lower triangular and D is diagonal, and solves
A x = b. D can hold negative entries, and it is exposed. By Sylvester's law of inertia, the
number of negative entries in D is the number of negative eigenvalues of A.
When to use it
Most pure-Rust sparse solvers offer only Cholesky, which needs a positive-definite matrix and does not report pivot signs. This crate is for problems where the matrix is indefinite or the signs matter:
- saddle-point (KKT) systems from constrained optimisation and mixed finite elements;
- shifted eigenvalue problems
K - σM, where counting negative pivots gives the number of eigenvalues belowσ(a Sturm count); - quasi-definite systems from interior-point methods.
It implements the up-looking elimination-tree method described in T. A. Davis, Direct Methods
for Sparse Linear Systems (SIAM, 2006). It has no runtime dependencies, uses no unsafe code
and builds on stable Rust.
Usage
Pass the matrix in compressed sparse column (CSC) form. Only the upper triangle (row ≤ column) is read, so you can pass either the upper triangle or the full matrix.
use SparseLdlt;
// [ 2 1 0 ]
// [ 1 -3 1 ]
// [ 0 1 2 ]
let col_ptr = vec!;
let row_idx = vec!;
let values = vec!;
let f = factor.unwrap;
let x = f.solve.unwrap;
// One negative pivot, so one negative eigenvalue.
let negative = f.d.iter.filter.count;
assert_eq!;
Ordering
For anything other than a banded matrix, reorder it first. amd computes an approximate
minimum degree ordering (Amestoy, Davis and Duff, 1996) and factor_perm applies it. solve
handles the permutation for you.
use ;
let order = amd;
let f = factor_perm.unwrap;
let x = f.solve.unwrap;
On one machine, a random 2%-dense matrix with n = 1024 took about 0.27 s to factor without
ordering and about 70 ms with it. On banded matrices, ordering makes little difference. Run
cargo bench to measure on yours.
A symmetric permutation does not change the inertia, so the negative-pivot count is the same with or without ordering.
Pivot breakdown
The factorization does no pivoting. When a pivot is zero or close to it, factor returns an
error instead of a result:
LdltError::ZeroPivot(k)when pivotkis exactly zero.LdltError::NearZeroPivot { column, pivot, scale, suggested_shift }when|D[k]| < NEAR_ZERO_PIVOT_REL * scale, wherescaleis the largest absolute diagonal entry ofAandNEAR_ZERO_PIVOT_RELis1e-13. The sign of such a pivot is rounding error, so an inertia count taken from it could be wrong.
factor_shifted and factor_perm_shifted recover from these errors. They try the unshifted
factorization first. If it breaks down, they factor A + shift·I, starting from
suggested_shift and multiplying the shift by 8 on each retry, for up to 8 attempts.
let f = factor_shifted?;
if f.shift != 0.0
Always check shift() after a shifted factorization. If you need exact pivots with no
perturbation, use a solver with Bunch-Kaufman or multifrontal pivoting instead.
Errors
Every failure is an LdltError, never a panic:
ZeroPivotandNearZeroPivot, described above.InvalidInputfor malformed CSC arrays or non-finite values (NaN, ±inf).SizeMismatchwhen a right-hand side does not match the matrix order.
Testing
- Inertia (
tests/inertia_oracle.rs): matrices whose inertia is known by construction (congruencesXᵀSX, quasi-definite KKT blocks, Sturm shifts at known eigenvalues). Whenever a factorization succeeds, its inertia must be exactly right. A second set places pivots near zero on purpose. Each of those must either be refused or give the right inertia, and every refusal must then be recovered byfactor_shifted. - Input handling (
tests/property.rs): duplicate entries are summed, explicit zeros and any row order within a column are accepted, and empty or malformed input never panics. About 330 random matrices each give either a correct factorization or a pivot error. - Real matrices (
tests/corpus.rs): structural stiffness matrices from the SuiteSparse collection, which are positive definite, so every pivot must be positive. With thecorpus-testsfeature, the test also factors every.mtxfile in the directory named byCK_LDLT_CORPUS_DIR. - Ordering (
tests/ordering.rs): AMD reduces fill and never changes the inertia. - Benchmarks (
cargo bench, using criterion): factor and solve times against n, for banded and random patterns, with and without AMD.
criterion is a dev-dependency only. It is not part of what you download.
Provenance
This is an independent implementation, written from the published description of the method
in Davis (2006). It does not copy or translate code from Tim Davis's LDL or from sprs-ldl.
It was written by MAGE Engineering for its FEM Analysis Studio, where it replaced an LGPL-licensed dependency.
Licence
MIT. See LICENSE.