Skip to content

Commit f8d80a0

Browse files
committed
Changed: consolidate and expand const-evaluability tests via macros
Refactor manual const-evaluation tests in `Ldlt` and `Matrix` into macros to standardize coverage across matrix dimensions 2 through 5. This ensures all determinant and norm variants are fully exercised at compile time. Refs: #86
1 parent 0b98d3f commit f8d80a0

2 files changed

Lines changed: 123 additions & 89 deletions

File tree

src/ldlt.rs

Lines changed: 55 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -558,54 +558,63 @@ mod tests {
558558
// some toolchains; we therefore construct `Ldlt<D>` directly.
559559
// -----------------------------------------------------------------------
560560

561-
#[test]
562-
fn ldlt_det_const_eval_d2() {
563-
const DET: f64 = {
564-
// Diagonal D = [4.0, 0.25] ⇒ det = 1.0.
565-
let mut factors = Matrix::<2>::identity();
566-
factors.rows[0][0] = 4.0;
567-
factors.rows[1][1] = 0.25;
568-
let ldlt = Ldlt::<2> {
569-
factors,
570-
tol: DEFAULT_SINGULAR_TOL,
571-
};
572-
ldlt.det()
573-
};
574-
assert!((DET - 1.0).abs() <= 1e-12);
575-
}
576-
577-
#[test]
578-
fn ldlt_det_const_eval_d3() {
579-
const DET: f64 = {
580-
// Diagonal D = [2.0, 3.0, 5.0] ⇒ det = 30.0.
581-
let mut factors = Matrix::<3>::identity();
582-
factors.rows[0][0] = 2.0;
583-
factors.rows[1][1] = 3.0;
584-
factors.rows[2][2] = 5.0;
585-
let ldlt = Ldlt::<3> {
586-
factors,
587-
tol: DEFAULT_SINGULAR_TOL,
588-
};
589-
ldlt.det()
590-
};
591-
assert!((DET - 30.0).abs() <= 1e-12);
592-
}
561+
macro_rules! gen_ldlt_const_eval_tests {
562+
($d:literal) => {
563+
paste! {
564+
/// `Ldlt::det` must be fully const-evaluable. Setting
565+
/// `factors[0][0] = 2.0` and leaving the remaining identity
566+
/// diagonals at `1.0` gives `det = 2.0` for every `D ≥ 1`,
567+
/// exercising the multiply-accumulate loop at each dimension.
568+
#[test]
569+
fn [<ldlt_det_const_eval_ $d d>]() {
570+
const DET: f64 = {
571+
let mut factors = Matrix::<$d>::identity();
572+
factors.rows[0][0] = 2.0;
573+
let ldlt = Ldlt::<$d> {
574+
factors,
575+
tol: DEFAULT_SINGULAR_TOL,
576+
};
577+
ldlt.det()
578+
};
579+
assert!((DET - 2.0).abs() <= 1e-12);
580+
}
593581

594-
#[test]
595-
fn ldlt_solve_vec_const_eval_d2() {
596-
// Identity factors ⇒ solve_vec returns the RHS untouched.
597-
const X: [f64; 2] = {
598-
let ldlt = Ldlt::<2> {
599-
factors: Matrix::<2>::identity(),
600-
tol: DEFAULT_SINGULAR_TOL,
601-
};
602-
let b = Vector::<2>::new([1.0, 2.0]);
603-
match ldlt.solve_vec(b) {
604-
Ok(v) => v.into_array(),
605-
Err(_) => [0.0, 0.0],
582+
/// `Ldlt::solve_vec` must be fully const-evaluable. Identity
583+
/// factors with RHS `b = [1.0, 2.0, …, D]` round-trips `b`
584+
/// unchanged, exercising the full forward sub / diagonal solve
585+
/// / back sub pipeline inside a `const { … }` initializer.
586+
#[test]
587+
fn [<ldlt_solve_vec_const_eval_ $d d>]() {
588+
#[allow(clippy::cast_precision_loss)]
589+
const X: [f64; $d] = {
590+
let ldlt = Ldlt::<$d> {
591+
factors: Matrix::<$d>::identity(),
592+
tol: DEFAULT_SINGULAR_TOL,
593+
};
594+
let mut b_arr = [0.0f64; $d];
595+
let mut i = 0;
596+
while i < $d {
597+
b_arr[i] = i as f64 + 1.0;
598+
i += 1;
599+
}
600+
let b = Vector::<$d>::new(b_arr);
601+
match ldlt.solve_vec(b) {
602+
Ok(v) => v.into_array(),
603+
Err(_) => [0.0f64; $d],
604+
}
605+
};
606+
#[allow(clippy::cast_precision_loss)]
607+
for i in 0..$d {
608+
let expected = i as f64 + 1.0;
609+
assert!((X[i] - expected).abs() <= 1e-12);
610+
}
611+
}
606612
}
607613
};
608-
assert!((X[0] - 1.0).abs() <= 1e-12);
609-
assert!((X[1] - 2.0).abs() <= 1e-12);
610614
}
615+
616+
gen_ldlt_const_eval_tests!(2);
617+
gen_ldlt_const_eval_tests!(3);
618+
gen_ldlt_const_eval_tests!(4);
619+
gen_ldlt_const_eval_tests!(5);
611620
}

src/matrix.rs

Lines changed: 68 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -873,23 +873,36 @@ mod tests {
873873
);
874874
}
875875

876-
#[test]
877-
fn det_direct_const_eval_d2() {
878-
// Const evaluation proves the function is truly const fn.
879-
const DET: Option<f64> = {
880-
let m = Matrix::<2>::from_rows([[1.0, 0.0], [0.0, 1.0]]);
881-
m.det_direct()
876+
// === det_direct const-evaluability tests (D = 2..=5) ===
877+
//
878+
// Every dimension hits a distinct arm of the `match D { … }` body inside
879+
// `det_direct`, so exercising each at compile time is the tightest
880+
// const-fn proof available.
881+
882+
macro_rules! gen_det_direct_const_eval_tests {
883+
($d:literal) => {
884+
paste! {
885+
/// `Matrix::<D>::det_direct()` on the identity must const-evaluate
886+
/// to `Some(1.0)` for every closed-form dimension `D ∈ {1, 2, 3, 4}`.
887+
#[test]
888+
fn [<det_direct_const_eval_ $d d>]() {
889+
const DET: Option<f64> = Matrix::<$d>::identity().det_direct();
890+
assert_eq!(DET, Some(1.0));
891+
}
892+
}
882893
};
883-
assert_eq!(DET, Some(1.0));
884894
}
885895

896+
gen_det_direct_const_eval_tests!(2);
897+
gen_det_direct_const_eval_tests!(3);
898+
gen_det_direct_const_eval_tests!(4);
899+
886900
#[test]
887-
fn det_direct_const_eval_d3() {
888-
const DET: Option<f64> = {
889-
let m = Matrix::<3>::from_rows([[2.0, 0.0, 0.0], [0.0, 3.0, 0.0], [0.0, 0.0, 5.0]]);
890-
m.det_direct()
891-
};
892-
assert_eq!(DET, Some(30.0));
901+
fn det_direct_const_eval_d5_is_none() {
902+
// D ≥ 5 has no closed-form arm; `det_direct` returns `None`. Verify
903+
// that the wildcard arm is reachable in a `const { … }` context.
904+
const DET: Option<f64> = Matrix::<5>::identity().det_direct();
905+
assert_eq!(DET, None);
893906
}
894907

895908
// === det_errbound tests (no `exact` feature required) ===
@@ -909,46 +922,58 @@ mod tests {
909922
assert_eq!(Matrix::<5>::identity().det_errbound(), None);
910923
}
911924

912-
#[test]
913-
fn det_errbound_const_eval_d2() {
914-
// Const evaluation proves the function is truly const fn.
915-
const BOUND: Option<f64> = {
916-
let m = Matrix::<2>::from_rows([[1.0, 2.0], [3.0, 4.0]]);
917-
m.det_errbound()
918-
};
919-
assert!(BOUND.is_some());
920-
assert!(BOUND.unwrap() > 0.0);
921-
}
925+
// === det_errbound const-evaluability tests (D = 2..=5) ===
922926

923-
#[test]
924-
fn det_errbound_const_eval_d3() {
925-
const BOUND: Option<f64> = {
926-
let m = Matrix::<3>::from_rows([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [7.0, 8.0, 9.0]]);
927-
m.det_errbound()
927+
macro_rules! gen_det_errbound_const_eval_tests {
928+
($d:literal) => {
929+
paste! {
930+
/// `Matrix::<D>::det_errbound()` on the identity must const-evaluate
931+
/// to `Some(bound)` with `bound > 0` for every closed-form dimension
932+
/// `D ∈ {2, 3, 4}`. Each dimension hits a distinct arm of
933+
/// `det_errbound` with a dimension-specific permanent computation.
934+
#[test]
935+
fn [<det_errbound_const_eval_ $d d>]() {
936+
const BOUND: Option<f64> = Matrix::<$d>::identity().det_errbound();
937+
assert!(BOUND.is_some());
938+
assert!(BOUND.unwrap() > 0.0);
939+
}
940+
}
928941
};
929-
assert!(BOUND.is_some());
930-
assert!(BOUND.unwrap() > 0.0);
931942
}
932943

944+
gen_det_errbound_const_eval_tests!(2);
945+
gen_det_errbound_const_eval_tests!(3);
946+
gen_det_errbound_const_eval_tests!(4);
947+
933948
#[test]
934-
fn inf_norm_const_eval_d2() {
935-
// Maximum absolute row sum: row 0 = 3, row 1 = 7 ⇒ 7.
936-
const NORM: f64 = {
937-
let m = Matrix::<2>::from_rows([[1.0, -2.0], [3.0, 4.0]]);
938-
m.inf_norm()
939-
};
940-
assert!((NORM - 7.0).abs() <= 1e-12);
949+
fn det_errbound_const_eval_d5_is_none() {
950+
// D ≥ 5 has no fast-filter bound; `det_errbound` returns `None`.
951+
const BOUND: Option<f64> = Matrix::<5>::identity().det_errbound();
952+
assert_eq!(BOUND, None);
941953
}
942954

943-
#[test]
944-
fn inf_norm_const_eval_d3() {
945-
const NORM: f64 = {
946-
let m = Matrix::<3>::identity();
947-
m.inf_norm()
955+
// === inf_norm const-evaluability tests (D = 2..=5) ===
956+
957+
macro_rules! gen_inf_norm_const_eval_tests {
958+
($d:literal) => {
959+
paste! {
960+
/// `Matrix::<D>::inf_norm()` on the identity must const-evaluate
961+
/// to `1.0` for every `D ≥ 1` — each row has a single `1.0`
962+
/// entry, so the max absolute row sum is exactly `1.0`.
963+
#[test]
964+
fn [<inf_norm_const_eval_ $d d>]() {
965+
const NORM: f64 = Matrix::<$d>::identity().inf_norm();
966+
assert!((NORM - 1.0).abs() <= 1e-12);
967+
}
968+
}
948969
};
949-
assert!((NORM - 1.0).abs() <= 1e-12);
950970
}
951971

972+
gen_inf_norm_const_eval_tests!(2);
973+
gen_inf_norm_const_eval_tests!(3);
974+
gen_inf_norm_const_eval_tests!(4);
975+
gen_inf_norm_const_eval_tests!(5);
976+
952977
// === inf_norm NaN / Inf propagation (regression tests for #85) ===
953978

954979
macro_rules! gen_inf_norm_nonfinite_tests {

0 commit comments

Comments
 (0)