use core::hint::cold_path;
use core::mem::take;
use std::array::from_fn;
use num_bigint::{BigInt, Sign};
use num_rational::BigRational;
use num_traits::ToPrimitive;
use crate::LaError;
use crate::matrix::Matrix;
use crate::vector::Vector;
fn f64_decompose(x: f64) -> Option<(u64, i32, bool)> {
let bits = x.to_bits();
let biased_exp = ((bits >> 52) & 0x7FF) as i32;
let fraction = bits & 0x000F_FFFF_FFFF_FFFF;
if biased_exp == 0 && fraction == 0 {
return None;
}
assert!(biased_exp != 0x7FF, "non-finite f64 in exact conversion");
let (mantissa, raw_exp) = if biased_exp == 0 {
(fraction, -1074_i32)
} else {
((1u64 << 52) | fraction, biased_exp - 1075)
};
let tz = mantissa.trailing_zeros();
let mantissa = mantissa >> tz;
let exponent = raw_exp + tz.cast_signed();
let is_negative = bits >> 63 != 0;
Some((mantissa, exponent, is_negative))
}
fn bigint_exp_to_bigrational(mut value: BigInt, mut exp: i32) -> BigRational {
if value == BigInt::from(0) {
return BigRational::from_integer(BigInt::from(0));
}
if exp < 0
&& let Some(tz) = value.trailing_zeros()
{
let reduce = tz.min(u64::from((-exp).cast_unsigned()));
value >>= reduce;
exp += i32::try_from(reduce).expect("reduce ≤ -exp which fits in i32");
}
if exp >= 0 {
BigRational::new_raw(value << exp.cast_unsigned(), BigInt::from(1u32))
} else {
BigRational::new_raw(value, BigInt::from(1u32) << (-exp).cast_unsigned())
}
}
#[derive(Clone, Copy, Default)]
struct Component {
mantissa: u64,
exponent: i32,
is_negative: bool,
}
fn decompose_matrix<const D: usize>(m: &Matrix<D>) -> Result<([[Component; D]; D], i32), LaError> {
let mut components = [[Component::default(); D]; D];
let mut e_min = i32::MAX;
for (r, row) in m.rows.iter().enumerate() {
for (c, &entry) in row.iter().enumerate() {
if !entry.is_finite() {
cold_path();
return Err(LaError::non_finite_cell(r, c));
}
if let Some((mantissa, exponent, is_negative)) = f64_decompose(entry) {
components[r][c] = Component {
mantissa,
exponent,
is_negative,
};
e_min = e_min.min(exponent);
}
}
}
Ok((components, e_min))
}
fn decompose_vec<const D: usize>(v: &Vector<D>) -> Result<([Component; D], i32), LaError> {
let mut components = [Component::default(); D];
let mut e_min = i32::MAX;
for (i, &entry) in v.data.iter().enumerate() {
if !entry.is_finite() {
cold_path();
return Err(LaError::non_finite_at(i));
}
if let Some((mantissa, exponent, is_negative)) = f64_decompose(entry) {
components[i] = Component {
mantissa,
exponent,
is_negative,
};
e_min = e_min.min(exponent);
}
}
Ok((components, e_min))
}
#[inline]
fn component_to_bigint(c: Component, e_min: i32) -> BigInt {
if c.mantissa == 0 {
BigInt::from(0)
} else {
let v = BigInt::from(c.mantissa) << (c.exponent - e_min).cast_unsigned();
if c.is_negative { -v } else { v }
}
}
fn build_bigint_matrix<const D: usize>(
components: &[[Component; D]; D],
e_min: i32,
) -> [[BigInt; D]; D] {
from_fn(|r| from_fn(|c| component_to_bigint(components[r][c], e_min)))
}
fn build_bigint_vec<const D: usize>(components: &[Component; D], e_min: i32) -> [BigInt; D] {
from_fn(|i| component_to_bigint(components[i], e_min))
}
#[derive(Debug)]
enum BareissResult {
Upper { sign: i8 },
Singular { pivot_col: usize },
}
fn bareiss_forward_eliminate<const D: usize>(
a: &mut [[BigInt; D]; D],
mut rhs: Option<&mut [BigInt; D]>,
) -> BareissResult {
let zero = BigInt::from(0);
let mut prev_pivot = BigInt::from(1);
let mut sign: i8 = 1;
for k in 0..D {
if a[k][k] == zero {
let mut found = false;
for i in (k + 1)..D {
if a[i][k] != zero {
a.swap(k, i);
if let Some(r) = &mut rhs {
r.swap(k, i);
}
sign = -sign;
found = true;
break;
}
}
if !found {
cold_path();
return BareissResult::Singular { pivot_col: k };
}
}
for i in (k + 1)..D {
for j in (k + 1)..D {
a[i][j] = (&a[k][k] * &a[i][j] - &a[i][k] * &a[k][j]) / &prev_pivot;
}
if let Some(r) = &mut rhs {
r[i] = (&a[k][k] * &r[i] - &a[i][k] * &r[k]) / &prev_pivot;
}
a[i][k].clone_from(&zero);
}
prev_pivot.clone_from(&a[k][k]);
}
#[cfg(debug_assertions)]
#[allow(clippy::needless_range_loop)]
for k in 0..D {
assert_ne!(a[k][k], zero, "pivot at ({k}, {k}) must be non-zero");
for i in (k + 1)..D {
assert_eq!(a[i][k], zero, "sub-diagonal at ({i}, {k}) must be zero");
}
}
BareissResult::Upper { sign }
}
fn bareiss_det_int<const D: usize>(m: &Matrix<D>) -> Result<(BigInt, i32), LaError> {
if D == 0 {
return Ok((BigInt::from(1), 0));
}
let (components, e_min) = decompose_matrix(m)?;
if e_min == i32::MAX {
return Ok((BigInt::from(0), 0));
}
let mut a = build_bigint_matrix(&components, e_min);
let sign = match bareiss_forward_eliminate(&mut a, None) {
BareissResult::Upper { sign } => sign,
BareissResult::Singular { .. } => {
cold_path();
return Ok((BigInt::from(0), 0));
}
};
let det_int = if sign < 0 {
-&a[D - 1][D - 1]
} else {
a[D - 1][D - 1].clone()
};
let d_i32 = i32::try_from(D).expect("dimension exceeds i32");
let total_exp = e_min
.checked_mul(d_i32)
.expect("exponent overflow in bareiss_det_int");
Ok((det_int, total_exp))
}
fn bareiss_det<const D: usize>(m: &Matrix<D>) -> Result<BigRational, LaError> {
let (det_int, total_exp) = bareiss_det_int(m)?;
Ok(bigint_exp_to_bigrational(det_int, total_exp))
}
fn gauss_solve<const D: usize>(m: &Matrix<D>, b: &Vector<D>) -> Result<[BigRational; D], LaError> {
let (m_components, m_e_min) = decompose_matrix(m)?;
let (b_components, b_e_min) = decompose_vec(b)?;
let mut e_min = m_e_min.min(b_e_min);
if e_min == i32::MAX {
e_min = 0;
}
let mut a = build_bigint_matrix(&m_components, e_min);
let mut rhs = build_bigint_vec(&b_components, e_min);
if let BareissResult::Singular { pivot_col } = bareiss_forward_eliminate(&mut a, Some(&mut rhs))
{
cold_path();
return Err(LaError::Singular { pivot_col });
}
let mut x: [BigRational; D] = from_fn(|_| BigRational::from_integer(BigInt::from(0)));
for i in (0..D).rev() {
let mut sum = BigRational::from_integer(take(&mut rhs[i]));
for j in (i + 1)..D {
let a_ij = BigRational::from_integer(take(&mut a[i][j]));
sum -= &a_ij * &x[j];
}
let a_ii = BigRational::from_integer(take(&mut a[i][i]));
x[i] = sum / &a_ii;
}
Ok(x)
}
impl<const D: usize> Matrix<D> {
#[inline]
pub fn det_exact(&self) -> Result<BigRational, LaError> {
bareiss_det(self)
}
#[inline]
pub fn det_exact_f64(&self) -> Result<f64, LaError> {
let exact = self.det_exact()?;
let val = exact.to_f64().unwrap_or(f64::INFINITY);
if val.is_finite() {
Ok(val)
} else {
cold_path();
Err(LaError::Overflow { index: None })
}
}
#[inline]
pub fn solve_exact(&self, b: Vector<D>) -> Result<[BigRational; D], LaError> {
gauss_solve(self, &b)
}
#[inline]
pub fn solve_exact_f64(&self, b: Vector<D>) -> Result<Vector<D>, LaError> {
let exact = self.solve_exact(b)?;
let mut result = [0.0f64; D];
for (i, val) in exact.iter().enumerate() {
let f = val.to_f64().unwrap_or(f64::INFINITY);
if !f.is_finite() {
cold_path();
return Err(LaError::Overflow { index: Some(i) });
}
result[i] = f;
}
Ok(Vector::new(result))
}
#[inline]
pub fn det_sign_exact(&self) -> Result<i8, LaError> {
match self.det_direct() {
Some(det_f64)
if let Some(err) = self.det_errbound()
&& det_f64.is_finite() =>
{
if det_f64 > err {
return Ok(1);
}
if det_f64 < -err {
return Ok(-1);
}
}
_ => {}
}
cold_path();
let (det_int, _) = bareiss_det_int(self)?;
Ok(match det_int.sign() {
Sign::Plus => 1,
Sign::Minus => -1,
Sign::NoSign => 0,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::DEFAULT_PIVOT_TOL;
use num_traits::Signed;
use pastey::paste;
use std::array::from_fn;
fn f64_to_bigrational(x: f64) -> BigRational {
let Some((mantissa, exponent, is_negative)) = f64_decompose(x) else {
return BigRational::from_integer(BigInt::from(0));
};
let numer = if is_negative {
-BigInt::from(mantissa)
} else {
BigInt::from(mantissa)
};
if exponent >= 0 {
BigRational::new_raw(numer << exponent.cast_unsigned(), BigInt::from(1u32))
} else {
BigRational::new_raw(numer, BigInt::from(1u32) << (-exponent).cast_unsigned())
}
}
macro_rules! gen_det_exact_tests {
($d:literal) => {
paste! {
#[test]
fn [<det_exact_identity_ $d d>]() {
let det = Matrix::<$d>::identity().det_exact().unwrap();
assert_eq!(det, BigRational::from_integer(BigInt::from(1)));
}
#[test]
fn [<det_exact_err_on_nan_ $d d>]() {
let mut m = Matrix::<$d>::identity();
m.set(0, 0, f64::NAN);
assert_eq!(m.det_exact(), Err(LaError::NonFinite { row: Some(0), col: 0 }));
}
#[test]
fn [<det_exact_err_on_inf_ $d d>]() {
let mut m = Matrix::<$d>::identity();
m.set(0, 0, f64::INFINITY);
assert_eq!(m.det_exact(), Err(LaError::NonFinite { row: Some(0), col: 0 }));
}
}
};
}
gen_det_exact_tests!(2);
gen_det_exact_tests!(3);
gen_det_exact_tests!(4);
gen_det_exact_tests!(5);
macro_rules! gen_det_exact_f64_tests {
($d:literal) => {
paste! {
#[test]
fn [<det_exact_f64_identity_ $d d>]() {
let det = Matrix::<$d>::identity().det_exact_f64().unwrap();
assert!((det - 1.0).abs() <= f64::EPSILON);
}
#[test]
fn [<det_exact_f64_err_on_nan_ $d d>]() {
let mut m = Matrix::<$d>::identity();
m.set(0, 0, f64::NAN);
assert_eq!(m.det_exact_f64(), Err(LaError::NonFinite { row: Some(0), col: 0 }));
}
}
};
}
gen_det_exact_f64_tests!(2);
gen_det_exact_f64_tests!(3);
gen_det_exact_f64_tests!(4);
gen_det_exact_f64_tests!(5);
macro_rules! gen_det_exact_f64_agrees_with_det_direct {
($d:literal) => {
paste! {
#[test]
#[allow(clippy::cast_precision_loss)]
fn [<det_exact_f64_agrees_with_det_direct_ $d d>]() {
let mut rows = [[0.0f64; $d]; $d];
for r in 0..$d {
for c in 0..$d {
rows[r][c] = if r == c {
(r as f64) + f64::from($d) + 1.0
} else {
0.1 / ((r + c + 1) as f64)
};
}
}
let m = Matrix::<$d>::from_rows(rows);
let exact = m.det_exact_f64().unwrap();
let direct = m.det_direct().unwrap();
let eps = direct.abs().mul_add(1e-12, 1e-12);
assert!((exact - direct).abs() <= eps);
}
}
};
}
gen_det_exact_f64_agrees_with_det_direct!(2);
gen_det_exact_f64_agrees_with_det_direct!(3);
gen_det_exact_f64_agrees_with_det_direct!(4);
#[test]
fn det_sign_exact_d0_is_positive() {
assert_eq!(Matrix::<0>::zero().det_sign_exact().unwrap(), 1);
}
#[test]
fn det_sign_exact_d1_positive() {
let m = Matrix::<1>::from_rows([[42.0]]);
assert_eq!(m.det_sign_exact().unwrap(), 1);
}
#[test]
fn det_sign_exact_d1_negative() {
let m = Matrix::<1>::from_rows([[-3.5]]);
assert_eq!(m.det_sign_exact().unwrap(), -1);
}
#[test]
fn det_sign_exact_d1_zero() {
let m = Matrix::<1>::from_rows([[0.0]]);
assert_eq!(m.det_sign_exact().unwrap(), 0);
}
#[test]
fn det_sign_exact_identity_2d() {
assert_eq!(Matrix::<2>::identity().det_sign_exact().unwrap(), 1);
}
#[test]
fn det_sign_exact_identity_3d() {
assert_eq!(Matrix::<3>::identity().det_sign_exact().unwrap(), 1);
}
#[test]
fn det_sign_exact_identity_4d() {
assert_eq!(Matrix::<4>::identity().det_sign_exact().unwrap(), 1);
}
#[test]
fn det_sign_exact_identity_5d() {
assert_eq!(Matrix::<5>::identity().det_sign_exact().unwrap(), 1);
}
#[test]
fn det_sign_exact_singular_duplicate_rows() {
let m = Matrix::<3>::from_rows([
[1.0, 2.0, 3.0],
[4.0, 5.0, 6.0],
[1.0, 2.0, 3.0], ]);
assert_eq!(m.det_sign_exact().unwrap(), 0);
}
#[test]
fn det_sign_exact_singular_linear_combination() {
let m = Matrix::<3>::from_rows([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [5.0, 7.0, 9.0]]);
assert_eq!(m.det_sign_exact().unwrap(), 0);
}
#[test]
fn det_sign_exact_negative_det_row_swap() {
let m = Matrix::<3>::from_rows([[0.0, 1.0, 0.0], [1.0, 0.0, 0.0], [0.0, 0.0, 1.0]]);
assert_eq!(m.det_sign_exact().unwrap(), -1);
}
#[test]
fn det_sign_exact_negative_det_known() {
let m = Matrix::<2>::from_rows([[1.0, 2.0], [3.0, 4.0]]);
assert_eq!(m.det_sign_exact().unwrap(), -1);
}
#[test]
fn det_sign_exact_agrees_with_det_for_spd() {
let m = Matrix::<3>::from_rows([[4.0, 2.0, 0.0], [2.0, 5.0, 1.0], [0.0, 1.0, 3.0]]);
assert_eq!(m.det_sign_exact().unwrap(), 1);
assert!(m.det(DEFAULT_PIVOT_TOL).unwrap() > 0.0);
}
#[test]
fn det_sign_exact_near_singular_perturbation() {
let perturbation = f64::from_bits(0x3CD0_0000_0000_0000); let m = Matrix::<3>::from_rows([
[1.0 + perturbation, 2.0, 3.0],
[4.0, 5.0, 6.0],
[7.0, 8.0, 9.0],
]);
assert_eq!(m.det_sign_exact().unwrap(), -1);
}
#[test]
fn det_sign_exact_fast_filter_positive_4x4() {
let m = Matrix::<4>::from_rows([
[2.0, 1.0, 0.0, 0.0],
[1.0, 3.0, 1.0, 0.0],
[0.0, 1.0, 4.0, 1.0],
[0.0, 0.0, 1.0, 5.0],
]);
assert_eq!(m.det_sign_exact().unwrap(), 1);
}
#[test]
fn det_sign_exact_fast_filter_negative_4x4() {
let m = Matrix::<4>::from_rows([
[1.0, 3.0, 1.0, 0.0],
[2.0, 1.0, 0.0, 0.0],
[0.0, 1.0, 4.0, 1.0],
[0.0, 0.0, 1.0, 5.0],
]);
assert_eq!(m.det_sign_exact().unwrap(), -1);
}
#[test]
fn det_sign_exact_subnormal_entries() {
let tiny = 5e-324_f64; assert!(tiny.is_subnormal());
let m = Matrix::<2>::from_rows([[tiny, 0.0], [0.0, tiny]]);
assert_eq!(m.det_sign_exact().unwrap(), 1);
}
#[test]
fn det_sign_exact_returns_err_on_nan() {
let m = Matrix::<2>::from_rows([[f64::NAN, 0.0], [0.0, 1.0]]);
assert_eq!(
m.det_sign_exact(),
Err(LaError::NonFinite {
row: Some(0),
col: 0
})
);
}
#[test]
fn det_sign_exact_returns_err_on_infinity() {
let m = Matrix::<2>::from_rows([[f64::INFINITY, 0.0], [0.0, 1.0]]);
assert_eq!(
m.det_sign_exact(),
Err(LaError::NonFinite {
row: Some(0),
col: 0
})
);
}
#[test]
fn det_sign_exact_returns_err_on_nan_5x5() {
let mut m = Matrix::<5>::identity();
m.set(2, 3, f64::NAN);
assert_eq!(
m.det_sign_exact(),
Err(LaError::NonFinite {
row: Some(2),
col: 3
})
);
}
#[test]
fn det_sign_exact_returns_err_on_infinity_5x5() {
let mut m = Matrix::<5>::identity();
m.set(0, 0, f64::INFINITY);
assert_eq!(
m.det_sign_exact(),
Err(LaError::NonFinite {
row: Some(0),
col: 0
})
);
}
#[test]
fn det_sign_exact_pivot_needed_5x5() {
let m = Matrix::<5>::from_rows([
[0.0, 1.0, 0.0, 0.0, 0.0],
[1.0, 0.0, 0.0, 0.0, 0.0],
[0.0, 0.0, 1.0, 0.0, 0.0],
[0.0, 0.0, 0.0, 1.0, 0.0],
[0.0, 0.0, 0.0, 0.0, 1.0],
]);
assert_eq!(m.det_sign_exact().unwrap(), -1);
}
#[test]
fn det_sign_exact_5x5_known() {
let m = Matrix::<5>::from_rows([
[0.0, 1.0, 0.0, 0.0, 0.0],
[1.0, 0.0, 0.0, 0.0, 0.0],
[0.0, 0.0, 0.0, 1.0, 0.0],
[0.0, 0.0, 1.0, 0.0, 0.0],
[0.0, 0.0, 0.0, 0.0, 1.0],
]);
assert_eq!(m.det_sign_exact().unwrap(), 1);
}
#[test]
fn det_errbound_d0_is_zero() {
assert_eq!(Matrix::<0>::zero().det_errbound(), Some(0.0));
}
#[test]
fn det_errbound_d1_is_zero() {
assert_eq!(Matrix::<1>::from_rows([[42.0]]).det_errbound(), Some(0.0));
}
#[test]
fn det_errbound_d2_positive() {
let m = Matrix::<2>::from_rows([[1.0, 2.0], [3.0, 4.0]]);
let bound = m.det_errbound().unwrap();
assert!(bound > 0.0);
assert!(crate::ERR_COEFF_2.mul_add(-10.0, bound).abs() < 1e-30);
}
#[test]
fn det_errbound_d3_positive() {
let m = Matrix::<3>::identity();
let bound = m.det_errbound().unwrap();
assert!(bound > 0.0);
}
#[test]
fn det_errbound_d3_non_identity() {
let m = Matrix::<3>::from_rows([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [7.0, 8.0, 10.0]]);
let bound = m.det_errbound().unwrap();
assert!(bound > 0.0);
}
#[test]
fn det_errbound_d4_positive() {
let m = Matrix::<4>::identity();
let bound = m.det_errbound().unwrap();
assert!(bound > 0.0);
}
#[test]
fn det_errbound_d4_non_identity() {
let m = Matrix::<4>::from_rows([
[1.0, 0.0, 0.0, 0.0],
[0.0, 2.0, 0.0, 0.0],
[0.0, 0.0, 3.0, 0.0],
[0.0, 0.0, 0.0, 4.0],
]);
let bound = m.det_errbound().unwrap();
assert!(bound > 0.0);
}
#[test]
fn det_errbound_d5_is_none() {
assert_eq!(Matrix::<5>::identity().det_errbound(), None);
}
#[test]
fn f64_decompose_zero() {
assert!(f64_decompose(0.0).is_none());
assert!(f64_decompose(-0.0).is_none());
}
#[test]
fn f64_decompose_one() {
let (mant, exp, neg) = f64_decompose(1.0).unwrap();
assert_eq!(mant, 1);
assert_eq!(exp, 0);
assert!(!neg);
}
#[test]
fn f64_decompose_negative() {
let (mant, exp, neg) = f64_decompose(-3.5).unwrap();
assert_eq!(mant, 7);
assert_eq!(exp, -1);
assert!(neg);
}
#[test]
fn f64_decompose_subnormal() {
let tiny = 5e-324_f64;
assert!(tiny.is_subnormal());
let (mant, exp, neg) = f64_decompose(tiny).unwrap();
assert_eq!(mant, 1);
assert_eq!(exp, -1074);
assert!(!neg);
}
#[test]
fn f64_decompose_power_of_two() {
let (mant, exp, neg) = f64_decompose(1024.0).unwrap();
assert_eq!(mant, 1);
assert_eq!(exp, 10); assert!(!neg);
}
#[test]
#[should_panic(expected = "non-finite f64 in exact conversion")]
fn f64_decompose_panics_on_nan() {
f64_decompose(f64::NAN);
}
#[test]
fn bareiss_det_int_d0() {
let (det, exp) = bareiss_det_int(&Matrix::<0>::zero()).unwrap();
assert_eq!(det, BigInt::from(1));
assert_eq!(exp, 0);
}
#[test]
fn bareiss_det_int_d1_cases() {
let cases: &[(f64, i64, i32)] = &[
(7.0, 7, 0), (0.0, 0, 0), (-3.5, -7, -1), (0.5, 1, -1), ];
for &(input, expected_det_int, expected_exp) in cases {
let (det, exp) = bareiss_det_int(&Matrix::<1>::from_rows([[input]])).unwrap();
assert_eq!(
det,
BigInt::from(expected_det_int),
"det_int for input={input}"
);
assert_eq!(exp, expected_exp, "exp for input={input}");
}
}
#[test]
fn bareiss_det_int_d2_known() {
let m = Matrix::<2>::from_rows([[1.0, 2.0], [3.0, 4.0]]);
let (det_int, total_exp) = bareiss_det_int(&m).unwrap();
let det = bigint_exp_to_bigrational(det_int, total_exp);
assert_eq!(det, BigRational::from_integer(BigInt::from(-2)));
}
#[test]
fn bareiss_det_int_all_zeros() {
let (det, _) = bareiss_det_int(&Matrix::<3>::zero()).unwrap();
assert_eq!(det, BigInt::from(0));
}
#[test]
fn bareiss_det_int_sign_matches_det_sign_exact() {
let m = Matrix::<3>::from_rows([[0.0, 1.0, 0.0], [1.0, 0.0, 0.0], [0.0, 0.0, 1.0]]);
let (det_int, _) = bareiss_det_int(&m).unwrap();
assert_eq!(det_int.sign(), Sign::Minus); }
#[test]
fn bareiss_det_int_fractional_entries() {
let m = Matrix::<2>::from_rows([[0.5, 0.25], [1.0, 1.0]]);
let (det_int, total_exp) = bareiss_det_int(&m).unwrap();
let det = bigint_exp_to_bigrational(det_int, total_exp);
assert_eq!(det, BigRational::new(BigInt::from(1), BigInt::from(4)));
}
#[test]
fn bareiss_det_int_d3_with_pivoting() {
let m = Matrix::<3>::from_rows([[0.0, 1.0, 0.0], [1.0, 0.0, 0.0], [0.0, 0.0, 1.0]]);
let (det_int, total_exp) = bareiss_det_int(&m).unwrap();
let det = bigint_exp_to_bigrational(det_int, total_exp);
assert_eq!(det, BigRational::from_integer(BigInt::from(-1)));
}
#[test]
fn bareiss_det_int_errs_on_nan() {
let mut m = Matrix::<3>::identity();
m.set(1, 2, f64::NAN);
assert_eq!(
bareiss_det_int(&m),
Err(LaError::NonFinite {
row: Some(1),
col: 2
})
);
}
#[test]
fn bareiss_det_int_errs_on_inf() {
let mut m = Matrix::<2>::identity();
m.set(0, 0, f64::INFINITY);
assert_eq!(
bareiss_det_int(&m),
Err(LaError::NonFinite {
row: Some(0),
col: 0
})
);
}
macro_rules! gen_bareiss_det_int_identity_tests {
($d:literal) => {
paste! {
#[test]
fn [<bareiss_det_int_identity_ $d d>]() {
let (det_int, total_exp) =
bareiss_det_int(&Matrix::<$d>::identity()).unwrap();
let det = bigint_exp_to_bigrational(det_int, total_exp);
assert_eq!(det, BigRational::from_integer(BigInt::from(1)));
}
}
};
}
gen_bareiss_det_int_identity_tests!(2);
gen_bareiss_det_int_identity_tests!(3);
gen_bareiss_det_int_identity_tests!(4);
gen_bareiss_det_int_identity_tests!(5);
#[test]
fn bigint_exp_to_bigrational_zero() {
let r = bigint_exp_to_bigrational(BigInt::from(0), -50);
assert_eq!(r, BigRational::from_integer(BigInt::from(0)));
}
#[test]
fn bigint_exp_to_bigrational_positive_exp() {
let r = bigint_exp_to_bigrational(BigInt::from(3), 2);
assert_eq!(r, BigRational::from_integer(BigInt::from(12)));
}
#[test]
fn bigint_exp_to_bigrational_negative_exp_reduced() {
let r = bigint_exp_to_bigrational(BigInt::from(6), -2);
assert_eq!(*r.numer(), BigInt::from(3));
assert_eq!(*r.denom(), BigInt::from(2));
}
#[test]
fn bigint_exp_to_bigrational_negative_exp_already_odd() {
let r = bigint_exp_to_bigrational(BigInt::from(3), -2);
assert_eq!(*r.numer(), BigInt::from(3));
assert_eq!(*r.denom(), BigInt::from(4));
}
#[test]
fn bigint_exp_to_bigrational_negative_value() {
let r = bigint_exp_to_bigrational(BigInt::from(-5), 1);
assert_eq!(r, BigRational::from_integer(BigInt::from(-10)));
}
#[test]
fn bigint_exp_to_bigrational_negative_value_with_denominator() {
let r = bigint_exp_to_bigrational(BigInt::from(-3), -2);
assert_eq!(*r.numer(), BigInt::from(-3));
assert_eq!(*r.denom(), BigInt::from(4));
}
#[test]
fn bareiss_det_d0_is_one() {
let det = bareiss_det(&Matrix::<0>::zero()).unwrap();
assert_eq!(det, BigRational::from_integer(BigInt::from(1)));
}
#[test]
fn bareiss_det_d1_returns_entry() {
let det = bareiss_det(&Matrix::<1>::from_rows([[7.0]])).unwrap();
assert_eq!(det, f64_to_bigrational(7.0));
}
#[test]
fn bareiss_det_d3_with_pivoting() {
let m = Matrix::<3>::from_rows([[0.0, 1.0, 0.0], [1.0, 0.0, 0.0], [0.0, 0.0, 1.0]]);
let det = bareiss_det(&m).unwrap();
assert_eq!(det, BigRational::from_integer(BigInt::from(-1)));
}
#[test]
fn bareiss_det_singular_all_zeros_in_column() {
let m = Matrix::<3>::from_rows([[1.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0, 1.0]]);
let det = bareiss_det(&m).unwrap();
assert_eq!(det, BigRational::from_integer(BigInt::from(0)));
}
#[test]
fn det_sign_exact_overflow_determinant_finite_entries() {
let big = f64::MAX / 2.0;
assert!(big.is_finite());
let m = Matrix::<3>::from_rows([[0.0, 0.0, 1.0], [big, 0.0, 1.0], [0.0, big, 1.0]]);
assert_eq!(m.det_sign_exact().unwrap(), 1);
}
#[test]
fn det_exact_d0_is_one() {
let det = Matrix::<0>::zero().det_exact().unwrap();
assert_eq!(det, BigRational::from_integer(BigInt::from(1)));
}
#[test]
fn det_exact_known_2x2() {
let m = Matrix::<2>::from_rows([[1.0, 2.0], [3.0, 4.0]]);
let det = m.det_exact().unwrap();
assert_eq!(det, BigRational::from_integer(BigInt::from(-2)));
}
#[test]
fn det_exact_singular_returns_zero() {
let m = Matrix::<3>::from_rows([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [7.0, 8.0, 9.0]]);
let det = m.det_exact().unwrap();
assert_eq!(det, BigRational::from_integer(BigInt::from(0)));
}
#[test]
fn det_exact_near_singular_perturbation() {
let perturbation = f64::from_bits(0x3CD0_0000_0000_0000); let m = Matrix::<3>::from_rows([
[1.0 + perturbation, 2.0, 3.0],
[4.0, 5.0, 6.0],
[7.0, 8.0, 9.0],
]);
let det = m.det_exact().unwrap();
let expected = BigRational::new(BigInt::from(-3), BigInt::from(1u64 << 50));
assert_eq!(det, expected);
}
#[test]
fn det_exact_5x5_permutation() {
let m = Matrix::<5>::from_rows([
[0.0, 1.0, 0.0, 0.0, 0.0],
[1.0, 0.0, 0.0, 0.0, 0.0],
[0.0, 0.0, 1.0, 0.0, 0.0],
[0.0, 0.0, 0.0, 1.0, 0.0],
[0.0, 0.0, 0.0, 0.0, 1.0],
]);
let det = m.det_exact().unwrap();
assert_eq!(det, BigRational::from_integer(BigInt::from(-1)));
}
#[test]
fn det_exact_f64_known_2x2() {
let m = Matrix::<2>::from_rows([[1.0, 2.0], [3.0, 4.0]]);
let det = m.det_exact_f64().unwrap();
assert!((det - (-2.0)).abs() <= f64::EPSILON);
}
#[test]
fn det_exact_f64_overflow_returns_err() {
let big = f64::MAX / 2.0;
let m = Matrix::<3>::from_rows([[0.0, 0.0, 1.0], [big, 0.0, 1.0], [0.0, big, 1.0]]);
assert_eq!(m.det_exact_f64(), Err(LaError::Overflow { index: None }));
}
fn arbitrary_rhs<const D: usize>() -> Vector<D> {
let values = [1.0, -2.5, 3.0, 0.25, -4.0];
let mut arr = [0.0f64; D];
for (dst, src) in arr.iter_mut().zip(values.iter()) {
*dst = *src;
}
Vector::<D>::new(arr)
}
macro_rules! gen_solve_exact_tests {
($d:literal) => {
paste! {
#[test]
fn [<solve_exact_identity_ $d d>]() {
let a = Matrix::<$d>::identity();
let b = arbitrary_rhs::<$d>();
let x = a.solve_exact(b).unwrap();
for (i, xi) in x.iter().enumerate() {
assert_eq!(*xi, f64_to_bigrational(b.data[i]));
}
}
#[test]
fn [<solve_exact_err_on_nan_matrix_ $d d>]() {
let mut a = Matrix::<$d>::identity();
a.set(0, 0, f64::NAN);
let b = arbitrary_rhs::<$d>();
assert_eq!(a.solve_exact(b), Err(LaError::NonFinite { row: Some(0), col: 0 }));
}
#[test]
fn [<solve_exact_err_on_inf_matrix_ $d d>]() {
let mut a = Matrix::<$d>::identity();
a.set(0, 0, f64::INFINITY);
let b = arbitrary_rhs::<$d>();
assert_eq!(a.solve_exact(b), Err(LaError::NonFinite { row: Some(0), col: 0 }));
}
#[test]
fn [<solve_exact_err_on_nan_vector_ $d d>]() {
let a = Matrix::<$d>::identity();
let mut b_arr = [1.0f64; $d];
b_arr[0] = f64::NAN;
let b = Vector::<$d>::new(b_arr);
assert_eq!(a.solve_exact(b), Err(LaError::NonFinite { row: None, col: 0 }));
}
#[test]
fn [<solve_exact_err_on_inf_vector_ $d d>]() {
let a = Matrix::<$d>::identity();
let mut b_arr = [1.0f64; $d];
b_arr[$d - 1] = f64::INFINITY;
let b = Vector::<$d>::new(b_arr);
assert_eq!(a.solve_exact(b), Err(LaError::NonFinite { row: None, col: $d - 1 }));
}
#[test]
fn [<solve_exact_singular_ $d d>]() {
let a = Matrix::<$d>::zero();
let b = arbitrary_rhs::<$d>();
assert_eq!(a.solve_exact(b), Err(LaError::Singular { pivot_col: 0 }));
}
}
};
}
gen_solve_exact_tests!(2);
gen_solve_exact_tests!(3);
gen_solve_exact_tests!(4);
gen_solve_exact_tests!(5);
macro_rules! gen_solve_exact_f64_tests {
($d:literal) => {
paste! {
#[test]
fn [<solve_exact_f64_identity_ $d d>]() {
let a = Matrix::<$d>::identity();
let b = arbitrary_rhs::<$d>();
let x = a.solve_exact_f64(b).unwrap().into_array();
for i in 0..$d {
assert!((x[i] - b.data[i]).abs() <= f64::EPSILON);
}
}
#[test]
fn [<solve_exact_f64_err_on_nan_ $d d>]() {
let mut a = Matrix::<$d>::identity();
a.set(0, 0, f64::NAN);
let b = arbitrary_rhs::<$d>();
assert_eq!(a.solve_exact_f64(b), Err(LaError::NonFinite { row: Some(0), col: 0 }));
}
}
};
}
gen_solve_exact_f64_tests!(2);
gen_solve_exact_f64_tests!(3);
gen_solve_exact_f64_tests!(4);
gen_solve_exact_f64_tests!(5);
macro_rules! gen_solve_exact_f64_agrees_with_lu {
($d:literal) => {
paste! {
#[test]
#[allow(clippy::cast_precision_loss)]
fn [<solve_exact_f64_agrees_with_lu_ $d d>]() {
let mut rows = [[0.0f64; $d]; $d];
for r in 0..$d {
for c in 0..$d {
rows[r][c] = if r == c {
(r as f64) + f64::from($d) + 1.0
} else {
0.1 / ((r + c + 1) as f64)
};
}
}
let a = Matrix::<$d>::from_rows(rows);
let b = arbitrary_rhs::<$d>();
let exact = a.solve_exact_f64(b).unwrap().into_array();
let lu_sol = a.lu(DEFAULT_PIVOT_TOL).unwrap()
.solve_vec(b).unwrap().into_array();
for i in 0..$d {
let eps = lu_sol[i].abs().mul_add(1e-12, 1e-12);
assert!((exact[i] - lu_sol[i]).abs() <= eps);
}
}
}
};
}
gen_solve_exact_f64_agrees_with_lu!(2);
gen_solve_exact_f64_agrees_with_lu!(3);
gen_solve_exact_f64_agrees_with_lu!(4);
gen_solve_exact_f64_agrees_with_lu!(5);
macro_rules! gen_solve_exact_roundtrip_tests {
($d:literal) => {
paste! {
#[test]
#[allow(clippy::cast_precision_loss)]
fn [<solve_exact_roundtrip_ $d d>]() {
let mut rows = [[0.0f64; $d]; $d];
for r in 0..$d {
for c in 0..$d {
rows[r][c] = if r == c {
f64::from($d) + 1.0
} else {
1.0
};
}
}
let a = Matrix::<$d>::from_rows(rows);
let mut x0 = [0.0f64; $d];
for i in 0..$d {
x0[i] = (i + 1) as f64;
}
let mut b_arr = [0.0f64; $d];
for r in 0..$d {
let mut sum = 0.0_f64;
for c in 0..$d {
sum += rows[r][c] * x0[c];
}
b_arr[r] = sum;
}
let b = Vector::<$d>::new(b_arr);
let x = a.solve_exact(b).unwrap();
for i in 0..$d {
assert_eq!(x[i], f64_to_bigrational(x0[i]));
}
}
}
};
}
gen_solve_exact_roundtrip_tests!(2);
gen_solve_exact_roundtrip_tests!(3);
gen_solve_exact_roundtrip_tests!(4);
gen_solve_exact_roundtrip_tests!(5);
#[test]
fn solve_exact_d0_returns_empty() {
let a = Matrix::<0>::zero();
let b = Vector::<0>::zero();
let x = a.solve_exact(b).unwrap();
assert!(x.is_empty());
}
#[test]
fn solve_exact_known_2x2() {
let a = Matrix::<2>::from_rows([[1.0, 2.0], [3.0, 4.0]]);
let b = Vector::<2>::new([5.0, 11.0]);
let x = a.solve_exact(b).unwrap();
assert_eq!(x[0], BigRational::from_integer(BigInt::from(1)));
assert_eq!(x[1], BigRational::from_integer(BigInt::from(2)));
}
#[test]
fn solve_exact_pivoting_needed() {
let a = Matrix::<3>::from_rows([[0.0, 1.0, 0.0], [1.0, 0.0, 0.0], [0.0, 0.0, 1.0]]);
let b = Vector::<3>::new([2.0, 3.0, 4.0]);
let x = a.solve_exact(b).unwrap();
assert_eq!(x[0], f64_to_bigrational(3.0));
assert_eq!(x[1], f64_to_bigrational(2.0));
assert_eq!(x[2], f64_to_bigrational(4.0));
}
#[test]
fn solve_exact_fractional_result() {
let a = Matrix::<2>::from_rows([[2.0, 1.0], [1.0, 3.0]]);
let b = Vector::<2>::new([1.0, 1.0]);
let x = a.solve_exact(b).unwrap();
assert_eq!(x[0], BigRational::new(BigInt::from(2), BigInt::from(5)));
assert_eq!(x[1], BigRational::new(BigInt::from(1), BigInt::from(5)));
}
#[test]
fn solve_exact_singular_duplicate_rows() {
let a = Matrix::<3>::from_rows([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [1.0, 2.0, 3.0]]);
let b = Vector::<3>::new([1.0, 2.0, 3.0]);
assert!(matches!(a.solve_exact(b), Err(LaError::Singular { .. })));
}
#[test]
fn solve_exact_5x5_permutation() {
let a = Matrix::<5>::from_rows([
[0.0, 1.0, 0.0, 0.0, 0.0],
[1.0, 0.0, 0.0, 0.0, 0.0],
[0.0, 0.0, 1.0, 0.0, 0.0],
[0.0, 0.0, 0.0, 1.0, 0.0],
[0.0, 0.0, 0.0, 0.0, 1.0],
]);
let b = Vector::<5>::new([10.0, 20.0, 30.0, 40.0, 50.0]);
let x = a.solve_exact(b).unwrap();
assert_eq!(x[0], f64_to_bigrational(20.0));
assert_eq!(x[1], f64_to_bigrational(10.0));
assert_eq!(x[2], f64_to_bigrational(30.0));
assert_eq!(x[3], f64_to_bigrational(40.0));
assert_eq!(x[4], f64_to_bigrational(50.0));
}
macro_rules! gen_solve_exact_large_finite_entries_tests {
($d:literal) => {
paste! {
#[test]
fn [<solve_exact_large_finite_entries_ $d d>]() {
let big = f64::MAX / 2.0;
assert!(big.is_finite());
let mut rows = [[0.0f64; $d]; $d];
for i in 0..$d {
rows[i][i] = big;
}
let a = Matrix::<$d>::from_rows(rows);
let mut b_arr = [big; $d];
b_arr[$d - 1] = 0.0;
let b = Vector::<$d>::new(b_arr);
let x = a.solve_exact(b).unwrap();
for i in 0..($d - 1) {
assert_eq!(x[i], BigRational::from_integer(BigInt::from(1)));
}
assert_eq!(x[$d - 1], BigRational::from_integer(BigInt::from(0)));
}
}
};
}
gen_solve_exact_large_finite_entries_tests!(2);
gen_solve_exact_large_finite_entries_tests!(3);
gen_solve_exact_large_finite_entries_tests!(4);
gen_solve_exact_large_finite_entries_tests!(5);
macro_rules! gen_solve_exact_mixed_magnitude_entries_tests {
($d:literal) => {
paste! {
#[test]
fn [<solve_exact_mixed_magnitude_entries_ $d d>]() {
let tiny = f64::MIN_POSITIVE; let huge = 1.0e100_f64;
let mut rows = [[0.0f64; $d]; $d];
let mut b_arr = [0.0f64; $d];
for i in 0..$d {
let val = if i % 2 == 0 { huge } else { tiny };
rows[i][i] = val;
b_arr[i] = val;
}
let a = Matrix::<$d>::from_rows(rows);
let b = Vector::<$d>::new(b_arr);
let x = a.solve_exact(b).unwrap();
for i in 0..$d {
assert_eq!(x[i], BigRational::from_integer(BigInt::from(1)));
}
}
}
};
}
gen_solve_exact_mixed_magnitude_entries_tests!(2);
gen_solve_exact_mixed_magnitude_entries_tests!(3);
gen_solve_exact_mixed_magnitude_entries_tests!(4);
gen_solve_exact_mixed_magnitude_entries_tests!(5);
macro_rules! gen_solve_exact_subnormal_rhs_tests {
($d:literal) => {
paste! {
#[test]
#[allow(clippy::cast_precision_loss)]
fn [<solve_exact_subnormal_rhs_ $d d>]() {
let tiny = 5e-324_f64; assert!(tiny.is_subnormal());
let a = Matrix::<$d>::identity();
let mut b_arr = [0.0f64; $d];
for i in 0..$d {
b_arr[i] = (i + 1) as f64 * tiny;
assert!(b_arr[i].is_subnormal());
}
let b = Vector::<$d>::new(b_arr);
let x = a.solve_exact(b).unwrap();
for i in 0..$d {
assert_eq!(x[i], f64_to_bigrational((i + 1) as f64 * tiny));
}
}
}
};
}
gen_solve_exact_subnormal_rhs_tests!(2);
gen_solve_exact_subnormal_rhs_tests!(3);
gen_solve_exact_subnormal_rhs_tests!(4);
gen_solve_exact_subnormal_rhs_tests!(5);
macro_rules! gen_solve_exact_pivot_swap_fractional_tests {
($d:literal) => {
paste! {
#[test]
#[allow(clippy::cast_precision_loss)]
#[allow(clippy::reversed_empty_ranges)]
fn [<solve_exact_pivot_swap_with_fractional_result_ $d d>]() {
let mut rows = [[0.0f64; $d]; $d];
rows[0][1] = 1.0;
rows[1][0] = 2.0;
rows[1][1] = 1.0;
for i in 2..$d {
rows[i][i] = 1.0;
}
let a = Matrix::<$d>::from_rows(rows);
let mut b_arr = [0.0f64; $d];
b_arr[0] = 3.0;
b_arr[1] = 4.0;
for i in 2..$d {
b_arr[i] = (i + 10) as f64;
}
let b = Vector::<$d>::new(b_arr);
let x = a.solve_exact(b).unwrap();
assert_eq!(x[0], BigRational::new(BigInt::from(1), BigInt::from(2)));
assert_eq!(x[1], BigRational::from_integer(BigInt::from(3)));
for i in 2..$d {
assert_eq!(x[i], f64_to_bigrational((i + 10) as f64));
}
}
}
};
}
gen_solve_exact_pivot_swap_fractional_tests!(2);
gen_solve_exact_pivot_swap_fractional_tests!(3);
gen_solve_exact_pivot_swap_fractional_tests!(4);
gen_solve_exact_pivot_swap_fractional_tests!(5);
macro_rules! gen_solve_exact_mid_pivot_swap_tests {
($d:literal) => {
paste! {
#[test]
#[allow(clippy::cast_precision_loss)]
#[allow(clippy::reversed_empty_ranges)]
fn [<solve_exact_mid_pivot_swap_ $d d>]() {
let mut rows = [[0.0f64; $d]; $d];
rows[0][0] = 1.0; rows[0][1] = 2.0; rows[0][2] = 3.0;
rows[1][2] = 4.0;
rows[2][1] = 5.0; rows[2][2] = 6.0;
for i in 3..$d {
rows[i][i] = 1.0;
}
let a = Matrix::<$d>::from_rows(rows);
let mut b_arr = [0.0f64; $d];
b_arr[0] = 6.0;
b_arr[1] = 7.0;
b_arr[2] = 8.0;
for i in 3..$d {
b_arr[i] = (i + 10) as f64;
}
let b = Vector::<$d>::new(b_arr);
let x = a.solve_exact(b).unwrap();
assert_eq!(x[0], BigRational::new(BigInt::from(7), BigInt::from(4)));
assert_eq!(x[1], BigRational::new(BigInt::from(-1), BigInt::from(2)));
assert_eq!(x[2], BigRational::new(BigInt::from(7), BigInt::from(4)));
for i in 3..$d {
assert_eq!(x[i], f64_to_bigrational((i + 10) as f64));
}
}
}
};
}
gen_solve_exact_mid_pivot_swap_tests!(3);
gen_solve_exact_mid_pivot_swap_tests!(4);
gen_solve_exact_mid_pivot_swap_tests!(5);
macro_rules! gen_solve_exact_singular_rank_deficient_tests {
($d:literal) => {
paste! {
#[test]
fn [<solve_exact_singular_rank_deficient_ $d d>]() {
let mut rows = [[0.0f64; $d]; $d];
for i in 0..($d - 1) {
rows[i][i] = 1.0;
rows[$d - 1][i] = 1.0;
}
let a = Matrix::<$d>::from_rows(rows);
let b = Vector::<$d>::new([1.0; $d]);
assert_eq!(
a.solve_exact(b),
Err(LaError::Singular { pivot_col: $d - 1 })
);
}
}
};
}
gen_solve_exact_singular_rank_deficient_tests!(2);
gen_solve_exact_singular_rank_deficient_tests!(3);
gen_solve_exact_singular_rank_deficient_tests!(4);
gen_solve_exact_singular_rank_deficient_tests!(5);
macro_rules! gen_solve_exact_zero_rhs_tests {
($d:literal) => {
paste! {
#[test]
fn [<solve_exact_zero_rhs_ $d d>]() {
let mut rows = [[1.0f64; $d]; $d];
for i in 0..$d {
rows[i][i] = f64::from($d) + 1.0;
}
let a = Matrix::<$d>::from_rows(rows);
let b = Vector::<$d>::zero();
let x = a.solve_exact(b).unwrap();
for xi in &x {
assert_eq!(*xi, BigRational::from_integer(BigInt::from(0)));
}
}
}
};
}
gen_solve_exact_zero_rhs_tests!(2);
gen_solve_exact_zero_rhs_tests!(3);
gen_solve_exact_zero_rhs_tests!(4);
gen_solve_exact_zero_rhs_tests!(5);
fn bigrational_matvec<const D: usize>(a: &Matrix<D>, x: &[BigRational; D]) -> [BigRational; D] {
from_fn(|i| {
let mut sum = BigRational::from_integer(BigInt::from(0));
for (aij, xj) in a.rows[i].iter().zip(x.iter()) {
sum += f64_to_bigrational(*aij) * xj;
}
sum
})
}
#[test]
fn solve_exact_near_singular_3x3_integer_x0() {
let perturbation = f64::from_bits(0x3CD0_0000_0000_0000); let a = Matrix::<3>::from_rows([
[1.0 + perturbation, 2.0, 3.0],
[4.0, 5.0, 6.0],
[7.0, 8.0, 9.0],
]);
let b = Vector::<3>::new([6.0 + perturbation, 15.0, 24.0]);
let x = a.solve_exact(b).unwrap();
let one = BigRational::from_integer(BigInt::from(1));
assert_eq!(x[0], one);
assert_eq!(x[1], one);
assert_eq!(x[2], one);
}
#[test]
fn solve_exact_large_entries_3x3_unit_vector() {
let big = f64::MAX / 2.0;
assert!(big.is_finite());
let a = Matrix::<3>::from_rows([[big, 1.0, 1.0], [1.0, big, 1.0], [1.0, 1.0, big]]);
let b = Vector::<3>::new([big, 1.0, 1.0]);
let x = a.solve_exact(b).unwrap();
let zero = BigRational::from_integer(BigInt::from(0));
let one = BigRational::from_integer(BigInt::from(1));
assert_eq!(x[0], one);
assert_eq!(x[1], zero);
assert_eq!(x[2], zero);
}
#[test]
fn det_sign_exact_large_entries_3x3_positive() {
let big = f64::MAX / 2.0;
let a = Matrix::<3>::from_rows([[big, 1.0, 1.0], [1.0, big, 1.0], [1.0, 1.0, big]]);
assert!(!a.det_direct().is_some_and(f64::is_finite));
assert_eq!(a.det_sign_exact().unwrap(), 1);
assert!(a.det_exact().unwrap().is_positive());
assert_eq!(a.det_exact_f64(), Err(LaError::Overflow { index: None }));
}
macro_rules! gen_det_sign_exact_hilbert_positive_tests {
($d:literal) => {
paste! {
#[test]
#[allow(clippy::cast_precision_loss)]
fn [<det_sign_exact_hilbert_positive_ $d d>]() {
let mut rows = [[0.0f64; $d]; $d];
for r in 0..$d {
for c in 0..$d {
rows[r][c] = 1.0 / ((r + c + 1) as f64);
}
}
let h = Matrix::<$d>::from_rows(rows);
assert_eq!(h.det_sign_exact().unwrap(), 1);
}
}
};
}
gen_det_sign_exact_hilbert_positive_tests!(2);
gen_det_sign_exact_hilbert_positive_tests!(3);
gen_det_sign_exact_hilbert_positive_tests!(4);
gen_det_sign_exact_hilbert_positive_tests!(5);
macro_rules! gen_solve_exact_hilbert_residual_tests {
($d:literal) => {
paste! {
#[test]
#[allow(clippy::cast_precision_loss)]
fn [<solve_exact_hilbert_residual_ $d d>]() {
let mut rows = [[0.0f64; $d]; $d];
for r in 0..$d {
for c in 0..$d {
rows[r][c] = 1.0 / ((r + c + 1) as f64);
}
}
let h = Matrix::<$d>::from_rows(rows);
let mut b_arr = [0.0f64; $d];
for i in 0usize..$d {
let sign = if i.is_multiple_of(2) { 1.0 } else { -1.0 };
b_arr[i] = sign * ((i + 1) as f64);
}
let b = Vector::<$d>::new(b_arr);
let x = h.solve_exact(b).unwrap();
let ax = bigrational_matvec(&h, &x);
for i in 0..$d {
assert_eq!(ax[i], f64_to_bigrational(b_arr[i]));
}
}
}
};
}
gen_solve_exact_hilbert_residual_tests!(2);
gen_solve_exact_hilbert_residual_tests!(3);
gen_solve_exact_hilbert_residual_tests!(4);
gen_solve_exact_hilbert_residual_tests!(5);
#[test]
fn solve_exact_f64_known_2x2() {
let a = Matrix::<2>::from_rows([[1.0, 2.0], [3.0, 4.0]]);
let b = Vector::<2>::new([5.0, 11.0]);
let x = a.solve_exact_f64(b).unwrap().into_array();
assert!((x[0] - 1.0).abs() <= f64::EPSILON);
assert!((x[1] - 2.0).abs() <= f64::EPSILON);
}
#[test]
fn solve_exact_f64_overflow_returns_err() {
let big = f64::MAX / 2.0;
let a = Matrix::<2>::from_rows([[1.0 / big, 0.0], [0.0, 1.0 / big]]);
let b = Vector::<2>::new([big, big]);
assert_eq!(
a.solve_exact_f64(b),
Err(LaError::Overflow { index: Some(0) })
);
}
#[test]
fn gauss_solve_d0_returns_empty() {
let a = Matrix::<0>::zero();
let b = Vector::<0>::zero();
assert_eq!(gauss_solve(&a, &b).unwrap().len(), 0);
}
#[test]
fn gauss_solve_d1() {
let a = Matrix::<1>::from_rows([[2.0]]);
let b = Vector::<1>::new([6.0]);
let x = gauss_solve(&a, &b).unwrap();
assert_eq!(x[0], f64_to_bigrational(3.0));
}
#[test]
fn gauss_solve_singular_column_all_zero() {
let a = Matrix::<3>::from_rows([[1.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0, 1.0]]);
let b = Vector::<3>::new([1.0, 2.0, 3.0]);
assert_eq!(gauss_solve(&a, &b), Err(LaError::Singular { pivot_col: 1 }));
}
#[test]
fn f64_to_bigrational_positive_zero() {
let r = f64_to_bigrational(0.0);
assert_eq!(r, BigRational::from_integer(BigInt::from(0)));
}
#[test]
fn f64_to_bigrational_negative_zero() {
let r = f64_to_bigrational(-0.0);
assert_eq!(r, BigRational::from_integer(BigInt::from(0)));
}
#[test]
fn f64_to_bigrational_one() {
let r = f64_to_bigrational(1.0);
assert_eq!(r, BigRational::from_integer(BigInt::from(1)));
}
#[test]
fn f64_to_bigrational_negative_one() {
let r = f64_to_bigrational(-1.0);
assert_eq!(r, BigRational::from_integer(BigInt::from(-1)));
}
#[test]
fn f64_to_bigrational_half() {
let r = f64_to_bigrational(0.5);
assert_eq!(r, BigRational::new(BigInt::from(1), BigInt::from(2)));
}
#[test]
fn f64_to_bigrational_quarter() {
let r = f64_to_bigrational(0.25);
assert_eq!(r, BigRational::new(BigInt::from(1), BigInt::from(4)));
}
#[test]
fn f64_to_bigrational_negative_three_and_a_half() {
let r = f64_to_bigrational(-3.5);
assert_eq!(r, BigRational::new(BigInt::from(-7), BigInt::from(2)));
}
#[test]
fn f64_to_bigrational_integer() {
let r = f64_to_bigrational(42.0);
assert_eq!(r, BigRational::from_integer(BigInt::from(42)));
}
#[test]
fn f64_to_bigrational_power_of_two() {
let r = f64_to_bigrational(1024.0);
assert_eq!(r, BigRational::from_integer(BigInt::from(1024)));
}
#[test]
fn f64_to_bigrational_subnormal() {
let tiny = 5e-324_f64; assert!(tiny.is_subnormal());
let r = f64_to_bigrational(tiny);
assert_eq!(
r,
BigRational::new(BigInt::from(1), BigInt::from(1u32) << 1074u32)
);
}
#[test]
fn f64_to_bigrational_already_lowest_terms() {
let r = f64_to_bigrational(0.5);
assert_eq!(*r.numer(), BigInt::from(1));
assert_eq!(*r.denom(), BigInt::from(2));
}
#[test]
fn f64_to_bigrational_round_trip() {
let values = [
0.0,
1.0,
-1.0,
0.5,
0.25,
0.1,
42.0,
-3.5,
1e10,
1e-10,
f64::MAX / 2.0,
f64::MIN_POSITIVE,
5e-324,
];
for &v in &values {
let r = f64_to_bigrational(v);
let back = r.to_f64().expect("round-trip to_f64 failed");
assert!(
v.to_bits() == back.to_bits(),
"round-trip failed for {v}: got {back}"
);
}
}
#[test]
#[should_panic(expected = "non-finite f64 in exact conversion")]
fn f64_to_bigrational_panics_on_nan() {
f64_to_bigrational(f64::NAN);
}
#[test]
#[should_panic(expected = "non-finite f64 in exact conversion")]
fn f64_to_bigrational_panics_on_inf() {
f64_to_bigrational(f64::INFINITY);
}
#[test]
#[should_panic(expected = "non-finite f64 in exact conversion")]
fn f64_to_bigrational_panics_on_neg_inf() {
f64_to_bigrational(f64::NEG_INFINITY);
}
}