1use num_bigint::{BigInt, Sign};
18use num_rational::BigRational;
19
20use crate::LaError;
21use crate::matrix::Matrix;
22
23const EPS: f64 = f64::EPSILON; const ERR_COEFF_2: f64 = 3.0 * EPS + 16.0 * EPS * EPS;
36
37const ERR_COEFF_3: f64 = 8.0 * EPS + 64.0 * EPS * EPS;
39
40const ERR_COEFF_4: f64 = 12.0 * EPS + 128.0 * EPS * EPS;
42
43fn det_errbound<const D: usize>(m: &Matrix<D>) -> Option<f64> {
47 match D {
48 0 | 1 => Some(0.0), 2 => {
50 let r = &m.rows;
51 let permanent = (r[0][0] * r[1][1]).abs() + (r[0][1] * r[1][0]).abs();
52 Some(ERR_COEFF_2 * permanent)
53 }
54 3 => {
55 let r = &m.rows;
56 let pm00 = (r[1][1] * r[2][2]).abs() + (r[1][2] * r[2][1]).abs();
57 let pm01 = (r[1][0] * r[2][2]).abs() + (r[1][2] * r[2][0]).abs();
58 let pm02 = (r[1][0] * r[2][1]).abs() + (r[1][1] * r[2][0]).abs();
59 let permanent = r[0][2]
60 .abs()
61 .mul_add(pm02, r[0][1].abs().mul_add(pm01, r[0][0].abs() * pm00));
62 Some(ERR_COEFF_3 * permanent)
63 }
64 4 => {
65 let r = &m.rows;
66 let sp23 = (r[2][2] * r[3][3]).abs() + (r[2][3] * r[3][2]).abs();
68 let sp13 = (r[2][1] * r[3][3]).abs() + (r[2][3] * r[3][1]).abs();
69 let sp12 = (r[2][1] * r[3][2]).abs() + (r[2][2] * r[3][1]).abs();
70 let sp03 = (r[2][0] * r[3][3]).abs() + (r[2][3] * r[3][0]).abs();
71 let sp02 = (r[2][0] * r[3][2]).abs() + (r[2][2] * r[3][0]).abs();
72 let sp01 = (r[2][0] * r[3][1]).abs() + (r[2][1] * r[3][0]).abs();
73 let pc0 = r[1][3]
75 .abs()
76 .mul_add(sp12, r[1][2].abs().mul_add(sp13, r[1][1].abs() * sp23));
77 let pc1 = r[1][3]
78 .abs()
79 .mul_add(sp02, r[1][2].abs().mul_add(sp03, r[1][0].abs() * sp23));
80 let pc2 = r[1][3]
81 .abs()
82 .mul_add(sp01, r[1][1].abs().mul_add(sp03, r[1][0].abs() * sp13));
83 let pc3 = r[1][2]
84 .abs()
85 .mul_add(sp01, r[1][1].abs().mul_add(sp02, r[1][0].abs() * sp12));
86 let permanent = r[0][3].abs().mul_add(
87 pc3,
88 r[0][2]
89 .abs()
90 .mul_add(pc2, r[0][1].abs().mul_add(pc1, r[0][0].abs() * pc0)),
91 );
92 Some(ERR_COEFF_4 * permanent)
93 }
94 _ => None,
95 }
96}
97
98fn f64_to_bigrational(x: f64) -> BigRational {
106 BigRational::from_float(x).expect("non-finite matrix entry in det_sign_exact")
107}
108
109fn bareiss_det<const D: usize>(m: &Matrix<D>) -> BigRational {
114 if D == 0 {
115 return BigRational::from_integer(BigInt::from(1));
116 }
117 if D == 1 {
118 return f64_to_bigrational(m.rows[0][0]);
119 }
120
121 let mut a: Vec<Vec<BigRational>> = Vec::with_capacity(D);
123 for r in 0..D {
124 let mut row = Vec::with_capacity(D);
125 for c in 0..D {
126 row.push(f64_to_bigrational(m.rows[r][c]));
127 }
128 a.push(row);
129 }
130
131 let zero = BigRational::from_integer(BigInt::from(0));
132 let mut prev_pivot = BigRational::from_integer(BigInt::from(1));
133 let mut sign: i8 = 1;
134
135 for k in 0..D {
136 if a[k][k] == zero {
138 let mut found = false;
139 for i in (k + 1)..D {
140 if a[i][k] != zero {
141 a.swap(k, i);
142 sign = -sign;
143 found = true;
144 break;
145 }
146 }
147 if !found {
148 return zero;
150 }
151 }
152
153 for i in (k + 1)..D {
155 for j in (k + 1)..D {
156 a[i][j] = (&a[k][k] * &a[i][j] - &a[i][k] * &a[k][j]) / &prev_pivot;
158 }
159 a[i][k] = zero.clone();
162 }
163
164 prev_pivot = a[k][k].clone();
165 }
166
167 let det = &a[D - 1][D - 1];
168 if sign < 0 { -det } else { det.clone() }
169}
170
171impl<const D: usize> Matrix<D> {
172 #[inline]
207 pub fn det_sign_exact(&self) -> Result<i8, LaError> {
208 for r in 0..D {
210 for c in 0..D {
211 if !self.rows[r][c].is_finite() {
212 return Err(LaError::NonFinite { col: c });
213 }
214 }
215 }
216
217 if let (Some(det_f64), Some(err)) = (self.det_direct(), det_errbound(self)) {
219 if det_f64.is_finite() {
224 if det_f64 > err {
225 return Ok(1);
226 }
227 if det_f64 < -err {
228 return Ok(-1);
229 }
230 }
231 }
232
233 let det = bareiss_det(self);
235 Ok(match det.numer().sign() {
236 Sign::Plus => 1,
237 Sign::Minus => -1,
238 Sign::NoSign => 0,
239 })
240 }
241}
242
243#[cfg(test)]
244mod tests {
245 use super::*;
246 use crate::DEFAULT_PIVOT_TOL;
247
248 #[test]
249 fn det_sign_exact_d0_is_positive() {
250 assert_eq!(Matrix::<0>::zero().det_sign_exact().unwrap(), 1);
251 }
252
253 #[test]
254 fn det_sign_exact_d1_positive() {
255 let m = Matrix::<1>::from_rows([[42.0]]);
256 assert_eq!(m.det_sign_exact().unwrap(), 1);
257 }
258
259 #[test]
260 fn det_sign_exact_d1_negative() {
261 let m = Matrix::<1>::from_rows([[-3.5]]);
262 assert_eq!(m.det_sign_exact().unwrap(), -1);
263 }
264
265 #[test]
266 fn det_sign_exact_d1_zero() {
267 let m = Matrix::<1>::from_rows([[0.0]]);
268 assert_eq!(m.det_sign_exact().unwrap(), 0);
269 }
270
271 #[test]
272 fn det_sign_exact_identity_2d() {
273 assert_eq!(Matrix::<2>::identity().det_sign_exact().unwrap(), 1);
274 }
275
276 #[test]
277 fn det_sign_exact_identity_3d() {
278 assert_eq!(Matrix::<3>::identity().det_sign_exact().unwrap(), 1);
279 }
280
281 #[test]
282 fn det_sign_exact_identity_4d() {
283 assert_eq!(Matrix::<4>::identity().det_sign_exact().unwrap(), 1);
284 }
285
286 #[test]
287 fn det_sign_exact_identity_5d() {
288 assert_eq!(Matrix::<5>::identity().det_sign_exact().unwrap(), 1);
289 }
290
291 #[test]
292 fn det_sign_exact_singular_duplicate_rows() {
293 let m = Matrix::<3>::from_rows([
294 [1.0, 2.0, 3.0],
295 [4.0, 5.0, 6.0],
296 [1.0, 2.0, 3.0], ]);
298 assert_eq!(m.det_sign_exact().unwrap(), 0);
299 }
300
301 #[test]
302 fn det_sign_exact_singular_linear_combination() {
303 let m = Matrix::<3>::from_rows([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [5.0, 7.0, 9.0]]);
305 assert_eq!(m.det_sign_exact().unwrap(), 0);
306 }
307
308 #[test]
309 fn det_sign_exact_negative_det_row_swap() {
310 let m = Matrix::<3>::from_rows([[0.0, 1.0, 0.0], [1.0, 0.0, 0.0], [0.0, 0.0, 1.0]]);
312 assert_eq!(m.det_sign_exact().unwrap(), -1);
313 }
314
315 #[test]
316 fn det_sign_exact_negative_det_known() {
317 let m = Matrix::<2>::from_rows([[1.0, 2.0], [3.0, 4.0]]);
319 assert_eq!(m.det_sign_exact().unwrap(), -1);
320 }
321
322 #[test]
323 fn det_sign_exact_agrees_with_det_for_spd() {
324 let m = Matrix::<3>::from_rows([[4.0, 2.0, 0.0], [2.0, 5.0, 1.0], [0.0, 1.0, 3.0]]);
326 assert_eq!(m.det_sign_exact().unwrap(), 1);
327 assert!(m.det(DEFAULT_PIVOT_TOL).unwrap() > 0.0);
328 }
329
330 #[test]
337 fn det_sign_exact_near_singular_perturbation() {
338 let perturbation = f64::from_bits(0x3CD0_0000_0000_0000); let m = Matrix::<3>::from_rows([
340 [1.0 + perturbation, 2.0, 3.0],
341 [4.0, 5.0, 6.0],
342 [7.0, 8.0, 9.0],
343 ]);
344 assert_eq!(m.det_sign_exact().unwrap(), -1);
346 }
347
348 #[test]
352 fn det_sign_exact_fast_filter_positive_4x4() {
353 let m = Matrix::<4>::from_rows([
354 [2.0, 1.0, 0.0, 0.0],
355 [1.0, 3.0, 1.0, 0.0],
356 [0.0, 1.0, 4.0, 1.0],
357 [0.0, 0.0, 1.0, 5.0],
358 ]);
359 assert_eq!(m.det_sign_exact().unwrap(), 1);
361 }
362
363 #[test]
364 fn det_sign_exact_fast_filter_negative_4x4() {
365 let m = Matrix::<4>::from_rows([
367 [1.0, 3.0, 1.0, 0.0],
368 [2.0, 1.0, 0.0, 0.0],
369 [0.0, 1.0, 4.0, 1.0],
370 [0.0, 0.0, 1.0, 5.0],
371 ]);
372 assert_eq!(m.det_sign_exact().unwrap(), -1);
373 }
374
375 #[test]
376 fn det_sign_exact_subnormal_entries() {
377 let tiny = 5e-324_f64; assert!(tiny.is_subnormal());
380
381 let m = Matrix::<2>::from_rows([[tiny, 0.0], [0.0, tiny]]);
382 assert_eq!(m.det_sign_exact().unwrap(), 1);
384 }
385
386 #[test]
387 fn det_sign_exact_returns_err_on_nan() {
388 let m = Matrix::<2>::from_rows([[f64::NAN, 0.0], [0.0, 1.0]]);
389 assert_eq!(m.det_sign_exact(), Err(LaError::NonFinite { col: 0 }));
390 }
391
392 #[test]
393 fn det_sign_exact_returns_err_on_infinity() {
394 let m = Matrix::<2>::from_rows([[f64::INFINITY, 0.0], [0.0, 1.0]]);
395 assert_eq!(m.det_sign_exact(), Err(LaError::NonFinite { col: 0 }));
396 }
397
398 #[test]
399 fn det_sign_exact_returns_err_on_nan_5x5() {
400 let mut m = Matrix::<5>::identity();
402 m.set(2, 3, f64::NAN);
403 assert_eq!(m.det_sign_exact(), Err(LaError::NonFinite { col: 3 }));
404 }
405
406 #[test]
407 fn det_sign_exact_returns_err_on_infinity_5x5() {
408 let mut m = Matrix::<5>::identity();
409 m.set(0, 0, f64::INFINITY);
410 assert_eq!(m.det_sign_exact(), Err(LaError::NonFinite { col: 0 }));
411 }
412
413 #[test]
414 fn det_sign_exact_pivot_needed_5x5() {
415 let m = Matrix::<5>::from_rows([
418 [0.0, 1.0, 0.0, 0.0, 0.0],
419 [1.0, 0.0, 0.0, 0.0, 0.0],
420 [0.0, 0.0, 1.0, 0.0, 0.0],
421 [0.0, 0.0, 0.0, 1.0, 0.0],
422 [0.0, 0.0, 0.0, 0.0, 1.0],
423 ]);
424 assert_eq!(m.det_sign_exact().unwrap(), -1);
425 }
426
427 #[test]
428 fn det_sign_exact_5x5_known() {
429 let m = Matrix::<5>::from_rows([
431 [0.0, 1.0, 0.0, 0.0, 0.0],
432 [1.0, 0.0, 0.0, 0.0, 0.0],
433 [0.0, 0.0, 0.0, 1.0, 0.0],
434 [0.0, 0.0, 1.0, 0.0, 0.0],
435 [0.0, 0.0, 0.0, 0.0, 1.0],
436 ]);
437 assert_eq!(m.det_sign_exact().unwrap(), 1);
439 }
440
441 #[test]
446 fn det_errbound_d0_is_zero() {
447 assert_eq!(det_errbound(&Matrix::<0>::zero()), Some(0.0));
448 }
449
450 #[test]
451 fn det_errbound_d1_is_zero() {
452 assert_eq!(det_errbound(&Matrix::<1>::from_rows([[42.0]])), Some(0.0));
453 }
454
455 #[test]
456 fn det_errbound_d2_positive() {
457 let m = Matrix::<2>::from_rows([[1.0, 2.0], [3.0, 4.0]]);
458 let bound = det_errbound(&m).unwrap();
459 assert!(bound > 0.0);
460 assert!(ERR_COEFF_2.mul_add(-10.0, bound).abs() < 1e-30);
462 }
463
464 #[test]
465 fn det_errbound_d3_positive() {
466 let m = Matrix::<3>::identity();
467 let bound = det_errbound(&m).unwrap();
468 assert!(bound > 0.0);
469 }
470
471 #[test]
472 fn det_errbound_d4_positive() {
473 let m = Matrix::<4>::identity();
474 let bound = det_errbound(&m).unwrap();
475 assert!(bound > 0.0);
476 }
477
478 #[test]
479 fn det_errbound_d5_is_none() {
480 assert_eq!(det_errbound(&Matrix::<5>::identity()), None);
481 }
482
483 #[test]
484 fn bareiss_det_d0_is_one() {
485 let det = bareiss_det(&Matrix::<0>::zero());
486 assert_eq!(det, BigRational::from_integer(BigInt::from(1)));
487 }
488
489 #[test]
490 fn bareiss_det_d1_returns_entry() {
491 let det = bareiss_det(&Matrix::<1>::from_rows([[7.0]]));
492 assert_eq!(det, f64_to_bigrational(7.0));
493 }
494
495 #[test]
496 fn bareiss_det_d3_with_pivoting() {
497 let m = Matrix::<3>::from_rows([[0.0, 1.0, 0.0], [1.0, 0.0, 0.0], [0.0, 0.0, 1.0]]);
499 let det = bareiss_det(&m);
500 assert_eq!(det, BigRational::from_integer(BigInt::from(-1)));
502 }
503
504 #[test]
505 fn bareiss_det_singular_all_zeros_in_column() {
506 let m = Matrix::<3>::from_rows([[1.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0, 1.0]]);
508 let det = bareiss_det(&m);
509 assert_eq!(det, BigRational::from_integer(BigInt::from(0)));
510 }
511
512 #[test]
513 fn det_sign_exact_overflow_determinant_finite_entries() {
514 let big = f64::MAX / 2.0;
518 assert!(big.is_finite());
519 let m = Matrix::<3>::from_rows([[0.0, 0.0, 1.0], [big, 0.0, 1.0], [0.0, big, 1.0]]);
520 assert_eq!(m.det_sign_exact().unwrap(), 1);
522 }
523}