Monte Carlo复现中重置simmer环境并生成新timeout随机值的方法
我希望对simmer模拟进行Monte Carlo复现,想了解是否可以不在复现循环中调用simmer::simmer(),同时确保每次迭代都能生成新的simmer::timeout()随机值。
我的大致设置如下:
env <- setup_sim(input_params) results <- run_sim(env, n_mc = 100)
其中setup_sim()是初始化simmer环境并添加资源和轨迹的函数,我希望将环境创建与模拟执行分离,因为通过编程定义环境的耗时并不短。
随后我调用run_sim()执行模拟,每次重置simmer环境:
run_sim <- function(.env, run_until = Inf, n_mc = 1, use_parallel = FALSE) { # Run simulation start_time <- Sys.time() print('Simulating...') if (use_parallel == TRUE & n_mc > 1) { envs <- parallel::mclapply(1:n_mc, function(i) { .env |> simmer::reset() |> simmer::run(until = run_until) |> simmer::wrap() }) } else { envs <- purrr::map( seq.int(1, n_mc), function(i) { .env |> simmer::reset() |> simmer::run( until = run_until, progress = simmer_progress ) }, .progress = pb_bar_options ) } duration <- Sys.time() - start_time print('Simulation complete. ') print(duration) envs }
但我注意到,即使timeout的随机数是通过函数调用生成的,其值在各次复现中并未改变:
... |> simmer::timeout( \() { step_nbr <- simmer::get_attribute(env, keys = 'step_nbr') cycle_time <- get_cycle_time_wrapper( step_nbr, simmer::get_selected(env), rand_seed = sample.int(100000, 1) ) * simmer::get_attribute(env, keys = 'TimeMultiplier') cycle_time } ) |> ...
其中get_cycle_time_wrapper()是从查找表中随机抽样的函数。
请问在当前设置下,是否有办法确保每次复现都能生成新的随机样本,还是必须重新创建simmer环境?
解决方案
核心问题原因
你遇到的问题根源有两个:
- 默认情况下
simmer::reset()不会重置simmer环境内置的随机数生成器(RNG)状态,导致每次运行时随机序列会延续上一次的结果; - 你在
timeout的匿名函数中使用了全局R的sample.int(),而非simmer专属的随机函数,这会脱离simmer的RNG管理,容易出现重复随机值的情况。
具体解决步骤
重置simmer环境时同时重置RNG
在调用reset()时添加reset_rng = TRUE参数,确保每次蒙特卡洛迭代都会重置simmer的随机数状态:# 串行循环中修改 .env |> simmer::reset(reset_rng = TRUE) |> simmer::run(until = run_until, progress = simmer_progress) # 并行循环中同样修改 .env |> simmer::reset(reset_rng = TRUE) |> simmer::run(until = run_until) |> simmer::wrap()改用simmer内置的随机函数
替换你代码中全局的sample.int()等随机函数,使用simmer提供的simmer::sample()(或其他simmer专属随机函数,如simmer::rnorm()、simmer::runif()),这些函数会绑定当前simmer环境的RNG,确保每次运行都能生成新的随机值。
调整你的timeout代码:... |> simmer::timeout( \() { step_nbr <- simmer::get_attribute(env, keys = 'step_nbr') # 使用simmer::sample替代全局sample.int rand_seed <- simmer::sample(1:100000, size = 1) cycle_time <- get_cycle_time_wrapper( step_nbr, simmer::get_selected(env), rand_seed = rand_seed ) * simmer::get_attribute(env, keys = 'TimeMultiplier') cycle_time } ) |> ...如果
get_cycle_time_wrapper()内部也有随机抽样逻辑,同样建议替换为simmer的随机函数,确保全程使用simmer的RNG管理。并行场景下的额外注意
使用mclapply时,每个子进程会复制原始环境的状态,包括RNG状态。虽然已经设置了reset_rng = TRUE,但为了保险,可以在每个并行任务中显式设置不同的初始种子:envs <- parallel::mclapply(1:n_mc, function(i) { .env |> simmer::reset(reset_rng = TRUE) |> # 手动设置不同的种子,增强随机性 simmer::set_seed(i * 100) |> simmer::run(until = run_until) |> simmer::wrap() })
是否需要重新创建环境?
不需要重新创建simmer环境!通过上述调整,你可以复用同一个初始化好的环境,每次迭代仅重置环境状态和RNG,就能得到不同的随机timeout值,完全满足蒙特卡洛复现的需求,同时保留环境初始化的性能优势。
内容的提问来源于stack exchange,提问作者Aorus

