#![cfg(feature = "exact")]
use std::array::from_fn;
use pastey::paste;
use proptest::prelude::*;
use la_stack::prelude::*;
fn small_nonzero_f64() -> impl Strategy<Value = f64> {
prop_oneof![(-1000i16..=-1i16), (1i16..=1000i16)].prop_map(|x| f64::from(x) / 10.0)
}
fn small_nonzero_int_f64() -> impl Strategy<Value = f64> {
prop_oneof![(-5i32..=-1i32), (1i32..=5i32)].prop_map(f64::from)
}
fn small_int_f64() -> impl Strategy<Value = f64> {
(-10i32..=10i32).prop_map(f64::from)
}
fn bigrational_matvec<const D: usize>(a: &[[f64; D]; D], x: &[BigRational; D]) -> [BigRational; D] {
from_fn(|i| {
let mut sum = BigRational::from_integer(BigInt::from(0));
for (aij, xj) in a[i].iter().zip(x.iter()) {
let entry = BigRational::from_f64(*aij).expect("small int fits in BigRational");
sum += entry * xj;
}
sum
})
}
fn make_diagonally_dominant<const D: usize>(
offdiag: [[f64; D]; D],
diag: [f64; D],
) -> [[f64; D]; D] {
let mut rows = offdiag;
let shift = f64::from(u8::try_from(D).unwrap_or(u8::MAX)).mul_add(10.0, 1.0);
for i in 0..D {
rows[i][i] = if diag[i] >= 0.0 {
diag[i] + shift
} else {
diag[i] - shift
};
}
rows
}
macro_rules! gen_det_sign_exact_proptests {
($d:literal) => {
paste! {
proptest! {
#![proptest_config(ProptestConfig::with_cases(64))]
#[test]
fn [<det_sign_exact_agrees_with_det_for_diagonal_ $d d>](
diag in proptest::array::[<uniform $d>](small_nonzero_f64()),
) {
let mut rows = [[0.0f64; $d]; $d];
for i in 0..$d {
rows[i][i] = diag[i];
}
let m = Matrix::<$d>::from_rows(rows);
let exact_sign = m.det_sign_exact().unwrap();
let neg_count = diag.iter().filter(|&&x| x < 0.0).count();
let expected_sign: i8 = if neg_count % 2 == 0 { 1 } else { -1 };
prop_assert_eq!(exact_sign, expected_sign);
}
#[test]
fn [<det_sign_exact_agrees_with_det_signum_ $d d>](
diag in proptest::array::[<uniform $d>](small_nonzero_f64()),
) {
let mut rows = [[0.0f64; $d]; $d];
for i in 0..$d {
rows[i][i] = diag[i];
}
let m = Matrix::<$d>::from_rows(rows);
let exact_sign = m.det_sign_exact().unwrap();
let fp_det = m.det(DEFAULT_PIVOT_TOL).unwrap();
let fp_sign: i8 = if fp_det > 0.0 {
1
} else if fp_det < 0.0 {
-1
} else {
0
};
prop_assert_eq!(exact_sign, fp_sign);
}
}
}
};
}
gen_det_sign_exact_proptests!(2);
gen_det_sign_exact_proptests!(3);
gen_det_sign_exact_proptests!(4);
gen_det_sign_exact_proptests!(5);
macro_rules! gen_solve_exact_roundtrip_proptests {
($d:literal) => {
paste! {
proptest! {
#![proptest_config(ProptestConfig::with_cases(64))]
#[test]
fn [<solve_exact_integer_roundtrip_ $d d>](
offdiag in proptest::array::[<uniform $d>](
proptest::array::[<uniform $d>](small_int_f64()),
),
diag in proptest::array::[<uniform $d>](small_nonzero_int_f64()),
x0 in proptest::array::[<uniform $d>](small_int_f64()),
) {
let rows = make_diagonally_dominant::<$d>(offdiag, diag);
let a = Matrix::<$d>::from_rows(rows);
let mut b_arr = [0.0f64; $d];
for i in 0..$d {
let mut sum = 0.0f64;
for j in 0..$d {
sum += rows[i][j] * x0[j];
}
b_arr[i] = sum;
}
let b = Vector::<$d>::new(b_arr);
let x = a.solve_exact(b).expect("diagonally-dominant A is non-singular");
let expected: [BigRational; $d] = from_fn(|i| {
BigRational::from_f64(x0[i]).expect("small int fits in BigRational")
});
for i in 0..$d {
prop_assert_eq!(&x[i], &expected[i]);
}
}
}
}
};
}
gen_solve_exact_roundtrip_proptests!(2);
gen_solve_exact_roundtrip_proptests!(3);
gen_solve_exact_roundtrip_proptests!(4);
gen_solve_exact_roundtrip_proptests!(5);
macro_rules! gen_solve_exact_residual_proptests {
($d:literal) => {
paste! {
proptest! {
#![proptest_config(ProptestConfig::with_cases(32))]
#[test]
fn [<solve_exact_residual_ $d d>](
offdiag in proptest::array::[<uniform $d>](
proptest::array::[<uniform $d>](small_int_f64()),
),
diag in proptest::array::[<uniform $d>](small_nonzero_int_f64()),
b_arr in proptest::array::[<uniform $d>](small_int_f64()),
) {
let rows = make_diagonally_dominant::<$d>(offdiag, diag);
let a = Matrix::<$d>::from_rows(rows);
let b = Vector::<$d>::new(b_arr);
let x = a.solve_exact(b).expect("diagonally-dominant A is non-singular");
let ax = bigrational_matvec::<$d>(&rows, &x);
for i in 0..$d {
let b_rat = BigRational::from_f64(b_arr[i])
.expect("small int fits in BigRational");
prop_assert_eq!(&ax[i], &b_rat);
}
}
}
}
};
}
gen_solve_exact_residual_proptests!(2);
gen_solve_exact_residual_proptests!(3);
gen_solve_exact_residual_proptests!(4);
gen_solve_exact_residual_proptests!(5);
macro_rules! gen_det_sign_agrees_with_det_exact_proptests {
($d:literal) => {
paste! {
proptest! {
#![proptest_config(ProptestConfig::with_cases(64))]
#[test]
fn [<det_sign_exact_agrees_with_det_exact_ $d d>](
entries in proptest::array::[<uniform $d>](
proptest::array::[<uniform $d>](small_int_f64()),
),
) {
let m = Matrix::<$d>::from_rows(entries);
let sign = m.det_sign_exact().unwrap();
let det = m.det_exact().unwrap();
let expected: i8 = if det.is_positive() {
1
} else if det.is_negative() {
-1
} else {
0
};
prop_assert_eq!(sign, expected);
}
}
}
};
}
gen_det_sign_agrees_with_det_exact_proptests!(2);
gen_det_sign_agrees_with_det_exact_proptests!(3);
gen_det_sign_agrees_with_det_exact_proptests!(4);
gen_det_sign_agrees_with_det_exact_proptests!(5);
macro_rules! gen_det_sign_fast_filter_boundary_proptests {
($d:literal) => {
paste! {
proptest! {
#![proptest_config(ProptestConfig::with_cases(64))]
#[test]
fn [<det_sign_exact_agrees_with_det_direct_when_filter_conclusive_ $d d>](
entries in proptest::array::[<uniform $d>](
proptest::array::[<uniform $d>](small_int_f64()),
),
) {
let m = Matrix::<$d>::from_rows(entries);
let det = m.det_direct().expect("D<=4 has closed-form det_direct");
let bound = m.det_errbound().expect("D<=4 has a det_errbound");
let sign = m.det_sign_exact().unwrap();
if det.abs() > bound {
let direct_sign: i8 = if det > 0.0 {
1
} else if det < 0.0 {
-1
} else {
0
};
prop_assert_eq!(direct_sign, sign);
}
}
}
}
};
}
gen_det_sign_fast_filter_boundary_proptests!(2);
gen_det_sign_fast_filter_boundary_proptests!(3);
gen_det_sign_fast_filter_boundary_proptests!(4);