use rayon::prelude::*;
use std::time::Instant;
use crate::prelude::*;
use bixverse_sc_data::data_io::CsrCellChunk;
const QC_CELL_BATCH_SIZE: usize = 100_000;
#[inline]
fn fraction_of_library(numerator: f32, library_size: usize) -> f32 {
if library_size == 0 {
0.0
} else {
numerator / library_size as f32
}
}
fn top_genes_fractions(cell_chunks: &[CsrCellChunk], top_n_values: &[usize]) -> Vec<Vec<f32>> {
let n_top = top_n_values.len();
if n_top == 0 {
return Vec::new();
}
let mut descending: Vec<usize> = (0..n_top).collect();
descending.sort_unstable_by(|&a, &b| top_n_values[b].cmp(&top_n_values[a]));
let mut flat = vec![0.0_f32; cell_chunks.len() * n_top];
flat.par_chunks_mut(n_top)
.zip(cell_chunks.par_iter())
.for_each_init(Vec::<u32>::new, |gene_counts, (out, chunk)| {
gene_counts.clear();
gene_counts.extend(chunk.data_raw.iter());
let mut bound = gene_counts.len();
for &slot in &descending {
let top_n = top_n_values[slot];
out[slot] = if gene_counts.len() <= top_n {
1.0
} else {
if top_n < bound {
gene_counts[..bound].select_nth_unstable_by(top_n, |a, b| b.cmp(a));
bound = top_n;
}
let top_sum = gene_counts[..top_n].iter().map(|&x| x as f32).sum::<f32>();
fraction_of_library(top_sum, chunk.library_size)
};
}
});
(0..n_top)
.map(|slot| flat.iter().skip(slot).step_by(n_top).copied().collect())
.collect()
}
fn gene_set_mask(gene_set: &[u32], n_genes: usize) -> Vec<bool> {
let mut mask = vec![false; n_genes];
for &g in gene_set {
if let Some(slot) = mask.get_mut(g as usize) {
*slot = true;
}
}
mask
}
fn gene_set_fractions(cell_chunks: &[CsrCellChunk], mask: &[bool]) -> Vec<f32> {
cell_chunks
.par_iter()
.map(|chunk| {
let total_sum = chunk
.indices
.iter()
.zip(chunk.data_raw.iter())
.filter(|(col_idx, _)| mask.get(**col_idx as usize).copied().unwrap_or(false))
.map(|(_, val)| val)
.sum::<u32>() as f32;
fraction_of_library(total_sum, chunk.library_size)
})
.collect()
}
pub fn get_top_genes_perc<S: SingleCellReading>(
reader: &S,
top_n_values: &[usize],
cell_indices: &[usize],
verbose: usize,
) -> Result<Vec<Vec<f32>>, ScErrors> {
let verbosity = parse_verbosity_level(verbose);
let start_reading = Instant::now();
let cell_chunks = reader.read_cells_parallel(cell_indices)?;
let end_read = start_reading.elapsed();
if verbosity.normal_verbosity() {
println!("Load in data: {:.2?}", end_read);
}
let start_calculations = Instant::now();
let results = top_genes_fractions(&cell_chunks, top_n_values);
let end_calculations = start_calculations.elapsed();
if verbosity.normal_verbosity() {
println!(
"Finished the top genes proportion calculations: {:.2?}",
end_calculations
);
}
Ok(results)
}
pub fn get_top_genes_perc_streaming<S: SingleCellReading>(
reader: &S,
top_n_values: &[usize],
cell_indices: &[usize],
verbose: usize,
) -> Result<Vec<Vec<f32>>, ScErrors> {
let verbosity = parse_verbosity_level(verbose);
let start_total = Instant::now();
let mut results: Vec<Vec<f32>> = vec![Vec::new(); top_n_values.len()];
if verbosity.normal_verbosity() {
println!("Using a streaming approach for top gene percentage calculations.");
}
for batch_start in (0..cell_indices.len()).step_by(QC_CELL_BATCH_SIZE) {
let batch_end = (batch_start + QC_CELL_BATCH_SIZE).min(cell_indices.len());
let cell_batch = &cell_indices[batch_start..batch_end];
let cell_chunks = reader.read_cells_parallel(cell_batch)?;
for (slot, proportions) in top_genes_fractions(&cell_chunks, top_n_values)
.into_iter()
.enumerate()
{
results[slot].extend(proportions);
}
if verbosity.detailed_verbosity() {
report_decile_progress(
batch_end,
batch_start,
cell_indices.len(),
"cells",
start_total.elapsed(),
);
}
}
let end_total = start_total.elapsed();
if verbosity.normal_verbosity() {
println!(
"Finished the top genes proportion calculations: {:.2?}",
end_total
);
}
Ok(results)
}
pub fn get_gene_set_perc<S: SingleCellReading>(
reader: &S,
gene_indices: Vec<Vec<u32>>,
cell_indices: &[usize],
verbose: usize,
) -> Result<Vec<Vec<f32>>, ScErrors> {
let verbosity = parse_verbosity_level(verbose);
let start_reading = Instant::now();
let cell_chunks = reader.read_cells_parallel(cell_indices)?;
let end_read = start_reading.elapsed();
if verbosity.normal_verbosity() {
println!("Load in data: {:.2?}", end_read);
}
let start_calculations = Instant::now();
let n_genes = reader.get_header().total_genes;
let results: Vec<Vec<f32>> = gene_indices
.iter()
.map(|gene_set| gene_set_fractions(&cell_chunks, &gene_set_mask(gene_set, n_genes)))
.collect();
let end_calculations = start_calculations.elapsed();
if verbosity.normal_verbosity() {
println!(
"Finished the gene set proportion calculations: {:.2?}",
end_calculations
);
}
Ok(results)
}
pub fn get_gene_set_perc_streaming<S: SingleCellReading>(
reader: &S,
gene_indices: Vec<Vec<u32>>,
cell_indices: &[usize],
verbose: usize,
) -> Result<Vec<Vec<f32>>, ScErrors> {
let verbosity = parse_verbosity_level(verbose);
let start_total = Instant::now();
let mut results: Vec<Vec<f32>> = vec![Vec::new(); gene_indices.len()];
let n_genes = reader.get_header().total_genes;
let masks: Vec<Vec<bool>> = gene_indices
.iter()
.map(|gs| gene_set_mask(gs, n_genes))
.collect();
if verbosity.normal_verbosity() {
println!("Using a streaming approach for gene set percentage calculation.");
}
for batch_start in (0..cell_indices.len()).step_by(QC_CELL_BATCH_SIZE) {
let batch_end = (batch_start + QC_CELL_BATCH_SIZE).min(cell_indices.len());
let cell_batch = &cell_indices[batch_start..batch_end];
let cell_chunks = reader.read_cells_parallel(cell_batch)?;
for (gs_idx, mask) in masks.iter().enumerate() {
results[gs_idx].extend(gene_set_fractions(&cell_chunks, mask));
}
if verbosity.detailed_verbosity() {
report_decile_progress(
batch_end,
batch_start,
cell_indices.len(),
"cells",
start_total.elapsed(),
);
}
}
let end_total = start_total.elapsed();
if verbosity.normal_verbosity() {
println!(
"Finished the gene set proportion calculations: {:.2?}",
end_total
);
}
Ok(results)
}