1use hoomd_geometry::shape::Hypercuboid;
7use hoomd_gsd::hoomd::{AppendError, Dimensions, Frame, HoomdGsdFile};
8use hoomd_manifold::{Hyperbolic, Spherical};
9use hoomd_vector::{Angle, Cartesian, Versor};
10
11use crate::{
12 AppendMicrostate, Microstate,
13 boundary::{Closed, Periodic},
14 property::{OrientedHyperbolicPoint, OrientedPoint, Point},
15};
16
17impl<B, X> AppendMicrostate<B, Point<Cartesian<2>>, X, Closed<Hypercuboid<2>>> for HoomdGsdFile {
18 #[inline]
19 fn append_microstate(
20 &mut self,
21 microstate: &Microstate<B, Point<Cartesian<2>>, X, Closed<Hypercuboid<2>>>,
22 ) -> Result<Frame<'_>, AppendError> {
23 self.append_frame(microstate.step())?
24 .configuration_box(microstate.boundary().0.to_gsd_box())?
25 .configuration_dimensions(Dimensions::Two)?
26 .particles_position(
27 microstate
28 .iter_sites_tag_order()
29 .map(|s| s.properties.position)
30 .map(|p| [p[0], p[1], 0.0].into()),
31 )
32 }
33}
34
35impl<B, X> AppendMicrostate<B, Point<Cartesian<2>>, X, Periodic<Hypercuboid<2>>> for HoomdGsdFile {
36 #[inline]
37 fn append_microstate(
38 &mut self,
39 microstate: &Microstate<B, Point<Cartesian<2>>, X, Periodic<Hypercuboid<2>>>,
40 ) -> Result<Frame<'_>, AppendError> {
41 self.append_frame(microstate.step())?
42 .configuration_box(microstate.boundary().shape().to_gsd_box())?
43 .configuration_dimensions(Dimensions::Two)?
44 .particles_position(
45 microstate
46 .iter_sites_tag_order()
47 .map(|s| s.properties.position)
48 .map(|p| [p[0], p[1], 0.0].into()),
49 )
50 }
51}
52
53impl<B, X> AppendMicrostate<B, OrientedPoint<Cartesian<2>, Angle>, X, Closed<Hypercuboid<2>>>
54 for HoomdGsdFile
55{
56 #[inline]
57 fn append_microstate(
58 &mut self,
59 microstate: &Microstate<B, OrientedPoint<Cartesian<2>, Angle>, X, Closed<Hypercuboid<2>>>,
60 ) -> Result<Frame<'_>, AppendError> {
61 self.append_frame(microstate.step())?
62 .configuration_box(microstate.boundary().0.to_gsd_box())?
63 .configuration_dimensions(Dimensions::Two)?
64 .particles_position(
65 microstate
66 .iter_sites_tag_order()
67 .map(|s| s.properties.position)
68 .map(|p| [p[0], p[1], 0.0].into()),
69 )?
70 .particles_orientation(
71 microstate
72 .iter_sites_tag_order()
73 .map(|s| s.properties.orientation.theta)
74 .map(|a| {
75 Versor::from_axis_angle(
76 [0.0, 0.0, 1.0]
77 .try_into()
78 .expect("hard-coded vector can be normalized"),
79 a,
80 )
81 }),
82 )
83 }
84}
85
86impl<B, X> AppendMicrostate<B, OrientedPoint<Cartesian<2>, Angle>, X, Periodic<Hypercuboid<2>>>
87 for HoomdGsdFile
88{
89 #[inline]
90 fn append_microstate(
91 &mut self,
92 microstate: &Microstate<B, OrientedPoint<Cartesian<2>, Angle>, X, Periodic<Hypercuboid<2>>>,
93 ) -> Result<Frame<'_>, AppendError> {
94 self.append_frame(microstate.step())?
95 .configuration_box(microstate.boundary().shape().to_gsd_box())?
96 .configuration_dimensions(Dimensions::Two)?
97 .particles_position(
98 microstate
99 .iter_sites_tag_order()
100 .map(|s| s.properties.position)
101 .map(|p| [p[0], p[1], 0.0].into()),
102 )?
103 .particles_orientation(
104 microstate
105 .iter_sites_tag_order()
106 .map(|s| s.properties.orientation.theta)
107 .map(|a| {
108 Versor::from_axis_angle(
109 [0.0, 0.0, 1.0]
110 .try_into()
111 .expect("hard-coded vector can be normalized"),
112 a,
113 )
114 }),
115 )
116 }
117}
118
119impl<B, X> AppendMicrostate<B, Point<Cartesian<3>>, X, Closed<Hypercuboid<3>>> for HoomdGsdFile {
120 #[inline]
121 fn append_microstate(
122 &mut self,
123 microstate: &Microstate<B, Point<Cartesian<3>>, X, Closed<Hypercuboid<3>>>,
124 ) -> Result<Frame<'_>, AppendError> {
125 self.append_frame(microstate.step())?
126 .configuration_box(microstate.boundary().0.to_gsd_box())?
127 .configuration_dimensions(Dimensions::Three)?
128 .particles_position(
129 microstate
130 .iter_sites_tag_order()
131 .map(|s| s.properties.position),
132 )
133 }
134}
135
136impl<B, X> AppendMicrostate<B, Point<Cartesian<3>>, X, Periodic<Hypercuboid<3>>> for HoomdGsdFile {
137 #[inline]
138 fn append_microstate(
139 &mut self,
140 microstate: &Microstate<B, Point<Cartesian<3>>, X, Periodic<Hypercuboid<3>>>,
141 ) -> Result<Frame<'_>, AppendError> {
142 self.append_frame(microstate.step())?
143 .configuration_box(microstate.boundary().shape().to_gsd_box())?
144 .configuration_dimensions(Dimensions::Three)?
145 .particles_position(
146 microstate
147 .iter_sites_tag_order()
148 .map(|s| s.properties.position),
149 )
150 }
151}
152
153impl<B, X> AppendMicrostate<B, OrientedPoint<Cartesian<3>, Versor>, X, Closed<Hypercuboid<3>>>
154 for HoomdGsdFile
155{
156 #[inline]
157 fn append_microstate(
158 &mut self,
159 microstate: &Microstate<B, OrientedPoint<Cartesian<3>, Versor>, X, Closed<Hypercuboid<3>>>,
160 ) -> Result<Frame<'_>, AppendError> {
161 self.append_frame(microstate.step())?
162 .configuration_box(microstate.boundary().0.to_gsd_box())?
163 .configuration_dimensions(Dimensions::Three)?
164 .particles_position(
165 microstate
166 .iter_sites_tag_order()
167 .map(|s| s.properties.position),
168 )?
169 .particles_orientation(
170 microstate
171 .iter_sites_tag_order()
172 .map(|s| s.properties.orientation),
173 )
174 }
175}
176
177impl<B, X> AppendMicrostate<B, OrientedPoint<Cartesian<3>, Versor>, X, Periodic<Hypercuboid<3>>>
178 for HoomdGsdFile
179{
180 #[inline]
181 fn append_microstate(
182 &mut self,
183 microstate: &Microstate<
184 B,
185 OrientedPoint<Cartesian<3>, Versor>,
186 X,
187 Periodic<Hypercuboid<3>>,
188 >,
189 ) -> Result<Frame<'_>, AppendError> {
190 self.append_frame(microstate.step())?
191 .configuration_box(microstate.boundary().shape().to_gsd_box())?
192 .configuration_dimensions(Dimensions::Three)?
193 .particles_position(
194 microstate
195 .iter_sites_tag_order()
196 .map(|s| s.properties.position),
197 )?
198 .particles_orientation(
199 microstate
200 .iter_sites_tag_order()
201 .map(|s| s.properties.orientation),
202 )
203 }
204}
205
206impl<B, X, C> AppendMicrostate<B, Point<Spherical<3>>, X, C> for HoomdGsdFile {
207 #[inline]
208 fn append_microstate(
209 &mut self,
210 microstate: &Microstate<B, Point<Spherical<3>>, X, C>,
211 ) -> Result<Frame<'_>, AppendError> {
212 self.append_frame(microstate.step())?
213 .configuration_box([2.0, 2.0, 2.0, 0.0, 0.0, 0.0])?
214 .configuration_dimensions(Dimensions::Three)?
215 .particles_position(
216 microstate
217 .iter_sites_tag_order()
218 .map(|s| *s.properties.position.point()),
219 )
220 }
221}
222
223impl<B, X, C> AppendMicrostate<B, Point<Spherical<4>>, X, C> for HoomdGsdFile {
224 #[inline]
225 fn append_microstate(
226 &mut self,
227 microstate: &Microstate<B, Point<Spherical<4>>, X, C>,
228 ) -> Result<Frame<'_>, AppendError> {
229 self.append_frame(microstate.step())?
230 .configuration_box([2.0, 2.0, 2.0, 0.0, 0.0, 0.0])?
231 .configuration_dimensions(Dimensions::Three)?
232 .particles_position(microstate.iter_sites_tag_order().map(|s| {
233 let proj: Vec<f64> = s.properties.position.stereographic_projection();
234 [proj[0], proj[1], proj[2]].into()
235 }))
236 }
237}
238
239impl<B, X, C> AppendMicrostate<B, OrientedHyperbolicPoint<3, Angle>, X, C> for HoomdGsdFile {
240 #[inline]
241 fn append_microstate(
242 &mut self,
243 microstate: &Microstate<B, OrientedHyperbolicPoint<3, Angle>, X, C>,
244 ) -> Result<Frame<'_>, AppendError> {
245 self.append_frame(microstate.step())?
246 .configuration_box([2.0, 2.0, 0.0, 0.0, 0.0, 0.0])?
247 .configuration_dimensions(Dimensions::Two)?
248 .particles_position(
249 microstate
250 .iter_sites_tag_order()
251 .map(|s| s.properties.position.to_poincare())
252 .map(|p| [p[0], p[1], 0.0].into()),
253 )?
254 .particles_orientation(
255 microstate
256 .iter_sites_tag_order()
257 .map(|s| s.properties.orientation.theta)
258 .map(|a| {
259 Versor::from_axis_angle(
260 [0.0, 0.0, 1.0]
261 .try_into()
262 .expect("hard-coded vector can be normalized"),
263 a,
264 )
265 }),
266 )
267 }
268}
269
270impl<B, X, C> AppendMicrostate<B, Point<Hyperbolic<3>>, X, C> for HoomdGsdFile {
271 #[inline]
272 fn append_microstate(
273 &mut self,
274 microstate: &Microstate<B, Point<Hyperbolic<3>>, X, C>,
275 ) -> Result<Frame<'_>, AppendError> {
276 self.append_frame(microstate.step())?
277 .configuration_box([2.0, 2.0, 0.0, 0.0, 0.0, 0.0])?
278 .configuration_dimensions(Dimensions::Two)?
279 .particles_position(
280 microstate
281 .iter_sites_tag_order()
282 .map(|s| s.properties.position.to_poincare())
283 .map(|p| [p[0], p[1], 0.0].into()),
284 )
285 }
286}
287
288#[cfg(test)]
289mod test {
290 use approxim::assert_relative_eq;
291 use assert2::assert;
292 use std::f64::consts::PI;
293 use tempfile::tempdir;
294
295 use super::*;
296 use crate::{Body, boundary::Open};
297 use hoomd_geometry::shape::{EightEight, Rectangle};
298 use hoomd_gsd::file_layer::{GsdFile, Mode};
299
300 #[test]
301 fn point_closed_rectangle_2d() -> anyhow::Result<()> {
302 let boundary = Closed(Rectangle {
303 edge_lengths: [12.0.try_into()?, 18.0.try_into()?],
304 });
305
306 let microstate = Microstate::builder()
307 .boundary(boundary)
308 .bodies([
309 Body::point(Cartesian::from([1.0, 0.0])),
310 Body::point(Cartesian::from([-1.0, 2.0])),
311 ])
312 .step(1234)
313 .try_build()?;
314
315 let tmp_dir = tempdir()?;
316 let path = tmp_dir.path().join("test.gsd");
317 let mut hoomd_gsd_file = HoomdGsdFile::create(path.clone())?;
318 hoomd_gsd_file.append_microstate(µstate)?;
319
320 drop(hoomd_gsd_file);
321
322 let gsd_file = GsdFile::open(path, Mode::Read)?;
323
324 assert!(gsd_file.n_frames() == 1);
325
326 let step = gsd_file.iter_scalars::<u64>(0, "configuration/step")?;
327 itertools::assert_equal(step, [1234]);
328
329 let positions = gsd_file.iter_arrays::<f32, 3>(0, "particles/position")?;
330 itertools::assert_equal(positions, [[1.0, 0.0, 0.0], [-1.0, 2.0, 0.0]]);
331
332 let dimensions = gsd_file.iter_scalars::<u8>(0, "configuration/dimensions")?;
333 itertools::assert_equal(dimensions, [2]);
334
335 let box_ = gsd_file.iter_scalars::<f32>(0, "configuration/box")?;
336 itertools::assert_equal(box_, [12.0_f32, 18.0, 0.0, 0.0, 0.0, 0.0]);
337
338 Ok(())
339 }
340
341 #[test]
342 fn point_periodic_rectangle_2d() -> anyhow::Result<()> {
343 let boundary = Periodic::new(
344 0.0,
345 Rectangle {
346 edge_lengths: [12.0.try_into()?, 18.0.try_into()?],
347 },
348 )?;
349
350 let microstate = Microstate::builder()
351 .boundary(boundary)
352 .bodies([
353 Body::point(Cartesian::from([1.0, 0.0])),
354 Body::point(Cartesian::from([-1.0, 2.0])),
355 ])
356 .step(1234)
357 .try_build()?;
358
359 let tmp_dir = tempdir()?;
360 let path = tmp_dir.path().join("test.gsd");
361 let mut hoomd_gsd_file = HoomdGsdFile::create(path.clone())?;
362 hoomd_gsd_file.append_microstate(µstate)?;
363
364 drop(hoomd_gsd_file);
365
366 let gsd_file = GsdFile::open(path, Mode::Read)?;
367
368 assert!(gsd_file.n_frames() == 1);
369
370 let step = gsd_file.iter_scalars::<u64>(0, "configuration/step")?;
371 itertools::assert_equal(step, [1234]);
372
373 let positions = gsd_file.iter_arrays::<f32, 3>(0, "particles/position")?;
374 itertools::assert_equal(positions, [[1.0, 0.0, 0.0], [-1.0, 2.0, 0.0]]);
375
376 let dimensions = gsd_file.iter_scalars::<u8>(0, "configuration/dimensions")?;
377 itertools::assert_equal(dimensions, [2]);
378
379 let box_ = gsd_file.iter_scalars::<f32>(0, "configuration/box")?;
380 itertools::assert_equal(box_, [12.0_f32, 18.0, 0.0, 0.0, 0.0, 0.0]);
381
382 Ok(())
383 }
384
385 #[test]
386 fn point_spherical_2d() -> anyhow::Result<()> {
387 let microstate = Microstate::builder()
388 .boundary(Open)
389 .bodies([
390 Body::point(Spherical::from_cartesian_coordinates(Cartesian::from([
391 -1.0, 0.0, 0.0,
392 ]))),
393 Body::point(Spherical::from_cartesian_coordinates(Cartesian::from([
394 0.0,
395 f64::sqrt(0.5),
396 f64::sqrt(0.5),
397 ]))),
398 Body::point(Spherical::from_cartesian_coordinates(Cartesian::from([
399 -f64::sqrt(0.25),
400 0.0,
401 f64::sqrt(0.75),
402 ]))),
403 ])
404 .step(1234)
405 .try_build()?;
406
407 let tmp_dir = tempdir()?;
408 let path = tmp_dir.path().join("test.gsd");
409 let mut hoomd_gsd_file = HoomdGsdFile::create(path.clone())?;
410 hoomd_gsd_file.append_microstate(µstate)?;
411
412 drop(hoomd_gsd_file);
413
414 let gsd_file = GsdFile::open(path, Mode::Read)?;
415
416 assert!(gsd_file.n_frames() == 1);
417
418 let step = gsd_file.iter_scalars::<u64>(0, "configuration/step")?;
419 itertools::assert_equal(step, [1234]);
420
421 let positions: Vec<[f32; 3]> = gsd_file
422 .iter_arrays::<f32, 3>(0, "particles/position")?
423 .collect();
424 assert_relative_eq!(positions[0][0], -1.0);
425 assert_relative_eq!(positions[0][1], 0.0);
426 assert_relative_eq!(positions[0][2], 0.0);
427 assert_relative_eq!(positions[1][0], 0.0);
428 assert_relative_eq!(positions[1][1], f32::sqrt(0.5));
429 assert_relative_eq!(positions[1][2], f32::sqrt(0.5));
430 assert_relative_eq!(positions[2][0], -f32::sqrt(0.25));
431 assert_relative_eq!(positions[2][1], 0.0);
432 assert_relative_eq!(positions[2][2], f32::sqrt(0.75));
433 Ok(())
434 }
435
436 #[test]
437 fn point_spherical_3d() -> anyhow::Result<()> {
438 let microstate = Microstate::builder()
439 .boundary(Open)
440 .bodies([
441 Body::point(Spherical::from_cartesian_coordinates(Cartesian::from([
442 1.0, 0.0, 0.0, 0.0,
443 ]))),
444 Body::point(Spherical::from_cartesian_coordinates(Cartesian::from([
445 f64::sqrt(1.0 / 3.0),
446 -f64::sqrt(1.0 / 3.0),
447 f64::sqrt(1.0 / 3.0),
448 0.0,
449 ]))),
450 Body::point(Spherical::from_cartesian_coordinates(Cartesian::from([
451 0.0,
452 0.0,
453 f64::sqrt(0.5),
454 f64::sqrt(0.5),
455 ]))),
456 ])
457 .step(1234)
458 .try_build()?;
459
460 let tmp_dir = tempdir()?;
461 let path = tmp_dir.path().join("test.gsd");
462 let mut hoomd_gsd_file = HoomdGsdFile::create(path.clone())?;
463 hoomd_gsd_file.append_microstate(µstate)?;
464
465 drop(hoomd_gsd_file);
466
467 let gsd_file = GsdFile::open(path, Mode::Read)?;
468
469 assert!(gsd_file.n_frames() == 1);
470
471 let step = gsd_file.iter_scalars::<u64>(0, "configuration/step")?;
472 itertools::assert_equal(step, [1234]);
473
474 let positions: Vec<[f32; 3]> = gsd_file
475 .iter_arrays::<f32, 3>(0, "particles/position")?
476 .collect();
477 assert_relative_eq!(positions[0][0], 1.0);
478 assert_relative_eq!(positions[0][1], 0.0);
479 assert_relative_eq!(positions[0][2], 0.0);
480 assert_relative_eq!(positions[1][0], f32::sqrt(1.0 / 3.0));
481 assert_relative_eq!(positions[1][1], -f32::sqrt(1.0 / 3.0));
482 assert_relative_eq!(positions[1][2], f32::sqrt(1.0 / 3.0));
483 assert_relative_eq!(positions[2][0], 0.0);
484 assert_relative_eq!(positions[2][1], 0.0);
485 assert_relative_eq!(positions[2][2], f32::sqrt(0.5) / (1.0 - f32::sqrt(0.5)));
486 Ok(())
487 }
488
489 #[test]
490 fn point_hyperbolic_2d_open() -> anyhow::Result<()> {
491 let microstate = Microstate::builder()
492 .boundary(Open)
493 .bodies([
494 Body::point(Hyperbolic::<3>::from_polar_coordinates(1.2, 0.0)),
495 Body::point(Hyperbolic::<3>::from_polar_coordinates(0.6, 1.5)),
496 ])
497 .step(1234)
498 .try_build()?;
499
500 let tmp_dir = tempdir()?;
501 let path = tmp_dir.path().join("test.gsd");
502 let mut hoomd_gsd_file = HoomdGsdFile::create(path.clone())?;
503 hoomd_gsd_file.append_microstate(µstate)?;
504
505 drop(hoomd_gsd_file);
506
507 let gsd_file = GsdFile::open(path, Mode::Read)?;
508
509 assert!(gsd_file.n_frames() == 1);
510
511 let step = gsd_file.iter_scalars::<u64>(0, "configuration/step")?;
512 itertools::assert_equal(step, [1234]);
513
514 let positions: Vec<[f32; 3]> = gsd_file
515 .iter_arrays::<f32, 3>(0, "particles/position")?
516 .collect();
517 assert_relative_eq!(positions[0][0], (f32::sinh(1.2)) / (1.0 + f32::cosh(1.2)));
518 assert_relative_eq!(positions[0][1], 0.0);
519 assert_relative_eq!(
520 positions[1][0],
521 (f32::sinh(0.6) * f32::cos(1.5)) / (1.0 + f32::cosh(0.6))
522 );
523 assert_relative_eq!(
524 positions[1][1],
525 (f32::sinh(0.6) * f32::sin(1.5)) / (1.0 + f32::cosh(0.6))
526 );
527 Ok(())
528 }
529
530 #[test]
531 fn point_hyperbolic_2d_eighteight_periodic() -> anyhow::Result<()> {
532 let boundary = Periodic::new(0.6, EightEight {})?;
533 let microstate = Microstate::builder()
534 .boundary(boundary)
535 .bodies([
536 Body::point(Hyperbolic::<3>::from_polar_coordinates(0.2, 0.3)),
537 Body::point(Hyperbolic::<3>::from_polar_coordinates(0.6, 0.7)),
538 ])
539 .step(1234)
540 .try_build()?;
541
542 let tmp_dir = tempdir()?;
543 let path = tmp_dir.path().join("test.gsd");
544 let mut hoomd_gsd_file = HoomdGsdFile::create(path.clone())?;
545 hoomd_gsd_file.append_microstate(µstate)?;
546
547 drop(hoomd_gsd_file);
548
549 let gsd_file = GsdFile::open(path, Mode::Read)?;
550
551 assert!(gsd_file.n_frames() == 1);
552
553 let step = gsd_file.iter_scalars::<u64>(0, "configuration/step")?;
554 itertools::assert_equal(step, [1234]);
555
556 let positions: Vec<[f32; 3]> = gsd_file
557 .iter_arrays::<f32, 3>(0, "particles/position")?
558 .collect();
559 assert_relative_eq!(
560 positions[0][0],
561 (f32::sinh(0.2) * f32::cos(0.3)) / (1.0 + f32::cosh(0.2))
562 );
563 assert_relative_eq!(
564 positions[0][1],
565 (f32::sinh(0.2) * f32::sin(0.3)) / (1.0 + f32::cosh(0.2))
566 );
567 assert_relative_eq!(
568 positions[1][0],
569 (f32::sinh(0.6) * f32::cos(0.7)) / (1.0 + f32::cosh(0.6))
570 );
571 assert_relative_eq!(
572 positions[1][1],
573 (f32::sinh(0.6) * f32::sin(0.7)) / (1.0 + f32::cosh(0.6))
574 );
575 Ok(())
576 }
577
578 #[test]
579 fn oriented_point_hyperbolic_2d_open() -> anyhow::Result<()> {
580 let microstate = Microstate::builder()
581 .boundary(Open)
582 .bodies([
583 Body {
584 properties: OrientedHyperbolicPoint {
585 position: Hyperbolic::<3>::from_polar_coordinates(0.5, 0.6),
586 orientation: Angle::from(0.3),
587 },
588 sites: vec![OrientedHyperbolicPoint {
589 position: Hyperbolic::<3>::default(),
590 orientation: Angle::default(),
591 }],
592 },
593 Body {
594 properties: OrientedHyperbolicPoint {
595 position: Hyperbolic::<3>::from_polar_coordinates(0.9, 0.3),
596 orientation: Angle::from(1.2),
597 },
598 sites: vec![OrientedHyperbolicPoint {
599 position: Hyperbolic::<3>::default(),
600 orientation: Angle::default(),
601 }],
602 },
603 ])
604 .step(1234)
605 .try_build()?;
606
607 let tmp_dir = tempdir()?;
608 let path = tmp_dir.path().join("test.gsd");
609 let mut hoomd_gsd_file = HoomdGsdFile::create(path.clone())?;
610 hoomd_gsd_file.append_microstate(µstate)?;
611
612 drop(hoomd_gsd_file);
613
614 let gsd_file = GsdFile::open(path, Mode::Read)?;
615
616 assert!(gsd_file.n_frames() == 1);
617
618 let step = gsd_file.iter_scalars::<u64>(0, "configuration/step")?;
619 itertools::assert_equal(step, [1234]);
620
621 let positions: Vec<[f32; 3]> = gsd_file
622 .iter_arrays::<f32, 3>(0, "particles/position")?
623 .collect();
624 assert_relative_eq!(
625 positions[0][0],
626 (f32::sinh(0.5) * f32::cos(0.6)) / (1.0 + f32::cosh(0.5))
627 );
628 assert_relative_eq!(
629 positions[0][1],
630 (f32::sinh(0.5) * f32::sin(0.6)) / (1.0 + f32::cosh(0.5))
631 );
632 assert_relative_eq!(
633 positions[1][0],
634 (f32::sinh(0.9) * f32::cos(0.3)) / (1.0 + f32::cosh(0.9))
635 );
636 assert_relative_eq!(
637 positions[1][1],
638 (f32::sinh(0.9) * f32::sin(0.3)) / (1.0 + f32::cosh(0.9))
639 );
640 Ok(())
641 }
642
643 #[test]
644 fn oriented_point_hyperbolic_2d_periodic_eighteight() -> anyhow::Result<()> {
645 let boundary = Periodic::new(0.6, EightEight {})?;
646 let microstate = Microstate::builder()
647 .boundary(boundary)
648 .bodies([
649 Body {
650 properties: OrientedHyperbolicPoint {
651 position: Hyperbolic::<3>::from_polar_coordinates(0.5, 0.6),
652 orientation: Angle::from(0.3),
653 },
654 sites: vec![OrientedHyperbolicPoint {
655 position: Hyperbolic::<3>::default(),
656 orientation: Angle::default(),
657 }],
658 },
659 Body {
660 properties: OrientedHyperbolicPoint {
661 position: Hyperbolic::<3>::from_polar_coordinates(0.9, 0.3),
662 orientation: Angle::from(1.2),
663 },
664 sites: vec![OrientedHyperbolicPoint {
665 position: Hyperbolic::<3>::default(),
666 orientation: Angle::default(),
667 }],
668 },
669 ])
670 .step(1234)
671 .try_build()?;
672
673 let tmp_dir = tempdir()?;
674 let path = tmp_dir.path().join("test.gsd");
675 let mut hoomd_gsd_file = HoomdGsdFile::create(path.clone())?;
676 hoomd_gsd_file.append_microstate(µstate)?;
677
678 drop(hoomd_gsd_file);
679
680 let gsd_file = GsdFile::open(path, Mode::Read)?;
681
682 assert!(gsd_file.n_frames() == 1);
683
684 let step = gsd_file.iter_scalars::<u64>(0, "configuration/step")?;
685 itertools::assert_equal(step, [1234]);
686
687 let positions: Vec<[f32; 3]> = gsd_file
688 .iter_arrays::<f32, 3>(0, "particles/position")?
689 .collect();
690 assert_relative_eq!(
691 positions[0][0],
692 (f32::sinh(0.5) * f32::cos(0.6)) / (1.0 + f32::cosh(0.5))
693 );
694 assert_relative_eq!(
695 positions[0][1],
696 (f32::sinh(0.5) * f32::sin(0.6)) / (1.0 + f32::cosh(0.5))
697 );
698 assert_relative_eq!(
699 positions[1][0],
700 (f32::sinh(0.9) * f32::cos(0.3)) / (1.0 + f32::cosh(0.9))
701 );
702 assert_relative_eq!(
703 positions[1][1],
704 (f32::sinh(0.9) * f32::sin(0.3)) / (1.0 + f32::cosh(0.9))
705 );
706 Ok(())
707 }
708
709 #[test]
710 fn oriented_point_closed_rectangle_2d() -> anyhow::Result<()> {
711 let boundary = Closed(Rectangle {
712 edge_lengths: [12.0.try_into()?, 18.0.try_into()?],
713 });
714
715 let site = OrientedPoint {
716 position: Cartesian::from([0.0, 0.0]),
717 orientation: Angle::default(),
718 };
719 let a = OrientedPoint {
720 position: Cartesian::from([1.0, 0.0]),
721 orientation: Angle::from(PI / 2.0),
722 };
723 let b = OrientedPoint {
724 position: Cartesian::from([-1.0, 2.0]),
725 orientation: Angle::from(PI),
726 };
727 let body_a = Body {
728 properties: a,
729 sites: [site].into(),
730 };
731 let body_b = Body {
732 properties: b,
733 sites: [site].into(),
734 };
735
736 let microstate = Microstate::builder()
737 .boundary(boundary)
738 .bodies([body_a, body_b])
739 .step(1234)
740 .try_build()?;
741
742 let tmp_dir = tempdir()?;
743 let path = tmp_dir.path().join("test.gsd");
744 let mut hoomd_gsd_file = HoomdGsdFile::create(path.clone())?;
745 hoomd_gsd_file.append_microstate(µstate)?;
746
747 drop(hoomd_gsd_file);
748
749 let gsd_file = GsdFile::open(path, Mode::Read)?;
750
751 assert!(gsd_file.n_frames() == 1);
752
753 let step = gsd_file.iter_scalars::<u64>(0, "configuration/step")?;
754 itertools::assert_equal(step, [1234]);
755
756 let positions = gsd_file.iter_arrays::<f32, 3>(0, "particles/position")?;
757 itertools::assert_equal(positions, [[1.0, 0.0, 0.0], [-1.0, 2.0, 0.0]]);
758
759 assert!(
760 gsd_file
761 .iter_arrays::<f32, 4>(0, "particles/orientation")?
762 .count()
763 == 2
764 );
765
766 let dimensions = gsd_file.iter_scalars::<u8>(0, "configuration/dimensions")?;
767 itertools::assert_equal(dimensions, [2]);
768
769 let box_ = gsd_file.iter_scalars::<f32>(0, "configuration/box")?;
770 itertools::assert_equal(box_, [12.0_f32, 18.0, 0.0, 0.0, 0.0, 0.0]);
771
772 Ok(())
773 }
774
775 #[test]
776 fn oriented_point_periodic_rectangle_2d() -> anyhow::Result<()> {
777 let boundary = Periodic::new(
778 0.0,
779 Rectangle {
780 edge_lengths: [12.0.try_into()?, 18.0.try_into()?],
781 },
782 )?;
783
784 let site = OrientedPoint {
785 position: Cartesian::from([0.0, 0.0]),
786 orientation: Angle::default(),
787 };
788 let a = OrientedPoint {
789 position: Cartesian::from([1.0, 0.0]),
790 orientation: Angle::from(PI / 2.0),
791 };
792 let b = OrientedPoint {
793 position: Cartesian::from([-1.0, 2.0]),
794 orientation: Angle::from(PI),
795 };
796 let body_a = Body {
797 properties: a,
798 sites: [site].into(),
799 };
800 let body_b = Body {
801 properties: b,
802 sites: [site].into(),
803 };
804
805 let microstate = Microstate::builder()
806 .boundary(boundary)
807 .bodies([body_a, body_b])
808 .step(1234)
809 .try_build()?;
810
811 let tmp_dir = tempdir()?;
812 let path = tmp_dir.path().join("test.gsd");
813 let mut hoomd_gsd_file = HoomdGsdFile::create(path.clone())?;
814 hoomd_gsd_file.append_microstate(µstate)?;
815
816 drop(hoomd_gsd_file);
817
818 let gsd_file = GsdFile::open(path, Mode::Read)?;
819
820 assert!(gsd_file.n_frames() == 1);
821
822 let step = gsd_file.iter_scalars::<u64>(0, "configuration/step")?;
823 itertools::assert_equal(step, [1234]);
824
825 let positions = gsd_file.iter_arrays::<f32, 3>(0, "particles/position")?;
826 itertools::assert_equal(positions, [[1.0, 0.0, 0.0], [-1.0, 2.0, 0.0]]);
827
828 assert!(
829 gsd_file
830 .iter_arrays::<f32, 4>(0, "particles/orientation")?
831 .count()
832 == 2
833 );
834
835 let dimensions = gsd_file.iter_scalars::<u8>(0, "configuration/dimensions")?;
836 itertools::assert_equal(dimensions, [2]);
837
838 let box_ = gsd_file.iter_scalars::<f32>(0, "configuration/box")?;
839 itertools::assert_equal(box_, [12.0_f32, 18.0, 0.0, 0.0, 0.0, 0.0]);
840
841 Ok(())
842 }
843
844 #[test]
845 fn point_closed_cuboid_3d() -> anyhow::Result<()> {
846 let boundary = Closed(Hypercuboid {
847 edge_lengths: [12.0.try_into()?, 18.0.try_into()?, 24.0.try_into()?],
848 });
849
850 let microstate = Microstate::builder()
851 .boundary(boundary)
852 .bodies([
853 Body::point(Cartesian::from([1.0, 0.0, 4.0])),
854 Body::point(Cartesian::from([-1.0, 2.0, -2.0])),
855 ])
856 .step(1234)
857 .try_build()?;
858
859 let tmp_dir = tempdir()?;
860 let path = tmp_dir.path().join("test.gsd");
861 let mut hoomd_gsd_file = HoomdGsdFile::create(path.clone())?;
862 hoomd_gsd_file.append_microstate(µstate)?;
863
864 drop(hoomd_gsd_file);
865
866 let gsd_file = GsdFile::open(path, Mode::Read)?;
867
868 assert!(gsd_file.n_frames() == 1);
869
870 let step = gsd_file.iter_scalars::<u64>(0, "configuration/step")?;
871 itertools::assert_equal(step, [1234]);
872
873 let positions = gsd_file.iter_arrays::<f32, 3>(0, "particles/position")?;
874 itertools::assert_equal(positions, [[1.0, 0.0, 4.0], [-1.0, 2.0, -2.0]]);
875
876 let dimensions = gsd_file.iter_scalars::<u8>(0, "configuration/dimensions")?;
877 itertools::assert_equal(dimensions, [3]);
878
879 let box_ = gsd_file.iter_scalars::<f32>(0, "configuration/box")?;
880 itertools::assert_equal(box_, [12.0_f32, 18.0, 24.0, 0.0, 0.0, 0.0]);
881
882 Ok(())
883 }
884
885 #[test]
886 fn point_periodic_cuboid_3d() -> anyhow::Result<()> {
887 let boundary = Periodic::new(
888 0.0,
889 Hypercuboid {
890 edge_lengths: [12.0.try_into()?, 18.0.try_into()?, 24.0.try_into()?],
891 },
892 )?;
893
894 let microstate = Microstate::builder()
895 .boundary(boundary)
896 .bodies([
897 Body::point(Cartesian::from([1.0, 0.0, 4.0])),
898 Body::point(Cartesian::from([-1.0, 2.0, -2.0])),
899 ])
900 .step(1234)
901 .try_build()?;
902
903 let tmp_dir = tempdir()?;
904 let path = tmp_dir.path().join("test.gsd");
905 let mut hoomd_gsd_file = HoomdGsdFile::create(path.clone())?;
906 hoomd_gsd_file.append_microstate(µstate)?;
907
908 drop(hoomd_gsd_file);
909
910 let gsd_file = GsdFile::open(path, Mode::Read)?;
911
912 assert!(gsd_file.n_frames() == 1);
913
914 let step = gsd_file.iter_scalars::<u64>(0, "configuration/step")?;
915 itertools::assert_equal(step, [1234]);
916
917 let positions = gsd_file.iter_arrays::<f32, 3>(0, "particles/position")?;
918 itertools::assert_equal(positions, [[1.0, 0.0, 4.0], [-1.0, 2.0, -2.0]]);
919
920 let dimensions = gsd_file.iter_scalars::<u8>(0, "configuration/dimensions")?;
921 itertools::assert_equal(dimensions, [3]);
922
923 let box_ = gsd_file.iter_scalars::<f32>(0, "configuration/box")?;
924 itertools::assert_equal(box_, [12.0_f32, 18.0, 24.0, 0.0, 0.0, 0.0]);
925
926 Ok(())
927 }
928
929 #[test]
930 fn oriented_point_closed_cuboid_3d() -> anyhow::Result<()> {
931 let boundary = Closed(Hypercuboid {
932 edge_lengths: [12.0.try_into()?, 18.0.try_into()?, 24.0.try_into()?],
933 });
934
935 let site = OrientedPoint {
936 position: Cartesian::default(),
937 orientation: Versor::default(),
938 };
939 let a = OrientedPoint {
940 position: Cartesian::from([1.0, 0.0, 4.0]),
941 orientation: Versor::from_axis_angle([1.0, 0.0, 0.0].try_into()?, PI / 2.0),
942 };
943 let b = OrientedPoint {
944 position: Cartesian::from([-1.0, 2.0, -2.0]),
945 orientation: Versor::from_axis_angle([0.0, 1.0, 0.0].try_into()?, PI / 2.0),
946 };
947 let body_a = Body {
948 properties: a,
949 sites: [site].into(),
950 };
951 let body_b = Body {
952 properties: b,
953 sites: [site].into(),
954 };
955
956 let microstate = Microstate::builder()
957 .boundary(boundary)
958 .bodies([body_a, body_b])
959 .step(1234)
960 .try_build()?;
961
962 let tmp_dir = tempdir()?;
963 let path = tmp_dir.path().join("test.gsd");
964 let mut hoomd_gsd_file = HoomdGsdFile::create(path.clone())?;
965 hoomd_gsd_file.append_microstate(µstate)?;
966
967 drop(hoomd_gsd_file);
968
969 let gsd_file = GsdFile::open(path, Mode::Read)?;
970
971 assert!(gsd_file.n_frames() == 1);
972
973 let step = gsd_file.iter_scalars::<u64>(0, "configuration/step")?;
974 itertools::assert_equal(step, [1234]);
975
976 let positions = gsd_file.iter_arrays::<f32, 3>(0, "particles/position")?;
977 itertools::assert_equal(positions, [[1.0, 0.0, 4.0], [-1.0, 2.0, -2.0]]);
978
979 assert!(
980 gsd_file
981 .iter_arrays::<f32, 4>(0, "particles/orientation")?
982 .count()
983 == 2
984 );
985
986 let dimensions = gsd_file.iter_scalars::<u8>(0, "configuration/dimensions")?;
987 itertools::assert_equal(dimensions, [3]);
988
989 let box_ = gsd_file.iter_scalars::<f32>(0, "configuration/box")?;
990 itertools::assert_equal(box_, [12.0_f32, 18.0, 24.0, 0.0, 0.0, 0.0]);
991
992 Ok(())
993 }
994
995 #[test]
996 fn oriented_point_periodic_cuboid_3d() -> anyhow::Result<()> {
997 let boundary = Periodic::new(
998 0.0,
999 Hypercuboid {
1000 edge_lengths: [12.0.try_into()?, 18.0.try_into()?, 24.0.try_into()?],
1001 },
1002 )?;
1003
1004 let site = OrientedPoint {
1005 position: Cartesian::from([0.0, 0.0, 0.0]),
1006 orientation: Versor::default(),
1007 };
1008 let a = OrientedPoint {
1009 position: Cartesian::from([1.0, 0.0, 4.0]),
1010 orientation: Versor::from_axis_angle([1.0, 0.0, 0.0].try_into()?, PI / 2.0),
1011 };
1012 let b = OrientedPoint {
1013 position: Cartesian::from([-1.0, 2.0, -2.0]),
1014 orientation: Versor::from_axis_angle([0.0, 1.0, 0.0].try_into()?, PI / 2.0),
1015 };
1016 let body_a = Body {
1017 properties: a,
1018 sites: [site].into(),
1019 };
1020 let body_b = Body {
1021 properties: b,
1022 sites: [site].into(),
1023 };
1024
1025 let microstate = Microstate::builder()
1026 .boundary(boundary)
1027 .bodies([body_a, body_b])
1028 .step(1234)
1029 .try_build()?;
1030
1031 let tmp_dir = tempdir()?;
1032 let path = tmp_dir.path().join("test.gsd");
1033 let mut hoomd_gsd_file = HoomdGsdFile::create(path.clone())?;
1034 hoomd_gsd_file.append_microstate(µstate)?;
1035
1036 drop(hoomd_gsd_file);
1037
1038 let gsd_file = GsdFile::open(path, Mode::Read)?;
1039
1040 assert!(gsd_file.n_frames() == 1);
1041
1042 let step = gsd_file.iter_scalars::<u64>(0, "configuration/step")?;
1043 itertools::assert_equal(step, [1234]);
1044
1045 let positions = gsd_file.iter_arrays::<f32, 3>(0, "particles/position")?;
1046 itertools::assert_equal(positions, [[1.0, 0.0, 4.0], [-1.0, 2.0, -2.0]]);
1047
1048 assert!(
1049 gsd_file
1050 .iter_arrays::<f32, 4>(0, "particles/orientation")?
1051 .count()
1052 == 2
1053 );
1054
1055 let dimensions = gsd_file.iter_scalars::<u8>(0, "configuration/dimensions")?;
1056 itertools::assert_equal(dimensions, [3]);
1057
1058 let box_ = gsd_file.iter_scalars::<f32>(0, "configuration/box")?;
1059 itertools::assert_equal(box_, [12.0_f32, 18.0, 24.0, 0.0, 0.0, 0.0]);
1060
1061 Ok(())
1062 }
1063}