use faer::Mat;
use rayon::prelude::*;
use crate::prelude::*;
#[derive(Clone, Debug)]
pub struct ClusterExpressionStats<T> {
pub mean: Mat<T>,
pub frac: Mat<T>,
}
pub fn compute_cluster_expression_stats<T, S>(
reader: &S,
gene_indices: &[usize],
clusters: &[Vec<usize>],
) -> Result<ClusterExpressionStats<T>, ScErrors>
where
T: BixverseFloat + Send + Sync,
S: SingleCellReading,
{
if !reader.is_gene_based() {
return Err(ScDataErrors::ReaderModeMismatch {
actual: "cell-based",
requested: "gene-based",
}
.into());
}
let n_cells = reader.get_header().total_cells;
let n_genes = gene_indices.len();
let n_clusters = clusters.len();
let mut cell_to_cluster: Vec<Option<u32>> = vec![None; n_cells];
for (cluster_idx, cells) in clusters.iter().enumerate() {
for &c in cells {
cell_to_cluster[c] = Some(cluster_idx as u32);
}
}
let cluster_sizes: Vec<usize> = clusters.iter().map(|c| c.len()).collect();
let chunks = reader.read_gene_parallel(gene_indices)?;
let aggregated: Vec<(Vec<T>, Vec<T>)> = chunks
.par_iter()
.map(|chunk| {
let mut sum = vec![T::zero(); n_clusters];
let mut count = vec![0usize; n_clusters];
for (i, &cell_idx) in chunk.indices.iter().enumerate() {
if let Some(cluster) = cell_to_cluster[cell_idx as usize] {
let v = T::from_f32(chunk.data_norm[i].to_f32()).unwrap();
sum[cluster as usize] += v;
count[cluster as usize] += 1;
}
}
let mean: Vec<T> = sum
.iter()
.zip(cluster_sizes.iter())
.map(|(&s, &n)| {
if n > 0 {
s / T::from_usize(n).unwrap()
} else {
T::zero()
}
})
.collect();
let frac: Vec<T> = count
.iter()
.zip(cluster_sizes.iter())
.map(|(&c, &n)| {
if n > 0 {
T::from_usize(c).unwrap() / T::from_usize(n).unwrap()
} else {
T::zero()
}
})
.collect();
(mean, frac)
})
.collect();
let mut mean_mat = Mat::zeros(n_genes, n_clusters);
let mut frac_mat = Mat::zeros(n_genes, n_clusters);
for (i, (mean_row, frac_row)) in aggregated.into_iter().enumerate() {
for (j, v) in mean_row.into_iter().enumerate() {
mean_mat[(i, j)] = v;
}
for (j, v) in frac_row.into_iter().enumerate() {
frac_mat[(i, j)] = v;
}
}
Ok(ClusterExpressionStats {
mean: mean_mat,
frac: frac_mat,
})
}