Skip to main content

hoomd_interaction/pairwise/
hard_shape.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 `HardShape`
5
6use serde::{Deserialize, Serialize};
7
8use crate::{MaximumInteractionRange, SitePairEnergy};
9use hoomd_geometry::{BoundingSphereRadius, IntersectsAtGlobal};
10use hoomd_microstate::property::{Orientation, Position};
11use hoomd_vector::Metric;
12
13/// Infinite energy when sites overlap, 0 when they don't (*not differentiable*).
14///
15/// [`HardShape`] represents each site with a hard orientable shape.
16///
17/// The generic type names are:
18/// * `G`: The [`shape`](hoomd_geometry::shape) type.
19///
20/// # Example
21///
22/// ```
23/// use hoomd_geometry::{Convex, shape::Rectangle};
24/// use hoomd_interaction::pairwise::HardShape;
25///
26/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
27/// let square = Rectangle::with_equal_edges(1.0.try_into()?);
28/// let hard_shape = HardShape(Convex(square));
29/// # Ok(())
30/// # }
31/// ```
32#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
33pub struct HardShape<G>(pub G);
34
35impl<G, S, R, P> SitePairEnergy<S> for HardShape<G>
36where
37    S: Position<Position = P> + Orientation<Rotation = R>,
38    G: IntersectsAtGlobal<G, P, R>,
39{
40    /// Compute the energy contribution from a pair of sites.
41    ///
42    /// A pair of hard shapes contributes an infinite energy when they overlap,
43    /// and zero when they do not.
44    ///
45    /// # Example
46    ///
47    /// ```
48    /// use hoomd_geometry::{Convex, shape::Rectangle};
49    /// use hoomd_interaction::{SitePairEnergy, pairwise::HardShape};
50    /// use hoomd_microstate::property::OrientedPoint;
51    /// use hoomd_vector::{Angle, Cartesian};
52    /// use std::f64::consts::PI;
53    ///
54    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
55    /// let square = Rectangle::with_equal_edges(1.0.try_into()?);
56    /// let hard_shape = HardShape(Convex(square));
57    ///
58    /// let a = OrientedPoint {
59    ///     position: Cartesian::from([1.0, -1.0]),
60    ///     orientation: Angle::from(PI / 2.0),
61    /// };
62    /// let b = OrientedPoint {
63    ///     position: Cartesian::from([2.0, 0.0]),
64    ///     orientation: Angle::from(PI / 4.0),
65    /// };
66    ///
67    /// assert_eq!(hard_shape.site_pair_energy(&a, &b), 0.0);
68    ///
69    /// let c = OrientedPoint {
70    ///     position: Cartesian::from([1.5, -0.5]),
71    ///     orientation: Angle::from(PI / 4.0),
72    /// };
73    ///
74    /// assert_eq!(hard_shape.site_pair_energy(&a, &c), f64::INFINITY);
75    /// # Ok(())
76    /// # }
77    /// ```
78    #[inline]
79    fn site_pair_energy(&self, site_properties_i: &S, site_properties_j: &S) -> f64 {
80        // Use the global form of `intersects_at` so that the circumsphere early
81        // rejection test can be performed before the expensive transformation
82        // into the local coordinate system.
83        if self.0.intersects_at_global(
84            &self.0,
85            site_properties_i.position(),
86            site_properties_i.orientation(),
87            site_properties_j.position(),
88            site_properties_j.orientation(),
89        ) {
90            f64::INFINITY
91        } else {
92            0.0
93        }
94    }
95
96    /// Evaluate the energy contribution from a pair of sites *in the initial state*.
97    ///
98    /// Hard shapes are assumed to be non-overlapping in the initial state.
99    /// This method always returns zero.
100    #[inline]
101    fn site_pair_energy_initial(&self, _site_properties_i: &S, _site_properties_j: &S) -> f64 {
102        0.0
103    }
104
105    #[inline]
106    fn is_only_infinite_or_zero() -> bool {
107        true
108    }
109}
110
111impl<G> MaximumInteractionRange for HardShape<G>
112where
113    G: BoundingSphereRadius,
114{
115    #[inline]
116    fn maximum_interaction_range(&self) -> f64 {
117        self.0.bounding_sphere_radius().get() * 2.0
118    }
119}
120
121/// Model infinitely hard spheres when used with [`PairwiseCutoff`] (*not differentiable*).
122///
123/// [`HardSphere`] represents each site as a hard sphere with a diameter given
124/// by the `diameter` field.
125///
126/// [`PairwiseCutoff`]: crate::PairwiseCutoff
127#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
128pub struct HardSphere {
129    /// The sphere's diameter.
130    pub diameter: f64,
131}
132
133impl<S, P> SitePairEnergy<S> for HardSphere
134where
135    S: Position<Position = P>,
136    P: Metric,
137{
138    /// Compute the energy contribution from a pair of sites.
139    ///
140    /// The interaction energy is infinite when two spheres overlap and zero
141    /// when they do not.
142    #[inline]
143    fn site_pair_energy(&self, site_properties_i: &S, site_properties_j: &S) -> f64 {
144        if site_properties_i
145            .position()
146            .distance_squared(site_properties_j.position())
147            < self.diameter.powi(2)
148        {
149            f64::INFINITY
150        } else {
151            0.0
152        }
153    }
154
155    /// Evaluate the energy contribution from a pair of sites *in the initial state*.
156    ///
157    /// Hard shapes are assumed to be non-overlapping in the initial state.
158    /// This implementation always returns zero.
159    #[inline]
160    fn site_pair_energy_initial(&self, _site_properties_i: &S, _site_properties_j: &S) -> f64 {
161        0.0
162    }
163
164    #[inline]
165    fn is_only_infinite_or_zero() -> bool {
166        true
167    }
168}
169
170impl MaximumInteractionRange for HardSphere {
171    #[inline]
172    fn maximum_interaction_range(&self) -> f64 {
173        self.diameter
174    }
175}