1use num_bigint::{BigInt, Sign};
18use num_rational::BigRational;
19
20use crate::matrix::Matrix;
21
22const EPS: f64 = f64::EPSILON; const ERR_COEFF_2: f64 = 3.0 * EPS + 16.0 * EPS * EPS;
35
36const ERR_COEFF_3: f64 = 8.0 * EPS + 64.0 * EPS * EPS;
38
39const ERR_COEFF_4: f64 = 12.0 * EPS + 128.0 * EPS * EPS;
41
42fn det_errbound<const D: usize>(m: &Matrix<D>) -> Option<f64> {
46 match D {
47 0 | 1 => Some(0.0), 2 => {
49 let r = &m.rows;
50 let permanent = (r[0][0] * r[1][1]).abs() + (r[0][1] * r[1][0]).abs();
51 Some(ERR_COEFF_2 * permanent)
52 }
53 3 => {
54 let r = &m.rows;
55 let pm00 = (r[1][1] * r[2][2]).abs() + (r[1][2] * r[2][1]).abs();
56 let pm01 = (r[1][0] * r[2][2]).abs() + (r[1][2] * r[2][0]).abs();
57 let pm02 = (r[1][0] * r[2][1]).abs() + (r[1][1] * r[2][0]).abs();
58 let permanent = r[0][2]
59 .abs()
60 .mul_add(pm02, r[0][1].abs().mul_add(pm01, r[0][0].abs() * pm00));
61 Some(ERR_COEFF_3 * permanent)
62 }
63 4 => {
64 let r = &m.rows;
65 let sp23 = (r[2][2] * r[3][3]).abs() + (r[2][3] * r[3][2]).abs();
67 let sp13 = (r[2][1] * r[3][3]).abs() + (r[2][3] * r[3][1]).abs();
68 let sp12 = (r[2][1] * r[3][2]).abs() + (r[2][2] * r[3][1]).abs();
69 let sp03 = (r[2][0] * r[3][3]).abs() + (r[2][3] * r[3][0]).abs();
70 let sp02 = (r[2][0] * r[3][2]).abs() + (r[2][2] * r[3][0]).abs();
71 let sp01 = (r[2][0] * r[3][1]).abs() + (r[2][1] * r[3][0]).abs();
72 let pc0 = r[1][3]
74 .abs()
75 .mul_add(sp12, r[1][2].abs().mul_add(sp13, r[1][1].abs() * sp23));
76 let pc1 = r[1][3]
77 .abs()
78 .mul_add(sp02, r[1][2].abs().mul_add(sp03, r[1][0].abs() * sp23));
79 let pc2 = r[1][3]
80 .abs()
81 .mul_add(sp01, r[1][1].abs().mul_add(sp03, r[1][0].abs() * sp13));
82 let pc3 = r[1][2]
83 .abs()
84 .mul_add(sp01, r[1][1].abs().mul_add(sp02, r[1][0].abs() * sp12));
85 let permanent = r[0][3].abs().mul_add(
86 pc3,
87 r[0][2]
88 .abs()
89 .mul_add(pc2, r[0][1].abs().mul_add(pc1, r[0][0].abs() * pc0)),
90 );
91 Some(ERR_COEFF_4 * permanent)
92 }
93 _ => None,
94 }
95}
96
97fn f64_to_bigrational(x: f64) -> BigRational {
105 BigRational::from_float(x).expect("non-finite matrix entry in det_sign_exact")
106}
107
108fn bareiss_det<const D: usize>(m: &Matrix<D>) -> BigRational {
113 if D == 0 {
114 return BigRational::from_integer(BigInt::from(1));
115 }
116 if D == 1 {
117 return f64_to_bigrational(m.rows[0][0]);
118 }
119
120 let mut a: Vec<Vec<BigRational>> = Vec::with_capacity(D);
122 for r in 0..D {
123 let mut row = Vec::with_capacity(D);
124 for c in 0..D {
125 row.push(f64_to_bigrational(m.rows[r][c]));
126 }
127 a.push(row);
128 }
129
130 let zero = BigRational::from_integer(BigInt::from(0));
131 let mut prev_pivot = BigRational::from_integer(BigInt::from(1));
132 let mut sign: i8 = 1;
133
134 for k in 0..D {
135 if a[k][k] == zero {
137 let mut found = false;
138 for i in (k + 1)..D {
139 if a[i][k] != zero {
140 a.swap(k, i);
141 sign = -sign;
142 found = true;
143 break;
144 }
145 }
146 if !found {
147 return zero;
149 }
150 }
151
152 for i in (k + 1)..D {
154 for j in (k + 1)..D {
155 a[i][j] = (&a[k][k] * &a[i][j] - &a[i][k] * &a[k][j]) / &prev_pivot;
157 }
158 a[i][k] = zero.clone();
161 }
162
163 prev_pivot = a[k][k].clone();
164 }
165
166 let det = &a[D - 1][D - 1];
167 if sign < 0 { -det } else { det.clone() }
168}
169
170impl<const D: usize> Matrix<D> {
171 #[must_use]
206 pub fn det_sign_exact(&self) -> i8 {
207 if let (Some(det_f64), Some(err)) = (self.det_direct(), det_errbound(self)) {
209 assert!(
210 det_f64.is_finite(),
211 "non-finite matrix entry in det_sign_exact"
212 );
213 if det_f64 > err {
214 return 1;
215 }
216 if det_f64 < -err {
217 return -1;
218 }
219 }
220
221 let det = bareiss_det(self);
223 match det.numer().sign() {
224 Sign::Plus => 1,
225 Sign::Minus => -1,
226 Sign::NoSign => 0,
227 }
228 }
229}
230
231#[cfg(test)]
232mod tests {
233 use super::*;
234 use crate::DEFAULT_PIVOT_TOL;
235
236 #[test]
237 fn det_sign_exact_d0_is_positive() {
238 assert_eq!(Matrix::<0>::zero().det_sign_exact(), 1);
239 }
240
241 #[test]
242 fn det_sign_exact_d1_positive() {
243 let m = Matrix::<1>::from_rows([[42.0]]);
244 assert_eq!(m.det_sign_exact(), 1);
245 }
246
247 #[test]
248 fn det_sign_exact_d1_negative() {
249 let m = Matrix::<1>::from_rows([[-3.5]]);
250 assert_eq!(m.det_sign_exact(), -1);
251 }
252
253 #[test]
254 fn det_sign_exact_d1_zero() {
255 let m = Matrix::<1>::from_rows([[0.0]]);
256 assert_eq!(m.det_sign_exact(), 0);
257 }
258
259 #[test]
260 fn det_sign_exact_identity_2d() {
261 assert_eq!(Matrix::<2>::identity().det_sign_exact(), 1);
262 }
263
264 #[test]
265 fn det_sign_exact_identity_3d() {
266 assert_eq!(Matrix::<3>::identity().det_sign_exact(), 1);
267 }
268
269 #[test]
270 fn det_sign_exact_identity_4d() {
271 assert_eq!(Matrix::<4>::identity().det_sign_exact(), 1);
272 }
273
274 #[test]
275 fn det_sign_exact_identity_5d() {
276 assert_eq!(Matrix::<5>::identity().det_sign_exact(), 1);
277 }
278
279 #[test]
280 fn det_sign_exact_singular_duplicate_rows() {
281 let m = Matrix::<3>::from_rows([
282 [1.0, 2.0, 3.0],
283 [4.0, 5.0, 6.0],
284 [1.0, 2.0, 3.0], ]);
286 assert_eq!(m.det_sign_exact(), 0);
287 }
288
289 #[test]
290 fn det_sign_exact_singular_linear_combination() {
291 let m = Matrix::<3>::from_rows([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [5.0, 7.0, 9.0]]);
293 assert_eq!(m.det_sign_exact(), 0);
294 }
295
296 #[test]
297 fn det_sign_exact_negative_det_row_swap() {
298 let m = Matrix::<3>::from_rows([[0.0, 1.0, 0.0], [1.0, 0.0, 0.0], [0.0, 0.0, 1.0]]);
300 assert_eq!(m.det_sign_exact(), -1);
301 }
302
303 #[test]
304 fn det_sign_exact_negative_det_known() {
305 let m = Matrix::<2>::from_rows([[1.0, 2.0], [3.0, 4.0]]);
307 assert_eq!(m.det_sign_exact(), -1);
308 }
309
310 #[test]
311 fn det_sign_exact_agrees_with_det_for_spd() {
312 let m = Matrix::<3>::from_rows([[4.0, 2.0, 0.0], [2.0, 5.0, 1.0], [0.0, 1.0, 3.0]]);
314 assert_eq!(m.det_sign_exact(), 1);
315 assert!(m.det(DEFAULT_PIVOT_TOL).unwrap() > 0.0);
316 }
317
318 #[test]
325 fn det_sign_exact_near_singular_perturbation() {
326 let perturbation = f64::from_bits(0x3CD0_0000_0000_0000); let m = Matrix::<3>::from_rows([
328 [1.0 + perturbation, 2.0, 3.0],
329 [4.0, 5.0, 6.0],
330 [7.0, 8.0, 9.0],
331 ]);
332 assert_eq!(m.det_sign_exact(), -1);
334 }
335
336 #[test]
340 fn det_sign_exact_fast_filter_positive_4x4() {
341 let m = Matrix::<4>::from_rows([
342 [2.0, 1.0, 0.0, 0.0],
343 [1.0, 3.0, 1.0, 0.0],
344 [0.0, 1.0, 4.0, 1.0],
345 [0.0, 0.0, 1.0, 5.0],
346 ]);
347 assert_eq!(m.det_sign_exact(), 1);
349 }
350
351 #[test]
352 fn det_sign_exact_fast_filter_negative_4x4() {
353 let m = Matrix::<4>::from_rows([
355 [1.0, 3.0, 1.0, 0.0],
356 [2.0, 1.0, 0.0, 0.0],
357 [0.0, 1.0, 4.0, 1.0],
358 [0.0, 0.0, 1.0, 5.0],
359 ]);
360 assert_eq!(m.det_sign_exact(), -1);
361 }
362
363 #[test]
364 fn det_sign_exact_subnormal_entries() {
365 let tiny = 5e-324_f64; assert!(tiny.is_subnormal());
368
369 let m = Matrix::<2>::from_rows([[tiny, 0.0], [0.0, tiny]]);
370 assert_eq!(m.det_sign_exact(), 1);
372 }
373
374 #[test]
375 #[should_panic(expected = "non-finite matrix entry")]
376 fn det_sign_exact_panics_on_nan() {
377 let m = Matrix::<2>::from_rows([[f64::NAN, 0.0], [0.0, 1.0]]);
378 let _ = m.det_sign_exact();
379 }
380
381 #[test]
382 #[should_panic(expected = "non-finite matrix entry")]
383 fn det_sign_exact_panics_on_infinity() {
384 let m = Matrix::<2>::from_rows([[f64::INFINITY, 0.0], [0.0, 1.0]]);
385 let _ = m.det_sign_exact();
386 }
387
388 #[test]
389 #[should_panic(expected = "non-finite matrix entry")]
390 fn det_sign_exact_panics_on_nan_5x5() {
391 let mut m = Matrix::<5>::identity();
393 m.set(2, 3, f64::NAN);
394 let _ = m.det_sign_exact();
395 }
396
397 #[test]
398 #[should_panic(expected = "non-finite matrix entry")]
399 fn det_sign_exact_panics_on_infinity_5x5() {
400 let mut m = Matrix::<5>::identity();
401 m.set(0, 0, f64::INFINITY);
402 let _ = m.det_sign_exact();
403 }
404
405 #[test]
406 fn det_sign_exact_pivot_needed_5x5() {
407 let m = Matrix::<5>::from_rows([
410 [0.0, 1.0, 0.0, 0.0, 0.0],
411 [1.0, 0.0, 0.0, 0.0, 0.0],
412 [0.0, 0.0, 1.0, 0.0, 0.0],
413 [0.0, 0.0, 0.0, 1.0, 0.0],
414 [0.0, 0.0, 0.0, 0.0, 1.0],
415 ]);
416 assert_eq!(m.det_sign_exact(), -1);
417 }
418
419 #[test]
420 fn det_sign_exact_5x5_known() {
421 let m = Matrix::<5>::from_rows([
423 [0.0, 1.0, 0.0, 0.0, 0.0],
424 [1.0, 0.0, 0.0, 0.0, 0.0],
425 [0.0, 0.0, 0.0, 1.0, 0.0],
426 [0.0, 0.0, 1.0, 0.0, 0.0],
427 [0.0, 0.0, 0.0, 0.0, 1.0],
428 ]);
429 assert_eq!(m.det_sign_exact(), 1);
431 }
432
433 #[test]
438 fn det_errbound_d0_is_zero() {
439 assert_eq!(det_errbound(&Matrix::<0>::zero()), Some(0.0));
440 }
441
442 #[test]
443 fn det_errbound_d1_is_zero() {
444 assert_eq!(det_errbound(&Matrix::<1>::from_rows([[42.0]])), Some(0.0));
445 }
446
447 #[test]
448 fn det_errbound_d2_positive() {
449 let m = Matrix::<2>::from_rows([[1.0, 2.0], [3.0, 4.0]]);
450 let bound = det_errbound(&m).unwrap();
451 assert!(bound > 0.0);
452 assert!(ERR_COEFF_2.mul_add(-10.0, bound).abs() < 1e-30);
454 }
455
456 #[test]
457 fn det_errbound_d3_positive() {
458 let m = Matrix::<3>::identity();
459 let bound = det_errbound(&m).unwrap();
460 assert!(bound > 0.0);
461 }
462
463 #[test]
464 fn det_errbound_d4_positive() {
465 let m = Matrix::<4>::identity();
466 let bound = det_errbound(&m).unwrap();
467 assert!(bound > 0.0);
468 }
469
470 #[test]
471 fn det_errbound_d5_is_none() {
472 assert_eq!(det_errbound(&Matrix::<5>::identity()), None);
473 }
474
475 #[test]
476 fn bareiss_det_d0_is_one() {
477 let det = bareiss_det(&Matrix::<0>::zero());
478 assert_eq!(det, BigRational::from_integer(BigInt::from(1)));
479 }
480
481 #[test]
482 fn bareiss_det_d1_returns_entry() {
483 let det = bareiss_det(&Matrix::<1>::from_rows([[7.0]]));
484 assert_eq!(det, f64_to_bigrational(7.0));
485 }
486
487 #[test]
488 fn bareiss_det_d3_with_pivoting() {
489 let m = Matrix::<3>::from_rows([[0.0, 1.0, 0.0], [1.0, 0.0, 0.0], [0.0, 0.0, 1.0]]);
491 let det = bareiss_det(&m);
492 assert_eq!(det, BigRational::from_integer(BigInt::from(-1)));
494 }
495
496 #[test]
497 fn bareiss_det_singular_all_zeros_in_column() {
498 let m = Matrix::<3>::from_rows([[1.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0, 1.0]]);
500 let det = bareiss_det(&m);
501 assert_eq!(det, BigRational::from_integer(BigInt::from(0)));
502 }
503}