hoomd_mc/translate/
cartesian.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 Translate for Cartesian
5
6use rand::{Rng, distr::Distribution};
7
8use super::Translate;
9use crate::LocalTrial;
10use hoomd_microstate::property::Position;
11use hoomd_vector::{Cartesian, distribution::Ball};
12
13impl<const N: usize, B> LocalTrial<B> for Translate<Cartesian<N>>
14where
15    B: Position<Position = Cartesian<N>>,
16    Ball: Distribution<Cartesian<N>>,
17{
18    /// Perturb a body's position by a random amount.
19    ///
20    /// # Example
21    ///
22    /// ```
23    /// use hoomd_mc::{LocalTrial, Translate};
24    /// use hoomd_microstate::property::Point;
25    /// use hoomd_vector::{Cartesian, InnerProduct};
26    /// use rand::{Rng, SeedableRng, rngs::StdRng};
27    ///
28    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
29    /// let mut rng = StdRng::seed_from_u64(1);
30    /// let body_properties = Point::new(Cartesian::from([0.0, 0.0]));
31    /// let d = 1.0;
32    /// let translate = Translate::with_maximum_distance(d.try_into()?);
33    ///
34    /// let new_body_properties = translate.propose(&mut rng, body_properties);
35    /// assert!(new_body_properties.position.norm() < 1.0);
36    /// # Ok(())
37    /// # }
38    /// ```
39    #[inline]
40    fn propose<R: Rng>(&self, rng: &mut R, body_properties: B) -> B {
41        let mut trial = body_properties;
42
43        let ball = Ball {
44            radius: self.maximum_distance,
45        };
46
47        let delta_r = ball.sample(rng);
48        *trial.position_mut() += delta_r;
49
50        trial
51    }
52}
53
54#[cfg(test)]
55mod tests {
56    use super::*;
57    use hoomd_microstate::property::Point;
58    use hoomd_vector::{Cartesian, InnerProduct};
59    use rand::{SeedableRng, rngs::StdRng};
60    use rstest::*;
61
62    /// Number of trial moves to test.
63    const N: usize = 2048;
64
65    #[rstest]
66    fn translate(#[values(0.1, 1.0)] d: f64) {
67        // Ensure that `Translate` proposes moves that translate the body with a valid
68        // range of maximum distances.
69
70        let mut total = Cartesian::from([0.0, 0.0, 0.0]);
71        let mut min_norm = f64::INFINITY;
72        let mut max_norm = 0.0_f64;
73
74        let mut rng = StdRng::seed_from_u64(1);
75        let a = Point::new(Cartesian::from([1.0, -5.0, 2.5]));
76        let translate = Translate::<Cartesian<3>>::with_maximum_distance(
77            d.try_into()
78                .expect("hard-coded constant should be a positive real"),
79        );
80
81        for _ in 0..N {
82            let b = translate.propose(&mut rng, a);
83
84            let delta_r = b.position - a.position;
85            total += delta_r;
86            min_norm = min_norm.min(delta_r.norm());
87            max_norm = max_norm.max(delta_r.norm());
88        }
89
90        assert!(max_norm <= d);
91
92        let average = total / N as f64;
93
94        // Validate with appropriately loose tolerances to account for the small sample size.
95        assert!(min_norm < d * 0.1);
96        assert!(max_norm > d * 0.9);
97        for x in average.coordinates {
98            assert!(x.abs() < d * 0.1);
99        }
100    }
101}