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_vector::{Angle, Cartesian, Versor};
9
10use crate::{
11    AppendMicrostate, Microstate,
12    boundary::{Closed, Periodic},
13    property::{OrientedPoint, Point},
14};
15
16impl<B, X> AppendMicrostate<B, Point<Cartesian<2>>, X, Closed<Hypercuboid<2>>> for HoomdGsdFile {
17    #[inline]
18    fn append_microstate(
19        &mut self,
20        microstate: &Microstate<B, Point<Cartesian<2>>, X, Closed<Hypercuboid<2>>>,
21    ) -> Result<Frame<'_>, AppendError> {
22        self.append_frame(microstate.step())?
23            .configuration_box(microstate.boundary().0.to_gsd_box())?
24            .configuration_dimensions(Dimensions::Two)?
25            .particles_position(
26                microstate
27                    .iter_sites_tag_order()
28                    .map(|s| s.properties.position)
29                    .map(|p| [p[0], p[1], 0.0].into()),
30            )
31    }
32}
33
34impl<B, X> AppendMicrostate<B, Point<Cartesian<2>>, X, Periodic<Hypercuboid<2>>> for HoomdGsdFile {
35    #[inline]
36    fn append_microstate(
37        &mut self,
38        microstate: &Microstate<B, Point<Cartesian<2>>, X, Periodic<Hypercuboid<2>>>,
39    ) -> Result<Frame<'_>, AppendError> {
40        self.append_frame(microstate.step())?
41            .configuration_box(microstate.boundary().shape().to_gsd_box())?
42            .configuration_dimensions(Dimensions::Two)?
43            .particles_position(
44                microstate
45                    .iter_sites_tag_order()
46                    .map(|s| s.properties.position)
47                    .map(|p| [p[0], p[1], 0.0].into()),
48            )
49    }
50}
51
52impl<B, X> AppendMicrostate<B, OrientedPoint<Cartesian<2>, Angle>, X, Closed<Hypercuboid<2>>>
53    for HoomdGsdFile
54{
55    #[inline]
56    fn append_microstate(
57        &mut self,
58        microstate: &Microstate<B, OrientedPoint<Cartesian<2>, Angle>, X, Closed<Hypercuboid<2>>>,
59    ) -> Result<Frame<'_>, AppendError> {
60        self.append_frame(microstate.step())?
61            .configuration_box(microstate.boundary().0.to_gsd_box())?
62            .configuration_dimensions(Dimensions::Two)?
63            .particles_position(
64                microstate
65                    .iter_sites_tag_order()
66                    .map(|s| s.properties.position)
67                    .map(|p| [p[0], p[1], 0.0].into()),
68            )?
69            .particles_orientation(
70                microstate
71                    .iter_sites_tag_order()
72                    .map(|s| s.properties.orientation.theta)
73                    .map(|a| {
74                        Versor::from_axis_angle(
75                            [0.0, 0.0, 1.0]
76                                .try_into()
77                                .expect("hard-coded vector can be normalized"),
78                            a,
79                        )
80                    }),
81            )
82    }
83}
84
85impl<B, X> AppendMicrostate<B, OrientedPoint<Cartesian<2>, Angle>, X, Periodic<Hypercuboid<2>>>
86    for HoomdGsdFile
87{
88    #[inline]
89    fn append_microstate(
90        &mut self,
91        microstate: &Microstate<B, OrientedPoint<Cartesian<2>, Angle>, X, Periodic<Hypercuboid<2>>>,
92    ) -> Result<Frame<'_>, AppendError> {
93        self.append_frame(microstate.step())?
94            .configuration_box(microstate.boundary().shape().to_gsd_box())?
95            .configuration_dimensions(Dimensions::Two)?
96            .particles_position(
97                microstate
98                    .iter_sites_tag_order()
99                    .map(|s| s.properties.position)
100                    .map(|p| [p[0], p[1], 0.0].into()),
101            )?
102            .particles_orientation(
103                microstate
104                    .iter_sites_tag_order()
105                    .map(|s| s.properties.orientation.theta)
106                    .map(|a| {
107                        Versor::from_axis_angle(
108                            [0.0, 0.0, 1.0]
109                                .try_into()
110                                .expect("hard-coded vector can be normalized"),
111                            a,
112                        )
113                    }),
114            )
115    }
116}
117
118impl<B, X> AppendMicrostate<B, Point<Cartesian<3>>, X, Closed<Hypercuboid<3>>> for HoomdGsdFile {
119    #[inline]
120    fn append_microstate(
121        &mut self,
122        microstate: &Microstate<B, Point<Cartesian<3>>, X, Closed<Hypercuboid<3>>>,
123    ) -> Result<Frame<'_>, AppendError> {
124        self.append_frame(microstate.step())?
125            .configuration_box(microstate.boundary().0.to_gsd_box())?
126            .configuration_dimensions(Dimensions::Three)?
127            .particles_position(
128                microstate
129                    .iter_sites_tag_order()
130                    .map(|s| s.properties.position),
131            )
132    }
133}
134
135impl<B, X> AppendMicrostate<B, Point<Cartesian<3>>, X, Periodic<Hypercuboid<3>>> for HoomdGsdFile {
136    #[inline]
137    fn append_microstate(
138        &mut self,
139        microstate: &Microstate<B, Point<Cartesian<3>>, X, Periodic<Hypercuboid<3>>>,
140    ) -> Result<Frame<'_>, AppendError> {
141        self.append_frame(microstate.step())?
142            .configuration_box(microstate.boundary().shape().to_gsd_box())?
143            .configuration_dimensions(Dimensions::Three)?
144            .particles_position(
145                microstate
146                    .iter_sites_tag_order()
147                    .map(|s| s.properties.position),
148            )
149    }
150}
151
152impl<B, X> AppendMicrostate<B, OrientedPoint<Cartesian<3>, Versor>, X, Closed<Hypercuboid<3>>>
153    for HoomdGsdFile
154{
155    #[inline]
156    fn append_microstate(
157        &mut self,
158        microstate: &Microstate<B, OrientedPoint<Cartesian<3>, Versor>, X, Closed<Hypercuboid<3>>>,
159    ) -> Result<Frame<'_>, AppendError> {
160        self.append_frame(microstate.step())?
161            .configuration_box(microstate.boundary().0.to_gsd_box())?
162            .configuration_dimensions(Dimensions::Three)?
163            .particles_position(
164                microstate
165                    .iter_sites_tag_order()
166                    .map(|s| s.properties.position),
167            )?
168            .particles_orientation(
169                microstate
170                    .iter_sites_tag_order()
171                    .map(|s| s.properties.orientation),
172            )
173    }
174}
175
176impl<B, X> AppendMicrostate<B, OrientedPoint<Cartesian<3>, Versor>, X, Periodic<Hypercuboid<3>>>
177    for HoomdGsdFile
178{
179    #[inline]
180    fn append_microstate(
181        &mut self,
182        microstate: &Microstate<
183            B,
184            OrientedPoint<Cartesian<3>, Versor>,
185            X,
186            Periodic<Hypercuboid<3>>,
187        >,
188    ) -> Result<Frame<'_>, AppendError> {
189        self.append_frame(microstate.step())?
190            .configuration_box(microstate.boundary().shape().to_gsd_box())?
191            .configuration_dimensions(Dimensions::Three)?
192            .particles_position(
193                microstate
194                    .iter_sites_tag_order()
195                    .map(|s| s.properties.position),
196            )?
197            .particles_orientation(
198                microstate
199                    .iter_sites_tag_order()
200                    .map(|s| s.properties.orientation),
201            )
202    }
203}
204
205#[cfg(test)]
206mod test {
207    use assert2::assert;
208    use std::f64::consts::PI;
209    use tempfile::tempdir;
210
211    use super::*;
212    use crate::Body;
213    use hoomd_geometry::shape::Rectangle;
214    use hoomd_gsd::file_layer::{GsdFile, Mode};
215
216    #[test]
217    fn point_closed_rectangle_2d() -> anyhow::Result<()> {
218        let boundary = Closed(Rectangle {
219            edge_lengths: [12.0.try_into()?, 18.0.try_into()?],
220        });
221
222        let microstate = Microstate::builder()
223            .boundary(boundary)
224            .bodies([
225                Body::point(Cartesian::from([1.0, 0.0])),
226                Body::point(Cartesian::from([-1.0, 2.0])),
227            ])
228            .step(1234)
229            .try_build()?;
230
231        let tmp_dir = tempdir()?;
232        let path = tmp_dir.path().join("test.gsd");
233        let mut hoomd_gsd_file = HoomdGsdFile::create(path.clone())?;
234        hoomd_gsd_file.append_microstate(&microstate)?;
235
236        drop(hoomd_gsd_file);
237
238        let gsd_file = GsdFile::open(path, Mode::Read)?;
239
240        assert!(gsd_file.n_frames() == 1);
241
242        let step = gsd_file.iter_scalars::<u64>(0, "configuration/step")?;
243        itertools::assert_equal(step, [1234]);
244
245        let positions = gsd_file.iter_arrays::<f32, 3>(0, "particles/position")?;
246        itertools::assert_equal(positions, [[1.0, 0.0, 0.0], [-1.0, 2.0, 0.0]]);
247
248        let dimensions = gsd_file.iter_scalars::<u8>(0, "configuration/dimensions")?;
249        itertools::assert_equal(dimensions, [2]);
250
251        let box_ = gsd_file.iter_scalars::<f32>(0, "configuration/box")?;
252        itertools::assert_equal(box_, [12.0_f32, 18.0, 0.0, 0.0, 0.0, 0.0]);
253
254        Ok(())
255    }
256
257    #[test]
258    fn point_periodic_rectangle_2d() -> anyhow::Result<()> {
259        let boundary = Periodic::new(
260            0.0,
261            Rectangle {
262                edge_lengths: [12.0.try_into()?, 18.0.try_into()?],
263            },
264        )?;
265
266        let microstate = Microstate::builder()
267            .boundary(boundary)
268            .bodies([
269                Body::point(Cartesian::from([1.0, 0.0])),
270                Body::point(Cartesian::from([-1.0, 2.0])),
271            ])
272            .step(1234)
273            .try_build()?;
274
275        let tmp_dir = tempdir()?;
276        let path = tmp_dir.path().join("test.gsd");
277        let mut hoomd_gsd_file = HoomdGsdFile::create(path.clone())?;
278        hoomd_gsd_file.append_microstate(&microstate)?;
279
280        drop(hoomd_gsd_file);
281
282        let gsd_file = GsdFile::open(path, Mode::Read)?;
283
284        assert!(gsd_file.n_frames() == 1);
285
286        let step = gsd_file.iter_scalars::<u64>(0, "configuration/step")?;
287        itertools::assert_equal(step, [1234]);
288
289        let positions = gsd_file.iter_arrays::<f32, 3>(0, "particles/position")?;
290        itertools::assert_equal(positions, [[1.0, 0.0, 0.0], [-1.0, 2.0, 0.0]]);
291
292        let dimensions = gsd_file.iter_scalars::<u8>(0, "configuration/dimensions")?;
293        itertools::assert_equal(dimensions, [2]);
294
295        let box_ = gsd_file.iter_scalars::<f32>(0, "configuration/box")?;
296        itertools::assert_equal(box_, [12.0_f32, 18.0, 0.0, 0.0, 0.0, 0.0]);
297
298        Ok(())
299    }
300
301    #[test]
302    fn oriented_point_closed_rectangle_2d() -> anyhow::Result<()> {
303        let boundary = Closed(Rectangle {
304            edge_lengths: [12.0.try_into()?, 18.0.try_into()?],
305        });
306
307        let site = OrientedPoint {
308            position: Cartesian::from([0.0, 0.0]),
309            orientation: Angle::default(),
310        };
311        let a = OrientedPoint {
312            position: Cartesian::from([1.0, 0.0]),
313            orientation: Angle::from(PI / 2.0),
314        };
315        let b = OrientedPoint {
316            position: Cartesian::from([-1.0, 2.0]),
317            orientation: Angle::from(PI),
318        };
319        let body_a = Body {
320            properties: a,
321            sites: [site].into(),
322        };
323        let body_b = Body {
324            properties: b,
325            sites: [site].into(),
326        };
327
328        let microstate = Microstate::builder()
329            .boundary(boundary)
330            .bodies([body_a, body_b])
331            .step(1234)
332            .try_build()?;
333
334        let tmp_dir = tempdir()?;
335        let path = tmp_dir.path().join("test.gsd");
336        let mut hoomd_gsd_file = HoomdGsdFile::create(path.clone())?;
337        hoomd_gsd_file.append_microstate(&microstate)?;
338
339        drop(hoomd_gsd_file);
340
341        let gsd_file = GsdFile::open(path, Mode::Read)?;
342
343        assert!(gsd_file.n_frames() == 1);
344
345        let step = gsd_file.iter_scalars::<u64>(0, "configuration/step")?;
346        itertools::assert_equal(step, [1234]);
347
348        let positions = gsd_file.iter_arrays::<f32, 3>(0, "particles/position")?;
349        itertools::assert_equal(positions, [[1.0, 0.0, 0.0], [-1.0, 2.0, 0.0]]);
350
351        assert!(
352            gsd_file
353                .iter_arrays::<f32, 4>(0, "particles/orientation")?
354                .count()
355                == 2
356        );
357
358        let dimensions = gsd_file.iter_scalars::<u8>(0, "configuration/dimensions")?;
359        itertools::assert_equal(dimensions, [2]);
360
361        let box_ = gsd_file.iter_scalars::<f32>(0, "configuration/box")?;
362        itertools::assert_equal(box_, [12.0_f32, 18.0, 0.0, 0.0, 0.0, 0.0]);
363
364        Ok(())
365    }
366
367    #[test]
368    fn oriented_point_periodic_rectangle_2d() -> anyhow::Result<()> {
369        let boundary = Periodic::new(
370            0.0,
371            Rectangle {
372                edge_lengths: [12.0.try_into()?, 18.0.try_into()?],
373            },
374        )?;
375
376        let site = OrientedPoint {
377            position: Cartesian::from([0.0, 0.0]),
378            orientation: Angle::default(),
379        };
380        let a = OrientedPoint {
381            position: Cartesian::from([1.0, 0.0]),
382            orientation: Angle::from(PI / 2.0),
383        };
384        let b = OrientedPoint {
385            position: Cartesian::from([-1.0, 2.0]),
386            orientation: Angle::from(PI),
387        };
388        let body_a = Body {
389            properties: a,
390            sites: [site].into(),
391        };
392        let body_b = Body {
393            properties: b,
394            sites: [site].into(),
395        };
396
397        let microstate = Microstate::builder()
398            .boundary(boundary)
399            .bodies([body_a, body_b])
400            .step(1234)
401            .try_build()?;
402
403        let tmp_dir = tempdir()?;
404        let path = tmp_dir.path().join("test.gsd");
405        let mut hoomd_gsd_file = HoomdGsdFile::create(path.clone())?;
406        hoomd_gsd_file.append_microstate(&microstate)?;
407
408        drop(hoomd_gsd_file);
409
410        let gsd_file = GsdFile::open(path, Mode::Read)?;
411
412        assert!(gsd_file.n_frames() == 1);
413
414        let step = gsd_file.iter_scalars::<u64>(0, "configuration/step")?;
415        itertools::assert_equal(step, [1234]);
416
417        let positions = gsd_file.iter_arrays::<f32, 3>(0, "particles/position")?;
418        itertools::assert_equal(positions, [[1.0, 0.0, 0.0], [-1.0, 2.0, 0.0]]);
419
420        assert!(
421            gsd_file
422                .iter_arrays::<f32, 4>(0, "particles/orientation")?
423                .count()
424                == 2
425        );
426
427        let dimensions = gsd_file.iter_scalars::<u8>(0, "configuration/dimensions")?;
428        itertools::assert_equal(dimensions, [2]);
429
430        let box_ = gsd_file.iter_scalars::<f32>(0, "configuration/box")?;
431        itertools::assert_equal(box_, [12.0_f32, 18.0, 0.0, 0.0, 0.0, 0.0]);
432
433        Ok(())
434    }
435
436    #[test]
437    fn point_closed_cuboid_3d() -> anyhow::Result<()> {
438        let boundary = Closed(Hypercuboid {
439            edge_lengths: [12.0.try_into()?, 18.0.try_into()?, 24.0.try_into()?],
440        });
441
442        let microstate = Microstate::builder()
443            .boundary(boundary)
444            .bodies([
445                Body::point(Cartesian::from([1.0, 0.0, 4.0])),
446                Body::point(Cartesian::from([-1.0, 2.0, -2.0])),
447            ])
448            .step(1234)
449            .try_build()?;
450
451        let tmp_dir = tempdir()?;
452        let path = tmp_dir.path().join("test.gsd");
453        let mut hoomd_gsd_file = HoomdGsdFile::create(path.clone())?;
454        hoomd_gsd_file.append_microstate(&microstate)?;
455
456        drop(hoomd_gsd_file);
457
458        let gsd_file = GsdFile::open(path, Mode::Read)?;
459
460        assert!(gsd_file.n_frames() == 1);
461
462        let step = gsd_file.iter_scalars::<u64>(0, "configuration/step")?;
463        itertools::assert_equal(step, [1234]);
464
465        let positions = gsd_file.iter_arrays::<f32, 3>(0, "particles/position")?;
466        itertools::assert_equal(positions, [[1.0, 0.0, 4.0], [-1.0, 2.0, -2.0]]);
467
468        let dimensions = gsd_file.iter_scalars::<u8>(0, "configuration/dimensions")?;
469        itertools::assert_equal(dimensions, [3]);
470
471        let box_ = gsd_file.iter_scalars::<f32>(0, "configuration/box")?;
472        itertools::assert_equal(box_, [12.0_f32, 18.0, 24.0, 0.0, 0.0, 0.0]);
473
474        Ok(())
475    }
476
477    #[test]
478    fn point_periodic_cuboid_3d() -> anyhow::Result<()> {
479        let boundary = Periodic::new(
480            0.0,
481            Hypercuboid {
482                edge_lengths: [12.0.try_into()?, 18.0.try_into()?, 24.0.try_into()?],
483            },
484        )?;
485
486        let microstate = Microstate::builder()
487            .boundary(boundary)
488            .bodies([
489                Body::point(Cartesian::from([1.0, 0.0, 4.0])),
490                Body::point(Cartesian::from([-1.0, 2.0, -2.0])),
491            ])
492            .step(1234)
493            .try_build()?;
494
495        let tmp_dir = tempdir()?;
496        let path = tmp_dir.path().join("test.gsd");
497        let mut hoomd_gsd_file = HoomdGsdFile::create(path.clone())?;
498        hoomd_gsd_file.append_microstate(&microstate)?;
499
500        drop(hoomd_gsd_file);
501
502        let gsd_file = GsdFile::open(path, Mode::Read)?;
503
504        assert!(gsd_file.n_frames() == 1);
505
506        let step = gsd_file.iter_scalars::<u64>(0, "configuration/step")?;
507        itertools::assert_equal(step, [1234]);
508
509        let positions = gsd_file.iter_arrays::<f32, 3>(0, "particles/position")?;
510        itertools::assert_equal(positions, [[1.0, 0.0, 4.0], [-1.0, 2.0, -2.0]]);
511
512        let dimensions = gsd_file.iter_scalars::<u8>(0, "configuration/dimensions")?;
513        itertools::assert_equal(dimensions, [3]);
514
515        let box_ = gsd_file.iter_scalars::<f32>(0, "configuration/box")?;
516        itertools::assert_equal(box_, [12.0_f32, 18.0, 24.0, 0.0, 0.0, 0.0]);
517
518        Ok(())
519    }
520
521    #[test]
522    fn oriented_point_closed_cuboid_3d() -> anyhow::Result<()> {
523        let boundary = Closed(Hypercuboid {
524            edge_lengths: [12.0.try_into()?, 18.0.try_into()?, 24.0.try_into()?],
525        });
526
527        let site = OrientedPoint {
528            position: Cartesian::default(),
529            orientation: Versor::default(),
530        };
531        let a = OrientedPoint {
532            position: Cartesian::from([1.0, 0.0, 4.0]),
533            orientation: Versor::from_axis_angle([1.0, 0.0, 0.0].try_into()?, PI / 2.0),
534        };
535        let b = OrientedPoint {
536            position: Cartesian::from([-1.0, 2.0, -2.0]),
537            orientation: Versor::from_axis_angle([0.0, 1.0, 0.0].try_into()?, PI / 2.0),
538        };
539        let body_a = Body {
540            properties: a,
541            sites: [site].into(),
542        };
543        let body_b = Body {
544            properties: b,
545            sites: [site].into(),
546        };
547
548        let microstate = Microstate::builder()
549            .boundary(boundary)
550            .bodies([body_a, body_b])
551            .step(1234)
552            .try_build()?;
553
554        let tmp_dir = tempdir()?;
555        let path = tmp_dir.path().join("test.gsd");
556        let mut hoomd_gsd_file = HoomdGsdFile::create(path.clone())?;
557        hoomd_gsd_file.append_microstate(&microstate)?;
558
559        drop(hoomd_gsd_file);
560
561        let gsd_file = GsdFile::open(path, Mode::Read)?;
562
563        assert!(gsd_file.n_frames() == 1);
564
565        let step = gsd_file.iter_scalars::<u64>(0, "configuration/step")?;
566        itertools::assert_equal(step, [1234]);
567
568        let positions = gsd_file.iter_arrays::<f32, 3>(0, "particles/position")?;
569        itertools::assert_equal(positions, [[1.0, 0.0, 4.0], [-1.0, 2.0, -2.0]]);
570
571        assert!(
572            gsd_file
573                .iter_arrays::<f32, 4>(0, "particles/orientation")?
574                .count()
575                == 2
576        );
577
578        let dimensions = gsd_file.iter_scalars::<u8>(0, "configuration/dimensions")?;
579        itertools::assert_equal(dimensions, [3]);
580
581        let box_ = gsd_file.iter_scalars::<f32>(0, "configuration/box")?;
582        itertools::assert_equal(box_, [12.0_f32, 18.0, 24.0, 0.0, 0.0, 0.0]);
583
584        Ok(())
585    }
586
587    #[test]
588    fn oriented_point_periodic_cuboid_3d() -> anyhow::Result<()> {
589        let boundary = Periodic::new(
590            0.0,
591            Hypercuboid {
592                edge_lengths: [12.0.try_into()?, 18.0.try_into()?, 24.0.try_into()?],
593            },
594        )?;
595
596        let site = OrientedPoint {
597            position: Cartesian::from([0.0, 0.0, 0.0]),
598            orientation: Versor::default(),
599        };
600        let a = OrientedPoint {
601            position: Cartesian::from([1.0, 0.0, 4.0]),
602            orientation: Versor::from_axis_angle([1.0, 0.0, 0.0].try_into()?, PI / 2.0),
603        };
604        let b = OrientedPoint {
605            position: Cartesian::from([-1.0, 2.0, -2.0]),
606            orientation: Versor::from_axis_angle([0.0, 1.0, 0.0].try_into()?, PI / 2.0),
607        };
608        let body_a = Body {
609            properties: a,
610            sites: [site].into(),
611        };
612        let body_b = Body {
613            properties: b,
614            sites: [site].into(),
615        };
616
617        let microstate = Microstate::builder()
618            .boundary(boundary)
619            .bodies([body_a, body_b])
620            .step(1234)
621            .try_build()?;
622
623        let tmp_dir = tempdir()?;
624        let path = tmp_dir.path().join("test.gsd");
625        let mut hoomd_gsd_file = HoomdGsdFile::create(path.clone())?;
626        hoomd_gsd_file.append_microstate(&microstate)?;
627
628        drop(hoomd_gsd_file);
629
630        let gsd_file = GsdFile::open(path, Mode::Read)?;
631
632        assert!(gsd_file.n_frames() == 1);
633
634        let step = gsd_file.iter_scalars::<u64>(0, "configuration/step")?;
635        itertools::assert_equal(step, [1234]);
636
637        let positions = gsd_file.iter_arrays::<f32, 3>(0, "particles/position")?;
638        itertools::assert_equal(positions, [[1.0, 0.0, 4.0], [-1.0, 2.0, -2.0]]);
639
640        assert!(
641            gsd_file
642                .iter_arrays::<f32, 4>(0, "particles/orientation")?
643                .count()
644                == 2
645        );
646
647        let dimensions = gsd_file.iter_scalars::<u8>(0, "configuration/dimensions")?;
648        itertools::assert_equal(dimensions, [3]);
649
650        let box_ = gsd_file.iter_scalars::<f32>(0, "configuration/box")?;
651        itertools::assert_equal(box_, [12.0_f32, 18.0, 24.0, 0.0, 0.0, 0.0]);
652
653        Ok(())
654    }
655}