Skip to main content

hoomd_microstate/
append.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 `AppendMicrostate` for built-in site and boundary types.
5
6use 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 std::f64::consts::PI;
292    use tempfile::tempdir;
293
294    use super::*;
295    use crate::{Body, boundary::Open};
296    use hoomd_geometry::shape::{EightEight, Rectangle};
297    use hoomd_gsd::file_layer::{GsdFile, Mode};
298
299    #[test]
300    fn point_closed_rectangle_2d() -> anyhow::Result<()> {
301        let boundary = Closed(Rectangle {
302            edge_lengths: [12.0.try_into()?, 18.0.try_into()?],
303        });
304
305        let microstate = Microstate::builder()
306            .boundary(boundary)
307            .bodies([
308                Body::point(Cartesian::from([1.0, 0.0])),
309                Body::point(Cartesian::from([-1.0, 2.0])),
310            ])
311            .step(1234)
312            .try_build()?;
313
314        let tmp_dir = tempdir()?;
315        let path = tmp_dir.path().join("test.gsd");
316        let mut hoomd_gsd_file = HoomdGsdFile::create(path.clone())?;
317        hoomd_gsd_file.append_microstate(&microstate)?;
318
319        drop(hoomd_gsd_file);
320
321        let gsd_file = GsdFile::open(path, Mode::Read)?;
322
323        assert!(gsd_file.n_frames() == 1);
324
325        let step = gsd_file.iter_scalars::<u64>(0, "configuration/step")?;
326        itertools::assert_equal(step, [1234]);
327
328        let positions = gsd_file.iter_arrays::<f32, 3>(0, "particles/position")?;
329        itertools::assert_equal(positions, [[1.0, 0.0, 0.0], [-1.0, 2.0, 0.0]]);
330
331        let dimensions = gsd_file.iter_scalars::<u8>(0, "configuration/dimensions")?;
332        itertools::assert_equal(dimensions, [2]);
333
334        let box_ = gsd_file.iter_scalars::<f32>(0, "configuration/box")?;
335        itertools::assert_equal(box_, [12.0_f32, 18.0, 0.0, 0.0, 0.0, 0.0]);
336
337        Ok(())
338    }
339
340    #[test]
341    fn point_periodic_rectangle_2d() -> anyhow::Result<()> {
342        let boundary = Periodic::new(
343            0.0,
344            Rectangle {
345                edge_lengths: [12.0.try_into()?, 18.0.try_into()?],
346            },
347        )?;
348
349        let microstate = Microstate::builder()
350            .boundary(boundary)
351            .bodies([
352                Body::point(Cartesian::from([1.0, 0.0])),
353                Body::point(Cartesian::from([-1.0, 2.0])),
354            ])
355            .step(1234)
356            .try_build()?;
357
358        let tmp_dir = tempdir()?;
359        let path = tmp_dir.path().join("test.gsd");
360        let mut hoomd_gsd_file = HoomdGsdFile::create(path.clone())?;
361        hoomd_gsd_file.append_microstate(&microstate)?;
362
363        drop(hoomd_gsd_file);
364
365        let gsd_file = GsdFile::open(path, Mode::Read)?;
366
367        assert!(gsd_file.n_frames() == 1);
368
369        let step = gsd_file.iter_scalars::<u64>(0, "configuration/step")?;
370        itertools::assert_equal(step, [1234]);
371
372        let positions = gsd_file.iter_arrays::<f32, 3>(0, "particles/position")?;
373        itertools::assert_equal(positions, [[1.0, 0.0, 0.0], [-1.0, 2.0, 0.0]]);
374
375        let dimensions = gsd_file.iter_scalars::<u8>(0, "configuration/dimensions")?;
376        itertools::assert_equal(dimensions, [2]);
377
378        let box_ = gsd_file.iter_scalars::<f32>(0, "configuration/box")?;
379        itertools::assert_equal(box_, [12.0_f32, 18.0, 0.0, 0.0, 0.0, 0.0]);
380
381        Ok(())
382    }
383
384    #[test]
385    fn point_spherical_2d() -> anyhow::Result<()> {
386        let microstate = Microstate::builder()
387            .boundary(Open)
388            .bodies([
389                Body::point(Spherical::from_cartesian_coordinates(Cartesian::from([
390                    -1.0, 0.0, 0.0,
391                ]))),
392                Body::point(Spherical::from_cartesian_coordinates(Cartesian::from([
393                    0.0,
394                    f64::sqrt(0.5),
395                    f64::sqrt(0.5),
396                ]))),
397                Body::point(Spherical::from_cartesian_coordinates(Cartesian::from([
398                    -f64::sqrt(0.25),
399                    0.0,
400                    f64::sqrt(0.75),
401                ]))),
402            ])
403            .step(1234)
404            .try_build()?;
405
406        let tmp_dir = tempdir()?;
407        let path = tmp_dir.path().join("test.gsd");
408        let mut hoomd_gsd_file = HoomdGsdFile::create(path.clone())?;
409        hoomd_gsd_file.append_microstate(&microstate)?;
410
411        drop(hoomd_gsd_file);
412
413        let gsd_file = GsdFile::open(path, Mode::Read)?;
414
415        assert!(gsd_file.n_frames() == 1);
416
417        let step = gsd_file.iter_scalars::<u64>(0, "configuration/step")?;
418        itertools::assert_equal(step, [1234]);
419
420        let positions: Vec<[f32; 3]> = gsd_file
421            .iter_arrays::<f32, 3>(0, "particles/position")?
422            .collect();
423        assert_relative_eq!(positions[0][0], -1.0);
424        assert_relative_eq!(positions[0][1], 0.0);
425        assert_relative_eq!(positions[0][2], 0.0);
426        assert_relative_eq!(positions[1][0], 0.0);
427        assert_relative_eq!(positions[1][1], f32::sqrt(0.5));
428        assert_relative_eq!(positions[1][2], f32::sqrt(0.5));
429        assert_relative_eq!(positions[2][0], -f32::sqrt(0.25));
430        assert_relative_eq!(positions[2][1], 0.0);
431        assert_relative_eq!(positions[2][2], f32::sqrt(0.75));
432        Ok(())
433    }
434
435    #[test]
436    fn point_spherical_3d() -> anyhow::Result<()> {
437        let microstate = Microstate::builder()
438            .boundary(Open)
439            .bodies([
440                Body::point(Spherical::from_cartesian_coordinates(Cartesian::from([
441                    1.0, 0.0, 0.0, 0.0,
442                ]))),
443                Body::point(Spherical::from_cartesian_coordinates(Cartesian::from([
444                    f64::sqrt(1.0 / 3.0),
445                    -f64::sqrt(1.0 / 3.0),
446                    f64::sqrt(1.0 / 3.0),
447                    0.0,
448                ]))),
449                Body::point(Spherical::from_cartesian_coordinates(Cartesian::from([
450                    0.0,
451                    0.0,
452                    f64::sqrt(0.5),
453                    f64::sqrt(0.5),
454                ]))),
455            ])
456            .step(1234)
457            .try_build()?;
458
459        let tmp_dir = tempdir()?;
460        let path = tmp_dir.path().join("test.gsd");
461        let mut hoomd_gsd_file = HoomdGsdFile::create(path.clone())?;
462        hoomd_gsd_file.append_microstate(&microstate)?;
463
464        drop(hoomd_gsd_file);
465
466        let gsd_file = GsdFile::open(path, Mode::Read)?;
467
468        assert!(gsd_file.n_frames() == 1);
469
470        let step = gsd_file.iter_scalars::<u64>(0, "configuration/step")?;
471        itertools::assert_equal(step, [1234]);
472
473        let positions: Vec<[f32; 3]> = gsd_file
474            .iter_arrays::<f32, 3>(0, "particles/position")?
475            .collect();
476        assert_relative_eq!(positions[0][0], 1.0);
477        assert_relative_eq!(positions[0][1], 0.0);
478        assert_relative_eq!(positions[0][2], 0.0);
479        assert_relative_eq!(positions[1][0], f32::sqrt(1.0 / 3.0));
480        assert_relative_eq!(positions[1][1], -f32::sqrt(1.0 / 3.0));
481        assert_relative_eq!(positions[1][2], f32::sqrt(1.0 / 3.0));
482        assert_relative_eq!(positions[2][0], 0.0);
483        assert_relative_eq!(positions[2][1], 0.0);
484        assert_relative_eq!(positions[2][2], f32::sqrt(0.5) / (1.0 - f32::sqrt(0.5)));
485        Ok(())
486    }
487
488    #[test]
489    fn point_hyperbolic_2d_open() -> anyhow::Result<()> {
490        let microstate = Microstate::builder()
491            .boundary(Open)
492            .bodies([
493                Body::point(Hyperbolic::<3>::from_polar_coordinates(1.2, 0.0)),
494                Body::point(Hyperbolic::<3>::from_polar_coordinates(0.6, 1.5)),
495            ])
496            .step(1234)
497            .try_build()?;
498
499        let tmp_dir = tempdir()?;
500        let path = tmp_dir.path().join("test.gsd");
501        let mut hoomd_gsd_file = HoomdGsdFile::create(path.clone())?;
502        hoomd_gsd_file.append_microstate(&microstate)?;
503
504        drop(hoomd_gsd_file);
505
506        let gsd_file = GsdFile::open(path, Mode::Read)?;
507
508        assert!(gsd_file.n_frames() == 1);
509
510        let step = gsd_file.iter_scalars::<u64>(0, "configuration/step")?;
511        itertools::assert_equal(step, [1234]);
512
513        let positions: Vec<[f32; 3]> = gsd_file
514            .iter_arrays::<f32, 3>(0, "particles/position")?
515            .collect();
516        assert_relative_eq!(positions[0][0], (f32::sinh(1.2)) / (1.0 + f32::cosh(1.2)));
517        assert_relative_eq!(positions[0][1], 0.0);
518        assert_relative_eq!(
519            positions[1][0],
520            (f32::sinh(0.6) * f32::cos(1.5)) / (1.0 + f32::cosh(0.6))
521        );
522        assert_relative_eq!(
523            positions[1][1],
524            (f32::sinh(0.6) * f32::sin(1.5)) / (1.0 + f32::cosh(0.6))
525        );
526        Ok(())
527    }
528
529    #[test]
530    fn point_hyperbolic_2d_eighteight_periodic() -> anyhow::Result<()> {
531        let boundary = Periodic::new(0.6, EightEight {})?;
532        let microstate = Microstate::builder()
533            .boundary(boundary)
534            .bodies([
535                Body::point(Hyperbolic::<3>::from_polar_coordinates(0.2, 0.3)),
536                Body::point(Hyperbolic::<3>::from_polar_coordinates(0.6, 0.7)),
537            ])
538            .step(1234)
539            .try_build()?;
540
541        let tmp_dir = tempdir()?;
542        let path = tmp_dir.path().join("test.gsd");
543        let mut hoomd_gsd_file = HoomdGsdFile::create(path.clone())?;
544        hoomd_gsd_file.append_microstate(&microstate)?;
545
546        drop(hoomd_gsd_file);
547
548        let gsd_file = GsdFile::open(path, Mode::Read)?;
549
550        assert!(gsd_file.n_frames() == 1);
551
552        let step = gsd_file.iter_scalars::<u64>(0, "configuration/step")?;
553        itertools::assert_equal(step, [1234]);
554
555        let positions: Vec<[f32; 3]> = gsd_file
556            .iter_arrays::<f32, 3>(0, "particles/position")?
557            .collect();
558        assert_relative_eq!(
559            positions[0][0],
560            (f32::sinh(0.2) * f32::cos(0.3)) / (1.0 + f32::cosh(0.2))
561        );
562        assert_relative_eq!(
563            positions[0][1],
564            (f32::sinh(0.2) * f32::sin(0.3)) / (1.0 + f32::cosh(0.2))
565        );
566        assert_relative_eq!(
567            positions[1][0],
568            (f32::sinh(0.6) * f32::cos(0.7)) / (1.0 + f32::cosh(0.6))
569        );
570        assert_relative_eq!(
571            positions[1][1],
572            (f32::sinh(0.6) * f32::sin(0.7)) / (1.0 + f32::cosh(0.6))
573        );
574        Ok(())
575    }
576
577    #[test]
578    fn oriented_point_hyperbolic_2d_open() -> anyhow::Result<()> {
579        let microstate = Microstate::builder()
580            .boundary(Open)
581            .bodies([
582                Body {
583                    properties: OrientedHyperbolicPoint {
584                        position: Hyperbolic::<3>::from_polar_coordinates(0.5, 0.6),
585                        orientation: Angle::from(0.3),
586                    },
587                    sites: vec![OrientedHyperbolicPoint {
588                        position: Hyperbolic::<3>::default(),
589                        orientation: Angle::default(),
590                    }],
591                },
592                Body {
593                    properties: OrientedHyperbolicPoint {
594                        position: Hyperbolic::<3>::from_polar_coordinates(0.9, 0.3),
595                        orientation: Angle::from(1.2),
596                    },
597                    sites: vec![OrientedHyperbolicPoint {
598                        position: Hyperbolic::<3>::default(),
599                        orientation: Angle::default(),
600                    }],
601                },
602            ])
603            .step(1234)
604            .try_build()?;
605
606        let tmp_dir = tempdir()?;
607        let path = tmp_dir.path().join("test.gsd");
608        let mut hoomd_gsd_file = HoomdGsdFile::create(path.clone())?;
609        hoomd_gsd_file.append_microstate(&microstate)?;
610
611        drop(hoomd_gsd_file);
612
613        let gsd_file = GsdFile::open(path, Mode::Read)?;
614
615        assert!(gsd_file.n_frames() == 1);
616
617        let step = gsd_file.iter_scalars::<u64>(0, "configuration/step")?;
618        itertools::assert_equal(step, [1234]);
619
620        let positions: Vec<[f32; 3]> = gsd_file
621            .iter_arrays::<f32, 3>(0, "particles/position")?
622            .collect();
623        assert_relative_eq!(
624            positions[0][0],
625            (f32::sinh(0.5) * f32::cos(0.6)) / (1.0 + f32::cosh(0.5))
626        );
627        assert_relative_eq!(
628            positions[0][1],
629            (f32::sinh(0.5) * f32::sin(0.6)) / (1.0 + f32::cosh(0.5))
630        );
631        assert_relative_eq!(
632            positions[1][0],
633            (f32::sinh(0.9) * f32::cos(0.3)) / (1.0 + f32::cosh(0.9))
634        );
635        assert_relative_eq!(
636            positions[1][1],
637            (f32::sinh(0.9) * f32::sin(0.3)) / (1.0 + f32::cosh(0.9))
638        );
639        Ok(())
640    }
641
642    #[test]
643    fn oriented_point_hyperbolic_2d_periodic_eighteight() -> anyhow::Result<()> {
644        let boundary = Periodic::new(0.6, EightEight {})?;
645        let microstate = Microstate::builder()
646            .boundary(boundary)
647            .bodies([
648                Body {
649                    properties: OrientedHyperbolicPoint {
650                        position: Hyperbolic::<3>::from_polar_coordinates(0.5, 0.6),
651                        orientation: Angle::from(0.3),
652                    },
653                    sites: vec![OrientedHyperbolicPoint {
654                        position: Hyperbolic::<3>::default(),
655                        orientation: Angle::default(),
656                    }],
657                },
658                Body {
659                    properties: OrientedHyperbolicPoint {
660                        position: Hyperbolic::<3>::from_polar_coordinates(0.9, 0.3),
661                        orientation: Angle::from(1.2),
662                    },
663                    sites: vec![OrientedHyperbolicPoint {
664                        position: Hyperbolic::<3>::default(),
665                        orientation: Angle::default(),
666                    }],
667                },
668            ])
669            .step(1234)
670            .try_build()?;
671
672        let tmp_dir = tempdir()?;
673        let path = tmp_dir.path().join("test.gsd");
674        let mut hoomd_gsd_file = HoomdGsdFile::create(path.clone())?;
675        hoomd_gsd_file.append_microstate(&microstate)?;
676
677        drop(hoomd_gsd_file);
678
679        let gsd_file = GsdFile::open(path, Mode::Read)?;
680
681        assert!(gsd_file.n_frames() == 1);
682
683        let step = gsd_file.iter_scalars::<u64>(0, "configuration/step")?;
684        itertools::assert_equal(step, [1234]);
685
686        let positions: Vec<[f32; 3]> = gsd_file
687            .iter_arrays::<f32, 3>(0, "particles/position")?
688            .collect();
689        assert_relative_eq!(
690            positions[0][0],
691            (f32::sinh(0.5) * f32::cos(0.6)) / (1.0 + f32::cosh(0.5))
692        );
693        assert_relative_eq!(
694            positions[0][1],
695            (f32::sinh(0.5) * f32::sin(0.6)) / (1.0 + f32::cosh(0.5))
696        );
697        assert_relative_eq!(
698            positions[1][0],
699            (f32::sinh(0.9) * f32::cos(0.3)) / (1.0 + f32::cosh(0.9))
700        );
701        assert_relative_eq!(
702            positions[1][1],
703            (f32::sinh(0.9) * f32::sin(0.3)) / (1.0 + f32::cosh(0.9))
704        );
705        Ok(())
706    }
707
708    #[test]
709    fn oriented_point_closed_rectangle_2d() -> anyhow::Result<()> {
710        let boundary = Closed(Rectangle {
711            edge_lengths: [12.0.try_into()?, 18.0.try_into()?],
712        });
713
714        let site = OrientedPoint {
715            position: Cartesian::from([0.0, 0.0]),
716            orientation: Angle::default(),
717        };
718        let a = OrientedPoint {
719            position: Cartesian::from([1.0, 0.0]),
720            orientation: Angle::from(PI / 2.0),
721        };
722        let b = OrientedPoint {
723            position: Cartesian::from([-1.0, 2.0]),
724            orientation: Angle::from(PI),
725        };
726        let body_a = Body {
727            properties: a,
728            sites: [site].into(),
729        };
730        let body_b = Body {
731            properties: b,
732            sites: [site].into(),
733        };
734
735        let microstate = Microstate::builder()
736            .boundary(boundary)
737            .bodies([body_a, body_b])
738            .step(1234)
739            .try_build()?;
740
741        let tmp_dir = tempdir()?;
742        let path = tmp_dir.path().join("test.gsd");
743        let mut hoomd_gsd_file = HoomdGsdFile::create(path.clone())?;
744        hoomd_gsd_file.append_microstate(&microstate)?;
745
746        drop(hoomd_gsd_file);
747
748        let gsd_file = GsdFile::open(path, Mode::Read)?;
749
750        assert!(gsd_file.n_frames() == 1);
751
752        let step = gsd_file.iter_scalars::<u64>(0, "configuration/step")?;
753        itertools::assert_equal(step, [1234]);
754
755        let positions = gsd_file.iter_arrays::<f32, 3>(0, "particles/position")?;
756        itertools::assert_equal(positions, [[1.0, 0.0, 0.0], [-1.0, 2.0, 0.0]]);
757
758        assert!(
759            gsd_file
760                .iter_arrays::<f32, 4>(0, "particles/orientation")?
761                .count()
762                == 2
763        );
764
765        let dimensions = gsd_file.iter_scalars::<u8>(0, "configuration/dimensions")?;
766        itertools::assert_equal(dimensions, [2]);
767
768        let box_ = gsd_file.iter_scalars::<f32>(0, "configuration/box")?;
769        itertools::assert_equal(box_, [12.0_f32, 18.0, 0.0, 0.0, 0.0, 0.0]);
770
771        Ok(())
772    }
773
774    #[test]
775    fn oriented_point_periodic_rectangle_2d() -> anyhow::Result<()> {
776        let boundary = Periodic::new(
777            0.0,
778            Rectangle {
779                edge_lengths: [12.0.try_into()?, 18.0.try_into()?],
780            },
781        )?;
782
783        let site = OrientedPoint {
784            position: Cartesian::from([0.0, 0.0]),
785            orientation: Angle::default(),
786        };
787        let a = OrientedPoint {
788            position: Cartesian::from([1.0, 0.0]),
789            orientation: Angle::from(PI / 2.0),
790        };
791        let b = OrientedPoint {
792            position: Cartesian::from([-1.0, 2.0]),
793            orientation: Angle::from(PI),
794        };
795        let body_a = Body {
796            properties: a,
797            sites: [site].into(),
798        };
799        let body_b = Body {
800            properties: b,
801            sites: [site].into(),
802        };
803
804        let microstate = Microstate::builder()
805            .boundary(boundary)
806            .bodies([body_a, body_b])
807            .step(1234)
808            .try_build()?;
809
810        let tmp_dir = tempdir()?;
811        let path = tmp_dir.path().join("test.gsd");
812        let mut hoomd_gsd_file = HoomdGsdFile::create(path.clone())?;
813        hoomd_gsd_file.append_microstate(&microstate)?;
814
815        drop(hoomd_gsd_file);
816
817        let gsd_file = GsdFile::open(path, Mode::Read)?;
818
819        assert!(gsd_file.n_frames() == 1);
820
821        let step = gsd_file.iter_scalars::<u64>(0, "configuration/step")?;
822        itertools::assert_equal(step, [1234]);
823
824        let positions = gsd_file.iter_arrays::<f32, 3>(0, "particles/position")?;
825        itertools::assert_equal(positions, [[1.0, 0.0, 0.0], [-1.0, 2.0, 0.0]]);
826
827        assert!(
828            gsd_file
829                .iter_arrays::<f32, 4>(0, "particles/orientation")?
830                .count()
831                == 2
832        );
833
834        let dimensions = gsd_file.iter_scalars::<u8>(0, "configuration/dimensions")?;
835        itertools::assert_equal(dimensions, [2]);
836
837        let box_ = gsd_file.iter_scalars::<f32>(0, "configuration/box")?;
838        itertools::assert_equal(box_, [12.0_f32, 18.0, 0.0, 0.0, 0.0, 0.0]);
839
840        Ok(())
841    }
842
843    #[test]
844    fn point_closed_cuboid_3d() -> anyhow::Result<()> {
845        let boundary = Closed(Hypercuboid {
846            edge_lengths: [12.0.try_into()?, 18.0.try_into()?, 24.0.try_into()?],
847        });
848
849        let microstate = Microstate::builder()
850            .boundary(boundary)
851            .bodies([
852                Body::point(Cartesian::from([1.0, 0.0, 4.0])),
853                Body::point(Cartesian::from([-1.0, 2.0, -2.0])),
854            ])
855            .step(1234)
856            .try_build()?;
857
858        let tmp_dir = tempdir()?;
859        let path = tmp_dir.path().join("test.gsd");
860        let mut hoomd_gsd_file = HoomdGsdFile::create(path.clone())?;
861        hoomd_gsd_file.append_microstate(&microstate)?;
862
863        drop(hoomd_gsd_file);
864
865        let gsd_file = GsdFile::open(path, Mode::Read)?;
866
867        assert!(gsd_file.n_frames() == 1);
868
869        let step = gsd_file.iter_scalars::<u64>(0, "configuration/step")?;
870        itertools::assert_equal(step, [1234]);
871
872        let positions = gsd_file.iter_arrays::<f32, 3>(0, "particles/position")?;
873        itertools::assert_equal(positions, [[1.0, 0.0, 4.0], [-1.0, 2.0, -2.0]]);
874
875        let dimensions = gsd_file.iter_scalars::<u8>(0, "configuration/dimensions")?;
876        itertools::assert_equal(dimensions, [3]);
877
878        let box_ = gsd_file.iter_scalars::<f32>(0, "configuration/box")?;
879        itertools::assert_equal(box_, [12.0_f32, 18.0, 24.0, 0.0, 0.0, 0.0]);
880
881        Ok(())
882    }
883
884    #[test]
885    fn point_periodic_cuboid_3d() -> anyhow::Result<()> {
886        let boundary = Periodic::new(
887            0.0,
888            Hypercuboid {
889                edge_lengths: [12.0.try_into()?, 18.0.try_into()?, 24.0.try_into()?],
890            },
891        )?;
892
893        let microstate = Microstate::builder()
894            .boundary(boundary)
895            .bodies([
896                Body::point(Cartesian::from([1.0, 0.0, 4.0])),
897                Body::point(Cartesian::from([-1.0, 2.0, -2.0])),
898            ])
899            .step(1234)
900            .try_build()?;
901
902        let tmp_dir = tempdir()?;
903        let path = tmp_dir.path().join("test.gsd");
904        let mut hoomd_gsd_file = HoomdGsdFile::create(path.clone())?;
905        hoomd_gsd_file.append_microstate(&microstate)?;
906
907        drop(hoomd_gsd_file);
908
909        let gsd_file = GsdFile::open(path, Mode::Read)?;
910
911        assert!(gsd_file.n_frames() == 1);
912
913        let step = gsd_file.iter_scalars::<u64>(0, "configuration/step")?;
914        itertools::assert_equal(step, [1234]);
915
916        let positions = gsd_file.iter_arrays::<f32, 3>(0, "particles/position")?;
917        itertools::assert_equal(positions, [[1.0, 0.0, 4.0], [-1.0, 2.0, -2.0]]);
918
919        let dimensions = gsd_file.iter_scalars::<u8>(0, "configuration/dimensions")?;
920        itertools::assert_equal(dimensions, [3]);
921
922        let box_ = gsd_file.iter_scalars::<f32>(0, "configuration/box")?;
923        itertools::assert_equal(box_, [12.0_f32, 18.0, 24.0, 0.0, 0.0, 0.0]);
924
925        Ok(())
926    }
927
928    #[test]
929    fn oriented_point_closed_cuboid_3d() -> anyhow::Result<()> {
930        let boundary = Closed(Hypercuboid {
931            edge_lengths: [12.0.try_into()?, 18.0.try_into()?, 24.0.try_into()?],
932        });
933
934        let site = OrientedPoint {
935            position: Cartesian::default(),
936            orientation: Versor::default(),
937        };
938        let a = OrientedPoint {
939            position: Cartesian::from([1.0, 0.0, 4.0]),
940            orientation: Versor::from_axis_angle([1.0, 0.0, 0.0].try_into()?, PI / 2.0),
941        };
942        let b = OrientedPoint {
943            position: Cartesian::from([-1.0, 2.0, -2.0]),
944            orientation: Versor::from_axis_angle([0.0, 1.0, 0.0].try_into()?, PI / 2.0),
945        };
946        let body_a = Body {
947            properties: a,
948            sites: [site].into(),
949        };
950        let body_b = Body {
951            properties: b,
952            sites: [site].into(),
953        };
954
955        let microstate = Microstate::builder()
956            .boundary(boundary)
957            .bodies([body_a, body_b])
958            .step(1234)
959            .try_build()?;
960
961        let tmp_dir = tempdir()?;
962        let path = tmp_dir.path().join("test.gsd");
963        let mut hoomd_gsd_file = HoomdGsdFile::create(path.clone())?;
964        hoomd_gsd_file.append_microstate(&microstate)?;
965
966        drop(hoomd_gsd_file);
967
968        let gsd_file = GsdFile::open(path, Mode::Read)?;
969
970        assert!(gsd_file.n_frames() == 1);
971
972        let step = gsd_file.iter_scalars::<u64>(0, "configuration/step")?;
973        itertools::assert_equal(step, [1234]);
974
975        let positions = gsd_file.iter_arrays::<f32, 3>(0, "particles/position")?;
976        itertools::assert_equal(positions, [[1.0, 0.0, 4.0], [-1.0, 2.0, -2.0]]);
977
978        assert!(
979            gsd_file
980                .iter_arrays::<f32, 4>(0, "particles/orientation")?
981                .count()
982                == 2
983        );
984
985        let dimensions = gsd_file.iter_scalars::<u8>(0, "configuration/dimensions")?;
986        itertools::assert_equal(dimensions, [3]);
987
988        let box_ = gsd_file.iter_scalars::<f32>(0, "configuration/box")?;
989        itertools::assert_equal(box_, [12.0_f32, 18.0, 24.0, 0.0, 0.0, 0.0]);
990
991        Ok(())
992    }
993
994    #[test]
995    fn oriented_point_periodic_cuboid_3d() -> anyhow::Result<()> {
996        let boundary = Periodic::new(
997            0.0,
998            Hypercuboid {
999                edge_lengths: [12.0.try_into()?, 18.0.try_into()?, 24.0.try_into()?],
1000            },
1001        )?;
1002
1003        let site = OrientedPoint {
1004            position: Cartesian::from([0.0, 0.0, 0.0]),
1005            orientation: Versor::default(),
1006        };
1007        let a = OrientedPoint {
1008            position: Cartesian::from([1.0, 0.0, 4.0]),
1009            orientation: Versor::from_axis_angle([1.0, 0.0, 0.0].try_into()?, PI / 2.0),
1010        };
1011        let b = OrientedPoint {
1012            position: Cartesian::from([-1.0, 2.0, -2.0]),
1013            orientation: Versor::from_axis_angle([0.0, 1.0, 0.0].try_into()?, PI / 2.0),
1014        };
1015        let body_a = Body {
1016            properties: a,
1017            sites: [site].into(),
1018        };
1019        let body_b = Body {
1020            properties: b,
1021            sites: [site].into(),
1022        };
1023
1024        let microstate = Microstate::builder()
1025            .boundary(boundary)
1026            .bodies([body_a, body_b])
1027            .step(1234)
1028            .try_build()?;
1029
1030        let tmp_dir = tempdir()?;
1031        let path = tmp_dir.path().join("test.gsd");
1032        let mut hoomd_gsd_file = HoomdGsdFile::create(path.clone())?;
1033        hoomd_gsd_file.append_microstate(&microstate)?;
1034
1035        drop(hoomd_gsd_file);
1036
1037        let gsd_file = GsdFile::open(path, Mode::Read)?;
1038
1039        assert!(gsd_file.n_frames() == 1);
1040
1041        let step = gsd_file.iter_scalars::<u64>(0, "configuration/step")?;
1042        itertools::assert_equal(step, [1234]);
1043
1044        let positions = gsd_file.iter_arrays::<f32, 3>(0, "particles/position")?;
1045        itertools::assert_equal(positions, [[1.0, 0.0, 4.0], [-1.0, 2.0, -2.0]]);
1046
1047        assert!(
1048            gsd_file
1049                .iter_arrays::<f32, 4>(0, "particles/orientation")?
1050                .count()
1051                == 2
1052        );
1053
1054        let dimensions = gsd_file.iter_scalars::<u8>(0, "configuration/dimensions")?;
1055        itertools::assert_equal(dimensions, [3]);
1056
1057        let box_ = gsd_file.iter_scalars::<f32>(0, "configuration/box")?;
1058        itertools::assert_equal(box_, [12.0_f32, 18.0, 24.0, 0.0, 0.0, 0.0]);
1059
1060        Ok(())
1061    }
1062}