from BPTK_Py import Model
from BPTK_Py import sd_functions as sd
from BPTK_Py.bptk import bptk
import numpy as np
=bptk() bptk
SD DSL Functions
SD DSL Functions
This document illustrates how to use the operators for the SD DSL. To use the operators, you need to import the sd_functions
, in addition to importing the Model
class.
IF / THEN / ELSE / AND /NOT / OR
It is possible to write up if clauses. We even support NOT and AND / OR operators.
Please note that these function names begin with a capital letter. This is because the actual words if, and, or
etc. are protected in Python and cannot / should not be overwritten.
An if clause requires 3 arguments: If ( <condition> , <then>, <else>)
condition
: Must be a boolean expression, e.g. sd.time() > 1
is true iff the simulation time is larger than 1 then
: Any expression that returns a float value if the condition is true else
: Any expression that returns a float value if the condition is false
A simple if clause may look like this:
= Model(starttime=0.0,stoptime=10.0,dt=0.1,name='if')
model = model.converter("converter")
converter = sd.If( sd.time()>5, 10, 5 )
converter.equation converter.plot()
You see that its value is 5 until t
reaches 6.
You can also add and
/ or
/ not
conditions easily:
Signature: And(<condition1>, <condition2>)
: Logical and between 2 conditions Or(<condition1>, <condition2>)
: Logical or between 2 conditions Not(<condition>)
: Logical not: True if condition is False
Each condition within the operators has to return a boolean value. Nesting of the operators is easily possible!
= sd.If( sd.And(sd.time()>5,sd.time()>10), 10, 5 ) # 5 (else case) as long as t <= 10, then 10
converter.equation = sd.If( sd.Or( sd.And(sd.time()>5,sd.time()>10), True), 10, 5 ) # Always 10 (then condition, because Or always evaluates to True)
converter.equation converter.plot()
ABS Function
The ABS
function returns the absolute value of its input.
Signature: abs(input)
input
may be any model element.
= Model(starttime=0.0,stoptime=10.0,dt=0.1,name='abs')
model
= model.converter("input_converter")
input_converter
=sd.time()-5
input_converter.equation
= model.converter("abs_converter")
abs_converter
= sd.abs(input_converter)
abs_converter.equation
bptk.register_model(model)=["smAbs"],scenarios=["base"],equations=["input_converter","abs_converter"]) bptk.plot_scenarios(scenario_managers
DELAY Function
The DELAY function returns a delayed value of input, using a fixed lag time of delay duration, and an optional initial value initial for the delay. If you don’t specify an initial value initial, DELAY assumes the value to be the initial value of input. If you specify delay duration as a variable, the DELAY function uses the initial value for its fixed lag time
Signature: delay(model, input_function, delay_duration, initial_value)
input_function
must be a model element delay_duration
and initial_value
must be floats or model elements.
= Model(starttime=0.0,stoptime=10.0,dt=0.5,name='delay')
model
= model.converter("input_function")
input_function
=sd.time()
input_function.equation
= model.converter("delayed_input_1")
delayed_input_1 = model.converter("delayed_input_2")
delayed_input_2 = model.converter("delayed_input_3")
delayed_input_3
= sd.delay(model,input_function, 1.0,1.0)
delayed_input_1.equation = sd.delay(model,input_function, 2.0,0.0)
delayed_input_2.equation = sd.delay(model,input_function, 2.5,0.5)
delayed_input_3.equation
bptk.register_model(model)
bptk.plot_scenarios(=["smDelay"],
scenario_managers=["base"],
scenarios=["input_function","delayed_input_1","delayed_input_2","delayed_input_3"]) equations
DT Function
The DT
function returns the models dt..
Signature: dt(model)
= Model(starttime=5,stoptime=12,dt=0.25,name='dt')
model = model.converter("dt")
dt = sd.dt(model)
dt.equation dt.plot()
EXP Function
The exp
function returns the exponential value of the input.
Signature: exp(element)
element
can be any model element (stock, flow, converter, constant)
= Model(starttime=0,stoptime=10,dt=0.1,name='exp')
model
= model.constant("growth_rate")
growth_rate
=np.log(2)
growth_rate.equation
= model.converter("exp")
exp
= sd.exp(growth_rate*sd.time())
exp.equation
exp.plot()
MAX Function
The max
function always chooses the larger of its two input values.
Signature: max(element, element)
element
can be any model element (stock, flow, converter, constant)
= Model(starttime=0.0,stoptime=10.0,dt=1.0,name='max') model
= model.converter("a") a
= 5.0+sd.step(5.0, 5.0) a.equation
a.plot()
= model.converter("b") b
= 10.0 - sd.step(5.0, 5.0) b.equation
b.plot()
= model.converter("c") c
=sd.max(a,b) c.equation
bptk.register_model(model)=["smMax"],scenarios=["base"],equations=["a","b","c"]) bptk.plot_scenarios(scenario_managers
MIN Function
The min
function always chooses the smaller of its two input values.
Signature: min(element, element)
element
can be any model element (stock, flow, converter, constant)
= Model(starttime=0,stoptime=10,dt=1,name='min')
model
= model.converter("a")
a
= 5.0+sd.step(5.0, 5.0)
a.equation
= model.converter("b")
b
= 10.0 - sd.step(5.0, 5.0)
b.equation
= model.converter("c")
c
= sd.min(a,b)
c.equation
bptk.register_model(model)=["smMin"],scenarios=["base"],equations=["a","b","c"]) bptk.plot_scenarios(scenario_managers
PULSE Function
The PULSE
function generates a pulse input of a specified size (volume). When using the PULSE builtin, you have the option of setting the time at which the PULSE will first fire (first pulse), as well as the interval between subsequent PULSEs. Each time that it fires a pulse, the framework pulses the specified volume over a period of one time step (DT). Thus, the instantaneous value taken on by the PULSE function is volume/DT.
Signature: pulse(model, volume, first_pulse=0, interval=0)
Setting interval
to 0 yields a single pulse that doesn’t repeat
volume
can be either a variable or a constant, first_pulse
and interval
must be constants.
= Model(starttime=0.0,stoptime=10.0,dt=0.25,name='pulse')
model
= model.stock("stock")
stock =0.0
stock.initial_value
= model.flow("flow")
flow =sd.pulse(model,10.0,2.0,2.0)
flow.equation
= flow
stock.equation
bptk.register_model(model)=["smPulse"],scenarios=["base"],equations=["stock","flow"]) bptk.plot_scenarios(scenario_managers
SMOOTH Function
The SMOOTH function calculates the exponential average of the input, given the input function, an initial value and an averaging time.
Signature: smooth(model, input_function, averaging_time, initial_value)
model
: The model you are writing equations for
input_function
: any model element
averaging_time
: any model element
initial_value
: a floating point value or constant
The SMOOTH operator is a shorthand for the following stock and flow structure and equations:
= Model(starttime=1.0,stoptime=10.0,dt=0.1,name='smooth')
model = model.converter("input_function")
input_function =sd.step(10.0,3.0)
input_function.equation= model.converter("smooth")
smooth =sd.smooth(model, input_function,2.0,0.0)
smooth.equation
bptk.register_model(model)=["smSmooth"],scenarios=["base"],equations=["input_function","smooth"]) bptk.plot_scenarios(scenario_managers
STARTTIME Function
The STARTTIME
function returns the models starttime.
Signature: starttime(model)
= Model(starttime=5,stoptime=12,dt=1,name='starttime')
model = model.converter("starttime")
starttime = sd.starttime(model)
starttime.equation starttime.plot()
STOPTIME Function
The STOPTIME
function returns the models starttime.
Signature: stoptime(model)
= Model(starttime=5,stoptime=12,dt=1,name='stoptime')
model = model.converter("stoptime")
stoptime = sd.stoptime(model)
stoptime.equation stoptime.plot()
STEP Function
The STEP function generates a change of specified height, which occurs at a specified time.
Signature: step(height, timestep)
input_function
: any model element or a floating point number
averaging_time
: any model element or a floating point numnber
initial_value
: a floating point value or a constant
= Model(starttime=1,stoptime=10,dt=1,name='step')
model
= model.converter("step")
step =sd.step(10.0,5.0) step.equation
step.plot()
TIME Function
The time
function returns the current simulation time.
Signature: time()
= Model(starttime=0,stoptime=10,dt=1,name='time')
model
= model.stock("stock")
stock
=0.0
stock.initial_value
= model.flow("inflow")
inflow
= sd.time()
inflow.equation
= inflow
stock.equation
inflow.plot()
TREND Function
The TREND function calculates the trend in the input, given the input, an initial value and an averaging time. The TREND is defined to be the fractional change in input compared to the exponential average of input per averaging time. The TREND function thus estimates the growth rate of is input function.
Signature: trend(model, input_function, averaging_time, initial_value)
model
: The model you are writing equations for
input_function
: any model element
averaging_time
: any model element
initial_value
: a floating point value or constant
The TREND operator is a shorthand for the following stock and flow structure and equations:
= Model(starttime=1,stoptime=10,dt=0.01,name='trend')
model
= model.constant("growth_rate")
growth_rate
=np.log(2)
growth_rate.equation
= model.converter("input_function")
input_function
= sd.exp(growth_rate*sd.time())
input_function.equation
= model.converter("trend")
trend
= sd.trend(model,input_function,1.0,2/(1+np.log(2))) trend.equation
As an example, we set up a small model that has an input function that doubles every timestep - i.e the exponential growth rate is log 2 ≈ 0.69 and then apply the trend function to estimate the growth rate.
Here is a plot of the growth rate, which is constant:
growth_rate.plot()
This gives an input function which doubles in value on every timestep:
input_function.plot()
As expexted, the plot of the trend function converges to the input growth rate:
trend.plot()
ROUND Function
This function rounds any input to a specified number of digits.
Signature: round(expression, digits)
expression
can be any float input by any expression. digits
must be an int value
A minimal example that rounds random numbers between 0 and 2 to 0 digits (int number):
= Model(starttime=0.0,stoptime=10.0,dt=0.25,name='round')
model = model.flow("round")
flow = sd.round( sd.random(0, 2), 0 )
flow.equation flow.plot()
SQRT
Computes the Square root of an input expression.
Signature: sqrt(expression)
expression
can be any element that returns a float value.
Simple Example:
= Model(starttime=0,stoptime=10,dt=1)
m= m.flow(name="sqrt")
f
= sd.time()
val
= sd.sqrt(val)
f.equation f.plot()
NAN / INF / PI
sd.nan()
returns a NAN value, sd.Inf()
gives you the infinity value, sd.pi()
returns the number pi.
SIN / TAN / COS and ARCCOS / ARCSIN / ARCTAN
The SD DSl supports all trigonometric that you are also used to from other SD simulation / modelling tools
Use sd.sin(x) / sd.cos(x) / sd.tan(x)
for sinus, cosinus or tangent of x (radians) and sd.arcsin(x) / sd.arctan(x) / sd.arccos(x)
for the respective arctan / arccos and arcsine operators.
Let’s easily plot sin / cos and tan for the current simulation time:
= Model(starttime=0,stoptime=10,dt=0.1)
m= m.biflow(name="sin")
sin = m.biflow(name="tan")
tan = m.biflow(name="cos")
cos = sd.time()
x
= sd.sin(x)
sin.equation = sd.tan(x)
tan.equation = sd.cos(x)
cos.equation
sin.plot()
tan.plot() cos.plot()
SINWAVE and COSWAVE function
SINWAVE returns a time-dependent sine wave, with the specified amplitude and period. To generate the sine wave, the SINWAVE builtin uses the absolute value of the amplitude you specify. To produce meaningful wave results, choose a DT that’s significantly smaller than the period of the wave. A DT equal to a quarter of the period gives triangle waves. A smaller DT gives results which better approximate a continuous curve.
COSWAVE generates a time-dependent cosine wave. It uses the same arguments
Signature: sinwave(amplitude,period)
amplitude
: Amplitude of the sine wave period
: Period of the sine wave
Example:
= Model(starttime=0,stoptime=10,dt=0.1)
m= m.biflow(name="sinwave")
f = m.biflow("coswave")
g = 10
amplitude = 5
period
= sd.sinwave(amplitude, period)
f.equation = sd.coswave(amplitude, period)
g.equation
f.plot() g.plot()
BETA Function
The BETA operator generates a series of random numbers that conforms to a beta distribution defined by two shape arguments, alpha
and beta
.
Example:
= Model(starttime=0,stoptime=10,dt=0.1)
m= m.biflow(name="beta")
f = 1
alpha = 2
beta = sd.beta(alpha, beta)
f.equation f.plot()
BINOMIAL
This operator generates a series of random numbers from a discrete probability distribution of the number of successes in a sequence of trials with a given success probability. The success probability should be a number between 0 and 1.
Arguments are number of trials (n)
and success probability (p)
.
A quick example:
= Model(starttime=0,stoptime=10,dt=0.1)
m= m.flow(name="binomial")
f = 100
n = 0.1
p = sd.binomial(n, p)
f.equation f.plot()
COMBINATIONS
The COMBINATIONS operator calculates the number of r-element subsets (or r-combinations) of an n-element set without repetition.
Arguments n
and r
must follow n >= r >= 0 and be integers.
Example using n
as time - because n
must be an integer, we have to wrap it in a user defined function.
= Model(starttime=3,stoptime=10,dt=1)
m = m.flow(name="combinations")
f = m.function("n", lambda model, t: int(t))
n = 3
r = sd.combinations(n(), r)
f.equation f.plot()
EXPRND Function
This operator generates a series of exponentially distributed random numbers with a given mean
.
Example:
= Model(starttime=0,stoptime=10,dt=0.1)
m= m.flow(name="exprnd")
f
= sd.time()
mean = sd.exprnd(mean)
f.equation f.plot()
FACTORIAL Function
The FACTORIAL function calculates the factorial of the single argument n
(traditionally noted as n!). n
must be an integer value, decimal values are not allowed.
The following example wraps the time t
in a user-defined function to ensure that this value is always an integer.
= Model(starttime=0,stoptime=5,dt=1)
m= m.flow(name="factorial")
f = m.function("n", lambda model, t: int(t))
n
= sd.factorial(n())
f.equation f.plot()
GAMMA Function
The GAMMA builtin generates a series of random numbers that conforms to a gamma distribution with the specified shape
and scale
. If unspecified, scale
uses the value 1.0
Example:
= Model(starttime=0,stoptime=10,dt=0.1)
m= m.biflow(name="gamma")
f
= 10
shape = sd.time()
scale
= sd.gamma(shape, scale)
f.equation f.plot()
GAMMALN Function
The GAMMALN operator returns the natural log of the GAMMA function, given input n. The GAMMA function is a continuous version of the FACTORIAL builtin, with GAMMA(n) the same as FACTORIAL(n-1).
Only argument is n
= Model(starttime=0,stoptime=10,dt=0.1)
m= m.biflow(name="gammaln")
f
= sd.time()
n = sd.gammaln(n)
f.equation f.plot()
GEOMETRIC Function
The GEOMETRIC operator generates a series of random numbers from a discrete probability distribution of the number of trials before the first success with a given success probability (p)
.
p
is the only parameter. It should be any value between 0 and 1.
Example:
= Model(starttime=0,stoptime=10,dt=0.1)
m= m.biflow(name="geometric")
f
= 0.1
p
= sd.geometric(p)
f.equation f.plot()
INVNORM Function
The INVNORM operator calculates the inverse of the NORMALCDF function (see below).
Parameter is the probability p
(any value between 0 and 1).
Example:
= Model(starttime=-0.5,stoptime=1,dt=0.1)
m= m.biflow(name="invnorm")
f
= sd.time()
p
= sd.invnorm(p)
f.equation f.plot()
LOGISTIC Function
The LOGISTIC operator generates a series of random numbers that conforms to a logistic distribution with a specified mean
and scale
.
Example:
= Model(starttime=-1,stoptime=10,dt=0.1)
m= m.biflow(name="logistic")
f
= 0
mean = 1
scale
= sd.logistic(mean, scale)
f.equation f.plot()
LOGNORMAL Function
The LOGNORMAL operator generates a series of random numbers that conform to a Log-Normal distribution (that is, the log of the independent variable follows a normal distribution) with a specified mean and stddev (standard deviation). LOGNORMAL samples a new random number in each iteration of a simulation.
Arguments are mean
and standard deviation
Example:
= Model(starttime=0,stoptime=10,dt=0.1)
m= m.biflow(name="lognormal")
f
= 0
mean = 1
stdev = sd.lognormal(mean, stdev)
f.equation f.plot()
MONTECARLO Function
The MONTECARLO operator randomly generates a series of zeros and ones from a Bernoulli distribution based on the probability you’ve provided. The probability is the percentage probability of an event happening per unit of simulation time. The probability value can be either a variable or a constant, but should evaluate to a number between 0 and 100.
MONTECARLO is equivalent to the following logic:
IF (RANDOM(0,100,
Example:
= Model(starttime=0,stoptime=10,dt=0.1)
m= m.biflow(name="montecarlo")
f
= 50
probability = sd.montecarlo(probability)
f.equation f.plot()
NORMAL Function
The NORMAL operator generates a series of normally distributed random numbers with a specified mean and stddev (standard deviation).
Arguments are mean
and the standard deviation
of the underlying normal distribution.
Example:
= Model(starttime=0,stoptime=10,dt=1)
m= m.biflow(name="normal")
f
= 0
mean = 1
stdev = sd.normal(mean, stdev)
f.equation f.plot()
NORMALCDF Function
The NORMALCDF operator calculates the cumulative Normal distribution function between the specified z-scores, or, when the mean and stddev (standard deviation) are given, between two data values.
Arguments are the left
and right
boundaries and optionally mean
and stddev
. If not given, mean will be set to 0, stddev to 1.
A really simple example:
= Model(starttime=-4,stoptime=4,dt=0.1)
m= m.biflow(name="normalCDF")
f = -4
left = sd.time()
right = 0
mean = 1
stddev = sd.normalcdf(left, right, mean, stddev)
f.equation f.plot()
PARETO Function
The PARETO operator generates a series of random numbers that conforms to a distribution whose log is exponentially distributed with a specified shape and scale
Arguments are shape
and scale
.
Example:
= Model(starttime=1,stoptime=10,dt=0.1)
m= m.biflow(name="pareto")
f = 1
shape = 1
scale
= sd.pareto(shape, scale)
f.equation f.plot()
PERMUTATIONS
The PERMUTATIONS operator calculates the number of permutations of an n-element set with r-element subsets.
Arguments are n
and r
. Note that both numbers should be integer values and must follow n >= r >= 0.
Example:
= Model(starttime=1,stoptime=10,dt=0.1)
m= m.biflow(name="permutations")
f = 7.0
n = 3
r
= sd.permutations(n, r)
f.equation f.plot()
POISSON Function
The POISSON operator generates a series of random numbers that conform to a Poisson distribution. The mean value of the output is mu * DT.
Only argument is mu
, a float or integer number or any operator that returns a number.
Example (with an increasing mu
expressed as the current simulation time):
= Model(starttime=1,stoptime=10,dt=0.1)
m= m.biflow(name="poisson")
f = sd.time()
mu
= sd.poisson(mu)
f.equation f.plot()
RANDOM / UNIFORM Function
RANDOM and UNIFORM both draw a random number between a minimum and maximum value that conforms to a uniform distribution. For compatibility to modelling practices, we included both into the SD DSL (just as the Stella Architect builtins).
Arguments are the min_value
and max_value
between which the random number should lie. If not given, the random number is between 0 and 1.
Simple example where the number always lies between DT and the current simulation time:
= Model(starttime=1,stoptime=10,dt=0.1)
m= m.biflow(name="uniform / random")
f = 0.1
min_value = sd.time()
max_value
= sd.random(min_value, max_value)
f.equation f.plot()
TRIANGULAR Function
The TRIANGULAR operator generates a series of random numbers that conforms to a triangular distribution with a specified lower bound
, mode
, and upper bound
.
A simple example with the current simulation time as upper bound:
= Model(starttime=1,stoptime=10,dt=0.1)
m= m.biflow(name="triangular")
f = 0
lower_bound = 1
mode = sd.time()
upper_bound
= sd.triangular(lower_bound, mode, upper_bound)
f.equation f.plot()
WEIBULL Function
The WEIBULL operator generates a series of random numbers that conforms to a Weibull distribution with the specified shape
and scale
.
Let’s create a quick example with scale
set to the current simulation time:
= Model(starttime=1,stoptime=10,dt=0.1)
m= m.biflow(name="weibull")
f = 1
shape = sd.time()
scale
= sd.weibull(shape, scale)
f.equation f.plot()