forio Toggle navigation

Model Creation in Python

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

Creating and Uploading your Model Code

Use the Epicenter user interface to upload your model code (.py files) to the Model folder within your project. This folder must contain all of your model's files. Sub-folders are ok.

IMPORTANT: Model file names (.py files) can contain letters (A-Z, a-z), numbers (0-9), and underscore (_). However, file names cannot contain hyphens (-).

Understanding the Environment

Epicenter includes a Python environment for running your models.

Python Version

The default version of Python is 2.7.12, no matter whether you are the v1 or v2 version of the Epicenter APIs. (See more on the Epicenter APIs Version History.)

You can change the Python version by including a context file in the Model folder of your project. Specifically, if you set "language": "python3" in your context file, then the version of Python is 3.5.2.

Working with Packages and Modules

Epicenter includes the following Python packages by default:

These packages are available whether you are using Python 2.7.6 or 3.4.

You can reference these packages in your model using the standard from and import statements.

To use additional Python packages in your model:

  • Upload the source code into your project's Model folder.
  • Then import as needed into particular model files.

To use the Epicenter package in your local environment:

Working with Sub-folders

When you place your model code in the Model folder, your primary file — for example, your main module — 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.py) allows you to save variables to the Epicenter backend database, as well as providing other utilities.

Saving Variables

When you work with the Epicenter platform, metadata about every "run" — the collection of end user interactions with the project and its model — is automatically saved to the Epicenter platform's 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 some or all of the 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 platform's backend database from your Python model:

  1. At the start of your Python model, import the Epicenter class from the epicenter package:

     from epicenter import Epicenter
    
  2. In your Python model, when you want to save a variable, add the line:

     Epicenter.record('variable_name_as_string', actual_variable)
    

    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 a Python function in your model, but it does not have to be.

Notes:

  • Python variable names cannot contain spaces.

  • Once you've added a 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 Python variable types using Epicenter: boolean, number, string, array, dictionary, object.

    • However, note that Epicenter stores complex variable types internally as JSON objects. In particular:
      • Python booleans are stored as booleans
      • Python strings are stored as strings
      • Python numbers are stored as numbers (int, float, etc.)
      • Python arrays and tuples are stored as JSON lists
      • Python dictionaries, objects, types (classes), and modules are stored as JSON objects
  • Important: You cannot save null in lists or dicts with Epicenter.record.

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.py) package.

There are three opportunities for registration:

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

    # the function to initialize the model, can be named anything
def initializeModel():
           ...

   # the function to reset the model, can be named anything
   def resetModel():
       ...

   # the function to call after a snapshot restore, can be named anything
   def restoreModel():
       ...

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

# in my_model.py

from epicenter import Epicenter

Epicenter.subscribe("initialize", initializeModel)
Epicenter.subscribe("reset", resetModel)
Epicenter.subscribe("restore", restoreModel)

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.)

Once your model is complete, you can create the user interface for your project.

Access and Update Variables

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

Epicenter stores basic and compound objects internally as JSON objects.

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 []

For example, given the published_versions list

published_versions = list( (0.8, 0.9, 1.0, 1.1, 1.2, 2.0, 2.1) )

you could reference the first element using

published_versions[0]

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

<span data-f-bind="published_versions[0]"></span>
<input data-f-bind="published_versions[0]"></input>

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

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

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

Using PATCH with indices allows you to replace particular elements in the list. You cannot add new elements to the list just using PATCH. For example, trying to PATCH to published_versions[7] throws an error, because the list only contains elements 0-6; you need to append to the list in your Python model before this Epicenter API call will work.

Using PATCH without indices allows you to replace the entire list.

Important: Note that you cannot save null in lists.

Dicts

To reference an entire dictionary, use the name of the dictionary.

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

  • key names, with [] and the key string

For example, given the state_info dictionary

state_info = dict({ 'name': 'California', 'abbrev': 'CA', 
        'population': 38802500, 'admission': 1850 })

you could reference the population using

state_info['population']

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

<span data-f-bind="state_info['population']"></span>
<input data-f-bind="state_info['population']"></input>

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

curl -G \
    'https://api.forio.com/v2/run/acme-simulations/sample-python-model/000001576501ce3b3387aa984aa94676ae17/variables/state_info["population"]' \
    --header 'Authorization: Bearer eyJhbGciOiJSUzI1NiJ9'

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

Using PATCH with a key name allows you to replace additional elements to the dictionary. The above request replaces the value of population. If you instead used

--data '{"state_info": { "capital": "Sacramento" }}'

you are adding key:value pairs to state_info; existing key:value pairs are retained. Note that the PATCH syntax for the data changes when you are replacing values vs. adding new key:value pairs.

Important: You cannot completely replace a dictionary or save null in a dictionary.

Tuples

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

To reference an element of a tuple, you can use:

  • numeric indices, with []

For example, given the al_west tuple

al_west = tuple(['oak', 'laa', 'sea', 'hou', 'tex'])

you could reference the first element using

al_west[0]

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

<span data-f-bind="al_west[0]"></span>

<ul data-f-foreach="al_west">
    <li></li>
</ul>

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

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

You can update an entire tuple, but you cannot update just one element:

curl -X PATCH \
    'https://api.forio.com/v2/run/acme-simulations/sample-python-model/000001576501ce3b3387aa984aa94676ae17/variables' \
    --header 'Content-Type:application/json' \
    --header 'Authorization: Bearer eyJhbGciOiJSUzI1NiJ9' \
    --data '{"al_west": ["oakland", "los angeles", "seattle", "houston", "texas"]}'

replaces the entire tuple; but using

    --data '{"al_west[0]": "oakland"}'

gives an error.

Calling Operations

You can call any operation (function) in your Python 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>