Skip to content

Commit 0b98d3f

Browse files
committed
Changed: finalize documentation, benchmarks, and error handling
Synchronize the CHANGELOG, README, and REFERENCES for the upcoming release. This includes documenting the new absolute error bound coefficients for determinants, adding a dedicated section for AI agent governance, and refining internal error helpers to use a more consistent coordinate convention for non-finite values. Refs: #86
1 parent 81ecb35 commit 0b98d3f

9 files changed

Lines changed: 266 additions & 117 deletions

File tree

CHANGELOG.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,39 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [Unreleased]
9+
10+
### Added
11+
12+
- Regression tests for solver and determinant overflow handling [`f763b11`](https://github.com/acgetchell/la-stack/commit/f763b119bcc57276b83f370b0bf7abce654c7eb8)
13+
- Defensive-path test coverage for LU and LDLT solve_vec [`87d426f`](https://github.com/acgetchell/la-stack/commit/87d426fca1627445b804fd26b62fc7d9d4f0ae48)
14+
- Const-ify Lu/Ldlt det + solve_vec and Matrix inf_norm + det_errbound [`81ecb35`](https://github.com/acgetchell/la-stack/commit/81ecb35bdaf159f1f44d1eb24274ecf82c6567d5)
15+
16+
### Changed
17+
18+
- Report infinite vs finite off-diagonal pairs as asymmetric [`1805779`](https://github.com/acgetchell/la-stack/commit/1805779dbca49183fbfa95c68ec00984966aa551)
19+
20+
### Documentation
21+
22+
- Strengthen LDLT symmetry precondition and add is_symmetric API [`1693307`](https://github.com/acgetchell/la-stack/commit/1693307c58e30a804e9b79abc783af99be8dc947)
23+
24+
### Fixed
25+
26+
- Propagate NaN in Matrix::inf_norm [#85](https://github.com/acgetchell/la-stack/pull/85) [`16ffa45`](https://github.com/acgetchell/la-stack/commit/16ffa45ade11b21a179cad8fcecc51d802086a1d)
27+
28+
### Maintenance
29+
30+
- Bump taiki-e/install-action from 2.73.0 to 2.75.7 [`a1d1c1e`](https://github.com/acgetchell/la-stack/commit/a1d1c1edba7d5bf6928de4e8cab86ba853685430)
31+
- Bump actions/download-artifact from 4.3.0 to 8.0.1 [`8772ea2`](https://github.com/acgetchell/la-stack/commit/8772ea2f18cf1b94a21e269b4bc0c8e0a3b650eb)
32+
- Bump rand from 0.9.2 to 0.9.4 [`b91670a`](https://github.com/acgetchell/la-stack/commit/b91670a46f82306dde5433ca7c406a8757e3fc59)
33+
- Bump actions/upload-artifact from 7.0.0 to 7.0.1 [`7579274`](https://github.com/acgetchell/la-stack/commit/7579274d56ab8c46eae6906c07a3c0d0fd41c84f)
34+
- Bump actions/github-script from 8.0.0 to 9.0.0 [`a522abd`](https://github.com/acgetchell/la-stack/commit/a522abdabf211d47f0f52cbfc2a349fa72ce87e2)
35+
- Bump actions/cache from 5.0.4 to 5.0.5 [`b12d95c`](https://github.com/acgetchell/la-stack/commit/b12d95c8ce242ea6014c6a6aaa7845ff501d2e4d)
36+
- Bump actions-rust-lang/setup-rust-toolchain [`c728774`](https://github.com/acgetchell/la-stack/commit/c72877460efba3d598f64ff253d2df8f3695acb1)
37+
- Bump astral-sh/setup-uv from 8.0.0 to 8.1.0 [`7a18733`](https://github.com/acgetchell/la-stack/commit/7a18733ba0f39102678bbe30772fcde10f20e9d8)
38+
- Bump taiki-e/install-action from 2.75.7 to 2.75.18 [`433cfc1`](https://github.com/acgetchell/la-stack/commit/433cfc1b01c9128e93c82cb553aa63d4091bace3)
39+
- Bump MSRV to Rust 1.95 and adopt new stable features [`0ab3c33`](https://github.com/acgetchell/la-stack/commit/0ab3c336074f2b866256fbe5db8a8ec5306d580a)
40+
841
## [0.4.0] - 2026-04-11
942

1043
### Added
@@ -35,6 +68,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
3568
- Bump taiki-e/install-action from 2.70.1 to 2.73.0 [`7de720d`](https://github.com/acgetchell/la-stack/commit/7de720dfb8328d01843cfabd15d23086ee98832b)
3669
- Bump astral-sh/setup-uv from 7.6.0 to 8.0.0 [`af40753`](https://github.com/acgetchell/la-stack/commit/af40753130fac56d48da7fce2f18b11dc391ebe6)
3770
- Add performance regression detection for exact-arithmetic benchmarks [`44bce99`](https://github.com/acgetchell/la-stack/commit/44bce99bcae8f852dbf7500e71fa182730e08bca)
71+
- Release v0.4.0 [`843bb3c`](https://github.com/acgetchell/la-stack/commit/843bb3cfcc88e114bdd5e00d242b605af9b2b434)
3872

3973
### Performance
4074

@@ -228,6 +262,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
228262

229263
- Add tarpaulin coverage upload [`7486dfd`](https://github.com/acgetchell/la-stack/commit/7486dfd54e16a6dbde41575c3f35a1acb65f57d2)
230264

265+
[unreleased]: https://github.com/acgetchell/la-stack/compare/v0.4.0..HEAD
231266
[0.4.0]: https://github.com/acgetchell/la-stack/compare/v0.3.0..v0.4.0
232267
[0.3.0]: https://github.com/acgetchell/la-stack/compare/v0.2.2..v0.3.0
233268
[0.2.2]: https://github.com/acgetchell/la-stack/compare/v0.2.1..v0.2.2

README.md

Lines changed: 37 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,27 @@ Storage shown above reflects the `f64` implementation.
242242

243243
¹ Requires `features = ["exact"]`.
244244

245+
## 📊 Benchmarks (vs nalgebra/faer)
246+
247+
![LU solve (factor + solve): median time vs dimension](docs/assets/bench/vs_linalg_lu_solve_median.svg)
248+
249+
Raw data: [docs/assets/bench/vs_linalg_lu_solve_median.csv](docs/assets/bench/vs_linalg_lu_solve_median.csv)
250+
251+
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):
252+
253+
<!-- BENCH_TABLE:lu_solve:median:new:BEGIN -->
254+
| D | la-stack median (ns) | nalgebra median (ns) | faer median (ns) | la-stack vs nalgebra | la-stack vs faer |
255+
|---:|--------------------:|--------------------:|----------------:|---------------------:|----------------:|
256+
| 2 | 2.309 | 4.365 | 140.156 | +47.1% | +98.4% |
257+
| 3 | 18.331 | 22.706 | 181.074 | +19.3% | +89.9% |
258+
| 4 | 27.430 | 51.372 | 210.451 | +46.6% | +87.0% |
259+
| 5 | 53.819 | 70.722 | 276.064 | +23.9% | +80.5% |
260+
| 8 | 143.611 | 160.309 | 356.960 | +10.4% | +59.8% |
261+
| 16 | 611.393 | 580.793 | 871.704 | -5.3% | +29.9% |
262+
| 32 | 2,631.241 | 2,733.946 | 2,832.816 | +3.8% | +7.1% |
263+
| 64 | 17,233.345 | 14,112.678 | 12,164.571 | -22.1% | -41.7% |
264+
<!-- BENCH_TABLE:lu_solve:median:new:END -->
265+
245266
## 📋 Examples
246267

247268
The `examples/` directory contains small, runnable programs:
@@ -289,26 +310,26 @@ If you use this library in academic work, please cite it using [CITATION.cff](CI
289310

290311
For canonical references to the algorithms used by this crate, see [REFERENCES.md](REFERENCES.md).
291312

292-
## 📊 Benchmarks (vs nalgebra/faer)
313+
## 🤖 AI Agents
293314

294-
![LU solve (factor + solve): median time vs dimension](docs/assets/bench/vs_linalg_lu_solve_median.svg)
315+
This repository contains an `AGENTS.md` file, which defines the canonical rules and invariants
316+
for all AI coding assistants and autonomous agents working on this codebase.
295317

296-
Raw data: [docs/assets/bench/vs_linalg_lu_solve_median.csv](docs/assets/bench/vs_linalg_lu_solve_median.csv)
318+
AI tools (including `ChatGPT`, `Claude`, `CodeRabbit`, `KiloCode`, and `WARP`) are expected to read
319+
and follow `AGENTS.md` when proposing or applying changes.
297320

298-
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):
321+
Portions of this library were developed with the assistance of these AI tools:
299322

300-
<!-- BENCH_TABLE:lu_solve:median:new:BEGIN -->
301-
| D | la-stack median (ns) | nalgebra median (ns) | faer median (ns) | la-stack vs nalgebra | la-stack vs faer |
302-
|---:|--------------------:|--------------------:|----------------:|---------------------:|----------------:|
303-
| 2 | 2.309 | 4.365 | 140.156 | +47.1% | +98.4% |
304-
| 3 | 18.331 | 22.706 | 181.074 | +19.3% | +89.9% |
305-
| 4 | 27.430 | 51.372 | 210.451 | +46.6% | +87.0% |
306-
| 5 | 53.819 | 70.722 | 276.064 | +23.9% | +80.5% |
307-
| 8 | 143.611 | 160.309 | 356.960 | +10.4% | +59.8% |
308-
| 16 | 611.393 | 580.793 | 871.704 | -5.3% | +29.9% |
309-
| 32 | 2,631.241 | 2,733.946 | 2,832.816 | +3.8% | +7.1% |
310-
| 64 | 17,233.345 | 14,112.678 | 12,164.571 | -22.1% | -41.7% |
311-
<!-- BENCH_TABLE:lu_solve:median:new:END -->
323+
- [ChatGPT](https://openai.com/chatgpt)
324+
- [Claude](https://www.anthropic.com/claude)
325+
- [CodeRabbit](https://coderabbit.ai/)
326+
- [KiloCode](https://kilocode.ai/)
327+
- [WARP](https://www.warp.dev)
328+
329+
> All code was written and/or reviewed and validated by the author.
330+
331+
For full tool citation metadata, see the [AI-Assisted Development Tools](REFERENCES.md#ai-assisted-development-tools)
332+
section of `REFERENCES.md`.
312333

313334
## 📄 License
314335

REFERENCES.md

Lines changed: 41 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,46 @@ processed by GitHub and other platforms.
88

99
A Zenodo DOI will be added for tagged releases.
1010

11+
## AI-Assisted Development Tools
12+
13+
- Anthropic. "Claude." <https://www.anthropic.com/claude>.
14+
- CodeRabbit AI, Inc. "CodeRabbit." <https://coderabbit.ai/>.
15+
- KiloCode. "KiloCode AI Engineering Assistant." <https://kilocode.ai/>.
16+
- OpenAI. "ChatGPT." <https://openai.com/chatgpt>.
17+
- Warp Dev, Inc. "WARP." <https://www.warp.dev/>.
18+
19+
All AI-generated output was reviewed and/or edited by the maintainer.
20+
No generated content was used without human oversight.
21+
1122
## Linear algebra algorithms
1223

13-
### LU decomposition (Gaussian elimination with partial pivoting)
24+
### Absolute error bound for closed-form determinants
1425

15-
The LU implementation in `la-stack` follows the standard Gaussian elimination / LU factorization
16-
approach with partial pivoting for numerical stability.
26+
`Matrix::det_errbound()` returns a conservative Shewchuk-style absolute error bound [8]
27+
for `Matrix::det_direct()` in dimensions 2–4. The bound has the form
28+
`ERR_COEFF_D · p(|A|)`, where `p(|A|)` is the absolute Leibniz sum (the cofactor-expansion
29+
tree with `|·|` at every leaf) and `ERR_COEFF_D ∈ {ERR_COEFF_2, ERR_COEFF_3, ERR_COEFF_4}`
30+
is a dimension-specific constant derived from the rounding-event count of `det_direct`.
31+
The same bound is used internally by `det_sign_exact()`'s fast filter, but
32+
`det_errbound()` itself is available without the `exact` feature, so downstream crates
33+
can build custom adaptive-precision logic with pure f64 arithmetic.
1734

18-
See references [1-3] below.
35+
### Exact determinant sign (adaptive-precision Bareiss)
36+
37+
`det_sign_exact()` uses a Shewchuk-style f64 error-bound filter [8] (the same bound exposed
38+
by `det_errbound()` above) backed by integer-only Bareiss elimination [7] in `BigInt`. Each
39+
f64 entry is decomposed into `mantissa × 2^exponent`, scaled to a common integer base, and
40+
eliminated without any `BigRational` or GCD overhead.
41+
See `src/exact.rs` for the full architecture description.
42+
43+
### f64 → BigRational conversion (`f64_to_bigrational`)
44+
45+
`f64_to_bigrational` converts an f64 to an exact `BigRational` by decomposing the IEEE 754
46+
binary64 bit representation [9] into its sign, exponent, and significand fields. Because
47+
every finite f64 is exactly `±m × 2^e` (where `m` is an integer), the rational can be
48+
constructed directly via `BigRational::new_raw` without GCD normalization — trailing zeros
49+
in the significand are stripped first so the fraction is already in lowest terms. See
50+
Goldberg [10] for background on IEEE 754 representation and exact rational reconstruction.
1951

2052
### LDL^T factorization (symmetric SPD/PSD)
2153

@@ -26,16 +58,14 @@ pivoting.
2658
For background on the SPD/PSD setting, see [4-5]. For pivoted variants used for symmetric *indefinite*
2759
matrices, see [6].
2860

29-
### Exact determinant sign (adaptive-precision Bareiss)
61+
### LU decomposition (Gaussian elimination with partial pivoting)
3062

31-
`det_sign_exact()` uses a Shewchuk-style f64 error-bound filter [8] backed by integer-only
32-
Bareiss elimination [7] in `BigInt`. Each f64 entry is decomposed into `mantissa × 2^exponent`,
33-
scaled to a common integer base, and eliminated without any `BigRational` or GCD overhead.
34-
See `src/exact.rs` for the full architecture description.
63+
The LU implementation in `la-stack` follows the standard Gaussian elimination / LU factorization
64+
approach with partial pivoting for numerical stability.
3565

36-
## References
66+
See references [1-3] below.
3767

38-
### LU / Gaussian elimination
68+
## References
3969

4070
1. Trefethen, Lloyd N., and Robert S. Schreiber. "Average-case stability of Gaussian elimination."
4171
*SIAM Journal on Matrix Analysis and Applications* 11.3 (1990): 335–360.
@@ -46,23 +76,14 @@ See `src/exact.rs` for the full architecture description.
4676
3. Huang, Han, and K. Tikhomirov. "Average-case analysis of the Gaussian elimination with partial pivoting."
4777
*Probability Theory and Related Fields* 189 (2024): 501–567.
4878
[Open-access PDF](https://link.springer.com/article/10.1007/s00440-024-01276-2) (also: [arXiv:2206.01726](https://arxiv.org/abs/2206.01726))
49-
50-
### LDL^T / Cholesky (symmetric SPD/PSD)
51-
5279
4. Cholesky, Andre-Louis. "On the numerical solution of systems of linear equations"
5380
(manuscript dated 2 Dec 1910; published 2005).
5481
Scan + English analysis: [BibNum](https://www.bibnum.education.fr/mathematiques/algebre/sur-la-resolution-numerique-des-systemes-d-equations-lineaires)
5582
5. Brezinski, Claude. "La methode de Cholesky." (2005).
5683
[PDF](https://eudml.org/doc/252115)
57-
58-
### Pivoted LDL^T (symmetric indefinite)
59-
6084
6. Bunch, J. R., L. Kaufman, and B. N. Parlett. "Decomposition of a Symmetric Matrix."
6185
*Numerische Mathematik* 27 (1976/1977): 95–110.
6286
[Full text](https://eudml.org/doc/132435)
63-
64-
### Exact determinant (`det_exact`, `det_exact_f64`, `det_sign_exact`)
65-
6687
7. Bareiss, Erwin H. "Sylvester's Identity and Multistep Integer-Preserving Gaussian
6788
Elimination." *Mathematics of Computation* 22.103 (1968): 565–578.
6889
[DOI](https://doi.org/10.1090/S0025-5718-1968-0226829-0) ·
@@ -72,17 +93,6 @@ See `src/exact.rs` for the full architecture description.
7293
[DOI](https://doi.org/10.1007/PL00009321) ·
7394
[PDF](https://people.eecs.berkeley.edu/~jrs/papers/robustr.pdf)
7495
Also: Technical Report CMU-CS-96-140, Carnegie Mellon University, May 1996.
75-
76-
### f64 → BigRational conversion (`f64_to_bigrational`)
77-
78-
`f64_to_bigrational` converts an f64 to an exact `BigRational` by decomposing the IEEE 754
79-
binary64 bit representation into its sign, exponent, and significand fields. Because every
80-
finite f64 is exactly `±m × 2^e` (where `m` is an integer), the rational can be constructed
81-
directly via `BigRational::new_raw` without GCD normalization — trailing zeros in the
82-
significand are stripped first so the fraction is already in lowest terms.
83-
84-
See references [9-10] below.
85-
8696
9. IEEE Computer Society. "IEEE Standard for Floating-Point Arithmetic." *IEEE Std 754-2019*
8797
(Revision of IEEE 754-2008), 2019.
8898
[DOI](https://doi.org/10.1109/IEEESTD.2019.8766229)

src/exact.rs

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -63,10 +63,7 @@ fn validate_finite<const D: usize>(m: &Matrix<D>) -> Result<(), LaError> {
6363
for c in 0..D {
6464
if !m.rows[r][c].is_finite() {
6565
cold_path();
66-
return Err(LaError::NonFinite {
67-
row: Some(r),
68-
col: c,
69-
});
66+
return Err(LaError::non_finite_cell(r, c));
7067
}
7168
}
7269
}
@@ -81,7 +78,7 @@ fn validate_finite_vec<const D: usize>(v: &Vector<D>) -> Result<(), LaError> {
8178
for (i, &x) in v.data.iter().enumerate() {
8279
if !x.is_finite() {
8380
cold_path();
84-
return Err(LaError::NonFinite { row: None, col: i });
81+
return Err(LaError::non_finite_at(i));
8582
}
8683
}
8784
Ok(())

src/ldlt.rs

Lines changed: 7 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -62,10 +62,7 @@ impl<const D: usize> Ldlt<D> {
6262
let d = f.rows[j][j];
6363
if !d.is_finite() {
6464
cold_path();
65-
return Err(LaError::NonFinite {
66-
row: Some(j),
67-
col: j,
68-
});
65+
return Err(LaError::non_finite_cell(j, j));
6966
}
7067
if d <= tol {
7168
cold_path();
@@ -77,10 +74,7 @@ impl<const D: usize> Ldlt<D> {
7774
let l = f.rows[i][j] / d;
7875
if !l.is_finite() {
7976
cold_path();
80-
return Err(LaError::NonFinite {
81-
row: Some(i),
82-
col: j,
83-
});
77+
return Err(LaError::non_finite_cell(i, j));
8478
}
8579
f.rows[i][j] = l;
8680
}
@@ -95,10 +89,7 @@ impl<const D: usize> Ldlt<D> {
9589
let new_val = (-l_i_d).mul_add(l_k, f.rows[i][k]);
9690
if !new_val.is_finite() {
9791
cold_path();
98-
return Err(LaError::NonFinite {
99-
row: Some(i),
100-
col: k,
101-
});
92+
return Err(LaError::non_finite_cell(i, k));
10293
}
10394
f.rows[i][k] = new_val;
10495
}
@@ -181,7 +172,7 @@ impl<const D: usize> Ldlt<D> {
181172
}
182173
if !sum.is_finite() {
183174
cold_path();
184-
return Err(LaError::NonFinite { row: None, col: i });
175+
return Err(LaError::non_finite_at(i));
185176
}
186177
x[i] = sum;
187178
i += 1;
@@ -197,10 +188,7 @@ impl<const D: usize> Ldlt<D> {
197188
// `Matrix::det`, `Lu::factor`, and `Ldlt::factor`.
198189
if !diag.is_finite() {
199190
cold_path();
200-
return Err(LaError::NonFinite {
201-
row: Some(i),
202-
col: i,
203-
});
191+
return Err(LaError::non_finite_cell(i, i));
204192
}
205193
if diag <= self.tol {
206194
cold_path();
@@ -210,7 +198,7 @@ impl<const D: usize> Ldlt<D> {
210198
let quotient = x[i] / diag;
211199
if !quotient.is_finite() {
212200
cold_path();
213-
return Err(LaError::NonFinite { row: None, col: i });
201+
return Err(LaError::non_finite_at(i));
214202
}
215203
x[i] = quotient;
216204
i += 1;
@@ -228,7 +216,7 @@ impl<const D: usize> Ldlt<D> {
228216
}
229217
if !sum.is_finite() {
230218
cold_path();
231-
return Err(LaError::NonFinite { row: None, col: i });
219+
return Err(LaError::non_finite_at(i));
232220
}
233221
x[i] = sum;
234222
ii += 1;

0 commit comments

Comments
 (0)