Skip to main content

moldyn_core/
particle.rs

1//! This module contains the [Particle] struct.
2
3use crate::Vec3;
4use serde::{Deserialize, Serialize};
5
6/// A struct representing a particle record in the simulation.
7#[derive(Debug, PartialEq, Serialize, Deserialize, Clone, Copy, Default)]
8pub struct Particle {
9    /// Position of the particle in 3D space.
10    #[serde(default)]
11    position: Vec3,
12
13    /// Velocity of the particle in 3D space.
14    #[serde(default)]
15    velocity: Vec3,
16
17    /// Force effective on the particle in 3D space.
18    #[serde(default, skip_deserializing)]
19    force: Vec3,
20
21    /// Force which was effective on the particle in the previous time step.
22    #[serde(default, skip_deserializing)]
23    old_force: Vec3,
24
25    /// Mass of the particle.
26    #[serde(default)]
27    mass: f64,
28}
29
30impl Particle {
31    /// [Particle] constructor from position, initial velocity and mass.
32    pub fn from_data(position: Vec3, velocity: Vec3, mass: f64) -> Self {
33        Self {
34            position,
35            velocity,
36            mass,
37            ..Default::default()
38        }
39    }
40
41    /// Returns the current force of the particle.
42    pub fn get_force(&self) -> Vec3 {
43        self.force
44    }
45
46    /// Propagates the current force to the old force. This has to be called
47    /// every time step before invoking [Particle::apply_force] to apply new
48    /// forces.
49    pub fn delay_force(&mut self) {
50        self.old_force = self.force;
51        self.force = Vec3::zero();
52    }
53
54    /// Applies the given force to the particle (addition). It assumes that the
55    /// force was reset with [Particle::delay_force] in a timestep.
56    pub fn apply_force(&mut self, force: Vec3) {
57        self.force += force;
58    }
59
60    /// Returns the current position of the particle.
61    pub fn get_position(&self) -> Vec3 {
62        self.position
63    }
64
65    /// Calculate the updated position of the particle given a delta time step.
66    /// This functionality is constant across different simulation algorithms,
67    /// so it is implemented here.
68    pub fn update_position(&mut self, delta_time: f64) {
69        self.position +=
70            self.velocity * delta_time + self.force * (delta_time.powi(2) / (2.0 * self.mass));
71    }
72
73    /// Returns the current velocity of the particle.
74    pub fn get_velocity(&self) -> Vec3 {
75        self.velocity
76    }
77
78    /// Calculate the updated velocity of the particle given a delta time step.
79    /// This functionality is constant across different simulation algorithms,
80    /// so it is implemented here.
81    pub fn update_velocity(&mut self, delta_time: f64) {
82        self.velocity += (self.force + self.old_force) * (delta_time / (2.0 * self.mass));
83    }
84
85    /// Returns the constant mass of the particle.
86    pub fn get_mass(&self) -> f64 {
87        self.mass
88    }
89
90    /// Calculate the vector difference between two particles' positions. Note
91    /// that the order of the particles affects the sign.
92    ///
93    /// - `direction(a, b) == -direction(b, a)`.
94    pub fn position_difference(particle1: &Particle, particle2: &Particle) -> Vec3 {
95        particle1.position - particle2.position
96    }
97
98    /// Calculate the normalized vector difference between two particles' positions.
99    /// Note that the order of the particles affects the sign.
100    ///
101    /// - If result is `Some`: `direction(a, b) == -direction(b, a)`.
102    /// - If result is `None`: `direction(a, b) == direction(b, a) == None`.
103    pub fn direction(particle1: &Particle, particle2: &Particle) -> Option<Vec3> {
104        Particle::position_difference(particle1, particle2).normal()
105    }
106
107    /// Calculate the distance between two particles' positions. This function is
108    /// symmetric:
109    ///
110    /// - `distance(a, b) == distance(b, a)`.
111    pub fn distance(particle1: &Particle, particle2: &Particle) -> f64 {
112        Particle::position_difference(particle1, particle2).length()
113    }
114
115    /// Calculate the product of the masses of two particles.
116    pub fn mass_product(particle1: &Particle, particle2: &Particle) -> f64 {
117        particle1.mass * particle2.mass
118    }
119}
120
121/// These methods are used for creating particles in tests.
122#[cfg(test)]
123impl Particle {
124    /// Creates a particle at the given position with zero velocity.
125    pub fn at(x: f64, y: f64, z: f64) -> Self {
126        Self {
127            position: Vec3::new(x, y, z),
128            ..Default::default()
129        }
130    }
131
132    /// Builder method for testing. Manually sets the velocity of the particle
133    /// to the given value.
134    pub fn with_velocity(mut self, x: f64, y: f64, z: f64) -> Self {
135        self.velocity = Vec3::new(x, y, z);
136        self
137    }
138
139    /// Builder method for testing. Manually sets the force of the particle
140    /// to the given value.
141    pub fn with_force(mut self, x: f64, y: f64, z: f64) -> Self {
142        self.force = Vec3::new(x, y, z);
143        self
144    }
145
146    /// Builder method for testing. Manually sets the mass of the particle
147    /// to the given value.
148    pub fn with_mass(mut self, mass: f64) -> Self {
149        self.mass = mass;
150        self
151    }
152}
153
154// TODO tests for [update_velocity], [update_position], and [delay_force]