From 078bef2d51289007b1900ad945a1555b85d64758 Mon Sep 17 00:00:00 2001 From: Canleskis <91223490+Canleskis@users.noreply.github.com> Date: Sun, 14 Jan 2024 17:09:40 +0100 Subject: [PATCH] Format doc comments --- particular/README.md | 113 +++++++--- particular/src/compute_method/gpu.rs | 7 +- .../src/compute_method/gpu_compute/mod.rs | 3 +- particular/src/compute_method/mod.rs | 4 +- particular/src/compute_method/parallel.rs | 13 +- particular/src/compute_method/sequential.rs | 9 +- particular/src/compute_method/storage.rs | 61 ++--- particular/src/compute_method/tree/mod.rs | 15 +- .../src/compute_method/tree/partition.rs | 3 +- particular/src/lib.rs | 211 +++++++++++------- particular/src/particle.rs | 14 +- 11 files changed, 279 insertions(+), 174 deletions(-) diff --git a/particular/README.md b/particular/README.md index 145a91f..5143102 100644 --- a/particular/README.md +++ b/particular/README.md @@ -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 @@ -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); @@ -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`] @@ -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() @@ -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::(&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 @@ -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 @@ -177,7 +231,7 @@ impl ComputeMethod> for MyComputeMethod { #[inline] fn compute(&mut self, storage: ParticleReordered) -> Self::Output { // Only return the accelerations of the massless particles. - sequential::BruteForceScalar.compute(ParticleSliceSystem { + sequential::BruteForceScalar.compute(ParticleSystem { affected: storage.massless(), massive: storage.massive(), }) @@ -185,21 +239,6 @@ impl ComputeMethod> for MyComputeMethod { } ``` -#### 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::(&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. diff --git a/particular/src/compute_method/gpu.rs b/particular/src/compute_method/gpu.rs index 8f59f4d..3599215 100644 --- a/particular/src/compute_method/gpu.rs +++ b/particular/src/compute_method/gpu.rs @@ -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, diff --git a/particular/src/compute_method/gpu_compute/mod.rs b/particular/src/compute_method/gpu_compute/mod.rs index c5685ad..9ac1a51 100644 --- a/particular/src/compute_method/gpu_compute/mod.rs +++ b/particular/src/compute_method/gpu_compute/mod.rs @@ -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::() * affected_count) as u64; diff --git a/particular/src/compute_method/mod.rs b/particular/src/compute_method/mod.rs index ac2fd0d..735ea8d 100644 --- a/particular/src/compute_method/mod.rs +++ b/particular/src/compute_method/mod.rs @@ -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; diff --git a/particular/src/compute_method/parallel.rs b/particular/src/compute_method/parallel.rs index 65d4ad3..636625a 100644 --- a/particular/src/compute_method/parallel.rs +++ b/particular/src/compute_method/parallel.rs @@ -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; @@ -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; @@ -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 { - /// 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, } diff --git a/particular/src/compute_method/sequential.rs b/particular/src/compute_method/sequential.rs index de264a6..4603d04 100644 --- a/particular/src/compute_method/sequential.rs +++ b/particular/src/compute_method/sequential.rs @@ -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; @@ -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 { - /// 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, } diff --git a/particular/src/compute_method/storage.rs b/particular/src/compute_method/storage.rs index bdeea95..5de284a 100644 --- a/particular/src/compute_method/storage.rs +++ b/particular/src/compute_method/storage.rs @@ -39,7 +39,8 @@ impl PointMass { Self::new(V::new_lane(position), S::new_lane(mass)) } - /// Returns the [`PointMass`] corresponding to the center of mass and total mass of the given slice of point-masses. + /// Returns the [`PointMass`] corresponding to the center of mass and total mass of the given + /// slice of point-masses. #[inline] pub fn new_com(data: &[Self]) -> Self where @@ -112,11 +113,11 @@ impl PointMass { self.mass != S::ZERO } - /// Computes the gravitational force exerted on the current point-mass using the given position and mass, - /// provided `V` and `S` are scalar types. + /// Computes the gravitational force exerted on the current point-mass using the given position + /// and mass, provided `V` and `S` are scalar types. /// - /// If the position of the current point-mass is guaranteed to be different from the given position, - /// this computation can be more efficient with `CHECK_ZERO` set to false. + /// If the position of the current point-mass is guaranteed to be different from the given + /// position, this computation can be more efficient with `CHECK_ZERO` set to false. #[inline] pub fn force_mul_mass_scalar(&self, position: V, mass: S) -> V where @@ -137,8 +138,8 @@ impl PointMass { /// Computes the gravitational force exerted on the current point-mass by the given point-mass, /// provided `V` and `S` are scalar types. /// - /// If the position of the current point-mass is guaranteed to be different from the position of the given point-mass, - /// this computation can be more efficient with `CHECK_ZERO` set to false. + /// If the position of the current point-mass is guaranteed to be different from the position of + /// the given point-mass, this computation can be more efficient with `CHECK_ZERO` set to false. #[inline] pub fn force_scalar(&self, point_mass: &Self) -> V where @@ -148,11 +149,11 @@ impl PointMass { self.force_mul_mass_scalar::(point_mass.position, self.mass * point_mass.mass) } - /// Computes the gravitational acceleration exerted on the current point-mass by the given point-mass, - /// provided `V` and `S` are scalar types. + /// Computes the gravitational acceleration exerted on the current point-mass by the given + /// point-mass, provided `V` and `S` are scalar types. /// - /// If the position of the current point-mass is guaranteed to be different from the position of the given point-mass, - /// this computation can be more efficient with `CHECK_ZERO` set to false. + /// If the position of the current point-mass is guaranteed to be different from the position of + /// the given point-mass, this computation can be more efficient with `CHECK_ZERO` set to false. #[inline] pub fn acceleration_scalar(&self, point_mass: &Self) -> V where @@ -162,11 +163,11 @@ impl PointMass { self.force_mul_mass_scalar::(point_mass.position, point_mass.mass) } - /// Computes the gravitational force exerted on the current point-mass using the given position and mass, - /// provided `V` and `S` are SIMD types. + /// Computes the gravitational force exerted on the current point-mass using the given position + /// and mass, provided `V` and `S` are SIMD types. /// - /// If the position of the current point-mass is guaranteed to be different from the given position, - /// this computation can be more efficient with `CHECK_ZERO` set to false. + /// If the position of the current point-mass is guaranteed to be different from the given + /// position, this computation can be more efficient with `CHECK_ZERO` set to false. #[inline] pub fn force_mul_mass_simd(&self, position: V, mass: S) -> V where @@ -184,11 +185,12 @@ impl PointMass { } } - /// Computes the gravitational acceleration exerted on the current point-mass by the given point-mass, - /// provided `V` and `S` are SIMD types. + /// Computes the gravitational acceleration exerted on the current point-mass by the given + /// point-mass, provided `V` and `S` are SIMD types. /// - /// If the position of the current point-mass is guaranteed to be different from the position of the given point-mass, - /// this computation can be more efficient with `CHECK_ZERO` set to false. + /// If the position of the current point-mass is guaranteed to be different from the position of + /// the given point-mass, this computation can be more efficient with `CHECK_ZERO` set to + /// false. #[inline] pub fn force_simd(&self, point_mass: &Self) -> V where @@ -198,11 +200,12 @@ impl PointMass { self.force_mul_mass_simd::(point_mass.position, self.mass * point_mass.mass) } - /// Computes the gravitational acceleration exerted on the current point-mass by the given point-mass, - /// provided `V` and `S` are SIMD types. + /// Computes the gravitational acceleration exerted on the current point-mass by the given + /// point-mass, provided `V` and `S` are SIMD types. /// - /// If the position of the current point-mass is guaranteed to be different from the position of the given point-mass, - /// this computation can be more efficient with `CHECK_ZERO` set to false. + /// If the position of the current point-mass is guaranteed to be different from the position of + /// the given point-mass, this computation can be more efficient with `CHECK_ZERO` set to + /// false. #[inline] pub fn acceleration_simd(&self, point_mass: &Self) -> V where @@ -212,9 +215,9 @@ impl PointMass { self.force_mul_mass_simd::(point_mass.position, point_mass.mass) } - /// Computes the gravitational acceleration exerted on the current point-mass by the specified node of the given [`Orthtree`] following the - /// [Barnes-Hut](https://en.wikipedia.org/wiki/Barnes%E2%80%93Hut_simulation) approximation with the given `theta` parameter, - /// provided `V` and `S` are scalar types. + /// Computes the gravitational acceleration exerted on the current point-mass by the specified + /// node of the given [`Orthtree`] following the [Barnes-Hut](https://en.wikipedia.org/wiki/Barnes%E2%80%93Hut_simulation) + /// approximation with the given `theta` parameter, provided `V` and `S` are scalar types. #[inline] pub fn acceleration_tree( &self, @@ -257,7 +260,8 @@ impl PointMass { pub struct ParticleSystem<'p, V, S, T: ?Sized> { /// Particles for which the acceleration is computed. pub affected: &'p [PointMass], - /// Particles responsible for the acceleration exerted on the `affected` particles, in a storage `S`. + /// Particles responsible for the acceleration exerted on the `affected` particles, in a + /// storage `S`. pub massive: &'p T, } @@ -322,7 +326,8 @@ pub type ParticleTreeSystem<'p, const X: usize, const D: usize, V, S> = /// Storage inside of which the massive particles are placed before the massless ones. /// -/// Allows for easy optimisation of the computation of forces between massive and massless particles. +/// Allows for easy optimisation of the computation of forces between massive and massless +/// particles. #[derive(Clone, Debug)] pub struct ParticleOrdered { massive_len: usize, diff --git a/particular/src/compute_method/tree/mod.rs b/particular/src/compute_method/tree/mod.rs index 115b728..bbd32fc 100644 --- a/particular/src/compute_method/tree/mod.rs +++ b/particular/src/compute_method/tree/mod.rs @@ -15,7 +15,8 @@ pub struct Tree { /// Vector of generic `Data` objects that contain information about the associated `Node`. /// - /// The `data` vector is parallel to the `nodes` vector, so the `i`-th element of the `data` vector corresponds to the `i`-th element of the `nodes` vector. + /// The `data` vector is parallel to the `nodes` vector, so the `i`-th element of the `data` + /// vector corresponds to the `i`-th element of the `nodes` vector. pub data: Vec, } @@ -28,7 +29,8 @@ impl Tree { } } - /// Creates a new empty [`Tree`] with at least the specified capacity in the `nodes` and `data` vectors. + /// Creates a new empty [`Tree`] with at least the specified capacity in the `nodes` and `data` + /// vectors. pub fn with_capacity(capacity: usize) -> Self { Self { nodes: Vec::with_capacity(capacity), @@ -43,7 +45,8 @@ impl Default for Tree { } } -/// Node for trees that can either be internal and containing data or external and containing no data. +/// Node for trees that can either be internal and containing data or external and containing no +/// data. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Node { /// Node with child nodes. @@ -57,7 +60,8 @@ pub type Orthtree = Tree>, Data>; impl Orthtree { - /// Recursively inserts new [`Nodes`](Node) in the current [`Orthtree`] from the given input and functions until the created square bounding box stops subdividing. + /// Recursively inserts new [`Nodes`](Node) in the current [`Orthtree`] from the given input and + /// functions until the created square bounding box stops subdividing. pub fn build_node(&mut self, input: &[I], position: P, compute: C) -> Option where I: Copy, @@ -74,7 +78,8 @@ impl Orthtree { ) } - /// Recursively inserts new [`Nodes`](Node) in the current [`Orthtree`] from the given input and functions until the given bounding box stops subdividing. + /// Recursively inserts new [`Nodes`](Node) in the current [`Orthtree`] from the given input and + /// functions until the given bounding box stops subdividing. pub fn build_node_with( &mut self, bbox: BoundingBox<[S; D]>, diff --git a/particular/src/compute_method/tree/partition.rs b/particular/src/compute_method/tree/partition.rs index 626d16b..c750083 100644 --- a/particular/src/compute_method/tree/partition.rs +++ b/particular/src/compute_method/tree/partition.rs @@ -110,7 +110,8 @@ where Self: SubDivide, S: Copy + Float, { - /// Subdivides this [`BoundingBox`] into mutliple bounding boxes defined by [`SubDivide`] implementation. + /// Subdivides this [`BoundingBox`] into mutliple bounding boxes defined by [`SubDivide`] + /// implementation. #[inline] pub fn subdivide(&self) -> [Self; X] { let bbox_min = self.min; diff --git a/particular/src/lib.rs b/particular/src/lib.rs index 15a173a..6741e8c 100644 --- a/particular/src/lib.rs +++ b/particular/src/lib.rs @@ -1,40 +1,56 @@ //! # 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. -//! -//! Particular is also built with performance in mind and provides multiple ways of computing the acceleration between particles. -//! +//! +//! 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. +//! //! ### 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). -//! -//! 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. +//! +//! 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. //! 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 -//! +//! //! When possible, it can be useful to implement [`Particle`] on a type. -//! +//! //! ##### Deriving -//! +//! //! Used when the type has fields named `position` and `mu`: -//! +//! //! ``` //! # use particular::prelude::*; //! # use ultraviolet::Vec3; @@ -46,11 +62,11 @@ //! // ... //! } //! ``` -//! +//! //! ##### Manual implementation -//! +//! //! Used when the type does not directly provide a position and a gravitational parameter. -//! +//! //! ``` //! # const G: f32 = 1.0; //! # use particular::prelude::*; @@ -73,9 +89,10 @@ //! } //! } //! ``` -//! -//! 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. +//! //! ``` //! # use particular::prelude::*; //! let particle = ([1.0, 1.0, 0.0], 5.0); @@ -83,14 +100,18 @@ //! assert_eq!(particle.position(), [1.0, 1.0, 0.0]); //! 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`] -//! +//! //! ``` //! # use particular::prelude::*; //! # use ultraviolet::Vec3; @@ -109,9 +130,9 @@ //! body.position += body.velocity * DT; //! } //! ``` -//! +//! //! ##### When the iterated type doesn't implement [`Particle`] -//! +//! //! ``` //! # use particular::prelude::*; //! # use ultraviolet::Vec3; @@ -124,7 +145,7 @@ //! # (Vec3::zero(), Vec3::one(), 10.0), //! # ]; //! // 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() @@ -136,26 +157,60 @@ //! *position += *velocity * DT; //! } //! ``` -//! +//! //! ### 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 [compute] 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 the [compute] method 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 +//! +//! ``` +//! # use particular::prelude::*; +//! # use ultraviolet::Vec2; +//! 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::(&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. -//! -//! 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. -//! +//! +//! 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 [`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 -//! +//! //! ``` //! # use particular::prelude::*; -//! # use storage::{ParticleOrdered, ParticleSystem, ParticleTree, PointMass}; +//! # use storage::PointMass; //! # use ultraviolet::Vec3; +//! use storage::{ParticleOrdered, ParticleSystem, ParticleTree}; +//! //! let particles = vec![ //! // ... //! # PointMass::new(Vec3::new(-10.0, 0.0, 0.0), 5.0), @@ -164,18 +219,18 @@ //! # PointMass::new(Vec3::new(10.0, 5.0, 5.0), 10.0), //! # PointMass::new(Vec3::new(0.0, -5.0, 20.0), 0.0), //! ]; -//! +//! //! // Create a `ParticleOrdered` to split massive and massless particles. //! let ordered = ParticleOrdered::from(&*particles); -//! +//! //! // Build a `ParticleTree` from the massive particles. //! let tree = ParticleTree::from(ordered.massive()); -//! +//! //! // Do something with the tree. //! for (node, data) in std::iter::zip(&tree.get().nodes, &tree.get().data) { //! // ... //! } -//! +//! //! let bh = &mut sequential::BarnesHut { theta: 0.5 }; //! // The implementation computes the acceleration exerted on the particles in //! // the `affected` slice. @@ -185,23 +240,26 @@ //! massive: &tree, //! }); //! ``` -//! +//! //! #### Custom [`ComputeMethod`] implementations -//! -//! 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. -//! +//! +//! 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 -//! +//! //! ``` //! # use particular::prelude::*; //! # use ultraviolet::Vec3; //! use storage::{ParticleReordered, ParticleSystem}; -//! +//! //! struct MyComputeMethod; -//! +//! //! impl ComputeMethod> for MyComputeMethod { //! type Output = Vec; -//! +//! //! #[inline] //! fn compute(&mut self, storage: ParticleReordered) -> Self::Output { //! // Only return the accelerations of the massless particles. @@ -212,24 +270,7 @@ //! } //! } //! ``` -//! -//! #### 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 -//! -//! ``` -//! # use particular::prelude::*; -//! # use ultraviolet::Vec2; -//! 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::(&p2), Vec2::new(0.0, -1.0)); -//! ``` -//! +//! //! [accelerations]: particle::Accelerations::accelerations //! [point_mass]: particle::IntoPointMass::point_mass //! [compute]: compute_method::ComputeMethod::compute diff --git a/particular/src/particle.rs b/particular/src/particle.rs index 711a247..f65f307 100644 --- a/particular/src/particle.rs +++ b/particular/src/particle.rs @@ -5,7 +5,8 @@ use crate::compute_method::{ }; use std::vec::IntoIter; -/// Trait to describe a particle which consists of a [position](Particle::position) and a [gravitational parameter mu](Particle::mu). +/// Trait to describe a particle which consists of a [position](Particle::position) and a +/// [gravitational parameter mu](Particle::mu). /// /// #### Deriving: /// @@ -52,7 +53,8 @@ use std::vec::IntoIter; /// } /// ``` /// -/// 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. /// /// ``` /// # use particular::prelude::*; @@ -117,7 +119,8 @@ pub trait IntoPointMass: Particle + Sized { } impl IntoPointMass for P {} -/// Marker trait for [`ComputeMethod`]s implemented with a [`ParticleReordered`] storage for a [`Particle`] of type `P`. +/// Marker trait for [`ComputeMethod`]s implemented with a [`ParticleReordered`] storage for a +/// [`Particle`] of type `P`. pub trait ReorderedCompute

: for<'a> ComputeMethod< ParticleReordered<'a, ParticleVector

, ParticleScalar

>, @@ -167,8 +170,9 @@ where where A: ScalarArray, { - // If the array and its associated vector have the same layout (which is the case for all current implementations of `ScalarArray`), - // this doesn't actually allocate and is a no-op. + // If the array and its associated vector have the same layout (which is the case for + // all current implementations of `ScalarArray`), this doesn't actually + // allocate and is a no-op. vec.into_iter().map(A::from).collect() }