Skip to content

feat!(api): enforce fallible numeric invariants#127

Merged
acgetchell merged 1 commit into
mainfrom
feat/83-matrix-set-option
Jun 3, 2026
Merged

feat!(api): enforce fallible numeric invariants#127
acgetchell merged 1 commit into
mainfrom
feat/83-matrix-set-option

Conversation

@acgetchell
Copy link
Copy Markdown
Owner

@acgetchell acgetchell commented Jun 3, 2026

  • Introduce validated tolerance values for factorization and symmetry APIs.
  • Return typed errors for non-finite matrix, vector, determinant, and norm results.
  • Make determinant and vector operations propagate overflow/non-finite failures.
  • Update examples, docs, benchmarks, and property tests for the fallible API.
  • Document the v0.4.2 roadmap order for finite Matrix/Vector proof-type work.

BREAKING CHANGE: tolerance arguments now use Tolerance instead of raw f64, Matrix::set returns Option<()>, determinant helpers return Result, Lu::det and Ldlt::det return Result<f64, LaError>, and Vector::dot / Vector::norm2_sq return Result<f64, LaError>.

Closes #83

Summary by CodeRabbit

  • New Features

    • Introduced a new Tolerance type for robust tolerance handling across factorization and symmetry operations.
  • Bug Fixes

    • Improved error handling by making APIs properly return Result types instead of panicking on invalid operations or non-finite values.
  • Documentation

    • Updated README examples and guidance to demonstrate idiomatic error handling patterns.
    • Expanded examples throughout the library to show proper error propagation.
  • Chores

    • Updated feature flag configurations and build recipes for consistency.

- Introduce validated tolerance values for factorization and symmetry APIs.
- Return typed errors for non-finite matrix, vector, determinant, and norm results.
- Make determinant and vector operations propagate overflow/non-finite failures.
- Update examples, docs, benchmarks, and property tests for the fallible API.
- Document the v0.4.2 roadmap order for finite Matrix/Vector proof-type work.

BREAKING CHANGE: tolerance arguments now use Tolerance instead of raw f64, Matrix::set returns Option<()>, determinant helpers return Result, Lu::det and Ldlt::det return Result<f64, LaError>, and Vector::dot / Vector::norm2_sq return Result<f64, LaError>.

Closes #83
@acgetchell acgetchell self-assigned this Jun 3, 2026
@acgetchell acgetchell enabled auto-merge June 3, 2026 07:18
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jun 3, 2026

Review Change Stack

📝 Walkthrough

Walkthrough

This PR introduces a validated Tolerance type throughout the library and converts determinant, norm, and dot-product APIs from infallible to fallible Result types. Matrix::set returns Option<()>, inf_norm rejects non-finite values, det_direct/det_errbound return Result<Option<f64>, LaError>, and Vector::dot/norm2_sq return Result<f64, LaError>. All factorizations, examples, benchmarks, and tests are updated to use the new APIs.

Changes

Tolerance Type and Core Constants

Layer / File(s) Summary
Tolerance type definition and validation
src/lib.rs
Tolerance struct introduced with new() validation constructor, new_unchecked(), and get() accessor; LaError::NonFinite documentation clarified for computed scalar terminology.
Tolerance constants and prelude exports
src/lib.rs
DEFAULT_SINGULAR_TOL and DEFAULT_PIVOT_TOL converted from f64 to Tolerance constants; Tolerance added to prelude module re-exports.
LaError non-finite diagnostic constructors
src/lib.rs
LaError::non_finite_cell and LaError::non_finite_at doctests expanded to show structured (row, col) error payloads.

Matrix Core Methods: Bounds, Norm, and Determinants

Layer / File(s) Summary
Matrix::set return type and inf_norm fallibility
src/matrix.rs
Matrix::set changed from bool to Option<()>; inf_norm now returns Result<f64, LaError> and rejects non-finite cells and row-sums with source coordinates.
Symmetry predicates with Tolerance
src/matrix.rs
is_symmetric and first_asymmetry now accept Tolerance instead of f64; epsilon computation refactored to extract and scale tolerance value.
Determinant methods: det_direct and det_errbound
src/matrix.rs
det_direct and det_errbound converted from Option<f64> to Result<Option<f64>, LaError>; internal helpers first_non_finite_cell and computed_scalar_result added to surface non-finite/overflow errors; dimension-specific fast-path logic updated.
Factorization and det tolerance parameters
src/matrix.rs
Matrix::lu and Matrix::ldlt signatures updated to accept Tolerance; Matrix::det tolerance parameter converted to Tolerance with fast-path updated to use det_direct()?.
Matrix tests: set, inf_norm, det, symmetry
src/matrix.rs, tests/proptest_matrix.rs
Test harness updated to match new Option<()> from set, unwrap Result from inf_norm/determinant methods, construct Tolerance::new(...), and assert coordinate-bearing LaError::NonFinite on non-finite inputs.

Vector Fallibility: Dot and Norm2_sq

Layer / File(s) Summary
Vector dot and norm2_sq fallibility
src/vector.rs
Vector::dot now returns Result<f64, LaError> with per-element and accumulation finiteness validation; norm2_sq delegates to dot and returns the Result.
Vector tests for fallible operations
src/vector.rs, tests/proptest_vector.rs
Property tests updated to unwrap dot() and norm2_sq() results before assertions; new test added to verify non-finite input rejection with LaError::NonFinite { row: None, col: d - 1 }.

LU and LDLT Factorization

Layer / File(s) Summary
LU factorization with Tolerance
src/lu.rs
Lu struct tol field and factor signature changed to Tolerance; pivot singularity checks use tol.get(); Lu::det now returns Result<f64, LaError> with finiteness validation of the determinant product.
LDLT factorization with Tolerance
src/ldlt.rs
Ldlt struct tol field and factor parameter converted to Tolerance; solve_vec singularity check updated to use tol.get(); Ldlt::det rewritten to return Result<f64, LaError> with diagonal-product finiteness checking.
LU/LDLT test harness and tolerance validation
src/lu.rs, src/ldlt.rs, tests/proptest_factorizations.rs
Tests updated to construct Tolerance::new(...) for parameters, unwrap fallible det() calls, and use assert_matches! for error shape validation including new NonFinite payload tests.

Exact Arithmetic: Sign and Determinant

Layer / File(s) Summary
det_sign_exact fast-filter refactoring
src/exact.rs
det_sign_exact D≤4 fast filter now matches on (det_direct(), det_errbound()) with explicit LaError::NonFinite branches; routes row: Some(..) immediately while row: None (inconclusive) falls through to Bareiss fallback.
Exact method doctests and examples
src/exact.rs
det_exact, det_exact_f64, solve_exact, solve_exact_f64 doctests updated to use main() -> Result<(), LaError> with ? error propagation and Ok(()) markers.
Exact tests and assertion patterns
src/exact.rs
Test harness updated with core::assert_matches import; Matrix::set assertions check Some(()) return; det_errbound tests match new Result<Option<_>, LaError> structure; det_sign_exact tests assert LaError::NonFinite patterns with row: None vs row: Some(..).

Documentation, Examples, and Benchmarks

Layer / File(s) Summary
README and lib.rs doctests
README.md, src/lib.rs
README examples for LU solve, LDLT determinant, det_direct, exact feature, and det_errbound converted to main() -> Result<(), LaError> blocks using ? instead of unwrap(); ERR_COEFF_2 documentation example refactored to use fallible det_direct()? with branching.
Example binaries and usage
examples/*
const_det_4x4.rs match arms expanded to handle Err(_) for non-finite entries; det_5x5.rs uses det()? for error propagation; exact_det_3x3.rs uses nested unwrap().unwrap(); exact_solve_3x3.rs constructs Tolerance::new(0.0).unwrap(); ldlt_solve_3x3.rs uses det()?.
Benchmark updates for fallible APIs
benches/vs_linalg.rs, benches/exact.rs
Benchmark code pattern-matches lu.det() as Result, unwraps dot() and norm2_sq() before black-boxing, and uses imported DEFAULT_PIVOT_TOL instead of fully-qualified paths.
Property test updates
tests/proptest_exact.rs, tests/proptest_factorizations.rs, tests/proptest_matrix.rs, tests/proptest_vector.rs
Tests updated to unwrap Result values from determinant, dot, norm, and norm-squared calls before assertions; matrix set tests verify Some(()) on success and None on out-of-bounds.
Documentation and tooling updates
AGENTS.md, CHANGELOG.md, Cargo.toml, docs/roadmap.md, justfile
AGENTS.md clarifies branch-name and test-filter usage; CHANGELOG.md documents fallible matrix invariants; Cargo.toml switches feature flags to dep: syntax; roadmap adds proof types and Semgrep guardrails; justfile example recipe dynamically discovers examples.

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related issues

Possibly related PRs

  • acgetchell/la-stack#96: Modifies src/exact.rs determinant/sign-solving flow, specifically det_sign_exact finiteness and Result error handling.
  • acgetchell/la-stack#124: Introduces the fallible matrix invariant/error API framework that this PR builds directly upon, evolving symmetry/tolerance/determinant error handling.
  • acgetchell/la-stack#68: Refactors non-finite error reporting to use structured LaError::NonFinite { row, col } payload—this PR adopts and propagates the same error-shape convention throughout.

Suggested labels

api, breaking change, rust, testing, documentation, enhancement

Poem

🐰 Through stacks so tall and matrices so wide,
Tolerance takes form with validation's pride,
Result flows where errors dared to hide,
Now fallible and fearless, hand in hand they ride!

🚥 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 PR title clearly and concisely summarizes the main change: enforcing fallible numeric invariants through typed errors and validated Tolerance values.
Linked Issues check ✅ Passed The PR fully implements the requirements from issue #83: Matrix::set now returns Option<()> instead of bool, with comprehensive updates across all affected APIs, tests, examples, and documentation.
Out of Scope Changes check ✅ Passed All changes are in scope. Beyond the core Matrix::set change, the PR introduces coordinated API improvements (Tolerance type, fallible determinant/vector operations) that are necessary to fully realize the idiomatic error-handling goals and are consistent with the PR objectives.
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/83-matrix-set-option

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 99.04459% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 99.45%. Comparing base (6124948) to head (adfc33b).
⚠️ Report is 2 commits behind head on main.

Files with missing lines Patch % Lines
src/vector.rs 93.33% 2 Missing ⚠️
src/exact.rs 96.42% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #127      +/-   ##
==========================================
- Coverage   99.55%   99.45%   -0.11%     
==========================================
  Files           5        5              
  Lines        2255     2383     +128     
==========================================
+ Hits         2245     2370     +125     
- Misses         10       13       +3     
Flag Coverage Δ
unittests 99.45% <99.04%> (-0.11%) ⬇️

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 (3)
src/matrix.rs (1)

355-374: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Fail when the scaled symmetry threshold overflows.

row_eps = rel_tol.mul_add(entry.abs(), row_eps) can still overflow for large finite matrices or loose tolerances. Once that happens, eps becomes , and first_asymmetry() can return Ok(None) for a genuinely asymmetric matrix because diff > eps is never true. Return LaError::NonFinite as soon as the accumulator stops being finite.

Suggested fix
 fn symmetry_epsilon(&self, rel_tol: Tolerance) -> Result<f64, LaError> {
     let rel_tol = rel_tol.get();
     let mut eps = rel_tol;

     for r in 0..D {
         let mut row_eps = 0.0;
         for c in 0..D {
             let entry = self.rows[r][c];
             if !entry.is_finite() {
                 cold_path();
                 return Err(LaError::non_finite_cell(r, c));
             }
             row_eps = rel_tol.mul_add(entry.abs(), row_eps);
+            if !row_eps.is_finite() {
+                cold_path();
+                return Err(LaError::non_finite_at(r));
+            }
         }
         if row_eps > eps {
             eps = row_eps;
         }
     }

     Ok(eps)
 }

As per coding guidelines, non-finite values (NaN, ±∞) must 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 355 - 374, In symmetry_epsilon, the accumulator
row_eps (and consequently eps) can overflow to non-finite when using
rel_tol.mul_add(entry.abs(), row_eps); after each mul_add check whether the new
row_eps is finite and if not return Err(LaError::non_finite_cell(r, c));
likewise ensure eps is only updated with finite row_eps (and if eps becomes
non-finite return LaError::non_finite_cell(r, c)); reference the
symmetry_epsilon function, the row_eps and eps variables, and
LaError::non_finite_cell to locate where to add these finiteness checks and
early returns so non-finite accumulators surface as LaError::NonFinite with the
proper row/col metadata.
src/vector.rs (1)

72-124: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Document the rounding contract of dot() and norm2_sq().

Both APIs now accumulate in f64, but their docs do not state an absolute error bound or that no bound is provided. Please spell that out explicitly, the same way the determinant helpers do.

As per coding guidelines, any f64 operation that accumulates rounding error must document its absolute bound or explicitly state that no bound is provided.

🤖 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 72 - 124, Update the docs for Vector::dot and
Vector::norm2_sq to state the rounding/accuracy contract: mention that both
methods accumulate in f64 (using f64 mul_add on each term) and either provide
the same absolute error bound used by the determinant helpers or explicitly
state that no absolute bound is provided; mirror the phrasing and examples from
the determinant helper doc comments, include that intermediate rounding occurs
and the returned Result is checked for non-finite accumulation, and reference
the functions by name (dot, norm2_sq) so readers can find the implementation
details.
examples/exact_det_3x3.rs (1)

12-29: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Inconsistent error handling pattern across examples.

This example uses fn main() without a Result return and chains .unwrap().unwrap() to extract the determinant (line 27), which is not idiomatic. Other examples in this PR layer (e.g., det_5x5.rs, README snippets) consistently use fn main() -> Result<(), LaError> with ? for error propagation.

Recommend updating to match the established pattern:

♻️ Refactor to use Result and ? operator
-fn main() {
+fn main() -> Result<(), LaError> {
     // Base matrix: rows in arithmetic progression → exactly singular (det = 0).
     //   [[1, 2, 3],
     //    [4, 5, 6],
     //    [7, 8, 9]]
     //
     // Perturb entry (0,0) by 2^-50 ≈ 8.9e-16.
     // Exact det = 2^-50 × cofactor(0,0) = 2^-50 × (5×9 − 6×8) = −3 × 2^-50.
     let perturbation = f64::from_bits(0x3CD0_0000_0000_0000); // 2^-50
     let m = Matrix::<3>::from_rows([
         [1.0 + perturbation, 2.0, 3.0],
         [4.0, 5.0, 6.0],
         [7.0, 8.0, 9.0],
     ]);

-    let det_f64_approx = m.det_direct().unwrap().unwrap();
-    let det_exact = m.det_exact().unwrap();
-    let det_exact_as_f64 = m.det_exact_f64().unwrap();
+    let det_f64_approx = m.det_direct()?.expect("D=3 is supported by det_direct");
+    let det_exact = m.det_exact()?;
+    let det_exact_as_f64 = m.det_exact_f64()?;

     println!("Near-singular 3×3 matrix (perturbation = 2^-50 ≈ {perturbation:.2e}):");
     for r in 0..3 {
         print!("  [");
         for c in 0..3 {
             if c > 0 {
                 print!(", ");
             }
-            print!("{:22.18}", m.get(r, c).unwrap());
+            print!("{:22.18}", m.get(r, c)?);
         }
         println!("]");
     }
     println!();
     println!("f64 det_direct()   = {det_f64_approx:+.6e}");
     println!("det_exact()        = {det_exact}");
     println!("det_exact_f64()    = {det_exact_as_f64:+.6e}");
     println!();
     println!("The exact determinant is −3/2^50 ≈ −2.66e-15.");
+
+    Ok(())
 }
🤖 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 `@examples/exact_det_3x3.rs` around lines 12 - 29, The main function in this
example uses panicking unwraps (fn main()) and double .unwrap().unwrap() on
det_direct; change it to the established pattern by making fn main() ->
Result<(), LaError> and propagate errors with ? on calls to
Matrix::<3>::det_direct(), det_exact(), and det_exact_f64() (e.g. replace the
unwrap chain with det_direct()? and det_exact()? etc.), then end with Ok(()) so
the example matches det_5x5.rs and README snippets; keep the same Matrix::<3>
instantiation and variable names (det_f64_approx, det_exact, det_exact_as_f64)
but obtain their values via ? instead of unwrap.
🧹 Nitpick comments (2)
tests/proptest_vector.rs (1)

43-55: ⚡ Quick win

Add adversarial generators for the new vector error contract.

This property test still samples only small_f64(), so dot() and norm2_sq() can never hit the new Err(LaError::NonFinite { .. }) paths. Add a second case with large or injected non-finite components and assert the structured error.

As per coding guidelines, property-based tests under tests/proptest_*.rs must include adversarial inputs alongside well-conditioned inputs.

🤖 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 `@tests/proptest_vector.rs` around lines 43 - 55, The property test currently
only uses small_f64() so calls to Vector::<$d>::dot and norm2_sq never exercise
the Err(LaError::NonFinite { .. }) path; update the test to include a second
case that generates adversarial vectors (e.g., large_f64() and injected
non-finite values like NaN/Inf) alongside the existing well-conditioned
generator, then for those adversarial inputs assert that a.dot(b) and
a.dot(a)/a.norm2_sq() return Err(LaError::NonFinite { .. }) and match the
structured error rather than using assert_abs_diff_eq!, locating changes around
the usages of dot, norm2_sq, and the vector generator setup.
tests/proptest_matrix.rs (1)

127-128: ⚡ Quick win

Property-test the new matrix error paths instead of only unwrap()ing them.

small_f64() and the SPD generator only produce well-conditioned finite matrices, so the new fallible branches exercised by inf_norm() and ldlt.det() never run here. Please add at least one adversarial generator with NaN / large-magnitude / asymmetric inputs and assert the structured LaError results.

As per coding guidelines, property-based tests under tests/proptest_*.rs must include adversarial inputs alongside well-conditioned inputs.

Also applies to: 214-214

🤖 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 `@tests/proptest_matrix.rs` around lines 127 - 128, Add an adversarial
generator alongside small_f64() / the SPD generator that can produce NaN,
huge-magnitude, and asymmetric matrices and update the assertions for inf_norm()
and ldlt.det() to check for the structured LaError rather than unwrapping;
specifically, in the property test that calls Matrix::inf_norm() and LDLT::det()
replace unwrap() usage with pattern matching or prop_assert matches to assert
the returned Result is Err(LaError::...) for the adversarial cases and still
assert Ok(expected) for well-conditioned cases, referencing the inf_norm(),
ldlt.det(), LaError, small_f64(), and the SPD generator to locate the test logic
to modify.
🤖 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 `@examples/exact_solve_3x3.rs`:
- Around line 29-34: The example currently uses unwrap() on fallible operations
(a.lu(Tolerance::new(0.0).unwrap()).unwrap().solve_vec(b).unwrap().into_array())
which panics; change main to return Result<(), LaError> and propagate errors
with ? instead of unwrap so each fallible call (Tolerance::new, Matrix::lu,
LU::solve_vec) uses ? and the final result is handled; update the function
signature to fn main() -> Result<(), LaError> and replace the chained unwraps
with the same calls using ? to return errors up the stack.

In `@src/matrix.rs`:
- Around line 192-221: Update the inf_norm() documentation to state the
rounding/accuracy contract: describe that row sums are accumulated in f64 and
either give a specific absolute rounding bound for the returned value or
explicitly state that no rounding bound is provided (i.e., result is heuristic
and may incur typical floating‑point accumulation error). Place this sentence
near the existing Non-finite handling / # Errors section of the inf_norm() doc
comment so callers of inf_norm() know whether the norm is certified or has no
guaranteed rounding bound.

---

Outside diff comments:
In `@examples/exact_det_3x3.rs`:
- Around line 12-29: The main function in this example uses panicking unwraps
(fn main()) and double .unwrap().unwrap() on det_direct; change it to the
established pattern by making fn main() -> Result<(), LaError> and propagate
errors with ? on calls to Matrix::<3>::det_direct(), det_exact(), and
det_exact_f64() (e.g. replace the unwrap chain with det_direct()? and
det_exact()? etc.), then end with Ok(()) so the example matches det_5x5.rs and
README snippets; keep the same Matrix::<3> instantiation and variable names
(det_f64_approx, det_exact, det_exact_as_f64) but obtain their values via ?
instead of unwrap.

In `@src/matrix.rs`:
- Around line 355-374: In symmetry_epsilon, the accumulator row_eps (and
consequently eps) can overflow to non-finite when using
rel_tol.mul_add(entry.abs(), row_eps); after each mul_add check whether the new
row_eps is finite and if not return Err(LaError::non_finite_cell(r, c));
likewise ensure eps is only updated with finite row_eps (and if eps becomes
non-finite return LaError::non_finite_cell(r, c)); reference the
symmetry_epsilon function, the row_eps and eps variables, and
LaError::non_finite_cell to locate where to add these finiteness checks and
early returns so non-finite accumulators surface as LaError::NonFinite with the
proper row/col metadata.

In `@src/vector.rs`:
- Around line 72-124: Update the docs for Vector::dot and Vector::norm2_sq to
state the rounding/accuracy contract: mention that both methods accumulate in
f64 (using f64 mul_add on each term) and either provide the same absolute error
bound used by the determinant helpers or explicitly state that no absolute bound
is provided; mirror the phrasing and examples from the determinant helper doc
comments, include that intermediate rounding occurs and the returned Result is
checked for non-finite accumulation, and reference the functions by name (dot,
norm2_sq) so readers can find the implementation details.

---

Nitpick comments:
In `@tests/proptest_matrix.rs`:
- Around line 127-128: Add an adversarial generator alongside small_f64() / the
SPD generator that can produce NaN, huge-magnitude, and asymmetric matrices and
update the assertions for inf_norm() and ldlt.det() to check for the structured
LaError rather than unwrapping; specifically, in the property test that calls
Matrix::inf_norm() and LDLT::det() replace unwrap() usage with pattern matching
or prop_assert matches to assert the returned Result is Err(LaError::...) for
the adversarial cases and still assert Ok(expected) for well-conditioned cases,
referencing the inf_norm(), ldlt.det(), LaError, small_f64(), and the SPD
generator to locate the test logic to modify.

In `@tests/proptest_vector.rs`:
- Around line 43-55: The property test currently only uses small_f64() so calls
to Vector::<$d>::dot and norm2_sq never exercise the Err(LaError::NonFinite { ..
}) path; update the test to include a second case that generates adversarial
vectors (e.g., large_f64() and injected non-finite values like NaN/Inf)
alongside the existing well-conditioned generator, then for those adversarial
inputs assert that a.dot(b) and a.dot(a)/a.norm2_sq() return
Err(LaError::NonFinite { .. }) and match the structured error rather than using
assert_abs_diff_eq!, locating changes around the usages of dot, norm2_sq, and
the vector generator setup.
🪄 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: 15349511-692b-42de-b29c-f34a71cb1f8e

📥 Commits

Reviewing files that changed from the base of the PR and between 6124948 and adfc33b.

📒 Files selected for processing (23)
  • AGENTS.md
  • CHANGELOG.md
  • Cargo.toml
  • README.md
  • benches/exact.rs
  • benches/vs_linalg.rs
  • docs/roadmap.md
  • examples/const_det_4x4.rs
  • examples/det_5x5.rs
  • examples/exact_det_3x3.rs
  • examples/exact_solve_3x3.rs
  • examples/ldlt_solve_3x3.rs
  • justfile
  • 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

Comment on lines +29 to +34
let lu_x = a
.lu(Tolerance::new(0.0).unwrap())
.unwrap()
.solve_vec(b)
.unwrap()
.into_array();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Propagate fallible calls in the example instead of panicking.

Line [30]-Line [33] currently unwrap fallible operations; this example should model the new fallible API and return Result<(), LaError> from main with ?.

Suggested change
-fn main() {
+fn main() -> Result<(), LaError> {
@@
-    let lu_x = a
-        .lu(Tolerance::new(0.0).unwrap())
-        .unwrap()
-        .solve_vec(b)
-        .unwrap()
-        .into_array();
+    let lu_x = a.lu(Tolerance::new(0.0)?)?.solve_vec(b)?.into_array();
@@
-    let exact_x = a.solve_exact(b).unwrap();
-    let exact_x_f64 = a.solve_exact_f64(b).unwrap().into_array();
+    let exact_x = a.solve_exact(b)?;
+    let exact_x_f64 = a.solve_exact_f64(b)?.into_array();
@@
+    Ok(())
 }

As per coding guidelines: **/*.rs: “Use Result<_, LaError> for all fallible operations. Panics are reserved for debug-only precondition violations (e.g., LDLT symmetry check) and must be documented on the method.”

🤖 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 `@examples/exact_solve_3x3.rs` around lines 29 - 34, The example currently uses
unwrap() on fallible operations
(a.lu(Tolerance::new(0.0).unwrap()).unwrap().solve_vec(b).unwrap().into_array())
which panics; change main to return Result<(), LaError> and propagate errors
with ? instead of unwrap so each fallible call (Tolerance::new, Matrix::lu,
LU::solve_vec) uses ? and the final result is handled; update the function
signature to fn main() -> Result<(), LaError> and replace the chained unwraps
with the same calls using ? to return errors up the stack.

Comment thread src/matrix.rs
Comment on lines 192 to +221
/// Infinity norm (maximum absolute row sum).
///
/// # Non-finite handling
/// If any entry is NaN, the result is NaN. NaN is detected explicitly
/// because a naive `row_sum > max_row_sum` comparison silently skips NaN
/// rows (every ordered comparison against NaN is `false`). If any entry
/// is infinite (and no entry is NaN), the result is `+∞`.
/// Non-finite entries are rejected with source coordinates instead of
/// silently propagating NaN or infinity through the norm.
///
/// # Examples
/// ```
/// use la_stack::prelude::*;
///
/// # fn main() -> Result<(), LaError> {
/// let m = Matrix::<2>::from_rows([[1.0, -2.0], [3.0, 4.0]]);
/// assert!((m.inf_norm() - 7.0).abs() <= 1e-12);
/// assert!((m.inf_norm()? - 7.0).abs() <= 1e-12);
///
/// // NaN entries propagate to the norm.
/// // NaN entries are rejected with coordinates.
/// let nan = Matrix::<2>::from_rows([[f64::NAN, 1.0], [2.0, 3.0]]);
/// assert!(nan.inf_norm().is_nan());
/// assert_eq!(
/// nan.inf_norm(),
/// Err(LaError::NonFinite {
/// row: Some(0),
/// col: 0,
/// })
/// );
/// # Ok(())
/// # }
/// ```
///
/// # Errors
/// Returns [`LaError::NonFinite`] when any entry is NaN or infinity, or when
/// a row sum overflows to NaN or infinity.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Document the rounding contract of inf_norm().

This method now accumulates f64 row sums, but the doc comment neither gives an absolute bound nor states that no bound is provided. Please add that sentence explicitly so callers know whether this result is certified or heuristic.

As per coding guidelines, any f64 operation that accumulates rounding error must document its absolute bound or explicitly state that no bound is provided.

🤖 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 192 - 221, Update the inf_norm() documentation to
state the rounding/accuracy contract: describe that row sums are accumulated in
f64 and either give a specific absolute rounding bound for the returned value or
explicitly state that no rounding bound is provided (i.e., result is heuristic
and may incur typical floating‑point accumulation error). Place this sentence
near the existing Non-finite handling / # Errors section of the inf_norm() doc
comment so callers of inf_norm() know whether the norm is certified or has no
guaranteed rounding bound.

@acgetchell acgetchell merged commit 1dac6cc into main Jun 3, 2026
20 checks passed
@acgetchell acgetchell deleted the feat/83-matrix-set-option branch June 3, 2026 07:29
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.

api: change Matrix::set to return Option<()> instead of bool

1 participant