forio Toggle navigation

SimLang: Language Overview

A model written in SimLang is an equations file (.eqn). The equations describe external factors and conditions affecting the simulation, as well as the connections between user decisions and simulation results. Together, these equations define the cause-and-effect model that underlies your project.

The model file (equations file) can contain, in any order: decisions, variables, and properties.

Each decision or variable definition can include functions that are built in to the language.

Like other system dynamics -inspired languages (such as Vensim), SimLang is designed to support time-based models. Therefore, each project with a SimLang model automatically has access to several operations that advance the model through time (e.g. step). When you build your User Interface, you set up some elements to call these operations. (However, you do not define your own operations within a SimLang model: the only functions in an equations file are those used as part of the definition of a decision or variable.)

In this section, read more about:

In addition to this Language Overview, you can also jump to the Function Reference for details on all of the built-in functions that are part of SimLang.

SimLang was originally developed for use with the previous version of the Forio platform, Simulate. If you already have a model in SimLang because of your work with Simulate, see Migrating your model from Simulate to Epicenter.

Decisions & Variables

Decisions

A decision is a model element subject to end user control. It contains an equation that sets the default value, which is calculated at the start of the simulation. Otherwise, it takes its value from the end user's input. Decisions can also be recalculated, which resets their value to the equation.

When you write your model (.eqn) file, decisions are prefixed with D. For example:

D Initial Savings = 5000
D Interest Rate = 10%
D Yearly Deposits = 1000

For example, when you create the user interface for your project, you might use a decision input, slider, or radio group to capture an end user's input and store it in a model decision.

Variables

A variable is a model element with an equation that is automatically recalculated every time step. It can also take its value from the end user's input. However, once it is set by the end user, its value is fixed: it will not be automatically recalculated with each time step until a recalculate operation is explicitly called.

When you write your model (.eqn) file, variables are prefixed with V. For example:

V Interest = Previous Year Savings * Interest Rate
V Total Savings = Previous Year Savings + Interest + Yearly Deposits

For example, when you create the user interface for your project, you might use a variable, results table, line chart, or column chart to display model variables.

Naming Conventions

The names of both decisions and variables must start with a letter. They can contain uppercase letters, lowercase letters, spaces, and numbers.

Often models written in SimLang follow the traditional system dynamics convention of using multi-word variables names, including spaces, but this is only a convention.

Working with Scalars

Many decisions and variables are scalars, that is, singular values. Scalar decisions and variables can be strings, boolean values, any kind of number (integers, floating points), or a mathematical formula or equation.

See more on referencing variables from the user interface of your model.

Working with Arrays

Many decisions and variables are arrays, that is, sets of values that use the same equations. For example, a sales model might have the variables Sales and Revenue, and the decision Price, each of which is split among Books, CDs, and Games.

See more on referencing variables from the user interface of your model.

To define an array:

To define an arrayed variable or decision, you must specify both an equation and a range.

  • The equation can be numeric values, mathematical formulas, strings, etc., just as with scalar variables and decisions. To specify the equation for the initial values of an array, the values are enclosed in curly braces ({ }) and separated by commas (,).

  • The range indicates how the variable or decision is segmented. There are two ways to specify a range:

    • For enumerated ranges, use the prefix R and the name of the range, then list out the values in the range.

        R Products = Books, CDs, Games
        D Price[Products] = {100, 150, 175}
        V Sales[Products] = {100, 200, 150}
        V Revenue[Products] = Sales * Price
      
    • For numeric ranges, specify the variable or decision and append square brackets. Inside the brackets, list either the total number of items, or a starting index and an ending index with two periods in between. Numeric ranges are 1-based.

        D Price[3] = {100, 150, 175}
        V Sales[1..3] = {100, 200, 150}
        V Other Sales[11..12] = {1100, 1200}
      

To define a multidimensional array:

Arrays can also be multidimensional. Use a comma (,) in the range and enclose each dimension in curly braces ({ }).

V MySales[5, 2] = {{100, 200}, {100, 200}, {100, 200}, {100, 200}, {100, 200}}

To define arrays using default values:

To simplify the process of defining arrays, you can define particular elements by using the array index and a colon (:). Additionally, you can specify a default value using the Default keyword.

# Define an array with 1st element 100, 2nd element 200,
# and all other elements 1000
V MySales[10] = {100, 200, Default: 1000}

# Define an array with 5th element 100, 2nd element 200,
# and all other elements 1000
V MySales[10] = {5: 100, 2: 200, Default: 1000}

# Define an array and set Pencils to 100, 
# all other elements to 200
R Products = Pencils, Markers, Crayons 
V Sales[Products] = {Pencils: 100, Default: 200}

# Define an array and set the first five elements to 1000,
# and all other elements to 6000
V MySales[10] = {1..5: 1000, Default: 6000}

To define array values using expressions:

Any array definition can include an expression:

V Other Sales[1..2] = {New Sales, New Sales + Repeat Sales}

You can also use a loop to apply an expression to each array element. The syntax is:

FOREACH(<item>, <range>, <expression>)

For example:

V NewSales[10] = FOREACH(item, 10, item * 10)

is equivalent to:

V NewSales[10] = {10, 20, 30, 40, 50, 60, 70, 80, 90, 100}

See more about loops under Control Flow.

To reference arrays in equations:

You can define an array using another array of the same dimensions in an equation. For example:

R Products = Books, CDs, Games
D Price[Products] = {100, 150, 175}
V Sales[Products] = {100, 200, 150}
V Revenue[Products] = Sales * Price

Equations can also refer to individual elements of an array, either by numeric index or by enumerated index:

V MyBookProfit = Revenue[Books] - Cost[Books]
V Demand = NewSales[1]

You can also reference sub-dimensions:

V X[5, 2] = {{100, 200}, {100, 200}, {100, 200}, {100, 200}, {100, 200}}

# Y is a one-dimensional array of two elements
V Y[2] = X[1, *]    

# Z is a two-dimensional array, but with three rows instead of five
V Z[3, 2] = X[1..3, *]

Array definitions can refer to themselves in equations, as long as they do not cause a circular reference error:

V Fib[1..5] = {1, 1, Fib[1] + Fib[2], Fib[2] + Fib[3], Fib[3] + Fib[4]}

To combine arrays with different dimensions:

Often, you may want to combine arrays of different ranges. The way to do this is with the FOREACH statement.

R Products = Books, CDs, Games
R Market Segments = 1..2

V Market Size[Market Segments] = {1000, 2000}
V Market Share[Products, Market Segments] = 
        {{20%, 30%}, {22%, 33%}, {44%, 46%}}

V Our Market[Products, Market Segments] = 
    FOREACH(p, Products,
        FOREACH(m, Market Segments,
            Market Share[p, m] * Market Size[m]
        )
    )

The equation for Our Market is arrayed over Products and Market Segments. The two FOREACH statements construct that array, applying the given expression to each new array item. This can be thought of in English as "For each product p and each market segment m in the result, the equation is Market Share[p, m] multiplied by Market Size[m]."

See more about loops under Control Flow.

Control Flow

Models written in SimLang don't have control flow in the same sense as, say, a Julia or Python program does. Rather than defining your own methods and control flow within a SimLang model, each project automatically has access to several operations that advance the model through time. At each time step, the decisions and variables are re-evaluated.

However, within the equation that defines each decision or variable, you do have a chance to use both built-in functions, as well as control flow in the form of conditionals and loops.

To learn more about built-in functions, see the Function Reference.

Loops: FOREACH

You can use FOREACH in SimLang to loop through values. This is almost always used in the definition of arrays, to apply an expression to each array element. The syntax is:

FOREACH(<item>, <range>, <expression>)

For example:

V NewSales[10] = FOREACH(item, 10, item * 10)

is a more compact way of writing:

V NewSales[10] = {10, 20, 30, 40, 50, 60, 70, 80, 90, 100}

Loops are nestable, which is useful for multidimensional arrays. See additional examples in the Working with Arrays section, above.

Conditionals: IF

You can use IF in SimLang to choose values conditionally. The syntax is:

IF(<expression>, <equation if true>, <equation if false>)

For example:

V Profitable = IF(Profit > 0, true, false)

Note that each equation can itself include not only built-in functions but also loops or other conditionals.

Operations Available from the UI

Like other system dynamics -inspired languages, SimLang is designed to support time-based models. Therefore, although you do not define your own methods within a SimLang model, each project with a SimLang model automatically has access to several operations that advance the model through time.

For example, when you create the user interface for your project, you might have a button click call an operation. If you are using Flow.js, this looks like:

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

Operation: getTimeDetails

Arguments: none

Result: This method returns an object listing the current time and step, as well as the model properties TimeStep, StartTime and EndTime.

Example: getTimeDetails()

Example Response:

{
    "name": "getTimeDetails",
    "result": {
        "step": 2,
        "timeStep": 1,
        "endTime": 2015,
        "time": 2007,
        "startTime": 2005
    }
}


Operation: goBack

Arguments: Optional, number of time units to move backwards in the model.

Result:

  • If no argument is specified, the run goes back by 1 time unit.
  • If an argument is specified, the run goes back by the specified number of time units.
  • After the goBack() operation is complete, the sim is at the end of the previous step, with the decisions for that (previous) step still present.
  • If you try to go back before the start of the sim, the goBack() call acts just like the reset() call (see below): The sim will not go back before the simulation start time, but all the decisions will be cleared.

For example, consider the following sequence of calls:

step()   // advance from step 1 to step 2
step()   // advance from step 2 to step 3
step(2)  // advance from step 3 to step 5
goBack() // go back from step 5 to step 4, with decisions made in step 4

Note that TimeStep is a model property. If undefined, it defaults to 1, meaning time steps and time units are synonymous. However, if you set the TimeStep property, then goBack() and goBack(n) have different behaviors. For example, if you set M TimeStep = 0.5, then goBack() goes backwards by one time unit (two time steps), and goBack(2) also goes backwards by two time steps (one time unit).

Example: goBack(), goBack(2)


Operation: step

Arguments: Optional, floating point number of time steps to advance the model.

Result:

  • If no argument is specified, the run advances by 1 time unit.
  • If an argument is specified, the run advances by the specified number of time steps.

Note that TimeStep is a model property. If undefined, it defaults to 1, meaning time steps and time units are synonymous. However, if you set the TimeStep property, then step() and step(n) have different behaviors. For example, if you set M TimeStep = 0.5, then step() advances by one time unit (two time steps), and step(2) also advances by two time steps (one time unit).

Example: step(), step(5)


Operation: stepTo

Arguments: The time unit to step to (but not past).

Result: The run advances to the specified time unit.

Example: stepTo(2018)


Operation: recalculate

Arguments: Array of strings. Each string must be a variable or decision in the model.

Result: This operation causes both variables and decisions to recalculate based on their equations in the model.

Example: recalculate(["var1", "var2", "dec1"])

Example Response:

{
  "var1": "10",
  "var2": "20",
  "dec1": "30",
}

Notes:

  • Typically decisions take their value from the end user's input; an equation defines their initial value. This operation causes the named decision to be recalculated based on its equation.
  • Typically variables are automatically recalculated every time step based on their equation (definition) in the model. It can also take its value from the end user's input. However, once it is set by the end user, its value is fixed: it will not be automatically recalculated with each time step. Calling recalculate updates the variable based on its equation, and additionally "unfreezes" the variable: after calling this operation, the variable is again automatically recalculated every time step.

Operation: reset

Arguments: none

Result: 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.

Example: reset()

Notes: This 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 create a new run instead of calling reset().)


Properties

Properties are characteristics of your model, declared in the model, that help set the context for your simulation and govern its overall behavior.

Properties apply to the entire model.

  • These model properties can be declared anywhere in your model (.eqn) file.
  • They are prefixed with an M.
  • For example: M StartTime = 2014.

If you have used SimLang in Simulate, you may have used other properties specific to Simulate (for example, properties applied specifically to a variable or decision, which started with P). These are not supported in Epicenter. However, they will not cause an error if present in your model file; they are simply ignored. See more on migrating from Simulate.

The model configuration file, which is Epicenter-specific, provides a few additional model properties. Properties in the configuration file describe characteristics such as how often decisions and variables are saved to the Epicenter backend database.

Index of Properties


Property: EndTime

Definition: The final time of the simulation. If less than StartTime, the simulation will not advance.

Scope: Model

Default Value: If undefined, defaults to 10.

Example: M EndTime = 2020


Property: StartTime

Definition: The starting time of the simulation.

Scope: Model

Default Value: If undefined, defaults to 0.

Example: M StartTime = 2010


Property: TimeStep

Definition: The number of time units to advance in the model with each call to the operation step().

Scope: Model

Default Value: If undefined, defaults to 1.

Example: M TimeStep = 0.5

Typically this is used in models that by nature are very granular, in order to properly model the underlying equations.

For instance: A call to step(1) always advances the model by one time unit, e.g. from 2010 to 2011, no matter what the value of the TimeStep property.

If you have M TimeStep = 0.5, a call to step() advances the model by half a time unit, e.g. from 2010 to 2010.5. A call to step(2) advances the model by two time units, or one time step, from 2010 to 2011. See additional examples under Operations, above.