1use std::array::from_fn;
47
48use num_bigint::{BigInt, Sign};
49use num_rational::BigRational;
50use num_traits::ToPrimitive;
51
52use crate::LaError;
53use crate::matrix::Matrix;
54use crate::vector::Vector;
55
56fn validate_finite<const D: usize>(m: &Matrix<D>) -> Result<(), LaError> {
61 for r in 0..D {
62 for c in 0..D {
63 if !m.rows[r][c].is_finite() {
64 return Err(LaError::NonFinite {
65 row: Some(r),
66 col: c,
67 });
68 }
69 }
70 }
71 Ok(())
72}
73
74fn validate_finite_vec<const D: usize>(v: &Vector<D>) -> Result<(), LaError> {
79 for (i, &x) in v.data.iter().enumerate() {
80 if !x.is_finite() {
81 return Err(LaError::NonFinite { row: None, col: i });
82 }
83 }
84 Ok(())
85}
86
87fn f64_decompose(x: f64) -> Option<(u64, i32, bool)> {
96 let bits = x.to_bits();
97 let biased_exp = ((bits >> 52) & 0x7FF) as i32;
98 let fraction = bits & 0x000F_FFFF_FFFF_FFFF;
99
100 if biased_exp == 0 && fraction == 0 {
102 return None;
103 }
104
105 assert!(biased_exp != 0x7FF, "non-finite f64 in exact conversion");
107
108 let (mantissa, raw_exp) = if biased_exp == 0 {
109 (fraction, -1074_i32)
112 } else {
113 ((1u64 << 52) | fraction, biased_exp - 1075)
116 };
117
118 let tz = mantissa.trailing_zeros();
120 let mantissa = mantissa >> tz;
121 let exponent = raw_exp + tz.cast_signed();
122 let is_negative = bits >> 63 != 0;
123
124 Some((mantissa, exponent, is_negative))
125}
126
127fn f64_to_bigrational(x: f64) -> BigRational {
142 let Some((mantissa, exponent, is_negative)) = f64_decompose(x) else {
143 return BigRational::from_integer(BigInt::from(0));
144 };
145
146 let numer = if is_negative {
147 -BigInt::from(mantissa)
148 } else {
149 BigInt::from(mantissa)
150 };
151
152 if exponent >= 0 {
153 BigRational::new_raw(numer << exponent.cast_unsigned(), BigInt::from(1u32))
154 } else {
155 BigRational::new_raw(numer, BigInt::from(1u32) << (-exponent).cast_unsigned())
156 }
157}
158
159fn bigint_exp_to_bigrational(mut value: BigInt, mut exp: i32) -> BigRational {
165 if value == BigInt::from(0) {
166 return BigRational::from_integer(BigInt::from(0));
167 }
168
169 if exp < 0
171 && let Some(tz) = value.trailing_zeros()
172 {
173 let reduce = tz.min(u64::from((-exp).cast_unsigned()));
174 value >>= reduce;
175 exp += i32::try_from(reduce).expect("reduce ≤ -exp which fits in i32");
176 }
177
178 if exp >= 0 {
179 BigRational::new_raw(value << exp.cast_unsigned(), BigInt::from(1u32))
180 } else {
181 BigRational::new_raw(value, BigInt::from(1u32) << (-exp).cast_unsigned())
182 }
183}
184
185fn bareiss_det_int<const D: usize>(m: &Matrix<D>) -> (BigInt, i32) {
196 if D == 0 {
197 return (BigInt::from(1), 0);
198 }
199 if D == 1 {
200 return match f64_decompose(m.rows[0][0]) {
201 None => (BigInt::from(0), 0),
202 Some((mant, exp, neg)) => {
203 let v = if neg {
204 -BigInt::from(mant)
205 } else {
206 BigInt::from(mant)
207 };
208 (v, exp)
209 }
210 };
211 }
212
213 let mut components = [[(0u64, 0i32, false); D]; D];
215 let mut e_min = i32::MAX;
216
217 for (r, row) in m.rows.iter().enumerate() {
218 for (c, &entry) in row.iter().enumerate() {
219 if let Some((mant, exp, neg)) = f64_decompose(entry) {
220 components[r][c] = (mant, exp, neg);
221 e_min = e_min.min(exp);
222 }
223 }
226 }
227
228 if e_min == i32::MAX {
230 return (BigInt::from(0), 0);
231 }
232
233 let mut a: [[BigInt; D]; D] = from_fn(|r| {
235 from_fn(|c| {
236 let (mant, exp, neg) = components[r][c];
237 if mant == 0 {
238 BigInt::from(0)
239 } else {
240 let shift = (exp - e_min).cast_unsigned();
241 let v = BigInt::from(mant) << shift;
242 if neg { -v } else { v }
243 }
244 })
245 });
246
247 let zero = BigInt::from(0);
249 let mut prev_pivot = BigInt::from(1);
250 let mut sign: i8 = 1;
251
252 for k in 0..D {
253 if a[k][k] == zero {
255 let mut found = false;
256 for i in (k + 1)..D {
257 if a[i][k] != zero {
258 a.swap(k, i);
259 sign = -sign;
260 found = true;
261 break;
262 }
263 }
264 if !found {
265 return (BigInt::from(0), 0);
266 }
267 }
268
269 for i in (k + 1)..D {
271 for j in (k + 1)..D {
272 a[i][j] = (&a[k][k] * &a[i][j] - &a[i][k] * &a[k][j]) / &prev_pivot;
273 }
274 a[i][k].clone_from(&zero);
275 }
276
277 prev_pivot.clone_from(&a[k][k]);
278 }
279
280 let det_int = if sign < 0 {
281 -&a[D - 1][D - 1]
282 } else {
283 a[D - 1][D - 1].clone()
284 };
285
286 let d_i32 = i32::try_from(D).expect("dimension exceeds i32");
288 let total_exp = e_min
289 .checked_mul(d_i32)
290 .expect("exponent overflow in bareiss_det_int");
291
292 (det_int, total_exp)
293}
294
295fn bareiss_det<const D: usize>(m: &Matrix<D>) -> BigRational {
298 let (det_int, total_exp) = bareiss_det_int(m);
299 bigint_exp_to_bigrational(det_int, total_exp)
300}
301
302fn gauss_solve<const D: usize>(m: &Matrix<D>, b: &Vector<D>) -> Result<[BigRational; D], LaError> {
312 let zero = BigRational::from_integer(BigInt::from(0));
313
314 let mut mat: [[BigRational; D]; D] = from_fn(|r| from_fn(|c| f64_to_bigrational(m.rows[r][c])));
317 let mut rhs: [BigRational; D] = from_fn(|r| f64_to_bigrational(b.data[r]));
318
319 for k in 0..D {
321 if mat[k][k] == zero {
323 if let Some(swap_row) = ((k + 1)..D).find(|&i| mat[i][k] != zero) {
324 mat.swap(k, swap_row);
325 rhs.swap(k, swap_row);
326 } else {
327 return Err(LaError::Singular { pivot_col: k });
328 }
329 }
330
331 let pivot = mat[k][k].clone();
333 for i in (k + 1)..D {
334 if mat[i][k] != zero {
335 let factor = &mat[i][k] / &pivot;
336 #[allow(clippy::needless_range_loop)]
339 for j in (k + 1)..D {
340 let term = &factor * &mat[k][j];
341 mat[i][j] -= term;
342 }
343 let rhs_term = &factor * &rhs[k];
344 rhs[i] -= rhs_term;
345 mat[i][k] = zero.clone();
346 }
347 }
348 }
349
350 let mut x: [BigRational; D] = from_fn(|_| zero.clone());
352 for i in (0..D).rev() {
353 let mut sum = rhs[i].clone();
354 for j in (i + 1)..D {
355 sum -= &mat[i][j] * &x[j];
356 }
357 x[i] = sum / &mat[i][i];
358 }
359
360 Ok(x)
361}
362
363impl<const D: usize> Matrix<D> {
364 #[inline]
390 pub fn det_exact(&self) -> Result<BigRational, LaError> {
391 validate_finite(self)?;
392 Ok(bareiss_det(self))
393 }
394
395 #[inline]
416 pub fn det_exact_f64(&self) -> Result<f64, LaError> {
417 let exact = self.det_exact()?;
418 let val = exact.to_f64().unwrap_or(f64::INFINITY);
419 if val.is_finite() {
420 Ok(val)
421 } else {
422 Err(LaError::Overflow { index: None })
423 }
424 }
425
426 #[inline]
456 pub fn solve_exact(&self, b: Vector<D>) -> Result<[BigRational; D], LaError> {
457 validate_finite(self)?;
458 validate_finite_vec(&b)?;
459 gauss_solve(self, &b)
460 }
461
462 #[inline]
487 pub fn solve_exact_f64(&self, b: Vector<D>) -> Result<Vector<D>, LaError> {
488 let exact = self.solve_exact(b)?;
489 let mut result = [0.0f64; D];
490 for (i, val) in exact.iter().enumerate() {
491 let f = val.to_f64().unwrap_or(f64::INFINITY);
492 if !f.is_finite() {
493 return Err(LaError::Overflow { index: Some(i) });
494 }
495 result[i] = f;
496 }
497 Ok(Vector::new(result))
498 }
499
500 #[inline]
536 pub fn det_sign_exact(&self) -> Result<i8, LaError> {
537 validate_finite(self)?;
538
539 if let (Some(det_f64), Some(err)) = (self.det_direct(), self.det_errbound()) {
541 if det_f64.is_finite() {
546 if det_f64 > err {
547 return Ok(1);
548 }
549 if det_f64 < -err {
550 return Ok(-1);
551 }
552 }
553 }
554
555 let (det_int, _) = bareiss_det_int(self);
558 Ok(match det_int.sign() {
559 Sign::Plus => 1,
560 Sign::Minus => -1,
561 Sign::NoSign => 0,
562 })
563 }
564}
565
566#[cfg(test)]
567mod tests {
568 use super::*;
569 use crate::DEFAULT_PIVOT_TOL;
570
571 use pastey::paste;
572
573 macro_rules! gen_det_exact_tests {
578 ($d:literal) => {
579 paste! {
580 #[test]
581 fn [<det_exact_identity_ $d d>]() {
582 let det = Matrix::<$d>::identity().det_exact().unwrap();
583 assert_eq!(det, BigRational::from_integer(BigInt::from(1)));
584 }
585
586 #[test]
587 fn [<det_exact_err_on_nan_ $d d>]() {
588 let mut m = Matrix::<$d>::identity();
589 m.set(0, 0, f64::NAN);
590 assert_eq!(m.det_exact(), Err(LaError::NonFinite { row: Some(0), col: 0 }));
591 }
592
593 #[test]
594 fn [<det_exact_err_on_inf_ $d d>]() {
595 let mut m = Matrix::<$d>::identity();
596 m.set(0, 0, f64::INFINITY);
597 assert_eq!(m.det_exact(), Err(LaError::NonFinite { row: Some(0), col: 0 }));
598 }
599 }
600 };
601 }
602
603 gen_det_exact_tests!(2);
604 gen_det_exact_tests!(3);
605 gen_det_exact_tests!(4);
606 gen_det_exact_tests!(5);
607
608 macro_rules! gen_det_exact_f64_tests {
609 ($d:literal) => {
610 paste! {
611 #[test]
612 fn [<det_exact_f64_identity_ $d d>]() {
613 let det = Matrix::<$d>::identity().det_exact_f64().unwrap();
614 assert!((det - 1.0).abs() <= f64::EPSILON);
615 }
616
617 #[test]
618 fn [<det_exact_f64_err_on_nan_ $d d>]() {
619 let mut m = Matrix::<$d>::identity();
620 m.set(0, 0, f64::NAN);
621 assert_eq!(m.det_exact_f64(), Err(LaError::NonFinite { row: Some(0), col: 0 }));
622 }
623 }
624 };
625 }
626
627 gen_det_exact_f64_tests!(2);
628 gen_det_exact_f64_tests!(3);
629 gen_det_exact_f64_tests!(4);
630 gen_det_exact_f64_tests!(5);
631
632 macro_rules! gen_det_exact_f64_agrees_with_det_direct {
635 ($d:literal) => {
636 paste! {
637 #[test]
638 #[allow(clippy::cast_precision_loss)]
639 fn [<det_exact_f64_agrees_with_det_direct_ $d d>]() {
640 let mut rows = [[0.0f64; $d]; $d];
642 for r in 0..$d {
643 for c in 0..$d {
644 rows[r][c] = if r == c {
645 (r as f64) + f64::from($d) + 1.0
646 } else {
647 0.1 / ((r + c + 1) as f64)
648 };
649 }
650 }
651 let m = Matrix::<$d>::from_rows(rows);
652 let exact = m.det_exact_f64().unwrap();
653 let direct = m.det_direct().unwrap();
654 let eps = direct.abs().mul_add(1e-12, 1e-12);
655 assert!((exact - direct).abs() <= eps);
656 }
657 }
658 };
659 }
660
661 gen_det_exact_f64_agrees_with_det_direct!(2);
662 gen_det_exact_f64_agrees_with_det_direct!(3);
663 gen_det_exact_f64_agrees_with_det_direct!(4);
664
665 #[test]
666 fn det_sign_exact_d0_is_positive() {
667 assert_eq!(Matrix::<0>::zero().det_sign_exact().unwrap(), 1);
668 }
669
670 #[test]
671 fn det_sign_exact_d1_positive() {
672 let m = Matrix::<1>::from_rows([[42.0]]);
673 assert_eq!(m.det_sign_exact().unwrap(), 1);
674 }
675
676 #[test]
677 fn det_sign_exact_d1_negative() {
678 let m = Matrix::<1>::from_rows([[-3.5]]);
679 assert_eq!(m.det_sign_exact().unwrap(), -1);
680 }
681
682 #[test]
683 fn det_sign_exact_d1_zero() {
684 let m = Matrix::<1>::from_rows([[0.0]]);
685 assert_eq!(m.det_sign_exact().unwrap(), 0);
686 }
687
688 #[test]
689 fn det_sign_exact_identity_2d() {
690 assert_eq!(Matrix::<2>::identity().det_sign_exact().unwrap(), 1);
691 }
692
693 #[test]
694 fn det_sign_exact_identity_3d() {
695 assert_eq!(Matrix::<3>::identity().det_sign_exact().unwrap(), 1);
696 }
697
698 #[test]
699 fn det_sign_exact_identity_4d() {
700 assert_eq!(Matrix::<4>::identity().det_sign_exact().unwrap(), 1);
701 }
702
703 #[test]
704 fn det_sign_exact_identity_5d() {
705 assert_eq!(Matrix::<5>::identity().det_sign_exact().unwrap(), 1);
706 }
707
708 #[test]
709 fn det_sign_exact_singular_duplicate_rows() {
710 let m = Matrix::<3>::from_rows([
711 [1.0, 2.0, 3.0],
712 [4.0, 5.0, 6.0],
713 [1.0, 2.0, 3.0], ]);
715 assert_eq!(m.det_sign_exact().unwrap(), 0);
716 }
717
718 #[test]
719 fn det_sign_exact_singular_linear_combination() {
720 let m = Matrix::<3>::from_rows([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [5.0, 7.0, 9.0]]);
722 assert_eq!(m.det_sign_exact().unwrap(), 0);
723 }
724
725 #[test]
726 fn det_sign_exact_negative_det_row_swap() {
727 let m = Matrix::<3>::from_rows([[0.0, 1.0, 0.0], [1.0, 0.0, 0.0], [0.0, 0.0, 1.0]]);
729 assert_eq!(m.det_sign_exact().unwrap(), -1);
730 }
731
732 #[test]
733 fn det_sign_exact_negative_det_known() {
734 let m = Matrix::<2>::from_rows([[1.0, 2.0], [3.0, 4.0]]);
736 assert_eq!(m.det_sign_exact().unwrap(), -1);
737 }
738
739 #[test]
740 fn det_sign_exact_agrees_with_det_for_spd() {
741 let m = Matrix::<3>::from_rows([[4.0, 2.0, 0.0], [2.0, 5.0, 1.0], [0.0, 1.0, 3.0]]);
743 assert_eq!(m.det_sign_exact().unwrap(), 1);
744 assert!(m.det(DEFAULT_PIVOT_TOL).unwrap() > 0.0);
745 }
746
747 #[test]
754 fn det_sign_exact_near_singular_perturbation() {
755 let perturbation = f64::from_bits(0x3CD0_0000_0000_0000); let m = Matrix::<3>::from_rows([
757 [1.0 + perturbation, 2.0, 3.0],
758 [4.0, 5.0, 6.0],
759 [7.0, 8.0, 9.0],
760 ]);
761 assert_eq!(m.det_sign_exact().unwrap(), -1);
763 }
764
765 #[test]
769 fn det_sign_exact_fast_filter_positive_4x4() {
770 let m = Matrix::<4>::from_rows([
771 [2.0, 1.0, 0.0, 0.0],
772 [1.0, 3.0, 1.0, 0.0],
773 [0.0, 1.0, 4.0, 1.0],
774 [0.0, 0.0, 1.0, 5.0],
775 ]);
776 assert_eq!(m.det_sign_exact().unwrap(), 1);
778 }
779
780 #[test]
781 fn det_sign_exact_fast_filter_negative_4x4() {
782 let m = Matrix::<4>::from_rows([
784 [1.0, 3.0, 1.0, 0.0],
785 [2.0, 1.0, 0.0, 0.0],
786 [0.0, 1.0, 4.0, 1.0],
787 [0.0, 0.0, 1.0, 5.0],
788 ]);
789 assert_eq!(m.det_sign_exact().unwrap(), -1);
790 }
791
792 #[test]
793 fn det_sign_exact_subnormal_entries() {
794 let tiny = 5e-324_f64; assert!(tiny.is_subnormal());
797
798 let m = Matrix::<2>::from_rows([[tiny, 0.0], [0.0, tiny]]);
799 assert_eq!(m.det_sign_exact().unwrap(), 1);
801 }
802
803 #[test]
804 fn det_sign_exact_returns_err_on_nan() {
805 let m = Matrix::<2>::from_rows([[f64::NAN, 0.0], [0.0, 1.0]]);
806 assert_eq!(
807 m.det_sign_exact(),
808 Err(LaError::NonFinite {
809 row: Some(0),
810 col: 0
811 })
812 );
813 }
814
815 #[test]
816 fn det_sign_exact_returns_err_on_infinity() {
817 let m = Matrix::<2>::from_rows([[f64::INFINITY, 0.0], [0.0, 1.0]]);
818 assert_eq!(
819 m.det_sign_exact(),
820 Err(LaError::NonFinite {
821 row: Some(0),
822 col: 0
823 })
824 );
825 }
826
827 #[test]
828 fn det_sign_exact_returns_err_on_nan_5x5() {
829 let mut m = Matrix::<5>::identity();
831 m.set(2, 3, f64::NAN);
832 assert_eq!(
833 m.det_sign_exact(),
834 Err(LaError::NonFinite {
835 row: Some(2),
836 col: 3
837 })
838 );
839 }
840
841 #[test]
842 fn det_sign_exact_returns_err_on_infinity_5x5() {
843 let mut m = Matrix::<5>::identity();
844 m.set(0, 0, f64::INFINITY);
845 assert_eq!(
846 m.det_sign_exact(),
847 Err(LaError::NonFinite {
848 row: Some(0),
849 col: 0
850 })
851 );
852 }
853
854 #[test]
855 fn det_sign_exact_pivot_needed_5x5() {
856 let m = Matrix::<5>::from_rows([
859 [0.0, 1.0, 0.0, 0.0, 0.0],
860 [1.0, 0.0, 0.0, 0.0, 0.0],
861 [0.0, 0.0, 1.0, 0.0, 0.0],
862 [0.0, 0.0, 0.0, 1.0, 0.0],
863 [0.0, 0.0, 0.0, 0.0, 1.0],
864 ]);
865 assert_eq!(m.det_sign_exact().unwrap(), -1);
866 }
867
868 #[test]
869 fn det_sign_exact_5x5_known() {
870 let m = Matrix::<5>::from_rows([
872 [0.0, 1.0, 0.0, 0.0, 0.0],
873 [1.0, 0.0, 0.0, 0.0, 0.0],
874 [0.0, 0.0, 0.0, 1.0, 0.0],
875 [0.0, 0.0, 1.0, 0.0, 0.0],
876 [0.0, 0.0, 0.0, 0.0, 1.0],
877 ]);
878 assert_eq!(m.det_sign_exact().unwrap(), 1);
880 }
881
882 #[test]
887 fn det_errbound_d0_is_zero() {
888 assert_eq!(Matrix::<0>::zero().det_errbound(), Some(0.0));
889 }
890
891 #[test]
892 fn det_errbound_d1_is_zero() {
893 assert_eq!(Matrix::<1>::from_rows([[42.0]]).det_errbound(), Some(0.0));
894 }
895
896 #[test]
897 fn det_errbound_d2_positive() {
898 let m = Matrix::<2>::from_rows([[1.0, 2.0], [3.0, 4.0]]);
899 let bound = m.det_errbound().unwrap();
900 assert!(bound > 0.0);
901 assert!(crate::ERR_COEFF_2.mul_add(-10.0, bound).abs() < 1e-30);
903 }
904
905 #[test]
906 fn det_errbound_d3_positive() {
907 let m = Matrix::<3>::identity();
908 let bound = m.det_errbound().unwrap();
909 assert!(bound > 0.0);
910 }
911
912 #[test]
913 fn det_errbound_d3_non_identity() {
914 let m = Matrix::<3>::from_rows([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [7.0, 8.0, 10.0]]);
916 let bound = m.det_errbound().unwrap();
917 assert!(bound > 0.0);
918 }
919
920 #[test]
921 fn det_errbound_d4_positive() {
922 let m = Matrix::<4>::identity();
923 let bound = m.det_errbound().unwrap();
924 assert!(bound > 0.0);
925 }
926
927 #[test]
928 fn det_errbound_d4_non_identity() {
929 let m = Matrix::<4>::from_rows([
931 [1.0, 0.0, 0.0, 0.0],
932 [0.0, 2.0, 0.0, 0.0],
933 [0.0, 0.0, 3.0, 0.0],
934 [0.0, 0.0, 0.0, 4.0],
935 ]);
936 let bound = m.det_errbound().unwrap();
937 assert!(bound > 0.0);
938 }
939
940 #[test]
941 fn det_errbound_d5_is_none() {
942 assert_eq!(Matrix::<5>::identity().det_errbound(), None);
943 }
944
945 #[test]
950 fn f64_decompose_zero() {
951 assert!(f64_decompose(0.0).is_none());
952 assert!(f64_decompose(-0.0).is_none());
953 }
954
955 #[test]
956 fn f64_decompose_one() {
957 let (mant, exp, neg) = f64_decompose(1.0).unwrap();
958 assert_eq!(mant, 1);
959 assert_eq!(exp, 0);
960 assert!(!neg);
961 }
962
963 #[test]
964 fn f64_decompose_negative() {
965 let (mant, exp, neg) = f64_decompose(-3.5).unwrap();
966 assert_eq!(mant, 7);
968 assert_eq!(exp, -1);
969 assert!(neg);
970 }
971
972 #[test]
973 fn f64_decompose_subnormal() {
974 let tiny = 5e-324_f64;
975 assert!(tiny.is_subnormal());
976 let (mant, exp, neg) = f64_decompose(tiny).unwrap();
977 assert_eq!(mant, 1);
978 assert_eq!(exp, -1074);
979 assert!(!neg);
980 }
981
982 #[test]
983 fn f64_decompose_power_of_two() {
984 let (mant, exp, neg) = f64_decompose(1024.0).unwrap();
985 assert_eq!(mant, 1);
986 assert_eq!(exp, 10); assert!(!neg);
988 }
989
990 #[test]
991 #[should_panic(expected = "non-finite f64 in exact conversion")]
992 fn f64_decompose_panics_on_nan() {
993 f64_decompose(f64::NAN);
994 }
995
996 #[test]
1001 fn bareiss_det_int_d0() {
1002 let (det, exp) = bareiss_det_int(&Matrix::<0>::zero());
1003 assert_eq!(det, BigInt::from(1));
1004 assert_eq!(exp, 0);
1005 }
1006
1007 #[test]
1008 fn bareiss_det_int_d1_value() {
1009 let (det, exp) = bareiss_det_int(&Matrix::<1>::from_rows([[7.0]]));
1011 assert_eq!(det, BigInt::from(7));
1012 assert_eq!(exp, 0);
1013 }
1014
1015 #[test]
1016 fn bareiss_det_int_d1_zero() {
1017 let (det, _) = bareiss_det_int(&Matrix::<1>::from_rows([[0.0]]));
1018 assert_eq!(det, BigInt::from(0));
1019 }
1020
1021 #[test]
1022 fn bareiss_det_int_d2_known() {
1023 let m = Matrix::<2>::from_rows([[1.0, 2.0], [3.0, 4.0]]);
1025 let (det_int, total_exp) = bareiss_det_int(&m);
1026 let det = bigint_exp_to_bigrational(det_int, total_exp);
1028 assert_eq!(det, BigRational::from_integer(BigInt::from(-2)));
1029 }
1030
1031 #[test]
1032 fn bareiss_det_int_all_zeros() {
1033 let (det, _) = bareiss_det_int(&Matrix::<3>::zero());
1034 assert_eq!(det, BigInt::from(0));
1035 }
1036
1037 #[test]
1038 fn bareiss_det_int_sign_matches_det_sign_exact() {
1039 let m = Matrix::<3>::from_rows([[0.0, 1.0, 0.0], [1.0, 0.0, 0.0], [0.0, 0.0, 1.0]]);
1041 let (det_int, _) = bareiss_det_int(&m);
1042 assert_eq!(det_int.sign(), Sign::Minus); }
1044
1045 #[test]
1046 fn bareiss_det_int_fractional_entries() {
1047 let m = Matrix::<2>::from_rows([[0.5, 0.25], [1.0, 1.0]]);
1050 let (det_int, total_exp) = bareiss_det_int(&m);
1051 let det = bigint_exp_to_bigrational(det_int, total_exp);
1052 assert_eq!(det, BigRational::new(BigInt::from(1), BigInt::from(4)));
1053 }
1054
1055 #[test]
1056 fn bareiss_det_int_d1_negative() {
1057 let (det, exp) = bareiss_det_int(&Matrix::<1>::from_rows([[-3.5]]));
1059 assert_eq!(det, BigInt::from(-7));
1060 assert_eq!(exp, -1);
1061 }
1062
1063 #[test]
1064 fn bareiss_det_int_d1_fractional() {
1065 let (det, exp) = bareiss_det_int(&Matrix::<1>::from_rows([[0.5]]));
1067 assert_eq!(det, BigInt::from(1));
1068 assert_eq!(exp, -1);
1069 }
1070
1071 #[test]
1072 fn bareiss_det_int_d3_with_pivoting() {
1073 let m = Matrix::<3>::from_rows([[0.0, 1.0, 0.0], [1.0, 0.0, 0.0], [0.0, 0.0, 1.0]]);
1075 let (det_int, total_exp) = bareiss_det_int(&m);
1076 let det = bigint_exp_to_bigrational(det_int, total_exp);
1077 assert_eq!(det, BigRational::from_integer(BigInt::from(-1)));
1078 }
1079
1080 macro_rules! gen_bareiss_det_int_identity_tests {
1082 ($d:literal) => {
1083 paste! {
1084 #[test]
1085 fn [<bareiss_det_int_identity_ $d d>]() {
1086 let (det_int, total_exp) = bareiss_det_int(&Matrix::<$d>::identity());
1087 let det = bigint_exp_to_bigrational(det_int, total_exp);
1088 assert_eq!(det, BigRational::from_integer(BigInt::from(1)));
1089 }
1090 }
1091 };
1092 }
1093
1094 gen_bareiss_det_int_identity_tests!(2);
1095 gen_bareiss_det_int_identity_tests!(3);
1096 gen_bareiss_det_int_identity_tests!(4);
1097 gen_bareiss_det_int_identity_tests!(5);
1098
1099 #[test]
1104 fn bigint_exp_to_bigrational_zero() {
1105 let r = bigint_exp_to_bigrational(BigInt::from(0), -50);
1106 assert_eq!(r, BigRational::from_integer(BigInt::from(0)));
1107 }
1108
1109 #[test]
1110 fn bigint_exp_to_bigrational_positive_exp() {
1111 let r = bigint_exp_to_bigrational(BigInt::from(3), 2);
1113 assert_eq!(r, BigRational::from_integer(BigInt::from(12)));
1114 }
1115
1116 #[test]
1117 fn bigint_exp_to_bigrational_negative_exp_reduced() {
1118 let r = bigint_exp_to_bigrational(BigInt::from(6), -2);
1120 assert_eq!(*r.numer(), BigInt::from(3));
1121 assert_eq!(*r.denom(), BigInt::from(2));
1122 }
1123
1124 #[test]
1125 fn bigint_exp_to_bigrational_negative_exp_already_odd() {
1126 let r = bigint_exp_to_bigrational(BigInt::from(3), -2);
1128 assert_eq!(*r.numer(), BigInt::from(3));
1129 assert_eq!(*r.denom(), BigInt::from(4));
1130 }
1131
1132 #[test]
1133 fn bigint_exp_to_bigrational_negative_value() {
1134 let r = bigint_exp_to_bigrational(BigInt::from(-5), 1);
1136 assert_eq!(r, BigRational::from_integer(BigInt::from(-10)));
1137 }
1138
1139 #[test]
1140 fn bigint_exp_to_bigrational_negative_value_with_denominator() {
1141 let r = bigint_exp_to_bigrational(BigInt::from(-3), -2);
1143 assert_eq!(*r.numer(), BigInt::from(-3));
1144 assert_eq!(*r.denom(), BigInt::from(4));
1145 }
1146
1147 #[test]
1152 fn bareiss_det_d0_is_one() {
1153 let det = bareiss_det(&Matrix::<0>::zero());
1154 assert_eq!(det, BigRational::from_integer(BigInt::from(1)));
1155 }
1156
1157 #[test]
1158 fn bareiss_det_d1_returns_entry() {
1159 let det = bareiss_det(&Matrix::<1>::from_rows([[7.0]]));
1160 assert_eq!(det, f64_to_bigrational(7.0));
1161 }
1162
1163 #[test]
1164 fn bareiss_det_d3_with_pivoting() {
1165 let m = Matrix::<3>::from_rows([[0.0, 1.0, 0.0], [1.0, 0.0, 0.0], [0.0, 0.0, 1.0]]);
1167 let det = bareiss_det(&m);
1168 assert_eq!(det, BigRational::from_integer(BigInt::from(-1)));
1170 }
1171
1172 #[test]
1173 fn bareiss_det_singular_all_zeros_in_column() {
1174 let m = Matrix::<3>::from_rows([[1.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0, 1.0]]);
1176 let det = bareiss_det(&m);
1177 assert_eq!(det, BigRational::from_integer(BigInt::from(0)));
1178 }
1179
1180 #[test]
1181 fn det_sign_exact_overflow_determinant_finite_entries() {
1182 let big = f64::MAX / 2.0;
1186 assert!(big.is_finite());
1187 let m = Matrix::<3>::from_rows([[0.0, 0.0, 1.0], [big, 0.0, 1.0], [0.0, big, 1.0]]);
1188 assert_eq!(m.det_sign_exact().unwrap(), 1);
1190 }
1191
1192 #[test]
1197 fn det_exact_d0_is_one() {
1198 let det = Matrix::<0>::zero().det_exact().unwrap();
1199 assert_eq!(det, BigRational::from_integer(BigInt::from(1)));
1200 }
1201
1202 #[test]
1203 fn det_exact_known_2x2() {
1204 let m = Matrix::<2>::from_rows([[1.0, 2.0], [3.0, 4.0]]);
1206 let det = m.det_exact().unwrap();
1207 assert_eq!(det, BigRational::from_integer(BigInt::from(-2)));
1208 }
1209
1210 #[test]
1211 fn det_exact_singular_returns_zero() {
1212 let m = Matrix::<3>::from_rows([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [7.0, 8.0, 9.0]]);
1214 let det = m.det_exact().unwrap();
1215 assert_eq!(det, BigRational::from_integer(BigInt::from(0)));
1216 }
1217
1218 #[test]
1219 fn det_exact_near_singular_perturbation() {
1220 let perturbation = f64::from_bits(0x3CD0_0000_0000_0000); let m = Matrix::<3>::from_rows([
1223 [1.0 + perturbation, 2.0, 3.0],
1224 [4.0, 5.0, 6.0],
1225 [7.0, 8.0, 9.0],
1226 ]);
1227 let det = m.det_exact().unwrap();
1228 let expected = BigRational::new(BigInt::from(-3), BigInt::from(1u64 << 50));
1230 assert_eq!(det, expected);
1231 }
1232
1233 #[test]
1234 fn det_exact_5x5_permutation() {
1235 let m = Matrix::<5>::from_rows([
1237 [0.0, 1.0, 0.0, 0.0, 0.0],
1238 [1.0, 0.0, 0.0, 0.0, 0.0],
1239 [0.0, 0.0, 1.0, 0.0, 0.0],
1240 [0.0, 0.0, 0.0, 1.0, 0.0],
1241 [0.0, 0.0, 0.0, 0.0, 1.0],
1242 ]);
1243 let det = m.det_exact().unwrap();
1244 assert_eq!(det, BigRational::from_integer(BigInt::from(-1)));
1245 }
1246
1247 #[test]
1252 fn det_exact_f64_known_2x2() {
1253 let m = Matrix::<2>::from_rows([[1.0, 2.0], [3.0, 4.0]]);
1254 let det = m.det_exact_f64().unwrap();
1255 assert!((det - (-2.0)).abs() <= f64::EPSILON);
1256 }
1257
1258 #[test]
1259 fn det_exact_f64_overflow_returns_err() {
1260 let big = f64::MAX / 2.0;
1262 let m = Matrix::<3>::from_rows([[0.0, 0.0, 1.0], [big, 0.0, 1.0], [0.0, big, 1.0]]);
1263 assert_eq!(m.det_exact_f64(), Err(LaError::Overflow { index: None }));
1265 }
1266
1267 fn arbitrary_rhs<const D: usize>() -> Vector<D> {
1273 let values = [1.0, -2.5, 3.0, 0.25, -4.0];
1274 let mut arr = [0.0f64; D];
1275 for (dst, src) in arr.iter_mut().zip(values.iter()) {
1276 *dst = *src;
1277 }
1278 Vector::<D>::new(arr)
1279 }
1280
1281 macro_rules! gen_solve_exact_tests {
1282 ($d:literal) => {
1283 paste! {
1284 #[test]
1285 fn [<solve_exact_identity_ $d d>]() {
1286 let a = Matrix::<$d>::identity();
1287 let b = arbitrary_rhs::<$d>();
1288 let x = a.solve_exact(b).unwrap();
1289 for (i, xi) in x.iter().enumerate() {
1290 assert_eq!(*xi, f64_to_bigrational(b.data[i]));
1291 }
1292 }
1293
1294 #[test]
1295 fn [<solve_exact_err_on_nan_matrix_ $d d>]() {
1296 let mut a = Matrix::<$d>::identity();
1297 a.set(0, 0, f64::NAN);
1298 let b = arbitrary_rhs::<$d>();
1299 assert_eq!(a.solve_exact(b), Err(LaError::NonFinite { row: Some(0), col: 0 }));
1300 }
1301
1302 #[test]
1303 fn [<solve_exact_err_on_inf_matrix_ $d d>]() {
1304 let mut a = Matrix::<$d>::identity();
1305 a.set(0, 0, f64::INFINITY);
1306 let b = arbitrary_rhs::<$d>();
1307 assert_eq!(a.solve_exact(b), Err(LaError::NonFinite { row: Some(0), col: 0 }));
1308 }
1309
1310 #[test]
1311 fn [<solve_exact_err_on_nan_vector_ $d d>]() {
1312 let a = Matrix::<$d>::identity();
1313 let mut b_arr = [1.0f64; $d];
1314 b_arr[0] = f64::NAN;
1315 let b = Vector::<$d>::new(b_arr);
1316 assert_eq!(a.solve_exact(b), Err(LaError::NonFinite { row: None, col: 0 }));
1317 }
1318
1319 #[test]
1320 fn [<solve_exact_err_on_inf_vector_ $d d>]() {
1321 let a = Matrix::<$d>::identity();
1322 let mut b_arr = [1.0f64; $d];
1323 b_arr[$d - 1] = f64::INFINITY;
1324 let b = Vector::<$d>::new(b_arr);
1325 assert_eq!(a.solve_exact(b), Err(LaError::NonFinite { row: None, col: $d - 1 }));
1326 }
1327
1328 #[test]
1329 fn [<solve_exact_singular_ $d d>]() {
1330 let a = Matrix::<$d>::zero();
1332 let b = arbitrary_rhs::<$d>();
1333 assert_eq!(a.solve_exact(b), Err(LaError::Singular { pivot_col: 0 }));
1334 }
1335 }
1336 };
1337 }
1338
1339 gen_solve_exact_tests!(2);
1340 gen_solve_exact_tests!(3);
1341 gen_solve_exact_tests!(4);
1342 gen_solve_exact_tests!(5);
1343
1344 macro_rules! gen_solve_exact_f64_tests {
1345 ($d:literal) => {
1346 paste! {
1347 #[test]
1348 fn [<solve_exact_f64_identity_ $d d>]() {
1349 let a = Matrix::<$d>::identity();
1350 let b = arbitrary_rhs::<$d>();
1351 let x = a.solve_exact_f64(b).unwrap().into_array();
1352 for i in 0..$d {
1353 assert!((x[i] - b.data[i]).abs() <= f64::EPSILON);
1354 }
1355 }
1356
1357 #[test]
1358 fn [<solve_exact_f64_err_on_nan_ $d d>]() {
1359 let mut a = Matrix::<$d>::identity();
1360 a.set(0, 0, f64::NAN);
1361 let b = arbitrary_rhs::<$d>();
1362 assert_eq!(a.solve_exact_f64(b), Err(LaError::NonFinite { row: Some(0), col: 0 }));
1363 }
1364 }
1365 };
1366 }
1367
1368 gen_solve_exact_f64_tests!(2);
1369 gen_solve_exact_f64_tests!(3);
1370 gen_solve_exact_f64_tests!(4);
1371 gen_solve_exact_f64_tests!(5);
1372
1373 macro_rules! gen_solve_exact_f64_agrees_with_lu {
1376 ($d:literal) => {
1377 paste! {
1378 #[test]
1379 #[allow(clippy::cast_precision_loss)]
1380 fn [<solve_exact_f64_agrees_with_lu_ $d d>]() {
1381 let mut rows = [[0.0f64; $d]; $d];
1383 for r in 0..$d {
1384 for c in 0..$d {
1385 rows[r][c] = if r == c {
1386 (r as f64) + f64::from($d) + 1.0
1387 } else {
1388 0.1 / ((r + c + 1) as f64)
1389 };
1390 }
1391 }
1392 let a = Matrix::<$d>::from_rows(rows);
1393 let b = arbitrary_rhs::<$d>();
1394 let exact = a.solve_exact_f64(b).unwrap().into_array();
1395 let lu_sol = a.lu(DEFAULT_PIVOT_TOL).unwrap()
1396 .solve_vec(b).unwrap().into_array();
1397 for i in 0..$d {
1398 let eps = lu_sol[i].abs().mul_add(1e-12, 1e-12);
1399 assert!((exact[i] - lu_sol[i]).abs() <= eps);
1400 }
1401 }
1402 }
1403 };
1404 }
1405
1406 gen_solve_exact_f64_agrees_with_lu!(2);
1407 gen_solve_exact_f64_agrees_with_lu!(3);
1408 gen_solve_exact_f64_agrees_with_lu!(4);
1409 gen_solve_exact_f64_agrees_with_lu!(5);
1410
1411 #[test]
1416 fn solve_exact_d0_returns_empty() {
1417 let a = Matrix::<0>::zero();
1418 let b = Vector::<0>::zero();
1419 let x = a.solve_exact(b).unwrap();
1420 assert!(x.is_empty());
1421 }
1422
1423 #[test]
1424 fn solve_exact_known_2x2() {
1425 let a = Matrix::<2>::from_rows([[1.0, 2.0], [3.0, 4.0]]);
1427 let b = Vector::<2>::new([5.0, 11.0]);
1428 let x = a.solve_exact(b).unwrap();
1429 assert_eq!(x[0], BigRational::from_integer(BigInt::from(1)));
1430 assert_eq!(x[1], BigRational::from_integer(BigInt::from(2)));
1431 }
1432
1433 #[test]
1434 fn solve_exact_pivoting_needed() {
1435 let a = Matrix::<3>::from_rows([[0.0, 1.0, 0.0], [1.0, 0.0, 0.0], [0.0, 0.0, 1.0]]);
1437 let b = Vector::<3>::new([2.0, 3.0, 4.0]);
1438 let x = a.solve_exact(b).unwrap();
1439 assert_eq!(x[0], f64_to_bigrational(3.0));
1441 assert_eq!(x[1], f64_to_bigrational(2.0));
1442 assert_eq!(x[2], f64_to_bigrational(4.0));
1443 }
1444
1445 #[test]
1446 fn solve_exact_fractional_result() {
1447 let a = Matrix::<2>::from_rows([[2.0, 1.0], [1.0, 3.0]]);
1449 let b = Vector::<2>::new([1.0, 1.0]);
1450 let x = a.solve_exact(b).unwrap();
1451 assert_eq!(x[0], BigRational::new(BigInt::from(2), BigInt::from(5)));
1452 assert_eq!(x[1], BigRational::new(BigInt::from(1), BigInt::from(5)));
1453 }
1454
1455 #[test]
1456 fn solve_exact_singular_duplicate_rows() {
1457 let a = Matrix::<3>::from_rows([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [1.0, 2.0, 3.0]]);
1458 let b = Vector::<3>::new([1.0, 2.0, 3.0]);
1459 assert!(matches!(a.solve_exact(b), Err(LaError::Singular { .. })));
1460 }
1461
1462 #[test]
1463 fn solve_exact_5x5_permutation() {
1464 let a = Matrix::<5>::from_rows([
1466 [0.0, 1.0, 0.0, 0.0, 0.0],
1467 [1.0, 0.0, 0.0, 0.0, 0.0],
1468 [0.0, 0.0, 1.0, 0.0, 0.0],
1469 [0.0, 0.0, 0.0, 1.0, 0.0],
1470 [0.0, 0.0, 0.0, 0.0, 1.0],
1471 ]);
1472 let b = Vector::<5>::new([10.0, 20.0, 30.0, 40.0, 50.0]);
1473 let x = a.solve_exact(b).unwrap();
1474 assert_eq!(x[0], f64_to_bigrational(20.0));
1475 assert_eq!(x[1], f64_to_bigrational(10.0));
1476 assert_eq!(x[2], f64_to_bigrational(30.0));
1477 assert_eq!(x[3], f64_to_bigrational(40.0));
1478 assert_eq!(x[4], f64_to_bigrational(50.0));
1479 }
1480
1481 #[test]
1486 fn solve_exact_f64_known_2x2() {
1487 let a = Matrix::<2>::from_rows([[1.0, 2.0], [3.0, 4.0]]);
1488 let b = Vector::<2>::new([5.0, 11.0]);
1489 let x = a.solve_exact_f64(b).unwrap().into_array();
1490 assert!((x[0] - 1.0).abs() <= f64::EPSILON);
1491 assert!((x[1] - 2.0).abs() <= f64::EPSILON);
1492 }
1493
1494 #[test]
1495 fn solve_exact_f64_overflow_returns_err() {
1496 let big = f64::MAX / 2.0;
1499 let a = Matrix::<2>::from_rows([[1.0 / big, 0.0], [0.0, 1.0 / big]]);
1500 let b = Vector::<2>::new([big, big]);
1501 assert_eq!(
1502 a.solve_exact_f64(b),
1503 Err(LaError::Overflow { index: Some(0) })
1504 );
1505 }
1506
1507 #[test]
1512 fn gauss_solve_d0_returns_empty() {
1513 let a = Matrix::<0>::zero();
1514 let b = Vector::<0>::zero();
1515 assert_eq!(gauss_solve(&a, &b).unwrap().len(), 0);
1516 }
1517
1518 #[test]
1519 fn gauss_solve_d1() {
1520 let a = Matrix::<1>::from_rows([[2.0]]);
1521 let b = Vector::<1>::new([6.0]);
1522 let x = gauss_solve(&a, &b).unwrap();
1523 assert_eq!(x[0], f64_to_bigrational(3.0));
1524 }
1525
1526 #[test]
1527 fn gauss_solve_singular_column_all_zero() {
1528 let a = Matrix::<3>::from_rows([[1.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0, 1.0]]);
1529 let b = Vector::<3>::new([1.0, 2.0, 3.0]);
1530 assert_eq!(gauss_solve(&a, &b), Err(LaError::Singular { pivot_col: 1 }));
1531 }
1532
1533 #[test]
1538 fn f64_to_bigrational_positive_zero() {
1539 let r = f64_to_bigrational(0.0);
1540 assert_eq!(r, BigRational::from_integer(BigInt::from(0)));
1541 }
1542
1543 #[test]
1544 fn f64_to_bigrational_negative_zero() {
1545 let r = f64_to_bigrational(-0.0);
1546 assert_eq!(r, BigRational::from_integer(BigInt::from(0)));
1547 }
1548
1549 #[test]
1550 fn f64_to_bigrational_one() {
1551 let r = f64_to_bigrational(1.0);
1552 assert_eq!(r, BigRational::from_integer(BigInt::from(1)));
1553 }
1554
1555 #[test]
1556 fn f64_to_bigrational_negative_one() {
1557 let r = f64_to_bigrational(-1.0);
1558 assert_eq!(r, BigRational::from_integer(BigInt::from(-1)));
1559 }
1560
1561 #[test]
1562 fn f64_to_bigrational_half() {
1563 let r = f64_to_bigrational(0.5);
1564 assert_eq!(r, BigRational::new(BigInt::from(1), BigInt::from(2)));
1565 }
1566
1567 #[test]
1568 fn f64_to_bigrational_quarter() {
1569 let r = f64_to_bigrational(0.25);
1570 assert_eq!(r, BigRational::new(BigInt::from(1), BigInt::from(4)));
1571 }
1572
1573 #[test]
1574 fn f64_to_bigrational_negative_three_and_a_half() {
1575 let r = f64_to_bigrational(-3.5);
1577 assert_eq!(r, BigRational::new(BigInt::from(-7), BigInt::from(2)));
1578 }
1579
1580 #[test]
1581 fn f64_to_bigrational_integer() {
1582 let r = f64_to_bigrational(42.0);
1583 assert_eq!(r, BigRational::from_integer(BigInt::from(42)));
1584 }
1585
1586 #[test]
1587 fn f64_to_bigrational_power_of_two() {
1588 let r = f64_to_bigrational(1024.0);
1589 assert_eq!(r, BigRational::from_integer(BigInt::from(1024)));
1590 }
1591
1592 #[test]
1593 fn f64_to_bigrational_subnormal() {
1594 let tiny = 5e-324_f64; assert!(tiny.is_subnormal());
1596 let r = f64_to_bigrational(tiny);
1597 assert_eq!(
1599 r,
1600 BigRational::new(BigInt::from(1), BigInt::from(1u32) << 1074u32)
1601 );
1602 }
1603
1604 #[test]
1605 fn f64_to_bigrational_already_lowest_terms() {
1606 let r = f64_to_bigrational(0.5);
1608 assert_eq!(*r.numer(), BigInt::from(1));
1609 assert_eq!(*r.denom(), BigInt::from(2));
1610 }
1611
1612 #[test]
1613 fn f64_to_bigrational_round_trip() {
1614 let values = [
1617 0.0,
1618 1.0,
1619 -1.0,
1620 0.5,
1621 0.25,
1622 0.1,
1623 42.0,
1624 -3.5,
1625 1e10,
1626 1e-10,
1627 f64::MAX / 2.0,
1628 f64::MIN_POSITIVE,
1629 5e-324,
1630 ];
1631 for &v in &values {
1632 let r = f64_to_bigrational(v);
1633 let back = r.to_f64().expect("round-trip to_f64 failed");
1634 assert!(
1635 v.to_bits() == back.to_bits(),
1636 "round-trip failed for {v}: got {back}"
1637 );
1638 }
1639 }
1640
1641 #[test]
1642 #[should_panic(expected = "non-finite f64 in exact conversion")]
1643 fn f64_to_bigrational_panics_on_nan() {
1644 f64_to_bigrational(f64::NAN);
1645 }
1646
1647 #[test]
1648 #[should_panic(expected = "non-finite f64 in exact conversion")]
1649 fn f64_to_bigrational_panics_on_inf() {
1650 f64_to_bigrational(f64::INFINITY);
1651 }
1652
1653 #[test]
1654 #[should_panic(expected = "non-finite f64 in exact conversion")]
1655 fn f64_to_bigrational_panics_on_neg_inf() {
1656 f64_to_bigrational(f64::NEG_INFINITY);
1657 }
1658
1659 #[test]
1664 fn validate_finite_vec_ok() {
1665 assert!(validate_finite_vec(&Vector::<3>::new([1.0, 2.0, 3.0])).is_ok());
1666 }
1667
1668 #[test]
1669 fn validate_finite_vec_err_on_nan() {
1670 assert_eq!(
1671 validate_finite_vec(&Vector::<2>::new([f64::NAN, 1.0])),
1672 Err(LaError::NonFinite { row: None, col: 0 })
1673 );
1674 }
1675
1676 #[test]
1677 fn validate_finite_vec_err_on_inf() {
1678 assert_eq!(
1679 validate_finite_vec(&Vector::<2>::new([1.0, f64::NEG_INFINITY])),
1680 Err(LaError::NonFinite { row: None, col: 1 })
1681 );
1682 }
1683
1684 #[test]
1689 fn validate_finite_ok_for_finite() {
1690 assert!(validate_finite(&Matrix::<3>::identity()).is_ok());
1691 }
1692
1693 #[test]
1694 fn validate_finite_err_on_nan() {
1695 let mut m = Matrix::<2>::identity();
1696 m.set(1, 0, f64::NAN);
1697 assert_eq!(
1698 validate_finite(&m),
1699 Err(LaError::NonFinite {
1700 row: Some(1),
1701 col: 0
1702 })
1703 );
1704 }
1705
1706 #[test]
1707 fn validate_finite_err_on_inf() {
1708 let mut m = Matrix::<2>::identity();
1709 m.set(0, 1, f64::NEG_INFINITY);
1710 assert_eq!(
1711 validate_finite(&m),
1712 Err(LaError::NonFinite {
1713 row: Some(0),
1714 col: 1
1715 })
1716 );
1717 }
1718}