1use core::hint::cold_path;
69use core::mem::take;
70use core::num::NonZeroU64;
71use std::array::from_fn;
72
73use num_bigint::{BigInt, Sign};
74use num_rational::BigRational;
75use num_traits::ToPrimitive;
76
77use crate::LaError;
78use crate::matrix::{FiniteMatrix, Matrix};
79use crate::vector::{FiniteVector, Vector};
80
81const fn f64_decompose(x: f64) -> Result<Option<(NonZeroU64, i32, bool)>, LaError> {
91 let bits = x.to_bits();
92 let biased_exp = ((bits >> 52) & 0x7FF) as i32;
93 let fraction = bits & 0x000F_FFFF_FFFF_FFFF;
94
95 if biased_exp == 0 && fraction == 0 {
97 return Ok(None);
98 }
99
100 if biased_exp == 0x7FF {
101 cold_path();
102 return Err(LaError::non_finite_at(0));
103 }
104
105 let (mantissa, raw_exp) = if biased_exp == 0 {
106 (fraction, -1074_i32)
109 } else {
110 ((1u64 << 52) | fraction, biased_exp - 1075)
113 };
114
115 let tz = mantissa.trailing_zeros();
117 let mantissa = mantissa >> tz;
118 let Some(mantissa) = NonZeroU64::new(mantissa) else {
119 cold_path();
120 return Ok(None);
121 };
122 let exponent = raw_exp + tz.cast_signed();
123 let is_negative = bits >> 63 != 0;
124
125 Ok(Some((mantissa, exponent, is_negative)))
126}
127
128fn bigint_exp_to_bigrational(mut value: BigInt, mut exp: i32) -> BigRational {
134 if value == BigInt::from(0) {
135 return BigRational::from_integer(BigInt::from(0));
136 }
137
138 if exp < 0
140 && let Some(tz) = value.trailing_zeros()
141 {
142 let exp_abs = exp.unsigned_abs();
143 let reduce = tz.min(u64::from(exp_abs));
144 value >>= reduce;
145 let reduce = u32::try_from(reduce).unwrap_or(u32::MAX);
146 let remaining_abs = exp_abs - reduce;
147 exp = match remaining_abs {
148 0 => 0,
149 2_147_483_648 => i32::MIN,
150 value => -value.cast_signed(),
151 };
152 }
153
154 if exp >= 0 {
155 BigRational::new_raw(value << exp.cast_unsigned(), BigInt::from(1u32))
156 } else {
157 BigRational::new_raw(value, BigInt::from(1u32) << exp.unsigned_abs())
158 }
159}
160
161#[derive(Clone, Copy, Default)]
180enum Component {
181 #[default]
182 Zero,
183 NonZero {
184 mantissa: NonZeroU64,
185 exponent: i32,
186 is_negative: bool,
187 },
188}
189
190fn decompose_finite_matrix<const D: usize>(
195 m: &FiniteMatrix<D>,
196) -> Result<([[Component; D]; D], i32), LaError> {
197 let mut components = [[Component::default(); D]; D];
198 let mut e_min = i32::MAX;
199 let matrix = m.as_matrix();
200 for (r, row) in matrix.rows.iter().enumerate() {
201 for (c, &entry) in row.iter().enumerate() {
202 if let Some((mantissa, exponent, is_negative)) =
203 f64_decompose(entry).map_err(|_| LaError::non_finite_cell(r, c))?
204 {
205 components[r][c] = Component::NonZero {
206 mantissa,
207 exponent,
208 is_negative,
209 };
210 e_min = e_min.min(exponent);
211 }
212 }
213 }
214 Ok((components, e_min))
215}
216
217fn decompose_finite_vec<const D: usize>(
222 v: &FiniteVector<D>,
223) -> Result<([Component; D], i32), LaError> {
224 let mut components = [Component::default(); D];
225 let mut e_min = i32::MAX;
226 let data = v.as_array();
227 for (i, &entry) in data.iter().enumerate() {
228 if let Some((mantissa, exponent, is_negative)) =
229 f64_decompose(entry).map_err(|_| LaError::non_finite_at(i))?
230 {
231 components[i] = Component::NonZero {
232 mantissa,
233 exponent,
234 is_negative,
235 };
236 e_min = e_min.min(exponent);
237 }
238 }
239 Ok((components, e_min))
240}
241
242#[inline]
245fn component_to_bigint(c: Component, e_min: i32) -> BigInt {
246 match c {
247 Component::Zero => BigInt::from(0),
248 Component::NonZero {
249 mantissa,
250 exponent,
251 is_negative,
252 } => {
253 let v = BigInt::from(mantissa.get()) << (exponent - e_min).cast_unsigned();
254 if is_negative { -v } else { v }
255 }
256 }
257}
258
259fn build_bigint_matrix<const D: usize>(
262 components: &[[Component; D]; D],
263 e_min: i32,
264) -> [[BigInt; D]; D] {
265 from_fn(|r| from_fn(|c| component_to_bigint(components[r][c], e_min)))
266}
267
268fn build_bigint_vec<const D: usize>(components: &[Component; D], e_min: i32) -> [BigInt; D] {
271 from_fn(|i| component_to_bigint(components[i], e_min))
272}
273
274#[derive(Debug)]
276enum BareissResult {
277 Upper { sign: i8 },
280 Singular { pivot_col: usize },
282}
283
284fn bareiss_forward_eliminate<const D: usize>(
295 a: &mut [[BigInt; D]; D],
296 mut rhs: Option<&mut [BigInt; D]>,
297) -> BareissResult {
298 let zero = BigInt::from(0);
299 let mut prev_pivot = BigInt::from(1);
300 let mut sign: i8 = 1;
301
302 for k in 0..D {
303 if a[k][k] == zero {
305 let mut found = false;
306 for i in (k + 1)..D {
307 if a[i][k] != zero {
308 a.swap(k, i);
309 if let Some(r) = &mut rhs {
310 r.swap(k, i);
311 }
312 sign = -sign;
313 found = true;
314 break;
315 }
316 }
317 if !found {
318 cold_path();
319 return BareissResult::Singular { pivot_col: k };
320 }
321 }
322
323 for i in (k + 1)..D {
327 for j in (k + 1)..D {
328 a[i][j] = (&a[k][k] * &a[i][j] - &a[i][k] * &a[k][j]) / &prev_pivot;
329 }
330 if let Some(r) = &mut rhs {
331 r[i] = (&a[k][k] * &r[i] - &a[i][k] * &r[k]) / &prev_pivot;
332 }
333 a[i][k].clone_from(&zero);
334 }
335
336 prev_pivot.clone_from(&a[k][k]);
337 }
338
339 #[cfg(debug_assertions)]
345 #[allow(clippy::needless_range_loop)]
346 for k in 0..D {
347 assert_ne!(a[k][k], zero, "pivot at ({k}, {k}) must be non-zero");
348 for i in (k + 1)..D {
349 assert_eq!(a[i][k], zero, "sub-diagonal at ({i}, {k}) must be zero");
350 }
351 }
352
353 BareissResult::Upper { sign }
354}
355
356fn bareiss_det_int_finite<const D: usize>(m: &FiniteMatrix<D>) -> Result<(BigInt, i32), LaError> {
368 if D == 0 {
371 return Ok((BigInt::from(1), 0));
372 }
373
374 let (components, e_min) = decompose_finite_matrix(m)?;
375
376 if e_min == i32::MAX {
378 return Ok((BigInt::from(0), 0));
379 }
380
381 let mut a = build_bigint_matrix(&components, e_min);
382 let sign = match bareiss_forward_eliminate(&mut a, None) {
383 BareissResult::Upper { sign } => sign,
384 BareissResult::Singular { .. } => {
385 cold_path();
386 return Ok((BigInt::from(0), 0));
387 }
388 };
389
390 let det_int = if sign < 0 {
391 -&a[D - 1][D - 1]
392 } else {
393 a[D - 1][D - 1].clone()
394 };
395
396 let Ok(d_i32) = i32::try_from(D) else {
398 cold_path();
399 return Err(LaError::unsupported_dimension(D, i32::MAX as usize));
400 };
401 let Some(total_exp) = e_min.checked_mul(d_i32) else {
402 cold_path();
403 return Err(LaError::Overflow { index: None });
404 };
405
406 Ok((det_int, total_exp))
407}
408
409fn bareiss_det_finite<const D: usize>(m: &FiniteMatrix<D>) -> Result<BigRational, LaError> {
412 let (det_int, total_exp) = bareiss_det_int_finite(m)?;
413 Ok(bigint_exp_to_bigrational(det_int, total_exp))
414}
415
416fn gauss_solve_finite<const D: usize>(
425 m: &FiniteMatrix<D>,
426 b: &FiniteVector<D>,
427) -> Result<[BigRational; D], LaError> {
428 let (m_components, m_e_min) = decompose_finite_matrix(m)?;
429 let (b_components, b_e_min) = decompose_finite_vec(b)?;
430 gauss_solve_components(m_components, m_e_min, b_components, b_e_min)
431}
432
433fn gauss_solve_components<const D: usize>(
449 m_components: [[Component; D]; D],
450 m_e_min: i32,
451 b_components: [Component; D],
452 b_e_min: i32,
453) -> Result<[BigRational; D], LaError> {
454 let mut e_min = m_e_min.min(b_e_min);
455 if e_min == i32::MAX {
456 e_min = 0;
457 }
458
459 let mut a = build_bigint_matrix(&m_components, e_min);
460 let mut rhs = build_bigint_vec(&b_components, e_min);
461
462 if let BareissResult::Singular { pivot_col } = bareiss_forward_eliminate(&mut a, Some(&mut rhs))
463 {
464 cold_path();
465 return Err(LaError::Singular { pivot_col });
466 }
467
468 let mut x: [BigRational; D] = from_fn(|_| BigRational::from_integer(BigInt::from(0)));
469 for i in (0..D).rev() {
470 let mut sum = BigRational::from_integer(take(&mut rhs[i]));
471 for j in (i + 1)..D {
472 let a_ij = BigRational::from_integer(take(&mut a[i][j]));
473 sum -= &a_ij * &x[j];
474 }
475 let a_ii = BigRational::from_integer(take(&mut a[i][i]));
476 x[i] = sum / &a_ii;
477 }
478
479 Ok(x)
480}
481
482impl<const D: usize> FiniteMatrix<D> {
483 #[inline]
485 fn det_exact(&self) -> Result<BigRational, LaError> {
486 bareiss_det_finite(self)
487 }
488
489 #[inline]
495 fn det_exact_f64(&self) -> Result<f64, LaError> {
496 let exact = self.det_exact()?;
497 let Some(val) = exact.to_f64() else {
498 cold_path();
499 return Err(LaError::Overflow { index: None });
500 };
501 if val.is_finite() {
502 Ok(val)
503 } else {
504 cold_path();
505 Err(LaError::Overflow { index: None })
506 }
507 }
508
509 #[inline]
514 fn solve_exact(&self, b: FiniteVector<D>) -> Result<[BigRational; D], LaError> {
515 gauss_solve_finite(self, &b)
516 }
517
518 #[inline]
525 fn solve_exact_f64(&self, b: FiniteVector<D>) -> Result<FiniteVector<D>, LaError> {
526 let exact = self.solve_exact(b)?;
527 let mut result = [0.0f64; D];
528 for (i, val) in exact.iter().enumerate() {
529 let Some(f) = val.to_f64() else {
530 cold_path();
531 return Err(LaError::Overflow { index: Some(i) });
532 };
533 if !f.is_finite() {
534 cold_path();
535 return Err(LaError::Overflow { index: Some(i) });
536 }
537 result[i] = f;
538 }
539 Ok(FiniteVector::new_unchecked(Vector::new_unchecked(result)))
540 }
541
542 #[inline]
552 fn det_sign_exact(&self) -> Result<i8, LaError> {
553 match (self.det_direct(), self.det_errbound()) {
554 (Ok(Some(det_f64)), Ok(Some(err))) => {
555 if det_f64 > err {
556 return Ok(1);
557 }
558 if det_f64 < -err {
559 return Ok(-1);
560 }
561 }
562 (Err(LaError::NonFinite { row: None, .. }), _)
563 | (_, Err(LaError::NonFinite { row: None, .. })) => {}
564 (Err(err), _) | (_, Err(err)) => return Err(err),
565 _ => {}
566 }
567
568 cold_path();
569 let (det_int, _) = bareiss_det_int_finite(self)?;
570 Ok(match det_int.sign() {
571 Sign::Plus => 1,
572 Sign::Minus => -1,
573 Sign::NoSign => 0,
574 })
575 }
576}
577
578impl<const D: usize> Matrix<D> {
579 #[inline]
616 pub fn det_exact(&self) -> Result<BigRational, LaError> {
617 FiniteMatrix::new(*self)?.det_exact()
618 }
619
620 #[inline]
648 pub fn det_exact_f64(&self) -> Result<f64, LaError> {
649 FiniteMatrix::new(*self)?.det_exact_f64()
650 }
651
652 #[inline]
700 pub fn solve_exact(&self, b: Vector<D>) -> Result<[BigRational; D], LaError> {
701 let finite_m = FiniteMatrix::new(*self)?;
702 let finite_b = FiniteVector::new(b)?;
703 finite_m.solve_exact(finite_b)
704 }
705
706 #[inline]
737 pub fn solve_exact_f64(&self, b: Vector<D>) -> Result<Vector<D>, LaError> {
738 let finite_m = FiniteMatrix::new(*self)?;
739 let finite_b = FiniteVector::new(b)?;
740 Ok(finite_m.solve_exact_f64(finite_b)?.into_vector())
741 }
742
743 #[inline]
783 pub fn det_sign_exact(&self) -> Result<i8, LaError> {
784 FiniteMatrix::new(*self)?.det_sign_exact()
785 }
786}
787
788#[cfg(test)]
789mod tests {
790 use super::*;
791 use crate::DEFAULT_PIVOT_TOL;
792
793 use core::assert_matches;
794 use num_traits::Signed;
795 use pastey::paste;
796 use std::array::from_fn;
797
798 fn f64_to_bigrational(x: f64) -> BigRational {
818 let Some((mantissa, exponent, is_negative)) =
819 f64_decompose(x).expect("test helper requires finite f64 input")
820 else {
821 return BigRational::from_integer(BigInt::from(0));
822 };
823
824 let numer = if is_negative {
825 -BigInt::from(mantissa.get())
826 } else {
827 BigInt::from(mantissa.get())
828 };
829
830 if exponent >= 0 {
831 BigRational::new_raw(numer << exponent.cast_unsigned(), BigInt::from(1u32))
832 } else {
833 BigRational::new_raw(numer, BigInt::from(1u32) << (-exponent).cast_unsigned())
834 }
835 }
836
837 macro_rules! gen_internal_finite_exact_tests {
842 ($d:literal) => {
843 paste! {
844 #[test]
845 fn [<internal_finite_exact_paths_reuse_validation_ $d d>]() {
846 let a = FiniteMatrix::<$d>::new_unchecked(Matrix::<$d>::identity());
847 let b = FiniteVector::<$d>::new_unchecked(Vector::<$d>::new([1.0; $d]));
848
849 assert_eq!(
850 a.det_exact().unwrap(),
851 BigRational::from_integer(BigInt::from(1))
852 );
853 assert!((a.det_exact_f64().unwrap() - 1.0).abs() <= f64::EPSILON);
854 assert_eq!(a.det_sign_exact().unwrap(), 1);
855
856 let exact = a.solve_exact(b).unwrap();
857 for value in exact {
858 assert_eq!(value, BigRational::from_integer(BigInt::from(1)));
859 }
860
861 let exact_f64 = a.solve_exact_f64(b).unwrap();
862 for value in exact_f64.into_array() {
863 assert!((value - 1.0).abs() <= f64::EPSILON);
864 }
865 }
866 }
867 };
868 }
869
870 gen_internal_finite_exact_tests!(2);
871 gen_internal_finite_exact_tests!(3);
872 gen_internal_finite_exact_tests!(4);
873 gen_internal_finite_exact_tests!(5);
874
875 macro_rules! gen_public_exact_revalidation_tests {
876 ($d:literal) => {
877 paste! {
878 #[test]
879 fn [<public_exact_methods_revalidate_unchecked_matrix_storage_ $d d>]() {
880 let mut rows = [[0.0f64; $d]; $d];
881 for i in 0..$d {
882 rows[i][i] = 1.0;
883 }
884 rows[0][$d - 1] = f64::NAN;
885 let raw = Matrix::<$d>::from_rows_unchecked(rows);
886 let rhs = Vector::<$d>::new([1.0; $d]);
887 let expected = LaError::NonFinite {
888 row: Some(0),
889 col: $d - 1,
890 };
891
892 assert_eq!(raw.det_exact(), Err(expected));
893 assert_eq!(raw.det_exact_f64(), Err(expected));
894 assert_eq!(raw.det_sign_exact(), Err(expected));
895 assert_eq!(raw.solve_exact(rhs), Err(expected));
896 assert_eq!(raw.solve_exact_f64(rhs), Err(expected));
897 }
898
899 #[test]
900 fn [<public_exact_solve_methods_revalidate_unchecked_rhs_storage_ $d d>]() {
901 let matrix = Matrix::<$d>::identity();
902 let mut rhs = [1.0; $d];
903 rhs[$d - 1] = f64::INFINITY;
904 let rhs = Vector::<$d>::new_unchecked(rhs);
905 let expected = LaError::NonFinite {
906 row: None,
907 col: $d - 1,
908 };
909
910 assert_eq!(matrix.solve_exact(rhs), Err(expected));
911 assert_eq!(matrix.solve_exact_f64(rhs), Err(expected));
912 }
913 }
914 };
915 }
916
917 gen_public_exact_revalidation_tests!(2);
918 gen_public_exact_revalidation_tests!(3);
919 gen_public_exact_revalidation_tests!(4);
920 gen_public_exact_revalidation_tests!(5);
921
922 macro_rules! gen_det_exact_tests {
923 ($d:literal) => {
924 paste! {
925 #[test]
926 fn [<det_exact_identity_ $d d>]() {
927 let det = Matrix::<$d>::identity().det_exact().unwrap();
928 assert_eq!(det, BigRational::from_integer(BigInt::from(1)));
929 }
930 }
931 };
932 }
933
934 gen_det_exact_tests!(2);
935 gen_det_exact_tests!(3);
936 gen_det_exact_tests!(4);
937 gen_det_exact_tests!(5);
938
939 macro_rules! gen_det_exact_f64_tests {
940 ($d:literal) => {
941 paste! {
942 #[test]
943 fn [<det_exact_f64_identity_ $d d>]() {
944 let det = Matrix::<$d>::identity().det_exact_f64().unwrap();
945 assert!((det - 1.0).abs() <= f64::EPSILON);
946 }
947 }
948 };
949 }
950
951 gen_det_exact_f64_tests!(2);
952 gen_det_exact_f64_tests!(3);
953 gen_det_exact_f64_tests!(4);
954 gen_det_exact_f64_tests!(5);
955
956 macro_rules! gen_det_exact_f64_agrees_with_det_direct {
959 ($d:literal) => {
960 paste! {
961 #[test]
962 #[allow(clippy::cast_precision_loss)]
963 fn [<det_exact_f64_agrees_with_det_direct_ $d d>]() {
964 let mut rows = [[0.0f64; $d]; $d];
966 for r in 0..$d {
967 for c in 0..$d {
968 rows[r][c] = if r == c {
969 (r as f64) + f64::from($d) + 1.0
970 } else {
971 0.1 / ((r + c + 1) as f64)
972 };
973 }
974 }
975 let m = Matrix::<$d>::from_rows(rows);
976 let exact = m.det_exact_f64().unwrap();
977 let direct = m.det_direct().unwrap().unwrap();
978 let eps = direct.abs().mul_add(1e-12, 1e-12);
979 assert!((exact - direct).abs() <= eps);
980 }
981 }
982 };
983 }
984
985 gen_det_exact_f64_agrees_with_det_direct!(2);
986 gen_det_exact_f64_agrees_with_det_direct!(3);
987 gen_det_exact_f64_agrees_with_det_direct!(4);
988
989 #[test]
990 fn det_sign_exact_d0_is_positive() {
991 assert_eq!(Matrix::<0>::zero().det_sign_exact().unwrap(), 1);
992 }
993
994 #[test]
995 fn det_sign_exact_d1_positive() {
996 let m = Matrix::<1>::from_rows([[42.0]]);
997 assert_eq!(m.det_sign_exact().unwrap(), 1);
998 }
999
1000 #[test]
1001 fn det_sign_exact_d1_negative() {
1002 let m = Matrix::<1>::from_rows([[-3.5]]);
1003 assert_eq!(m.det_sign_exact().unwrap(), -1);
1004 }
1005
1006 #[test]
1007 fn det_sign_exact_d1_zero() {
1008 let m = Matrix::<1>::from_rows([[0.0]]);
1009 assert_eq!(m.det_sign_exact().unwrap(), 0);
1010 }
1011
1012 #[test]
1013 fn det_sign_exact_identity_2d() {
1014 assert_eq!(Matrix::<2>::identity().det_sign_exact().unwrap(), 1);
1015 }
1016
1017 #[test]
1018 fn det_sign_exact_identity_3d() {
1019 assert_eq!(Matrix::<3>::identity().det_sign_exact().unwrap(), 1);
1020 }
1021
1022 #[test]
1023 fn det_sign_exact_identity_4d() {
1024 assert_eq!(Matrix::<4>::identity().det_sign_exact().unwrap(), 1);
1025 }
1026
1027 #[test]
1028 fn det_sign_exact_identity_5d() {
1029 assert_eq!(Matrix::<5>::identity().det_sign_exact().unwrap(), 1);
1030 }
1031
1032 #[test]
1033 fn det_sign_exact_singular_duplicate_rows() {
1034 let m = Matrix::<3>::from_rows([
1035 [1.0, 2.0, 3.0],
1036 [4.0, 5.0, 6.0],
1037 [1.0, 2.0, 3.0], ]);
1039 assert_eq!(m.det_sign_exact().unwrap(), 0);
1040 }
1041
1042 #[test]
1043 fn det_sign_exact_singular_linear_combination() {
1044 let m = Matrix::<3>::from_rows([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [5.0, 7.0, 9.0]]);
1046 assert_eq!(m.det_sign_exact().unwrap(), 0);
1047 }
1048
1049 #[test]
1050 fn det_sign_exact_negative_det_row_swap() {
1051 let m = Matrix::<3>::from_rows([[0.0, 1.0, 0.0], [1.0, 0.0, 0.0], [0.0, 0.0, 1.0]]);
1053 assert_eq!(m.det_sign_exact().unwrap(), -1);
1054 }
1055
1056 #[test]
1057 fn det_sign_exact_negative_det_known() {
1058 let m = Matrix::<2>::from_rows([[1.0, 2.0], [3.0, 4.0]]);
1060 assert_eq!(m.det_sign_exact().unwrap(), -1);
1061 }
1062
1063 #[test]
1064 fn det_sign_exact_agrees_with_det_for_spd() {
1065 let m = Matrix::<3>::from_rows([[4.0, 2.0, 0.0], [2.0, 5.0, 1.0], [0.0, 1.0, 3.0]]);
1067 assert_eq!(m.det_sign_exact().unwrap(), 1);
1068 assert!(m.det().unwrap() > 0.0);
1069 }
1070
1071 #[test]
1078 fn det_sign_exact_near_singular_perturbation() {
1079 let perturbation = f64::from_bits(0x3CD0_0000_0000_0000); let m = Matrix::<3>::from_rows([
1081 [1.0 + perturbation, 2.0, 3.0],
1082 [4.0, 5.0, 6.0],
1083 [7.0, 8.0, 9.0],
1084 ]);
1085 assert_eq!(m.det_sign_exact().unwrap(), -1);
1087 }
1088
1089 #[test]
1093 fn det_sign_exact_fast_filter_positive_4x4() {
1094 let m = Matrix::<4>::from_rows([
1095 [2.0, 1.0, 0.0, 0.0],
1096 [1.0, 3.0, 1.0, 0.0],
1097 [0.0, 1.0, 4.0, 1.0],
1098 [0.0, 0.0, 1.0, 5.0],
1099 ]);
1100 assert_eq!(m.det_sign_exact().unwrap(), 1);
1102 }
1103
1104 #[test]
1105 fn det_sign_exact_fast_filter_negative_4x4() {
1106 let m = Matrix::<4>::from_rows([
1108 [1.0, 3.0, 1.0, 0.0],
1109 [2.0, 1.0, 0.0, 0.0],
1110 [0.0, 1.0, 4.0, 1.0],
1111 [0.0, 0.0, 1.0, 5.0],
1112 ]);
1113 assert_eq!(m.det_sign_exact().unwrap(), -1);
1114 }
1115
1116 #[test]
1117 fn det_sign_exact_subnormal_entries() {
1118 let tiny = 5e-324_f64; assert!(tiny.is_subnormal());
1121
1122 let m = Matrix::<2>::from_rows([[tiny, 0.0], [0.0, tiny]]);
1123 assert_eq!(m.det_sign_exact().unwrap(), 1);
1125 }
1126
1127 #[test]
1128 fn det_sign_exact_returns_err_on_nan() {
1129 assert_eq!(
1130 Matrix::<2>::try_from_rows([[f64::NAN, 0.0], [0.0, 1.0]]),
1131 Err(LaError::NonFinite {
1132 row: Some(0),
1133 col: 0
1134 })
1135 );
1136 }
1137
1138 #[test]
1139 fn det_sign_exact_returns_err_on_infinity() {
1140 assert_eq!(
1141 Matrix::<2>::try_from_rows([[f64::INFINITY, 0.0], [0.0, 1.0]]),
1142 Err(LaError::NonFinite {
1143 row: Some(0),
1144 col: 0
1145 })
1146 );
1147 }
1148
1149 #[test]
1150 fn det_sign_exact_returns_err_on_nan_5x5() {
1151 let mut m = Matrix::<5>::identity();
1153 assert_eq!(
1154 m.set(2, 3, f64::NAN),
1155 Err(LaError::NonFinite {
1156 row: Some(2),
1157 col: 3
1158 })
1159 );
1160 }
1161
1162 #[test]
1163 fn det_sign_exact_returns_err_on_infinity_5x5() {
1164 let mut m = Matrix::<5>::identity();
1165 assert_eq!(
1166 m.set(0, 0, f64::INFINITY),
1167 Err(LaError::NonFinite {
1168 row: Some(0),
1169 col: 0
1170 })
1171 );
1172 }
1173
1174 #[test]
1175 fn det_sign_exact_pivot_needed_5x5() {
1176 let m = Matrix::<5>::from_rows([
1179 [0.0, 1.0, 0.0, 0.0, 0.0],
1180 [1.0, 0.0, 0.0, 0.0, 0.0],
1181 [0.0, 0.0, 1.0, 0.0, 0.0],
1182 [0.0, 0.0, 0.0, 1.0, 0.0],
1183 [0.0, 0.0, 0.0, 0.0, 1.0],
1184 ]);
1185 assert_eq!(m.det_sign_exact().unwrap(), -1);
1186 }
1187
1188 #[test]
1189 fn det_sign_exact_5x5_known() {
1190 let m = Matrix::<5>::from_rows([
1192 [0.0, 1.0, 0.0, 0.0, 0.0],
1193 [1.0, 0.0, 0.0, 0.0, 0.0],
1194 [0.0, 0.0, 0.0, 1.0, 0.0],
1195 [0.0, 0.0, 1.0, 0.0, 0.0],
1196 [0.0, 0.0, 0.0, 0.0, 1.0],
1197 ]);
1198 assert_eq!(m.det_sign_exact().unwrap(), 1);
1200 }
1201
1202 #[test]
1207 fn det_errbound_d0_is_zero() {
1208 assert_eq!(Matrix::<0>::zero().det_errbound(), Ok(Some(0.0)));
1209 }
1210
1211 #[test]
1212 fn det_errbound_d1_is_zero() {
1213 assert_eq!(
1214 Matrix::<1>::from_rows([[42.0]]).det_errbound(),
1215 Ok(Some(0.0))
1216 );
1217 }
1218
1219 #[test]
1220 fn det_errbound_d2_positive() {
1221 let m = Matrix::<2>::from_rows([[1.0, 2.0], [3.0, 4.0]]);
1222 let bound = m.det_errbound().unwrap().unwrap();
1223 assert!(bound > 0.0);
1224 assert!(crate::ERR_COEFF_2.mul_add(-10.0, bound).abs() < 1e-30);
1226 }
1227
1228 #[test]
1229 fn det_errbound_d3_positive() {
1230 let m = Matrix::<3>::identity();
1231 let bound = m.det_errbound().unwrap().unwrap();
1232 assert!(bound > 0.0);
1233 }
1234
1235 #[test]
1236 fn det_errbound_d3_non_identity() {
1237 let m = Matrix::<3>::from_rows([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [7.0, 8.0, 10.0]]);
1239 let bound = m.det_errbound().unwrap().unwrap();
1240 assert!(bound > 0.0);
1241 }
1242
1243 #[test]
1244 fn det_errbound_d4_positive() {
1245 let m = Matrix::<4>::identity();
1246 let bound = m.det_errbound().unwrap().unwrap();
1247 assert!(bound > 0.0);
1248 }
1249
1250 #[test]
1251 fn det_errbound_d4_non_identity() {
1252 let m = Matrix::<4>::from_rows([
1254 [1.0, 0.0, 0.0, 0.0],
1255 [0.0, 2.0, 0.0, 0.0],
1256 [0.0, 0.0, 3.0, 0.0],
1257 [0.0, 0.0, 0.0, 4.0],
1258 ]);
1259 let bound = m.det_errbound().unwrap().unwrap();
1260 assert!(bound > 0.0);
1261 }
1262
1263 #[test]
1264 fn det_errbound_d5_is_none() {
1265 assert_eq!(Matrix::<5>::identity().det_errbound(), Ok(None));
1266 }
1267
1268 #[test]
1273 fn f64_decompose_zero() {
1274 assert!(f64_decompose(0.0).unwrap().is_none());
1275 assert!(f64_decompose(-0.0).unwrap().is_none());
1276 }
1277
1278 #[test]
1279 fn f64_decompose_one() {
1280 let (mant, exp, neg) = f64_decompose(1.0).unwrap().unwrap();
1281 assert_eq!(mant.get(), 1);
1282 assert_eq!(exp, 0);
1283 assert!(!neg);
1284 }
1285
1286 #[test]
1287 fn f64_decompose_negative() {
1288 let (mant, exp, neg) = f64_decompose(-3.5).unwrap().unwrap();
1289 assert_eq!(mant.get(), 7);
1291 assert_eq!(exp, -1);
1292 assert!(neg);
1293 }
1294
1295 #[test]
1296 fn f64_decompose_subnormal() {
1297 let tiny = 5e-324_f64;
1298 assert!(tiny.is_subnormal());
1299 let (mant, exp, neg) = f64_decompose(tiny).unwrap().unwrap();
1300 assert_eq!(mant.get(), 1);
1301 assert_eq!(exp, -1074);
1302 assert!(!neg);
1303 }
1304
1305 #[test]
1306 fn f64_decompose_power_of_two() {
1307 let (mant, exp, neg) = f64_decompose(1024.0).unwrap().unwrap();
1308 assert_eq!(mant.get(), 1);
1309 assert_eq!(exp, 10); assert!(!neg);
1311 }
1312
1313 #[test]
1314 fn f64_decompose_rejects_nan() {
1315 assert_eq!(
1316 f64_decompose(f64::NAN),
1317 Err(LaError::NonFinite { row: None, col: 0 })
1318 );
1319 }
1320
1321 #[test]
1322 fn component_to_bigint_distinguishes_zero_from_nonzero_mantissa() {
1323 assert_eq!(
1324 component_to_bigint(Component::default(), -10),
1325 BigInt::from(0)
1326 );
1327
1328 let positive = Component::NonZero {
1329 mantissa: NonZeroU64::new(3).unwrap(),
1330 exponent: 4,
1331 is_negative: false,
1332 };
1333 assert_eq!(component_to_bigint(positive, 1), BigInt::from(24));
1334
1335 let negative = Component::NonZero {
1336 mantissa: NonZeroU64::new(5).unwrap(),
1337 exponent: 3,
1338 is_negative: true,
1339 };
1340 assert_eq!(component_to_bigint(negative, 1), BigInt::from(-20));
1341 }
1342
1343 #[test]
1348 fn bareiss_det_int_d0() {
1349 let m = FiniteMatrix::new_unchecked(Matrix::<0>::zero());
1350 let (det, exp) = bareiss_det_int_finite(&m).unwrap();
1351 assert_eq!(det, BigInt::from(1));
1352 assert_eq!(exp, 0);
1353 }
1354
1355 #[test]
1361 fn bareiss_det_int_d1_cases() {
1362 let cases: &[(f64, i64, i32)] = &[
1363 (7.0, 7, 0), (0.0, 0, 0), (-3.5, -7, -1), (0.5, 1, -1), ];
1369 for &(input, expected_det_int, expected_exp) in cases {
1370 let m = FiniteMatrix::new_unchecked(Matrix::<1>::from_rows([[input]]));
1371 let (det, exp) = bareiss_det_int_finite(&m).unwrap();
1372 assert_eq!(
1373 det,
1374 BigInt::from(expected_det_int),
1375 "det_int for input={input}"
1376 );
1377 assert_eq!(exp, expected_exp, "exp for input={input}");
1378 }
1379 }
1380
1381 #[test]
1382 fn bareiss_det_int_d2_known() {
1383 let m = FiniteMatrix::new_unchecked(Matrix::<2>::from_rows([[1.0, 2.0], [3.0, 4.0]]));
1385 let (det_int, total_exp) = bareiss_det_int_finite(&m).unwrap();
1386 let det = bigint_exp_to_bigrational(det_int, total_exp);
1388 assert_eq!(det, BigRational::from_integer(BigInt::from(-2)));
1389 }
1390
1391 #[test]
1392 fn bareiss_det_int_all_zeros() {
1393 let m = FiniteMatrix::new_unchecked(Matrix::<3>::zero());
1394 let (det, _) = bareiss_det_int_finite(&m).unwrap();
1395 assert_eq!(det, BigInt::from(0));
1396 }
1397
1398 #[test]
1399 fn bareiss_det_int_sign_matches_det_sign_exact() {
1400 let m = FiniteMatrix::new_unchecked(Matrix::<3>::from_rows([
1402 [0.0, 1.0, 0.0],
1403 [1.0, 0.0, 0.0],
1404 [0.0, 0.0, 1.0],
1405 ]));
1406 let (det_int, _) = bareiss_det_int_finite(&m).unwrap();
1407 assert_eq!(det_int.sign(), Sign::Minus); }
1409
1410 #[test]
1411 fn bareiss_det_int_fractional_entries() {
1412 let m = FiniteMatrix::new_unchecked(Matrix::<2>::from_rows([[0.5, 0.25], [1.0, 1.0]]));
1415 let (det_int, total_exp) = bareiss_det_int_finite(&m).unwrap();
1416 let det = bigint_exp_to_bigrational(det_int, total_exp);
1417 assert_eq!(det, BigRational::new(BigInt::from(1), BigInt::from(4)));
1418 }
1419
1420 #[test]
1421 fn bareiss_det_int_d3_with_pivoting() {
1422 let m = FiniteMatrix::new_unchecked(Matrix::<3>::from_rows([
1424 [0.0, 1.0, 0.0],
1425 [1.0, 0.0, 0.0],
1426 [0.0, 0.0, 1.0],
1427 ]));
1428 let (det_int, total_exp) = bareiss_det_int_finite(&m).unwrap();
1429 let det = bigint_exp_to_bigrational(det_int, total_exp);
1430 assert_eq!(det, BigRational::from_integer(BigInt::from(-1)));
1431 }
1432
1433 macro_rules! gen_bareiss_det_int_identity_tests {
1435 ($d:literal) => {
1436 paste! {
1437 #[test]
1438 fn [<bareiss_det_int_identity_ $d d>]() {
1439 let m = FiniteMatrix::new_unchecked(Matrix::<$d>::identity());
1440 let (det_int, total_exp) = bareiss_det_int_finite(&m).unwrap();
1441 let det = bigint_exp_to_bigrational(det_int, total_exp);
1442 assert_eq!(det, BigRational::from_integer(BigInt::from(1)));
1443 }
1444 }
1445 };
1446 }
1447
1448 gen_bareiss_det_int_identity_tests!(2);
1449 gen_bareiss_det_int_identity_tests!(3);
1450 gen_bareiss_det_int_identity_tests!(4);
1451 gen_bareiss_det_int_identity_tests!(5);
1452
1453 #[test]
1458 fn bigint_exp_to_bigrational_zero() {
1459 let r = bigint_exp_to_bigrational(BigInt::from(0), -50);
1460 assert_eq!(r, BigRational::from_integer(BigInt::from(0)));
1461 }
1462
1463 #[test]
1464 fn bigint_exp_to_bigrational_positive_exp() {
1465 let r = bigint_exp_to_bigrational(BigInt::from(3), 2);
1467 assert_eq!(r, BigRational::from_integer(BigInt::from(12)));
1468 }
1469
1470 #[test]
1471 fn bigint_exp_to_bigrational_negative_exp_reduced() {
1472 let r = bigint_exp_to_bigrational(BigInt::from(6), -2);
1474 assert_eq!(*r.numer(), BigInt::from(3));
1475 assert_eq!(*r.denom(), BigInt::from(2));
1476 }
1477
1478 #[test]
1479 fn bigint_exp_to_bigrational_negative_exp_reduces_to_integer() {
1480 let r = bigint_exp_to_bigrational(BigInt::from(8), -3);
1482 assert_eq!(r, BigRational::from_integer(BigInt::from(1)));
1483 }
1484
1485 #[test]
1486 fn bigint_exp_to_bigrational_negative_exp_already_odd() {
1487 let r = bigint_exp_to_bigrational(BigInt::from(3), -2);
1489 assert_eq!(*r.numer(), BigInt::from(3));
1490 assert_eq!(*r.denom(), BigInt::from(4));
1491 }
1492
1493 #[test]
1494 fn bigint_exp_to_bigrational_negative_value() {
1495 let r = bigint_exp_to_bigrational(BigInt::from(-5), 1);
1497 assert_eq!(r, BigRational::from_integer(BigInt::from(-10)));
1498 }
1499
1500 #[test]
1501 fn bigint_exp_to_bigrational_negative_value_with_denominator() {
1502 let r = bigint_exp_to_bigrational(BigInt::from(-3), -2);
1504 assert_eq!(*r.numer(), BigInt::from(-3));
1505 assert_eq!(*r.denom(), BigInt::from(4));
1506 }
1507
1508 #[test]
1513 fn bareiss_det_d0_is_one() {
1514 let det = Matrix::<0>::zero().det_exact().unwrap();
1515 assert_eq!(det, BigRational::from_integer(BigInt::from(1)));
1516 }
1517
1518 #[test]
1519 fn bareiss_det_d1_returns_entry() {
1520 let det = Matrix::<1>::from_rows([[7.0]]).det_exact().unwrap();
1521 assert_eq!(det, f64_to_bigrational(7.0));
1522 }
1523
1524 #[test]
1525 fn bareiss_det_d3_with_pivoting() {
1526 let m = Matrix::<3>::from_rows([[0.0, 1.0, 0.0], [1.0, 0.0, 0.0], [0.0, 0.0, 1.0]]);
1528 let det = m.det_exact().unwrap();
1529 assert_eq!(det, BigRational::from_integer(BigInt::from(-1)));
1531 }
1532
1533 #[test]
1534 fn bareiss_det_singular_all_zeros_in_column() {
1535 let m = Matrix::<3>::from_rows([[1.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0, 1.0]]);
1537 let det = m.det_exact().unwrap();
1538 assert_eq!(det, BigRational::from_integer(BigInt::from(0)));
1539 }
1540
1541 #[test]
1542 fn det_sign_exact_overflow_determinant_finite_entries() {
1543 let big = f64::MAX / 2.0;
1547 assert!(big.is_finite());
1548 let m = Matrix::<3>::from_rows([[0.0, 0.0, 1.0], [big, 0.0, 1.0], [0.0, big, 1.0]]);
1549 assert_eq!(m.det_sign_exact().unwrap(), 1);
1551 }
1552
1553 #[test]
1558 fn det_exact_d0_is_one() {
1559 let det = Matrix::<0>::zero().det_exact().unwrap();
1560 assert_eq!(det, BigRational::from_integer(BigInt::from(1)));
1561 }
1562
1563 #[test]
1564 fn det_exact_known_2x2() {
1565 let m = Matrix::<2>::from_rows([[1.0, 2.0], [3.0, 4.0]]);
1567 let det = m.det_exact().unwrap();
1568 assert_eq!(det, BigRational::from_integer(BigInt::from(-2)));
1569 }
1570
1571 #[test]
1572 fn det_exact_singular_returns_zero() {
1573 let m = Matrix::<3>::from_rows([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [7.0, 8.0, 9.0]]);
1575 let det = m.det_exact().unwrap();
1576 assert_eq!(det, BigRational::from_integer(BigInt::from(0)));
1577 }
1578
1579 #[test]
1580 fn det_exact_near_singular_perturbation() {
1581 let perturbation = f64::from_bits(0x3CD0_0000_0000_0000); let m = Matrix::<3>::from_rows([
1584 [1.0 + perturbation, 2.0, 3.0],
1585 [4.0, 5.0, 6.0],
1586 [7.0, 8.0, 9.0],
1587 ]);
1588 let det = m.det_exact().unwrap();
1589 let expected = BigRational::new(BigInt::from(-3), BigInt::from(1u64 << 50));
1591 assert_eq!(det, expected);
1592 }
1593
1594 #[test]
1595 fn det_exact_5x5_permutation() {
1596 let m = Matrix::<5>::from_rows([
1598 [0.0, 1.0, 0.0, 0.0, 0.0],
1599 [1.0, 0.0, 0.0, 0.0, 0.0],
1600 [0.0, 0.0, 1.0, 0.0, 0.0],
1601 [0.0, 0.0, 0.0, 1.0, 0.0],
1602 [0.0, 0.0, 0.0, 0.0, 1.0],
1603 ]);
1604 let det = m.det_exact().unwrap();
1605 assert_eq!(det, BigRational::from_integer(BigInt::from(-1)));
1606 }
1607
1608 #[test]
1613 fn det_exact_f64_known_2x2() {
1614 let m = Matrix::<2>::from_rows([[1.0, 2.0], [3.0, 4.0]]);
1615 let det = m.det_exact_f64().unwrap();
1616 assert!((det - (-2.0)).abs() <= f64::EPSILON);
1617 }
1618
1619 #[test]
1620 fn det_exact_f64_overflow_returns_err() {
1621 let big = f64::MAX / 2.0;
1623 let m = Matrix::<3>::from_rows([[0.0, 0.0, 1.0], [big, 0.0, 1.0], [0.0, big, 1.0]]);
1624 assert_eq!(m.det_exact_f64(), Err(LaError::Overflow { index: None }));
1626 }
1627
1628 fn arbitrary_rhs<const D: usize>() -> Vector<D> {
1634 let values = [1.0, -2.5, 3.0, 0.25, -4.0];
1635 let mut arr = [0.0f64; D];
1636 for (dst, src) in arr.iter_mut().zip(values.iter()) {
1637 *dst = *src;
1638 }
1639 Vector::<D>::new(arr)
1640 }
1641
1642 macro_rules! gen_solve_exact_tests {
1643 ($d:literal) => {
1644 paste! {
1645 #[test]
1646 fn [<solve_exact_identity_ $d d>]() {
1647 let a = Matrix::<$d>::identity();
1648 let b = arbitrary_rhs::<$d>();
1649 let x = a.solve_exact(b).unwrap();
1650 for (i, xi) in x.iter().enumerate() {
1651 assert_eq!(*xi, f64_to_bigrational(b.data[i]));
1652 }
1653 }
1654
1655 #[test]
1656 fn [<solve_exact_singular_ $d d>]() {
1657 let a = Matrix::<$d>::zero();
1659 let b = arbitrary_rhs::<$d>();
1660 assert_eq!(a.solve_exact(b), Err(LaError::Singular { pivot_col: 0 }));
1661 }
1662 }
1663 };
1664 }
1665
1666 gen_solve_exact_tests!(2);
1667 gen_solve_exact_tests!(3);
1668 gen_solve_exact_tests!(4);
1669 gen_solve_exact_tests!(5);
1670
1671 macro_rules! gen_solve_exact_f64_tests {
1672 ($d:literal) => {
1673 paste! {
1674 #[test]
1675 fn [<solve_exact_f64_identity_ $d d>]() {
1676 let a = Matrix::<$d>::identity();
1677 let b = arbitrary_rhs::<$d>();
1678 let x = a.solve_exact_f64(b).unwrap().into_array();
1679 for i in 0..$d {
1680 assert!((x[i] - b.data[i]).abs() <= f64::EPSILON);
1681 }
1682 }
1683 }
1684 };
1685 }
1686
1687 gen_solve_exact_f64_tests!(2);
1688 gen_solve_exact_f64_tests!(3);
1689 gen_solve_exact_f64_tests!(4);
1690 gen_solve_exact_f64_tests!(5);
1691
1692 macro_rules! gen_solve_exact_f64_agrees_with_lu {
1695 ($d:literal) => {
1696 paste! {
1697 #[test]
1698 #[allow(clippy::cast_precision_loss)]
1699 fn [<solve_exact_f64_agrees_with_lu_ $d d>]() {
1700 let mut rows = [[0.0f64; $d]; $d];
1702 for r in 0..$d {
1703 for c in 0..$d {
1704 rows[r][c] = if r == c {
1705 (r as f64) + f64::from($d) + 1.0
1706 } else {
1707 0.1 / ((r + c + 1) as f64)
1708 };
1709 }
1710 }
1711 let a = Matrix::<$d>::from_rows(rows);
1712 let b = arbitrary_rhs::<$d>();
1713 let exact = a.solve_exact_f64(b).unwrap().into_array();
1714 let lu_sol = a.lu(DEFAULT_PIVOT_TOL).unwrap()
1715 .solve_vec(b).unwrap().into_array();
1716 for i in 0..$d {
1717 let eps = lu_sol[i].abs().mul_add(1e-12, 1e-12);
1718 assert!((exact[i] - lu_sol[i]).abs() <= eps);
1719 }
1720 }
1721 }
1722 };
1723 }
1724
1725 gen_solve_exact_f64_agrees_with_lu!(2);
1726 gen_solve_exact_f64_agrees_with_lu!(3);
1727 gen_solve_exact_f64_agrees_with_lu!(4);
1728 gen_solve_exact_f64_agrees_with_lu!(5);
1729
1730 macro_rules! gen_solve_exact_roundtrip_tests {
1736 ($d:literal) => {
1737 paste! {
1738 #[test]
1739 #[allow(clippy::cast_precision_loss)]
1740 fn [<solve_exact_roundtrip_ $d d>]() {
1741 let mut rows = [[0.0f64; $d]; $d];
1744 for r in 0..$d {
1745 for c in 0..$d {
1746 rows[r][c] = if r == c {
1747 f64::from($d) + 1.0
1748 } else {
1749 1.0
1750 };
1751 }
1752 }
1753 let a = Matrix::<$d>::from_rows(rows);
1754
1755 let mut x0 = [0.0f64; $d];
1757 for i in 0..$d {
1758 x0[i] = (i + 1) as f64;
1759 }
1760
1761 let mut b_arr = [0.0f64; $d];
1764 for r in 0..$d {
1765 let mut sum = 0.0_f64;
1766 for c in 0..$d {
1767 sum = rows[r][c].mul_add(x0[c], sum);
1768 }
1769 b_arr[r] = sum;
1770 }
1771 let b = Vector::<$d>::new(b_arr);
1772
1773 let x = a.solve_exact(b).unwrap();
1774 for i in 0..$d {
1775 assert_eq!(x[i], f64_to_bigrational(x0[i]));
1776 }
1777 }
1778 }
1779 };
1780 }
1781
1782 gen_solve_exact_roundtrip_tests!(2);
1783 gen_solve_exact_roundtrip_tests!(3);
1784 gen_solve_exact_roundtrip_tests!(4);
1785 gen_solve_exact_roundtrip_tests!(5);
1786
1787 #[test]
1792 fn solve_exact_d0_returns_empty() {
1793 let a = Matrix::<0>::zero();
1794 let b = Vector::<0>::zero();
1795 let x = a.solve_exact(b).unwrap();
1796 assert!(x.is_empty());
1797 }
1798
1799 #[test]
1800 fn solve_exact_known_2x2() {
1801 let a = Matrix::<2>::from_rows([[1.0, 2.0], [3.0, 4.0]]);
1803 let b = Vector::<2>::new([5.0, 11.0]);
1804 let x = a.solve_exact(b).unwrap();
1805 assert_eq!(x[0], BigRational::from_integer(BigInt::from(1)));
1806 assert_eq!(x[1], BigRational::from_integer(BigInt::from(2)));
1807 }
1808
1809 #[test]
1810 fn solve_exact_pivoting_needed() {
1811 let a = Matrix::<3>::from_rows([[0.0, 1.0, 0.0], [1.0, 0.0, 0.0], [0.0, 0.0, 1.0]]);
1813 let b = Vector::<3>::new([2.0, 3.0, 4.0]);
1814 let x = a.solve_exact(b).unwrap();
1815 assert_eq!(x[0], f64_to_bigrational(3.0));
1817 assert_eq!(x[1], f64_to_bigrational(2.0));
1818 assert_eq!(x[2], f64_to_bigrational(4.0));
1819 }
1820
1821 #[test]
1822 fn solve_exact_fractional_result() {
1823 let a = Matrix::<2>::from_rows([[2.0, 1.0], [1.0, 3.0]]);
1825 let b = Vector::<2>::new([1.0, 1.0]);
1826 let x = a.solve_exact(b).unwrap();
1827 assert_eq!(x[0], BigRational::new(BigInt::from(2), BigInt::from(5)));
1828 assert_eq!(x[1], BigRational::new(BigInt::from(1), BigInt::from(5)));
1829 }
1830
1831 #[test]
1832 fn solve_exact_singular_duplicate_rows() {
1833 let a = Matrix::<3>::from_rows([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [1.0, 2.0, 3.0]]);
1834 let b = Vector::<3>::new([1.0, 2.0, 3.0]);
1835 assert_matches!(a.solve_exact(b), Err(LaError::Singular { .. }));
1836 }
1837
1838 #[test]
1839 fn solve_exact_5x5_permutation() {
1840 let a = Matrix::<5>::from_rows([
1842 [0.0, 1.0, 0.0, 0.0, 0.0],
1843 [1.0, 0.0, 0.0, 0.0, 0.0],
1844 [0.0, 0.0, 1.0, 0.0, 0.0],
1845 [0.0, 0.0, 0.0, 1.0, 0.0],
1846 [0.0, 0.0, 0.0, 0.0, 1.0],
1847 ]);
1848 let b = Vector::<5>::new([10.0, 20.0, 30.0, 40.0, 50.0]);
1849 let x = a.solve_exact(b).unwrap();
1850 assert_eq!(x[0], f64_to_bigrational(20.0));
1851 assert_eq!(x[1], f64_to_bigrational(10.0));
1852 assert_eq!(x[2], f64_to_bigrational(30.0));
1853 assert_eq!(x[3], f64_to_bigrational(40.0));
1854 assert_eq!(x[4], f64_to_bigrational(50.0));
1855 }
1856
1857 macro_rules! gen_solve_exact_large_finite_entries_tests {
1863 ($d:literal) => {
1864 paste! {
1865 #[test]
1866 fn [<solve_exact_large_finite_entries_ $d d>]() {
1867 let big = f64::MAX / 2.0;
1868 assert!(big.is_finite());
1869 let mut rows = [[0.0f64; $d]; $d];
1871 for i in 0..$d {
1872 rows[i][i] = big;
1873 }
1874 let a = Matrix::<$d>::from_rows(rows);
1875 let mut b_arr = [big; $d];
1877 b_arr[$d - 1] = 0.0;
1878 let b = Vector::<$d>::new(b_arr);
1879 let x = a.solve_exact(b).unwrap();
1880 for i in 0..($d - 1) {
1881 assert_eq!(x[i], BigRational::from_integer(BigInt::from(1)));
1882 }
1883 assert_eq!(x[$d - 1], BigRational::from_integer(BigInt::from(0)));
1884 }
1885 }
1886 };
1887 }
1888
1889 gen_solve_exact_large_finite_entries_tests!(2);
1890 gen_solve_exact_large_finite_entries_tests!(3);
1891 gen_solve_exact_large_finite_entries_tests!(4);
1892 gen_solve_exact_large_finite_entries_tests!(5);
1893
1894 macro_rules! gen_solve_exact_mixed_magnitude_entries_tests {
1900 ($d:literal) => {
1901 paste! {
1902 #[test]
1903 fn [<solve_exact_mixed_magnitude_entries_ $d d>]() {
1904 let tiny = f64::MIN_POSITIVE; let huge = 1.0e100_f64;
1906 let mut rows = [[0.0f64; $d]; $d];
1908 let mut b_arr = [0.0f64; $d];
1909 for i in 0..$d {
1910 let val = if i % 2 == 0 { huge } else { tiny };
1911 rows[i][i] = val;
1912 b_arr[i] = val;
1913 }
1914 let a = Matrix::<$d>::from_rows(rows);
1915 let b = Vector::<$d>::new(b_arr);
1916 let x = a.solve_exact(b).unwrap();
1917 for i in 0..$d {
1918 assert_eq!(x[i], BigRational::from_integer(BigInt::from(1)));
1919 }
1920 }
1921 }
1922 };
1923 }
1924
1925 gen_solve_exact_mixed_magnitude_entries_tests!(2);
1926 gen_solve_exact_mixed_magnitude_entries_tests!(3);
1927 gen_solve_exact_mixed_magnitude_entries_tests!(4);
1928 gen_solve_exact_mixed_magnitude_entries_tests!(5);
1929
1930 macro_rules! gen_solve_exact_subnormal_rhs_tests {
1936 ($d:literal) => {
1937 paste! {
1938 #[test]
1939 #[allow(clippy::cast_precision_loss)]
1940 fn [<solve_exact_subnormal_rhs_ $d d>]() {
1941 let tiny = 5e-324_f64; assert!(tiny.is_subnormal());
1943 let a = Matrix::<$d>::identity();
1944 let mut b_arr = [0.0f64; $d];
1946 for i in 0..$d {
1947 b_arr[i] = (i + 1) as f64 * tiny;
1948 assert!(b_arr[i].is_subnormal());
1949 }
1950 let b = Vector::<$d>::new(b_arr);
1951 let x = a.solve_exact(b).unwrap();
1952 for i in 0..$d {
1953 assert_eq!(x[i], f64_to_bigrational((i + 1) as f64 * tiny));
1954 }
1955 }
1956 }
1957 };
1958 }
1959
1960 gen_solve_exact_subnormal_rhs_tests!(2);
1961 gen_solve_exact_subnormal_rhs_tests!(3);
1962 gen_solve_exact_subnormal_rhs_tests!(4);
1963 gen_solve_exact_subnormal_rhs_tests!(5);
1964
1965 macro_rules! gen_solve_exact_pivot_swap_fractional_tests {
1975 ($d:literal) => {
1976 paste! {
1977 #[test]
1978 #[allow(clippy::cast_precision_loss)]
1979 #[allow(clippy::reversed_empty_ranges)]
1982 fn [<solve_exact_pivot_swap_with_fractional_result_ $d d>]() {
1983 let mut rows = [[0.0f64; $d]; $d];
1986 rows[0][1] = 1.0;
1987 rows[1][0] = 2.0;
1988 rows[1][1] = 1.0;
1989 for i in 2..$d {
1991 rows[i][i] = 1.0;
1992 }
1993 let a = Matrix::<$d>::from_rows(rows);
1994 let mut b_arr = [0.0f64; $d];
1997 b_arr[0] = 3.0;
1998 b_arr[1] = 4.0;
1999 for i in 2..$d {
2000 b_arr[i] = (i + 10) as f64;
2001 }
2002 let b = Vector::<$d>::new(b_arr);
2003 let x = a.solve_exact(b).unwrap();
2004 assert_eq!(x[0], BigRational::new(BigInt::from(1), BigInt::from(2)));
2005 assert_eq!(x[1], BigRational::from_integer(BigInt::from(3)));
2006 for i in 2..$d {
2007 assert_eq!(x[i], f64_to_bigrational((i + 10) as f64));
2008 }
2009 }
2010 }
2011 };
2012 }
2013
2014 gen_solve_exact_pivot_swap_fractional_tests!(2);
2015 gen_solve_exact_pivot_swap_fractional_tests!(3);
2016 gen_solve_exact_pivot_swap_fractional_tests!(4);
2017 gen_solve_exact_pivot_swap_fractional_tests!(5);
2018
2019 macro_rules! gen_solve_exact_mid_pivot_swap_tests {
2028 ($d:literal) => {
2029 paste! {
2030 #[test]
2031 #[allow(clippy::cast_precision_loss)]
2032 #[allow(clippy::reversed_empty_ranges)]
2035 fn [<solve_exact_mid_pivot_swap_ $d d>]() {
2036 let mut rows = [[0.0f64; $d]; $d];
2037 rows[0][0] = 1.0; rows[0][1] = 2.0; rows[0][2] = 3.0;
2038 rows[1][2] = 4.0;
2040 rows[2][1] = 5.0; rows[2][2] = 6.0;
2041 for i in 3..$d {
2043 rows[i][i] = 1.0;
2044 }
2045 let a = Matrix::<$d>::from_rows(rows);
2046 let mut b_arr = [0.0f64; $d];
2047 b_arr[0] = 6.0;
2048 b_arr[1] = 7.0;
2049 b_arr[2] = 8.0;
2050 for i in 3..$d {
2051 b_arr[i] = (i + 10) as f64;
2052 }
2053 let b = Vector::<$d>::new(b_arr);
2054 let x = a.solve_exact(b).unwrap();
2055 assert_eq!(x[0], BigRational::new(BigInt::from(7), BigInt::from(4)));
2057 assert_eq!(x[1], BigRational::new(BigInt::from(-1), BigInt::from(2)));
2058 assert_eq!(x[2], BigRational::new(BigInt::from(7), BigInt::from(4)));
2059 for i in 3..$d {
2060 assert_eq!(x[i], f64_to_bigrational((i + 10) as f64));
2061 }
2062 }
2063 }
2064 };
2065 }
2066
2067 gen_solve_exact_mid_pivot_swap_tests!(3);
2068 gen_solve_exact_mid_pivot_swap_tests!(4);
2069 gen_solve_exact_mid_pivot_swap_tests!(5);
2070
2071 macro_rules! gen_solve_exact_singular_rank_deficient_tests {
2079 ($d:literal) => {
2080 paste! {
2081 #[test]
2082 fn [<solve_exact_singular_rank_deficient_ $d d>]() {
2083 let mut rows = [[0.0f64; $d]; $d];
2084 for i in 0..($d - 1) {
2085 rows[i][i] = 1.0;
2086 rows[$d - 1][i] = 1.0;
2087 }
2088 let a = Matrix::<$d>::from_rows(rows);
2090 let b = Vector::<$d>::new([1.0; $d]);
2091 assert_eq!(
2092 a.solve_exact(b),
2093 Err(LaError::Singular { pivot_col: $d - 1 })
2094 );
2095 }
2096 }
2097 };
2098 }
2099
2100 gen_solve_exact_singular_rank_deficient_tests!(2);
2101 gen_solve_exact_singular_rank_deficient_tests!(3);
2102 gen_solve_exact_singular_rank_deficient_tests!(4);
2103 gen_solve_exact_singular_rank_deficient_tests!(5);
2104
2105 macro_rules! gen_solve_exact_zero_rhs_tests {
2111 ($d:literal) => {
2112 paste! {
2113 #[test]
2114 fn [<solve_exact_zero_rhs_ $d d>]() {
2115 let mut rows = [[1.0f64; $d]; $d];
2117 for i in 0..$d {
2118 rows[i][i] = f64::from($d) + 1.0;
2119 }
2120 let a = Matrix::<$d>::from_rows(rows);
2121 let b = Vector::<$d>::zero();
2122 let x = a.solve_exact(b).unwrap();
2123 for xi in &x {
2124 assert_eq!(*xi, BigRational::from_integer(BigInt::from(0)));
2125 }
2126 }
2127 }
2128 };
2129 }
2130
2131 gen_solve_exact_zero_rhs_tests!(2);
2132 gen_solve_exact_zero_rhs_tests!(3);
2133 gen_solve_exact_zero_rhs_tests!(4);
2134 gen_solve_exact_zero_rhs_tests!(5);
2135
2136 fn bigrational_matvec<const D: usize>(a: &Matrix<D>, x: &[BigRational; D]) -> [BigRational; D] {
2149 from_fn(|i| {
2150 let mut sum = BigRational::from_integer(BigInt::from(0));
2151 for (aij, xj) in a.rows[i].iter().zip(x.iter()) {
2152 sum += f64_to_bigrational(*aij) * xj;
2153 }
2154 sum
2155 })
2156 }
2157
2158 #[test]
2166 fn solve_exact_near_singular_3x3_integer_x0() {
2167 let perturbation = f64::from_bits(0x3CD0_0000_0000_0000); let a = Matrix::<3>::from_rows([
2169 [1.0 + perturbation, 2.0, 3.0],
2170 [4.0, 5.0, 6.0],
2171 [7.0, 8.0, 9.0],
2172 ]);
2173 let b = Vector::<3>::new([6.0 + perturbation, 15.0, 24.0]);
2174 let x = a.solve_exact(b).unwrap();
2175 let one = BigRational::from_integer(BigInt::from(1));
2176 assert_eq!(x[0], one);
2177 assert_eq!(x[1], one);
2178 assert_eq!(x[2], one);
2179 }
2180
2181 #[test]
2188 fn solve_exact_large_entries_3x3_unit_vector() {
2189 let big = f64::MAX / 2.0;
2190 assert!(big.is_finite());
2191 let a = Matrix::<3>::from_rows([[big, 1.0, 1.0], [1.0, big, 1.0], [1.0, 1.0, big]]);
2192 let b = Vector::<3>::new([big, 1.0, 1.0]);
2193 let x = a.solve_exact(b).unwrap();
2194 let zero = BigRational::from_integer(BigInt::from(0));
2195 let one = BigRational::from_integer(BigInt::from(1));
2196 assert_eq!(x[0], one);
2197 assert_eq!(x[1], zero);
2198 assert_eq!(x[2], zero);
2199 }
2200
2201 #[test]
2207 fn det_sign_exact_large_entries_3x3_positive() {
2208 let big = f64::MAX / 2.0;
2209 let a = Matrix::<3>::from_rows([[big, 1.0, 1.0], [1.0, big, 1.0], [1.0, 1.0, big]]);
2210 assert_matches!(a.det_direct(), Err(LaError::NonFinite { row: None, .. }));
2213 assert_eq!(a.det_sign_exact().unwrap(), 1);
2214 assert!(a.det_exact().unwrap().is_positive());
2218 assert_eq!(a.det_exact_f64(), Err(LaError::Overflow { index: None }));
2219 }
2220
2221 macro_rules! gen_det_sign_exact_hilbert_positive_tests {
2229 ($d:literal) => {
2230 paste! {
2231 #[test]
2232 #[allow(clippy::cast_precision_loss)]
2233 fn [<det_sign_exact_hilbert_positive_ $d d>]() {
2234 let mut rows = [[0.0f64; $d]; $d];
2235 for r in 0..$d {
2236 for c in 0..$d {
2237 rows[r][c] = 1.0 / ((r + c + 1) as f64);
2238 }
2239 }
2240 let h = Matrix::<$d>::from_rows(rows);
2241 assert_eq!(h.det_sign_exact().unwrap(), 1);
2242 }
2243 }
2244 };
2245 }
2246
2247 gen_det_sign_exact_hilbert_positive_tests!(2);
2248 gen_det_sign_exact_hilbert_positive_tests!(3);
2249 gen_det_sign_exact_hilbert_positive_tests!(4);
2250 gen_det_sign_exact_hilbert_positive_tests!(5);
2251
2252 macro_rules! gen_solve_exact_hilbert_residual_tests {
2259 ($d:literal) => {
2260 paste! {
2261 #[test]
2262 #[allow(clippy::cast_precision_loss)]
2263 fn [<solve_exact_hilbert_residual_ $d d>]() {
2264 let mut rows = [[0.0f64; $d]; $d];
2265 for r in 0..$d {
2266 for c in 0..$d {
2267 rows[r][c] = 1.0 / ((r + c + 1) as f64);
2268 }
2269 }
2270 let h = Matrix::<$d>::from_rows(rows);
2271 let mut b_arr = [0.0f64; $d];
2274 for i in 0usize..$d {
2275 let sign = if i.is_multiple_of(2) { 1.0 } else { -1.0 };
2276 b_arr[i] = sign * ((i + 1) as f64);
2277 }
2278 let b = Vector::<$d>::new(b_arr);
2279 let x = h.solve_exact(b).unwrap();
2280 let ax = bigrational_matvec(&h, &x);
2281 for i in 0..$d {
2282 assert_eq!(ax[i], f64_to_bigrational(b_arr[i]));
2283 }
2284 }
2285 }
2286 };
2287 }
2288
2289 gen_solve_exact_hilbert_residual_tests!(2);
2290 gen_solve_exact_hilbert_residual_tests!(3);
2291 gen_solve_exact_hilbert_residual_tests!(4);
2292 gen_solve_exact_hilbert_residual_tests!(5);
2293
2294 #[test]
2299 fn solve_exact_f64_known_2x2() {
2300 let a = Matrix::<2>::from_rows([[1.0, 2.0], [3.0, 4.0]]);
2301 let b = Vector::<2>::new([5.0, 11.0]);
2302 let x = a.solve_exact_f64(b).unwrap().into_array();
2303 assert!((x[0] - 1.0).abs() <= f64::EPSILON);
2304 assert!((x[1] - 2.0).abs() <= f64::EPSILON);
2305 }
2306
2307 #[test]
2308 fn solve_exact_f64_overflow_returns_err() {
2309 let big = f64::MAX / 2.0;
2312 let a = Matrix::<2>::from_rows([[1.0 / big, 0.0], [0.0, 1.0 / big]]);
2313 let b = Vector::<2>::new([big, big]);
2314 assert_eq!(
2315 a.solve_exact_f64(b),
2316 Err(LaError::Overflow { index: Some(0) })
2317 );
2318 }
2319
2320 #[test]
2325 fn gauss_solve_d0_returns_empty() {
2326 let a = Matrix::<0>::zero();
2327 let b = Vector::<0>::zero();
2328 assert_eq!(a.solve_exact(b).unwrap().len(), 0);
2329 }
2330
2331 #[test]
2332 fn gauss_solve_d1() {
2333 let a = Matrix::<1>::from_rows([[2.0]]);
2334 let b = Vector::<1>::new([6.0]);
2335 let x = a.solve_exact(b).unwrap();
2336 assert_eq!(x[0], f64_to_bigrational(3.0));
2337 }
2338
2339 #[test]
2340 fn gauss_solve_singular_column_all_zero() {
2341 let a = Matrix::<3>::from_rows([[1.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0, 1.0]]);
2342 let b = Vector::<3>::new([1.0, 2.0, 3.0]);
2343 assert_eq!(a.solve_exact(b), Err(LaError::Singular { pivot_col: 1 }));
2344 }
2345
2346 #[test]
2351 fn f64_to_bigrational_positive_zero() {
2352 let r = f64_to_bigrational(0.0);
2353 assert_eq!(r, BigRational::from_integer(BigInt::from(0)));
2354 }
2355
2356 #[test]
2357 fn f64_to_bigrational_negative_zero() {
2358 let r = f64_to_bigrational(-0.0);
2359 assert_eq!(r, BigRational::from_integer(BigInt::from(0)));
2360 }
2361
2362 #[test]
2363 fn f64_to_bigrational_one() {
2364 let r = f64_to_bigrational(1.0);
2365 assert_eq!(r, BigRational::from_integer(BigInt::from(1)));
2366 }
2367
2368 #[test]
2369 fn f64_to_bigrational_negative_one() {
2370 let r = f64_to_bigrational(-1.0);
2371 assert_eq!(r, BigRational::from_integer(BigInt::from(-1)));
2372 }
2373
2374 #[test]
2375 fn f64_to_bigrational_half() {
2376 let r = f64_to_bigrational(0.5);
2377 assert_eq!(r, BigRational::new(BigInt::from(1), BigInt::from(2)));
2378 }
2379
2380 #[test]
2381 fn f64_to_bigrational_quarter() {
2382 let r = f64_to_bigrational(0.25);
2383 assert_eq!(r, BigRational::new(BigInt::from(1), BigInt::from(4)));
2384 }
2385
2386 #[test]
2387 fn f64_to_bigrational_negative_three_and_a_half() {
2388 let r = f64_to_bigrational(-3.5);
2390 assert_eq!(r, BigRational::new(BigInt::from(-7), BigInt::from(2)));
2391 }
2392
2393 #[test]
2394 fn f64_to_bigrational_integer() {
2395 let r = f64_to_bigrational(42.0);
2396 assert_eq!(r, BigRational::from_integer(BigInt::from(42)));
2397 }
2398
2399 #[test]
2400 fn f64_to_bigrational_power_of_two() {
2401 let r = f64_to_bigrational(1024.0);
2402 assert_eq!(r, BigRational::from_integer(BigInt::from(1024)));
2403 }
2404
2405 #[test]
2406 fn f64_to_bigrational_subnormal() {
2407 let tiny = 5e-324_f64; assert!(tiny.is_subnormal());
2409 let r = f64_to_bigrational(tiny);
2410 assert_eq!(
2412 r,
2413 BigRational::new(BigInt::from(1), BigInt::from(1u32) << 1074u32)
2414 );
2415 }
2416
2417 #[test]
2418 fn f64_to_bigrational_already_lowest_terms() {
2419 let r = f64_to_bigrational(0.5);
2421 assert_eq!(*r.numer(), BigInt::from(1));
2422 assert_eq!(*r.denom(), BigInt::from(2));
2423 }
2424
2425 #[test]
2426 fn f64_to_bigrational_round_trip() {
2427 let values = [
2430 0.0,
2431 1.0,
2432 -1.0,
2433 0.5,
2434 0.25,
2435 0.1,
2436 42.0,
2437 -3.5,
2438 1e10,
2439 1e-10,
2440 f64::MAX / 2.0,
2441 f64::MIN_POSITIVE,
2442 5e-324,
2443 ];
2444 for &v in &values {
2445 let r = f64_to_bigrational(v);
2446 let back = r.to_f64().expect("round-trip to_f64 failed");
2447 assert!(
2448 v.to_bits() == back.to_bits(),
2449 "round-trip failed for {v}: got {back}"
2450 );
2451 }
2452 }
2453
2454 #[test]
2455 fn f64_decompose_rejects_nonfinite_inputs() {
2456 for value in [f64::NAN, f64::INFINITY, f64::NEG_INFINITY] {
2457 assert_eq!(
2458 f64_decompose(value),
2459 Err(LaError::NonFinite { row: None, col: 0 })
2460 );
2461 }
2462 }
2463}