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