use rayon::prelude::*;
use crate::prelude::*;
pub const METACELL_UMIS_REGULARISATION: f64 = 1.0 / 16.0;
fn metacell_profile(rows: &CompressedSparseData2<u32, f32>) -> Vec<(u32, u32)> {
let n_cells = rows.shape.0;
let umis: Vec<f64> = (0..n_cells)
.map(|i| {
rows.data[rows.indptr[i] as usize..rows.indptr[i + 1] as usize]
.iter()
.map(|&v| v as f64)
.sum()
})
.collect();
let total_umis: u64 = umis.iter().map(|&u| u as u64).sum();
let mut entries: Vec<(u32, f64, u32)> = Vec::new();
let mut weight_sum = 0.0f64;
let mut base = 0.0f64;
for i in 0..n_cells {
if umis[i] <= 0.0 {
continue;
}
let w = umis[i].log2();
let r = METACELL_UMIS_REGULARISATION / umis[i];
weight_sum += w;
base += w * r.ln();
for idx in rows.indptr[i] as usize..rows.indptr[i + 1] as usize {
let u = rows.data[idx];
if u > 0 {
let correction = w * ((u as f64 / umis[i] + r).ln() - r.ln());
entries.push((rows.indices[idx], correction, u));
}
}
}
entries.sort_unstable_by_key(|e| e.0);
let mut fractions: Vec<(u32, f64)> = Vec::new();
for group in entries.chunk_by(|a, b| a.0 == b.0) {
let correction: f64 = group.iter().map(|e| e.1).sum();
let fraction = if weight_sum > 0.0 {
((base + correction) / weight_sum).exp() - (base / weight_sum).exp()
} else {
group.iter().map(|e| e.2 as f64).sum::<f64>()
};
if fraction > 0.0 {
fractions.push((group[0].0, fraction));
}
}
let norm: f64 = fractions.iter().map(|f| f.1).sum();
if norm <= 0.0 {
return Vec::new();
}
fractions
.into_iter()
.filter_map(|(g, f)| {
let count = ((f / norm) as f32 * total_umis as f32).floor();
(count >= 1.0).then_some((g, count as u32))
})
.collect()
}
pub(super) fn collect_metacell_profiles<F>(
load_rows: F,
members_of_metacell: &[Vec<usize>],
n_genes: usize,
max_cells_per_load: usize,
) -> Result<(CompressedSparseData2<u32, f32>, Vec<f32>), ScErrors>
where
F: Fn(&[usize]) -> Result<CompressedSparseData2<u32, f32>, ScErrors>,
{
let mut profiles: Vec<Vec<(u32, u32)>> = Vec::with_capacity(members_of_metacell.len());
let mut start = 0;
while start < members_of_metacell.len() {
let mut end = start;
let mut cells = 0;
while end < members_of_metacell.len()
&& (end == start || cells + members_of_metacell[end].len() <= max_cells_per_load)
{
cells += members_of_metacell[end].len();
end += 1;
}
let batch = &members_of_metacell[start..end];
let elements: Vec<usize> = batch.iter().flatten().copied().collect();
let rows = load_rows(&elements)?;
let mut offsets = Vec::with_capacity(batch.len() + 1);
offsets.push(0usize);
for members in batch {
offsets.push(offsets.last().unwrap() + members.len());
}
let batch_profiles: Vec<Vec<(u32, u32)>> = (0..batch.len())
.into_par_iter()
.map(|m| {
let local: Vec<usize> = (offsets[m]..offsets[m + 1]).collect();
rows.slice_rows(&local).map(|r| metacell_profile(&r))
})
.collect::<Result<_, _>>()?;
profiles.extend(batch_profiles);
start = end;
}
let mut data = Vec::new();
let mut indices = Vec::new();
let mut indptr = vec![0u32];
for profile in profiles {
for (g, c) in profile {
indices.push(g);
data.push(c);
}
indptr.push(data.len() as u32);
}
let sizes = members_of_metacell.iter().map(|m| m.len() as f32).collect();
Ok((
CompressedSparseData2 {
data,
indices,
indptr,
cs_type: CompressedSparseFormat::Csr,
data_2: None,
shape: (members_of_metacell.len(), n_genes),
},
sizes,
))
}
#[cfg(test)]
mod tests {
use super::*;
fn csr(rows: &[&[(u32, u32)]], n_genes: usize) -> CompressedSparseData2<u32, f32> {
let mut data = Vec::new();
let mut indices = Vec::new();
let mut indptr = vec![0u32];
for row in rows {
for &(g, v) in *row {
indices.push(g);
data.push(v);
}
indptr.push(data.len() as u32);
}
CompressedSparseData2 {
data,
indices,
indptr,
cs_type: CompressedSparseFormat::Csr,
data_2: None,
shape: (rows.len(), n_genes),
}
}
#[test]
fn test_metacell_profile_identical_cells() {
let row: &[(u32, u32)] = &[(0, 60), (2, 30), (3, 10)];
let rows = csr(&[row, row, row], 4);
let profile = metacell_profile(&rows);
let genes: Vec<u32> = profile.iter().map(|p| p.0).collect();
assert_eq!(genes, vec![0, 2, 3]);
let total: u32 = profile.iter().map(|p| p.1).sum();
assert!((297..=300).contains(&total), "total {total}");
assert!(profile[0].1 > profile[1].1 && profile[1].1 > profile[2].1);
}
#[test]
fn test_collect_batches_metacells() {
let rows = csr(
&[
&[(0, 5)],
&[(0, 5)],
&[(1, 7)],
&[(1, 7)],
&[(0, 3), (1, 3)],
],
2,
);
let members = vec![vec![0, 1], vec![2, 3], vec![4]];
let load = |e: &[usize]| Ok(rows.slice_rows(e)?);
let (a, sizes) = collect_metacell_profiles(load, &members, 2, 2).unwrap();
let (b, _) = collect_metacell_profiles(load, &members, 2, 100).unwrap();
assert_eq!(a.indptr, b.indptr);
assert_eq!(a.data, b.data);
assert_eq!(a.shape, (3, 2));
assert_eq!(sizes, vec![2.0, 2.0, 1.0]);
}
}