# Module Reproducibility

Classification of all local modules and subworkflows by output reproducibility.

**Paired docs:** Strategy labels in the **Test strategy (this branch)** column are the same vocabulary as [`.cursor/rules/nf-test-assertions.mdc`](../.cursor/rules/nf-test-assertions.mdc).
This file is the **catalog** (every component + the exact snapshot story per row).
That rule is the **how-to** (assertion order, stubs, `versions` channel, passthrough H5AD, common mistakes).

## Legend

| Level                            | Meaning                                                                                                                                                                                                                                   |
| -------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Fully deterministic**          | Same input always produces bit-identical output across all platforms and library versions.                                                                                                                                                |
| **Seeded / quasi-deterministic** | A fixed random seed is set, but floating-point results (PCA coordinates, embeddings) can differ slightly across hardware, LAPACK/BLAS backends, or library versions. File hashes may differ between platforms even when logic is correct. |
| **Non-deterministic**            | Output varies between runs or platforms even with identical inputs — either due to unseeded stochasticity, iterative ML training, or live external API/download calls.                                                                    |

The **Test strategy (this branch)** column describes what the tests on this branch actually assert. Default `snapshot(...)` contents for each strategy label are in [`.cursor/rules/nf-test-assertions.mdc`](../.cursor/rules/nf-test-assertions.mdc) under **Module strategies — default snapshot contents** (and **Subworkflow tests** there for shared patterns).

### Module tests

| Strategy                | What is checked                                                                                                                                                                                                                                                                                                  |
| ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `hash`                  | Module **emits H5AD**: `process.out`, `path(process.out.versions[0]).yaml`, and `adata.yaml` (AnnData read from the staged output file). Field checks run before the snapshot. Only appropriate when outputs are **deterministic** (or you accept the risk on quasi-deterministic rows such as `scanpy/sample`). |
| `hash (no H5AD output)` | Module **does not emit H5AD** (e.g. RDS, PNG, MultiQC JSON, TSV). Tests snapshot **`process.out` + `path(process.out.versions[0]).yaml` only** — no `adata.yaml` (nothing to parse as AnnData). Same row in nf-test-assertions **Module strategies — default snapshot contents**.                                |
| `structural`            | `path(process.out.versions[0]).yaml` and `adata.yaml`; **`process.out` file hashes omitted** so matrix floats and unstable binaries do not break CI.                                                                                                                                                             |
| `hash + structural`     | Same snapshot shape as **`hash`** (outputs + versions YAML + `adata.yaml`); the label is kept for modules that also emphasize schema checks in the test name or have multiple scenarios (e.g. `scanpy/filter`).                                                                                                  |
| `column names`          | `versions.yml` as YAML plus **obs column names** (or similar) — **no** `process.out` file MD5s. Used where cell-level values are intentionally excluded from the snapshot (e.g. annotation scores) or may drift across R/package stacks.                                                                         |
| `column names only`     | Shorthand in tables: same idea as `column names`.                                                                                                                                                                                                                                                                |
| `obs CSV + file names`  | SingleR tests snapshot **obs-related outputs** (e.g. CSV paths/names) plus versions — not a full H5AD hash.                                                                                                                                                                                                      |
| `range assertion`       | A numeric value (e.g. cluster count, cell count) is asserted **outside** or **alongside** the snapshot window.                                                                                                                                                                                                   |
| `stub only`             | No non-stub test; stub uses `snapshot(process.out).match()` only.                                                                                                                                                                                                                                                |

### Subworkflow tests

| Strategy     | What is checked                                                                                                                                                                                                                                                                                                                                                                                     |
| ------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `structural` | Typically `workflow.out.versions.collect { path(it).yaml }` and optionally `adata.yaml`, **`workflow.out` omitted** for unstable integration outputs.                                                                                                                                                                                                                                               |
| `hash`       | **Deterministic** workflows: often `snapshot(workflow.out)` **or** a deliberate subset (e.g. `workflow.out.h5ad`) **plus** collected versions as YAML and **`adata.yaml`** when the merged H5AD is read from the work directory. **Exact `snapshot(...)` arguments differ per subworkflow** — the **Subworkflows** table below is authoritative; nf-test-assertions links subworkflow authors here. |

**Passthrough outputs:** when workflow outputs still point at **unstaged** test-dataset paths, tests use `snapshot(workflow.out)` only and do **not** call `anndata(...).yaml` on those paths (see **Known nft-anndata limitation: passthrough h5ad files** in [`.cursor/rules/nf-test-assertions.mdc`](../.cursor/rules/nf-test-assertions.mdc)).

---

## Modules

### `adata/`

| Module                  | Description                                                                                                                | Reproducibility                                                                                 | Test strategy (this branch)                                                                                                                              |
| ----------------------- | -------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `adata/entropy`         | Computes per-group normalised Shannon entropy of a categorical obs column and optionally plots it.                         | Fully deterministic                                                                             | structural — versions + `adata.yaml` only                                                                                                                |
| `adata/extend`          | Extends an AnnData object with additional obs/var/obsm/obsp/uns/layers data from pickle or CSV files.                      | Fully deterministic                                                                             | hash                                                                                                                                                     |
| `adata/merge`           | Concatenates multiple AnnData objects into inner (gene intersection) and outer (union) merged datasets.                    | Fully deterministic                                                                             | hash                                                                                                                                                     |
| `adata/mergeembeddings` | Merges embeddings from a newly integrated AnnData back into the combined base+integrated AnnData by cell-index alignment.  | Fully deterministic                                                                             | hash                                                                                                                                                     |
| `adata/mygene`          | Translates gene identifiers (Ensembl/Entrez/symbol) to HGNC symbols via the MyGene.info REST API.                          | **Non-deterministic** — live external API call; results depend on database state at query time. | ⚠️ **hash** (`process.out` + versions YAML + `adata.yaml`) — same shape as deterministic modules; **snapshots can drift** when the API response changes. |
| `adata/prepcellxgene`   | Prepares an AnnData object for CellxGene by renaming embedding keys and converting counts to log-normalised float32.       | Fully deterministic                                                                             | hash                                                                                                                                                     |
| `adata/readcsv`         | Reads a CSV file and writes it as an AnnData H5AD.                                                                         | Fully deterministic                                                                             | hash                                                                                                                                                     |
| `adata/readrds`         | Converts an RDS file (Seurat or SingleCellExperiment) to AnnData H5AD via anndata2ri/rpy2.                                 | Fully deterministic                                                                             | hash                                                                                                                                                     |
| `adata/setindex`        | Sets a specified obs or var column as the new AnnData index.                                                               | Fully deterministic                                                                             | hash                                                                                                                                                     |
| `adata/splitcol`        | Splits an AnnData object into one H5AD file per unique value of a specified obs column.                                    | Fully deterministic                                                                             | hash                                                                                                                                                     |
| `adata/splitembeddings` | Splits multiple named embeddings from a single AnnData into separate H5AD files each containing `X_emb`.                   | Fully deterministic                                                                             | hash                                                                                                                                                     |
| `adata/tords`           | Converts an AnnData H5AD to an RDS SingleCellExperiment object using anndataR.                                             | Fully deterministic                                                                             | hash (no H5AD output — `.rds` + versions)                                                                                                                |
| `adata/unify`           | Normalises gene symbols, resolves duplicates, unifies batch/label/condition columns, and prefixes barcodes with sample ID. | Fully deterministic                                                                             | hash                                                                                                                                                     |
| `adata/upsetgenes`      | Plots an UpSet diagram of gene set overlaps across multiple AnnData objects and outputs a MultiQC JSON image.              | Fully deterministic                                                                             | hash (no H5AD output — plot / MultiQC JSON + versions)                                                                                                   |

### `celda/`

| Module          | Description                                                                                     | Reproducibility                                                                    | Test strategy (this branch)                            |
| --------------- | ----------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- | ------------------------------------------------------ |
| `celda/decontx` | Removes ambient RNA contamination using the decontX variational EM algorithm (celda R package). | **Non-deterministic** — decontX uses stochastic initialisation with no fixed seed. | structural — h5ad hash omitted; versions + schema only |

### `celldex/`

| Module                   | Description                                                                                             | Reproducibility                                                                              | Test strategy (this branch) |
| ------------------------ | ------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- | --------------------------- |
| `celldex/fetchreference` | Downloads a named celldex reference dataset and saves it as a tar-compressed HDF5 SummarizedExperiment. | **Non-deterministic** — live download from ExperimentHub; database version varies over time. | structural                  |

### `celltypist/`

| Module       | Description                                                                                                                 | Reproducibility                                                                                        | Test strategy (this branch)                                                                        |
| ------------ | --------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------- |
| `celltypist` | Annotates cell types using one or more CellTypist logistic regression models after normalising and log-transforming counts. | Fully deterministic — uses a fixed, pre-trained model with no stochastic inference at prediction time. | column names — `path(process.out.versions[0]).yaml` + `adata.obs.colnames` (no `process.out` MD5s) |

### `cytetype/`

| Module     | Description                                                                                                                                                                                                  | Reproducibility                                                                                                                                         | Test strategy (this branch)                                                                                                        |
| ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| `cytetype` | Submits per-cluster markers from pre-computed cluster labels and differential-expression results to the [CyteType](https://github.com/NygenAnalytics/cytetype) multi-agent LLM API for cell type annotation. | **Non-deterministic** — calls a live LLM-backed remote API; outputs depend on the CyteType service state and (potentially) on stochastic LLM responses. | structural — `path(process.out.versions[0]).yaml` + `adata.yaml`; field checks for cluster and additional `cytetype_*` obs columns |

### `custom/`

| Module                  | Description                                                                                                               | Reproducibility     | Test strategy (this branch)                                      |
| ----------------------- | ------------------------------------------------------------------------------------------------------------------------- | ------------------- | ---------------------------------------------------------------- |
| `custom/collectsizes`   | Pivots a TSV of per-sample cell counts at different QC stages into a MultiQC summary table.                               | Fully deterministic | hash (no H5AD output — TSV / MultiQC JSON + versions)            |
| `custom/doubletremoval` | Merges doublet-caller predictions into `adata.obs` and optionally removes cells when a threshold is met (`removal=true`). | Fully deterministic | hash (removal path); structural assertions on annotate-only path |

### `scdblfinder/`

| Module        | Description                                                                                                                   | Reproducibility                                                                                                                | Test strategy (this branch) |
| ------------- | ----------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ | --------------------------- |
| `scdblfinder` | Scores and calls doublets with scDblFinder (`set.seed(123)` and BiocParallel `RNGseed=123`) and exports per-cell predictions. | Seeded / quasi-deterministic — seeds are fixed, but iterative model fitting can still vary slightly across R/package versions. | hash                        |

### `scry/`

| Module          | Description                                                                                                                                                 | Reproducibility                                                                                                               | Test strategy (this branch) |
| --------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- | --------------------------- |
| `scry/deviance` | Ranks genes by binomial deviance on raw counts (`set.seed(123)`), subsets to the top _N_ genes, and writes `highly_deviant` / `binomial_deviance` in `var`. | Seeded / quasi-deterministic — deviance is deterministic on fixed counts; R floating-point may vary slightly across versions. | hash                        |

### `singler/`

| Module    | Description                                                                                                   | Reproducibility                                                             | Test strategy (this branch) |
| --------- | ------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------- | --------------------------- |
| `singler` | Assigns cell types using the SingleR correlation-based method against one or more celldex reference datasets. | Fully deterministic — correlation/label-transfer method with no randomness. | obs CSV + file names        |

### `hugounifier/`

| Module              | Description                                                                                   | Reproducibility                                                                                                             | Test strategy (this branch)                   |
| ------------------- | --------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------- |
| `hugounifier/get`   | Computes a cross-dataset gene symbol renaming change table using the `hugo-unifier` CLI tool. | **Non-deterministic** — uses the HUGO Gene Nomenclature Committee REST API; results depend on database state at query time. | structural — CSV column names + versions only |
| `hugounifier/apply` | Applies a gene symbol renaming change table to a single AnnData H5AD file.                    | Fully deterministic once the change table is fixed; but see `hugounifier/get` above.                                        | structural — versions + schema only           |

### `liana/`

| Module                | Description                                                                                                                                           | Reproducibility                                                                                                                                                                                                                                                                                                           | Test strategy (this branch)                               |
| --------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------- |
| `liana/rankaggregate` | Runs LIANA rank-aggregate ligand–receptor interaction analysis on a clustered AnnData object and writes dotplot / circle / tileplot PNGs for MultiQC. | **Seeded / quasi-deterministic** — optional stratified subsampling (`liana_max_cells`, `liana_subsample_seed`) and LIANA `seed`/`n_perms`; magnitude ranks are less affected than specificity ranks when `liana_n_perms` is reduced or set to `0`. Floating-point aggregation and plot backends can vary across machines. | structural — versions + schema only (stub includes plots) |
| `liana/bysample`      | Runs LIANA rank-aggregate by sample/context for Tensor-cell2cell.                                                                                     | **Seeded / quasi-deterministic** — same subsampling and permutation controls as `liana/rankaggregate`; per-context floating-point aggregation can vary across BLAS/NumPy backends.                                                                                                                                        | structural — versions + schema only                       |

### `cell2cell/`

| Module             | Description                                                                                                   | Reproducibility                                                                                                                                             | Test strategy (this branch)        |
| ------------------ | ------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------- |
| `cell2cell/tensor` | Builds a CCC tensor from by-sample LIANA results, runs Tensor-cell2cell, and plots loadings-product heatmaps. | **Seeded / quasi-deterministic** — `cell2cell_seed` and optional fixed `cell2cell_rank`; factor loadings and PNG heatmaps can vary across library backends. | structural — versions + file names |

### `scanpy/`

| Module                   | Description                                                                                                                                             | Reproducibility                                                                                                                                                                                            | Test strategy (this branch)                                                                    |
| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- |
| `scanpy/bbknn`           | Constructs a batch-balanced k-nearest-neighbour graph (BBKNN) on a PCA embedding.                                                                       | Fully deterministic — kNN construction is deterministic given the input embedding.                                                                                                                         | structural — versions + schema only                                                            |
| `scanorama/integrate`    | Runs Scanorama integration via `scanpy.external.pp.scanorama_integrate` after optional PCA, storing `X_scanorama` and `X_emb`.                          | Seeded / quasi-deterministic — integration coordinates can vary across library versions.                                                                                                                   | structural — versions + schema only                                                            |
| `scanpy/cellcycle`       | Scores each cell for S-phase and G2M-phase activity and assigns a predicted cell cycle phase.                                                           | Fully deterministic                                                                                                                                                                                        | hash                                                                                           |
| `scanpy/combat`          | Applies ComBat batch correction and then runs PCA, storing the result as `X_emb`.                                                                       | Seeded / quasi-deterministic — ComBat is deterministic; downstream PCA floats may vary across LAPACK backends.                                                                                             | structural — versions + schema only                                                            |
| `scanpy/filter`          | Filters cells and genes by count, gene, and mitochondrial percentage thresholds.                                                                        | Fully deterministic                                                                                                                                                                                        | hash + structural — standard `hash` triple; multiple parameter scenarios                       |
| `scanpy/hvgs`            | Normalizes counts (`normalize_total` → `log1p`), selects highly variable genes, and subsets the AnnData to those genes while keeping raw counts in `X`. | Seeded / quasi-deterministic — HVG variance statistics rely on NumPy/SciPy floating-point operations that can produce slightly different results across library versions.                                  | structural — versions + schema only                                                            |
| `scanpy/leiden`          | Performs Leiden community-detection clustering at a specified resolution using `flavor="igraph"`.                                                       | **Non-deterministic** — Leiden uses random restarts with no fixed seed; `n_iterations` follows scanpy defaults.                                                                                            | structural — range assertion on cluster count + versions + schema                              |
| `scanpy/neighbors`       | Computes a k-nearest-neighbour graph on a specified embedding. Optional `n_pcs` limits the number of principal components used.                         | Fully deterministic given a fixed input embedding.                                                                                                                                                         | structural — versions + schema only                                                            |
| `scanpy/tsne`            | Computes a t-SNE embedding from a pre-built neighbour graph using `random_state=0`.                                                                     | Seeded / quasi-deterministic — coordinates vary across library versions.                                                                                                                                   | structural — versions + schema only                                                            |
| `scanpy/paga`            | Computes PAGA coarse-grained cluster connectivity and saves a graph and plot.                                                                           | Fully deterministic — PAGA is a deterministic graph-summarisation step given fixed Leiden labels.                                                                                                          | hash                                                                                           |
| `scanpy/pca`             | Runs library-size normalization, log1p, and PCA with `random_state=0`, storing the result under a specified key.                                        | Seeded / quasi-deterministic — seed is fixed, but float coordinates can differ across LAPACK/MKL backends. Embeddings differ from earlier pipeline versions that ran PCA on unnormalized counts.           | structural — versions + schema only                                                            |
| `scanpy/plotqc`          | Calculates QC metrics and produces a counts-vs-genes scatter plot for MultiQC.                                                                          | Fully deterministic                                                                                                                                                                                        | hash (no H5AD output — PNG / MultiQC JSON + versions)                                          |
| `scanpy/rankgenesgroups` | Runs differential gene expression (rank genes groups) across clusters using a configurable statistical method; also writes per-group volcano PNGs/MQC.  | **Seeded / quasi-deterministic** — wilcoxon and t-test are deterministic in theory, but tied-rank handling and floating-point tie-breaking can differ across SciPy versions. PNG volcanoes are not hashed. | structural — versions + `adata.yaml`; one path with **empty h5ad** snapshots **versions only** |
| `scanpy/readh5`          | Reads a 10x Genomics HDF5 (`.h5`) file and writes it as an AnnData H5AD.                                                                                | Fully deterministic                                                                                                                                                                                        | hash                                                                                           |
| `scanpy/sample`          | Down-samples cells to a fixed count or fraction using `rng=0`.                                                                                          | Seeded / quasi-deterministic — seed is fixed, but sampled cell set may vary across NumPy versions.                                                                                                         | hash                                                                                           |
| `scanpy/umap`            | Computes a UMAP embedding from a pre-built neighbour graph using `random_state=0`.                                                                      | Seeded / quasi-deterministic — seed is fixed, but float coordinates vary across umap-learn/numba versions.                                                                                                 | structural — versions + schema only                                                            |

### `symphony/`

| Module                      | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                             | Reproducibility                                                                                                               | Test strategy (this branch)         |
| --------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- | ----------------------------------- |
| `symphony/harmonyintegrate` | Runs Symphony integration via symphonypy/Harmony after normalize_total → log1p → scale(max_value=10) → PCA(zero_center=False), storing `X_symphony`, `X_emb`, Symphony reference metadata (`var` mean/std/HVG, `varm['PCs']`, `uns['harmony']`, `uns['normalize']`), and publishing a compact `symphony_reference.h5ad`. Requires symphonypy ≥0.2.3 ([symphonypy#8](https://github.com/potulabe/symphonypy/issues/8), [symphonypy#9](https://github.com/potulabe/symphonypy/issues/9)). | **Non-deterministic** — Harmony is an iterative optimisation; symphonypy passes `random_seed=1` but upstream PCA is unseeded. | structural — versions + schema only |
| `symphony/mapembedding`     | Maps query cells onto a Symphony reference via symphonypy `map_embedding`, storing mapped coordinates in `X_symphony` and `X_emb`.                                                                                                                                                                                                                                                                                                                                                      | **Non-deterministic** — inherits Symphony/Harmony mapping variability.                                                        | structural — versions + schema only |

### `scimilarity/`

| Module                       | Description                                                                                                        | Reproducibility                                                                                                                                                                                                     | Test strategy (this branch) |
| ---------------------------- | ------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------- |
| `scimilarity/annotate`       | Annotates cells with cell type labels using SCimilarity's kNN lookup into the pretrained reference embedding.      | Fully deterministic — pure kNN lookup against a fixed reference with no stochastic inference. ⚠️ No non-stub test exists: the SCimilarity model file (~30 GB) exceeds practical test data limits.                   | stub only                   |
| `scimilarity/embed`          | Embeds cells into SCimilarity's pretrained metric-learning latent space.                                           | Fully deterministic — inference-only forward pass through a fixed pretrained network (no dropout at inference). ⚠️ No non-stub test exists: the SCimilarity model file (~30 GB) exceeds practical test data limits. | stub only                   |
| `decoupler/pseudobulk`       | Aggregates single-cell counts with `decoupler.pp.pseudobulk` and `filter_samples` (donor × cell type × condition). | Fully deterministic                                                                                                                                                                                                 | hash                        |
| `pydeseq2/differential`      | PyDESeq2 on pseudobulk counts per cell-type stratum; writes volcano PNGs/MQC per contrast.                         | Fully deterministic for tables; volcano PNGs may vary across machines                                                                                                                                               | hash (stub includes plots)  |
| `edgepython/differential`    | edgePython QL F-test on pseudobulk counts per cell-type stratum; writes volcano PNGs/MQC per contrast.             | Fully deterministic for tables; volcano PNGs may vary across machines                                                                                                                                               | hash (stub includes plots)  |
| `edgepython/sc_differential` | edgePython single-cell mixed model per Leiden cluster; writes volcano PNGs/MQC per contrast.                       | Version-pinned; treat as quasi-deterministic                                                                                                                                                                        | hash (stub includes plots)  |

### `scarches/`

| Module             | Description                                                                        | Reproducibility                                                                                                                                                  | Test strategy (this branch)         |
| ------------------ | ---------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------- |
| `scarches/expimap` | Trains EXPIMAP (scArches) with pathway priors; stores latent embedding as `X_emb`. | **Non-deterministic** / **seeded quasi-deterministic** — neural training, early stopping, and torch/BLAS stacks can differ across hardware and library versions. | structural — versions + schema only |

### `scvitools/`

| Module             | Description                                                                                                            | Reproducibility                                                                                                                                                                                      | Test strategy (this branch)                 |
| ------------------ | ---------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------- |
| `scvitools/scanvi` | Trains a scANVI semi-supervised VAE for batch integration and label transfer with `seed=0` and deterministic CUDA ops. | Seeded / quasi-deterministic — seed and `torch.use_deterministic_algorithms(True)` are set, but variational training and early stopping can still differ across GPU drivers and torch/scvi versions. | structural — file names + versions + schema |
| `scvitools/scvi`   | Trains a scVI VAE for batch integration with `seed=0` and deterministic CUDA ops.                                      | Seeded / quasi-deterministic — same caveats as scANVI above.                                                                                                                                         | structural — versions + schema only         |

### `seurat/`

| Module               | Description                                                                                                                              | Reproducibility                                                                                                                                              | Test strategy (this branch)         |
| -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------- |
| `seurat/integration` | Performs Seurat v5 CCA-based batch integration after SCTransform and PCA with `set.seed(0)`, storing the corrected embedding as `X_emb`. | Seeded / quasi-deterministic — seed is fixed, but CCA/SCTransform involve floating-point iterative optimisation that can vary across Seurat/LAPACK versions. | structural — versions + schema only |

### `soupx/`

| Module  | Description                                                                                                                       | Reproducibility                                                                                                                                                               | Test strategy (this branch)                            |
| ------- | --------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------ |
| `soupx` | Estimates and removes ambient RNA contamination using SoupX, preceded by Seurat clustering to define cluster-level contamination. | **Non-deterministic** — Seurat clustering is seeded but graph-based steps (Louvain/Leiden) vary across library versions; soup fraction estimates can differ across platforms. | structural — h5ad hash omitted; versions + schema only |

---

## Subworkflows

| Subworkflow               | Description                                                                                                                                                                                 | Reproducibility                                                                                                                                                                                               | Test strategy (this branch)                                                                                                                                                                                    |
| ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `ambient_correction`      | Dispatches ambient RNA correction to decontX, SoupX, or none based on a parameter.                                                                                                          | **Non-deterministic** for decontX (no seed) and SoupX (seeded clustering but variable results); fully deterministic for the `none` passthrough.                                                               | **Scenario-dependent:** often `versions` as YAML + `adata.yaml` when an H5AD is produced; **`none` / meta-disabled** paths may snapshot **only `versions` + `workflow.out.h5ad.size()`** (counts, not hashes). |
| `per_cell_annotation`     | Orchestrates per-cell annotation by running SingleR and/or CellTypist before sample merge.                                                                                                  | Fully deterministic at inference time for both methods.                                                                                                                                                       | **`workflow.out.versions` + `workflow.out.obs.size()`** for non-stub tests; separate **stub** test exercises subworkflow wiring.                                                                               |
| `cluster`                 | Full clustering pipeline: neighbours → UMAP → Leiden at multiple resolutions → Shannon entropy.                                                                                             | Seeded / quasi-deterministic for UMAP; **non-deterministic** due to unseeded Leiden.                                                                                                                          | structural — **`workflow.out.versions` only** (each as YAML); graph / embedding presence asserted in code outside `snapshot`.                                                                                  |
| `combine`                 | Merges all samples and runs all configured integration methods.                                                                                                                             | Inherits from constituent modules — ranges from fully deterministic (no integration) to seeded/quasi-deterministic (scVI, Symphony, Seurat).                                                                  | structural — **`workflow.out.versions` (YAML) + `adata.yaml`** on merged H5AD.                                                                                                                                 |
| `differential_expression` | Orchestrates pseudobulking, pseudobulk DE, rank-genes-groups, and edgePython single-cell DE when enabled by analysis plan and `de_methods`.                                                 | Inherits from constituent subworkflows.                                                                                                                                                                       | — (logic tested via `rank_genes_groups` and `per_group`).                                                                                                                                                      |
| `rank_genes_groups`       | Runs rank-genes-groups DE for global cluster markers, per-condition cluster markers, and per-cluster condition contrasts; supports multiple Scanpy methods in parallel.                     | Fully deterministic for wilcoxon/t-test methods in theory; tie-breaking may differ across SciPy versions.                                                                                                     | structural — **`workflow.out.versions` only** (YAML); DE / MultiQC presence asserted outside `snapshot` where needed.                                                                                          |
| `doublet_detection`       | Runs one or more doublet-detection methods (scdblfinder, solo, scrublet, doubletdetection) and annotates or removes called doublets depending on `doublet_removal`.                         | **Non-deterministic** — solo, scrublet, and doubletdetection have stochastic components; scdblfinder is seeded.                                                                                               | structural + **range assertion** on **`n_obs`** when `doublet_removal=true`; annotate-only path asserts unchanged **`n_obs`** and prediction columns in **`obs`**.                                             |
| `finalize`                | Assembles the final AnnData by extending it with all collected obs/obsm/uns/layers outputs.                                                                                                 | Fully deterministic                                                                                                                                                                                           | hash — **`workflow.out.h5ad` + `workflow.out.versions` (YAML) + `adata.yaml`** — not a bare `snapshot(workflow.out)` in non-stub tests.                                                                        |
| `integrate`               | Applies feature selection (`hvgs`, `deviance`, or `none`) then one or more integration methods (scVI, scANVI, Symphony, BBKNN, ComBat, Seurat, SCimilarity, PCA, EXPIMAP).                  | Seeded / quasi-deterministic for scVI/scANVI/ComBat/Seurat/BBKNN/PCA and deviance; **non-deterministic** for Symphony and EXPIMAP (iterative training).                                                       | structural — **`workflow.out.versions` (YAML) + `adata.yaml`** on integration H5AD (e.g. Symphony / BBKNN / ComBat / PCA tests).                                                                               |
| `load_h5ad`               | Loads input files in H5AD, 10x H5, RDS, or CSV format and converts all to AnnData H5AD.                                                                                                     | Fully deterministic                                                                                                                                                                                           | hash — **`snapshot(workflow.out)` only** (passthrough-safe; avoids `anndata().yaml` on unstaged inputs per nf-test rules).                                                                                     |
| `per_group`               | Runs PAGA, LIANA, optional Tensor-cell2cell (by-sample LIANA + tensor), differential expression (rank-genes-groups, edgePython sc, pseudobulk), and optional CyteType per cluster grouping. | **Seeded / quasi-deterministic** — inherits from constituent modules; CyteType is non-deterministic when enabled.                                                                                             | structural — **`workflow.out.versions` only** (YAML); optional `workflow.out.obs.size()` when CyteType is enabled.                                                                                             |
| `pseudobulking`           | Aggregates single-cell data into pseudobulk profiles (donor × Leiden × condition).                                                                                                          | Fully deterministic                                                                                                                                                                                           | hash — pseudobulk H5AD + samples TSV.                                                                                                                                                                          |
| `differential_expression` | Runs rank-genes-groups, optional pseudobulk aggregation, and pseudobulk DE when selected in `de_methods`.                                                                                   | Fully deterministic                                                                                                                                                                                           | CSV marker tables, pseudobulk matrices, and pseudobulk DE results per cell-type stratum.                                                                                                                       |
| `quality_control`         | Full per-sample QC pipeline: empty droplet filtering, ambient correction, gene unification, cell/gene filtering, optional downsampling, doublet detection, and optional cell cycle scoring. | Seeded / quasi-deterministic to **non-deterministic** depending on configured ambient and doublet-detection methods.                                                                                          | structural — **`versions` (YAML) + `workflow.out.multiqc_files`**; **heavier paths** (e.g. ambient + doublet) also add **`adata.yaml` and/or `n_obs` / `n_vars`**.                                             |
| `scimilarity`             | Embeds cells into the SCimilarity pretrained latent space and annotates with cell type labels.                                                                                              | Fully deterministic — fixed pretrained model, inference-only.                                                                                                                                                 | stub only                                                                                                                                                                                                      |
| `singler`                 | Downloads (or uses pre-provided) celldex reference datasets and runs SingleR annotation.                                                                                                    | Fully deterministic for annotation; **non-deterministic** if downloading references at runtime via ExperimentHub.                                                                                             | hash — **`workflow.out.obs` + `versions` (YAML)** — **not** full `workflow.out` MD5s.                                                                                                                          |
| `unify`                   | Standardises gene symbols (optionally via MyGene.info and HUGO-unifier) and normalises metadata columns (batch, label, condition).                                                          | **Non-deterministic** when `symbol_col == "none"` (MyGene.info) or when `unify_gene_symbols` is true (HUGO-unifier / `unify_genes`); deterministic for `symbol_col != "none"` and `unify_gene_symbols` false. | **hash** — **`workflow.out` + `adata.yaml`** when deterministic. **structural** — **`workflow.out.versions` (YAML) + `adata.yaml`** on live-API paths (MyGene / HUGO), same idea as `unify_genes`.             |
| `unify_genes`             | Applies HUGO-unifier to compute and apply a cross-dataset gene symbol renaming table.                                                                                                       | **Non-deterministic** — `hugounifier/get` uses a live HUGO API; results depend on database state at query time.                                                                                               | structural — **`workflow.out.versions` (YAML) + `adata.yaml`** (multi-sample tests may include two `adata.yaml`).                                                                                              |
