use indexmap::IndexSet;
use rand::prelude::IndexedRandom;
use rand::rngs::StdRng;
use rand::{Rng, SeedableRng};
use rayon::prelude::*;
use rustc_hash::FxHashMap;
use std::time::Instant;
use crate::prelude::*;
struct GeneBins {
gene_to_bin: FxHashMap<usize, usize>,
bins: Vec<Vec<usize>>,
}
fn get_average_expression(
f_path_gene: &str,
cell_set: &IndexSet<u32>,
streaming: bool,
) -> Result<Vec<(usize, f32)>, BixverseErrors> {
let reader = ParallelSparseReader::new(f_path_gene)?;
let total_genes = reader.get_header().total_genes;
if streaming {
const CHUNK_SIZE: usize = 500;
let gene_indices: Vec<usize> = (0..total_genes).collect();
let mut results: Vec<(usize, f32)> = Vec::with_capacity(total_genes);
for chunk in gene_indices.chunks(CHUNK_SIZE) {
let gene_chunks = reader.read_gene_parallel(chunk)?;
let chunk_results: Vec<(usize, f32)> = gene_chunks
.par_iter()
.map(|gene| gene.calculate_avg_exp(cell_set))
.collect();
results.extend(chunk_results);
}
Ok(results)
} else {
let gene_chunks = reader.get_all_genes()?;
let results: Vec<(usize, f32)> = gene_chunks
.par_iter()
.map(|gene| gene.calculate_avg_exp(cell_set))
.collect();
Ok(results)
}
}
fn create_expression_bins(gene_means: &[(usize, f32)], nbin: usize, seed: &usize) -> GeneBins {
let mut sorted_genes = gene_means.to_vec();
let mut rng = StdRng::seed_from_u64(*seed as u64);
for (_, exp) in sorted_genes.iter_mut() {
*exp += rng.random::<f32>() / 1e30;
}
sorted_genes.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap());
let total_genes = sorted_genes.len();
let genes_per_bin = (total_genes as f32 / nbin as f32).ceil() as usize;
let mut gene_to_bin = FxHashMap::default();
let mut bins: Vec<Vec<usize>> = vec![Vec::new(); nbin];
for (i, (gene_idx, _)) in sorted_genes.iter().enumerate() {
let bin_id = (i / genes_per_bin).min(nbin - 1);
gene_to_bin.insert(*gene_idx, bin_id);
bins[bin_id].push(*gene_idx);
}
GeneBins { gene_to_bin, bins }
}
fn sample_control_genes(
gene_set: &[usize],
gene_bins: &GeneBins,
ctrl: usize,
rng: &mut impl Rng,
) -> Vec<usize> {
let mut controls = IndexSet::new();
for &gene_idx in gene_set {
if let Some(&bin_id) = gene_bins.gene_to_bin.get(&gene_idx) {
let bin_genes = &gene_bins.bins[bin_id];
let sampled = bin_genes.choose_multiple(rng, ctrl.min(bin_genes.len()));
controls.extend(sampled.copied());
}
}
controls.into_iter().collect()
}
fn calculate_cell_module_score(
cell: &CsrCellChunk,
gene_set: &[usize],
control_set: &[usize],
) -> f32 {
let lookup = |idx: usize| -> f32 {
match cell.indices.binary_search(&(idx as u32)) {
Ok(pos) => cell.data_norm[pos].to_f32(),
Err(_) => 0.0,
}
};
let gene_mean = if gene_set.is_empty() {
0.0
} else {
gene_set.iter().map(|&idx| lookup(idx)).sum::<f32>() / gene_set.len() as f32
};
let ctrl_mean = if control_set.is_empty() {
0.0
} else {
control_set.iter().map(|&idx| lookup(idx)).sum::<f32>() / control_set.len() as f32
};
gene_mean - ctrl_mean
}
fn calculate_module_scores(
f_path_cell: &str,
gene_sets: &[Vec<usize>],
cells_to_keep: &[usize],
gene_bins: &GeneBins,
ctrl: usize,
seed: &usize,
) -> Result<Vec<Vec<f32>>, BixverseErrors> {
let mut rng = StdRng::seed_from_u64(*seed as u64);
let control_sets: Vec<Vec<usize>> = gene_sets
.iter()
.map(|gene_set| sample_control_genes(gene_set, gene_bins, ctrl, &mut rng))
.collect();
let reader = ParallelSparseReader::new(f_path_cell)?;
let cell_chunks = reader.read_cells_parallel(cells_to_keep)?;
let all_scores: Vec<Vec<f32>> = cell_chunks
.par_iter()
.map(|cell| {
gene_sets
.iter()
.zip(control_sets.iter())
.map(|(gene_set, control_set)| {
calculate_cell_module_score(cell, gene_set, control_set)
})
.collect()
})
.collect();
let mut results: Vec<Vec<f32>> = vec![Vec::with_capacity(cell_chunks.len()); gene_sets.len()];
for cell_scores in all_scores {
for (module_idx, score) in cell_scores.into_iter().enumerate() {
results[module_idx].push(score);
}
}
Ok(results)
}
fn calculate_module_scores_streaming(
f_path_cell: &str,
gene_sets: &[Vec<usize>],
cells_to_keep: &[usize],
gene_bins: &GeneBins,
ctrl: usize,
seed: &usize,
verbose: bool,
) -> Result<Vec<Vec<f32>>, BixverseErrors> {
const CHUNK_SIZE: usize = 50000;
let mut rng = StdRng::seed_from_u64(*seed as u64);
let control_sets: Vec<Vec<usize>> = gene_sets
.iter()
.map(|gene_set| sample_control_genes(gene_set, gene_bins, ctrl, &mut rng))
.collect();
let reader = ParallelSparseReader::new(f_path_cell)?;
let total_chunks = cells_to_keep.len().div_ceil(CHUNK_SIZE);
let mut results: Vec<Vec<f32>> = vec![Vec::with_capacity(cells_to_keep.len()); gene_sets.len()];
for (chunk_idx, cell_indices_chunk) in cells_to_keep.chunks(CHUNK_SIZE).enumerate() {
let start = Instant::now();
let cell_chunks = reader.read_cells_parallel(cell_indices_chunk)?;
let chunk_scores: Vec<Vec<f32>> = cell_chunks
.par_iter()
.map(|cell| {
gene_sets
.iter()
.zip(control_sets.iter())
.map(|(gene_set, control_set)| {
calculate_cell_module_score(cell, gene_set, control_set)
})
.collect()
})
.collect();
for cell_scores in chunk_scores {
for (module_idx, score) in cell_scores.into_iter().enumerate() {
results[module_idx].push(score);
}
}
if verbose {
let elapsed = start.elapsed();
let pct = ((chunk_idx + 1) as f32 / total_chunks as f32) * 100.0;
println!(
"Chunk {} of {} (took {:.2?}, {:.1}% complete)",
chunk_idx + 1,
total_chunks,
elapsed,
pct
);
}
}
Ok(results)
}
#[allow(clippy::too_many_arguments)]
pub fn calculate_module_scores_main(
f_path_gene: &str,
f_path_cell: &str,
gene_sets: &[Vec<usize>],
cells_to_use: &[usize],
nbin: usize,
ctrl: usize,
streaming: bool,
seed: usize,
verbose: usize,
) -> Result<Vec<Vec<f32>>, BixverseErrors> {
let verbosity = parse_verbosity_level(verbose);
let cell_set: IndexSet<u32> = cells_to_use.iter().map(|&x| x as u32).collect();
let start_total = Instant::now();
if verbosity.normal_verbosity() {
println!("Calculating the average expression across the cells.")
}
let start_avg_exp = Instant::now();
let avg_exp = get_average_expression(f_path_gene, &cell_set, streaming)?;
let end_evg_exp = start_avg_exp.elapsed();
if verbosity.normal_verbosity() {
println!(
"Finished the calculation of the avg gene expression in {:.2?}",
end_evg_exp
);
println!("Calculating the module scores now.")
}
let start_modules = Instant::now();
let gene_bins = create_expression_bins(&avg_exp, nbin, &seed);
let module_scores = if streaming {
calculate_module_scores_streaming(
f_path_cell,
gene_sets,
cells_to_use,
&gene_bins,
ctrl,
&seed,
verbosity.detailed_verbosity(),
)
} else {
calculate_module_scores(
f_path_cell,
gene_sets,
cells_to_use,
&gene_bins,
ctrl,
&seed,
)
}?;
let end_modules = start_modules.elapsed();
let end_total = start_total.elapsed();
if verbosity.normal_verbosity() {
println!(
"Finished the calculation of the modules in {:.2?}",
end_modules
);
println!("Total runtime: {:.2?}", end_total)
}
Ok(module_scores)
}