1use num_bigint::{BigInt, Sign};
18use num_rational::BigRational;
19
20use crate::LaError;
21use crate::matrix::Matrix;
22
23fn f64_to_bigrational(x: f64) -> BigRational {
31 BigRational::from_float(x).expect("non-finite matrix entry in det_sign_exact")
32}
33
34fn bareiss_det<const D: usize>(m: &Matrix<D>) -> BigRational {
39 if D == 0 {
40 return BigRational::from_integer(BigInt::from(1));
41 }
42 if D == 1 {
43 return f64_to_bigrational(m.rows[0][0]);
44 }
45
46 let mut a: Vec<Vec<BigRational>> = Vec::with_capacity(D);
48 for r in 0..D {
49 let mut row = Vec::with_capacity(D);
50 for c in 0..D {
51 row.push(f64_to_bigrational(m.rows[r][c]));
52 }
53 a.push(row);
54 }
55
56 let zero = BigRational::from_integer(BigInt::from(0));
57 let mut prev_pivot = BigRational::from_integer(BigInt::from(1));
58 let mut sign: i8 = 1;
59
60 for k in 0..D {
61 if a[k][k] == zero {
63 let mut found = false;
64 for i in (k + 1)..D {
65 if a[i][k] != zero {
66 a.swap(k, i);
67 sign = -sign;
68 found = true;
69 break;
70 }
71 }
72 if !found {
73 return zero;
75 }
76 }
77
78 for i in (k + 1)..D {
80 for j in (k + 1)..D {
81 a[i][j] = (&a[k][k] * &a[i][j] - &a[i][k] * &a[k][j]) / &prev_pivot;
83 }
84 a[i][k] = zero.clone();
87 }
88
89 prev_pivot = a[k][k].clone();
90 }
91
92 let det = &a[D - 1][D - 1];
93 if sign < 0 { -det } else { det.clone() }
94}
95
96impl<const D: usize> Matrix<D> {
97 #[inline]
132 pub fn det_sign_exact(&self) -> Result<i8, LaError> {
133 for r in 0..D {
135 for c in 0..D {
136 if !self.rows[r][c].is_finite() {
137 return Err(LaError::NonFinite { col: c });
138 }
139 }
140 }
141
142 if let (Some(det_f64), Some(err)) = (self.det_direct(), self.det_errbound()) {
144 if det_f64.is_finite() {
149 if det_f64 > err {
150 return Ok(1);
151 }
152 if det_f64 < -err {
153 return Ok(-1);
154 }
155 }
156 }
157
158 let det = bareiss_det(self);
160 Ok(match det.numer().sign() {
161 Sign::Plus => 1,
162 Sign::Minus => -1,
163 Sign::NoSign => 0,
164 })
165 }
166}
167
168#[cfg(test)]
169mod tests {
170 use super::*;
171 use crate::DEFAULT_PIVOT_TOL;
172
173 #[test]
174 fn det_sign_exact_d0_is_positive() {
175 assert_eq!(Matrix::<0>::zero().det_sign_exact().unwrap(), 1);
176 }
177
178 #[test]
179 fn det_sign_exact_d1_positive() {
180 let m = Matrix::<1>::from_rows([[42.0]]);
181 assert_eq!(m.det_sign_exact().unwrap(), 1);
182 }
183
184 #[test]
185 fn det_sign_exact_d1_negative() {
186 let m = Matrix::<1>::from_rows([[-3.5]]);
187 assert_eq!(m.det_sign_exact().unwrap(), -1);
188 }
189
190 #[test]
191 fn det_sign_exact_d1_zero() {
192 let m = Matrix::<1>::from_rows([[0.0]]);
193 assert_eq!(m.det_sign_exact().unwrap(), 0);
194 }
195
196 #[test]
197 fn det_sign_exact_identity_2d() {
198 assert_eq!(Matrix::<2>::identity().det_sign_exact().unwrap(), 1);
199 }
200
201 #[test]
202 fn det_sign_exact_identity_3d() {
203 assert_eq!(Matrix::<3>::identity().det_sign_exact().unwrap(), 1);
204 }
205
206 #[test]
207 fn det_sign_exact_identity_4d() {
208 assert_eq!(Matrix::<4>::identity().det_sign_exact().unwrap(), 1);
209 }
210
211 #[test]
212 fn det_sign_exact_identity_5d() {
213 assert_eq!(Matrix::<5>::identity().det_sign_exact().unwrap(), 1);
214 }
215
216 #[test]
217 fn det_sign_exact_singular_duplicate_rows() {
218 let m = Matrix::<3>::from_rows([
219 [1.0, 2.0, 3.0],
220 [4.0, 5.0, 6.0],
221 [1.0, 2.0, 3.0], ]);
223 assert_eq!(m.det_sign_exact().unwrap(), 0);
224 }
225
226 #[test]
227 fn det_sign_exact_singular_linear_combination() {
228 let m = Matrix::<3>::from_rows([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [5.0, 7.0, 9.0]]);
230 assert_eq!(m.det_sign_exact().unwrap(), 0);
231 }
232
233 #[test]
234 fn det_sign_exact_negative_det_row_swap() {
235 let m = Matrix::<3>::from_rows([[0.0, 1.0, 0.0], [1.0, 0.0, 0.0], [0.0, 0.0, 1.0]]);
237 assert_eq!(m.det_sign_exact().unwrap(), -1);
238 }
239
240 #[test]
241 fn det_sign_exact_negative_det_known() {
242 let m = Matrix::<2>::from_rows([[1.0, 2.0], [3.0, 4.0]]);
244 assert_eq!(m.det_sign_exact().unwrap(), -1);
245 }
246
247 #[test]
248 fn det_sign_exact_agrees_with_det_for_spd() {
249 let m = Matrix::<3>::from_rows([[4.0, 2.0, 0.0], [2.0, 5.0, 1.0], [0.0, 1.0, 3.0]]);
251 assert_eq!(m.det_sign_exact().unwrap(), 1);
252 assert!(m.det(DEFAULT_PIVOT_TOL).unwrap() > 0.0);
253 }
254
255 #[test]
262 fn det_sign_exact_near_singular_perturbation() {
263 let perturbation = f64::from_bits(0x3CD0_0000_0000_0000); let m = Matrix::<3>::from_rows([
265 [1.0 + perturbation, 2.0, 3.0],
266 [4.0, 5.0, 6.0],
267 [7.0, 8.0, 9.0],
268 ]);
269 assert_eq!(m.det_sign_exact().unwrap(), -1);
271 }
272
273 #[test]
277 fn det_sign_exact_fast_filter_positive_4x4() {
278 let m = Matrix::<4>::from_rows([
279 [2.0, 1.0, 0.0, 0.0],
280 [1.0, 3.0, 1.0, 0.0],
281 [0.0, 1.0, 4.0, 1.0],
282 [0.0, 0.0, 1.0, 5.0],
283 ]);
284 assert_eq!(m.det_sign_exact().unwrap(), 1);
286 }
287
288 #[test]
289 fn det_sign_exact_fast_filter_negative_4x4() {
290 let m = Matrix::<4>::from_rows([
292 [1.0, 3.0, 1.0, 0.0],
293 [2.0, 1.0, 0.0, 0.0],
294 [0.0, 1.0, 4.0, 1.0],
295 [0.0, 0.0, 1.0, 5.0],
296 ]);
297 assert_eq!(m.det_sign_exact().unwrap(), -1);
298 }
299
300 #[test]
301 fn det_sign_exact_subnormal_entries() {
302 let tiny = 5e-324_f64; assert!(tiny.is_subnormal());
305
306 let m = Matrix::<2>::from_rows([[tiny, 0.0], [0.0, tiny]]);
307 assert_eq!(m.det_sign_exact().unwrap(), 1);
309 }
310
311 #[test]
312 fn det_sign_exact_returns_err_on_nan() {
313 let m = Matrix::<2>::from_rows([[f64::NAN, 0.0], [0.0, 1.0]]);
314 assert_eq!(m.det_sign_exact(), Err(LaError::NonFinite { col: 0 }));
315 }
316
317 #[test]
318 fn det_sign_exact_returns_err_on_infinity() {
319 let m = Matrix::<2>::from_rows([[f64::INFINITY, 0.0], [0.0, 1.0]]);
320 assert_eq!(m.det_sign_exact(), Err(LaError::NonFinite { col: 0 }));
321 }
322
323 #[test]
324 fn det_sign_exact_returns_err_on_nan_5x5() {
325 let mut m = Matrix::<5>::identity();
327 m.set(2, 3, f64::NAN);
328 assert_eq!(m.det_sign_exact(), Err(LaError::NonFinite { col: 3 }));
329 }
330
331 #[test]
332 fn det_sign_exact_returns_err_on_infinity_5x5() {
333 let mut m = Matrix::<5>::identity();
334 m.set(0, 0, f64::INFINITY);
335 assert_eq!(m.det_sign_exact(), Err(LaError::NonFinite { col: 0 }));
336 }
337
338 #[test]
339 fn det_sign_exact_pivot_needed_5x5() {
340 let m = Matrix::<5>::from_rows([
343 [0.0, 1.0, 0.0, 0.0, 0.0],
344 [1.0, 0.0, 0.0, 0.0, 0.0],
345 [0.0, 0.0, 1.0, 0.0, 0.0],
346 [0.0, 0.0, 0.0, 1.0, 0.0],
347 [0.0, 0.0, 0.0, 0.0, 1.0],
348 ]);
349 assert_eq!(m.det_sign_exact().unwrap(), -1);
350 }
351
352 #[test]
353 fn det_sign_exact_5x5_known() {
354 let m = Matrix::<5>::from_rows([
356 [0.0, 1.0, 0.0, 0.0, 0.0],
357 [1.0, 0.0, 0.0, 0.0, 0.0],
358 [0.0, 0.0, 0.0, 1.0, 0.0],
359 [0.0, 0.0, 1.0, 0.0, 0.0],
360 [0.0, 0.0, 0.0, 0.0, 1.0],
361 ]);
362 assert_eq!(m.det_sign_exact().unwrap(), 1);
364 }
365
366 #[test]
371 fn det_errbound_d0_is_zero() {
372 assert_eq!(Matrix::<0>::zero().det_errbound(), Some(0.0));
373 }
374
375 #[test]
376 fn det_errbound_d1_is_zero() {
377 assert_eq!(Matrix::<1>::from_rows([[42.0]]).det_errbound(), Some(0.0));
378 }
379
380 #[test]
381 fn det_errbound_d2_positive() {
382 let m = Matrix::<2>::from_rows([[1.0, 2.0], [3.0, 4.0]]);
383 let bound = m.det_errbound().unwrap();
384 assert!(bound > 0.0);
385 assert!(crate::ERR_COEFF_2.mul_add(-10.0, bound).abs() < 1e-30);
387 }
388
389 #[test]
390 fn det_errbound_d3_positive() {
391 let m = Matrix::<3>::identity();
392 let bound = m.det_errbound().unwrap();
393 assert!(bound > 0.0);
394 }
395
396 #[test]
397 fn det_errbound_d3_non_identity() {
398 let m = Matrix::<3>::from_rows([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [7.0, 8.0, 10.0]]);
400 let bound = m.det_errbound().unwrap();
401 assert!(bound > 0.0);
402 }
403
404 #[test]
405 fn det_errbound_d4_positive() {
406 let m = Matrix::<4>::identity();
407 let bound = m.det_errbound().unwrap();
408 assert!(bound > 0.0);
409 }
410
411 #[test]
412 fn det_errbound_d4_non_identity() {
413 let m = Matrix::<4>::from_rows([
415 [1.0, 0.0, 0.0, 0.0],
416 [0.0, 2.0, 0.0, 0.0],
417 [0.0, 0.0, 3.0, 0.0],
418 [0.0, 0.0, 0.0, 4.0],
419 ]);
420 let bound = m.det_errbound().unwrap();
421 assert!(bound > 0.0);
422 }
423
424 #[test]
425 fn det_errbound_d5_is_none() {
426 assert_eq!(Matrix::<5>::identity().det_errbound(), None);
427 }
428
429 #[test]
430 fn bareiss_det_d0_is_one() {
431 let det = bareiss_det(&Matrix::<0>::zero());
432 assert_eq!(det, BigRational::from_integer(BigInt::from(1)));
433 }
434
435 #[test]
436 fn bareiss_det_d1_returns_entry() {
437 let det = bareiss_det(&Matrix::<1>::from_rows([[7.0]]));
438 assert_eq!(det, f64_to_bigrational(7.0));
439 }
440
441 #[test]
442 fn bareiss_det_d3_with_pivoting() {
443 let m = Matrix::<3>::from_rows([[0.0, 1.0, 0.0], [1.0, 0.0, 0.0], [0.0, 0.0, 1.0]]);
445 let det = bareiss_det(&m);
446 assert_eq!(det, BigRational::from_integer(BigInt::from(-1)));
448 }
449
450 #[test]
451 fn bareiss_det_singular_all_zeros_in_column() {
452 let m = Matrix::<3>::from_rows([[1.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0, 1.0]]);
454 let det = bareiss_det(&m);
455 assert_eq!(det, BigRational::from_integer(BigInt::from(0)));
456 }
457
458 #[test]
459 fn det_sign_exact_overflow_determinant_finite_entries() {
460 let big = f64::MAX / 2.0;
464 assert!(big.is_finite());
465 let m = Matrix::<3>::from_rows([[0.0, 0.0, 1.0], [big, 0.0, 1.0], [0.0, big, 1.0]]);
466 assert_eq!(m.det_sign_exact().unwrap(), 1);
468 }
469}