# la-stack
[](https://doi.org/10.5281/zenodo.18158926)
[](https://crates.io/crates/la-stack)
[](https://crates.io/crates/la-stack)
[](https://github.com/acgetchell/la-stack/blob/v0.4.4/LICENSE)
[](https://docs.rs/la-stack)
[](https://github.com/acgetchell/la-stack/actions/workflows/ci.yml)
[![rust-clippy analyze][clippy-badge]][clippy-workflow]
[](https://codecov.io/gh/acgetchell/la-stack)
[![Audit dependencies][audit-badge]][audit-workflow]

Fast, stack-allocated linear algebra for fixed dimensions in Rust.
This crate grew from the need to support [`delaunay`](https://crates.io/crates/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 `f64` vectors backed by `[f64; D]`
- `Matrix<const D: usize>` for fixed-size square `f64` matrices backed by `[[f64; D]; D]`
- `Lu<const D: usize>` for LU factorization with partial pivoting (solve + det)
- `Ldlt<const D: usize>` for no-pivot factorization intended for exactly
symmetric positive-definite matrices (solve + det; typed pivot diagnostics)
## ๐งฎ Mathematical basis
`la-stack` operates on finite IEEE 754 binary64 values in small, fixed
dimensions. Its floating-point paths use LU with partial pivoting, LDLT without
pivoting for exactly symmetric positive-definite matrices, and closed-form
determinants through D=4. These results remain subject to conditioning and
binary64 rounding;
factorization tolerances are rejection thresholds, not accuracy guarantees. For
Dโค4, direct determinants can be paired with a conservative absolute roundoff
bound when its range preconditions hold.
With `features = ["exact"]`, stored binary64 inputs are lifted losslessly to
rationals for exact determinant signs, determinant values, and solves. Exactness
starts at the stored values and cannot recover information rounded away before
construction. See the
[mathematical basis](https://github.com/acgetchell/la-stack/blob/main/docs/mathematical_basis.md)
for the algorithms, validity boundaries, and supporting references.
## โจ Design goals
- โ
`Copy` types where possible
- โ
Const-generic storage (no dynamically sized matrix or vector representation)
- โ
`const fn` where possible (compile-time evaluation of determinants, dot products, etc.)
- โ
Explicit algorithms (LU, solve, determinant)
- โ
Error-bounded f64 determinant filtering plus optional exact signs
(`det_errbound`, `det_sign_exact`)
- โ
Exact determinant values and linear solves via optional arbitrary-precision
arithmetic (`det_exact`, `solve_exact`, strict/rounded f64 conversions)
- โ
No runtime dependencies by default (optional features may add deps)
- โ
Inline, stack-backed storage for core types; optional arbitrary-precision
exact values allocate as required
- โ
`unsafe` forbidden
See [CHANGELOG.md](https://github.com/acgetchell/la-stack/blob/v0.4.4/CHANGELOG.md)
for release history and
[docs/roadmap.md](https://github.com/acgetchell/la-stack/blob/v0.4.4/docs/roadmap.md)
for current release planning.
## ๐ซ Anti-goals
- Bare-metal performance: see [`blas-src`](https://crates.io/crates/blas-src),
[`lapack-src`](https://crates.io/crates/lapack-src), or [`openblas-src`](https://crates.io/crates/openblas-src)
- Broad general-purpose linear algebra: use [`nalgebra`](https://crates.io/crates/nalgebra)
- Large matrices/dimensions with parallelism: use [`faer`](https://crates.io/crates/faer)
- Alternate floating-point scalar families: `la-stack` supports `f64` and optional exact arithmetic, not `f32` / `f16` APIs
## โ
Use this crate when
- Your matrices and vectors have small, fixed dimensions known at compile time
- Stack allocation and `Copy` value semantics fit your data flow
- You want explicit LU / LDLT / determinant APIs rather than a broad algebra toolkit
- You need exact determinants, exact determinant signs, or exact linear solves
for fixed-size systems
- Robust predicates matter for geometry-style workloads near degeneracy
- You prefer a default build with no runtime dependencies
## ๐ข Scalar types
The scalar model is intentionally limited to `f64` for floating-point work and
exact rationals behind the optional `"exact"` feature. This matches the crate's
focus on small, robustness-sensitive numerical and computational geometry
workloads. When `f64` precision is insufficient (e.g. near-degenerate geometric
configurations), the optional `"exact"` feature provides arbitrary-precision
arithmetic via `BigRational` (see below).
Lower-precision `f32` / `f16` throughput-oriented workloads are outside the
crate's scope; they usually indicate large-matrix or accelerator-oriented use
cases better served by broader linear-algebra libraries.
## ๐ Quickstart
Add this to your `Cargo.toml`:
```toml
[dependencies]
la-stack = "0.4.4"
```
### Feature flags
- `default`: no runtime dependencies
- `exact`: `BigRational` exact determinant and solve APIs
- `bench`: repository-development gate used only by benchmark targets and
benchmark-input tests; application crates should not enable it
### LU solve
Solve a 5ร5 system via LU:
```rust
use la_stack::prelude::*;
fn main() -> Result<(), LaError> {
// 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 = Matrix::<5>::try_from_rows([
[0.0, 1.0, 1.0, 1.0, 1.0],
[1.0, 0.0, 1.0, 1.0, 1.0],
[1.0, 1.0, 0.0, 1.0, 1.0],
[1.0, 1.0, 1.0, 0.0, 1.0],
[1.0, 1.0, 1.0, 1.0, 0.0],
])?;
let b = Vector::<5>::try_new([14.0, 13.0, 12.0, 11.0, 10.0])?;
let lu = a.lu(DEFAULT_SINGULAR_TOL)?;
let x = lu.solve(b)?.into_array();
// Floating-point rounding is expected; compare with a tolerance.
let expected = [1.0, 2.0, 3.0, 4.0, 5.0];
for (x_i, e_i) in x.iter().zip(expected.iter()) {
assert!((*x_i - *e_i).abs() <= 1e-12);
}
Ok(())
}
```
### LDLT determinant
Compute a determinant for a symmetric positive-definite matrix via LDLT (no
pivoting).
For these matrices, `LDLแต` is a square-root-free Cholesky form. Multiplying each
column of `L` by the square root of the corresponding diagonal entry yields a
Cholesky factor:
```rust
use la_stack::prelude::*;
fn main() -> Result<(), LaError> {
// This matrix is symmetric positive-definite (A = L*L^T) so LDLT works without pivoting.
let a = Matrix::<5>::try_from_rows([
[1.0, 1.0, 0.0, 0.0, 0.0],
[1.0, 2.0, 1.0, 0.0, 0.0],
[0.0, 1.0, 2.0, 1.0, 0.0],
[0.0, 0.0, 1.0, 2.0, 1.0],
[0.0, 0.0, 0.0, 1.0, 2.0],
])?;
let ldlt = match a.ldlt(DEFAULT_SINGULAR_TOL) {
Ok(ldlt) => ldlt,
Err(err @ LaError::Asymmetric {
row,
col,
upper,
lower,
allowed_abs_diff,
..
}) => {
eprintln!(
"LDLT mismatch at ({row}, {col}): {upper} vs {lower} (allowed {allowed_abs_diff})"
);
return Err(err);
}
Err(err) => return Err(err),
};
let det = ldlt.det()?;
assert!((det - 1.0).abs() <= 1e-12);
Ok(())
}
```
> โ ๏ธ **LDLT invariant:** The input matrix must be **exactly symmetric**: every
> mirrored pair must compare equal (`+0.0 == -0.0` is accepted). Asymmetric
> inputs passed to
> [`Matrix::ldlt`](https://docs.rs/la-stack/latest/la_stack/struct.Matrix.html#method.ldlt)
> return a typed `LaError::Asymmetric` containing both observed values and the
> required allowed difference of zero. The tolerance-based
> [`Matrix::first_asymmetry`](https://docs.rs/la-stack/latest/la_stack/struct.Matrix.html#method.first_asymmetry)
> and `Matrix::is_symmetric` methods remain useful diagnostics, but do not prove
> the exact precondition required by LDLT. Use `lu()` when exact symmetry or
> positive definiteness is not guaranteed. A negative LDLT diagonal or a zero
> diagonal with nonzero remaining coupling returns
> `LaError::NotPositiveSemidefinite` with a typed
> `PositiveSemidefiniteViolation`. An uncoupled zero or positive pivot
> at or below the caller's tolerance returns `LaError::Singular` with a
> numerical `SingularityReason`. Because these pivots are computed in binary64,
> success is not an exact positive-definiteness certificate for the stored
> matrix.
## โก 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. It returns `Ok(Some(det))` for those
dimensions and `Ok(None)` for D โฅ 5. `Matrix::<0>::zero().det_direct()` returns
`Ok(Some(1.0))` (the empty-product convention). For D=1โ4, direct formulas
bypass LU factorization entirely. This enables compile-time evaluation when
inputs are known:
```rust
use la_stack::prelude::*;
// Evaluated entirely at compile time โ no runtime cost.
const DET: Result<Option<f64>, LaError> = match Matrix::<4>::try_from_rows([
[2.0, 0.0, 0.0, 0.0],
[0.0, 3.0, 0.0, 0.0],
[0.0, 0.0, 5.0, 0.0],
[0.0, 0.0, 0.0, 7.0],
]) {
Ok(matrix) => matrix.det_direct(),
Err(err) => Err(err),
};
fn main() -> Result<(), LaError> {
assert_eq!(DET?, Some(210.0));
Ok(())
}
```
The public `det()` method automatically dispatches through the closed-form path
for D โค 4 and falls back to zero-tolerance LU for D โฅ 5. Tiny nonzero
determinants are not flattened by a configured pivot tolerance. The LU fallback
returns `LaError::Singular` when floating-point elimination cannot produce a
non-zero pivot; it does not misreport that numerical failure as an exact zero.
Use `lu()` directly when you need a different tolerance policy, and use the
exact determinant APIs when exact singularity classification matters.
## ๐ฌ Exact arithmetic (`"exact"` feature)
The default build has **zero runtime dependencies**. Enable the optional
`exact` Cargo feature to add exact arithmetic methods using arbitrary-precision
rationals (this pulls in `num-bigint`, `num-rational`, and `num-traits` for
`BigRational`):
```toml
[dependencies]
la-stack = { version = "0.4.4", features = ["exact"] }
```
These routines are exact with respect to the finite binary64 values stored in
`Matrix` and `Vector`. They treat each stored value as the exact rational number
represented by its bits, so the exact determinant or solve stage introduces no
further roundoff. They cannot recover information already lost when source
values were rounded to `f64` before construction.
**Determinants:**
- **`det_exact()`** โ returns the exact determinant as a `BigRational`
- **`det_exact_f64()`** โ returns the exact determinant as `f64` only when
it is exactly representable (or `LaError::Unrepresentable` otherwise)
- **`det_exact_rounded_f64()`** โ returns the exact determinant rounded to a
finite `f64` using IEEE 754 round-to-nearest, ties-to-even
- **`det_sign_exact()`** โ infallibly returns the provably correct
`DeterminantSign` variant (`Negative`, `Zero`, or `Positive`)
**Linear system solve:**
- **`solve_exact(b)`** โ solves `Ax = b` exactly, returning `[BigRational; D]`
- **`solve_exact_f64(b)`** โ solves `Ax = b` exactly, returning `Vector<D>` only when
every component is exactly representable as `f64`
- **`solve_exact_rounded_f64(b)`** โ solves `Ax = b` exactly, returning each
component rounded to finite `f64` using IEEE 754 round-to-nearest,
ties-to-even
- **`ExactF64Conversion`** โ converts an existing exact determinant or solution
under the strict or rounded contract without repeating exact elimination
For exact-to-f64 output, strict conversions use
`UnrepresentableReason::RequiresRounding` when explicit rounding can produce a
finite value and `UnrepresentableReason::NotFinite` otherwise. Rounded
conversions opt into nearest-even rounding but still report `NotFinite` when no
finite `f64` exists.
```rust,ignore
use la_stack::prelude::*;
fn main() -> Result<(), LaError> {
// Exact determinant
let m = Matrix::<3>::try_from_rows([
[1.0, 2.0, 3.0],
[4.0, 5.0, 6.0],
[7.0, 8.0, 9.0],
])?;
assert_eq!(m.det_sign_exact(), DeterminantSign::Zero); // exactly singular
let det = m.det_exact()?;
assert_eq!(det, BigRational::from_integer(0.into())); // exact zero
let det_f64 = det.try_to_f64()?;
assert_eq!(det_f64, 0.0);
// If strict exact-to-f64 conversion would require rounding, opt in
// explicitly with the rounded API.
let inexact = Matrix::<2>::try_from_rows([
[1.0 + f64::EPSILON, 0.0],
[0.0, 1.0 - f64::EPSILON],
])?;
let exact_det = inexact.det_exact()?;
let rounded_det = match exact_det.try_to_f64() {
Ok(det) => det,
Err(err) if err.requires_rounding() => exact_det.to_rounded_f64()?,
Err(err) => return Err(err),
};
assert_eq!(rounded_det.to_bits(), 1.0f64.to_bits());
// If the exact determinant cannot fit in f64, keep the BigRational value.
let big = f64::MAX / 2.0;
let huge = Matrix::<3>::try_from_rows([
[0.0, 0.0, 1.0],
[big, 0.0, 1.0],
[0.0, big, 1.0],
])?;
let huge_det = huge.det_exact()?;
assert_eq!(
huge_det
.try_to_f64()
.err()
.and_then(|err| err.unrepresentable_reason()),
Some(UnrepresentableReason::NotFinite)
);
println!("exact determinant = {huge_det}");
// Exact linear system solve
let a = Matrix::<2>::try_from_rows([[1.0, 2.0], [3.0, 4.0]])?;
let b = Vector::<2>::try_new([5.0, 11.0])?;
let exact_x = a.solve_exact(b)?;
let x = exact_x.try_to_f64()?.into_array();
assert!((x[0] - 1.0).abs() <= f64::EPSILON);
assert!((x[1] - 2.0).abs() <= f64::EPSILON);
Ok(())
}
```
With the `exact` feature enabled, `DeterminantSign`, `ExactF64Conversion`,
`BigInt`, and `BigRational` are re-exported from the crate root and prelude,
alongside the most commonly needed `num-traits` items (`FromPrimitive`,
`ToPrimitive`, `Signed`). This lets consumers construct exact values
(`BigRational::from_f64`, `from_i64`), query sign (`is_positive` /
`is_negative`), and convert back to `f64` (`try_to_f64`, `to_rounded_f64`, or
the raw `to_f64`) with a single
`use la_stack::prelude::*;` โ no need to add `num-bigint`, `num-rational`,
or `num-traits` to their own `Cargo.toml`. Use
`DeterminantSign::as_i8()` only when numeric โ1/0/+1 interoperability is
required.
For `det_sign_exact()`, D โค 4 matrices first use a fast f64 filter
(error-bounded `det_direct()`) when its rounded intermediates stay in the normal
range or are exact structural zeros. An inconclusive filter falls back to the
same direct determinant expansion in `BigInt`. D โฅ 5 skips the closed-form
filter and uses fraction-free Bareiss elimination in `BigInt`.
Because `Matrix` stores only finite entries, arithmetic range failures in the
filter are inconclusive rather than errors and the exact fallback is total.
## ๐ก๏ธ Adaptive determinant filtering (D โค 4)
`det_direct_with_errbound()` returns a closed-form determinant together with
the conservative absolute error bound used by the fast filter, computed from
one call that evaluates the determinant once and computes its matching bound.
It returns `None` when a D โค 4 computation may be affected by gradual
underflow, as well as for unsupported D โฅ 5 dimensions.
This method does NOT require the `exact` feature โ it uses pure f64 arithmetic
and is available by default. Use `det_errbound()` when only the bound is needed.
The paired API enables custom adaptive-precision logic for geometric predicates:
```rust,ignore
use la_stack::prelude::*;
fn adaptive_det_sign<const D: usize>(
matrix: &Matrix<D>,
) -> DeterminantSign {
if let Ok(Some(estimate)) = matrix.det_direct_with_errbound() {
if estimate.determinant().abs() > estimate.absolute_error_bound() {
return if estimate.determinant() > 0.0 {
DeterminantSign::Positive
} else {
DeterminantSign::Negative
};
}
}
matrix.det_sign_exact()
}
fn main() -> Result<(), LaError> {
let identity = Matrix::<3>::identity();
assert_eq!(
adaptive_det_sign(&identity),
DeterminantSign::Positive
);
// A zero determinant cannot pass the f64 sign filter, so this exercises
// the exact fallback.
let singular = Matrix::<3>::try_from_rows([
[1.0, 2.0, 3.0],
[4.0, 5.0, 6.0],
[7.0, 8.0, 9.0],
])?;
assert_eq!(adaptive_det_sign(&singular), DeterminantSign::Zero);
// The f64 filter overflows for this finite matrix, but the exact fallback
// still resolves its positive determinant sign.
let big = f64::MAX / 2.0;
let overflowing = Matrix::<3>::try_from_rows([
[0.0, 0.0, 1.0],
[big, 0.0, 1.0],
[0.0, big, 1.0],
])?;
assert_eq!(
adaptive_det_sign(&overflowing),
DeterminantSign::Positive
);
Ok(())
}
```
The error coefficients (`ERR_COEFF_2`, `ERR_COEFF_3`, `ERR_COEFF_4`) are
conservative, dimension-specific constants, not caller-tunable tolerances. The
[mathematical basis](https://github.com/acgetchell/la-stack/blob/main/docs/mathematical_basis.md#determinants-and-certified-sign-filtering)
documents the bound and states its range preconditions. The constants are explicit
crate-root exports for advanced users who want to compose the same bound:
`use la_stack::{ERR_COEFF_2, ERR_COEFF_3, ERR_COEFF_4};`. They intentionally stay
out of the common prelude.
## ๐งฉ API at a glance
| `Vector<D>` | `[f64; D]` | Finite fixed-length vector for input and computation | `try_new`, `as_array`, `into_array`, `dot`, `norm2_sq` |
| `Matrix<D>` | `[[f64; D]; D]` | Finite square matrix for input and computation | See below |
| `DeterminantWithErrorBound` | Opaque validated pair | Paired direct determinant and certified absolute bound | `determinant`, `absolute_error_bound` |
| `Lu<D>` | Inline factors + permutation | Factorization for solves/det | `solve`, `det` |
| `Ldlt<D>` | Inline factors | No-pivot SPD factorization for solves/det | `solve`, `det` |
| `Tolerance` | finite non-negative `f64` | Validated numerical threshold | `try_new`, `get` |
| `LaError` | typed variants and reasons | Structured, actionable failure reporting | See error semantics below |
| `DeterminantSign`ยน | enum | Exact determinant sign | `as_i8` |
Storage shown above reflects the intentional `f64` scalar model.
For a runtime dimension from 0 through `MAX_STACK_MATRIX_DISPATCH_DIM` (7),
`try_with_stack_matrix!` dispatches to a concrete `Matrix<N>` while preserving
inline stack storage. Larger dimensions return `LaError::UnsupportedDimension`;
the macro does not introduce a dynamically sized matrix representation.
`Matrix<D>` key methods: `as_rows`, `into_rows`, `lu`, `ldlt`, `det`,
`det_direct`, `det_direct_with_errbound`, `det_errbound`,
`det_exact`ยน, `det_exact_f64`ยน, `det_exact_rounded_f64`ยน, `det_sign_exact`ยน,
`solve_exact`ยน, `solve_exact_f64`ยน, `solve_exact_rounded_f64`ยน.
Matrix and vector constructors validate non-finite inputs at public API
boundaries. After construction, `Matrix<D>` and `Vector<D>` carry that
finite-storage invariant directly, so factorization kernels do not repeat an
O(Dยฒ) input scan. Computed factor matrices are still checked before they become
observable results.
`Matrix::as_rows` and `Vector::as_array` borrow their validated backing arrays;
`Matrix::into_rows` and `Vector::into_array` consume the value and return the
owned fixed-size arrays.
`Matrix::get(row, col)` returns `None` for out-of-bounds coordinates;
`Matrix::try_get` instead returns a structured `LaError` preserving those
coordinates. The single fallible `Matrix::set` validates both coordinates and
finiteness before mutating the matrix.
`LaError` and its reason/location enums are non-exhaustive. Numerical
singularity records the `FactorizationKind`,
observed pivot magnitude, and tolerance, while exact-arithmetic singularity is
identified separately. `LaError::NonFinite` retains the crate-wide non-finite
contract but uses `NonFiniteOrigin`, `NonFiniteLocation`, and
`ArithmeticOperation` to distinguish invalid inputs from computed overflow.
`InvalidToleranceReason` distinguishes negative from non-finite tolerances, and
`PositiveSemidefiniteViolation` distinguishes negative LDLT pivots from a zero
pivot with nonzero coupling. Match these public enums with a wildcard and use
`..` for struct-style variants so future error context can be added without
breaking callers.
ยน Requires `features = ["exact"]`.
## ๐ Benchmarks (vs nalgebra/faer)
![LU solve (factor + solve): median time vs dimension][lu-solve-benchmark]
Raw data:
[docs/assets/bench/vs_linalg_lu_solve_median.csv](https://github.com/acgetchell/la-stack/blob/v0.4.4/docs/assets/bench/vs_linalg_lu_solve_median.csv)
Measurement provenance:
[docs/assets/bench/vs_linalg_lu_solve_median.provenance.json][benchmark-provenance]
Representative benchmark: `lu_solve` factors the matrix and solves one
right-hand side. Median time is lower-is-better, and the โla-stack vs
nalgebra/faerโ columns show the % time reduction relative to each baseline
(positive means the recorded la-stack median is lower). These are descriptive
point-estimate ratios, not statistical significance claims or an aggregate score
across operations.
Timings count only when the implementation preserves the documented
correctness guarantees and invariants. Performance claims require comparable
before-and-after evidence using the same inputs, configuration, and environment.
This snapshot records the measured source state, CPU, operating system, Rust
toolchain, dependency lock and harness digests, Criterion command, and
correctness-gate result in the adjacent JSON sidecar. The publication workflow
requires complete canonical-dimension coverage and regenerates the CSV, SVG,
README table, and provenance together.
For the full per-kernel comparison methodology, input construction, and
release-comparison workflow details, see
[docs/BENCHMARKING.md](https://github.com/acgetchell/la-stack/blob/v0.4.4/docs/BENCHMARKING.md).
For the current release-to-release performance snapshot, see
[docs/PERFORMANCE.md](https://github.com/acgetchell/la-stack/blob/v0.4.4/docs/PERFORMANCE.md).
| 2 | 2.051 | 4.609 | 149.537 | +55.5% | +98.6% |
| 3 | 10.032 | 23.094 | 185.935 | +56.6% | +94.6% |
| 4 | 21.806 | 53.542 | 218.921 | +59.3% | +90.0% |
| 5 | 43.787 | 70.437 | 282.262 | +37.8% | +84.5% |
| 8 | 128.337 | 167.505 | 414.791 | +23.4% | +69.1% |
| 16 | 672.680 | 581.273 | 875.411 | -15.7% | +23.2% |
| 32 | 2,873.720 | 2,470.435 | 2,861.209 | -16.3% | -0.4% |
| 64 | 18,165.369 | 15,021.737 | 12,225.703 | -20.9% | -48.6% |
## ๐ Examples
The `examples/` directory contains small, runnable programs:
- **`solve_5x5`** โ solve a 5ร5 system via LU with partial pivoting
- **`det_5x5`** โ determinant of a 5ร5 matrix via LU
- **`ldlt_solve_3x3`** โ solve a 3ร3 symmetric positive definite system via LDLT
- **`const_det_4x4`** โ compile-time 4ร4 determinant via `det_direct()`
- **`exact_det_3x3`** โ exact determinant value of a near-singular 3ร3 matrix (requires `exact` feature)
- **`exact_sign_3x3`** โ exact determinant sign of a near-singular 3ร3 matrix (requires `exact` feature)
- **`exact_solve_3x3`** โ exact solve of a near-singular 3ร3 system vs f64 LU (requires `exact` feature)
```bash
just examples
# or individually:
cargo run --example solve_5x5
cargo run --example det_5x5
cargo run --example ldlt_solve_3x3
cargo run --example const_det_4x4
cargo run --features exact --example exact_det_3x3
cargo run --features exact --example exact_sign_3x3
cargo run --features exact --example exact_solve_3x3
```
## ๐ค Contributing
A short contributor workflow:
Install Rust 1.97.0 through [rustup](https://rustup.rs/), Git, Python 3.14,
[`uv` 0.11.28](https://docs.astral.sh/uv/), and `jq`. Then install the pinned
`just` release from its locked dependency graph:
```bash
cargo install --locked just --version 1.56.0
just setup # install/verify dev tools + sync Python deps
just check # lint/validate (non-mutating)
just fix # apply auto-fixes (mutating)
just ci # lint + tests + examples + bench compile
```
The repository uses `cargo-nextest` for runnable Rust tests, `cargo-machete`
for unused-dependency checks, `rumdl` for Markdown, `dprint` plus `yamllint`
for YAML/CFF, `taplo` for TOML, and `typos` for spelling. Python 3.14 support
tooling is locked with `uv` and checked by Ruff, Ty, and Semgrep. GitHub Actions
references are SHA-pinned, restricted to an explicit allowlist, and kept with
readable version comments for review.
CI runs `just ci` on Ubuntu, macOS, and Windows to keep platform coverage
aligned with the local comprehensive validation path.
For coverage commands and report locations, see
[`docs/COVERAGE.md`](https://github.com/acgetchell/la-stack/blob/v0.4.4/docs/COVERAGE.md).
For the full contributor workflow, see
[CONTRIBUTING.md](https://github.com/acgetchell/la-stack/blob/v0.4.4/CONTRIBUTING.md).
## ๐ Citation
If you use this library in academic work, please cite it using
[CITATION.cff](https://github.com/acgetchell/la-stack/blob/v0.4.4/CITATION.cff)
(or GitHub's "Cite this repository" feature). Tagged releases are archived on
Zenodo under the
[all-versions concept DOI](https://doi.org/10.5281/zenodo.18158926).
## ๐ References
For canonical references to the algorithms used by this crate, see
[REFERENCES.md](https://github.com/acgetchell/la-stack/blob/v0.4.4/REFERENCES.md).
## ๐ค AI Agents
AI coding assistants should read
[AGENTS.md](https://github.com/acgetchell/la-stack/blob/v0.4.4/AGENTS.md)
before proposing or applying changes. See
[CONTRIBUTING.md](https://github.com/acgetchell/la-stack/blob/v0.4.4/CONTRIBUTING.md)
for the repository's AI-assisted development note.
## ๐ License
BSD 3-Clause License. See [LICENSE](https://github.com/acgetchell/la-stack/blob/v0.4.4/LICENSE).
[audit-badge]: https://github.com/acgetchell/la-stack/actions/workflows/audit.yml/badge.svg
[audit-workflow]: https://github.com/acgetchell/la-stack/actions/workflows/audit.yml
[benchmark-provenance]: https://github.com/acgetchell/la-stack/blob/v0.4.4/docs/assets/bench/vs_linalg_lu_solve_median.provenance.json
[clippy-badge]: https://github.com/acgetchell/la-stack/actions/workflows/rust-clippy.yml/badge.svg
[clippy-workflow]: https://github.com/acgetchell/la-stack/actions/workflows/rust-clippy.yml
[lu-solve-benchmark]: https://raw.githubusercontent.com/acgetchell/la-stack/v0.4.4/docs/assets/bench/vs_linalg_lu_solve_median.svg