Skip to main content

hoomd_interaction/univariate/
expanded.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 [`Expanded`]
5
6use serde::{Deserialize, Serialize};
7
8use super::{UnivariateEnergy, UnivariateForce};
9
10/// Expand another potential.
11///
12/// ```math
13/// U(r) = f(r - \delta)
14/// ```
15///
16/// # Example
17///
18/// Expanded Lennard-Jones:
19/// ```
20/// use approxim::{assert_abs_diff_eq, assert_relative_eq};
21/// use hoomd_interaction::univariate::{
22///     Expanded, LennardJones, UnivariateEnergy,
23/// };
24///
25/// let epsilon = 1.5;
26/// let sigma = 1.0;
27/// let delta = 0.75;
28/// let lj: LennardJones = LennardJones { epsilon, sigma };
29/// let expanded_lj = Expanded { f: lj, delta };
30///
31/// assert_abs_diff_eq!(
32///     expanded_lj.energy(1.0),
33///     expanded_lj.f.energy(1.0 - 0.75)
34/// );
35/// ```
36#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
37pub struct Expanded<F> {
38    /// The original potential.
39    pub f: F,
40    /// $`\delta`$ value $`[\mathrm{length}]`$.
41    pub delta: f64,
42}
43
44impl<F> Default for Expanded<F>
45where
46    F: Default,
47{
48    /// Construct a shifted potential with default parameters
49    ///
50    /// The defaults are:
51    /// `f = F::default()`
52    /// `delta = 0.0`
53    #[inline]
54    fn default() -> Self {
55        Self {
56            f: F::default(),
57            delta: 0.0,
58        }
59    }
60}
61
62impl<F: UnivariateEnergy> UnivariateEnergy for Expanded<F> {
63    #[inline]
64    fn energy(&self, r: f64) -> f64 {
65        self.f.energy(r - self.delta)
66    }
67}
68
69impl<F: UnivariateForce> UnivariateForce for Expanded<F> {
70    #[inline]
71    fn force(&self, r: f64) -> f64 {
72        self.f.force(r - self.delta)
73    }
74}
75
76#[cfg(test)]
77mod tests {
78    use super::*;
79    use crate::univariate::LennardJones;
80    use approxim::{assert_abs_diff_eq, assert_relative_eq};
81    use rstest::*;
82
83    #[rstest]
84    fn special_points_12_6(
85        #[values(1.0, 2.0, 12.125, 0.25)] epsilon: f64,
86        #[values(1.0, 2.0, 0.5)] sigma: f64,
87        #[values(0.125, 0.5, 2.0)] delta: f64,
88    ) {
89        let lj: LennardJones = LennardJones { epsilon, sigma };
90        let expanded_lj = Expanded { f: lj, delta };
91
92        // Zero crossing
93        assert_abs_diff_eq!(expanded_lj.energy(sigma + delta), 0.0);
94        assert_relative_eq!(expanded_lj.force(sigma + delta), 24.0 * epsilon / sigma);
95
96        // Bottom of the well
97        assert_relative_eq!(
98            expanded_lj.energy(2.0_f64.powf(1.0 / 6.0) * sigma + delta),
99            -epsilon
100        );
101        assert_abs_diff_eq!(
102            expanded_lj.force(2.0_f64.powf(1.0 / 6.0) * sigma + delta),
103            0.0,
104            epsilon = 1e-12
105        );
106
107        // r = 2 sigma
108        assert_relative_eq!(
109            expanded_lj.energy(2.0 * sigma + delta),
110            -63.0 / 1024.0 * epsilon
111        );
112        assert_relative_eq!(
113            expanded_lj.force(2.0 * sigma + delta),
114            -93.0 / 512.0 * epsilon / sigma
115        );
116    }
117}