Skip to content

Make the GSEA permutation RNG seedable - #31

Merged
zhiaos merged 1 commit into
masterfrom
seed-gsea
Aug 10, 2026
Merged

Make the GSEA permutation RNG seedable#31
zhiaos merged 1 commit into
masterfrom
seed-gsea

Conversation

@zhiaos

@zhiaos zhiaos commented Aug 10, 2026

Copy link
Copy Markdown
Collaborator

Makes the GSEA permutation RNG seedable, so results can be reproduced.

make_permutations seeds from SmallRng::from_entropy(), so every GSEA run draws a
different permutation set. Because the permutations are generated here rather than in R,
set.seed() in the calling R session cannot reach them, and WebGestaltR has never been
able to offer reproducible GSEA. Two runs of one analysis can return a different number of
significant sets
— measured at 26 rows vs 24, max ΔFDR 0.113 single-threaded and 0.691
across four threads. Requested in
WebGestaltR#9, open since 2021.

The change

GSEAConfig gains seed: Option<u64>, defaulting to None, threaded to the single call
site and into make_permutations:

let mut smallrng = match seed {
    Some(s) => rand::rngs::SmallRng::seed_from_u64(s),
    None => rand::rngs::SmallRng::from_entropy(),   // unchanged default
};

SeedableRng was already imported — from_entropy needs it too.

Backward compatible by construction. None preserves the current behaviour exactly, so
nothing changes for callers that do not ask for a seed.

Why threading is not a problem

Permutations are generated serially up front (gsea.rs:320) and shared across the parallel
scoring that follows, so the RNG is never touched concurrently. Verified: seed 42 at
nThreads=1 and nThreads=4 give bit-identical p-values, FDRs and enrichment scores. Only
NES moves, by 2–4e-16, which is floating-point summation in the normalisation step.

Verified

cargo test --all passes, including the make_permutations doctest whose signature changed.

End to end through the R package, on a branch of WebGestaltR that vendors this commit:
two runs at seed = 42 produce bit-identical p_val, fdr and ES (compared at
tolerance = 0); a run at seed = 99 differs in p_val and fdr while ES is unchanged
to every digit — the enrichment score has no randomness in it, so a seed that moved it would
mean the change reached further than intended.

Not in this PR

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant