Skip to content

Commit ad660ba

Browse files
authored
Merge pull request #143 from acgetchell/feat/138-release-performance-comparison
feat(bench): add release performance comparison workflow
2 parents 4b792c4 + 1222c93 commit ad660ba

12 files changed

Lines changed: 1247 additions & 266 deletions

File tree

‎CONTRIBUTING.md‎

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# Contributing
2+
3+
Thanks for helping improve `la-stack`. This crate is intentionally small and
4+
invariant-heavy, so changes should preserve mathematical correctness, API
5+
clarity, and the fixed-dimension stack-allocation model.
6+
7+
## Workflow
8+
9+
```bash
10+
cargo install just
11+
just setup # install/verify dev tools + sync Python deps
12+
just check # lint/validate (non-mutating)
13+
just fix # apply auto-fixes (mutating)
14+
just ci # lint + tests + examples + bench compile
15+
```
16+
17+
The repository uses Rust-native tooling for documentation and config checks:
18+
`rumdl` for Markdown, `dprint` with `pretty_yaml` for YAML, `taplo` for TOML,
19+
and `typos` for spelling. GitHub Actions references are SHA-pinned, restricted
20+
to an explicit allowlist, and kept with readable version comments for review.
21+
22+
CI runs `just ci` on Ubuntu, macOS, and Windows to keep platform coverage
23+
aligned with the local comprehensive validation path.
24+
25+
For coverage commands and report locations, see [`docs/COVERAGE.md`](docs/COVERAGE.md).
26+
For benchmark methodology, see [`docs/BENCHMARKING.md`](docs/BENCHMARKING.md).
27+
For the full set of developer commands, run `just --list`.
28+
29+
## AI-Assisted Development
30+
31+
This repository contains an [`AGENTS.md`](AGENTS.md) file, which defines the
32+
canonical rules and invariants for AI coding assistants and autonomous agents
33+
working on this codebase.
34+
35+
AI tools, including ChatGPT, Claude, CodeRabbit, Codex, KiloCode, and WARP, are
36+
expected to read and follow `AGENTS.md` when proposing or applying changes.
37+
38+
Portions of this library were developed with the assistance of these tools:
39+
40+
- [ChatGPT](https://openai.com/chatgpt)
41+
- [Claude](https://www.anthropic.com/claude)
42+
- [CodeRabbit](https://coderabbit.ai/)
43+
- [Codex](https://openai.com/codex/)
44+
- [KiloCode](https://kilocode.ai/)
45+
- [WARP](https://www.warp.dev)
46+
47+
All code was written and/or reviewed and validated by the author.
48+
49+
For full tool citation metadata, see the
50+
[AI-Assisted Development Tools](REFERENCES.md#ai-assisted-development-tools)
51+
section of [`REFERENCES.md`](REFERENCES.md).

‎README.md‎

Lines changed: 29 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,16 @@ See [CHANGELOG.md](CHANGELOG.md) for release history and
4949
- Large matrices/dimensions with parallelism: use [`faer`](https://crates.io/crates/faer) if you need this
5050
- Alternate floating-point scalar families: `la-stack` supports `f64` and optional exact arithmetic, not `f32` / `f16` APIs
5151

52+
## ✅ Use this crate when
53+
54+
- Your matrices and vectors have small, fixed dimensions known at compile time
55+
- Stack allocation and `Copy` value semantics fit your data flow
56+
- You want explicit LU / LDLT / determinant APIs rather than a broad algebra toolkit
57+
- You need exact determinants, exact determinant signs, or exact linear solves
58+
for fixed-size systems
59+
- Robust predicates matter for geometry-style workloads near degeneracy
60+
- You prefer a default build with no runtime dependencies
61+
5262
## 🔢 Scalar types
5363

5464
The scalar model is intentionally limited to `f64` for floating-point work and
@@ -71,6 +81,12 @@ Add this to your `Cargo.toml`:
7181
la-stack = "0.4.2"
7282
```
7383

84+
### Feature flags
85+
86+
- `default`: no runtime dependencies
87+
- `exact`: `BigRational` exact determinant and solve APIs
88+
- `bench`: Criterion, nalgebra, and faer for internal benchmarks
89+
7490
Solve a 5×5 system via LU:
7591

7692
```rust
@@ -335,7 +351,14 @@ not store NaN or infinity.
335351

336352
Raw data: [docs/assets/bench/vs_linalg_lu_solve_median.csv](docs/assets/bench/vs_linalg_lu_solve_median.csv)
337353

338-
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):
354+
Representative benchmark: `lu_solve` factors the matrix and solves one
355+
right-hand side. Median time is lower-is-better, and the “la-stack vs
356+
nalgebra/faer” columns show the % time reduction relative to each baseline
357+
(positive = la-stack faster). This is not an aggregate score across all
358+
operations.
359+
360+
For the full per-kernel comparison methodology, input construction, and
361+
release-comparison workflow details, see [docs/BENCHMARKING.md](docs/BENCHMARKING.md).
339362

340363
<!-- BENCH_TABLE:lu_solve:median:new:BEGIN -->
341364

@@ -397,37 +420,22 @@ CI runs `just ci` on Ubuntu, macOS, and Windows to keep platform coverage
397420
aligned with the local comprehensive validation path.
398421

399422
For coverage commands and report locations, see [`docs/COVERAGE.md`](docs/COVERAGE.md).
400-
For the full set of developer commands, see `just --list` and `AGENTS.md`.
423+
For the full contributor workflow, see [CONTRIBUTING.md](CONTRIBUTING.md).
401424

402425
## 📝 Citation
403426

404427
If you use this library in academic work, please cite it using [CITATION.cff](CITATION.cff) (or GitHub's
405-
"Cite this repository" feature). A Zenodo DOI will be added for tagged releases.
428+
"Cite this repository" feature). Tagged releases are archived on Zenodo.
406429

407430
## 📚 References
408431

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

411434
## 🤖 AI Agents
412435

413-
This repository contains an `AGENTS.md` file, which defines the canonical rules and invariants
414-
for all AI coding assistants and autonomous agents working on this codebase.
415-
416-
AI tools (including `ChatGPT`, `Claude`, `CodeRabbit`, `KiloCode`, and `WARP`) are expected to read
417-
and follow `AGENTS.md` when proposing or applying changes.
418-
419-
Portions of this library were developed with the assistance of these AI tools:
420-
421-
- [ChatGPT](https://openai.com/chatgpt)
422-
- [Claude](https://www.anthropic.com/claude)
423-
- [CodeRabbit](https://coderabbit.ai/)
424-
- [KiloCode](https://kilocode.ai/)
425-
- [WARP](https://www.warp.dev)
426-
427-
> All code was written and/or reviewed and validated by the author.
428-
429-
For full tool citation metadata, see the [AI-Assisted Development Tools](REFERENCES.md#ai-assisted-development-tools)
430-
section of `REFERENCES.md`.
436+
AI coding assistants should read [AGENTS.md](AGENTS.md) before proposing or
437+
applying changes. See [CONTRIBUTING.md](CONTRIBUTING.md) for the repository's
438+
AI-assisted development note.
431439

432440
## 📄 License
433441

‎benches/common/vs_linalg.rs‎

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
//! Shared helpers for the `vs_linalg` benchmark and its smoke tests.
2+
3+
use faer::linalg::solvers::{Ldlt as FaerLdlt, PartialPivLu};
4+
use faer::perm::PermRef;
5+
use nalgebra::SMatrix;
6+
7+
/// Return `det(P)` for faer's permutation representation.
8+
///
9+
/// Sign(det(P)) is +1 for even permutations and -1 for odd. Parity is computed
10+
/// from the number of cycles: `sign = (-1)^(n - cycles)`.
11+
pub fn faer_perm_sign(p: PermRef<'_, usize>) -> f64 {
12+
let (forward, _inverse) = p.arrays();
13+
let n = forward.len();
14+
15+
let mut seen = vec![false; n];
16+
let mut cycles = 0usize;
17+
18+
for start in 0..n {
19+
if seen[start] {
20+
continue;
21+
}
22+
cycles += 1;
23+
24+
let mut i = start;
25+
while !seen[i] {
26+
seen[i] = true;
27+
i = forward[i];
28+
}
29+
}
30+
31+
if (n - cycles).is_multiple_of(2) {
32+
1.0
33+
} else {
34+
-1.0
35+
}
36+
}
37+
38+
/// Compute a determinant from a faer partial-pivot LU factorization.
39+
pub fn faer_det_from_partial_piv_lu(lu: &PartialPivLu<f64>) -> f64 {
40+
// For PA = LU with unit-lower L, det(A) = det(P) * det(U).
41+
let u = lu.U();
42+
let mut det = 1.0;
43+
for i in 0..u.nrows() {
44+
det *= u[(i, i)];
45+
}
46+
det * faer_perm_sign(lu.P())
47+
}
48+
49+
/// Compute a determinant from a faer LDLT factorization.
50+
pub fn faer_det_from_ldlt(ldlt: &FaerLdlt<f64>) -> f64 {
51+
let d = ldlt.D().column_vector();
52+
let mut det = 1.0;
53+
for i in 0..d.nrows() {
54+
det *= d[i];
55+
}
56+
det
57+
}
58+
59+
/// Return a deterministic, strictly diagonally-dominant benchmark matrix entry.
60+
#[inline]
61+
#[allow(clippy::cast_precision_loss)] // D, r, c are small integers, precision loss is not an issue.
62+
pub fn matrix_entry<const D: usize>(r: usize, c: usize) -> f64 {
63+
if r == c {
64+
// Strict diagonal dominance for stability.
65+
(r as f64).mul_add(1.0e-3, (D as f64) + 1.0)
66+
} else {
67+
// Small, varying off-diagonals.
68+
0.1 / ((r + c + 1) as f64)
69+
}
70+
}
71+
72+
/// Build the shared matrix rows used by all crates for a dimension.
73+
#[inline]
74+
pub fn make_matrix_rows<const D: usize>() -> [[f64; D]; D] {
75+
let mut rows = [[0.0; D]; D];
76+
77+
for (r, row) in rows.iter_mut().enumerate() {
78+
for (c, entry) in row.iter_mut().enumerate() {
79+
*entry = matrix_entry::<D>(r, c);
80+
}
81+
}
82+
83+
rows
84+
}
85+
86+
/// Return a deterministic benchmark vector entry.
87+
#[inline]
88+
#[allow(clippy::cast_precision_loss)] // i is a small integer, precision loss is not an issue.
89+
pub fn vector_entry(i: usize, offset: f64) -> f64 {
90+
(i as f64) + 1.0 + offset
91+
}
92+
93+
/// Build the shared vector input used by all crates for a dimension.
94+
#[inline]
95+
pub fn make_vector_array<const D: usize>(offset: f64) -> [f64; D] {
96+
let mut data = [0.0; D];
97+
98+
for (i, entry) in data.iter_mut().enumerate() {
99+
*entry = vector_entry(i, offset);
100+
}
101+
102+
data
103+
}
104+
105+
/// Compute nalgebra's matrix infinity norm using la-stack's row-sum convention.
106+
#[inline]
107+
pub fn nalgebra_inf_norm<const D: usize>(m: &SMatrix<f64, D, D>) -> f64 {
108+
// Infinity norm = max absolute row sum.
109+
let mut max_row_sum = 0.0;
110+
111+
for r in 0..D {
112+
let mut row_sum = 0.0;
113+
for c in 0..D {
114+
row_sum += m[(r, c)].abs();
115+
}
116+
if row_sum > max_row_sum {
117+
max_row_sum = row_sum;
118+
}
119+
}
120+
121+
max_row_sum
122+
}

0 commit comments

Comments
 (0)