hoomd_interaction/pairwise/anisotropic.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 `Anisotropic`
5
6use serde::{Deserialize, Serialize};
7
8use super::AnisotropicEnergy;
9use crate::{MaximumInteractionRange, SitePairEnergy};
10use hoomd_microstate::property::{Orientation, Position};
11use hoomd_vector::{Rotate, Rotation, Vector};
12
13/// Compute anisotropic properties from a pair of sites.
14///
15/// [`Anisotropic`] provides a single implementation that computes pairwise
16/// interactions that are a function of the sites' positions and orientations.
17/// It fills the gap between traits like [`SitePairEnergy`] which operates on
18/// site properties and [`AnisotropicEnergy`] which is a function only of the
19/// relative position and orientation.
20///
21/// Use [`Anisotropic`] with [`PairwiseCutoff`] in MD and MC simulations.
22///
23/// [`PairwiseCutoff`]: crate::PairwiseCutoff
24///
25/// # Example
26///
27/// ```
28/// use hoomd_interaction::{
29/// pairwise::{AngularMask, Anisotropic, angular_mask::Patch},
30/// univariate::Boxcar,
31/// };
32/// use hoomd_vector::Angle;
33/// use std::f64::consts::PI;
34///
35/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
36/// let boxcar = Boxcar {
37/// epsilon: -1.0,
38/// left: 1.0,
39/// right: 1.5,
40/// };
41/// let masks = [Patch {
42/// director: [1.0, 0.0].try_into()?,
43/// cos_delta: (PI / 8.0).cos(),
44/// }];
45///
46/// let angular_mask = Anisotropic {
47/// interaction: AngularMask::new(boxcar, masks),
48/// r_cut: 1.5,
49/// };
50/// # Ok(())
51/// # }
52/// ```
53#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
54pub struct Anisotropic<E> {
55 /// The site-site interaction.
56 pub interaction: E,
57 /// Maximum distance between two interacting sites.
58 pub r_cut: f64,
59}
60
61impl<P, R, S, E> SitePairEnergy<S> for Anisotropic<E>
62where
63 S: Position<Position = P> + Orientation<Rotation = R>,
64 P: Vector,
65 R: Rotation + Rotate<P>,
66 E: AnisotropicEnergy<P, R>,
67{
68 /// Compute the pair energy between two sites.
69 ///
70 ///
71 /// # Example
72 ///
73 /// ```
74 /// use hoomd_interaction::{
75 /// SitePairEnergy,
76 /// pairwise::{AngularMask, Anisotropic, angular_mask::Patch},
77 /// univariate::Boxcar,
78 /// };
79 /// use hoomd_microstate::property::OrientedPoint;
80 /// use hoomd_vector::{Angle, Cartesian};
81 /// use std::f64::consts::PI;
82 ///
83 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
84 /// let boxcar = Boxcar {
85 /// epsilon: -1.0,
86 /// left: 1.0,
87 /// right: 1.5,
88 /// };
89 /// let masks = [Patch {
90 /// director: [1.0, 0.0].try_into()?,
91 /// cos_delta: (PI / 8.0).cos(),
92 /// }];
93 ///
94 /// let angular_mask = Anisotropic {
95 /// interaction: AngularMask::new(boxcar, masks),
96 /// r_cut: 1.5,
97 /// };
98 ///
99 /// let a = OrientedPoint {
100 /// position: Cartesian::from([0.0, 0.0]),
101 /// orientation: Angle::from(0.0),
102 /// };
103 /// let b = OrientedPoint {
104 /// position: Cartesian::from([1.0, 0.0]),
105 /// orientation: Angle::from(0.0),
106 /// };
107 /// let energy = angular_mask.site_pair_energy(&a, &b);
108 /// assert_eq!(energy, 0.0);
109 ///
110 /// let c = OrientedPoint {
111 /// position: Cartesian::from([1.0, 0.0]),
112 /// orientation: Angle::from(PI),
113 /// };
114 /// let energy = angular_mask.site_pair_energy(&a, &c);
115 /// assert_eq!(energy, -1.0);
116 /// # Ok(())
117 /// # }
118 /// ```
119 #[inline]
120 fn site_pair_energy(&self, site_properties_i: &S, site_properties_j: &S) -> f64 {
121 let r = site_properties_i
122 .position()
123 .distance(site_properties_j.position());
124 if r >= self.r_cut {
125 return 0.0;
126 }
127
128 let (r_ab, o_ab) = hoomd_vector::pair_system_to_local(
129 site_properties_i.position(),
130 site_properties_i.orientation(),
131 site_properties_j.position(),
132 site_properties_j.orientation(),
133 );
134 self.interaction.energy(&r_ab, &o_ab)
135 }
136}
137
138impl<E> MaximumInteractionRange for Anisotropic<E> {
139 #[inline]
140 fn maximum_interaction_range(&self) -> f64 {
141 self.r_cut
142 }
143}