Given one or more simList objects, run a series of spades calls in a structured, organized way. Methods are available to deal with outputs, such as as.data.table.simLists() which can pull out simple to complex values from every resulting simList or object saved by outputs in every simList run. This uses future internally, allowing for various backends and parallelism.

experiment2(
  ...,
  replicates = 1,
  clearSimEnv = FALSE,
  createUniquePaths = c("outputPath"),
  useCache = FALSE,
  debug = getOption("spades.debug"),
  drive_auth_account = NULL,
  meanStaggerIntervalInSecs = 1
)

Arguments

...

One or more simList objects. Additional named arguments are passed through to SpaDES.core::spades() (see Controlling events below).

replicates

The number of replicates to run of the same simList. See details and examples. To minimize memory overhead, currently, this must be length 1, i.e., all ... simList objects will receive the same number of replicates.

clearSimEnv

Logical. If TRUE, then the envir(sim) of each simList in the return list is emptied. This is to reduce RAM load of large return object. Default FALSE.

createUniquePaths

A character vector of the paths passed to simInit, indicating which should create a new, unique path, as a sub-path to the original paths. Currently only "outputPath" is honoured. Pass character(0) (or NULL) to disable this nesting entirely, e.g. when the caller (such as experiment()) has already set each simList's outputPath.

useCache

Logical. Passed to spades. This will be passed with the simList name and replicate number, allowing each replicate and each simList to be seen as a non-cached call to spades.

debug

Passed to SpaDES.core::spades().

drive_auth_account

Optional character string. If provided, it will be passed to each worker and run as googledrive::drive_auth(drive_auth_account) to allow a specific user account for googledrive

meanStaggerIntervalInSecs

If used, this will use Sys.sleep(cumsum(c(0, rnorm(nbrOfWorkers() - 1, mean = meanStaggerIntervalInSecs, sd = meanStaggerIntervalInSecs/10)))) and distribute these delays to the workers.

Value

Invisibly returns a simLists object. This class extends the environment class and contains simList objects.

Details

This function was moved here from the now-unmaintained SpaDES.experiment package. See also the file-queue based experiment_family (e.g. experimentFuture()) for a different, script-oriented approach.

This function, because of its class formalism, allows for methods to be used. For example, as.data.table.simLists() allows user to pull out specific objects (in the simList objects or on disk saved in outputPath(sim)).

The outputPath is changed so that every simulation puts outputs in a sub-directory of the original outputPath of each simList (unless createUniquePaths is character(0)/NULL).

Note

A simLists object can be made manually, if, say, many manual spades calls have already been run. See example, via new("simLists")

Controlling events

Any named argument in ... that is not consumed by experiment2 is passed straight to SpaDES.core::spades(). In particular, spades()'s events argument is honoured, so experiment2(sim1, sim2, events = list(...)) restricts the events that run for every simulation. Note this applies the same events specification to all simLists / replicates. For per-scenario control of events, use the file-queue experiment_family with an events column in df (see experiment_family).

Author

Eliot McIntire

Examples

if (FALSE) { # \dontrun{
  if (require("ggplot2", quietly = TRUE) &&
      require("NLMR", quietly = TRUE) &&
      require("RColorBrewer", quietly = TRUE)) {
    library(SpaDES.core)
    library(SpaDES.project)

    tmpdir <- file.path(tempdir(), "examples")
    # Make 3 simLists -- set up scenarios
    endTime <- 2

    # Example of changing parameter values
    # Make 3 simLists with some differences between them
    mySim <- lapply(c(10, 20, 30), function(nFires) {
      simInit(
        times = list(start = 0.0, end = endTime, timeunit = "year"),
        params = list(
          .globals = list(stackName = "landscape", burnStats = "nPixelsBurned"),
          # Turn off interactive plotting
          fireSpread = list(.plotInitialTime = NA, spreadprob = c(0.2), nFires = c(10)),
          caribouMovement = list(.plotInitialTime = NA),
          randomLandscapes = list(.plotInitialTime = NA, .useCache = "init")
        ),
        modules = list("randomLandscapes", "fireSpread", "caribouMovement"),
        paths = list(modulePath = system.file("sampleModules", package = "SpaDES.core"),
                     outputPath = tmpdir),
        # Save final state of landscape and caribou
        outputs = data.frame(
          objectName = c(rep("landscape", endTime), "caribou", "caribou"),
          saveTimes = c(seq_len(endTime), unique(c(ceiling(endTime / 2), endTime))),
          stringsAsFactors = FALSE
        )
      )
    })

    planTypes <- c("sequential") # try others! ?future::plan
    sims <- experiment2(sim1 = mySim[[1]], sim2 = mySim[[2]], sim3 = mySim[[3]],
                        replicates = 3)

    # Try pulling out values from simulation experiments
    # 2 variables
    df1 <- as.data.table(sims, vals = c("nPixelsBurned", NCaribou = quote(length(caribou$x1))))

    # Now use objects that were saved to disk at different times during spades call
    df1 <- as.data.table(sims,
                         vals = c("nPixelsBurned", NCaribou = quote(length(caribou$x1))),
                         objectsFromOutputs = list(nPixelsBurned = NA, NCaribou = "caribou"))


    # now calculate 4 different values, some from data saved at different times
    # Define new function -- this calculates perimeter to area ratio
    fn <- quote({
      landscape$Fires[landscape$Fires[] == 0] <- NA;
      a <- boundaries(landscape$Fires, type = "inner");
      a[landscape$Fires[] > 0 & a[] == 1] <- landscape$Fires[landscape$Fires[] > 0 & a[] == 1];
      peri <- table(a[]);
      area <- table(landscape$Fires[]);
      keep <- match(names(area),names(peri));
      mean(peri[keep]/area)
    })

    df1 <- as.data.table(sims,
                         vals = c("nPixelsBurned",
                                  perimToArea = fn,
                                  meanFireSize = quote(mean(table(landscape$Fires[])[-1])),
                                  caribouPerHaFire = quote({
                                    NROW(caribou) /
                                      mean(table(landscape$Fires[])[-1])
                                  })),
                         objectsFromOutputs = list(NA, c("landscape"), c("landscape"),
                                                   c("landscape", "caribou")),
                         objectsFromSim = "nPixelsBurned")

    if (interactive()) {
      # with an unevaluated string
      library(ggplot2)
      p <- lapply(unique(df1$vals), function(var) {
        ggplot(df1[vals == var,],
               aes(x = saveTime, y = value, group = simList, color = simList)) +
          stat_summary(geom = "point", fun.y = mean) +
          stat_summary(geom = "line", fun.y = mean) +
          stat_summary(geom = "errorbar", fun.data = mean_se, width = 0.2) +
          ylab(var)
      })

      # Arrange all 4 -- could use gridExtra::grid.arrange -- easier
      pushViewport(viewport(layout = grid.layout(2, 2)))
      vplayout <- function(x, y) viewport(layout.pos.row = x, layout.pos.col = y)
      print(p[[1]], vp = vplayout(1, 1))
      print(p[[2]], vp = vplayout(1, 2))
      print(p[[3]], vp = vplayout(2, 1))
      print(p[[4]], vp = vplayout(2, 2))
    }
  }
} # }