use faer::{Mat, MatRef};
use num_traits::ToPrimitive;
use rand::rngs::StdRng;
use rand::{Rng, SeedableRng};
use rayon::prelude::*;
use std::ops::{Add, AddAssign, Mul};
use crate::core::math::pca_svd::SvdResults;
use crate::prelude::*;
pub fn count_zeroes<T>(mat: &MatRef<T>) -> (usize, Vec<usize>, Vec<usize>)
where
T: BixverseFloat,
{
let (nrow, ncol) = mat.shape();
let mut total_zeroes = 0_usize;
let mut row_zeroes = vec![0_usize; nrow];
let mut col_zeroes = vec![0_usize; ncol];
let zero = T::zero();
for j in 0..ncol {
for i in 0..nrow {
let val = unsafe { mat.get_unchecked(i, j) };
if *val == zero {
total_zeroes += 1;
row_zeroes[i] += 1;
col_zeroes[j] += 1;
}
}
}
(total_zeroes, row_zeroes, col_zeroes)
}
#[derive(Debug, Clone)]
pub enum CompressedSparseFormat {
Csc,
Csr,
}
impl CompressedSparseFormat {
#[inline(always)]
pub fn is_csc(&self) -> bool {
matches!(self, CompressedSparseFormat::Csc)
}
#[inline(always)]
pub fn is_csr(&self) -> bool {
matches!(self, CompressedSparseFormat::Csr)
}
}
pub fn parse_compressed_sparse_format(s: &str) -> Option<CompressedSparseFormat> {
match s.to_lowercase().as_str() {
"csr" => Some(CompressedSparseFormat::Csr),
"csc" => Some(CompressedSparseFormat::Csc),
_ => None,
}
}
pub struct SparseAxis<T, U = T> {
pub indices: Vec<usize>,
pub data: Vec<T>,
pub data_2: Option<Vec<U>>,
pub cs_type: CompressedSparseFormat,
pub len: usize,
}
impl<T, U> SparseAxis<T, U>
where
T: BixverseNumeric,
U: BixverseNumeric,
{
pub fn new_csc(indices: Vec<usize>, data: Vec<T>, data_2: Option<Vec<U>>, len: usize) -> Self {
SparseAxis {
indices,
data,
data_2,
cs_type: CompressedSparseFormat::Csc,
len,
}
}
pub fn get_indices_data_2(&self) -> (&[usize], &[U]) {
let indices = &self.indices;
let data_2 = self.data_2.as_ref().expect("target gene requires data_2");
(indices, data_2)
}
pub fn from_vecs_to_csc(indices: Vec<T>, values: Vec<U>) -> Self
where
T: Into<usize>,
{
assert_eq!(
indices.len(),
values.len(),
"indices and values must have equal length"
);
let usize_indices: Vec<usize> = indices.into_iter().map(Into::into).collect();
let len = usize_indices.last().map_or(0, |&i| i + 1);
Self {
indices: usize_indices,
data: Vec::new(),
data_2: Some(values),
cs_type: CompressedSparseFormat::Csc,
len,
}
}
}
#[derive(Debug, Clone)]
pub struct CompressedSparseData2<T, U = T>
where
T: Clone,
U: Clone,
{
pub data: Vec<T>,
pub indices: Vec<usize>,
pub indptr: Vec<usize>,
pub cs_type: CompressedSparseFormat,
pub data_2: Option<Vec<U>>,
pub shape: (usize, usize),
}
impl<T, U> CompressedSparseData2<T, U>
where
T: BixverseNumeric,
U: BixverseNumeric,
{
#[allow(dead_code)]
pub fn new_csc(
data: &[T],
indices: &[usize],
indptr: &[usize],
data2: Option<&[U]>,
shape: (usize, usize),
) -> Self {
Self {
data: data.to_vec(),
indices: indices.to_vec(),
indptr: indptr.to_vec(), cs_type: CompressedSparseFormat::Csc,
data_2: data2.map(|d| d.to_vec()),
shape,
}
}
pub fn new_csr(
data: &[T],
indices: &[usize],
indptr: &[usize],
data2: Option<&[U]>,
shape: (usize, usize),
) -> Self {
Self {
data: data.to_vec(),
indices: indices.to_vec(),
indptr: indptr.to_vec(), cs_type: CompressedSparseFormat::Csr,
data_2: data2.map(|d| d.to_vec()),
shape,
}
}
pub fn transform(&self) -> Self {
transpose_sparse(self)
}
pub fn transpose_and_convert(&self) -> Self {
match self.cs_type {
CompressedSparseFormat::Csr => {
let csc_version = transpose_sparse(self);
CompressedSparseData2 {
data: csc_version.data,
indices: csc_version.indices,
indptr: csc_version.indptr,
cs_type: CompressedSparseFormat::Csr, data_2: csc_version.data_2,
shape: (self.shape.1, self.shape.0), }
}
CompressedSparseFormat::Csc => {
CompressedSparseData2 {
data: self.data.clone(),
indices: self.indices.clone(),
indptr: self.indptr.clone(),
cs_type: CompressedSparseFormat::Csr,
data_2: self.data_2.clone(),
shape: (self.shape.1, self.shape.0),
}
}
}
}
#[allow(dead_code)]
pub fn transpose_from_h5ad(&self) -> Self {
CompressedSparseData2 {
data: self.data.clone(),
indices: self.indices.clone(),
indptr: self.indptr.clone(),
cs_type: self.cs_type.clone(),
data_2: self.data_2.clone(),
shape: (self.shape.1, self.shape.0),
}
}
pub fn from_dense_matrix(mat: faer::MatRef<T>, format: CompressedSparseFormat) -> Self
where
T: BixverseFloat,
{
let (nrows, ncols) = (mat.nrows(), mat.ncols());
let mut data = Vec::new();
let mut indices = Vec::new();
let mut indptr = Vec::new();
match format {
CompressedSparseFormat::Csr => {
indptr.push(0);
for i in 0..nrows {
for j in 0..ncols {
let val = mat[(i, j)];
if val != T::zero() {
data.push(val);
indices.push(j);
}
}
indptr.push(data.len());
}
}
CompressedSparseFormat::Csc => {
indptr.push(0);
for j in 0..ncols {
for i in 0..nrows {
let val = mat[(i, j)];
if val != T::zero() {
data.push(val);
indices.push(i);
}
}
indptr.push(data.len());
}
}
}
Self {
data,
indices,
indptr,
cs_type: format,
data_2: None,
shape: (nrows, ncols),
}
}
pub fn from_upper_triangle_sym(
upper_triangle: &[T],
n: usize,
include_diagonal: bool,
format: CompressedSparseFormat,
) -> Self
where
T: BixverseFloat,
{
let get_value = |row: usize, col: usize| -> T {
if row == col {
if include_diagonal {
let offset = row * n - row * (row + 1) / 2 + col;
upper_triangle[offset]
} else {
T::one()
}
} else if row < col {
let offset = if include_diagonal {
row * n - row * (row + 1) / 2 + col
} else {
row * (n - 1) - row * (row + 1) / 2 + col - 1
};
upper_triangle[offset]
} else {
let offset = if include_diagonal {
col * n - col * (col + 1) / 2 + row
} else {
col * (n - 1) - col * (col + 1) / 2 + row - 1
};
upper_triangle[offset]
}
};
let mut data = Vec::new();
let mut indices = Vec::new();
let mut indptr = Vec::new();
match format {
CompressedSparseFormat::Csr => {
indptr.push(0);
for row in 0..n {
for col in 0..n {
let value = get_value(row, col);
if value != T::zero() {
data.push(value);
indices.push(col);
}
}
indptr.push(data.len());
}
}
CompressedSparseFormat::Csc => {
indptr.push(0);
for col in 0..n {
for row in 0..n {
let value = get_value(row, col);
if value != T::zero() {
data.push(value);
indices.push(row);
}
}
indptr.push(data.len());
}
}
}
Self {
data,
indices,
indptr,
cs_type: format,
data_2: None,
shape: (n, n),
}
}
pub fn shape(&self) -> (usize, usize) {
self.shape
}
pub fn get_nnz(&self) -> usize {
self.data.len()
}
pub fn get_data2_unsafe(&self) -> Vec<U> {
self.data_2.clone().unwrap()
}
}
pub fn transpose_sparse<T, U>(
sparse_data: &CompressedSparseData2<T, U>,
) -> CompressedSparseData2<T, U>
where
T: BixverseNumeric,
U: BixverseNumeric,
{
let nnz = sparse_data.get_nnz();
let (nrow, ncol) = sparse_data.shape();
let (new_major, new_type) = match sparse_data.cs_type {
CompressedSparseFormat::Csc => (nrow, CompressedSparseFormat::Csr),
CompressedSparseFormat::Csr => (ncol, CompressedSparseFormat::Csc),
};
let mut new_indptr = vec![0usize; new_major + 1];
for &idx in &sparse_data.indices {
new_indptr[idx + 1] += 1;
}
for i in 0..new_major {
new_indptr[i + 1] += new_indptr[i];
}
let mut new_data: Vec<T> = vec![T::default(); nnz];
let mut new_indices: Vec<usize> = vec![0usize; nnz];
let mut new_data2: Option<Vec<U>> =
sparse_data.data_2.as_ref().map(|_| vec![U::default(); nnz]);
unsafe {
new_data.set_len(nnz);
new_indices.set_len(nnz);
}
let old_major_len = sparse_data.indptr.len() - 1;
for major in 0..old_major_len {
for idx in sparse_data.indptr[major]..sparse_data.indptr[major + 1] {
let minor = sparse_data.indices[idx];
let pos = new_indptr[minor];
unsafe {
*new_data.get_unchecked_mut(pos) = sparse_data.data[idx];
*new_indices.get_unchecked_mut(pos) = major;
}
if let (Some(src), Some(dst)) = (&sparse_data.data_2, &mut new_data2) {
unsafe {
*dst.get_unchecked_mut(pos) = src[idx];
}
}
new_indptr[minor] += 1;
}
}
for i in (1..=new_major).rev() {
new_indptr[i] = new_indptr[i - 1];
}
new_indptr[0] = 0;
CompressedSparseData2 {
data: new_data,
indices: new_indices,
indptr: new_indptr,
cs_type: new_type,
data_2: new_data2,
shape: (nrow, ncol),
}
}
pub fn coo_to_csr<T>(
rows: &[usize],
cols: &[usize],
vals: &[T],
shape: (usize, usize),
) -> CompressedSparseData2<T>
where
T: BixverseNumeric,
{
let n_rows = shape.0;
let mut entries: Vec<(usize, usize, T)> = rows
.iter()
.zip(cols.iter())
.zip(vals.iter())
.map(|((&r, &c), &v)| (r, c, v))
.collect();
entries.sort_unstable_by_key(|&(r, c, _)| (r, c));
let mut merged_entries = Vec::new();
if !entries.is_empty() {
let mut current = entries[0];
for &(r, c, v) in &entries[1..] {
if r == current.0 && c == current.1 {
current.2 += v;
} else {
if current.2 != T::default() {
merged_entries.push(current);
}
current = (r, c, v);
}
}
if current.2 != T::default() {
merged_entries.push(current);
}
}
let final_nnz = merged_entries.len();
let mut data = Vec::with_capacity(final_nnz);
let mut indices = Vec::with_capacity(final_nnz);
let mut indptr = vec![0usize; n_rows + 1];
for &(row, col, val) in &merged_entries {
data.push(val);
indices.push(col);
indptr[row + 1] += 1;
}
for i in 0..n_rows {
indptr[i + 1] += indptr[i];
}
CompressedSparseData2::new_csr(&data, &indices, &indptr, None, shape)
}
pub fn coo_to_csr_presorted<T>(
rows: &[usize],
cols: &[usize],
vals: &[T],
shape: (usize, usize),
) -> CompressedSparseData2<T>
where
T: BixverseNumeric,
{
let n_rows = shape.0;
let nnz = rows.len();
let mut data = Vec::with_capacity(nnz);
let mut indices = Vec::with_capacity(nnz);
let mut indptr = vec![0usize; n_rows + 1];
unsafe {
data.set_len(nnz);
indices.set_len(nnz);
let data_ptr: *mut T = data.as_mut_ptr();
let indices_ptr: *mut usize = indices.as_mut_ptr();
let indptr_ptr: *mut usize = indptr.as_mut_ptr();
for i in 0..nnz {
*data_ptr.add(i) = *vals.get_unchecked(i);
*indices_ptr.add(i) = *cols.get_unchecked(i);
let row = *rows.get_unchecked(i);
*indptr_ptr.add(row + 1) += 1;
}
for i in 0..n_rows {
*indptr_ptr.add(i + 1) += *indptr_ptr.add(i);
}
}
CompressedSparseData2::new_csr(&data, &indices, &indptr, None, shape)
}
pub fn sparse_add_csr<T>(
a: &CompressedSparseData2<T>,
b: &CompressedSparseData2<T>,
) -> CompressedSparseData2<T>
where
T: BixverseNumeric + Into<f64> + Add<Output = T>,
{
assert_eq!(a.shape, b.shape);
assert!(a.cs_type.is_csr() && b.cs_type.is_csr());
const EPSILON: f32 = 1e-9;
let n_rows = a.shape.0;
let mut rows = Vec::new();
let mut cols = Vec::new();
let mut vals = Vec::new();
for i in 0..n_rows {
let a_start = a.indptr[i];
let a_end = a.indptr[i + 1];
let b_start = b.indptr[i];
let b_end = b.indptr[i + 1];
let mut a_idx = a_start;
let mut b_idx = b_start;
while a_idx < a_end || b_idx < b_end {
if a_idx < a_end && (b_idx >= b_end || a.indices[a_idx] < b.indices[b_idx]) {
rows.push(i);
cols.push(a.indices[a_idx]);
vals.push(a.data[a_idx]);
a_idx += 1;
} else if b_idx < b_end && (a_idx >= a_end || b.indices[b_idx] < a.indices[a_idx]) {
rows.push(i);
cols.push(b.indices[b_idx]);
vals.push(b.data[b_idx]);
b_idx += 1;
} else {
let val = a.data[a_idx] + b.data[b_idx];
if val.into().abs() > EPSILON as f64 {
rows.push(i);
cols.push(a.indices[a_idx]);
vals.push(val);
}
a_idx += 1;
b_idx += 1;
}
}
}
coo_to_csr_presorted(&rows, &cols, &vals, a.shape)
}
pub fn sparse_scalar_multiply_csr<T>(
a: &CompressedSparseData2<T>,
scalar: T,
) -> CompressedSparseData2<T>
where
T: BixverseNumeric,
<T as Mul>::Output: Send,
Vec<T>: FromParallelIterator<<T as Mul>::Output>,
{
let data: Vec<T> = a.data.par_iter().map(|&v| v * scalar).collect();
CompressedSparseData2::new_csr(&data, &a.indices, &a.indptr, None, a.shape)
}
pub fn sparse_subtract_csr<T>(
a: &CompressedSparseData2<T>,
b: &CompressedSparseData2<T>,
) -> CompressedSparseData2<T>
where
T: BixverseNumeric + Into<f64>,
{
assert_eq!(a.shape, b.shape);
assert!(a.cs_type.is_csr() && b.cs_type.is_csr());
const EPSILON: f32 = 1e-9;
let n_rows = a.shape.0;
let mut rows = Vec::new();
let mut cols = Vec::new();
let mut vals = Vec::new();
for i in 0..n_rows {
let a_start = a.indptr[i];
let a_end = a.indptr[i + 1];
let b_start = b.indptr[i];
let b_end = b.indptr[i + 1];
let mut a_idx = a_start;
let mut b_idx = b_start;
while a_idx < a_end || b_idx < b_end {
if a_idx < a_end && (b_idx >= b_end || a.indices[a_idx] < b.indices[b_idx]) {
rows.push(i);
cols.push(a.indices[a_idx]);
vals.push(a.data[a_idx]);
a_idx += 1;
} else if b_idx < b_end && (a_idx >= a_end || b.indices[b_idx] < a.indices[a_idx]) {
rows.push(i);
cols.push(b.indices[b_idx]);
vals.push(T::default() - b.data[b_idx]);
b_idx += 1;
} else {
let val = a.data[a_idx] - b.data[b_idx];
if val.into().abs() > EPSILON as f64 {
rows.push(i);
cols.push(a.indices[a_idx]);
vals.push(val);
}
a_idx += 1;
b_idx += 1;
}
}
}
coo_to_csr_presorted(&rows, &cols, &vals, a.shape)
}
pub fn sparse_multiply_elementwise_csr<T>(
a: &CompressedSparseData2<T>,
b: &CompressedSparseData2<T>,
) -> CompressedSparseData2<T>
where
T: BixverseNumeric,
<T as std::ops::Add>::Output: std::cmp::PartialEq<T>,
{
assert_eq!(a.shape, b.shape);
assert!(a.cs_type.is_csr() && b.cs_type.is_csr());
let n_rows = a.shape.0;
let mut rows = Vec::new();
let mut cols = Vec::new();
let mut vals = Vec::new();
for i in 0..n_rows {
let a_start = a.indptr[i];
let a_end = a.indptr[i + 1];
let b_start = b.indptr[i];
let b_end = b.indptr[i + 1];
let mut a_idx = a_start;
let mut b_idx = b_start;
while a_idx < a_end && b_idx < b_end {
match a.indices[a_idx].cmp(&b.indices[b_idx]) {
std::cmp::Ordering::Less => {
a_idx += 1;
}
std::cmp::Ordering::Greater => {
b_idx += 1;
}
std::cmp::Ordering::Equal => {
let val = a.data[a_idx] * b.data[b_idx];
if val != T::default() {
rows.push(i);
cols.push(a.indices[a_idx]);
vals.push(val);
}
a_idx += 1;
b_idx += 1;
}
}
}
}
coo_to_csr(&rows, &cols, &vals, a.shape)
}
pub fn normalise_csr_columns_l1<T>(csr: &mut CompressedSparseData2<T>)
where
T: BixverseNumeric + Into<f64>,
<T as std::ops::Add>::Output: std::cmp::PartialEq<T>,
{
assert!(csr.cs_type.is_csr(), "Matrix must be in CSR format");
let ncols = csr.shape.1;
let mut col_sums = vec![T::default(); ncols];
for (idx, &col) in csr.indices.iter().enumerate() {
col_sums[col] += csr.data[idx]
}
for (idx, &col) in csr.indices.iter().enumerate() {
let sum = col_sums[col];
if sum.into() > 1e-15 {
csr.data[idx] /= sum;
}
}
}
#[allow(dead_code)]
pub fn normalise_csr_rows_l1<T>(csr: &mut CompressedSparseData2<T>)
where
T: BixverseNumeric + Into<f64>,
T: std::iter::Sum<T>,
{
assert!(csr.cs_type.is_csr(), "Matrix must be in CSR format");
let nrows = csr.shape.0;
for i in 0..nrows {
let start = csr.indptr[i];
let end = csr.indptr[i + 1];
let row_data_slice = &mut csr.data[start..end];
let row_sum: T = row_data_slice.iter().copied().sum();
if row_sum.into() > 1e-15 {
for val in row_data_slice.iter_mut() {
*val /= row_sum;
}
} else {
panic!(
"Row {} has sum {}, indicating isolated node",
i,
row_sum.into()
);
}
}
}
pub fn frobenius_norm<T>(mat: &CompressedSparseData2<T>) -> f32
where
T: BixverseNumeric + Into<f32>,
{
mat.data
.par_iter()
.with_min_len(10000)
.map(|&v| {
let val: f32 = v.into();
val * val
})
.sum::<f32>()
.sqrt()
}
pub fn eliminate_zeros_csr<T>(mat: CompressedSparseData2<T>) -> CompressedSparseData2<T>
where
T: BixverseNumeric,
<T as std::ops::Add>::Output: std::cmp::PartialEq<T>,
{
let mut rows = Vec::new();
let mut cols = Vec::new();
let mut vals = Vec::new();
let n_rows = mat.shape.0;
for i in 0..n_rows {
let start = mat.indptr[i];
let end = mat.indptr[i + 1];
for j in start..end {
if mat.data[j] != T::default() {
rows.push(i);
cols.push(mat.indices[j]);
vals.push(mat.data[j]);
}
}
}
coo_to_csr(&rows, &cols, &vals, mat.shape)
}
pub fn csr_matvec<T>(mat: &CompressedSparseData2<T>, vec: &[T]) -> Vec<T>
where
T: BixverseNumeric,
<T as std::ops::Add>::Output: std::cmp::PartialEq<T>,
{
let mut result = vec![T::default(); mat.shape.0];
for i in 0..mat.shape.0 {
let row_start = mat.indptr[i];
let row_end = mat.indptr[i + 1];
let mut sum = T::default();
for idx in row_start..row_end {
sum += mat.data[idx] * vec[mat.indices[idx]];
}
result[i] = sum;
}
result
}
struct SparseAccumulator<T>
where
T: Copy + Default + AddAssign,
{
values: Vec<T>,
indices: Vec<usize>,
flags: Vec<bool>,
}
impl<T> SparseAccumulator<T>
where
T: Copy + Default + AddAssign,
{
fn new(size: usize) -> Self {
Self {
values: vec![T::default(); size],
indices: Vec::with_capacity(size / 10),
flags: vec![false; size],
}
}
#[inline]
unsafe fn add(&mut self, idx: usize, val: T) {
unsafe {
if !*self.flags.get_unchecked(idx) {
*self.flags.get_unchecked_mut(idx) = true;
self.indices.push(idx);
*self.values.get_unchecked_mut(idx) = val;
} else {
*self.values.get_unchecked_mut(idx) += val;
}
}
}
#[inline]
fn extract_sorted(&mut self) -> Vec<(usize, T)> {
self.indices.sort_unstable();
let result: Vec<(usize, T)> = unsafe {
self.indices
.iter()
.map(|&i| (i, *self.values.get_unchecked(i)))
.collect()
};
unsafe {
for &idx in &self.indices {
*self.flags.get_unchecked_mut(idx) = false;
*self.values.get_unchecked_mut(idx) = T::default();
}
}
self.indices.clear();
result
}
}
pub fn csr_matmul_csr<T>(
a: &CompressedSparseData2<T>,
b: &CompressedSparseData2<T>,
) -> CompressedSparseData2<T>
where
T: BixverseNumeric,
{
assert!(a.cs_type.is_csr() && b.cs_type.is_csr());
assert_eq!(a.shape.1, b.shape.0, "Dimension mismatch");
let nrows = a.shape.0;
let ncols = b.shape.1;
let row_results: Vec<Vec<(usize, T)>> = (0..nrows)
.into_par_iter()
.map(|i| {
let mut acc = SparseAccumulator::new(ncols);
unsafe {
let a_indptr = a.indptr.as_ptr();
let a_indices = a.indices.as_ptr();
let a_data = a.data.as_ptr();
let b_indptr = b.indptr.as_ptr();
let b_indices = b.indices.as_ptr();
let b_data = b.data.as_ptr();
let a_start = *a_indptr.add(i);
let a_end = *a_indptr.add(i + 1);
for a_idx in a_start..a_end {
let k = *a_indices.add(a_idx);
let a_val = *a_data.add(a_idx);
let b_start = *b_indptr.add(k);
let b_end = *b_indptr.add(k + 1);
for b_idx in b_start..b_end {
let j = *b_indices.add(b_idx);
let b_val = *b_data.add(b_idx);
acc.add(j, a_val * b_val);
}
}
}
acc.extract_sorted()
})
.collect();
let total_nnz: usize = row_results.iter().map(|r| r.len()).sum();
let mut data = Vec::with_capacity(total_nnz);
let mut indices = Vec::with_capacity(total_nnz);
let mut indptr = Vec::with_capacity(nrows + 1);
indptr.push(0);
for row in row_results {
for (col, val) in row {
data.push(val);
indices.push(col);
}
indptr.push(data.len());
}
CompressedSparseData2::new_csr(&data, &indices, &indptr, None, (nrows, ncols))
}
fn dot<T>(a: &[T], b: &[T]) -> T
where
T: BixverseSimd,
{
assert_same_len!(a, b);
T::bxv_dot_simd(a, b)
}
fn norm<T>(v: &[T]) -> T
where
T: BixverseSimd + BixverseFloat,
{
let dot = dot(v, v);
dot.sqrt()
}
fn normalise<T>(v: &mut [T])
where
T: BixverseSimd + BixverseFloat,
{
let n = norm(v);
v.par_iter_mut().for_each(|x| *x /= n);
}
fn tridiag_eig<T>(alpha: &[T], beta: &[T]) -> (Vec<T>, Mat<T>)
where
T: BixverseFloat,
{
let n = alpha.len();
let mut t = Mat::<T>::zeros(n, n);
for i in 0..n {
t[(i, i)] = alpha[i];
if i < n - 1 {
t[(i, i + 1)] = beta[i];
t[(i + 1, i)] = beta[i];
}
}
let eig = t.self_adjoint_eigen(faer::Side::Lower).unwrap();
let evals = eig.S().column_vector().iter().copied().collect();
let evecs = eig.U().to_owned();
(evals, evecs)
}
pub fn compute_largest_eigenpairs_lanczos<T>(
matrix: &CompressedSparseData2<T>,
n_components: usize,
seed: u64,
) -> (Vec<f32>, Vec<Vec<f32>>)
where
T: BixverseNumeric + BixverseSimd + Into<f64>,
{
let n = matrix.shape.0;
let n_iter = (n_components * 2 + 10).max(n_components).min(n);
let csr = match matrix.cs_type {
CompressedSparseFormat::Csr => matrix.clone(),
CompressedSparseFormat::Csc => matrix.transform(),
};
let data_f64: Vec<f64> = csr.data.iter().map(|&v| v.into()).collect();
let matvec = |x: &[f64], y: &mut [f64]| {
y.par_iter_mut().enumerate().for_each(|(i, yi)| {
let mut sum = 0.0;
for idx in csr.indptr[i]..csr.indptr[i + 1] {
let j = csr.indices[idx];
sum += data_f64[idx] * x[j];
}
*yi = sum;
});
};
let mut v = vec![0.0; n];
let mut v_old = vec![0.0; n];
let mut w = vec![0.0; n];
let mut v_matrix = vec![vec![0.0; n]; n_iter];
let mut rng = StdRng::seed_from_u64(seed);
for i in 0..n {
v[i] = rng.random::<f64>() - 0.5;
}
normalise(&mut v);
let mut alpha = vec![0.0; n_iter];
let mut beta = vec![0.0; n_iter];
for j in 0..n_iter {
v_matrix[j].copy_from_slice(&v);
matvec(&v, &mut w);
alpha[j] = dot(&w, &v);
for i in 0..n {
w[i] -= alpha[j] * v[i];
if j > 0 {
w[i] -= beta[j - 1] * v_old[i];
}
}
for k in 0..=j {
let coeff = dot(&w, &v_matrix[k]);
for i in 0..n {
w[i] -= coeff * v_matrix[k][i];
}
}
beta[j] = norm(&w);
if beta[j] < 1e-12 {
break;
}
v_old.copy_from_slice(&v);
v.copy_from_slice(&w);
normalise(&mut v);
}
let (evals, evecs) = tridiag_eig(&alpha[..n_iter], &beta[..n_iter - 1]);
let mut indices: Vec<usize> = (0..evals.len()).collect();
indices.sort_by(|&i, &j| evals[j].partial_cmp(&evals[i]).unwrap());
let mut largest_evals: Vec<f32> = Vec::with_capacity(n_components);
let mut largest_evecs: Vec<Vec<f32>> = Vec::with_capacity(n_components);
for &idx in indices.iter().take(n_components) {
let mut evec = vec![0.0; n];
for i in 0..n {
for j in 0..n_iter {
evec[i] += v_matrix[j][i] * evecs[(j, idx)].to_f64().unwrap();
}
}
let norm: f64 = evec.iter().map(|x| x * x).sum::<f64>().sqrt();
for x in &mut evec {
*x /= norm;
}
largest_evals.push(evals[idx].to_f64().unwrap() as f32);
largest_evecs.push(evec.iter().map(|&x| x as f32).collect());
}
let mut transposed = vec![vec![0.0f32; n_components]; n];
for comp_idx in 0..n_components {
for point_idx in 0..n {
transposed[point_idx][comp_idx] = largest_evecs[comp_idx][point_idx];
}
}
(largest_evals, transposed)
}
pub fn sparse_svd_lanczos<T, U, F>(
matrix: &CompressedSparseData2<T, U>,
n_components: usize,
seed: u64,
use_second_layer: bool,
col_means: Option<&[F]>,
col_stds: Option<&[F]>,
) -> SvdResults<F>
where
T: BixverseNumeric + BixverseSimd + Into<F> + Clone,
U: BixverseNumeric + Into<F> + Clone,
F: BixverseFloat + BixverseSimd + std::iter::Sum,
{
let (n, m) = matrix.shape;
let use_ata = n > m;
let krylov_dim = if use_ata { m } else { n };
let n_iter = (n_components * 2 + 10).max(n_components).min(krylov_dim);
let (csr, csc);
let csr_owned;
let csc_owned;
match matrix.cs_type {
CompressedSparseFormat::Csr => {
csr = matrix;
csc_owned = matrix.transform();
csc = &csc_owned;
}
CompressedSparseFormat::Csc => {
csc = matrix;
csr_owned = matrix.transform();
csr = &csr_owned;
}
};
let extract_data = |mat: &CompressedSparseData2<T, U>| -> Vec<F> {
if use_second_layer {
mat.data_2
.as_ref()
.expect("data_2 is None but use_second_layer is true")
.iter()
.copied()
.map(|v| v.into())
.collect()
} else {
mat.data.iter().copied().map(|v| v.into()).collect()
}
};
let data_csr_f = extract_data(csr);
let data_csc_f = extract_data(csc);
let matvec_a = |x: &[F], y: &mut [F]| {
let x_scaled: Vec<F> = if let Some(sd) = col_stds {
x.iter().enumerate().map(|(j, &v)| v / sd[j]).collect()
} else {
x.to_vec()
};
let mean_dot: F = if let Some(mu) = col_means {
x_scaled.iter().enumerate().map(|(j, &v)| mu[j] * v).sum()
} else {
F::zero()
};
y.par_iter_mut().enumerate().for_each(|(i, yi)| {
let mut sum = F::zero();
for idx in csr.indptr[i]..csr.indptr[i + 1] {
let j = csr.indices[idx];
sum += data_csr_f[idx] * x_scaled[j];
}
if col_means.is_some() {
sum -= mean_dot;
}
*yi = sum;
});
};
let matvec_at = |x: &[F], y: &mut [F]| {
let x_sum: F = x.iter().copied().sum();
y.par_iter_mut().enumerate().for_each(|(j, yj)| {
let mut sum = F::zero();
for idx in csc.indptr[j]..csc.indptr[j + 1] {
let i = csc.indices[idx];
sum += data_csc_f[idx] * x[i];
}
if let Some(mu) = col_means {
sum -= mu[j] * x_sum;
}
if let Some(sd) = col_stds {
sum /= sd[j];
}
*yj = sum;
});
};
#[allow(clippy::type_complexity)]
let matvec_gram: Box<dyn Fn(&[F], &mut [F]) + Sync> = if use_ata {
Box::new(|x: &[F], y: &mut [F]| {
let mut temp = vec![F::zero(); n];
matvec_a(x, &mut temp);
matvec_at(&temp, y);
})
} else {
Box::new(|x: &[F], y: &mut [F]| {
let mut temp = vec![F::zero(); m];
matvec_at(x, &mut temp);
matvec_a(&temp, y);
})
};
let mut v_matrix = Mat::<F>::zeros(krylov_dim, n_iter);
let mut v = vec![F::zero(); krylov_dim];
let mut v_old = vec![F::zero(); krylov_dim];
let mut w = vec![F::zero(); krylov_dim];
let mut w_faer = faer::Col::<F>::zeros(krylov_dim);
let mut rng = StdRng::seed_from_u64(seed);
for i in 0..krylov_dim {
v[i] = F::from(rng.random::<f64>() - 0.5).unwrap();
}
normalise(&mut v);
let mut alpha = vec![F::zero(); n_iter];
let mut beta = vec![F::zero(); n_iter];
for j in 0..n_iter {
for i in 0..krylov_dim {
v_matrix[(i, j)] = v[i];
}
matvec_gram(&v, &mut w);
alpha[j] = dot(&w, &v);
for i in 0..krylov_dim {
w[i] -= alpha[j] * v[i];
if j > 0 {
w[i] -= beta[j - 1] * v_old[i];
}
}
for i in 0..krylov_dim {
w_faer[i] = w[i];
}
let vj = v_matrix.as_ref().subcols(0, j + 1);
let coeffs = vj.transpose() * w_faer.as_ref();
let proj = vj * coeffs.as_ref();
for i in 0..krylov_dim {
w[i] -= proj[i];
}
beta[j] = norm(&w);
if beta[j] < F::from(1e-12).unwrap() {
break;
}
v_old.copy_from_slice(&v);
v.copy_from_slice(&w);
normalise(&mut v);
}
let (evals, evecs) = tridiag_eig(&alpha[..n_iter], &beta[..n_iter - 1]);
let mut indices: Vec<usize> = (0..evals.len()).collect();
indices.sort_by(|&i, &j| evals[j].partial_cmp(&evals[i]).unwrap());
let mut singular_values: Vec<F> = Vec::with_capacity(n_components);
let mut u_vecs: Vec<Vec<F>> = Vec::with_capacity(n_components);
let mut v_vecs: Vec<Vec<F>> = Vec::with_capacity(n_components);
for &idx in indices.iter().take(n_components) {
let eval = evals[idx];
if eval <= F::zero() {
continue;
}
let sigma = eval.sqrt();
singular_values.push(sigma);
let gram_col = &v_matrix * evecs.col(idx);
let norm_val: F = gram_col.iter().map(|x| *x * *x).sum::<F>().sqrt();
let gram_evec: Vec<F> = gram_col.iter().map(|x| *x / norm_val).collect();
if use_ata {
let mut u_vec = vec![F::zero(); n];
matvec_a(&gram_evec, &mut u_vec);
for x in &mut u_vec {
*x /= sigma;
}
u_vecs.push(u_vec);
v_vecs.push(gram_evec);
} else {
let mut v_vec = vec![F::zero(); m];
matvec_at(&gram_evec, &mut v_vec);
for x in &mut v_vec {
*x /= sigma;
}
u_vecs.push(gram_evec);
v_vecs.push(v_vec);
}
}
let u = Mat::from_fn(n, singular_values.len(), |i, j| u_vecs[j][i]);
let v = Mat::from_fn(m, singular_values.len(), |i, j| v_vecs[j][i]);
SvdResults {
u,
s: singular_values,
v,
}
}
#[cfg(test)]
mod tests {
use super::*;
use faer::Mat;
#[test]
fn test_parse_sparse_format() {
assert!(parse_compressed_sparse_format("csr").unwrap().is_csr());
assert!(parse_compressed_sparse_format("CSC").unwrap().is_csc());
assert!(parse_compressed_sparse_format("dense").is_none());
}
#[test]
fn test_from_dense_and_count_zeroes() {
let mat: Mat<f64> = Mat::from_fn(3, 2, |i, j| if i == j { (i + 1) as f64 } else { 0.0 });
let (total_zeroes, row_zeroes, col_zeroes) = count_zeroes(&mat.as_ref());
assert_eq!(total_zeroes, 4);
assert_eq!(row_zeroes, vec![1, 1, 2]);
assert_eq!(col_zeroes, vec![2, 2]);
let csr = CompressedSparseData2::<f64, f64>::from_dense_matrix(
mat.as_ref(),
CompressedSparseFormat::Csr,
);
assert_eq!(csr.shape, (3, 2));
assert_eq!(csr.data, vec![1.0, 2.0]);
}
#[test]
fn test_sparse_add_csr() {
let shape = (2, 2);
let a = CompressedSparseData2::<f64, f64>::new_csr(
&[1.0, 2.0],
&[0, 1],
&[0, 1, 2],
None,
shape,
);
let b = CompressedSparseData2::<f64, f64>::new_csr(
&[3.0, 4.0],
&[1, 1],
&[0, 1, 2],
None,
shape,
);
let c = sparse_add_csr(&a, &b);
assert_eq!(c.data, vec![1.0, 3.0, 6.0]);
assert_eq!(c.indices, vec![0, 1, 1]);
assert_eq!(c.indptr, vec![0, 2, 3]);
}
#[test]
fn test_csr_matvec() {
let a = CompressedSparseData2::<f64, f64>::new_csr(
&[1.0, 2.0, 3.0],
&[0, 1, 1],
&[0, 2, 3],
None,
(2, 2),
);
let vec = vec![2.0, 1.0];
let result = csr_matvec(&a, &vec);
assert_eq!(result, vec![4.0, 3.0]);
}
#[test]
fn test_lanczos_eigenpairs_logic() {
let data = vec![1.0, 2.0, 2.0, 4.0];
let indices = vec![0, 2, 0, 2];
let indptr = vec![0, 2, 2, 4, 4]; let shape = (4, 4);
let csr = CompressedSparseData2::<f64, f64>::new_csr(&data, &indices, &indptr, None, shape);
let (evals, evecs) = compute_largest_eigenpairs_lanczos(&csr, 1, 42);
assert!((evals[0] - 5.0).abs() < 1e-3);
let x_norm = 5.0_f32.sqrt();
let dot_x = (evecs[0][0] * 1.0 + evecs[2][0] * 2.0) / x_norm;
assert!(dot_x.abs() > 0.999);
}
#[test]
fn test_sparse_svd_lanczos_logic() {
let data = vec![2.0, 1.0, 4.0, 2.0];
let indices = vec![0, 2, 0, 2];
let indptr = vec![0, 0, 2, 2, 4];
let shape = (4, 3);
let csr = CompressedSparseData2::<f64, f64>::new_csr(&data, &indices, &indptr, None, shape);
let no_params: Option<&[f64]> = None;
let svd = sparse_svd_lanczos(&csr, 1, 42, false, no_params, no_params);
let u_col = svd.u.col(0);
let x_norm = 20.0_f64.sqrt();
let dot_u = (u_col[1] * 2.0 + u_col[3] * 4.0) / x_norm;
assert!(dot_u.abs() > 0.999);
let v_col = svd.v.col(0);
let y_norm = 1.25_f64.sqrt();
let dot_v = (v_col[0] * 1.0 + v_col[2] * 0.5) / y_norm;
assert!(dot_v.abs() > 0.999);
}
}