Crates.io | sssf |
lib.rs | sssf |
version | 0.1.0 |
source | src |
created_at | 2021-07-16 19:52:12.135219 |
updated_at | 2021-07-16 19:52:12.135219 |
description | Super simple simulation framework. |
homepage | |
repository | https://github.com/zylkowski/sssf |
max_upload_size | |
id | 423770 |
size | 17,129 |
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:
BodyParameters{
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.
EnvironmentParameters{
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.);
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:
simulation.to_file(PathBuf::from(r"output.csv"))
.run_with_save(stop_fn);