potentials/base.rs
1//! # Physics Interface Traits
2//!
3//! Defines the core trait abstractions for potential energy computations.
4//! Different interaction types have specialized signatures optimized for
5//! their specific computational patterns.
6//!
7//! ## Trait Hierarchy
8//!
9//! - [`Potential2`]: 2-body interactions (pairs, bonds)
10//! - [`Potential3`]: 3-body interactions (angles)
11//! - [`Potential4`]: 4-body interactions (torsions, impropers)
12//!
13//! ## Design Philosophy
14//!
15//! ### Raw Derivatives
16//!
17//! Potentials return "raw" derivatives rather than Cartesian forces:
18//!
19//! - **2-Body**: Returns force factor `S = -(dV/dr) / r`
20//! - Actual force: `F_vec = S * r_vec`
21//!
22//! - **3-Body**: Returns `dV/d(cos_theta)`
23//! - Caller applies chain rule for Cartesian mapping
24//!
25//! - **4-Body**: Returns `dV/d(phi)`
26//! - Caller applies chain rule for Cartesian mapping
27//!
28//! This separation keeps potential implementations simple and pure mathematical,
29//! while allowing the caller (force field engine) to handle coordinate transformations.
30
31use crate::math::Vector;
32
33// ============================================================================
34// 2-Body Potential (Pair & Bond)
35// ============================================================================
36
37/// Two-body potential energy function.
38///
39/// Used for both non-bonded pair interactions (LJ, Coulomb) and
40/// bonded stretch interactions (harmonic, Morse).
41///
42/// ## Input Convention
43///
44/// All methods accept `r_sq` (squared distance) rather than `r` to avoid
45/// unnecessary square root operations in the common case.
46///
47/// ## Force Factor Convention
48///
49/// The `force_factor` method returns:
50///
51/// ```text
52/// S = -(dV/dr) / r = -dV/d(r^2) * 2
53/// ```
54///
55/// To obtain the force vector from particle j to particle i:
56///
57/// ```text
58/// F_ij = S * r_ij_vec
59/// ```
60///
61/// where `r_ij_vec = r_i - r_j` (vector from j to i).
62pub trait Potential2<T: Vector> {
63 /// Computes the potential energy.
64 ///
65 /// ## Arguments
66 ///
67 /// - `r_sq`: Squared distance between particles (r^2)
68 ///
69 /// ## Returns
70 ///
71 /// Potential energy V(r)
72 fn energy(&self, r_sq: T) -> T;
73
74 /// Computes the force factor.
75 ///
76 /// ## Arguments
77 ///
78 /// - `r_sq`: Squared distance between particles (r^2)
79 ///
80 /// ## Returns
81 ///
82 /// Force factor `S = -(dV/dr) / r`
83 ///
84 /// The actual force vector is: `F = S * r_vec`
85 fn force_factor(&self, r_sq: T) -> T;
86
87 /// Computes both energy and force factor simultaneously.
88 ///
89 /// Default implementation calls both methods separately.
90 /// Override for potentials with shared subexpressions.
91 ///
92 /// ## Returns
93 ///
94 /// Tuple of (energy, force_factor)
95 #[inline(always)]
96 fn energy_force(&self, r_sq: T) -> (T, T) {
97 (self.energy(r_sq), self.force_factor(r_sq))
98 }
99}
100
101// ============================================================================
102// 3-Body Potential (Angle)
103// ============================================================================
104
105/// Three-body angular potential energy function.
106///
107/// Computes energy as a function of the angle formed by three particles (i-j-k),
108/// where j is the central vertex.
109///
110/// ## Input Convention
111///
112/// Methods accept:
113/// - `r_ij_sq`: Squared distance i-j
114/// - `r_jk_sq`: Squared distance j-k
115/// - `cos_theta`: Cosine of angle i-j-k (computed from dot product)
116///
117/// The cosine is preferred over the angle itself because:
118/// 1. It's directly available from the dot product: `cos_theta = (r_ij ยท r_jk) / (|r_ij| |r_jk|)`
119/// 2. Many angle potentials (GROMOS, DREIDING) use cosine directly
120/// 3. Avoids expensive `acos` operation in the common case
121///
122/// ## Derivative Convention
123///
124/// The `derivative` method returns `dV/d(cos_theta)`.
125///
126/// The caller is responsible for applying the chain rule to obtain
127/// Cartesian forces. For angle i-j-k:
128///
129/// ```text
130/// d(cos_theta)/d(r_i) = (r_jk/|r_jk| - cos_theta * r_ij/|r_ij|) / |r_ij|
131/// ```
132///
133/// (and similar expressions for j and k)
134pub trait Potential3<T: Vector> {
135 /// Computes the potential energy.
136 ///
137 /// ## Arguments
138 ///
139 /// - `r_ij_sq`: Squared distance from i to j
140 /// - `r_jk_sq`: Squared distance from j to k
141 /// - `cos_theta`: Cosine of angle i-j-k
142 ///
143 /// ## Returns
144 ///
145 /// Potential energy V(theta)
146 fn energy(&self, r_ij_sq: T, r_jk_sq: T, cos_theta: T) -> T;
147
148 /// Computes the derivative with respect to cos(theta).
149 ///
150 /// ## Arguments
151 ///
152 /// - `r_ij_sq`: Squared distance from i to j
153 /// - `r_jk_sq`: Squared distance from j to k
154 /// - `cos_theta`: Cosine of angle i-j-k
155 ///
156 /// ## Returns
157 ///
158 /// Derivative `dV/d(cos_theta)`
159 fn derivative(&self, r_ij_sq: T, r_jk_sq: T, cos_theta: T) -> T;
160
161 /// Computes both energy and derivative simultaneously.
162 ///
163 /// ## Returns
164 ///
165 /// Tuple of (energy, dV/d(cos_theta))
166 #[inline(always)]
167 fn energy_derivative(&self, r_ij_sq: T, r_jk_sq: T, cos_theta: T) -> (T, T) {
168 (
169 self.energy(r_ij_sq, r_jk_sq, cos_theta),
170 self.derivative(r_ij_sq, r_jk_sq, cos_theta),
171 )
172 }
173}
174
175// ============================================================================
176// 4-Body Potential (Torsion & Improper)
177// ============================================================================
178
179/// Four-body dihedral potential energy function.
180///
181/// Computes energy as a function of the dihedral angle formed by four particles.
182///
183/// For proper torsions (i-j-k-l): phi is the angle between planes (i,j,k) and (j,k,l)
184/// For improper torsions: phi typically measures out-of-plane displacement
185///
186/// ## Input Convention
187///
188/// Methods accept both `cos_phi` and `sin_phi` because:
189/// 1. Both are efficiently computed from cross products
190/// 2. Many potentials need `cos(n*phi)` which requires both via Chebyshev/recurrence
191/// 3. The sign of `sin_phi` determines the quadrant
192///
193/// ## Derivative Convention
194///
195/// The `derivative` method returns `dV/d(phi)`.
196///
197/// Note: This is the derivative with respect to the angle itself, not its
198/// trigonometric functions. The caller handles the chain rule:
199///
200/// ```text
201/// dV/d(cos_phi) = -dV/d(phi) / sin_phi (where sin_phi != 0)
202/// ```
203pub trait Potential4<T: Vector> {
204 /// Computes the potential energy.
205 ///
206 /// ## Arguments
207 ///
208 /// - `cos_phi`: Cosine of dihedral angle
209 /// - `sin_phi`: Sine of dihedral angle
210 ///
211 /// ## Returns
212 ///
213 /// Potential energy V(phi)
214 fn energy(&self, cos_phi: T, sin_phi: T) -> T;
215
216 /// Computes the derivative with respect to phi.
217 ///
218 /// ## Arguments
219 ///
220 /// - `cos_phi`: Cosine of dihedral angle
221 /// - `sin_phi`: Sine of dihedral angle
222 ///
223 /// ## Returns
224 ///
225 /// Derivative `dV/d(phi)`
226 fn derivative(&self, cos_phi: T, sin_phi: T) -> T;
227
228 /// Computes both energy and derivative simultaneously.
229 ///
230 /// ## Returns
231 ///
232 /// Tuple of (energy, dV/d(phi))
233 #[inline(always)]
234 fn energy_derivative(&self, cos_phi: T, sin_phi: T) -> (T, T) {
235 (
236 self.energy(cos_phi, sin_phi),
237 self.derivative(cos_phi, sin_phi),
238 )
239 }
240}