use indexmap::IndexSet;
use rayon::prelude::*;
use crate::core::math::vector_helpers::rank_vector;
use crate::prelude::*;
use crate::utils::simd::*;
pub fn pairwise_gene_correlations_in_memory<T: BixverseNumeric>(
matrix: &CompressedSparseData2<T, f32>,
gene_indices_1: &[usize],
gene_indices_2: &[usize],
spearman: bool,
) -> Result<Vec<f32>, BixverseErrors> {
assert_same_len!(gene_indices_1, gene_indices_2);
if !matrix.cs_type.is_csc() {
return Err(BixverseErrors::SparseMatrixMustBeCsc);
}
let data_norm = matrix
.data_2
.as_ref()
.ok_or(BixverseErrors::Data2NotAvailable)?;
let n_cells = matrix.shape.0;
let n_genes = matrix.shape.1;
let mut unique_genes: IndexSet<usize> = IndexSet::default();
for &idx in gene_indices_1.iter().chain(gene_indices_2.iter()) {
unique_genes.insert(idx);
}
let unique_vec: Vec<usize> = unique_genes.iter().copied().collect();
let standardised: Vec<Vec<f32>> = unique_vec
.par_iter()
.map(|&g| {
if g >= n_genes {
return Err(BixverseErrors::SliceIndexOutOfBounds {
index: g,
len: n_genes,
});
}
let start = matrix.indptr[g] as usize;
let end = matrix.indptr[g + 1] as usize;
let mut dense = vec![0_f32; n_cells];
for p in start..end {
dense[matrix.indices[p] as usize] = data_norm[p];
}
let dense = if spearman { rank_vector(&dense) } else { dense };
let mean = sum_simd_f32(&dense) / n_cells as f32;
let var = sum_squared_dev_simd_f32(&dense, mean) / (n_cells as f32 - 1.0);
let std = var.sqrt();
Ok(if std < 1e-8 {
vec![0_f32; n_cells]
} else {
dense.iter().map(|&x| (x - mean) / std).collect()
})
})
.collect::<Result<Vec<_>, _>>()?;
let denom = n_cells as f32 - 1.0;
let res = gene_indices_1
.par_iter()
.zip(gene_indices_2.par_iter())
.map(|(&g1, &g2)| {
let a = &standardised[unique_genes.get_index_of(&g1).unwrap()];
let b = &standardised[unique_genes.get_index_of(&g2).unwrap()];
let cor = a.iter().zip(b.iter()).map(|(x, y)| x * y).sum::<f32>() / denom;
cor.clamp(-1_f32, 1_f32)
})
.collect();
Ok(res)
}