Skip to content

perf: stack-backed storage for exact arithmetic kernels#68

Merged
acgetchell merged 3 commits intomainfrom
perf/62-stack-backed-exact-kernels
Apr 10, 2026
Merged

perf: stack-backed storage for exact arithmetic kernels#68
acgetchell merged 3 commits intomainfrom
perf/62-stack-backed-exact-kernels

Conversation

@acgetchell
Copy link
Copy Markdown
Owner

@acgetchell acgetchell commented Apr 10, 2026

Replace heap-allocated Vec<Vec> with stack-backed [[BigRational; D]; D] arrays in bareiss_det and gauss_solve, and return [BigRational; D] from gauss_solve directly — eliminating per-call heap traffic and a redundant clone in solve_exact.

Exact kernel changes (src/exact.rs):

  • bareiss_det: Vec<Vec> → [[BigRational; D]; D] via std::array::from_fn
  • gauss_solve: Vec<Vec> augmented matrix → separate [[BigRational; D]; D] + [BigRational; D] (avoids unstable const-generic [T; D+1])
  • gauss_solve return type: Vec → [BigRational; D]
  • solve_exact: drop intermediate clone, forward array directly

Error enrichment (src/lib.rs, src/lu.rs, src/ldlt.rs, src/matrix.rs, src/exact.rs):

  • LaError::NonFinite gains row: Option for full (row, col) location on matrix inputs; None for vectors/intermediates
  • LaError::Overflow becomes a struct variant with index: Option
    to identify which solution component overflowed
  • Display messages updated to show positional context

Benchmarks:

  • New benches/exact.rs: Criterion benchmarks for det, det_direct, det_exact, det_exact_f64, det_sign_exact, solve_exact, and solve_exact_f64 across D=2..5, plus near-singular 3×3 fallback
  • Cargo.toml: add [[bench]] entry with required-features = ["bench", "exact"]

Closes #62

Summary by CodeRabbit

  • New Features

    • Added a benchmark suite for exact rational arithmetic with per-dimension performance measurements.
  • Bug Fixes

    • Improved non-finite error messages to report row and column locations when available.
    • Enhanced overflow error reporting to include an index when applicable, clarifying where conversions fail.

Replace heap-allocated Vec<Vec<BigRational>> with stack-backed
[[BigRational; D]; D] arrays in bareiss_det and gauss_solve, and return
[BigRational; D] from gauss_solve directly — eliminating per-call heap
traffic and a redundant clone in solve_exact.

Exact kernel changes (src/exact.rs):
- bareiss_det: Vec<Vec<BigRational>> → [[BigRational; D]; D] via
  std::array::from_fn
- gauss_solve: Vec<Vec<BigRational>> augmented matrix → separate
  [[BigRational; D]; D] + [BigRational; D] (avoids unstable
  const-generic [T; D+1])
- gauss_solve return type: Vec<BigRational> → [BigRational; D]
- solve_exact: drop intermediate clone, forward array directly

Error enrichment (src/lib.rs, src/lu.rs, src/ldlt.rs, src/matrix.rs,
src/exact.rs):
- LaError::NonFinite gains row: Option<usize> for full (row, col)
  location on matrix inputs; None for vectors/intermediates
- LaError::Overflow becomes a struct variant with index: Option<usize>
  to identify which solution component overflowed
- Display messages updated to show positional context

Benchmarks:
- New benches/exact.rs: Criterion benchmarks for det, det_direct,
  det_exact, det_exact_f64, det_sign_exact, solve_exact, and
  solve_exact_f64 across D=2..5, plus near-singular 3×3 fallback
- Cargo.toml: add [[bench]] entry with required-features = ["bench",
  "exact"]

Closes #62
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 10, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 72826ecb-bdac-447a-be53-b1b0e0ba174d

📥 Commits

Reviewing files that changed from the base of the PR and between b64ac60 and d4b1452.

📒 Files selected for processing (4)
  • benches/exact.rs
  • src/ldlt.rs
  • src/lu.rs
  • src/matrix.rs
✅ Files skipped from review due to trivial changes (1)
  • benches/exact.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/matrix.rs

📝 Walkthrough

Walkthrough

Refactors exact-arithmetic kernels to use fixed-size, stack-backed arrays, expands error payloads with row/index context across matrix routines, and adds Criterion benchmarks for exact arithmetic over D=2..5 (including a near-singular 3×3 case).

Changes

Cohort / File(s) Summary
Manifest / Bench target
Cargo.toml
Added a new Cargo bench target [[bench]] name = "exact" with harness = false and required-features = ["bench","exact"].
Benchmarks
benches/exact.rs
Added Criterion benchmark binary exercising exact arithmetic (determinant, direct det, exact det, exact→f64 det, sign test, exact solve, exact→f64 solve) for D=2..5 plus a near-singular 3×3 group.
Exact arithmetic implementation
src/exact.rs
Replaced heap-backed Vec<Vec<BigRational>> with [[BigRational; D]; D] and [BigRational; D] for solver; gauss_solve now returns [BigRational; D]; updated validation and overflow payloads; removed D==0 special-case and adjusted tests.
Error enum & formatting
src/lib.rs
Changed LaError::NonFiniteNonFinite { row: Option<usize>, col: usize } and LaError::OverflowOverflow { index: Option<usize> }; updated Display and unit tests.
Factorization & solves: LU / LDLᵗ / Matrix det
src/lu.rs, src/ldlt.rs, src/matrix.rs
Updated non-finite error construction to include row context (row: Some(...) or row: None) and adjusted solve/forward-back-substitution non-finite checks; updated and added tests accordingly.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related issues

Possibly related PRs

Suggested labels

performance, rust, testing, api, breaking change

Poem

🐰 I swapped the Vecs for tidy rows,
Little arrays where stack-space grows.
Benchmarks hop to measure the gain,
Errors now point to row and lane.
Hop, hop — exact math springs again!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Out of Scope Changes check ❓ Inconclusive Error reporting enrichment (NonFinite row/col and Overflow index variants) was not in the original #62 issue scope but represents a necessary enhancement to error diagnostics during the refactoring process. Verify with the team whether comprehensive error reporting improvements during this refactoring are acceptable as in-scope enhancements, or should be deferred to a separate PR.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title "perf: stack-backed storage for exact arithmetic kernels" accurately describes the main performance optimization of replacing heap-allocated Vec with stack-backed arrays in exact arithmetic code.
Linked Issues check ✅ Passed The PR implementation fully addresses issue #62 requirements: replaces Vec<Vec> with [[BigRational; D]; D] in bareiss_det and gauss_solve, changes gauss_solve return type from Vec to array, eliminates redundant clones, adds benchmarks, and improves error reporting with row/column context for non-finite and overflow errors.
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 perf/62-stack-backed-exact-kernels

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 10, 2026

Codecov Report

❌ Patch coverage is 61.76471% with 26 lines in your changes missing coverage. Please review.
✅ Project coverage is 88.23%. Comparing base (c1495b0) to head (d4b1452).
⚠️ Report is 4 commits behind head on main.

Files with missing lines Patch % Lines
src/lu.rs 38.88% 11 Missing ⚠️
src/ldlt.rs 38.46% 8 Missing ⚠️
src/exact.rs 86.66% 4 Missing ⚠️
src/matrix.rs 57.14% 3 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main      #68      +/-   ##
==========================================
- Coverage   93.31%   88.23%   -5.09%     
==========================================
  Files           5        5              
  Lines         374      391      +17     
==========================================
- Hits          349      345       -4     
- Misses         25       46      +21     
Flag Coverage Δ
unittests 88.23% <61.76%> (-5.09%) ⬇️

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.

Caution

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

⚠️ Outside diff range comments (3)
src/lu.rs (1)

149-157: ⚠️ Potential issue | 🔴 Critical

Validate the back-substitution quotient before storing it.

Line 157 assigns sum / diag without checking the result. If that division overflows on the last row, solve_vec() returns Ok(...) with a non-finite component instead of LaError::NonFinite.

Suggested fix
             if diag.abs() <= self.tol {
                 return Err(LaError::Singular { pivot_col: i });
             }
 
-            x[i] = sum / diag;
+            let v = sum / diag;
+            if !v.is_finite() {
+                return Err(LaError::NonFinite { row: None, col: i });
+            }
+            x[i] = v;
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lu.rs` around lines 149 - 157, The back-substitution in solve_vec
currently assigns x[i] = sum / diag without validating the quotient; modify
solve_vec (the back-substitution loop where diag, sum, and x are used) to
compute the quotient let q = sum / diag, check q.is_finite() (or otherwise
validate), and if not return Err(LaError::NonFinite { row: None, col: i })
before assigning to x[i]; keep existing checks for diag and sum beforehand and
ensure the error uses pivot column i as in the other checks.
src/ldlt.rs (1)

68-75: ⚠️ Potential issue | 🟡 Minor

Report the updated column here, not the pivot column.

new_val is the value for f.rows[i][k], but Line 74 reports col: j. When this overflow trips off the pivot column, the new error metadata points at the wrong entry.

Suggested fix
                     if !new_val.is_finite() {
                         return Err(LaError::NonFinite {
                             row: Some(i),
-                            col: j,
+                            col: k,
                         });
                     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/ldlt.rs` around lines 68 - 75, The NonFinite error is reporting the pivot
column j instead of the updated column k where new_val is assigned; update the
error metadata in the block inside the loop that computes new_val so
LaError::NonFinite uses col: k (and row: Some(i) stays the same) — locate the
loop in ldlt.rs that computes new_val (uses variables i, j, k, l_k, l_i_d and
f.rows[i][k]) and change the error construction to reference k instead of j.
src/matrix.rs (1)

294-300: ⚠️ Potential issue | 🟠 Major

Preserve matrix coordinates in the direct-det non-finite path.

Line 299 currently collapses every det_direct() failure into NonFinite { row: None, col: 0 }. For D ≤ 4, a NaN/∞ input matrix now loses its (row, col) location, while the D ≥ 5 LU path still reports the offending matrix entry. That makes Matrix::det’s public error shape depend on dimension.

Suggested fix
 pub fn det(self, tol: f64) -> Result<f64, LaError> {
     if let Some(d) = self.det_direct() {
-        return if d.is_finite() {
-            Ok(d)
-        } else {
-            Err(LaError::NonFinite { row: None, col: 0 })
-        };
+        if d.is_finite() {
+            return Ok(d);
+        }
+
+        for r in 0..D {
+            for c in 0..D {
+                if !self.rows[r][c].is_finite() {
+                    return Err(LaError::NonFinite {
+                        row: Some(r),
+                        col: c,
+                    });
+                }
+            }
+        }
+
+        return Err(LaError::NonFinite { row: None, col: 0 });
     }
     self.lu(tol).map(|lu| lu.det())
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/matrix.rs` around lines 294 - 300, The det method currently turns any
non-finite result from det_direct() into LaError::NonFinite { row: None, col: 0
}, losing the offending coordinate; instead, when det_direct() returns a
non-finite determinant, scan the matrix for the first non-finite entry and
return Err(LaError::NonFinite { row: Some(r), col: c }) so the error preserves
the (row,col); update the det function (the branch handling det_direct() in
Matrix::det) to perform this quick nested-loop check over the matrix elements
and populate the LaError::NonFinite fields rather than using row: None, col: 0.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@src/ldlt.rs`:
- Around line 68-75: The NonFinite error is reporting the pivot column j instead
of the updated column k where new_val is assigned; update the error metadata in
the block inside the loop that computes new_val so LaError::NonFinite uses col:
k (and row: Some(i) stays the same) — locate the loop in ldlt.rs that computes
new_val (uses variables i, j, k, l_k, l_i_d and f.rows[i][k]) and change the
error construction to reference k instead of j.

In `@src/lu.rs`:
- Around line 149-157: The back-substitution in solve_vec currently assigns x[i]
= sum / diag without validating the quotient; modify solve_vec (the
back-substitution loop where diag, sum, and x are used) to compute the quotient
let q = sum / diag, check q.is_finite() (or otherwise validate), and if not
return Err(LaError::NonFinite { row: None, col: i }) before assigning to x[i];
keep existing checks for diag and sum beforehand and ensure the error uses pivot
column i as in the other checks.

In `@src/matrix.rs`:
- Around line 294-300: The det method currently turns any non-finite result from
det_direct() into LaError::NonFinite { row: None, col: 0 }, losing the offending
coordinate; instead, when det_direct() returns a non-finite determinant, scan
the matrix for the first non-finite entry and return Err(LaError::NonFinite {
row: Some(r), col: c }) so the error preserves the (row,col); update the det
function (the branch handling det_direct() in Matrix::det) to perform this quick
nested-loop check over the matrix elements and populate the LaError::NonFinite
fields rather than using row: None, col: 0.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 3e18022f-e746-450e-9a0a-a2793cc6d318

📥 Commits

Reviewing files that changed from the base of the PR and between c1495b0 and b64ac60.

📒 Files selected for processing (7)
  • Cargo.toml
  • benches/exact.rs
  • src/exact.rs
  • src/ldlt.rs
  • src/lib.rs
  • src/lu.rs
  • src/matrix.rs

acgetchell and others added 2 commits April 10, 2026 15:35
Refine error reporting by ensuring correct column indices in LDLT
decomposition and LU back-substitution. Add missing finiteness checks to
LU solve paths and update determinant calculations to provide specific
row/column coordinates for non-finite inputs.

Refs: #62
@acgetchell acgetchell self-assigned this Apr 10, 2026
@acgetchell acgetchell added this to the v0.4.0 milestone Apr 10, 2026
@acgetchell acgetchell merged commit 0771fdc into main Apr 10, 2026
9 checks passed
@acgetchell acgetchell deleted the perf/62-stack-backed-exact-kernels branch April 10, 2026 23:22
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: stack-backed storage for exact arithmetic kernels

1 participant