hoomd_interaction/univariate/harmonic_repulsion.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 [`HarmonicRepulsion`]
5
6use serde::{Deserialize, Serialize};
7
8use super::{UnivariateEnergy, UnivariateForce};
9
10/// Repulsive half of a quadratic potential well.
11///
12/// [`HarmonicRepulsion`] is the conservative part of the dissipative particle
13/// dynamics soft potential. The parameter `a` controls the strength of the
14/// potential and `r_cut` sets the distance at which the energy goes to 0.
15///
16///
17/// The potential produce the radially repulsive force between particles as:
18/// ```math
19/// F(r) = \begin{cases}
20/// -\frac{A}{r_\mathrm{cut}} \left( r - r_\mathrm{cut}\right) & r < r_\mathrm{cut} \\
21///
22/// 0 & r \ge r_\mathrm{cut}
23/// \end{cases}
24/// ```
25///
26/// resulting from the potential energy:
27/// ```math
28/// U(r) = \begin{cases}
29/// \frac{1}{2} \frac{A}{r_\mathrm{cut}} \left(r - r_\mathrm{cut}\right)^2 & r \lt r_\mathrm{cut} \\
30///
31/// 0 & r \ge r_\mathrm{cut}
32/// \end{cases}
33/// ```
34///
35/// This potential forms the left half of a harmonic well centered at
36/// $`r = r_\mathrm{cut}`$ with a spring constant $`k = \frac{A}{r_\mathrm{cut}}`$.
37///
38/// Note that this potential has a maximum value of $`A`$ at $`r=0`$ and is
39/// therefore allows particles to completely overlap.
40///
41/// # Examples
42///
43///
44/// ```
45/// use approxim::{assert_abs_diff_eq, assert_relative_eq};
46/// use hoomd_interaction::univariate::{
47/// HarmonicRepulsion, UnivariateEnergy, UnivariateForce,
48/// };
49///
50/// let a = 1.0;
51/// let r_cut = 1.0;
52///
53/// let h_repsulsion = HarmonicRepulsion { a, r_cut };
54/// assert_abs_diff_eq!(h_repsulsion.energy(1.5), 0.0);
55/// assert_relative_eq!(h_repsulsion.energy(0.5), 0.125);
56/// assert_abs_diff_eq!(h_repsulsion.force(0.5), 0.5, epsilon = 1e-12);
57/// ```
58///
59/// The parameters are public fields and may be accessed directly:
60///
61/// ```
62/// use hoomd_interaction::univariate::HarmonicRepulsion;
63///
64/// let mut h_repulsion = HarmonicRepulsion { a: 1.0, r_cut: 1.0 };
65/// h_repulsion.a = 5.0;
66/// h_repulsion.r_cut = 0.75;
67/// ```
68#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
69pub struct HarmonicRepulsion {
70 /// Potential strength $`[\mathrm{energy}] [\mathrm{length}]^{-1}`$.
71 pub a: f64,
72 /// Distance cut-off $`[\mathrm{length}]`$.
73 pub r_cut: f64,
74}
75
76impl UnivariateEnergy for HarmonicRepulsion {
77 #[inline]
78 fn energy(&self, r: f64) -> f64 {
79 if r < self.r_cut {
80 0.5 * self.a / self.r_cut * (r - self.r_cut).powi(2)
81 } else {
82 0.0
83 }
84 }
85}
86
87impl UnivariateForce for HarmonicRepulsion {
88 #[inline]
89 fn force(&self, r: f64) -> f64 {
90 if r < self.r_cut {
91 self.a / self.r_cut * (self.r_cut - r)
92 } else {
93 0.0
94 }
95 }
96}
97
98#[cfg(test)]
99mod tests {
100 use super::*;
101 use approxim::assert_relative_eq;
102 use rstest::*;
103
104 #[rstest]
105 fn outside_rcut(#[values(1.0, 2.0, 5.0, 10.0)] a: f64, #[values(1.0, 2.0, 3.0)] r_cut: f64) {
106 let h_repulsion = HarmonicRepulsion { a, r_cut };
107
108 assert_eq!(h_repulsion.a, a);
109 assert_eq!(h_repulsion.r_cut, r_cut);
110
111 assert_eq!(h_repulsion.energy(r_cut + 1.0), 0.0);
112 assert_eq!(h_repulsion.force(r_cut + 1.0), 0.0);
113 }
114
115 #[rstest]
116 fn general_case(#[values(1.0, 2.0, 5.0, 10.0)] a: f64, #[values(1.0, 2.0, 3.0)] r_cut: f64) {
117 let r = 0.5;
118 let h_repulsion = HarmonicRepulsion { a, r_cut };
119
120 assert_eq!(h_repulsion.a, a);
121 assert_eq!(h_repulsion.r_cut, r_cut);
122
123 let expected_energy = a * (r_cut - r) - 0.5 * a / r_cut * (r_cut * r_cut - r * r);
124 let expected_force = a * (1.0 - r / r_cut);
125
126 assert_relative_eq!(h_repulsion.energy(r), expected_energy);
127 assert_relative_eq!(h_repulsion.force(r), expected_force);
128 }
129}