hoomd_microstate/boundary.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//! Traits that describe boundary conditions and a selection of types that implement them.
5//!
6//! See the [crate-level documentation](crate) for an overview of how boundary
7//! conditions interact with [`Microstate`](crate::Microstate) and model methods.
8//!
9//! hoomd-rs* provides the boundary types [`Open`], [`Closed`], and [`Periodic`].
10//! * [`Open`] boundaries allow bodies and sites to exist anywhere in space.
11//! * [`Closed`] boundaries limit bodies and sites to the inside of a shape and
12//! are not periodic in any direction.
13//! * [`Periodic`] boundaries limit bodies and sites to the inside of a shape,
14//! wrap particles anywhere outside that shape back inside, and place ghosts
15//! following the periodic tiling of the shape.
16//!
17//! The documentation of [`Closed`] and [`Periodic`] describes the shapes that
18//! they each implement. If the shape you want is not supported, you can write
19//! a custom shape type and implement `IsInside` so that it will work with
20//! [`Closed`]. To implement a custom periodic boundary, create your custom
21//! type and implement both [`Wrap`] and [`GenerateGhosts`] for it.
22
23use arrayvec::ArrayVec;
24use thiserror::Error;
25
26mod closed;
27mod open;
28mod periodic;
29
30pub use closed::Closed;
31pub use open::Open;
32pub use periodic::Periodic;
33
34/// Enumerate possible sources of error in fallible boundary methods.
35#[non_exhaustive]
36#[derive(Error, PartialEq, Debug)]
37pub enum Error {
38 /// Failed to wrap body or site properties.
39 #[error("property cannot be wrapped")]
40 CannotWrapProperties,
41 /// The maximum interaction range is larger than the periodic boundary condition will allow.
42 #[error("the requested interaction range ({0}) is larger than the boundary will allow ({1})")]
43 InteractionRangeTooLarge(f64, f64),
44}
45
46/// The maximum number of possible ghosts.
47pub(crate) const MAX_GHOSTS: usize = 8;
48
49// Ideally, MAX_GHOSTS would be associated with the boundary type, but that is
50// not currently possible in Rust.
51
52/// Attempt to move any body/site properties back into the simulation boundary.
53///
54/// [`Wrap`] and [`GenerateGhosts`] together define the behavior of simulation
55/// boundary conditions.
56///
57/// The **boundary** defines the subset of points where bodies and sites are
58/// allowed. The [`wrap`] method takes a given body or site properties that
59/// is anywhere in space and attempts to wrap it back into the boundary. This
60/// process succeeds when the boundary is periodic and fails when it is not
61/// (some boundaries may be periodic in some directions and not in others).
62///
63/// The generic type name is:
64/// * `P`: The [`Body::properties`](crate::Body) or [`Site::properties`](crate::Site) type.
65///
66/// [`wrap`]: Self::wrap
67pub trait Wrap<P> {
68 /// Transform body/point properties into the boundary.
69 ///
70 /// `wrap` takes a body or site properties with a position that may be outside
71 /// the boundary. It attempts to wrap that position back inside following the
72 /// boundary's periodicity. `wrap` returns [`Ok(properties)`](Ok) when this
73 /// process is successful.
74 ///
75 /// # Errors
76 ///
77 /// `wrap` returns [`Error::CannotWrapProperties`] when it is not possible
78 /// to wrap the body into the boundary. For example, when the position is
79 /// outside the radius of a cylinder that is only periodic along its axis.
80 fn wrap(&self, properties: P) -> Result<P, Error>;
81}
82
83/// Place periodic images of sites within the interaction range.
84///
85/// [`Wrap`] and [`GenerateGhosts`] together define the behavior of simulation
86/// boundary conditions.
87///
88/// The **boundary** defines the subset of points where bodies and sites are
89/// allowed. [`generate_ghosts`] places 0 or more sites that are periodic images
90/// of the given site **and** within the maximum interaction range of the boundary.
91/// [`Closed`] boundary conditions place no ghosts. [`Periodic`] boundary conditions
92/// may place 0, 1, 2, or more ghosts depending on the location of the site. For
93/// example sites in the center of a cubic box will have 0 ghosts, those near the
94/// center of a face will have 1, those near an edge will have 2, and those near a
95/// vertex will have 3.
96///
97/// To avoid costly dynamic memory allocations, [`generate_ghosts`] returns an
98/// array-backed storage with a hard-coded maximum size of `MAX_GHOSTS`.
99///
100/// [`generate_ghosts`]: Self::generate_ghosts
101pub trait GenerateGhosts<S> {
102 /// The largest interaction distance between sites.
103 ///
104 /// The maximum interaction range is the largest distance between two
105 /// interacting sites. [`Microstate`](crate::Microstate) will place ghosts
106 /// within this range outside periodic boundaries.
107 fn maximum_interaction_range(&self) -> f64;
108
109 /// Place periodic images of sites within the interaction range.
110 ///
111 /// Given `site_properties` inside the boundary, `generate_ghosts` places
112 /// periodic images of that site. It must place all ghosts needed to compute
113 /// interactions with other sites in the given [`maximum_interaction_range`].
114 ///
115 /// [`maximum_interaction_range`]: Self::maximum_interaction_range
116 fn generate_ghosts(&self, _site_properties: &S) -> ArrayVec<S, MAX_GHOSTS>;
117}
118
119/// Compute the largest value of the maximum interaction range.
120///
121/// In *hoomd-rs*, sites interact only with the *minimum images* of other sites.
122/// In periodic boundary conditions, there is a maximum allowable interaction
123/// range beyond which sites would start interacting with multiple images. The
124/// [`MaximumAllowableInteractionRange`] trait computes that distance for a given
125/// shape.
126///
127/// [`Periodic`] uses [`MaximumAllowableInteractionRange`] to trigger an error when
128/// the caller requires an interaction range larger than is possible.
129pub trait MaximumAllowableInteractionRange {
130 /// The largest value that the maximum interaction range can take.
131 fn maximum_allowable_interaction_range(&self) -> f64;
132}