Skip to main content

moldyn_core/simulation/
mod.rs

1//! TODO document
2
3mod args;
4mod sum;
5
6use crate::{Force, Particle};
7pub use args::SimulationArgs;
8use serde::{Deserialize, Serialize, de::Visitor};
9use std::sync::Arc;
10pub use sum::DirectSum;
11
12// to self: tried to keep dyn-compatibility. following approaches failed:
13// - fn ...(... impl Fn) is technically generic
14// - type PairIter = ... is also generic/ typed
15// - returning `Iter` and `IterMut` works for `particles` (`particles_mut`)
16//   but `particle_pairs` had implementation problems returning slide::IntoIter
17
18/// An interface-level abstraction of a molecular dynamics simulation. A
19/// [Simulation] is a method of organizing the particles and forces in a way
20/// that allows for efficient computation.
21pub trait Simulation {
22    /// # Returns
23    ///
24    /// Name of the simulation system, which is used for serialization and
25    /// deserialization. The characters are expected to be in `lowercase`.
26    fn system_name(&self) -> &str;
27
28    fn particles(&self) -> &[Particle];
29
30    fn particles_mut(&mut self) -> &mut [Particle];
31
32    /// Invokes a lambda callback for each particle in the simulation.
33    fn for_each_particles(&self, f: &mut dyn FnMut(&Particle)) {
34        for part in self.particles().iter() {
35            f(part);
36        }
37    }
38
39    /// Invokes a lambda callback for each particle (mutable) in the simulation.
40    fn for_each_particles_mut(&mut self, f: &mut dyn FnMut(&mut Particle)) {
41        for part in self.particles_mut().iter_mut() {
42            f(part);
43        }
44    }
45
46    /// The core method of the trait. Different implementations of [Simulation] vary
47    /// in performance as this is the heaviest part of the simulation. Invokes a lambda
48    /// callback for pair of particles in the simulation, with the following limitations:
49    ///
50    /// - An iterator over distinct pairs of particles, accounting for symmetry.
51    /// - If you receive a pair `(a, b)` it is guaranteed that you will not receive `(b, a)`.
52    /// - There is no guarantee you will receive all pairs.
53    fn for_each_particle_pairs_mut(&mut self, f: &mut dyn FnMut(&mut Particle, &mut Particle));
54
55    /// The number of particles in the simulation.
56    fn particle_count(&self) -> usize;
57
58    /// Set the particles in the simulation.
59    fn add_particles(&mut self, particles: Vec<Particle>);
60
61    /// Get the force calculation method.
62    fn get_force(&self) -> Arc<dyn Force>;
63
64    /// Set the force calculation method.
65    fn set_force(&mut self, force: Arc<dyn Force>);
66
67    /// Get the simulation arguments.
68    fn args(&self) -> SimulationArgs;
69
70    /// Set the simulation arguments.
71    fn set_args(&mut self, args: SimulationArgs);
72
73    /// Updates the position of all particles.
74    fn update_position(&mut self, delta_t: f64) {
75        self.for_each_particles_mut(&mut |p| p.update_position(delta_t));
76    }
77
78    /// Delays the force.
79    fn delay_force(&mut self) {
80        self.for_each_particles_mut(&mut |p| p.delay_force());
81    }
82
83    /// Updates the velocity of all particles.
84    fn update_force(&mut self) {
85        // cannot borrow `*self` as mutable because it is also borrowed as immutable
86        // mutable borrow occurs hererustcClick for full compiler diagnostic
87        // mod.rs(50, 21): immutable borrow occurs here
88        // mod.rs(52, 14): immutable borrow later used by call
89        let force: Arc<dyn Force> = self.get_force();
90
91        self.for_each_particle_pairs_mut(&mut |p1, p2| {
92            force.apply_force(p1, p2);
93        });
94    }
95
96    /// Updates the velocity of all particles.
97    fn update_velocity(&mut self, delta_t: f64) {
98        self.for_each_particles_mut(&mut |p| p.update_velocity(delta_t));
99    }
100
101    /// TODO document
102    fn step(&mut self, delta_t: f64) {
103        self.update_position(delta_t);
104        self.delay_force();
105        self.update_force();
106        // APPLY GRAVITY HERE
107        // CALCULATE BORDER BEHAVIOUR
108        self.update_velocity(delta_t);
109        // TODO UPDATE CURRENT TIME += DELTA TIME
110    }
111
112    // TODO PLOT PARTICLES
113}
114
115impl<'a> Serialize for dyn Simulation + 'a {
116    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
117    where
118        S: serde::Serializer,
119    {
120        serializer.serialize_str(self.system_name())
121    }
122}
123
124struct SimulationVisitor;
125
126impl<'de> Visitor<'de> for SimulationVisitor {
127    type Value = Box<dyn Simulation>;
128
129    fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
130        formatter.write_str("a simulation")
131    }
132
133    /// If the simulation is represented as a string, we can parse it as a known simulation
134    /// type. Strings are case-insensitive.
135    ///
136    /// # Example
137    ///
138    /// ```yaml
139    /// # Particle definition input file example
140    /// name: halleys-comet
141    /// algorithm: direct-sum
142    /// ```
143    fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
144    where
145        E: serde::de::Error,
146    {
147        match value.to_ascii_lowercase().as_str() {
148            "direct-sum" | "ds" => Ok(Box::new(DirectSum::default())),
149            // TODO linked-cells
150            _ => Err(E::custom(format!("Unknown simulation type: {}", value))),
151        }
152    }
153}
154
155impl<'de> Deserialize<'de> for Box<dyn Simulation> {
156    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
157    where
158        D: serde::Deserializer<'de>,
159    {
160        deserializer.deserialize_any(SimulationVisitor)
161    }
162}
163
164impl Default for Box<dyn Simulation> {
165    /// The default simulation system for this project is the direct sum.
166    fn default() -> Self {
167        Box::new(DirectSum::default())
168    }
169}