Hello everyone,
I’m very happy to announce my first larger Julia package SimpleSim.jl
which is now available on the Julia package registry!
SimpleSim.jl
aims at being a lightweight simulation engine 100% written in Julia with support for systems described by (continuous-time) ordinary differential equations or discrete-time processes.
Hence, the package can be used to simulate most physical and digital systems, which makes it perfect for robotics but also other areas.
Below, I will briefly describe how the package works for anyone only reading on Julia discourse.
If you want to learn more, please read the documentation. All features currently available are documented there in a lot more detail.
The package itself lives in this GitHub repo.
Overview of Features
- Simulation/numerical solutions of ordinary differential equations
- Integrated support for different ODE solvers (Euler, Heun, RK4, Adaptive RKF45)
- Simulation of discrete processes
- Simulation of models with both continous-time processes (ODEs) and discrete-time processes (“hybrid” models)
- Zero-crossing detection for continuous-time system states
- Random variable handling for repeatable results
- Support for nested model structures
How it works
The main point of interaction with the package is the simulate
function. Let’s say you have set up a model object my_model
that contains all of the physics and processes you want to simulate (more on that later). Then, simulating that model can be done by running
data = simulate(my_model, T = 10 // 1)
where T
denotes the total time the simulation will be run for.
The package does not export any types and only very few functions/macros. The whole “design process” of what a model could look like is left up to the user.
For example, if you want to simulate a continuous-time ODE system, SimpleSim.jl
only expects your model to have the named fields p
, fc
and yc
. The field p
can contain model parameters, or it can be set to nothing
if parameters aren’t needed.
fc
defines the models behavior. It should contain a function taking the arguments (x, u, p, t)
for the current state, input, parameters and time of the model, and returns the state derivative. yc
takes the same arguments, but returns the current system output.
For example, if you want to simulate a falling object without air resistance, i.e. a double integrator of the gravitational constant, a complete model and simulation looks like this:
falling_object = (
p = (g = 9.81,)
fc = (z, u, p, t) -> [z[2], -p.g],
yc = (z, u, p, t) -> z[1],
)
data = simulate(falling_object, T = 3 // 1, xc0 = [0, 0])
These few lines of code will create a valid SimpleSim.jl
model and will simulate it for 3 seconds.
The resulting data
object has the fields tcs
, xcs
and ycs
which contain the time, state and output evolution of the system.
If you want to simulate a more complex system, it may make sense to define your own types. Just make sure your model provides the necessary named fields as interfaces for SimpleSim.jl
.
If you are just using SimpleSim.jl
to numerically solve an ODE, then defining a model as a NamedTuple
(like above) works just fine.
Some Simple Examples
An extension of the “falling object” example from above is a ball bouncing down a flight of stairs. This example also makes use of SimpleSim.jl
’s zero-crossing detection feature.
An example of a discrete process would be the random walk below. This example also uses SimplSim.jl
’s random variable handling.
I have written quite a few more examples, which you can find in the project repo. I will make sure to document these examples a bit better soon. Especially the more complex and nested models SimpleSim.jl
is capable of handling are not very well documented at the moment.
Nested Models
A key feature of SimpleSim.jl
’s is its support for nested models. For example, if you want to simulate a whole airplane, you don’t have to set up a single differential equation that describes every aspect of the aircraft’s behavior. Instead, you model each component of the system separately and define separate models. Then, you use a supermodel that “calls” your components and makes sure they receive the correct inputs. Nesting on multiple levels is also possible.
The support for nested models may be SimpleSim.jl
’s most powerful feature. It allows you to implement models in a very similar way to how you would draw block diagrams. If you want to learn more about this, please read the documentation and take a look at the examples.
What I still want to improve
All core features are implemented and fully functional (I think). But of course there are still things that I would like to improve and you are more than welcome to contribute.
I have listed a few ideas for future work on the issues page on GitHub.
If you have read all the way until here, let me know what you think!
Best,
Jannes