use crate::matrix::traits::*;
use nalgebra::{DMatrix, DVector};
use nalgebra_sparse::{csc::CscMatrix, csr::CsrMatrix};
use rayon::prelude::*;
use std::borrow::Cow;
const RSVD_SUBSPACE_SEED: u64 = 0x5253_5644_5342_5350;
pub fn nystrom_basis(u: &DMatrix<f32>, s: &DVector<f32>) -> DMatrix<f32> {
let eps = 1e-8;
let sinv = DVector::from_iterator(s.len(), s.iter().map(|&si| 1.0 / (si + eps)));
u * DMatrix::from_diagonal(&sinv)
}
trait LinOp<T: nalgebra::Scalar> {
fn matmul(&self, other: &DMatrix<T>) -> DMatrix<T>;
fn transpose_matmul(&self, other: &DMatrix<T>) -> DMatrix<T>;
fn num_rows(&self) -> usize;
fn num_columns(&self) -> usize;
}
impl<T> LinOp<T> for DMatrix<T>
where
T: nalgebra::RealField + num_traits::Float + Copy,
{
fn matmul(&self, other: &DMatrix<T>) -> DMatrix<T> {
self * other
}
fn transpose_matmul(&self, other: &DMatrix<T>) -> DMatrix<T> {
self.transpose() * other
}
fn num_rows(&self) -> usize {
self.nrows()
}
fn num_columns(&self) -> usize {
self.ncols()
}
}
struct SparseOp<'a, T: nalgebra::Scalar> {
csr: Cow<'a, CsrMatrix<T>>,
csc: Cow<'a, CscMatrix<T>>,
}
impl<'a, T> SparseOp<'a, T>
where
T: nalgebra::RealField + Copy,
{
fn from_csr(x: &'a CsrMatrix<T>) -> Self {
Self {
csr: Cow::Borrowed(x),
csc: Cow::Owned(CscMatrix::from(x)),
}
}
fn from_csc(x: &'a CscMatrix<T>) -> Self {
Self {
csr: Cow::Owned(CsrMatrix::from(x)),
csc: Cow::Borrowed(x),
}
}
}
impl<T> LinOp<T> for SparseOp<'_, T>
where
T: nalgebra::RealField + Copy,
{
fn matmul(&self, other: &DMatrix<T>) -> DMatrix<T> {
let x = &*self.csr;
rows_times_dense(x.row_offsets(), x.col_indices(), x.values(), other)
}
fn transpose_matmul(&self, other: &DMatrix<T>) -> DMatrix<T> {
let x = &*self.csc;
rows_times_dense(x.col_offsets(), x.row_indices(), x.values(), other)
}
fn num_rows(&self) -> usize {
self.csr.nrows()
}
fn num_columns(&self) -> usize {
self.csr.ncols()
}
}
const MIN_ROWS_PER_TASK: usize = 256;
const COLUMN_BLOCK: usize = 8;
fn rows_times_dense<T>(
offsets: &[usize],
indices: &[usize],
values: &[T],
b: &DMatrix<T>,
) -> DMatrix<T>
where
T: nalgebra::RealField + Copy,
{
let m = offsets.len() - 1;
let c = b.ncols();
if c == 0 {
return DMatrix::zeros(m, 0);
}
let bt = b.transpose();
let bt = bt.as_slice();
let mut out = vec![T::zero(); m * c];
out.par_chunks_mut(c)
.enumerate()
.with_min_len(MIN_ROWS_PER_TASK)
.for_each(|(i, row)| {
let entries = offsets[i]..offsets[i + 1];
let (idx, val) = (&indices[entries.clone()], &values[entries]);
let mut k0 = 0;
while k0 + COLUMN_BLOCK <= c {
let mut acc = [T::zero(); COLUMN_BLOCK];
for (&j, &v) in idx.iter().zip(val) {
let src = &bt[j * c + k0..j * c + k0 + COLUMN_BLOCK];
for (a, &s) in acc.iter_mut().zip(src) {
*a += v * s;
}
}
row[k0..k0 + COLUMN_BLOCK].copy_from_slice(&acc);
k0 += COLUMN_BLOCK;
}
for (&j, &v) in idx.iter().zip(val) {
for (o, &s) in row[k0..].iter_mut().zip(&bt[j * c + k0..(j + 1) * c]) {
*o += v * s;
}
}
});
DMatrix::from_row_slice(m, c, &out)
}
fn _subspace_iteration<T, D>(
xx: &D,
rank_and_oversample: usize,
power_iters: usize,
) -> anyhow::Result<DMatrix<T>>
where
T: nalgebra::RealField + num_traits::Float + Copy,
D: LinOp<T>,
{
let nc = xx.num_columns();
let mut qq = DMatrix::<T>::runif_seeded(nc, rank_and_oversample, RSVD_SUBSPACE_SEED);
let half = T::from(0.5).expect("no half found");
qq.iter_mut().for_each(|x| *x -= half);
for _i in 0..power_iters {
let ll = xx.matmul(&qq).qr().q();
qq = xx.transpose_matmul(&ll).qr().q();
}
let qr_q = xx.matmul(&qq).qr().q();
let kk = rank_and_oversample.min(qr_q.ncols());
let ret = qr_q.columns(0, kk).into_owned();
Ok(ret)
}
fn _randomized_svd<T, D>(
xx: &D,
max_rank: usize,
args: &RsvdArgs,
) -> anyhow::Result<(DMatrix<T>, DVector<T>, DMatrix<T>)>
where
T: nalgebra::RealField + num_traits::Float + Copy,
D: LinOp<T>,
{
let nr = xx.num_rows();
let nc = xx.num_columns();
let mut rank = nr.min(nc);
let mut oversample = 0;
if max_rank > 0 && rank > max_rank {
rank = max_rank;
oversample = args.oversample;
}
anyhow::ensure!(rank > 0, "randomized SVD of an empty {nr} x {nc} matrix");
let width = rank
.saturating_add(oversample)
.min(nr.min(nc) + RsvdArgs::default().oversample);
let qq = _subspace_iteration(xx, width, args.power_iters)?;
let rank = rank.min(qq.ncols());
let bb = xx.transpose_matmul(&qq).transpose();
let svd = bb.svd(true, true);
if let (Some(svd_u), Some(svd_vt)) = (svd.u, svd.v_t) {
return Ok((
&qq * svd_u.columns(0, rank),
svd.singular_values.rows(0, rank).into_owned(),
svd_vt.transpose().columns(0, rank).into_owned(),
));
}
Err(anyhow::anyhow!("randomized SVD failed"))
}
impl<T> RandomizedAlgs for DMatrix<T>
where
T: nalgebra::RealField + num_traits::Float + Copy,
{
type InMat = DMatrix<T>;
type OutMat = DMatrix<T>;
type DVec = DVector<T>;
type Scalar = T;
fn rsvd_with(
&self,
max_rank: usize,
args: &RsvdArgs,
) -> anyhow::Result<(Self::OutMat, Self::DVec, Self::OutMat)> {
_randomized_svd(self, max_rank, args)
}
}
impl<T> RandomizedAlgs for CscMatrix<T>
where
T: nalgebra::RealField + num_traits::Float + Copy,
{
type InMat = CscMatrix<T>;
type OutMat = DMatrix<T>;
type DVec = DVector<T>;
type Scalar = T;
fn rsvd_with(
&self,
max_rank: usize,
args: &RsvdArgs,
) -> anyhow::Result<(Self::OutMat, Self::DVec, Self::OutMat)> {
_randomized_svd(&SparseOp::from_csc(self), max_rank, args)
}
}
impl<T> RandomizedAlgs for CsrMatrix<T>
where
T: nalgebra::RealField + num_traits::Float + Copy,
{
type InMat = CsrMatrix<T>;
type OutMat = DMatrix<T>;
type DVec = DVector<T>;
type Scalar = T;
fn rsvd_with(
&self,
max_rank: usize,
args: &RsvdArgs,
) -> anyhow::Result<(Self::OutMat, Self::DVec, Self::OutMat)> {
_randomized_svd(&SparseOp::from_csr(self), max_rank, args)
}
}
#[cfg(test)]
#[path = "dmatrix_rsvd_tests.rs"]
mod tests;