Skip to main content

hoomd_microstate/boundary/
closed.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 Closed
5
6use arrayvec::ArrayVec;
7use rand::{Rng, distr::Distribution};
8use serde::{Deserialize, Serialize};
9
10use super::{Error, GenerateGhosts, MAX_GHOSTS, Wrap};
11use crate::property::Position;
12use hoomd_geometry::{IsPointInside, MapPoint, Scale, Volume};
13use hoomd_utility::valid::PositiveReal;
14
15/// Restrict points to the inside of a shape.
16///
17/// [`Closed`] is a newtype that wraps a shape. It prevents bodies and sites from
18/// existing outside the shape. Bodies and sites are never wrapped, and there are no
19/// ghost sites.
20#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
21pub struct Closed<T>(pub T);
22
23impl<BS, T, P> Wrap<BS> for Closed<T>
24where
25    BS: Position<Position = P>,
26    T: IsPointInside<P>,
27{
28    #[inline]
29    fn wrap(&self, properties: BS) -> Result<BS, Error> {
30        if self.0.is_point_inside(properties.position()) {
31            Ok(properties)
32        } else {
33            Err(Error::CannotWrapProperties)
34        }
35    }
36}
37
38impl<S, T> GenerateGhosts<S> for Closed<T>
39where
40    S: Default,
41{
42    #[inline]
43    fn maximum_interaction_range(&self) -> f64 {
44        f64::INFINITY
45    }
46
47    #[inline]
48    fn generate_ghosts(&self, _site_properties: &S) -> ArrayVec<S, MAX_GHOSTS> {
49        ArrayVec::new()
50    }
51}
52
53impl<T, P> Distribution<P> for Closed<T>
54where
55    T: Distribution<P>,
56{
57    /// Generate points uniformly distributed in the wrapped shape.
58    ///
59    /// # Example
60    ///
61    /// ```
62    /// use hoomd_geometry::{IsPointInside, shape::Sphere};
63    /// use hoomd_microstate::boundary::Closed;
64    /// use rand::{SeedableRng, distr::Distribution, rngs::StdRng};
65    ///
66    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
67    /// let sphere = Closed(Sphere {
68    ///     radius: 5.0.try_into()?,
69    /// });
70    /// let mut rng = StdRng::seed_from_u64(1);
71    ///
72    /// let point = sphere.sample(&mut rng);
73    /// assert!(sphere.0.is_point_inside(&point));
74    /// # Ok(())
75    /// # }
76    /// ```
77    #[inline]
78    fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> P {
79        self.0.sample(rng)
80    }
81}
82
83impl<T> Scale for Closed<T>
84where
85    T: Scale,
86{
87    /// Scale the wrapped shape.
88    ///
89    /// # Example
90    ///
91    /// ```
92    /// use hoomd_geometry::{Scale, shape::Sphere};
93    /// use hoomd_microstate::boundary::Closed;
94    ///
95    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
96    /// let sphere = Closed(Sphere {
97    ///     radius: 5.0.try_into()?,
98    /// });
99    ///
100    /// let scaled_sphere = sphere.scale_length(0.5.try_into()?);
101    ///
102    /// assert_eq!(scaled_sphere.0.radius.get(), 2.5);
103    /// # Ok(())
104    /// # }
105    /// ```
106    #[inline]
107    fn scale_length(&self, v: PositiveReal) -> Self {
108        Self(self.0.scale_length(v))
109    }
110
111    /// Scale the wrapped shape.
112    ///
113    /// # Example
114    ///
115    /// ```
116    /// use hoomd_geometry::{Scale, shape::Rectangle};
117    /// use hoomd_microstate::boundary::Closed;
118    ///
119    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
120    /// let closed = Closed(Rectangle::with_equal_edges(10.0.try_into()?));
121    ///
122    /// let scaled_closed = closed.scale_volume(4.0.try_into()?);
123    ///
124    /// assert_eq!(scaled_closed.0.edge_lengths[0].get(), 20.0);
125    /// # Ok(())
126    /// # }
127    /// ```
128    #[inline]
129    fn scale_volume(&self, v: PositiveReal) -> Self {
130        Self(self.0.scale_volume(v))
131    }
132}
133
134impl<P, T> MapPoint<P> for Closed<T>
135where
136    T: MapPoint<P>,
137{
138    /// Map a point in the wrapped shape to another.
139    ///
140    /// # Errors
141    ///
142    /// [`hoomd_geometry::Error::PointOutsideShape`] when `point` is outside
143    /// `self.shape()`.
144    ///
145    /// # Example
146    ///
147    /// ```
148    /// use hoomd_geometry::{MapPoint, shape::Rectangle};
149    /// use hoomd_microstate::boundary::Closed;
150    /// use hoomd_vector::Cartesian;
151    ///
152    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
153    /// let closed_a = Closed(Rectangle::with_equal_edges(10.0.try_into()?));
154    /// let closed_b = Closed(Rectangle::with_equal_edges(20.0.try_into()?));
155    ///
156    /// let mapped_point =
157    ///     closed_a.map_point(Cartesian::from([-1.0, 1.0]), &closed_b);
158    ///
159    /// assert_eq!(mapped_point, Ok(Cartesian::from([-2.0, 2.0])));
160    /// assert_eq!(
161    ///     closed_a.map_point(Cartesian::from([-100.0, 1.0]), &closed_b),
162    ///     Err(hoomd_geometry::Error::PointOutsideShape)
163    /// );
164    /// # Ok(())
165    /// # }
166    /// ```
167    #[inline]
168    fn map_point(&self, point: P, other: &Self) -> Result<P, hoomd_geometry::Error> {
169        self.0.map_point(point, &other.0)
170    }
171}
172
173impl<T> Volume for Closed<T>
174where
175    T: Volume,
176{
177    /// Volume of the wrapped shape.
178    ///
179    /// # Examples
180    ///
181    /// ```
182    /// use hoomd_geometry::{Volume, shape::Rectangle};
183    /// use hoomd_microstate::boundary::Closed;
184    ///
185    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
186    /// let closed = Closed(Rectangle::with_equal_edges(10.0.try_into()?));
187    ///
188    /// let volume = closed.volume();
189    ///
190    /// assert_eq!(volume, 100.0);
191    /// # Ok(())
192    /// # }
193    /// ```
194    #[inline]
195    fn volume(&self) -> f64 {
196        self.0.volume()
197    }
198}