Skip to content

Commit 54b603c

Browse files
committed
refactor(invariants): simplify finite proof wrappers
- Use the checked proof-wrapper constructors as the single internal path for finite matrices and vectors. - Remove exact-arithmetic tests that duplicated the matrix and vector non-finite boundary checks.
1 parent ed7dc46 commit 54b603c

6 files changed

Lines changed: 57 additions & 104 deletions

File tree

CHANGELOG.md

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1111

1212
- Feat!(matrix): enforce fallible matrix invariants [`e26c283`](https://github.com/acgetchell/la-stack/commit/e26c28358b2358100353b2895441b68892e92cd7)
1313
- Feat!(api): enforce fallible numeric invariants [`adfc33b`](https://github.com/acgetchell/la-stack/commit/adfc33b945b259721bd1067e797ed2e7d4ec0e6e)
14+
- Feat!(matrix): make determinant API tolerance-free [`11a355c`](https://github.com/acgetchell/la-stack/commit/11a355c099eaf366daec8c95af61b6934f914960)
15+
- Feat!(api): hide finite and symmetry proofs behind matrix APIs
16+
[`7219336`](https://github.com/acgetchell/la-stack/commit/721933671c28eb71953f1386a201622d6171caf7)
17+
- Guard public Rust examples against unwrap [`df1130a`](https://github.com/acgetchell/la-stack/commit/df1130a7ad0ba69a1072ef231e14f3efb7e4b8de)
18+
19+
- Add repository-owned Semgrep rules for unwrap and expect usage in public doctests, examples, and benchmarks.
20+
- Add fixture-based Semgrep rule tests and include them in the lint workflow.
21+
- Update examples and benchmarks to model typed fallible flow or operation-labeled benchmark failures.
22+
- Feat!(api): enforce finite Matrix and Vector construction [`92ba403`](https://github.com/acgetchell/la-stack/commit/92ba4034b194875c62a27f24dfbf6d43f380f54e)
1423

1524
### Changed
1625

@@ -26,6 +35,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2635
- Reapply "ci: modernize tooling checks and example execution"
2736
[`758321a`](https://github.com/acgetchell/la-stack/commit/758321acf872b1f17286ff3bb7bee6a807e4b440)
2837

38+
- Encode nonzero mantissas in exact decomposition [`7a664ed`](https://github.com/acgetchell/la-stack/commit/7a664ede2f4add168c5813f8d24e16732fa03b30)
39+
40+
- Replace the exact-arithmetic zero mantissa sentinel with `Option<NonZeroU64>`.
41+
- Carry nonzero mantissa proof through matrix/vector decomposition and BigInt scaling.
42+
- Clarify determinant documentation around uncertified `det()` bounds.
43+
- Keep SPD determinant proptests on the tolerance-aware LU path.
44+
2945
### Dependencies
3046

3147
- Bump taiki-e/install-action from 2.75.18 to 2.75.22 [`d6c944b`](https://github.com/acgetchell/la-stack/commit/d6c944bb7dd30bb00dfe820bc355c4351cb1f242)
@@ -47,6 +63,38 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
4763
- Clarify that la-stack intentionally supports f64 floating-point APIs plus optional exact rationals, not alternate scalar families.
4864
- Add a roadmap covering the v0.4.x stable-Rust issue sequence and the v0.5.0 generic_const_exprs anchor.
4965
- Refresh generated changelog entries and archived changelog grouping.
66+
- Document finite RHS solve validation [`075aed7`](https://github.com/acgetchell/la-stack/commit/075aed78cf8264fc920258f1f1d977ddd589ffd7)
67+
68+
- Document that LU and LDLT solve_vec reject non-finite RHS entries with LaError::NonFinite metadata.
69+
- Cite the Bareiss reference in the exact solve helper docs and describe exact-arithmetic growth and complexity.
70+
- Cover finite proof defaults and non-finite RHS solve boundaries in unit tests.
71+
72+
### Fixed
73+
74+
- Reject overflowed symmetry tolerance scaling [`a7b052a`](https://github.com/acgetchell/la-stack/commit/a7b052af5dc6361198bbfe1e17d6b1f0ba225ed7)
75+
76+
- Enforce the tolerance contract around symmetry checks by surfacing scaled
77+
tolerance overflow as a typed non-finite intermediate error.
78+
79+
- Document finite, non-negative tolerance requirements across tolerance-taking
80+
matrix APIs.
81+
82+
- Add regression coverage for invalid tolerance construction and symmetry
83+
tolerance overflow.
84+
85+
- Update exact examples to propagate typed crate errors instead of unwrapping.
86+
- Harden Semgrep fixture parsing [`ac44c07`](https://github.com/acgetchell/la-stack/commit/ac44c078cc4435d5beca27f1890fbb4046cf5952)
87+
88+
- Ignore non-canonical todoruleid annotations when counting expected rule hits.
89+
- Reject malformed Semgrep JSON results with clear stderr diagnostics instead of propagating KeyError.
90+
- Revalidate finite proof conversions [`419a90f`](https://github.com/acgetchell/la-stack/commit/419a90f7267608051736498154ac5e6faf0909c5)
91+
92+
Ensure internal finite proof conversions cannot accept raw Matrix or Vector storage without checking the invariant.
93+
94+
- Revalidate TryFrom<Matrix<D>> and TryFrom<Vector<D>> before constructing finite wrappers.
95+
- Measure exact random percentile benchmarks over repeated corpus timings and cumulative input sets.
96+
- Tighten Codecov status thresholds and extend benchmark workflow timeout.
97+
- Keep Semgrep constructor fixtures aligned with public API guardrails.
5098

5199
### Maintenance
52100

src/exact.rs

Lines changed: 1 addition & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -536,7 +536,7 @@ impl<const D: usize> FiniteMatrix<D> {
536536
}
537537
result[i] = f;
538538
}
539-
Ok(FiniteVector::new_unchecked(Vector::new_unchecked(result)))
539+
Ok(FiniteVector::new(Vector::new_unchecked(result)))
540540
}
541541

542542
/// Exact determinant sign for an already finite matrix.
@@ -870,18 +870,6 @@ mod tests {
870870
let det = Matrix::<$d>::identity().det_exact().unwrap();
871871
assert_eq!(det, BigRational::from_integer(BigInt::from(1)));
872872
}
873-
874-
#[test]
875-
fn [<det_exact_err_on_nan_ $d d>]() {
876-
let mut m = Matrix::<$d>::identity();
877-
assert_eq!(m.set(0, 0, f64::NAN), Err(LaError::NonFinite { row: Some(0), col: 0 }));
878-
}
879-
880-
#[test]
881-
fn [<det_exact_err_on_inf_ $d d>]() {
882-
let mut m = Matrix::<$d>::identity();
883-
assert_eq!(m.set(0, 0, f64::INFINITY), Err(LaError::NonFinite { row: Some(0), col: 0 }));
884-
}
885873
}
886874
};
887875
}
@@ -899,12 +887,6 @@ mod tests {
899887
let det = Matrix::<$d>::identity().det_exact_f64().unwrap();
900888
assert!((det - 1.0).abs() <= f64::EPSILON);
901889
}
902-
903-
#[test]
904-
fn [<det_exact_f64_err_on_nan_ $d d>]() {
905-
let mut m = Matrix::<$d>::identity();
906-
assert_eq!(m.set(0, 0, f64::NAN), Err(LaError::NonFinite { row: Some(0), col: 0 }));
907-
}
908890
}
909891
};
910892
}
@@ -1391,32 +1373,6 @@ mod tests {
13911373
assert_eq!(det, BigRational::from_integer(BigInt::from(-1)));
13921374
}
13931375

1394-
/// Non-finite matrix entries surface as `LaError::NonFinite` with the
1395-
/// row/col of the first offending entry.
1396-
#[test]
1397-
fn bareiss_det_int_errs_on_nan() {
1398-
let mut m = Matrix::<3>::identity();
1399-
assert_eq!(
1400-
m.set(1, 2, f64::NAN),
1401-
Err(LaError::NonFinite {
1402-
row: Some(1),
1403-
col: 2
1404-
})
1405-
);
1406-
}
1407-
1408-
#[test]
1409-
fn bareiss_det_int_errs_on_inf() {
1410-
let mut m = Matrix::<2>::identity();
1411-
assert_eq!(
1412-
m.set(0, 0, f64::INFINITY),
1413-
Err(LaError::NonFinite {
1414-
row: Some(0),
1415-
col: 0
1416-
})
1417-
);
1418-
}
1419-
14201376
/// Per AGENTS.md: dimension-generic tests must cover D=2–5.
14211377
macro_rules! gen_bareiss_det_int_identity_tests {
14221378
($d:literal) => {
@@ -1639,32 +1595,6 @@ mod tests {
16391595
}
16401596
}
16411597

1642-
#[test]
1643-
fn [<solve_exact_err_on_nan_matrix_ $d d>]() {
1644-
let mut a = Matrix::<$d>::identity();
1645-
assert_eq!(a.set(0, 0, f64::NAN), Err(LaError::NonFinite { row: Some(0), col: 0 }));
1646-
}
1647-
1648-
#[test]
1649-
fn [<solve_exact_err_on_inf_matrix_ $d d>]() {
1650-
let mut a = Matrix::<$d>::identity();
1651-
assert_eq!(a.set(0, 0, f64::INFINITY), Err(LaError::NonFinite { row: Some(0), col: 0 }));
1652-
}
1653-
1654-
#[test]
1655-
fn [<solve_exact_err_on_nan_vector_ $d d>]() {
1656-
let mut b_arr = [1.0f64; $d];
1657-
b_arr[0] = f64::NAN;
1658-
assert_eq!(Vector::<$d>::try_new(b_arr), Err(LaError::NonFinite { row: None, col: 0 }));
1659-
}
1660-
1661-
#[test]
1662-
fn [<solve_exact_err_on_inf_vector_ $d d>]() {
1663-
let mut b_arr = [1.0f64; $d];
1664-
b_arr[$d - 1] = f64::INFINITY;
1665-
assert_eq!(Vector::<$d>::try_new(b_arr), Err(LaError::NonFinite { row: None, col: $d - 1 }));
1666-
}
1667-
16681598
#[test]
16691599
fn [<solve_exact_singular_ $d d>]() {
16701600
// Zero matrix is singular.
@@ -1693,12 +1623,6 @@ mod tests {
16931623
assert!((x[i] - b.data[i]).abs() <= f64::EPSILON);
16941624
}
16951625
}
1696-
1697-
#[test]
1698-
fn [<solve_exact_f64_err_on_nan_ $d d>]() {
1699-
let mut a = Matrix::<$d>::identity();
1700-
assert_eq!(a.set(0, 0, f64::NAN), Err(LaError::NonFinite { row: Some(0), col: 0 }));
1701-
}
17021626
}
17031627
};
17041628
}

src/ldlt.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -264,7 +264,7 @@ impl<const D: usize> Ldlt<D> {
264264
ii += 1;
265265
}
266266

267-
Ok(FiniteVector::new_unchecked(Vector::new_unchecked(x)))
267+
Ok(FiniteVector::new(Vector::new_unchecked(x)))
268268
}
269269
}
270270

src/lu.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,7 @@ impl<const D: usize> Lu<D> {
217217
ii += 1;
218218
}
219219

220-
Ok(FiniteVector::new_unchecked(Vector::new_unchecked(x)))
220+
Ok(FiniteVector::new(Vector::new_unchecked(x)))
221221
}
222222

223223
/// Determinant of the original matrix.

src/matrix.rs

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -26,21 +26,11 @@ pub(crate) struct FiniteMatrix<const D: usize> {
2626
}
2727

2828
impl<const D: usize> FiniteMatrix<D> {
29-
/// Construct a finite matrix without checking the invariant.
30-
///
31-
/// This is crate-internal so raw storage still goes through
32-
/// [`Matrix::try_from_rows`], which preserves diagnostics for rejected
33-
/// entries.
34-
#[inline]
35-
pub(crate) const fn new_unchecked(matrix: Matrix<D>) -> Self {
36-
Self { matrix }
37-
}
38-
3929
/// Wrap an already-finite matrix for algorithms that carry the invariant
4030
/// explicitly.
4131
#[inline]
4232
pub const fn new(matrix: Matrix<D>) -> Self {
43-
Self::new_unchecked(matrix)
33+
Self { matrix }
4434
}
4535

4636
/// Validate raw row-major storage and construct a finite matrix.
@@ -50,15 +40,15 @@ impl<const D: usize> FiniteMatrix<D> {
5040
#[inline]
5141
pub const fn from_rows(rows: [[f64; D]; D]) -> Result<Self, LaError> {
5242
match Matrix::try_from_rows(rows) {
53-
Ok(matrix) => Ok(Self::new_unchecked(matrix)),
43+
Ok(matrix) => Ok(Self::new(matrix)),
5444
Err(err) => Err(err),
5545
}
5646
}
5747

5848
/// All-zeros finite matrix.
5949
#[inline]
6050
pub const fn zero() -> Self {
61-
Self::new_unchecked(Matrix::zero())
51+
Self::new(Matrix::zero())
6252
}
6353

6454
/// Consume the wrapper and return the underlying raw matrix.

src/vector.rs

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -22,20 +22,11 @@ pub(crate) struct FiniteVector<const D: usize> {
2222
}
2323

2424
impl<const D: usize> FiniteVector<D> {
25-
/// Construct a finite vector without checking the invariant.
26-
///
27-
/// This is crate-internal so raw storage still goes through
28-
/// [`Vector::try_new`], which preserves diagnostics for rejected entries.
29-
#[inline]
30-
pub(crate) const fn new_unchecked(vector: Vector<D>) -> Self {
31-
Self { vector }
32-
}
33-
3425
/// Wrap an already-finite vector for algorithms that carry the invariant
3526
/// explicitly.
3627
#[inline]
3728
pub const fn new(vector: Vector<D>) -> Self {
38-
Self::new_unchecked(vector)
29+
Self { vector }
3930
}
4031

4132
/// Validate raw vector storage and construct a finite vector.
@@ -45,15 +36,15 @@ impl<const D: usize> FiniteVector<D> {
4536
#[inline]
4637
pub const fn from_array(data: [f64; D]) -> Result<Self, LaError> {
4738
match Vector::try_new(data) {
48-
Ok(vector) => Ok(Self::new_unchecked(vector)),
39+
Ok(vector) => Ok(Self::new(vector)),
4940
Err(err) => Err(err),
5041
}
5142
}
5243

5344
/// All-zeros finite vector.
5445
#[inline]
5546
pub const fn zero() -> Self {
56-
Self::new_unchecked(Vector::zero())
47+
Self::new(Vector::zero())
5748
}
5849

5950
/// Consume the wrapper and return the underlying raw vector.

0 commit comments

Comments
 (0)