Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,7 @@ Imports:
ComplexHeatmap,
RANN
Suggests:
RidgeFast,
RidgeCuda,
FlashReg,
rhdf5,
shiny,
shinyWidgets,
Expand Down
8 changes: 6 additions & 2 deletions R/activity.R
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,12 @@
#' @param nrand Number of randomization in the permutation test, with a default value of 1000.
#' @param ncores Number of threads for accelerator backends (ignored by pure R). Default 1.
#' @param backend One of \code{"auto"}, \code{"gpu"}, \code{"cpu-fast"}, \code{"cpu-pure"}.
#' \code{"auto"} picks GPU (RidgeCuda) > CPU-fast (RidgeFast) > CPU-pure
#' depending on what is installed. Default \code{"auto"}.
#' \code{"auto"} picks the best FlashReg backend available:
#' GPU (\code{FlashReg::ridge(backend="cuda_native")}) > CPU-fast
#' (\code{FlashReg::ridge(backend="omp")}) > CPU-pure (in-house loop)
#' depending on what FlashReg detects at install / load time. Default
#' \code{"auto"}. Legacy backend names \code{"gpu"} / \code{"cpu-fast"}
#' are kept for backward compatibility.
#' @param rng_method RNG for permutations. \code{"mt19937"} (default) is
#' GSL-compatible MT19937 seed 0 — bit-identical across backends when
#' \code{ncores=1}. Accelerators may support \code{"srand"} for faster,
Expand Down
105 changes: 65 additions & 40 deletions R/ridge.R
Original file line number Diff line number Diff line change
Expand Up @@ -49,30 +49,43 @@
list(beta = beta, se = se, zscore = zscore, pvalue = pvalue)
}

#' Resolve backend based on installed packages and GPU availability
#' Resolve backend based on installed FlashReg and GPU availability
#'
#' Legacy backend names ("gpu", "cpu-fast", "cpu-pure") are still
#' accepted and remap onto the corresponding FlashReg backends so
#' downstream code keeps working without changes.
#' @keywords internal
.resolve_backend <- function(backend = c("auto", "gpu", "cpu-fast", "cpu-pure")) {
backend <- match.arg(backend)
if (backend != "auto") return(backend)

if (requireNamespace("RidgeCuda", quietly = TRUE)) {
gpu_ok <- tryCatch(
RidgeCuda::check_cuda_available(),
error = function(e) FALSE
)
if (requireNamespace("FlashReg", quietly = TRUE)) {
gpu_ok <- tryCatch(FlashReg::cuda_available(), error = function(e) FALSE)
if (isTRUE(gpu_ok)) return("gpu")
return("cpu-fast")
}
if (requireNamespace("RidgeFast", quietly = TRUE)) return("cpu-fast")
"cpu-pure"
}

#' Dispatch ridge+permutation call to installed backend
# Map SecAct's legacy backend name onto FlashReg's backend name.
.secact_to_flashreg_backend <- function(chosen) {
switch(chosen,
gpu = "cuda_native",
`cpu-fast` = "omp",
`cpu-pure` = "pure_r",
stop("internal: unknown SecAct backend label '", chosen, "'"))
}

#' Dispatch ridge+permutation call to FlashReg or the in-house pure-R loop
#'
#' Picks GPU (\code{FlashReg::ridge(backend="cuda_native")}) > CPU-fast
#' (\code{FlashReg::ridge(backend="omp")}) > CPU-pure (this package's
#' \code{.ridge_pureR}). FlashReg replaces the historical pair of
#' optional packages (RidgeFast for CPU, RidgeCuda for GPU); legacy
#' backend names are kept as aliases for backward compatibility.
#'
#' Picks GPU (RidgeCuda) > CPU-fast (RidgeFast) > CPU-pure (this
#' package) based on \code{backend}. All three accelerators share the
#' API \code{ridge(X, Y, lambda, nrand, ncores, rng_method)}.
#' With \code{rng_method="mt19937"} and \code{ncores=1}, results are
#' bit-identical across backends.
#' With \code{rng_method="mt19937"} and \code{ncores=1}, FlashReg's
#' backends are bit-identical to the pure-R loop.
#'
#' @keywords internal
.ridge_dispatch <- function(X, Y, lambda, nrand,
Expand All @@ -86,15 +99,18 @@
", ncores=", ncores, ")")
}

if (chosen == "gpu") {
RidgeCuda::ridge(X = X, Y = Y, lambda = lambda, nrand = nrand,
ncores = ncores, rng_method = rng_method)
} else if (chosen == "cpu-fast") {
RidgeFast::ridge(X = X, Y = Y, lambda = lambda, nrand = nrand,
ncores = ncores, rng_method = rng_method)
if (chosen %in% c("gpu", "cpu-fast")) {
if (!requireNamespace("FlashReg", quietly = TRUE)) {
stop("backend='", chosen, "' requires the FlashReg package. ",
"Install it from https://github.com/data2intelligence/FlashReg ",
"or set backend='cpu-pure' to use the in-house pure-R loop.")
}
FlashReg::ridge(X = X, Y = Y, lambda = lambda, nrand = nrand,
backend = .secact_to_flashreg_backend(chosen),
ncores = ncores, rng_method = rng_method)
} else {
if (rng_method != "mt19937") {
stop("rng_method='", rng_method, "' requires RidgeFast or RidgeCuda; ",
stop("rng_method='", rng_method, "' requires FlashReg; ",
"pure-R supports only 'mt19937'.")
}
.ridge_pureR(X, Y, lambda, nrand)
Expand All @@ -111,9 +127,11 @@
#' Dispatch ridge+permutation over Y-column batches
#'
#' Memory-efficient version of \code{\link{.ridge_dispatch}}. Processes
#' \code{Y} in column-batches, forwarding to the chosen backend's
#' \code{ridge_batch()} (RidgeCuda > RidgeFast) or to the in-house
#' pure-R batched path. Supports HDF5 Y input and streaming HDF5 output.
#' \code{Y} in column-batches, calling the resolved backend on each
#' batch. With FlashReg installed, the per-batch kernel is the
#' \code{FlashReg::ridge()} dispatcher (GPU or C+OpenMP CPU);
#' otherwise the pure-R loop is used. The same column-batching code
#' handles HDF5 Y input and streaming HDF5 output for all backends.
#'
#' @keywords internal
.ridge_batch_dispatch <- function(X, Y, lambda, nrand,
Expand All @@ -130,26 +148,31 @@
" (batch_size=", batch_size, ", rng_method=", rng_method, ")")
}

if (chosen == "gpu") {
RidgeCuda::ridge_batch(X = X, Y = Y, lambda = lambda, nrand = nrand,
ncores = ncores, rng_method = rng_method,
batch_size = batch_size, reader = reader,
n_samples = n_samples, output_h5 = output_h5)
} else if (chosen == "cpu-fast") {
RidgeFast::ridge_batch(X = X, Y = Y, lambda = lambda, nrand = nrand,
ncores = ncores, rng_method = rng_method,
batch_size = batch_size, reader = reader,
n_samples = n_samples, output_h5 = output_h5)
if (chosen %in% c("gpu", "cpu-fast")) {
if (!requireNamespace("FlashReg", quietly = TRUE)) {
stop("backend='", chosen, "' requires the FlashReg package. ",
"Install it from https://github.com/data2intelligence/FlashReg ",
"or set backend='cpu-pure' to use the in-house pure-R loop.")
}
flashreg_backend <- .secact_to_flashreg_backend(chosen)
kernel_fn <- function(X, Y_batch, lambda, nrand) {
FlashReg::ridge(X = X, Y = Y_batch, lambda = lambda, nrand = nrand,
backend = flashreg_backend,
ncores = ncores, rng_method = rng_method)
}
} else {
if (rng_method != "mt19937") {
stop("rng_method='", rng_method, "' requires RidgeFast or RidgeCuda; ",
stop("rng_method='", rng_method, "' requires FlashReg; ",
"pure-R supports only 'mt19937'.")
}
.ridge_pureR_batch(X, Y, lambda, nrand,
batch_size = batch_size,
reader = reader, n_samples = n_samples,
output_h5 = output_h5)
kernel_fn <- .ridge_pureR
}

.ridge_pureR_batch(X, Y, lambda, nrand,
batch_size = batch_size,
reader = reader, n_samples = n_samples,
output_h5 = output_h5,
kernel_fn = kernel_fn)
}

#' Pure-R batched ridge+permutation
Expand All @@ -164,7 +187,9 @@
.ridge_pureR_batch <- function(X, Y, lambda, nrand,
batch_size = 5000L,
reader = NULL, n_samples = NULL,
output_h5 = NULL) {
output_h5 = NULL,
kernel_fn = NULL) {
if (is.null(kernel_fn)) kernel_fn <- .ridge_pureR
if (!is.matrix(X)) X <- as.matrix(X)
storage.mode(X) <- "double"
n <- nrow(X); p <- ncol(X)
Expand Down Expand Up @@ -209,7 +234,7 @@
storage.mode(Y_batch) <- "double"
if (is.null(colnames(Y_batch))) colnames(Y_batch) <- samp_names[s:e]

res <- .ridge_pureR(X, Y_batch, lambda, nrand)
res <- kernel_fn(X, Y_batch, lambda, nrand)

if (h5_out) {
for (nm in c("beta", "se", "zscore", "pvalue")) {
Expand Down
2 changes: 2 additions & 0 deletions README.Rmd
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ knitr::opts_chunk$set(

SecAct is an R package designed for inferring the intercellular signaling activity of secreted proteins from gene expression profiles. Users can input multiple modalities of expression data, including spatial, single-cell, or bulk transcriptomics data. The outputs are the inferred <b>signaling activities of 1,170 secreted proteins</b> for each spatial spot, individual cell, or sample, depending on the input data type. Based on the inferred activities, SecAct provides multiple downstream application modules. For <b>spatial</b> data, SecAct can infer the signaling pattern and signaling velocity for secreted proteins. For <b>single-cell</b> data, SecAct can infer the intercellular communication network and signaling flow from source cells to receiver cells. For <b>bulk</b> data, SecAct can infer secreted protein risk scores for a large cohort linked to clinical data, and can infer secreted protein activities that are differentially regulated between two phenotypes. Check the tutorials below for details on how to use the package.

> **Fast regression engine.** SecAct's ridge-regression + permutation-testing core can be accelerated by [FlashReg](https://github.com/data2intelligence/FlashReg) (R) / [FlashRegPy](https://github.com/data2intelligence/FlashRegPy) (Python) — an ultra-fast, GPU-capable ridge engine — which makes atlas-scale (millions of cells) activity inference tractable: the permutation nulls that project to days–months on naïve refit-per-permutation solvers run in minutes on a single GPU.

<p align="center"><img src="man/figures/workflow.png" width="100%"/></p>


Expand Down
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,15 @@ infer secreted protein activities that are differentially regulated
between two phenotypes. Check the tutorials below for details on how to
use the package.

> **Fast regression engine.** SecAct’s ridge-regression + permutation-testing
> core can be accelerated by
> [FlashReg](https://github.com/data2intelligence/FlashReg) (R) /
> [FlashRegPy](https://github.com/data2intelligence/FlashRegPy) (Python) — an
> ultra-fast, GPU-capable ridge engine — which makes atlas-scale (millions of
> cells) activity inference tractable: the permutation nulls that project to
> days–months on naïve refit-per-permutation solvers run in minutes on a single
> GPU.

<p align="center">

<img src="man/figures/workflow.png" width="100%"/>
Expand Down
2 changes: 1 addition & 1 deletion tests/testthat/test-backend-parity.R
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
test_that("cpu-pure and cpu-fast backends produce identical z-scores in mt19937 mode", {
skip_if_not_installed("RidgeFast")
skip_if_not_installed("FlashReg")

set.seed(7)
n <- 60; p <- 10; m <- 4
Expand Down
7 changes: 3 additions & 4 deletions vignettes/accelerator.Rmd
Original file line number Diff line number Diff line change
Expand Up @@ -25,16 +25,15 @@ After installing the `SecAct` R package, you can complete all analyses in the tu
## Installation

<span style="color:red;">
Once either package is installed, no additional configuration is required—the activity inference functions will automatically use the available accelerator.
Once FlashReg is installed, no additional configuration is required—the activity inference functions will automatically use the available accelerator.
</span>


* [RidgeFast](https://github.com/data2intelligence/RidgeFast) — multi-threaded CPU (GSL + OpenMP)
* [RidgeCuda](https://github.com/data2intelligence/RidgeCuda) — NVIDIA GPU (CUDA)
* [FlashReg](https://github.com/data2intelligence/FlashReg) — one R package, both CPU (C+OpenMP) and GPU (NVIDIA CUDA) backends, GSL required and CUDA toolkit optional. FlashReg replaces the historical pair of accelerator packages (RidgeFast for CPU, RidgeCuda for GPU).

## Example

With either installed, `SecAct.activity.inference(..., backend = "auto")` (the default) picks GPU > CPU-fast > pure-R automatically. At `rng_method = "mt19937"` (default) and `ncores = 1`, output is bit-identical across all three backends.
With FlashReg installed, `SecAct.activity.inference(..., backend = "auto")` (the default) picks GPU > CPU-fast > pure-R automatically — FlashReg detects CUDA availability at runtime. At `rng_method = "mt19937"` (default) and `ncores = 1`, output is bit-identical across all three backends.

``` r
# Memory-bounded inference on 100k samples
Expand Down