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