1 unstable release

0.1.0 Jul 16, 2021

#164 in Simulation

Custom license

190 lines

SSSF - Super Simple Simulation Framework

It's crate that lets you write Rust physics simulations without boring and repetetive boilerplate. I created this for my personal usage because of my Uni classes in which we wrote lots of physical simulations but now I want to share because I think it's pretty elegant and may be useful for people in similar situations. It's well suited for small physical simulations but I can see it usefull in bigger projects too, although in bigger projects it may be a good idea to create something tailored for your problem.


Let's say you would like to build simulation of body moving with constant velocity on x-y plane. Then you need to create struct that holds information about that body in for each timestep:

  t: f32          // time,
  x: f32,
  y: f32,
  v_x: f32,
  v_y: f32,

These parameters will be stored as history after EACH timestep, that's why we added t field in BodyParameters to keep track of history of position (x,y) and velocity (V_x,v_y) changing in time. Now that we took care of parameters that we want to save history of we can move to those parameters that are either constant throughout whole simulation or those that we don't need/want to save history of - for example we wouldn't like to save whole grid for Conway's Game of Life for each timestep - we would hold it in parameters without history. So coming back to the example of moving body. We can think of those parameters as environment that we don't want to track.

  dt: f32

For purpose of this example we don't need to store there anything complicated so lets just store dt - delta time. After that we can start our simulation providing BodyParameters, EnvironmentParameters and step function for simulation like this:

let mut simulation = SimManger::new(
    BodyParameters { t: 0., x: 0., y:0., v_x:1., v_y:1. },                      // Initial parameters for the body
    EnvironmentParameters { dt: 0.1 },
    |body_parameteres, environment_parameters| BodyParameters {                 // Defining step function
        t: body_parameters.t + environment_parameters.dt,
        x: body_parameters.x + environment_parameters.dt * body_parameters.v_x,
        y: body_parameters.y + environment_parameters.dt * body_parameters.v_y,
        v_x: body_parameters.v_x,
        v_y: body_parameters.v_y,

As shown above you need to provide step_fn: FnMut(&BodyParameters, &mut EnvironmentParameters) ->BodyParameters it has to return BodyParameters after this simulation step, they will be automatically saved. Now in order to run the simulation you call .run(stop_fn) where stop_fn: Fn(&DynamicParameters, &EnvironmentParameters) -> bool it's simple function that is checked after each timestep. If it's true, simulation stops. Eg.
If you want to stop simulation when t = 5 s you call:
simulation.run(|body_parameters, _environment_parameters| body_parameters.t == 5.);
If you want to stop simulation when x = 20 m you call:
simulation.run(|body_parameters, _environment_parameters| body_parameters.x == 20.);

Writing to file

It's very useful to save simulation history to file to analyze it further, make some beautiful plots etc. In this case you would like to implement ToCSV trait for BodyParameters. I didn't want to use serde for serialization because I want this framework to be super lightweight and it's not really necessary. You can implement ToCSV like this:

impl ToCSV for BodyParameters {
    fn get_header() -> String {
        String::from("t,x,y")             // Header for .csv file (don't contain "\n" in this string, it's added automatically)
    fn get_row(&self) -> String {
        format!("{},{},{}", self.t, self.x, self.y)     // Contents of .csv file (don't contain "\n" in this string, it's added automatically)

Then insted of calling .run(stop_fn) you have to call like this: