single_svdlib/randomized/
mod.rs1use crate::error::SvdLibError;
2use crate::{Diagnostics, SMat, SvdFloat, SvdRec};
3use nalgebra_sparse::na::{ComplexField, DMatrix, DVector, RealField};
4use ndarray::Array1;
5use nshare::IntoNdarray2;
6use rand::prelude::{Distribution, StdRng};
7use rand::SeedableRng;
8use rand_distr::Normal;
9use rayon::iter::ParallelIterator;
10use rayon::prelude::{IndexedParallelIterator, IntoParallelIterator};
11use std::ops::Mul;
12use crate::utils::determine_chunk_size;
13
14pub enum PowerIterationNormalizer {
15 QR,
16 LU,
17 None,
18}
19
20const PARALLEL_THRESHOLD_ROWS: usize = 5000;
21const PARALLEL_THRESHOLD_COLS: usize = 1000;
22const PARALLEL_THRESHOLD_ELEMENTS: usize = 100_000;
23
24pub fn randomized_svd<T, M>(
25 m: &M,
26 target_rank: usize,
27 n_oversamples: usize,
28 n_power_iters: usize,
29 power_iteration_normalizer: PowerIterationNormalizer,
30 seed: Option<u64>,
31) -> anyhow::Result<SvdRec<T>>
32where
33 T: SvdFloat + RealField,
34 M: SMat<T>,
35 T: ComplexField,
36{
37 let start = std::time::Instant::now(); let m_rows = m.nrows();
39 let m_cols = m.ncols();
40
41 let rank = target_rank.min(m_rows.min(m_cols));
42 let l = rank + n_oversamples;
43 println!("Basic statistics: {:?}", start.elapsed());
44
45 let omega = generate_random_matrix(m_cols, l, seed);
46 println!("Generated Random Matrix here: {:?}", start.elapsed());
47
48 let mut y = DMatrix::<T>::zeros(m_rows, l);
49 multiply_matrix(m, &omega, &mut y, false);
50 println!(
51 "First multiplication took: {:?}, Continuing for power iterations:",
52 start.elapsed()
53 );
54
55 if n_power_iters > 0 {
56 let mut z = DMatrix::<T>::zeros(m_cols, l);
57
58 for w in 0..n_power_iters {
59 multiply_matrix(m, &y, &mut z, true);
60 println!("{}-nd power-iteration forward: {:?}", w, start.elapsed());
61 match power_iteration_normalizer {
62 PowerIterationNormalizer::QR => {
63 let qr = z.qr();
64 z = qr.q();
65 }
66 PowerIterationNormalizer::LU => {
67 normalize_columns(&mut z);
68 }
69 PowerIterationNormalizer::None => {}
70 }
71 println!(
72 "{}-nd power-iteration forward, normalization: {:?}",
73 w,
74 start.elapsed()
75 );
76
77 multiply_matrix(m, &z, &mut y, false);
78 println!("{}-nd power-iteration backward: {:?}", w, start.elapsed());
79 match power_iteration_normalizer {
80 PowerIterationNormalizer::QR => {
81 let qr = y.qr();
82 y = qr.q();
83 }
84 PowerIterationNormalizer::LU => normalize_columns(&mut y),
85 PowerIterationNormalizer::None => {}
86 }
87 println!(
88 "{}-nd power-iteration backward, normalization: {:?}",
89 w,
90 start.elapsed()
91 );
92 }
93 }
94 println!(
95 "Finished power-iteration, continuing QR: {:?}",
96 start.elapsed()
97 );
98 let qr = y.qr();
99 println!("QR finished: {:?}", start.elapsed());
100 let q = qr.q();
101
102 let mut b = DMatrix::<T>::zeros(q.ncols(), m_cols);
103 multiply_transposed_by_matrix(&q, m, &mut b);
104 println!(
105 "QMB matrix multiplication transposed: {:?}",
106 start.elapsed()
107 );
108
109 let svd = b.svd(true, true);
110 println!("SVD decomposition took: {:?}", start.elapsed());
111 let u_b = svd
112 .u
113 .ok_or_else(|| SvdLibError::Las2Error("SVD U computation failed".to_string()))?;
114 let singular_values = svd.singular_values;
115 let vt = svd
116 .v_t
117 .ok_or_else(|| SvdLibError::Las2Error("SVD V_t computation failed".to_string()))?;
118
119 let actual_rank = target_rank.min(singular_values.len());
120
121 let u_b_subset = u_b.columns(0, actual_rank);
122 let u = q.mul(&u_b_subset);
123
124 let vt_subset = vt.rows(0, actual_rank).into_owned();
125
126 let d = actual_rank;
128 println!("SVD Result Cropping: {:?}", start.elapsed());
129
130 let ut = u.transpose().into_ndarray2();
131 let s = convert_singular_values(
132 <DVector<T>>::from(singular_values.rows(0, actual_rank)),
133 actual_rank,
134 );
135 let vt = vt_subset.into_ndarray2();
136 println!("Translation to ndarray: {:?}", start.elapsed());
137
138 Ok(SvdRec {
139 d,
140 ut,
141 s,
142 vt,
143 diagnostics: create_diagnostics(m, d, target_rank, n_power_iters, seed.unwrap_or(0) as u32),
144 })
145}
146
147fn convert_singular_values<T: SvdFloat + ComplexField>(
148 values: DVector<T::RealField>,
149 size: usize,
150) -> Array1<T> {
151 let mut array = Array1::zeros(size);
152
153 for i in 0..size {
154 array[i] = T::from_real(values[i].clone());
156 }
157
158 array
159}
160
161fn create_diagnostics<T, M: SMat<T>>(
162 a: &M,
163 d: usize,
164 target_rank: usize,
165 power_iterations: usize,
166 seed: u32,
167) -> Diagnostics<T>
168where
169 T: SvdFloat,
170{
171 Diagnostics {
172 non_zero: a.nnz(),
173 dimensions: target_rank,
174 iterations: power_iterations,
175 transposed: false,
176 lanczos_steps: 0, ritz_values_stabilized: d,
178 significant_values: d,
179 singular_values: d,
180 end_interval: [T::from(-1e-30).unwrap(), T::from(1e-30).unwrap()],
181 kappa: T::from(1e-6).unwrap(),
182 random_seed: seed,
183 }
184}
185
186fn normalize_columns<T: SvdFloat + RealField + Send + Sync>(matrix: &mut DMatrix<T>) {
187 let rows = matrix.nrows();
188 let cols = matrix.ncols();
189
190 if rows < PARALLEL_THRESHOLD_ROWS && cols < PARALLEL_THRESHOLD_COLS {
192 for j in 0..cols {
193 let mut norm = T::zero();
194
195 for i in 0..rows {
197 norm += ComplexField::powi(matrix[(i, j)], 2);
198 }
199 norm = ComplexField::sqrt(norm);
200
201 if norm > T::from_f64(1e-10).unwrap() {
203 let scale = T::one() / norm;
204 for i in 0..rows {
205 matrix[(i, j)] *= scale;
206 }
207 }
208 }
209 return;
210 }
211
212 let norms: Vec<T> = (0..cols)
213 .into_par_iter()
214 .map(|j| {
215 let mut norm = T::zero();
216 for i in 0..rows {
217 let val = unsafe { *matrix.get_unchecked((i, j)) };
218 norm += ComplexField::powi(val, 2);
219 }
220 ComplexField::sqrt(norm)
221 })
222 .collect();
223
224 let scales: Vec<(usize, T)> = norms
226 .into_iter()
227 .enumerate()
228 .filter_map(|(j, norm)| {
229 if norm > T::from_f64(1e-10).unwrap() {
230 Some((j, T::one() / norm))
231 } else {
232 None }
234 })
235 .collect();
236
237 scales.iter().for_each(|(j, scale)| {
239 for i in 0..rows {
240 let value = matrix.get_mut((i, *j)).unwrap();
241 *value = value.clone() * scale.clone();
242 }
243 });
244}
245
246fn generate_random_matrix<T: SvdFloat + RealField>(
251 rows: usize,
252 cols: usize,
253 seed: Option<u64>,
254) -> DMatrix<T> {
255 let mut rng = match seed {
256 Some(s) => StdRng::seed_from_u64(s),
257 None => StdRng::seed_from_u64(0),
258 };
259
260 let normal = Normal::new(0.0, 1.0).unwrap();
261 DMatrix::from_fn(rows, cols, |_, _| {
262 T::from_f64(normal.sample(&mut rng)).unwrap()
263 })
264}
265
266fn multiply_matrix<T: SvdFloat, M: SMat<T>>(
267 sparse: &M,
268 dense: &DMatrix<T>,
269 result: &mut DMatrix<T>,
270 transpose_sparse: bool,
271) {
272 let cols = dense.ncols();
273
274 let results: Vec<(usize, Vec<T>)> = (0..cols)
275 .into_par_iter()
276 .map(|j| {
277 let mut col_vec = vec![T::zero(); dense.nrows()];
278 let mut result_vec = vec![T::zero(); result.nrows()];
279
280 for i in 0..dense.nrows() {
281 col_vec[i] = dense[(i, j)];
282 }
283
284 sparse.svd_opa(&col_vec, &mut result_vec, transpose_sparse);
285
286 (j, result_vec)
287 })
288 .collect();
289
290 for (j, col_result) in results {
291 for i in 0..result.nrows() {
292 result[(i, j)] = col_result[i];
293 }
294 }
295}
296
297fn multiply_transposed_by_matrix<T: SvdFloat, M: SMat<T>>(
298 q: &DMatrix<T>,
299 sparse: &M,
300 result: &mut DMatrix<T>,
301) {
302 let q_rows = q.nrows();
303 let q_cols = q.ncols();
304 let sparse_rows = sparse.nrows();
305 let sparse_cols = sparse.ncols();
306
307 eprintln!("Q dimensions: {} x {}", q_rows, q_cols);
308 eprintln!("Sparse dimensions: {} x {}", sparse_rows, sparse_cols);
309 eprintln!("Result dimensions: {} x {}", result.nrows(), result.ncols());
310
311 assert_eq!(
312 q_rows, sparse_rows,
313 "Dimension mismatch: Q has {} rows but sparse has {} rows",
314 q_rows, sparse_rows
315 );
316
317 assert_eq!(
318 result.nrows(),
319 q_cols,
320 "Result matrix has incorrect row count: expected {}, got {}",
321 q_cols,
322 result.nrows()
323 );
324 assert_eq!(
325 result.ncols(),
326 sparse_cols,
327 "Result matrix has incorrect column count: expected {}, got {}",
328 sparse_cols,
329 result.ncols()
330 );
331
332 let chunk_size = determine_chunk_size(q_cols);
333
334 let chunk_results: Vec<Vec<(usize, Vec<T>)>> = (0..q_cols)
335 .into_par_iter()
336 .chunks(chunk_size)
337 .map(|chunk| {
338 let mut chunk_results = Vec::with_capacity(chunk.len());
339
340 for &col_idx in &chunk {
341 let mut q_col = vec![T::zero(); q_rows];
342 for i in 0..q_rows {
343 q_col[i] = q[(i, col_idx)];
344 }
345
346 let mut result_row = vec![T::zero(); sparse_cols];
347
348 sparse.svd_opa(&q_col, &mut result_row, true);
349
350 chunk_results.push((col_idx, result_row));
351 }
352 chunk_results
353 })
354 .collect();
355
356 for chunk_result in chunk_results {
357 for (row_idx, row_values) in chunk_result {
358 for j in 0..sparse_cols {
359 result[(row_idx, j)] = row_values[j];
360 }
361 }
362 }
363}