Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
113 changes: 106 additions & 7 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,98 @@ When making changes in this repo, prioritize (in order):
- Speed
- Coverage (but keep the code idiomatic Rust)

## Design Principles

This is a scientific linear-algebra library. Design decisions trade off in
roughly this priority: mathematical correctness → API stability →
composability → idiomatic Rust → performance within scope. The sections
below spell out what each means in practice; when in doubt, favour the
invariant over the convenient edit.

### Mathematical correctness as an invariant

- Exact paths (`*_exact`) never silently lose precision. When f64 output
is required, a separate `*_exact_f64` method returns
[`LaError::Overflow`] on unrepresentability — not a truncation.
- Any f64 operation that can accumulate rounding error either documents
its absolute bound (`det_errbound`, `ERR_COEFF_*`) or explicitly states
that no bound is provided.
- Non-finite values (NaN, ±∞) always surface as
`LaError::NonFinite { row, col }` with source-location metadata. No
silent NaN propagation, no `unwrap_or(f64::NAN)`.
- Algorithms cite their source (Shewchuk, Bareiss, Goldberg, …) via
`REFERENCES.md` and document their conditioning behaviour.

### Public-API stability

- Error enums are `#[non_exhaustive]`; public wrapper types are
`#[must_use]`.
- New functionality is additive: use the prelude for ergonomic re-exports;
never silently rename or remove a public item.
- Pre-1.0 semver: `0.x.Y` is a patch-level additive bump, `0.X.y` is a
minor bump that may include breaking changes. Conventional-commit
types (`feat`, `fix`, `refactor`, …) mirror this convention.

### Composability

- Const-generic `D` for every core type (`Matrix<D>`, `Vector<D>`,
`Lu<D>`, `Ldlt<D>`). No runtime dimension.
- Stack allocation by default; heap only behind a feature flag or where
exact arithmetic inherently requires it (`BigInt` / `BigRational`).
- Feature flags isolate optional dependency weight; default builds stay
dep-minimal.

### Idiomatic Rust as a proxy for mathematical clarity

- `const fn` wherever possible — not for micro-optimisation, but because
compile-time evaluation forces a pure function of inputs.
- `Result<_, LaError>` for all fallible operations. Panics are reserved
for debug-only precondition violations (e.g. LDLT symmetry check) and
documented on the method.
- Borrow by default (`&T`, `&[T]`); return borrowed views when possible.
- Type and function names match textbook vocabulary (`Matrix`, `Vector`,
`Lu`, `Ldlt`, `solve_vec`, `det`, `inf_norm`). Avoid Rust-ecosystem
abstractions that obscure the math.

### Scientific notation in docs

- Unicode math (×, ≤, ≥, ∈, Σ, ², `2^-50`, …) is welcome in doc
comments — readability trumps ASCII-only preference.
- Reference literature via `REFERENCES.md` numbered citations (e.g.
`\[8\]`, `\[9-10\]`).
- State invariants mathematically where possible
(`|A[i][i]| > Σ_{j≠i} |A[i][j]|`) rather than prose-only.

### Performance within scope

- Performance is a design goal, but strictly subordinate to the
principles above. Never trade correctness, stability, or clarity for
speed; if the two conflict, re-scope the problem rather than
compromise the invariant.
- The library earns its speed through *deliberate scope restriction*:
fixed small dimensions via const generics, stack-allocated storage,
and closed-form algorithms where available (D ≤ 4 for `det_direct` /
`det_errbound`). Problems outside this scope — large or dynamic
dimensions, sparse matrices, parallelism — belong to `nalgebra` or
`faer` (see anti-goals in `README.md`).
- Within scope, prefer allocation-free paths, `const fn` wherever the
inputs allow, and FMA where applicable. Validate any performance
claim against the `bench-vs-linalg` (vs nalgebra / faer) or
`bench-exact` (exact-arithmetic) suites before relying on it.

### Testing mirrors the principles

- Unit tests cover known values, error paths, and dimension-generic
correctness across D=2..=5 (see **Dimension Coverage** below).
- Proptests under `tests/proptest_*.rs` cover algebraic invariants
(round-trip, residual, sign agreement) — not just "does it not panic".
- Adversarial inputs (near-singular, large-entry, Hilbert-style
ill-conditioning) accompany well-conditioned inputs in both tests and
benchmarks.
- When a public API has two paths for the same question (fast filter +
exact fallback), a proptest verifies they agree on the domain where
both are defined.

## Core Rules

### Git Operations
Expand Down Expand Up @@ -124,17 +216,19 @@ just examples # Run all examples
### Detailed Command Reference

- All tests (Rust + Python): `just test-all`
- Benchmark comparison (generate `docs/PERFORMANCE.md`): `just bench-compare` (snapshot) or `just bench-compare v0.3.0` (vs baseline)
- Benchmark comparison (generate `docs/PERFORMANCE.md`): `just bench-compare` (snapshot) or `just bench-compare v0.4.1` (vs baseline)
- Benchmarks: `cargo bench` (or `just bench`)
- Benchmarks (exact arithmetic): `just bench-exact`
- Benchmarks (save baseline): `just bench-save-baseline v0.3.0`
- Benchmarks (la-stack vs nalgebra/faer): `just bench-vs-linalg [filter]` (full run) or `just bench-vs-linalg-quick [filter]` (reduced)
- Benchmarks (plot vs_linalg CSV/SVG): `just plot-vs-linalg [metric] [stat] [sample] [update_readme]` / `just plot-vs-linalg-readme [metric] [stat] [sample] [update_readme]`
- Benchmarks (save baseline): `just bench-save-baseline v0.4.1`
- Build (debug): `cargo build` (or `just build`)
- Build (release): `cargo build --release` (or `just build-release`)
- Changelog (generate full): `just changelog` (runs `git-cliff -o CHANGELOG.md` + post-processing)
- Changelog (prepend unreleased): `just changelog-unreleased v0.3.0`
- Changelog (prepend unreleased): `just changelog-unreleased v0.4.1`
- Coverage (CI XML): `just coverage-ci`
- Coverage (HTML): `just coverage`
- Create release tag: `just tag v0.3.0` (creates annotated tag from CHANGELOG.md section)
- Create release tag: `just tag v0.4.1` (creates annotated tag from CHANGELOG.md section) / `just tag-force v0.4.1` (recreate if the tag already exists)
- Fast compile check (no binary produced): `cargo check` (or `just check-fast`)
- Fast Rust tests (lib + doc): `just test`
- Format: `cargo fmt` (or `just fmt`)
Expand Down Expand Up @@ -195,7 +289,7 @@ When using `gh` to view issues, PRs, or other GitHub objects:
Use the `gh` CLI to read, create, and edit issues:

- **Read**: `gh issue view <number> --json title,body,labels,milestone | cat`
- **List**: `gh issue list --json number,title,labels --jq '.[] | "#\(.number) \(.title)"' | cat` (add `--label enhancement`, `--milestone v0.4.0`, etc. to filter)
- **List**: `gh issue list --json number,title,labels --jq '.[] | "#\(.number) \(.title)"' | cat` (add `--label enhancement`, `--milestone v0.4.1`, etc. to filter)
- **Create**: `gh issue create --title "..." --body "..." --label enhancement --label rust`
- **Edit**: `gh issue edit <number> --add-label "..."`, `--milestone "..."`, `--title "..."`
- **Comment**: `gh issue comment <number> --body "..."`
Expand All @@ -204,7 +298,7 @@ Use the `gh` CLI to read, create, and edit issues:
When creating or updating issues:

- **Labels**: Use appropriate labels: `enhancement`, `bug`, `performance`, `documentation`, `rust`, `python`, etc.
- **Milestones**: Assign to the appropriate milestone (e.g., `v0.3.0`, `v0.4.0`)
- **Milestones**: Assign to the appropriate milestone (e.g., `v0.4.1`, `v0.5.0`)
- **Dependencies**: Document relationships in issue body and comments:
- "Depends on: #XXX" - this issue cannot start until #XXX is complete
- "Blocks: #YYY" - #YYY cannot start until this issue is complete
Expand Down Expand Up @@ -243,7 +337,12 @@ When creating or updating issues:
f64 filter for fast sign resolution
- Linear system solve: `solve_exact()`, `solve_exact_f64()` via Gaussian elimination
with first-non-zero pivoting in `BigRational`
- Rust tests are inline `#[cfg(test)]` modules in each `src/*.rs` file.
- Rust unit tests are inline `#[cfg(test)]` modules in each `src/*.rs` file.
- Property-based tests live under `tests/proptest_*.rs` (uses the `proptest`
dev-dependency): `proptest_matrix.rs`, `proptest_vector.rs`,
`proptest_factorizations.rs`, and `proptest_exact.rs` (the last gated on
the `exact` feature). They run as integration tests via
`just test-integration` or `just test-all`.
- Python tests live in `scripts/tests/` and run via `just test-python` (`uv run pytest`).
- The public API re-exports these items from `src/lib.rs`.
- The `justfile` defines all dev workflows (see `just --list`).
Expand Down
16 changes: 14 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,25 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]
## [0.4.1] - 2026-04-21

### Added

- Regression tests for solver and determinant overflow handling [`f763b11`](https://github.com/acgetchell/la-stack/commit/f763b119bcc57276b83f370b0bf7abce654c7eb8)
- Defensive-path test coverage for LU and LDLT solve_vec [`87d426f`](https://github.com/acgetchell/la-stack/commit/87d426fca1627445b804fd26b62fc7d9d4f0ae48)
- Const-ify Lu/Ldlt det + solve_vec and Matrix inf_norm + det_errbound [`81ecb35`](https://github.com/acgetchell/la-stack/commit/81ecb35bdaf159f1f44d1eb24274ecf82c6567d5)
- Fast-filter boundary proptests for exact determinant sign [`6357db3`](https://github.com/acgetchell/la-stack/commit/6357db35c70bca1b93e6bbf9a4fd231913631950)

### Changed

- Report infinite vs finite off-diagonal pairs as asymmetric [`1805779`](https://github.com/acgetchell/la-stack/commit/1805779dbca49183fbfa95c68ec00984966aa551)
- Finalize documentation, benchmarks, and error handling [`0b98d3f`](https://github.com/acgetchell/la-stack/commit/0b98d3f6dbdd74699c318c4744a2b2f9a1b78481)
- Consolidate and expand const-evaluability tests via macros [`f8d80a0`](https://github.com/acgetchell/la-stack/commit/f8d80a01e9913f87e1b19b2ad5ffbc0994e2bfdb)
- Refactor solve_exact to use hybrid Bareiss forward elimination [`ecbbe8a`](https://github.com/acgetchell/la-stack/commit/ecbbe8a571ccaeb9cfedbf0269b8db44d43a5773)
- Polish exact module (Component struct, errors, perf) [`53a5be6`](https://github.com/acgetchell/la-stack/commit/53a5be6abecc0af332398236ed6803ed75564b03)
- Add adversarial-input coverage for exact arithmetic [#80](https://github.com/acgetchell/la-stack/pull/80) [`5bf5815`](https://github.com/acgetchell/la-stack/commit/5bf5815cb165c3b6145c5592420a58085a66efaa)
- Expand exact-arithmetic re-exports and adversarial benchmarks [`b1a491d`](https://github.com/acgetchell/la-stack/commit/b1a491d902ccdaba6f9cd2e6f8e05514b6dfa3de)
- Update AGENTS.md [`1e0648d`](https://github.com/acgetchell/la-stack/commit/1e0648dad3147ef127c775d8969b7cd214a2a6ed)

### Documentation

Expand All @@ -38,6 +46,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Bump taiki-e/install-action from 2.75.7 to 2.75.18 [`433cfc1`](https://github.com/acgetchell/la-stack/commit/433cfc1b01c9128e93c82cb553aa63d4091bace3)
- Bump MSRV to Rust 1.95 and adopt new stable features [`0ab3c33`](https://github.com/acgetchell/la-stack/commit/0ab3c336074f2b866256fbe5db8a8ec5306d580a)

### Performance

- Integer-only forward elimination for gauss_solve [#72](https://github.com/acgetchell/la-stack/pull/72) [`a1d3bdb`](https://github.com/acgetchell/la-stack/commit/a1d3bdbdb6fcb778a78a2a3d0cb66b79484e1472)

## [0.4.0] - 2026-04-11

### Added
Expand Down Expand Up @@ -262,7 +274,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

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

[unreleased]: https://github.com/acgetchell/la-stack/compare/v0.4.0..HEAD
[0.4.1]: https://github.com/acgetchell/la-stack/compare/v0.4.0..v0.4.1
[0.4.0]: https://github.com/acgetchell/la-stack/compare/v0.3.0..v0.4.0
[0.3.0]: https://github.com/acgetchell/la-stack/compare/v0.2.2..v0.3.0
[0.2.2]: https://github.com/acgetchell/la-stack/compare/v0.2.1..v0.2.2
Expand Down
2 changes: 1 addition & 1 deletion CITATION.cff
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ cff-version: 1.2.0
message: "If you use this software, please cite it as below."
type: software
title: "la-stack: Fast, stack-allocated linear algebra for fixed dimensions in Rust"
version: 0.4.0
version: 0.4.1
url: "https://github.com/acgetchell/la-stack"
repository-code: "https://github.com/acgetchell/la-stack"
identifiers:
Expand Down
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "la-stack"
version = "0.4.0"
version = "0.4.1"
edition = "2024"
rust-version = "1.95"
license = "BSD-3-Clause"
Expand Down
20 changes: 10 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ Add this to your `Cargo.toml`:

```toml
[dependencies]
la-stack = "0.4.0"
la-stack = "0.4.1"
```

Solve a 5×5 system via LU:
Expand Down Expand Up @@ -154,7 +154,7 @@ rationals (this pulls in `num-bigint`, `num-rational`, and `num-traits` for

```toml
[dependencies]
la-stack = { version = "0.4.0", features = ["exact"] }
la-stack = { version = "0.4.1", features = ["exact"] }
```

**Determinants:**
Expand Down Expand Up @@ -258,14 +258,14 @@ Summary (median time; lower is better). The “la-stack vs nalgebra/faer” colu
<!-- BENCH_TABLE:lu_solve:median:new:BEGIN -->
| D | la-stack median (ns) | nalgebra median (ns) | faer median (ns) | la-stack vs nalgebra | la-stack vs faer |
|---:|--------------------:|--------------------:|----------------:|---------------------:|----------------:|
| 2 | 2.309 | 4.365 | 140.156 | +47.1% | +98.4% |
| 3 | 18.331 | 22.706 | 181.074 | +19.3% | +89.9% |
| 4 | 27.430 | 51.372 | 210.451 | +46.6% | +87.0% |
| 5 | 53.819 | 70.722 | 276.064 | +23.9% | +80.5% |
| 8 | 143.611 | 160.309 | 356.960 | +10.4% | +59.8% |
| 16 | 611.393 | 580.793 | 871.704 | -5.3% | +29.9% |
| 32 | 2,631.241 | 2,733.946 | 2,832.816 | +3.8% | +7.1% |
| 64 | 17,233.345 | 14,112.678 | 12,164.571 | -22.1% | -41.7% |
| 2 | 2.173 | 4.448 | 139.923 | +51.2% | +98.4% |
| 3 | 13.989 | 34.607 | 180.026 | +59.6% | +92.2% |
| 4 | 27.580 | 48.435 | 203.163 | +43.1% | +86.4% |
| 5 | 53.517 | 75.935 | 274.375 | +29.5% | +80.5% |
| 8 | 134.859 | 162.859 | 371.463 | +17.2% | +63.7% |
| 16 | 635.775 | 576.171 | 846.189 | -10.3% | +24.9% |
| 32 | 2,704.570 | 2,731.740 | 2,589.494 | +1.0% | -4.4% |
| 64 | 17,381.460 | 13,744.505 | 11,276.642 | -26.5% | -54.1% |
<!-- BENCH_TABLE:lu_solve:median:new:END -->

## 📋 Examples
Expand Down
4 changes: 2 additions & 2 deletions docs/BENCHMARKING.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,10 @@ just bench-vs-linalg
just bench-exact

# Save an exact baseline (e.g., before optimising)
just bench-save-baseline v0.4.0
just bench-save-baseline v0.4.1

# Compare current code against a saved baseline
just bench-compare v0.4.0
just bench-compare v0.4.1

# Generate a snapshot without comparison
just bench-compare
Expand Down
16 changes: 8 additions & 8 deletions docs/assets/bench/vs_linalg_lu_solve_median.csv
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
D,la_stack,la_lo,la_hi,nalgebra,na_lo,na_hi,faer,fa_lo,fa_hi
2,2.309084094214292,2.303508072051772,2.320556696513248,4.364518872374484,4.358154936085015,4.36964789592874,140.15575262034162,139.53334588117198,140.4495844508752
3,18.33097673394614,18.273359518783604,18.361403406211608,22.705897034612143,22.564093728463128,22.91072188569287,181.0737171931479,180.38682460200042,181.70129206459293
4,27.430461775038097,27.41326480258085,27.462758331001304,51.37175309721522,51.29468685146327,51.477167992692316,210.45129217481627,210.11591462124417,211.12191423331922
5,53.8187502446859,53.70850909928479,53.85944288329581,70.72209914126024,70.58461080060759,70.80642561266758,276.0642099442515,275.26718428973214,277.08908382769255
8,143.61107101616628,143.08566824669077,144.23973111184426,160.3091383596412,157.74277868119285,162.4765631219522,356.9597107252403,356.09333193008604,357.7579293922214
16,611.393474121212,607.0554516008083,614.0988895347266,580.7933953429945,578.7449824562907,581.0957836912207,871.7039621195331,869.4837470449172,873.6488504064271
32,2631.241121426466,2626.669908635426,2635.1795337149238,2733.9455793946936,2732.3185346416067,2737.926103179753,2832.8157108843534,2829.2559523809523,2837.7406015037595
64,17233.344659711875,17191.217013729976,17341.914285714287,14112.678414368063,13965.669014084508,14162.695618153364,12164.571092017737,12137.462174452254,12181.512446567765
2,2.172623282439358,2.165444772092298,2.185506739324025,4.448408979470227,4.439782928520427,4.459719277058991,139.9226022608816,139.06474375246668,140.51212151197308
3,13.989121746672993,13.955023991167852,14.013050521832692,34.606732435908626,33.499470537742226,34.761521044572746,180.02609852620088,179.59708359326262,180.8791974100286
4,27.58037208037632,27.526517442670773,27.627054765320764,48.435129151777026,48.360737894391505,48.549664199225454,203.162599789916,202.79373021936047,203.32348086504715
5,53.5167292278733,53.2079241828104,53.645503456564526,75.93470878300764,75.4146378575776,76.19896610821027,274.374828180443,272.8463967468175,275.1594136908799
8,134.8587910191369,134.33353258195461,135.23630002147613,162.85903692402766,162.5110280122886,163.16671953470214,371.4632572777341,369.98617150369097,373.2859170186846
16,635.7754523403698,630.758052532669,638.095305070158,576.1714016094243,575.8381924198251,577.8533527696793,846.1888291695698,843.6908420833043,847.8866115184559
32,2704.5696137095592,2696.7101307744565,2715.9278532608696,2731.7404900064475,2726.3126052188554,2735.1429635561008,2589.4939744827334,2587.8085641182724,2594.9184784361632
64,17381.45994623656,17346.594885060753,17418.868905879215,13744.504879275653,13711.504555743551,13761.18247949234,11276.642192271553,11263.305944055945,11293.149440751858
Loading
Loading