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:
In the code below, I define two functions to generate random numbers, settings the seed with either withr::with_seed
or R.utils::withSeed
.
withr::with_seed
version gives the same answer inside or outside the promise.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?
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 to set.seed
. In your example, the s0_R
can be modified as follows to get the same results inside and outside the promise.
s0_R = function(seed = 1, n = 1){
R.utils::withSeed(expr = {
rnorm(n)
}, seed = seed, kind = "default")
}