diff --git a/CHANGELOG.md b/CHANGELOG.md index 798be0d..55b330b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Feat!(matrix): enforce fallible matrix invariants [`e26c283`](https://github.com/acgetchell/la-stack/commit/e26c28358b2358100353b2895441b68892e92cd7) - Feat!(api): enforce fallible numeric invariants [`adfc33b`](https://github.com/acgetchell/la-stack/commit/adfc33b945b259721bd1067e797ed2e7d4ec0e6e) +- Feat!(matrix): make determinant API tolerance-free [`11a355c`](https://github.com/acgetchell/la-stack/commit/11a355c099eaf366daec8c95af61b6934f914960) +- Feat!(api): hide finite and symmetry proofs behind matrix APIs + [`7219336`](https://github.com/acgetchell/la-stack/commit/721933671c28eb71953f1386a201622d6171caf7) +- Guard public Rust examples against unwrap [`df1130a`](https://github.com/acgetchell/la-stack/commit/df1130a7ad0ba69a1072ef231e14f3efb7e4b8de) + + - Add repository-owned Semgrep rules for unwrap and expect usage in public doctests, examples, and benchmarks. + - Add fixture-based Semgrep rule tests and include them in the lint workflow. + - Update examples and benchmarks to model typed fallible flow or operation-labeled benchmark failures. +- Feat!(api): enforce finite Matrix and Vector construction [`92ba403`](https://github.com/acgetchell/la-stack/commit/92ba4034b194875c62a27f24dfbf6d43f380f54e) ### Changed @@ -26,6 +35,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Reapply "ci: modernize tooling checks and example execution" [`758321a`](https://github.com/acgetchell/la-stack/commit/758321acf872b1f17286ff3bb7bee6a807e4b440) +- Encode nonzero mantissas in exact decomposition [`7a664ed`](https://github.com/acgetchell/la-stack/commit/7a664ede2f4add168c5813f8d24e16732fa03b30) + + - Replace the exact-arithmetic zero mantissa sentinel with `Option<NonZeroU64>`. + - Carry nonzero mantissa proof through matrix/vector decomposition and BigInt scaling. + - Clarify determinant documentation around uncertified `det()` bounds. + - Keep SPD determinant proptests on the tolerance-aware LU path. + ### Dependencies - 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 - Clarify that la-stack intentionally supports f64 floating-point APIs plus optional exact rationals, not alternate scalar families. - Add a roadmap covering the v0.4.x stable-Rust issue sequence and the v0.5.0 generic_const_exprs anchor. - Refresh generated changelog entries and archived changelog grouping. +- Document finite RHS solve validation [`075aed7`](https://github.com/acgetchell/la-stack/commit/075aed78cf8264fc920258f1f1d977ddd589ffd7) + + - Document that LU and LDLT solve_vec reject non-finite RHS entries with LaError::NonFinite metadata. + - Cite the Bareiss reference in the exact solve helper docs and describe exact-arithmetic growth and complexity. + - Cover finite proof defaults and non-finite RHS solve boundaries in unit tests. + +### Fixed + +- Reject overflowed symmetry tolerance scaling [`a7b052a`](https://github.com/acgetchell/la-stack/commit/a7b052af5dc6361198bbfe1e17d6b1f0ba225ed7) + + - Enforce the tolerance contract around symmetry checks by surfacing scaled + tolerance overflow as a typed non-finite intermediate error. + + - Document finite, non-negative tolerance requirements across tolerance-taking + matrix APIs. + + - Add regression coverage for invalid tolerance construction and symmetry + tolerance overflow. + + - Update exact examples to propagate typed crate errors instead of unwrapping. +- Harden Semgrep fixture parsing [`ac44c07`](https://github.com/acgetchell/la-stack/commit/ac44c078cc4435d5beca27f1890fbb4046cf5952) + + - Ignore non-canonical todoruleid annotations when counting expected rule hits. + - Reject malformed Semgrep JSON results with clear stderr diagnostics instead of propagating KeyError. +- Revalidate finite proof conversions [`419a90f`](https://github.com/acgetchell/la-stack/commit/419a90f7267608051736498154ac5e6faf0909c5) + + Ensure internal finite proof conversions cannot accept raw Matrix or Vector storage without checking the invariant. + + - Revalidate TryFrom<Matrix<D>> and TryFrom<Vector<D>> before constructing finite wrappers. + - Measure exact random percentile benchmarks over repeated corpus timings and cumulative input sets. + - Tighten Codecov status thresholds and extend benchmark workflow timeout. + - Keep Semgrep constructor fixtures aligned with public API guardrails. ### Maintenance diff --git a/src/exact.rs b/src/exact.rs index a1f9d87..d7e98b1 100644 --- a/src/exact.rs +++ b/src/exact.rs @@ -536,7 +536,7 @@ impl FiniteMatrix { } result[i] = f; } - Ok(FiniteVector::new_unchecked(Vector::new_unchecked(result))) + Ok(FiniteVector::new(Vector::new_unchecked(result))) } /// Exact determinant sign for an already finite matrix. @@ -870,18 +870,6 @@ mod tests { let det = Matrix::<$d>::identity().det_exact().unwrap(); assert_eq!(det, BigRational::from_integer(BigInt::from(1))); } - - #[test] - fn []() { - let mut m = Matrix::<$d>::identity(); - assert_eq!(m.set(0, 0, f64::NAN), Err(LaError::NonFinite { row: Some(0), col: 0 })); - } - - #[test] - fn []() { - let mut m = Matrix::<$d>::identity(); - assert_eq!(m.set(0, 0, f64::INFINITY), Err(LaError::NonFinite { row: Some(0), col: 0 })); - } } }; } @@ -899,12 +887,6 @@ mod tests { let det = Matrix::<$d>::identity().det_exact_f64().unwrap(); assert!((det - 1.0).abs() <= f64::EPSILON); } - - #[test] - fn []() { - let mut m = Matrix::<$d>::identity(); - assert_eq!(m.set(0, 0, f64::NAN), Err(LaError::NonFinite { row: Some(0), col: 0 })); - } } }; } @@ -1391,32 +1373,6 @@ mod tests { assert_eq!(det, BigRational::from_integer(BigInt::from(-1))); } - /// Non-finite matrix entries surface as `LaError::NonFinite` with the - /// row/col of the first offending entry. - #[test] - fn bareiss_det_int_errs_on_nan() { - let mut m = Matrix::<3>::identity(); - assert_eq!( - m.set(1, 2, f64::NAN), - Err(LaError::NonFinite { - row: Some(1), - col: 2 - }) - ); - } - - #[test] - fn bareiss_det_int_errs_on_inf() { - let mut m = Matrix::<2>::identity(); - assert_eq!( - m.set(0, 0, f64::INFINITY), - Err(LaError::NonFinite { - row: Some(0), - col: 0 - }) - ); - } - /// Per AGENTS.md: dimension-generic tests must cover D=2–5. macro_rules! gen_bareiss_det_int_identity_tests { ($d:literal) => { @@ -1639,32 +1595,6 @@ mod tests { } } - #[test] - fn []() { - let mut a = Matrix::<$d>::identity(); - assert_eq!(a.set(0, 0, f64::NAN), Err(LaError::NonFinite { row: Some(0), col: 0 })); - } - - #[test] - fn []() { - let mut a = Matrix::<$d>::identity(); - assert_eq!(a.set(0, 0, f64::INFINITY), Err(LaError::NonFinite { row: Some(0), col: 0 })); - } - - #[test] - fn []() { - let mut b_arr = [1.0f64; $d]; - b_arr[0] = f64::NAN; - assert_eq!(Vector::<$d>::try_new(b_arr), Err(LaError::NonFinite { row: None, col: 0 })); - } - - #[test] - fn []() { - let mut b_arr = [1.0f64; $d]; - b_arr[$d - 1] = f64::INFINITY; - assert_eq!(Vector::<$d>::try_new(b_arr), Err(LaError::NonFinite { row: None, col: $d - 1 })); - } - #[test] fn []() { // Zero matrix is singular. @@ -1693,12 +1623,6 @@ mod tests { assert!((x[i] - b.data[i]).abs() <= f64::EPSILON); } } - - #[test] - fn []() { - let mut a = Matrix::<$d>::identity(); - assert_eq!(a.set(0, 0, f64::NAN), Err(LaError::NonFinite { row: Some(0), col: 0 })); - } } }; } diff --git a/src/ldlt.rs b/src/ldlt.rs index b815591..08e0089 100644 --- a/src/ldlt.rs +++ b/src/ldlt.rs @@ -264,7 +264,7 @@ impl Ldlt { ii += 1; } - Ok(FiniteVector::new_unchecked(Vector::new_unchecked(x))) + Ok(FiniteVector::new(Vector::new_unchecked(x))) } } diff --git a/src/lu.rs b/src/lu.rs index 86fa617..64257a9 100644 --- a/src/lu.rs +++ b/src/lu.rs @@ -217,7 +217,7 @@ impl Lu { ii += 1; } - Ok(FiniteVector::new_unchecked(Vector::new_unchecked(x))) + Ok(FiniteVector::new(Vector::new_unchecked(x))) } /// Determinant of the original matrix. diff --git a/src/matrix.rs b/src/matrix.rs index bb2bd22..c7e254b 100644 --- a/src/matrix.rs +++ b/src/matrix.rs @@ -26,21 +26,11 @@ pub(crate) struct FiniteMatrix { } impl FiniteMatrix { - /// Construct a finite matrix without checking the invariant. - /// - /// This is crate-internal so raw storage still goes through - /// [`Matrix::try_from_rows`], which preserves diagnostics for rejected - /// entries. - #[inline] - pub(crate) const fn new_unchecked(matrix: Matrix) -> Self { - Self { matrix } - } - /// Wrap an already-finite matrix for algorithms that carry the invariant /// explicitly. #[inline] pub const fn new(matrix: Matrix) -> Self { - Self::new_unchecked(matrix) + Self { matrix } } /// Validate raw row-major storage and construct a finite matrix. @@ -50,7 +40,7 @@ impl FiniteMatrix { #[inline] pub const fn from_rows(rows: [[f64; D]; D]) -> Result { match Matrix::try_from_rows(rows) { - Ok(matrix) => Ok(Self::new_unchecked(matrix)), + Ok(matrix) => Ok(Self::new(matrix)), Err(err) => Err(err), } } @@ -58,7 +48,7 @@ impl FiniteMatrix { /// All-zeros finite matrix. #[inline] pub const fn zero() -> Self { - Self::new_unchecked(Matrix::zero()) + Self::new(Matrix::zero()) } /// Consume the wrapper and return the underlying raw matrix. diff --git a/src/vector.rs b/src/vector.rs index 13234eb..6bd71d9 100644 --- a/src/vector.rs +++ b/src/vector.rs @@ -22,20 +22,11 @@ pub(crate) struct FiniteVector { } impl FiniteVector { - /// Construct a finite vector without checking the invariant. - /// - /// This is crate-internal so raw storage still goes through - /// [`Vector::try_new`], which preserves diagnostics for rejected entries. - #[inline] - pub(crate) const fn new_unchecked(vector: Vector) -> Self { - Self { vector } - } - /// Wrap an already-finite vector for algorithms that carry the invariant /// explicitly. #[inline] pub const fn new(vector: Vector) -> Self { - Self::new_unchecked(vector) + Self { vector } } /// Validate raw vector storage and construct a finite vector. @@ -45,7 +36,7 @@ impl FiniteVector { #[inline] pub const fn from_array(data: [f64; D]) -> Result { match Vector::try_new(data) { - Ok(vector) => Ok(Self::new_unchecked(vector)), + Ok(vector) => Ok(Self::new(vector)), Err(err) => Err(err), } } @@ -53,7 +44,7 @@ impl FiniteVector { /// All-zeros finite vector. #[inline] pub const fn zero() -> Self { - Self::new_unchecked(Vector::zero()) + Self::new(Vector::zero()) } /// Consume the wrapper and return the underlying raw vector.