#gravity #n-body

particular

N-body simulation library written in Rust featuring BarnesHut and GPU accelerated algorithms

14 releases (4 breaking)

new 0.5.2 May 15, 2023
0.5.1 Mar 30, 2023
0.4.0 Feb 22, 2023
0.3.1 Nov 26, 2022
0.1.5 Aug 25, 2022

#13 in Simulation

Download history 10/week @ 2023-01-30 14/week @ 2023-02-06 18/week @ 2023-02-13 50/week @ 2023-02-20 4/week @ 2023-02-27 4/week @ 2023-03-06 4/week @ 2023-03-13 44/week @ 2023-03-20 32/week @ 2023-03-27 22/week @ 2023-04-03 13/week @ 2023-04-10 25/week @ 2023-04-17 8/week @ 2023-04-24 10/week @ 2023-05-01 34/week @ 2023-05-08 43/week @ 2023-05-15

95 downloads per month

MIT/Apache

56KB
1K SLoC

Particular

MIT/Apache 2.0 Crates.io Docs

Particular is a crate providing a simple way to simulate N-body gravitational interaction of particles in Rust.

Goals

The main goal of this crate is to provide users with a simple API to set up N-body gravitational simulations that can easily be integrated into existing game and physics engines. Thus it does not concern itself with numerical integration or other similar tools and instead only focuses on the acceleration calculations.

Multiple algorithms are available to compute the acceleration between particles as ComputeMethods.

Computation algorithms

ComputeMethod BruteForce BarnesHut
GPU
CPU single-threaded
CPU multi-threaded

Generally speaking, the BruteForce algoritm is more accurate, but slower. The BarnesHut algorithm allows trading accuracy for speed by increasing the theta parameter.
You can read more about their relative performance here.

Particular uses rayon for parallelization. Enable the "parallel" feature to access the available compute methods.

Particular uses wgpu for GPU computation. Enable the "gpu" feature to access the available compute methods.

Using Particular

Implementing the Particle trait

When possible, it can be useful to implement Particle on a type.

Deriving

Used when the type has fields named position and mu:

#[derive(Particle)]
struct Body {
    position: Vec3,
    mu: f32,
//  ...
}

Manual implementation

Used when the type cannot directly provide a position and a gravitational parameter.

struct Body {
    position: Vec3,
    mass: f32,
//  ...
}

impl Particle for Body {
    type Scalar = f32;

    type Vector = Vec3;
    
    fn position(&self) -> Vec3 {
        self.position
    }
    
    fn mu(&self) -> f32 {
        self.mass * G
    }
}

If you can't implement Particle on a type, you can almost certainly use the fact that it is implemented for tuples of a vector and its scalar type.

let particle = (Vec3::ONE, 5.0);

assert_eq!(particle.position(), Vec3::ONE);
assert_eq!(particle.mu(), 5.0);

Computing and using the gravitational acceleration

In order to get the acceleration of a type, you can use the accelerations or map_accelerations methods on iterators.
These effectively return the original iterator zipped with the acceleration of its items.

As such, you can create an iterator from a collection and get the acceleration using either methods depending on if the items implement Particle.

Pass a mutable reference to a ComputeMethod when calling either method.

Examples

When the iterated type doesn't implement Particle

// Items are a tuple of a velocity, a position and a mass.
// We map them to a tuple of the position and the mu, since this implements `Particle`.
let accelerations = items
    .iter_mut()
    .map_accelerations(|(_, position, mass)| (*position, *mass * G), &mut cm);

for ((velocity, position, _), acceleration) in accelerations {
    *velocity += acceleration * DT;
    *position += *velocity * DT;
}

When the iterated type implements Particle

for (body, acceleration) in bodies.iter_mut().accelerations(&mut cm) {
    body.velocity += acceleration * DT;
    body.position += body.velocity * DT;
}

Notes on performance

Particular is built with performance in mind and uses multiple ways of computing the acceleration between particles in the form of ComputeMethods.

Here is a comparison of the five current available compute methods on an i9 9900KF and an RTX 3080:

Performance chart

Depending on your needs and platform, you may opt for one compute method or another. You can also implement the trait on your own type to combine multiple compute methods and switch between them depending on certain conditions (e.g. the particle count).

Contribution

PRs are welcome!

Dependencies

~3–37MB
~646K SLoC