diff --git a/DESCRIPTION b/DESCRIPTION index 6cd4dd8..bfee245 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -26,8 +26,7 @@ Imports: ComplexHeatmap, RANN Suggests: - RidgeFast, - RidgeCuda, + FlashReg, rhdf5, shiny, shinyWidgets, diff --git a/R/activity.R b/R/activity.R index 2beaff1..93858b8 100644 --- a/R/activity.R +++ b/R/activity.R @@ -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, diff --git a/R/ridge.R b/R/ridge.R index 1cd95be..831e8fc 100644 --- a/R/ridge.R +++ b/R/ridge.R @@ -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, @@ -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) @@ -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, @@ -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 @@ -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) @@ -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")) { diff --git a/README.Rmd b/README.Rmd index 0bc4440..c201ffe 100644 --- a/README.Rmd +++ b/README.Rmd @@ -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 signaling activities of 1,170 secreted proteins 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 spatial data, SecAct can infer the signaling pattern and signaling velocity for secreted proteins. For single-cell data, SecAct can infer the intercellular communication network and signaling flow from source cells to receiver cells. For bulk 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. +

diff --git a/tests/testthat/test-backend-parity.R b/tests/testthat/test-backend-parity.R
index 766a7a2..8891fce 100644
--- a/tests/testthat/test-backend-parity.R
+++ b/tests/testthat/test-backend-parity.R
@@ -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
diff --git a/vignettes/accelerator.Rmd b/vignettes/accelerator.Rmd
index 740d957..3aba7c2 100644
--- a/vignettes/accelerator.Rmd
+++ b/vignettes/accelerator.Rmd
@@ -25,16 +25,15 @@ After installing the `SecAct` R package, you can complete all analyses in the tu
## Installation
-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.
-* [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