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 ...
end
Calls 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
# ...
end
Discrete-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},
}