model_macro_builder: EXPERIMENTAL: Turn a function into a model macro

View source: R/BUGS_macros.R

model_macro_builderR Documentation

EXPERIMENTAL: Turn a function into a model macro

Description

A model macro expands one line of code in a nimbleModel into one or more new lines. This supports compact programming by defining re-usable modules. model_macro_builder takes as input a function that constructs new lines of model code from the original line of code. It returns a function suitable for internal use by nimbleModel that arranges arguments for input function. Macros are an experimental feature and are available only after setting nimbleOptions(enableModelMacros = TRUE).

Usage

model_macro_builder(fun, use3pieces = TRUE, unpackArgs = TRUE)

Arguments

fun

A function written to construct new lines of model code (see below).

use3pieces

logical indicating whether the arguments from the input line be split into pieces for the LHS (left-hand side), RHS (right-hand side, possibly further split depending on unpackArgs), and stoch (TRUE if the line uses a ~ and FALSE otherwise). (default = TRUE)

unpackArgs

logical indicating whether arguments be passed as a list (FALSE) or as separate arguments (TRUE). (default = TRUE)

Details

The arguments use3pieces and unpackArgs indicate how fun expects to have arguments arranged from an input line of code (processed by nimbleModel).

Consider the defaults use3pieces = TRUE and unpackArgs = TRUE, for a macro called macro1. In this case, the line of model code x ~ macro1(arg1 = z[1:10], arg2 = "hello") will be passed to fun as fun(stoch = TRUE, LHS = x, arg1 = z[1:10], arg2 = "hello").

If use3pieces = TRUE but unpackArgs = FALSE, then the RHS will be passed as is, without unpacking its arguments into separate arguments to fun. In this case, x ~ macro1(arg1 = z[1:10], arg2 = "hello") will be passed to fun as fun(stoch = TRUE, LHS = x, RHS = macro1(arg1 = z[1:10], arg2 = "hello")).

If use3pieces = FALSE and unpackArgs = FALSE, the entire line of code is passed as a single object. In this case, x ~ macro1(arg1 = z[1:10], arg2 = "hello") will be passed to fun as fun(x ~ macro1(arg1 = z[1:10], arg2 = "hello")). It is also possible in this case to pass a macro without using a ~ or <-. For example, the line macro1(arg1 = z[1:10], arg2 = "hello") will be passed to fun as fun(macro1(arg1 = z[1:10], arg2 = "hello")).

If use3pieces = FALSE and unpackArgs = TRUE, it won't make sense to anticipate a declaration using ~ or <-. Instead, arguments from an arbitrary call will be passed as separate arguments. For example, the line macro1(arg1 = z[1:10], arg2 = "hello") will be passed to fun as fun(arg1 = z[1:10], arg2 = "hello").

In addition, the final two arguments of fun must be called modelInfo and .env respectively.

During macro processing, nimbleModel passes a named list to the modelInfo argument of fun containing, among other things, elements called constants and dimensions. Macro developers can modify these two elements (for example, to add a new constant needed for a macro) and these changes will be reflected in the final model object. Note that currently it is not possible for a macro to modify the data. Furthermore, if your macro add a new element to the constants that nimbleModel then moves to the data, this new data will not be retained in the final model object and thus will not be usable.

nimbleModel passes the R environment from which nimbleModel was called to the .env argument.

The fun function must return a named list with two elements: code, the replacement code, and modelInfo, the modelInfo list described above. modelInfo must be in the output even if the macro does not modify it.

It is extremely useful to be familiar with processing R code as an object to write fun correctly. Functions such as substitute and as.name (e.g. as.name('~')), quote, parse and deparse are particularly handy.

Multiple lines of new code should be contained in {} . Extra curly braces are not a problem. See example 2.

Macro expansion is done recursively: One macro can return code that invokes another macro.

Value

A list of class model_macro with one element called process, which contains the macro function suitable for use by nimbleModel.

Examples

nimbleOptions(enableModelMacros = TRUE)
nimbleOptions(enableMacroComments = FALSE)
nimbleOptions(verbose = FALSE)

## Example 1: Say one is tired of writing "for" loops.
## This macro will generate a "for" loop with dnorm declarations
all_dnorm <- model_macro_builder(
    function(stoch, LHS, RHSvar, start, end, sd = 1, modelInfo, .env) {
        newCode <- substitute(
            for(i in START:END) {
                LHS[i] ~ dnorm(RHSvar[i], SD)
            },
            list(START = start,
                 END = end,
                 LHS = LHS,
                 RHSvar = RHSvar,
                 SD = sd))
        list(code = newCode)
    },
    use3pieces = TRUE,
    unpackArgs = TRUE 
)

model1 <- nimbleModel(
    nimbleCode(
    {
        ## Create a "for" loop of dnorm declarations by invoking the macro
        x ~ all_dnorm(mu, start = 1, end = 10)
    }
    ))

## show code from expansion of macro
model1$getCode()
## The result should be:
## {
##     for (i in 1:10) {
##         x[i] ~ dnorm(mu[i], 1)
##     }
## }

## Example 2: Say one is tired of writing priors.
## This macro will generate a set of priors in one statement
flat_normal_priors <- model_macro_builder(
    function(..., modelInfo, .env) {
        allVars <- list(...)
        priorDeclarations <- lapply(allVars,
                                    function(x)
                                        substitute(VAR ~ dnorm(0, sd = 1000),
                                                   list(VAR = x)))
        newCode <- quote({})
        newCode[2:(length(allVars)+1)] <- priorDeclarations
        list(code = newCode)
    },
    use3pieces = FALSE,
    unpackArgs = TRUE
)

model2 <- nimbleModel(
    nimbleCode(
    {
        flat_normal_priors(mu, beta, gamma)
    }
    ))

## show code from expansion of macro
model2$getCode()
## The result should be:
## {
##    mu ~ dnorm(0, sd = 1000)
##    beta ~ dnorm(0, sd = 1000)
##    gamma ~ dnorm(0, sd = 1000)
## }

## Example 3: Macro that modifies constants
new_constant <- model_macro_builder(
   function(stoch, LHS, RHS, modelInfo, .env) {
     # number of elements
     n <- as.numeric(length(modelInfo$constants[[deparse(LHS)]]))
     code <- substitute({
       for (i in 1:N){
         L[i] ~ dnorm(mu[i], 1)
       }
     }, list(L = LHS, N = n))

     # Add a new constant mu
     modelInfo$constants$mu <- rnorm(n, 0, 1)

     list(code = code, modelInfo = modelInfo)
   },
   use3pieces = TRUE,
   unpackArgs = TRUE
)

const <- list(y = rnorm(10))
code <- nimbleCode({
 y ~ new_constant()
})

mod <- nimbleModel(code = code, constants=const)
mod$getCode()
mod$getConstants() # new constant is here

nimble documentation built on June 22, 2024, 9:49 a.m.