1use crate::math;
9use crate::types::{F, F3, F3View, F3x3, FNx3, FNx3View, Pbc3};
10use ndarray::{Array1, Array2, Array3, ArrayView1, ArrayView2, array};
11
12#[derive(Debug, Clone, PartialEq)]
14pub enum BoxKind {
15 Ortho { len: F3, inv_len: F3 },
17 Triclinic,
19}
20
21#[derive(Debug, Clone)]
23pub struct SimBox {
24 h: F3x3,
26 inv: F3x3,
28 origin: F3,
30 pbc: Pbc3,
32 kind: BoxKind,
34 cell_defined: bool,
41}
42
43#[derive(Debug)]
45pub enum BoxError {
46 SingularCell,
48 InvalidMatrixShape { rows: usize, cols: usize },
50 InvalidVectorLength { len: usize },
52 NonContiguous(&'static str),
54 InvalidAngles,
56}
57
58impl SimBox {
59 pub fn new(h: F3x3, origin: F3, pbc: Pbc3) -> Result<Self, BoxError> {
61 Self::new_cell(h, origin, pbc, true)
62 }
63
64 pub fn new_cell(h: F3x3, origin: F3, pbc: Pbc3, cell_defined: bool) -> Result<Self, BoxError> {
69 if let Some(inv) = math::inv3(&h) {
70 let kind = detect_box_kind(&h);
71 Ok(Self {
72 h,
73 inv,
74 origin,
75 pbc,
76 kind,
77 cell_defined,
78 })
79 } else {
80 Err(BoxError::SingularCell)
81 }
82 }
83
84 pub fn is_cell_defined(&self) -> bool {
89 self.cell_defined
90 }
91
92 pub fn try_new(h: F3x3, origin: F3, pbc: Pbc3) -> Result<Self, BoxError> {
93 Self::new(h, origin, pbc)
94 }
95
96 pub fn cube(a: F, origin: F3, pbc: Pbc3) -> Result<Self, BoxError> {
98 if a <= 0.0 {
99 return Err(BoxError::InvalidVectorLength { len: 0 });
100 }
101 let h = array![[a, 0.0, 0.0], [0.0, a, 0.0], [0.0, 0.0, a]];
102 Self::new(h, origin, pbc)
103 }
104
105 pub fn ortho(lengths: F3, origin: F3, pbc: Pbc3) -> Result<Self, BoxError> {
107 if lengths.len() != 3 {
108 return Err(BoxError::InvalidVectorLength { len: lengths.len() });
109 }
110 if lengths.iter().any(|v| *v <= 0.0) {
111 return Err(BoxError::InvalidVectorLength { len: 0 });
112 }
113 let h = array![
114 [lengths[0], 0.0, 0.0],
115 [0.0, lengths[1], 0.0],
116 [0.0, 0.0, lengths[2]],
117 ];
118 Self::new(h, origin, pbc)
119 }
120
121 pub fn matrix_from_lengths_angles(lengths: [F; 3], angles: [F; 3]) -> Result<F3x3, BoxError> {
123 let [a, b, c] = lengths;
124 let [alpha, beta, gamma] = angles.map(F::to_radians);
125 if [a, b, c].iter().any(|value| *value <= 0.0)
126 || [alpha, beta, gamma]
127 .iter()
128 .any(|angle| !(*angle > 0.0 && *angle < std::f64::consts::PI))
129 {
130 return Err(BoxError::InvalidAngles);
131 }
132 let (cos_a, cos_b, cos_c) = (alpha.cos(), beta.cos(), gamma.cos());
133 let xy = b * cos_c;
134 let xz = c * cos_b;
135 let ly = (b * b - xy * xy).sqrt();
136 if !ly.is_finite() || ly <= 0.0 {
137 return Err(BoxError::InvalidAngles);
138 }
139 let yz = (b * c * cos_a - xy * xz) / ly;
140 let lz2 = c * c - xz * xz - yz * yz;
141 if !lz2.is_finite() || lz2 <= 0.0 {
142 return Err(BoxError::InvalidAngles);
143 }
144 Ok(array![[a, xy, xz], [0.0, ly, yz], [0.0, 0.0, lz2.sqrt()]])
145 }
146
147 pub fn matrix_from_lengths_tilts(lengths: [F; 3], tilts: [F; 3]) -> F3x3 {
149 array![
150 [lengths[0], tilts[0], tilts[1]],
151 [0.0, lengths[1], tilts[2]],
152 [0.0, 0.0, lengths[2]],
153 ]
154 }
155
156 pub fn restricted_matrix(matrix: FNx3View<'_>) -> Result<F3x3, BoxError> {
158 if matrix.dim() != (3, 3) {
159 return Err(BoxError::InvalidMatrixShape {
160 rows: matrix.nrows(),
161 cols: matrix.ncols(),
162 });
163 }
164 let a = matrix.column(0).to_owned();
165 let b = matrix.column(1).to_owned();
166 let c = matrix.column(2).to_owned();
167 let ax = math::norm3(&a);
168 if ax <= 0.0 {
169 return Err(BoxError::SingularCell);
170 }
171 let ua = &a / ax;
172 let bx = b.dot(&ua);
173 let cross_ab = math::cross3(&a, &b);
174 let cross_norm = math::norm3(&cross_ab);
175 if cross_norm <= 0.0 {
176 return Err(BoxError::SingularCell);
177 }
178 let by = math::norm3(&math::cross3(&ua, &b));
179 let uab = &cross_ab / cross_norm;
180 let cx = c.dot(&ua);
181 let cy = c.dot(&math::cross3(&uab, &ua));
182 let cz = c.dot(&uab);
183 Ok(array![[ax, bx, cx], [0.0, by, cy], [0.0, 0.0, cz]])
184 }
185
186 pub fn free(points: FNx3View<'_>, padding: F) -> Result<Self, BoxError> {
200 assert!(padding > 0.0, "padding must be positive");
201 let n = points.nrows();
202 if n == 0 {
203 return Self::cube(padding, array![0.0 as F, 0.0, 0.0], [false, false, false]);
205 }
206 let mut min = array![points[[0, 0]], points[[0, 1]], points[[0, 2]]];
207 let mut max = min.clone();
208 for i in 1..n {
209 for d in 0..3 {
210 if points[[i, d]] < min[d] {
211 min[d] = points[[i, d]];
212 }
213 if points[[i, d]] > max[d] {
214 max[d] = points[[i, d]];
215 }
216 }
217 }
218 let origin = array![min[0] - padding, min[1] - padding, min[2] - padding,];
219 let lengths = array![
220 (max[0] - min[0] + 2.0 * padding).max(padding),
221 (max[1] - min[1] + 2.0 * padding).max(padding),
222 (max[2] - min[2] + 2.0 * padding).max(padding),
223 ];
224 Self::ortho(lengths, origin, [false, false, false])
225 }
226
227 pub fn from_bounds(points: FNx3View<'_>, padding: [F; 3], pbc: Pbc3) -> Result<Self, BoxError> {
233 if points.nrows() == 0 {
234 return Err(BoxError::InvalidVectorLength { len: 0 });
235 }
236 if padding.iter().any(|value| *value < 0.0) {
237 return Err(BoxError::InvalidVectorLength { len: 0 });
238 }
239 let mut min = [points[[0, 0]], points[[0, 1]], points[[0, 2]]];
240 let mut max = min;
241 for point in points.rows().into_iter().skip(1) {
242 for d in 0..3 {
243 min[d] = min[d].min(point[d]);
244 max[d] = max[d].max(point[d]);
245 }
246 }
247 let origin = array![
248 min[0] - padding[0],
249 min[1] - padding[1],
250 min[2] - padding[2]
251 ];
252 let lengths = array![
253 max[0] - min[0] + 2.0 * padding[0],
254 max[1] - min[1] + 2.0 * padding[1],
255 max[2] - min[2] + 2.0 * padding[2]
256 ];
257 Self::ortho(lengths, origin, pbc)
258 }
259
260 pub fn free_columns(xs: &[F], ys: &[F], zs: &[F], padding: F) -> Result<Self, BoxError> {
275 assert!(padding > 0.0, "padding must be positive");
276 assert!(
277 xs.len() == ys.len() && ys.len() == zs.len(),
278 "x/y/z slices must have equal length"
279 );
280 let n = xs.len();
281 if n == 0 {
282 return Self::cube(padding, array![0.0 as F, 0.0, 0.0], [false, false, false]);
284 }
285 let mut min = array![xs[0], ys[0], zs[0]];
286 let mut max = min.clone();
287 for i in 1..n {
288 let p = [xs[i], ys[i], zs[i]];
289 for d in 0..3 {
290 if p[d] < min[d] {
291 min[d] = p[d];
292 }
293 if p[d] > max[d] {
294 max[d] = p[d];
295 }
296 }
297 }
298 let origin = array![min[0] - padding, min[1] - padding, min[2] - padding,];
299 let lengths = array![
300 (max[0] - min[0] + 2.0 * padding).max(padding),
301 (max[1] - min[1] + 2.0 * padding).max(padding),
302 (max[2] - min[2] + 2.0 * padding).max(padding),
303 ];
304 Self::ortho(lengths, origin, [false, false, false])
305 }
306
307 pub fn h_view(&self) -> FNx3View<'_> {
309 self.h.view()
310 }
311
312 pub fn inv_view(&self) -> FNx3View<'_> {
314 self.inv.view()
315 }
316
317 pub fn origin_view(&self) -> F3View<'_> {
319 self.origin.view()
320 }
321
322 pub fn pbc_view(&self) -> ArrayView1<'_, bool> {
324 ArrayView1::from_shape(3, &self.pbc).expect("pbc_view shape")
325 }
326
327 pub fn pbc(&self) -> Pbc3 {
329 self.pbc
330 }
331
332 pub fn volume(&self) -> F {
334 math::det3(&self.h).abs()
335 }
336
337 pub fn is_free(&self) -> bool {
339 self.pbc.iter().all(|&p| !p)
340 }
341
342 pub fn style(&self) -> &'static str {
345 if self.is_free() {
346 "free"
347 } else {
348 match self.kind {
349 BoxKind::Ortho { .. } => "orthogonal",
350 BoxKind::Triclinic => "triclinic",
351 }
352 }
353 }
354
355 pub fn tilts(&self) -> F3 {
357 array![self.h[[0, 1]], self.h[[0, 2]], self.h[[1, 2]]]
358 }
359
360 pub fn lengths(&self) -> F3 {
362 let a = self.lattice(0);
363 let b = self.lattice(1);
364 let c = self.lattice(2);
365 array![math::norm3(&a), math::norm3(&b), math::norm3(&c)]
366 }
367
368 pub fn angles(&self) -> F3 {
370 let a = self.lattice(0);
371 let b = self.lattice(1);
372 let c = self.lattice(2);
373 let angle = |u: &F3, v: &F3| {
374 (u.dot(v) / (math::norm3(u) * math::norm3(v)))
375 .clamp(-1.0, 1.0)
376 .acos()
377 .to_degrees()
378 };
379 array![angle(&b, &c), angle(&a, &c), angle(&a, &b)]
380 }
381
382 pub fn nearest_plane_distance(&self) -> F3 {
385 let v = self.volume();
386 let a1 = self.lattice(0);
387 let a2 = self.lattice(1);
388 let a3 = self.lattice(2);
389
390 let c23 = math::cross3(&a2, &a3);
391 let c31 = math::cross3(&a3, &a1);
392 let c12 = math::cross3(&a1, &a2);
393
394 array![
395 v / math::norm3(&c23),
396 v / math::norm3(&c31),
397 v / math::norm3(&c12)
398 ]
399 }
400
401 pub fn kind(&self) -> &BoxKind {
402 &self.kind
403 }
404
405 pub fn lattice(&self, index: usize) -> F3 {
407 assert!(index < 3, "lattice index must be 0..2");
408 self.h.column(index).to_owned()
409 }
410
411 pub fn make_fractional(&self, r: F3View<'_>) -> F3 {
413 let dr = &r - &self.origin.view();
414 let mut frac = self.inv.dot(&dr);
415 for f in frac.iter_mut() {
416 *f -= f.floor();
417 }
418 frac
419 }
420
421 #[inline(always)]
423 pub fn make_fractional_fast(&self, r: F3View<'_>) -> F3 {
424 match &self.kind {
425 BoxKind::Ortho { inv_len, .. } => {
426 let mut frac = array![
427 (r[0] - self.origin[0]) * inv_len[0],
428 (r[1] - self.origin[1]) * inv_len[1],
429 (r[2] - self.origin[2]) * inv_len[2],
430 ];
431 for f in frac.iter_mut() {
432 *f -= f.floor();
433 }
434 frac
435 }
436 BoxKind::Triclinic => self.make_fractional(r),
437 }
438 }
439
440 #[inline(always)]
455 pub fn make_fractional_raw_arr3(&self, r: [F; 3]) -> [F; 3] {
456 match &self.kind {
457 BoxKind::Ortho { inv_len, .. } => [
458 (r[0] - self.origin[0]) * inv_len[0],
459 (r[1] - self.origin[1]) * inv_len[1],
460 (r[2] - self.origin[2]) * inv_len[2],
461 ],
462 BoxKind::Triclinic => {
463 let rv = ArrayView1::from_shape(3, &r).expect("make_fractional_raw_arr3 shape");
464 let dr = &rv - &self.origin.view();
465 let f = self.inv.dot(&dr);
466 [f[0], f[1], f[2]]
467 }
468 }
469 }
470
471 pub fn make_cartesian(&self, frac: F3View<'_>) -> F3 {
473 &self.origin + &self.h.dot(&frac)
474 }
475
476 pub fn mic(&self) -> Mic {
488 match &self.kind {
489 BoxKind::Ortho { len, inv_len } => Mic::Ortho {
490 len: [len[0], len[1], len[2]],
491 inv_len: [inv_len[0], inv_len[1], inv_len[2]],
492 pbc: self.pbc(),
493 },
494 BoxKind::Triclinic => {
495 let mut h = [0.0; 9];
496 let mut inv = [0.0; 9];
497 for i in 0..3 {
498 for j in 0..3 {
499 h[3 * i + j] = self.h[[i, j]];
500 inv[3 * i + j] = self.inv[[i, j]];
501 }
502 }
503 Mic::Triclinic {
504 h,
505 inv,
506 pbc: self.pbc(),
507 }
508 }
509 }
510 }
511
512 #[inline(always)]
522 fn mic_kernel(&self, a: [F; 3], b: [F; 3]) -> [F; 3] {
523 match &self.kind {
524 BoxKind::Ortho { len, inv_len } => {
525 let mut dr = [b[0] - a[0], b[1] - a[1], b[2] - a[2]];
526 if self.pbc[0] {
527 dr[0] -= (dr[0] * inv_len[0]).round() * len[0];
528 }
529 if self.pbc[1] {
530 dr[1] -= (dr[1] * inv_len[1]).round() * len[1];
531 }
532 if self.pbc[2] {
533 dr[2] -= (dr[2] * inv_len[2]).round() * len[2];
534 }
535 dr
536 }
537 BoxKind::Triclinic => {
538 let d = [b[0] - a[0], b[1] - a[1], b[2] - a[2]];
551 let mut f = [
552 self.inv[[0, 0]] * d[0] + self.inv[[0, 1]] * d[1] + self.inv[[0, 2]] * d[2],
553 self.inv[[1, 0]] * d[0] + self.inv[[1, 1]] * d[1] + self.inv[[1, 2]] * d[2],
554 self.inv[[2, 0]] * d[0] + self.inv[[2, 1]] * d[1] + self.inv[[2, 2]] * d[2],
555 ];
556 for (fk, &periodic) in f.iter_mut().zip(self.pbc.iter()) {
557 if periodic {
558 *fk -= fk.round();
559 }
560 }
561 [
562 self.h[[0, 0]] * f[0] + self.h[[0, 1]] * f[1] + self.h[[0, 2]] * f[2],
563 self.h[[1, 0]] * f[0] + self.h[[1, 1]] * f[1] + self.h[[1, 2]] * f[2],
564 self.h[[2, 0]] * f[0] + self.h[[2, 1]] * f[1] + self.h[[2, 2]] * f[2],
565 ]
566 }
567 }
568 }
569
570 #[inline]
577 pub fn shortest_vector(&self, r1: F3View<'_>, r2: F3View<'_>) -> F3 {
578 let dr = self.mic_kernel([r1[0], r1[1], r1[2]], [r2[0], r2[1], r2[2]]);
579 array![dr[0], dr[1], dr[2]]
580 }
581
582 #[inline(always)]
589 pub fn shortest_vector_impl(&self, a: [F; 3], b: [F; 3]) -> [F; 3] {
590 self.mic_kernel(a, b)
591 }
592
593 #[inline]
595 pub fn calc_distance2(&self, a: F3View<'_>, b: F3View<'_>) -> F {
596 let dr = self.shortest_vector(a, b);
597 dr.dot(&dr)
598 }
599
600 pub fn to_frac(&self, xyz: FNx3View<'_>) -> FNx3 {
602 let n = xyz.nrows();
603 let mut result = FNx3::zeros((n, 3));
604 for i in 0..n {
605 let dr = &xyz.row(i) - &self.origin.view();
606 result.row_mut(i).assign(&self.inv.dot(&dr));
607 }
608 result
609 }
610
611 pub fn to_cart(&self, frac: FNx3View<'_>) -> FNx3 {
613 let n = frac.nrows();
614 let mut result = FNx3::zeros((n, 3));
615 for i in 0..n {
616 let cart = &self.origin + &self.h.dot(&frac.row(i));
617 result.row_mut(i).assign(&cart);
618 }
619 result
620 }
621
622 pub fn isin(&self, xyz: FNx3View<'_>) -> Array1<bool> {
624 let n = xyz.nrows();
625 let mut mask = Vec::with_capacity(n);
626 for i in 0..n {
627 let dr = &xyz.row(i) - &self.origin.view();
628 let frac = self.inv.dot(&dr);
629 let inside = (0..3).all(|d| frac[d] >= 0.0 && frac[d] < 1.0);
630 mask.push(inside);
631 }
632 Array1::from_vec(mask)
633 }
634
635 pub fn delta_out(
638 &self,
639 xyzu1: FNx3View<'_>,
640 xyzu2: FNx3View<'_>,
641 out: &mut FNx3,
642 minimum_image: bool,
643 ) {
644 assert_eq!(xyzu1.nrows(), xyzu2.nrows());
645 let n = xyzu1.nrows();
646 if minimum_image {
647 for i in 0..n {
648 let dr = self.shortest_vector(xyzu1.row(i), xyzu2.row(i));
649 out.row_mut(i).assign(&dr);
650 }
651 } else {
652 for i in 0..n {
653 let dr = &xyzu2.row(i) - &xyzu1.row(i);
654 out.row_mut(i).assign(&dr);
655 }
656 }
657 }
658
659 pub fn delta(&self, xyzu1: FNx3View<'_>, xyzu2: FNx3View<'_>, minimum_image: bool) -> FNx3 {
661 assert_eq!(xyzu1.nrows(), xyzu2.nrows());
662 let n = xyzu1.nrows();
663 let mut out = FNx3::zeros((n, 3));
664 self.delta_out(xyzu1, xyzu2, &mut out, minimum_image);
665 out
666 }
667
668 pub fn distances(&self, points1: FNx3View<'_>, points2: FNx3View<'_>) -> Array1<F> {
670 assert_eq!(points1.raw_dim(), points2.raw_dim());
671 let values = points1
672 .rows()
673 .into_iter()
674 .zip(points2.rows())
675 .map(|(a, b)| {
676 let dr = self.shortest_vector_impl([a[0], a[1], a[2]], [b[0], b[1], b[2]]);
677 (dr[0] * dr[0] + dr[1] * dr[1] + dr[2] * dr[2]).sqrt()
678 })
679 .collect();
680 Array1::from_vec(values)
681 }
682
683 pub fn pairwise_delta(&self, points1: FNx3View<'_>, points2: FNx3View<'_>) -> Array3<F> {
685 let mut out = Array3::zeros((points1.nrows(), points2.nrows(), 3));
686 for (i, a) in points1.rows().into_iter().enumerate() {
687 for (j, b) in points2.rows().into_iter().enumerate() {
688 let dr = self.shortest_vector_impl([a[0], a[1], a[2]], [b[0], b[1], b[2]]);
689 for d in 0..3 {
690 out[[i, j, d]] = dr[d];
691 }
692 }
693 }
694 out
695 }
696
697 pub fn pairwise_distances(&self, points1: FNx3View<'_>, points2: FNx3View<'_>) -> Array2<F> {
699 let mut out = Array2::zeros((points1.nrows(), points2.nrows()));
700 for (i, a) in points1.rows().into_iter().enumerate() {
701 for (j, b) in points2.rows().into_iter().enumerate() {
702 let dr = self.shortest_vector_impl([a[0], a[1], a[2]], [b[0], b[1], b[2]]);
703 out[[i, j]] = (dr[0] * dr[0] + dr[1] * dr[1] + dr[2] * dr[2]).sqrt();
704 }
705 }
706 out
707 }
708
709 pub fn transformed(&self, transformation: &F3x3) -> Result<Self, BoxError> {
711 Self::new_cell(
712 self.h.dot(transformation),
713 self.origin.clone(),
714 self.pbc,
715 self.cell_defined,
716 )
717 }
718
719 pub fn wrap(&self, xyz: FNx3View<'_>) -> FNx3 {
721 let mut frac = self.to_frac(xyz);
722 let n = frac.nrows();
723 for i in 0..n {
724 for d in 0..3 {
725 if self.pbc[d] {
726 frac[[i, d]] -= frac[[i, d]].floor();
727 }
728 }
729 }
730 self.to_cart(frac.view())
731 }
732
733 pub fn images(&self, xyz: FNx3View<'_>) -> Array2<i64> {
735 let frac = self.to_frac(xyz);
736 let mut images = Array2::zeros((frac.nrows(), 3));
737 for i in 0..frac.nrows() {
738 for d in 0..3 {
739 if self.pbc[d] {
740 images[[i, d]] = (frac[[i, d]] + 1e-8).floor() as i64;
741 }
742 }
743 }
744 images
745 }
746
747 pub fn unwrap(&self, xyz: FNx3View<'_>, images: ArrayView2<'_, i64>) -> FNx3 {
749 assert_eq!(xyz.raw_dim(), images.raw_dim());
750 assert_eq!(xyz.ncols(), 3);
751 let mut result = xyz.to_owned();
752 for i in 0..xyz.nrows() {
753 let image = array![
754 images[[i, 0]] as F,
755 images[[i, 1]] as F,
756 images[[i, 2]] as F,
757 ];
758 let shift = self.h.dot(&image);
759 for d in 0..3 {
760 result[[i, d]] += shift[d];
761 }
762 }
763 result
764 }
765
766 pub fn get_corners(&self) -> FNx3 {
767 self.to_cart(
768 array![
769 [0.0, 0.0, 0.0],
770 [1.0, 0.0, 0.0],
771 [1.0, 1.0, 0.0],
772 [0.0, 1.0, 0.0],
773 [0.0, 0.0, 1.0],
774 [1.0, 0.0, 1.0],
775 [1.0, 1.0, 1.0],
776 [0.0, 1.0, 1.0],
777 ]
778 .view(),
779 )
780 }
781
782 pub fn bounds(&self) -> FNx3 {
790 let corners = self.get_corners();
791 let mut b = Array2::zeros((3, 2));
792 for d in 0..3 {
793 let mut lo = corners[[0, d]];
794 let mut hi = lo;
795 for i in 1..corners.nrows() {
796 lo = lo.min(corners[[i, d]]);
797 hi = hi.max(corners[[i, d]]);
798 }
799 b[[d, 0]] = lo;
800 b[[d, 1]] = hi;
801 }
802 b
803 }
804}
805
806#[derive(Debug, Clone, Copy, PartialEq)]
813pub enum Mic {
814 Free,
816 Ortho {
817 len: [F; 3],
818 inv_len: [F; 3],
819 pbc: [bool; 3],
820 },
821 Triclinic {
822 h: [F; 9],
824 inv: [F; 9],
826 pbc: [bool; 3],
827 },
828}
829
830impl Mic {
831 #[inline(always)]
836 pub fn apply(&self, d: [F; 3]) -> [F; 3] {
837 match self {
838 Mic::Free => d,
839 Mic::Ortho { len, inv_len, pbc } => {
840 let mut dr = d;
841 for k in 0..3 {
842 if pbc[k] {
843 dr[k] -= (dr[k] * inv_len[k]).round() * len[k];
844 }
845 }
846 dr
847 }
848 Mic::Triclinic { h, inv, pbc } => {
849 let mut f = [
850 inv[0] * d[0] + inv[1] * d[1] + inv[2] * d[2],
851 inv[3] * d[0] + inv[4] * d[1] + inv[5] * d[2],
852 inv[6] * d[0] + inv[7] * d[1] + inv[8] * d[2],
853 ];
854 for (fk, &periodic) in f.iter_mut().zip(pbc.iter()) {
855 if periodic {
856 *fk -= fk.round();
857 }
858 }
859 [
860 h[0] * f[0] + h[1] * f[1] + h[2] * f[2],
861 h[3] * f[0] + h[4] * f[1] + h[5] * f[2],
862 h[6] * f[0] + h[7] * f[1] + h[8] * f[2],
863 ]
864 }
865 }
866 }
867
868 #[inline]
871 pub fn simplified(self) -> Self {
872 let pbc = match self {
873 Mic::Free => return self,
874 Mic::Ortho { pbc, .. } | Mic::Triclinic { pbc, .. } => pbc,
875 };
876 if pbc.iter().any(|&p| p) {
877 self
878 } else {
879 Mic::Free
880 }
881 }
882}
883
884fn detect_box_kind(h: &F3x3) -> BoxKind {
885 let eps: F = 1e-12;
886 let is_ortho = h[[0, 1]].abs() < eps
887 && h[[0, 2]].abs() < eps
888 && h[[1, 0]].abs() < eps
889 && h[[1, 2]].abs() < eps
890 && h[[2, 0]].abs() < eps
891 && h[[2, 1]].abs() < eps;
892 if is_ortho {
893 let len = array![h[[0, 0]], h[[1, 1]], h[[2, 2]]];
894 let inv_len = array![1.0 / len[0], 1.0 / len[1], 1.0 / len[2]];
895 BoxKind::Ortho { len, inv_len }
896 } else {
897 BoxKind::Triclinic
898 }
899}
900
901#[cfg(test)]
902mod tests {
903 use super::*;
904
905 fn assert_close(a: F, b: F) {
906 assert!((a - b).abs() < 1e-6 as F, "{} != {}", a, b);
907 }
908
909 #[test]
910 fn cell_defined_distinct_from_pbc() {
911 let defined = SimBox::ortho(
913 array![2.0, 2.0, 2.0],
914 array![0.0, 0.0, 0.0],
915 [false, false, false],
916 )
917 .unwrap();
918 assert!(defined.is_cell_defined());
919 assert!(defined.is_free()); assert_close(defined.volume(), 8.0); let nocell = SimBox::new_cell(
925 ndarray::Array2::eye(3),
926 array![0.0, 0.0, 0.0],
927 [false, false, false],
928 false,
929 )
930 .unwrap();
931 assert!(!nocell.is_cell_defined());
932 let pts = array![[1.0, 2.0, 3.0]];
934 assert_eq!(nocell.wrap(pts.view()), pts);
935 }
936
937 #[test]
938 fn roundtrip_frac_cart() {
939 let bx = SimBox::ortho(
940 array![2.0, 3.0, 4.0],
941 array![0.5, -1.0, 2.0],
942 [true, true, true],
943 )
944 .expect("invalid box lengths");
945 let pts = array![[0.5, -1.0, 2.0], [2.5, 2.0, 6.0]];
946 let frac = bx.to_frac(pts.view());
947 let cart = bx.to_cart(frac.view());
948 assert!((&pts - &cart).iter().all(|v| v.abs() < 1e-5));
949 }
950
951 #[test]
952 fn wrap_into_cell() {
953 let bx = SimBox::cube(2.0, array![0.0, 0.0, 0.0], [true, true, true])
954 .expect("invalid box length");
955 let pts = array![[2.1, -0.1, 3.9], [-1.9, 4.2, 0.0]];
956 let wrapped = bx.wrap(pts.view());
957 let frac = bx.to_frac(wrapped.view());
958 for i in 0..wrapped.nrows() {
959 let fx = frac[[i, 0]];
960 let fy = frac[[i, 1]];
961 let fz = frac[[i, 2]];
962 assert!((0.0..1.0).contains(&fx));
963 assert!((0.0..1.0).contains(&fy));
964 assert!((0.0..1.0).contains(&fz));
965 }
966 }
967
968 #[test]
969 fn calc_distance_matches_components() {
970 let bx = SimBox::cube(3.0, array![0.0, 0.0, 0.0], [true, true, true])
971 .expect("invalid box length");
972 let a = array![0.1, 0.2, 0.3];
973 let b = array![2.9, 0.2, 0.3];
974 let d2 = bx.calc_distance2(a.view(), b.view());
975 let dr = bx.shortest_vector(a.view(), b.view());
976 let expected = dr.dot(&dr);
977 assert!((d2 - expected).abs() < 1e-6);
978 }
979
980 #[test]
981 fn test_lengths_ortho() {
982 let bx = SimBox::ortho(
983 array![2.0, 4.0, 5.0],
984 array![0.0, 0.0, 0.0],
985 [true, true, true],
986 )
987 .expect("invalid box lengths");
988 let lengths = bx.lengths();
989 assert_close(lengths[0], 2.0);
990 assert_close(lengths[1], 4.0);
991 assert_close(lengths[2], 5.0);
992 }
993
994 #[test]
995 fn test_tilts_values() {
996 let h = array![[2.0, 1.0, 2.0], [0.0, 4.0, 3.0], [0.0, 0.0, 5.0]];
997 let bx = SimBox::new(h, array![0.0, 0.0, 0.0], [true, true, true]).expect("invalid box");
998 let tilts = bx.tilts();
999 assert_close(tilts[0], 1.0);
1000 assert_close(tilts[1], 2.0);
1001 assert_close(tilts[2], 3.0);
1002 }
1003
1004 #[test]
1005 fn test_volume() {
1006 let bx = SimBox::ortho(
1007 array![2.0, 3.0, 4.0],
1008 array![0.0, 0.0, 0.0],
1009 [true, true, true],
1010 )
1011 .expect("invalid box lengths");
1012 assert_close(bx.volume(), 24.0);
1013 }
1014
1015 #[test]
1016 fn test_wrap_single_and_multi() {
1017 let bx = SimBox::cube(2.0, array![0.0, 0.0, 0.0], [true, true, true])
1018 .expect("invalid box length");
1019 let pts = array![[10.0, -5.0, -5.0], [0.0, 0.5, 0.0]];
1020 let wrapped = bx.wrap(pts.view());
1021 assert_close(wrapped[[0, 0]], 0.0);
1022 assert_close(wrapped[[0, 1]], 1.0);
1023 assert_close(wrapped[[0, 2]], 1.0);
1024 assert_close(wrapped[[1, 0]], 0.0);
1025 assert_close(wrapped[[1, 1]], 0.5);
1026 assert_close(wrapped[[1, 2]], 0.0);
1027 }
1028
1029 #[test]
1030 fn test_fractional_and_cartesian() {
1031 let bx = SimBox::cube(2.0, array![0.0, 0.0, 0.0], [true, true, true])
1032 .expect("invalid box length");
1033 let p = array![-1.0, -1.0, -1.0];
1034 let frac = bx.make_fractional(p.view());
1035 assert_close(frac[0], 0.5);
1036 assert_close(frac[1], 0.5);
1037 assert_close(frac[2], 0.5);
1038 let cart = bx.make_cartesian(frac.view());
1039 assert_close(cart[0], 1.0);
1040 assert_close(cart[1], 1.0);
1041 assert_close(cart[2], 1.0);
1042 }
1043
1044 #[test]
1045 fn test_to_frac_to_cart_roundtrip() {
1046 let bx = SimBox::ortho(
1047 array![2.0, 3.0, 4.0],
1048 array![1.0, 2.0, 3.0],
1049 [true, true, true],
1050 )
1051 .expect("invalid box lengths");
1052 let pts = array![[1.0, 2.0, 3.0], [2.0, 3.0, 4.0]];
1053 let frac = bx.to_frac(pts.view());
1054 let cart = bx.to_cart(frac.view());
1055 for i in 0..pts.nrows() {
1056 for j in 0..3 {
1057 assert_close(pts[[i, j]], cart[[i, j]]);
1058 }
1059 }
1060 }
1061
1062 #[test]
1063 fn test_shortest_vector_and_distance() {
1064 let bx = SimBox::cube(2.0, array![0.0, 0.0, 0.0], [true, true, true])
1065 .expect("invalid box length");
1066 let a = array![0.1, 0.0, 0.0];
1067 let b = array![1.9, 0.0, 0.0];
1068 let dr = bx.shortest_vector(a.view(), b.view());
1069 assert_close(dr[0], -0.2);
1070 assert_close(dr[1], 0.0);
1071 assert_close(dr[2], 0.0);
1072 let d2 = bx.calc_distance2(a.view(), b.view());
1073 assert_close(d2, 0.04);
1074 }
1075
1076 #[test]
1077 fn test_isin_point_non_pbc() {
1078 let bx = SimBox::cube(2.0, array![0.0, 0.0, 0.0], [false, false, false])
1079 .expect("invalid box length");
1080 let pts = array![[0.5, 0.5, 0.5], [-0.1, 0.5, 0.5], [2.1, 0.5, 0.5]];
1081 let mask = bx.isin(pts.view());
1082 assert!(mask[0]);
1083 assert!(!mask[1]);
1084 assert!(!mask[2]);
1085 }
1086
1087 #[test]
1088 fn test_isin_mask() {
1089 let bx = SimBox::cube(2.0, array![0.0, 0.0, 0.0], [true, true, true])
1090 .expect("invalid box length");
1091 let pts = array![[0.1, 0.1, 0.1], [2.1, 0.0, 0.0], [-0.1, 0.0, 0.0]];
1092 let mask = bx.isin(pts.view());
1093 assert!(mask[0]);
1094 assert!(!mask[1]);
1095 assert!(!mask[2]);
1096 }
1097
1098 #[test]
1099 fn test_simbox_free_basic() {
1100 let pts = array![[1.0 as F, 2.0, 3.0], [4.0, 5.0, 6.0]];
1101 let bx = SimBox::free(pts.view(), 1.0).unwrap();
1102 assert_eq!(bx.pbc(), [false, false, false]);
1103 let o = bx.origin_view();
1105 assert!((o[0] - 0.0).abs() < 1e-5);
1106 assert!((o[1] - 1.0).abs() < 1e-5);
1107 assert!((o[2] - 2.0).abs() < 1e-5);
1108 let l = bx.lengths();
1110 assert!((l[0] - 5.0).abs() < 1e-5);
1111 assert!((l[1] - 5.0).abs() < 1e-5);
1112 assert!((l[2] - 5.0).abs() < 1e-5);
1113 }
1114
1115 #[test]
1116 fn test_simbox_free_single_point() {
1117 let pts = array![[1.0 as F, 2.0, 3.0]];
1118 let bx = SimBox::free(pts.view(), 2.0).unwrap();
1119 assert_eq!(bx.pbc(), [false, false, false]);
1120 let l = bx.lengths();
1122 assert!(l[0] >= 2.0);
1123 assert!(l[1] >= 2.0);
1124 assert!(l[2] >= 2.0);
1125 }
1126
1127 #[test]
1128 fn test_simbox_free_empty() {
1129 use ndarray::Array2;
1130 let pts = Array2::<F>::zeros((0, 3));
1131 let bx = SimBox::free(pts.view(), 1.0).unwrap();
1132 assert_eq!(bx.pbc(), [false, false, false]);
1133 }
1134
1135 #[test]
1136 fn test_simbox_pbc_accessor() {
1137 let bx = SimBox::cube(1.0, array![0.0 as F, 0.0, 0.0], [true, false, true]).unwrap();
1138 assert_eq!(bx.pbc(), [true, false, true]);
1139 }
1140
1141 #[test]
1142 fn free_columns_matches_free_bitwise() {
1143 let pts = array![[1.0 as F, 2.0, 3.0], [4.0, -5.0, 6.0], [-2.5, 5.5, 0.25]];
1144 let xs = vec![1.0 as F, 4.0, -2.5];
1145 let ys = vec![2.0 as F, -5.0, 5.5];
1146 let zs = vec![3.0 as F, 6.0, 0.25];
1147 let a = SimBox::free(pts.view(), 1.5).unwrap();
1148 let b = SimBox::free_columns(&xs, &ys, &zs, 1.5).unwrap();
1149
1150 let (oa, ob) = (a.origin_view(), b.origin_view());
1151 let (ha, hb) = (a.h_view(), b.h_view());
1152 for d in 0..3 {
1153 assert_eq!(oa[d], ob[d], "origin bitwise");
1154 }
1155 for i in 0..3 {
1156 for j in 0..3 {
1157 assert_eq!(ha[[i, j]], hb[[i, j]], "H bitwise");
1158 }
1159 }
1160 assert_eq!(a.pbc(), b.pbc());
1161 }
1162
1163 #[test]
1164 fn mic_matches_the_borrowed_kernel_on_both_box_kinds() {
1165 let ortho = SimBox::ortho(
1168 array![10.0, 11.0, 12.0],
1169 array![0.5, -1.0, 2.0],
1170 [true, false, true],
1171 )
1172 .unwrap();
1173 let tri = SimBox::new(
1174 SimBox::matrix_from_lengths_angles([10.0, 11.0, 12.0], [70.0, 80.0, 65.0]).unwrap(),
1175 array![0.3, -0.2, 1.1],
1176 [true, true, false],
1177 )
1178 .unwrap();
1179
1180 for bx in [&ortho, &tri] {
1181 let mic = bx.mic();
1182 for (a, b) in [
1183 ([0.0 as F, 0.0, 0.0], [9.0 as F, 1.0, -3.0]),
1184 ([1.5, -2.5, 3.5], [-7.25, 8.125, 0.0625]),
1185 ([4.0, 4.0, 4.0], [4.0, 4.0, 4.0]),
1186 ] {
1187 let want = bx.shortest_vector_impl(a, b);
1188 let got = mic.apply([b[0] - a[0], b[1] - a[1], b[2] - a[2]]);
1189 for d in 0..3 {
1190 assert_eq!(got[d], want[d], "component {d} for {a:?} -> {b:?}");
1191 }
1192 }
1193 }
1194 }
1195
1196 #[test]
1197 fn a_box_with_no_periodic_axis_simplifies_to_free() {
1198 let free = SimBox::cube(10.0, array![0.0, 0.0, 0.0], [false; 3]).unwrap();
1199 assert_eq!(free.mic().simplified(), Mic::Free);
1200 let d = [123.0 as F, -456.0, 789.0];
1201 assert_eq!(Mic::Free.apply(d), d);
1202
1203 let periodic = SimBox::cube(10.0, array![0.0, 0.0, 0.0], [false, true, false]).unwrap();
1204 assert_ne!(periodic.mic().simplified(), Mic::Free);
1205 }
1206
1207 #[test]
1208 fn triclinic_mic_matches_the_allocating_matrix_form() {
1209 let bx = SimBox::new(
1213 SimBox::matrix_from_lengths_angles([10.0, 11.0, 12.0], [70.0, 80.0, 65.0]).unwrap(),
1214 array![0.3, -0.2, 1.1],
1215 [true, true, false],
1216 )
1217 .unwrap();
1218
1219 let pts = [
1220 ([0.0 as F, 0.0, 0.0], [9.0 as F, 1.0, -3.0]),
1221 ([1.5, -2.5, 3.5], [-7.25, 8.125, 0.0625]),
1222 ([4.0, 4.0, 4.0], [4.0, 4.0, 4.0]),
1223 ];
1224 for (a, b) in pts {
1225 let dr_cart = array![b[0] - a[0], b[1] - a[1], b[2] - a[2]];
1226 let mut dr_frac = bx.inv.dot(&dr_cart);
1227 for d in 0..3 {
1228 if bx.pbc[d] {
1229 dr_frac[d] -= dr_frac[d].round();
1230 }
1231 }
1232 let want = bx.h.dot(&dr_frac);
1233 let got = bx.shortest_vector_impl(a, b);
1234 for d in 0..3 {
1235 assert_eq!(got[d], want[d], "component {d} for {a:?} -> {b:?}");
1236 }
1237 }
1238 }
1239}