Model Creation in R
When you create a model in R, you can use it with the Epicenter platform by:
- Creating and uploading your model code to the Model folder of your project.
- Understanding the environment when running your R model on Epicenter.
- Optionally, creating a model context file and uploading it to the Model folder of your project.
- Optionally, using the Epicenter package in your model. This package allows you to:
- Save variables to the Epicenter backend database
- Create custom mappings between complex R variable types in your model and their storage in the Epicenter backend database
- Register operations for automatic execution
- Creating the user interface for your project. You may want to:
- Access and update variables from the Epicenter backend database
- Call operations from your project's user interface
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.2.3.
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
orlibrary
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:
At the start of your R model, import the Epicenter package:
library(Epicenter)
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 inx <- NA
. Usex <- NULL
instead. - You cannot save
NULL
values in lists withEpicenter::record
.
- You cannot update a literal
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:
- Initializing the model when a new run is created, and
- Resetting the model when a new run is created based on an existing run.
- Immediately following the restore of a run if the model's
restoreMode
isSNAPSHOT
(see the Model Context page for details onrestoreMode
).
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 callingreset()
.)