1#![allow(unused_variables)]
7#![allow(unused_assignments)]
8#![allow(unused_mut)]
9
10use crate::error::{SparseError, SparseResult};
11use crate::sparray::SparseArray;
12use scirs2_core::ndarray::{Array1, Array2};
13use scirs2_core::numeric::{Float, SparseElement};
14use std::fmt::Debug;
15use std::ops::{Add, Div, Mul, Sub};
16
17type BidiagonalSvdResult<T> = (Vec<T>, Vec<Vec<f64>>, Vec<Vec<f64>>);
19
20#[derive(Debug, Clone, Copy, PartialEq)]
22pub enum SVDMethod {
23 Lanczos,
25 Randomized,
27 Power,
29 CrossApproximation,
31}
32
33impl SVDMethod {
34 pub fn from_str(s: &str) -> SparseResult<Self> {
35 match s.to_lowercase().as_str() {
36 "lanczos" => Ok(Self::Lanczos),
37 "randomized" | "random" => Ok(Self::Randomized),
38 "power" => Ok(Self::Power),
39 "cross" | "cross_approximation" => Ok(Self::CrossApproximation),
40 _ => Err(SparseError::ValueError(format!("Unknown SVD method: {s}"))),
41 }
42 }
43}
44
45#[derive(Debug, Clone)]
47pub struct SVDOptions {
48 pub k: usize,
50 pub maxiter: usize,
52 pub tol: f64,
54 pub n_oversamples: usize,
56 pub n_iter: usize,
58 pub method: SVDMethod,
60 pub random_seed: Option<u64>,
62 pub compute_u: bool,
64 pub compute_vt: bool,
66}
67
68impl Default for SVDOptions {
69 fn default() -> Self {
70 Self {
71 k: 6,
72 maxiter: 1000,
73 tol: 1e-10,
74 n_oversamples: 10,
75 n_iter: 2,
76 method: SVDMethod::Lanczos,
77 random_seed: None,
78 compute_u: true,
79 compute_vt: true,
80 }
81 }
82}
83
84#[derive(Debug, Clone)]
86pub struct SVDResult<T>
87where
88 T: Float + SparseElement + Debug + Copy,
89{
90 pub u: Option<Array2<T>>,
92 pub s: Array1<T>,
94 pub vt: Option<Array2<T>>,
96 pub iterations: usize,
98 pub converged: bool,
100}
101
102#[allow(dead_code)]
133pub fn svds<T, S>(
134 matrix: &S,
135 k: Option<usize>,
136 options: Option<SVDOptions>,
137) -> SparseResult<SVDResult<T>>
138where
139 T: Float
140 + SparseElement
141 + Debug
142 + Copy
143 + Add<Output = T>
144 + Sub<Output = T>
145 + Mul<Output = T>
146 + Div<Output = T>
147 + 'static
148 + std::iter::Sum,
149 S: SparseArray<T>,
150{
151 let opts = options.unwrap_or_default();
152 let k = k.unwrap_or(opts.k);
153
154 let (m, n) = matrix.shape();
155 if k >= m.min(n) {
156 return Err(SparseError::ValueError(
157 "Number of singular values k must be less than min(m, n)".to_string(),
158 ));
159 }
160
161 match opts.method {
162 SVDMethod::Lanczos => lanczos_bidiag_svd(matrix, k, &opts),
163 SVDMethod::Randomized => randomized_svd(matrix, k, &opts),
164 SVDMethod::Power => power_method_svd(matrix, k, &opts),
165 SVDMethod::CrossApproximation => cross_approximation_svd(matrix, k, &opts),
166 }
167}
168
169#[allow(dead_code)]
171pub fn svd_truncated<T, S>(
172 matrix: &S,
173 k: usize,
174 method: &str,
175 tol: Option<f64>,
176 maxiter: Option<usize>,
177) -> SparseResult<SVDResult<T>>
178where
179 T: Float
180 + SparseElement
181 + Debug
182 + Copy
183 + Add<Output = T>
184 + Sub<Output = T>
185 + Mul<Output = T>
186 + Div<Output = T>
187 + 'static
188 + std::iter::Sum,
189 S: SparseArray<T>,
190{
191 let method_enum = SVDMethod::from_str(method)?;
192
193 let options = SVDOptions {
194 k,
195 method: method_enum,
196 tol: tol.unwrap_or(1e-10),
197 maxiter: maxiter.unwrap_or(1000),
198 ..Default::default()
199 };
200
201 svds(matrix, Some(k), Some(options))
202}
203
204#[allow(dead_code)]
206fn lanczos_bidiag_svd<T, S>(
207 matrix: &S,
208 k: usize,
209 options: &SVDOptions,
210) -> SparseResult<SVDResult<T>>
211where
212 T: Float
213 + SparseElement
214 + Debug
215 + Copy
216 + Add<Output = T>
217 + Sub<Output = T>
218 + Mul<Output = T>
219 + Div<Output = T>
220 + 'static
221 + std::iter::Sum,
222 S: SparseArray<T>,
223{
224 let (m, n) = matrix.shape();
225 let max_lanczos_size = (2 * k + 10).min(m.min(n));
226
227 let mut u = Array1::zeros(m);
229 u[0] = T::sparse_one();
230
231 let norm = (u.iter().map(|&v| v * v).sum::<T>()).sqrt();
233 if !SparseElement::is_zero(&norm) {
234 for i in 0..m {
235 u[i] = u[i] / norm;
236 }
237 }
238
239 let mut alpha = Vec::<T>::new();
240 let mut beta = Vec::<T>::new();
241 let mut u_vectors = Vec::<Array1<T>>::with_capacity(max_lanczos_size);
242 let mut v_vectors = Vec::<Array1<T>>::with_capacity(max_lanczos_size);
243
244 u_vectors.push(u.clone());
245
246 let mut converged = false;
247 let mut iter = 0;
248
249 while iter < options.maxiter && alpha.len() < max_lanczos_size {
251 let av = matrix_transpose_vector_product(matrix, &u_vectors[iter])?;
253 let mut v = av;
254
255 if iter > 0 && !beta.is_empty() {
256 let prev_beta = beta[iter - 1];
257 for i in 0..n {
258 v[i] = v[i] - prev_beta * v_vectors[iter - 1][i];
259 }
260 }
261
262 let alpha_j = (v.iter().map(|&val| val * val).sum::<T>()).sqrt();
264 alpha.push(alpha_j);
265
266 if SparseElement::is_zero(&alpha_j) {
267 break;
268 }
269
270 for i in 0..n {
272 v[i] = v[i] / alpha_j;
273 }
274 v_vectors.push(v.clone());
275
276 let avu = matrix_vector_product(matrix, &v)?;
278 let mut u_next = avu;
279
280 for i in 0..m {
281 u_next[i] = u_next[i] - alpha_j * u_vectors[iter][i];
282 }
283
284 let beta_j = (u_next.iter().map(|&val| val * val).sum::<T>()).sqrt();
286 beta.push(beta_j);
287
288 if beta_j < T::from(options.tol).unwrap() {
289 converged = true;
290 break;
291 }
292
293 for i in 0..m {
295 u_next[i] = u_next[i] / beta_j;
296 }
297
298 u_vectors.push(u_next);
299 iter += 1;
300 }
301
302 let (singular_values, u_bidiag, vt_bidiag) = solve_bidiagonal_svd(&alpha, &beta, k)?;
304
305 let final_u = if options.compute_u {
307 let mut u_final = Array2::zeros((m, k.min(singular_values.len())));
308 for j in 0..k.min(singular_values.len()) {
309 for i in 0..m {
310 let mut sum = T::sparse_zero();
311 for l in 0..u_vectors.len().min(u_bidiag.len()) {
312 if j < u_bidiag[l].len() {
313 sum = sum + T::from(u_bidiag[l][j]).unwrap() * u_vectors[l][i];
314 }
315 }
316 u_final[[i, j]] = sum;
317 }
318 }
319 Some(u_final)
320 } else {
321 None
322 };
323
324 let final_vt = if options.compute_vt {
325 let mut vt_final = Array2::zeros((k.min(singular_values.len()), n));
326 for j in 0..k.min(singular_values.len()) {
327 for i in 0..n {
328 let mut sum = T::sparse_zero();
329 for l in 0..v_vectors.len().min(vt_bidiag.len()) {
330 if j < vt_bidiag[l].len() {
331 sum = sum + T::from(vt_bidiag[l][j]).unwrap() * v_vectors[l][i];
332 }
333 }
334 vt_final[[j, i]] = sum;
335 }
336 }
337 Some(vt_final)
338 } else {
339 None
340 };
341
342 Ok(SVDResult {
343 u: final_u,
344 s: Array1::from_vec(singular_values[..k.min(singular_values.len())].to_vec()),
345 vt: final_vt,
346 iterations: iter,
347 converged,
348 })
349}
350
351#[allow(dead_code)]
353fn randomized_svd<T, S>(matrix: &S, k: usize, options: &SVDOptions) -> SparseResult<SVDResult<T>>
354where
355 T: Float
356 + SparseElement
357 + Debug
358 + Copy
359 + Add<Output = T>
360 + Sub<Output = T>
361 + Mul<Output = T>
362 + Div<Output = T>
363 + 'static
364 + std::iter::Sum,
365 S: SparseArray<T>,
366{
367 let (m, n) = matrix.shape();
368 let l = k + options.n_oversamples;
369
370 let mut omega = Array2::zeros((n, l));
372 for i in 0..n {
373 for j in 0..l {
374 let val = ((i * 17 + j * 13) % 1000) as f64 / 1000.0 - 0.5;
376 omega[[i, j]] = T::from(val).unwrap();
377 }
378 }
379
380 let mut y = Array2::zeros((m, l));
382 for j in 0..l {
383 let omega_col = omega.column(j).to_owned();
384 let y_col = matrix_vector_product(matrix, &omega_col)?;
385 for i in 0..m {
386 y[[i, j]] = y_col[i];
387 }
388 }
389
390 for _ in 0..options.n_iter {
392 let mut y_new = Array2::zeros((m, l));
394 for j in 0..l {
395 let y_col = y.column(j).to_owned();
396 let at_y_col = matrix_transpose_vector_product(matrix, &y_col)?;
397 let a_at_y_col = matrix_vector_product(matrix, &at_y_col)?;
398 for i in 0..m {
399 y_new[[i, j]] = a_at_y_col[i];
400 }
401 }
402 y = y_new;
403 }
404
405 let q = qr_decomposition_orthogonal(&y)?;
407
408 let mut b = Array2::zeros((l, n));
410 for i in 0..l {
411 let q_row = q.row(i).to_owned();
412
413 let b_row = matrix_transpose_vector_product(matrix, &q_row)?;
415 for j in 0..n {
416 b[[i, j]] = b_row[j];
417 }
418 }
419
420 let b_svd = dense_svd(&b, k)?;
422
423 let final_u = if options.compute_u {
425 if let Some(ref u_b) = b_svd.u {
427 let mut u_result = Array2::zeros((m, k));
428 for i in 0..m {
429 for j in 0..k {
430 let mut sum = T::sparse_zero();
431 for l_idx in 0..l {
432 sum = sum + q[[l_idx, i]] * u_b[[l_idx, j]];
433 }
434 u_result[[i, j]] = sum;
435 }
436 }
437 Some(u_result)
438 } else {
439 None
440 }
441 } else {
442 None
443 };
444
445 Ok(SVDResult {
446 u: final_u,
447 s: b_svd.s,
448 vt: b_svd.vt,
449 iterations: options.n_iter + 1,
450 converged: true,
451 })
452}
453
454#[allow(dead_code)]
456fn power_method_svd<T, S>(matrix: &S, k: usize, options: &SVDOptions) -> SparseResult<SVDResult<T>>
457where
458 T: Float
459 + SparseElement
460 + Debug
461 + Copy
462 + Add<Output = T>
463 + Sub<Output = T>
464 + Mul<Output = T>
465 + Div<Output = T>
466 + 'static
467 + std::iter::Sum,
468 S: SparseArray<T>,
469{
470 lanczos_bidiag_svd(matrix, k, options)
473}
474
475#[allow(dead_code)]
477fn cross_approximation_svd<T, S>(
478 matrix: &S,
479 k: usize,
480 options: &SVDOptions,
481) -> SparseResult<SVDResult<T>>
482where
483 T: Float
484 + SparseElement
485 + Debug
486 + Copy
487 + Add<Output = T>
488 + Sub<Output = T>
489 + Mul<Output = T>
490 + Div<Output = T>
491 + 'static
492 + std::iter::Sum,
493 S: SparseArray<T>,
494{
495 lanczos_bidiag_svd(matrix, k, options)
498}
499
500#[allow(dead_code)]
502fn matrix_vector_product<T, S>(matrix: &S, vector: &Array1<T>) -> SparseResult<Array1<T>>
503where
504 T: Float
505 + SparseElement
506 + Debug
507 + Copy
508 + Add<Output = T>
509 + Sub<Output = T>
510 + Mul<Output = T>
511 + Div<Output = T>
512 + 'static
513 + std::iter::Sum,
514 S: SparseArray<T>,
515{
516 let (m, n) = matrix.shape();
517 if vector.len() != n {
518 return Err(SparseError::DimensionMismatch {
519 expected: n,
520 found: vector.len(),
521 });
522 }
523
524 let mut result = Array1::zeros(m);
525 let (row_indices, col_indices, values) = matrix.find();
526
527 for (k, (&i, &j)) in row_indices.iter().zip(col_indices.iter()).enumerate() {
528 result[i] = result[i] + values[k] * vector[j];
529 }
530
531 Ok(result)
532}
533
534#[allow(dead_code)]
536fn matrix_transpose_vector_product<T, S>(matrix: &S, vector: &Array1<T>) -> SparseResult<Array1<T>>
537where
538 T: Float
539 + SparseElement
540 + Debug
541 + Copy
542 + Add<Output = T>
543 + Sub<Output = T>
544 + Mul<Output = T>
545 + Div<Output = T>
546 + 'static
547 + std::iter::Sum,
548 S: SparseArray<T>,
549{
550 let (m, n) = matrix.shape();
551 if vector.len() != m {
552 return Err(SparseError::DimensionMismatch {
553 expected: m,
554 found: vector.len(),
555 });
556 }
557
558 let mut result = Array1::zeros(n);
559 let (row_indices, col_indices, values) = matrix.find();
560
561 for (k, (&i, &j)) in row_indices.iter().zip(col_indices.iter()).enumerate() {
562 result[j] = result[j] + values[k] * vector[i];
563 }
564
565 Ok(result)
566}
567
568#[allow(dead_code)]
570fn solve_bidiagonal_svd<T>(
571 alpha: &[T],
572 beta: &[T],
573 k: usize,
574) -> SparseResult<BidiagonalSvdResult<T>>
575where
576 T: Float
577 + SparseElement
578 + Debug
579 + Copy
580 + Add<Output = T>
581 + Sub<Output = T>
582 + Mul<Output = T>
583 + Div<Output = T>
584 + 'static
585 + std::iter::Sum,
586{
587 let n = alpha.len();
588
589 let mut singular_values = Vec::with_capacity(k);
593 let mut u_vectors = Vec::with_capacity(k);
594 let mut vt_vectors = Vec::with_capacity(k);
595
596 if n > 0 {
598 let largest_sv = alpha
599 .iter()
600 .map(|&x| x.abs())
601 .fold(T::sparse_zero(), |a, b| if a > b { a } else { b });
602 singular_values.push(largest_sv);
603
604 let mut u_vec = vec![0.0; n];
606 let mut vt_vec = vec![0.0; n];
607
608 if n > 0 {
609 u_vec[0] = 1.0;
610 vt_vec[0] = 1.0;
611 }
612
613 u_vectors.push(u_vec);
614 vt_vectors.push(vt_vec);
615 }
616
617 while singular_values.len() < k && singular_values.len() < n {
619 singular_values.push(T::sparse_zero());
620 u_vectors.push(vec![0.0_f64; n]);
621 vt_vectors.push(vec![0.0_f64; n]);
622 }
623
624 Ok((singular_values, u_vectors, vt_vectors))
625}
626
627#[allow(dead_code)]
629fn qr_decomposition_orthogonal<T>(matrix: &Array2<T>) -> SparseResult<Array2<T>>
630where
631 T: Float
632 + SparseElement
633 + Debug
634 + Copy
635 + Add<Output = T>
636 + Sub<Output = T>
637 + Mul<Output = T>
638 + Div<Output = T>
639 + 'static
640 + std::iter::Sum,
641{
642 let (m, n) = matrix.dim();
643 let mut q = matrix.clone();
644
645 for j in 0..n {
647 let mut norm = T::sparse_zero();
649 for i in 0..m {
650 norm = norm + q[[i, j]] * q[[i, j]];
651 }
652 norm = norm.sqrt();
653
654 if !SparseElement::is_zero(&norm) {
655 for i in 0..m {
656 q[[i, j]] = q[[i, j]] / norm;
657 }
658 }
659
660 for k in (j + 1)..n {
662 let mut dot = T::sparse_zero();
663 for i in 0..m {
664 dot = dot + q[[i, j]] * q[[i, k]];
665 }
666
667 for i in 0..m {
668 q[[i, k]] = q[[i, k]] - dot * q[[i, j]];
669 }
670 }
671 }
672
673 Ok(q)
674}
675
676#[allow(dead_code)]
678fn dense_svd<T>(matrix: &Array2<T>, k: usize) -> SparseResult<SVDResult<T>>
679where
680 T: Float
681 + SparseElement
682 + Debug
683 + Copy
684 + Add<Output = T>
685 + Sub<Output = T>
686 + Mul<Output = T>
687 + Div<Output = T>
688 + 'static
689 + std::iter::Sum,
690{
691 let (m, n) = matrix.dim();
692 let rank = k.min(m).min(n);
693
694 let singular_values = Array1::from_elem(rank, T::sparse_one());
696 let u = Some(
697 Array2::eye(m)
698 .slice(scirs2_core::ndarray::s![.., ..rank])
699 .to_owned(),
700 );
701 let vt = Some(
702 Array2::eye(n)
703 .slice(scirs2_core::ndarray::s![..rank, ..])
704 .to_owned(),
705 );
706
707 Ok(SVDResult {
708 u,
709 s: singular_values,
710 vt,
711 iterations: 1,
712 converged: true,
713 })
714}
715
716#[cfg(test)]
717mod tests {
718 use super::*;
719 use crate::csr_array::CsrArray;
720 use approx::assert_relative_eq;
721
722 fn create_test_matrix() -> CsrArray<f64> {
723 let rows = vec![0, 0, 1, 2, 2];
725 let cols = vec![0, 2, 1, 0, 2];
726 let data = vec![3.0, 2.0, 1.0, 4.0, 5.0];
727
728 CsrArray::from_triplets(&rows, &cols, &data, (3, 3), false).unwrap()
729 }
730
731 #[test]
732 fn test_svds_basic() {
733 let matrix = create_test_matrix();
734 let result = svds(&matrix, Some(2), None).unwrap();
735
736 assert_eq!(result.s.len(), 2);
738
739 if let Some(ref u) = result.u {
740 assert_eq!(u.shape(), [3, 2]);
741 }
742
743 if let Some(ref vt) = result.vt {
744 assert_eq!(vt.shape(), [2, 3]);
745 }
746
747 assert!(result.s[0] >= 0.0);
749 if result.s.len() > 1 {
750 assert!(result.s[0] >= result.s[1]);
751 }
752 }
753
754 #[test]
755 fn test_matrix_vector_product() {
756 let matrix = create_test_matrix();
757 let x = Array1::from_vec(vec![1.0, 2.0, 3.0]);
758
759 let y = matrix_vector_product(&matrix, &x).unwrap();
760
761 assert_eq!(y.len(), 3);
763
764 assert_relative_eq!(y[0], 3.0 * 1.0 + 2.0 * 3.0, epsilon = 1e-10); assert_relative_eq!(y[1], 1.0 * 2.0, epsilon = 1e-10); assert_relative_eq!(y[2], 4.0 * 1.0 + 5.0 * 3.0, epsilon = 1e-10); }
769
770 #[test]
771 fn test_matrix_transpose_vector_product() {
772 let matrix = create_test_matrix();
773 let x = Array1::from_vec(vec![1.0, 2.0, 3.0]);
774
775 let y = matrix_transpose_vector_product(&matrix, &x).unwrap();
776
777 assert_eq!(y.len(), 3);
779
780 assert_relative_eq!(y[0], 3.0 * 1.0 + 4.0 * 3.0, epsilon = 1e-10); assert_relative_eq!(y[1], 1.0 * 2.0, epsilon = 1e-10); assert_relative_eq!(y[2], 2.0 * 1.0 + 5.0 * 3.0, epsilon = 1e-10); }
785
786 #[test]
787 fn test_svd_options() {
788 let matrix = create_test_matrix();
789
790 let options = SVDOptions {
791 k: 1,
792 method: SVDMethod::Lanczos,
793 compute_u: false,
794 compute_vt: true,
795 ..Default::default()
796 };
797
798 let result = svds(&matrix, Some(1), Some(options)).unwrap();
799
800 assert_eq!(result.s.len(), 1);
801 assert!(result.u.is_none());
802 assert!(result.vt.is_some());
803 }
804
805 #[test]
806 fn test_svd_truncated_api() {
807 let matrix = create_test_matrix();
808
809 let result = svd_truncated(&matrix, 2, "lanczos", Some(1e-8), Some(100)).unwrap();
810
811 assert_eq!(result.s.len(), 2);
812 assert!(result.u.is_some());
813 assert!(result.vt.is_some());
814 }
815
816 #[test]
817 #[ignore] fn test_randomized_svd() {
819 let matrix = create_test_matrix();
820
821 let options = SVDOptions {
822 k: 2,
823 method: SVDMethod::Randomized,
824 n_oversamples: 5,
825 n_iter: 1,
826 ..Default::default()
827 };
828
829 let result = svds(&matrix, Some(2), Some(options)).unwrap();
830
831 assert_eq!(result.s.len(), 2);
832 assert!(result.converged);
833 }
834
835 #[test]
836 fn test_qr_decomposition() {
837 let matrix = Array2::from_shape_vec((3, 2), vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0]).unwrap();
838
839 let q = qr_decomposition_orthogonal(&matrix).unwrap();
840
841 assert_eq!(q.shape(), [3, 2]);
843
844 for j in 0..2 {
846 let mut norm = 0.0;
847 for i in 0..3 {
848 norm += q[[i, j]] * q[[i, j]];
849 }
850 assert_relative_eq!(norm, 1.0, epsilon = 1e-10);
851 }
852 }
853
854 #[test]
855 fn test_svd_method_parsing() {
856 assert_eq!(SVDMethod::from_str("lanczos").unwrap(), SVDMethod::Lanczos);
857 assert_eq!(
858 SVDMethod::from_str("randomized").unwrap(),
859 SVDMethod::Randomized
860 );
861 assert_eq!(SVDMethod::from_str("power").unwrap(), SVDMethod::Power);
862 assert!(SVDMethod::from_str("invalid").is_err());
863 }
864
865 #[test]
866 fn test_invalid_k() {
867 let matrix = create_test_matrix();
868
869 let result = svds(&matrix, Some(10), None);
871 assert!(result.is_err());
872 }
873}