Skip to main content

hoomd_interaction/
rigid.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 Rigid.
5
6use serde::{Deserialize, Serialize};
7use std::ops::{Add, AddAssign, Sub};
8
9use crate::{
10    DeltaEnergyInsert, DeltaEnergyOne, DeltaEnergyRemove, MaximumInteractionRange,
11    NetBodyForceAndVirial, NetBodyForceVirialAndTorque, NetSiteForceAndVirial,
12    NetSiteForceVirialAndTorque, TotalEnergy,
13};
14use hoomd_microstate::{
15    Body, Microstate, Transform,
16    property::{Orientation, Position},
17};
18use hoomd_vector::{Outer, Rotate, Vector, Wedge};
19
20/// Rigid body interactions.
21///
22/// The [`Rigid`] newtype implements [`NetBodyForceAndVirial`]  for wrapped force
23/// interaction model types that implement [`NetSiteForceAndVirial`]. It also implements
24/// [`NetBodyForceVirialAndTorque`] for interaction model types that implement
25/// [`NetSiteForceVirialAndTorque`].
26///
27/// [`Rigid`] computes the net force and torque on a rigid body that results
28/// from the forces/torques on all of its sites:
29/// ```math
30/// \vec{F}_\mathrm{body} = \sum_{i \in \mathrm{body}} \vec{F}_{i}
31/// ```
32/// ```math
33/// \vec{\tau}_\mathrm{body} = \sum_{i \in \mathrm{body}} (\mathbf{q}_\mathrm{body} \cdot \vec{r}_{\mathrm{body},i} \cdot \mathbf{q}_\mathrm{body}^*) \wedge \vec{F}_i + \vec{\tau}_{i}
34/// ```
35///
36/// The generic type names are:
37/// * `F`: The evaluator that implements [`NetSiteForceAndVirial`] and/or [`NetSiteForceVirialAndTorque`].
38///
39/// # Example
40///
41/// ```
42/// use hoomd_interaction::{
43///     PairwiseCutoff, Rigid, pairwise::Isotropic, univariate::LennardJones,
44/// };
45///
46/// let lennard_jones: LennardJones = LennardJones {
47///     epsilon: 1.0,
48///     sigma: 1.0,
49/// };
50/// let evaluator = Isotropic {
51///     interaction: lennard_jones,
52///     r_cut: 2.5,
53/// };
54/// let rigid = Rigid(PairwiseCutoff(evaluator));
55/// ```
56#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
57pub struct Rigid<F>(pub F);
58
59impl<V, B, S, X, C, F> NetBodyForceAndVirial<B, S, X, C> for Rigid<F>
60where
61    V: Vector + Default + Outer,
62    B: Transform<S> + Position<Position = V>,
63    S: Position<Position = V>,
64    F: NetSiteForceAndVirial<B, S, X, C, Force = V>,
65    V::Tensor: Default + AddAssign + Sub<Output = V::Tensor>,
66{
67    type Force = V;
68
69    /// Compute the net force and virial on a body in the microstate.
70    ///
71    /// The net force and virial on a body are the sums of the net forces and
72    /// virials on all sites in the body:
73    ///
74    /// ```math
75    /// \begin{align*}
76    /// \vec{F}_\mathrm{body} &= \sum_{\mathrm{site} \in \mathrm{body}} \vec{F}_\mathrm{site} \\
77    /// \mathbf{W}_\mathrm{body} &= \sum_{\mathrm{site} \in \mathrm{body}} \mathbf{W}_\mathrm{site} - \mathbf{F}_\mathrm{site} \otimes \left( \vec{r}_\mathrm{site}^\mathrm{global} - \vec{r}_{body}^\mathrm{global} \right) \\
78    /// \end{align*}
79    /// ```
80    ///
81    /// where the net site forces and virials are given by `F`'s implementation of
82    /// [`NetSiteForceAndVirial`], and $`\vec{r}_\mathrm{site}^\mathrm{global}`$ and $`\vec{r}_\mathrm{body}^\mathrm{global}`$
83    /// are the positions in the global frame of the site and body, respectively.
84    ///
85    /// The second term in the virial summation is required to correct for
86    /// the centripetal forces implicit in the rigid body constraint. For more
87    /// information, see [Glaser et al. 2020](https://doi.org/10.1016/j.commatsci.2019.109430),
88    /// especially equations 23 and 24 and algorithm 2.
89    ///
90    /// # Example
91    /// ```
92    /// use approxim::assert_relative_eq;
93    ///
94    /// use hoomd_interaction::{
95    ///     NetBodyForceAndVirial, PairwiseCutoff, Rigid, pairwise::Isotropic,
96    ///     univariate::LennardJones,
97    /// };
98    /// use hoomd_linear_algebra::matrix::Matrix;
99    /// use hoomd_microstate::{
100    ///     Body, Microstate,
101    ///     boundary::Open,
102    ///     property::{OrientedPoint, Point},
103    /// };
104    /// use hoomd_vector::{Cartesian, Versor};
105    ///
106    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
107    /// let mut microstate = Microstate::new();
108    /// microstate.extend_bodies([
109    ///     Body::single_site(
110    ///         OrientedPoint {
111    ///             position: Cartesian::from([0.0, 0.0, 0.0]),
112    ///             orientation: Versor::default(),
113    ///         },
114    ///         Point::new(Cartesian::<3>::default()),
115    ///     ),
116    ///     Body::single_site(
117    ///         OrientedPoint {
118    ///             position: Cartesian::from([1.0, 0.0, 0.0]),
119    ///             orientation: Versor::default(),
120    ///         },
121    ///         Point::new(Cartesian::<3>::default()),
122    ///     ),
123    /// ])?;
124    ///
125    /// let lennard_jones: LennardJones = LennardJones {
126    ///     epsilon: 1.0,
127    ///     sigma: 1.0,
128    /// };
129    ///
130    /// let force_interaction_model = PairwiseCutoff(Isotropic {
131    ///     interaction: lennard_jones,
132    ///     r_cut: 2.5,
133    /// });
134    /// let rigid = Rigid(force_interaction_model);
135    ///
136    /// let (body_force_0, body_virial_0) =
137    ///     rigid.net_body_force_and_virial(&microstate, 0);
138    /// let (body_force_1, body_virial_1) =
139    ///     rigid.net_body_force_and_virial(&microstate, 1);
140    ///
141    /// assert_relative_eq!(body_force_0, Cartesian::from([-24.0, 0.0, 0.0]));
142    /// assert_eq!(
143    ///     body_virial_0,
144    ///     Matrix {
145    ///         rows: [[12.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0, 0.0]]
146    ///     }
147    /// );
148    ///
149    /// assert_relative_eq!(body_force_1, Cartesian::from([24.0, 0.0, 0.0]));
150    /// assert_eq!(
151    ///     body_virial_1,
152    ///     Matrix {
153    ///         rows: [[12.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0, 0.0]]
154    ///     }
155    /// );
156    /// # Ok(())
157    /// # }
158    /// ```
159    #[inline]
160    fn net_body_force_and_virial(
161        &self,
162        microstate: &Microstate<B, S, X, C>,
163        body_index: usize,
164    ) -> (V, V::Tensor) {
165        let body_position_global = microstate.bodies()[body_index].item.properties.position();
166
167        let mut total_force = V::default();
168        let mut total_virial = V::Tensor::default();
169
170        for (body_site_index, microstate_site_index) in
171            microstate.iter_body_site_indices(body_index).enumerate()
172        {
173            let (site_force, site_virial) = self
174                .0
175                .net_site_force_and_virial(microstate, microstate_site_index);
176
177            // NetBodyForceAndVirial is implemented with as few assumptions as possible.
178            // Use Transform to discover the relative position of the site in the global
179            // frame and then subtract to find it relative to the body.
180            let body = &microstate.bodies()[body_index];
181            let site_position_global = *body
182                .item
183                .properties
184                .transform(&body.item.sites[body_site_index])
185                .position();
186
187            let virial_correction =
188                site_force.outer(&(site_position_global - *body_position_global));
189
190            total_force += site_force;
191            total_virial += site_virial - virial_correction;
192        }
193        (total_force, total_virial)
194    }
195}
196
197impl<V, B, S, X, C, F, R> NetBodyForceVirialAndTorque<B, S, X, C> for Rigid<F>
198where
199    V: Vector + Wedge + Default + Outer,
200    B: Transform<S> + Orientation<Rotation = R> + Position<Position = V>,
201    S: Position<Position = V>,
202    F: NetSiteForceVirialAndTorque<B, S, X, C, Force = V>,
203    R: Rotate<V>,
204    V::Bivector: Default + Add<Output = V::Bivector> + AddAssign,
205    V::Tensor: Default + AddAssign + Sub<Output = V::Tensor>,
206{
207    type Force = V;
208
209    /// Compute the net force, virial, and torque on a body in the microstate.
210    ///
211    /// The net force and virial on a body are the sums of the net forces and
212    /// virials on all sites in the body, and the net torque is the sum of the
213    /// torques resulting from those forces *and* intrinsic torques applied to
214    /// the sites:
215    ///
216    /// ```math
217    /// \begin{align*}
218    /// \vec{F}_\mathrm{body} &= \sum_{\mathrm{site} \in \mathrm{body}} \vec{F}_\mathrm{site} \\
219    /// \mathbf{W}_\mathrm{body} &= \sum_{\mathrm{site} \in \mathrm{body}} \mathbf{W}_\mathrm{site} - \mathbf{F}_\mathrm{site} \otimes \left( \vec{r}_\mathrm{site}^\mathrm{global} - \vec{r}_\mathrm{body}^\mathrm{global} \right) \\
220    /// \vec{\tau}_\mathrm{body} &= \sum_{\mathrm{site} \in \mathrm{body}} (\mathbf{q}_{body} \cdot \vec{r}_{body,site} \cdot \mathbf{q}_{body}^*) \wedge \vec{F}_\mathrm{site} + \vec{\tau}_\mathrm{site} \\
221    /// \end{align*}
222    /// ```
223    ///
224    /// where $` \mathbf{q}_{body} `$ is the body's orientation,
225    /// $` \vec{r}_{body,site} `$ is the position of site *i* in the body
226    /// frame, and the net site forces, virials, and torques are given by `F`'s
227    /// implementation of [`NetSiteForceVirialAndTorque`].
228    ///
229    /// The symbol $` \wedge `$ denotes the [`Wedge`] product. The resulting torque
230    /// $` \vec{\tau}_{body} `$ is in the system frame.
231    ///
232    /// The second term in the virial summation is required to correct for
233    /// the centripetal forces implicit in the rigid body constraint. For more
234    /// information, see [Glaser et al. 2020](https://doi.org/10.1016/j.commatsci.2019.109430),
235    /// especially equations 23 and 24 and algorithm 2.
236    ///
237    /// # Example
238    /// ```
239    /// use hoomd_interaction::{
240    ///     NetBodyForceVirialAndTorque, PairwiseCutoff, Rigid,
241    ///     pairwise::Isotropic, univariate::LennardJones,
242    /// };
243    ///
244    /// use hoomd_linear_algebra::matrix::Matrix;
245    /// use hoomd_microstate::{
246    ///     Body, Microstate,
247    ///     boundary::Open,
248    ///     property::{OrientedPoint, Point},
249    /// };
250    /// use hoomd_vector::{Cartesian, Versor};
251    ///
252    /// use approxim::assert_relative_eq;
253    ///
254    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
255    /// let mut microstate = Microstate::new();
256    /// microstate.extend_bodies([
257    ///     Body::single_site(
258    ///         OrientedPoint {
259    ///             position: Cartesian::from([0.0, 2.0, 0.0]),
260    ///             orientation: Versor::default(),
261    ///         },
262    ///         Point::new(Cartesian::from([0.0, -2.0, 0.0])),
263    ///     ),
264    ///     Body::single_site(
265    ///         OrientedPoint {
266    ///             position: Cartesian::from([1.0, 0.0, 0.0]),
267    ///             orientation: Versor::default(),
268    ///         },
269    ///         Point::new(Cartesian::<3>::default()),
270    ///     ),
271    /// ])?;
272    ///
273    /// let lennard_jones: LennardJones = LennardJones {
274    ///     epsilon: 1.0,
275    ///     sigma: 1.0,
276    /// };
277    ///
278    /// let force_interaction_model = PairwiseCutoff(Isotropic {
279    ///     interaction: lennard_jones,
280    ///     r_cut: 2.5,
281    /// });
282    /// let rigid = Rigid(force_interaction_model);
283    ///
284    /// let (body_force, body_virial, body_torque) =
285    ///     rigid.net_body_force_virial_and_torque(&microstate, 0);
286    ///
287    /// assert_relative_eq!(body_force, Cartesian::from([-24.0, 0.0, 0.0]));
288    /// assert_relative_eq!(body_torque, Cartesian::from([0.0, 0.0, -48.0]));
289    /// assert_eq!(
290    ///     body_virial,
291    ///     Matrix {
292    ///         rows: [[12.0, -48.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0, 0.0]]
293    ///     }
294    /// );
295    ///
296    /// let (body_force, body_virial, body_torque) =
297    ///     rigid.net_body_force_virial_and_torque(&microstate, 1);
298    ///
299    /// assert_relative_eq!(body_force, Cartesian::from([24.0, 0.0, 0.0]));
300    /// assert_relative_eq!(body_torque, Cartesian::from([0.0, 0.0, 0.0]));
301    /// assert_eq!(
302    ///     body_virial,
303    ///     Matrix {
304    ///         rows: [[12.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0, 0.0]]
305    ///     }
306    /// );
307    /// # Ok(())
308    /// # }
309    /// ```
310    #[inline]
311    fn net_body_force_virial_and_torque(
312        &self,
313        microstate: &Microstate<B, S, X, C>,
314        body_index: usize,
315    ) -> (V, V::Tensor, V::Bivector) {
316        let mut total_force = V::default();
317        let mut total_virial = V::Tensor::default();
318        let mut total_torque = V::Bivector::default();
319
320        let q = microstate.bodies()[body_index]
321            .item
322            .properties
323            .orientation();
324
325        for (body_site_index, microstate_site_index) in
326            microstate.iter_body_site_indices(body_index).enumerate()
327        {
328            let site_body_frame = &microstate.bodies()[body_index].item.sites[body_site_index];
329            let r_body_frame = site_body_frame.position();
330            let r = q.rotate(r_body_frame);
331            let (site_force, site_virial, site_torque) = self
332                .0
333                .net_site_force_virial_and_torque(microstate, microstate_site_index);
334
335            let virial_correction = site_force.outer(&r);
336
337            total_force += site_force;
338            total_virial += site_virial - virial_correction;
339            total_torque += r.wedge(&site_force) + site_torque;
340        }
341
342        (total_force, total_virial, total_torque)
343    }
344}
345
346impl<F> MaximumInteractionRange for Rigid<F>
347where
348    F: MaximumInteractionRange,
349{
350    #[inline]
351    fn maximum_interaction_range(&self) -> f64 {
352        self.0.maximum_interaction_range()
353    }
354}
355
356impl<M, F> TotalEnergy<M> for Rigid<F>
357where
358    F: TotalEnergy<M>,
359{
360    #[inline]
361    fn total_energy(&self, microstate: &M) -> f64 {
362        self.0.total_energy(microstate)
363    }
364
365    #[inline]
366    fn delta_energy_total(&self, initial_microstate: &M, final_microstate: &M) -> f64 {
367        self.0
368            .delta_energy_total(initial_microstate, final_microstate)
369    }
370}
371
372impl<B, S, X, C, F> DeltaEnergyOne<B, S, X, C> for Rigid<F>
373where
374    F: DeltaEnergyOne<B, S, X, C>,
375{
376    #[inline]
377    fn delta_energy_one(
378        &self,
379        initial_microstate: &Microstate<B, S, X, C>,
380        body_index: usize,
381        final_body: &Body<B, S>,
382    ) -> f64 {
383        self.0
384            .delta_energy_one(initial_microstate, body_index, final_body)
385    }
386}
387
388impl<B, S, X, C, F> DeltaEnergyInsert<B, S, X, C> for Rigid<F>
389where
390    F: DeltaEnergyInsert<B, S, X, C>,
391{
392    #[inline]
393    fn delta_energy_insert(
394        &self,
395        initial_microstate: &Microstate<B, S, X, C>,
396        new_body: &Body<B, S>,
397    ) -> f64 {
398        self.0.delta_energy_insert(initial_microstate, new_body)
399    }
400}
401
402impl<B, S, X, C, F> DeltaEnergyRemove<B, S, X, C> for Rigid<F>
403where
404    F: DeltaEnergyRemove<B, S, X, C>,
405{
406    #[inline]
407    fn delta_energy_remove(
408        &self,
409        initial_microstate: &Microstate<B, S, X, C>,
410        body_index: usize,
411    ) -> f64 {
412        self.0.delta_energy_remove(initial_microstate, body_index)
413    }
414}