use ann_search_rs::utils::dist::SimdDistance;
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::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,
}
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,
}
}
#[allow(dead_code)]
impl CompressedSparseFormat {
pub fn is_csc(&self) -> bool {
matches!(self, CompressedSparseFormat::Csc)
}
pub fn is_csr(&self) -> bool {
matches!(self, CompressedSparseFormat::Csr)
}
}
#[derive(Debug, Clone)]
pub struct CompressedSparseData<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> CompressedSparseData<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 {
match self.cs_type {
CompressedSparseFormat::Csc => csc_to_csr(self),
CompressedSparseFormat::Csr => csr_to_csc(self),
}
}
pub fn transpose_and_convert(&self) -> Self {
match self.cs_type {
CompressedSparseFormat::Csr => {
let csc_version = csr_to_csc(self);
CompressedSparseData {
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 => {
CompressedSparseData {
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 {
CompressedSparseData {
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 csc_to_csr<T, U>(sparse_data: &CompressedSparseData<T, U>) -> CompressedSparseData<T, U>
where
T: BixverseNumeric,
U: BixverseNumeric,
{
let (nrow, _) = sparse_data.shape();
let nnz = sparse_data.get_nnz();
let mut row_ptr = vec![0; nrow + 1];
for &r in &sparse_data.indices {
row_ptr[r + 1] += 1;
}
for i in 0..nrow {
row_ptr[i + 1] += row_ptr[i];
}
let mut csr_data = vec![T::default(); nnz];
let mut csr_data2 = sparse_data.data_2.as_ref().map(|_| vec![U::default(); nnz]);
let mut csr_col_ind = vec![0; nnz];
let mut next = row_ptr[..nrow].to_vec();
for col in 0..(sparse_data.indptr.len() - 1) {
for idx in sparse_data.indptr[col]..sparse_data.indptr[col + 1] {
let row = sparse_data.indices[idx];
let pos = next[row];
csr_data[pos] = sparse_data.data[idx];
csr_col_ind[pos] = col;
if let (Some(source_data2), Some(csr_d2)) = (&sparse_data.data_2, &mut csr_data2) {
csr_d2[pos] = source_data2[idx];
}
next[row] += 1;
}
}
CompressedSparseData {
data: csr_data,
indices: csr_col_ind,
indptr: row_ptr,
cs_type: CompressedSparseFormat::Csr,
data_2: csr_data2,
shape: sparse_data.shape(),
}
}
pub fn csr_to_csc<T, U>(sparse_data: &CompressedSparseData<T, U>) -> CompressedSparseData<T, U>
where
T: BixverseNumeric,
U: BixverseNumeric,
{
let nnz = sparse_data.get_nnz();
let (_, ncol) = sparse_data.shape();
let mut col_ptr = vec![0; ncol + 1];
for &c in &sparse_data.indices {
col_ptr[c + 1] += 1;
}
for i in 0..ncol {
col_ptr[i + 1] += col_ptr[i];
}
let mut csc_data = vec![T::default(); nnz];
let mut csc_data2 = sparse_data.data_2.as_ref().map(|_| vec![U::default(); nnz]);
let mut csc_row_ind = vec![0; nnz];
let mut next = col_ptr[..ncol].to_vec();
for row in 0..(sparse_data.indptr.len() - 1) {
for idx in sparse_data.indptr[row]..sparse_data.indptr[row + 1] {
let col = sparse_data.indices[idx];
let pos = next[col];
csc_data[pos] = sparse_data.data[idx];
csc_row_ind[pos] = row;
if let (Some(source_data2), Some(csc_d2)) = (&sparse_data.data_2, &mut csc_data2) {
csc_d2[pos] = source_data2[idx];
}
next[col] += 1;
}
}
CompressedSparseData {
data: csc_data,
indices: csc_row_ind,
indptr: col_ptr,
cs_type: CompressedSparseFormat::Csc,
data_2: csc_data2,
shape: sparse_data.shape(),
}
}
pub fn coo_to_csr<T>(
rows: &[usize],
cols: &[usize],
vals: &[T],
shape: (usize, usize),
) -> CompressedSparseData<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];
}
CompressedSparseData::new_csr(&data, &indices, &indptr, None, shape)
}
pub fn coo_to_csr_presorted<T>(
rows: &[usize],
cols: &[usize],
vals: &[T],
shape: (usize, usize),
) -> CompressedSparseData<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);
}
}
CompressedSparseData::new_csr(&data, &indices, &indptr, None, shape)
}
pub fn sparse_add_csr<T>(
a: &CompressedSparseData<T>,
b: &CompressedSparseData<T>,
) -> CompressedSparseData<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: &CompressedSparseData<T>,
scalar: T,
) -> CompressedSparseData<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();
CompressedSparseData::new_csr(&data, &a.indices, &a.indptr, None, a.shape)
}
pub fn sparse_subtract_csr<T>(
a: &CompressedSparseData<T>,
b: &CompressedSparseData<T>,
) -> CompressedSparseData<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: &CompressedSparseData<T>,
b: &CompressedSparseData<T>,
) -> CompressedSparseData<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 CompressedSparseData<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 CompressedSparseData<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: &CompressedSparseData<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: CompressedSparseData<T>) -> CompressedSparseData<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: &CompressedSparseData<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: &CompressedSparseData<T>,
b: &CompressedSparseData<T>,
) -> CompressedSparseData<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());
}
CompressedSparseData::new_csr(&data, &indices, &indptr, None, (nrows, ncols))
}
fn dot<T>(a: &[T], b: &[T]) -> T
where
T: SimdDistance,
{
assert_same_len!(a, b);
T::dot_simd(a, b)
}
fn norm<T>(v: &[T]) -> T
where
T: SimdDistance + BixverseFloat,
{
let dot = dot(v, v);
dot.sqrt()
}
fn normalise<T>(v: &mut [T])
where
T: SimdDistance + 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: &CompressedSparseData<T>,
n_components: usize,
seed: u64,
) -> (Vec<f32>, Vec<Vec<f32>>)
where
T: BixverseNumeric + SimdDistance + 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.fill(0.0);
for i in 0..n {
for idx in csr.indptr[i]..csr.indptr[i + 1] {
let j = csr.indices[idx];
y[i] += data_f64[idx] * x[j];
}
}
};
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];
}
}
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)
}