From 6c15f21b4d5f644539fc9a066ab40452be76d997 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikko=20R=C3=B6nkk=C3=B6?= Date: Fri, 5 Jun 2026 10:46:18 +0300 Subject: [PATCH] Fix serial-path reproducibility for L'Ecuyer-CMRG (list) seeds set_seed() assigned `.Random.seed` to its local function frame rather than .GlobalEnv when given a list-type (L'Ecuyer-CMRG) seed. R's RNG only consults globalenv()$.Random.seed, so the seed state was silently discarded on the serial (non-parallel) path. This made runArraySimulation(..., iseed, parallel = FALSE) and runSimulation(seed = , parallel = FALSE) non-reproducible across runs, while the parallel path (which installs the seed via clusterSetRNGSubStream() into the workers' global env) was unaffected. Assigning into .GlobalEnv installs a valid L'Ecuyer-CMRG state; R derives the RNG kind from .Random.seed[1], so the generator switches automatically. Co-Authored-By: Claude Opus 4.8 (1M context) --- NEWS.md | 7 +++++++ R/util.R | 2 +- tests/tests/test-03-array.R | 16 ++++++++++++++++ 3 files changed, 24 insertions(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index afbfde19..a3f11003 100644 --- a/NEWS.md +++ b/NEWS.md @@ -2,6 +2,13 @@ ## Changes in SimDesign 2.25 +- Fixed a bug where list-based (L'Ecuyer-CMRG) seeds were not applied on the + serial (non-parallel) execution path, causing `runArraySimulation(..., iseed)` + and `runSimulation(seed = )` to be non-reproducible when + `parallel = FALSE`. The internal `set_seed()` helper was assigning + `.Random.seed` to its local frame rather than `.GlobalEnv`, so the RNG state + was silently discarded. The parallel path was unaffected + - `SimErrors()` and `SimWarnings()` functions added to better track and extract error/warning information. Makes it easier to track down specific seed states to replicate any of the recorded messages diff --git a/R/util.R b/R/util.R index d5a3d307..88670309 100644 --- a/R/util.R +++ b/R/util.R @@ -678,7 +678,7 @@ pickReps <- function(replications, iter){ } set_seed <- function(seed){ - if(is.list(seed)) .Random.seed <- seed[[1L]] + if(is.list(seed)) .GlobalEnv$.Random.seed <- seed[[1L]] else set.seed(seed) invisible(NULL) } diff --git a/tests/tests/test-03-array.R b/tests/tests/test-03-array.R index 50befea0..46b0e126 100644 --- a/tests/tests/test-03-array.R +++ b/tests/tests/test-03-array.R @@ -70,6 +70,22 @@ test_that('array', { SimClean('mysim-1.rds') + # serial iseed must be byte-for-byte reproducible (L'Ecuyer-CMRG seed + # applied via set_seed(); see GitHub bug re: .GlobalEnv assignment) + res_s1 <- runArraySimulation(design=Design, replications=20, + generate=Generate, analyse=Analyse, + summarise=Summarise, arrayID=1L, + iseed=iseed, filename='serialseed', + parallel=FALSE, verbose=FALSE) + res_s2 <- runArraySimulation(design=Design, replications=20, + generate=Generate, analyse=Analyse, + summarise=Summarise, arrayID=1L, + iseed=iseed, filename='serialseed', + parallel=FALSE, verbose=FALSE) + expect_identical(SimExtract(res_s1, what='results'), + SimExtract(res_s2, what='results')) + SimClean('serialseed-1.rds') + ######################## # Same submission job as above, however split the replications over multiple # evaluations and combine when complete