la-stack
Fast, stack-allocated linear algebra for fixed dimensions in Rust.
This crate grew from the need to support delaunay with fast, stack-allocated linear algebra primitives and algorithms
while keeping the API intentionally small and explicit.
π Introduction
la-stack provides a handful of const-generic, stack-backed building blocks:
Vector<const D: usize>for fixed-length vectors ([f64; D]today)Matrix<const D: usize>for fixed-size square matrices ([[f64; D]; D]today)Lu<const D: usize>for LU factorization with partial pivoting (solve + det)Ldlt<const D: usize>for LDLT factorization without pivoting (solve + det; symmetric SPD/PSD)
β¨ Design goals
- β
Copytypes where possible - β Const-generic dimensions (no dynamic sizes)
- β
const fnwhere possible (compile-time evaluation of determinants, dot products, etc.) - β Explicit algorithms (LU, solve, determinant)
- β
Robust geometric predicates via optional exact arithmetic (
det_sign_exact) - β No runtime dependencies by default (optional features may add deps)
- β Stack storage only (no heap allocation in core types)
- β
unsafeforbidden
See CHANGELOG.md for details.
π« Anti-goals
- Bare-metal performance: see
blas-src,lapack-src,openblas-src - Comprehensive: use
nalgebraif you need a full-featured library - Large matrices/dimensions with parallelism: use
faerif you need this
π’ Scalar types
Today, the core types are implemented for f64. The intent is to support f32 and f64
(and f128 if/when Rust gains a stable primitive for it). Arbitrary-precision arithmetic
is available via the optional "exact" feature (see below).
π Quickstart
Add this to your Cargo.toml:
[]
= "0.2"
Solve a 5Γ5 system via LU:
use *;
// This system requires pivoting (a[0][0] = 0), so it's a good LU demo.
// A = J - I: zeros on diagonal, ones elsewhere.
let a = from_rows;
let b = new;
let lu = a.lu.unwrap;
let x = lu.solve_vec.unwrap.into_array;
// Floating-point rounding is expected; compare with a tolerance.
let expected = ;
for in x.iter.zip
Compute a determinant for a symmetric SPD matrix via LDLT (no pivoting).
For symmetric positive-definite matrices, LDL^T is essentially a square-root-free form of the Cholesky decomposition
(you can recover a Cholesky factor by absorbing sqrt(D) into L):
use *;
// This matrix is symmetric positive-definite (A = L*L^T) so LDLT works without pivoting.
let a = from_rows;
let det = a.ldlt.unwrap.det;
assert!;
β‘ Compile-time determinants (D β€ 4)
det_direct() is a const fn providing closed-form determinants for D=0β4,
using fused multiply-add where applicable. Matrix::<0>::zero().det_direct()
returns Some(1.0) (the empty-product convention). For D=1β4, cofactor
expansion bypasses LU factorization entirely. This enables compile-time
evaluation when inputs are known:
use *;
// Evaluated entirely at compile time β no runtime cost.
const DET: = ;
assert_eq!;
The public det() method automatically dispatches through the closed-form path
for D β€ 4 and falls back to LU for D β₯ 5 β no API change needed.
π¬ Exact determinant sign ("exact" feature)
The default build has zero runtime dependencies. Enable the optional
exact Cargo feature to add det_sign_exact(), which returns the provably
correct sign (β1, 0, or +1) of the determinant using adaptive-precision
arithmetic (this pulls in num-bigint and num-rational for BigRational):
[]
= { = "0.2", = ["exact"] }
use *;
let m = from_rows;
assert_eq!; // exactly singular
For D β€ 4, a fast f64 filter (error-bounded det_direct()) resolves the sign
without allocating. Only near-degenerate or large (D β₯ 5) matrices fall through
to the exact Bareiss algorithm in BigRational.
π§© API at a glance
| Type | Storage | Purpose | Key methods |
|---|---|---|---|
Vector<D> |
[f64; D] |
Fixed-length vector | new, zero, dot, norm2_sq |
Matrix<D> |
[[f64; D]; D] |
Fixed-size square matrix | from_rows, zero, identity, lu, ldlt, det, det_direct, det_sign_exactΒΉ |
Lu<D> |
Matrix<D> + pivot array |
Factorization for solves/det | solve_vec, det |
Ldlt<D> |
Matrix<D> |
Factorization for symmetric SPD/PSD solves/det | solve_vec, det |
Storage shown above reflects the current f64 implementation.
ΒΉ Requires features = ["exact"].
π Examples
The examples/ directory contains small, runnable programs:
solve_5x5β solve a 5Γ5 system via LU with partial pivotingdet_5x5β determinant of a 5Γ5 matrix via LUconst_det_4x4β compile-time 4Γ4 determinant viadet_direct()exact_sign_3x3β exact determinant sign of a near-singular 3Γ3 matrix (requiresexactfeature)
# or individually:
π€ Contributing
A short contributor workflow:
For the full set of developer commands, see just --list and AGENTS.md.
π Citation
If you use this library in academic work, please cite it using CITATION.cff (or GitHub's "Cite this repository" feature). A Zenodo DOI will be added for tagged releases.
π References
For canonical references to the algorithms used by this crate, see REFERENCES.md.
π Benchmarks (vs nalgebra/faer)
Raw data: docs/assets/bench/vs_linalg_lu_solve_median.csv
Summary (median time; lower is better). The βla-stack vs nalgebra/faerβ columns show the % time reduction relative to each baseline (positive = la-stack faster):
| D | la-stack median (ns) | nalgebra median (ns) | faer median (ns) | la-stack vs nalgebra | la-stack vs faer |
|---|---|---|---|---|---|
| 2 | 2.038 | 4.409 | 137.408 | +53.8% | +98.5% |
| 3 | 14.787 | 23.140 | 180.339 | +36.1% | +91.8% |
| 4 | 27.731 | 54.408 | 207.008 | +49.0% | +86.6% |
| 5 | 47.460 | 72.275 | 270.919 | +34.3% | +82.5% |
| 8 | 134.751 | 162.781 | 362.415 | +17.2% | +62.8% |
| 16 | 600.785 | 584.991 | 851.458 | -2.7% | +29.4% |
| 32 | 2,646.475 | 2,752.157 | 2,818.074 | +3.8% | +6.1% |
| 64 | 17,319.650 | 13,811.398 | 12,383.818 | -25.4% | -39.9% |
π License
BSD 3-Clause License. See LICENSE.