R/RandomNumberGenerator.R

Defines functions setup_RNG set_RNG_stream set_full_RNG generate_RNG_streams

Documented in generate_RNG_streams set_full_RNG set_RNG_stream setup_RNG

#' Implementation \var{RngStreams} for N tasks according to Pierre L'Ecuyer
#'
#' The function \code{\link[parallel]{clusterSetRNGStream}} creates a stream for
#' each worker/worker, and thus replicability can only be realized if each task
#' is assigned to the same worker on repeated runs. This is usually not
#' guaranteed with load-balancing parallel computations or when a long
#' computation is being re-started from previous partially completed
#' tasks/results. This implementation generates a stream for each unique tasks
#' (instead for each worker) and thus avoids such problems.
#'
#' The current RNG kind, if required, must be captured before this function is
#' called because the function sets the kind to \var{\dQuote{L'Ecuyer-CMRG}}
#' (see examples).
#'
#' @param N An integer. The number of streams to generate.
#' @param seed An integer or \code{NULL}. The seed used by
#'   \code{\link{set.seed}} to set the (global/master) random generator, i.e.,
#'   before generating the seeds of the streams.
#' @param reproducible A logical value. If \code{TRUE}, then \code{N} are
#'   prepared. If \code{FALSE}, then instead \code{NA}s are returned.
#' @return A list of length \code{N} containing the seed for each stream.
#'
#' @seealso \code{\link[parallel]{clusterSetRNGStream}}
#'
#' @examples
#' RNGkind_prev <- RNGkind()
#' seeds <- generate_RNG_streams(10, seed = 123)
#' # do work with random numbers
#' RNGkind(kind = RNGkind_prev[1], normal.kind = RNGkind_prev[2])
#'
#' @export
generate_RNG_streams <- function(N, seed = NULL, reproducible = TRUE) {

  if (reproducible) {
    oldseed <- get0(".Random.seed", envir = as.environment(1L),
      inherits = FALSE)

    on.exit({
      if (!is.null(oldseed)) {
          assign(".Random.seed", oldseed, pos = 1L)
      } else {
        rm(.Random.seed, pos = 1L)
      }
    }, add = TRUE)

    RNGkind("L'Ecuyer-CMRG")

    if (!is.null(seed)) set.seed(seed)

    seeds <- vector("list", N)
    seeds[[1L]] <- .Random.seed

    for (i in seq_len(N - 1L)) {
      seeds[[i + 1L]] <- parallel::nextRNGStream(seeds[[i]])
    }

    seeds

  } else {
    as.list(rep(NA, N))
  }
}


#' Specify all aspects of the R random number generator
#'
#' @inheritParams base::Random
#'
#' @seealso \code{\link{set.seed}}, \code{\link{RNGkind}}
#' @export
set_full_RNG <- function(seed = NULL, kind = "default",
  normal.kind = "default") {

  RNGkind(kind = kind, normal.kind = normal.kind)
  set.seed(seed = seed)
}


#' Set the seed of a random number generator (stream)
#'
#' This function is to be called by a (parallel) worker using as argument a
#' pre-prepared seed from the function \code{\link{generate_RNG_streams}}. Note:
#' it will also set \var{RNGkind} accordingly to the first element of seed.
#'
#' @param seed A vector appropriate for \code{\link{.Random.seed}} of the
#'   current var{RNG}; a single integer or \code{NULL} that will be passed to
#'   \code{set.seed}; or \code{NA} which will not affect the random number
#'   generator.
#'
#' @seealso \code{\link{set.seed}}, \code{\link{RNGkind}}
#' @export
set_RNG_stream <- function(seed = NA) {
  if (!anyNA(seed)) {
    if (length(seed) > 1) {
      assign(".Random.seed", seed, pos = 1L)

    } else {
      print(paste("'seed' was not appropriate to init '.Random.seed':",
        paste(seed[seq_len(3)], collapse = "/"), "/...; instead the function",
        "'set.seed' will be used"))
      set_full_RNG(seed, kind = NULL, normal.kind = NULL)
    }
  }

  invisible(NULL)
}

#' Organizing previous state and streams of random number generator
#'
#' @section Usage: \var{RNG} - parallelized function calls by \pkg{rSFSW2}
#'  \itemize{
#'    \item \code{try_MonthlyScenarioWeather} wraps
#'      \code{calc_MonthlyScenarioWeather} which
#'      calls \code{set_RNG_stream} to prepare \var{RNG} for functions
#'      \itemize{
#'        \item \code{fix_PPTdata_length}
#'        \item \code{calc_Days_withLoweredPPT}
#'        \item \code{controlExtremePPTevents}}
#'    \item \code{do_OneSite} calls \code{set_RNG_stream} to prepare \var{RNG}
#'      for functions \itemize{
#'        \item \code{calc_TimeToGerminate}}}
#'
#' @section Note: Parallelized function calls without using \var{RNG}
#'  \itemize{
#'    \item \code{ISRICWISE5minV1b_extract_SUIDs}
#'    \item \code{ISRICWISE5minV1b_try_weightedMeanForSimulationCell}
#'    \item \code{collect_EnsembleFromScenarios}
#'    \item \code{missing_Pids_outputDB}
#'    \item \code{gribDailyWeatherData}
#'    \item \code{writeDailyWeatherData}
#'    \item \code{gribMonthlyClimate}
#'    \item \code{writeMonthlyClimate}
#'    }
#'
#' @param streams_N An integer value representing the number of tasks for which
#'  \var{RngStreams} according to Pierre L'Ecuyer should be generated.
#' @param global_seed A vector appropriate for \code{\link{.Random.seed}} of
#'  the current \var{RNG}; a single integer or \code{NULL} that will be passed
#'  to \code{\link{set.seed}}.
#' @param reproducible A logical value. If \code{TRUE}, then \code{streams_N}
#'  are prepared. If \code{FALSE}, then instead \code{NA}s are returned.
#'
#' @return A list with four elements: \itemize{
#'    \item \code{seed_prev} captures the previous state of the \var{RNG} seed.
#'    \item \code{RNGkind_prev} captures the previous kind of the \var{RNG}.
#'    \item \code{global_seed} is the seed used to set the \var{RNG} for stream
#'      generation.
#'    \item \code{seeds_runN} is a list with a stream of \code{streams_N} seeds.
#'    \item \code{seeds_DS} is an empty list -- a placeholder for a list with a
#'      stream of seeds for climate scenario downscaling.
#'    }
#'
#' @seealso \code{\link{set.seed}}
#' @export
setup_RNG <- function(streams_N, global_seed = NULL, reproducible = TRUE) {
  rng <- list()

  rng[["seed_prev"]] <- get0(".Random.seed", envir = as.environment(1L),
    inherits = FALSE)

  set_full_RNG(global_seed)
  rng[["RNGkind_prev"]] <- RNGkind()
  rng[["global_seed"]] <- global_seed

  # Seeds for climate change downscaling: only generate if required
  rng[["seeds_DS"]] <- list()

  # Seeds for simulation runs 'do_OneSite'
  rng[["seeds_runN"]] <- generate_RNG_streams(N = streams_N, seed = global_seed,
    reproducible = reproducible)

  rng
}
Burke-Lauenroth-Lab/rSFSW2 documentation built on Aug. 14, 2020, 5:20 p.m.