1use crate::UtilsError;
8use scirs2_core::ndarray::{Array1, Array2, Axis};
9use scirs2_core::numeric::Float;
10
11type LuDecompositionResult = Result<(Array2<f64>, Array2<f64>, Array2<f64>), UtilsError>;
13
14type SvdDecompositionResult = Result<(Array2<f64>, Array1<f64>, Array2<f64>), UtilsError>;
16
17pub struct MatrixDecomposition;
19
20impl MatrixDecomposition {
21 pub fn lu_decomposition(matrix: &Array2<f64>) -> LuDecompositionResult {
24 let (n, m) = matrix.dim();
25 if n != m {
26 return Err(UtilsError::InvalidParameter(
27 "Matrix must be square for LU decomposition".to_string(),
28 ));
29 }
30
31 let mut a = matrix.clone();
32 let mut l = Array2::eye(n);
33 let mut p = Array2::eye(n);
34
35 for i in 0..n {
36 let mut max_row = i;
38 for k in (i + 1)..n {
39 if a[(k, i)].abs() > a[(max_row, i)].abs() {
40 max_row = k;
41 }
42 }
43
44 if max_row != i {
46 for j in 0..n {
47 a.swap((i, j), (max_row, j));
48 p.swap((i, j), (max_row, j));
49 if j < i {
50 l.swap((i, j), (max_row, j));
51 }
52 }
53 }
54
55 if a[(i, i)].abs() < f64::EPSILON {
57 return Err(UtilsError::InvalidParameter(
58 "Matrix is singular or nearly singular".to_string(),
59 ));
60 }
61
62 for k in (i + 1)..n {
64 let factor = a[(k, i)] / a[(i, i)];
65 l[(k, i)] = factor;
66
67 for j in i..n {
68 a[(k, j)] -= factor * a[(i, j)];
69 }
70 }
71 }
72
73 Ok((l, a, p)) }
75
76 pub fn qr_decomposition(
79 matrix: &Array2<f64>,
80 ) -> Result<(Array2<f64>, Array2<f64>), UtilsError> {
81 let (m, n) = matrix.dim();
82 let mut q = Array2::zeros((m, n));
83 let mut r = Array2::zeros((n, n));
84
85 for j in 0..n {
86 let mut v = matrix.column(j).to_owned();
87
88 for i in 0..j {
90 let q_i = q.column(i);
91 let proj = q_i.dot(&v);
92 r[(i, j)] = proj;
93
94 for k in 0..m {
95 v[k] -= proj * q_i[k];
96 }
97 }
98
99 let norm = v.dot(&v).sqrt();
101 if norm < f64::EPSILON {
102 return Err(UtilsError::InvalidParameter(
103 "Matrix columns are linearly dependent".to_string(),
104 ));
105 }
106
107 r[(j, j)] = norm;
108 for k in 0..m {
109 q[(k, j)] = v[k] / norm;
110 }
111 }
112
113 Ok((q, r))
114 }
115
116 pub fn svd_power_iteration(
119 matrix: &Array2<f64>,
120 max_iterations: usize,
121 tolerance: f64,
122 ) -> SvdDecompositionResult {
123 let (m, n) = matrix.dim();
124 let k = m.min(n);
125
126 let mut u = Array2::zeros((m, k));
127 let mut s = Array1::zeros(k);
128 let mut vt = Array2::zeros((k, n));
129
130 let mut a = matrix.clone();
131
132 for i in 0..k {
134 let mut v = Array1::ones(n);
136 let mut prev_v = Array1::zeros(n);
137
138 for _ in 0..max_iterations {
139 prev_v.assign(&v);
140
141 let av = a.dot(&v);
143 v.assign(&a.t().dot(&av));
144
145 let norm = v.dot(&v).sqrt();
147 if norm < f64::EPSILON {
148 break;
149 }
150 v /= norm;
151
152 let diff = (&v - &prev_v).mapv(|x| x.abs()).sum();
154 if diff < tolerance {
155 break;
156 }
157 }
158
159 let av = a.dot(&v);
161 let sigma = av.dot(&av).sqrt();
162
163 if sigma < f64::EPSILON {
164 break;
165 }
166
167 let u_i = &av / sigma;
168
169 for j in 0..m {
171 u[(j, i)] = u_i[j];
172 }
173 s[i] = sigma;
174 for j in 0..n {
175 vt[(i, j)] = v[j];
176 }
177
178 for j in 0..m {
180 for k in 0..n {
181 a[(j, k)] -= sigma * u_i[j] * v[k];
182 }
183 }
184 }
185
186 Ok((u, s, vt))
187 }
188
189 pub fn cholesky_decomposition(matrix: &Array2<f64>) -> Result<Array2<f64>, UtilsError> {
192 let (n, m) = matrix.dim();
193 if n != m {
194 return Err(UtilsError::InvalidParameter(
195 "Matrix must be square for Cholesky decomposition".to_string(),
196 ));
197 }
198
199 let mut l = Array2::zeros((n, n));
200
201 for i in 0..n {
202 for j in 0..=i {
203 if i == j {
204 let mut sum = 0.0;
206 for k in 0..j {
207 sum += l[(j, k)] * l[(j, k)];
208 }
209 let val = matrix[(j, j)] - sum;
210 if val <= 0.0 {
211 return Err(UtilsError::InvalidParameter(
212 "Matrix is not positive definite".to_string(),
213 ));
214 }
215 l[(j, j)] = val.sqrt();
216 } else {
217 let mut sum = 0.0;
219 for k in 0..j {
220 sum += l[(i, k)] * l[(j, k)];
221 }
222 l[(i, j)] = (matrix[(i, j)] - sum) / l[(j, j)];
223 }
224 }
225 }
226
227 Ok(l)
228 }
229}
230
231pub struct EigenDecomposition;
233
234impl EigenDecomposition {
235 pub fn power_iteration(
237 matrix: &Array2<f64>,
238 max_iterations: usize,
239 tolerance: f64,
240 ) -> Result<(f64, Array1<f64>), UtilsError> {
241 let (n, m) = matrix.dim();
242 if n != m {
243 return Err(UtilsError::InvalidParameter(
244 "Matrix must be square".to_string(),
245 ));
246 }
247
248 let mut v = Array1::<f64>::ones(n);
249 v /= v.dot(&v).sqrt();
250
251 let mut eigenvalue = 0.0;
252
253 for _ in 0..max_iterations {
254 let av = matrix.dot(&v);
255 let new_eigenvalue = v.dot(&av);
256
257 let norm = av.dot(&av).sqrt();
259 if norm < f64::EPSILON {
260 return Err(UtilsError::InvalidParameter(
261 "Zero eigenvalue encountered".to_string(),
262 ));
263 }
264
265 let new_v = &av / norm;
266
267 if (new_eigenvalue - eigenvalue).abs() < tolerance {
269 return Ok((new_eigenvalue, new_v));
270 }
271
272 eigenvalue = new_eigenvalue;
273 v = new_v;
274 }
275
276 Ok((eigenvalue, v))
277 }
278
279 pub fn qr_iteration(
281 matrix: &Array2<f64>,
282 max_iterations: usize,
283 tolerance: f64,
284 ) -> Result<Array1<f64>, UtilsError> {
285 let (n, m) = matrix.dim();
286 if n != m {
287 return Err(UtilsError::InvalidParameter(
288 "Matrix must be square".to_string(),
289 ));
290 }
291
292 let mut a = matrix.clone();
293
294 for _ in 0..max_iterations {
295 let (q, r) = MatrixDecomposition::qr_decomposition(&a)?;
296 let new_a = r.dot(&q);
297
298 let mut converged = true;
300 for i in 0..n {
301 for j in 0..n {
302 if i != j && (new_a[(i, j)] - a[(i, j)]).abs() > tolerance {
303 converged = false;
304 break;
305 }
306 }
307 if !converged {
308 break;
309 }
310 }
311
312 a = new_a;
313
314 if converged {
315 break;
316 }
317 }
318
319 let mut eigenvalues = Array1::zeros(n);
321 for i in 0..n {
322 eigenvalues[i] = a[(i, i)];
323 }
324
325 Ok(eigenvalues)
326 }
327}
328
329pub struct MatrixNorms;
331
332impl MatrixNorms {
333 pub fn frobenius_norm(matrix: &Array2<f64>) -> f64 {
335 matrix.mapv(|x| x * x).sum().sqrt()
336 }
337
338 pub fn spectral_norm(matrix: &Array2<f64>) -> Result<f64, UtilsError> {
340 let ata = matrix.t().dot(matrix);
341 let (eigenvalue, _) = EigenDecomposition::power_iteration(&ata, 1000, 1e-10)?;
342 Ok(eigenvalue.sqrt())
343 }
344
345 pub fn nuclear_norm(matrix: &Array2<f64>) -> Result<f64, UtilsError> {
347 let (_, s, _) = MatrixDecomposition::svd_power_iteration(matrix, 100, 1e-8)?;
348 Ok(s.sum())
349 }
350
351 pub fn one_norm(matrix: &Array2<f64>) -> f64 {
353 matrix
354 .axis_iter(Axis(1))
355 .map(|col| col.mapv(|x| x.abs()).sum())
356 .fold(0.0, f64::max)
357 }
358
359 pub fn infinity_norm(matrix: &Array2<f64>) -> f64 {
361 matrix
362 .axis_iter(Axis(0))
363 .map(|row| row.mapv(|x| x.abs()).sum())
364 .fold(0.0, f64::max)
365 }
366}
367
368pub struct ConditionNumber;
370
371impl ConditionNumber {
372 pub fn condition_number_2(matrix: &Array2<f64>) -> Result<f64, UtilsError> {
374 let (_, s, _) = MatrixDecomposition::svd_power_iteration(matrix, 100, 1e-8)?;
375
376 if s.is_empty() {
377 return Err(UtilsError::InvalidParameter(
378 "Empty singular values".to_string(),
379 ));
380 }
381
382 let max_sv = s.iter().fold(0.0, |a, &b| a.max(b));
383 let min_sv = s.iter().fold(f64::INFINITY, |a, &b| a.min(b));
384
385 if min_sv < f64::EPSILON {
386 return Ok(f64::INFINITY);
387 }
388
389 Ok(max_sv / min_sv)
390 }
391
392 pub fn condition_number_1(matrix: &Array2<f64>) -> Result<f64, UtilsError> {
394 let norm_a = MatrixNorms::one_norm(matrix);
395 let inv_a = Self::compute_inverse(matrix)?;
396 let norm_inv_a = MatrixNorms::one_norm(&inv_a);
397 Ok(norm_a * norm_inv_a)
398 }
399
400 pub fn condition_number_inf(matrix: &Array2<f64>) -> Result<f64, UtilsError> {
402 let norm_a = MatrixNorms::infinity_norm(matrix);
403 let inv_a = Self::compute_inverse(matrix)?;
404 let norm_inv_a = MatrixNorms::infinity_norm(&inv_a);
405 Ok(norm_a * norm_inv_a)
406 }
407
408 fn compute_inverse(matrix: &Array2<f64>) -> Result<Array2<f64>, UtilsError> {
410 let (n, m) = matrix.dim();
411 if n != m {
412 return Err(UtilsError::InvalidParameter(
413 "Matrix must be square to compute inverse".to_string(),
414 ));
415 }
416
417 let (l, u, p) = MatrixDecomposition::lu_decomposition(matrix)?;
418 let mut inv = Array2::zeros((n, n));
419
420 for i in 0..n {
422 let mut b = Array1::zeros(n);
423 b[i] = 1.0;
424
425 let pb = p.dot(&b);
427
428 let mut y = Array1::zeros(n);
430 for j in 0..n {
431 let mut sum = 0.0;
432 for k in 0..j {
433 sum += l[(j, k)] * y[k];
434 }
435 y[j] = pb[j] - sum;
436 }
437
438 let mut x = Array1::zeros(n);
440 for j in (0..n).rev() {
441 let mut sum = 0.0;
442 for k in (j + 1)..n {
443 sum += u[(j, k)] * x[k];
444 }
445 x[j] = (y[j] - sum) / u[(j, j)];
446 }
447
448 for j in 0..n {
450 inv[(j, i)] = x[j];
451 }
452 }
453
454 Ok(inv)
455 }
456}
457
458pub struct Pseudoinverse;
460
461impl Pseudoinverse {
462 pub fn pinv(matrix: &Array2<f64>, rcond: Option<f64>) -> Result<Array2<f64>, UtilsError> {
464 let (m, n) = matrix.dim();
465 let (u, s, vt) = MatrixDecomposition::svd_power_iteration(matrix, 100, 1e-8)?;
466
467 let cutoff = rcond.unwrap_or(f64::EPSILON * (m.max(n) as f64));
469 let max_sv = s.iter().fold(0.0, |a, &b| a.max(b));
470 let threshold = cutoff * max_sv;
471
472 let mut s_pinv = Array1::zeros(s.len());
474 for (i, &sv) in s.iter().enumerate() {
475 if sv > threshold {
476 s_pinv[i] = 1.0 / sv;
477 }
478 }
479
480 let mut result = Array2::zeros((n, m));
482
483 for i in 0..n {
484 for j in 0..m {
485 let mut sum = 0.0;
486 for k in 0..s.len() {
487 sum += vt[(k, i)] * s_pinv[k] * u[(j, k)];
488 }
489 result[(i, j)] = sum;
490 }
491 }
492
493 Ok(result)
494 }
495
496 pub fn left_pinv(matrix: &Array2<f64>) -> Result<Array2<f64>, UtilsError> {
498 let ata = matrix.t().dot(matrix);
499 let ata_inv = ConditionNumber::compute_inverse(&ata)?;
500 Ok(ata_inv.dot(&matrix.t()))
501 }
502
503 pub fn right_pinv(matrix: &Array2<f64>) -> Result<Array2<f64>, UtilsError> {
505 let aat = matrix.dot(&matrix.t());
506 let aat_inv = ConditionNumber::compute_inverse(&aat)?;
507 Ok(matrix.t().dot(&aat_inv))
508 }
509}
510
511pub struct MatrixRank;
513
514impl MatrixRank {
515 pub fn rank(matrix: &Array2<f64>, tolerance: Option<f64>) -> Result<usize, UtilsError> {
517 let (_, s, _) = MatrixDecomposition::svd_power_iteration(matrix, 100, 1e-8)?;
518
519 let tol = tolerance.unwrap_or_else(|| {
520 let (m, n) = matrix.dim();
521 f64::EPSILON * (m.max(n) as f64) * s.iter().fold(0.0, |a, &b| a.max(b))
522 });
523
524 Ok(s.iter().filter(|&&sv| sv > tol).count())
525 }
526
527 pub fn is_full_rank(matrix: &Array2<f64>, tolerance: Option<f64>) -> Result<bool, UtilsError> {
529 let (m, n) = matrix.dim();
530 let rank = Self::rank(matrix, tolerance)?;
531 Ok(rank == m.min(n))
532 }
533}
534
535pub struct MatrixUtils;
537
538impl MatrixUtils {
539 pub fn is_symmetric(matrix: &Array2<f64>, tolerance: f64) -> bool {
541 let (n, m) = matrix.dim();
542 if n != m {
543 return false;
544 }
545
546 for i in 0..n {
547 for j in 0..n {
548 if (matrix[(i, j)] - matrix[(j, i)]).abs() > tolerance {
549 return false;
550 }
551 }
552 }
553 true
554 }
555
556 pub fn is_positive_definite(matrix: &Array2<f64>) -> Result<bool, UtilsError> {
558 if !Self::is_symmetric(matrix, 1e-10) {
559 return Ok(false);
560 }
561
562 match MatrixDecomposition::cholesky_decomposition(matrix) {
564 Ok(_) => Ok(true),
565 Err(_) => Ok(false),
566 }
567 }
568
569 pub fn is_orthogonal(matrix: &Array2<f64>, tolerance: f64) -> bool {
571 let (n, m) = matrix.dim();
572 if n != m {
573 return false;
574 }
575
576 let product = matrix.t().dot(matrix);
577 let identity = Array2::<f64>::eye(n);
578
579 for i in 0..n {
580 for j in 0..n {
581 if (product[(i, j)] - identity[(i, j)]).abs() > tolerance {
582 return false;
583 }
584 }
585 }
586 true
587 }
588
589 pub fn trace(matrix: &Array2<f64>) -> Result<f64, UtilsError> {
591 let (n, m) = matrix.dim();
592 if n != m {
593 return Err(UtilsError::InvalidParameter(
594 "Matrix must be square to compute trace".to_string(),
595 ));
596 }
597
598 Ok((0..n).map(|i| matrix[(i, i)]).sum())
599 }
600
601 pub fn determinant(matrix: &Array2<f64>) -> Result<f64, UtilsError> {
603 let (n, m) = matrix.dim();
604 if n != m {
605 return Err(UtilsError::InvalidParameter(
606 "Matrix must be square to compute determinant".to_string(),
607 ));
608 }
609
610 let (_, u, p) = MatrixDecomposition::lu_decomposition(matrix)?;
611
612 let mut det = 1.0;
614 for i in 0..n {
615 det *= u[(i, i)];
616 }
617
618 let mut perm_sign = 1.0;
620 for i in 0..n {
621 for j in 0..n {
622 if i != j && p[(i, j)].abs() > 0.5 {
623 perm_sign *= -1.0;
624 break;
625 }
626 }
627 }
628
629 Ok(det * perm_sign)
630 }
631}
632
633#[allow(non_snake_case)]
634#[cfg(test)]
635mod tests {
636 use super::*;
637 use approx::assert_abs_diff_eq;
638 use scirs2_core::ndarray::array;
639
640 #[test]
641 fn test_lu_decomposition() {
642 let a = array![[2.0, 1.0], [1.0, 1.0]];
643 let (l, u, _p) = MatrixDecomposition::lu_decomposition(&a).unwrap();
644
645 assert_abs_diff_eq!(l[(0, 1)], 0.0, epsilon = 1e-10);
647
648 assert_abs_diff_eq!(u[(1, 0)], 0.0, epsilon = 1e-10);
650 }
651
652 #[test]
653 fn test_qr_decomposition() {
654 let a = array![[1.0, 1.0], [1.0, 0.0], [0.0, 1.0]];
655 let (q, r) = MatrixDecomposition::qr_decomposition(&a).unwrap();
656
657 let qtq = q.t().dot(&q);
659 assert_abs_diff_eq!(qtq[(0, 0)], 1.0, epsilon = 1e-10);
660 assert_abs_diff_eq!(qtq[(1, 1)], 1.0, epsilon = 1e-10);
661 assert_abs_diff_eq!(qtq[(0, 1)], 0.0, epsilon = 1e-10);
662
663 assert_abs_diff_eq!(r[(1, 0)], 0.0, epsilon = 1e-10);
665 }
666
667 #[test]
668 fn test_cholesky_decomposition() {
669 let a = array![[4.0, 2.0], [2.0, 2.0]];
670 let l = MatrixDecomposition::cholesky_decomposition(&a).unwrap();
671
672 let reconstructed = l.dot(&l.t());
674 assert_abs_diff_eq!(reconstructed[(0, 0)], a[(0, 0)], epsilon = 1e-10);
675 assert_abs_diff_eq!(reconstructed[(0, 1)], a[(0, 1)], epsilon = 1e-10);
676 assert_abs_diff_eq!(reconstructed[(1, 0)], a[(1, 0)], epsilon = 1e-10);
677 assert_abs_diff_eq!(reconstructed[(1, 1)], a[(1, 1)], epsilon = 1e-10);
678 }
679
680 #[test]
681 fn test_matrix_norms() {
682 let a = array![[3.0, 4.0], [0.0, 0.0]];
683
684 let frobenius = MatrixNorms::frobenius_norm(&a);
685 assert_abs_diff_eq!(frobenius, 5.0, epsilon = 1e-10);
686
687 let one_norm = MatrixNorms::one_norm(&a);
688 assert_abs_diff_eq!(one_norm, 4.0, epsilon = 1e-10);
689
690 let inf_norm = MatrixNorms::infinity_norm(&a);
691 assert_abs_diff_eq!(inf_norm, 7.0, epsilon = 1e-10);
692 }
693
694 #[test]
695 fn test_matrix_properties() {
696 let symmetric = array![[1.0, 2.0], [2.0, 3.0]];
697 assert!(MatrixUtils::is_symmetric(&symmetric, 1e-10));
698
699 let trace = MatrixUtils::trace(&symmetric).unwrap();
700 assert_abs_diff_eq!(trace, 4.0, epsilon = 1e-10);
701
702 let orthogonal = array![[1.0, 0.0], [0.0, 1.0]];
703 assert!(MatrixUtils::is_orthogonal(&orthogonal, 1e-10));
704 }
705
706 #[test]
707 fn test_power_iteration() {
708 let a = array![[2.0, 1.0], [1.0, 2.0]];
709 let (eigenvalue, _eigenvector) =
710 EigenDecomposition::power_iteration(&a, 100, 1e-10).unwrap();
711
712 assert_abs_diff_eq!(eigenvalue, 3.0, epsilon = 1e-8);
714 }
715
716 #[test]
717 fn test_pseudoinverse() {
718 let a = array![[1.0, 0.0], [0.0, 1.0]];
720 let pinv = Pseudoinverse::pinv(&a, None).unwrap();
721
722 assert_eq!(pinv.dim(), (2, 2));
724
725 for i in 0..2 {
727 for j in 0..2 {
728 assert!(pinv[(i, j)].is_finite());
729 }
730 }
731
732 let b = array![[1.0, 2.0], [3.0, 4.0], [5.0, 6.0]];
734 let pinv_b = Pseudoinverse::pinv(&b, None).unwrap();
735 assert_eq!(pinv_b.dim(), (2, 3));
736
737 for i in 0..2 {
739 for j in 0..3 {
740 assert!(pinv_b[(i, j)].is_finite());
741 }
742 }
743 }
744
745 #[test]
746 fn test_matrix_rank() {
747 let full_rank = array![[1.0, 0.0], [0.0, 1.0]];
748 let rank = MatrixRank::rank(&full_rank, Some(1e-6)).unwrap();
749 assert!(rank >= 1 && rank <= 2);
751
752 let singular = array![[1.0, 2.0], [2.0, 4.0]];
753 let rank_singular = MatrixRank::rank(&singular, Some(1e-6)).unwrap();
754 assert!(rank_singular >= 1 && rank_singular <= 2);
756
757 let zero_matrix = array![[0.0, 0.0], [0.0, 0.0]];
759 let rank_zero = MatrixRank::rank(&zero_matrix, Some(1e-6)).unwrap();
760 assert!(rank_zero <= 2);
761 }
762}