Expand description
A from-scratch Rust port of the vendored SuiteSparse KLU solver
(vendor/suitesparse/{BTF,AMD,KLU}/, v7.12.2 — see
vendor/suitesparse/PROVENANCE.md for the upstream tag/commit this was
ported from) — a fourth, always-built JacobianBackend alongside
Scalar, Block, and the FFI-backed Klu (sparse_klu.rs).
Why this exists, separately from sparse_klu.rs: sparse_klu.rs
links the vendored C directly via FFI, which needs a C compiler and
libclang (for bindgen) at build time and is opt-in behind the klu
Cargo feature specifically to isolate that burden and the LGPL-2.1-or-
later license KLU/BTF carry (see vendor/suitesparse/PROVENANCE.md).
This module is a faithful translation of the same algorithm into Rust
instead — no C toolchain needed to build it.
Licensing: a close translation of licensed C source is reasonably a
derivative work regardless of language, so this module carries forward
its upstream licenses — not uniformly one license: amd/ translates
AMD, which is BSD-3-Clause upstream (confirmed directly from its
source headers’ SPDX-License-Identifier, same as
vendor/suitesparse/PROVENANCE.md’s own table), while everything else
here (btf/, analyze.rs, kernel.rs, factor.rs, scale.rs,
refactor.rs, solve.rs) translates BTF/KLU, both
LGPL-2.1-or-later. See PROVENANCE.md (this directory) for the full
per-file mapping. This whole module is always built (no feature
gate), per a deliberate, confirmed choice — unlike the FFI klu feature,
there’s no C-toolchain burden left to gate against — so a default
cargo build now always bundles this BSD/LGPL content alongside
gridoxide’s own Apache-2.0 code. See Cargo.toml’s license field and
docs/src/reference/provenance.md for that crate-wide consequence.
Scope: real (f64) matrices only, single right-hand side, int32-
range indices — gridoxide’s own Newton-Raphson Jacobian solve never needs
anything else (confirmed against every call site in sparse_klu.rs).
Faithfully ports what real KLU actually does for gridoxide’s one fixed
configuration (Options::default(), mirroring klu_defaults.c exactly):
BTF block-triangular preprocessing, per-block AMD ordering, a
partial-pivoting Gilbert-Peierls left-looking LU kernel with
Eisenstat-Liu symmetric pruning, and cheap numeric-only refactorization.
Explicitly out of scope (confirmed dead — no gridoxide call path reaches
them): COLAMD/user-supplied ordering (Options/klu_common’s ordering
is always AMD here), multi-RHS solve, complex arithmetic, and int64
(DLONG) indices.
Rows are scaled by default, as SuiteSparse’s KLU scales them
(KluNativeSystem::new; with_scale(.., 0) turns it off). Scaling divides
every row by its largest magnitude before each factorization; it decides
which pivots partial pivoting sees as large and how the arithmetic rounds,
never what the solution is. Scaled, the port solves as SuiteSparse’s KLU
does — the same pivots and, on most matrices, the same bits
(tests::scaled_matches_real_klu).
Power-flow Jacobians do not need it: examples/klu_scaling_stress.rs
runs the worst-scaled networks at hand, with rows spanning up to 24
orders of magnitude, and unscaled the backward error stays at machine
precision and every Newton solve takes the iterations SuiteSparse’s KLU
takes. State estimation does: its system weights every measurement by
1/σ², and on the three-phase transmission case with one sensor fifteen σ
off (se_three_phase_test::bad_data_detection_finds_the_corrupted_measurement)
the unscaled port’s steps stall at 6e-8 and Gauss-Newton runs out of
iterations, where scaled — like SuiteSparse’s KLU and faer — it converges
in 8. Scaling costs 11–15% of a power-flow solve; a caller that knows its
matrices are well scaled can switch it off.
Module layout (mirrors the phased port plan; each submodule’s doc
comment names the specific upstream .c file(s) it was translated from):
types (shared sentinel/Options plumbing) → btf → amd → kernel/
factor → scale → refactor → solve, with the public API
(KluNativeSystem) assembled here.
Modules§
- types
- Sentinel-index encoding and fixed solver configuration shared across
every
klu_nativesubmodule — mirrorsvendor/suitesparse/KLU/Include/ klu_internal.h’sEMPTY/FLIP/UNFLIP(identical, by inspection, toAMD/Include/amd_internal.h’s andBTF/Include/btf.h’s ownBTF_FLIP/BTF_UNFLIP/BTF_ISFLIPPED) andKLU/Source/klu_defaults.c’sKLU_commondefaults.
Structs§
- KluNative
System - A real sparse system whose sparsity pattern is fixed across repeated
solves — the pure-Rust twin of
sparse_klu::KluRealSystem, same public shape (new/factor_and_solve) sosolver.rscan dispatch to either backend mechanically. Caches the symbolic analysis (analyze::analyze) and numeric factorization (factor::Numeric) across calls, usingrefactor::refactorfor cheap numeric-only re-factorization on repeat calls — mirrorsKluRealSystem’s own doc comment and the same pattern validated forRealSparseSystem/BlockLu::refactorelsewhere in this crate.