Skip to content

feat!(api): enforce finite Matrix and Vector construction#133

Merged
acgetchell merged 2 commits into
mainfrom
feat/98-finite-api-invariants
Jun 3, 2026
Merged

feat!(api): enforce finite Matrix and Vector construction#133
acgetchell merged 2 commits into
mainfrom
feat/98-finite-api-invariants

Conversation

@acgetchell
Copy link
Copy Markdown
Owner

@acgetchell acgetchell commented Jun 3, 2026

Make finite storage a public API invariant instead of a validation step repeated by every operation.

  • Replace raw Matrix::from_rows and Vector::new usage with fallible try_from_rows and try_new constructors.
  • Change Matrix::set to return Result<(), LaError> and reject non-finite mutations before changing storage.
  • Carry finite Matrix/Vector and LU/LDLT factor invariants through internal proof types so operations can skip redundant validation and panic-only defensive branches.
  • Add Semgrep guardrails for public panic paths and raw infallible f64 constructors.
  • Add fixed-seed percentile exact-arithmetic benchmarks for p50/p95/p99 inputs.

BREAKING CHANGE: Matrix::from_rows and Vector::new are no longer public constructors; use Matrix::try_from_rows and Vector::try_new. Matrix::set now returns Result<(), LaError> instead of Option<()>.

Closes #98

Summary by CodeRabbit

  • New Features

    • Added random-percentile exact-arithmetic benchmarks with fixed-seed diagonally-dominant corpora and p50/p95/p99 selection.
  • Documentation

    • Updated examples and doctests to use fallible constructors and propagate errors.
    • Added design guidance requiring public APIs to surface failures via Result/Option rather than panics.
  • Refactor

    • Public constructors and validation boundaries made fallible; internal algorithm entry points assume validated inputs.
    • Matrix element setter now returns Result for error reporting.

Make finite storage a public API invariant instead of a validation step repeated by every operation.

- Replace raw Matrix::from_rows and Vector::new usage with fallible try_from_rows and try_new constructors.
- Change Matrix::set to return Result<(), LaError> and reject non-finite mutations before changing storage.
- Carry finite Matrix/Vector and LU/LDLT factor invariants through internal proof types so operations can skip redundant validation and panic-only defensive branches.
- Add Semgrep guardrails for public panic paths and raw infallible f64 constructors.
- Add fixed-seed percentile exact-arithmetic benchmarks for p50/p95/p99 inputs.

BREAKING CHANGE: Matrix::from_rows and Vector::new are no longer public constructors; use Matrix::try_from_rows and Vector::try_new. Matrix::set now returns Result<(), LaError> instead of Option<()>.

Closes #98
@acgetchell acgetchell self-assigned this Jun 3, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jun 3, 2026

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 0977574f-828b-4b25-964c-7ce4fb6f1bdb

📥 Commits

Reviewing files that changed from the base of the PR and between 92ba403 and 419a90f.

📒 Files selected for processing (7)
  • .codecov.yml
  • .github/workflows/benchmarks.yml
  • benches/exact.rs
  • src/exact.rs
  • src/matrix.rs
  • src/vector.rs
  • tests/semgrep/src/project_rules/raw_f64_constructors.rs
🚧 Files skipped from review as they are similar to previous changes (4)
  • tests/semgrep/src/project_rules/raw_f64_constructors.rs
  • benches/exact.rs
  • src/exact.rs
  • src/matrix.rs

📝 Walkthrough

Walkthrough

Moves finiteness validation to fallible Matrix/Vector constructors, refactors exact/LU/LDLT internals to assume finite proofs, adds deterministic random-percentile exact benchmarks, introduces Semgrep guardrails, and updates examples, scripts, and tests to the new contracts.

Changes

Finite-Entry Validation Boundary and Exact Arithmetic Refactor

Layer / File(s) Summary
Public constructor API overhaul
src/matrix.rs, src/vector.rs
Matrix::from_rows and Vector::new become test-only; try_from_rows and try_new are public fallible boundary constructors that validate finiteness. Matrix::set changes from Option<()> to Result<(), LaError> with non-finite rejection.
Factor storage invariant wrappers
src/lu.rs, src/ldlt.rs
LuFactors and LdltFactors internal wrappers guarantee stored U/D diagonal entries are finite. Lu and Ldlt replace raw matrix + tolerance fields with wrapped storage and accessor methods.
Algorithm integration with finite invariants
src/lu.rs, src/ldlt.rs, src/exact.rs, src/matrix.rs
lu(), ldlt(), and exact methods now assume finite inputs via FiniteMatrix::new (unchecked) instead of try_new (checked). Factor accessors (diag, row, entry) replace direct indexing. Defensive validation of corrupt stored factors is removed.
Exact arithmetic pipeline refactor
src/exact.rs
f64_decompose becomes fallible: const Result<Option<...>, LaError>, rejecting NaN/∞. Component enum strengthens to `Zero
Random-percentile benchmark implementation
benches/exact.rs
Fixed-seed, diagonally-dominant random matrix corpus generation (50 per dimension), deterministic SplitMix64 RNG, pre-timing logic to select p50/p95/p99 inputs, and Criterion wiring for exact_random_percentile_d{2..5}. Macro gen_random_percentile_benches_for_dim! registers all four exact operations per dimension.
Benchmark and script updates
benches/exact.rs, benches/vs_linalg.rs, scripts/bench_compare.py, scripts/tests/test_bench_compare.py
Matrix/vector construction in benchmarks switched from infallible to fallible constructors wrapped with require_ok. scripts/bench_compare.py adds _RANDOM_PERCENTILE_BENCHES list and _group_heading formatter for random-percentile groups. Test expectations updated to validate new group structure and headings.
Code-quality enforcement via Semgrep rules
semgrep.yaml, tests/semgrep/src/project_rules/*
Two new rules: la-stack.rust.no-public-infallible-raw-f64-constructors flags public infallible raw f64 constructors; la-stack.rust.no-public-api-panic_paths flags public functions containing panic/assert/unwrap/expect. Paired fixtures exercise violations and allowed cases.
Documentation and examples
AGENTS.md, README.md, docs/BENCHMARKING.md, examples/*, src/lib.rs
Examples and doctests updated to use try_from_rows/try_new with ?; const-evaluable example uses a match to panic on invalid entries. AGENTS.md adds design principle on public API infallibility; BENCHMARKING.md documents the random-percentile suite.
Tests and property test updates
tests/*, src/*
Unit and proptest suites updated to use fallible constructors (try_from_rows, try_new) and to expect LaError-typed rejections at construction/set sites. Several defensive-path tests removed and replaced with boundary tests aligned to the new validation model.
CI workflow tweak
.github/workflows/benchmarks.yml
Benchmark CI bench job timeout increased from 15 to 30 minutes.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related issues

Possibly related PRs

Suggested labels

api, breaking change, rust, testing, performance

Poem

🐰 I nibble at proofs, neat and small,

Where matrices no longer fall,
Results return with manners fine,
No panics in the public line.
Percentiles dance in seeded light.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main breaking API change: making finite storage a public invariant for Matrix and Vector by enforcing fallible constructors.
Linked Issues check ✅ Passed The PR implements all coding objectives from issue #98: adds random-input percentile benchmarks with seeded RNG, reports p50/p95/p99 timings, uses diagonally-dominant matrices, and updates benchmarking tooling.
Out of Scope Changes check ✅ Passed All changes align with the PR objectives: finite API invariants (constructors, set method), internal proof types, Semgrep guardrails, and random percentile benchmarks. No unrelated changes detected.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/98-finite-api-invariants

Comment @coderabbitai help to get the list of available commands and usage tips.

@codecov
Copy link
Copy Markdown

codecov Bot commented Jun 3, 2026

Codecov Report

❌ Patch coverage is 96.96970% with 9 lines in your changes missing coverage. Please review.
✅ Project coverage is 99.31%. Comparing base (eab56a6) to head (419a90f).
⚠️ Report is 3 commits behind head on main.

Files with missing lines Patch % Lines
src/exact.rs 94.69% 7 Missing ⚠️
src/matrix.rs 98.61% 1 Missing ⚠️
src/vector.rs 97.50% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #133      +/-   ##
==========================================
- Coverage   99.64%   99.31%   -0.34%     
==========================================
  Files           5        5              
  Lines        2832     2757      -75     
==========================================
- Hits         2822     2738      -84     
- Misses         10       19       +9     
Flag Coverage Δ
unittests 99.31% <96.96%> (-0.34%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/matrix.rs (1)

346-352: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Keep TryFrom<Matrix<D>> actually validating finiteness.

This conversion now accepts any Matrix<D>, including values built through from_rows_unchecked, so internal callers can manufacture a FiniteMatrix<D> proof around NaN/∞ storage and bypass the invariant every downstream algorithm now relies on.

Suggested fix
 impl<const D: usize> TryFrom<Matrix<D>> for FiniteMatrix<D> {
     type Error = LaError;
 
     #[inline]
     fn try_from(value: Matrix<D>) -> Result<Self, Self::Error> {
-        Ok(Self::new(value))
+        Self::from_rows(value.rows)
     }
 }
Based on learnings: Applies to src/{lib,matrix,lu,ldlt,vector,exact}.rs : Non-finite values (NaN, ±∞) always surface as `LaError::NonFinite { row, col }` with source-location metadata.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/matrix.rs` around lines 346 - 352, The TryFrom implementation for
TryFrom<Matrix<D>> for FiniteMatrix<D> currently accepts any Matrix<D> and must
instead validate every element for finiteness in try_from; update the
implementation of TryFrom::try_from (for FiniteMatrix<D>) to iterate the matrix
entries, return Err(LaError::NonFinite { row, col }) when a NaN or ±∞ is found
(including source-location metadata as required), and only call
FiniteMatrix::new and return Ok(...) if all entries are finite. Ensure the error
uses the LaError::NonFinite variant and supplies the offending element
coordinates so callers cannot bypass the invariant via from_rows_unchecked.
src/vector.rs (1)

94-100: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Keep TryFrom<Vector<D>> actually validating finiteness.

This conversion is now infallible even though crate-internal code can still build unchecked Vector<D> values, so a NaN/∞ RHS can be wrapped as FiniteVector<D> and slip past the invariant these algorithms now assume.

Suggested fix
 impl<const D: usize> TryFrom<Vector<D>> for FiniteVector<D> {
     type Error = LaError;
 
     #[inline]
     fn try_from(value: Vector<D>) -> Result<Self, Self::Error> {
-        Ok(Self::new(value))
+        Self::from_array(value.data)
     }
 }
Based on learnings: Applies to src/{lib,matrix,lu,ldlt,vector,exact}.rs : Non-finite values (NaN, ±∞) always surface as `LaError::NonFinite { row, col }` with source-location metadata.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/vector.rs` around lines 94 - 100, The TryFrom<Vector<D>> implementation
for FiniteVector<D> must validate every component and reject NaN/∞ instead of
being infallible: in the impl for TryFrom<Vector<D>> for FiniteVector<D> iterate
the input Vector<D> components, check each value.is_finite(), and if any
component is non-finite return Err(LaError::NonFinite { row: 0, col: i })
(populate the correct row/col and include source-location metadata as other
modules do); only construct and return FiniteVector (e.g., via FiniteVector::new
or a checked constructor) when all components pass the is_finite check.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@benches/exact.rs`:
- Around line 349-388: The current approach (percentile_input_indices ->
bench_random_percentile_operation) picks one input per percentile using a single
timing and then repeatedly benchmarks that single input, which reports
representative-input medians rather than corpus percentile timings; fix it by
computing per-input timings across the whole corpus (use time_random_operation
in a loop with enough samples per input or run multiple measurement passes),
compute the actual percentile thresholds from that timing distribution, then
change percentile_input_indices to return a set/Vec of indices per percentile
(or a predicate) instead of one index and update
bench_random_percentile_operation to benchmark across that whole subset (e.g.,
bench using an input-generator closure that selects inputs from the percentile
set or iterate bench_function over all indices in the set and aggregate),
referencing percentile_input_indices, bench_random_percentile_operation,
time_random_operation, run_random_operation and RANDOM_PERCENTILES so the code
selects and measures many inputs per percentile rather than a single
representative.

In `@tests/semgrep/src/project_rules/raw_f64_constructors.rs`:
- Around line 12-14: The public enum LaError should be marked #[non_exhaustive]
to prevent Semgrep from flagging public error enums; add the #[non_exhaustive]
attribute above the LaError declaration (and ensure any public wrapper types in
this test follow guidelines by being #[must_use] where applicable) so the enum
declaration for LaError is non-exhaustive and the test only triggers the
intended constructor rules.

---

Outside diff comments:
In `@src/matrix.rs`:
- Around line 346-352: The TryFrom implementation for TryFrom<Matrix<D>> for
FiniteMatrix<D> currently accepts any Matrix<D> and must instead validate every
element for finiteness in try_from; update the implementation of
TryFrom::try_from (for FiniteMatrix<D>) to iterate the matrix entries, return
Err(LaError::NonFinite { row, col }) when a NaN or ±∞ is found (including
source-location metadata as required), and only call FiniteMatrix::new and
return Ok(...) if all entries are finite. Ensure the error uses the
LaError::NonFinite variant and supplies the offending element coordinates so
callers cannot bypass the invariant via from_rows_unchecked.

In `@src/vector.rs`:
- Around line 94-100: The TryFrom<Vector<D>> implementation for FiniteVector<D>
must validate every component and reject NaN/∞ instead of being infallible: in
the impl for TryFrom<Vector<D>> for FiniteVector<D> iterate the input Vector<D>
components, check each value.is_finite(), and if any component is non-finite
return Err(LaError::NonFinite { row: 0, col: i }) (populate the correct row/col
and include source-location metadata as other modules do); only construct and
return FiniteVector (e.g., via FiniteVector::new or a checked constructor) when
all components pass the is_finite check.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 862241e8-2a61-41aa-9284-9fa28daae0a8

📥 Commits

Reviewing files that changed from the base of the PR and between eab56a6 and 92ba403.

📒 Files selected for processing (27)
  • AGENTS.md
  • README.md
  • benches/exact.rs
  • benches/vs_linalg.rs
  • docs/BENCHMARKING.md
  • examples/const_det_4x4.rs
  • examples/det_5x5.rs
  • examples/exact_det_3x3.rs
  • examples/exact_sign_3x3.rs
  • examples/exact_solve_3x3.rs
  • examples/ldlt_solve_3x3.rs
  • examples/solve_5x5.rs
  • scripts/bench_compare.py
  • scripts/tests/test_bench_compare.py
  • semgrep.yaml
  • src/exact.rs
  • src/ldlt.rs
  • src/lib.rs
  • src/lu.rs
  • src/matrix.rs
  • src/vector.rs
  • tests/proptest_exact.rs
  • tests/proptest_factorizations.rs
  • tests/proptest_matrix.rs
  • tests/proptest_vector.rs
  • tests/semgrep/src/project_rules/public_api_panic_paths.rs
  • tests/semgrep/src/project_rules/raw_f64_constructors.rs

Comment thread benches/exact.rs Outdated
Comment thread tests/semgrep/src/project_rules/raw_f64_constructors.rs
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.
@acgetchell acgetchell enabled auto-merge June 3, 2026 21:15
@acgetchell acgetchell merged commit ed7dc46 into main Jun 3, 2026
18 checks passed
@acgetchell acgetchell deleted the feat/98-finite-api-invariants branch June 3, 2026 21:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

perf: add random-input percentile benchmarks to exact arithmetic suite

1 participant