forio Toggle navigation

Model Creation in R

When you create a model in R, you can use it with the Epicenter platform by:

If you are new to R, see for example these external resources: CRAN, Advanced R, R language for programmers.

Creating and Uploading your Model Code

Use the Epicenter user interface to upload your model code (.R files) to the Model folder within your project. Make sure to save your .R source files in UTF-8 (e.g. no smart quotes).

The Model folder within your project must contain all of your model's files. Sub-folders are ok.

Understanding the Environment

Epicenter includes an R environment for running your models.

R Version

Epicenter uses R version 3.1.0.

Working with Packages

  • The Epicenter environment automatically includes:
    • R6, version 2.1.3
    • abind, version 1.4-5
    • digest, version 0.6.10
    • jsonlite, version 1.1
    • memoise, version 1.0.0
    • mime, version 0.5
    • rstudioapi, version 0.6
    • whisker, version 0.3-2
    • withr, version 1.0.2

In addition, for all versions of the Epicenter APIs, the Epicenter environment automatically includes the Epicenter package, Epicenter. The Epicenter package ("Epicenter.R") allows you to register functions for automatic execution, save variables, and create custom mappings between complex R variable types in your model and their storage in the Epicenter backend database.

You can reference these packages in your model using the standard library statement.

To use additional R packages in your model:

  • Upload the package source code into your project's Model folder.
  • Then use source or library as needed in particular model files.

These additional packages should be pure R. If you have packages you'd like to use that are compiled libraries, or if you have questions about using additional packages, contact us.

To use the Epicenter R package in your local environment:

  • Download the Epicenter.R package, Epicenter.R, v2.
  • Install it by unzipping into your local R workspace.
  • At the start of your R model, import the installed package: library(Epicenter).

Working with Sub-folders

When you place your model code in the Model folder, your primary file — for example, your main script — should be at the top level. This is the file whose name you pass to Epicenter APIs when creating new runs.

Other files can be in sub-folders or not, based on how you've structured your model.

Using the Epicenter Package

The Epicenter package, Epicenter ("Epicenter.R") allows you to register functions for automatic execution, save variables, and create custom mappings between complex R variable types in your model and their storage in the Epicenter backend database.

The Epicenter ("Epicenter.R") package is automatically available when you are logged in to the Epicenter authoring interface (http://forio.com/epicenter/). If you are developing your model locally before uploading it to Epicenter, you can still use the Epicenter package in your local environment.

To use the Epicenter R package in your local environment:

  • Download the Epicenter.R package, Epicenter.R, v2.
  • Install it by unzipping into your local R workspace.
  • At the start of your R model, import the installed package: library(Epicenter).

Saving Variables

When you work with Epicenter, metadata about every "run" — the collection of end user interactions with the project and its model — is automatically saved to the Epicenter backend database. However, specific variables and their values are not saved by default. (See more background on Run Persistence.)

Unsaved variables are only available in the current session. (A session lasts for a certain amount of time after the end user has stopped interacting with your project. This amount is set in the Model Session Timeout, in your project's Settings.)

Sometimes this is the behavior you want: often you only need your model to perform calculations and analyses in the current session.

For other simulations or models, you want end users to be able to review information from previous runs, or to resume working with a run they were using earlier. In these cases, you need to save model variables to the Epicenter backend database.

To save variables from a run to the Epicenter backend database:

  1. At the start of your R model, import the Epicenter package:

     library(Epicenter)
    
  2. In your R model, when you want to save a variable, add the line

     Epicenter::record("variable_name_as_string", actual_variable)
    

    to your function, for example

     Epicenter::record("my_var_name", my_var_name)
    

    This call queues the variables for persisting from memory to the database. The frequency of the queue processing is approximately every 30 seconds. Typically this call is inside an R function in your model, but it does not have to be.

Notes:

  • Once you've added an Epicenter::record call that saves a variable, you'll need to make sure the call is executed in your model. For example, if the call is inside an operation (function), you could add the operation to a component (such as a button) in your user interface using Flow.js. Or, you could call the operation directly.

  • Using Epicenter::record, you can save and update standard R variable types using Epicenter: boolean, number, string, lists, matrices, vectors, data frames. (You can also save and update S3 and S4 classes, but you'll need to use a custom data mapper.)

    • However, note Epicenter stores complex variable types internally as JSON objects. If you have special requirements for how or which parts of an R variable (lists, vectors, data frames) should be stored in the Epicenter backend database, consider writing a custom mapping.
  • You can only update (patch) global variables, and only one level deep. For example, if you have a list that contains a list, you can update the entire inner list with a new value, but you cannot update a particular element of the inner list. Similarly, you cannot update subsets of data frames.

  • Null values:

    • You cannot update a literal NA by itself, as in x <- NA. Use x <- NULL instead.
    • You cannot save NULL values in lists with Epicenter::record.
  • If you have special requirements for how or which parts of an R complex variable (list, vector, data frame) should be stored in the Epicenter backend database, consider writing a custom mapper.

Creating Custom Mappings

If you want end users to be able to review information from previous runs, or to resume working with a run they were using previously, you need to save model variables to the Epicenter backend database.

Epicenter stores complex variable types internally as JSON objects. If you have special requirements for how or which parts of an R variable (lists, vectors, data frames) should be stored in the Epicenter backend database, consider writing a custom mapping. (See above for more information on updating and referencing complex variables.)

A custom mapping gives you complete control of how your R model variables are made available to the user interface you create with Epicenter. This can be especially useful for large objects, or for objects for which only some fields should be exposed to the end user.

You'll need to create two separate mappings: one from R to Epicenter (model variables → Epicenter & user interface), and one from Epicenter back to R (Epicenter & user interface → model).

To map variables from R to Epicenter:

  • Create a function that converts your R data type to a JSON object. You can use the jsonlite::toJSON method to help.
  • Register this function using Epicenter::register_custom_encoder, which takes the R data type and your function as arguments.

To map variables from Epicenter to R:

  • Create a function that converts your JSON object back to an R data type.
  • Register this function using Epicenter::register_custom_decoder, which takes the R data type and your function as arguments.

Example:

Suppose your project makes use of complex numbers. We know each complex number will be stored as a JSON object. However, the default behavior in this case is to store the value of the variable string (e.g. "5+7i"), which is difficult to manipulate in the user interface. We want to construct the JSON object ourselves, rather than using this default.

First, we include the Epicenter library.

library(Epicenter)

Next, we specify how we want R's built-in complex type to be stored within Epicenter. For the mapping from R to Epicenter, we make a function that takes our R variable, which is of type complex, and creates a list with two elements, i (the real part) and j (the imaginary part).

complex_to_json <- function(myVariable) {
    jsonlite::toJSON(list(i=Re(myVariable), j=Im(myVariable)))
}

For the mapping from Epicenter to R, we make a function that takes our JSON object, extracts the two elements, and uses the built-in constructor for variables of complex type in R to recreate the R variable.

json_to_complex <- function(myVariable) {
    value <- complex(real=myVariable[["i"]], imaginary=myVariable[["j"]])
}

Finally, we register both of these functions. The arguments for the registration are the R data type and the mapping function.

Epicenter::register_custom_encoder("complex", complex_to_json)
Epicenter::register_custom_decoder("complex", json_to_complex)

After this registration, our mapping functions are called automatically during any Epicenter::record:

# myModel.R
library(Epicenter)

complex_to_json <- function(myVariable) {
    jsonlite::toJSON(list(i=Re(myVariable), j=Im(myVariable)))
}

json_to_complex <- function(myVariable) {
    value <- complex(real=myVariable[["i"]], imaginary=myVariable[["j"]])
}

Epicenter::register_custom_encoder("complex", complex_to_json)
Epicenter::register_custom_decoder("complex", json_to_complex)

myCVar <<- NULL

setCVar <<- function() {
    var1 <<- complex(real=1, imaginary=2)
    var2 <<- complex(real=4, imaginary=5)

    myCVar <<- var1 + var2
    Epicenter::record("myCVar", myCVar)
}

You can use the Run API to call setCVar and then to look up the value of myCVar:

curl -X POST \
    'https://api.forio.com/v2/run/acme-simulations/supply-chain-game/000001576501ce3b3387aa984aa94676ae17/operations/setCVar' \
    --header 'Content-Type:application/json' \
    --header 'Authorization: Bearer eyJhbGciOiJSUzI1NiJ9' \
    --data '{"arguments": []}'

curl -G \
    'https://api.forio.com/v2/run/acme-simulations/supply-chain-game/000001576501ce3b3387aa984aa94676ae17/variables?include=myCVar' \
    --header 'Authorization: Bearer eyJhbGciOiJSUzI1NiJ9'

With the above model, the response for a lookup of myCVar is:

{
    "myCVar": {
        "i": [
            5
        ],
        "j": [
            7
        ]
    }
}

whereas if you do not register the custom mapping functions, the response for a lookup of myCVar is:

{
    "myCVar": "5+7i"
}

Registering Operations for Automatic Execution

Optionally, you can create operations (functions) in your model, then register them so that they are automatically called when a new run is created.

This registration is done using the subscribe function in the Epicenter ("Epicenter.R") package.

There are three opportunities for registration:

For example, in your R model code, you would have:

# the function to initialize the model, can be named anything
initialize_model <- function() { ... }

# the function to reset the model, can be named anything
reset_model <- function() { ... }

# the function to call after a snapshot restore, can be named anything
restore_model <- function() { ... }

Then, in your primary model file (e.g. myModel.R), you register these functions using the subscribe function from the Epicenter.R package:

Epicenter::subscribe("initialize", initialize_model)
Epicenter::subscribe("reset", reset_model)
Epicenter::subscribe("restore", restore_model)

Creating the User Interface

Epicenter provides several different APIs, at different levels of abstraction, so you can connect your model's variables and operations with your UI. (Read more about the various options for this.)

Access and Update Variables

While the saving variables example (above) illustrates the basic concept, most real-world models make use of objects of various kinds. Recording and accessing variables from vectors, lists, data frames, and other objects includes some additional subtleties.

Epicenter stores basic and compound objects internally as JSON objects. See details on the mapping between R and JSON (pdf).

If you have special requirements for how or which parts of an R variable (list, vector, data frame) should be exposed to your project's end users, consider writing a custom mapper. (See more background on R accessors.)

Vectors

To update or reference an entire vector, use the name of the vector.

To update (patch) or reference an element of a vector, you can use:

  • numeric indices, with [, or less commonly with [[

For example, to reference the second element of

sales <<- c(100,200,400)

you could use:

sales[2]
sales[[2]]  ## rarely used with vectors

If you are using Flow.js, this looks like:

<span data-f-bind="sales[2]"></span>
<input data-f-bind="sales[2]"></input>

(Note that if you leave off the index with data-f-bind, Flow.js assumes you want the last element of the vector. You can use the data-f-foreach attribute to loop over all elements if needed. See details.)

And if you are using the Run API, this looks like:

curl -G \
    'https://api.forio.com/v2/run/acme-simulations/sample-r-model/000001576501ce3b3387aa984aa94676ae17/variables/sales[2]' \
    --header 'Authorization: Bearer eyJhbGciOiJSUzI1NiJ9'

curl -X PATCH \
    'https://api.forio.com/v2/run/acme-simulations/sample-r-model/000001576501ce3b3387aa984aa94676ae17/variables' \
    --header 'Content-Type:application/json' \
    --header 'Authorization: Bearer eyJhbGciOiJSUzI1NiJ9' \
    --data '{"sales[2]": 250}'

Lists

To update or reference an entire list, use the name of the list.

To update (patch) or reference an element of a list, you can use:

  • numeric indices, with [[
  • a reference to a named element, with [[
  • a reference to a named element, with $
  • if the list contains objects, numeric indices with [ to access the entire object

For example, to display the string Japanese from the following list

research <<- list(language="Japanese",topic="tourism")

you could use any of:

research[[1]]
research[["language"]]
research$language

However,

research[1]

returns the entire object {"language": ["Japanese"]} rather than the specific string Japanese.

If you are using Flow.js, this looks like:

<span data-f-bind="research[[1]]"></span>
<input data-f-bind="research[[1]]"></input>

And if you are using the Run API, this looks like:

curl -G \
    'https://api.forio.com/v2/run/acme-simulations/sample-r-model/000001576501ce3b3387aa984aa94676ae17/variables/research[[1]]' \
    --header 'Authorization: Bearer eyJhbGciOiJSUzI1NiJ9'

curl -X PATCH \
    'https://api.forio.com/v2/run/acme-simulations/sample-r-model/000001576501ce3b3387aa984aa94676ae17/variables' \
    --header 'Content-Type:application/json' \
    --header 'Authorization: Bearer eyJhbGciOiJSUzI1NiJ9' \
    --data '{"research[[1]]": "German"}'

Note that you cannot save null in lists.

Lists of Lists, and Other Nested Objects

You can only update (patch) global variables one level deep. For example, if you have a list that contains a list, you can update the entire inner list with a new value, but you cannot update a particular element of the inner list. Similarly, you cannot update subsets of data frames.

For example, if you have in your model

salesTeam <<- list( 
    list(manager="J. Smith", numReports=5, region="West"), 
    list(manager="R. Doe", region="South") 
)

then you can access the list element with details about J. Smith using numeric indices:

salesTeam[[1]]

returns

{
    "salesTeam[[1]]": {
        "manager": [
            "J. Smith"
        ],
        "region": [
            "West"
        ],
        "numReports": [
            5
        ]
    }
}

However, you cannot access or update ONLY the region element for J. Smith.

Note that you cannot save null in lists.

Data Frames

To update or reference an entire data frame, use the name of the data frame variable.

To update (patch) or reference a data frame element, you can use:

  • numeric indices with [[
  • numeric indices with [, if the data contains objects
  • a reference to a named element, with [[ or [ (depending on the type of the element)

For example, if you use the built-in data frame example of mtcars in your model

dfCars <<- mtcars

then to display the cylinders in a Mazda you could use:

dfCars[[1,2]]
dfCars[['Mazda RX4', 'cyl']]

to get the value 6. (You could also use [ and get the same response, because the data in this case is an integer.)

If you are using Flow.js, this looks like:

<span data-f-bind="dfCars[1,2]"></span>
<input data-f-bind="dfCars['Mazda RX4', 'cyl']"></input>

And if you are using the Run API, this looks like:

curl -G \
    'https://api.forio.com/v2/run/acme-simulations/sample-r-model/000001576501ce3b3387aa984aa94676ae17/variables/dfCars[1,2]' \
    --header 'Authorization: Bearer eyJhbGciOiJSUzI1NiJ9'

curl -X PATCH \
    'https://api.forio.com/v2/run/acme-simulations/sample-r-model/000001576501ce3b3387aa984aa94676ae17/variables' \
    --header 'Content-Type:application/json' \
    --header 'Authorization: Bearer eyJhbGciOiJSUzI1NiJ9' \
    --data '{"dfCars['Mazda RX4', 'cyl']": "8"}'

Calling Operations

You can call any operation (function) in your R model from your project's user interface.

For example, in Flow.js, add the operation to an element on the page, or call it when the run is created:

<button data-f-on-click="advanceMyModel(1)">Advance One Step</button>

In addition to the operations that you have defined yourself, you can also call the operation reset():

  • This operation takes no arguments.

  • This operation returns a new run, that is, a new instantiation of your model for an end user to play with. (Remember that a run is a collection of interactions with a model.) For example, this is useful if your end user needs to "start over" to create a new scenario within your project.

  • Note that the reset() operation can ONLY be called from your project's user interface if you are using Flow.js. (Under the hood, this is not an operation as defined for the RESTful Run API. If you are building your project's user interface with the Epicenter RESTful APIs, you should just create a new run instead of calling reset().)