diff --git a/.coderabbit.yaml b/.coderabbit.yaml new file mode 100644 index 0000000..cf4fc40 --- /dev/null +++ b/.coderabbit.yaml @@ -0,0 +1,4 @@ +# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json +--- +reviews: + request_changes_workflow: true diff --git a/.github/workflows/rust-clippy.yml b/.github/workflows/rust-clippy.yml index 2152fb2..051e9a6 100644 --- a/.github/workflows/rust-clippy.yml +++ b/.github/workflows/rust-clippy.yml @@ -56,12 +56,12 @@ jobs: - name: Run clippy with SARIF output run: | set -euo pipefail + # Lint levels are owned by Cargo.toml. cargo clippy \ --workspace \ --all-targets \ --all-features \ - --message-format=json \ - -- -W clippy::pedantic -W clippy::nursery -W clippy::cargo | \ + --message-format=json | \ clippy-sarif | \ tee rust-clippy-results.sarif | \ sarif-fmt diff --git a/AGENTS.md b/AGENTS.md index d82b32c..434b138 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -133,10 +133,10 @@ invariant over the convenient edit. ### Testing mirrors the principles - Unit tests cover known values, error paths, and dimension-generic + correctness across D=2..=5 (see **Dimension Coverage** below). - Error-path tests match the exact variant, typed reason/origin/location, and structured fields; do not replace an unexpected error with a numeric sentinel or assert only `is_err()`. - correctness across D=2..=5 (see **Dimension Coverage** below). - Proptests under `tests/proptest_*.rs` cover algebraic invariants (round-trip, residual, sign agreement) — not just "does it not panic". - Adversarial inputs (near-singular, large-entry, Hilbert-style diff --git a/Cargo.toml b/Cargo.toml index 3567a6f..fcbf98b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -96,5 +96,8 @@ bare_urls = "deny" broken_intra_doc_links = "deny" [lints.clippy] +cargo = { level = "warn", priority = -1 } extra_unused_type_parameters = "warn" +nursery = { level = "warn", priority = -1 } pedantic = { level = "warn", priority = -1 } +redundant_pub_crate = { level = "allow", priority = 0 } diff --git a/benches/common/bench_utils.rs b/benches/common/bench_utils.rs new file mode 100644 index 0000000..79d689c --- /dev/null +++ b/benches/common/bench_utils.rs @@ -0,0 +1,64 @@ +#![forbid(unsafe_code)] + +//! Shared helpers for benchmark operations that cannot recover from failure. + +use std::fmt::Display; + +/// Convert a fallible benchmark operation into its successful value or abort. +pub(crate) trait OrAbort { + /// Successful value produced by the operation. + type Output; + + /// Return the successful value or panic with the named operation. + /// + /// # Panics + /// + /// Panics when the benchmark operation contains an error or no value. + fn or_abort(self, operation: &str) -> Self::Output; +} + +impl OrAbort for Result { + type Output = T; + + fn or_abort(self, operation: &str) -> Self::Output { + match self { + Ok(value) => value, + Err(err) => panic!("{operation} failed: {err}"), + } + } +} + +impl OrAbort for Option { + type Output = T; + + fn or_abort(self, operation: &str) -> Self::Output { + self.unwrap_or_else(|| panic!("{operation} returned no result")) + } +} + +#[cfg(test)] +mod tests { + #[test] + fn returns_result_and_option_values() { + assert_eq!( + as super::OrAbort>::or_abort(Ok(7), "successful result"), + 7 + ); + assert_eq!( + as super::OrAbort>::or_abort(Some(11), "present option"), + 11 + ); + } + + #[test] + #[should_panic(expected = "fallible setup failed: fixture error")] + fn result_error_preserves_context_and_error() { + as super::OrAbort>::or_abort(Err("fixture error"), "fallible setup"); + } + + #[test] + #[should_panic(expected = "optional setup returned no result")] + fn missing_option_preserves_context() { + as super::OrAbort>::or_abort(None, "optional setup"); + } +} diff --git a/benches/common/exact.rs b/benches/common/exact.rs index 0cfca41..a21b3f3 100644 --- a/benches/common/exact.rs +++ b/benches/common/exact.rs @@ -12,6 +12,8 @@ use num_bigint::BigInt; use num_rational::BigRational; use num_traits::{FromPrimitive, Signed, ToPrimitive, Zero}; +use crate::bench_utils::OrAbort; + /// Number of matrices in each deterministic random benchmark corpus. pub const RANDOM_INPUT_ARRAY_LEN: usize = 50; /// Stable global seed used to derive one random corpus per dimension. @@ -147,14 +149,6 @@ impl ValidatedExactInput { } } -/// Return a successful fixture-construction result or panic with context. -fn require_ok(result: Result, operation: &str) -> T { - match result { - Ok(value) => value, - Err(err) => panic!("{operation} failed: {err}"), - } -} - /// Return one matrix entry through the bounds-checked API shared by current and /// v0.4.3 releases. fn stored_matrix_entry(matrix: &Matrix, row: usize, col: usize) -> f64 { @@ -172,10 +166,9 @@ fn checked_det_sign(matrix: &Matrix) -> i8 { #[cfg(la_stack_v0_4_3_api)] fn checked_det_sign(matrix: &Matrix) -> i8 { - require_ok( - matrix.det_sign_exact(), - "exact determinant sign oracle check", - ) + matrix + .det_sign_exact() + .or_abort("exact determinant sign oracle check") } /// Return a deterministic, strictly diagonally-dominant matrix entry. @@ -223,11 +216,11 @@ pub fn make_vector_array() -> [f64; D] { /// Derive a stable per-dimension seed from the global random benchmark seed. fn random_seed_for_dim() -> u64 { let mut seed = - 0xC0DE_CAFE_D15C_A11Au64 ^ require_ok(u64::try_from(D), "dimension seed conversion"); + 0xC0DE_CAFE_D15C_A11Au64 ^ u64::try_from(D).or_abort("dimension seed conversion"); for (i, byte) in RANDOM_SEED.iter().copied().enumerate() { - let shift = require_ok(u32::try_from((i % 8) * 8), "seed shift conversion"); + let shift = u32::try_from((i % 8) * 8).or_abort("seed shift conversion"); seed ^= u64::from(byte) << shift; - seed = seed.rotate_left(7) ^ require_ok(u64::try_from(i), "seed index conversion"); + seed = seed.rotate_left(7) ^ u64::try_from(i).or_abort("seed index conversion"); } seed } @@ -235,7 +228,7 @@ fn random_seed_for_dim() -> u64 { /// Build a fixed random corpus of finite, strictly diagonally-dominant inputs. pub fn make_random_input_corpus() -> [ExactInput; RANDOM_INPUT_ARRAY_LEN] { let mut rng = SplitMix64::new(random_seed_for_dim::()); - let entry_range = require_ok(I16Range::try_new(-10, 10), "random integer range"); + let entry_range = I16Range::try_new(-10, 10).or_abort("random integer range"); array::from_fn(|_| { let mut rows = [[0.0; D]; D]; let mut diag = [0_i16; D]; @@ -251,7 +244,7 @@ pub fn make_random_input_corpus() -> [ExactInput; RANDOM_INPU } let shift = - f64::from(require_ok(u8::try_from(D), "dimension shift conversion")).mul_add(10.0, 1.0); + f64::from(u8::try_from(D).or_abort("dimension shift conversion")).mul_add(10.0, 1.0); for (i, row) in rows.iter_mut().enumerate() { row[i] = if diag[i] >= 0 { f64::from(diag[i]) + shift @@ -263,11 +256,8 @@ pub fn make_random_input_corpus() -> [ExactInput; RANDOM_INPU let rhs = from_fn(|_| f64::from(rng.next_i16(entry_range))); ExactInput { - matrix: require_ok( - Matrix::::try_from_rows(rows), - "random matrix construction", - ), - rhs: require_ok(Vector::::try_new(rhs), "random RHS vector construction"), + matrix: Matrix::::try_from_rows(rows).or_abort("random matrix construction"), + rhs: Vector::::try_new(rhs).or_abort("random RHS vector construction"), } }) } @@ -276,18 +266,14 @@ pub fn make_random_input_corpus() -> [ExactInput; RANDOM_INPU pub fn near_singular_3x3_input() -> ExactInput<3> { let perturbation = f64::from_bits(0x3CD0_0000_0000_0000); // 2^-50 ExactInput { - matrix: require_ok( - Matrix::<3>::try_from_rows([ - [1.0 + perturbation, 2.0, 3.0], - [4.0, 5.0, 6.0], - [7.0, 8.0, 9.0], - ]), - "near-singular matrix construction", - ), - rhs: require_ok( - Vector::<3>::try_new([1.0, 2.0, 3.0]), - "near-singular RHS vector construction", - ), + matrix: Matrix::<3>::try_from_rows([ + [1.0 + perturbation, 2.0, 3.0], + [4.0, 5.0, 6.0], + [7.0, 8.0, 9.0], + ]) + .or_abort("near-singular matrix construction"), + rhs: Vector::<3>::try_new([1.0, 2.0, 3.0]) + .or_abort("near-singular RHS vector construction"), } } @@ -295,14 +281,9 @@ pub fn near_singular_3x3_input() -> ExactInput<3> { pub fn large_entries_3x3_input() -> ExactInput<3> { let big = f64::MAX / 2.0; ExactInput { - matrix: require_ok( - Matrix::<3>::try_from_rows([[big, 1.0, 1.0], [1.0, big, 1.0], [1.0, 1.0, big]]), - "large-entry matrix construction", - ), - rhs: require_ok( - Vector::<3>::try_new([1.0, 1.0, 1.0]), - "large-entry RHS vector construction", - ), + matrix: Matrix::<3>::try_from_rows([[big, 1.0, 1.0], [1.0, big, 1.0], [1.0, 1.0, big]]) + .or_abort("large-entry matrix construction"), + rhs: Vector::<3>::try_new([1.0, 1.0, 1.0]).or_abort("large-entry RHS vector construction"), } } @@ -314,14 +295,8 @@ pub fn large_entries_3x3_input() -> ExactInput<3> { pub fn hilbert_input() -> ExactInput { let rows = from_fn(|r| from_fn(|c| 1.0 / ((r + c + 1) as f64))); ExactInput { - matrix: require_ok( - Matrix::::try_from_rows(rows), - "Hilbert matrix construction", - ), - rhs: require_ok( - Vector::::try_new([1.0; D]), - "Hilbert RHS vector construction", - ), + matrix: Matrix::::try_from_rows(rows).or_abort("Hilbert matrix construction"), + rhs: Vector::::try_new([1.0; D]).or_abort("Hilbert RHS vector construction"), } } @@ -528,13 +503,16 @@ fn assert_approximate_determinant(actual: f64, exact: &BigRational, operation: & /// dimension, or disagrees with the independent exact oracle. pub fn validate_f64_determinant_benchmarks(input: &ValidatedExactInput) { let exact = determinant_leibniz(input.matrix()); - let determinant = require_ok(input.matrix().det(), "f64 determinant oracle check"); + let determinant = input + .matrix() + .det() + .or_abort("f64 determinant oracle check"); assert_approximate_determinant(determinant, &exact, "f64 determinant"); - let direct = require_ok( - input.matrix().det_direct(), - "direct f64 determinant oracle check", - ); + let direct = input + .matrix() + .det_direct() + .or_abort("direct f64 determinant oracle check"); if D <= 4 { let Some(direct) = direct else { panic!("det_direct must support benchmark dimension {D}"); @@ -543,10 +521,10 @@ pub fn validate_f64_determinant_benchmarks(input: &ValidatedExac #[cfg(not(la_stack_v0_4_3_api))] { - let estimate = require_ok( - input.matrix().det_direct_with_errbound(), - "combined direct determinant oracle check", - ); + let estimate = input + .matrix() + .det_direct_with_errbound() + .or_abort("combined direct determinant oracle check"); let Some(estimate) = estimate else { panic!("the baseline fixture must have a certified D={D} determinant bound"); }; @@ -563,11 +541,11 @@ pub fn validate_f64_determinant_benchmarks(input: &ValidatedExac #[cfg(not(la_stack_v0_4_3_api))] assert!( - require_ok( - input.matrix().det_direct_with_errbound(), - "combined direct determinant scope check", - ) - .is_none(), + input + .matrix() + .det_direct_with_errbound() + .or_abort("combined direct determinant scope check") + .is_none(), "combined direct determinant unexpectedly supports D={D}", ); } @@ -599,7 +577,10 @@ fn assert_exact_residual(input: &ExactInput, solution: &[BigR pub fn validate_exact_fixture(input: ExactInput) -> ValidatedExactInput { let determinant = determinant_leibniz(&input.matrix); assert_eq!( - require_ok(input.matrix.det_exact(), "exact determinant oracle check"), + input + .matrix + .det_exact() + .or_abort("exact determinant oracle check"), determinant ); assert_eq!( @@ -609,10 +590,10 @@ pub fn validate_exact_fixture(input: ExactInput) -> Validated assert_strict_scalar(input.matrix.det_exact_f64(), &determinant, None); assert_rounded_scalar(input.matrix.det_exact_rounded_f64(), &determinant); - let solution = require_ok( - input.matrix.solve_exact(input.rhs), - "exact solve oracle check", - ); + let solution = input + .matrix + .solve_exact(input.rhs) + .or_abort("exact solve oracle check"); assert_exact_residual(&input, &solution); let strict_solution = input.matrix.solve_exact_f64(input.rhs); diff --git a/benches/exact.rs b/benches/exact.rs index 47956d3..3f71646 100644 --- a/benches/exact.rs +++ b/benches/exact.rs @@ -26,30 +26,24 @@ //! the full `Result` path, including valid `Err(Unrepresentable)` outcomes for //! inputs whose exact answer cannot be represented as finite binary64. -use std::fmt::Display; use std::hint::black_box; use criterion::{BenchmarkGroup, Criterion, Throughput, measurement::WallTime}; use la_stack::{Matrix, Vector}; +#[path = "common/bench_utils.rs"] +mod bench_utils; #[path = "common/exact.rs"] pub mod exact_bench; +use bench_utils::OrAbort; use exact_bench::{ ExactInput, RANDOM_INPUT_ARRAY_LEN, ValidatedExactInput, hilbert_input, large_entries_3x3_input, make_matrix_rows, make_random_input_corpus, make_vector_array, near_singular_3x3_input, validate_exact_fixture, validate_f64_determinant_benchmarks, }; -/// Return a successful benchmark operation result or panic with the named operation. -fn require_ok(result: Result, operation: &str) -> T { - match result { - Ok(value) => value, - Err(err) => panic!("{operation} failed: {err}"), - } -} - /// Exact operation measured by a benchmark group. #[derive(Clone, Copy)] enum ExactOperation { @@ -103,7 +97,9 @@ fn run_exact_operation(operation: ExactOperation, input: &Valida let _ = black_box(sign); } ExactOperation::DetExact => { - let det = require_ok(black_box(input.matrix()).det_exact(), "exact determinant"); + let det = black_box(input.matrix()) + .det_exact() + .or_abort("exact determinant"); black_box(det); } ExactOperation::DetExactF64Result => { @@ -111,17 +107,15 @@ fn run_exact_operation(operation: ExactOperation, input: &Valida let _ = black_box(det); } ExactOperation::DetExactRoundedF64 => { - let det = require_ok( - black_box(input.matrix()).det_exact_rounded_f64(), - "exact determinant rounded to f64", - ); + let det = black_box(input.matrix()) + .det_exact_rounded_f64() + .or_abort("exact determinant rounded to f64"); let _ = black_box(det); } ExactOperation::SolveExact => { - let x = require_ok( - black_box(input.matrix()).solve_exact(black_box(input.rhs())), - "exact linear solve", - ); + let x = black_box(input.matrix()) + .solve_exact(black_box(input.rhs())) + .or_abort("exact linear solve"); let _ = black_box(x); } ExactOperation::SolveExactF64Result => { @@ -129,10 +123,9 @@ fn run_exact_operation(operation: ExactOperation, input: &Valida let _ = black_box(x); } ExactOperation::SolveExactRoundedF64 => { - let x = require_ok( - black_box(input.matrix()).solve_exact_rounded_f64(black_box(input.rhs())), - "exact linear solve rounded to f64", - ); + let x = black_box(input.matrix()) + .solve_exact_rounded_f64(black_box(input.rhs())) + .or_abort("exact linear solve rounded to f64"); let _ = black_box(x); } } @@ -187,15 +180,18 @@ fn bench_det_direct( group: &mut BenchmarkGroup<'_, WallTime>, input: &ValidatedExactInput, ) { - let Some(_) = require_ok(input.matrix().det_direct(), "direct determinant setup") else { + let Some(_) = input + .matrix() + .det_direct() + .or_abort("direct determinant setup") + else { panic!("det_direct must support this benchmark dimension"); }; group.bench_function("det_direct", |bencher| { bencher.iter(|| { - let det = require_ok( - black_box(input.matrix()).det_direct(), - "direct f64 determinant", - ); + let det = black_box(input.matrix()) + .det_direct() + .or_abort("direct f64 determinant"); let Some(det) = det else { panic!("det_direct support changed after benchmark setup"); }; @@ -214,14 +210,10 @@ macro_rules! register_det_direct_benchmark { macro_rules! gen_exact_benches_for_dim { ($c:expr, $d:literal, $direct:ident) => {{ let input = validate_exact_fixture(ExactInput { - matrix: require_ok( - Matrix::<$d>::try_from_rows(make_matrix_rows::<$d>()), - "benchmark matrix construction", - ), - rhs: require_ok( - Vector::<$d>::try_new(make_vector_array::<$d>()), - "benchmark RHS vector construction", - ), + matrix: Matrix::<$d>::try_from_rows(make_matrix_rows::<$d>()) + .or_abort("benchmark matrix construction"), + rhs: Vector::<$d>::try_new(make_vector_array::<$d>()) + .or_abort("benchmark RHS vector construction"), }); validate_f64_determinant_benchmarks(&input); @@ -230,7 +222,7 @@ macro_rules! gen_exact_benches_for_dim { // === f64 baselines === group.bench_function("det", |bencher| { bencher.iter(|| { - let det = require_ok(black_box(input.matrix()).det(), "f64 determinant"); + let det = black_box(input.matrix()).det().or_abort("f64 determinant"); black_box(det); }); }); @@ -250,10 +242,8 @@ macro_rules! gen_random_corpus_benches_for_dim { let corpus = make_random_input_corpus::<$d>().map(validate_exact_fixture); let mut group = ($c).benchmark_group(concat!("exact_random_corpus_d", stringify!($d))); - let input_count = require_ok( - u64::try_from(corpus.len()), - "random corpus throughput conversion", - ); + let input_count = + u64::try_from(corpus.len()).or_abort("random corpus throughput conversion"); group.throughput(Throughput::Elements(input_count)); for &operation in CORPUS_AND_EXTREME_OPERATIONS { diff --git a/benches/vs_linalg.rs b/benches/vs_linalg.rs index 8885a06..bc74007 100644 --- a/benches/vs_linalg.rs +++ b/benches/vs_linalg.rs @@ -9,7 +9,6 @@ //! - Determinant is benchmarked via LU on all sides (nalgebra uses closed-forms for 1×1/2×2/3×3). //! - Matrix infinity norm is the maximum absolute row sum on all sides. -use std::fmt::Display; use std::hint::black_box; use criterion::measurement::WallTime; @@ -21,39 +20,26 @@ use nalgebra::{Const, DimMin, SMatrix, SVector}; use la_stack::{DEFAULT_SINGULAR_TOL, Matrix, Vector}; +#[path = "common/bench_utils.rs"] +mod bench_utils; #[path = "common/vs_linalg.rs"] pub mod vs_linalg_common; +use bench_utils::OrAbort; use vs_linalg_common::{ PreparedFaerLuDet, faer_det_from_ldlt, la_stack_dot, la_stack_tolerance, make_balanced_dynamic_range_rows, make_ill_conditioned_matrix_rows, make_matrix_rows, make_pivoting_matrix_rows, make_vector_array, matrix_entry, nalgebra_inf_norm, vector_entry, }; -/// Return a successful benchmark operation result or panic with the named operation. -fn require_ok(result: Result, operation: &str) -> T { - match result { - Ok(value) => value, - Err(err) => panic!("{operation} failed: {err}"), - } -} - -/// Return a present third-party benchmark result or panic with the named operation. -fn require_some(value: Option, operation: &str) -> T { - value.unwrap_or_else(|| panic!("{operation} returned no result")) -} - /// Build the deterministic la-stack matrix shared by a benchmark family. fn la_matrix() -> Matrix { - require_ok( - Matrix::try_from_rows(make_matrix_rows()), - "la_stack matrix construction", - ) + Matrix::try_from_rows(make_matrix_rows()).or_abort("la_stack matrix construction") } /// Build a deterministic la-stack vector with the requested offset. fn la_vector(offset: f64, operation: &str) -> Vector { - require_ok(Vector::try_new(make_vector_array(offset)), operation) + Vector::try_new(make_vector_array(offset)).or_abort(operation) } /// Build the deterministic nalgebra matrix shared by a benchmark family. @@ -89,11 +75,10 @@ where bencher.iter_batched( || a, |a| { - let lu = require_ok( - black_box(a).lu(DEFAULT_SINGULAR_TOL), - "la_stack LU factorization", - ); - let det = require_ok(lu.det(), "la_stack LU determinant"); + let lu = black_box(a) + .lu(DEFAULT_SINGULAR_TOL) + .or_abort("la_stack LU factorization"); + let det = lu.det().or_abort("la_stack LU determinant"); black_box(det); }, BatchSize::SmallInput, @@ -128,7 +113,7 @@ where bencher.iter_batched( || a, |a| { - let det = require_ok(black_box(a).det(), "la_stack determinant"); + let det = black_box(a).det().or_abort("la_stack determinant"); black_box(det); }, BatchSize::SmallInput, @@ -149,10 +134,9 @@ where bencher.iter_batched( || a, |a| { - let lu = require_ok( - black_box(a).lu(DEFAULT_SINGULAR_TOL), - "la_stack LU factorization", - ); + let lu = black_box(a) + .lu(DEFAULT_SINGULAR_TOL) + .or_abort("la_stack LU factorization"); let _ = black_box(lu); }, BatchSize::SmallInput, @@ -185,10 +169,9 @@ where bencher.iter_batched( || a, |a| { - let ldlt = require_ok( - black_box(a).ldlt(DEFAULT_SINGULAR_TOL), - "la_stack LDLT factorization", - ); + let ldlt = black_box(a) + .ldlt(DEFAULT_SINGULAR_TOL) + .or_abort("la_stack LDLT factorization"); let _ = black_box(ldlt); }, BatchSize::SmallInput, @@ -199,8 +182,9 @@ where bencher.iter_batched( || na, |na| { - let chol = - require_some(black_box(na).cholesky(), "nalgebra Cholesky factorization"); + let chol = black_box(na) + .cholesky() + .or_abort("nalgebra Cholesky factorization"); black_box(chol); }, BatchSize::SmallInput, @@ -211,7 +195,9 @@ where bencher.iter_batched( || &fa, |fa| { - let ldlt = require_ok(black_box(fa).ldlt(Side::Lower), "faer LDLT factorization"); + let ldlt = black_box(fa) + .ldlt(Side::Lower) + .or_abort("faer LDLT factorization"); let _ = black_box(ldlt); }, BatchSize::SmallInput, @@ -235,11 +221,10 @@ where bencher.iter_batched( || (a, rhs), |(a, rhs)| { - let lu = require_ok( - black_box(a).lu(DEFAULT_SINGULAR_TOL), - "la_stack LU factorization", - ); - let x = require_ok(lu.solve(black_box(rhs)), "la_stack LU solve"); + let lu = black_box(a) + .lu(DEFAULT_SINGULAR_TOL) + .or_abort("la_stack LU factorization"); + let x = lu.solve(black_box(rhs)).or_abort("la_stack LU solve"); let _ = black_box(x); }, BatchSize::SmallInput, @@ -251,7 +236,7 @@ where || (na, nrhs), |(na, nrhs)| { let lu = black_box(na).lu(); - let x = require_some(lu.solve(black_box(&nrhs)), "nalgebra LU solve"); + let x = lu.solve(black_box(&nrhs)).or_abort("nalgebra LU solve"); black_box(x); }, BatchSize::SmallInput, @@ -284,11 +269,10 @@ fn register_ldlt_solve_benchmarks(group: &mut BenchmarkGroup<'_, bencher.iter_batched( || (a, rhs), |(a, rhs)| { - let ldlt = require_ok( - black_box(a).ldlt(DEFAULT_SINGULAR_TOL), - "la_stack LDLT factorization", - ); - let x = require_ok(ldlt.solve(black_box(rhs)), "la_stack LDLT solve"); + let ldlt = black_box(a) + .ldlt(DEFAULT_SINGULAR_TOL) + .or_abort("la_stack LDLT factorization"); + let x = ldlt.solve(black_box(rhs)).or_abort("la_stack LDLT solve"); let _ = black_box(x); }, BatchSize::SmallInput, @@ -299,8 +283,9 @@ fn register_ldlt_solve_benchmarks(group: &mut BenchmarkGroup<'_, bencher.iter_batched( || (na, nrhs), |(na, nrhs)| { - let chol = - require_some(black_box(na).cholesky(), "nalgebra Cholesky factorization"); + let chol = black_box(na) + .cholesky() + .or_abort("nalgebra Cholesky factorization"); let x = chol.solve(black_box(&nrhs)); black_box(x); }, @@ -312,7 +297,9 @@ fn register_ldlt_solve_benchmarks(group: &mut BenchmarkGroup<'_, bencher.iter_batched( || (&fa, &frhs), |(fa, rhs)| { - let ldlt = require_ok(black_box(fa).ldlt(Side::Lower), "faer LDLT factorization"); + let ldlt = black_box(fa) + .ldlt(Side::Lower) + .or_abort("faer LDLT factorization"); let x = ldlt.solve(black_box(rhs)); black_box(x); }, @@ -333,26 +320,26 @@ fn register_precomputed_lu_solve_benchmarks( let nrhs = nalgebra_vector::(0.0); let fa = faer_matrix::(); let frhs = faer_vector::(0.0); - let a_lu = require_ok(a.lu(DEFAULT_SINGULAR_TOL), "precomputed la_stack LU"); + let a_lu = a + .lu(DEFAULT_SINGULAR_TOL) + .or_abort("precomputed la_stack LU"); let na_lu = na.lu(); let fa_lu = fa.partial_piv_lu(); group.bench_function("la_stack_solve_from_lu", |bencher| { bencher.iter(|| { - let x = require_ok( - black_box(&a_lu).solve(black_box(rhs)), - "precomputed la_stack LU solve", - ); + let x = black_box(&a_lu) + .solve(black_box(rhs)) + .or_abort("precomputed la_stack LU solve"); let _ = black_box(x); }); }); group.bench_function("nalgebra_solve_from_lu", |bencher| { bencher.iter(|| { - let x = require_some( - black_box(&na_lu).solve(black_box(&nrhs)), - "precomputed nalgebra LU solve", - ); + let x = black_box(&na_lu) + .solve(black_box(&nrhs)) + .or_abort("precomputed nalgebra LU solve"); black_box(x); }); }); @@ -375,16 +362,17 @@ fn register_precomputed_ldlt_solve_benchmarks( let nrhs = nalgebra_vector::(0.0); let fa = faer_matrix::(); let frhs = faer_vector::(0.0); - let a_ldlt = require_ok(a.ldlt(DEFAULT_SINGULAR_TOL), "precomputed la_stack LDLT"); - let na_cholesky = require_some(na.cholesky(), "precomputed nalgebra Cholesky"); - let fa_ldlt = require_ok(fa.ldlt(Side::Lower), "precomputed faer LDLT"); + let a_ldlt = a + .ldlt(DEFAULT_SINGULAR_TOL) + .or_abort("precomputed la_stack LDLT"); + let na_cholesky = na.cholesky().or_abort("precomputed nalgebra Cholesky"); + let fa_ldlt = fa.ldlt(Side::Lower).or_abort("precomputed faer LDLT"); group.bench_function("la_stack_solve_from_ldlt", |bencher| { bencher.iter(|| { - let x = require_ok( - black_box(&a_ldlt).solve(black_box(rhs)), - "precomputed la_stack LDLT solve", - ); + let x = black_box(&a_ldlt) + .solve(black_box(rhs)) + .or_abort("precomputed la_stack LDLT solve"); let _ = black_box(x); }); }); @@ -413,17 +401,18 @@ fn register_precomputed_lu_determinant_benchmarks( let a = la_matrix::(); let na = nalgebra_matrix::(); let fa = faer_matrix::(); - let a_lu = require_ok(a.lu(DEFAULT_SINGULAR_TOL), "precomputed la_stack LU"); + let a_lu = a + .lu(DEFAULT_SINGULAR_TOL) + .or_abort("precomputed la_stack LU"); let na_lu = na.lu(); let fa_lu = fa.partial_piv_lu(); let fa_lu_det = PreparedFaerLuDet::new(&fa_lu); group.bench_function("la_stack_det_from_lu", |bencher| { bencher.iter(|| { - let det = require_ok( - black_box(&a_lu).det(), - "precomputed la_stack LU determinant", - ); + let det = black_box(&a_lu) + .det() + .or_abort("precomputed la_stack LU determinant"); black_box(det); }); }); @@ -450,16 +439,17 @@ fn register_precomputed_ldlt_determinant_benchmarks( let a = la_matrix::(); let na = nalgebra_matrix::(); let fa = faer_matrix::(); - let a_ldlt = require_ok(a.ldlt(DEFAULT_SINGULAR_TOL), "precomputed la_stack LDLT"); - let na_cholesky = require_some(na.cholesky(), "precomputed nalgebra Cholesky"); - let fa_ldlt = require_ok(fa.ldlt(Side::Lower), "precomputed faer LDLT"); + let a_ldlt = a + .ldlt(DEFAULT_SINGULAR_TOL) + .or_abort("precomputed la_stack LDLT"); + let na_cholesky = na.cholesky().or_abort("precomputed nalgebra Cholesky"); + let fa_ldlt = fa.ldlt(Side::Lower).or_abort("precomputed faer LDLT"); group.bench_function("la_stack_det_from_ldlt", |bencher| { bencher.iter(|| { - let det = require_ok( - black_box(&a_ldlt).det(), - "precomputed la_stack LDLT determinant", - ); + let det = black_box(&a_ldlt) + .det() + .or_abort("precomputed la_stack LDLT determinant"); black_box(det); }); }); @@ -490,7 +480,7 @@ fn register_vector_benchmarks(group: &mut BenchmarkGroup<'_, Wal group.bench_function("la_stack_dot", |bencher| { bencher.iter(|| { - let result = require_ok(la_stack_dot(black_box(&v1), black_box(&v2)), "la_stack dot"); + let result = la_stack_dot(black_box(&v1), black_box(&v2)).or_abort("la_stack dot"); black_box(result); }); }); @@ -516,7 +506,7 @@ fn register_vector_benchmarks(group: &mut BenchmarkGroup<'_, Wal group.bench_function("la_stack_norm2_sq", |bencher| { bencher.iter(|| { - let result = require_ok(black_box(&v1).norm2_sq(), "la_stack norm2_sq"); + let result = black_box(&v1).norm2_sq().or_abort("la_stack norm2_sq"); black_box(result); }); }); @@ -545,7 +535,7 @@ fn register_matrix_norm_benchmarks(group: &mut BenchmarkGroup<'_ group.bench_function("la_stack_inf_norm", |bencher| { bencher.iter(|| { - let result = require_ok(black_box(&a).inf_norm(), "la_stack inf_norm"); + let result = black_box(&a).inf_norm().or_abort("la_stack inf_norm"); black_box(result); }); }); @@ -579,29 +569,22 @@ fn register_matrix_norm_benchmarks(group: &mut BenchmarkGroup<'_ /// Register D=8 stress cases that exercise pivoting, conditioning, and scaled products. fn register_stress_benchmarks(group: &mut BenchmarkGroup<'_, WallTime>) { - let zero_tolerance = require_ok(la_stack_tolerance(0.0), "zero benchmark tolerance"); - let pivoting = require_ok( - Matrix::<8>::try_from_rows(make_pivoting_matrix_rows()), - "pivoting benchmark matrix construction", - ); - let ill_conditioned = require_ok( - Matrix::<8>::try_from_rows(make_ill_conditioned_matrix_rows()), - "ill-conditioned benchmark matrix construction", - ); + let zero_tolerance = la_stack_tolerance(0.0).or_abort("zero benchmark tolerance"); + let pivoting = Matrix::<8>::try_from_rows(make_pivoting_matrix_rows()) + .or_abort("pivoting benchmark matrix construction"); + let ill_conditioned = Matrix::<8>::try_from_rows(make_ill_conditioned_matrix_rows()) + .or_abort("ill-conditioned benchmark matrix construction"); #[cfg(not(la_stack_v0_4_3_api))] - let balanced = require_ok( - Matrix::<8>::try_from_rows(make_balanced_dynamic_range_rows()), - "balanced-range benchmark matrix construction", - ); + let balanced = Matrix::<8>::try_from_rows(make_balanced_dynamic_range_rows()) + .or_abort("balanced-range benchmark matrix construction"); group.bench_function("la_stack_lu_pivoting", |bencher| { bencher.iter_batched( || pivoting, |matrix| { - let lu = require_ok( - black_box(matrix).lu(zero_tolerance), - "pivoting LU factorization", - ); + let lu = black_box(matrix) + .lu(zero_tolerance) + .or_abort("pivoting LU factorization"); let _ = black_box(lu); }, BatchSize::SmallInput, @@ -612,10 +595,9 @@ fn register_stress_benchmarks(group: &mut BenchmarkGroup<'_, WallTime>) { bencher.iter_batched( || ill_conditioned, |matrix| { - let lu = require_ok( - black_box(matrix).lu(zero_tolerance), - "ill-conditioned LU factorization", - ); + let lu = black_box(matrix) + .lu(zero_tolerance) + .or_abort("ill-conditioned LU factorization"); let _ = black_box(lu); }, BatchSize::SmallInput, @@ -626,10 +608,9 @@ fn register_stress_benchmarks(group: &mut BenchmarkGroup<'_, WallTime>) { bencher.iter_batched( || ill_conditioned, |matrix| { - let ldlt = require_ok( - black_box(matrix).ldlt(zero_tolerance), - "ill-conditioned LDLT factorization", - ); + let ldlt = black_box(matrix) + .ldlt(zero_tolerance) + .or_abort("ill-conditioned LDLT factorization"); let _ = black_box(ldlt); }, BatchSize::SmallInput, @@ -637,23 +618,20 @@ fn register_stress_benchmarks(group: &mut BenchmarkGroup<'_, WallTime>) { }); #[cfg(not(la_stack_v0_4_3_api))] - let balanced_lu = require_ok( - balanced.lu(zero_tolerance), - "balanced-range LU factorization", - ); + let balanced_lu = balanced + .lu(zero_tolerance) + .or_abort("balanced-range LU factorization"); #[cfg(not(la_stack_v0_4_3_api))] - let balanced_ldlt = require_ok( - balanced.ldlt(zero_tolerance), - "balanced-range LDLT factorization", - ); + let balanced_ldlt = balanced + .ldlt(zero_tolerance) + .or_abort("balanced-range LDLT factorization"); #[cfg(not(la_stack_v0_4_3_api))] group.bench_function("la_stack_det_from_lu_balanced_range", |bencher| { bencher.iter(|| { - let det = require_ok( - black_box(&balanced_lu).det(), - "balanced-range LU determinant", - ); + let det = black_box(&balanced_lu) + .det() + .or_abort("balanced-range LU determinant"); black_box(det); }); }); @@ -661,10 +639,9 @@ fn register_stress_benchmarks(group: &mut BenchmarkGroup<'_, WallTime>) { #[cfg(not(la_stack_v0_4_3_api))] group.bench_function("la_stack_det_from_ldlt_balanced_range", |bencher| { bencher.iter(|| { - let det = require_ok( - black_box(&balanced_ldlt).det(), - "balanced-range LDLT determinant", - ); + let det = black_box(&balanced_ldlt) + .det() + .or_abort("balanced-range LDLT determinant"); black_box(det); }); }); diff --git a/clippy.toml b/clippy.toml index e6c93b4..6979f37 100644 --- a/clippy.toml +++ b/clippy.toml @@ -3,4 +3,4 @@ # Keep lint behavior aligned with Cargo.toml and rust-toolchain.toml. msrv = "1.96.0" -# Lint levels remain owned by Cargo.toml and the canonical just recipes. +# Lint levels are owned by Cargo.toml. diff --git a/justfile b/justfile index 3737f77..ebe763b 100644 --- a/justfile +++ b/justfile @@ -349,16 +349,16 @@ clean: rm -rf target/llvm-cov rm -rf coverage -# Code quality and formatting +# Code quality and formatting (lint levels are configured in Cargo.toml). clippy: clippy-all-targets clippy-all-targets: - cargo clippy --workspace --all-targets -- -D warnings -W clippy::pedantic -W clippy::nursery -W clippy::cargo - cargo clippy --workspace --all-targets --all-features -- -D warnings -W clippy::pedantic -W clippy::nursery -W clippy::cargo + cargo clippy --workspace --all-targets + cargo clippy --workspace --all-targets --all-features # Clippy for the "exact" feature (catches feature-gated lint issues) clippy-exact: - cargo clippy --features exact --all-targets -- -D warnings -W clippy::pedantic + cargo clippy --features exact --all-targets # Coverage (cargo-llvm-cov) # diff --git a/src/scaled_product.rs b/src/scaled_product.rs index 0ff67a4..65e2b76 100644 --- a/src/scaled_product.rs +++ b/src/scaled_product.rs @@ -1,8 +1,4 @@ #![forbid(unsafe_code)] -#![expect( - clippy::redundant_pub_crate, - reason = "the helper is shared by sibling modules through this private module" -)] //! Allocation-free scaled products for floating-point factor diagonals. diff --git a/tests/exact_bench_config.rs b/tests/exact_bench_config.rs index 4cf0f91..a1e419c 100644 --- a/tests/exact_bench_config.rs +++ b/tests/exact_bench_config.rs @@ -3,6 +3,8 @@ #![cfg(all(feature = "bench", feature = "exact"))] #![forbid(unsafe_code)] +#[path = "../benches/common/bench_utils.rs"] +mod bench_utils; #[path = "../benches/common/exact.rs"] pub mod exact_bench;