gsva-rust 0.1.0

Pure-Rust port of the GSVA family of gene-set enrichment methods (GSVA, ssGSEA, z-score, PLAGE), validated for numeric parity against the Bioconductor GSVA package.
Documentation
//! Parallelism helpers — the single place the `parallel`/serial split lives.
//!
//! With the default `parallel` feature these dispatch to rayon over the
//! independent outer axis (samples for gsva/ssgsea, gene sets for zscore/plage,
//! genes for the kcdf step); with `--no-default-features` they are plain serial
//! loops and the crate has no dependencies.
//!
//! The parallel axis only ever reorders *independent* units of work — each
//! unit's arithmetic (rank, walk, sum, SVD) is byte-for-byte the same as in the
//! serial build, and results are written back in index order — so the output is
//! **bit-identical** to the serial build and to R, never an approximation.

/// Map `f` over `0..n`, returning the results in index order.
///
/// Parallel over the range when the `parallel` feature is enabled; otherwise a
/// serial `map().collect()`. Used by gsva/ssgsea to compute one independent
/// score *column* (all gene sets for one sample) per unit of work.
#[cfg(feature = "parallel")]
pub(crate) fn map_collect<T, F>(n: usize, f: F) -> Vec<T>
where
    F: Fn(usize) -> T + Sync + Send,
    T: Send,
{
    use rayon::prelude::*;
    (0..n).into_par_iter().map(f).collect()
}

#[cfg(not(feature = "parallel"))]
pub(crate) fn map_collect<T, F>(n: usize, f: F) -> Vec<T>
where
    F: Fn(usize) -> T,
{
    (0..n).map(f).collect()
}

/// Fill `data` in contiguous `chunk_len`-sized chunks, invoking `f(chunk_index,
/// chunk)` on each.
///
/// Parallel over chunks when the `parallel` feature is enabled; otherwise a
/// serial `chunks_mut`. Used where each independent unit of work owns one
/// contiguous output row: the kcdf step (one gene per row) and zscore/plage
/// (one gene set per row).
#[cfg(feature = "parallel")]
pub(crate) fn fill_chunks_mut<F>(data: &mut [f64], chunk_len: usize, f: F)
where
    F: Fn(usize, &mut [f64]) + Sync + Send,
{
    use rayon::prelude::*;
    data.par_chunks_mut(chunk_len)
        .enumerate()
        .for_each(|(i, chunk)| f(i, chunk));
}

#[cfg(not(feature = "parallel"))]
pub(crate) fn fill_chunks_mut<F>(data: &mut [f64], chunk_len: usize, f: F)
where
    F: Fn(usize, &mut [f64]),
{
    data.chunks_mut(chunk_len)
        .enumerate()
        .for_each(|(i, chunk)| f(i, chunk));
}