1#![allow(clippy::needless_range_loop)]
35#![allow(clippy::manual_checked_ops)]
36
37use crate::error::{Result, SvdLibError};
38use crate::matrix::SparseMat;
39use crate::types::{Algorithm, Detail, Diagnostics, SvdFloat, SvdRec};
40use ndarray::{Array1, Array2};
41use num_traits::Float;
42use rand::rngs::StdRng;
43use rand::{rng, Rng, RngExt, SeedableRng};
44use rayon::prelude::*;
45use std::cell::Cell;
46use std::mem;
47
48const MAXLL: usize = 2;
49const MAX_QL_ITERATIONS: usize = 100;
50
51pub const DEFAULT_END_INTERVAL: [f64; 2] = [-1.0e-30, 1.0e-30];
53pub const DEFAULT_KAPPA: f64 = 1.0e-6;
55
56#[deprecated(
58 since = "2.0.0",
59 note = "LAS2 is numerically unreliable (18%-100% error vs LAPACK); use `single_svdlib::irlba` instead. See the module docs."
60)]
61pub fn svd<T: SvdFloat, M: SparseMat<T>>(a: &M) -> Result<SvdRec<T>> {
62 #[allow(deprecated)]
63 svd_dim_seed(a, 0, 0)
64}
65
66#[deprecated(
70 since = "2.0.0",
71 note = "LAS2 is numerically unreliable (18%-100% error vs LAPACK); use `single_svdlib::irlba` instead. See the module docs."
72)]
73pub fn svd_dim<T: SvdFloat, M: SparseMat<T>>(a: &M, dimensions: usize) -> Result<SvdRec<T>> {
74 #[allow(deprecated)]
75 svd_dim_seed(a, dimensions, 0)
76}
77
78#[deprecated(
82 since = "2.0.0",
83 note = "LAS2 is numerically unreliable (18%-100% error vs LAPACK); use `single_svdlib::irlba` instead. See the module docs."
84)]
85pub fn svd_dim_seed<T: SvdFloat, M: SparseMat<T>>(
86 a: &M,
87 dimensions: usize,
88 random_seed: u64,
89) -> Result<SvdRec<T>> {
90 #[allow(deprecated)]
91 svd_las2(
92 a,
93 dimensions,
94 0,
95 &[
96 T::from_f64_val(DEFAULT_END_INTERVAL[0]),
97 T::from_f64_val(DEFAULT_END_INTERVAL[1]),
98 ],
99 T::from_f64_val(DEFAULT_KAPPA),
100 random_seed,
101 )
102}
103
104#[deprecated(
116 since = "2.0.0",
117 note = "LAS2 is numerically unreliable (18%-100% error vs LAPACK); use `single_svdlib::irlba` instead. See the module docs."
118)]
119pub fn svd_las2<T: SvdFloat, M: SparseMat<T>>(
120 a: &M,
121 dimensions: usize,
122 iterations: usize,
123 end_interval: &[T; 2],
124 kappa: T,
125 random_seed: u64,
126) -> Result<SvdRec<T>> {
127 let random_seed = if random_seed > 0 {
128 random_seed
129 } else {
130 rng().next_u64()
131 };
132
133 let min_dim = a.rows().min(a.cols());
134 if min_dim < 2 {
135 return Err(SvdLibError::invalid(format!(
136 "svd_las2 needs both dimensions >= 2, got {}x{}",
137 a.rows(),
138 a.cols()
139 )));
140 }
141
142 let dimensions = match dimensions {
143 n if n == 0 || n > min_dim => min_dim,
144 n => n,
145 };
146 let iterations = match iterations {
147 n if n == 0 || n > min_dim => min_dim,
148 n if n < dimensions => dimensions,
149 n => n,
150 };
151 if dimensions < 2 {
152 return Err(SvdLibError::invalid(format!(
153 "svd_las2: insufficient dimensions: {dimensions}"
154 )));
155 }
156
157 let transposed = (a.cols() as f64) >= (a.rows() as f64) * 1.2;
159 let nrows = if transposed { a.cols() } else { a.rows() };
160 let ncols = if transposed { a.rows() } else { a.cols() };
161
162 let mut wrk = WorkSpace::new(nrows, ncols, transposed, iterations);
163 let mut store = Store::new(ncols);
164 let tuning = Tuning::for_matrix(a.nnz(), a.rows(), a.cols());
165
166 let mut neig = 0;
167 let steps = lanso(
168 a,
169 dimensions,
170 iterations,
171 end_interval,
172 &mut wrk,
173 &mut neig,
174 &mut store,
175 random_seed,
176 &tuning,
177 )?;
178
179 let kappa = Float::max(Float::abs(kappa), T::eps34());
180 let mut raw = ritvec(
181 a, dimensions, kappa, &mut wrk, steps, neig, &mut store, &tuning,
182 )?;
183
184 if transposed {
185 mem::swap(&mut raw.ut, &mut raw.vt);
186 }
187
188 let d = raw.d;
189 let u = Array2::from_shape_vec((d, raw.ut.cols), raw.ut.value)?
191 .t()
192 .to_owned();
193 let s = Array1::from_vec(raw.s);
194 let vt = Array2::from_shape_vec((d, raw.vt.cols), raw.vt.value)?;
195
196 let mut rec = SvdRec {
197 d,
198 u,
199 s,
200 vt,
201 total_squared_norm: T::from_f64_val(crate::matrix::total_squared_norm(a, None)),
203 diagnostics: Diagnostics {
204 algorithm: Algorithm::Las2,
205 non_zero: a.nnz(),
206 dimensions,
207 significant_values: raw.nsig,
208 transposed,
209 random_seed,
210 matvecs: wrk.matvecs.get(),
211 detail: Detail::Lanczos {
212 iterations,
213 lanczos_steps: steps + 1,
214 ritz_values_stabilized: neig,
215 end_interval: *end_interval,
216 kappa,
217 },
218 },
219 };
220 sort_descending(&mut rec);
221 Ok(rec)
222}
223
224fn sort_descending<T: SvdFloat>(rec: &mut SvdRec<T>) {
227 let d = rec.d;
228 let mut order: Vec<usize> = (0..d).collect();
229 order.sort_by(|&i, &j| {
230 rec.s[j]
231 .partial_cmp(&rec.s[i])
232 .unwrap_or(std::cmp::Ordering::Equal)
233 });
234 if order.iter().enumerate().all(|(i, &o)| i == o) {
235 return;
236 }
237 let s = Array1::from_iter(order.iter().map(|&i| rec.s[i]));
238 let u = rec.u.select(ndarray::Axis(1), &order);
239 let vt = rec.vt.select(ndarray::Axis(0), &order);
240 rec.s = s;
241 rec.u = u;
242 rec.vt = vt;
243}
244
245struct Tuning<T> {
250 eps: T,
252 ql_iterations: usize,
254 extra_steps: usize,
256 kappa_scale: T,
258}
259
260impl<T: SvdFloat> Tuning<T> {
261 fn for_matrix(nnz: usize, rows: usize, cols: usize) -> Self {
262 let denom = (rows as f64) * (cols as f64);
263 let sparsity = if denom > 0.0 {
264 1.0 - (nnz as f64 / denom)
265 } else {
266 0.0
267 };
268 let eps = T::eps();
269 let (eps_scale, ql_iterations, extra_steps, kappa_scale) = if sparsity > 0.999 {
270 (100.0, 500, 5, 10.0)
271 } else if sparsity > 0.99 {
272 (100.0, 300, 5, 10.0)
273 } else if sparsity > 0.9 {
274 (10.0, 200, 0, 1.0)
275 } else {
276 (1.0, MAX_QL_ITERATIONS, 0, 1.0)
277 };
278 Self {
279 eps: eps * T::from_f64_val(eps_scale),
280 ql_iterations,
281 extra_steps,
282 kappa_scale: T::from_f64_val(kappa_scale),
283 }
284 }
285}
286
287struct Store<T> {
292 n: usize,
293 vecs: Vec<Vec<T>>,
294}
295
296impl<T: SvdFloat> Store<T> {
297 fn new(n: usize) -> Self {
298 Self { n, vecs: vec![] }
299 }
300 fn storq(&mut self, idx: usize, v: &[T]) {
301 while idx + MAXLL >= self.vecs.len() {
302 self.vecs.push(vec![T::zero(); self.n]);
303 }
304 self.vecs[idx + MAXLL].copy_from_slice(v);
305 }
306 fn storp(&mut self, idx: usize, v: &[T]) {
307 while idx >= self.vecs.len() {
308 self.vecs.push(vec![T::zero(); self.n]);
309 }
310 self.vecs[idx].copy_from_slice(v);
311 }
312 fn retrq(&self, idx: usize) -> &[T] {
313 &self.vecs[idx + MAXLL]
314 }
315 fn retrp(&self, idx: usize) -> &[T] {
316 &self.vecs[idx]
317 }
318}
319
320struct WorkSpace<T> {
321 nrows: usize,
322 ncols: usize,
323 transposed: bool,
324 w0: Vec<T>,
325 w1: Vec<T>,
326 w2: Vec<T>,
327 w3: Vec<T>,
328 w4: Vec<T>,
329 w5: Vec<T>,
330 alf: Vec<T>,
332 eta: Vec<T>,
334 oldeta: Vec<T>,
336 bet: Vec<T>,
338 bnd: Vec<T>,
340 ritz: Vec<T>,
342 temp: Vec<T>,
343 matvecs: Cell<usize>,
346 ql_degraded: Cell<bool>,
348}
349
350impl<T: SvdFloat> WorkSpace<T> {
351 fn new(nrows: usize, ncols: usize, transposed: bool, iterations: usize) -> Self {
352 Self {
353 nrows,
354 ncols,
355 transposed,
356 w0: vec![T::zero(); ncols],
357 w1: vec![T::zero(); ncols],
358 w2: vec![T::zero(); ncols],
359 w3: vec![T::zero(); ncols],
360 w4: vec![T::zero(); ncols],
361 w5: vec![T::zero(); ncols],
362 alf: vec![T::zero(); iterations],
363 eta: vec![T::zero(); iterations],
364 oldeta: vec![T::zero(); iterations],
365 bet: vec![T::zero(); 1 + iterations],
366 ritz: vec![T::zero(); 1 + iterations],
367 bnd: vec![<T as num_traits::Bounded>::max_value(); 1 + iterations],
368 temp: vec![T::zero(); nrows],
369 matvecs: Cell::new(0),
370 ql_degraded: Cell::new(false),
371 }
372 }
373}
374
375struct DMat<T> {
377 cols: usize,
378 value: Vec<T>,
379}
380
381struct RawRec<T> {
382 d: usize,
383 nsig: usize,
384 ut: DMat<T>,
385 s: Vec<T>,
386 vt: DMat<T>,
387}
388
389#[inline]
390fn close<T: SvdFloat>(a: T, b: T) -> bool {
391 T::close(a, b)
392}
393
394fn sort_pair<T: SvdFloat>(n: usize, keys: &mut [T], vals: &mut [T]) {
399 let mut order: Vec<usize> = (0..n).collect();
400 order.sort_by(|&i, &j| {
401 keys[i]
402 .partial_cmp(&keys[j])
403 .unwrap_or(std::cmp::Ordering::Equal)
404 });
405 let sk: Vec<T> = order.iter().map(|&i| keys[i]).collect();
406 let sv: Vec<T> = order.iter().map(|&i| vals[i]).collect();
407 keys[..n].copy_from_slice(&sk);
408 vals[..n].copy_from_slice(&sv);
409}
410
411fn svd_opb<T: SvdFloat, M: SparseMat<T>>(
413 a: &M,
414 x: &[T],
415 y: &mut [T],
416 temp: &mut [T],
417 transposed: bool,
418 matvecs: &Cell<usize>,
419) {
420 a.mul_vec(x, temp, transposed);
421 a.mul_vec(temp, y, !transposed);
422 matvecs.set(matvecs.get() + 2);
423}
424
425fn daxpy<T: SvdFloat>(da: T, x: &[T], y: &mut [T]) {
426 if x.len() < 1024 {
427 for (yv, &xv) in y.iter_mut().zip(x.iter()) {
428 *yv += da * xv;
429 }
430 } else {
431 y.par_iter_mut()
432 .zip(x.par_iter())
433 .for_each(|(yv, &xv)| *yv += da * xv);
434 }
435}
436
437fn ddot<T: SvdFloat>(x: &[T], y: &[T]) -> T {
438 if x.len() < 1024 {
439 x.iter().zip(y).map(|(&a, &b)| a * b).sum()
440 } else {
441 x.par_iter().zip(y.par_iter()).map(|(&a, &b)| a * b).sum()
442 }
443}
444
445fn norm<T: SvdFloat>(x: &[T]) -> T {
446 ddot(x, x).sqrt()
447}
448
449fn datx<T: SvdFloat>(d: T, x: &[T], y: &mut [T]) {
450 for (yv, &xv) in y.iter_mut().zip(x.iter()) {
451 *yv = d * xv;
452 }
453}
454
455fn dscal<T: SvdFloat>(d: T, x: &mut [T]) {
456 if x.len() < 1024 {
457 for v in x.iter_mut() {
458 *v *= d;
459 }
460 } else {
461 x.par_iter_mut().for_each(|v| *v *= d);
462 }
463}
464
465fn dcopy_rev<T: SvdFloat>(n: usize, offset: usize, x: &[T], y: &mut [T]) {
467 if n > 0 {
468 let start = n - 1;
469 for i in 0..n {
470 y[offset + start - i] = x[offset + i];
471 }
472 }
473}
474
475fn idamax<T: SvdFloat>(n: usize, x: &[T]) -> usize {
477 debug_assert!(n > 0);
478 let mut imax = 0;
479 for i in 1..n {
480 if Float::abs(x[i]) > Float::abs(x[imax]) {
481 imax = i;
482 }
483 }
484 imax
485}
486
487fn fsign<T: SvdFloat>(a: T, b: T) -> T {
489 if (a >= T::zero()) == (b >= T::zero()) {
490 a
491 } else {
492 -a
493 }
494}
495
496fn pythag<T: SvdFloat>(a: T, b: T) -> T {
498 let n = Float::max(Float::abs(a), Float::abs(b));
499 if n <= T::zero() {
500 return T::zero();
501 }
502 let four = T::from_f64_val(4.0);
503 let two = T::from_f64_val(2.0);
504 let mut p = n;
505 let mut r = Float::powi(Float::min(Float::abs(a), Float::abs(b)) / p, 2);
506 let mut t = four + r;
507 let mut guard = 0usize;
511 while !close(t, four) && guard < 64 {
512 guard += 1;
513 let s = r / t;
514 let u = T::one() + two * s;
515 p *= u;
516 r = Float::powi(s / u, 2);
517 t = four + r;
518 }
519 p
520}
521
522fn imtqlb<T: SvdFloat>(
528 n: usize,
529 d: &mut [T],
530 e: &mut [T],
531 bnd: &mut [T],
532 max_iter: usize,
533 degraded: &Cell<bool>,
534) {
535 if n == 1 {
536 return;
537 }
538 let size_factor = T::from_f64_val((n as f64).sqrt());
539 bnd[0] = T::one();
540 let last = n - 1;
541 for i in 1..=last {
542 bnd[i] = T::zero();
543 e[i - 1] = e[i];
544 }
545 e[last] = T::zero();
546
547 let mut i = 0;
548 for l in 0..=last {
549 let mut iteration = 0;
550
551 while iteration <= max_iter {
552 let mut m = l;
553 while m < n {
554 if m == last {
555 break;
556 }
557 let test = Float::abs(d[m]) + Float::abs(d[m + 1]);
558 let tol =
559 T::eps() * T::from_f64_val(100.0) * Float::max(test, T::one()) * size_factor;
560 if Float::abs(e[m]) <= tol {
561 break;
562 }
563 m += 1;
564 }
565
566 let mut p = d[l];
573 let mut f = bnd[l];
574
575 if m == l {
576 let mut exchange = true;
578 if l > 0 {
579 i = l;
580 while i >= 1 && exchange {
581 if p < d[i - 1] {
582 d[i] = d[i - 1];
583 bnd[i] = bnd[i - 1];
584 i -= 1;
585 } else {
586 exchange = false;
587 }
588 }
589 }
590 if exchange {
591 i = 0;
592 }
593 d[i] = p;
594 bnd[i] = f;
595 break;
596 }
597
598 if iteration == max_iter {
599 degraded.set(true);
600 for b in bnd.iter_mut().take(m + 1).skip(l) {
601 *b = Float::max(*b, T::from_f64_val(0.1));
602 }
603 e[l] = T::zero();
604 break;
605 }
606 iteration += 1;
607
608 let two = T::from_f64_val(2.0);
609 let mut g = (d[l + 1] - p) / (two * e[l]);
610 let mut r = pythag(g, T::one());
611 g = d[m] - p + e[l] / (g + fsign(r, g));
612 let mut s = T::one();
613 let mut c = T::one();
614 p = T::zero();
615
616 debug_assert!(m > 0);
617 i = m - 1;
618 let mut underflow = false;
619 while !underflow && i >= l {
620 f = s * e[i];
621 let b = c * e[i];
622 r = pythag(f, g);
623 e[i + 1] = r;
624
625 if r < T::eps() * T::from_f64_val(1000.0) * (Float::abs(f) + Float::abs(g)) {
626 underflow = true;
627 break;
628 }
629 if Float::abs(r) < T::eps() * T::from_f64_val(100.0) {
630 r = T::eps() * T::from_f64_val(100.0) * fsign(T::one(), r);
631 }
632
633 s = f / r;
634 c = g / r;
635 g = d[i + 1] - p;
636 r = (d[i] - g) * s + two * c * b;
637 p = s * r;
638 d[i + 1] = g + p;
639 g = c * r - b;
640 f = bnd[i + 1];
641 bnd[i + 1] = s * bnd[i] + c * f;
642 bnd[i] = c * bnd[i] - s * f;
643 if i == 0 {
644 break;
645 }
646 i -= 1;
647 }
648 if underflow {
649 d[i + 1] -= p;
650 } else {
651 d[l] -= p;
652 e[l] = g;
653 }
654 e[m] = T::zero();
655 }
656 }
657}
658
659fn imtql2<T: SvdFloat>(
661 nm: usize,
662 n: usize,
663 d: &mut [T],
664 e: &mut [T],
665 z: &mut [T],
666 max_iter: usize,
667) -> Result<()> {
668 if n == 1 {
669 return Ok(());
670 }
671 let two = T::from_f64_val(2.0);
672 let last = n - 1;
673 for i in 1..n {
674 e[i - 1] = e[i];
675 }
676 e[last] = T::zero();
677
678 let nnm = n * nm;
679 for l in 0..n {
680 let mut iteration = 0;
681 while iteration <= max_iter {
682 let mut m = l;
683 while m < n {
684 if m == last {
685 break;
686 }
687 let test = Float::abs(d[m]) + Float::abs(d[m + 1]);
688 if close(test, test + Float::abs(e[m])) {
689 break;
690 }
691 m += 1;
692 }
693 if m == l {
694 break;
695 }
696 if iteration == max_iter {
697 return Err(SvdLibError::NoConvergence {
698 stage: "imtql2",
699 iterations: max_iter,
700 });
701 }
702 iteration += 1;
703
704 let mut g = (d[l + 1] - d[l]) / (two * e[l]);
705 let mut r = pythag(g, T::one());
706 g = d[m] - d[l] + e[l] / (g + fsign(r, g));
707 let mut s = T::one();
708 let mut c = T::one();
709 let mut p = T::zero();
710
711 debug_assert!(m > 0);
712 let mut i = m - 1;
713 let mut underflow = false;
714 while !underflow && i >= l {
715 let mut f = s * e[i];
716 let b = c * e[i];
717 r = pythag(f, g);
718 e[i + 1] = r;
719 if close(r, T::zero()) {
720 underflow = true;
721 } else {
722 s = f / r;
723 c = g / r;
724 g = d[i + 1] - p;
725 r = (d[i] - g) * s + two * c * b;
726 p = s * r;
727 d[i + 1] = g + p;
728 g = c * r - b;
729 for k in (0..nnm).step_by(n) {
730 let index = k + i;
731 f = z[index + 1];
732 z[index + 1] = s * z[index] + c * f;
733 z[index] = c * z[index] - s * f;
734 }
735 if i == 0 {
736 break;
737 }
738 i -= 1;
739 }
740 }
741 if underflow {
742 d[i + 1] -= p;
743 } else {
744 d[l] -= p;
745 e[l] = g;
746 }
747 e[m] = T::zero();
748 }
749 }
750
751 for l in 1..n {
753 let i = l - 1;
754 let mut k = i;
755 let mut p = d[i];
756 for (j, item) in d.iter().enumerate().take(n).skip(l) {
757 if *item < p {
758 k = j;
759 p = *item;
760 }
761 }
762 if k != i {
763 d[k] = d[i];
764 d[i] = p;
765 for j in (0..nnm).step_by(n) {
766 z.swap(j + i, j + k);
767 }
768 }
769 }
770 Ok(())
771}
772
773fn startv<T: SvdFloat, M: SparseMat<T>>(
775 a: &M,
776 wrk: &mut WorkSpace<T>,
777 step: usize,
778 store: &Store<T>,
779 random_seed: u64,
780) -> Result<T> {
781 let mut rnm2 = ddot(&wrk.w0, &wrk.w0);
782 for id in 0..3 {
783 if id > 0 || step > 0 || close(rnm2, T::zero()) {
784 let mut bytes = [0u8; 32];
785 for (i, b) in random_seed.to_le_bytes().iter().enumerate() {
786 bytes[i] = *b;
787 }
788 let mut seeded = StdRng::from_seed(bytes);
789 for val in wrk.w0.iter_mut() {
790 *val = T::from_f64_val(seeded.random_range(-1.0..1.0));
791 }
792 }
793 wrk.w3.copy_from_slice(&wrk.w0);
794 svd_opb(
795 a,
796 &wrk.w3,
797 &mut wrk.w0,
798 &mut wrk.temp,
799 wrk.transposed,
800 &wrk.matvecs,
801 );
802 wrk.w3.copy_from_slice(&wrk.w0);
803 rnm2 = ddot(&wrk.w3, &wrk.w3);
804 if rnm2 > T::zero() {
805 break;
806 }
807 }
808
809 if rnm2 <= T::zero() {
810 return Err(SvdLibError::failed(
811 "startv",
812 format!("could not find a starting vector in range (rnm2 = {rnm2:?})"),
813 ));
814 }
815
816 if step > 0 {
817 for i in 0..step {
818 let v = store.retrq(i);
819 daxpy(-ddot(&wrk.w3, v), v, &mut wrk.w0);
820 }
821 let t = -ddot(&wrk.w4, &wrk.w0);
823 let w2 = std::mem::take(&mut wrk.w2);
824 daxpy(t, &w2, &mut wrk.w0);
825 wrk.w2 = w2;
826 wrk.w3.copy_from_slice(&wrk.w0);
827 rnm2 = match ddot(&wrk.w3, &wrk.w3) {
828 dot if dot <= T::eps() * rnm2 => T::zero(),
829 dot => dot,
830 };
831 }
832 Ok(rnm2.sqrt())
833}
834
835fn stpone<T: SvdFloat, M: SparseMat<T>>(
837 a: &M,
838 wrk: &mut WorkSpace<T>,
839 store: &Store<T>,
840 random_seed: u64,
841) -> Result<(T, T)> {
842 let mut rnm = startv(a, wrk, 0, store, random_seed)?;
843 if close(rnm, T::zero()) {
844 return Err(SvdLibError::failed(
845 "stpone",
846 "starting vector has zero norm",
847 ));
848 }
849
850 datx(Float::recip(rnm), &wrk.w0, &mut wrk.w1);
851 dscal(Float::recip(rnm), &mut wrk.w3);
852
853 svd_opb(
854 a,
855 &wrk.w3,
856 &mut wrk.w0,
857 &mut wrk.temp,
858 wrk.transposed,
859 &wrk.matvecs,
860 );
861 wrk.alf[0] = ddot(&wrk.w0, &wrk.w3);
862 let alf0 = wrk.alf[0];
863 let w1 = std::mem::take(&mut wrk.w1);
864 daxpy(-alf0, &w1, &mut wrk.w0);
865 let t = ddot(&wrk.w0, &wrk.w3);
866 wrk.alf[0] += t;
867 daxpy(-t, &w1, &mut wrk.w0);
868 wrk.w1 = w1;
869 wrk.w4.copy_from_slice(&wrk.w0);
870 rnm = norm(&wrk.w4);
871 let anorm = rnm + Float::abs(wrk.alf[0]);
872 Ok((rnm, T::eps().sqrt() * anorm))
873}
874
875#[allow(clippy::too_many_arguments)]
876fn lanczos_step<T: SvdFloat, M: SparseMat<T>>(
877 a: &M,
878 wrk: &mut WorkSpace<T>,
879 first: usize,
880 last: usize,
881 ll: &mut usize,
882 enough: &mut bool,
883 rnm: &mut T,
884 tol: &mut T,
885 store: &mut Store<T>,
886) -> Result<usize> {
887 let eps1 = T::eps() * T::from_f64_val(wrk.ncols as f64).sqrt();
888 let mut j = first;
889 let four = T::from_f64_val(4.0);
890
891 while j < last {
892 mem::swap(&mut wrk.w1, &mut wrk.w2);
893 mem::swap(&mut wrk.w3, &mut wrk.w4);
894
895 store.storq(j - 1, &wrk.w2);
896 if j - 1 < MAXLL {
897 store.storp(j - 1, &wrk.w4);
898 }
899 wrk.bet[j] = *rnm;
900
901 if close(*rnm, T::zero()) {
903 *rnm = startv(a, wrk, j, store, 0)?;
904 if close(*rnm, T::zero()) {
905 *enough = true;
906 }
907 }
908 if *enough {
909 mem::swap(&mut wrk.w1, &mut wrk.w2);
910 break;
911 }
912
913 datx(Float::recip(*rnm), &wrk.w0, &mut wrk.w1);
914 dscal(Float::recip(*rnm), &mut wrk.w3);
915 svd_opb(
916 a,
917 &wrk.w3,
918 &mut wrk.w0,
919 &mut wrk.temp,
920 wrk.transposed,
921 &wrk.matvecs,
922 );
923 let rnm_v = *rnm;
924 let w2 = std::mem::take(&mut wrk.w2);
925 daxpy(-rnm_v, &w2, &mut wrk.w0);
926 wrk.w2 = w2;
927 wrk.alf[j] = ddot(&wrk.w0, &wrk.w3);
928 let alfj = wrk.alf[j];
929 let w1 = std::mem::take(&mut wrk.w1);
930 daxpy(-alfj, &w1, &mut wrk.w0);
931 wrk.w1 = w1;
932
933 if j <= MAXLL && Float::abs(wrk.alf[j - 1]) > four * Float::abs(wrk.alf[j]) {
935 *ll = j;
936 }
937 for i in 0..(j - 1).min(*ll) {
938 let t = ddot(store.retrp(i), &wrk.w0);
939 daxpy(-t, store.retrq(i), &mut wrk.w0);
940 wrk.eta[i] = eps1;
941 wrk.oldeta[i] = eps1;
942 }
943
944 let t = ddot(&wrk.w0, &wrk.w4);
946 let w2 = std::mem::take(&mut wrk.w2);
947 daxpy(-t, &w2, &mut wrk.w0);
948 wrk.w2 = w2;
949 if wrk.bet[j] > T::zero() {
950 wrk.bet[j] += t;
951 }
952 let t = ddot(&wrk.w0, &wrk.w3);
953 let w1 = std::mem::take(&mut wrk.w1);
954 daxpy(-t, &w1, &mut wrk.w0);
955 wrk.w1 = w1;
956 wrk.alf[j] += t;
957 wrk.w4.copy_from_slice(&wrk.w0);
958 *rnm = norm(&wrk.w4);
959 let anorm = wrk.bet[j] + Float::abs(wrk.alf[j]) + *rnm;
960 *tol = T::eps().sqrt() * anorm;
961
962 ortbnd(wrk, j, *rnm, eps1);
963 purge(wrk.ncols, *ll, wrk, j, rnm, *tol, store);
964 if *rnm <= *tol {
965 *rnm = T::zero();
966 }
967 j += 1;
968 }
969 Ok(j)
970}
971
972fn purge<T: SvdFloat>(
974 n: usize,
975 ll: usize,
976 wrk: &mut WorkSpace<T>,
977 step: usize,
978 rnm: &mut T,
979 tol: T,
980 store: &Store<T>,
981) {
982 if step < ll + 2 {
983 return;
984 }
985 let reps = T::eps().sqrt();
986 let eps1 = T::eps() * T::from_f64_val(n as f64).sqrt();
987
988 let k = idamax(step - (ll + 1), &wrk.eta) + ll;
989 if Float::abs(wrk.eta[k]) > reps {
990 let reps1 = eps1 / reps;
991 let mut iteration = 0;
992 let mut flag = true;
993 while iteration < 2 && flag {
994 if *rnm > tol {
995 let mut tq = T::zero();
996 let mut tr = T::zero();
997 for i in ll..step {
998 let v = store.retrq(i);
999 let t = ddot(v, &wrk.w3);
1000 tq += Float::abs(t);
1001 daxpy(-t, v, &mut wrk.w1);
1002 let t = ddot(v, &wrk.w4);
1003 tr += Float::abs(t);
1004 daxpy(-t, v, &mut wrk.w0);
1005 }
1006 wrk.w3.copy_from_slice(&wrk.w1);
1007 let t = ddot(&wrk.w0, &wrk.w3);
1008 tr += Float::abs(t);
1009 let w1 = std::mem::take(&mut wrk.w1);
1010 daxpy(-t, &w1, &mut wrk.w0);
1011 wrk.w1 = w1;
1012 wrk.w4.copy_from_slice(&wrk.w0);
1013 *rnm = norm(&wrk.w4);
1014 if tq <= reps1 && tr <= *rnm * reps1 {
1015 flag = false;
1016 }
1017 }
1018 iteration += 1;
1019 }
1020 for i in ll..=step {
1021 wrk.eta[i] = eps1;
1022 wrk.oldeta[i] = eps1;
1023 }
1024 }
1025}
1026
1027fn ortbnd<T: SvdFloat>(wrk: &mut WorkSpace<T>, step: usize, rnm: T, eps1: T) {
1029 if step < 1 {
1030 return;
1031 }
1032 if !close(rnm, T::zero()) && step > 1 {
1033 wrk.oldeta[0] = (wrk.bet[1] * wrk.eta[1] + (wrk.alf[0] - wrk.alf[step]) * wrk.eta[0]
1034 - wrk.bet[step] * wrk.oldeta[0])
1035 / rnm
1036 + eps1;
1037 if step > 2 {
1038 for i in 1..=step - 2 {
1039 wrk.oldeta[i] = (wrk.bet[i + 1] * wrk.eta[i + 1]
1040 + (wrk.alf[i] - wrk.alf[step]) * wrk.eta[i]
1041 + wrk.bet[i] * wrk.eta[i - 1]
1042 - wrk.bet[step] * wrk.oldeta[i])
1043 / rnm
1044 + eps1;
1045 }
1046 }
1047 }
1048 wrk.oldeta[step - 1] = eps1;
1049 mem::swap(&mut wrk.oldeta, &mut wrk.eta);
1050 wrk.eta[step] = eps1;
1051}
1052
1053fn error_bound<T: SvdFloat>(
1055 enough: &mut bool,
1056 endl: T,
1057 endr: T,
1058 ritz: &mut [T],
1059 bnd: &mut [T],
1060 step: usize,
1061 tol: T,
1062) -> usize {
1063 debug_assert!(step > 0);
1064 let mid = idamax(step + 1, bnd);
1065 let sixteen = T::from_f64_val(16.0);
1066
1067 let mut i = ((step + 1) + (step - 1)) / 2;
1069 while i > mid + 1 {
1070 if Float::abs(ritz[i - 1] - ritz[i]) < T::eps34() * Float::abs(ritz[i])
1071 && bnd[i] > tol
1072 && bnd[i - 1] > tol
1073 {
1074 bnd[i - 1] = (Float::powi(bnd[i], 2) + Float::powi(bnd[i - 1], 2)).sqrt();
1075 bnd[i] = T::zero();
1076 }
1077 i -= 1;
1078 }
1079 let mut i = ((step + 1) - (step - 1)) / 2;
1080 while i + 1 < mid {
1081 if Float::abs(ritz[i + 1] - ritz[i]) < T::eps34() * Float::abs(ritz[i])
1082 && bnd[i] > tol
1083 && bnd[i + 1] > tol
1084 {
1085 bnd[i + 1] = (Float::powi(bnd[i], 2) + Float::powi(bnd[i + 1], 2)).sqrt();
1086 bnd[i] = T::zero();
1087 }
1088 i += 1;
1089 }
1090
1091 let mut neig = 0;
1092 let mut gapl = ritz[step] - ritz[0];
1093 for i in 0..=step {
1094 let mut gap = gapl;
1095 if i < step {
1096 gapl = ritz[i + 1] - ritz[i];
1097 }
1098 gap = Float::min(gap, gapl);
1099 if gap > bnd[i] {
1100 bnd[i] *= bnd[i] / gap;
1101 }
1102 if bnd[i] <= sixteen * T::eps() * Float::abs(ritz[i]) {
1103 neig += 1;
1104 if !*enough {
1105 *enough = endl < ritz[i] && ritz[i] < endr;
1106 }
1107 }
1108 }
1109 neig
1110}
1111
1112#[allow(clippy::too_many_arguments)]
1114fn ritvec<T: SvdFloat, M: SparseMat<T>>(
1115 a: &M,
1116 dimensions: usize,
1117 kappa: T,
1118 wrk: &mut WorkSpace<T>,
1119 steps: usize,
1120 neig: usize,
1121 store: &mut Store<T>,
1122 tuning: &Tuning<T>,
1123) -> Result<RawRec<T>> {
1124 let js = steps + 1;
1125 let jsq = js * js;
1126 let adaptive_eps = tuning.eps;
1127
1128 let mut s = vec![T::zero(); jsq];
1129 for i in (0..jsq).step_by(js + 1) {
1130 s[i] = T::one();
1131 }
1132
1133 let mut eigenvalues = vec![T::zero(); wrk.ncols.max(js)];
1134 dcopy_rev(js, 0, &wrk.alf, &mut eigenvalues);
1135 dcopy_rev(steps, 1, &wrk.bet, &mut wrk.w5);
1136
1137 imtql2(
1139 js,
1140 js,
1141 &mut eigenvalues,
1142 &mut wrk.w5,
1143 &mut s,
1144 tuning.ql_iterations,
1145 )?;
1146
1147 let max_eigenvalue = eigenvalues
1148 .iter()
1149 .take(js)
1150 .fold(T::zero(), |mx, &v| Float::max(mx, Float::abs(v)));
1151 let adaptive_kappa = kappa * tuning.kappa_scale;
1152
1153 let store_vectors: Vec<&[T]> = (0..js).map(|i| store.retrq(i)).collect();
1154
1155 let significant: Vec<usize> = (0..js)
1156 .filter(|&k| {
1157 let bound =
1158 adaptive_kappa * Float::max(Float::abs(wrk.ritz[k]), max_eigenvalue * adaptive_eps);
1159 wrk.bnd[k] <= bound && k + 1 > js - neig
1160 })
1161 .collect();
1162 let nsig = significant.len();
1163
1164 let d = dimensions.min(nsig);
1165 if d == 0 {
1166 return Err(SvdLibError::failed(
1167 "ritvec",
1168 "no singular values met the significance threshold; \
1169 try more iterations or a larger kappa",
1170 ));
1171 }
1172
1173 let keep: Vec<usize> = significant[nsig - d..].to_vec();
1179
1180 let mut vt_vectors: Vec<(usize, Vec<T>)> = keep
1181 .into_par_iter()
1182 .map(|k| {
1183 let mut vec = vec![T::zero(); wrk.ncols];
1184 for (i, sv) in store_vectors.iter().enumerate().take(js) {
1185 let coeff = s[k * js + i];
1186 if Float::abs(coeff) > adaptive_eps {
1187 for (dst, &src) in vec.iter_mut().zip(sv.iter()).take(wrk.ncols) {
1188 *dst += coeff * src;
1189 }
1190 }
1191 }
1192 (k, vec)
1193 })
1194 .collect();
1195 vt_vectors.sort_by_key(|(k, _)| *k);
1196
1197 let mut vt = DMat {
1198 cols: wrk.ncols,
1199 value: vec![T::zero(); wrk.ncols * d],
1200 };
1201 for (i, (_, vec)) in vt_vectors.into_iter().enumerate() {
1202 let off = i * vt.cols;
1203 vt.value[off..off + vt.cols].copy_from_slice(&vec);
1204 }
1205
1206 let mut ut = DMat {
1207 cols: wrk.nrows,
1208 value: vec![T::zero(); wrk.nrows * d],
1209 };
1210 let mut sv = vec![T::zero(); d];
1211
1212 for i in 0..d {
1215 let off = i * vt.cols;
1216 let v = &vt.value[off..off + vt.cols];
1217 let mut abv = vec![T::zero(); vt.cols];
1218 let mut av = vec![T::zero(); wrk.nrows];
1219
1220 svd_opb(a, v, &mut abv, &mut wrk.temp, wrk.transposed, &wrk.matvecs);
1221 a.mul_vec(v, &mut av, wrk.transposed);
1222 wrk.matvecs.set(wrk.matvecs.get() + 1);
1223
1224 let t = ddot(v, &abv);
1225 let sval = Float::max(t, T::zero()).sqrt();
1226 sv[i] = sval;
1227
1228 let scale = T::one() / Float::max(sval, adaptive_eps);
1229 dscal(scale, &mut av);
1230 let uoff = i * ut.cols;
1231 ut.value[uoff..uoff + ut.cols].copy_from_slice(&av);
1232 }
1233
1234 Ok(RawRec {
1235 d,
1236 nsig,
1237 ut,
1238 s: sv,
1239 vt,
1240 })
1241}
1242
1243#[allow(clippy::too_many_arguments)]
1245fn lanso<T: SvdFloat, M: SparseMat<T>>(
1246 a: &M,
1247 dim: usize,
1248 iterations: usize,
1249 end_interval: &[T; 2],
1250 wrk: &mut WorkSpace<T>,
1251 neig: &mut usize,
1252 store: &mut Store<T>,
1253 random_seed: u64,
1254 tuning: &Tuning<T>,
1255) -> Result<usize> {
1256 let adaptive_eps = tuning.eps;
1257 let (endl, endr) = (end_interval[0], end_interval[1]);
1258
1259 let (mut rnm, mut tol) = stpone(a, wrk, store, random_seed)?;
1260
1261 let eps1 = adaptive_eps * T::from_f64_val(wrk.ncols as f64).sqrt();
1262 wrk.eta[0] = eps1;
1263 wrk.oldeta[0] = eps1;
1264 let mut ll = 0;
1265 let mut first = 1;
1266 let mut last = iterations.min(dim.max(8) + dim);
1267 let mut enough = false;
1268 let mut j = 0;
1269 let mut intro = 0;
1270
1271 while !enough {
1272 if rnm <= tol {
1273 rnm = T::zero();
1274 }
1275
1276 let steps = lanczos_step(
1277 a,
1278 wrk,
1279 first,
1280 last,
1281 &mut ll,
1282 &mut enough,
1283 &mut rnm,
1284 &mut tol,
1285 store,
1286 )?;
1287 j = if enough { steps - 1 } else { last - 1 };
1288
1289 first = j + 1;
1290 wrk.bet[first] = rnm;
1291
1292 let mut l = 0;
1294 for _ in 0..j {
1295 if l > j {
1296 break;
1297 }
1298 let mut i = l;
1299 while i <= j {
1300 if Float::abs(wrk.bet[i + 1]) <= adaptive_eps {
1301 break;
1302 }
1303 i += 1;
1304 }
1305 i = i.min(j);
1306
1307 let sz = i - l;
1308 dcopy_rev(sz + 1, l, &wrk.alf, &mut wrk.ritz);
1309 dcopy_rev(sz, l + 1, &wrk.bet, &mut wrk.w5);
1310
1311 imtqlb(
1312 sz + 1,
1313 &mut wrk.ritz[l..],
1314 &mut wrk.w5[l..],
1315 &mut wrk.bnd[l..],
1316 tuning.ql_iterations,
1317 &wrk.ql_degraded,
1318 );
1319
1320 for m in l..=i {
1321 wrk.bnd[m] = rnm * Float::abs(wrk.bnd[m]);
1322 }
1323 l = i + 1;
1324 }
1325
1326 sort_pair(j + 1, &mut wrk.ritz, &mut wrk.bnd);
1327 *neig = error_bound(&mut enough, endl, endr, &mut wrk.ritz, &mut wrk.bnd, j, tol);
1328
1329 if *neig < dim {
1330 if *neig == 0 {
1331 last = first + 9;
1332 intro = first;
1333 } else {
1334 last =
1335 first + 3.max(1 + ((j - intro) * (dim - *neig)) / *neig) + tuning.extra_steps;
1336 }
1337 last = last.min(iterations);
1338 } else {
1339 enough = true;
1340 }
1341 enough = enough || first >= iterations;
1342 }
1343 store.storq(j, &wrk.w1);
1344 Ok(j)
1345}
1346
1347#[cfg(test)]
1348#[allow(deprecated)]
1349mod tests {
1350 use super::*;
1351 use crate::matrix::SvdMat;
1352 use crate::testing::{dense_of, gen_lowrank, gen_sparse, reference_singular_values};
1353 use sprs::TriMatI;
1354
1355 fn diagonal(n: usize) -> SvdMat<f64> {
1358 let mut t = TriMatI::<f64, u32>::new((n, n));
1359 for i in 0..n {
1360 t.add_triplet(i, i, (n - i) as f64);
1361 }
1362 t.to_csr::<u64>()
1363 }
1364
1365 #[test]
1370 fn singular_values_descend() {
1371 let a = gen_sparse(200, 120, 0.05, 3);
1372 let svd = svd_dim_seed(&a, 20, 42).unwrap();
1373 for w in svd.s.to_vec().windows(2) {
1374 assert!(w[0] >= w[1], "not descending: {:?}", svd.s);
1375 }
1376 }
1377
1378 #[test]
1382 fn orientation_is_consistent_for_wide_and_tall() {
1383 for (r, c) in [(200usize, 60usize), (60, 200)] {
1384 let a = gen_sparse(r, c, 0.1, 11);
1385 let svd = svd_dim_seed(&a, 10, 42).unwrap();
1386 assert_eq!(svd.u.nrows(), r, "u rows for {r}x{c}");
1387 assert_eq!(svd.u.ncols(), svd.d, "u cols for {r}x{c}");
1388 assert_eq!(svd.vt.nrows(), svd.d, "vt rows for {r}x{c}");
1389 assert_eq!(svd.vt.ncols(), c, "vt cols for {r}x{c}");
1390 }
1391 }
1392
1393 #[test]
1394 fn csc_input_matches_csr() {
1395 let a = gen_sparse(150, 90, 0.08, 5);
1396 let csc = a.to_other_storage();
1397 let from_csr = svd_dim_seed(&a, 12, 42).unwrap();
1398 let from_csc = svd_dim_seed(&csc, 12, 42).unwrap();
1399 for (x, y) in from_csr.s.iter().zip(from_csc.s.iter()) {
1400 approx::assert_relative_eq!(x, y, max_relative = 1e-10);
1401 }
1402 }
1403
1404 #[test]
1405 fn rejects_degenerate_shapes() {
1406 let a = gen_sparse(1, 10, 1.0, 1);
1407 assert!(matches!(
1408 svd_dim_seed(&a, 0, 42),
1409 Err(SvdLibError::InvalidArgument(_))
1410 ));
1411 }
1412
1413 #[test]
1414 fn diagnostics_count_matvecs() {
1415 let a = gen_sparse(100, 60, 0.1, 13);
1416 let svd = svd_dim_seed(&a, 8, 42).unwrap();
1417 assert!(svd.diagnostics.matvecs > 0);
1418 assert_eq!(svd.diagnostics.algorithm, Algorithm::Las2);
1419 }
1420
1421 #[test]
1428 fn imtqlb_agrees_with_imtql2_on_the_same_tridiagonal() {
1429 let n = 24;
1431 let d0: Vec<f64> = (0..n).map(|i| 2.0 + i as f64).collect();
1432 let e0: Vec<f64> = (0..n).map(|i| 0.5 + 0.1 * (i as f64)).collect();
1433
1434 let mut d_b = d0.clone();
1435 let mut e_b = e0.clone();
1436 let mut bnd = vec![0.0f64; n];
1437 let degraded = Cell::new(false);
1438 imtqlb(
1439 n,
1440 &mut d_b,
1441 &mut e_b,
1442 &mut bnd,
1443 MAX_QL_ITERATIONS,
1444 °raded,
1445 );
1446 assert!(!degraded.get(), "imtqlb reported degraded convergence");
1447
1448 let mut d_2 = d0.clone();
1449 let mut e_2 = e0.clone();
1450 let mut z = vec![0.0f64; n * n];
1451 for i in (0..n * n).step_by(n + 1) {
1452 z[i] = 1.0;
1453 }
1454 imtql2(n, n, &mut d_2, &mut e_2, &mut z, MAX_QL_ITERATIONS).unwrap();
1455
1456 for i in 0..n {
1461 approx::assert_relative_eq!(d_b[i], d_2[i], max_relative = 1e-5);
1462 }
1463 }
1464
1465 fn assert_matches_lapack(name: &str, a: &SvdMat<f64>, dims: usize, tol: f64) {
1482 let want = reference_singular_values(&dense_of(a));
1483 let svd = svd_dim_seed(a, dims, 42).unwrap_or_else(|e| panic!("{name}: {e}"));
1484 for (i, &g) in svd.s.iter().enumerate() {
1485 let rel = (g - want[i]).abs() / want[i].abs().max(1e-30);
1486 assert!(
1487 rel < tol,
1488 "{name}: singular value {i}: got {g:.9e}, LAPACK {:.9e} (rel {rel:.3e})",
1489 want[i]
1490 );
1491 }
1492 }
1493
1494 #[test]
1495 #[ignore = "LAS2 accuracy defect inherited from 1.0.9; see module comment"]
1496 fn exact_on_diagonal_matrix() {
1497 assert_matches_lapack("diagonal_40", &diagonal(40), 10, 1e-8);
1498 }
1499
1500 #[test]
1501 #[ignore = "LAS2 accuracy defect inherited from 1.0.9; see module comment"]
1502 fn agrees_with_dense_reference_lowrank() {
1503 assert_matches_lapack("lowrank_80x50_r8", &gen_lowrank(80, 50, 8, 21), 8, 1e-6);
1504 }
1505
1506 #[test]
1507 #[ignore = "LAS2 accuracy defect inherited from 1.0.9; see module comment"]
1508 fn agrees_with_dense_reference_sparse() {
1509 assert_matches_lapack("sparse_500x40", &gen_sparse(500, 40, 0.10, 7), 10, 1e-6);
1510 }
1511
1512 #[test]
1513 #[ignore = "LAS2 accuracy defect inherited from 1.0.9; see module comment"]
1514 fn recompose_round_trips() {
1515 let a = gen_lowrank(40, 25, 25, 99);
1516 let dense = dense_of(&a);
1517 let svd = svd_dim_seed(&a, 25, 42).unwrap();
1518 let rec = svd.recompose();
1519 let err: f64 = (&rec - &dense).iter().map(|v| v * v).sum::<f64>().sqrt();
1520 let scale: f64 = dense.iter().map(|v| v * v).sum::<f64>().sqrt();
1521 assert!(
1522 err / scale < 1e-8,
1523 "relative reconstruction error {}",
1524 err / scale
1525 );
1526 }
1527
1528 #[test]
1531 #[ignore = "diagnostic, run explicitly"]
1532 fn report_accuracy_vs_lapack() {
1533 let cases: Vec<(&str, SvdMat<f64>, usize)> = vec![
1534 ("diagonal_40", diagonal(40), 10),
1535 ("diagonal_40_full", diagonal(40), 40),
1536 ("lowrank_80x50_r8", gen_lowrank(80, 50, 8, 21), 8),
1537 ("lowrank_200x80_r10", gen_lowrank(200, 80, 10, 555), 15),
1538 ("sparse_500x40_d10", gen_sparse(500, 40, 0.10, 7), 10),
1539 ("sparse_200x120_d05", gen_sparse(200, 120, 0.05, 3), 20),
1540 ];
1541 for (name, a, dims) in cases {
1542 let want = reference_singular_values(&dense_of(&a));
1543 match svd_dim_seed(&a, dims, 42) {
1544 Ok(svd) => {
1545 let got = svd.s.to_vec();
1546 let n = got.len().min(want.len());
1547 let worst = (0..n)
1548 .map(|i| (got[i] - want[i]).abs() / want[i].abs().max(1e-30))
1549 .fold(0.0f64, f64::max);
1550 println!(
1551 "{name:<24} dims={dims:<3} d={:<3} top_rel={:>9.2e} worst_rel={worst:>9.2e}",
1552 svd.d,
1553 (got[0] - want[0]).abs() / want[0].abs()
1554 );
1555 }
1556 Err(e) => println!("{name:<24} dims={dims:<3} ERROR {e}"),
1557 }
1558 }
1559 }
1560}