1use crate::matrix::traits::*;
2use nalgebra::{DMatrix, DVector};
3use nalgebra_sparse::{csc::CscMatrix, csr::CsrMatrix};
4use rayon::prelude::*;
5use std::borrow::Cow;
6
7const RSVD_SUBSPACE_SEED: u64 = 0x5253_5644_5342_5350; pub fn nystrom_basis(u: &DMatrix<f32>, s: &DVector<f32>) -> DMatrix<f32> {
18 let eps = 1e-8;
19 let sinv = DVector::from_iterator(s.len(), s.iter().map(|&si| 1.0 / (si + eps)));
20 u * DMatrix::from_diagonal(&sinv)
21}
22
23trait LinOp<T: nalgebra::Scalar> {
25 fn matmul(&self, other: &DMatrix<T>) -> DMatrix<T>;
26 fn transpose_matmul(&self, other: &DMatrix<T>) -> DMatrix<T>;
27 fn num_rows(&self) -> usize;
28 fn num_columns(&self) -> usize;
29}
30
31impl<T> LinOp<T> for DMatrix<T>
32where
33 T: nalgebra::RealField + num_traits::Float + Copy,
34{
35 fn matmul(&self, other: &DMatrix<T>) -> DMatrix<T> {
36 self * other
37 }
38
39 fn transpose_matmul(&self, other: &DMatrix<T>) -> DMatrix<T> {
40 self.transpose() * other
41 }
42
43 fn num_rows(&self) -> usize {
44 self.nrows()
45 }
46 fn num_columns(&self) -> usize {
47 self.ncols()
48 }
49}
50
51struct SparseOp<'a, T: nalgebra::Scalar> {
57 csr: Cow<'a, CsrMatrix<T>>,
58 csc: Cow<'a, CscMatrix<T>>,
59}
60
61impl<'a, T> SparseOp<'a, T>
62where
63 T: nalgebra::RealField + Copy,
64{
65 fn from_csr(x: &'a CsrMatrix<T>) -> Self {
66 Self {
67 csr: Cow::Borrowed(x),
68 csc: Cow::Owned(CscMatrix::from(x)),
69 }
70 }
71
72 fn from_csc(x: &'a CscMatrix<T>) -> Self {
73 Self {
74 csr: Cow::Owned(CsrMatrix::from(x)),
75 csc: Cow::Borrowed(x),
76 }
77 }
78}
79
80impl<T> LinOp<T> for SparseOp<'_, T>
81where
82 T: nalgebra::RealField + Copy,
83{
84 fn matmul(&self, other: &DMatrix<T>) -> DMatrix<T> {
85 let x = &*self.csr;
86 rows_times_dense(x.row_offsets(), x.col_indices(), x.values(), other)
87 }
88 fn transpose_matmul(&self, other: &DMatrix<T>) -> DMatrix<T> {
89 let x = &*self.csc;
90 rows_times_dense(x.col_offsets(), x.row_indices(), x.values(), other)
91 }
92 fn num_rows(&self) -> usize {
93 self.csr.nrows()
94 }
95 fn num_columns(&self) -> usize {
96 self.csr.ncols()
97 }
98}
99
100const MIN_ROWS_PER_TASK: usize = 256;
102
103const COLUMN_BLOCK: usize = 8;
106
107fn rows_times_dense<T>(
113 offsets: &[usize],
114 indices: &[usize],
115 values: &[T],
116 b: &DMatrix<T>,
117) -> DMatrix<T>
118where
119 T: nalgebra::RealField + Copy,
120{
121 let m = offsets.len() - 1;
122 let c = b.ncols();
123 if c == 0 {
124 return DMatrix::zeros(m, 0);
125 }
126 let bt = b.transpose();
127 let bt = bt.as_slice();
128 let mut out = vec![T::zero(); m * c];
129 out.par_chunks_mut(c)
130 .enumerate()
131 .with_min_len(MIN_ROWS_PER_TASK)
132 .for_each(|(i, row)| {
133 let entries = offsets[i]..offsets[i + 1];
134 let (idx, val) = (&indices[entries.clone()], &values[entries]);
135 let mut k0 = 0;
136 while k0 + COLUMN_BLOCK <= c {
137 let mut acc = [T::zero(); COLUMN_BLOCK];
138 for (&j, &v) in idx.iter().zip(val) {
139 let src = &bt[j * c + k0..j * c + k0 + COLUMN_BLOCK];
140 for (a, &s) in acc.iter_mut().zip(src) {
141 *a += v * s;
142 }
143 }
144 row[k0..k0 + COLUMN_BLOCK].copy_from_slice(&acc);
145 k0 += COLUMN_BLOCK;
146 }
147 for (&j, &v) in idx.iter().zip(val) {
148 for (o, &s) in row[k0..].iter_mut().zip(&bt[j * c + k0..(j + 1) * c]) {
149 *o += v * s;
150 }
151 }
152 });
153 DMatrix::from_row_slice(m, c, &out)
154}
155
156fn _subspace_iteration<T, D>(
157 xx: &D,
158 rank_and_oversample: usize,
159 power_iters: usize,
160) -> anyhow::Result<DMatrix<T>>
161where
162 T: nalgebra::RealField + num_traits::Float + Copy,
163 D: LinOp<T>,
164{
165 let nc = xx.num_columns();
166 let mut qq = DMatrix::<T>::runif_seeded(nc, rank_and_oversample, RSVD_SUBSPACE_SEED);
173 let half = T::from(0.5).expect("no half found");
174 qq.iter_mut().for_each(|x| *x -= half);
175
176 for _i in 0..power_iters {
181 let ll = xx.matmul(&qq).qr().q();
182 qq = xx.transpose_matmul(&ll).qr().q();
183 }
184
185 let qr_q = xx.matmul(&qq).qr().q();
186 let kk = rank_and_oversample.min(qr_q.ncols());
187 let ret = qr_q.columns(0, kk).into_owned();
188
189 Ok(ret)
190}
191
192fn _randomized_svd<T, D>(
193 xx: &D,
194 max_rank: usize,
195 args: &RsvdArgs,
196) -> anyhow::Result<(DMatrix<T>, DVector<T>, DMatrix<T>)>
197where
198 T: nalgebra::RealField + num_traits::Float + Copy,
199 D: LinOp<T>,
200{
201 let nr = xx.num_rows();
202 let nc = xx.num_columns();
203
204 let mut rank = nr.min(nc);
205 let mut oversample = 0;
206
207 if max_rank > 0 && rank > max_rank {
208 rank = max_rank;
209 oversample = args.oversample;
210 }
211
212 anyhow::ensure!(rank > 0, "randomized SVD of an empty {nr} x {nc} matrix");
213
214 let width = rank
222 .saturating_add(oversample)
223 .min(nr.min(nc) + RsvdArgs::default().oversample);
224 let qq = _subspace_iteration(xx, width, args.power_iters)?;
225 let rank = rank.min(qq.ncols());
226
227 let bb = xx.transpose_matmul(&qq).transpose();
229
230 let svd = bb.svd(true, true);
231
232 if let (Some(svd_u), Some(svd_vt)) = (svd.u, svd.v_t) {
233 return Ok((
234 &qq * svd_u.columns(0, rank),
235 svd.singular_values.rows(0, rank).into_owned(),
236 svd_vt.transpose().columns(0, rank).into_owned(),
237 ));
238 }
239 Err(anyhow::anyhow!("randomized SVD failed"))
240}
241
242impl<T> RandomizedAlgs for DMatrix<T>
243where
244 T: nalgebra::RealField + num_traits::Float + Copy,
245{
246 type InMat = DMatrix<T>;
247 type OutMat = DMatrix<T>;
248 type DVec = DVector<T>;
249 type Scalar = T;
250
251 fn rsvd_with(
252 &self,
253 max_rank: usize,
254 args: &RsvdArgs,
255 ) -> anyhow::Result<(Self::OutMat, Self::DVec, Self::OutMat)> {
256 _randomized_svd(self, max_rank, args)
257 }
258}
259
260impl<T> RandomizedAlgs for CscMatrix<T>
261where
262 T: nalgebra::RealField + num_traits::Float + Copy,
263{
264 type InMat = CscMatrix<T>;
265 type OutMat = DMatrix<T>;
266 type DVec = DVector<T>;
267 type Scalar = T;
268
269 fn rsvd_with(
270 &self,
271 max_rank: usize,
272 args: &RsvdArgs,
273 ) -> anyhow::Result<(Self::OutMat, Self::DVec, Self::OutMat)> {
274 _randomized_svd(&SparseOp::from_csc(self), max_rank, args)
275 }
276}
277
278impl<T> RandomizedAlgs for CsrMatrix<T>
279where
280 T: nalgebra::RealField + num_traits::Float + Copy,
281{
282 type InMat = CsrMatrix<T>;
283 type OutMat = DMatrix<T>;
284 type DVec = DVector<T>;
285 type Scalar = T;
286
287 fn rsvd_with(
288 &self,
289 max_rank: usize,
290 args: &RsvdArgs,
291 ) -> anyhow::Result<(Self::OutMat, Self::DVec, Self::OutMat)> {
292 _randomized_svd(&SparseOp::from_csr(self), max_rank, args)
293 }
294}
295
296#[cfg(test)]
297#[path = "dmatrix_rsvd_tests.rs"]
298mod tests;