single_svdlib/randomized/
mod.rs

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