moldyn_core/forces/mod.rs
1mod custom;
2mod ljp;
3mod newton;
4
5use crate::{Particle, Vec3};
6pub use custom::CustomForce;
7pub use ljp::LennardJonesForce;
8use meval::Expr;
9pub use newton::NewtonForce;
10use serde::de::Error;
11use serde::{Deserialize, Serialize, de::Visitor};
12
13/// The trait for force systems. A force system is a mathematical model which
14/// describes the [potential energy](https://en.wikipedia.org/wiki/Potential_energy)
15/// between two particles.
16///
17/// This trait provides two methods [Force::potential] and [Force::force] for interacting
18/// with the force system. The potential is the scalar value used as the factor to the
19/// normalized force vector.
20///
21/// The method [Force::system_name] is used for serialization of the force system.
22pub trait Force {
23 /// # Returns
24 ///
25 /// Name of the force system, which is used for serialization and
26 /// deserialization. The characters are expected to be in `dash-case`.
27 fn system_name(&self) -> &str;
28
29 /// Calculates the potential energy between two particles.
30 fn potential(&self, particle: &Particle, other: &Particle) -> f64;
31
32 /// Calculates the force between two particles. For directly applying the
33 /// force, see [Force::apply_force].
34 ///
35 /// # Formula
36 ///
37 /// ```text
38 /// F = -U / r (simplified to scalar, actually a vector)
39 /// ```
40 ///
41 /// The resulting force is a three dimensional vector pointing towards the other
42 /// particle. The magnitude is the fraction of potential energy and distance.
43 ///
44 /// # Returns
45 ///
46 /// The force vector that should be applied to the first particle. According
47 /// to the [Third Law](https://en.wikipedia.org/wiki/Newton%27s_laws_of_motion#Third_law)
48 /// the second particle should receive the negated force.
49 ///
50 /// # Example
51 ///
52 /// ```rust
53 /// use moldyn_core::{Particle, Vec3, LennardJonesForce, Force};
54 ///
55 /// let mut particle1 = Particle::from_data(Vec3::new(0.0, 0.0, 0.0), Vec3::zero(), 1.0);
56 /// let mut particle2 = Particle::from_data(Vec3::new(1.0, 0.0, 0.0), Vec3::zero(), 1.0);
57 ///
58 /// let force = LennardJonesForce::default().force(&particle1, &particle2);
59 ///
60 /// particle1.apply_force(force);
61 /// particle2.apply_force(-force);
62 /// ```
63 fn force(&self, particle: &Particle, other: &Particle) -> Vec3 {
64 let potential = self.potential(particle, other);
65 let diff = Particle::position_difference(other, particle);
66 let distance = diff.length2();
67
68 if distance == 0.0 {
69 Vec3::zero()
70 } else {
71 -diff * (potential / distance)
72 }
73
74 // TODO consider using the following code instead of above (after fixing paraview bugs)
75
76 // // If the direction is not calculatable (`particle.position == other.position`), we can skip the rest of the calculations.
77 // let direction = match Particle::direction(particle, other) {
78 // Some(dir) => dir,
79 // None => return Vec3::zero(),
80 // };
81
82 // // The force is the potential multiplied by the normalized direction vector.
83 // direction * self.potential(particle, other)
84 }
85
86 /// Applies the calculated force to a particle pair.
87 ///
88 /// # Example
89 ///
90 /// ```rust
91 /// use moldyn_core::{Particle, Vec3, LennardJonesForce, Force};
92 ///
93 /// let mut particle1 = Particle::from_data(Vec3::new(0.0, 0.0, 0.0), Vec3::zero(), 1.0);
94 /// let mut particle2 = Particle::from_data(Vec3::new(1.0, 0.0, 0.0), Vec3::zero(), 1.0);
95 ///
96 /// let lennard_jones = LennardJonesForce::default();
97 /// let force = lennard_jones.apply_force(&mut particle1, &mut particle2);
98 /// ```
99 fn apply_force(&self, particle: &mut Particle, other: &mut Particle) {
100 let force = self.force(particle, other);
101 particle.apply_force(force);
102 other.apply_force(-force);
103 }
104}
105
106impl<'a> Serialize for dyn Force + 'a {
107 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
108 where
109 S: serde::Serializer,
110 {
111 serializer.serialize_str(self.system_name())
112 }
113}
114
115struct ForceVisitor;
116
117impl<'de> Visitor<'de> for ForceVisitor {
118 type Value = Box<dyn Force>;
119
120 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
121 formatter.write_str("a force")
122 }
123
124 /// If the force is represented as a string, we can parse it as a known force type,
125 /// assuming default parameters. Strings are case-insensitive.
126 ///
127 /// # Example
128 ///
129 /// ```yaml
130 /// # Particle definition input file example
131 /// name: halleys-comet
132 /// force: gravitational
133 /// ```
134 fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
135 where
136 E: serde::de::Error,
137 {
138 match value.to_ascii_lowercase().as_str() {
139 "lennardjones" | "lennard-jones" | "lj" => Ok(Box::new(LennardJonesForce::default())),
140 "newton" | "gravitational" => Ok(Box::new(NewtonForce::default())),
141 _ => Err(E::custom(format!("Unknown force type: {}", value))),
142 }
143 }
144
145 // TODO: implement deserialization with parameters.
146 // idea: force: { type: ..., params... }
147 // idea: force: lennard-jones: { epsilon: ..., sigma: ... }
148 // idea: force: gravity: { factor: ... }
149
150 fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
151 where
152 A: serde::de::MapAccess<'de>,
153 {
154 let force_type = map
155 .next_key::<String>()?
156 .ok_or_else(|| A::Error::custom("Expected a force type key"))?;
157
158 match force_type.to_ascii_lowercase().as_str() {
159 "custom-potential" | "potential" => {
160 let params = map.next_value::<Expr>()?;
161 let custom: CustomForce = params.try_into().map_err(|e| {
162 Error::custom(format!("could not bind custom potential functtuion: {e}"))
163 })?;
164 Ok(Box::new(custom))
165 }
166 _ => Err(A::Error::custom(format!(
167 "Unknown force type: {}",
168 force_type
169 ))),
170 }
171 }
172}
173
174impl<'de> Deserialize<'de> for Box<dyn Force> {
175 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
176 where
177 D: serde::Deserializer<'de>,
178 {
179 deserializer.deserialize_any(ForceVisitor)
180 }
181}
182
183impl Default for Box<dyn Force> {
184 /// The default force calculation system for this project is the lennard
185 /// jones potential. If not specified, the system will be initialized with
186 /// default parameters.
187 fn default() -> Self {
188 Box::new(LennardJonesForce::default())
189 }
190}