hoomd_microstate/property/
point.rs1use serde::{Deserialize, Serialize};
7
8use super::Position;
9use crate::Transform;
10use hoomd_manifold::{Hyperbolic, Minkowski, Spherical};
11use hoomd_vector::Cartesian;
12
13#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)]
26pub struct Point<P> {
27 pub position: P,
29}
30
31impl<P> Point<P> {
32 #[inline]
43 #[must_use]
44 pub fn new(position: P) -> Self {
45 Self { position }
46 }
47}
48
49impl<const N: usize> Transform<Point<Cartesian<N>>> for Point<Cartesian<N>> {
51 #[inline]
68 fn transform(&self, site_properties: &Point<Cartesian<N>>) -> Point<Cartesian<N>> {
69 Point {
70 position: self.position + site_properties.position,
71 }
72 }
73}
74
75impl Transform<Point<Hyperbolic<3>>> for Point<Hyperbolic<3>> {
76 #[inline]
86 fn transform(&self, site_properties: &Point<Hyperbolic<3>>) -> Point<Hyperbolic<3>> {
87 let body_pos = self.position.coordinates();
88 let body_theta = body_pos[1].atan2(body_pos[0]);
89 let body_boost = (body_pos[2]).acosh();
90 let site_pos = site_properties.position.coordinates();
91 let transformed_point = Minkowski::from([
92 site_pos[0]
93 * ((body_boost.cosh()) * (body_theta.cos()).powi(2) + (body_theta.sin()).powi(2))
94 + site_pos[1]
95 * (body_theta.sin())
96 * (body_theta.cos())
97 * ((body_boost.cosh()) - 1.0)
98 + site_pos[2] * (body_boost.sinh()) * (body_theta.cos()),
99 site_pos[0] * (body_theta.sin()) * (body_theta.cos()) * ((body_boost.cosh()) - 1.0)
100 + site_pos[1]
101 * ((body_boost.cosh()) * (body_theta.sin()).powi(2)
102 + (body_theta.cos()).powi(2))
103 + site_pos[2] * (body_boost.sinh()) * (body_theta.sin()),
104 site_pos[0] * (body_boost.sinh()) * (body_theta.cos())
105 + site_pos[1] * (body_boost.sinh()) * (body_theta.sin())
106 + site_pos[2] * (body_boost.cosh()),
107 ]);
108 let new_hyperbolic = Hyperbolic::from_minkowski_coordinates(transformed_point);
109 Point::new(new_hyperbolic)
110 }
111}
112
113impl Transform<Point<Hyperbolic<4>>> for Point<Hyperbolic<4>> {
114 #[inline]
124 fn transform(&self, site_properties: &Point<Hyperbolic<4>>) -> Point<Hyperbolic<4>> {
125 let body_point = self.position.coordinates();
126 let body_theta = (body_point[2].powi(2) + body_point[1].powi(2))
127 .sqrt()
128 .atan2(body_point[0]);
129 let body_phi = body_point[2].atan2(body_point[1]);
130 let body_boost = (body_point[3]).acosh();
131 let site_pos = site_properties.position.coordinates();
132 let transformed_point = Minkowski::from([
133 site_pos[0]
134 * ((body_boost.cosh()) * ((body_theta.cos()).powi(2))
135 + ((body_theta.sin()).powi(2)))
136 + site_pos[1]
137 * (body_phi.cos())
138 * (body_theta.sin())
139 * (body_theta.cos())
140 * ((body_boost.cosh()) - 1.0)
141 + site_pos[2]
142 * (body_phi.sin())
143 * (body_theta.sin())
144 * (body_theta.cos())
145 * ((body_boost.cosh()) - 1.0)
146 + site_pos[3] * (body_boost.sinh()) * (body_theta.cos()),
147 site_pos[0]
148 * (body_phi.cos())
149 * (body_theta.sin())
150 * (body_theta.cos())
151 * ((body_boost.cosh()) - 1.0)
152 + site_pos[1]
153 * (((body_phi.cos()).powi(2))
154 * ((body_boost.cosh()) * ((body_theta.sin()).powi(2))
155 + ((body_theta.cos()).powi(2)))
156 + ((body_phi.sin()).powi(2)))
157 + site_pos[2]
158 * (body_phi.sin())
159 * (body_phi.cos())
160 * ((body_boost.cosh()) * ((body_theta.sin()).powi(2))
161 + ((body_theta.cos()).powi(2))
162 - 1.0)
163 + site_pos[3] * (body_boost.sinh()) * (body_theta.sin()) * (body_phi.cos()),
164 site_pos[0]
165 * (body_theta.cos())
166 * (body_theta.sin())
167 * (body_phi.sin())
168 * ((body_boost.cosh()) - 1.0)
169 + site_pos[1]
170 * (body_phi.cos())
171 * (body_phi.sin())
172 * ((body_boost.cosh()) * ((body_theta.sin()).powi(2))
173 + ((body_theta.cos()).powi(2))
174 - 1.0)
175 + site_pos[2]
176 * (((body_phi.sin()).powi(2))
177 * ((body_boost.cosh()) * ((body_theta.sin()).powi(2))
178 + ((body_theta.cos()).powi(2)))
179 + ((body_phi.cos()).powi(2)))
180 + site_pos[3] * (body_boost.sinh()) * (body_theta.sin()) * (body_phi.sin()),
181 site_pos[0] * (body_boost.sinh()) * (body_theta.cos())
182 + site_pos[1] * (body_boost.sinh()) * (body_phi.cos()) * (body_theta.sin())
183 + site_pos[2] * (body_boost.sinh()) * (body_phi.sin()) * (body_theta.sin())
184 + site_pos[3] * (body_boost.cosh()),
185 ]);
186 let new_hyperbolic = Hyperbolic::from_minkowski_coordinates(transformed_point);
187 Point::new(new_hyperbolic)
188 }
189}
190
191impl Transform<Point<Spherical<3>>> for Point<Spherical<3>> {
192 #[inline]
193 fn transform(&self, site_properties: &Point<Spherical<3>>) -> Point<Spherical<3>> {
203 let body_point = self.position.coordinates();
204 let body_phi = body_point[1].atan2(body_point[0]);
205 let body_theta = (body_point[2]).acos();
206 let trial_coords = site_properties.position.coordinates();
207 let transformed_point = Cartesian::from([
208 trial_coords[0] * (body_theta.cos()) * (body_phi.cos())
209 - trial_coords[1] * (body_phi.sin())
210 + trial_coords[2] * (body_theta.sin()) * (body_phi.cos()),
211 trial_coords[0] * (body_theta.cos()) * (body_phi.sin())
212 + trial_coords[1] * (body_phi.cos())
213 + trial_coords[2] * (body_theta.sin()) * (body_phi.sin()),
214 -trial_coords[0] * (body_theta.sin()) + trial_coords[2] * (body_theta.cos()),
215 ]);
216 let new_sphere = Spherical::from_cartesian_coordinates(transformed_point);
217 Point::new(new_sphere)
218 }
219}
220
221impl Transform<Point<Spherical<4>>> for Point<Spherical<4>> {
222 #[inline]
232 fn transform(&self, site_properties: &Point<Spherical<4>>) -> Point<Spherical<4>> {
233 let body_point = self.position.coordinates();
234 let body_phi_1 = (body_point[2].powi(2) + body_point[1].powi(2))
235 .sqrt()
236 .atan2(body_point[0]);
237 let body_theta = (body_point[0].powi(2) + body_point[1].powi(2) + body_point[2].powi(2))
238 .sqrt()
239 .atan2(body_point[3]);
240 let body_phi_2 = body_point[2].atan2(body_point[1]);
241 let trial_coords = site_properties.position.coordinates();
242 let transformed_point = Cartesian::from([
243 trial_coords[0] * (body_theta.cos()) * (body_phi_1.cos())
244 - trial_coords[1] * (body_phi_1.sin())
245 + trial_coords[3] * (body_theta.sin()) * (body_phi_1.cos()),
246 trial_coords[0] * (body_theta.cos()) * (body_phi_1.sin()) * (body_phi_2.cos())
247 + trial_coords[1] * (body_phi_1.cos()) * (body_phi_2.cos())
248 - trial_coords[2] * (body_phi_2.sin())
249 + trial_coords[3] * (body_theta.sin()) * (body_phi_1.sin()) * (body_phi_2.cos()),
250 trial_coords[0] * (body_theta.cos()) * (body_phi_1.sin()) * (body_phi_2.sin())
251 + trial_coords[1] * (body_phi_1.cos()) * (body_phi_2.sin())
252 + trial_coords[2] * (body_phi_2.cos())
253 + trial_coords[3] * (body_theta.sin()) * (body_phi_1.sin()) * (body_phi_2.sin()),
254 -trial_coords[0] * (body_theta.sin()) + trial_coords[3] * (body_theta.cos()),
255 ]);
256 let new_sphere = Spherical::from_cartesian_coordinates(transformed_point);
257 Point::new(new_sphere)
258 }
259}
260
261impl<P> Position for Point<P> {
262 type Position = P;
263
264 #[inline]
265 fn position(&self) -> &P {
266 &self.position
267 }
268
269 #[inline]
270 fn position_mut(&mut self) -> &mut P {
271 &mut self.position
272 }
273}
274
275#[cfg(test)]
276mod tests {
277 use super::*;
278
279 use approxim::assert_relative_eq;
280 use hoomd_manifold::{Hyperbolic, Spherical};
281 use hoomd_vector::Cartesian;
282 use std::f64::consts::PI;
283
284 #[test]
285 fn transform_point() {
286 let body = Point::new(Cartesian::from([3.0, -4.0, 5.0]));
287 let site = Point::new(Cartesian::from([-1.0, 2.0, -3.0]));
288 let transformed_site = body.transform(&site);
289 assert_eq!(transformed_site.position, [2.0, -2.0, 2.0].into());
290 }
291
292 #[test]
293 fn transform_h2_point() {
294 let boost: f64 = 1.3;
295 let bump: f64 = 0.1;
296 let body = Point::new(Hyperbolic::<3>::from_polar_coordinates(boost, 0.0));
297 let site = Point::new(Hyperbolic::<3>::from_polar_coordinates(bump, PI / 2.0));
298 let transformed_site = body.transform(&site);
299 assert_relative_eq!(
300 *transformed_site.position().point(),
301 [
302 (boost.sinh()) * (bump.cosh()),
303 bump.sinh(),
304 (boost.cosh()) * (bump.cosh())
305 ]
306 .into(),
307 epsilon = 1e-12
308 );
309 }
310
311 #[test]
312 fn transform_h3_point() {
313 let boost: f64 = 1.4;
314 let bump: f64 = 0.7;
315 let body = Point::new(Hyperbolic::<4>::from_polar_coordinates(boost, 0.0, 0.0));
316 let site = Point::new(Hyperbolic::<4>::from_polar_coordinates(bump, PI / 2.0, 0.0));
317 let transformed_site = body.transform(&site);
318 assert_relative_eq!(
319 *transformed_site.position().point(),
320 [
321 (boost.sinh()) * (bump.cosh()),
322 bump.sinh(),
323 0.0,
324 (boost.cosh()) * (bump.cosh())
325 ]
326 .into(),
327 epsilon = 1e-12
328 );
329 }
330
331 #[test]
332 fn transform_s2_point() {
333 let theta = PI / 5.0;
334 let blip = PI / 10.0;
335 let body = Point::new(Spherical::<3>::from_polar_coordinates(theta, 0.0));
336 let site = Point::new(Spherical::<3>::from_polar_coordinates(blip, PI / 2.0));
337 let transformed_site = body.transform(&site);
338 assert_relative_eq!(
339 *transformed_site.position().point(),
340 [
341 (theta.sin()) * (blip.cos()),
342 blip.sin(),
343 (theta.cos()) * (blip.cos())
344 ]
345 .into()
346 );
347 }
348
349 #[test]
350 fn transform_s3_point() {
351 let theta = PI / 5.0;
352 let blip = PI / 10.0;
353 let body = Point::new(Spherical::<4>::from_polar_coordinates(theta, 0.0, 0.0));
354 let site = Point::new(Spherical::<4>::from_polar_coordinates(blip, PI / 2.0, 0.0));
355 let transformed_site = body.transform(&site);
356 assert_relative_eq!(
357 *transformed_site.position().point(),
358 [
359 (theta.sin()) * (blip.cos()),
360 blip.sin(),
361 0.0,
362 (theta.cos()) * (blip.cos())
363 ]
364 .into()
365 );
366 }
367}