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}