particular/lib.rs
1//! # Particular
2//!
3//! Particular is a crate providing a simple way to simulate N-body gravitational interaction of
4//! particles in Rust.
5//!
6//! ## Goals
7//!
8//! The main goal of this crate is to provide users with a simple API to set up N-body gravitational
9//! simulations that can easily be integrated into existing game and physics engines. Thus it does
10//! not concern itself with numerical integration or other similar tools and instead only focuses on
11//! the acceleration calculations.
12//!
13//! Particular is also built with performance in mind and provides multiple ways of computing the
14//! acceleration between particles.
15//!
16//! ### Computation algorithms
17//!
18//! There are currently 2 algorithms used by the available compute methods:
19//! [Brute-force](https://en.wikipedia.org/wiki/N-body_problem#Simulation) and
20//! [Barnes-Hut](https://en.wikipedia.org/wiki/Barnes%E2%80%93Hut_simulation).
21//!
22//! Generally speaking, the Brute-force algorithm is more accurate, but slower. The Barnes-Hut
23//! algorithm allows trading accuracy for speed by increasing the `theta` parameter.
24//! You can see more about their relative performance [here](https://particular.rs/benchmarks/).
25//!
26//! Particular uses [rayon](https://github.com/rayon-rs/rayon) for parallelization and
27//! [wgpu](https://github.com/gfx-rs/wgpu) for GPU computation.
28//! Enable the respective `parallel` and `gpu` features to access the available compute methods.
29//!
30//! ## Using Particular
31//!
32//! Particular consists of two "modules", one that takes care of the abstraction of the computation
33//! of the gravitational forces between bodies for different floating-point types and dimensions,
34//! and one that facilitates usage of that abstraction for user-defined andnon-user-defined types.
35//! For most simple use cases, the latter is all that you need to know about.
36//!
37//! ### Simple usage
38//!
39//! The [`Particle`] trait provides the main abstraction layer between the internal representation
40//! of the position and mass of an object in N-dimensional space and external types by defining
41//! methods to retrieve a position and a gravitational parameter.
42//! These methods respectively return an array of scalars and a scalar, which are converted using
43//! the [point_mass] method to interface with the underlying algorithm implementations.
44//!
45//! #### Implementing the [`Particle`] trait
46//!
47//! When possible, it can be useful to implement [`Particle`] on a type.
48//!
49//! ##### Deriving
50//!
51//! Used when the type has fields named `position` and `mu`:
52//!
53//! ```
54//! # use particular::prelude::*;
55//! # use ultraviolet::Vec3;
56//! #[derive(Particle)]
57//! #[dim(3)]
58//! struct Body {
59//! position: Vec3,
60//! mu: f32,
61//! // ...
62//! }
63//! ```
64//!
65//! ##### Manual implementation
66//!
67//! Used when the type does not directly provide a position and a gravitational parameter.
68//!
69//! ```
70//! # const G: f32 = 1.0;
71//! # use particular::prelude::*;
72//! # use ultraviolet::Vec3;
73//! struct Body {
74//! position: Vec3,
75//! mass: f32,
76//! // ...
77//! }
78//!
79//! impl Particle for Body {
80//! type Array = [f32; 3];
81//!
82//! fn position(&self) -> [f32; 3] {
83//! self.position.into()
84//! }
85//!
86//! fn mu(&self) -> f32 {
87//! self.mass * G
88//! }
89//! }
90//! ```
91//!
92//! If you can't implement [`Particle`] on a type, you can use the fact that it is implemented for
93//! tuples of an array and its scalar type instead of creating an intermediate type.
94//!
95//! ```
96//! # use particular::prelude::*;
97//! let particle = ([1.0, 1.0, 0.0], 5.0);
98//!
99//! assert_eq!(particle.position(), [1.0, 1.0, 0.0]);
100//! assert_eq!(particle.mu(), 5.0);
101//! ```
102//!
103//! #### Computing and using the gravitational acceleration
104//!
105//! In order to compute the accelerations of your particles, you can use the [accelerations] method
106//! on iterators, passing in a mutable reference to a [`ComputeMethod`] of your choice. It returns
107//! the acceleration of each iterated item, preserving the original order.
108//! Because it collects the mapped particles in a [`ParticleReordered`] in order to optimise the
109//! computation of forces of massless particles, this method call results in one additional
110//! allocation. See the [advanced usage](#advanced-usage) section for information on how to opt out.
111//!
112//! ##### When the iterated type implements [`Particle`]
113//!
114//! ```
115//! # use particular::prelude::*;
116//! # use ultraviolet::Vec3;
117//! # const DT: f32 = 1.0 / 60.0;
118//! # let mut cm = sequential::BruteForceScalar;
119//! # #[derive(Particle)]
120//! # #[dim(3)]
121//! # struct Body {
122//! # position: Vec3,
123//! # velocity: Vec3,
124//! # mu: f32,
125//! # }
126//! # let mut bodies = Vec::<Body>::new();
127//! for (acceleration, body) in bodies.iter().accelerations(&mut cm).zip(&mut bodies) {
128//! body.velocity += Vec3::from(acceleration) * DT;
129//! body.position += body.velocity * DT;
130//! }
131//! ```
132//!
133//! ##### When the iterated type doesn't implement [`Particle`]
134//!
135//! ```
136//! # use particular::prelude::*;
137//! # use ultraviolet::Vec3;
138//! # const DT: f32 = 1.0 / 60.0;
139//! # const G: f32 = 1.0;
140//! # let mut cm = sequential::BruteForceScalar;
141//! # let mut items = vec![
142//! # (Vec3::zero(), -Vec3::one(), 5.0),
143//! # (Vec3::zero(), Vec3::zero(), 3.0),
144//! # (Vec3::zero(), Vec3::one(), 10.0),
145//! # ];
146//! // Items are a tuple of a velocity, a position and a mass.
147//! // We map them to a tuple of the positions as an array and the mu,
148//! // since this implements `Particle`.
149//! let accelerations = items
150//! .iter()
151//! .map(|(_, position, mass)| (*position.as_array(), *mass * G))
152//! .accelerations(&mut cm);
153//!
154//! for (acceleration, (velocity, position, _)) in accelerations.zip(&mut items) {
155//! *velocity += Vec3::from(acceleration) * DT;
156//! *position += *velocity * DT;
157//! }
158//! ```
159//!
160//! ### Advanced usage
161//!
162//! In some instances the iterator abstraction provided by particular might not be flexible enough.
163//! For example, you might need to access the tree built from the particles for the Barnes-Hut
164//! algorithm, want to compute the gravitational forces between two distinct collections of
165//! particles, or both at the same time.
166//!
167//! #### The [`PointMass`] type
168//!
169//! The underlying type used in storages is the [`PointMass`], a simple representation in
170//! N-dimensional space of a position and a gravitational parameter. Instead of going through a
171//! [`ComputeMethod`], you can directly use the different generic methods available to compute the
172//! gravitational forces between [`PointMass`]es, with variants optimised for scalar and simd types.
173//!
174//! ##### Example
175//!
176//! ```
177//! # use particular::prelude::*;
178//! use particular::math::Vec2;
179//!
180//! let p1 = PointMass::new(Vec2::new(0.0, 1.0), 1.0);
181//! let p2 = PointMass::new(Vec2::new(0.0, 0.0), 1.0);
182//! let softening = 0.0;
183//!
184//! assert_eq!(p1.force_scalar::<false>(p2.position, p2.mass, softening), Vec2::new(0.0, -1.0));
185//! ```
186//!
187//! #### Storages and built-in [`ComputeMethod`] implementations
188//!
189//! Storages are containers that make it easy to apply certain optimisation or algorithms on
190//! collections of particles when computing their gravitational acceleration.
191//!
192//! The [`ParticleSystem`] storage defines an `affected` slice of particles and a `massive` storage,
193//! allowing algorithms to compute gravitational forces the particles in the `massive` storage exert
194//! on the `affected` particles. It is used to implement most compute methods, and blanket
195//! implementations with the other storages allow a [`ComputeMethod`] implemented with
196//! [`ParticleSliceSystem`] or [`ParticleTreeSystem`] to also be implemented with the other
197//! storages.
198//!
199//! The [`ParticleReordered`] similarly defines a slice of particles, but stores a copy of them in a
200//! [`ParticleOrdered`]. These two storages make it easy for algorithms to skip particles with no
201//! mass when computing the gravitational forces of particles.
202//!
203//! ##### Example
204//!
205//! ```
206//! # use particular::prelude::*;
207//! use particular::math::Vec3;
208//!
209//! let particles = vec![
210//! // ...
211//! # PointMass::new(Vec3::new(-10.0, 0.0, 0.0), 5.0),
212//! # PointMass::new(Vec3::new(-5.0, 20.0, 0.0), 0.0),
213//! # PointMass::new(Vec3::new(-50.0, 0.0, 5.0), 0.0),
214//! # PointMass::new(Vec3::new(10.0, 5.0, 5.0), 10.0),
215//! # PointMass::new(Vec3::new(0.0, -5.0, 20.0), 0.0),
216//! ];
217//!
218//! // Create a `ParticleOrdered` to split massive and massless particles.
219//! let ordered = ParticleOrdered::from(&*particles);
220//!
221//! // Build a `ParticleTree` from the massive particles.
222//! let tree = ParticleTree::from(ordered.massive());
223//!
224//! // Do something with the tree.
225//! for (node, data) in std::iter::zip(&tree.get().nodes, &tree.get().data) {
226//! // ...
227//! }
228//!
229//! let bh = &mut sequential::BarnesHut { theta: 0.5 };
230//! // The implementation computes the acceleration exerted on the particles in
231//! // the `affected` slice.
232//! // As such, this only computes the acceleration of the massless particles.
233//! let accelerations = bh.compute(ParticleSystem {
234//! affected: ordered.massless(),
235//! massive: &tree,
236//! });
237//! ```
238//!
239//! #### Custom [`ComputeMethod`] implementations
240//!
241//! In order to work with the highest number of cases, built-in compute method implementations may
242//! not be the most appropriate or optimised for your specific use case. You can implement the
243//! [`ComputeMethod`] trait on your own type to satisfy your specific requirements but also if you
244//! want to implement other algorithms.
245//!
246//! ##### Example
247//!
248//! ```
249//! # use particular::prelude::*;
250//! use particular::math::Vec3;
251//!
252//! struct MyComputeMethod;
253//!
254//! impl ComputeMethod<ParticleReordered<'_, Vec3, f32>> for MyComputeMethod {
255//! type Output = Vec<Vec3>;
256//!
257//! #[inline]
258//! fn compute(&mut self, storage: ParticleReordered<Vec3, f32>) -> Self::Output {
259//! // Only return the accelerations of the massless particles.
260//! sequential::BruteForceScalar.compute(ParticleSystem {
261//! affected: storage.massless(),
262//! massive: storage.massive(),
263//! })
264//! }
265//! }
266//! ```
267//!
268//! [accelerations]: particle::Accelerations::accelerations
269//! [point_mass]: particle::IntoPointMass::point_mass
270//! [`Particle`]: particle::Particle
271//! [`ComputeMethod`]: compute_method::ComputeMethod
272//! [`ParticleReordered`]: compute_method::storage::ParticleReordered
273//! [`ParticleOrdered`]: compute_method::storage::ParticleOrdered
274//! [`ParticleSystem`]: compute_method::storage::ParticleSystem
275//! [`ParticleSliceSystem`]: compute_method::storage::ParticleSliceSystem
276//! [`ParticleTreeSystem`]: compute_method::storage::ParticleTreeSystem
277//! [`PointMass`]: compute_method::storage::PointMass
278
279#![warn(missing_docs)]
280
281/// Implementation of algorithms to compute the acceleration of particles.
282pub mod compute_method;
283/// Traits for particle representation of objects and computing their acceleration.
284pub mod particle;
285/// Built-in [`ComputeMethod`](crate::compute_method::ComputeMethod) implementations.
286pub mod compute_methods {
287 #[cfg(feature = "gpu")]
288 pub use crate::compute_method::gpu;
289 #[cfg(feature = "parallel")]
290 pub use crate::compute_method::parallel;
291 pub use crate::compute_method::sequential;
292}
293
294pub use compute_method::*;
295pub use particular_derive;
296
297/// Commonly used types, re-exported.
298pub mod prelude {
299 pub use crate::{
300 compute_method::{storage::*, ComputeMethod},
301 compute_methods::*,
302 particle::{Accelerations, IntoPointMass, Particle},
303 particular_derive::Particle,
304 };
305}