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 is shown here after its XMILE equation. The code runs on 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 is the variable whose equation it appears in. Its use is to read the variable’s own past, and in the SD DSL the variable itself is at hand for that:

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. A user-defined function reads it; it runs on the Rust engine too, which 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 a run is something Python starts, so the value is carried from one run to the next by Python: read it at the stop time, and set it as a constant for the next run. Setting an equation clears what the model has computed, so the next read runs 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.

RUNCOUNT and SENSIRUNCOUNT

RUNCOUNT counts the runs of a session, SENSIRUNCOUNT the runs of a sensitivity analysis. In the SD DSL Python does the counting, and the model sees it as a constant set 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 over a parameter instead of a count.

REWORK

Not supported, by the SD DSL or by the transpiler.