Skip to content

Commit

Permalink
Format doc comments
Browse files Browse the repository at this point in the history
  • Loading branch information
Canleskis committed Jan 14, 2024
1 parent 03fe524 commit 078bef2
Show file tree
Hide file tree
Showing 11 changed files with 279 additions and 174 deletions.
113 changes: 76 additions & 37 deletions particular/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,32 +8,48 @@
[![Crates.io](https://img.shields.io/crates/v/particular)](https://crates.io/crates/particular)
[![Docs](https://docs.rs/particular/badge.svg)](https://docs.rs/particular)

Particular is a crate providing a simple way to simulate N-body gravitational interaction of particles in Rust.
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.
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.

Particular is also built with performance in mind and provides multiple ways of computing the acceleration between particles.
Particular is also built with performance in mind and provides multiple ways of computing the
acceleration between particles.

### Computation algorithms

There are currently 2 algorithms used by the available compute methods: [Brute-force](https://en.wikipedia.org/wiki/N-body_problem#Simulation) and [Barnes-Hut](https://en.wikipedia.org/wiki/Barnes%E2%80%93Hut_simulation).
There are currently 2 algorithms used by the available compute methods:
[Brute-force](https://en.wikipedia.org/wiki/N-body_problem#Simulation) and
[Barnes-Hut](https://en.wikipedia.org/wiki/Barnes%E2%80%93Hut_simulation).

Generally speaking, the Brute-force algorithm is more accurate, but slower. The Barnes-Hut algorithm allows trading accuracy for speed by increasing the `theta` parameter.
Generally speaking, the Brute-force algorithm is more accurate, but slower. The Barnes-Hut
algorithm allows trading accuracy for speed by increasing the `theta` parameter.
You can see more about their relative performance [here](https://particular.rs/benchmarks/).

Particular uses [rayon](https://github.com/rayon-rs/rayon) for parallelization and [wgpu](https://github.com/gfx-rs/wgpu) for GPU computation. Enable the respective `parallel` and `gpu` features to access the available compute methods.
Particular uses [rayon](https://github.com/rayon-rs/rayon) for parallelization and
[wgpu](https://github.com/gfx-rs/wgpu) for GPU computation.
Enable the respective `parallel` and `gpu` features to access the available compute methods.

## Using Particular

Particular consists of two "modules", a "backend" that takes care of the abstraction of the computation of the gravitational forces between bodies for different floating-point types and dimensions, and a "frontend" that facilitates usage of that abstraction for user-defined and non-user-defined types. For most simple use cases, the latter is all that you need to know about.
Particular consists of two "modules", a "backend" that takes care of the abstraction of the
computation of the gravitational forces between bodies for different floating-point types and
dimensions, and a "frontend" that facilitates usage of that abstraction for user-defined and
non-user-defined types. For most simple use cases, the latter is all that you need to know
about.

### Simple usage

The [`Particle`] trait provides the main abstraction layer between the internal representation of the position and mass of an object in N-dimensional space and external types by defining methods to retrieve a position and a gravitational parameter.
These methods respectively return an array of scalars and a scalar, which are converted using the [point_mass] method to interface with the backend of Particular.
The [`Particle`] trait provides the main abstraction layer between the internal representation
of the position and mass of an object in N-dimensional space and external types by defining
methods to retrieve a position and a gravitational parameter.
These methods respectively return an array of scalars and a scalar, which are converted using
the [point_mass] method to interface with the backend of Particular.

#### Implementing the [`Particle`] trait

Expand Down Expand Up @@ -70,14 +86,15 @@ impl Particle for Body {
fn position(&self) -> [f32; 3] {
self.position.into()
}

fn mu(&self) -> f32 {
self.mass * G
}
}
```

If you can't implement [`Particle`] on a type, you can use the fact that it is implemented for tuples of an array and its scalar type instead of creating an intermediate type.
If you can't implement [`Particle`] on a type, you can use the fact that it is implemented for
tuples of an array and its scalar type instead of creating an intermediate type.

```rust
let particle = ([1.0, 1.0, 0.0], 5.0);
Expand All @@ -88,8 +105,12 @@ assert_eq!(particle.mu(), 5.0);

#### Computing and using the gravitational acceleration

In order to compute the accelerations of your particles, you can use the [accelerations] method on iterators, passing in a mutable reference to a [`ComputeMethod`] of your choice. It returns the acceleration of each iterated item, preserving the original order.
Because it collects the mapped particles in a [`ParticleReordered`] in order to optimise the computation of forces of massless particles, this method call results in one additional allocation. See the [advanced usage](#advanced-usage) section for information on how to opt out.
In order to compute the accelerations of your particles, you can use the [accelerations] method
on iterators, passing in a mutable reference to a [`ComputeMethod`] of your choice. It returns
the acceleration of each iterated item, preserving the original order.
Because it collects the mapped particles in a [`ParticleReordered`] in order to optimise the
computation of forces of massless particles, this method call results in one additional
allocation. See the [advanced usage](#advanced-usage) section for information on how to opt out.

##### When the iterated type implements [`Particle`]

Expand All @@ -104,7 +125,7 @@ for (acceleration, body) in bodies.iter().accelerations(&mut cm).zip(&mut bodies

```rust
// Items are a tuple of a velocity, a position and a mass.
// We map them to a tuple of the positions as an array and the mu,
// We map them to a tuple of the positions as an array and the mu,
// since this implements `Particle`.
let accelerations = items
.iter()
Expand All @@ -119,16 +140,46 @@ for (acceleration, (velocity, position, _)) in accelerations.zip(&mut items) {

### Advanced usage

The "frontend" is built on top of the "backend" but in some instances the abstraction provided by the frontend might not be flexible enough. For example, you might need to access the tree built from the particles for the Barnes-Hut algorithm, want to compute the gravitational forces between two distinct collections of particles, or both at the same time.
In that case, you can use the backend directly by calling the [compute] method on a struct implementing [`ComputeMethod`], passing in an appropriate storage.
The "frontend" is built on top of the "backend" but in some instances the abstraction provided
by the frontend might not be flexible enough. For example, you might need to access the tree
built from the particles for the Barnes-Hut algorithm, want to compute the gravitational forces
between two distinct collections of particles, or both at the same time.
In that case, you can use the backend directly by calling [compute] on a struct implementing
[`ComputeMethod`], passing in an appropriate storage.

#### The [`PointMass`] type

The underlying type used in storages is the [`PointMass`], a simple representation in
N-dimensional space of a position and a gravitational parameter. Instead of going through a
[`ComputeMethod`], you can directly use the different generic methods available to compute the
gravitational forces between [`PointMass`]es, with variants optimised for scalar and simd types.

##### Example

```rust
use storage::PointMass;

let p1 = PointMass::new(Vec2::new(0.0, 1.0), 1.0);
let p2 = PointMass::new(Vec2::new(0.0, 0.0), 1.0);

assert_eq!(p1.acceleration_scalar::<false>(&p2), Vec2::new(0.0, -1.0));
```

#### Storages and built-in [`ComputeMethod`] implementations

Storages are containers that make it easy to apply certain optimisation or algorithms on collections of particles when computing their gravitational acceleration.
Storages are containers that make it easy to apply certain optimisation or algorithms on
collections of particles when computing their gravitational acceleration.

The [`ParticleSystem`] storage defines an `affected` slice of particles and a `massive` storage, allowing algorithms to compute gravitational forces the particles in the `massive` storage exert on the `affected` particles. It is used to implement most compute methods, and blanket implementations with the other storages allow a [`ComputeMethod`] implemented with [`ParticleSliceSystem`] or [`ParticleTreeSystem`] to also be implemented with the other storages.
The [`ParticleSystem`] storage defines an `affected` slice of particles and a `massive` storage,
allowing algorithms to compute gravitational forces the particles in the `massive` storage exert
on the `affected` particles. It is used to implement most compute methods, and blanket
implementations with the other storages allow a [`ComputeMethod`] implemented with
[`ParticleSliceSystem`] or [`ParticleTreeSystem`] to also be implemented with the other
storages.

The [`ParticleReordered`] similarly defines a slice of particles, but stores a copy of them in a [`ParticleOrdered`]. These two storages make it easy for algorithms to skip particles with no mass when computing the gravitational forces of particles.
The [`ParticleReordered`] similarly defines a slice of particles, but stores a copy of them in a
[`ParticleOrdered`]. These two storages make it easy for algorithms to skip particles with no
mass when computing the gravitational forces of particles.

##### Example

Expand Down Expand Up @@ -162,7 +213,10 @@ let accelerations = bh.compute(ParticleSystem {

#### Custom [`ComputeMethod`] implementations

In order to work with the highest number of cases, built-in compute method implementations may not be the most appropriate for your specific use case. You can implement the [`ComputeMethod`] trait on your own type to satisfy your specific requirements but also if you want to implement other algorithms.
In order to work with the highest number of cases, built-in compute method implementations may
not be the most appropriate or optimised for your specific use case. You can implement the
[`ComputeMethod`] trait on your own type to satisfy your specific requirements but also if you
want to implement other algorithms.

##### Example

Expand All @@ -177,29 +231,14 @@ impl ComputeMethod<ParticleReordered<'_, Vec3, f32>> for MyComputeMethod {
#[inline]
fn compute(&mut self, storage: ParticleReordered<Vec3, f32>) -> Self::Output {
// Only return the accelerations of the massless particles.
sequential::BruteForceScalar.compute(ParticleSliceSystem {
sequential::BruteForceScalar.compute(ParticleSystem {
affected: storage.massless(),
massive: storage.massive(),
})
}
}
```

#### The [`PointMass`] type

The underlying type used in storages is the [`PointMass`], a simple representation in N-dimensional space of a position and a gravitational parameter. Instead of going through a [`ComputeMethod`], you can directly use the different generic methods available to compute the gravitational forces between [`PointMass`]es, with variants optimised for scalar and simd types.

##### Example

```rust
use storage::PointMass;

let p1 = PointMass::new(Vec2::new(0.0, 1.0), 1.0);
let p2 = PointMass::new(Vec2::new(0.0, 0.0), 1.0);

assert_eq!(p1.acceleration_scalar::<false>(&p2), Vec2::new(0.0, -1.0));
```

## License

This project is licensed under either of [Apache License, Version 2.0](https://github.com/Canleskis/particular/blob/main/LICENSE-APACHE) or [MIT license](https://github.com/Canleskis/particular/blob/main/LICENSE-MIT), at your option.
Expand Down
7 changes: 4 additions & 3 deletions particular/src/compute_method/gpu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,11 @@ impl Default for GpuData {

/// Brute-force [`ComputeMethod`] using the GPU with [wgpu](https://github.com/gfx-rs/wgpu).
///
/// Currently only available for 3D f32 vectors. You can still use it in 2D by converting your 2D f32 vectors to 3D f32 vectors.
/// Currently only available for 3D f32 vectors. You can still use it in 2D by converting your 2D
/// f32 vectors to 3D f32 vectors.
pub struct BruteForce<'a> {
/// Instanced [`GpuData`] used for the computation. It **should not** be recreated for every iteration.
/// Doing so would result in significantly reduced performance.
/// Instanced [`GpuData`] used for the computation. It **should not** be recreated for every
/// iteration. Doing so would result in significantly reduced performance.
pub gpu_data: &'a mut GpuData,
/// [`wgpu::Device`] used for the computation.
pub device: &'a wgpu::Device,
Expand Down
3 changes: 2 additions & 1 deletion particular/src/compute_method/gpu_compute/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ pub struct WgpuResources {
}

impl WgpuResources {
/// Creates a new [`WgpuResources`] with the given [`wgpu::Device`] and count of affected and massive particles.
/// Creates a new [`WgpuResources`] with the given [`wgpu::Device`] and count of affected and
/// massive particles.
#[inline]
pub fn init(device: &wgpu::Device, affected_count: usize, massive_count: usize) -> Self {
let affected_size = (std::mem::size_of::<PointMass>() * affected_count) as u64;
Expand Down
4 changes: 2 additions & 2 deletions particular/src/compute_method/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
pub mod gpu_compute;
/// Trait abstractions for generic vectors and associated floating-point numbers.
pub mod math;
/// Representation of the position and mass of an object in N-dimensional space and collections used by
/// built-in [`ComputeMethods`](crate::compute_method::ComputeMethod).
/// Representation of the position and mass of an object in N-dimensional space and collections used
/// by built-in [`ComputeMethod`] implementations.
pub mod storage;
/// Tree, bounding box and BarnesHut implementation details.
pub mod tree;
Expand Down
13 changes: 9 additions & 4 deletions particular/src/compute_method/parallel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ use crate::compute_method::{
};
use rayon::iter::{IntoParallelRefIterator, ParallelIterator};

/// Brute-force [`ComputeMethod`] using the CPU in parallel with [rayon](https://github.com/rayon-rs/rayon) and scalar vectors.
/// Brute-force [`ComputeMethod`] using the CPU in parallel with
/// [rayon](https://github.com/rayon-rs/rayon) and scalar vectors.
#[derive(Clone, Copy, Default)]
pub struct BruteForceScalar;

Expand All @@ -30,7 +31,8 @@ where
}
}

/// Brute-force [`ComputeMethod`] using the CPU in parallel with [rayon](https://github.com/rayon-rs/rayon) and simd vectors.
/// Brute-force [`ComputeMethod`] using the CPU in parallel with
/// [rayon](https://github.com/rayon-rs/rayon) and simd vectors.
#[derive(Clone, Copy, Default)]
pub struct BruteForceSIMD<const L: usize>;

Expand Down Expand Up @@ -60,10 +62,13 @@ where
}
}

/// [Barnes-Hut](https://en.wikipedia.org/wiki/Barnes%E2%80%93Hut_simulation) [`ComputeMethod`] using the CPU in parallel with [rayon](https://github.com/rayon-rs/rayon) for the force computation and scalar vectors.
/// [Barnes-Hut](https://en.wikipedia.org/wiki/Barnes%E2%80%93Hut_simulation) [`ComputeMethod`]
/// using the CPU in parallel with [rayon](https://github.com/rayon-rs/rayon) for the force
/// computation and scalar vectors.
#[derive(Clone, Copy, Default)]
pub struct BarnesHut<S> {
/// Parameter ruling the accuracy and speed of the algorithm. If 0, behaves the same as [`BruteForceScalar`].
/// Parameter ruling the accuracy and speed of the algorithm. If 0, behaves the same as
/// [`BruteForceScalar`].
pub theta: S,
}

Expand Down
9 changes: 6 additions & 3 deletions particular/src/compute_method/sequential.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,8 @@ where

/// Brute-force [`ComputeMethod`] using the CPU and scalar vectors.
///
/// Typically faster than [`BruteForceScalar`] because it computes the acceleration over the combination of pairs of particles instead of all the pairs.
/// Typically faster than [`BruteForceScalar`] because it computes the acceleration over the
/// combination of pairs of particles instead of all the pairs.
#[derive(Clone, Copy, Default)]
pub struct BruteForcePairs;

Expand Down Expand Up @@ -152,10 +153,12 @@ where
}
}

/// [Barnes-Hut](https://en.wikipedia.org/wiki/Barnes%E2%80%93Hut_simulation) [`ComputeMethod`] using the CPU and scalar vectors.
/// [Barnes-Hut](https://en.wikipedia.org/wiki/Barnes%E2%80%93Hut_simulation) [`ComputeMethod`]
/// using the CPU and scalar vectors.
#[derive(Clone, Copy, Default)]
pub struct BarnesHut<S> {
/// Parameter ruling the accuracy and speed of the algorithm. If 0, behaves the same as [`BruteForceScalar`].
/// Parameter ruling the accuracy and speed of the algorithm. If 0, behaves the same as
/// [`BruteForceScalar`].
pub theta: S,
}

Expand Down
Loading

0 comments on commit 078bef2

Please sign in to comment.