Introduction
SimpleSim.jl is a minimalist Julia framework for modular dynamical system simulation.
For installation, run import Pkg; Pkg.add("SimpleSim.jl") from within your Julia environment.
Philosophy
This software project aims at removing a lot of the overhead that a lot of the existing simulation frameworks out there have. SimpleSim.jl does not export any types. The interface almost solely consists of the function simulate and a agreed-upon model structure. The light interface results in most design decisions left up to the user.
At the same time SimpleSim.jl does not compromise on functionality and offers a feature-rich simulation framework.
Short overview
The main point of interaction with the SimpleSim.jl framework is the simulate function. As a first argument it expects to be passed some object that provides hooks with certain names for various functionalities.
Continuous-Time Systems
A simple example of a dynamical system model accepted by SimpleSim.jl would be
my_model = (
fc = dynamics_function,
gc = measurement_function,
)where we pass two functions dynamics_function and measurement_function that we defined elsewhere.
These two functions follow the typical dynamical systems approach for continuous-time systems
\[\dot{x}(t) = f(x(t), u(t), p, t)\\ y(t) = g(x(t), u(t), p, t)\]
or in Julia
dynamics_function = (x, u, p t) -> ...
measurement_function = (x, u, p t) -> ...If my_model has no field named p, SimpleSim.jl will pass nothing to fc and gc.
Discrete-Time Systems
Similarly for a discrete-time system we write
next_state_function = (x, u, p, t) -> ...
dt_measurement_function = (x, u, p, t) -> ...
my_dt_model = (
fd = next_state_function,
gd = dt_measurement_function,
Δt = 1 // 10,
)modeling the system
\[x_{k+1} = f(x_k, u_k, p, t)\\ y_k = g(x_k, u_k, p, t)\]
Running Simulations
Simulating a model is done by calling the simulate function
out = simulate(my_model, T = 10 // 1)add the keyword argument xc0 = or xd0 = to set the initial state of your continuous-time or discrete-time model, respectively.
Modularity
SimpleSim.jl takes a hierarchical approach to more complex, modular systems. That means any model can have any number of submodels. To define submodels, a Vector, Tuple or NamedTuple of models is passed to the models keywords on creation of the model.
submodel_1 = (
p = ...,
fc = ...,
gc = ...,
)
submodel_2 = ...
parent_model = (
p = ...,
fc = fc_parent,
gc = gc_parent,
models = (
model_1 = submodel_1,
model_2 = submodel_2,
)
)Now, parent_model has two submodels. Note, that submodels are not updated automatically. They have to be called by their parent model. Only the top-level model passed to simulate is actively updated by SimpleSim.jl.
For calling a submodel, use the @call! macro from within a gc function and add the input you want to give the submodel.
function gc_parent(x, u, p, t; models)
u_1 = ...
y_submodel_1 = @call! models.model_1 u_1
u_2 = ...
y_submodel_2 = @call! models.model_2 u_2
return ...
endCalls can only be made from within a gc function. Not from within an fc function. To obtain the current output of a submodel without updating it, use the @out macro.
function fc_parent(x, u, p, t; models)
y_1 = @out models.model_1
# ...
endDiscrete-time systems work in the same way. However, SimpleSim.jl makes sure that discrete-time models are only updated according to their update frequency.
Citing
If you used SimpleSim.jl in research and you are preparing a publication, please use the following BiBTeX entry:
@software{SimpleSim,
author = {H{\"u}hnerbein, Jannes},
title = {{S}imple{S}im.jl: {A} minimalist {J}ulia package for modular dynamical systems simulation},
url = {https://github.com/janneshb/SimpleSim.jl},
version = {0.1.4},
year = {2024},
month = {04},
}