hoomd_interaction/external/linear.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 [`Linear`]
5
6use serde::{Deserialize, Serialize};
7
8use hoomd_microstate::property::Position;
9use hoomd_vector::{InnerProduct, Unit};
10
11use super::super::SiteEnergy;
12
13/// Linear potential based on position.
14///
15/// ```math
16/// U = \alpha \cdot \vec{n} \cdot ( \vec{r} - \vec{p} )
17/// ```
18///
19/// Computes a linear external potential at a point in space relative to the plane
20/// origin `p`, plane normal `n`, and the interaction strength `alpha`.
21///
22/// # Example
23///
24/// Basic usage:
25///
26/// ```
27/// use hoomd_interaction::external::Linear;
28/// use hoomd_vector::{Cartesian, Unit};
29///
30/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
31/// let linear = Linear {
32/// alpha: 2.0,
33/// plane_origin: [0.0, -10.0].into(),
34/// plane_normal: [0.0, 1.0].try_into()?,
35/// };
36/// # Ok(())
37/// # }
38/// ```
39#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
40pub struct Linear<V> {
41 /// Interaction strength *(\[energy\] \[length\]^(-1))*.
42 pub alpha: f64,
43 /// Point on the plane where U=0 *(\[length\])*.
44 pub plane_origin: V,
45 /// Vector normal to the plane *(unitless)*.
46 pub plane_normal: Unit<V>,
47}
48
49impl<V> Linear<V>
50where
51 V: InnerProduct,
52{
53 /// Compute the energy of a point in the linear field.
54 ///
55 /// # Example
56 ///
57 /// ```
58 /// use hoomd_interaction::external::Linear;
59 /// use hoomd_vector::{Cartesian, Unit};
60 ///
61 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
62 /// let linear = Linear {
63 /// alpha: 2.0,
64 /// plane_origin: [0.0, -10.0].into(),
65 /// plane_normal: [0.0, 1.0].try_into()?,
66 /// };
67 ///
68 /// let energy = linear.energy(&[0.0, 0.0].into());
69 /// assert_eq!(energy, 20.0);
70 /// # Ok(())
71 /// # }
72 /// ```
73 #[inline]
74 #[must_use]
75 pub fn energy(&self, r: &V) -> f64 {
76 self.alpha * self.plane_normal.get().dot(&(*r - self.plane_origin))
77 }
78}
79
80impl<S, P> SiteEnergy<S> for Linear<P>
81where
82 S: Position<Position = P>,
83 P: InnerProduct,
84{
85 #[inline]
86 fn site_energy(&self, site_properties: &S) -> f64 where {
87 self.energy(site_properties.position())
88 }
89}
90
91#[cfg(test)]
92mod tests {
93 use hoomd_vector::Cartesian;
94
95 use super::*;
96 use approxim::assert_relative_eq;
97 use rstest::*;
98
99 #[rstest]
100 fn energy_2d(
101 #[values(1.0, 0.0, -2.0)] alpha: f64,
102 #[values([0.0, 0.0], [-10.0, 15.0], [16.0, 3.0])] plane_origin: [f64; 2],
103 #[values([1.0, 1.0], [-1.0, 0.2], [-5.0, -1.0])] plane_normal: [f64; 2],
104 ) {
105 let n = Unit::<Cartesian<2>>::try_from(plane_normal)
106 .expect("hard-coded vector should have non-zero length");
107
108 let linear = Linear {
109 plane_origin: plane_origin.into(),
110 plane_normal: n,
111 alpha,
112 };
113
114 let p = linear.plane_origin + *n.get() * 5.0;
115 assert_relative_eq!(linear.energy(&p), 5.0 * alpha, epsilon = 1e-6);
116 }
117}