1#![forbid(unsafe_code)]
2
3use core::hint::cold_path;
76use core::mem::take;
77use core::num::NonZeroU64;
78use std::array::from_fn;
79
80use num_bigint::{BigInt, Sign};
81use num_rational::BigRational;
82use num_traits::ToPrimitive;
83
84use crate::matrix::Matrix;
85use crate::vector::Vector;
86use crate::{LaError, UnrepresentableReason};
87
88const F64_SIGNIFICAND_BITS: i64 = 53;
89const F64_FRACTION_BITS: i64 = 52;
90const F64_MIN_BINARY_EXPONENT: i64 = -1074;
91const F64_MIN_NORMAL_EXPONENT: i64 = -1022;
92const F64_MAX_BINARY_EXPONENT: i64 = 1023;
93const F64_EXPONENT_BIAS: i64 = 1023;
94const F64_FRACTION_MASK: u64 = (1u64 << 52) - 1;
95
96const fn f64_decompose(x: f64) -> Result<Option<(NonZeroU64, i32, bool)>, LaError> {
106 let bits = x.to_bits();
107 let biased_exp = ((bits >> 52) & 0x7FF) as i32;
108 let fraction = bits & 0x000F_FFFF_FFFF_FFFF;
109
110 if biased_exp == 0 && fraction == 0 {
112 return Ok(None);
113 }
114
115 if biased_exp == 0x7FF {
116 cold_path();
117 return Err(LaError::non_finite_at(0));
118 }
119
120 let (mantissa, raw_exp) = if biased_exp == 0 {
121 (fraction, -1074_i32)
124 } else {
125 ((1u64 << 52) | fraction, biased_exp - 1075)
128 };
129
130 let tz = mantissa.trailing_zeros();
132 let mantissa = mantissa >> tz;
133 let Some(mantissa) = NonZeroU64::new(mantissa) else {
134 cold_path();
135 return Ok(None);
136 };
137 let exponent = raw_exp + tz.cast_signed();
138 let is_negative = bits >> 63 != 0;
139
140 Ok(Some((mantissa, exponent, is_negative)))
141}
142
143fn bigint_exp_to_bigrational(mut value: BigInt, mut exp: i32) -> BigRational {
149 if value == BigInt::from(0) {
150 return BigRational::from_integer(BigInt::from(0));
151 }
152
153 if exp < 0
155 && let Some(tz) = value.trailing_zeros()
156 {
157 let exp_abs = exp.unsigned_abs();
158 let reduce = tz.min(u64::from(exp_abs));
159 value >>= reduce;
160 #[allow(clippy::cast_possible_truncation)]
161 let reduce = reduce as u32;
162 let remaining_abs = exp_abs - reduce;
163 exp = match remaining_abs {
164 0 => 0,
165 2_147_483_648 => i32::MIN,
166 value => -value.cast_signed(),
167 };
168 }
169
170 if exp >= 0 {
171 BigRational::new_raw(value << exp.cast_unsigned(), BigInt::from(1u32))
172 } else {
173 BigRational::new_raw(value, BigInt::from(1u32) << exp.unsigned_abs())
174 }
175}
176
177fn exact_rational_to_finite_f64(exact: &BigRational, index: Option<usize>) -> Result<f64, LaError> {
188 let numerator = exact.numer();
189 if numerator.sign() == Sign::NoSign {
190 return Ok(0.0);
191 }
192
193 let denominator = exact.denom();
194 let Some(denominator_exp) = denominator.trailing_zeros() else {
195 cold_path();
196 return Err(LaError::unrepresentable(
197 index,
198 rounded_rational_unrepresentable_reason(exact),
199 ));
200 };
201
202 if denominator.bits().checked_sub(1) != Some(denominator_exp) {
203 cold_path();
204 return Err(LaError::unrepresentable(
205 index,
206 rounded_rational_unrepresentable_reason(exact),
207 ));
208 }
209
210 let Ok(denominator_exp) = i32::try_from(denominator_exp) else {
211 cold_path();
212 return Err(LaError::unrepresentable(
213 index,
214 rounded_rational_unrepresentable_reason(exact),
215 ));
216 };
217
218 bigint_exp_to_finite_f64(numerator.clone(), -denominator_exp, index)
219}
220
221fn rounded_rational_unrepresentable_reason(exact: &BigRational) -> UnrepresentableReason {
227 match exact.to_f64() {
228 Some(value) if value.is_finite() => UnrepresentableReason::RequiresRounding,
229 _ => UnrepresentableReason::NotFinite,
230 }
231}
232
233fn exact_rational_to_rounded_f64(
235 exact: &BigRational,
236 index: Option<usize>,
237) -> Result<f64, LaError> {
238 let Some(value) = exact.to_f64() else {
239 cold_path();
240 return Err(LaError::unrepresentable(
241 index,
242 UnrepresentableReason::NotFinite,
243 ));
244 };
245 if value.is_finite() {
246 Ok(value)
247 } else {
248 cold_path();
249 Err(LaError::unrepresentable(
250 index,
251 UnrepresentableReason::NotFinite,
252 ))
253 }
254}
255
256fn bigint_exp_to_finite_f64(
272 mut value: BigInt,
273 exp: i32,
274 index: Option<usize>,
275) -> Result<f64, LaError> {
276 if value == BigInt::from(0) {
277 return Ok(0.0);
278 }
279
280 let is_negative = value.sign() == Sign::Minus;
281 if is_negative {
282 value = -value;
283 }
284
285 let mut exp = i64::from(exp);
286 if let Some(tz) = value.trailing_zeros() {
287 value >>= tz;
288 let Ok(tz) = i64::try_from(tz) else {
289 cold_path();
290 return Err(LaError::unrepresentable(
291 index,
292 UnrepresentableReason::NotFinite,
293 ));
294 };
295 let Some(updated_exp) = exp.checked_add(tz) else {
296 cold_path();
297 return Err(LaError::unrepresentable(
298 index,
299 UnrepresentableReason::NotFinite,
300 ));
301 };
302 exp = updated_exp;
303 }
304
305 let bit_len = value.bits();
306 let Ok(bit_len) = i64::try_from(bit_len) else {
307 cold_path();
308 return Err(LaError::unrepresentable(
309 index,
310 UnrepresentableReason::NotFinite,
311 ));
312 };
313 let Some(top_bit_exp) = exp.checked_add(bit_len - 1) else {
314 cold_path();
315 return Err(LaError::unrepresentable(
316 index,
317 UnrepresentableReason::NotFinite,
318 ));
319 };
320 if exp < F64_MIN_BINARY_EXPONENT {
321 cold_path();
322 return Err(LaError::unrepresentable(
323 index,
324 UnrepresentableReason::RequiresRounding,
325 ));
326 }
327 if top_bit_exp > F64_MAX_BINARY_EXPONENT {
328 cold_path();
329 return Err(LaError::unrepresentable(
330 index,
331 UnrepresentableReason::NotFinite,
332 ));
333 }
334 if bit_len > F64_SIGNIFICAND_BITS {
335 cold_path();
336 return Err(LaError::unrepresentable(
337 index,
338 UnrepresentableReason::RequiresRounding,
339 ));
340 }
341
342 let Some(mantissa) = value.to_u64() else {
343 cold_path();
344 return Err(LaError::unrepresentable(
345 index,
346 UnrepresentableReason::NotFinite,
347 ));
348 };
349 let sign = if is_negative { 1u64 << 63 } else { 0 };
350
351 if top_bit_exp < F64_MIN_NORMAL_EXPONENT {
352 let Ok(shift) = u32::try_from(exp - F64_MIN_BINARY_EXPONENT) else {
353 cold_path();
354 return Err(LaError::unrepresentable(
355 index,
356 UnrepresentableReason::RequiresRounding,
357 ));
358 };
359 Ok(f64::from_bits(sign | (mantissa << shift)))
360 } else {
361 let Ok(biased_exp) = u64::try_from(top_bit_exp + F64_EXPONENT_BIAS) else {
362 cold_path();
363 return Err(LaError::unrepresentable(
364 index,
365 UnrepresentableReason::NotFinite,
366 ));
367 };
368 let Ok(shift) = u32::try_from(F64_FRACTION_BITS - (bit_len - 1)) else {
369 cold_path();
370 return Err(LaError::unrepresentable(
371 index,
372 UnrepresentableReason::RequiresRounding,
373 ));
374 };
375 let significand = mantissa << shift;
376 Ok(f64::from_bits(
377 sign | (biased_exp << F64_FRACTION_BITS) | (significand & F64_FRACTION_MASK),
378 ))
379 }
380}
381
382fn bigint_exp_to_rounded_f64(value: BigInt, exp: i32) -> Result<f64, LaError> {
384 let exact = bigint_exp_to_bigrational(value, exp);
385 exact_rational_to_rounded_f64(&exact, None)
386}
387
388#[derive(Clone, Copy, Default)]
407enum Component {
408 #[default]
409 Zero,
410 NonZero {
411 mantissa: NonZeroU64,
412 exponent: i32,
413 is_negative: bool,
414 },
415}
416
417fn decompose_finite_matrix<const D: usize>(
422 m: &Matrix<D>,
423) -> Result<([[Component; D]; D], i32), LaError> {
424 let mut components = [[Component::default(); D]; D];
425 let mut e_min = i32::MAX;
426 for (r, row) in m.rows().iter().enumerate() {
427 for (c, &entry) in row.iter().enumerate() {
428 if let Some((mantissa, exponent, is_negative)) =
429 f64_decompose(entry).map_err(|_| LaError::non_finite_cell(r, c))?
430 {
431 components[r][c] = Component::NonZero {
432 mantissa,
433 exponent,
434 is_negative,
435 };
436 e_min = e_min.min(exponent);
437 }
438 }
439 }
440 Ok((components, e_min))
441}
442
443fn decompose_finite_vec<const D: usize>(v: &Vector<D>) -> Result<([Component; D], i32), LaError> {
448 let mut components = [Component::default(); D];
449 let mut e_min = i32::MAX;
450 let data = v.as_array();
451 for (i, &entry) in data.iter().enumerate() {
452 if let Some((mantissa, exponent, is_negative)) =
453 f64_decompose(entry).map_err(|_| LaError::non_finite_at(i))?
454 {
455 components[i] = Component::NonZero {
456 mantissa,
457 exponent,
458 is_negative,
459 };
460 e_min = e_min.min(exponent);
461 }
462 }
463 Ok((components, e_min))
464}
465
466#[inline]
469fn component_to_bigint(c: Component, e_min: i32) -> BigInt {
470 match c {
471 Component::Zero => BigInt::from(0),
472 Component::NonZero {
473 mantissa,
474 exponent,
475 is_negative,
476 } => {
477 let v = BigInt::from(mantissa.get()) << (exponent - e_min).cast_unsigned();
478 if is_negative { -v } else { v }
479 }
480 }
481}
482
483fn build_bigint_matrix<const D: usize>(
486 components: &[[Component; D]; D],
487 e_min: i32,
488) -> [[BigInt; D]; D] {
489 from_fn(|r| from_fn(|c| component_to_bigint(components[r][c], e_min)))
490}
491
492fn build_bigint_vec<const D: usize>(components: &[Component; D], e_min: i32) -> [BigInt; D] {
495 from_fn(|i| component_to_bigint(components[i], e_min))
496}
497
498#[inline]
500fn det2_bigint<const D: usize>(a: &[[BigInt; D]; D]) -> BigInt {
501 &a[0][0] * &a[1][1] - &a[0][1] * &a[1][0]
502}
503
504#[inline]
506#[allow(clippy::too_many_arguments)]
507fn det3_bigint_entries(
508 a00: &BigInt,
509 a01: &BigInt,
510 a02: &BigInt,
511 a10: &BigInt,
512 a11: &BigInt,
513 a12: &BigInt,
514 a20: &BigInt,
515 a21: &BigInt,
516 a22: &BigInt,
517) -> BigInt {
518 let m00 = a11 * a22 - a12 * a21;
519 let m01 = a10 * a22 - a12 * a20;
520 let m02 = a10 * a21 - a11 * a20;
521 a00 * m00 - a01 * m01 + a02 * m02
522}
523
524#[inline]
526fn det3_bigint<const D: usize>(a: &[[BigInt; D]; D]) -> BigInt {
527 det3_bigint_entries(
528 &a[0][0], &a[0][1], &a[0][2], &a[1][0], &a[1][1], &a[1][2], &a[2][0], &a[2][1], &a[2][2],
529 )
530}
531
532#[inline]
534fn det4_bigint<const D: usize>(a: &[[BigInt; D]; D]) -> BigInt {
535 let mut det = BigInt::from(0);
536
537 if a[0][0].sign() != Sign::NoSign {
538 let c00 = det3_bigint_entries(
539 &a[1][1], &a[1][2], &a[1][3], &a[2][1], &a[2][2], &a[2][3], &a[3][1], &a[3][2],
540 &a[3][3],
541 );
542 det += &a[0][0] * c00;
543 }
544 if a[0][1].sign() != Sign::NoSign {
545 let c01 = det3_bigint_entries(
546 &a[1][0], &a[1][2], &a[1][3], &a[2][0], &a[2][2], &a[2][3], &a[3][0], &a[3][2],
547 &a[3][3],
548 );
549 det -= &a[0][1] * c01;
550 }
551 if a[0][2].sign() != Sign::NoSign {
552 let c02 = det3_bigint_entries(
553 &a[1][0], &a[1][1], &a[1][3], &a[2][0], &a[2][1], &a[2][3], &a[3][0], &a[3][1],
554 &a[3][3],
555 );
556 det += &a[0][2] * c02;
557 }
558 if a[0][3].sign() != Sign::NoSign {
559 let c03 = det3_bigint_entries(
560 &a[1][0], &a[1][1], &a[1][2], &a[2][0], &a[2][1], &a[2][2], &a[3][0], &a[3][1],
561 &a[3][2],
562 );
563 det -= &a[0][3] * c03;
564 }
565
566 det
567}
568
569#[derive(Debug)]
571enum BareissResult {
572 Upper { sign: i8 },
575 Singular { pivot_col: usize },
577}
578
579fn bareiss_forward_eliminate<const D: usize>(
590 a: &mut [[BigInt; D]; D],
591 mut rhs: Option<&mut [BigInt; D]>,
592) -> BareissResult {
593 let zero = BigInt::from(0);
594 let mut prev_pivot = BigInt::from(1);
595 let mut sign: i8 = 1;
596
597 for k in 0..D {
598 if a[k][k] == zero {
600 let mut found = false;
601 for i in (k + 1)..D {
602 if a[i][k] != zero {
603 a.swap(k, i);
604 if let Some(r) = &mut rhs {
605 r.swap(k, i);
606 }
607 sign = -sign;
608 found = true;
609 break;
610 }
611 }
612 if !found {
613 cold_path();
614 return BareissResult::Singular { pivot_col: k };
615 }
616 }
617
618 for i in (k + 1)..D {
622 for j in (k + 1)..D {
623 a[i][j] = (&a[k][k] * &a[i][j] - &a[i][k] * &a[k][j]) / &prev_pivot;
624 }
625 if let Some(r) = &mut rhs {
626 r[i] = (&a[k][k] * &r[i] - &a[i][k] * &r[k]) / &prev_pivot;
627 }
628 a[i][k].clone_from(&zero);
629 }
630
631 prev_pivot.clone_from(&a[k][k]);
632 }
633
634 #[cfg(debug_assertions)]
640 #[allow(clippy::needless_range_loop)]
641 for k in 0..D {
642 assert_ne!(a[k][k], zero, "pivot at ({k}, {k}) must be non-zero");
643 for i in (k + 1)..D {
644 assert_eq!(a[i][k], zero, "sub-diagonal at ({i}, {k}) must be zero");
645 }
646 }
647
648 BareissResult::Upper { sign }
649}
650
651fn determinant_scale_exp<const D: usize>(e_min: i32) -> Result<i32, LaError> {
662 let Ok(d_i32) = i32::try_from(D) else {
663 cold_path();
664 return Err(LaError::determinant_scale_overflow(D, e_min));
665 };
666 let Some(total_exp) = e_min.checked_mul(d_i32) else {
667 cold_path();
668 return Err(LaError::determinant_scale_overflow(D, e_min));
669 };
670 Ok(total_exp)
671}
672
673fn bareiss_det_int_finite<const D: usize>(m: &Matrix<D>) -> Result<(BigInt, i32), LaError> {
686 if D == 0 {
689 return Ok((BigInt::from(1), 0));
690 }
691
692 let (components, e_min) = decompose_finite_matrix(m)?;
693
694 if e_min == i32::MAX {
696 return Ok((BigInt::from(0), 0));
697 }
698
699 let mut a = build_bigint_matrix(&components, e_min);
700 let det_int = match D {
701 1 => a[0][0].clone(),
702 2 => det2_bigint(&a),
703 3 => det3_bigint(&a),
704 4 => det4_bigint(&a),
705 _ => {
706 let sign = match bareiss_forward_eliminate(&mut a, None) {
707 BareissResult::Upper { sign } => sign,
708 BareissResult::Singular { .. } => {
709 cold_path();
710 return Ok((BigInt::from(0), 0));
711 }
712 };
713
714 if sign < 0 {
715 -&a[D - 1][D - 1]
716 } else {
717 a[D - 1][D - 1].clone()
718 }
719 }
720 };
721
722 let total_exp = determinant_scale_exp::<D>(e_min)?;
723 Ok((det_int, total_exp))
724}
725
726fn bareiss_det_finite<const D: usize>(m: &Matrix<D>) -> Result<BigRational, LaError> {
729 let (det_int, total_exp) = bareiss_det_int_finite(m)?;
730 Ok(bigint_exp_to_bigrational(det_int, total_exp))
731}
732
733fn gauss_solve_finite<const D: usize>(
742 m: &Matrix<D>,
743 b: &Vector<D>,
744) -> Result<[BigRational; D], LaError> {
745 let (m_components, m_e_min) = decompose_finite_matrix(m)?;
746 let (b_components, b_e_min) = decompose_finite_vec(b)?;
747 gauss_solve_components(m_components, m_e_min, b_components, b_e_min)
748}
749
750fn gauss_solve_components<const D: usize>(
766 m_components: [[Component; D]; D],
767 m_e_min: i32,
768 b_components: [Component; D],
769 b_e_min: i32,
770) -> Result<[BigRational; D], LaError> {
771 let mut e_min = m_e_min.min(b_e_min);
772 if e_min == i32::MAX {
773 e_min = 0;
774 }
775
776 let mut a = build_bigint_matrix(&m_components, e_min);
777 let mut rhs = build_bigint_vec(&b_components, e_min);
778
779 match bareiss_forward_eliminate(&mut a, Some(&mut rhs)) {
780 BareissResult::Upper { .. } => {}
781 BareissResult::Singular { pivot_col } => {
782 cold_path();
783 return Err(LaError::Singular { pivot_col });
784 }
785 }
786
787 let mut x: [BigRational; D] = from_fn(|_| BigRational::from_integer(BigInt::from(0)));
788 for i in (0..D).rev() {
789 let mut sum = BigRational::from_integer(take(&mut rhs[i]));
790 for j in (i + 1)..D {
791 let a_ij = BigRational::from_integer(take(&mut a[i][j]));
792 sum -= &a_ij * &x[j];
793 }
794 let a_ii = BigRational::from_integer(take(&mut a[i][i]));
795 x[i] = sum / &a_ii;
796 }
797
798 Ok(x)
799}
800
801#[inline]
812fn det_exact_finite<const D: usize>(m: &Matrix<D>) -> Result<BigRational, LaError> {
813 bareiss_det_finite(m)
814}
815
816#[inline]
831fn det_exact_f64_finite<const D: usize>(m: &Matrix<D>) -> Result<f64, LaError> {
832 let (det_int, total_exp) = bareiss_det_int_finite(m)?;
833 bigint_exp_to_finite_f64(det_int, total_exp, None)
834}
835
836#[inline]
849fn det_exact_rounded_f64_finite<const D: usize>(m: &Matrix<D>) -> Result<f64, LaError> {
850 let (det_int, total_exp) = bareiss_det_int_finite(m)?;
851 bigint_exp_to_rounded_f64(det_int, total_exp)
852}
853
854#[inline]
863fn solve_exact_finite<const D: usize>(
864 m: &Matrix<D>,
865 b: Vector<D>,
866) -> Result<[BigRational; D], LaError> {
867 gauss_solve_finite(m, &b)
868}
869
870#[inline]
882fn solve_exact_f64_finite<const D: usize>(
883 m: &Matrix<D>,
884 b: Vector<D>,
885) -> Result<Vector<D>, LaError> {
886 let exact = solve_exact_finite(m, b)?;
887 let mut result = [0.0f64; D];
888 for (i, val) in exact.iter().enumerate() {
889 result[i] = exact_rational_to_finite_f64(val, Some(i))?;
890 }
891 Ok(Vector::new_unchecked(result))
892}
893
894#[inline]
906fn solve_exact_rounded_f64_finite<const D: usize>(
907 m: &Matrix<D>,
908 b: Vector<D>,
909) -> Result<Vector<D>, LaError> {
910 let exact = solve_exact_finite(m, b)?;
911 let mut result = [0.0f64; D];
912 for (i, val) in exact.iter().enumerate() {
913 result[i] = exact_rational_to_rounded_f64(val, Some(i))?;
914 }
915 Ok(Vector::new_unchecked(result))
916}
917
918#[inline]
931fn det_sign_exact_finite<const D: usize>(m: &Matrix<D>) -> Result<i8, LaError> {
932 match (m.det_direct(), m.det_errbound()) {
933 (Ok(Some(det_f64)), Ok(Some(err))) => {
934 if det_f64 > err {
935 return Ok(1);
936 }
937 if det_f64 < -err {
938 return Ok(-1);
939 }
940 }
941 (Err(LaError::NonFinite { row: None, .. }), _)
942 | (_, Err(LaError::NonFinite { row: None, .. })) => {}
943 (Err(err), _) | (_, Err(err)) => return Err(err),
944 _ => {}
945 }
946
947 cold_path();
948 let (det_int, _) = bareiss_det_int_finite(m)?;
949 Ok(match det_int.sign() {
950 Sign::Plus => 1,
951 Sign::Minus => -1,
952 Sign::NoSign => 0,
953 })
954}
955
956impl<const D: usize> Matrix<D> {
957 #[inline]
989 pub fn det_exact(&self) -> Result<BigRational, LaError> {
990 det_exact_finite(self)
991 }
992
993 #[inline]
1021 pub fn det_exact_f64(&self) -> Result<f64, LaError> {
1022 det_exact_f64_finite(self)
1023 }
1024
1025 #[inline]
1064 pub fn det_exact_rounded_f64(&self) -> Result<f64, LaError> {
1065 det_exact_rounded_f64_finite(self)
1066 }
1067
1068 #[inline]
1113 pub fn solve_exact(&self, b: Vector<D>) -> Result<[BigRational; D], LaError> {
1114 solve_exact_finite(self, b)
1115 }
1116
1117 #[inline]
1145 pub fn solve_exact_f64(&self, b: Vector<D>) -> Result<Vector<D>, LaError> {
1146 solve_exact_f64_finite(self, b)
1147 }
1148
1149 #[inline]
1184 pub fn solve_exact_rounded_f64(&self, b: Vector<D>) -> Result<Vector<D>, LaError> {
1185 solve_exact_rounded_f64_finite(self, b)
1186 }
1187
1188 #[inline]
1228 pub fn det_sign_exact(&self) -> Result<i8, LaError> {
1229 det_sign_exact_finite(self)
1230 }
1231}
1232
1233#[cfg(test)]
1234mod tests {
1235 use super::*;
1236 use crate::DEFAULT_SINGULAR_TOL;
1237
1238 use core::assert_matches;
1239 use num_traits::Signed;
1240 use pastey::paste;
1241 use std::array::from_fn;
1242
1243 fn f64_to_bigrational(x: f64) -> BigRational {
1263 let Some((mantissa, exponent, is_negative)) =
1264 f64_decompose(x).expect("test helper requires finite f64 input")
1265 else {
1266 return BigRational::from_integer(BigInt::from(0));
1267 };
1268
1269 let numer = if is_negative {
1270 -BigInt::from(mantissa.get())
1271 } else {
1272 BigInt::from(mantissa.get())
1273 };
1274
1275 if exponent >= 0 {
1276 BigRational::new_raw(numer << exponent.cast_unsigned(), BigInt::from(1u32))
1277 } else {
1278 BigRational::new_raw(numer, BigInt::from(1u32) << (-exponent).cast_unsigned())
1279 }
1280 }
1281
1282 macro_rules! gen_internal_matrix_exact_tests {
1287 ($d:literal) => {
1288 paste! {
1289 #[test]
1290 fn [<internal_matrix_exact_paths_reuse_validation_ $d d>]() {
1291 let a = Matrix::<$d>::identity();
1292 let b = Vector::<$d>::new([1.0; $d]);
1293
1294 assert_eq!(
1295 det_exact_finite(&a).unwrap(),
1296 BigRational::from_integer(BigInt::from(1))
1297 );
1298 assert!((det_exact_f64_finite(&a).unwrap() - 1.0).abs() <= f64::EPSILON);
1299 assert_eq!(det_sign_exact_finite(&a).unwrap(), 1);
1300
1301 let exact = solve_exact_finite(&a, b).unwrap();
1302 for value in exact {
1303 assert_eq!(value, BigRational::from_integer(BigInt::from(1)));
1304 }
1305
1306 let exact_f64 = a.solve_exact_f64(b).unwrap();
1307 for value in exact_f64.into_array() {
1308 assert!((value - 1.0).abs() <= f64::EPSILON);
1309 }
1310 }
1311 }
1312 };
1313 }
1314
1315 gen_internal_matrix_exact_tests!(2);
1316 gen_internal_matrix_exact_tests!(3);
1317 gen_internal_matrix_exact_tests!(4);
1318 gen_internal_matrix_exact_tests!(5);
1319
1320 macro_rules! gen_det_exact_tests {
1321 ($d:literal) => {
1322 paste! {
1323 #[test]
1324 fn [<det_exact_identity_ $d d>]() {
1325 let det = Matrix::<$d>::identity().det_exact().unwrap();
1326 assert_eq!(det, BigRational::from_integer(BigInt::from(1)));
1327 }
1328 }
1329 };
1330 }
1331
1332 gen_det_exact_tests!(2);
1333 gen_det_exact_tests!(3);
1334 gen_det_exact_tests!(4);
1335 gen_det_exact_tests!(5);
1336
1337 macro_rules! gen_det_exact_f64_tests {
1338 ($d:literal) => {
1339 paste! {
1340 #[test]
1341 fn [<det_exact_f64_identity_ $d d>]() {
1342 let det = Matrix::<$d>::identity().det_exact_f64().unwrap();
1343 assert!((det - 1.0).abs() <= f64::EPSILON);
1344 }
1345 }
1346 };
1347 }
1348
1349 gen_det_exact_f64_tests!(2);
1350 gen_det_exact_f64_tests!(3);
1351 gen_det_exact_f64_tests!(4);
1352 gen_det_exact_f64_tests!(5);
1353
1354 macro_rules! gen_det_exact_f64_agrees_with_det_direct {
1357 ($d:literal) => {
1358 paste! {
1359 #[test]
1360 fn [<det_exact_f64_agrees_with_det_direct_ $d d>]() {
1361 let mut rows = [[0.0f64; $d]; $d];
1364 let mut value = 2.0;
1365 for (i, row) in rows.iter_mut().enumerate() {
1366 row[i] = value;
1367 value *= 2.0;
1368 }
1369 let m = Matrix::<$d>::try_from_rows(rows).unwrap();
1370 let exact = m.det_exact_f64().unwrap();
1371 let direct = m.det_direct().unwrap().unwrap();
1372 assert_eq!(exact.to_bits(), direct.to_bits());
1373 }
1374 }
1375 };
1376 }
1377
1378 gen_det_exact_f64_agrees_with_det_direct!(2);
1379 gen_det_exact_f64_agrees_with_det_direct!(3);
1380 gen_det_exact_f64_agrees_with_det_direct!(4);
1381
1382 #[test]
1383 fn det_sign_exact_d0_is_positive() {
1384 assert_eq!(Matrix::<0>::zero().det_sign_exact().unwrap(), 1);
1385 }
1386
1387 #[test]
1388 fn det_sign_exact_d1_positive() {
1389 let m = Matrix::<1>::try_from_rows([[42.0]]).unwrap();
1390 assert_eq!(m.det_sign_exact().unwrap(), 1);
1391 }
1392
1393 #[test]
1394 fn det_sign_exact_d1_negative() {
1395 let m = Matrix::<1>::try_from_rows([[-3.5]]).unwrap();
1396 assert_eq!(m.det_sign_exact().unwrap(), -1);
1397 }
1398
1399 #[test]
1400 fn det_sign_exact_d1_zero() {
1401 let m = Matrix::<1>::try_from_rows([[0.0]]).unwrap();
1402 assert_eq!(m.det_sign_exact().unwrap(), 0);
1403 }
1404
1405 #[test]
1406 fn det_sign_exact_identity_2d() {
1407 assert_eq!(Matrix::<2>::identity().det_sign_exact().unwrap(), 1);
1408 }
1409
1410 #[test]
1411 fn det_sign_exact_identity_3d() {
1412 assert_eq!(Matrix::<3>::identity().det_sign_exact().unwrap(), 1);
1413 }
1414
1415 #[test]
1416 fn det_sign_exact_identity_4d() {
1417 assert_eq!(Matrix::<4>::identity().det_sign_exact().unwrap(), 1);
1418 }
1419
1420 #[test]
1421 fn det_sign_exact_identity_5d() {
1422 assert_eq!(Matrix::<5>::identity().det_sign_exact().unwrap(), 1);
1423 }
1424
1425 #[test]
1426 fn det_sign_exact_singular_duplicate_rows() {
1427 let m = Matrix::<3>::try_from_rows([
1428 [1.0, 2.0, 3.0],
1429 [4.0, 5.0, 6.0],
1430 [1.0, 2.0, 3.0], ])
1432 .unwrap();
1433 assert_eq!(m.det_sign_exact().unwrap(), 0);
1434 }
1435
1436 #[test]
1437 fn det_sign_exact_singular_linear_combination() {
1438 let m = Matrix::<3>::try_from_rows([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [5.0, 7.0, 9.0]])
1440 .unwrap();
1441 assert_eq!(m.det_sign_exact().unwrap(), 0);
1442 }
1443
1444 #[test]
1445 fn det_sign_exact_negative_det_row_swap() {
1446 let m = Matrix::<3>::try_from_rows([[0.0, 1.0, 0.0], [1.0, 0.0, 0.0], [0.0, 0.0, 1.0]])
1448 .unwrap();
1449 assert_eq!(m.det_sign_exact().unwrap(), -1);
1450 }
1451
1452 #[test]
1453 fn det_sign_exact_negative_det_known() {
1454 let m = Matrix::<2>::try_from_rows([[1.0, 2.0], [3.0, 4.0]]).unwrap();
1456 assert_eq!(m.det_sign_exact().unwrap(), -1);
1457 }
1458
1459 #[test]
1460 fn det_sign_exact_agrees_with_det_for_spd() {
1461 let m = Matrix::<3>::try_from_rows([[4.0, 2.0, 0.0], [2.0, 5.0, 1.0], [0.0, 1.0, 3.0]])
1463 .unwrap();
1464 assert_eq!(m.det_sign_exact().unwrap(), 1);
1465 assert!(m.det().unwrap() > 0.0);
1466 }
1467
1468 #[test]
1475 fn det_sign_exact_near_singular_perturbation() {
1476 let perturbation = f64::from_bits(0x3CD0_0000_0000_0000); let m = Matrix::<3>::try_from_rows([
1478 [1.0 + perturbation, 2.0, 3.0],
1479 [4.0, 5.0, 6.0],
1480 [7.0, 8.0, 9.0],
1481 ])
1482 .unwrap();
1483 assert_eq!(m.det_sign_exact().unwrap(), -1);
1485 }
1486
1487 #[test]
1491 fn det_sign_exact_fast_filter_positive_4x4() {
1492 let m = Matrix::<4>::try_from_rows([
1493 [2.0, 1.0, 0.0, 0.0],
1494 [1.0, 3.0, 1.0, 0.0],
1495 [0.0, 1.0, 4.0, 1.0],
1496 [0.0, 0.0, 1.0, 5.0],
1497 ])
1498 .unwrap();
1499 assert_eq!(m.det_sign_exact().unwrap(), 1);
1501 }
1502
1503 #[test]
1504 fn det_sign_exact_fast_filter_negative_4x4() {
1505 let m = Matrix::<4>::try_from_rows([
1507 [1.0, 3.0, 1.0, 0.0],
1508 [2.0, 1.0, 0.0, 0.0],
1509 [0.0, 1.0, 4.0, 1.0],
1510 [0.0, 0.0, 1.0, 5.0],
1511 ])
1512 .unwrap();
1513 assert_eq!(m.det_sign_exact().unwrap(), -1);
1514 }
1515
1516 #[test]
1517 fn det_sign_exact_subnormal_entries() {
1518 let tiny = 5e-324_f64; assert!(tiny.is_subnormal());
1521
1522 let m = Matrix::<2>::try_from_rows([[tiny, 0.0], [0.0, tiny]]).unwrap();
1523 assert_eq!(m.det_sign_exact().unwrap(), 1);
1525 }
1526
1527 #[test]
1528 fn det_sign_exact_returns_err_on_nan() {
1529 assert_eq!(
1530 Matrix::<2>::try_from_rows([[f64::NAN, 0.0], [0.0, 1.0]]),
1531 Err(LaError::NonFinite {
1532 row: Some(0),
1533 col: 0
1534 })
1535 );
1536 }
1537
1538 #[test]
1539 fn det_sign_exact_returns_err_on_infinity() {
1540 assert_eq!(
1541 Matrix::<2>::try_from_rows([[f64::INFINITY, 0.0], [0.0, 1.0]]),
1542 Err(LaError::NonFinite {
1543 row: Some(0),
1544 col: 0
1545 })
1546 );
1547 }
1548
1549 #[test]
1550 fn exact_public_methods_reject_unchecked_nonfinite_matrix_before_computation() {
1551 let m = Matrix::<2>::from_rows_unchecked([[1.0, 0.0], [f64::NAN, 1.0]]);
1552 let b = Vector::<2>::new([1.0, 1.0]);
1553 let expected = Err(LaError::NonFinite {
1554 row: Some(1),
1555 col: 0,
1556 });
1557
1558 assert_eq!(m.det_exact().map(|_| ()), expected);
1559 assert_eq!(m.det_exact_f64().map(|_| ()), expected);
1560 assert_eq!(m.det_exact_rounded_f64().map(|_| ()), expected);
1561 assert_eq!(m.det_sign_exact().map(|_| ()), expected);
1562 assert_eq!(m.solve_exact(b).map(|_| ()), expected);
1563 assert_eq!(m.solve_exact_f64(b).map(|_| ()), expected);
1564 assert_eq!(m.solve_exact_rounded_f64(b).map(|_| ()), expected);
1565 }
1566
1567 #[test]
1568 fn exact_solve_public_methods_reject_unchecked_nonfinite_rhs_before_computation() {
1569 let a = Matrix::<2>::identity();
1570 let b = Vector::<2>::new_unchecked([1.0, f64::INFINITY]);
1571 let expected = Err(LaError::NonFinite { row: None, col: 1 });
1572
1573 assert_eq!(a.solve_exact(b).map(|_| ()), expected);
1574 assert_eq!(a.solve_exact_f64(b).map(|_| ()), expected);
1575 assert_eq!(a.solve_exact_rounded_f64(b).map(|_| ()), expected);
1576 }
1577
1578 #[test]
1579 fn det_sign_exact_returns_err_on_nan_5x5() {
1580 let mut m = Matrix::<5>::identity();
1582 assert_eq!(
1583 m.set(2, 3, f64::NAN),
1584 Err(LaError::NonFinite {
1585 row: Some(2),
1586 col: 3
1587 })
1588 );
1589 }
1590
1591 #[test]
1592 fn det_sign_exact_returns_err_on_infinity_5x5() {
1593 let mut m = Matrix::<5>::identity();
1594 assert_eq!(
1595 m.set(0, 0, f64::INFINITY),
1596 Err(LaError::NonFinite {
1597 row: Some(0),
1598 col: 0
1599 })
1600 );
1601 }
1602
1603 #[test]
1604 fn det_sign_exact_pivot_needed_5x5() {
1605 let m = Matrix::<5>::try_from_rows([
1608 [0.0, 1.0, 0.0, 0.0, 0.0],
1609 [1.0, 0.0, 0.0, 0.0, 0.0],
1610 [0.0, 0.0, 1.0, 0.0, 0.0],
1611 [0.0, 0.0, 0.0, 1.0, 0.0],
1612 [0.0, 0.0, 0.0, 0.0, 1.0],
1613 ])
1614 .unwrap();
1615 assert_eq!(m.det_sign_exact().unwrap(), -1);
1616 }
1617
1618 #[test]
1619 fn det_sign_exact_5x5_known() {
1620 let m = Matrix::<5>::try_from_rows([
1622 [0.0, 1.0, 0.0, 0.0, 0.0],
1623 [1.0, 0.0, 0.0, 0.0, 0.0],
1624 [0.0, 0.0, 0.0, 1.0, 0.0],
1625 [0.0, 0.0, 1.0, 0.0, 0.0],
1626 [0.0, 0.0, 0.0, 0.0, 1.0],
1627 ])
1628 .unwrap();
1629 assert_eq!(m.det_sign_exact().unwrap(), 1);
1631 }
1632
1633 #[test]
1638 fn det_errbound_d0_is_zero() {
1639 assert_eq!(Matrix::<0>::zero().det_errbound(), Ok(Some(0.0)));
1640 }
1641
1642 #[test]
1643 fn det_errbound_d1_is_zero() {
1644 assert_eq!(
1645 Matrix::<1>::try_from_rows([[42.0]]).unwrap().det_errbound(),
1646 Ok(Some(0.0))
1647 );
1648 }
1649
1650 #[test]
1651 fn det_errbound_d2_positive() {
1652 let m = Matrix::<2>::try_from_rows([[1.0, 2.0], [3.0, 4.0]]).unwrap();
1653 let bound = m.det_errbound().unwrap().unwrap();
1654 assert!(bound > 0.0);
1655 assert!(crate::ERR_COEFF_2.mul_add(-10.0, bound).abs() < 1e-30);
1657 }
1658
1659 #[test]
1660 fn det_errbound_d3_positive() {
1661 let m = Matrix::<3>::identity();
1662 let bound = m.det_errbound().unwrap().unwrap();
1663 assert!(bound > 0.0);
1664 }
1665
1666 #[test]
1667 fn det_errbound_d3_non_identity() {
1668 let m = Matrix::<3>::try_from_rows([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [7.0, 8.0, 10.0]])
1670 .unwrap();
1671 let bound = m.det_errbound().unwrap().unwrap();
1672 assert!(bound > 0.0);
1673 }
1674
1675 #[test]
1676 fn det_errbound_d4_positive() {
1677 let m = Matrix::<4>::identity();
1678 let bound = m.det_errbound().unwrap().unwrap();
1679 assert!(bound > 0.0);
1680 }
1681
1682 #[test]
1683 fn det_errbound_d4_non_identity() {
1684 let m = Matrix::<4>::try_from_rows([
1686 [1.0, 0.0, 0.0, 0.0],
1687 [0.0, 2.0, 0.0, 0.0],
1688 [0.0, 0.0, 3.0, 0.0],
1689 [0.0, 0.0, 0.0, 4.0],
1690 ])
1691 .unwrap();
1692 let bound = m.det_errbound().unwrap().unwrap();
1693 assert!(bound > 0.0);
1694 }
1695
1696 #[test]
1697 fn det_errbound_d5_is_none() {
1698 assert_eq!(Matrix::<5>::identity().det_errbound(), Ok(None));
1699 }
1700
1701 #[test]
1706 fn f64_decompose_zero() {
1707 assert!(f64_decompose(0.0).unwrap().is_none());
1708 assert!(f64_decompose(-0.0).unwrap().is_none());
1709 }
1710
1711 #[test]
1712 fn f64_decompose_one() {
1713 let (mant, exp, neg) = f64_decompose(1.0).unwrap().unwrap();
1714 assert_eq!(mant.get(), 1);
1715 assert_eq!(exp, 0);
1716 assert!(!neg);
1717 }
1718
1719 #[test]
1720 fn f64_decompose_negative() {
1721 let (mant, exp, neg) = f64_decompose(-3.5).unwrap().unwrap();
1722 assert_eq!(mant.get(), 7);
1724 assert_eq!(exp, -1);
1725 assert!(neg);
1726 }
1727
1728 #[test]
1729 fn f64_decompose_subnormal() {
1730 let tiny = 5e-324_f64;
1731 assert!(tiny.is_subnormal());
1732 let (mant, exp, neg) = f64_decompose(tiny).unwrap().unwrap();
1733 assert_eq!(mant.get(), 1);
1734 assert_eq!(exp, -1074);
1735 assert!(!neg);
1736 }
1737
1738 #[test]
1739 fn f64_decompose_power_of_two() {
1740 let (mant, exp, neg) = f64_decompose(1024.0).unwrap().unwrap();
1741 assert_eq!(mant.get(), 1);
1742 assert_eq!(exp, 10); assert!(!neg);
1744 }
1745
1746 #[test]
1747 fn f64_decompose_rejects_nan() {
1748 assert_eq!(
1749 f64_decompose(f64::NAN),
1750 Err(LaError::NonFinite { row: None, col: 0 })
1751 );
1752 }
1753
1754 #[test]
1755 fn component_to_bigint_distinguishes_zero_from_nonzero_mantissa() {
1756 assert_eq!(
1757 component_to_bigint(Component::default(), -10),
1758 BigInt::from(0)
1759 );
1760
1761 let positive = Component::NonZero {
1762 mantissa: NonZeroU64::new(3).unwrap(),
1763 exponent: 4,
1764 is_negative: false,
1765 };
1766 assert_eq!(component_to_bigint(positive, 1), BigInt::from(24));
1767
1768 let negative = Component::NonZero {
1769 mantissa: NonZeroU64::new(5).unwrap(),
1770 exponent: 3,
1771 is_negative: true,
1772 };
1773 assert_eq!(component_to_bigint(negative, 1), BigInt::from(-20));
1774 }
1775
1776 #[test]
1777 fn determinant_scale_exp_multiplies_dimension_and_min_exponent() {
1778 assert_eq!(determinant_scale_exp::<4>(-1074), Ok(-4296));
1779 }
1780
1781 #[test]
1782 fn determinant_scale_exp_rejects_dimension_too_large_for_i32() {
1783 assert_eq!(
1784 determinant_scale_exp::<{ i32::MAX as usize + 1 }>(-1074),
1785 Err(LaError::DeterminantScaleOverflow {
1786 dim: i32::MAX as usize + 1,
1787 min_exponent: -1074,
1788 })
1789 );
1790 }
1791
1792 #[test]
1793 fn determinant_scale_exp_rejects_exponent_product_overflow() {
1794 assert_eq!(
1795 determinant_scale_exp::<3_000_000>(-1074),
1796 Err(LaError::DeterminantScaleOverflow {
1797 dim: 3_000_000,
1798 min_exponent: -1074,
1799 })
1800 );
1801 }
1802
1803 #[test]
1808 fn bareiss_det_int_d0() {
1809 let m = Matrix::<0>::zero();
1810 let (det, exp) = bareiss_det_int_finite(&m).unwrap();
1811 assert_eq!(det, BigInt::from(1));
1812 assert_eq!(exp, 0);
1813 }
1814
1815 #[test]
1821 fn bareiss_det_int_d1_cases() {
1822 let cases: &[(f64, i64, i32)] = &[
1823 (7.0, 7, 0), (0.0, 0, 0), (-3.5, -7, -1), (0.5, 1, -1), ];
1829 for &(input, expected_det_int, expected_exp) in cases {
1830 let m = Matrix::<1>::try_from_rows([[input]]).unwrap();
1831 let (det, exp) = bareiss_det_int_finite(&m).unwrap();
1832 assert_eq!(
1833 det,
1834 BigInt::from(expected_det_int),
1835 "det_int for input={input}"
1836 );
1837 assert_eq!(exp, expected_exp, "exp for input={input}");
1838 }
1839 }
1840
1841 #[test]
1842 fn bareiss_det_int_d2_known() {
1843 let m = Matrix::<2>::try_from_rows([[1.0, 2.0], [3.0, 4.0]]).unwrap();
1845 let (det_int, total_exp) = bareiss_det_int_finite(&m).unwrap();
1846 let det = bigint_exp_to_bigrational(det_int, total_exp);
1848 assert_eq!(det, BigRational::from_integer(BigInt::from(-2)));
1849 }
1850
1851 #[test]
1852 fn bareiss_det_int_all_zeros() {
1853 let m = Matrix::<3>::zero();
1854 let (det, _) = bareiss_det_int_finite(&m).unwrap();
1855 assert_eq!(det, BigInt::from(0));
1856 }
1857
1858 #[test]
1859 fn bareiss_det_int_sign_matches_det_sign_exact() {
1860 let m = Matrix::<3>::try_from_rows([[0.0, 1.0, 0.0], [1.0, 0.0, 0.0], [0.0, 0.0, 1.0]])
1862 .unwrap();
1863 let (det_int, _) = bareiss_det_int_finite(&m).unwrap();
1864 assert_eq!(det_int.sign(), Sign::Minus); }
1866
1867 #[test]
1868 fn bareiss_det_int_fractional_entries() {
1869 let m = Matrix::<2>::try_from_rows([[0.5, 0.25], [1.0, 1.0]]).unwrap();
1872 let (det_int, total_exp) = bareiss_det_int_finite(&m).unwrap();
1873 let det = bigint_exp_to_bigrational(det_int, total_exp);
1874 assert_eq!(det, BigRational::new(BigInt::from(1), BigInt::from(4)));
1875 }
1876
1877 #[test]
1878 fn bareiss_det_int_d3_with_pivoting() {
1879 let m = Matrix::<3>::try_from_rows([[0.0, 1.0, 0.0], [1.0, 0.0, 0.0], [0.0, 0.0, 1.0]])
1881 .unwrap();
1882 let (det_int, total_exp) = bareiss_det_int_finite(&m).unwrap();
1883 let det = bigint_exp_to_bigrational(det_int, total_exp);
1884 assert_eq!(det, BigRational::from_integer(BigInt::from(-1)));
1885 }
1886
1887 macro_rules! gen_bareiss_det_int_identity_tests {
1889 ($d:literal) => {
1890 paste! {
1891 #[test]
1892 fn [<bareiss_det_int_identity_ $d d>]() {
1893 let m = Matrix::<$d>::identity();
1894 let (det_int, total_exp) = bareiss_det_int_finite(&m).unwrap();
1895 let det = bigint_exp_to_bigrational(det_int, total_exp);
1896 assert_eq!(det, BigRational::from_integer(BigInt::from(1)));
1897 }
1898 }
1899 };
1900 }
1901
1902 gen_bareiss_det_int_identity_tests!(2);
1903 gen_bareiss_det_int_identity_tests!(3);
1904 gen_bareiss_det_int_identity_tests!(4);
1905 gen_bareiss_det_int_identity_tests!(5);
1906
1907 #[test]
1912 fn bigint_exp_to_bigrational_zero() {
1913 let r = bigint_exp_to_bigrational(BigInt::from(0), -50);
1914 assert_eq!(r, BigRational::from_integer(BigInt::from(0)));
1915 }
1916
1917 #[test]
1918 fn bigint_exp_to_bigrational_positive_exp() {
1919 let r = bigint_exp_to_bigrational(BigInt::from(3), 2);
1921 assert_eq!(r, BigRational::from_integer(BigInt::from(12)));
1922 }
1923
1924 #[test]
1925 fn bigint_exp_to_bigrational_negative_exp_reduced() {
1926 let r = bigint_exp_to_bigrational(BigInt::from(6), -2);
1928 assert_eq!(*r.numer(), BigInt::from(3));
1929 assert_eq!(*r.denom(), BigInt::from(2));
1930 }
1931
1932 #[test]
1933 fn bigint_exp_to_bigrational_negative_exp_reduces_to_integer() {
1934 let r = bigint_exp_to_bigrational(BigInt::from(8), -3);
1936 assert_eq!(r, BigRational::from_integer(BigInt::from(1)));
1937 }
1938
1939 #[test]
1940 fn bigint_exp_to_bigrational_negative_exp_already_odd() {
1941 let r = bigint_exp_to_bigrational(BigInt::from(3), -2);
1943 assert_eq!(*r.numer(), BigInt::from(3));
1944 assert_eq!(*r.denom(), BigInt::from(4));
1945 }
1946
1947 #[test]
1948 fn bigint_exp_to_bigrational_negative_value() {
1949 let r = bigint_exp_to_bigrational(BigInt::from(-5), 1);
1951 assert_eq!(r, BigRational::from_integer(BigInt::from(-10)));
1952 }
1953
1954 #[test]
1955 fn bigint_exp_to_bigrational_negative_value_with_denominator() {
1956 let r = bigint_exp_to_bigrational(BigInt::from(-3), -2);
1958 assert_eq!(*r.numer(), BigInt::from(-3));
1959 assert_eq!(*r.denom(), BigInt::from(4));
1960 }
1961
1962 #[test]
1967 fn bareiss_det_d1_returns_entry() {
1968 let det = Matrix::<1>::try_from_rows([[7.0]])
1969 .unwrap()
1970 .det_exact()
1971 .unwrap();
1972 assert_eq!(det, f64_to_bigrational(7.0));
1973 }
1974
1975 #[test]
1976 fn bareiss_det_d3_with_pivoting() {
1977 let m = Matrix::<3>::try_from_rows([[0.0, 1.0, 0.0], [1.0, 0.0, 0.0], [0.0, 0.0, 1.0]])
1979 .unwrap();
1980 let det = m.det_exact().unwrap();
1981 assert_eq!(det, BigRational::from_integer(BigInt::from(-1)));
1983 }
1984
1985 #[test]
1986 fn bareiss_det_singular_all_zeros_in_column() {
1987 let m = Matrix::<3>::try_from_rows([[1.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0, 1.0]])
1989 .unwrap();
1990 let det = m.det_exact().unwrap();
1991 assert_eq!(det, BigRational::from_integer(BigInt::from(0)));
1992 }
1993
1994 #[test]
1995 fn det_sign_exact_overflow_determinant_finite_entries() {
1996 let big = f64::MAX / 2.0;
2000 assert!(big.is_finite());
2001 let m = Matrix::<3>::try_from_rows([[0.0, 0.0, 1.0], [big, 0.0, 1.0], [0.0, big, 1.0]])
2002 .unwrap();
2003 assert_eq!(m.det_sign_exact().unwrap(), 1);
2005 }
2006
2007 #[test]
2012 fn det_exact_d0_is_one() {
2013 let det = Matrix::<0>::zero().det_exact().unwrap();
2014 assert_eq!(det, BigRational::from_integer(BigInt::from(1)));
2015 }
2016
2017 #[test]
2018 fn det_exact_known_2x2() {
2019 let m = Matrix::<2>::try_from_rows([[1.0, 2.0], [3.0, 4.0]]).unwrap();
2021 let det = m.det_exact().unwrap();
2022 assert_eq!(det, BigRational::from_integer(BigInt::from(-2)));
2023 }
2024
2025 #[test]
2026 fn det_exact_known_dense_4x4() {
2027 let m = Matrix::<4>::try_from_rows([
2028 [4.0, 1.0, 3.0, 2.0],
2029 [0.0, 5.0, 2.0, 1.0],
2030 [7.0, 2.0, 6.0, 3.0],
2031 [1.0, 8.0, 4.0, 9.0],
2032 ])
2033 .unwrap();
2034
2035 assert_eq!(
2036 m.det_exact(),
2037 Ok(BigRational::from_integer(BigInt::from(92)))
2038 );
2039 }
2040
2041 #[test]
2042 fn det_exact_singular_returns_zero() {
2043 let m = Matrix::<3>::try_from_rows([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [7.0, 8.0, 9.0]])
2045 .unwrap();
2046 let det = m.det_exact().unwrap();
2047 assert_eq!(det, BigRational::from_integer(BigInt::from(0)));
2048 }
2049
2050 #[test]
2051 fn det_exact_near_singular_perturbation() {
2052 let perturbation = f64::from_bits(0x3CD0_0000_0000_0000); let m = Matrix::<3>::try_from_rows([
2055 [1.0 + perturbation, 2.0, 3.0],
2056 [4.0, 5.0, 6.0],
2057 [7.0, 8.0, 9.0],
2058 ])
2059 .unwrap();
2060 let det = m.det_exact().unwrap();
2061 let expected = BigRational::new(BigInt::from(-3), BigInt::from(1u64 << 50));
2063 assert_eq!(det, expected);
2064 }
2065
2066 #[test]
2067 fn det_exact_5x5_permutation() {
2068 let m = Matrix::<5>::try_from_rows([
2070 [0.0, 1.0, 0.0, 0.0, 0.0],
2071 [1.0, 0.0, 0.0, 0.0, 0.0],
2072 [0.0, 0.0, 1.0, 0.0, 0.0],
2073 [0.0, 0.0, 0.0, 1.0, 0.0],
2074 [0.0, 0.0, 0.0, 0.0, 1.0],
2075 ])
2076 .unwrap();
2077 let det = m.det_exact().unwrap();
2078 assert_eq!(det, BigRational::from_integer(BigInt::from(-1)));
2079 }
2080
2081 #[test]
2086 fn det_exact_f64_known_2x2() {
2087 let m = Matrix::<2>::try_from_rows([[1.0, 2.0], [3.0, 4.0]]).unwrap();
2088 let det = m.det_exact_f64().unwrap();
2089 assert!((det - (-2.0)).abs() <= f64::EPSILON);
2090 }
2091
2092 #[test]
2093 fn det_exact_f64_overflow_returns_err() {
2094 let big = f64::MAX / 2.0;
2096 let m = Matrix::<3>::try_from_rows([[0.0, 0.0, 1.0], [big, 0.0, 1.0], [0.0, big, 1.0]])
2097 .unwrap();
2098 assert_eq!(
2100 m.det_exact_f64(),
2101 Err(LaError::Unrepresentable {
2102 index: None,
2103 reason: UnrepresentableReason::NotFinite,
2104 })
2105 );
2106 }
2107
2108 #[test]
2109 fn det_exact_rounded_f64_overflow_returns_err() {
2110 let big = f64::MAX / 2.0;
2111 let m = Matrix::<3>::try_from_rows([[0.0, 0.0, 1.0], [big, 0.0, 1.0], [0.0, big, 1.0]])
2112 .unwrap();
2113
2114 assert_eq!(
2115 m.det_exact_rounded_f64(),
2116 Err(LaError::Unrepresentable {
2117 index: None,
2118 reason: UnrepresentableReason::NotFinite,
2119 })
2120 );
2121 }
2122
2123 #[test]
2124 fn det_exact_f64_underflow_returns_err_for_nonzero_exact_result() {
2125 let tiny = f64::from_bits(1);
2126 let m = Matrix::<2>::try_from_rows([[tiny, 0.0], [0.0, tiny]]).unwrap();
2127
2128 assert!(m.det_exact().unwrap().is_positive());
2129 assert_eq!(
2130 m.det_exact_f64(),
2131 Err(LaError::Unrepresentable {
2132 index: None,
2133 reason: UnrepresentableReason::RequiresRounding,
2134 })
2135 );
2136 }
2137
2138 #[test]
2139 fn det_exact_f64_rejects_inexact_rounding() {
2140 let m = Matrix::<2>::try_from_rows([[1.0 + f64::EPSILON, 0.0], [0.0, 1.0 - f64::EPSILON]])
2141 .unwrap();
2142
2143 assert_eq!(
2144 m.det_exact(),
2145 Ok(BigRational::new(
2146 (BigInt::from(1_u128) << 104_u32) - BigInt::from(1),
2147 BigInt::from(1_u128 << 104),
2148 ))
2149 );
2150 assert_eq!(
2151 m.det_exact_f64(),
2152 Err(LaError::Unrepresentable {
2153 index: None,
2154 reason: UnrepresentableReason::RequiresRounding,
2155 })
2156 );
2157 }
2158
2159 #[test]
2160 fn det_exact_f64_accepts_min_positive_subnormal() {
2161 let tiny = f64::from_bits(1);
2162 let m = Matrix::<1>::try_from_rows([[tiny]]).unwrap();
2163
2164 assert_eq!(m.det_exact_f64().unwrap().to_bits(), tiny.to_bits());
2165 }
2166
2167 #[test]
2168 fn det_exact_f64_accepts_max_finite_binary64() {
2169 let m = Matrix::<1>::try_from_rows([[f64::MAX]]).unwrap();
2170
2171 assert_eq!(m.det_exact_f64().unwrap().to_bits(), f64::MAX.to_bits());
2172 }
2173
2174 #[test]
2175 fn det_exact_rounded_f64_rounds_inexact_result() {
2176 let m = Matrix::<2>::try_from_rows([[1.0 + f64::EPSILON, 0.0], [0.0, 1.0 - f64::EPSILON]])
2177 .unwrap();
2178
2179 assert_eq!(
2180 m.det_exact_rounded_f64().unwrap().to_bits(),
2181 1.0f64.to_bits()
2182 );
2183 }
2184
2185 fn arbitrary_rhs<const D: usize>() -> Vector<D> {
2191 let values = [1.0, -2.5, 3.0, 0.25, -4.0];
2192 let mut arr = [0.0f64; D];
2193 for (dst, src) in arr.iter_mut().zip(values.iter()) {
2194 *dst = *src;
2195 }
2196 Vector::<D>::new(arr)
2197 }
2198
2199 macro_rules! gen_solve_exact_tests {
2200 ($d:literal) => {
2201 paste! {
2202 #[test]
2203 fn [<solve_exact_identity_ $d d>]() {
2204 let a = Matrix::<$d>::identity();
2205 let b = arbitrary_rhs::<$d>();
2206 let x = a.solve_exact(b).unwrap();
2207 for (i, xi) in x.iter().enumerate() {
2208 assert_eq!(*xi, f64_to_bigrational(b.as_array()[i]));
2209 }
2210 }
2211
2212 #[test]
2213 fn [<solve_exact_singular_ $d d>]() {
2214 let a = Matrix::<$d>::zero();
2216 let b = arbitrary_rhs::<$d>();
2217 assert_eq!(a.solve_exact(b), Err(LaError::Singular { pivot_col: 0 }));
2218 }
2219 }
2220 };
2221 }
2222
2223 gen_solve_exact_tests!(2);
2224 gen_solve_exact_tests!(3);
2225 gen_solve_exact_tests!(4);
2226 gen_solve_exact_tests!(5);
2227
2228 macro_rules! gen_solve_exact_f64_tests {
2229 ($d:literal) => {
2230 paste! {
2231 #[test]
2232 fn [<solve_exact_f64_identity_ $d d>]() {
2233 let a = Matrix::<$d>::identity();
2234 let b = arbitrary_rhs::<$d>();
2235 let x = a.solve_exact_f64(b).unwrap().into_array();
2236 for i in 0..$d {
2237 assert!((x[i] - b.as_array()[i]).abs() <= f64::EPSILON);
2238 }
2239 }
2240 }
2241 };
2242 }
2243
2244 gen_solve_exact_f64_tests!(2);
2245 gen_solve_exact_f64_tests!(3);
2246 gen_solve_exact_f64_tests!(4);
2247 gen_solve_exact_f64_tests!(5);
2248
2249 macro_rules! gen_solve_exact_f64_agrees_with_lu {
2252 ($d:literal) => {
2253 paste! {
2254 #[test]
2255 fn [<solve_exact_f64_agrees_with_lu_ $d d>]() {
2256 let mut rows = [[0.0f64; $d]; $d];
2260 for r in 0..$d {
2261 for c in 0..$d {
2262 rows[r][c] = if r == c {
2263 f64::from($d) + 1.0
2264 } else {
2265 1.0
2266 };
2267 }
2268 }
2269 let a = Matrix::<$d>::try_from_rows(rows).unwrap();
2270 let x_true = {
2271 let mut arr = [0.0f64; $d];
2272 for (dst, src) in arr.iter_mut().zip([1.0, -2.0, 3.0, -4.0, 5.0]) {
2273 *dst = src;
2274 }
2275 arr
2276 };
2277 let mut b_arr = [0.0f64; $d];
2278 for i in 0..$d {
2279 let mut sum = 0.0;
2280 for j in 0..$d {
2281 sum = rows[i][j].mul_add(x_true[j], sum);
2282 }
2283 b_arr[i] = sum;
2284 }
2285 let b = Vector::<$d>::new(b_arr);
2286 let exact = a.solve_exact_f64(b).unwrap().into_array();
2287 let lu_sol = a.lu(DEFAULT_SINGULAR_TOL).unwrap()
2288 .solve(b).unwrap().into_array();
2289 for i in 0..$d {
2290 assert_eq!(exact[i].to_bits(), x_true[i].to_bits());
2291 let eps = lu_sol[i].abs().mul_add(1e-12, 1e-12);
2292 assert!((exact[i] - lu_sol[i]).abs() <= eps);
2293 }
2294 }
2295 }
2296 };
2297 }
2298
2299 gen_solve_exact_f64_agrees_with_lu!(2);
2300 gen_solve_exact_f64_agrees_with_lu!(3);
2301 gen_solve_exact_f64_agrees_with_lu!(4);
2302 gen_solve_exact_f64_agrees_with_lu!(5);
2303
2304 macro_rules! gen_solve_exact_roundtrip_tests {
2310 ($d:literal) => {
2311 paste! {
2312 #[test]
2313 #[allow(clippy::cast_precision_loss)]
2314 fn [<solve_exact_roundtrip_ $d d>]() {
2315 let mut rows = [[0.0f64; $d]; $d];
2318 for r in 0..$d {
2319 for c in 0..$d {
2320 rows[r][c] = if r == c {
2321 f64::from($d) + 1.0
2322 } else {
2323 1.0
2324 };
2325 }
2326 }
2327 let a = Matrix::<$d>::try_from_rows(rows).unwrap();
2328
2329 let mut x0 = [0.0f64; $d];
2331 for i in 0..$d {
2332 x0[i] = (i + 1) as f64;
2333 }
2334
2335 let mut b_arr = [0.0f64; $d];
2338 for r in 0..$d {
2339 let mut sum = 0.0_f64;
2340 for c in 0..$d {
2341 sum = rows[r][c].mul_add(x0[c], sum);
2342 }
2343 b_arr[r] = sum;
2344 }
2345 let b = Vector::<$d>::new(b_arr);
2346
2347 let x = a.solve_exact(b).unwrap();
2348 for i in 0..$d {
2349 assert_eq!(x[i], f64_to_bigrational(x0[i]));
2350 }
2351 }
2352 }
2353 };
2354 }
2355
2356 gen_solve_exact_roundtrip_tests!(2);
2357 gen_solve_exact_roundtrip_tests!(3);
2358 gen_solve_exact_roundtrip_tests!(4);
2359 gen_solve_exact_roundtrip_tests!(5);
2360
2361 #[test]
2366 fn solve_exact_d0_returns_empty() {
2367 let a = Matrix::<0>::zero();
2368 let b = Vector::<0>::zero();
2369 let x = a.solve_exact(b).unwrap();
2370 assert!(x.is_empty());
2371 }
2372
2373 #[test]
2374 fn solve_exact_known_2x2() {
2375 let a = Matrix::<2>::try_from_rows([[1.0, 2.0], [3.0, 4.0]]).unwrap();
2377 let b = Vector::<2>::new([5.0, 11.0]);
2378 let x = a.solve_exact(b).unwrap();
2379 assert_eq!(x[0], BigRational::from_integer(BigInt::from(1)));
2380 assert_eq!(x[1], BigRational::from_integer(BigInt::from(2)));
2381 }
2382
2383 #[test]
2384 fn solve_exact_pivoting_needed() {
2385 let a = Matrix::<3>::try_from_rows([[0.0, 1.0, 0.0], [1.0, 0.0, 0.0], [0.0, 0.0, 1.0]])
2387 .unwrap();
2388 let b = Vector::<3>::new([2.0, 3.0, 4.0]);
2389 let x = a.solve_exact(b).unwrap();
2390 assert_eq!(x[0], f64_to_bigrational(3.0));
2392 assert_eq!(x[1], f64_to_bigrational(2.0));
2393 assert_eq!(x[2], f64_to_bigrational(4.0));
2394 }
2395
2396 #[test]
2397 fn solve_exact_fractional_result() {
2398 let a = Matrix::<2>::try_from_rows([[2.0, 1.0], [1.0, 3.0]]).unwrap();
2400 let b = Vector::<2>::new([1.0, 1.0]);
2401 let x = a.solve_exact(b).unwrap();
2402 assert_eq!(x[0], BigRational::new(BigInt::from(2), BigInt::from(5)));
2403 assert_eq!(x[1], BigRational::new(BigInt::from(1), BigInt::from(5)));
2404 }
2405
2406 #[test]
2407 fn solve_exact_singular_duplicate_rows() {
2408 let a = Matrix::<3>::try_from_rows([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [1.0, 2.0, 3.0]])
2409 .unwrap();
2410 let b = Vector::<3>::new([1.0, 2.0, 3.0]);
2411 assert_matches!(a.solve_exact(b), Err(LaError::Singular { .. }));
2412 }
2413
2414 #[test]
2415 fn solve_exact_5x5_permutation() {
2416 let a = Matrix::<5>::try_from_rows([
2418 [0.0, 1.0, 0.0, 0.0, 0.0],
2419 [1.0, 0.0, 0.0, 0.0, 0.0],
2420 [0.0, 0.0, 1.0, 0.0, 0.0],
2421 [0.0, 0.0, 0.0, 1.0, 0.0],
2422 [0.0, 0.0, 0.0, 0.0, 1.0],
2423 ])
2424 .unwrap();
2425 let b = Vector::<5>::new([10.0, 20.0, 30.0, 40.0, 50.0]);
2426 let x = a.solve_exact(b).unwrap();
2427 assert_eq!(x[0], f64_to_bigrational(20.0));
2428 assert_eq!(x[1], f64_to_bigrational(10.0));
2429 assert_eq!(x[2], f64_to_bigrational(30.0));
2430 assert_eq!(x[3], f64_to_bigrational(40.0));
2431 assert_eq!(x[4], f64_to_bigrational(50.0));
2432 }
2433
2434 macro_rules! gen_solve_exact_large_finite_entries_tests {
2440 ($d:literal) => {
2441 paste! {
2442 #[test]
2443 fn [<solve_exact_large_finite_entries_ $d d>]() {
2444 let big = f64::MAX / 2.0;
2445 assert!(big.is_finite());
2446 let mut rows = [[0.0f64; $d]; $d];
2448 for i in 0..$d {
2449 rows[i][i] = big;
2450 }
2451 let a = Matrix::<$d>::try_from_rows(rows).unwrap();
2452 let mut b_arr = [big; $d];
2454 b_arr[$d - 1] = 0.0;
2455 let b = Vector::<$d>::new(b_arr);
2456 let x = a.solve_exact(b).unwrap();
2457 for i in 0..($d - 1) {
2458 assert_eq!(x[i], BigRational::from_integer(BigInt::from(1)));
2459 }
2460 assert_eq!(x[$d - 1], BigRational::from_integer(BigInt::from(0)));
2461 }
2462 }
2463 };
2464 }
2465
2466 gen_solve_exact_large_finite_entries_tests!(2);
2467 gen_solve_exact_large_finite_entries_tests!(3);
2468 gen_solve_exact_large_finite_entries_tests!(4);
2469 gen_solve_exact_large_finite_entries_tests!(5);
2470
2471 macro_rules! gen_solve_exact_mixed_magnitude_entries_tests {
2477 ($d:literal) => {
2478 paste! {
2479 #[test]
2480 fn [<solve_exact_mixed_magnitude_entries_ $d d>]() {
2481 let tiny = f64::MIN_POSITIVE; let huge = 1.0e100_f64;
2483 let mut rows = [[0.0f64; $d]; $d];
2485 let mut b_arr = [0.0f64; $d];
2486 for i in 0..$d {
2487 let val = if i % 2 == 0 { huge } else { tiny };
2488 rows[i][i] = val;
2489 b_arr[i] = val;
2490 }
2491 let a = Matrix::<$d>::try_from_rows(rows).unwrap();
2492 let b = Vector::<$d>::new(b_arr);
2493 let x = a.solve_exact(b).unwrap();
2494 for i in 0..$d {
2495 assert_eq!(x[i], BigRational::from_integer(BigInt::from(1)));
2496 }
2497 }
2498 }
2499 };
2500 }
2501
2502 gen_solve_exact_mixed_magnitude_entries_tests!(2);
2503 gen_solve_exact_mixed_magnitude_entries_tests!(3);
2504 gen_solve_exact_mixed_magnitude_entries_tests!(4);
2505 gen_solve_exact_mixed_magnitude_entries_tests!(5);
2506
2507 macro_rules! gen_solve_exact_subnormal_rhs_tests {
2513 ($d:literal) => {
2514 paste! {
2515 #[test]
2516 #[allow(clippy::cast_precision_loss)]
2517 fn [<solve_exact_subnormal_rhs_ $d d>]() {
2518 let tiny = 5e-324_f64; assert!(tiny.is_subnormal());
2520 let a = Matrix::<$d>::identity();
2521 let mut b_arr = [0.0f64; $d];
2523 for i in 0..$d {
2524 b_arr[i] = (i + 1) as f64 * tiny;
2525 assert!(b_arr[i].is_subnormal());
2526 }
2527 let b = Vector::<$d>::new(b_arr);
2528 let x = a.solve_exact(b).unwrap();
2529 for i in 0..$d {
2530 assert_eq!(x[i], f64_to_bigrational((i + 1) as f64 * tiny));
2531 }
2532 }
2533 }
2534 };
2535 }
2536
2537 gen_solve_exact_subnormal_rhs_tests!(2);
2538 gen_solve_exact_subnormal_rhs_tests!(3);
2539 gen_solve_exact_subnormal_rhs_tests!(4);
2540 gen_solve_exact_subnormal_rhs_tests!(5);
2541
2542 macro_rules! gen_solve_exact_pivot_swap_fractional_tests {
2552 ($d:literal) => {
2553 paste! {
2554 #[test]
2555 #[allow(clippy::cast_precision_loss)]
2556 #[allow(clippy::reversed_empty_ranges)]
2559 fn [<solve_exact_pivot_swap_with_fractional_result_ $d d>]() {
2560 let mut rows = [[0.0f64; $d]; $d];
2563 rows[0][1] = 1.0;
2564 rows[1][0] = 2.0;
2565 rows[1][1] = 1.0;
2566 for i in 2..$d {
2568 rows[i][i] = 1.0;
2569 }
2570 let a = Matrix::<$d>::try_from_rows(rows).unwrap();
2571 let mut b_arr = [0.0f64; $d];
2574 b_arr[0] = 3.0;
2575 b_arr[1] = 4.0;
2576 for i in 2..$d {
2577 b_arr[i] = (i + 10) as f64;
2578 }
2579 let b = Vector::<$d>::new(b_arr);
2580 let x = a.solve_exact(b).unwrap();
2581 assert_eq!(x[0], BigRational::new(BigInt::from(1), BigInt::from(2)));
2582 assert_eq!(x[1], BigRational::from_integer(BigInt::from(3)));
2583 for i in 2..$d {
2584 assert_eq!(x[i], f64_to_bigrational((i + 10) as f64));
2585 }
2586 }
2587 }
2588 };
2589 }
2590
2591 gen_solve_exact_pivot_swap_fractional_tests!(2);
2592 gen_solve_exact_pivot_swap_fractional_tests!(3);
2593 gen_solve_exact_pivot_swap_fractional_tests!(4);
2594 gen_solve_exact_pivot_swap_fractional_tests!(5);
2595
2596 macro_rules! gen_solve_exact_mid_pivot_swap_tests {
2605 ($d:literal) => {
2606 paste! {
2607 #[test]
2608 #[allow(clippy::cast_precision_loss)]
2609 #[allow(clippy::reversed_empty_ranges)]
2612 fn [<solve_exact_mid_pivot_swap_ $d d>]() {
2613 let mut rows = [[0.0f64; $d]; $d];
2614 rows[0][0] = 1.0; rows[0][1] = 2.0; rows[0][2] = 3.0;
2615 rows[1][2] = 4.0;
2617 rows[2][1] = 5.0; rows[2][2] = 6.0;
2618 for i in 3..$d {
2620 rows[i][i] = 1.0;
2621 }
2622 let a = Matrix::<$d>::try_from_rows(rows).unwrap();
2623 let mut b_arr = [0.0f64; $d];
2624 b_arr[0] = 6.0;
2625 b_arr[1] = 7.0;
2626 b_arr[2] = 8.0;
2627 for i in 3..$d {
2628 b_arr[i] = (i + 10) as f64;
2629 }
2630 let b = Vector::<$d>::new(b_arr);
2631 let x = a.solve_exact(b).unwrap();
2632 assert_eq!(x[0], BigRational::new(BigInt::from(7), BigInt::from(4)));
2634 assert_eq!(x[1], BigRational::new(BigInt::from(-1), BigInt::from(2)));
2635 assert_eq!(x[2], BigRational::new(BigInt::from(7), BigInt::from(4)));
2636 for i in 3..$d {
2637 assert_eq!(x[i], f64_to_bigrational((i + 10) as f64));
2638 }
2639 }
2640 }
2641 };
2642 }
2643
2644 gen_solve_exact_mid_pivot_swap_tests!(3);
2645 gen_solve_exact_mid_pivot_swap_tests!(4);
2646 gen_solve_exact_mid_pivot_swap_tests!(5);
2647
2648 macro_rules! gen_solve_exact_singular_rank_deficient_tests {
2656 ($d:literal) => {
2657 paste! {
2658 #[test]
2659 fn [<solve_exact_singular_rank_deficient_ $d d>]() {
2660 let mut rows = [[0.0f64; $d]; $d];
2661 for i in 0..($d - 1) {
2662 rows[i][i] = 1.0;
2663 rows[$d - 1][i] = 1.0;
2664 }
2665 let a = Matrix::<$d>::try_from_rows(rows).unwrap();
2667 let b = Vector::<$d>::new([1.0; $d]);
2668 assert_eq!(
2669 a.solve_exact(b),
2670 Err(LaError::Singular { pivot_col: $d - 1 })
2671 );
2672 }
2673 }
2674 };
2675 }
2676
2677 gen_solve_exact_singular_rank_deficient_tests!(2);
2678 gen_solve_exact_singular_rank_deficient_tests!(3);
2679 gen_solve_exact_singular_rank_deficient_tests!(4);
2680 gen_solve_exact_singular_rank_deficient_tests!(5);
2681
2682 macro_rules! gen_solve_exact_zero_rhs_tests {
2688 ($d:literal) => {
2689 paste! {
2690 #[test]
2691 fn [<solve_exact_zero_rhs_ $d d>]() {
2692 let mut rows = [[1.0f64; $d]; $d];
2694 for i in 0..$d {
2695 rows[i][i] = f64::from($d) + 1.0;
2696 }
2697 let a = Matrix::<$d>::try_from_rows(rows).unwrap();
2698 let b = Vector::<$d>::zero();
2699 let x = a.solve_exact(b).unwrap();
2700 for xi in &x {
2701 assert_eq!(*xi, BigRational::from_integer(BigInt::from(0)));
2702 }
2703 }
2704 }
2705 };
2706 }
2707
2708 gen_solve_exact_zero_rhs_tests!(2);
2709 gen_solve_exact_zero_rhs_tests!(3);
2710 gen_solve_exact_zero_rhs_tests!(4);
2711 gen_solve_exact_zero_rhs_tests!(5);
2712
2713 fn bigrational_matvec<const D: usize>(a: &Matrix<D>, x: &[BigRational; D]) -> [BigRational; D] {
2726 from_fn(|i| {
2727 let mut sum = BigRational::from_integer(BigInt::from(0));
2728 for (aij, xj) in a.rows()[i].iter().zip(x.iter()) {
2729 sum += f64_to_bigrational(*aij) * xj;
2730 }
2731 sum
2732 })
2733 }
2734
2735 fn hilbert<const D: usize>() -> Matrix<D> {
2736 let rows = from_fn(|r| from_fn(|c| 1.0 / f64::from(u32::try_from(r + c + 1).unwrap())));
2737 Matrix::<D>::try_from_rows(rows).unwrap()
2738 }
2739
2740 #[test]
2748 fn solve_exact_near_singular_3x3_integer_x0() {
2749 let perturbation = f64::from_bits(0x3CD0_0000_0000_0000); let a = Matrix::<3>::try_from_rows([
2751 [1.0 + perturbation, 2.0, 3.0],
2752 [4.0, 5.0, 6.0],
2753 [7.0, 8.0, 9.0],
2754 ])
2755 .unwrap();
2756 let b = Vector::<3>::new([6.0 + perturbation, 15.0, 24.0]);
2757 let x = a.solve_exact(b).unwrap();
2758 let one = BigRational::from_integer(BigInt::from(1));
2759 assert_eq!(x[0], one);
2760 assert_eq!(x[1], one);
2761 assert_eq!(x[2], one);
2762 }
2763
2764 #[test]
2765 fn solve_exact_f64_near_singular_benchmark_rhs_is_rejection_path() {
2766 let perturbation = f64::from_bits(0x3CD0_0000_0000_0000); let a = Matrix::<3>::try_from_rows([
2768 [1.0 + perturbation, 2.0, 3.0],
2769 [4.0, 5.0, 6.0],
2770 [7.0, 8.0, 9.0],
2771 ])
2772 .unwrap();
2773 let b = Vector::<3>::new([1.0, 2.0, 3.0]);
2774
2775 assert_eq!(
2776 a.solve_exact_f64(b),
2777 Err(LaError::Unrepresentable {
2778 index: Some(2),
2779 reason: UnrepresentableReason::RequiresRounding,
2780 })
2781 );
2782 assert_eq!(
2783 a.solve_exact_rounded_f64(b).unwrap().into_array()[2].to_bits(),
2784 (1.0f64 / 3.0).to_bits()
2785 );
2786 }
2787
2788 #[test]
2795 fn solve_exact_large_entries_3x3_unit_vector() {
2796 let big = f64::MAX / 2.0;
2797 assert!(big.is_finite());
2798 let a = Matrix::<3>::try_from_rows([[big, 1.0, 1.0], [1.0, big, 1.0], [1.0, 1.0, big]])
2799 .unwrap();
2800 let b = Vector::<3>::new([big, 1.0, 1.0]);
2801 let x = a.solve_exact(b).unwrap();
2802 let zero = BigRational::from_integer(BigInt::from(0));
2803 let one = BigRational::from_integer(BigInt::from(1));
2804 assert_eq!(x[0], one);
2805 assert_eq!(x[1], zero);
2806 assert_eq!(x[2], zero);
2807 }
2808
2809 #[test]
2815 fn det_sign_exact_large_entries_3x3_positive() {
2816 let big = f64::MAX / 2.0;
2817 let a = Matrix::<3>::try_from_rows([[big, 1.0, 1.0], [1.0, big, 1.0], [1.0, 1.0, big]])
2818 .unwrap();
2819 assert_matches!(a.det_direct(), Err(LaError::NonFinite { row: None, .. }));
2822 assert_eq!(a.det_sign_exact().unwrap(), 1);
2823 assert!(a.det_exact().unwrap().is_positive());
2827 assert_eq!(
2828 a.det_exact_f64(),
2829 Err(LaError::Unrepresentable {
2830 index: None,
2831 reason: UnrepresentableReason::NotFinite,
2832 })
2833 );
2834 }
2835
2836 macro_rules! gen_det_sign_exact_hilbert_positive_tests {
2844 ($d:literal) => {
2845 paste! {
2846 #[test]
2847 fn [<det_sign_exact_hilbert_positive_ $d d>]() {
2848 let h = hilbert::<$d>();
2849 assert_eq!(h.det_sign_exact().unwrap(), 1);
2850 }
2851 }
2852 };
2853 }
2854
2855 gen_det_sign_exact_hilbert_positive_tests!(2);
2856 gen_det_sign_exact_hilbert_positive_tests!(3);
2857 gen_det_sign_exact_hilbert_positive_tests!(4);
2858 gen_det_sign_exact_hilbert_positive_tests!(5);
2859
2860 macro_rules! gen_solve_exact_f64_hilbert_benchmark_rhs_tests {
2861 ($d:literal) => {
2862 paste! {
2863 #[test]
2864 fn [<solve_exact_f64_hilbert_benchmark_rhs_is_rejection_path_ $d d>]() {
2865 let h = hilbert::<$d>();
2866 let b = Vector::<$d>::new([1.0; $d]);
2867
2868 assert_matches!(
2869 h.solve_exact_f64(b),
2870 Err(LaError::Unrepresentable {
2871 reason: UnrepresentableReason::RequiresRounding,
2872 ..
2873 })
2874 );
2875 assert_matches!(h.solve_exact_rounded_f64(b), Ok(_));
2876 }
2877 }
2878 };
2879 }
2880
2881 gen_solve_exact_f64_hilbert_benchmark_rhs_tests!(4);
2882 gen_solve_exact_f64_hilbert_benchmark_rhs_tests!(5);
2883
2884 macro_rules! gen_solve_exact_hilbert_residual_tests {
2891 ($d:literal) => {
2892 paste! {
2893 #[test]
2894 fn [<solve_exact_hilbert_residual_ $d d>]() {
2895 let h = hilbert::<$d>();
2896 let mut b_arr = [0.0f64; $d];
2899 for i in 0usize..$d {
2900 let sign = if i.is_multiple_of(2) { 1.0 } else { -1.0 };
2901 b_arr[i] = sign * f64::from(u32::try_from(i + 1).unwrap());
2902 }
2903 let b = Vector::<$d>::new(b_arr);
2904 let x = h.solve_exact(b).unwrap();
2905 let ax = bigrational_matvec(&h, &x);
2906 for i in 0..$d {
2907 assert_eq!(ax[i], f64_to_bigrational(b_arr[i]));
2908 }
2909 }
2910 }
2911 };
2912 }
2913
2914 gen_solve_exact_hilbert_residual_tests!(2);
2915 gen_solve_exact_hilbert_residual_tests!(3);
2916 gen_solve_exact_hilbert_residual_tests!(4);
2917 gen_solve_exact_hilbert_residual_tests!(5);
2918
2919 #[test]
2924 fn solve_exact_f64_known_2x2() {
2925 let a = Matrix::<2>::try_from_rows([[1.0, 2.0], [3.0, 4.0]]).unwrap();
2926 let b = Vector::<2>::new([5.0, 11.0]);
2927 let x = a.solve_exact_f64(b).unwrap().into_array();
2928 assert!((x[0] - 1.0).abs() <= f64::EPSILON);
2929 assert!((x[1] - 2.0).abs() <= f64::EPSILON);
2930 }
2931
2932 #[test]
2933 fn solve_exact_f64_overflow_returns_err() {
2934 let big = f64::MAX / 2.0;
2937 let a = Matrix::<2>::try_from_rows([[1.0 / big, 0.0], [0.0, 1.0 / big]]).unwrap();
2938 let b = Vector::<2>::new([big, big]);
2939 assert_eq!(
2940 a.solve_exact_f64(b),
2941 Err(LaError::Unrepresentable {
2942 index: Some(0),
2943 reason: UnrepresentableReason::NotFinite,
2944 })
2945 );
2946 }
2947
2948 #[test]
2949 fn solve_exact_f64_huge_non_dyadic_component_returns_not_finite() {
2950 let a = Matrix::<1>::try_from_rows([[3.0 * f64::MIN_POSITIVE]]).unwrap();
2951 let b = Vector::<1>::new([f64::MAX]);
2952
2953 assert_eq!(
2954 a.solve_exact_f64(b),
2955 Err(LaError::Unrepresentable {
2956 index: Some(0),
2957 reason: UnrepresentableReason::NotFinite,
2958 })
2959 );
2960 }
2961
2962 #[test]
2963 fn solve_exact_rounded_f64_overflow_returns_err() {
2964 let big = f64::MAX / 2.0;
2965 let a = Matrix::<2>::try_from_rows([[1.0 / big, 0.0], [0.0, 1.0 / big]]).unwrap();
2966 let b = Vector::<2>::new([big, big]);
2967
2968 assert_eq!(
2969 a.solve_exact_rounded_f64(b),
2970 Err(LaError::Unrepresentable {
2971 index: Some(0),
2972 reason: UnrepresentableReason::NotFinite,
2973 })
2974 );
2975 }
2976
2977 #[test]
2978 fn solve_exact_f64_underflow_returns_err_for_nonzero_exact_component() {
2979 let tiny = f64::from_bits(1);
2980 let a = Matrix::<1>::try_from_rows([[2.0]]).unwrap();
2981 let b = Vector::<1>::new([tiny]);
2982
2983 assert_eq!(
2984 a.solve_exact_f64(b),
2985 Err(LaError::Unrepresentable {
2986 index: Some(0),
2987 reason: UnrepresentableReason::RequiresRounding,
2988 })
2989 );
2990 }
2991
2992 #[test]
2993 fn solve_exact_f64_accepts_smallest_subnormal_result() {
2994 let tiny = f64::from_bits(1);
2995 let a = Matrix::<1>::identity();
2996 let b = Vector::<1>::new([tiny]);
2997
2998 assert_eq!(
2999 a.solve_exact_f64(b).unwrap().into_array()[0].to_bits(),
3000 tiny.to_bits()
3001 );
3002 }
3003
3004 #[test]
3005 fn solve_exact_f64_rejects_non_dyadic_component() {
3006 let a = Matrix::<1>::try_from_rows([[3.0]]).unwrap();
3007 let b = Vector::<1>::new([1.0]);
3008
3009 assert_eq!(
3010 a.solve_exact_f64(b),
3011 Err(LaError::Unrepresentable {
3012 index: Some(0),
3013 reason: UnrepresentableReason::RequiresRounding,
3014 })
3015 );
3016 }
3017
3018 #[test]
3019 fn solve_exact_rounded_f64_rounds_non_dyadic_component() {
3020 let a = Matrix::<1>::try_from_rows([[3.0]]).unwrap();
3021 let b = Vector::<1>::new([1.0]);
3022
3023 assert_eq!(
3024 a.solve_exact_rounded_f64(b).unwrap().into_array()[0].to_bits(),
3025 (1.0f64 / 3.0).to_bits()
3026 );
3027 }
3028
3029 #[test]
3034 fn gauss_solve_d1() {
3035 let a = Matrix::<1>::try_from_rows([[2.0]]).unwrap();
3036 let b = Vector::<1>::new([6.0]);
3037 let x = a.solve_exact(b).unwrap();
3038 assert_eq!(x[0], f64_to_bigrational(3.0));
3039 }
3040
3041 #[test]
3042 fn gauss_solve_singular_column_all_zero() {
3043 let a = Matrix::<3>::try_from_rows([[1.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0, 1.0]])
3044 .unwrap();
3045 let b = Vector::<3>::new([1.0, 2.0, 3.0]);
3046 assert_eq!(a.solve_exact(b), Err(LaError::Singular { pivot_col: 1 }));
3047 }
3048
3049 #[test]
3054 fn f64_to_bigrational_positive_zero() {
3055 let r = f64_to_bigrational(0.0);
3056 assert_eq!(r, BigRational::from_integer(BigInt::from(0)));
3057 }
3058
3059 #[test]
3060 fn f64_to_bigrational_negative_zero() {
3061 let r = f64_to_bigrational(-0.0);
3062 assert_eq!(r, BigRational::from_integer(BigInt::from(0)));
3063 }
3064
3065 #[test]
3066 fn f64_to_bigrational_one() {
3067 let r = f64_to_bigrational(1.0);
3068 assert_eq!(r, BigRational::from_integer(BigInt::from(1)));
3069 }
3070
3071 #[test]
3072 fn f64_to_bigrational_negative_one() {
3073 let r = f64_to_bigrational(-1.0);
3074 assert_eq!(r, BigRational::from_integer(BigInt::from(-1)));
3075 }
3076
3077 #[test]
3078 fn f64_to_bigrational_half() {
3079 let r = f64_to_bigrational(0.5);
3080 assert_eq!(r, BigRational::new(BigInt::from(1), BigInt::from(2)));
3081 }
3082
3083 #[test]
3084 fn f64_to_bigrational_quarter() {
3085 let r = f64_to_bigrational(0.25);
3086 assert_eq!(r, BigRational::new(BigInt::from(1), BigInt::from(4)));
3087 }
3088
3089 #[test]
3090 fn f64_to_bigrational_negative_three_and_a_half() {
3091 let r = f64_to_bigrational(-3.5);
3093 assert_eq!(r, BigRational::new(BigInt::from(-7), BigInt::from(2)));
3094 }
3095
3096 #[test]
3097 fn f64_to_bigrational_integer() {
3098 let r = f64_to_bigrational(42.0);
3099 assert_eq!(r, BigRational::from_integer(BigInt::from(42)));
3100 }
3101
3102 #[test]
3103 fn f64_to_bigrational_power_of_two() {
3104 let r = f64_to_bigrational(1024.0);
3105 assert_eq!(r, BigRational::from_integer(BigInt::from(1024)));
3106 }
3107
3108 #[test]
3109 fn f64_to_bigrational_subnormal() {
3110 let tiny = 5e-324_f64; assert!(tiny.is_subnormal());
3112 let r = f64_to_bigrational(tiny);
3113 assert_eq!(
3115 r,
3116 BigRational::new(BigInt::from(1), BigInt::from(1u32) << 1074u32)
3117 );
3118 }
3119
3120 #[test]
3121 fn f64_to_bigrational_already_lowest_terms() {
3122 let r = f64_to_bigrational(0.5);
3124 assert_eq!(*r.numer(), BigInt::from(1));
3125 assert_eq!(*r.denom(), BigInt::from(2));
3126 }
3127
3128 #[test]
3129 fn f64_to_bigrational_round_trip() {
3130 let values = [
3133 0.0,
3134 1.0,
3135 -1.0,
3136 0.5,
3137 0.25,
3138 0.1,
3139 42.0,
3140 -3.5,
3141 1e10,
3142 1e-10,
3143 f64::MAX / 2.0,
3144 f64::MIN_POSITIVE,
3145 5e-324,
3146 ];
3147 for &v in &values {
3148 let r = f64_to_bigrational(v);
3149 let back = r.to_f64().expect("round-trip to_f64 failed");
3150 assert!(
3151 v.to_bits() == back.to_bits(),
3152 "round-trip failed for {v}: got {back}"
3153 );
3154 }
3155 }
3156
3157 #[test]
3158 fn f64_decompose_rejects_nonfinite_inputs() {
3159 for value in [f64::NAN, f64::INFINITY, f64::NEG_INFINITY] {
3160 assert_eq!(
3161 f64_decompose(value),
3162 Err(LaError::NonFinite { row: None, col: 0 })
3163 );
3164 }
3165 }
3166}