6  Providing Module Inputs

Author

Eliot McIntire

Published

November 20, 2024

See Barebones R script for the code shown in this chapter

We have seen functions called .inputObjects and reproducible::prepInputs that help deal with module inputs (Inputs Tip 6.1). We will show these used within a SpaDES module.

Tip 6.1

The inputs of a SpaDES module can be any R object, and should be specified in the inputObjects section of the metadata if it is needed for the module to run. All inputs should be used in the sim using e.g., sim$ somewhere in the module.

6.1 How to supply Inputs

There are several ways to get objects into the simList:

  • User passes during simInit:

  • using the objects argument (if it is an object);

  • using the inputs argument (if it is from disk);

  • using the params argument;

  • Developer creates defaults and specifies them in defineParameter() and .inputObjects;

  • A different module creates them prior to the running of this module.

Ultimately, it is the last of these that is the power of SpaDES. i.e., modularity.

6.1.1 During simInit

A user can directly pass objects into the simInit and these will be put into the simList

Code
a <- 1
sim <- SpaDES.core::simInit(objects = list(a = a))
sim$a
[1] 1

or do pass them via an .rds file

Code
b <- 1
tf <- tempfile(fileext = ".rds")
saveRDS(b, file = tf)
sim <- SpaDES.core::simInit(inputs = data.frame(object = "b", file = tf))
sim$b
[1] 1

Because parameters are usually module-specific, and need to be passed to with reference to a module. “Global” parameters are the exception to this rule:

Code
c <- 1
sim <- SpaDES.core::simInit(params = list(.globals = list(c = c)))
SpaDES.core::params(sim)$.globals$c
[1] 1

To pass parameters to a module, we go back to out simple module and make a minor change:

Code
modulePath <- "~/SpaDES_book/NewModuleIntro/NewModule"
SpaDES.core::newModule(name = "My_linear_model", path = modulePath, open = FALSE,
                       events = list(
                         init = {
                           x <- rnorm(P(sim)$length)
                           y <- x + rnorm(P(sim)$length)
                           # fit a linear model
                           model <- lm(y ~ x)
                         }
                       ))

Now we open My_linear_model.R and add the following

Code
defineParameter("length", "integer", 10L, 5L, 100L,
                "Number of values to generate")

… to the module metadata section for parameters. Remember to save the file after you’re done.

Code
defineModule(
  <...>
    parameters = bindrows(
      # here
      <...>
    )
  <...>
)

Now run the the module:

Code
out2 <- SpaDES.core::simInitAndSpades(modules = "My_linear_model",
                                      paths = list(modulePath = modulePath))
params(out2)   # can you find the length parameter?

6.1.2 .inputObjects

For every Input that is specified in the metadata, the module developer has the option of specifying a default. In R, functions can be given defaults when they are defined like this:

Code
rnormMean5 <- function(n = 1) { # the n = 1 is the default value
  rnorm(n = n, mean = 5)
}
rnormMean5() # can be run without any arguments
[1] 6.893804

This works for only the simplest cases. In the case of a module, objects can be arbitrarily complicated and so this approach will not be suitable.

Instead, there is a function that is called every time a module is initialized (i.e. by simInit()) where a developer can put anything they want to specify as defaults. It is generally most useful to use if (!SpaDES.core::suppliedElsewhere("obj", sim)) i.e., if the object isn’t in sim, then give it a value.

Code
.inputObjects <- function(sim) {
  
  if (!suppliedElsewhere("x", sim)) {
    sim$x <- rnorm(100)
  }
  
  return(invisible(sim))
}

6.1.2.1 Advanced .inputObjects example

In the Biomass_core module (this is an implementation of the LANDIS-II Biomass Succession Extension v3.2), there are many Inputs as defined in the metadata. The module needs each one of these to work.

The developers have created a .inputObjects function that will fill in these Inputs with defaults if the user hasn’t supplied them (using !SpaDES.core::suppliedElsewhere()).

We will look at a few examples in this module: Biomass_core::.inputObjects.

  1. some GIS tests that evaluate whether 2 objects are in the same CRS (i.e., they need to be the same projection, etc., for this module to work)

  2. if an object is missing, it may make sense to fill in the details with parameters from the Internet.

  3. an object called studyArea. The developer tests whether it exists and creates an error if it does not exist.

6.2 Questions

  1. In .inputObjects, why don’t we just write if (!is.null(sim$y))? See ?SpaDES.core::suppliedElsewhere
  • It turns out that other modules may create this object “after” this module is run, and this test will fail.

6.3 Try on your own

  • Using the My_linear_model shown above, try running simulations where you pass different values to the length parameter

  • Tip: have a look at the params argument of SpaDES.core::simInitAndSpades()

6.4 See also

?SpaDES.core::expectsInputs

?SpaDES.core::suppliedElsewhere

?SpaDES.core::defineParameter

?SpaDES.core::simInit

?SpaDES.core::simInitAndSpades

6.5 Barebones R script

Code
a <- 1
sim <- SpaDES.core::simInit(objects = list(a = a))
sim$a

b <- 1
tf <- tempfile(fileext = ".rds")
saveRDS(b, file = tf)
sim <- SpaDES.core::simInit(inputs = data.frame(object = "b", file = tf))
sim$b

c <- 1
sim <- SpaDES.core::simInit(params = list(.globals = list(c = c)))
SpaDES.core::params(sim)$.globals$c

modulePath <- "~/SpaDES_book/NewModuleIntro/NewModule"
SpaDES.core::newModule(name = "My_linear_model", path = modulePath, open = FALSE,
                       events = list(
                         init = {
                           x <- rnorm(P(sim)$length)
                           y <- x + rnorm(P(sim)$length)
                           # fit a linear model
                           model <- lm(y ~ x)
                         }
                       ))





out2 <- SpaDES.core::simInitAndSpades(modules = "My_linear_model",
                                      paths = list(modulePath = modulePath))
params(out2)   # can you find the length parameter?

rnormMean5 <- function(n = 1) { # the n = 1 is the default value
  rnorm(n = n, mean = 5)
}
rnormMean5() # can be run without any arguments