Skip to content

Commit 92ba403

Browse files
committed
feat!(api): enforce finite Matrix and Vector construction
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
1 parent eab56a6 commit 92ba403

27 files changed

Lines changed: 1290 additions & 825 deletions

AGENTS.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,9 @@ invariant over the convenient edit.
6363
- `Result<_, LaError>` for all fallible operations. Panics are reserved
6464
for debug-only precondition violations (e.g. LDLT symmetry check) and
6565
documented on the method.
66+
- Public APIs that return plain values must be genuinely infallible for all
67+
representable inputs. If callers can observe failure, return `Result` or
68+
`Option` instead of relying on `panic!`, `assert!`, `unwrap`, or `expect`.
6669
- Borrow by default (`&T`, `&[T]`); return borrowed views when possible.
6770
- Type and function names match textbook vocabulary (`Matrix`, `Vector`,
6871
`Lu`, `Ldlt`, `solve_vec`, `det`, `inf_norm`). Avoid Rust-ecosystem

README.md

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -79,15 +79,15 @@ use la_stack::prelude::*;
7979
fn main() -> Result<(), LaError> {
8080
// This system requires pivoting (a[0][0] = 0), so it's a good LU demo.
8181
// A = J - I: zeros on diagonal, ones elsewhere.
82-
let a = Matrix::<5>::from_rows([
82+
let a = Matrix::<5>::try_from_rows([
8383
[0.0, 1.0, 1.0, 1.0, 1.0],
8484
[1.0, 0.0, 1.0, 1.0, 1.0],
8585
[1.0, 1.0, 0.0, 1.0, 1.0],
8686
[1.0, 1.0, 1.0, 0.0, 1.0],
8787
[1.0, 1.0, 1.0, 1.0, 0.0],
88-
]);
88+
])?;
8989

90-
let b = Vector::<5>::new([14.0, 13.0, 12.0, 11.0, 10.0]);
90+
let b = Vector::<5>::try_new([14.0, 13.0, 12.0, 11.0, 10.0])?;
9191

9292
let lu = a.lu(DEFAULT_PIVOT_TOL)?;
9393
let x = lu.solve_vec(b)?.into_array();
@@ -112,13 +112,13 @@ use la_stack::prelude::*;
112112

113113
fn main() -> Result<(), LaError> {
114114
// This matrix is symmetric positive-definite (A = L*L^T) so LDLT works without pivoting.
115-
let a = Matrix::<5>::from_rows([
115+
let a = Matrix::<5>::try_from_rows([
116116
[1.0, 1.0, 0.0, 0.0, 0.0],
117117
[1.0, 2.0, 1.0, 0.0, 0.0],
118118
[0.0, 1.0, 2.0, 1.0, 0.0],
119119
[0.0, 0.0, 1.0, 2.0, 1.0],
120120
[0.0, 0.0, 0.0, 1.0, 2.0],
121-
]);
121+
])?;
122122

123123
let det = a.ldlt(DEFAULT_SINGULAR_TOL)?.det()?;
124124
assert!((det - 1.0).abs() <= 1e-12);
@@ -150,11 +150,14 @@ use la_stack::prelude::*;
150150

151151
// Evaluated entirely at compile time — no runtime cost.
152152
const DET: Result<Option<f64>, LaError> = {
153-
let m = Matrix::<3>::from_rows([
153+
let m = match Matrix::<3>::try_from_rows([
154154
[2.0, 0.0, 0.0],
155155
[0.0, 3.0, 0.0],
156156
[0.0, 0.0, 5.0],
157-
]);
157+
]) {
158+
Ok(matrix) => matrix,
159+
Err(_) => panic!("matrix entries must be finite"),
160+
};
158161
m.det_direct()
159162
};
160163
assert_eq!(DET, Ok(Some(30.0)));
@@ -196,19 +199,19 @@ use la_stack::prelude::*;
196199
197200
fn main() -> Result<(), LaError> {
198201
// Exact determinant
199-
let m = Matrix::<3>::from_rows([
202+
let m = Matrix::<3>::try_from_rows([
200203
[1.0, 2.0, 3.0],
201204
[4.0, 5.0, 6.0],
202205
[7.0, 8.0, 9.0],
203-
]);
206+
])?;
204207
assert_eq!(m.det_sign_exact()?, 0); // exactly singular
205208
206209
let det = m.det_exact()?;
207210
assert_eq!(det, BigRational::from_integer(0.into())); // exact zero
208211
209212
// Exact linear system solve
210-
let a = Matrix::<2>::from_rows([[1.0, 2.0], [3.0, 4.0]]);
211-
let b = Vector::<2>::new([5.0, 11.0]);
213+
let a = Matrix::<2>::try_from_rows([[1.0, 2.0], [3.0, 4.0]])?;
214+
let b = Vector::<2>::try_new([5.0, 11.0])?;
212215
let x = a.solve_exact_f64(b)?.into_array();
213216
assert!((x[0] - 1.0).abs() <= f64::EPSILON);
214217
assert!((x[1] - 2.0).abs() <= f64::EPSILON);

0 commit comments

Comments
 (0)