Skip to content

Commit 1fa2f55

Browse files
committed
feat!(api): make Matrix and Vector finite by construction
- Parse raw matrix and vector storage through Matrix::try_from_rows and Vector::try_new, with private storage carrying the finite-entry invariant through downstream kernels. - Rename LU and LDLT solves to solve, and remove compatibility aliases that weaken the finite-proof API model. - Add Semgrep guardrails for the finite API contract and public-documentation unwrap/expect usage. - Update benchmarks, examples, docs, and tooling pins for the new API and current Markdown/spelling lint behavior. BREAKING CHANGE: Matrix::from_rows is no longer a public raw constructor; use Matrix::try_from_rows for raw row-major input. BREAKING CHANGE: Vector::new is no longer a public raw constructor; use Vector::try_new for raw vector input. BREAKING CHANGE: Lu::solve_vec and Ldlt::solve_vec are removed; use solve. BREAKING CHANGE: DEFAULT_PIVOT_TOL is removed; use DEFAULT_SINGULAR_TOL. Closes #137
1 parent eeeb1e7 commit 1fa2f55

25 files changed

Lines changed: 894 additions & 843 deletions

.github/workflows/ci.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,9 @@ env:
2727
CARGO_NEXTEST_VERSION: "0.9.137"
2828
DPRINT_VERSION: "0.54.0"
2929
JUST_VERSION: "1.51.0"
30-
RUMDL_VERSION: "0.2.6"
30+
RUMDL_VERSION: "0.2.9"
3131
TAPLO_VERSION: "0.10.0"
32-
TYPOS_VERSION: "1.47.1"
32+
TYPOS_VERSION: "1.47.2"
3333
UV_VERSION: "0.11.19"
3434
ZIZMOR_VERSION: "1.25.2"
3535

AGENTS.md

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,12 @@ invariant over the convenient edit.
3636

3737
- Error enums are `#[non_exhaustive]`; public wrapper types are
3838
`#[must_use]`.
39-
- New functionality is additive: use the prelude for ergonomic re-exports;
40-
never silently rename or remove a public item.
41-
- Pre-1.0 semver: `0.x.Y` is a patch-level additive bump, `0.X.y` is a
42-
minor bump that may include breaking changes. Conventional-commit
43-
types (`feat`, `fix`, `refactor`, …) mirror this convention.
39+
- New functionality is additive by default: use the prelude for ergonomic
40+
re-exports, and avoid churn for its own sake.
41+
- Pre-1.0 semver: any `0.x.y` release may include breaking API changes when
42+
they materially improve correctness, orthogonality, or long-term API clarity.
43+
Do not keep compatibility aliases that weaken the public model; document
44+
intentional breaks clearly in release notes and commit messages.
4445
- Do **not** automatically update the library version in `Cargo.toml`,
4546
`Cargo.lock`, README dependency snippets, or related docs during ordinary
4647
feature, fix, review, or hygiene work. Version bumps are maintainer-driven
@@ -68,7 +69,7 @@ invariant over the convenient edit.
6869
`Option` instead of relying on `panic!`, `assert!`, `unwrap`, or `expect`.
6970
- Borrow by default (`&T`, `&[T]`); return borrowed views when possible.
7071
- Type and function names match textbook vocabulary (`Matrix`, `Vector`,
71-
`Lu`, `Ldlt`, `solve_vec`, `det`, `inf_norm`). Avoid Rust-ecosystem
72+
`Lu`, `Ldlt`, `solve`, `det`, `inf_norm`). Avoid Rust-ecosystem
7273
abstractions that obscure the math.
7374

7475
### Scientific notation in docs
@@ -210,7 +211,7 @@ testable.
210211
#### Reference examples
211212

212213
- `src/matrix.rs``gen_public_api_matrix_tests!`
213-
- `src/lu.rs``gen_public_api_pivoting_solve_vec_and_det_tests!`, `gen_public_api_tridiagonal_smoke_solve_vec_and_det_tests!`
214+
- `src/lu.rs``gen_public_api_pivoting_solve_and_det_tests!`, `gen_public_api_tridiagonal_smoke_solve_and_det_tests!`
214215
- `src/ldlt.rs``gen_public_api_ldlt_identity_tests!`, `gen_public_api_ldlt_diagonal_tests!`
215216
- `src/exact.rs``gen_det_exact_tests!`, `gen_det_exact_f64_tests!`, `gen_solve_exact_tests!`, `gen_solve_exact_f64_tests!`
216217

@@ -354,11 +355,11 @@ When creating or updating issues:
354355
355356
- This is a single Rust *library crate* (no `src/main.rs`). The crate root is `src/lib.rs`.
356357
- The linear algebra implementation is split across:
357-
- `src/lib.rs`: crate root + shared items (`LaError`, `DEFAULT_SINGULAR_TOL`, `DEFAULT_PIVOT_TOL`) + re-exports
358+
- `src/lib.rs`: crate root + shared items (`LaError`, `DEFAULT_SINGULAR_TOL`) + re-exports
358359
- `src/vector.rs`: `Vector<const D: usize>` (`[f64; D]`)
359360
- `src/matrix.rs`: `Matrix<const D: usize>` (`[[f64; D]; D]`) + helpers (`get`, `set`, `inf_norm`, `det`, `det_direct`)
360-
- `src/lu.rs`: `Lu<const D: usize>` factorization with partial pivoting (`solve_vec`, `det`)
361-
- `src/ldlt.rs`: `Ldlt<const D: usize>` factorization without pivoting for symmetric SPD/PSD matrices (`solve_vec`, `det`)
361+
- `src/lu.rs`: `Lu<const D: usize>` factorization with partial pivoting (`solve`, `det`)
362+
- `src/ldlt.rs`: `Ldlt<const D: usize>` factorization without pivoting for symmetric SPD/PSD matrices (`solve`, `det`)
362363
- `src/exact.rs`: exact arithmetic behind `features = ["exact"]`:
363364
- Determinants: `det_exact()`, `det_exact_f64()`, `det_sign_exact()` via integer-only
364365
Bareiss in `BigInt` (`bareiss_det_int`); `det_sign_exact()` adds a Shewchuk-style

CHANGELOG.md

Lines changed: 13 additions & 85 deletions
Large diffs are not rendered by default.

README.md

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -105,8 +105,8 @@ fn main() -> Result<(), LaError> {
105105

106106
let b = Vector::<5>::try_new([14.0, 13.0, 12.0, 11.0, 10.0])?;
107107

108-
let lu = a.lu(DEFAULT_PIVOT_TOL)?;
109-
let x = lu.solve_vec(b)?.into_array();
108+
let lu = a.lu(DEFAULT_SINGULAR_TOL)?;
109+
let x = lu.solve(b)?.into_array();
110110

111111
// Floating-point rounding is expected; compare with a tolerance.
112112
let expected = [1.0, 2.0, 3.0, 4.0, 5.0];
@@ -330,18 +330,18 @@ compose the same bound themselves.
330330

331331
| Type | Storage | Purpose | Key methods |
332332
|---|---|---|---|
333-
| `Vector<D>` | `[f64; D]` | Fixed-length vector for input and computation | `try_new`, `zero`, `dot`, `norm2_sq` |
334-
| `Matrix<D>` | `[[f64; D]; D]` | Square matrix for input and computation | See below |
335-
| `Lu<D>` | `Matrix<D>` + pivot array | Factorization for solves/det | `solve_vec`, `det` |
336-
| `Ldlt<D>` | `Matrix<D>` | Factorization for symmetric SPD/PSD solves/det | `solve_vec`, `det` |
333+
| `Vector<D>` | `[f64; D]` | Finite fixed-length vector for input and computation | `try_new`, `zero`, `dot`, `norm2_sq` |
334+
| `Matrix<D>` | `[[f64; D]; D]` | Finite square matrix for input and computation | See below |
335+
| `Lu<D>` | `Matrix<D>` + pivot array | Factorization for solves/det | `solve`, `det` |
336+
| `Ldlt<D>` | `Matrix<D>` | Factorization for symmetric SPD/PSD solves/det | `solve`, `det` |
337337

338338
Storage shown above reflects the intentional `f64` scalar model.
339339

340340
`Matrix<D>` key methods: `lu`, `ldlt`, `det`, `det_direct`, `det_errbound`,
341341
`det_exact`¹, `det_exact_f64`¹, `det_sign_exact`¹, `solve_exact`¹, `solve_exact_f64`¹.
342-
Matrix and vector methods validate non-finite inputs at public API boundaries and
343-
carry private proof-bearing types through computation so successful factors do
344-
not store NaN or infinity.
342+
Matrix and vector constructors validate non-finite inputs at public API
343+
boundaries. After construction, `Matrix<D>` and `Vector<D>` carry that
344+
finite-storage invariant directly, so kernels do not revalidate stored entries.
345345

346346
¹ Requires `features = ["exact"]`.
347347

benches/vs_linalg.rs

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ use faer::{Mat, Side};
1616
use nalgebra::{SMatrix, SVector};
1717
use pastey::paste;
1818

19-
use la_stack::{DEFAULT_PIVOT_TOL, DEFAULT_SINGULAR_TOL, Matrix, Vector};
19+
use la_stack::{DEFAULT_SINGULAR_TOL, Matrix, Vector};
2020

2121
mod common {
2222
pub mod vs_linalg;
@@ -63,7 +63,6 @@ macro_rules! define_vs_linalg_benches_for_dim {
6363
Vector::<$d>::try_new(make_vector_array::<$d>(1.0)),
6464
"la_stack vector construction",
6565
);
66-
6766
let na = SMatrix::<f64, $d, $d>::from_fn(|r, c| matrix_entry::<$d>(r, c));
6867
let nrhs = SVector::<f64, $d>::from_fn(|i, _| vector_entry(i, 0.0));
6968
let nv1 = SVector::<f64, $d>::from_fn(|i, _| vector_entry(i, 0.0));
@@ -75,7 +74,7 @@ macro_rules! define_vs_linalg_benches_for_dim {
7574
let fv2 = Mat::<f64>::from_fn($d, 1, |i, _| vector_entry(i, 1.0));
7675

7776
// Precompute LU once for solve-only / det-only benchmarks.
78-
let a_lu = require_ok(a.lu(DEFAULT_PIVOT_TOL), "precomputed la_stack LU");
77+
let a_lu = require_ok(a.lu(DEFAULT_SINGULAR_TOL), "precomputed la_stack LU");
7978
let a_ldlt = require_ok(a.ldlt(DEFAULT_SINGULAR_TOL), "precomputed la_stack LDLT");
8079
let na_lu = na.clone().lu();
8180
let na_cholesky = require_some(na.clone().cholesky(), "precomputed nalgebra Cholesky");
@@ -88,7 +87,7 @@ macro_rules! define_vs_linalg_benches_for_dim {
8887
[<group_d $d>].bench_function("la_stack_det_via_lu", |bencher| {
8988
bencher.iter(|| {
9089
let lu = require_ok(
91-
black_box(a).lu(DEFAULT_PIVOT_TOL),
90+
black_box(a).lu(DEFAULT_SINGULAR_TOL),
9291
"la_stack LU factorization",
9392
);
9493
let det = require_ok(lu.det(), "la_stack LU determinant");
@@ -124,7 +123,7 @@ macro_rules! define_vs_linalg_benches_for_dim {
124123
[<group_d $d>].bench_function("la_stack_lu", |bencher| {
125124
bencher.iter(|| {
126125
let lu = require_ok(
127-
black_box(a).lu(DEFAULT_PIVOT_TOL),
126+
black_box(a).lu(DEFAULT_SINGULAR_TOL),
128127
"la_stack LU factorization",
129128
);
130129
let _ = black_box(lu);
@@ -180,11 +179,11 @@ macro_rules! define_vs_linalg_benches_for_dim {
180179
[<group_d $d>].bench_function("la_stack_lu_solve", |bencher| {
181180
bencher.iter(|| {
182181
let lu = require_ok(
183-
black_box(a).lu(DEFAULT_PIVOT_TOL),
182+
black_box(a).lu(DEFAULT_SINGULAR_TOL),
184183
"la_stack LU factorization",
185184
);
186185
let x = require_ok(
187-
lu.solve_vec(black_box(rhs)),
186+
lu.solve(black_box(rhs)),
188187
"la_stack LU solve",
189188
);
190189
let _ = black_box(x);
@@ -215,7 +214,7 @@ macro_rules! define_vs_linalg_benches_for_dim {
215214
"la_stack LDLT factorization",
216215
);
217216
let x = require_ok(
218-
ldlt.solve_vec(black_box(rhs)),
217+
ldlt.solve(black_box(rhs)),
219218
"la_stack LDLT solve",
220219
);
221220
let _ = black_box(x);
@@ -248,7 +247,7 @@ macro_rules! define_vs_linalg_benches_for_dim {
248247
[<group_d $d>].bench_function("la_stack_solve_from_lu", |bencher| {
249248
bencher.iter(|| {
250249
let x = require_ok(
251-
a_lu.solve_vec(black_box(rhs)),
250+
a_lu.solve(black_box(rhs)),
252251
"precomputed la_stack LU solve",
253252
);
254253
let _ = black_box(x);
@@ -276,7 +275,7 @@ macro_rules! define_vs_linalg_benches_for_dim {
276275
[<group_d $d>].bench_function("la_stack_solve_from_ldlt", |bencher| {
277276
bencher.iter(|| {
278277
let x = require_ok(
279-
a_ldlt.solve_vec(black_box(rhs)),
278+
a_ldlt.solve(black_box(rhs)),
280279
"precomputed la_stack LDLT solve",
281280
);
282281
let _ = black_box(x);

docs/roadmap.md

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -58,12 +58,9 @@ API-invariant cleanup:
5858

5959
- [#126](https://github.com/acgetchell/la-stack/issues/126) is resolved as an
6060
internal parse-don't-validate design rather than a public proof-bearing API.
61-
`Matrix<D>` and `Vector<D>` remain the raw boundary types so callers can pass
62-
deserialized, fixture, or otherwise unchecked `f64` storage and receive
63-
structured diagnostics. Crate-private `FiniteMatrix<D>` and
64-
`FiniteVector<D>` are validated domain types that carry the finite-entry proof
65-
behind the scenes for LU, LDLT, determinant, error-bound, and exact-arithmetic
66-
paths.
61+
`Matrix<D>` and `Vector<D>` parse raw `f64` storage at construction and then
62+
carry the finite-entry proof directly. Crate-private finite wrappers remain
63+
only as implementation helpers at algorithm boundaries.
6764
- The public prelude stays focused on downstream composition: raw boundary
6865
types, factorization handles, tolerances, crate errors, dispatch helpers, and
6966
documented constants. Proof-bearing wrappers remain crate-private, and

examples/det_5x5.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ fn main() -> Result<(), LaError> {
1414
])?;
1515

1616
// Compute via explicit LU factorization.
17-
let lu = a.lu(DEFAULT_PIVOT_TOL)?;
17+
let lu = a.lu(DEFAULT_SINGULAR_TOL)?;
1818
let det = lu.det()?;
1919

2020
println!("det = {det}");

examples/exact_solve_3x3.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@ fn main() -> Result<(), LaError> {
2525
let b = Vector::<3>::try_new([1.0, 2.0, 3.0])?;
2626

2727
// f64 LU solve (using zero pivot tolerance since the matrix is nearly singular
28-
// and would be rejected by DEFAULT_PIVOT_TOL).
29-
let lu_x = a.lu(Tolerance::new(0.0)?)?.solve_vec(b)?.into_array();
28+
// and would be rejected by DEFAULT_SINGULAR_TOL).
29+
let lu_x = a.lu(Tolerance::new(0.0)?)?.solve(b)?.into_array();
3030

3131
// Exact solve.
3232
let exact_x = a.solve_exact(b)?;

examples/ldlt_solve_3x3.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ fn main() -> Result<(), LaError> {
1616
let b = Vector::<3>::try_new([2.0, 4.0, 10.0])?;
1717

1818
let ldlt = a.ldlt(DEFAULT_SINGULAR_TOL)?;
19-
let x = ldlt.solve_vec(b)?.into_array();
19+
let x = ldlt.solve(b)?.into_array();
2020
let det = ldlt.det()?;
2121

2222
println!("A (3×3 SPD tridiagonal):");

examples/solve_5x5.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ fn main() -> Result<(), LaError> {
1616
// Choose x = [1, 2, 3, 4, 5]. Then b = A x = [14, 13, 12, 11, 10].
1717
let b = Vector::<5>::try_new([14.0, 13.0, 12.0, 11.0, 10.0])?;
1818

19-
let lu = a.lu(DEFAULT_PIVOT_TOL)?;
20-
let x = lu.solve_vec(b)?.into_array();
19+
let lu = a.lu(DEFAULT_SINGULAR_TOL)?;
20+
let x = lu.solve(b)?.into_array();
2121

2222
println!("x = {x:?}");
2323
Ok(())

0 commit comments

Comments
 (0)