I'm trying to learn async programming using R so that I can implement an app that requires generation of random numbers with specified seeds (always with specified seeds). I've been using R.utils::withSeed
for this, but I know that withr::with_seed
also exists so I thought I might check that out.
I know random number generation is tricky, so I've been trying to run simple examples to try to understand how things work. I need:
- ...to always get the same random numbers when using the same seed
- ...to get the same random numbers with the same seed regardless of whether I'm using the async framework (so I should be able to run the code outside a promise and get the same answer)
In the code below, I define two functions to generate random numbers, settings the seed with either withr::with_seed
or R.utils::withSeed
.
- These two functions give me the same answer when run outside the promise.
- These two functions give different answers when run inside the promise.
- The
withr::with_seed
version gives the same answer inside or outside the promise. - The
R.utils::withSeed
version gives different answers inside or outside the promise.
The answers seem to be consistent across multiple runs, however.
My question is: why? Is this a bug in R.utils::withSeed
, or am I misunderstanding something?
Code
library(future)
library(promises)
plan(multisession)
s0_R = function(seed = 1, n = 1){
R.utils::withSeed(expr = {
rnorm(n)
}, seed = seed)
}
s0_w = function(seed = 1, n = 1){
withr::with_seed(
seed = seed,
code = {
rnorm(n)
})
}
s_R = function(seed = 1, n = 1){
future_promise(
{
Sys.sleep(5)
s0_R(seed, n)
},
seed = TRUE
)
}
s_w = function(seed = 1, n = 1){
future_promise(
{
Sys.sleep(5)
s0_w(seed, n)
},
seed = TRUE
)
}
s0_R(123) %>%
paste(" (R.utils::withSeed)\n") %>%
cat()
# -0.560475646552213 (R.utils::withSeed)
s0_w(123) %>%
paste(" (withr::with_seed)\n") %>%
cat()
# -0.560475646552213 (withr::with_seed)
s_R(123) %...>%
paste(" (async, R.utils::withSeed)\n") %...>%
cat()
s_w(123) %...>%
paste(" (async, withr::with_seed)\n") %...>%
cat()
# Results occur later...
# -0.968592726552943 (async, R.utils::withSeed)
# -0.560475646552213 (async, withr::with_seed)
The
future
package sets the default RNG kind to L'Ecuyer-CMRG, whereas R's default is Mersenne-Twister.withr::with_seed
resets the RNG kind to"default"
(i.e. Mersenne-Twister) unless it is explicitly specified in the.rng_kind
argument.R.utils::withSeed
, on the other hand, does not do anything about RNG kind by default, but the RNG kind can be specified using the...
argument list passed toset.seed
. In your example, thes0_R
can be modified as follows to get the same results inside and outside the promise.