doRNG/0000755000175100001440000000000014741236736011256 5ustar hornikusersdoRNG/tests/0000755000175100001440000000000013556341614012413 5ustar hornikusersdoRNG/tests/testthat/0000755000175100001440000000000014741236736014260 5ustar hornikusersdoRNG/tests/testthat/test-dorng.r0000644000175100001440000004072613760210061016521 0ustar hornikusers# Unit test for doRNG # # Author: Renaud Gaujoux # Creation: 28 Mar 2012 ############################################################################### context('dorng') #test.CMRGseed <- function(){ # # msg <- function(...) paste(.msg, ':', ...) # # # Unit tests # .msg <- "Call CMRGseed without argument" # rs <- .Random.seed # expect_identical( length(CMRGseed()), 7L, msg("Seed is of length 7") ) # expect_identical(rs, .Random.seed, msg("does not change .Random.seed")) # # .msg <- "Call CMRGseed with a single argument" # rs <- .Random.seed # expect_identical( length(CMRGseed(1)), 7L, msg("Seed is of length 7") ) # expect_identical(rs, .Random.seed, msg("does not change .Random.seed")) # expect_true( all(!is.na(CMRGseed(1))), msg("No NA in the returned seed") ) # #} checkRNG <- function(x, y, msg = NULL, ...){ expect_true(rng.equal(x, y), info = msg, ...) } # 1-length loop test_that("dorng1", { set.seed(1234) # needed to avoid weird behaviors in checks rng_seed <- RNGseq(n = 1, seed = 123, simplify = FALSE)[[1L]] set.seed(123) x <- foreach(i=1) %dorng% { runif(1) } y <- foreach(i=1, .options.RNG = 123) %dorng% { runif(1) } expect_identical(x, y) # check attributes on results result_attributes <- attributes(x) expect_true(setequal(names(result_attributes), c("rng", "doRNG_version")), info = "Result has all the expected attributes") expect_identical(result_attributes[["rng"]][[1L]], rng_seed, info = "Attribute 'rng' does not have the expected value") expect_identical(result_attributes[["doRNG_version"]], doRNGversion(), info = "Attribute 'doRNG_version' does not have the expected value") }) test_that("dorng", { test_dopar <- function(.msg, s.seq){ orng <- RNGseed() on.exit({ doRNGversion(NULL); RNGseed(orng); registerDoSEQ()} ) msg <- function(...) paste(.msg, ':', ...) noattr <- function(x){ attributes(x) <- NULL; x} # RNG restoration after %dorng% rng0 <- getRNG() foreach(i=1:4, .options.RNG = 123) %dorng% { runif(1) } checkRNG(rng0, msg = "RNG is restored after seeded %dorng%") # foreach(i=1:4) %dorng% { runif(1) } expect_identical(RNGtype(), RNGtype(rng0), info = "RNG kind is restored after unseeded %dorng%") # standard %dopar% loops are _not_ reproducible set.seed(1234) s1 <- foreach(i=1:4) %dopar% { runif(1) } set.seed(1234) s2 <- foreach(i=1:4) %dopar% { runif(1) } if( !missing(s.seq) ) expect_true( !identical(s1, s2), msg("Standard %dopar% loop is not reproducible")) # %dorng% loops ensure reproducibility local({ set.seed(1234) s1 <- foreach(i=1:4) %dorng% { runif(1) } runif(10) set.seed(1234) s2 <- foreach(i=1:4) %dorng% { runif(1) } expect_identical(s1, s2, msg("%dorng% loop is reproducible with set.seed")) # check RNG settings in result set.seed(1234) ref <- RNGseq(4) rngs <- attr(s1, 'rng') expect_true(!is.null(rngs), msg("Results contains RNG data")) expect_identical(rngs, ref, msg("Results contains whole sequence of RNG seeds")) }) # or local({ s1 <- foreach(i=1:4, .options.RNG=1234) %dorng% { runif(1) } s2 <- foreach(i=1:4, .options.RNG=1234) %dorng% { runif(1) } expect_identical(s1, s2, msg("%dorng% loop is reproducible with .options.RNG")) ref <- RNGseq(4, 1234) rngs <- attr(s1, 'rng') expect_true(!is.null(rngs), msg("Results of seed loop contains RNG data")) expect_identical(rngs, ref, msg("Results of seeded loop contains whole sequence of RNG seeds")) }) # check with unamed foreach arguments (issue #8) local({ on.exit( registerDoSEQ() ) registerDoRNG() set.seed(567) res <- foreach(a = 1:4, .combine = 'c') %dopar% {rnorm(1, mean = 0, sd = 1)} set.seed(567) res2 <- foreach(1:4, .combine = 'c') %dopar% {rnorm(1, mean = 0, sd = 1)} expect_identical(res, res2, info = "First argument named or unamed is equivalent") # set.seed(567) res <- foreach(a = 1:4, 1:2, .combine = 'c') %dopar% {rnorm(1, mean = 0, sd = 1)} set.seed(567) res2 <- foreach(1:4, 1:2, .combine = 'c') %dopar% {rnorm(1, mean = 0, sd = 1)} expect_identical(res, res2, info = "First argument named or unamed, with second unamed argument is equivalent") }) ## ## check extra arguments to .options.RNG # Normal RNG parameter is taken into account s.unif.noNk <- foreach(i=1:4, .options.RNG=1234) %dorng% { runif(5) } s.unif.wNk <- foreach(i=1:4, .options.RNG=list(1234, normal.kind="Ahrens-Dieter")) %dorng% { runif(5) } expect_identical(noattr(s.unif.noNk), noattr(s.unif.wNk) , msg("%dorng% loop (runif) with normal.kind in .options.RNG is identical as without")) s.norm.noNk <- foreach(i=1:4, .options.RNG=1234) %dorng% { rnorm(5) } s.norm.wNk <- foreach(i=1:4, .options.RNG=list(1234, normal.kind="Ahrens-Dieter")) %dorng% { rnorm(5) } expect_true(!isTRUE(all.equal(noattr(s.norm.noNk), noattr(s.norm.wNk))) , msg("%dorng% loop (rnorm) with normal.kind in .options.RNG is different as without")) # reproduce previous loop runif(10) s1 <- foreach(i=1:4) %dorng% { runif(5) } s2 <- foreach(i=1:4, .options.RNG=s1) %dorng% { runif(5) } expect_identical(s1, s2, "Seeding using .options.RNG={result from other loop} give identical results") # directly set sequence of seeds sL <- list(c(407,1:6), c(407,11:16), c(407,21:26), c(407, 31:36)) s1.L <- foreach(i=1:4, .options.RNG=sL) %dorng% { runif(5) } runif(10) s2.L <- foreach(i=1:4, .options.RNG=sL) %dorng% { runif(5) } expect_identical(s1.L, s2.L, "Seeding using .options.RNG=list twice give identical results") # directly set sequence of seeds as a matrix sM <- sapply(sL, identity) s1.M <- foreach(i=1:4, .options.RNG=sM) %dorng% { runif(5) } runif(10) s2.M <- foreach(i=1:4, .options.RNG=sM) %dorng% { runif(5) } expect_identical(s1.M, s2.M, "Seeding using .options.RNG=matrix twice give identical results") expect_identical(s1.M, s1.L, "Seeding using .options.RNG=matrix gives identical results as the same seed in list") # separate %dorng% loops are different set.seed(1234) rs1 <- .Random.seed s1 <- foreach(i=1:4) %dorng% { runif(1) } rs1_2 <- .Random.seed s2 <- foreach(i=1:4) %dorng% { runif(1) } expect_true( !identical(rs1, rs1_2), msg("unseed %dorng% loop changes .Random.seed")) expect_true( !identical(s1, s2), msg("two consecutive (unseeded) %dorng% loops are not identical")) expect_true( !identical(unlist(s1), unlist(s2)), msg("two consecutive (unseeded) %dorng% loops are not identical (values)")) # But the whole sequence of loops is reproducible set.seed(1234) s1.2 <- foreach(i=1:4) %dorng% { runif(1) } s2.2 <- foreach(i=1:4) %dorng% { runif(1) } expect_true( identical(s1, s1.2) && identical(s2, s2.2), msg("set.seed + two consecutive %dorng% loops are reproducible")) s <- list(s1, s2) if( !missing(s.seq) ) expect_identical(s, s.seq, msg("result is identical to sequential computation")) # check behaviour with set.seed set.seed(789) s1 <- foreach(i=1:6) %dorng%{ runif(1) } s2 <- foreach(i=1:6, .options.RNG=789) %dorng%{ runif(1) } expect_identical(s1, s2, "set.seed before %dorng% is identical to using .options.RNG") # current RNG is CRMG set.seed(789, "L'Ec") s1 <- foreach(i=1:6) %dorng%{ runif(1) } s2 <- foreach(i=1:6, .options.RNG=789) %dorng%{ runif(1) } expect_identical(s1, s2, "set.seed before %dorng% is identical to using .options.RNG, if current RNG is CRMG") s } orng <- RNGseed() on.exit({ doRNGversion(NULL); RNGseed(orng); registerDoSEQ()} ) library(doParallel) # Sequential computation registerDoSEQ() s.seq <- test_dopar("Sequential") # Multicore cluster if( .Platform$OS.type != 'windows'){ # Note: for some reason, running this test in RStudio fails when checking that the standard # %dopar% loop is not reproducible registerDoParallel(cores=2) s <- test_dopar("Multicore", s.seq) } # SNOW-like cluster cl <- makeCluster(2) on.exit( if( !is.null(cl) ) stopCluster(cl), add = TRUE) registerDoParallel(cl) test_dopar("SNOW-like cluster", s.seq) stopCluster(cl); cl <- NULL skip("doMPI test because doMPI::startMPIcluster hangs inexplicably") # Works with doMPI if( require(doMPI) ){ cl_mpi <- startMPIcluster(2) on.exit( if( !is.null(cl_mpi) ) closeCluster(cl_mpi), add = TRUE) registerDoMPI(cl_mpi) test_dopar("MPI cluster", s.seq) closeCluster(cl_mpi); cl_mpi <- NULL } }) test_that("registerDoRNG", { orng <- RNGseed() on.exit({ doRNGversion(NULL); RNGseed(orng); registerDoSEQ()} ) # RNG restoration after %dorng% over doSEQ registerDoSEQ() registerDoRNG() set.seed(123) rng0 <- getRNG() res1 <- foreach(i=1:4) %dorng% { runif(1) } expect_identical(RNGtype(), RNGtype(rng0), "RNG kind is restored after unseeded %dorng%") set.seed(123) res2 <- foreach(i=1:4) %dorng% { runif(1) } expect_identical(res1, res2, "%dorng% loop over doSEQ are reproducible") on.exit( if( !is.null(cl) ) stopCluster(cl), add = TRUE) library(doParallel) cl <- makeCluster(2) registerDoParallel(cl) # One can make existing %dopar% loops reproducible using %dorng% loops or registerDoRNG set.seed(1234) r1 <- foreach(i=1:4) %dorng% { runif(1) } registerDoRNG() set.seed(1234) r2 <- foreach(i=1:4) %dopar% { runif(1) } expect_identical(r1, r2, "registerDoRNG + set.seed: makes a %dopar% loop behave like a set.seed + %dorng% loop") stopCluster(cl); cl <- NULL # Registering another foreach backend disables doRNG cl2 <- makeCluster(2) on.exit( if( !is.null(cl2) ) stopCluster(cl2), add = TRUE) registerDoParallel(cl2) set.seed(1234) s1 <- foreach(i=1:4) %dopar% { runif(1) } set.seed(1234) s2 <- foreach(i=1:4) %dorng% { runif(1) } expect_true( !identical(s1, s2), "Registering another foreach backend disables doRNG") # doRNG is re-nabled by re-registering it registerDoRNG() set.seed(1234) r3 <- foreach(i=1:4) %dopar% { runif(1) } expect_identical(r2, r3, "doRNG is re-nabled by re-registering it") r4 <- foreach(i=1:4) %dopar% { runif(1) } # NB: the results are identical independently of the task scheduling # (r2 used 2 nodes, while r3 used 3 nodes) # Reproducibility of sequences of loops # pass seed to registerDoRNG runif(10) registerDoRNG(1234) r1 <- foreach(i=1:4) %dopar% { runif(1) } r2 <- foreach(i=1:4) %dopar% { runif(1) } registerDoRNG(1234) r3 <- foreach(i=1:4) %dopar% { runif(1) } r4 <- foreach(i=1:4) %dopar% { runif(1) } expect_identical(r3, r1, "registerDoRNG(1234) allow reproducing sequences of %dopar% loops (1)") expect_identical(r4, r2, "registerDoRNG(1234) allow reproducing sequences of %dopar% loops (2)") # use set.seed runif(10) registerDoRNG() set.seed(1234) s1 <- foreach(i=1:4) %dopar% { runif(1) } s2 <- foreach(i=1:4) %dopar% { runif(1) } set.seed(1234) s3 <- foreach(i=1:4) %dopar% { runif(1) } s4 <- foreach(i=1:4) %dopar% { runif(1) } expect_identical(s3, s1, "registerDoRNG + set.seed(1234) allow reproducing sequences of %dopar% loops (1)") expect_identical(s4, s2, "registerDoRNG + set.seed(1234) allow reproducing sequences of %dopar% loops (2)") runif(5) registerDoRNG() set.seed(1234) s5 <- foreach(i=1:4) %dopar% { runif(1) } s6 <- foreach(i=1:4) %dopar% { runif(1) } expect_identical(s5, r3, "registerDoRNG() + set.seed give same results as registerDoRNG(1234) (1)") expect_identical(s6, r4, "registerDoRNG() + set.seed give same results as registerDoRNG(1234) (2)") # argument `once=FALSE` reseed doRNG's seed at the beginning of each loop registerDoRNG(1234, once=FALSE) r1 <- foreach(i=1:4) %dopar% { runif(1) } r2 <- foreach(i=1:4) %dopar% { runif(1) } r3 <- foreach(i=1:4, .options.RNG=1234) %dopar% { runif(1) } expect_identical(r1, r2, "argument `once=FALSE` reseed doRNG's seed at the beginning of each loop") expect_identical(r1, r3, "argument `once=FALSE` reseed %dorng% loop as .options.RNG") # Once doRNG is registered the seed can also be passed as an option to %dopar% r1.2 <- foreach(i=1:4, .options.RNG=456) %dopar% { runif(1) } r2.2 <- foreach(i=1:4, .options.RNG=456) %dopar% { runif(1) } expect_identical(r1.2, r2.2, "Once doRNG is registered the seed can also be passed as an option to %dopar%") expect_true(!identical(r1.2, r1), "The seed passed as an option is really taken into account") }) # Test the use-case discussed in https://github.com/renozao/doRNG/issues/12 # Note: when run under RStudio, this test_that("Initial RNG state is properly handled", { # write script that loads the package being tested .run_test_script <- function(version){ pkg_path <- path.package("doRNG") lib_path <- dirname(pkg_path) # determine if the package is a development or installed package if( dir.exists(file.path(pkg_path, "Meta")) ) load_cmd <- sprintf("library(doRNG, lib = '%s')", lib_path) else load_cmd <- sprintf("devtools::load_all('%s')", pkg_path) # results are saved in a temporary .rds file (substitute backslashes with forward slash for Windows) tmp_res <- gsub("\\", "/", tempfile("rscript_res_", fileext = ".rds"), fixed = TRUE) on.exit( unlink(tmp_res) ) r_code <- paste0(collapse = "; ", c(load_cmd, if( !is.null(version) ) sprintf("doRNGversion('%s')", version), "r1 <- foreach(i=1:4, .options.RNG=1234) %dorng% { runif(1) }", "r2 <- foreach(i=1:4, .options.RNG=1234) %dorng% { runif(1) }", "r3 <- foreach(i=1:4, .options.RNG=1234) %dorng% { runif(1) }", sprintf("saveRDS(list(r1 = r1, r2 = r2, r3 = r3), '%s')", tmp_res) ) ) # run code in an independent fresh session rscript <- file.path(R.home("bin"), "Rscript") system(sprintf('"%s" -e "%s"', rscript, r_code), ignore.stdout = TRUE, ignore.stderr = TRUE) # load result readRDS(tmp_res) } # pre-1.7.4: results are not-reproducible res <- .run_test_script("1.7.3") expect_true(is.list(res) && identical(names(res), paste0("r", 1:3))) expect_true(all(sapply(res, attr, 'doRNG_version') == "1.7.3"), info = "doRNG version is correctly stored") expect_true(!identical(res[["r1"]], res[["r2"]]), info = "Version pre-1.7.3: results 1 & 2 are not identical") expect_identical(res[["r3"]], res[["r2"]], info = "Version pre-1.7.3: results 2 & 3 are identical") # post-1.7.4: results are reproducible res <- .run_test_script("1.7.4") expect_true(is.list(res) && identical(names(res), paste0("r", 1:3))) expect_true(all(sapply(res, attr, 'doRNG_version') == "1.7.4"), info = "doRNG version is correctly stored") expect_identical(res[["r1"]], res[["r2"]], info = "Version 1.7.4: results 1 & 2 are identical") expect_identical(res[["r3"]], res[["r2"]], info = "Version 1.7.4: results 3 & 3 are identical") # current version: results are reproducible res <- .run_test_script(NULL) expect_true(is.list(res) && identical(names(res), paste0("r", 1:3))) expect_true(all(sapply(res, attr, 'doRNG_version') == doRNGversion()), info = "doRNG version is correctly stored") expect_identical(res[["r1"]], res[["r2"]], info = "Current version: results 1 & 2 are identical") expect_identical(res[["r3"]], res[["r2"]], info = "Current version: results 2 & 3 are identical") }) test_that("RNG warnings", { .local <- function(){ orng <- RNGseed() oo <- options() on.exit({ options(oo); doRNGversion(NULL); RNGseed(orng); registerDoSEQ()} ) registerDoSEQ() registerDoRNG() expect_warning(y <- foreach(x = 1:2) %dorng% { rnorm(1); x }, NA) options(doRNG.rng_change_warning_force = TRUE) expect_warning(y <- foreach(x = 1:2) %dorng% { rnorm(1); x }, "Foreach loop \\(doSEQ\\) .* changed .* RNG type") options(doRNG.rng_change_warning_force = NULL) on.exit( if( !is.null(cl) ) stopCluster(cl), add = TRUE) library(doParallel) cl <- makeCluster(2) registerDoParallel(cl) options(doRNG.rng_change_warning_force = TRUE) options(doRNG.rng_change_warning_skip = "doParallelSNOW") expect_warning(y <- foreach(x = 1:2) %dorng% { rnorm(1); x }, "Foreach loop \\(doParallelSNOW\\) .* changed .* RNG type") options(doRNG.rng_change_warning_force = NULL) expect_warning(y <- foreach(x = 1:2) %dorng% { rnorm(1); x }, NA) options(doRNG.rng_change_warning_skip = NULL) } .local() }) doRNG/tests/testthat/test-dorng-nonattached.r0000644000175100001440000000037214360231303020777 0ustar hornikuserscontext('dorng - foreach not attached') test_that("%dorng% works also when foreach is not attached", { `%dorng%` <- doRNG::`%dorng%` y <- foreach::foreach(x = 1:2) %dorng% { x } stopifnot(all.equal(y, list(1, 2), check.attributes = FALSE)) }) doRNG/tests/testthat/test-doRepro.r0000644000175100001440000000353013556341614017026 0ustar hornikusers# Unit tests for the package doRepro # # Author: renaud gaujoux # Creation: 30 Jun 2011 ############################################################################### context("Reproducibility") test_that('xapply', { skip("Development is not finished yet") .seed <- 1:6 n <- 3 expect_identical(xapply(1:n, .seed, function(i) i), 1:n, "Main argument is correctly passed") expect_identical(xapply(1:n, .seed, function(i, b){b}, b='a'), rep('a',n), "Other arguments are correctly passed (1)") expect_identical(xapply(1:n, .seed, function(i, b, c){c}, c='a'), rep('a',n), "Other arguments are correctly passed (2)") rngs <- sapply(RNGseq(n, .seed), RNGdigest) expect_identical(xapply(1:n, .seed, function(i){ RNGdigest() }), rngs, "RNG are correctly set") # check that the stream seed is restored rngs <- sapply(RNGseq(n-1, .seed), RNGdigest) res <- xapply(1:n, .seed, function(i){ RNGdigest() }) rngs <- cbind(rngs, RNGdigest()) expect_identical(res, rngs, "RNG are correctly set") # results are reproducible orng <- getRNG() res <- xapply(1:n, .seed, function(i) runif(i) ) expect_true( rng.equal(orng), "RNG is restored") expect_identical(res, xapply(1:n, .seed, function(i) runif(i) ), "Results are reproducible") expect_identical(sapply(res, length), 1:n, "Test results have correct dimension") }) test_that("reproduce", { skip("Development is not finished yet") .seed <- 1:6 n <- 3 p <- 5 rngs <- sapply(RNGseq(3, .seed), RNGdigest) expect_identical(reproduce(n, .seed, RNGdigest()), rngs, "RNG are correctly set") # results are reproducible orng <- getRNG() res <- reproduce(n, .seed, runif(p) ) expect_true( rng.equal(orng), "RNG is restored") expect_identical(res, reproduce(n, .seed, runif(p) ), "Results are reproducible") expect_identical(dim(res), as.integer(c(p,n)), "Test results have correct dimension") }) doRNG/tests/testthat.R0000644000175100001440000000006613556341614014400 0ustar hornikuserslibrary(testthat) library(doRNG) test_check("doRNG") doRNG/.Rinstignore0000644000175100001440000000002713556341614013554 0ustar hornikusersvignettes/cleveref.sty doRNG/MD50000644000175100001440000000227314741236736011572 0ustar hornikusers6c6078c8cacdabfa73b2b48cf40e278d *DESCRIPTION 914736c637844ee4cc539cb363543c7a *NAMESPACE 8a66ba24ab8e03cee4fef43bf771e004 *NEWS.md c67a9d318dac7ce62b418bc2292e32e8 *R/doRNG-package.R 01bfea5d27934410581e132292d92ce4 *R/doRNG.R adaad203188938606a548fed7fb7982d *R/utils.R b36eab767d8fc4746b84b8e1e7b13e0a *build/vignette.rds 00e481a40fea9fb88bdd71c6e39fd332 *demo/00Index 3c7f2a369c28a79e7b45d8b4b0cda2d6 *demo/doRNG.R df50a05cd6bae38ad219a7146f68d147 *inst/REFERENCES.bib a64d2932a0800f4d39c4806c095876b2 *inst/doc/doRNG.R e4a549acb5bf2a06fc063a938930357f *inst/doc/doRNG.Rnw 0eac129c32b1c0aa371cbadfa4537e53 *inst/doc/doRNG.pdf 82ab6f44b8492bb865476203eec00e19 *man/doRNG-package.Rd 96ef81fe164e010f76c2501714e1c1ed *man/doRNGversion.Rd ac2481cec965412e06b391194868845c *man/grapes-dorng-grapes.Rd 7ef8b4537de4e040468b4cb6df8ad53b *man/infoDoRNG.Rd 9c95362f6151e58b5dd40bb5767643cc *man/registerDoRNG.Rd afa2c0e8da402461ed281edb4f4e20f7 *tests/testthat.R 7978c192923a6be825d7a6d4cb9f2cea *tests/testthat/test-doRepro.r 0bc30863de386f57746f7cdd31a24d3d *tests/testthat/test-dorng-nonattached.r 979bef80cc9c2ac61f77720925044b42 *tests/testthat/test-dorng.r e4a549acb5bf2a06fc063a938930357f *vignettes/doRNG.Rnw doRNG/R/0000755000175100001440000000000014360256113011443 5ustar hornikusersdoRNG/R/utils.R0000644000175100001440000000054513612460574012741 0ustar hornikusers # from pkgmaker 0.31 ns_get <- function (x, ns = NULL, ...){ if (is.null(ns)) { ns <- gsub("^([^:]+)::.*", "\\1", x) x <- gsub(".*::([^:]+)$", "\\1", x) } if (!isNamespace(ns)) { ns <- tryCatch(asNamespace(ns), error = function(e) NULL) if (is.null(ns)) return() } get0(x, envir = ns, ...) }doRNG/R/doRNG-package.R0000644000175100001440000000500614360256113014131 0ustar hornikusers#' The \emph{doRNG} package provides functions to perform #' reproducible parallel foreach loops, using independent random streams #' as generated by L'Ecuyer's combined multiple-recursive generator \citep{Lecuyer1999}. #' It enables to easily convert standard %dopar% loops into fully reproducible loops, #' independently of the number of workers, the task scheduling strategy, #' or the chosen parallel environment and associated foreach backend. #' It has been tested with the following foreach backend: doMC, doSNOW, doMPI. #' #' @encoding UTF-8 #' @name doRNG-package #' @docType package #' @title Generic Reproducible Parallel Backend for foreach Loops #' @keywords package #' @seealso \code{\link{doRNG}}, \code{\link{RNGseq}} #' @bibliography ~/Documents/articles/library.bib #' @cite Lecuyer1999 #' #' @import stats rngtools foreach #' @examples #' #' # register parallel backend #' library(doParallel) #' cl <- makeCluster(2) #' registerDoParallel(cl) #' #' ## standard %dopar% loop are not reproducible #' set.seed(123) #' r1 <- foreach(i=1:4) %dopar%{ runif(1) } #' set.seed(123) #' r2 <- foreach(i=1:4) %dopar%{ runif(1) } #' identical(r1, r2) #' \dontshow{ stopifnot(!identical(r1, r2)) } #' #' ## %dorng% loops _are_ reproducible #' set.seed(123) #' r1 <- foreach(i=1:4) %dorng%{ runif(1) } #' set.seed(123) #' r2 <- foreach(i=1:4) %dorng%{ runif(1) } #' identical(r1, r2) #' \dontshow{ stopifnot(identical(r1, r2)) } #' #' # alternative way of seeding #' a1 <- foreach(i=1:4, .options.RNG=123) %dorng%{ runif(1) } #' a2 <- foreach(i=1:4, .options.RNG=123) %dorng%{ runif(1) } #' identical(a1, a2) && identical(a1, r1) #' \dontshow{ stopifnot(identical(a1, a2) && identical(a1, r1)) } #' #' ## sequences of %dorng% loops _are_ reproducible #' set.seed(123) #' s1 <- foreach(i=1:4) %dorng%{ runif(1) } #' s2 <- foreach(i=1:4) %dorng%{ runif(1) } #' identical(s1, r1) && !identical(s1, s2) #' \dontshow{ stopifnot(identical(s1, r1) && !identical(s1, s2)) } #' #' set.seed(123) #' s1.2 <- foreach(i=1:4) %dorng%{ runif(1) } #' s2.2 <- foreach(i=1:4) %dorng%{ runif(1) } #' identical(s1, s1.2) && identical(s2, s2.2) #' \dontshow{ stopifnot(identical(s1, s1.2) && identical(s2, s2.2)) } #' #' ## Non-invasive way of converting %dopar% loops into reproducible loops #' registerDoRNG(123) #' s3 <- foreach(i=1:4) %dopar%{ runif(1) } #' s4 <- foreach(i=1:4) %dopar%{ runif(1) } #' identical(s3, s1) && identical(s4, s2) #' \dontshow{ stopifnot(identical(s3, s1) && identical(s4, s2)) } #' #' stopCluster(cl) #' NULL `_PACKAGE` <- "doRNG" doRNG/R/doRNG.R0000644000175100001440000004621214360233461012545 0ustar hornikusers# Development of a dorng equivalent to dopar for reproducible loops # # Author: Renaud Gaujoux # Creation: 17 Aug 2011 ############################################################################### #library(foreach) # or-NULL operator (borrowed from Hadley Wickham) '%||%' <- function(x, y) if( !is.null(x) ) x else y #' @importFrom utils head .collapse <- function(x, n=length(x), sep=', '){ res <- paste(if( missing(n) ) x else head(x, n), collapse=', ') if( length(x) > n ) res <- paste(res, '...', sep=', ') res } #' Back Compatibility Option for doRNG #' #' Sets the behaviour of %dorng% foreach loops from a #' given version number. #' #' @section Behaviour changes in versions: #' #' \describe{ #' \item{1.4}{ The behaviour of \code{doRNGseed}, and therefore of #' `%dorng%` loops, changed in the case where the current RNG was #' L'Ecuyer-CMRG. #' Using \code{set.seed} before a non-seeded loop used not to be identical #' to seeding via \code{.options.RNG}. #' Another bug was that non-seeded loops would share most of their RNG seed! #' } #' \item{1.7.4}{Prior to this version, in the case where the RNG had not been called yet, #' the first seeded `%dorng%` loops would not give the identical results as #' subsequent loops despite using the same seed #' (see \url{https://github.com/renozao/doRNG/issues/12}). #' #' This has been fixed in version 1.7.4, where the RNG is called once (\code{sample(NA)}), #' whenever the .Random.seed is not found in global environment. #' } #' } #' #' @param x version number to switch to, or missing to get the currently #' active version number, or \code{NULL} to reset to the default behaviour, #' i.e. of the latest version. #' #' @return a character string #' If \code{x} is missing this function returns the version number from the #' current behaviour. #' If \code{x} is specified, the function returns the old value of the #' version number (invisible). #' #' @importFrom utils packageVersion #' @export #' @examples #' #' \dontshow{ registerDoSEQ() } #' #' ## Seeding when current RNG is L'Ecuyer-CMRG #' RNGkind("L'Ecuyer") #' #' doRNGversion("1.4") #' # in version >= 1.4 seeding behaviour changed to fix a bug #' set.seed(123) #' res <- foreach(i=1:3) %dorng% runif(1) #' res2 <- foreach(i=1:3) %dorng% runif(1) #' stopifnot( !identical(attr(res, 'rng')[2:3], attr(res2, 'rng')[1:2]) ) #' res3 <- foreach(i=1:3, .options.RNG=123) %dorng% runif(1) #' stopifnot( identical(res, res3) ) #' #' # buggy behaviour in version < 1.4 #' doRNGversion("1.3") #' res <- foreach(i=1:3) %dorng% runif(1) #' res2 <- foreach(i=1:3) %dorng% runif(1) #' stopifnot( identical(attr(res, 'rng')[2:3], attr(res2, 'rng')[1:2]) ) #' res3 <- foreach(i=1:3, .options.RNG=123) %dorng% runif(1) #' stopifnot( !identical(res, res3) ) #' #' # restore default RNG #' RNGkind("default") #' # restore to current doRNG version #' doRNGversion(NULL) #' doRNGversion <- local({ currentV <- "1.7.4" #as.character(packageVersion('doRNG')) cache <- currentV function(x){ if( missing(x) ) return(cache) if( is.null(x) ) x <- currentV # update cache and return old value old <- cache cache <<- x invisible(old) } }) #' @importFrom utils compareVersion checkRNGversion <- function(x){ compareVersion(doRNGversion(), x) } doRNGseq <- function(n, seed=NULL, ...){ # compute sequence using rngtools::RNGseq # library(rngtools) res <- RNGseq(n, seed, ..., version=if( checkRNGversion('1.4') >=0 ) 2 else 1, simplify = FALSE) } #' Getting Information About doRNG Foreach Backend #' #' \code{infoDoRNG} returns information about the doRNG backend, e.g., version, #' number of workers. #' It is not meant to be called by the user. #' #' #' @param data a list of data used by the backend #' @param item the data item requested, as a character string #' (e.g. 'name', 'workers', 'version') #' #' @return \code{infoDoRNG} returns the requested info (usually as a character #' string or a numeric value). #' #' @keywords internal #' @author Renaud Gaujoux #' infoDoRNG <- function (data, item) { switch(item , workers = data$backend$info(data$backend$data, "workers") , name = "doRNG" , version = "doRNG 1.7.3" , NULL) } #' @describeIn infoDoRNG implements the generic reproducible foreach backend. It should #' not be called directly by the user. #' #' @param obj a foreach description of the loop arguments #' @param ex the lopp expression #' @param envir the loop's evaluation environment #' @param data configuration data of the doRNG backend #' doRNG <- function (obj, ex, envir, data){ if( is.null(obj$options) ) obj$options <- list() if( !'RNG' %in% names(obj$options) ){ obj$options$RNG <- if( !data$once || data$nseed==0 ){ #message("doRNG backend - use seed ", if( data$once ) "only once" else "for every loop", ":") data$seed } else NULL } # data$nseed <- data$nseed + 1 # assign('data', data, pos=foreach:::.foreachGlobals) rngBackend <- getDoBackend() # increment number of calls to doRNG rngBackend$data$nseed <- rngBackend$data$nseed + 1 # directly register (temporarly) the computing backend on.exit({setDoBackend(rngBackend)}, add=TRUE) setDoBackend(rngBackend$data$backend) do.call(doRNG::`%dorng%`, list(obj, ex), envir = envir) } ##% Get/Sets the registered foreach backend's data getDoBackend <- function(){ # one has to get the complete set of backend data from within the foreach Namespace foreach_ns <- asNamespace('foreach') # .foreachGlobals <- get('.foreachGlobals', foreach_ns) .foreachGlobals <- ns_get('.foreachGlobals', foreach_ns) # getDoPar <- get('getDoPar', foreach_ns) getDoPar <- ns_get('getDoPar', foreach_ns) c(getDoPar() , info= if( exists("info", where = .foreachGlobals, inherits = FALSE) ) .foreachGlobals$info else function(data, item) NULL) } setDoBackend <- function(backend){ ob <- getDoBackend() do.call(setDoPar, backend) invisible(ob) } .getDoParName <- function(backend = getDoBackend(), version = FALSE) { if ( !is.null(backend[['info']]) ){ res <- backend[['info']](backend[['data']], "name") if( version ) paste0(res, '(', backend[['info']](backend[['data']], "version"), ')') res } } #' Reproducible Parallel Foreach Backend #' #' `%dorng%` is a foreach operator that provides an alternative operator #' `%dopar%`, which enable reproducible foreach loops to be performed. #' #' @param obj a foreach object as returned by a call to \code{\link{foreach}}. #' @param ex the \code{R} expression to evaluate. #' #' @return `%dorng%` returns the result of the foreach loop. See [foreach::%dopar%]. #' The whole sequence of RNG seeds is stored in the result object as an attribute. #' Use \code{attr(res, 'rng')} to retrieve it. #' #' @section Global options: #' #' These options are for advanced users that develop `foreach backends: #' #' * 'doRNG.rng_change_warning_skip': if set to a single logical `FALSE/TRUE`, it indicates #' whether a warning should be thrown if the RNG seed is changed by the registered #' parallel backend (default=FALSE). #' Set it to `TRUE` if you know that running your backend will change the RNG state and #' want to disable the warning. #' This option can also be set to a character vector that specifies the name(s) of the backend(s) #' for which the warning should be skipped. #' #' @importFrom iterators iter #' @export #' @usage obj \%dorng\% ex #' @seealso \code{\link{foreach}}, \code{\link[doParallel]{doParallel}} #' , \code{\link[doParallel]{registerDoParallel}}, \code{\link[doMPI]{doMPI}} #' @examples #' #' library(doParallel) #' cl <- makeCluster(2) #' registerDoParallel(cl) #' #' # standard %dopar% loops are _not_ reproducible #' set.seed(1234) #' s1 <- foreach(i=1:4) %dopar% { runif(1) } #' set.seed(1234) #' s2 <- foreach(i=1:4) %dopar% { runif(1) } #' identical(s1, s2) #' #' # single %dorng% loops are reproducible #' r1 <- foreach(i=1:4, .options.RNG=1234) %dorng% { runif(1) } #' r2 <- foreach(i=1:4, .options.RNG=1234) %dorng% { runif(1) } #' identical(r1, r2) #' # the sequence os RNG seed is stored as an attribute #' attr(r1, 'rng') #' #' # stop cluster #' stopCluster(cl) #' #' # More examples can be found in demo `doRNG` #' \dontrun{ #' demo('doRNG') #' } #' #' @demo Some features of the %dorng% foreach operator #' #' library(doRNG) #' library(doParallel) #' #' if( .Platform$OS.type == "unix" ){ #' registerDoParallel(2) #' #' # single %dorng% loops are reproducible #' r1 <- foreach(i=1:4, .options.RNG=1234) %dorng% { runif(1) } #' r2 <- foreach(i=1:4, .options.RNG=1234) %dorng% { runif(1) } #' identical(r1, r2) #' # the sequence os RNG seed is stored as an attribute #' attr(r1, 'rng') #' #' # sequences of %dorng% loops are reproducible #' set.seed(1234) #' s1 <- foreach(i=1:4) %dorng% { runif(1) } #' s2 <- foreach(i=1:4) %dorng% { runif(1) } #' # two consecutive (unseed) %dorng% loops are not identical #' identical(s1, s2) #' #' # But the whole sequence of loops is reproducible #' set.seed(1234) #' s1.2 <- foreach(i=1:4) %dorng% { runif(1) } #' s2.2 <- foreach(i=1:4) %dorng% { runif(1) } #' identical(s1, s1.2) && identical(s2, s2.2) #' # it gives the same result as with .options.RNG #' identical(r1, s1) #' #' } #' #' # Works with SNOW-like and MPI clusters #' # SNOW-like cluster #' cl <- makeCluster(2) #' registerDoParallel(cl) #' #' s1 <- foreach(i=1:4, .options.RNG=1234) %dorng% { runif(1) } #' s2 <- foreach(i=1:4, .options.RNG=1234) %dorng% { runif(1) } #' identical(s1, s2) #' #' stopCluster(cl) #' registerDoSEQ() #' #' # MPI cluster #' library(doMPI) #' cl <- startMPIcluster(2) #' registerDoMPI(cl) #' #' s1 <- foreach(i=1:4, .options.RNG=1234) %dorng% { runif(1) } #' s2 <- foreach(i=1:4, .options.RNG=1234) %dorng% { runif(1) } #' identical(s1, s2) #' #' closeCluster(cl) #' registerDoSEQ() #' #' `%dorng%` <- function(obj, ex){ #library(rngtools) # str(obj) # dump verbose messages if not in verbose mode verbose <- !is.null(obj$verbose) && obj$verbose if( !verbose ) message <- function(...) NULL # exit if nested or conditional loop if( any(c('xforeach', 'filteredforeach') %in% class(obj)) ) stop("nested/conditional foreach loops are not supported yet.\nSee the package's vignette for a work around.") # if an RNG seed is provided then setup random streams # and add the list of RNGs to use as an iterated arguments for %dopar% # library(parallel) N_elem <- length(as.list(iter(obj))) obj$argnames <- c(obj$argnames, '.doRNG.stream') obj$args$.doRNG.stream <- rep(NA_integer_, N_elem) # make sure the RNG seed is initialized by calling getRNG() if( is.null(RNGseed()) ){ if( checkRNGversion("1.7.4") >= 0 ){ message("NOTE -- .Random.seed is not initialized: sampling once to ensure reproducibility.") getRNG() }else{ warning(paste0(".Random.seed is not initialized: results might not be reproducible.\n ", "Update to doRNG version >= 1.7.4 to get a fix for this issue.")) } } ## # restore current RNG on exit if a seed is passed rngSeed <- if( !is.null(obj$options$RNG) ){ # setup current RNG restoration RNG.old <- RNGseed() on.exit({RNGseed(RNG.old)}, add=TRUE) # extract RNG setting from object if possible (do not resolve single seed) rngSeed <- getRNG(obj$options$RNG, num.ok=TRUE) %||% obj$options$RNG # ensure it is a list # NB: unnamed lists are sequences of seeds if( !is.list(rngSeed) || is.null(names(rngSeed)) ){ rngSeed <- list(rngSeed) } rngSeed } # message("* Seed specification: ", str_out(rngSeed, 6, total = length(rngSeed) > 6)) # generate a sequence of streams # print("before RNGseq") # showRNG() obj$args$.doRNG.stream <- do.call(doRNGseq, c(list(n=N_elem, verbose=obj$verbose), rngSeed)) # print("after RNGseq") # showRNG() #print(obj$args$.doRNG.stream) message("* Registered backend: ", .getDoParName(version = TRUE)) dp <- getDoParName() # directly register (temporarly) the computing backend if( !is.null(dp) && dp == 'doRNG' ){ rngBackend <- getDoBackend() message("* Registering computing backend: ", .getDoParName(rngBackend$data$backend, version = TRUE)) on.exit({ message("* Restoring previous backend: ", .getDoParName(rngBackend)) setDoBackend(rngBackend) }, add=TRUE) setDoBackend(rngBackend$data$backend) dp <- getDoParName() } ## SEPCIAL CASE FOR doSEQ or doMPI # TODO: figure out why doMPI draws once from the current RNG (must be linked # to using own code to setup L'Ecuyer RNG) # restore RNG settings as after RNGseq if doSEQ is the backend and no seed was passed if( is.null(obj$options$RNG) ){ RNG.old <- RNGseed() on.exit({ rng_type_changed <- !identical(RNGtype(), RNGtype(RNG.old)) warning_skip <- getOption("doRNG.rng_change_warning_skip", FALSE) force_warning <- getOption("doRNG.rng_change_warning_force", FALSE) known_changing_cases <- is.null(dp) || dp %in% c("doSEQ", "doMPI") || (is.logical(warning_skip) && !is.na(warning_skip) && warning_skip) || (is.character(warning_skip) && dp %in% warning_skip) if( known_changing_cases || rng_type_changed ){ if( force_warning || (rng_type_changed && !known_changing_cases) ){ warning(sprintf("Foreach loop (%s) had changed the current RNG type: RNG was restored to same type, next state", dp %||% "unknown")) }else{ message("* Detected known RNG side effect: ", dp) } message("* Restoring RNG as after RNG sequence generation") if( verbose ) showRNG(RNG.old, indent = " -") RNGseed(RNG.old) message("OK") } }, add=TRUE) } ## # export package doRNG if not already exported if( !('doRNG' %in% obj$packages) ) obj$packages <- c(obj$packages, 'doRNG') # append code to the loop expression to set the RNG ex <- as.call(list(as.name('{'), quote({rngtools::RNGseed(.doRNG.stream);}), substitute(ex))) # call the standard %dopar% operator res <- do.call(`%dopar%`, list(obj, ex), envir=parent.frame()) # add seed sequence as an attribute (skip this for NULL results) if( !is.null(res) ){ attr(res, 'rng') <- obj$args$.doRNG.stream attr(res, 'doRNG_version') <- doRNGversion() } # return result res } #' Registering doRNG for Persistent Reproducible Parallel Foreach Loops #' #' \code{registerDoRNG} registers the doRNG foreach backend. #' Subsequent `%dopar%` loops are then performed using the previously #' registered foreach backend, but are internally performed as [%dorng%] loops, #' making them fully reproducible. #' #' Briefly, the RNG is set, before each iteration, with seeds for L'Ecuyer's CMRG #' that overall generate a reproducible sequence of statistically independent #' random streams. #' #' Note that (re-)registering a foreach backend other than doRNG, after a call #' to \code{registerDoRNG} disables doRNG -- which then needs to be registered. #' #' @param seed a numerical seed to use (as a single or 6-length numerical value) #' @param once a logical to indicate if the RNG sequence should be seeded at the #' beginning of each loop or only at the first loop. #' #' @return The value returned by [foreach::setDoPar] #' #' @seealso [%dorng%] #' @export #' @examples #' #' library(doParallel) #' cl <- makeCluster(2) #' registerDoParallel(cl) #' #' # One can make reproducible loops using the %dorng% operator #' r1 <- foreach(i=1:4, .options.RNG=1234) %dorng% { runif(1) } #' # or convert %dopar% loops using registerDoRNG #' registerDoRNG(1234) #' r2 <- foreach(i=1:4) %dopar% { runif(1) } #' identical(r1, r2) #' stopCluster(cl) #' #' # Registering another foreach backend disables doRNG #' cl <- makeCluster(2) #' registerDoParallel(cl) #' set.seed(1234) #' s1 <- foreach(i=1:4) %dopar% { runif(1) } #' set.seed(1234) #' s2 <- foreach(i=1:4) %dopar% { runif(1) } #' identical(s1, s2) #' \dontshow{ stopifnot(!identical(s1, s2)) } #' #' # doRNG is re-nabled by re-registering it #' registerDoRNG() #' set.seed(1234) #' r3 <- foreach(i=1:4) %dopar% { runif(1) } #' identical(r2, r3) #' # NB: the results are identical independently of the task scheduling #' # (r2 used 2 nodes, while r3 used 3 nodes) #' #' # argument `once=FALSE` reseeds doRNG's seed at the beginning of each loop #' registerDoRNG(1234, once=FALSE) #' r1 <- foreach(i=1:4) %dopar% { runif(1) } #' r2 <- foreach(i=1:4) %dopar% { runif(1) } #' identical(r1, r2) #' #' # Once doRNG is registered the seed can also be passed as an option to %dopar% #' r1.2 <- foreach(i=1:4, .options.RNG=456) %dopar% { runif(1) } #' r2.2 <- foreach(i=1:4, .options.RNG=456) %dopar% { runif(1) } #' identical(r1.2, r2.2) && !identical(r1.2, r1) #' \dontshow{ stopifnot(identical(r1.2, r2.2) && !identical(r1.2, r1)) } #' #' stopCluster(cl) #' registerDoRNG <- function(seed=NULL, once=TRUE){ backend <- getDoBackend() # use stored backend if registerDoRNG was called repeatedly if( identical(getDoParName(), 'doRNG') ) backend <- backend$data$backend # set the current RNG with seed immediately if only used once if( once && !is.null(seed) ){ if( !is.numeric(seed) || length(seed)!=1L ) stop("Invalid seed: must be a single numeric value.") set.seed(seed) seed <- NULL } setDoPar(doRNG, list(seed=seed, once=once, nseed=0, backend=backend), infoDoRNG) } ###% Reproducibly Apply a Function over a List or Vector ###% ###% @aliases xapply reproduce ###% ###% \code{reproduce} and \code{xapply} are a reproducible versions ###% of \code{\link{replicate}} and \code{\link{sapply}} respectively, ###% that ensures the reproducibility of the results, when stochastic computations ###% are involved. ###% ###% The reproducibility is achieved by using LEcuyer's RNG provided by R core ###% since R-2.14.0, to generate independent random streams ###% that are used as the random number generator for each replicate. ###% ###% @param n the number of replication as a single numeric (integer) ###% @param seed the main numerical seed used to initialize the sequence of random ###% streams ###% @param expr the expression (language object, usually a call) to evaluate repeatedly ###% @param simplify logical; should the result be simplified to a vector or ###% matrix if possible? ###% ###% ###% #reproduce <- function (n, expr, seed=NULL, simplify = TRUE){ # f <- eval.parent(substitute(function(...) expr)) # xapply(integer(n), seed, f, simplify = simplify) #} # #xapply <- function (X, FUN, seed=NULL, ..., simplify = TRUE, USE.NAMES = TRUE){ # # # generate a sequence of streams # .RNG.stream <- RNGseq(length(X), seed, packed=TRUE) # # # keep current RNG and restore it on exit (useful for the sequential backend doSEQ) # RNG.old <- rstream.RNG() # on.exit(rstream.RNG(RNG.old), add=TRUE) # # # append code to the loop expression to set the RNG # expr <- as.call(list(as.name('{'), # quote({doRNGseed(.rng);}), # quote(do.call(FUN, list(...))))) # # env <- environment(FUN) # f <- eval(substitute(function(.rng, ..., FUN) expr), env) # mapply(f, .RNG.stream, X, MoreArgs=c(list(...), FUN=FUN), # SIMPLIFY = simplify, USE.NAMES= USE.NAMES) #} doRNG/demo/0000755000175100001440000000000013556560424012177 5ustar hornikusersdoRNG/demo/00Index0000644000175100001440000000006413556341614013327 0ustar hornikusersdoRNG Some features of the %dorng% foreach operator doRNG/demo/doRNG.R0000644000175100001440000000257513556560424013304 0ustar hornikuserslibrary(doRNG) library(doParallel) if( .Platform$OS.type == "unix" ){ registerDoParallel(2) # single %dorng% loops are reproducible r1 <- foreach(i=1:4, .options.RNG=1234) %dorng% { runif(1) } r2 <- foreach(i=1:4, .options.RNG=1234) %dorng% { runif(1) } identical(r1, r2) # the sequence os RNG seed is stored as an attribute attr(r1, 'rng') # sequences of %dorng% loops are reproducible set.seed(1234) s1 <- foreach(i=1:4) %dorng% { runif(1) } s2 <- foreach(i=1:4) %dorng% { runif(1) } # two consecutive (unseed) %dorng% loops are not identical identical(s1, s2) # But the whole sequence of loops is reproducible set.seed(1234) s1.2 <- foreach(i=1:4) %dorng% { runif(1) } s2.2 <- foreach(i=1:4) %dorng% { runif(1) } identical(s1, s1.2) && identical(s2, s2.2) # it gives the same result as with .options.RNG identical(r1, s1) } # Works with SNOW-like and MPI clusters # SNOW-like cluster cl <- makeCluster(2) registerDoParallel(cl) s1 <- foreach(i=1:4, .options.RNG=1234) %dorng% { runif(1) } s2 <- foreach(i=1:4, .options.RNG=1234) %dorng% { runif(1) } identical(s1, s2) stopCluster(cl) registerDoSEQ() # MPI cluster library(doMPI) cl <- startMPIcluster(2) registerDoMPI(cl) s1 <- foreach(i=1:4, .options.RNG=1234) %dorng% { runif(1) } s2 <- foreach(i=1:4, .options.RNG=1234) %dorng% { runif(1) } identical(s1, s2) closeCluster(cl) registerDoSEQ() doRNG/vignettes/0000755000175100001440000000000014741201176013254 5ustar hornikusersdoRNG/vignettes/doRNG.Rnw0000644000175100001440000005004614741201176014722 0ustar hornikusers% \documentclass[a4paper,12pt]{article} %\VignetteIndexEntry{Using the package doRNG} %\VignetteDepends{doRNG,doParallel,knitr,doRedis,rbenchmark,pkgmaker} %\VignetteCompiler{knitr} %\VignetteEngine{knitr::knitr} \usepackage{a4wide} \usepackage{xspace} \usepackage[colorlinks]{hyperref} % for hyperlinks \usepackage{tocloft} \renewcommand{\cftsecleader}{\cftdotfill{\cftdotsep}} \usepackage[toc]{multitoc} % add preamble from pkgmaker <>= pkgmaker::latex_preamble() @ % REFERENCES \usepackage[minnames=1,maxnames=2,backend=bibtex]{biblatex} \AtEveryCitekey{\clearfield{url}} <>= pkgmaker::latex_bibliography('doRNG') @ \newcommand{\citet}[1]{\citeauthor{#1}~\cite{#1}} %% \newcommand{\graphwidth}{0.9\columnwidth} % clever references \usepackage[noabbrev, capitalise, nameinlink]{cleveref} \newcommand{\dorng}{\code{\%dorng\%}\xspace} \title{Using the \code{doRNG} package\\ {\small \Rpkg{doRNG} -- Version \Sexpr{packageVersion('doRNG')}}} \author{Renaud Gaujoux} \begin{document} \maketitle \tableofcontents \section*{Introduction} \addcontentsline{toc}{section}{Introduction} Research reproducibility is an issue of concern, e.g. in bioinformatics \cite{Hothorn2011,Stodden2011,Ioannidis2008}. Some analyses require multiple independent runs to be performed, or are amenable to a split-and-reduce scheme. For example, some optimisation algorithms are run multiple times from different random starting points, and the result that achieves the least approximation error is selected. The \citeCRANpkg{foreach} provides a very convenient way to perform parallel computations, with different parallel environments such as MPI or Redis, using a transparent loop-like syntax: <>= options(width=90) library(pkgmaker) library(knitr) opts_chunk$set(size = "footnotesize") knit_hooks$set(try = pkgmaker::hook_try) @ <>= # load and register parallel backend for multicore computations library(doParallel) cl <- makeCluster(2) registerDoParallel(cl) # perform 5 tasks in parallel x <- foreach(i=1:5) %dopar% { i + runif(1) } unlist(x) @ For each parallel environment a \emph{backend} is implemented as a specialised \code{\%dopar\%} operator, which performs the setup and pre/post-processing specifically required by the environment (e.g. export of variable to each worker). The \code{foreach} function and the \code{\%dopar\%} operator handle the generic parameter dispatch when the task are split between worker processes, as well as the reduce step -- when the results are returned to the master worker. When stochastic computations are involved, special random number generators must be used to ensure that the separate computations are indeed statistically independent -- unless otherwise wanted -- and that the loop is reproducible. In particular, standard \code{\%dopar\%} loops are not reproducible: <>= # with standard %dopar%: foreach loops are not reproducible set.seed(123) res <- foreach(i=1:5) %dopar% { runif(3) } set.seed(123) res2 <- foreach(i=1:5) %dopar% { runif(3) } identical(res, res2) @ A random number generator commonly used to achieve reproducibility is the combined multiple-recursive generator from \citet{Lecuyer1999}. This generator can generate independent random streams, from a 6-length numeric seed. The idea is then to generate a sequence of random stream of the same length as the number of iteration (i.e. tasks) and use a different stream when computing each one of them. The \citeCRANpkg{doRNG} provides convenient ways to implement reproducible parallel \code{foreach} loops, independently of the parallel backend used to perform the computation. We illustrate its use, showing how non-reproducible loops can be made reproducible, even when tasks are not scheduled in the same way in two separate set of runs, e.g. when the workers do not get to compute the same number of tasks or the number of workers is different. The package has been tested with the \CRANpkg*{doParallel} and \CRANpkg*{doMPI} packages \citepkg{Rpackage:doMPI,Rpackage:doParallel}, but should work with other backends such as provided by the \citeCRANpkg{doRedis}. \section{The \texttt{\%dorng\%} operator} The \Rpkg{doRNG} defines a new generic operator, \code{\%dorng\%}, to be used with foreach loops, instead of the standard {\%dopar\%}. Loops that use this operator are \emph{de facto} reproducible. <>= # load the doRNG package library(doRNG) # using %dorng%: loops _are_ reproducible set.seed(123) res <- foreach(i=1:5) %dorng% { runif(3) } set.seed(123) res2 <- foreach(i=1:5) %dorng% { runif(3) } identical(res, res2) @ \subsection{How it works} For a loop with $N$ iterations, the \code{\%dorng\%} operator internally performs the following tasks: \begin{enumerate} \item generate a sequence of random seeds $(S_i)_{1\leq i\leq N}$ for the \proglang{R} random number generator \code{"L'Ecuyer-CMRG"} \cite{Lecuyer1999}, using the function \code{nextRNGStream} from the \citeCRANpkg{parallel}, which ensure the different RNG streams are statistically independent; \item modify the loop's \proglang{R} expression so that the random number generator is set to \code{"L'Ecuyer-CMRG"} at the beginning of each iteration, and is seeded with consecutive seeds in $(S_n)$: iteration $i$ is seeded with $S_i$, $1\leq i\leq N$; \item call the standard \code{\%dopar\%} operator, which in turn calls the relevant (i.e. registered) foreach parallel backend; \item store the whole sequence of random seeds as an attribute in the result object: <>= attr(res, 'rng') @ \end{enumerate} \subsection{Seeding computations} Sequences of random streams for \code{"L'Ecuyer-CMRG"} are generated using a 6-length integer seed, e.g.,: <>= nextRNGStream(c(407L, 1:6)) @ However, the \code{\%dorng\%} operator provides alternative -- convenient -- ways of seeding reproducible loops. \begin{description} \item[\code{set.seed}:] as shown above, calling \code{set.seed} before the loop ensure reproducibility of the results, using a single integer as a seed. The actual 6-length seed is then generated with an internal call to \code{RNGkind("L'Ecuyer-CMRG")}. \item[\code{.options.RNG} with single integer:] the \dorng operator support options that can be passed in the \code{foreach} statement, containing arguments for the internal call to \code{set.seed}: <>= # use a single numeric as a seed s <- foreach(i=1:5, .options.RNG=123) %dorng% { runif(3) } s2 <- foreach(i=1:5, .options.RNG=123) %dorng% { runif(3) } identical(s, s2) @ \noindent \textbf{Note}: calling \code{set.seed} before the loop is equivalent to passing the seed in \code{.options.RNG}. See \cref{sec:set_seed} for more details. \medskip The kind of Normal generator may also be passed in \code{.options.RNG}: <>= ## Pass the Normal RNG kind to use within the loop # results are identical if not using the Normal kind in the loop optsN <- list(123, normal.kind="Ahrens") resN.U <- foreach(i=1:5, .options.RNG=optsN) %dorng% { runif(3) } identical(resN.U[1:5], res[1:5]) # Results are different if the Normal kind is used and is not the same resN <- foreach(i=1:5, .options.RNG=123) %dorng% { rnorm(3) } resN1 <- foreach(i=1:5, .options.RNG=optsN) %dorng% { rnorm(3) } resN2 <- foreach(i=1:5, .options.RNG=optsN) %dorng% { rnorm(3) } identical(resN[1:5], resN1[1:5]) identical(resN1[1:5], resN2[1:5]) @ \item[\code{.options.RNG} with 6-length:] the actual 6-length integer seed used for the first RNG stream may be passed via \code{options.RNG}: <>= # use a 6-length numeric s <- foreach(i=1:5, .options.RNG=1:6) %dorng% { runif(3) } attr(s, 'rng')[1:3] @ \item[\code{.options.RNG} with 7-length:] a 7-length integer seed may also be passed via \code{options.RNG}, which is useful to seed a loop with the value of \code{.Random.seed} as used in some iteration of another loop\footnote{Note that the RNG kind is then always required to be the \code{"L'Ecuyer-CMRG"}, i.e. the first element of the seed must have unit 7 (e.g. 407 or 107).}: <>= # use a 7-length numeric, used as first value for .Random.seed seed <- attr(res, 'rng')[[2]] s <- foreach(i=1:5, .options.RNG=seed) %dorng% { runif(3) } identical(s[1:4], res[2:5]) @ \item[\code{.options.RNG} with complete sequence of seeds:] the complete description of the sequence of seeds to be used may be passed via \code{options.RNG}, as a list or a matrix with the seeds in columns. This is useful to seed a loop exactly as desired, e.g. using an RNG other than \code{"L'Ecuyer-CMRG"}, or using different RNG kinds in each iteration, which probably have different seed length, in order to compare their stochastic properties. It also allows to reproduce \code{\%dorng\%} loops without knowing their seeding details: <>= # reproduce previous %dorng% loop s <- foreach(i=1:5, .options.RNG=res) %dorng% { runif(3) } identical(s, res) ## use completely custom sequence of seeds (e.g. using RNG "Marsaglia-Multicarry") # as a matrix seedM <- rbind(rep(401, 5), mapply(rep, 1:5, 2)) seedM sM <- foreach(i=1:5, .options.RNG=seedM) %dorng% { runif(3) } # same seeds passed as a list seedL <- lapply(seq(ncol(seedM)), function(i) seedM[,i]) sL <- foreach(i=1:5, .options.RNG=seedL) %dorng% { runif(3) } identical(sL, sM) @ \end{description} \subsection{Difference between \texttt{set.seed} and \texttt{.options.RNG}} \label{sec:set_seed} While it is equivalent to seed \dorng loops with \code{set.seed} and \code{.options.RNG}, it is important to note that the result depends on the current RNG kind \footnote{See \cref{sec:issues} about a bug in versions < 1.4 on this feature.}: <>= # default RNG kind RNGkind('default') def <- foreach(i=1:5, .options.RNG=123) %dorng% { runif(3) } # Marsaglia-Multicarry RNGkind('Marsaglia') mars <- foreach(i=1:5, .options.RNG=123) %dorng% { runif(3) } identical(def, mars) # revert to default RNG kind RNGkind('default') @ This is a ``normal'' behaviour, which is a side-effect of the expected equivalence between \code{set.seed} and \code{.options.RNG}. This should not be a problem for reproducibility though, as R RNGs are stable across versions, and loops are most of the time used with the default RNG settings. In order to ensure seeding is independent from the current RNG, one has to pass a 7-length numeric seed to \code{.options.RNG}, which is then used directly as a value for \code{.Random.seed} (see below). \section{Parallel environment independence} An important feature of \code{\%dorng\%} loops is that their result is independent of the underlying parallel physical settings. Two separate runs seeded with the same value will always produce the same results. Whether they use the same number of worker processes, parallel backend or task scheduling does not influence the final result. This also applies to computations performed sequentially with the \code{doSEQ} backend. The following code illustrates this feature using 2 or 3 workers. <>= # define a stochastic task to perform task <- function() c(pid=Sys.getpid(), val=runif(1)) # using the previously registered cluster with 2 workers set.seed(123) res_2workers <- foreach(i=1:5, .combine=rbind) %dorng% { task() } # stop cluster stopCluster(cl) # Sequential computation registerDoSEQ() set.seed(123) res_seq <- foreach(i=1:5, .combine=rbind) %dorng% { task() } # # Using 3 workers # NB: if re-running this vignette you should edit to force using 3 here cl <- makeCluster( if(isManualVignette()) 3 else 2) length(cl) # register new cluster registerDoParallel(cl) set.seed(123) res_3workers <- foreach(i=1:5, .combine=rbind) %dorng% { task() } # task schedule is different pid <- rbind(res1=res_seq[,1], res_2workers[,1], res2=res_3workers[,1]) storage.mode(pid) <- 'integer' pid # results are identical identical(res_seq[,2], res_2workers[,2]) && identical(res_2workers[,2], res_3workers[,2]) @ \section{Reproducible \texttt{\%dopar\%} loops} The \Rpkg{doRNG} also provides a non-invasive way to convert \code{\%dopar\%} loops into reproducible loops, i.e. without changing their actual definition. It is useful to quickly ensure the reproducibility of existing code or functions whose definition is not accessible (e.g. from other packages). This is achieved by registering the \code{doRNG} backend: <>= set.seed(123) res <- foreach(i=1:5) %dorng% { runif(3) } registerDoRNG(123) res_dopar <- foreach(i=1:5) %dopar% { runif(3) } identical(res_dopar, res) attr(res_dopar, 'rng') @ \section{Reproducibile sets of loops} Sequences of multiple loops are reproducible, whether using the \code{\%dorng\%} operator or the registered \code{doRNG} backend: <>= set.seed(456) s1 <- foreach(i=1:5) %dorng% { runif(3) } s2 <- foreach(i=1:5) %dorng% { runif(3) } # the two loops do not use the same streams: different results identical(s1, s2) # but the sequence of loops is reproducible as a whole set.seed(456) r1 <- foreach(i=1:5) %dorng% { runif(3) } r2 <- foreach(i=1:5) %dorng% { runif(3) } identical(r1, s1) && identical(r2, s2) # one can equivalently register the doRNG backend and use %dopar% registerDoRNG(456) r1 <- foreach(i=1:5) %dopar% { runif(3) } r2 <- foreach(i=1:5) %dopar% { runif(3) } identical(r1, s1) && identical(r2, s2) @ \section{Nested and conditional loops} \label{sec:nested} Nested and conditional foreach loops are currently not supported and generate an error: <>= # nested loop try( foreach(i=1:10) %:% foreach(j=1:i) %dorng% { rnorm(1) } ) # conditional loop try( foreach(i=1:10) %:% when(i %% 2 == 0) %dorng% { rnorm(1) } ) @ In this section, we propose a general work around for this kind of loops, that will eventually be incorporated in the \code{\%dorng\%} operator -- when I find out how to mimic its behaviour from the operator itself. \subsection{Nested loops} The idea is to create a sequence of RNG seeds before the outer loop, and use each of them successively to set the RNG in the inner loop -- which is exactly what \code{\%dorng\%} does for simple loops: <>= # doRNG must not be registered registerDoParallel(cl) # generate sequence of seeds of length the number of computations n <- 10; p <- 5 rng <- RNGseq( n * p, 1234) # run standard nested foreach loop res <- foreach(i=1:n) %:% foreach(j=1:p, r=rng[(i-1)*p + 1:p]) %dopar% { # set RNG seed rngtools::setRNG(r) # do your own computation ... c(i, j, rnorm(1)) } # Compare against the equivalent sequential computations k <- 1 res2 <- foreach(i=1:n) %:% foreach(j=1:p) %do%{ # set seed rngtools::setRNG(rng[[k]]) k <- k + 1 # do your own computation ... c(i, j, rnorm(1)) } stopifnot( identical(res, res2) ) @ The following is a more complex example with unequal -- but \textbf{known \emph{a priori}} -- numbers of iterations performed in the inner loops: <>= # generate sequence of seeds of length the number of computations n <- 10 rng <- RNGseq( n * (n+1) / 2, 1234) # run standard nested foreach loop res <- foreach(i=1:n) %:% foreach(j=1:i, r=rng[(i-1)*i/2 + 1:i]) %dopar%{ # set RNG seed rngtools::setRNG(r) # do your own computation ... c(i, j, rnorm(1)) } # Compare against the equivalent sequential computations k <- 1 res2 <- foreach(i=1:n) %:% foreach(j=1:i) %do%{ # set seed rngtools::setRNG(rng[[k]]) k <- k + 1 # do your own computation ... c(i, j, rnorm(1)) } stopifnot( identical(res, res2) ) @ \subsection{Conditional loops} The work around used for nested loops applies to conditional loops that use the \code{when()} clause. It ensures that the RNG seed use for a given inner iteration does not depend on the filter, but only on its index in the unconditional-unfolded loop: <>= # un-conditional single loop resAll <- foreach(i=1:n, .options.RNG=1234) %dorng%{ # do your own computation ... c(i, rnorm(1)) } # generate sequence of RNG rng <- RNGseq(n, 1234) # conditional loop: even iterations resEven <- foreach(i=1:n, r=rng) %:% when(i %% 2 == 0) %dopar%{ # set RNG seed rngtools::setRNG(r) # do your own computation ... c(i, rnorm(1)) } # conditional loop: odd iterations resOdd <- foreach(i=1:n, r=rng) %:% when(i %% 2 == 1) %dopar%{ # set RNG seed rngtools::setRNG(r) # do your own computation ... c(i, rnorm(1)) } # conditional loop: only first 2 and last 2 resFL <- foreach(i=1:n, r=rng) %:% when(i %in% c(1,2,n-1,n)) %dopar%{ # set RNG seed rngtools::setRNG(r) # do your own computation ... c(i, rnorm(1)) } # compare results stopifnot( identical(resAll[seq(2,n,by=2)], resEven) ) stopifnot( identical(resAll[seq(1,n,by=2)], resOdd) ) stopifnot( identical(resAll[c(1,2,n-1,n)], resFL) ) @ \subsection{Nested conditional loops} Conditional nested loops may use the same work around, as shown in this intricate example: <>= # generate sequence of seeds of length the number of computations n <- 10 rng <- RNGseq( n * (n+1) / 2, 1234) # run standard nested foreach loop res <- foreach(i=1:n) %:% when(i %% 2 == 0) %:% foreach(j=1:i, r=rng[(i-1)*i/2 + 1:i]) %dopar%{ # set RNG seed rngtools::setRNG(r) # do your own computation ... c(i, j, rnorm(1)) } # Compare against the equivalent sequential computations k <- 1 resAll <- foreach(i=1:n) %:% foreach(j=1:i) %do%{ # set seed rngtools::setRNG(rng[[k]]) k <- k + 1 # do your own computation ... c(i, j, rnorm(1)) } stopifnot( identical(resAll[seq(2,n,by=2)], res) ) @ \section{Performance overhead} The extra setup performed by the \code{\%dorng\%} operator leads to a slight performance over-head, which might be significant for very quick computations, but should not be a problem for realistic computations. The benchmarks below show that a \code{\%dorng\%} loop may take up to two seconds more than the equivalent \code{\%dopar\%} loop, which is not significant in practice, where parallelised computations typically take several minutes. <>= # load rbenchmark library(rbenchmark) # comparison is done on sequential computations registerDoSEQ() rPar <- function(n, s=0){ foreach(i=1:n) %dopar% { Sys.sleep(s) } } rRNG <- function(n, s=0){ foreach(i=1:n) %dorng% { Sys.sleep(s) } } # run benchmark cmp <- benchmark(rPar(10), rRNG(10) , rPar(25), rRNG(25) , rPar(50), rRNG(50) , rPar(50, .01), rRNG(50, .01) , rPar(10, .05), rRNG(10, .05) , replications=5) # order by increasing elapsed time cmp[order(cmp$elapsed), ] @ \section{Known issues} \label{sec:issues} \begin{itemize} \item Nested and/or conditional foreach loops using the operator \code{\%:\%} are not currently not supported (see \cref{sec:nested} for a work around). \item An error is thrown in \code{doRNG} 1.2.6, when the package \code{iterators} was not loaded, when used with \code{foreach} >= 1.4. \item There was a bug in versions prior to \code{1.4}, which caused \code{set.seed} and \code{.options.RNG} not to be equivalent when the current RNG was \code{"L'Ecuyer-CMRG"}. This behaviour can still be reproduced by setting: <>= doRNGversion('1.3') @ To revert to the latest default behaviour: <>= doRNGversion(NULL) @ \end{itemize} \section{News and changes} {\scriptsize \begin{verbatim} <>= cat(paste(readLines(system.file('NEWS', package='doRNG')), collapse="\n")) @ \end{verbatim} } \section*{Cleanup} <>= stopCluster(cl) @ \section*{Session information} \addcontentsline{toc}{section}{Session information} <>= sessionInfo() @ \printbibliography[heading=bibintoc] \end{document} doRNG/NAMESPACE0000644000175100001440000000041613621322014012453 0ustar hornikusers# Generated by roxygen2: do not edit by hand export("%dorng%") export(doRNGversion) export(registerDoRNG) import(foreach) import(rngtools) import(stats) importFrom(iterators,iter) importFrom(utils,compareVersion) importFrom(utils,head) importFrom(utils,packageVersion) doRNG/NEWS.md0000644000175100001440000001072014360232665012346 0ustar hornikusers# Changes in version 1.8.4 There is no changes in this version, which was published to reclaim ownership and take the package out of ORPHANED state (issue #23). # Changes in version 1.8 ## Changes o Unit tests are now run through testthat o Minor fixes in man pages and README file o Now depends on rngtools >= 1.3 o The result list gains an attribute 'doRNG_version' that contains the version of doRNG that was used, based on doRNGversion(). NB: this is not necessarily the same as the version of the installed package. o Added the following global option 'doRNG.rng_change_warning_skip'. See ?`%dorng%` (issue #14). o Moved dependency on pkgmaker to Suggests to make installation lighter (issue #10). ## Bug fixes o Enabled running %dorng% loops within a package (incorporating the solution proposed by Elizabeth Byerly in PR#3) o Fixed error with NULL iteration results when setting 'rng' attribute (issue #9) o Fixed error when using unamed foreach arguments (issue #8) o Fixed non-reproducibility issue when the .Random.seed is not yet initialized, e.g., when the session starts and the RNG has not been used yet (issue #12) o Fixed runtime error when package is not attached (issue #13) # Changes in version 1.6.2 ## Bug fixes o Non reproducible %dorng% loop when doRNG is registered over doSEQ (Issue #1 reported by Brenton Kenkel). Actually due to %dorng% not restoring the RNG (to state + 1) when doRNG is registered over doSEQ. o %dorng% was not working properly on loops of length one (Issue #2) # Changes in version 1.6 ## Changes o doRNG now depends on the package pkgmaker (>= 0.20) ## Bug fixes o Check error due number of cores used. Now limited to 2 in examples, vignette and unit test. # Changes in version 1.5 ## Changes o doRNG now depends on the package pkgmaker (>= 0.9) o improved vignette o most of the general RNG utilities have been incorporated in a new package called rngtools. # Changes in version 1.4.1 ## Changes o when the current RNG was L'Ecuyer-CMRG, unseeded loops now use the current RNG stream as for the first stream in the RNG sequence and # Changes the current RNG to the next RNG stream of the last stream in the sequence. ## Bug fixes o fix error "'iter' not found" due to # Changes in foreach package dependencies -- that was announced by Rich Calaway. o loops seeded with set.seed and .options.RNG were not reproducible when current RNG was L'Ecuyer-CMRG (reported by Zhang Peng) o separate unseeded loops were sharing most of their streams, when current RNG was L'Ecuyer-CMRG the RNG seed. o nested/conditional loops were crashing with a bad error. They are still not supported but the error message is nicer and a work around has been added to the vignette (reported by Chanhee Yi and Zhang Peng). # Changes in version 1.2.3 ## Bug fixes o fixed error when running a %dorng% loop on a fresh session, with no parallel backend registered. ## Changes o improved vignette o added more unit tests o changed the name of the RNG attribute on result of %dorng% looops from 'RNG' to 'rng'. It now contains the whole sequence of RNG seeds, instead of only the first one. o RNGseq now accepts a list or a matrix describing the whole sequence of seeds. See vignette for more details. o %dorng% loops can be seeded with a complete sequence of seeds passed as a list, a matrix, or an object with attribute 'rng', e.g. the results of %dorng% loops. See vignette for more details. # Changes in version 1.2.2 ## Bug fixes o separate %dorng% loops were using the same seed. ## New features o add unit tests o first seed is set as an attribute of the loop's result ## Changes o function doRNGseed now returns the seed to use for the first iteration. o RNGseq now change the current RNG state if called with no seed specific. ## Defunct o removed function CMRGseed # Changes in version 1.2 ## Bug fixes o An error was thrown if using %dorng% loops before using any random generator. Thanks to Eric Lehmann for reporting this. ## Changes o add vignette o use package doParallel in examples # Changes in version 1.1 ## Changes o use R core RNG "L'Ecuyer-CMRG" and the parallel package, instead of the implementation provided by the rstream package. doRNG/inst/0000755000175100001440000000000014360272312012216 5ustar hornikusersdoRNG/inst/REFERENCES.bib0000644000175100001440000000713214360256116014324 0ustar hornikusers@Article{Hothorn2011, abstract = {Reproducible research is a concept of providing access to data and software along with published scientific findings. By means of some case studies from different disciplines, we will illustrate reasons why readers should be given the possibility to look at the data and software independently from the authors of the original publication. We report results of a survey comprising 100 papers recently published in Bioinformatics. The main finding is that authors of this journal share a culture of making data available. However, the number of papers where source code for simulation studies or analyzes is available is still rather limited.}, author = {Torsten Hothorn and Friedrich Leisch}, doi = {10.1093/bib/bbq084}, file = {::}, issn = {1477-4054}, journal = {Briefings in bioinformatics}, keywords = {reproducible research,software,statistical analyzes,sweave}, month = {jan}, pmid = {21278369}, title = {{Case studies in reproducibility.}}, url = {http://www.ncbi.nlm.nih.gov/pubmed/21278369}, year = {2011}, } @Misc{Stodden2011, author = {Victoria C Stodden}, booktitle = {AAAS Annual Meeting}, title = {{The Digitization of Science: Reproducibility and Interdisciplinary Knowledge Transfer}}, url = {http://aaas.confex.com/aaas/2011/webprogram/Session3166.html}, year = {2011}, } @Article{Ioannidis2008, abstract = {Given the complexity of microarray-based gene expression studies, guidelines encourage transparent design and public data availability. Several journals require public data deposition and several public databases exist. However, not all data are publicly available, and even when available, it is unknown whether the published results are reproducible by independent scientists. Here we evaluated the replication of data analyses in 18 articles on microarray-based gene expression profiling published in Nature Genetics in 20052006. One table or figure from each article was independently evaluated by two teams of analysts. We reproduced two analyses in principle and six partially or with some discrepancies; ten could not be reproduced. The main reason for failure to reproduce was data unavailability, and discrepancies were mostly due to incomplete data annotation or specification of data processing and analysis. Repeatability of published microarray studies is apparently limited. More strict publication rules enforcing public data availability and explicit description of data processing and analysis should be considered.}, author = {John P A Ioannidis and David B Allison and Catherine A Ball and Issa Coulibaly and Xiangqin Cui and Aed\'in C Culhane and Mario Falchi and Cesare Furlanello and Laurence Game and Giuseppe Jurman and Jon Mangion and Tapan Mehta and Michael Nitzberg and Grier P Page and Enrico Petretto and Vera {Van Noort}}, doi = {10.1038/ng.295}, issn = {10614036}, journal = {Nature Genetics}, number = {2}, pages = {149--155}, publisher = {Nature Publishing Group}, title = {{The reproducibility of lists of differentially expressed genes in microarray studies}}, url = {http://www.nature.com/doifinder/10.1038/ng.295}, volume = {41}, year = {2008}, } @Article{Lecuyer1999, author = {Pierre L'Ecuyer}, doi = {10.1287/opre.47.1.159}, file = {::}, issn = {0030-364X}, journal = {Operations Research}, month = {feb}, number = {1}, pages = {159--164}, title = {{Good Parameters and Implementations for Combined Multiple Recursive Random Number Generators}}, url = {http://www.jstor.org/stable/10.2307/222902 http://pubsonline.informs.org/doi/abs/10.1287/opre.47.1.159}, volume = {47}, year = {1999}, } doRNG/inst/doc/0000755000175100001440000000000014360272312012763 5ustar hornikusersdoRNG/inst/doc/doRNG.pdf0000644000175100001440000036006514741202424014441 0ustar hornikusers%PDF-1.5 % 1 0 obj << /Type /ObjStm /Length 4605 /Filter /FlateDecode /N 90 /First 761 >> stream x\is_oTj1ʻU|ʒ7 I\S,CM'NX$iPfә23&W!shW>Ag!LLXpgD08LjGt&H)E6S2-<:c$,dFhId&Ef+UfEάݷ J Oe )y |ȼ8{#LQLh.KLs~Qq[N8hRn==z=,i2ss>'7gYv8tfpPތg$֤RmuL7WGBJD%^:|[ѻ&x\!YTQ ATG&m:&e$54H3*S JT<O'x: NtM=كr<+ƘW`{X~l &^\35'fN̞̈K%枔bкBROM/Q=NHH`L"I2THel"MT6HJ*":R蚯G77S܈(}RAfnO3hW 5%I&MbmÞ߉)E|8xٳz=p|Bd8.{r∏ʪp\/{q8"+p(O>t8M ^an8]EA&|*Br~gߔbp6NN6e3O_tԛ^]NnKv>[d."G =.p"c=GQ4"_d8* UwU,!|a4j8BV-' ],>- yKO01((f]Q4KVJN.F˳Xԭum)sf~uڑB}͏ jݞGJg&w]'UYϭ+MCoISHI/s95l< V/GAy;2\Jr4KrDz֕:".*imWbZ22"ŝ2bV鯧Z/ɈX *7Yc>:x<~y}s//dR6D& Vd@K w b+tLUOWSԅQFP>@jD/ݧձn#HjD-^6 CG0rO v`M[;> ٤T̢JjKNNkXIi4bJ)jc/հB~KCj#X!ޥl J-*’8釣}KfE-)#(gb^oVߪwm]\0E *3y]6GA]ǧ1΂rUt5b)KR;%$$/rEMR?]-}dW:$ ^ d,\Q\.:|`R0n^V];r <_ѐMQ[}}rs'- WFRZ((G1Y ,Td:DqssQ&X]}&uȭ[z6dpޡҌM?\f0֟IIy9QAyߢZ?K2Tm*Uy{گ{J #U} JѮJ XvC 7x.j߯G䢽R kpG?c@KClu,ƴv"ӺSixҖǔd݇jL}NUvU}TrWEFS䆪E%^;y#sy`ש"`e7[~ 0k50M56ZV7Raa~B j:=@ͷGQ qZ- kJ^ssg7BiaR,@\d&PFd~]e<ٯX>O^N:Yd:(ngK?QF.(UޣW^ y#km['yH7 g 6,Y p3V"RT_0 ;ν!rkbe03+Ps-ⷷ6ǣ|i|gf!2J=7f?g\ o`ٛ`sO8߃^!rꓽ`8EyǒLr! d;&%*z&e 9XKz'M'l㿃@Lce $1 J?k.מԁR3{^7q_k&8<.&+vR v,JX_ήvyyLk a59Lgs29 FU!ѹnFmӮS-ʊ XN| :-e;9P*C5yhTi_nR̼#"ݙ5{JUfN§{kG`8;l~Zv7.Em7۬endstream endobj 92 0 obj << /Subtype /XML /Type /Metadata /Length 1388 >> stream GPL Ghostscript 10.04.0 2025-01-13T13:20:36+01:00 2025-01-13T13:20:36+01:00 LaTeX with hyperref endstream endobj 93 0 obj << /Type /ObjStm /Length 3197 /Filter /FlateDecode /N 89 /First 814 >> stream x[io_wx#p HXI%ɌBR>xYv\~^uUW4,f24 i!XhB`Nj&8{<B*H&q8^sÄWI*LjE LOΤh}J5PLIN4SÔQt`rԏpLLsAҢ ə6h^Hs%%3`bF:̨xpюXt8.$CDCVY%UN(ɬޕbZ44xA e#Pw#L ɡ3ə=CZ2яЊy).ځ<ޠg4 Q+HCZvӸ+$#Ql G" $tMv KPS[%M) wMR ĘԂ tu>^TC$a‘h>Kv8 qD|ΒJ#5rbZ90"XB4+Q!ʞl*e3ZUKoц]ctYAˬ>/m✞+M?5|D䉣:.gWpgI  QHajqbSk&*ZL/2"9}"?U\g9(6&» Q<3N%%p{YSHU(%r5(QG\߇rv'N)7)asY2#֡U^qZSՌƫeH =^^mӭDPDrW;Spw' bb淸*(s1bv}pڙĚn%~sPc/ٷ}wbՉVKÔBE|=x h{&Mgѥ)jG ~, =xmHL2krfyֽl2X+)VYIٲ-ʊh?ȊU{eJKفJ)5skb[iZik|o\w{DxZ)vPm(s2"<5gGk7` HۼJ(yEUIĖd9=?xǧ &}:..gk͏Y}!ǟ'KM>A1rv^,cm!^}Z JTmf>CD˻ԠiCEǙY RSۈ?gtqG)klxrK6S 'gS&4K,t?Xn&+ty&Ic~&SLR䏬+Ëf6=_C`O:[At vr 9ň%ջg?͝k.EOsU;֜uő gd՝ JYz^ҲZ(!m%慌Mu];)6]w-+lY^N)OpcLT,Jm%b+Of/%֣|> Վ *D$PBfjŘvQI`ȢPr"?&TWHdA'jgyfr+TP U8.XmSr\X;J+TH,}idDB_E`Nƅ21K%2II ĵB<4+:Jz7SQσ۰`Kkgb$~=u[;a`VɈp,3ӣO]900L9$@3*y}IDA K]$D{6v3A\*:Q`850.5%|# 5swHNsWn\\;Ra\wHUyD8dø%FN-IJ[Rm\1܈˂/ ;4& ?/9 Q]z~^߮d,.DbyN_&߯l2sm@xڍ.HԸQgHc: Jц,rDpؼ@T z,aFpP1@[Ғ~`g= m*v2M*N4+k ʖhqzPWcj5 !oOO=A"~,~!D_TPV)KE6)/q9ۥ,uU;y$LQhU9:]=Ύ~}|&v闱vTܡWV*cmrS,4m:~f::ڮmQQ}Myt5:Ԕ6*)%Ru;lI*)m_,R*Jlu7i)*)*S6_6e]+MÀlZL$TT7,(p=cĽyv9[TАPP,8Ȍ5*bo7-%c&.klXZzkCK2)5IۦU"*a2,yHJg&9Ae֏ۍ|p\Au+@nc4d{ev$26QcUKQ5 zͺo@;u!N_v RB D|. T2ѐ{G9=2(&Ǟ5-E푡_Pg'RQ2<d91ʴ㾭E"h!H4I9}v9=~~jqƧ=돶EE}"ͦ+pa@&~sJP/R`Zjocpw#8~֏kl-rf5R|KeBw2qFg on "M,1 [wTcY}w精5Op {F-1zf5|A?]]e|*]'s%0aendstream endobj 183 0 obj << /Filter /FlateDecode /Length 3577 >> stream x[oW{ W~3@_i6H/A;YR"?;e) X<.o_D,.N~9ػ7?|BZioN+`[o::7'?4^Dp.CP64W6!-bRBV恺ATsɆS[ ȁ_#5Zpji7):-R5˽@+n;FeH0yi/U>ڎDvhm ؉%.*5_uS6%CrN54_C@mTݜNv[)9@R4l7*oIa3yQ.`m΂m~}a]ft;EaRAʴ^&Z$ tZp7GHKR$z\OôT%q4P^D|io1w@b4ĵHU#Y`m9jf_kq:1׹v3sR$6.qA#,F=F*rz]v)(6#rrj*It>'~⪀,1.*e,QQ855uڷ]󓤬c*V(IEUǹ;GjFնU`#Vu(/p ,N.z.{}66$ޅ}5[۰m-eMҀܿe |?9N!wQ(0{S":Bo[a'%FTcJNM@%Y2 ZO!€:15Ejy\'DhC0A.8ɽ:PڻAC@PNϷD ww,A(+g!A|O =J~HAjYxΘ@`O:[9.yJvZ%W#(}|A!rJ s2h>Ke[ܳq?s] ZC+a*vHs09UَL 򪥤1R&-!V*Wi{yuU׎c: ؏) TBkJ$0@ь73+e\ wf$p fv.JJ.߮+GPSmPL5:Bg٢Ԡў iUZDi0bzț|tC`6Ķ)n0C|JԜ2r7WD>2pQ)rg=\jvi XnvÞ-p<*Ue 䡙q+rPX$FLs7±Jwr&e+msFpJ$7śG).J/GEiv".0jDLv ЎJƲerRo8eITT,\=9TAX)8 r}a1}!v&Dg D/댵B;W}+FI7,yWT*P/Hn~Al1!ʡQ)<2NuVB |# ޣ1< Ôϒ)Uk].dک|mTtM$&x-T5yFUuy⡁)>oBl`B/˜y089A[y-Ynt*8heճ^A^L, md ,4nj9 $q"'M|>!q8RAj2ǫoKyIucCb`b'k/ID||2aL΁@i(6pI\u(M{LY}"yw0EFW6&(/ E+*G2?R%t{25g/LUWgDkJю^h YTh}~%{j'A r'nnLf Ĕ?ON0ܩ<369ttK!*WhR+2Dy ZWM^A'e IZo1|Ik8K 鴡Ny6Q|rR&UːFybk9mmMutƲC礃Fk0P6?6hޗS8͈84;Jg>4r⩃礣Imzt[Mu!`h쵶6uDg.++| 0~aF@6$_t@֥FuhD7]PNc}^ʇJX৥|.9{Aq{M_$菇wDz3Mvk]"D[rD^b*-ڶ;zOA2fYAw<9xXEUd:ATTo`t9:4Z6ox_W.3t6]B--Zˋ*o #Jwf4//R8V]]:L۴rOBѝzC6]f#IgiZIXrsT҆yLDBrݺӼfO)?w%\k2%9Tq?ۇ&@3x M^UL:=K W~0ȹ-嫖R(᜞4ŀj?Y,:z^MTkͬ>cҋ$s4^I]hgY H^F'Ɍ2J3dFs&E|m|~KafumMV`~6^V !CFLU ୷2u^}s{ԁ2t\22e!,0(>[V)5Q~0PnrλRE$Fw7 F 7y;Tcbm֟]} ҙ[=J.ilLD&'}Tc9!rRwD7 dy! К3 PЇy"-kQvBw%!dFGG"/ |`@*3NKg?" c4u9鋉qO/.v'x-ƂND\9{OfsrV[#ſ@O x-B#AII2}tpٶ_Qb|_;[|vqG6B[£*~w?endstream endobj 184 0 obj << /Filter /FlateDecode /Length 4355 >> stream xَΣ?!d9~I(dÖ7OaPKa+e}{f{fIhv:)ӿǗ6=|v9h2SkLW ӣLz.ljcUÊՌR% ʫz6Wj9s6\V7 cҙ&~fN>s9s-fWfc|-·XKKv/ xu/n-kQU3\G>&v~<._WfsjymSONȞ) "iYo[@fѷ5ct;i= 'GK>=RcI%l^S.ʝKW9@xH\Jda(=pdWRqVGDNnJgvTZeO}8Yup,%]BilͲ!vKl`5zR_M~n Vz7_ / X6FhÖbBLZy \C".V.`&koπ[Ds\"h Dz`,0ٜ.*kbPP$h(mDŽ ̕?VVtL%`tK^*]N҈v:Sk'uOD|7Fv@ج{~O4Z`]m)8 ?ǟJ$~mk/1h8+Ò^u,:om={\ k}$3iY\ ^,pRƳ0۸OJ(i-u܌{@v1b Ԕ^c H1|XKq_}Jd""`(>otk1i)HZYCڗ-znF5 $Sw/ҁHolbml\qR qro>92)E>` >ӵNᢄ D,!}ڍ݌ "bgm2& yޒ5y>%d!25Pt]&y0*SE |i-V'\I؉Nю \2ڄ>jpж.k]*XZ1RTeH#WBv0}u9>je2c ҩhy&z7)Jv;l:'3O,sow'HAL%Ii]5Cr ()]؅+w '1 gM\$A3TEN| \ 8#%"LD|]YC-)Cp>8B1~!F!B MCmCL$tJU+C=~눍SV4 }]/wC㺛1 Xg<{aq:B{lې[o78qQJm`LKLsĿ:YD{z]qL-d'c6SnSeBbcPwm@!7ABPi/;ƳPŷSe kv]MNZ_ Œi쮐)j+j#/fs̫@Ab``=wZ_H ȓ/% y :`vFvA="X].fG6@1C!AjE OԟfbQ6C}h 5|}pWUՇenU]l64دP/ xϖՋ[bsn.'.ג^+`!XPx?UR>H8kCKB*jkTNUSn0p^is,˃96'&1SH'jq|v"', qCŒCҐAHd:i:O@zo|2P?9e0ӽ?b!?!xqX/,Ǵ?Vx)ø4qZ0wxqqr-&8q-hq=3G ܕ؎{v$@)9qh̅ymGVjX$r6V5cRPװG\ oۃia};&PW@oΜYN +jü {<㽾9cAI V.8):as(oW {m "?GHsGwsxHGUk?o&ͺl4{{OJ쵍f9}@](E8$D -As$6ci83dGajۃ6N &wesEu(HJ7QعKnG/>nHqCtZޜ|kkU(օ 7cCGkG.jɮI[F3f:"-$7l&c 2 XO-. hNs|ǝH5Өɐ 2Dycd/|M[_89yy| y<<e_1Z'0aϛi[c:j!˶XPG+퇃! 6ޖ{M&uu7ϓhKOϿҜߐ 9qXu]2t(7S O\"5e8 "Uܦ:+7= .zƔc4 2#u#'sc:WpJBoxJ9XvhL"k%'\wlN`ɢmβSYrXC%t i`):+A|1;+7icl_o*}+KTwl~-+L.n!&C7WeջWsnsb.Q5y"e:ov%29X|8ñoI)ܧ ƫKW~shn41S#B0|l@'8&zm ${m$1.ܪa{4fOvp'F"jLpWdٔX5I7lnG5#i.iރ| O,Ihcj #UhxjkkD>R.ʯ!tŧiP7ZMO'h'ljbÑBk~WߑTJ촵h(0/endstream endobj 185 0 obj << /Filter /FlateDecode /Length 3502 >> stream x[o_qh`tU?EH V?(IV::)i3$w ,ޒəߐs_Wz^o/5r ~Ppދau98 JGQ^{oxҟ_Qە'BҬ"/@@+^Y 2 WGѿ?:hRjT?3Q>N4i7x+ɽ@ZRV>h;UNE/Xʪ5,=*׶* tJTGnf^l,FBHZ 5/h5G,4Z *С Y져m7?kl,*Іͧ ?F!Ш&9%6 R 1NoNo/ IHɟo#esw{`?4Ѭ▊"p>K$RB3</j/w4H'b>IDW12DfFrX#rW²gO>'kl|rx/'wנD7KZJ1[.C6}6>i2 j7ojnx?f)o#;NEf8r|P@<_&_\,YdtdzJ!:Ed'/ M4$\#EÊe(yzZ6}Nr>uXki \DǪg,_ 4o1iIRM/C<ԥ؂ hٕ2׺2}mQn󟻏ݜ\/A.d^+JYR:]D$JJyӜ)jǐFQo i.#yaJJ0P o={M/9k[{o[-KJf *tTN nO؉bᠲi V Ǣ0Vqړb @.''TN0 B1 2LSXO%RJ;j :J)PV €zE8b4Uj ^yJQ!\qlB)kɢY6{4lg7n ʵh`bTc+) =H0;v-[D(q.U2DQřD tq8ǃG=gQ8pjƊmfarln0 VƜT{7/~RC]芥W)H+Whn}Ҳn%6\jEDοܾ,PJ Yix92(#`gE܃ 6הһQ ^KƓYUq°K~?/`_ٽzd8w ^Y*rïj.;{g'Ox. 6i&!zA+>a?SDtժNҹ05WJz{88~p]zt u\Ύyn΀EGyB;|B%Y3|8_L<ťKz܉X[%e+$+";UӃY?`õR!OJ?'WAytMaErczp20C "\.\%l87_Q {@F#d'cL^+>IȨ#f7*ykU><)@z5Q frQyevԻ tdU^vw=Z^ {x7-NQCb#Bi=owW0l~ݞvc/ٜex5 @Gp+qDF$"U LAvsrzQ WY!adWS͡KJe( t̅Olj Pm^^Cjzy|35T.lbLI $."TdseL8|g#^B-ߜ.޼PSJ>_"^~=_2Ș\hz[KRx:lOx[mZMwEX9Q,޶Υfr@`JRMlI@2)Mg)Og1Mgt](}%lͥAE%eXZtizqo»Ar@[YE%ui!@`[n9@֯ \h9I-<̄=^=v µNʘ6"ɊɢÁ^6b toݯx?޻NOuMv2+ރ싑KS3tΐCB܋'zRC{_EԺS\ !OCC~`OkF>P#ϘoNM /h.B.ҥMvymS=(ʅ'6RoUoWNܚnY{[ _ݲjo(ݥO%#9ܸtvr+{>w-R-"n^1a!(jl 0#6ۮ%B`X[ ,uqJؒubh9sW=!Au)PwL.vs8e(`웅B_ ֻߌ5bh3͇QtZz$,9ulf:t NwEK;B*GcXXBd衝ȓF5.[3pMز??endstream endobj 186 0 obj << /Filter /FlateDecode /Length 3923 >> stream xko'*-ߏݗERr.!$;jl#;ܕ,S93gF;aj>f/ߎX퍳zy'jkr)kJj[s9Jy aq3rWڎ= f|y5pX|Fuw^TkDu߿L\sF`^kAY16tBTO6Ig >i4ڑy w{d@i_.Oa5"R{2~7J3|Co|XX2ƽ-riBH]}NYQyemy-'Rrtя*IZްjOvK8UԢ7ɨoqU=0^ݤm}Nr\ 2-ضd?V++'`w} SՌ[x@둙8uLeP*p+S\0)WRG>&{Uu3d I~Stc!o@ Jsx; d -^ЙC|"{M_".nf\Ǿ ϙF);**Q&vP136T^yevIc<8/~5{<# FIȎdnr2iS2P*"dIP8$Eј;> ӒBH"NZH&"& ,!r4e=i!B YGx0t:S&'a9<Vt4z fջDhGC`TiWXzN޿-IakJvZ+]sNJd39dg -a)ρ]\J`&xr_g7E\Y:5swM/m \ TzϏFrJ =r,onϲ-?6Wͷe.]Iϲχj}}Dv7Yͦ/7(˳ИQ@Hbt,(̺~\4SՐŸHSdbj(&Q\o<ߩbjyW.t>i(E~*x 2Te9"1>B5(}sWq;[`۞V͍)亶,Y&WMZs#l ?ȉRO B^۱5V4^_IddIeAh]7 4&8 ,8c/PvC uGG)KX\^N~(P2H9$SAY0"ITkD]jT~(BŨPl :ū(Tr |Q\BߓՉk27SPvxiꩰy#zCbALJKVY`vܰDZpfx@}DZ6S|OcѣbױaO;:\(ב2ڵKpw{4Ak:) ֎C:S ϋћȸ陕왕CGO+L WS@]E4m 8g}hˌm˜;0 lBMCi2nn{{'4G;1m'wcɛ[jgg]10E [hS.{M,q閵}8__T60bHv4m&r5)ce< ]A&0å@{{ppje[NGb0me~̥=wEti^j}N*óM}[ E8}y?p(YyA}UFFS2hQ+TӠ|:T·&ZQB_ӣ }0 ,T背BSV p"> 79>МOm cL f|;Á<ɶ372^:JӀ]@$]T3ܥ,R!3ދ_) oM~(y @T CEB RndeSL{8G:qCo+`%AxeX=J\5oE_W)0+mXoɊCH :2?!G/)&WLEc <Ywxv)tW&UC,O|Cw> stream x\o# 7G(]|\AТ|Hks'ɹϗ嬴ƹ1$W!9!|Z/~o~˳o*\YVfV..Ϯ*}~еՏ;^j{.|gcƬ/5ZkqWmT6ЇG_ܒO{|8p_eYϘT5 dg3:t/1 k@S ?1.1\nʽ7< *:4~\aL:Sm@;W&MKqW#Vy_笖rRa'oԚ(59YǬ> baQu[uK=! 8⎴9xEqsԲS A98R8 n!H.k)8|J?m!勻h&Vf)gJv?+j^92$ENڛ^0Tg?z193 VL/QtZŶ9Z+A`RvZY$1<_x`$(,Z(QmݾY m4)ZjɄ>cA 4X7(C~98|x.gk*Ul8PNY>XFM/LJ._oɣ!dn,IZwQut8&:6Nj|N>s045H2#KȎX^Kks#;?UQ)2޼*ᓂڂ_'xO|R(N@)$8/9O @[N|j2EoOSvsENI!aG'X #vnKl bĴOgҀSN*3sHTo,r%F$?is vVJM`e+vn3v}vnw8P?/]Ne6wڴf_/RE@><,W7v_oo6~ AAe7WnJ>n5Es"yIs p^fyfSQ8O\p, 3ha8OY}l :CƍT@.^Ӕ8N#i~W6opVJ›sA.]mUerr,ܭ9⬟;O 3Ɂ tC'Sì>CV_k8PQKH"ևCTo*wAUc K !RYgKjW$F"QS5%HF–p*|c,tF>QT,-b iq=Z狛Dž EiHųۏjםrC #6ss(BGӃwBꘝ NPãt  G“^hFk뉣mN :Q"W6`!Sc XCICd*q҃{m&sLz:ⅅ+"­ЃgE[kα*˪#P/jfWc t$NNUx)uCNpF-ZbX?Yޱ8}D0hP<~&ΒDno~k/E8;#n' &xW5*zG4p'C-1ӷ1)/01#*p5zx-@bZT1 Ggg5h8(8蘾G'uhX3DNF|Mi:4o<$w0s8bLps8,o';;: 8\DϠI|)t#.&Q[hL^7E6H_P.nK{ 31Zb,(zLQ6 2?OyԥZP3%ZK6,#=M:Gq4FV$Eaaql +Aّ6Rv1D?vIڧ\36X|/=Npr̚PThJ1 WXpB;2Mv C]6YUO_asC-ؾF$UBQ n_`jlFFE'8 ?ԏ{wof ǟOI׀lzvmjDc[0Ia-^{>IHf2lݾ6RU3i^ib2-qKvywpL]IyDi4fY]٬țz،]ӬIJi ?t%M>l/U{_b*kGx6m%*<~MJ­2LgxQ$WF)B*0sD9]U2uY{6UBp/X!U+]Bh4Sܔ0BcB47[Ԍ!3N!xNJ9v74=ԋ0]7*9eKfHq>U0+p!vfܮslW^R."u5OY~ʩdkNc >ʩLkNjn F9ܚcqIie( 3]FX MK9Jה^QbWzʴ.[լuwahG+|hɶȒ.dG!]`7nY悁%eQׂH#8)%3E0T全k֓}-R7%(7J<>?r`y{n"w Uuv eTW$1S3]G<9Fԏ%f}NI%~ȅC5XUI6ږgЇ9!} 3͒iUu]I"s!\JlD$0szX/,7mb}0+G("x5> [I!J?UU˛?x;40K.dgUǒ ʨp邲*b7mj}q%T֓*qKr*qKy0ʮT-&(2)RNθpKiʸ!6r2nذ}FT > stream xZo_認8f+*Pg;{Hv orw{) X#.9h?o ;Sw p+muh@ov^i~U8^|Ƹ*FV뷋*'+g˕ Aְ\i)D#dq\1rƙ(\IHA_mR>驑F]@jz*H3+5bXJxs2l跐*UF 0sF[ê)kRsc<}g˜^10gO4 F)Nhy\GF<2ᑍ>e4qVO[/=fI,ŕj1렀.< 3(Ed:`Vco׋sk$]ecfI#p ]D0՛RFmG!xy\)|hᒾRq,zbSrrePDoH$,"'E $B@d C}q%@3xh]WyKzltK AYSpʊ>gI"*Ţ[\ücNK8L&O1Y|/C6h9LZ"Biq ."~Ta@mIxuM'<J]._]lp2]&^l:gA"(TU~V`4\'lI[ٗC>w$JۤTbdW)5%rqC?]Гrf_@7{$jDtmFt E}Z9|˒N厔4E:(40*r?u2\dAvOΓ$%]t[ahz?] K?~A‡2|cҢMo))*č^%T+@t eh#ϸVSG "Md=}7 zՅVuGV/6[糩t\,qQ։La _Y*͠x-w`+)P19n8v**둩dtMZ`R.8c JtMfrB;;F4NU ϧY2sq.ܝ3=E-o ٷ ٖ<ѥ>|y\03g[)]NXv2ifQeg@qTϔ<]9}{H!,йQ?~u0%?Ti\M ps  Ā،ևPEa08H P@<6 MdMNGPon޴>m2(l˴>XN5zZ.E2 ,Ƨ_jpZ5(sPXGD|N_֓AǞj`ڏYA;5]FRWs`Ä 373ro{ZǕt^ؕ2XKAbAsTVt vi KqG {FY‡fh]1`#ן4mjG's`h6 3eBod"cYiyҩ L:WlkF26aŜ%L ǜr<~ ??toeU<~o4S?$WA'l =Cr_"g0(G /lz~nsFog3f@_@6tF[!8y¨yҳ9k[X:[`G\qH.yPnR±gB/S]`]H9^spez;ySL:M m>.WlW+YR|Fr|r {th ZX?f;\?^W{6軒ێ\L{Gΰl|Ӓ;u ?ٌ.B*ӍR[+/Yu%J5R˛p~[{?`O? gB~{ae24FڤqtrcsLWAWLCۏ`36~M;F2}+mD,'_Kj\2_Q/~/endstream endobj 189 0 obj << /Filter /FlateDecode /Length 2720 >> stream xXo7G} U@~8`-1(}gKr#si\ofyD|:"@r':G\r+9~F<I|:~X'7rt.ÑX0ZM\NoPzC~&NZӇ4:7?|*e.75 sJ}|2U[Gjmtˎۇ8oqQ<ǥ8!j4\_X`c]>[j٤5x6JU$Y2=4FS,*I-эhed22dH "ߟ/~qٿp5_>ODȪUM,nt1se׳t[uvnvfz3 7.2Gݾww՘]n~>{5~Ϯf׋2ԩy{/~8.._{ы"ZnU{q=:>y}Gi&ٴETlܙ`y$CC2>4z,YES3۸Vۓ:bSg={UlF=!iV;-T~P9H{y.v`*P 񍳑eC^L ݻP1S_%EVqz==Mur(@$'PUiz95EycMLe:k܍{LrKMjMG \msڤmt8yQl" ?CND$L<˯KK0/дbi}ͤ[+E+O* Jh9`lU%nz3*%NJVN{Vh}]R׫ICjC_Ùr"\vxeWaGw!by4.rFՀl7O4 3Oq/ |B `>jt ea1.ˣ1`M ޡu8@em[(>8>_[AKqd h|'( P!2*0) J@/K妌aL?+fe}r;jMf(ɲ|*c+w9ޥ0k$UsHA"(UULGh7EpYg  )\ηqɇ )\/\*\j`?w* }"wV}_(0CۤL84Xϻ2g:Ehp7,ŗXڢjg*keР.&6AZC FZYE 94L7)"J`0> stream x]1n@D{7ݝ/6v"A4T"h33SFğ7j\nv[7_ik׏mlݹ^M|oú?}qw}xkc:u6,mq-y=> stream xX XS׶>1prp@EOEw*Y:S"jEAQ@0CaQDPĢb9_Yk_k! LF@d:gWkm?Hk7UB0$)3,aX7 1I9vPj}9Vs/^ٳ[ xX9zI|!E+O* ]2kVTTWpXwʇVQR?+gp_InF`_+em ىC#+Gn_IAKVBHI#GFyɼ>ѻ7ٴwK@`PG6fϙ;ol]egĻJl&i J [mgjb;aG|D6b 1XK#s<žOl Gb#10'1H[$’L)I0E&D$`h鈰O;&^&'M5kzFyNSTś国6樹Gct1_wq;U_2^5}e,M'h&&'F5ԷNM:x KkȜ/41A)_i( A !-%g !! ,m &p!YLL(.Ĝ㔊TKK7̓Hnix:!v?w-^WʹRf=-cAczY_i8A1ޢ7"?%qNNDA"f@3_'hȖs7/]fLǕv,6!]T婉)iLB!OO>^(D[!C6fwd4rz#h24G]4+T k Y½:(é η^Uչ-YGT QYŪP j"9(b)csc {:ꓯ4s FНZaQYqyHo_NMg_cXcͷB Epi 7ӚDVB3q57B GHA(Z:!f*M})RW7e8]~rvR6#PeTwSf..N Wd?G)Cª^!gx{>,NUo}]lB& !N c((7HX2 R1++sʾ-x QBJE5D:؁+DžE٠j2=$G4k`]FҀ|r4 :p: 舖16of" 4oCU?gv<ئmFb~ C1NU:@=+Sݯ0xeFD2<1&U~m-qbu;TK@,;ȱ~Y&^\/?9@V=m,A}꼐]imhfLLG5 cfTɔ)JV>UloP䁮k+.>8לm!6fX͍&o^LF}gV{e0v_Q߇E M:s F8R~wdQп(#$0yIԹfǧG^޽>t:#_ t:;;ĵUV֔zGywYŬ$s V`NT4% .v{G#z>2uP~RIaLE*W~ E"BR89թlҜd*#33!?]I'up  Oq?@+-\VGKc͠uB's砘!Cܠ;4!>gih$a䄓d5z5 ʿ<>5jKƒ3“S}>TNo $PjWÐ+Gv9?$.J#DEI$:A+;PP"q @rh?;Jȯ"3^ [s+mjݝdxUCr-"N\Q(bjvmR̲̊16+H1y1গl֧C=V@] >NuxF-ˊ7lqa}nå87F:ZPB$=~bpUt6U v˥!v?;n ܠp4ʜ v4#$ZlL7?_1 iͫx4kz&F > d>a3'LpRB~;쥫4'QE (D 6M,F z^ bSGdB:+#r?xi= nʞ=ZuGNNKLA.BS2sBtތdE*skr.[#NxI3r=] [P\ A`@h*T/-!I`p}چFf^gB~iu:QHEtR*CzJz HYP//1Paڍ#g@Kmo AR8ZC닿^rq:F3^`x3- ԇD+㕌3`ִ FE&TvR3 SHy\}Va{a(]Z!B ַyVV6Tf3Z9@=6&-;Y\Ad sRFip) R xxkM8]R(1UQAn'ŗ3_7_PV0e;V%W(*fŬ @u"}7ݓ2RAśFpX ^a;L -JF8 OG\3>I3R2f&(, Co7!C\  %#XB*A,҈dyvNa>hyOqXmk19'iitr`E8_K)bǮc\Wa5x|HBo&,&-5-RSIy %y<Лg2wOiJjŒNswo|tϵ|'ԝaam(()=[TM~vnbQ!K(= EҁazeAoNpFkp,=v@/u&2Cfo>톔P?d1/={.̬l8qmΛ,p g9}~"}i[ZHNPO? ^GjR쓩v+B>DUXT :@OZ}O~@'+[Ta@De㳰 D ym$4y錏p98mXXLp244 X+h=̠Jz[.roV2*e (.KA/#l,qgzB _vQV6>i,)AUekL9Y^[YWY[pnn{6\ٶĉ6`(k8v*tNYh"6wgvl߻iv^5lm}B~-|AZ M/ЇtY hi ,zEcoހN 7):^C\m> JHIIÅSW}qk FWF6`a6v|3 JR p*zVZ'wajydf{we|͑M]lLmNEkΌ4G{Oendstream endobj 192 0 obj << /Filter /FlateDecode /Subtype /Type1C /Length 4468 >> stream xXiXS׺1֙t_Qz8:աU: s!a 2D$d0ʔ@EJic[-=tm8Ϲ+A}ss.?d2n+6lU~߅Mg3\aFrGN@q(x<%M+bb{#~s !/^^y7w"e2|OHP ED o>1!{$$璷#7_{-!!aN,~N<.=("ਲ&&6a2@rckC-$>pM=DKl/9¹xzÆnc BM9۾"ѬGE,z bQ<]YK+{Y=VjTD4˚P~_b^IK溊֥_~k f^ Ox*bHW1!)OKfƻF}~˾$~2(B߲hŎ8( :8pDg/LW"FsG_Aotcb)rkqc>XkRɥ9½*?EwQ(e-o f{Db&r}~` 3I=d,1**8ǂħ>-kH/G6)ZSrarsM{?%5r0S92Se =jgh`H(UEwNeZJ$=_&h,z1E9Q98VWUf~MP -*;k`lBW@<6&[E H Fz{`Ԩ.!'մA9ILS@,dFː.iyJ ~aǖ.GwSfDZJrߘ8|lޒDޫA6x@br&նA%95Z]2~zj+ rEą8c6*&qf?]1| +NA8 _=rRMqssZ<[U&;aEK EYjy?aquy_n(F_!A/Z|NCoU9m᭡m;Jwh{䚺ʚ⬦qmsgQ#0Ch۵+eK?͇9N`2tz=;OE%Cٟi 1,Pe2J k;Q}SG؍bۗDXE4Tj8-qՒ`0ݦhRI6VB&(wi6$u['0\2³J?rf4~.+UBd j5PAPӿK~NFYCHurk̪uLm(ejK 4=I'txb,~> ֪eĕ'T5|<5LA%qs֦#}E O[w'*?˾G7o'`)X /,09㇭ 2 $NJW:r^a\ ).i?nf7uPLXnDvi4)1YH3|d4C=뇟 %HNcZ7fMʾv0rhv8hmĐad1u**Hx*+PrꅽBrp-=mPfs@XiJpppPlT#e1n8n9hZz`ި>tZJ5\ R >JPoy9(cNABqtP;ˊ _?F; dͧR]";2pSlEиDѨH0<XxI~iD:[[x3$B#n3On(8F.ZғP'҂Ÿ i잱OO`OAru{㆖J.X}LdMZEUUiG[Q۱nP|A {QT{?83ķD$ŕŶY`Ч'VIT&3GSW&$EDy g-Ccsl}|utt||ttu|}}uuc2a>!p+V绿_T`y\UY-Xվ: |i>]㕜u6޷&}#3hV2z*fXKaj]-2oZP>s!|۰n>D#+V5!^\ԭthN򎢽EbVh"{u {E/JM݄pڟ;?ڦ)2_CJYm,ֺ!-?Ձ̧T~=?JOI)fC<"kjjj˛8w_ D@_4Q-]v KiCAŀoĢp%b}7Y'=r=aF/ >SƕvV`Grl,3J3 9w|,?8)35nx,J4-ZLKro A!n}oc2 Obx'NBgyH<*#w>8j)N9~w4Vl ݵ)n^slNʭDo^3cԿ~k%;LbѸu28i-\q߅ƞo;y8%aL.{ ?--GЄGLLƤXHmqs^m^p= ynendstream endobj 193 0 obj << /Filter /FlateDecode /Subtype /Type1C /Length 7115 >> stream xY xSն>!4L [ē2,2h)cc;="cI+|Cm}/ D:{DJ(Z6_-lA{EQoF/Y}yUA;<\+d]Qe.&N<啩ӦϘ;sksFn~9zZI͠^VQék'5ZCRS^(j5ZO6PnFj5ܩBʅZD-&RoRKIRj2B-8,I*(|}!DT4h5 zTn8j[(6H^2⮣:}%S(U\#B1~P9ܝ|E轺6 qגӱP:Qg# ZI>mG2E9j]Guׅ"v}/AߎA"[@Z"9~Pfx`큶kr9lӖ tXjas tZyzjqp˰+l~h2ЫG> 2M'afh`5Aj㗮xIKU0Ei"ubǕ ~8J `r"[' &m^_؊ˆ4qο@<>#hK56s '~n!BcXZhZuZKBCh䁼]L1+RQtk]rFьwi$|o6\G% l(p||y$=^F!AG[U{{\Y/JT[ Q˟\zqg-^BP.-`PmCQIhܰ V2C_ a|ӦfC&0/#Iɍ$ gv7/KPez. 6?L/1p n| - ahyH3.h)ٽgpoGH|ÃR>?[l>~K?`2p&(J=̀P=+qߜ LndK/P5Q:a2ZW6>CbzV7f('4fuPh< ngeDk1R=RQ L8B/N>*F<P{qЦBIe~ .]rv:~t̿dR!?}斗8e,D-DbPCZL~ ;➨AY2,RLD*~ꨗ ]v)A1=l)?YazN;ϭ0]7Ys:S֕A#S*X! t?q*tߑ϶JEI@❗ݻp e(\pHH\ASm'a.bn $}zةX8U) ES>0|H2F,- B/ d3!oQ㷸Y[|g݄U5 >@_'6X->vŽ^K%FYEeiiek$+)k;~4ޢ7 2|-G\O},>JbnQvm G4Q@eiBYK=X5!Da$"OO]6q\vΞ c`TQ/ZY :'Թy2&P.KcK?cAG;:fk m/y- g@Ȗ!ED>wQf2+=L 8bڛR_`|GԢb#59hge0m0Q2Up>юoXӨCZs\&2$dwEeIIeSe^q |oU$zªB$Ad#B~Kbů8[}FFUC a/Q5ͺa_3:y׌tf{ЂKܠ% ?Im޵G0 jERrF\S//X+QƅV($k7lK''K#nQnIE=Q7'UP Hx"/f(51(I ZKkFK]`4'$`j0bǸ};GZ\^ `y]-WW So,ѠD'^݂T WvB~&z~SYy>'wP.b}$ {+E0"ڶ'F=r HWhʕ]SOh3#j3 X"EP\m3y2M~ROʊK` :[=xsg\XYvնl.+ܔSw6.=o=B=ޓ +<5?dҕ|lys$‡$.Q]]gH["Z4^+ZERZc$c",*2!>jSG+rC¢[>ٍudqC6"W2k!GmRiAtY`$=a{lGA[bBC.$+Gg#aWhR=(ʪMqŦN] G/UtNvͳ-~vwJ%uu""2Ι+w>*_z)>>}͖ڵڇ`Y Ӕ"iSRixlOA!kJ m>I|>E j#+IM1&6Rt _Yc]TL*'U9V/&w2?]K*!D Hu1Pd(*P&q xfpoEI׫ o_(w Dc#lz$@LeLiSMx/}jA?i/El:(4jdDB5wD\BʤҲʂcΝ{oĔP pt MqͿ?iZ⻗A{IggbK#uz%@\g8CE`z>UDs1ZPniw2ҕi%Qfn`^u^;Fߝ5-6 fh4Ty[jfBi ߠWޤB.),сhVwQUl."e\xkZ l)FVUԔk9p4?]ct yέ޺iB!5I)9qfgY cLCL(纐_>dVw{jpN[LlF}.E{~rk8m* !]N$mZNc8Гh&̓=^HZ,m1. Qk'V6 8ȞZA~*TɓF{+#*hJG=}HC_fURV;-œ {j*;W]I6(SN) \]63^8tOL﹌f 5kZ%{B}( 0 =qD6/_\{ӌ%=J&c ~f+cJ{U>Gͯ>\Y\'(:QŇD2{'SlFo$,IN1k,Uɩ r tZfp&wev(4!|I }K ʝ,>0qFxM&U,ͦL20[ۉI/ky\mv坓P&uxm!KW4$ҚjJ"#R^Ic&J~,ŸȾp TX:ml̓a*$2L"Nl'btt籠Q6GDGGTWsU6SCF<@.ҸPX,n]ɩ9 Qɡ*:.c4gg,)|nӕJ^g#f_X&tbax#ݺemξ|jR~lc!KƄe>7Yh[ةC>̺rHx +sFnd67Yge6*"[6ejjđwst^0xE.>pJi!^Ʊ)j(乩yl=Wŏ#rMhTss-*2c$dW b.و_H^q"B()X9ic4tRVMtKrs!OP̞E~-cyuM\yhxRI$DaDg.t5hmFzeP2f,m!u\sǝ}n/%Ğ^+_KL" fP/ݶ"ku"5p`^LjOn LDHȕg$bɉq{N3EQ Uendstream endobj 194 0 obj << /Filter /FlateDecode /Subtype /Type1C /Length 4420 >> stream xWTTgӾ+*(IĂ5Ơ(*bY4RH,&$jb&K&+.Fs|{`Yv;3<1H$GwuNߔo HluB! `̷#pDwxgVżyL57g=991wregpffaaa3}fz{40`OAn~k}Mo:wwH{{03v/  ^bmN;^}|0xws,Λ?a2vxf3Y3;&f 820+f)1caf2+Ylf5cˬapf3yיQhƄyySbĘ2?sVb#95`WVs$ ^=`װMx}ā oU1yl!17eC[3tp #Gn$S{ A+i$(E nGT<].+[E l&ek[ *F/G˃ LXc+A|C#b bA,g#QZ}#&x:[maix>eN\ -:rtJX1E;}++ '3;T5hFʼnQqq#`+af9FӀXeۙן朄C}ِ\%4d'hQ 44:u{x)oI{?=}*y,n8wa(Ck/>)]uԡ֥t (ϭVX|*FZ MN٦[mhqd98=TlzJy].V7qWm]7&7wgI CxM&ā؄\4՚vN RJedaxzP9blmF4=$iq1_ QJ%$&$ΗL8,.I^IL}M̽g eB!%XNXv$fL8Pz#(8<MNhJ.26] 'C9J3,T!nC2)CZ!NqG{gj)ܮ{+3ׄNgg-\kD_؍AOi DCJH7h?} [j)DZOX s\W`"k\}ZG,~U8v_M:_K2 mds^ y8@+PNgz&h(Sh)4EtOxT.Z/YӲJ%nPzؚd !<6<+Q#?Xzs,.GT &E.({ui<)iB] SM=$&FÏ uHYoŦNߗILIP,&FKu=NP&IYe=X2eޚT@ZFf ^m]jpFrE׼ ~p'FI5jKJ"rMvw}ܕ%;K_ji-OB< *^T(JH-ýhN|Lݏ)$ ^h%qnκ&Zv--vE؟ RTlj>^&[ DP?^nv#$+A7S\3O?ťťEeN6Qzn3cu|dGI l6a}N8oX;OO8Tā|^,ET1IBž+̀#cJQ8*KR*yJJtr·#n6c#^FK 'Er_v6tG5ęĔ{2MNjX~x"K$}x5є 0_9[(;ӕQ ɦmbbs\UjR-T0U8p Y~|UPV>7&U&UQ)oPz D]ZB ?= !XJPRožR >p}[ܹ> d1}fSq(HTD'Vڄ:Q *|@ݜzH". N#Jj1$uW2-L"MZE@(Z~٣e9NDq7>;G9X)'<'y̴;doZGG3,ȱSFV>^ч%u0'_Ǖɀ3P/5kҋ)I)ͅ9p*Y*Te;< [%IUQ<2(6sv8Gﭫ VLȌi\Z ~\:q.ݽ ۶,y9s:=4?!Q'nY?~ϟ%jow&TG5ؠG#52J×y/?_RQv)$kC^+qi𼥪i{T *yfPX r#;FC'>-mP" 9ŋסQARyW{W1M]@J ]SJzlũqe$g)'W\]ݭdT XJHQ[7BDak^yOCѵ)BD~' H MP !77;muR> j J {9nM8Y6Jxn#Sn38 Wݯ YjeMp-)nHjxs^Nn%ݜ PNU''&+rL4ql \z7آBGgSSqqaN*Դ EoFBĦMKNJW[q{a}0]; .G{6Cb0qrH!U0lCInv;Zt0$=>a?ǥCi㥸n|\\4>h$?L$$H”@b0cS1@{CP~Xy`ud` \YOfV@=*5;.Htԥi[$ԋ|%6-GQ*;M\_+?#ct[-Gv &;RBA%O3Ycw_ۋC#+ IUYP~ŧ']h:љ^mrQȉ/ Gn!չi:8r_kihxg&&\]XfZg??gb8 j>; p׭91g7GJOTޚ)4_ o6*U*Y Ed@O'Jϯu, -)Y*=0\ Q1yܷPGHt %jBEϓ+q'4̣eNnE!kBs]iqKC^J Iq¶-r'S)@\RƉ-Qd y whaB+G\]𱗼3!f𭳾A8}s1N bH4I#t5T'GnΣ&S&ɢE`B?@S4r [s\OUQr^ tC6C;;Ia8}wӎ0ҥ^35#>;#:4Ҧ*4:==u5 ˀ&>YEBmFy9s +vEXv5-^'DxgC-^l?n|tyvq&[ /9JLR;T^,S,lH"^-D2H>t?D%L]>r矅GSؤgDƂe _endstream endobj 195 0 obj << /Filter /FlateDecode /Subtype /Type1C /Length 5158 >> stream xY tSu!4\i @AAqAe(e/;;]ҽi}nfm-mJKAW(ZDvQp|Q<=μi cwN-%|o>x`ڥ3g0_!1- aHۄ q7!(Aɔ ĒijrJv䖨Q3}YQQs#HeFNÏI\#OM.:jJv3edMK\d^jvJĬDinbB[踌ГN}[ ΐd'J#WA|FE(xlc ]Cq>-G#ku  &M{|L;Y2Q!F"LFQ i Mm mLK ](٤50i5AT FNrh5n ?v^h{=5`cMJWA.9v jq@/7*U2s Ejl ݆#VlI޶2 ٰ[#?d%0qXk*N՜j2}1="]^uVWYB\=_GeׅA͞ ӂU)Zu44@@Wc% %YdqtpDhZUL N4auYdq0j݊'gO7FGe~-j~\877#[wh"Lq R_ZV EK+*17{tWJ>%"nlbpf^`&*Z`t*3@1:OX=s57H.\GaeQCM6ERZԻHZX+XD b ,bll C4ֵ3Zy4tte`6ShYRC!5AP8A4=>'< 2E|p N=9^+.̭TWT7_loMِDN 10(js9gX%2f$RݩC&} WNF RXj(0dL10ToG"]LW ,N5h !><\XEIv%.^p >TO ˧(FX>ˮ14HavA4%_CFMyEZyI" O4|O~&r\! dMcRUvnfP.Xu@1)AS'L}XT^ +5|?ԶrGX-L\`*(/.)e+T}=O۽!:6ErOkUAf75|u2.KܝNU SYGzyZ(WBs>WɐTK)b st5wtlH_i; dcP z: $HSRj(lPu 4 zYdf1N a@NS;t@iHd'b|1hPa5YUcmN8^f*@296T3F:k\݃^"rץm2{mCϺuJCH(|?^hvs٥./H$e4I09x(lg-A{ǚjv9\\-;ڂTv g{y6Kc4(&A_-D(K6Ydg,TVEKn&2 ͔9|hˍN@\1 F^芣j? * tt $1ɀhJs{tݴ0&}?Q Ekۛq:JX5dFQ h3%p3桠VN%vy y(Hr M6֌ۂ?*3>(6P@b5qRh:jTU2kϢ( EqgLF1n+~U49Ng+e0w0H[HԠ!rC>SP ,X>A8(Y1`lZFa4JUTo@yK<;N_DV4UVRؔشmɹ) q \gVv_z> ī||hԘYNp[ڣdIgV~ۦk1DUjj7AJzO GG{31) C@&n  q?X?,q$G=K63Hq9Ĝl<}؆çE;"XL&CpL)GF_sOK6<TiQsi( 4GSo{w lo~rK ;a]kR+Z:O PdUʱ@POooW_D$Tv55h(ɖ<,J[ؓ䂝18,.ЮxJvaņEEJ%yG-̃d7F=é[FEK5Bd컪tm(󇰒TXyn"Uab`2xSOn(U8jk# #Z# һP>% ~oAaa-U!CRz SHp @{:b&r$n_70ouvV =gg5]^qj$2a[QzۿZ&Aams*N=u( š"$Zu񢗮Q{s..~mh[JcX?+}Ku |hxhqSAv=PJ@A滕U^oukgwRT –S|JF%)[ARfi;YS,.ؕО}O4S.` >'?}=.,6R㎻6"CZ$:U#5bU䑯i`| vZq. &1K3 vpUs˄GUl&wl*<=t.šbWan8[>hspS:s[=s5 yi}{؃jQƄ)}Kmuح-3yhYw  ١q;b CԶIބ,csL;'еպr;S\Ȩמ;ZO0|1.h`-x 5YXInٛv DhNjGnզE47|L޼vs4%PϤ-ΓF7 1X6g^k)LX־5Ȃ[ Mp+11W&ԄXQPA# ddY:գ @ ǩ>8u9 ;}0erO.(+ÙAB%9,W>*+(pEKD nbr*]<ūwY w5ׁɱ.s >Sx9ߡ`TZ ECk%!i{[5Kj.Fc>rrs=֟;1Ād2rF;!"@uki/.S)uCs}0F`‘Y D4n fYFG"Cm^7U꾀UD׀˷A_dPl*\MAPhn?$k)-Ͱ 6'o5{JF:kmi8};<|^UJ)%wk7퇞i(|uP@ SJ|ՖjK} ]shAFqru9z=ߔCNÞtR%dCŰl ,PS"//٤'Dc R~"'ki q~y^ckcs#"wKؔ(Ks* [Ԣ7I|aih +ՕQ81.۞&q ƌ|3v4A/endstream endobj 196 0 obj << /Filter /FlateDecode /Length 202 >> stream x]=0 F"7?퀼BAF mؒ?gkg|w{|RΫI*;M||"jj@ŌʺbӀh,&y{@aewkgUTIKHK Ҋfm@ mXw X[H[NV9NwJf9D>jq< hendstream endobj 197 0 obj << /Filter /FlateDecode /Subtype /Type1C /Length 1189 >> stream x{LSwʘ U:5[ CpOT|Rh,WJE,Ӷ*țNA`EΨɘK~\ &'q~?$E$)[>,?2! 2H\Pc65jK0B"Ot*FRyJʋ[kPjTyj%RK/6L@1#j3ix5F2Ҹ=ϭLթI%Ơʫ\!MeA|q蒰p'"&b1CČ7 cIWuj uJNZ#txIdG+~L$ݬLQ)k8LAn(Xuz=~LaOzCy':61.gU{qp,E7޻odPHґ]ڝ]¶޼xG'",>M9dz,`c $ZTh8+ܶB]/7^Phz}nZ~7de)/<{>E{-CW>}?xp4>:1]ڙU={DZ crܓg/)hGH vb.ㆊv{AoVJ2ѡp^agJG0(F(kU|d3_L0%\631n_Q=E@#:v9vl{5*G!己&STB,/n8fmPh5l;뗔X9X"ϟ8]s}^xHa*fSr"3 ٹ].;m[?!є%YmL aQӸ\jm2{Ek!i:VTU-]Y,ŊE|8U'dD>'/Oh#E1皯]C^iԳueix尖^hƳK:v^:@ŏ}]N+/]~ׄz6:)!il]k,XEG-."$ƒsC:鬏wwAD94endstream endobj 198 0 obj << /Filter /FlateDecode /Length 203 >> stream x]10 E"7hy242p{lؒ}cXtqΓ}CyzfUe1nIMWBM W?1> stream x{LSw!2N:75AQh0lQ1X(>A:@ 1C4D6ŤS2㶄!d-9~I"@F$o>>!2i!wqDAPcAPTAC$mȨh^U*WJ*ۨ獺,u>6kyB7ͬ՚͆H֛"f ΢3kT l.I7szcFK7y#,d| 訕cVD(F 11G'f4D !.ٗGT5$YQpK $r+'Ԃ7TE#8 JOCq9} P:(M` ~/zDEv)'%{W(%BaIUMQ(Rdn%<u5'ΜYO+M-Ats"T]e_+MQ(tffVu9W9C a>Ө9`ʀ8kZCU%HBA(LPd,3>q=n#BZ0050sZE(|X_lh y8עuHN(lɟx֚,Z0! w?P3{}੠6dG24Jj}!dMQR7S] vhq< l3;ϊk=kqN`^W=zSy/UJ.\t 7.ү70;ƾB1^O?Kn/Dz{tN ,t0-KOiL!E2O1=ŖTih;Zw,cEQ=&&1hږc_B7퍉\;<1>ël0A@lC,r=endstream endobj 200 0 obj << /Filter /FlateDecode /Length 644 >> stream x]nP=O7%tE sX!}gE`,c6_~?Οץv<6Ӱ?.I}/_˯ߗ>p1yWqlái߭g45 1z :c4 b0f &cgƧ'Aݹig] A\ww3A쌇A+gqE4 V5XM^`5y^5z V5X9|k&kFjk&"8 ."8 ."8 ."8 ."8 ΎΎή؏kG9ڑsGvQ\p!`A`\p!`ЄDAQ@E!B(*,(*,(*,(*,(*,(*,(*,@j -$:';'>ԇS$!RKIhR(RKIR$RK)XKޢ`-yނ-z ֒-XKޢ`-yނ-z ֒-XK^\y=.n<&|^tYGO Sendstream endobj 201 0 obj << /Filter /FlateDecode /Subtype /Type1C /Length 6987 >> stream xY\S1J֭UV\-(ցA;$@䆍lHK)֭8Q ZkWVڞK {={{nJ ;̜a5 ӱp๓z !Vc'ܱ1l 'J($f/ K||#w̘;c7g ޜj?kƌA^^Adh1dW#|#"BN.JyO ,12^k+K~UHpZ /{L- ;S!xY+VJVnccs|^yYoKAMԝoLs>cs{sNxkICO~QQuBjHm&Qɔ *zLM\-2j*ZNAmVPӨtj5zIKٔ͢5ZMͥP(gMj-5D QCj5ZJFR6zbQhʖʊzR4/JDRP$䕜M,88`倿޲iLϠ3DbQ30pO68?CC١iClXp_H#+Fb:8R_RKG^-m`mndn]c .F/4@ qO [M{`aIV=&5#1 +~qoD{`{g݊H4a.̙ SE'YXc;p,8U8 T狢 gVb1pDk,9Um 88 gw/qL8ԁ{FwX-7fKd Jʭ"aŴ@ܣ1 LPcp[}~\@:ֈ~㪍 oaՃ w?PN^&ALNd5.3΅%ba.?0QoC= c>!4{d"̎vRup؈,i$;8BSAa::#z1;)J]d$oE,,?jK,noo;6ܕpia1F"ARuk!sNYO=S)ڀl'aPK` *=lv:[m۱1  &rJ^9*-/m]Al2_ xJG"&\>CbYo!Gڂ9\C5K~{Gʑ_ͩ7$F f :[[e=:Wck ݬ[!1^ڢGI>\c&̪1Қ+x7O`otGHiEh3Uȧ_ׇIJzo%NYȁ[)AypG'6 H\-3|z ,;FDQi]BvkABԐ 44%GrRiy8Y UmV|ZEƝ?,%|HܛsRFE6@kQ#weEdBvPf~kuqf%][rTZQfiV%': ]]LBeVRDL}/y$fg&(xx®hY6>U͡n(1Ξ>h&f鹫]  S}B dfcg(WXCE&k=?zBω; {iݯDY9 6A'C a!.B0ֵ}*4yAU<ϵm~ MBW0 ˴=v@K- NLZHp6Ä_`x^H=\ZEroVxG!}IO*4pdH2NpP˱_w<%NEU$iS*Am͋qyV"R"RJI]|*CEL\-8q--=w `QjLR¥v8 fߞ%j E5be #7=qACe56传Ϣm ^Ƃ{7S~#8jGGZ!6Ɋ>Ƈ7mt1Q;Dl0df" 12L7 1iXӽȷTU 3QW,y*D+:^t z&ķ6 (r˦-=pE?z#%Dcg#Pp5upÞ8$gtu': ;%ĥpi-rF+6%l K $yFr`􇝤~=-omlOq$ j6Sۤ^F{uRE$حsG"GXnּXmPXFptͻVNbarepx2lpIf.9Z{(`之ǎ=d L_6Sy0 1ojLj絅YEЪ})*l5mJئVD+U_"ŘJI |%n}^u~*?t2C('4(eegc`0N^zXy~s)Gh]{^%<[R*r'TE)E[G?RԌ\Nܹyė`=@DIgutO*PyP}tSrM{I&R$ 9f-m8w:k_Nq#`jm6Dk:=RZb`ጛcos[?_NK{B3_}ŧ)܈C*P$@N(",>bT_;LZd"T|;:8w%Uq ZEAn{b^OVAa kjYd5C- qAiUYRY__YYB^o:>s)øZ(d[VnΣ.\U*VmORSܜ \% s뮫>GxCӅ^-ݙSà7sȂTM_akjҢ9ϖMnX¥{p2eGey8Tq3U+feFuAhHtZ0ψPge#ʢ""ÐzzӃm2}Ak2ퟜ/9v Wf9FpYXibTkxfL;m݊mE+mR.z\3ȼ|`)w`l&ZTSm|hԬ/?Ƀ0mGUO" k1oA&kkGHhVZ%#n]FՒ^:`gu*E ȮUsٜk?K'a"l"9bǝ"rɓQȇwDⶮ'WG;qX!՜;NI#YAq{B! @AH@:AA%4w)Ev_%MqU^T9IU(`RHZxG3w([Ur`oSt~=nMo5id枇Cr;je(hp',;=pHRɱ){y%x.1_biM[7ma9~t_Xcs`F!yknxd 7Br F m#pm z75ij20o#4KBln<> Sƨ(T9x^8CdYf &5h ?&%!c9xhqm,ItǙ=tUC&E y'i ӊr'. ;gܶх7]f\6~q.&, lyY}Bc\8uLc>nA|@DܐP0#ňәwׯݸf%'aeIDdl ō#N}ñR:@Hڅa/Gb)㟟A6-ֻf9u:=mR2GaRe!G\e@Orܻs7,1Kkh /JH;5~84p o2]<#3FtSK 0?)=zOE $Xӻ1ޣ>f9o?~w/nw쾌w Y6t?.h[ƉYz1S͆`w:"wl.]5kW9nSI_Te E' 'daw[ulfT=A2cw3LY $rz.kL P&_}0:4+;;1rMDPr#a b~*NObI= HEgˎf,vn?i)__t{Jĸl0GTia ti-mBxLt9L>e~E(5|_, DXKWWw}鶑 h`uC[X[ge왜}y/&i 0 wijD5A 8B~ӊ-Dģ84 uO]nA֞%Ib8/ms0Vm~ANǘIٍޝdv(./a_ |l2U\J+ *\GkS(^E;ؒ#i&s՟'HsC7i=H?}:HuQغm܍5\rJ,<d>6q[HIp0 S䍼N$j[4'0x LacϪq@ƻIr9)bsc*#*d'{oIS -Jݩ“g =B<QbRϐbu:4% HUa74 -OR)xa)I:7gx*[Jt?yJ  |]3RwTSVOyXx&17[2,*ۻ|N>9-1`N^(M6Y;Ģ,y!NC`|[ZJ4MX0ޅmq>|bfVܲ;Sq!CRDO;&endstream endobj 202 0 obj << /Filter /FlateDecode /Length 3372 >> stream x[moCxȭ"' 6#I$N|tX8n|gH<ɱ $Ey|gyfqZ>c_{usfWG?;Knf*3ZQgֶNK[Y9;9v/XwV7{|~1Ln |˸i^bӶylzMg/I{V[wkW Ƹ>v[e 2ƛ6lwdY&`1v0#CV;D'psc;~. ͻx( 674>a?mW:hX |YHn|Yp9(]9.C8 +hqoVwr_Xiy.y/7TmȬd VatzCtc$]NͽcfLan23 #I7%/.>hސ䑓_6 $?ӅKm0<͋ʈ rAVq |A{b?aNE N w}1`~^ʻ=W) b,e"+4"b g͗aä7lD[YHf%5-7'$hSCpްu%\9w YGY7[Z29 h~0 D 5֬^OePۂ@"b5잱~G- M -teYBKu|< 񿤑 s r4dJ`%W Qh< C^YM7~WƂuMlYx!- ZB610ފ1I(x2PO䢻 ;"p w p!1 J1[(|׬$_PIR nth6xF7xzfކ{NʂtRVXy ͑& 43<-s[Qn-딮 UimeaRvK>?Ҷ^s2 %.늛] )9cGqq)JsQu_y/:j'K qǜcB6*C6MhSiΛxqZҏWɉ3H)3nݟynќToj< ^^?,tef >eumG0lso"}L7u&Y0rB%/O!T«iИaJ$W@? 3iΙ:WVźGL 2FdJwxvHThtCkj`Gp^(JԤMe% UX^%N(ZeV#u '4Ht}0$Ik 2o{ɐ aP-HJD۟6kZ&_(mVjq,wŧ].+KҨ~ !T2R"լB0Ӑ/$UGo%tĉn:fGI@+p r[!PHB`/#&AScue}*!*n $xӥw%KY-'sŋ@@]G+'7)Ov̶X[_slBȄH3~yT-qOJ۟6 ,DzO`ĒyjoUkCnb{q{cȟL,)QGkyxpMF7)|6h`yHI]a :"Uii}8zۣԗxdD;.@E( yN%H(dҼ%i CSURjql _KZV;/=.xG'CKb{T!0t]z!!J^/&`s⣑)3x AWO%.~H<5=pzfw.inKL5ܾu:gDsuw6{`f}\-϶hNVT K0gӡN-5SYݭo%R"C/c??vz_ ('^Qʱ+WQrhME p 3*JTےSidnpɮw$~RҴ\guH 3NX-kQ|ǶFwPK!tEo u+߈=F1*l07…@eJmq:-&uCsH 4m1;נ|&.)=-r[~ftŰыxb~/Ujo/_K˓cT2uB5cmc!WGr$'endstream endobj 203 0 obj << /Filter /FlateDecode /Length 4787 >> stream xw$?^ 3. pW%*}<)`q\$ey1dZ+~uE[ܬޟ_p=2gXsxm@ ?IYwC:H[J7c$YFHq^irlc]@oF6AƍyA`Xô1I_4\$8YHkKΧ`hBckHaY#D@-QJatߙqq;?|A*$@ 4kN ){)$KU+1D덟O^ }CI$()i̛f%$0bdM ;,~"^[#4~sKW8JSCSi>*u Fh;I4ܮC( O8GA),Lr⃜L6۰i2Rh?E/$//0"`2YZ]! EΉjR̐1q$𘍣! dϜTO0|w~b齳zɴ:g+|,i4z<^- vR8@"y[W  o"XL-_r#Yn;w/a! cܲdHZ VHBs0/V ✒JA#yF@W`s_< }H$iҝ4a nG|ꀅ %tUf*8r48B{|{(Xn+e$=]4l~z_%Wd.l'UդD~E~S&?=K[4ahpSI=!V@(mq %Ė`j2<* JUw~ >O㑮wq<R7+Q&jb1qR&7M {o*I.m-T9"Yآ6^aZ\dD\4(fO`Ep6 IyxSxiB0?Gr[oGfViY#j|e<.%E-dF" 2S&ST'pԁZy |`lЬk:I l$u0% pAjԾnΠEtU1,$I2&Bx^-ԦI| 6CW$#!VL6Fn&oDaD_ GrbM d!$MEh( E^#d2ŒM ;Y +R#YagV0HD$>Guk`*4=־.$:%QdaJ嘐CnZ֋:4t2+\hpU9B8aEˢp՞E}$jtU0WRK RW"\yTOSggkP+rꜺt1Y*b%(8퍨#2!B'E/V#;c! $WeGEjkEc}諨W*g*3, rxNcL`ky7`~g6VaK93. ȈE/* }HYT[,9*%uV,}W *8:!Lk'Xu`DTuxt18U}s F_ʼnyKF[=4NlYR8Qo )¢&k*5$JFOYFD/fD%"#'rEhH  `1憪 {Nܘ(La49ƥN%æ"/$D 9NHYAv?~4I)|eu7<>mVz CkߏM46/GIƖ+_d'،UEuuLe0`NdHD$M8:UO%}@:o|VXնz \&鲕be}lj' }$7贍B-bSm~w\ڬ강`x {Xju50PTbk[BN!U`ϴ7<߯StyxVgRA0-SB-kQpuv- iEMԁzh(@KZt͉:R 4MZhs}uaE@ÔZ9 z<}TN5V7՜eT,b8Y<bޟPS{ՠI?39vAM3 )T?9ei}24O@j}2Ys*24wqr}WTIܿRFiis'i[%iܿÚl#dt\^.r0BG Fl8^pDG?}6n=c,}uAoT ϡچQ|{5eӦ~w)N&5ϼ-I}Wuo*e4k]| sϴ tpl9L\OL4%\J^2[bY;nK w aͧ jW莇94=uWmhF߱1EwI"1RMsvW/]8!Da~{GH/׸oQ&Z1Ҍ¶dqưݒ 8$F';d$Jj\bሬj;H}cIN9;vʺCi$ܹNnÌC?xG7mWҚВ=֦3TMg@{zGu!ZQjnl.lj[_7t*˸3cޮ6AQ\)**(^Bv[a;|ij[ hO׾g.Moh/ƌfèU UsEFt]Rܾ\Ypxe|nkqբ7xĭ.)t[S9ek^36LuE+彔7?G{@fE{̶tt4r[;4/r ֹ7.W-/2.Vw>\lT?MɽL"xuqPn6x ~#Ꞵf }gkH6BnQIhTmH#U=>]%J铙:k?k@ |vt\hY5-֯]e<1o -c7 yܛ c[`-@(k%biHbKbkcr-tJ+^X9!br=7VK# EkaH\Zq*zE , eIfkbvs1p4ڴ`sרɺ!-ÛtW븛XߝRzon#;^xGse2L@gxo`n]iݏ7xź)݄Ԕ@-;r6\y"ql^4!JߢɹMAAXM}z'M eYx͸0h</I̠]r9[oAߡ12B'%mT3 Q@q,kR+x7q :2DayKuҺu2 u}`U"MI)z/DEgׅ-BVXָpZY;u0u!}C]E$aC@ IYN)Ɣ Ѵ /`zReo8?$c1pZPJP|׶IQ`EGxð2}`T {<:( c^|g*;~Ia[ONkcxѥsiVZ%r2'o !kQn"άJg)Lh''@zd8Ս2B}#Yܹ{E9Mx'Kwb=w^-n.}d9ѐ8OG󥿟Gendstream endobj 204 0 obj << /Filter /FlateDecode /Length 490 >> stream x]n@C{}vv&m&EA~@VȂ#+(ř^^yY~\>ͱoe~;=akk-P5kߛ~:>/S؆VSו<r<ux¦"&\$LxO{W$/ގEi"NHv.,>%lUV %mUFI[%nHN)ؠ;RMdC)٘lH5% 1(cDd$2ИD( 3f͘57sn̬s3j# ?E+qE9܊kHu%;=rvs::P:us`ϴhՆ G6m8plA"r ,' BA\0 p! `. `0ēZ ܸv^uOuyeW-^+endstream endobj 205 0 obj << /Filter /FlateDecode /Subtype /Type1C /Length 5477 >> stream xXy|SUھ!zYe"A޲( ʎRP}o4ifk'{&m&Җ. &P BAttOљQO.~E~?ڦ}}†X,6rstE៟ = ==,6`c`pS6!4r<}f,LX˗33q.JF-XbŲyK-Zrn*?391/"2Q(@D%g $!\P$-H-\)˼Q #bGja*(5%b/O5175a ~_ RT~a/X/(I[$ɑ)[SӶgܙ+'y$,\Mr9 mf`Ql`,[b}:l[m6bMf9yl mFbcK8l"6 {Sl)J:5ĺ=,}؇l_5I= lc#1#L5zo492fck_9ǯd '&r&<1}qbO<񀔐II_?ɒ'PPB!iR` D^V}MܮJHǙӀYHY"ekNZ3 r+JBgֿG%MX O(%Z J̶o, L ό6 >sJD t:3|$+R\v$m ^YJO^ Ζ%E2s.a덥LLKcbx(-!.if`% LJ)a01`8C0}Q7[;ݍXfg# ߉3`#{0| 8Ҙ06<&S 'ZAMe5ةfppWݫIiF\/T%#C۩G&ԃ` a"ppg \Q7s<Hz W#0/[Mg{Zp/' km׵Q(`< YA&yjtygEO d>Z$Zc Pz?Xul\UJsfPhnxVF >XC5Dv{S&t)PEwgI$DMi:V-7F#-d09~fܛ =TSG'V{=1}pS=an ­UJR*y`ᛘvNф7ᰛplSX **KHLdfk#%y%@CZ r*xZ:33_LMu(hq>)yACdxR}êS* *]1:U 'Mmo*D à 4+NCɶ!7zݿ~lNmWD"naUQIt$-k;^sy@ q*3fftn8ZFսYDC[.+Պ9= x;=g}y /XR/aơ"FӾv]w59eeF0he2DUP#ԉ5be&8@̼-eEyf̳ =LԵ].ڦK:NNOM[y|Xe2e;Eٝff6Ma$1p,}@.2p*Z3/s񸫭jK5ނO.0(zZlq]Fl+\7PE2Z#\3:9U$SmY,@LȰ@ZݕV:ptS ڪٺ2U!g[@%CWHlG0G֋/g>dPp=ME*3M^E~Ck'yaHǛ=aHCcp/cgG1;Te׆vWeJ&L_1kZ;[ҮFwghTFU v"5XKJTFO͖ǂxbZ* @ v]U  [S.wOja=vh3oRWi@!WZ ^=! S Hn͡&-1uWzm7>qD3($ C\~gR/!ŋA!Ng6LΌHR2\Bm ,r%u֪i8*㘤8:Oƙ$(AIs)„3#p$v_.؎_An$Tf y}k(q8A͌x)÷tN C1a=qOpd(c/=Y&Quara'DPPQ p/+ @\YÌǦ' = ,TC}u)҃ }WCcP;*8jm# AP]\Xhl[1C1,[$4o;DcMn*UWTͻα]C Z-}ЈP6a) $f0 P8i۷qg~? GP/;D&$G!1䍩ٗjԷe2A"B/fdA z@] g]bfAL(9Y#[rڻ3yRF?A^^a8 q@t؅q,Z'ˁזڗ$)l4"*K `v A\ nuDJˋkf&|r,'<6F,l=QL<~*@^bҊx rb% h P4y R[e,:f@X(g[JͨkkюoLH\yωSTQ9wH|RVPL]^a;‘g*ޞ,wTr" V/Mv횹 6WsjXmPFO-XxgNStU ۬<13ϙV]]r7nyf2WP&Ɇ2 h)P,Bzr;e2{j- qd +{ʲ*בusĥ rih&Ҡ?hb]kmw5 2"OgR L8ⳕ ތiSvyj=w32QVɁd˜^l)j9# Kj})u 3ke=U_#}f>CF}ͷ5>\tޜ_%yb(;^6HQWq?hz@DBn&krC^g/eL/ܜ@;07+//@ǀJY9-^5tq\rE`mչ*b37ZoO+4џԔVG} 䊊EKi__gF4l 'E[Gw]CmF%+l*}3*,Vf'3LV7nxFó%tY0 NeVۓ J:PDhu=ps'Uewd ? o 72zdfnh<{6Ȇ_$%-)MCm` -f#{(im\1ǀZ/N>d&.T>yuD[S?nU+FP•T򁻠k!3*^j8V GO7a̒LD)ocĵKV%2:/IE8:PC(pj)t=]~_b,ncmҔiy8`fݛ- l$t~f{ 扔/]f`r>e]́7cA\zFLi/;~\"nz VN]$ '"Tا|[Q'!2L-^6wS7Ũ.iX]T nv'Ξz]Y!/Q/ ӥ9+H)O?̬8q˷o{BkCDk+:9{Ρ֚`g켹FE0t"I0_b߇hyt̄;sxT/s\~yd:g@sP<3oтw]CGbHD8WC] J=x^MLX!gOkn*pQ剑xPbJ@ &i@xؗ<>_T:uq YP'lȄzIdMJeK5+2n?Xku0 LѤˍhe#+,ap 8ϺC 2C!it4~2GWse>QV6P{.goaOwhf''WIR-H[ЧwT|?N x|6>pO zM̴ܴZ~jՕxN̂zo~y9cI{ՎJDK=Ҩ+q]*-;篌lkroPCs].P+ygq8yߕqό5I{O_8, w8lʢ10; ~GCBd+h J(&nqM;-vu$fs)Ǵ3e693Bj *U p eCdt &9rsv!ܶf@4z,5r7z/kEendstream endobj 206 0 obj << /Filter /FlateDecode /Length 177 >> stream x]OA  ~ыb/=i~a1x+l2;6\/W2e 2ΛKX:<5NԳ 7ߟt{󻚁=EqU" ,QiHO@zeo$ߩ=>F"hIλN"6jLD=0g<n _Yendstream endobj 207 0 obj << /Filter /FlateDecode /Subtype /Type1C /Length 618 >> stream x-KhA&5[)EE4DD( KCXmmXt4_Gl[hITb-J[o^zG}'lY/|͇a-}"&ZH Ln= &h7/;Ϫ3j<?˭3!Q [_n}tѽ6Ĺ =^6Onf-0yKE;+h#6̆"8`iC0J= ٩iƏ2Mx!4TȌVUB$e*d X35 ox9f!O)q8zS6~ ө,<塔:=pFEPڰ;#i(JIW_|Ja j:aK||U _k{ꞆF$%)P{ڦ~^z6 III:~_ I 5/ou(3:i*͹wqTG}CUU?j_V%W>˅(Z4L@yr_M,AFA|`oT+7D7j'f# nﰝ4Ɇ!endstream endobj 208 0 obj << /Filter /FlateDecode /Length 166 >> stream x337U0P0U0S01C.=Cɹ\ `A RN\ %E\@i.}0`ȥ 43KM V8qy(-xǡ7oݺu;GGBS! 4 endstream endobj 209 0 obj << /Filter /FlateDecode /Length 362 >> stream x]=n@{N X3cK4N"Q,a\ylHFlV>yx_|~MƔw{]c/vYRp~(}.mLk;}4nx|ZƄ V{c{ǃ1cg s#j 쁍&m ’%%KJm vl6b Pq^{rBAqAPP\P 'HH18KO FinQ)lFFJuE_egΊ%1}!/[\<5MW6No[yS9Xendstream endobj 210 0 obj << /Filter /FlateDecode /Subtype /Type1C /Length 2969 >> stream xVkTSW1p 8Z뽱[ߏT[_j!@ !Ph`'M0@>Dmk+Z-v} j=Κ9%8f$swP>(@@_?AlJ_cS?#a8 4s".Y%UM=]Lff̝dee8L. SK%051v>P%dϑyR:i40jBd&Z-/QI2\-Z&da}HseqCz]7].K.K^7<7pX S &fq{B3})|8ZDt MKp %ăkxWaA-ig9hh,3+7G""Q^Z)gqof8̾H:I9O/B"#Y~4ȉ#/^;fa. fJw?%8B[Fخ}P`*+)JIZ i-!`L Pi1%)S'R {*>K\RO6ilY|AN9DQLrNF6:^i(mL$č7ڻ|,7AkvlbX|"cdϟr]YZy?isD,E?@%m9vƋoAʂ9aS-Mn8{Jobo]N4D'Z)B:5ı&]+0]}`wC*%e`*PUۀqleݦR>@R bKx>swZ(@nQXRT-?vG";810GDŽq\hf7={T!BCx^A>vxPim9x GA~k7qX+Now7YV=FcTV ULoaq> \gm1뫵 ӓ8ذʜFAHa.)f5f] 6 -'JSΓ8 TcbhsU\o3PbZե苳fem~Z뻰&IXqOQ8[$ct)H"@a~oD3XxD,:"f {u*]Y&Y6e-O/"yteՑS]yAh#@[9z3 IYh˵9ٺS[#l4{bPۗ$%̆Nh'hA/K) PmW 4 & 2`RMtKcsa=4]y ʀ)'^Dϛ30P 5Ň RJi,#- W97.tO}`ρPyk!b 6c ä*ʝ͵OQ'nQy Djev|ZI9+iԘN&ʠPѨSr)ј`1<ƎnēO۟=故.g~ʒᣘt5Yz頵NI B:G62 muSCy^ݕ^i<=[ϴ4! 7['+bFŁֺ>Գ^>["tE=lI3!mY?:1 %<OfBKɊ|uendstream endobj 211 0 obj << /Filter /FlateDecode /Length 204 >> stream x]=0 F"7hh)?R, \ Ni,$Vhc..i%gB}PRwIFU'H[l*z9Z-M8v4ElI5@( nixcA`gEX1+JYW-A0+kk#so8ϔ(̲@1.ͨ72jendstream endobj 212 0 obj << /Filter /FlateDecode /Subtype /Type1C /Length 1114 >> stream xUSkLSW}{aXP֙Efj4f`=DB+-1F)Bi X1Pa:2L)d2272a\HQ@Tw@] Q.HDUIB&t:RevRgӳ+Aedg;JegSǭ&RiQd58K5Ulu-Κ4Ǔj֦76<*2՚hHۜTjn:4Thm6;]2Z1;cc ,bJ̏=ˏg)jŌЦր #Ɉ!Mso>BLUD[]JuZN>4#hy=R;tWkCp{En\^(cD+P$9\|1*kU!5 J_bTb<:$(ъQU+ =:TX(EyQ.~kxD~7|_ߓ1*4#}6ԉϑ/ ~4|Mϼ+Ԋ-j T*"ȓ؏3PdIs<ĘϪC1]NԷ 4-UL x7p50[O4Ϳ9J^Q׼BxV`5W)0.6lLT׋HYEBCnuzcIGIDD,3t6 |RG{zG:YvOqQǂ..>$/_9sw ߀12J.vh- D/pkdz#<+@B&׹,YncB.pAqnZ~#PNVh}$);@8;q:T6߰4")V܎(nEe?RJH1'IAOrǪU:{n>hC5/ֻi@W| b){ 0gBpe.XN+`0W8ALVDrܔ$UqjhզL=(GڽJ:U[O?B+E0o $@endstream endobj 213 0 obj << /Filter /FlateDecode /Length 3911 >> stream x\moG)REK|Hk" z9p YmlIGIwfwIΒr=rgwgz;c51/|8c7xhLjg(_[7FZY:V*xQ;^3?ˇ_/3]ܜű3g?ϵPwazh/:{qq`%Ɂh%/<5mO51T?ݛz~VT[>_VIp$ KSac̺e@Rө)2=kim.tIGJ'yl֛[kT:^V_tr3ڧOxKhbTIy?,=6gYkUsX?>Q+ -`dEJg0 `!sQ9'[RI7fXA:11?vIdEd\7&f5{NF'I{IǺeFoMm`!$]dA}UbBz;} ~&«mB^aJ~ym6_~.7[AT>wsܬoJ46O烛4v`M8X.,D*tq}VݖXJ$׊WHiJxEkf%zuUKd3 dTb{5;gTC__Z(|4MxTE[CRf^;"]`f =GE 7dmlm\I W6$5IRĪ24$OYLZ[/xDbc\ ?7g(ZKuuy/Ƥ3m`F]5xmW^UpAZ/+ a+tY]#Z̅z`5_y4\FCZQ +%(;r. T8NsM^8k6;rM/!ʳ-QM[t3S[g^GK8@C^m|~-i~li&PH=(DX@2WN;ۈ l)\i^v+aJ% Нs=*YՒ]4~IfZ. U&mrB@X2ޫ{<;2:~?u\H{z`NH2`Qn-eo2#rvX" У?4%qЎ!JXDrwrs˔|TGKG)x: 59`LTWi @%Fn&Wt}]xhNXH>(`q$8 GU VcֺPHU"pB/ m6^P"i *eGnM(6)w-PFV;PtEa XґO+ꍏUۣU ]c;0|IH9`{*|ŮuC _@cc6=q3V lE43]9S;tނ @|yIü~nn< ^Ws,8G! Rt-d+t 8|3p_#T\gKvA: 9+"\cJTSeE⇤yolLP~qd D%H:O븎<YA)XyE9d㻓hɧ_V0:SUarP|^:}fA*$}r0D98pKCD;HI* ,@zO8p/2Zf:K0`lN&o3GۡY~t.A lsJxM(4)OFe Ila?E3ro TF8J&Pbxy)س&!0]{ByݶIHjX3tɥߒ) +[si+#RܤFv/8;˧786p8Q4{mth71|Nlr 炠"(# 9mV}"Nq<!JSpV~di4W SuOSif \Ǟ FnUyF AIDIAɬWZNKVWZE(*(ht 30GЌT0X}]1LMœPg"Ui| MeT?+P\b&MA8ʖҔlz7SXwА5`j(0$(Oϊh?`'+Md){tm?e3^}ͷ ZAv8ZTXxu%0cnX^2]ё҅=p9}f`o.Ӟ0_FՃv![s]օ\zDQG[C2puI>H!ɾE2:-юذ6c~L.$uxhlښP ?tf C') ?ԟ2F>**ngs%~&VQiorycs;һ&E1>(?q+aKNgP!ޛ'Q)h ZM--y8|9Ncs9~X8=KkeOņOٞJx^eoSIa;`ɉl'NTw{q(6cupN}=o,2@V>kϹ9H eF&ã /P̻UQ[ʝF${"п7XtQck$t5 B 5Aq'㵅1_8^P%νH&k+h3fY!a[y1xM{AgWͶy˞0r7̹uJ6갷죷QI77Q9Pyβg<[SY2pkLȖJڭloD}SbV^=աMC97׫Lػ2{Q^~ɼ=ər{7C(^dNR֚瘿gn9C0@7 >"w~Y[o>6jV=S-x}pfG_͛U/~uU؆L"CTT3@\*|enT<PG+Zq) By#-%!J \&Yh#8TfB~} L)k!. $PIE` P\庫ۇ+ Sp\aO=u:'r,yZQ{~"`%Ff!DAL|tYs5szDg*-)DO8pf.#;KJ0b[tWG

$=zy=&endstream endobj 214 0 obj << /Filter /FlateDecode /Length 2946 >> stream x[[~#,!f#F$G(QrО(2쮃׆ ߧ2YqL}u˼rSμqVOw9gOQYg];R"H"47|5K/^L/0bj0Wߓ_&"Jޅ Vp+fXx0d{7pR żo)_Bqӽm뫿Nffyoxte_66? ]]ۏfn_-e.ݦͦ "ݵ]:j{\F |u^ihkɅ ^{oYf菗J#;pɤ+EmowmG Ϧ@0` 8#, [ ֐sҞ|Cƕ2m>[gQVABaQF^_\_g\;yH8)<`#h vmH.+'jXKJ4@!*\.sv}b}"hט~Xː^-&X 5X{P:0Wή]|znNǜE(ֳhmTM*Hq^5 KzAJI7BS_X io UJq0: 3)pQ%,I7Q6]U3Ӫvf-,P}ס5w3 W͙!gq[\])X\۶rL,×iW~ v4_ FF`DݜyD7a2]PXnDqz D3ؒzFҀ0%qz<`J qxʟ=q̺l-\gZ+bAj*+ci^7u~{bV)` 3-!-0ԦNzn &N]PawG}_Kщf V -ԗVZRioLW/(cz00qì&lмDzﭷM~ͻ.\cSn64|oެ2NArU(Hd}Ƹt5pm8daCߑ8Q 3׃nH[Ȑ8Gm–03-%P`pMa0づv ќ4D(4͋p:5ϣ+p( .T ly{\q6NaƫU%msxyk07O@FhaT*-;Y"MG6Jv."u^(Ed){IܘN7x4[R7mu+ÆKTq4C _MFHqC\Š8SMFYk\St9[hI.= Hr0;Р7$6a]S ԂQAbhYX8`50*@&Y0˼ץx %KZu1d8 K|xS K&1 \I9ʬ0cNKB93K]=m""n3}KNR8'G'{Pߍ2(Zxͤ;u}RmR$(5M>'VUd gt1R9X<܏iP1^p$h1 zՔQгEnHB8}TGu7W]Jr(m(o;c/7x^˃#F\ v4JSq@IQպOA(q@yX@*IGpQ$R%;: Sa~"3srYU ABS+6[TaS>l9.Sչҡ#GÉ}8ً \/vTg8u"*Cȗxwm)~ 2qxht2S,͐zߤǴe5'kju*OuNJg+6+l?SKr\?X;%NAߌk}=/Z.l2= * Ldl9 EuU>DG*+! 91 Wi_c%RsC T]"}g u)Jޅ JQ3!/guuukއtqņ5JgGmlYLG85woeF |NL'(h UY*.%v0p~y7j4 `qBX_ofb Z. /!@u\N ~@/ PcC\߽N?c)&l"\$ N1>sŐwR:؇n)KWD' clxI wj ,2x5ݮ~N 8If:(4Sܐ!8;p*Kt,WFҿN !:;Bendstream endobj 215 0 obj << /Filter /FlateDecode /Length 2187 >> stream x[mo_A "fg~H4@|r,QY)E,Ǘ8/yffgvL="XLvـDȌq.V lB*GK|^iʻxYkm:Ƹ, t9jt́+EOG_'F"‡` dGӯDYrZ3Rh/O$Q{PփWiS0DxlX-?:F% %*\a/JjUo@K'Z ѴIArJ Osཏc 9)-Fsg_JVŔWQm:X0 \7WJu?&T&'0)'_U*tnb' !}N|]4[8)0So ƒ"^/@j! %Rǃs?N PQekL#]?40``GМ QW5ߌ'Ƽ ./DUTű[aj@xh> Rh :iro+\}'wr-^66FxĘdB_جu}!m!}6A 0qB81#pkCf7lxPe3-lk$V]|'I\T <[!xcb:TW3j2xBwRAO-U7;d Ew wx_H|8k'kƒ.?w&g6u(! &tXcNҴ 檣Dy| B6]hmQS$U6&aXoC:2PH5:]۠VeMMQA\'.ZA}=y89~|{U9N"}SG:)}}wt),`ܻ8ZGZ|W]Y.w\TᑶRzctE rdx e CYZ5H+qz;,i \ķ[|2AZnr6nd&KbF!0k]!;|2F#SIHIʓ z'˴xXA*=@E ˆ8(K ?y(&B-g pR*t}/!MY-96Fb '&-fKR⛌y!w67T"9ꋁH$ ! c/ Bz/D&s}!HXoЬd/\K~BxE~}v)m*8hZwKae|P)=]AOEũ|7{X<wOa,VeY\z#^߰UP՜ D^gkW潗bHkPwsu6^iq\t;z8QZŦEȖ'A+f^u> stream x\o#G(Xֆ E Xk[9{'ɹ;CrtD#{"GC?NYͧ ߛ qVOwSɩ2M1nn&XkI8E'0QtOv^Oy9lPLX|{1k{`̜)9 x9]I× 誺V|QfZÊl[? j.R-V3g(8W@ٚ>zX tsB9x9ȨζVLQpAѪͶ{ZݶݶRlU'EZ-vY<5a^Eo$~޻qDeQt0Ew#^/gs`lΫf6WFܻ|3pV]=` s9~;$/7bc cҙj=CX#Q@`0q/w5z5| މ15fD0PQMƩ&"aV[B>cTω ƮG'4Cx#^{>͞(ގ-X84WlmQje2-fBd8HT,DFQ2o~3 3,4Ja[ϕ1%s2̔\Lp|#ay״zmRWsސ7> 4r74,}O֮}~Y¦I<߁;YpA-u^$*AqV#,H(c Ae;_\m̐=|@Iz ^{x^)&Ÿ/F0`TP|)́"}fTRp#QN:'m> 1ϐD > ʁ.L  L ܸף+ fp$3.b;le|uuۭ}B{*!W2z,!u&3!H=7KX'eČ=|yJH"SJy6 2w꽌 9Jm9';>Hyfּ&^AQ-EŒ|Eꬊ"ݔWaAh;)ܡ5 L$îIeݮ1[EIٰk1:t j'|fFK1 ucטii8@I:zPg.sC^f{($YtvBz udW(ӛeX9qN9p%NgզO~l8CR ڶ]95K6mP3 `$Hy v]wkc}C#2]ztohP74(Tٜ^tC!Lk)a~4hޝ(jqX9oUyi~t]-^Iq-%Ϟ(P' UD@o "ol(0P7d0(\la2L0(PlaQ)Gi,![ BTϠk)J+(}"^yE3{T>W\QD@jS.SeWd,ϱ165dJF")SkdՇ)G4w2E~HA^$SSi\0pwo(҇$Tf*)ԡHb+qJoH)kzb+ޠPEٜ^Vo ~WBPW)@!g,@qSNJ1\9ej'95V;v<H]}*nkƤU\O[:#\`nTXkk%邽^=m-|',k%5gEii9̠eCl BhZE𼻌Yِ1 ʞu2+$-7 )+&NjrV#"m u,,-yh!Gh;(# N`byÌcJ f&O}I5$dTAQlg=XѷK̨({?{ YZu>sÜT%zMYM l5=`B6>H9XZC= EJM2j8h;G:C%3x~#ےophJ] *`haaiP`?T]Y^P%>ߩam #1fGxVkx Z0VˁƄ*-s#vp<<_W(i2|>'<_WU[;v8 ,3T[`4@qܮhe`9_cc̸cgBX{Pt*1y5o4.kfЎVd~*q z|n7mgڬڻfyŬx(I)ZH%R2: K=T(,&QBGaiEN> K JQXJ7P}P3{ B#X U:?ɴ;D,u{W\!\ipMcoT |]T:nXzB>B?9U&an-ds:e*=k s Q"'+yީď~endstream endobj 217 0 obj << /Filter /FlateDecode /Length 2705 >> stream x\Yo#~ ,0rVNaCo}őD$TOpa 聣9뫎K2ǙǿOLs*έIu cvv&|s6"~o^n}SR@ϯf.j<( pqvi+R}۵žw5綻Ew6wŻξU4֦0]A]8QE Tb/P|L֙\3R|\JQI+GDHx+CszOh6k҇ԑBh\t0SYy, JDi.p(#>xHFn7r_mź zy1M$4}*b%,[/ $0: #6vq2zHH}H>w>)(aB H+&U*1> e^t"l#eG@[iRMU0XO~֐hkaBт_]~ RuWZw$PDTeQ{ã=5"*c xZRB-rؾ؄d~-K[+)`)$Ϯm,^ΡJ05h~Y砟<.ũ:2wa C{lyH+ܱǬfd63޼Ʃsy|zp36B<7s onYA%!eU⇱f3xiPiMEht򥒶R)\YsDF},sQ~bnw?:EE0$V*Z)Vsh_DE+HөhL*Z9/⢢Z^",p^2g`̑aRD U"OXCTٸ(Do:5P#EMS\^2ӹBYɨW.r ~Idԁ}`h^:(Fc'6qN~F)bgaC2RҦ/Z2Ǣ?2navQL݁fǏ4}:'a聾;jŽ8k^xk5V&ʈqLd ^OH*{-4y諅)H\Rz*l58 UrVKo k+6֓g$u-V9cNQq9+<"i&eNu7q>4 %O/kރ&Q7 ^ꔮC Ty1F4`|%Vz*AI -EF40DG4w~eG4ak.kDqG40{D6|`(ֈiӗΟǢUdfv@HEfr=$KS:f<͞Ul ]gOHeW5g3m _"f:v<;xrΧeV g5w]P?0y?ɧ Tںog`zn¿j)|4F*4" /mRƮTWKT0 #(/A2G2O9te^t_a+ܾn5 nF6zh,rs xuxzR; x 'MAO=%/DPF葶?ZvЎ>os$Pѱ,&ǶOAJ'* y<<B^ansC2nW|[(f7Zr)<\+ij՟+iGMh_4R &O:LTi0Ѩ^npE>u~-h̻vouC,*lYKC51AP!qX[' cJv@EIB)z0iruMeX M6*öc\*W2rrkJ\Bg+nY1c0 5@ON '!6YM0 \NJWz4hnwfK55ڕG˖dmnt`DarDuT=^endstream endobj 218 0 obj << /Filter /FlateDecode /Length 3380 >> stream x\[o~# lD}eQ(-$8vFvfsH:NjbPx/ҧQSQYS g6@&tpv~SʠkF9AUxa1aDӇ.C@Ҏ.oXHU{FN:@gW7 #u6-bꦑgf>?\˳aHJØ۽/ ɸ/ u#<jWz_bWwm (]w JzQtxLHBHK`v<W 몽\lOmvϫǧdXnkS3G7feWSKP7S,{:16]1uE-VS7Ƈ`3+Ս-OZP4N1#B6u0Ϊ[hsrdw@!&,]Mn'f{zZ|,mRҧ',{$07, EVJFn4I[p;  EAQov.|Cyk+TV(xI |N5@Y%-P-4+ҖR Fu`ɉ@“mS">\EЊld@wqD l)N)bS'i p2"ug "E-xsH)RHz)Pex7[{+aA4s&dh)>sN̊Fy@ؕ8ysWắxhCbzvq~.lDX RWEAk6pmjM6zpᅯVSURU}y@ƅ gu(BuGC49( rJ[Kc>UuiȑH *N A]muK'Q -$>h#K~RP]B7Ym! `;>`97Y f٬OxY s5zG1#{+9*>%C:Ո,҄Ɨeth]NJ1Q"awfoi]K|zᦈSI &X(pNHqqHeȨ7sHyᨈ)p6ľD84܅` qF$kESޙsMuꂡP|Tŀz ʢG?71఩$4}?TE ThDGi ӱ>Ϲ?ȋ\'t1\:vO3 +m)ܵ5fGlDrs(LqȚS;,= Du9]"l`R iGHIPģ{ !6Z3!KI=9*ֈ>D^oSgojpCvBK\q/WRfgAT.៚&OX@zqpdcޠU~ZMn0yΎOFR)YFR_SJAVJ2X: eK 1X"Ϋ>eTqX)ptb(=W܆!g)Ot!?pMUN&ID$EA;Ҥ |>JBn@Wȿ1~1x(X* {%Oz Bx#о5.H5~K^Q4FPEbU!GEʐEe ZlBYb ދ{[%/N痊0 bjIk Iy۠f)Э )apY+i}[U)llC-m.`[{ʀp](#P)k怣B15wV]۪ VQ`jNR(sQ(ހH<24@(NSVSQʚS82 wkpp@_6~15xP 5 .^1϶!(l;QB1ͶBiH+Kn &SnD*`ue^:y f.H-y޼rji>?IJ&[غ@beP C@Mjm7wdi;l|9ygv0ژP˧ t v]Ė_{֗{:R]u>e0>Dp n1n}i5|IW<,OH;nS=mm/o!lpc1!w|#Z[ F\k.kT HGL{endstream endobj 219 0 obj << /Filter /FlateDecode /Length 233 >> stream x]1n0 EwB7$V\%Cl4DgI':<O"AWb2o^X>?J`;5&S7vayp볩}lWӬG:\te9G44.WnGskkRCnHnDjs͞ݓ݋HDR(eNjPu~K tz?_>(QQ EԼ$/> stream xTmLSgޫ2tw[3nT#t(":q~SJGJ[,WUJiJ)P(HD6#l̯lncN4:X%?vٲlظ9ӕo)iժϤ\iO ~ :CILgj3l1Ԛ,mv*XN RUĻB"XL$KlI3!'vady;e|X)V@PTȡ ęӻ=?cxp#5Pw)85RͮJ14;NDeG8JJe:WJW[>1O>!92ෙD˩61C蟆nYݬs)p随ehjCȇ=@OIN]TŲoXJƳ'@4DXh[[׀Um?`]{n yq| `fg=lZ33+yS̮OԄNdtё6? \'CK24~f,zl5m:~1fkGԾ2tA9s:Ih*~oiKj{J[Psh4Ϣ" \ࡇWLcw=D򞇍{GpقE٫!қ =0ZE}}.'IN,ވІ[uHljBwː -f mڅ37n];QOIV^bva;m]S[驩/lQk-F=kHϿ(ynʡ,.N (Gӱu5C kwӐ=轊&og1[u?tBBf& A7f3 kk :$6#}?6@:qs+ ?WrItWý24C|zS7-MzGZBSÜ"E Gt$B/cWJu˪StVxY/ࢍW`m6[OlhUvd=F oT`%ET.NxvVzȑ Atk]g+fO`C1=s~:ie}0> stream x]1 E{N @IИ"L ,}US.v.G3w> S\+Jnw"7NL7ޟ m|#gyؚL0%m 0kTsAWл(舠HX$%:kΕ"I qu]r ab]}Di(v`endstream endobj 222 0 obj << /Filter /FlateDecode /Subtype /Type1C /Length 1068 >> stream x={H[W5n8dfκY}΍շB+3ӨiLbcbbm^FͽܼL+GuJ"Ҋ -+eAa W)f?vmq<8pp,1qҚ Y!#S$H@Jbg!S:*jL$8ۻ V>d~8( O(**QS**(FGFcyyf9WZ>OQVECeT5)>댊2Vxər)kMFAQoRt%Kʪ0;Xrq_T>N(!>I9! R!Sκ_ JCRޑ ᪴q}I6Ʌ۳K@ˊS՜PDj|!+pSIS&U # =d) 4Sv0JQfB X Ko:F; :Iݵx>މKHg3o! OeBN=E ;#A&|%h`N>{ e>Z[NtLD !3Wpm[~3^ x>E9q# CN#lWXJ"r<_LF:o_b{b^v1ɭՖj .PSdŔVa og9t B^Z[!dbpwX7'.swqN7O#=+(^jm_| ,fܠCfv {gh{HOv&)')dXkm_2 PArnMD!*/= B7|4i_C ɵ 7h.M¿I!oPs0ăh;KNFVo?Gϧwt0?/7)VR8RJ@2,",-ji7(B>H’W6TtԻ`!4} SZMZjfnbIvҿ pu\HL> stream x]OA  ~U¥^z@(={<$;epMݣWOLX#~ 鈓ui*mz@X HͪoѶ]5kHys t 1n fsPTd&؉ ̄΢ sRqoD#T=K=jh?IYendstream endobj 224 0 obj << /Filter /FlateDecode /Subtype /Type1C /Length 767 >> stream x%OhygR3;XT,˲*86iIbMC&M&3o24mTqY..v]Tŋ{+.^3L} }~qX8muڌk-j_PozcV7"orG LJc.@s[Q6mu{==vxl\y. {ɲ?XPl~JiC.IeHts~]^@et"0:aV [3̈=Ǐ4)v y|Q{fЮo̅?*2!K|!t](\ KTD]=Ѥ)Q E7Յ]f2K B\LQeu.zj0؝$cb86rIJ ( @d 0I<._ȥjVmA)M'0 z+j~qş/οԨ՜D5P.\qȕJBFr.; 3q^If2?"_/FOM '!l9{ hGlWt|7H%8hJ-!s~^ 'D`Bo^s Q~i2$r ( g];;mwqF)c$R҄"d( I"?AZqre? 3ZP= gӜ*7b>}^> stream x]OA  ~p6M , A<zIfgg36=S/XVt"j* G4-].uݖQӰz HtAu96 8OL'N29Mqkg.FTm!GY߫ uZendstream endobj 226 0 obj << /Filter /FlateDecode /Subtype /Type1C /Length 732 >> stream xU[HSqN!vc5("D- -Psmzvtι͹:yܜtLPIQ SXBCu˟PD(UUSstϝO ΧdBq#r!"0UNRDs"Ůee%ą2RMR*\C6R-wV"iCRMwZWR n#HIU&jjHY*&)FJRADZ@ t-DlLޝZgy|nD\3W*a2} x2ɞr! 7Xݪam[2]hc.p`TTU^Djd7w4(ءk6 Bo&( >l&@uvc2~.Ebp8(c$ I~'GIxmjzP pzݕPffֳ6sdhN0.]-- Dz f5a248.^4vu_:,C>3[`_&ҫ"Qr 74z=#Ʊn'|Gm&?)d8Fwit1c|L8-d~r~ϛhiQfXdglb$1E:8Xlȓ fH6:!UKfG Mendstream endobj 227 0 obj << /Filter /FlateDecode /Length 162 >> stream x]O10 $EL. 2D! }I3l_X#-~H0dt2qV6L)/MY=^RTn"VU1rO:9 l?%ΛkRiZCN/(S1endstream endobj 228 0 obj << /Filter /FlateDecode /Subtype /Type1C /Length 341 >> stream xcd`ab`ddds TH3a!gOCnnυ ~-XU79(3=D! H*ZZ(X*8e&')&d&99 ə%`-6%%VzzEv St3K2RSRSJsS%E )Ey9ũ9 " ] L,?:~m3>)3'S&v8^[Z$릴Noq:-}3N1yRD?{cB -*鮖sdaly>nendstream endobj 229 0 obj << /Filter /FlateDecode /Length 3637 >> stream x\o#GY'Ahז}jl); %w>kV w3om,K/FR$o_ތR"x=>I2cB!5C.s6F*˻NG9IjH袠ڻqPA$;u_}u4qڊt ?9!O꺓7e6"-n{XMnggtůG)%sj|vtgЯ[NWvKj:]]j6m(Ҏtspj}۠bo5 OX'+ޯNۃ3ɥfV7:FaLlng{G.5X$t1%3hgC+la U#%Z|<HKa[y " i GTR1l2O(e_䍰7@W8fz?!DK#0OjX3Ӄ`<$ ^8ӝ ]/Ӌ5}oC%'jf,!@}4g -rgV42^yeX h XH@-+/Al 1ޖW%RJ#r~ Gcp! Zq&Fth0]s*+ & d#t,f,,ujmrnxÒwDJ6_ w5RI_K6ԍL0ߧCs64;=Fgtӷ1Z-lt{Aa!Er^$pȉN"h0b\Ǚ:ӓhloC2k\JIJjv}=shUɐo.dۭ'Lcuq7e9\uIS&CUb9LgsnИ>eg* ijEzqĭʉ%UQ$[wsgSX}ntL}sj7rRɹsV;xqVHu%r,vI'nwޟ->N g>nC(#P Ѵ&D˚V>\?@"q rnиК Ӛ |MP(V* 1 J^d 5fvU֑YP#mDa]k&4je^nthf^[(&:y6}q 6zYhA>@%HH9$4J(A[%2(AM7Qm{ND!( mO>](:@lQK'w+bJk $fk88D- !(Jߓ=}w݊\OH @H5n$V'1;.=i?w\ HRuSlæI%EԱo׫fjU|g)dkQ%a*{ o-bLe4$>BwKnsTfӖ(χ#p5h3JKczy}-Lbp調je`w5\#+OyI:P/BN=u)t 54Y3BH&[jL;pWƇm>7Ew&:b<ȶZFP=r[Avn?&*_SN³djiHyeNm~r;ܿnnp7].Yd Wb!A@Lq>$N[!ujXEa n}znbm0A9)`sV'cn>id(`m Ryick<XJG|LTb[u3J>y$m]s|~,zX`ٗѰƒ`g<2 BIQ0WT1)G烉OQ(G7 Q 2K!jj BlK]kIhJiB!Z g.znPX8"mt"wRCC(byn:#Ɯ4yf@)S',@6lLGic, mCu 4HeW/XF^C|;OdNG·*A:[QV`%Lh^, (RÚaWpeoVlZNnb9 .N.W;0 `PJ_X}S$6(V>C _P^6-c1:ۀiQsx캕U5Xknw57t㭹Q$ΚaQ}qfT&/gFXknFUsk 3"οNs6;Yṽ-'j|79' .?R-"ES=WF? Ӂ/JÝCU1b]5, dOw~:y󪤷;jT\~7 1^Lyi m[(<\**P婾Nh6GI5$v%}>-_e5Y Exjr[^ccqp7ٰ>.x DS< wJkyK@(ϑ]Ʌendstream endobj 230 0 obj << /Filter /FlateDecode /Length 190 >> stream x] <o pq[m/P +UwooKv.4,`Ӛ X)fىudE{w=B񔧊Rd& sF8Tm +ϥ" V$:S$Dtk.h%FDHfyl>ؗ5% ]IW}G).b_˾`"endstream endobj 231 0 obj << /Filter /FlateDecode /Subtype /Type1C /Length 873 >> stream xoLuh1404лK6V$SHƢtjsw-}Z`-bWFYι /F^̗0fhfwx }x<8.p/khv@,ҋ%K*KKA:W앨qt+os %V Wu&4 g:]#8JwEEE z1pE[өSNhKg\h''t'3L[EP5vEd73ðR&9 {{c-X;& *T[|=@621*YBڱz;HϪ)!az ;2ITҶDh'SI"IO0 /@'&F._oTށ-X'smez|0Bop@ $2Xbr:1 x$i !rwP2TNQ!Be8:kiT_͹-qkw 'G?>]8 pA DN Ʈ\2Q?f'Y:+:$eipFH WyM endstream endobj 232 0 obj << /Type /XRef /Length 213 /Filter /FlateDecode /DecodeParms << /Columns 5 /Predictor 12 >> /W [ 1 3 1 ] /Info 3 0 R /Root 2 0 R /Size 233 /ID [<994aff81082e13b17b8cfbc29f265610>] >> stream xұP{o) -2ue2xM0DbfE$d Lxvr1z [XC"v#Q{ШȐ@t3}P0(f tf4bg>= pkgmaker::latex_preamble() @ % REFERENCES \usepackage[minnames=1,maxnames=2,backend=bibtex]{biblatex} \AtEveryCitekey{\clearfield{url}} <>= pkgmaker::latex_bibliography('doRNG') @ \newcommand{\citet}[1]{\citeauthor{#1}~\cite{#1}} %% \newcommand{\graphwidth}{0.9\columnwidth} % clever references \usepackage[noabbrev, capitalise, nameinlink]{cleveref} \newcommand{\dorng}{\code{\%dorng\%}\xspace} \title{Using the \code{doRNG} package\\ {\small \Rpkg{doRNG} -- Version \Sexpr{packageVersion('doRNG')}}} \author{Renaud Gaujoux} \begin{document} \maketitle \tableofcontents \section*{Introduction} \addcontentsline{toc}{section}{Introduction} Research reproducibility is an issue of concern, e.g. in bioinformatics \cite{Hothorn2011,Stodden2011,Ioannidis2008}. Some analyses require multiple independent runs to be performed, or are amenable to a split-and-reduce scheme. For example, some optimisation algorithms are run multiple times from different random starting points, and the result that achieves the least approximation error is selected. The \citeCRANpkg{foreach} provides a very convenient way to perform parallel computations, with different parallel environments such as MPI or Redis, using a transparent loop-like syntax: <>= options(width=90) library(pkgmaker) library(knitr) opts_chunk$set(size = "footnotesize") knit_hooks$set(try = pkgmaker::hook_try) @ <>= # load and register parallel backend for multicore computations library(doParallel) cl <- makeCluster(2) registerDoParallel(cl) # perform 5 tasks in parallel x <- foreach(i=1:5) %dopar% { i + runif(1) } unlist(x) @ For each parallel environment a \emph{backend} is implemented as a specialised \code{\%dopar\%} operator, which performs the setup and pre/post-processing specifically required by the environment (e.g. export of variable to each worker). The \code{foreach} function and the \code{\%dopar\%} operator handle the generic parameter dispatch when the task are split between worker processes, as well as the reduce step -- when the results are returned to the master worker. When stochastic computations are involved, special random number generators must be used to ensure that the separate computations are indeed statistically independent -- unless otherwise wanted -- and that the loop is reproducible. In particular, standard \code{\%dopar\%} loops are not reproducible: <>= # with standard %dopar%: foreach loops are not reproducible set.seed(123) res <- foreach(i=1:5) %dopar% { runif(3) } set.seed(123) res2 <- foreach(i=1:5) %dopar% { runif(3) } identical(res, res2) @ A random number generator commonly used to achieve reproducibility is the combined multiple-recursive generator from \citet{Lecuyer1999}. This generator can generate independent random streams, from a 6-length numeric seed. The idea is then to generate a sequence of random stream of the same length as the number of iteration (i.e. tasks) and use a different stream when computing each one of them. The \citeCRANpkg{doRNG} provides convenient ways to implement reproducible parallel \code{foreach} loops, independently of the parallel backend used to perform the computation. We illustrate its use, showing how non-reproducible loops can be made reproducible, even when tasks are not scheduled in the same way in two separate set of runs, e.g. when the workers do not get to compute the same number of tasks or the number of workers is different. The package has been tested with the \CRANpkg*{doParallel} and \CRANpkg*{doMPI} packages \citepkg{Rpackage:doMPI,Rpackage:doParallel}, but should work with other backends such as provided by the \citeCRANpkg{doRedis}. \section{The \texttt{\%dorng\%} operator} The \Rpkg{doRNG} defines a new generic operator, \code{\%dorng\%}, to be used with foreach loops, instead of the standard {\%dopar\%}. Loops that use this operator are \emph{de facto} reproducible. <>= # load the doRNG package library(doRNG) # using %dorng%: loops _are_ reproducible set.seed(123) res <- foreach(i=1:5) %dorng% { runif(3) } set.seed(123) res2 <- foreach(i=1:5) %dorng% { runif(3) } identical(res, res2) @ \subsection{How it works} For a loop with $N$ iterations, the \code{\%dorng\%} operator internally performs the following tasks: \begin{enumerate} \item generate a sequence of random seeds $(S_i)_{1\leq i\leq N}$ for the \proglang{R} random number generator \code{"L'Ecuyer-CMRG"} \cite{Lecuyer1999}, using the function \code{nextRNGStream} from the \citeCRANpkg{parallel}, which ensure the different RNG streams are statistically independent; \item modify the loop's \proglang{R} expression so that the random number generator is set to \code{"L'Ecuyer-CMRG"} at the beginning of each iteration, and is seeded with consecutive seeds in $(S_n)$: iteration $i$ is seeded with $S_i$, $1\leq i\leq N$; \item call the standard \code{\%dopar\%} operator, which in turn calls the relevant (i.e. registered) foreach parallel backend; \item store the whole sequence of random seeds as an attribute in the result object: <>= attr(res, 'rng') @ \end{enumerate} \subsection{Seeding computations} Sequences of random streams for \code{"L'Ecuyer-CMRG"} are generated using a 6-length integer seed, e.g.,: <>= nextRNGStream(c(407L, 1:6)) @ However, the \code{\%dorng\%} operator provides alternative -- convenient -- ways of seeding reproducible loops. \begin{description} \item[\code{set.seed}:] as shown above, calling \code{set.seed} before the loop ensure reproducibility of the results, using a single integer as a seed. The actual 6-length seed is then generated with an internal call to \code{RNGkind("L'Ecuyer-CMRG")}. \item[\code{.options.RNG} with single integer:] the \dorng operator support options that can be passed in the \code{foreach} statement, containing arguments for the internal call to \code{set.seed}: <>= # use a single numeric as a seed s <- foreach(i=1:5, .options.RNG=123) %dorng% { runif(3) } s2 <- foreach(i=1:5, .options.RNG=123) %dorng% { runif(3) } identical(s, s2) @ \noindent \textbf{Note}: calling \code{set.seed} before the loop is equivalent to passing the seed in \code{.options.RNG}. See \cref{sec:set_seed} for more details. \medskip The kind of Normal generator may also be passed in \code{.options.RNG}: <>= ## Pass the Normal RNG kind to use within the loop # results are identical if not using the Normal kind in the loop optsN <- list(123, normal.kind="Ahrens") resN.U <- foreach(i=1:5, .options.RNG=optsN) %dorng% { runif(3) } identical(resN.U[1:5], res[1:5]) # Results are different if the Normal kind is used and is not the same resN <- foreach(i=1:5, .options.RNG=123) %dorng% { rnorm(3) } resN1 <- foreach(i=1:5, .options.RNG=optsN) %dorng% { rnorm(3) } resN2 <- foreach(i=1:5, .options.RNG=optsN) %dorng% { rnorm(3) } identical(resN[1:5], resN1[1:5]) identical(resN1[1:5], resN2[1:5]) @ \item[\code{.options.RNG} with 6-length:] the actual 6-length integer seed used for the first RNG stream may be passed via \code{options.RNG}: <>= # use a 6-length numeric s <- foreach(i=1:5, .options.RNG=1:6) %dorng% { runif(3) } attr(s, 'rng')[1:3] @ \item[\code{.options.RNG} with 7-length:] a 7-length integer seed may also be passed via \code{options.RNG}, which is useful to seed a loop with the value of \code{.Random.seed} as used in some iteration of another loop\footnote{Note that the RNG kind is then always required to be the \code{"L'Ecuyer-CMRG"}, i.e. the first element of the seed must have unit 7 (e.g. 407 or 107).}: <>= # use a 7-length numeric, used as first value for .Random.seed seed <- attr(res, 'rng')[[2]] s <- foreach(i=1:5, .options.RNG=seed) %dorng% { runif(3) } identical(s[1:4], res[2:5]) @ \item[\code{.options.RNG} with complete sequence of seeds:] the complete description of the sequence of seeds to be used may be passed via \code{options.RNG}, as a list or a matrix with the seeds in columns. This is useful to seed a loop exactly as desired, e.g. using an RNG other than \code{"L'Ecuyer-CMRG"}, or using different RNG kinds in each iteration, which probably have different seed length, in order to compare their stochastic properties. It also allows to reproduce \code{\%dorng\%} loops without knowing their seeding details: <>= # reproduce previous %dorng% loop s <- foreach(i=1:5, .options.RNG=res) %dorng% { runif(3) } identical(s, res) ## use completely custom sequence of seeds (e.g. using RNG "Marsaglia-Multicarry") # as a matrix seedM <- rbind(rep(401, 5), mapply(rep, 1:5, 2)) seedM sM <- foreach(i=1:5, .options.RNG=seedM) %dorng% { runif(3) } # same seeds passed as a list seedL <- lapply(seq(ncol(seedM)), function(i) seedM[,i]) sL <- foreach(i=1:5, .options.RNG=seedL) %dorng% { runif(3) } identical(sL, sM) @ \end{description} \subsection{Difference between \texttt{set.seed} and \texttt{.options.RNG}} \label{sec:set_seed} While it is equivalent to seed \dorng loops with \code{set.seed} and \code{.options.RNG}, it is important to note that the result depends on the current RNG kind \footnote{See \cref{sec:issues} about a bug in versions < 1.4 on this feature.}: <>= # default RNG kind RNGkind('default') def <- foreach(i=1:5, .options.RNG=123) %dorng% { runif(3) } # Marsaglia-Multicarry RNGkind('Marsaglia') mars <- foreach(i=1:5, .options.RNG=123) %dorng% { runif(3) } identical(def, mars) # revert to default RNG kind RNGkind('default') @ This is a ``normal'' behaviour, which is a side-effect of the expected equivalence between \code{set.seed} and \code{.options.RNG}. This should not be a problem for reproducibility though, as R RNGs are stable across versions, and loops are most of the time used with the default RNG settings. In order to ensure seeding is independent from the current RNG, one has to pass a 7-length numeric seed to \code{.options.RNG}, which is then used directly as a value for \code{.Random.seed} (see below). \section{Parallel environment independence} An important feature of \code{\%dorng\%} loops is that their result is independent of the underlying parallel physical settings. Two separate runs seeded with the same value will always produce the same results. Whether they use the same number of worker processes, parallel backend or task scheduling does not influence the final result. This also applies to computations performed sequentially with the \code{doSEQ} backend. The following code illustrates this feature using 2 or 3 workers. <>= # define a stochastic task to perform task <- function() c(pid=Sys.getpid(), val=runif(1)) # using the previously registered cluster with 2 workers set.seed(123) res_2workers <- foreach(i=1:5, .combine=rbind) %dorng% { task() } # stop cluster stopCluster(cl) # Sequential computation registerDoSEQ() set.seed(123) res_seq <- foreach(i=1:5, .combine=rbind) %dorng% { task() } # # Using 3 workers # NB: if re-running this vignette you should edit to force using 3 here cl <- makeCluster( if(isManualVignette()) 3 else 2) length(cl) # register new cluster registerDoParallel(cl) set.seed(123) res_3workers <- foreach(i=1:5, .combine=rbind) %dorng% { task() } # task schedule is different pid <- rbind(res1=res_seq[,1], res_2workers[,1], res2=res_3workers[,1]) storage.mode(pid) <- 'integer' pid # results are identical identical(res_seq[,2], res_2workers[,2]) && identical(res_2workers[,2], res_3workers[,2]) @ \section{Reproducible \texttt{\%dopar\%} loops} The \Rpkg{doRNG} also provides a non-invasive way to convert \code{\%dopar\%} loops into reproducible loops, i.e. without changing their actual definition. It is useful to quickly ensure the reproducibility of existing code or functions whose definition is not accessible (e.g. from other packages). This is achieved by registering the \code{doRNG} backend: <>= set.seed(123) res <- foreach(i=1:5) %dorng% { runif(3) } registerDoRNG(123) res_dopar <- foreach(i=1:5) %dopar% { runif(3) } identical(res_dopar, res) attr(res_dopar, 'rng') @ \section{Reproducibile sets of loops} Sequences of multiple loops are reproducible, whether using the \code{\%dorng\%} operator or the registered \code{doRNG} backend: <>= set.seed(456) s1 <- foreach(i=1:5) %dorng% { runif(3) } s2 <- foreach(i=1:5) %dorng% { runif(3) } # the two loops do not use the same streams: different results identical(s1, s2) # but the sequence of loops is reproducible as a whole set.seed(456) r1 <- foreach(i=1:5) %dorng% { runif(3) } r2 <- foreach(i=1:5) %dorng% { runif(3) } identical(r1, s1) && identical(r2, s2) # one can equivalently register the doRNG backend and use %dopar% registerDoRNG(456) r1 <- foreach(i=1:5) %dopar% { runif(3) } r2 <- foreach(i=1:5) %dopar% { runif(3) } identical(r1, s1) && identical(r2, s2) @ \section{Nested and conditional loops} \label{sec:nested} Nested and conditional foreach loops are currently not supported and generate an error: <>= # nested loop try( foreach(i=1:10) %:% foreach(j=1:i) %dorng% { rnorm(1) } ) # conditional loop try( foreach(i=1:10) %:% when(i %% 2 == 0) %dorng% { rnorm(1) } ) @ In this section, we propose a general work around for this kind of loops, that will eventually be incorporated in the \code{\%dorng\%} operator -- when I find out how to mimic its behaviour from the operator itself. \subsection{Nested loops} The idea is to create a sequence of RNG seeds before the outer loop, and use each of them successively to set the RNG in the inner loop -- which is exactly what \code{\%dorng\%} does for simple loops: <>= # doRNG must not be registered registerDoParallel(cl) # generate sequence of seeds of length the number of computations n <- 10; p <- 5 rng <- RNGseq( n * p, 1234) # run standard nested foreach loop res <- foreach(i=1:n) %:% foreach(j=1:p, r=rng[(i-1)*p + 1:p]) %dopar% { # set RNG seed rngtools::setRNG(r) # do your own computation ... c(i, j, rnorm(1)) } # Compare against the equivalent sequential computations k <- 1 res2 <- foreach(i=1:n) %:% foreach(j=1:p) %do%{ # set seed rngtools::setRNG(rng[[k]]) k <- k + 1 # do your own computation ... c(i, j, rnorm(1)) } stopifnot( identical(res, res2) ) @ The following is a more complex example with unequal -- but \textbf{known \emph{a priori}} -- numbers of iterations performed in the inner loops: <>= # generate sequence of seeds of length the number of computations n <- 10 rng <- RNGseq( n * (n+1) / 2, 1234) # run standard nested foreach loop res <- foreach(i=1:n) %:% foreach(j=1:i, r=rng[(i-1)*i/2 + 1:i]) %dopar%{ # set RNG seed rngtools::setRNG(r) # do your own computation ... c(i, j, rnorm(1)) } # Compare against the equivalent sequential computations k <- 1 res2 <- foreach(i=1:n) %:% foreach(j=1:i) %do%{ # set seed rngtools::setRNG(rng[[k]]) k <- k + 1 # do your own computation ... c(i, j, rnorm(1)) } stopifnot( identical(res, res2) ) @ \subsection{Conditional loops} The work around used for nested loops applies to conditional loops that use the \code{when()} clause. It ensures that the RNG seed use for a given inner iteration does not depend on the filter, but only on its index in the unconditional-unfolded loop: <>= # un-conditional single loop resAll <- foreach(i=1:n, .options.RNG=1234) %dorng%{ # do your own computation ... c(i, rnorm(1)) } # generate sequence of RNG rng <- RNGseq(n, 1234) # conditional loop: even iterations resEven <- foreach(i=1:n, r=rng) %:% when(i %% 2 == 0) %dopar%{ # set RNG seed rngtools::setRNG(r) # do your own computation ... c(i, rnorm(1)) } # conditional loop: odd iterations resOdd <- foreach(i=1:n, r=rng) %:% when(i %% 2 == 1) %dopar%{ # set RNG seed rngtools::setRNG(r) # do your own computation ... c(i, rnorm(1)) } # conditional loop: only first 2 and last 2 resFL <- foreach(i=1:n, r=rng) %:% when(i %in% c(1,2,n-1,n)) %dopar%{ # set RNG seed rngtools::setRNG(r) # do your own computation ... c(i, rnorm(1)) } # compare results stopifnot( identical(resAll[seq(2,n,by=2)], resEven) ) stopifnot( identical(resAll[seq(1,n,by=2)], resOdd) ) stopifnot( identical(resAll[c(1,2,n-1,n)], resFL) ) @ \subsection{Nested conditional loops} Conditional nested loops may use the same work around, as shown in this intricate example: <>= # generate sequence of seeds of length the number of computations n <- 10 rng <- RNGseq( n * (n+1) / 2, 1234) # run standard nested foreach loop res <- foreach(i=1:n) %:% when(i %% 2 == 0) %:% foreach(j=1:i, r=rng[(i-1)*i/2 + 1:i]) %dopar%{ # set RNG seed rngtools::setRNG(r) # do your own computation ... c(i, j, rnorm(1)) } # Compare against the equivalent sequential computations k <- 1 resAll <- foreach(i=1:n) %:% foreach(j=1:i) %do%{ # set seed rngtools::setRNG(rng[[k]]) k <- k + 1 # do your own computation ... c(i, j, rnorm(1)) } stopifnot( identical(resAll[seq(2,n,by=2)], res) ) @ \section{Performance overhead} The extra setup performed by the \code{\%dorng\%} operator leads to a slight performance over-head, which might be significant for very quick computations, but should not be a problem for realistic computations. The benchmarks below show that a \code{\%dorng\%} loop may take up to two seconds more than the equivalent \code{\%dopar\%} loop, which is not significant in practice, where parallelised computations typically take several minutes. <>= # load rbenchmark library(rbenchmark) # comparison is done on sequential computations registerDoSEQ() rPar <- function(n, s=0){ foreach(i=1:n) %dopar% { Sys.sleep(s) } } rRNG <- function(n, s=0){ foreach(i=1:n) %dorng% { Sys.sleep(s) } } # run benchmark cmp <- benchmark(rPar(10), rRNG(10) , rPar(25), rRNG(25) , rPar(50), rRNG(50) , rPar(50, .01), rRNG(50, .01) , rPar(10, .05), rRNG(10, .05) , replications=5) # order by increasing elapsed time cmp[order(cmp$elapsed), ] @ \section{Known issues} \label{sec:issues} \begin{itemize} \item Nested and/or conditional foreach loops using the operator \code{\%:\%} are not currently not supported (see \cref{sec:nested} for a work around). \item An error is thrown in \code{doRNG} 1.2.6, when the package \code{iterators} was not loaded, when used with \code{foreach} >= 1.4. \item There was a bug in versions prior to \code{1.4}, which caused \code{set.seed} and \code{.options.RNG} not to be equivalent when the current RNG was \code{"L'Ecuyer-CMRG"}. This behaviour can still be reproduced by setting: <>= doRNGversion('1.3') @ To revert to the latest default behaviour: <>= doRNGversion(NULL) @ \end{itemize} \section{News and changes} {\scriptsize \begin{verbatim} <>= cat(paste(readLines(system.file('NEWS', package='doRNG')), collapse="\n")) @ \end{verbatim} } \section*{Cleanup} <>= stopCluster(cl) @ \section*{Session information} \addcontentsline{toc}{section}{Session information} <>= sessionInfo() @ \printbibliography[heading=bibintoc] \end{document} doRNG/inst/doc/doRNG.R0000644000175100001440000002436314360272307014073 0ustar hornikusers## ----pkgmaker_preamble, echo=FALSE, results='asis'---------------------------- pkgmaker::latex_preamble() ## ----bibliofile, echo=FALSE, results='asis'----------------------------------- pkgmaker::latex_bibliography('doRNG') ## ----init, include = FALSE-------------------------------------------------------------- options(width=90) library(pkgmaker) library(knitr) opts_chunk$set(size = "footnotesize") knit_hooks$set(try = pkgmaker::hook_try) ## ----foreach---------------------------------------------------------------------------- # load and register parallel backend for multicore computations library(doParallel) cl <- makeCluster(2) registerDoParallel(cl) # perform 5 tasks in parallel x <- foreach(i=1:5) %dopar% { i + runif(1) } unlist(x) ## ----dopar, tidy=FALSE------------------------------------------------------------------ # with standard %dopar%: foreach loops are not reproducible set.seed(123) res <- foreach(i=1:5) %dopar% { runif(3) } set.seed(123) res2 <- foreach(i=1:5) %dopar% { runif(3) } identical(res, res2) ## ----dorng, tidy=FALSE------------------------------------------------------------------ # load the doRNG package library(doRNG) # using %dorng%: loops _are_ reproducible set.seed(123) res <- foreach(i=1:5) %dorng% { runif(3) } set.seed(123) res2 <- foreach(i=1:5) %dorng% { runif(3) } identical(res, res2) ## ----attr------------------------------------------------------------------------------- attr(res, 'rng') ## ----nextRNGstream---------------------------------------------------------------------- nextRNGStream(c(407L, 1:6)) ## ----options_single--------------------------------------------------------------------- # use a single numeric as a seed s <- foreach(i=1:5, .options.RNG=123) %dorng% { runif(3) } s2 <- foreach(i=1:5, .options.RNG=123) %dorng% { runif(3) } identical(s, s2) ## ----options_single_normalkind---------------------------------------------------------- ## Pass the Normal RNG kind to use within the loop # results are identical if not using the Normal kind in the loop optsN <- list(123, normal.kind="Ahrens") resN.U <- foreach(i=1:5, .options.RNG=optsN) %dorng% { runif(3) } identical(resN.U[1:5], res[1:5]) # Results are different if the Normal kind is used and is not the same resN <- foreach(i=1:5, .options.RNG=123) %dorng% { rnorm(3) } resN1 <- foreach(i=1:5, .options.RNG=optsN) %dorng% { rnorm(3) } resN2 <- foreach(i=1:5, .options.RNG=optsN) %dorng% { rnorm(3) } identical(resN[1:5], resN1[1:5]) identical(resN1[1:5], resN2[1:5]) ## ----options_6length-------------------------------------------------------------------- # use a 6-length numeric s <- foreach(i=1:5, .options.RNG=1:6) %dorng% { runif(3) } attr(s, 'rng')[1:3] ## ----options_7length-------------------------------------------------------------------- # use a 7-length numeric, used as first value for .Random.seed seed <- attr(res, 'rng')[[2]] s <- foreach(i=1:5, .options.RNG=seed) %dorng% { runif(3) } identical(s[1:4], res[2:5]) ## ----options_list----------------------------------------------------------------------- # reproduce previous %dorng% loop s <- foreach(i=1:5, .options.RNG=res) %dorng% { runif(3) } identical(s, res) ## use completely custom sequence of seeds (e.g. using RNG "Marsaglia-Multicarry") # as a matrix seedM <- rbind(rep(401, 5), mapply(rep, 1:5, 2)) seedM sM <- foreach(i=1:5, .options.RNG=seedM) %dorng% { runif(3) } # same seeds passed as a list seedL <- lapply(seq(ncol(seedM)), function(i) seedM[,i]) sL <- foreach(i=1:5, .options.RNG=seedL) %dorng% { runif(3) } identical(sL, sM) ## ----set_seed_diff---------------------------------------------------------------------- # default RNG kind RNGkind('default') def <- foreach(i=1:5, .options.RNG=123) %dorng% { runif(3) } # Marsaglia-Multicarry RNGkind('Marsaglia') mars <- foreach(i=1:5, .options.RNG=123) %dorng% { runif(3) } identical(def, mars) # revert to default RNG kind RNGkind('default') ## ----schedule, tidy=FALSE--------------------------------------------------------------- # define a stochastic task to perform task <- function() c(pid=Sys.getpid(), val=runif(1)) # using the previously registered cluster with 2 workers set.seed(123) res_2workers <- foreach(i=1:5, .combine=rbind) %dorng% { task() } # stop cluster stopCluster(cl) # Sequential computation registerDoSEQ() set.seed(123) res_seq <- foreach(i=1:5, .combine=rbind) %dorng% { task() } # # Using 3 workers # NB: if re-running this vignette you should edit to force using 3 here cl <- makeCluster( if(isManualVignette()) 3 else 2) length(cl) # register new cluster registerDoParallel(cl) set.seed(123) res_3workers <- foreach(i=1:5, .combine=rbind) %dorng% { task() } # task schedule is different pid <- rbind(res1=res_seq[,1], res_2workers[,1], res2=res_3workers[,1]) storage.mode(pid) <- 'integer' pid # results are identical identical(res_seq[,2], res_2workers[,2]) && identical(res_2workers[,2], res_3workers[,2]) ## ----registerDoRNG---------------------------------------------------------------------- set.seed(123) res <- foreach(i=1:5) %dorng% { runif(3) } registerDoRNG(123) res_dopar <- foreach(i=1:5) %dopar% { runif(3) } identical(res_dopar, res) attr(res_dopar, 'rng') ## ----multiple, tidy=FALSE--------------------------------------------------------------- set.seed(456) s1 <- foreach(i=1:5) %dorng% { runif(3) } s2 <- foreach(i=1:5) %dorng% { runif(3) } # the two loops do not use the same streams: different results identical(s1, s2) # but the sequence of loops is reproducible as a whole set.seed(456) r1 <- foreach(i=1:5) %dorng% { runif(3) } r2 <- foreach(i=1:5) %dorng% { runif(3) } identical(r1, s1) && identical(r2, s2) # one can equivalently register the doRNG backend and use %dopar% registerDoRNG(456) r1 <- foreach(i=1:5) %dopar% { runif(3) } r2 <- foreach(i=1:5) %dopar% { runif(3) } identical(r1, s1) && identical(r2, s2) ## ----nested_error, error = TRUE, try = TRUE--------------------------------------------- # nested loop try( foreach(i=1:10) %:% foreach(j=1:i) %dorng% { rnorm(1) } ) # conditional loop try( foreach(i=1:10) %:% when(i %% 2 == 0) %dorng% { rnorm(1) } ) ## ----nested----------------------------------------------------------------------------- # doRNG must not be registered registerDoParallel(cl) # generate sequence of seeds of length the number of computations n <- 10; p <- 5 rng <- RNGseq( n * p, 1234) # run standard nested foreach loop res <- foreach(i=1:n) %:% foreach(j=1:p, r=rng[(i-1)*p + 1:p]) %dopar% { # set RNG seed rngtools::setRNG(r) # do your own computation ... c(i, j, rnorm(1)) } # Compare against the equivalent sequential computations k <- 1 res2 <- foreach(i=1:n) %:% foreach(j=1:p) %do%{ # set seed rngtools::setRNG(rng[[k]]) k <- k + 1 # do your own computation ... c(i, j, rnorm(1)) } stopifnot( identical(res, res2) ) ## ----nested_unequal--------------------------------------------------------------------- # generate sequence of seeds of length the number of computations n <- 10 rng <- RNGseq( n * (n+1) / 2, 1234) # run standard nested foreach loop res <- foreach(i=1:n) %:% foreach(j=1:i, r=rng[(i-1)*i/2 + 1:i]) %dopar%{ # set RNG seed rngtools::setRNG(r) # do your own computation ... c(i, j, rnorm(1)) } # Compare against the equivalent sequential computations k <- 1 res2 <- foreach(i=1:n) %:% foreach(j=1:i) %do%{ # set seed rngtools::setRNG(rng[[k]]) k <- k + 1 # do your own computation ... c(i, j, rnorm(1)) } stopifnot( identical(res, res2) ) ## ----conditional------------------------------------------------------------------------ # un-conditional single loop resAll <- foreach(i=1:n, .options.RNG=1234) %dorng%{ # do your own computation ... c(i, rnorm(1)) } # generate sequence of RNG rng <- RNGseq(n, 1234) # conditional loop: even iterations resEven <- foreach(i=1:n, r=rng) %:% when(i %% 2 == 0) %dopar%{ # set RNG seed rngtools::setRNG(r) # do your own computation ... c(i, rnorm(1)) } # conditional loop: odd iterations resOdd <- foreach(i=1:n, r=rng) %:% when(i %% 2 == 1) %dopar%{ # set RNG seed rngtools::setRNG(r) # do your own computation ... c(i, rnorm(1)) } # conditional loop: only first 2 and last 2 resFL <- foreach(i=1:n, r=rng) %:% when(i %in% c(1,2,n-1,n)) %dopar%{ # set RNG seed rngtools::setRNG(r) # do your own computation ... c(i, rnorm(1)) } # compare results stopifnot( identical(resAll[seq(2,n,by=2)], resEven) ) stopifnot( identical(resAll[seq(1,n,by=2)], resOdd) ) stopifnot( identical(resAll[c(1,2,n-1,n)], resFL) ) ## ----nested_conditional----------------------------------------------------------------- # generate sequence of seeds of length the number of computations n <- 10 rng <- RNGseq( n * (n+1) / 2, 1234) # run standard nested foreach loop res <- foreach(i=1:n) %:% when(i %% 2 == 0) %:% foreach(j=1:i, r=rng[(i-1)*i/2 + 1:i]) %dopar%{ # set RNG seed rngtools::setRNG(r) # do your own computation ... c(i, j, rnorm(1)) } # Compare against the equivalent sequential computations k <- 1 resAll <- foreach(i=1:n) %:% foreach(j=1:i) %do%{ # set seed rngtools::setRNG(rng[[k]]) k <- k + 1 # do your own computation ... c(i, j, rnorm(1)) } stopifnot( identical(resAll[seq(2,n,by=2)], res) ) ## ----perf, cache=TRUE------------------------------------------------------------------- # load rbenchmark library(rbenchmark) # comparison is done on sequential computations registerDoSEQ() rPar <- function(n, s=0){ foreach(i=1:n) %dopar% { Sys.sleep(s) } } rRNG <- function(n, s=0){ foreach(i=1:n) %dorng% { Sys.sleep(s) } } # run benchmark cmp <- benchmark(rPar(10), rRNG(10) , rPar(25), rRNG(25) , rPar(50), rRNG(50) , rPar(50, .01), rRNG(50, .01) , rPar(10, .05), rRNG(10, .05) , replications=5) # order by increasing elapsed time cmp[order(cmp$elapsed), ] ## ----doRNGversion----------------------------------------------------------------------- doRNGversion('1.3') ## ----doRNGversion_revert---------------------------------------------------------------- doRNGversion(NULL) ## ----news, echo=FALSE, results='asis'--------------------------------------------------- cat(paste(readLines(system.file('NEWS', package='doRNG')), collapse="\n")) ## ----stopCluster------------------------------------------------------------------------ stopCluster(cl) ## ----session_info, echo=FALSE, comment=NA----------------------------------------------- sessionInfo() doRNG/build/0000755000175100001440000000000014360272312012340 5ustar hornikusersdoRNG/build/vignette.rds0000644000175100001440000000036714360272312014705 0ustar hornikusersmQM@ Mt[Kkqpؘ%p44DT[4) Z j']hJfFԸBT߻:5vX";w'$.Wc宆OcSua+wBa.l1;ofdoRNG/man/0000755000175100001440000000000014360256113012015 5ustar hornikusersdoRNG/man/registerDoRNG.Rd0000644000175100001440000000507414360233410014763 0ustar hornikusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/doRNG.R \name{registerDoRNG} \alias{registerDoRNG} \title{Registering doRNG for Persistent Reproducible Parallel Foreach Loops} \usage{ registerDoRNG(seed = NULL, once = TRUE) } \arguments{ \item{seed}{a numerical seed to use (as a single or 6-length numerical value)} \item{once}{a logical to indicate if the RNG sequence should be seeded at the beginning of each loop or only at the first loop.} } \value{ The value returned by \link[foreach:setDoPar]{foreach::setDoPar} } \description{ \code{registerDoRNG} registers the doRNG foreach backend. Subsequent \verb{\%dopar\%} loops are then performed using the previously registered foreach backend, but are internally performed as \link{\%dorng\%} loops, making them fully reproducible. } \details{ Briefly, the RNG is set, before each iteration, with seeds for L'Ecuyer's CMRG that overall generate a reproducible sequence of statistically independent random streams. Note that (re-)registering a foreach backend other than doRNG, after a call to \code{registerDoRNG} disables doRNG -- which then needs to be registered. } \examples{ library(doParallel) cl <- makeCluster(2) registerDoParallel(cl) # One can make reproducible loops using the \%dorng\% operator r1 <- foreach(i=1:4, .options.RNG=1234) \%dorng\% { runif(1) } # or convert \%dopar\% loops using registerDoRNG registerDoRNG(1234) r2 <- foreach(i=1:4) \%dopar\% { runif(1) } identical(r1, r2) stopCluster(cl) # Registering another foreach backend disables doRNG cl <- makeCluster(2) registerDoParallel(cl) set.seed(1234) s1 <- foreach(i=1:4) \%dopar\% { runif(1) } set.seed(1234) s2 <- foreach(i=1:4) \%dopar\% { runif(1) } identical(s1, s2) \dontshow{ stopifnot(!identical(s1, s2)) } # doRNG is re-nabled by re-registering it registerDoRNG() set.seed(1234) r3 <- foreach(i=1:4) \%dopar\% { runif(1) } identical(r2, r3) # NB: the results are identical independently of the task scheduling # (r2 used 2 nodes, while r3 used 3 nodes) # argument `once=FALSE` reseeds doRNG's seed at the beginning of each loop registerDoRNG(1234, once=FALSE) r1 <- foreach(i=1:4) \%dopar\% { runif(1) } r2 <- foreach(i=1:4) \%dopar\% { runif(1) } identical(r1, r2) # Once doRNG is registered the seed can also be passed as an option to \%dopar\% r1.2 <- foreach(i=1:4, .options.RNG=456) \%dopar\% { runif(1) } r2.2 <- foreach(i=1:4, .options.RNG=456) \%dopar\% { runif(1) } identical(r1.2, r2.2) && !identical(r1.2, r1) \dontshow{ stopifnot(identical(r1.2, r2.2) && !identical(r1.2, r1)) } stopCluster(cl) } \seealso{ \link{\%dorng\%} } doRNG/man/doRNG-package.Rd0000644000175100001440000000514414360256113014652 0ustar hornikusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/doRNG-package.R \docType{package} \encoding{UTF-8} \name{doRNG-package} \alias{doRNG-package} \title{Generic Reproducible Parallel Backend for foreach Loops} \description{ The \emph{doRNG} package provides functions to perform reproducible parallel foreach loops, using independent random streams as generated by L'Ecuyer's combined multiple-recursive generator (L'Ecuyer (1999)). It enables to easily convert standard \%dopar\% loops into fully reproducible loops, independently of the number of workers, the task scheduling strategy, or the chosen parallel environment and associated foreach backend. It has been tested with the following foreach backend: doMC, doSNOW, doMPI. } \examples{ # register parallel backend library(doParallel) cl <- makeCluster(2) registerDoParallel(cl) ## standard \%dopar\% loop are not reproducible set.seed(123) r1 <- foreach(i=1:4) \%dopar\%{ runif(1) } set.seed(123) r2 <- foreach(i=1:4) \%dopar\%{ runif(1) } identical(r1, r2) \dontshow{ stopifnot(!identical(r1, r2)) } ## \%dorng\% loops _are_ reproducible set.seed(123) r1 <- foreach(i=1:4) \%dorng\%{ runif(1) } set.seed(123) r2 <- foreach(i=1:4) \%dorng\%{ runif(1) } identical(r1, r2) \dontshow{ stopifnot(identical(r1, r2)) } # alternative way of seeding a1 <- foreach(i=1:4, .options.RNG=123) \%dorng\%{ runif(1) } a2 <- foreach(i=1:4, .options.RNG=123) \%dorng\%{ runif(1) } identical(a1, a2) && identical(a1, r1) \dontshow{ stopifnot(identical(a1, a2) && identical(a1, r1)) } ## sequences of \%dorng\% loops _are_ reproducible set.seed(123) s1 <- foreach(i=1:4) \%dorng\%{ runif(1) } s2 <- foreach(i=1:4) \%dorng\%{ runif(1) } identical(s1, r1) && !identical(s1, s2) \dontshow{ stopifnot(identical(s1, r1) && !identical(s1, s2)) } set.seed(123) s1.2 <- foreach(i=1:4) \%dorng\%{ runif(1) } s2.2 <- foreach(i=1:4) \%dorng\%{ runif(1) } identical(s1, s1.2) && identical(s2, s2.2) \dontshow{ stopifnot(identical(s1, s1.2) && identical(s2, s2.2)) } ## Non-invasive way of converting \%dopar\% loops into reproducible loops registerDoRNG(123) s3 <- foreach(i=1:4) \%dopar\%{ runif(1) } s4 <- foreach(i=1:4) \%dopar\%{ runif(1) } identical(s3, s1) && identical(s4, s2) \dontshow{ stopifnot(identical(s3, s1) && identical(s4, s2)) } stopCluster(cl) } \references{ L'Ecuyer P (1999). “Good Parameters and Implementations for Combined Multiple Recursive Random Number Generators.” _Operations Research_, *47*(1), 159-164. ISSN 0030-364X, doi:10.1287/opre.47.1.159 . } \seealso{ \code{\link{doRNG}}, \code{\link{RNGseq}} } \keyword{package} doRNG/man/grapes-dorng-grapes.Rd0000644000175100001440000000405514360233466016165 0ustar hornikusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/doRNG.R \name{\%dorng\%} \alias{\%dorng\%} \title{Reproducible Parallel Foreach Backend} \usage{ obj \%dorng\% ex } \arguments{ \item{obj}{a foreach object as returned by a call to \code{\link{foreach}}.} \item{ex}{the \code{R} expression to evaluate.} } \value{ \verb{\%dorng\%} returns the result of the foreach loop. See \link[foreach:foreach]{foreach::\%dopar\%}. The whole sequence of RNG seeds is stored in the result object as an attribute. Use \code{attr(res, 'rng')} to retrieve it. } \description{ \verb{\%dorng\%} is a foreach operator that provides an alternative operator \verb{\%dopar\%}, which enable reproducible foreach loops to be performed. } \section{Global options}{ These options are for advanced users that develop `foreach backends: \itemize{ \item 'doRNG.rng_change_warning_skip': if set to a single logical \code{FALSE/TRUE}, it indicates whether a warning should be thrown if the RNG seed is changed by the registered parallel backend (default=FALSE). Set it to \code{TRUE} if you know that running your backend will change the RNG state and want to disable the warning. This option can also be set to a character vector that specifies the name(s) of the backend(s) for which the warning should be skipped. } } \examples{ library(doParallel) cl <- makeCluster(2) registerDoParallel(cl) # standard \%dopar\% loops are _not_ reproducible set.seed(1234) s1 <- foreach(i=1:4) \%dopar\% { runif(1) } set.seed(1234) s2 <- foreach(i=1:4) \%dopar\% { runif(1) } identical(s1, s2) # single \%dorng\% loops are reproducible r1 <- foreach(i=1:4, .options.RNG=1234) \%dorng\% { runif(1) } r2 <- foreach(i=1:4, .options.RNG=1234) \%dorng\% { runif(1) } identical(r1, r2) # the sequence os RNG seed is stored as an attribute attr(r1, 'rng') # stop cluster stopCluster(cl) # More examples can be found in demo `doRNG` \dontrun{ demo('doRNG') } } \seealso{ \code{\link{foreach}}, \code{\link[doParallel]{doParallel}} , \code{\link[doParallel]{registerDoParallel}}, \code{\link[doMPI]{doMPI}} } doRNG/man/doRNGversion.Rd0000644000175100001440000000446614360233070014672 0ustar hornikusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/doRNG.R \name{doRNGversion} \alias{doRNGversion} \title{Back Compatibility Option for doRNG} \usage{ doRNGversion(x) } \arguments{ \item{x}{version number to switch to, or missing to get the currently active version number, or \code{NULL} to reset to the default behaviour, i.e. of the latest version.} } \value{ a character string If \code{x} is missing this function returns the version number from the current behaviour. If \code{x} is specified, the function returns the old value of the version number (invisible). } \description{ Sets the behaviour of \%dorng\% foreach loops from a given version number. } \section{Behaviour changes in versions}{ \describe{ \item{1.4}{ The behaviour of \code{doRNGseed}, and therefore of \verb{\%dorng\%} loops, changed in the case where the current RNG was L'Ecuyer-CMRG. Using \code{set.seed} before a non-seeded loop used not to be identical to seeding via \code{.options.RNG}. Another bug was that non-seeded loops would share most of their RNG seed! } \item{1.7.4}{Prior to this version, in the case where the RNG had not been called yet, the first seeded \verb{\%dorng\%} loops would not give the identical results as subsequent loops despite using the same seed (see \url{https://github.com/renozao/doRNG/issues/12}). This has been fixed in version 1.7.4, where the RNG is called once (\code{sample(NA)}), whenever the .Random.seed is not found in global environment. } } } \examples{ \dontshow{ registerDoSEQ() } ## Seeding when current RNG is L'Ecuyer-CMRG RNGkind("L'Ecuyer") doRNGversion("1.4") # in version >= 1.4 seeding behaviour changed to fix a bug set.seed(123) res <- foreach(i=1:3) \%dorng\% runif(1) res2 <- foreach(i=1:3) \%dorng\% runif(1) stopifnot( !identical(attr(res, 'rng')[2:3], attr(res2, 'rng')[1:2]) ) res3 <- foreach(i=1:3, .options.RNG=123) \%dorng\% runif(1) stopifnot( identical(res, res3) ) # buggy behaviour in version < 1.4 doRNGversion("1.3") res <- foreach(i=1:3) \%dorng\% runif(1) res2 <- foreach(i=1:3) \%dorng\% runif(1) stopifnot( identical(attr(res, 'rng')[2:3], attr(res2, 'rng')[1:2]) ) res3 <- foreach(i=1:3, .options.RNG=123) \%dorng\% runif(1) stopifnot( !identical(res, res3) ) # restore default RNG RNGkind("default") # restore to current doRNG version doRNGversion(NULL) } doRNG/man/infoDoRNG.Rd0000644000175100001440000000176514360232706014104 0ustar hornikusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/doRNG.R \name{infoDoRNG} \alias{infoDoRNG} \alias{doRNG} \title{Getting Information About doRNG Foreach Backend} \usage{ infoDoRNG(data, item) doRNG(obj, ex, envir, data) } \arguments{ \item{data}{configuration data of the doRNG backend} \item{item}{the data item requested, as a character string (e.g. 'name', 'workers', 'version')} \item{obj}{a foreach description of the loop arguments} \item{ex}{the lopp expression} \item{envir}{the loop's evaluation environment} } \value{ \code{infoDoRNG} returns the requested info (usually as a character string or a numeric value). } \description{ \code{infoDoRNG} returns information about the doRNG backend, e.g., version, number of workers. It is not meant to be called by the user. } \author{ Renaud Gaujoux } \keyword{internal} \section{Functions}{ \itemize{ \item \code{doRNG}: implements the generic reproducible foreach backend. It should not be called directly by the user. }} doRNG/DESCRIPTION0000644000175100001440000000241114741236736012762 0ustar hornikusersPackage: doRNG Type: Package Title: Generic Reproducible Parallel Backend for 'foreach' Loops Version: 1.8.6.1 Encoding: UTF-8 Authors@R: person("Renaud", "Gaujoux", email = "renozao@protonmail.com", role = c("aut", "cre")) Description: Provides functions to perform reproducible parallel foreach loops, using independent random streams as generated by L'Ecuyer's combined multiple-recursive generator [L'Ecuyer (1999), ]. It enables to easily convert standard '%dopar%' loops into fully reproducible loops, independently of the number of workers, the task scheduling strategy, or the chosen parallel environment and associated foreach backend. License: GPL (>= 2) LazyLoad: yes URL: https://renozao.github.io/doRNG/ BugReports: https://github.com/renozao/doRNG/issues VignetteBuilder: knitr Depends: R (>= 3.0.0), foreach, rngtools (>= 1.5) Imports: stats, utils, iterators Suggests: doParallel, doMPI, doRedis, rbenchmark, devtools, knitr, rbibutils (>= 1.3), testthat, pkgmaker (>= 0.32.7), covr RoxygenNote: 7.2.3 NeedsCompilation: no Packaged: 2025-01-13 12:20:36 UTC; hornik Author: Renaud Gaujoux [aut, cre] Maintainer: Renaud Gaujoux Repository: CRAN Date/Publication: 2025-01-13 16:22:54 UTC