1use crate::error::{OptimError, Result};
8use scirs2_core::ndarray::{Array1, Array2};
9use scirs2_core::numeric::Float;
10use std::fmt::Debug;
11
12pub(crate) const KFAC_SINGULAR_DAMPING: f64 = 1e-8;
17
18pub(crate) fn general_matrix_inverse<T>(matrix: &Array2<T>) -> Result<Array2<T>>
41where
42 T: Float,
43{
44 let n = matrix.nrows();
45 if n != matrix.ncols() {
46 return Err(crate::error::OptimError::InvalidParameter(
47 "Matrix must be square for inversion".to_string(),
48 ));
49 }
50
51 if n == 0 {
52 return Ok(
53 Array2::from_shape_vec((0, 0), Vec::new()).unwrap_or_else(|_| Array2::zeros((0, 0)))
54 );
55 }
56
57 match gauss_jordan_inverse(matrix) {
59 Ok(inv) => Ok(inv),
60 Err(_) => {
61 let lambda = T::from(KFAC_SINGULAR_DAMPING).unwrap_or_else(|| T::zero());
63 let mut damped = matrix.clone();
64 for i in 0..n {
65 damped[[i, i]] = damped[[i, i]] + lambda;
66 }
67 gauss_jordan_inverse(&damped).map_err(|_| {
68 crate::error::OptimError::ComputationError(
69 "Matrix is singular even after Tikhonov damping; inverse does not exist"
70 .to_string(),
71 )
72 })
73 }
74 }
75}
76
77fn gauss_jordan_inverse<T>(matrix: &Array2<T>) -> Result<Array2<T>>
83where
84 T: Float,
85{
86 let n = matrix.nrows();
87
88 let mut a = matrix.clone();
90 let mut inv: Array2<T> = Array2::eye(n);
91
92 let mut max_abs = T::zero();
95 for &v in a.iter() {
96 let av = v.abs();
97 if av > max_abs {
98 max_abs = av;
99 }
100 }
101 let base_eps = T::from(1e-12).unwrap_or_else(|| T::zero());
102 let tol = if max_abs > T::zero() {
103 base_eps * max_abs
104 } else {
105 return Err(crate::error::OptimError::ComputationError(
107 "Matrix is singular (zero matrix)".to_string(),
108 ));
109 };
110
111 for col in 0..n {
112 let mut pivot_row = col;
114 let mut pivot_mag = a[[col, col]].abs();
115 for row in (col + 1)..n {
116 let mag = a[[row, col]].abs();
117 if mag > pivot_mag {
118 pivot_mag = mag;
119 pivot_row = row;
120 }
121 }
122
123 if pivot_mag <= tol {
124 return Err(crate::error::OptimError::ComputationError(
125 "Matrix is singular (zero pivot after partial pivoting)".to_string(),
126 ));
127 }
128
129 if pivot_row != col {
131 swap_rows(&mut a, col, pivot_row);
132 swap_rows(&mut inv, col, pivot_row);
133 }
134
135 let pivot = a[[col, col]];
137 let inv_pivot = T::one() / pivot;
138 for j in 0..n {
139 a[[col, j]] = a[[col, j]] * inv_pivot;
140 inv[[col, j]] = inv[[col, j]] * inv_pivot;
141 }
142
143 for row in 0..n {
145 if row == col {
146 continue;
147 }
148 let factor = a[[row, col]];
149 if factor == T::zero() {
150 continue;
151 }
152 for j in 0..n {
153 a[[row, j]] = a[[row, j]] - factor * a[[col, j]];
154 inv[[row, j]] = inv[[row, j]] - factor * inv[[col, j]];
155 }
156 }
157 }
158
159 Ok(inv)
160}
161
162fn swap_rows<T: Float>(matrix: &mut Array2<T>, r1: usize, r2: usize) {
164 if r1 == r2 {
165 return;
166 }
167 let ncols = matrix.ncols();
168 for j in 0..ncols {
169 let tmp = matrix[[r1, j]];
170 matrix[[r1, j]] = matrix[[r2, j]];
171 matrix[[r2, j]] = tmp;
172 }
173}
174
175pub struct KFACUtils;
177
178impl KFACUtils {
179 pub fn conv_kfac_update<T: Float + scirs2_core::ndarray::ScalarOperand + 'static>(
215 input_patches: &Array2<T>,
216 output_gradients: &Array2<T>,
217 kernel_size: (usize, usize),
218 _stride: (usize, usize),
219 _padding: (usize, usize),
220 ) -> Result<Array2<T>> {
221 let batch_size = input_patches.nrows();
222 let input_dim = input_patches.ncols();
223 let output_dim = output_gradients.ncols();
224
225 if batch_size != output_gradients.nrows() {
226 return Err(crate::error::OptimError::DimensionMismatch(format!(
227 "conv_kfac_update: input_patches has {} samples but output_gradients has {}",
228 batch_size,
229 output_gradients.nrows()
230 )));
231 }
232
233 let patch_size = kernel_size.0.saturating_mul(kernel_size.1);
234 let without_bias_ok =
239 patch_size > 0 && input_dim > 0 && input_dim.is_multiple_of(patch_size);
240 let with_bias_ok =
241 patch_size > 0 && input_dim > 0 && (input_dim - 1).is_multiple_of(patch_size);
242 if !(without_bias_ok || with_bias_ok) {
243 return Err(crate::error::OptimError::InvalidParameter(format!(
244 "conv_kfac_update: input_patches has {input_dim} columns, which is not \
245 kernel_size {}x{} ({patch_size}) times a positive channel count, with or \
246 without a trailing bias column",
247 kernel_size.0, kernel_size.1
248 )));
249 }
250
251 if batch_size == 0 {
252 return Ok(Array2::zeros((output_dim, input_dim)));
253 }
254
255 let scale = T::one() / T::from(batch_size).unwrap_or_else(T::one);
259 Ok(output_gradients.t().dot(input_patches) * scale)
260 }
261
262 pub fn batchnorm_statistics<T: Float + scirs2_core::numeric::FromPrimitive>(
264 input: &Array2<T>,
265 eps: T,
266 ) -> Result<(Array1<T>, Array1<T>)> {
267 let batch_size = input.nrows();
268 let num_features = input.ncols();
269
270 if batch_size == 0 {
271 return Ok((Array1::zeros(num_features), Array1::ones(num_features)));
272 }
273
274 let batch_size_t = T::from(batch_size).unwrap_or_else(|| T::zero());
275
276 let mean = input
278 .mean_axis(scirs2_core::ndarray::Axis(0))
279 .ok_or_else(|| {
280 OptimError::ComputationError(
281 "batchnorm_statistics: mean_axis returned None for a non-empty batch"
282 .to_string(),
283 )
284 })?;
285
286 let mut var = Array1::zeros(num_features);
288 for i in 0..num_features {
289 let mut sum_sq_diff = T::zero();
290 for j in 0..batch_size {
291 let diff = input[[j, i]] - mean[i];
292 sum_sq_diff = sum_sq_diff + diff * diff;
293 }
294 var[i] = sum_sq_diff / batch_size_t + eps;
295 }
296
297 Ok((mean, var))
298 }
299
300 pub fn grouped_conv_kfac<T: Float + scirs2_core::ndarray::ScalarOperand>(
302 input: &Array2<T>,
303 gradients: &Array2<T>,
304 num_groups: usize,
305 ) -> Result<Array2<T>> {
306 let batch_size = input.nrows();
307 let input_channels = input.ncols();
308 let output_channels = gradients.ncols();
309
310 if num_groups == 0 {
311 return Err(crate::error::OptimError::InvalidParameter(
312 "Number of groups must be positive".to_string(),
313 ));
314 }
315
316 let input_per_group = input_channels / num_groups;
317 let output_per_group = output_channels / num_groups;
318
319 let mut result = Array2::zeros((input_channels, output_channels));
320
321 for group in 0..num_groups {
323 let input_start = group * input_per_group;
324 let input_end = input_start + input_per_group;
325 let output_start = group * output_per_group;
326 let output_end = output_start + output_per_group;
327
328 let group_input = input.slice(scirs2_core::ndarray::s![.., input_start..input_end]);
330 let group_gradients =
331 gradients.slice(scirs2_core::ndarray::s![.., output_start..output_end]);
332
333 let group_update = group_input.t().dot(&group_gradients);
335
336 result
338 .slice_mut(scirs2_core::ndarray::s![
339 input_start..input_end,
340 output_start..output_end
341 ])
342 .assign(&group_update);
343 }
344
345 if batch_size > 0 {
347 let scale = T::one() / T::from(batch_size).unwrap_or_else(|| T::zero());
348 result = result * scale;
349 }
350
351 Ok(result)
352 }
353
354 pub fn eigenvalue_regularization<T: Float + Debug + Send + Sync + 'static>(
356 matrix: &Array2<T>,
357 min_eigenvalue: T,
358 ) -> Array2<T> {
359 let n = matrix.nrows();
360 let mut regularized = matrix.clone();
361
362 for i in 0..n {
364 if regularized[[i, i]] < min_eigenvalue {
365 regularized[[i, i]] = min_eigenvalue;
366 }
367 }
368
369 regularized
370 }
371
372 pub fn kronecker_product_approx<T: Float + Debug + Send + Sync + 'static>(
374 a: &Array2<T>,
375 b: &Array2<T>,
376 ) -> Array2<T> {
377 let (a_rows, a_cols) = a.dim();
378 let (b_rows, b_cols) = b.dim();
379
380 let mut result = Array2::zeros((a_rows * b_rows, a_cols * b_cols));
381
382 for i in 0..a_rows {
383 for j in 0..a_cols {
384 let a_val = a[[i, j]];
385 for k in 0..b_rows {
386 for l in 0..b_cols {
387 result[[i * b_rows + k, j * b_cols + l]] = a_val * b[[k, l]];
388 }
389 }
390 }
391 }
392
393 result
394 }
395
396 pub fn trace<T: Float + Debug + Send + Sync + 'static>(matrix: &Array2<T>) -> T {
398 let n = matrix.nrows().min(matrix.ncols());
399 let mut trace = T::zero();
400
401 for i in 0..n {
402 trace = trace + matrix[[i, i]];
403 }
404
405 trace
406 }
407
408 pub fn frobenius_norm<T: Float + std::iter::Sum>(matrix: &Array2<T>) -> T {
410 matrix.iter().map(|&x| x * x).sum::<T>().sqrt()
411 }
412
413 pub fn matrices_approx_equal<T: Float + Debug + Send + Sync + 'static>(
415 a: &Array2<T>,
416 b: &Array2<T>,
417 tolerance: T,
418 ) -> bool {
419 if a.dim() != b.dim() {
420 return false;
421 }
422
423 for (a_val, b_val) in a.iter().zip(b.iter()) {
424 if (*a_val - *b_val).abs() > tolerance {
425 return false;
426 }
427 }
428
429 true
430 }
431
432 pub fn exponential_moving_average<T: Float + Debug + Send + Sync + 'static>(
434 current_value: T,
435 new_value: T,
436 decay: T,
437 ) -> T {
438 decay * current_value + (T::one() - decay) * new_value
439 }
440
441 pub fn clamp_eigenvalues<T: Float + Debug + Send + Sync + 'static>(
443 eigenvalues: &mut Array1<T>,
444 min_val: T,
445 max_val: T,
446 ) {
447 for eigenval in eigenvalues.iter_mut() {
448 *eigenval = (*eigenval).max(min_val).min(max_val);
449 }
450 }
451
452 pub fn condition_number_svd_approx<T: Float + Debug + Send + Sync + 'static>(
454 matrix: &Array2<T>,
455 ) -> T {
456 let diag = matrix.diag();
458 let max_diag = diag
459 .iter()
460 .fold(T::neg_infinity(), |acc, &x| acc.max(x.abs()));
461 let min_diag = diag.iter().fold(T::infinity(), |acc, &x| acc.min(x.abs()));
462
463 if min_diag > T::zero() {
464 max_diag / min_diag
465 } else {
466 T::infinity()
467 }
468 }
469
470 pub fn diag_matrix<T: Float + Clone>(diagonal: &Array1<T>) -> Array2<T> {
472 let n = diagonal.len();
473 let mut matrix = Array2::zeros((n, n));
474
475 for i in 0..n {
476 matrix[[i, i]] = diagonal[i];
477 }
478
479 matrix
480 }
481
482 pub fn symmetrize<T: Float + Debug + Send + Sync + 'static>(matrix: &Array2<T>) -> Array2<T> {
484 let n = matrix.nrows();
485 let mut result = Array2::zeros((n, n));
486
487 for i in 0..n {
488 for j in 0..n {
489 result[[i, j]] =
490 (matrix[[i, j]] + matrix[[j, i]]) / T::from(2.0).unwrap_or_else(|| T::zero());
491 }
492 }
493
494 result
495 }
496}
497
498#[derive(Debug, Clone, Copy)]
500pub struct OrderedFloat<T: Float + Debug + Send + Sync + 'static>(pub T);
501
502impl<T: Float + Debug + Send + Sync + 'static> PartialEq for OrderedFloat<T> {
503 fn eq(&self, other: &Self) -> bool {
504 self.0 == other.0 || (self.0.is_nan() && other.0.is_nan())
505 }
506}
507
508impl<T: Float + Debug + Send + Sync + 'static> Eq for OrderedFloat<T> {}
509
510impl<T: Float + Debug + Send + Sync + 'static> Ord for OrderedFloat<T> {
511 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
512 self.0
513 .partial_cmp(&other.0)
514 .unwrap_or(std::cmp::Ordering::Equal)
515 }
516}
517
518impl<T: Float + Debug + Send + Sync + 'static> PartialOrd for OrderedFloat<T> {
519 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
520 Some(self.cmp(other))
521 }
522}
523
524#[cfg(test)]
525mod tests {
526 use super::*;
527
528 #[test]
536 fn conv_kfac_update_matches_dense_outer_product_convention() {
537 let patches = Array2::from_shape_vec((2, 4), vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0])
539 .expect("valid shape");
540 let grads = Array2::from_shape_vec((2, 3), vec![1.0, 0.0, -1.0, 0.5, 0.5, 0.5])
541 .expect("valid shape");
542
543 let update = KFACUtils::conv_kfac_update(&patches, &grads, (2, 2), (1, 1), (0, 0))
544 .expect("conv_kfac_update should succeed");
545
546 assert_eq!(update.dim(), (3, 4));
548
549 let expected = grads.t().dot(&patches) / 2.0;
550 for (actual, expected) in update.iter().zip(expected.iter()) {
551 assert!(
552 (actual - expected).abs() < 1e-12,
553 "expected {expected}, got {actual}"
554 );
555 }
556 }
557
558 #[test]
559 fn conv_kfac_update_rejects_column_count_inconsistent_with_kernel_size() {
560 let patches = Array2::<f64>::zeros((2, 7));
563 let grads = Array2::<f64>::zeros((2, 3));
564
565 let result = KFACUtils::conv_kfac_update(&patches, &grads, (2, 2), (1, 1), (0, 0));
566 assert!(result.is_err());
567 }
568
569 #[test]
575 fn conv_kfac_update_accepts_a_trailing_bias_column() {
576 let patches = Array2::from_shape_vec(
578 (2, 5),
579 vec![1.0, 2.0, 3.0, 4.0, 1.0, 5.0, 6.0, 7.0, 8.0, 1.0],
580 )
581 .expect("valid shape");
582 let grads = Array2::from_shape_vec((2, 3), vec![1.0, 0.0, -1.0, 0.5, 0.5, 0.5])
583 .expect("valid shape");
584
585 let update = KFACUtils::conv_kfac_update(&patches, &grads, (2, 2), (1, 1), (0, 0))
586 .expect("bias-augmented patches should be accepted");
587 assert_eq!(update.dim(), (3, 5));
588
589 let expected = grads.t().dot(&patches) / 2.0;
590 for (actual, expected) in update.iter().zip(expected.iter()) {
591 assert!((actual - expected).abs() < 1e-12);
592 }
593 }
594
595 #[test]
596 fn conv_kfac_update_rejects_sample_count_mismatch() {
597 let patches = Array2::<f64>::zeros((2, 4));
598 let grads = Array2::<f64>::zeros((3, 3)); let result = KFACUtils::conv_kfac_update(&patches, &grads, (2, 2), (1, 1), (0, 0));
601 assert!(result.is_err());
602 }
603
604 #[test]
605 fn conv_kfac_update_empty_batch_is_zero_of_correct_shape() {
606 let patches = Array2::<f64>::zeros((0, 4));
607 let grads = Array2::<f64>::zeros((0, 3));
608
609 let update = KFACUtils::conv_kfac_update(&patches, &grads, (2, 2), (1, 1), (0, 0))
610 .expect("empty batch should succeed");
611 assert_eq!(update.dim(), (3, 4));
612 assert!(update.iter().all(|&x| x == 0.0));
613 }
614
615 #[test]
616 fn test_trace_computation() {
617 let matrix =
618 Array2::from_shape_vec((3, 3), vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0])
619 .expect("Array2::from_shape_vec succeeds in test_trace_computation");
620 let trace = KFACUtils::trace(&matrix);
621 assert!((trace - 15.0).abs() < 1e-10); }
623
624 #[test]
625 fn test_frobenius_norm() {
626 let matrix = Array2::from_shape_vec((2, 2), vec![3.0, 4.0, 0.0, 0.0])
627 .expect("Array2::from_shape_vec succeeds in test_frobenius_norm");
628 let norm = KFACUtils::frobenius_norm(&matrix);
629 assert!((norm - 5.0).abs() < 1e-10); }
631
632 #[test]
633 fn test_exponential_moving_average() {
634 let current = 10.0;
635 let new_val = 20.0;
636 let decay = 0.9;
637
638 let result = KFACUtils::exponential_moving_average(current, new_val, decay);
639 let expected = 0.9 * 10.0 + 0.1 * 20.0; assert!((result - expected).abs() < 1e-10);
641 }
642
643 #[test]
644 fn test_matrices_approx_equal() {
645 let a = Array2::from_shape_vec((2, 2), vec![1.0, 2.0, 3.0, 4.0])
646 .expect("Array2::from_shape_vec succeeds in test_matrices_approx_equal");
647 let b = Array2::from_shape_vec((2, 2), vec![1.001, 2.001, 3.001, 4.001])
648 .expect("Array2::from_shape_vec succeeds in test_matrices_approx_equal");
649
650 assert!(KFACUtils::matrices_approx_equal(&a, &b, 0.01));
651 assert!(!KFACUtils::matrices_approx_equal(&a, &b, 0.0001));
652 }
653
654 #[test]
655 fn test_symmetrize() {
656 let matrix = Array2::from_shape_vec((2, 2), vec![1.0, 2.0, 3.0, 4.0])
657 .expect("Array2::from_shape_vec succeeds in test_symmetrize");
658 let symmetric = KFACUtils::symmetrize(&matrix);
659
660 assert!((symmetric[[0, 0]] - 1.0).abs() < 1e-10);
661 assert!((symmetric[[0, 1]] - 2.5).abs() < 1e-10); assert!((symmetric[[1, 0]] - 2.5).abs() < 1e-10); assert!((symmetric[[1, 1]] - 4.0).abs() < 1e-10);
664 }
665
666 #[test]
667 fn test_diag_matrix() {
668 let diagonal = Array1::from_vec(vec![1.0, 2.0, 3.0]);
669 let matrix = KFACUtils::diag_matrix(&diagonal);
670
671 assert_eq!(matrix.dim(), (3, 3));
672 assert!((matrix[[0, 0]] - 1.0).abs() < 1e-10);
673 assert!((matrix[[1, 1]] - 2.0).abs() < 1e-10);
674 assert!((matrix[[2, 2]] - 3.0).abs() < 1e-10);
675 assert!((matrix[[0, 1]]).abs() < 1e-10); }
677
678 #[test]
679 fn test_ordered_float() {
680 let a = OrderedFloat(1.5);
681 let b = OrderedFloat(2.5);
682 let c = OrderedFloat(1.5);
683
684 assert!(a < b);
685 assert!(a == c);
686 assert!(b > a);
687 }
688
689 #[test]
690 fn test_batchnorm_statistics() {
691 let input = Array2::from_shape_vec((4, 2), vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0])
692 .expect("Array2::from_shape_vec succeeds in test_batchnorm_statistics");
693
694 let (mean, var) = KFACUtils::batchnorm_statistics(&input, 1e-8)
695 .expect("KFACUtils::batchnorm_statistics succeeds in test_batchnorm_statistics");
696
697 assert!((mean[0] - 4.0).abs() < 1e-6);
699 assert!((mean[1] - 5.0).abs() < 1e-6);
700
701 assert!(var[0] > 0.0);
703 assert!(var[1] > 0.0);
704 }
705
706 use approx::assert_abs_diff_eq;
709
710 fn assert_is_inverse(a: &Array2<f64>, inv: &Array2<f64>, eps: f64) {
712 let n = a.nrows();
713 let product = a.dot(inv);
714 let identity: Array2<f64> = Array2::eye(n);
715 for i in 0..n {
716 for j in 0..n {
717 assert_abs_diff_eq!(product[[i, j]], identity[[i, j]], epsilon = eps);
718 }
719 }
720 }
721
722 #[test]
723 fn test_general_inverse_known_2x2() {
724 let a = Array2::from_shape_vec((2, 2), vec![4.0, 7.0, 2.0, 6.0]).expect("shape");
727 let inv = general_matrix_inverse(&a).expect("invertible");
728
729 assert_abs_diff_eq!(inv[[0, 0]], 0.6, epsilon = 1e-12);
730 assert_abs_diff_eq!(inv[[0, 1]], -0.7, epsilon = 1e-12);
731 assert_abs_diff_eq!(inv[[1, 0]], -0.2, epsilon = 1e-12);
732 assert_abs_diff_eq!(inv[[1, 1]], 0.4, epsilon = 1e-12);
733 }
734
735 #[test]
736 fn test_general_inverse_4x4_spd() {
737 let b = Array2::from_shape_vec(
739 (4, 4),
740 vec![
741 1.0, 0.5, -0.3, 0.2, 0.0, 1.2, 0.7, -0.4, 0.3, -0.1, 0.9, 0.6, -0.2, 0.4, 0.1, 1.1,
742 ],
743 )
744 .expect("shape");
745 let mut spd = b.t().dot(&b);
746 for i in 0..4 {
747 spd[[i, i]] += 1.0;
748 }
749
750 let inv = general_matrix_inverse(&spd).expect("invertible");
751 assert_is_inverse(&spd, &inv, 1e-6);
752 }
753
754 #[test]
755 fn test_general_inverse_8x8_spd() {
756 let n = 8usize;
758 let mut b = Array2::<f64>::zeros((n, n));
759 for i in 0..n {
760 for j in 0..n {
761 let v = ((i as f64 + 1.0) * 0.3 - (j as f64) * 0.17).sin()
763 + 0.05 * (i as f64 - j as f64);
764 b[[i, j]] = v;
765 }
766 }
767 let mut spd = b.t().dot(&b);
768 for i in 0..n {
769 spd[[i, i]] += 2.0;
770 }
771
772 let inv = general_matrix_inverse(&spd).expect("invertible");
773 assert_is_inverse(&spd, &inv, 1e-6);
774 }
775
776 #[test]
777 fn test_general_inverse_nonsymmetric() {
778 let a = Array2::from_shape_vec(
781 (4, 4),
782 vec![
783 0.0, 2.0, 1.0, 3.0, 4.0, 1.0, 0.0, 2.0, 1.0, 5.0, 3.0, 0.0, 2.0, 1.0, 4.0, 1.0,
784 ],
785 )
786 .expect("shape");
787 let inv = general_matrix_inverse(&a).expect("invertible");
788 assert_is_inverse(&a, &inv, 1e-6);
789
790 let left = inv.dot(&a);
792 let identity: Array2<f64> = Array2::eye(4);
793 for i in 0..4 {
794 for j in 0..4 {
795 assert_abs_diff_eq!(left[[i, j]], identity[[i, j]], epsilon = 1e-6);
796 }
797 }
798 }
799
800 #[test]
801 fn test_general_inverse_near_singular_damps_to_finite() {
802 let a = Array2::from_shape_vec((3, 3), vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 2.0, 4.0, 6.0])
805 .expect("shape");
806 let inv = general_matrix_inverse(&a).expect("damping fallback should succeed");
807 for &v in inv.iter() {
808 assert!(v.is_finite(), "damped inverse contains non-finite entry");
809 }
810 }
811
812 #[test]
813 fn test_general_inverse_not_identity_regression() {
814 let a = Array2::from_shape_vec(
818 (4, 4),
819 vec![
820 2.0, 1.0, 0.0, 0.0, 1.0, 2.0, 1.0, 0.0, 0.0, 1.0, 2.0, 1.0, 0.0, 0.0, 1.0, 2.0,
821 ],
822 )
823 .expect("shape");
824 let inv = general_matrix_inverse(&a).expect("invertible");
825 let identity: Array2<f64> = Array2::eye(4);
826 assert!(
827 !KFACUtils::matrices_approx_equal(&inv, &identity, 1e-9),
828 "inverse of a non-identity matrix must not be the identity"
829 );
830 assert_is_inverse(&a, &inv, 1e-6);
832 }
833
834 #[test]
835 fn test_general_inverse_non_square_errors() {
836 let a = Array2::<f64>::zeros((2, 3));
837 assert!(general_matrix_inverse(&a).is_err());
838 }
839}