1use 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(µstate)?;
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(µstate)?;
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(µstate)?;
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(µstate)?;
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(µstate)?;
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(µstate)?;
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(µstate)?;
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(µstate)?;
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}