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}