use faer::Mat;
use crate::prelude::*;
pub struct Pile {
pub cell_indices: Vec<usize>,
pub raw: CompressedSparseData2<u32, f32>,
pub umis_per_cell: Vec<f32>,
pub n_genes: usize,
pub downsampled: Option<CompressedSparseData2<u32, f32>>,
pub selected_gene_indices: Option<Vec<usize>>,
pub selected_dense: Option<Mat<f32>>,
}
impl Pile {
pub fn from_reader(
reader: &ParallelSparseReader,
cell_indices: &[usize],
) -> Result<Self, BixverseErrors> {
let chunks = reader.read_cells_parallel(cell_indices)?;
let n_genes = reader.get_header().total_genes;
let raw = assemble_pile_csr(&chunks, n_genes);
let umis_per_cell = sum_rows_csr(&raw);
Ok(Self {
cell_indices: cell_indices.to_vec(),
raw,
umis_per_cell,
n_genes,
downsampled: None,
selected_gene_indices: None,
selected_dense: None,
})
}
#[inline]
pub fn n_cells(&self) -> usize {
self.cell_indices.len()
}
}
fn assemble_pile_csr(chunks: &[CsrCellChunk], n_genes: usize) -> CompressedSparseData2<u32, f32> {
let n_cells = chunks.len();
let total_nnz: usize = chunks.iter().map(|c| c.indices.len()).sum();
let mut data: Vec<u32> = Vec::with_capacity(total_nnz);
let mut indices: Vec<usize> = Vec::with_capacity(total_nnz);
let mut indptr: Vec<usize> = Vec::with_capacity(n_cells + 1);
indptr.push(0);
for chunk in chunks {
for v in chunk.data_raw.iter() {
data.push(v);
}
for &idx in &chunk.indices {
indices.push(idx as usize);
}
indptr.push(data.len());
}
CompressedSparseData2 {
data,
indices: indices.index_cast(),
indptr: indptr.index_cast(),
cs_type: CompressedSparseFormat::Csr,
data_2: None,
shape: (n_cells, n_genes),
}
}
fn sum_rows_csr(mat: &CompressedSparseData2<u32, f32>) -> Vec<f32> {
let n_rows = mat.shape.0;
let mut sums = vec![0.0f32; n_rows];
for i in 0..n_rows {
let s: u64 = (mat.indptr[i]..mat.indptr[i + 1])
.map(|idx| mat.data[idx as usize] as u64)
.sum();
sums[i] = s as f32;
}
sums
}
#[cfg(test)]
mod tests {
use super::*;
fn make_csr(
data: Vec<u32>,
indices: Vec<usize>,
indptr: Vec<usize>,
shape: (usize, usize),
) -> CompressedSparseData2<u32, f32> {
CompressedSparseData2 {
data,
indices: indices.index_cast(),
indptr: indptr.index_cast(),
cs_type: CompressedSparseFormat::Csr,
data_2: None,
shape,
}
}
#[test]
fn sum_rows_csr_basic() {
let mat = make_csr(vec![3, 5, 2], vec![0, 2, 1], vec![0, 2, 3], (2, 3));
let sums = sum_rows_csr(&mat);
assert_eq!(sums, vec![8.0, 2.0]);
}
#[test]
fn sum_rows_csr_empty_row() {
let mat = make_csr(vec![], vec![], vec![0, 0], (1, 5));
let sums = sum_rows_csr(&mat);
assert_eq!(sums, vec![0.0]);
}
#[test]
fn pile_n_cells_and_field_access() {
let pile = Pile {
cell_indices: vec![10, 20, 30],
raw: make_csr(vec![1, 2, 3], vec![0, 1, 2], vec![0, 1, 2, 3], (3, 5)),
umis_per_cell: vec![1.0, 2.0, 3.0],
n_genes: 5,
downsampled: None,
selected_gene_indices: None,
selected_dense: None,
};
assert_eq!(pile.n_cells(), 3);
assert_eq!(pile.cell_indices, vec![10, 20, 30]);
assert_eq!(pile.n_genes, 5);
assert!(pile.downsampled.is_none());
}
}