Built from Other Functions

The XMILE builtins the SD DSL has no function for - SELF, CLOCKTIME, ENDVAL, RUNCOUNT and SENSIRUNCOUNT - and how to write them with what it has.
Keywords

system dynamics, xmile, stella, sd dsl, bptk, bptk-py, python, self, clocktime, endval, runcount

Built from Other Functions

A few XMILE builtins have no function of their own in the SD DSL, because what they do is already there in another form: a function used in a particular way, or a few lines of Python around the model. Each builtin is shown with its XMILE equation first, then the SD DSL code. The code uses this model:

from BPTK_Py import Model
from BPTK_Py import sd_functions as sd

model = Model(starttime=0.0, stoptime=10.0, dt=0.5,
              name="built_from_others")
demand = model.converter("demand")
demand.equation = 100.0 + 10.0 * sd.time()

SELF

SELF stands for the variable in whose equation it appears. It is used to read the variable’s own past. In the SD DSL the variable is available by name, so it is passed directly:

XMILE

growth = PREVIOUS(SELF, 1) * 2

SD DSL

growth = model.converter("growth")
growth.equation = sd.previous(model, growth, 1.0) * 2.0

CLOCKTIME

CLOCKTIME is the wall-clock time in seconds, not the model’s time. In the SD DSL a user-defined function reads it. Such a function runs on the Rust engine too: the engine calls back into Python for it.

XMILE

started_at = CLOCKTIME

SD DSL

import time

clocktime = model.function("clocktime",
                           lambda model, t: float(int(time.time())))
started_at = model.converter("started_at")
started_at.equation = clocktime()

ENDVAL

ENDVAL is a variable’s value at the end of the previous run, and its initial value in the first one. In the SD DSL, Python starts each run, so Python also carries the value from one run to the next: read the value at the stop time and set it as a constant for the next run. Setting an equation clears the values the model has computed, so the next read runs the model again.

XMILE

last_end = ENDVAL(demand, 0)

SD DSL

last_end = model.constant("last_end")
last_end.equation = 0.0          # the first run

end_of_run = demand(model.stoptime)
last_end.equation = end_of_run   # the next run

The same works across scenarios: the end value of one scenario becomes a constant of the next one.

RUNCOUNT and SENSIRUNCOUNT

RUNCOUNT is the number of the current run in a session, SENSIRUNCOUNT the number of the current run in a sensitivity analysis. In the SD DSL, Python counts the runs and sets the count as a constant before each run:

XMILE

run_number = RUNCOUNT

SD DSL

run_number = model.constant("run_number")
ends = []
for count in range(1, 4):
    run_number.equation = float(count)
    ends.append(demand(model.stoptime) * run_number(model.stoptime))

A sensitivity analysis is the same loop, run over the values of a parameter instead of a count.