hoomd_interaction/univariate/
lennard_jones.rs

1// Copyright (c) 2024-2026 The Regents of the University of Michigan.
2// Part of hoomd-rs, released under the BSD 3-Clause License.
3
4//! Implement [`LennardJones`]
5
6use serde::{Deserialize, Serialize};
7
8use super::{UnivariateEnergy, UnivariateForce};
9
10/// Potential with a steep repulsive core and an attractive well.
11///
12/// ```math
13/// U(r) = 4 \varepsilon \left[ \left( \frac{\sigma}{r} \right)^{N} - \left( \frac{\sigma}{r} \right)^{M} \right]
14/// ```
15///
16/// Compute the Lennard-Jones (LJ) potential and force as a function of `r`.
17///
18/// # Examples
19///
20/// In basic usage, the exponents `N` and `M` default to 12 and 6, respectively:
21///
22/// ```
23/// use approxim::{assert_abs_diff_eq, assert_relative_eq};
24/// use hoomd_interaction::univariate::{
25///     LennardJones, UnivariateEnergy, UnivariateForce,
26/// };
27///
28/// let epsilon = 1.5;
29/// let sigma = 2.5;
30///
31/// let lennard_jones: LennardJones = LennardJones { epsilon, sigma };
32/// assert_abs_diff_eq!(lennard_jones.energy(sigma), 0.0);
33/// assert_relative_eq!(
34///     lennard_jones.energy(2.0_f64.powf(1.0 / 6.0) * sigma),
35///     -epsilon
36/// );
37/// assert_abs_diff_eq!(
38///     lennard_jones.force(2.0_f64.powf(1.0 / 6.0) * sigma),
39///     0.0,
40///     epsilon = 1e-12
41/// );
42/// ```
43///
44/// You can choose any values for `N` and `M` _at compile time_:
45///
46/// ```
47/// use approxim::{assert_abs_diff_eq, assert_relative_eq};
48/// use hoomd_interaction::univariate::{
49///     LennardJones, UnivariateEnergy, UnivariateForce,
50/// };
51///
52/// let epsilon = 1.5;
53/// let sigma = 2.5;
54///
55/// let lennard_jones: LennardJones<8, 4> = LennardJones { epsilon, sigma };
56/// assert_abs_diff_eq!(lennard_jones.energy(sigma), 0.0);
57/// assert_relative_eq!(
58///     lennard_jones.energy(2.0_f64.powf(1.0 / 4.0) * sigma),
59///     -epsilon
60/// );
61/// assert_abs_diff_eq!(
62///     lennard_jones.force(2.0_f64.powf(1.0 / 4.0) * sigma),
63///     0.0,
64///     epsilon = 1e-12
65/// );
66/// ```
67///
68/// The parameters are public fields and may be accessed directly:
69///
70/// ```
71/// use hoomd_interaction::univariate::LennardJones;
72///
73/// let mut lennard_jones: LennardJones = LennardJones::default();
74/// lennard_jones.epsilon = 1.5;
75/// lennard_jones.sigma = 3.0;
76/// ```
77#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
78pub struct LennardJones<const N: i32 = 12, const M: i32 = 6> {
79    /// Energy scale *(\[energy\])*.
80    pub epsilon: f64,
81    /// Interaction width *(\[length\])*.
82    pub sigma: f64,
83}
84
85impl<const N: i32, const M: i32> Default for LennardJones<N, M> {
86    /// Construct a [`LennardJones`] with default parameters (epsilon=1.0, sigma=1.0)
87    ///
88    /// # Example
89    ///
90    /// ```
91    /// use hoomd_interaction::univariate::LennardJones;
92    ///
93    /// let lennard_jones: LennardJones = LennardJones::default();
94    /// ```
95    #[inline]
96    fn default() -> Self {
97        Self {
98            epsilon: 1.0,
99            sigma: 1.0,
100        }
101    }
102}
103
104impl<const N: i32, const M: i32> UnivariateEnergy for LennardJones<N, M> {
105    #[inline]
106    fn energy(&self, r: f64) -> f64 {
107        let sigma_r = self.sigma / r;
108
109        4.0 * self.epsilon * (sigma_r.powi(N) - sigma_r.powi(M))
110    }
111}
112
113impl<const N: i32, const M: i32> UnivariateForce for LennardJones<N, M> {
114    #[inline]
115    fn force(&self, r: f64) -> f64 {
116        let r_inv = r.recip();
117        let sigma_r = self.sigma * r_inv;
118
119        self.epsilon
120            * r_inv
121            * (4.0 * f64::from(N) * sigma_r.powi(N) - 4.0 * f64::from(M) * sigma_r.powi(M))
122    }
123}
124
125#[cfg(test)]
126mod tests {
127    use super::*;
128    use approxim::{assert_abs_diff_eq, assert_relative_eq};
129    use rstest::*;
130
131    #[rstest]
132    fn special_points_12_6(
133        #[values(1.0, 2.0, 12.125, 0.25)] epsilon: f64,
134        #[values(1.0, 2.0, 0.5)] sigma: f64,
135    ) {
136        let lj: LennardJones = LennardJones { epsilon, sigma };
137
138        assert_eq!(lj.epsilon, epsilon);
139        assert_eq!(lj.sigma, sigma);
140
141        // Zero crossing
142        assert_abs_diff_eq!(lj.energy(sigma), 0.0);
143        assert_relative_eq!(lj.force(sigma), 24.0 * epsilon / sigma);
144
145        // Bottom of the well
146        assert_relative_eq!(lj.energy(2.0_f64.powf(1.0 / 6.0) * sigma), -epsilon);
147        assert_abs_diff_eq!(
148            lj.force(2.0_f64.powf(1.0 / 6.0) * sigma),
149            0.0,
150            epsilon = 1e-12
151        );
152
153        // r = 2 sigma
154        assert_relative_eq!(lj.energy(2.0 * sigma), -63.0 / 1024.0 * epsilon);
155        assert_relative_eq!(lj.force(2.0 * sigma), -93.0 / 512.0 * epsilon / sigma);
156    }
157
158    #[rstest]
159    fn special_points_8_4(
160        #[values(1.0, 2.0, 12.125, 0.25)] epsilon: f64,
161        #[values(1.0, 2.0, 0.5)] sigma: f64,
162    ) {
163        let lj: LennardJones<8, 4> = LennardJones { epsilon, sigma };
164
165        assert_eq!(lj.epsilon, epsilon);
166        assert_eq!(lj.sigma, sigma);
167
168        // Zero crossing
169        assert_abs_diff_eq!(lj.energy(sigma), 0.0);
170        assert_relative_eq!(lj.force(sigma), 16.0 * epsilon / sigma);
171
172        // Bottom of the well
173        assert_relative_eq!(lj.energy(2.0_f64.powf(1.0 / 4.0) * sigma), -epsilon);
174        assert_abs_diff_eq!(
175            lj.force(2.0_f64.powf(1.0 / 4.0) * sigma),
176            0.0,
177            epsilon = 1e-12
178        );
179
180        // r = 2 sigma
181        assert_relative_eq!(lj.energy(2.0 * sigma), -15.0 / 64.0 * epsilon);
182        assert_relative_eq!(lj.force(2.0 * sigma), -7.0 / 16.0 * epsilon / sigma);
183    }
184}