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}