1use std::collections::HashSet;
4
5use anyhow::{self, ensure, format_err};
6use itertools::{self, Itertools};
7use log;
8use nalgebra::Vector3;
9
10use crate::auxiliary::geometry;
11use crate::rotsym::RotationalSymmetry;
12use crate::symmetry::symmetry_element::SIG;
13use crate::symmetry::symmetry_element_order::{ElementOrder, ORDER_1, ORDER_2};
14
15use super::{PreSymmetry, Symmetry};
16
17impl Symmetry {
18 fn search_c2_spherical(
33 &mut self,
34 presym: &PreSymmetry,
35 tr: bool,
36 ) -> Result<u32, anyhow::Error> {
37 ensure!(
38 matches!(presym.rotational_symmetry, RotationalSymmetry::Spherical),
39 "Unexpected rotational symmetry -- expected: {}, actual: {}",
40 RotationalSymmetry::Spherical,
41 presym.rotational_symmetry
42 );
43
44 let start_guard: u32 = 30;
45 let stable_c2_ratio: f64 = 0.5;
46 let c2_termination_counts = HashSet::from([3, 9, 15]);
47
48 let mut count_c2: u32 = 0;
49 let mut count_c2_stable: u32 = 0;
50 let mut n_pairs: u32 = 0;
51
52 let sea_groups = &presym.sea_groups;
53 for sea_group in sea_groups.iter() {
54 if sea_group.len() < 2 {
55 continue;
56 }
57 for atom2s in sea_group.iter().combinations(2) {
58 n_pairs += 1;
59 let atom_i_pos = atom2s[0].coordinates;
60 let atom_j_pos = atom2s[1].coordinates;
61
62 if let Some(proper_kind) = presym.check_proper(&ORDER_2, &atom_i_pos.coords, tr)
64 && self.add_proper(
65 ORDER_2,
66 &atom_i_pos.coords,
67 false,
68 presym.recentred_molecule.threshold,
69 proper_kind.contains_time_reversal(),
70 )
71 {
72 count_c2 += 1;
73 count_c2_stable = 0;
74 }
75
76 let midvec = 0.5 * (atom_i_pos.coords + atom_j_pos.coords);
78 if let Some(proper_kind) = presym.check_proper(&ORDER_2, &midvec, tr)
79 && midvec.norm() > presym.recentred_molecule.threshold
80 && self.add_proper(
81 ORDER_2,
82 &midvec,
83 false,
84 presym.recentred_molecule.threshold,
85 proper_kind.contains_time_reversal(),
86 )
87 {
88 count_c2 += 1;
89 count_c2_stable = 0;
90 }
91
92 if (f64::from(count_c2_stable)) / (f64::from(n_pairs)) > stable_c2_ratio
94 && n_pairs > start_guard
95 && c2_termination_counts.contains(&count_c2)
96 {
97 break;
98 }
99 count_c2_stable += 1;
100 }
101
102 if c2_termination_counts.contains(&count_c2) {
103 break;
104 }
105 }
106 Ok(count_c2)
107 }
108
109 #[allow(clippy::too_many_lines)]
119 pub(super) fn analyse_spherical(
120 &mut self,
121 presym: &PreSymmetry,
122 tr: bool,
123 ) -> Result<(), anyhow::Error> {
124 ensure!(
125 matches!(presym.rotational_symmetry, RotationalSymmetry::Spherical),
126 "Unexpected rotational symmetry -- expected: {}, actual: {}",
127 RotationalSymmetry::Spherical,
128 presym.rotational_symmetry
129 );
130 if presym.recentred_molecule.atoms.len() == 1 {
131 self.set_group_name("O(3)".to_owned());
132 self.add_proper(
133 ElementOrder::Inf,
134 &Vector3::z(),
135 true,
136 presym.recentred_molecule.threshold,
137 false,
138 );
139 self.add_proper(
140 ElementOrder::Inf,
141 &Vector3::y(),
142 true,
143 presym.recentred_molecule.threshold,
144 false,
145 );
146 self.add_proper(
147 ElementOrder::Inf,
148 &Vector3::x(),
149 true,
150 presym.recentred_molecule.threshold,
151 false,
152 );
153 self.add_improper(
154 ElementOrder::Int(2),
155 &Vector3::z(),
156 true,
157 SIG,
158 None,
159 presym.recentred_molecule.threshold,
160 false,
161 );
162 return Ok(());
163 }
164
165 let count_c2 = self.search_c2_spherical(presym, tr)?;
167 log::debug!("Located {} C2 axes.", count_c2);
168 ensure!(
169 HashSet::from([3, 9, 15]).contains(&count_c2),
170 "Unexpected number of C2 axes -- expected: 3 or 9 or 15, actual: {count_c2}"
171 );
172
173 match count_c2 {
175 3 => {
176 log::debug!("Tetrahedral family.");
178 if let Some(improper_kind) =
179 presym.check_improper(&ORDER_2, &Vector3::z(), &SIG, tr)
180 {
181 log::debug!("Located an inversion centre.");
183 self.set_group_name("Th".to_owned());
184 ensure!(
185 self.add_improper(
186 ORDER_2,
187 &Vector3::z(),
188 false,
189 SIG,
190 None,
191 presym.recentred_molecule.threshold,
192 improper_kind.contains_time_reversal()
193 ),
194 "Expected improper element not added."
195 );
196 ensure!(
197 self.add_improper(
198 ORDER_2,
199 &Vector3::z(),
200 true,
201 SIG,
202 None,
203 presym.recentred_molecule.threshold,
204 improper_kind.contains_time_reversal()
205 ),
206 "Expected improper generator not added."
207 );
208 } else {
209 let mut c2s = self
210 .get_proper(&ORDER_2)
211 .ok_or_else(|| format_err!("No C2 elements found."))?
212 .into_iter()
213 .take(2)
214 .cloned()
215 .collect_vec()
216 .into_iter();
217 let normal = c2s
218 .next()
219 .ok_or_else(|| format_err!("No C2 elements found."))?
220 .raw_axis()
221 + c2s
222 .next()
223 .ok_or_else(|| {
224 format_err!("Two C2 elements expected, but only one found.")
225 })?
226 .raw_axis();
227 if presym.check_improper(&ORDER_1, &normal, &SIG, tr).is_some() {
228 log::debug!("Located σd.");
230 self.set_group_name("Td".to_owned());
231 let sigmad_normals = {
232 let mut axes = vec![];
233 for c2s in self
234 .get_proper(&ORDER_2)
235 .ok_or_else(|| format_err!("No C2 elements found."))?
236 .iter()
237 .combinations(2)
238 {
239 let axis_p = c2s[0].raw_axis() + c2s[1].raw_axis();
240 let p_improper_check =
241 presym.check_improper(&ORDER_1, &axis_p, &SIG, tr);
242 ensure!(
243 p_improper_check.is_some(),
244 "Expected mirror plane perpendicular to {axis_p} not found."
245 );
246 axes.push((
247 axis_p,
248 p_improper_check
249 .ok_or_else(|| format_err!("Expected mirror plane perpendicular to {axis_p} not found."))?
250 .contains_time_reversal(),
251 ));
252
253 let axis_m = c2s[0].raw_axis() - c2s[1].raw_axis();
254 let m_improper_check =
255 presym.check_improper(&ORDER_1, &axis_m, &SIG, tr);
256 ensure!(
257 m_improper_check.is_some(),
258 "Expected mirror plane perpendicular to {axis_m} not found."
259 );
260 axes.push((
261 axis_m,
262 m_improper_check
263 .ok_or_else(|| format_err!("Expected mirror plane perpendicular to {axis_m} not found."))?
264 .contains_time_reversal(),
265 ));
266 }
267 axes
268 };
269 let sigmad_generator_normal = sigmad_normals[0];
270 for (axis, axis_tr) in sigmad_normals {
271 ensure!(
272 self.add_improper(
273 ORDER_1,
274 &axis,
275 false,
276 SIG,
277 Some("d".to_owned()),
278 presym.recentred_molecule.threshold,
279 axis_tr
280 ),
281 "Expected improper element not added."
282 );
283 }
284 ensure!(
285 self.add_improper(
286 ORDER_1,
287 &sigmad_generator_normal.0,
288 true,
289 SIG,
290 Some("d".to_owned()),
291 presym.recentred_molecule.threshold,
292 sigmad_generator_normal.1
293 ),
294 "Expected improper generator not added."
295 );
296 } else {
297 self.set_group_name("T".to_owned());
299 }
300 }
301 } 9 => {
303 log::debug!("Octahedral family.");
305 if let Some(improper_kind) =
306 presym.check_improper(&ORDER_2, &Vector3::z(), &SIG, tr)
307 {
308 log::debug!("Located an inversion centre.");
310 self.set_group_name("Oh".to_owned());
311 ensure!(
312 self.add_improper(
313 ORDER_2,
314 &Vector3::z(),
315 false,
316 SIG,
317 None,
318 presym.recentred_molecule.threshold,
319 improper_kind.contains_time_reversal()
320 ),
321 "Expected improper element not added."
322 );
323 ensure!(
324 self.add_improper(
325 ORDER_2,
326 &Vector3::z(),
327 true,
328 SIG,
329 None,
330 presym.recentred_molecule.threshold,
331 improper_kind.contains_time_reversal()
332 ),
333 "Expected improper generator not added."
334 );
335 } else {
336 self.set_group_name("O".to_owned());
338 }
339 } 15 => {
341 log::debug!("Icosahedral family.");
343 if let Some(improper_kind) =
344 presym.check_improper(&ORDER_2, &Vector3::z(), &SIG, tr)
345 {
346 log::debug!("Located an inversion centre.");
348 self.set_group_name("Ih".to_owned());
349 ensure!(
350 self.add_improper(
351 ORDER_2,
352 &Vector3::z(),
353 false,
354 SIG,
355 None,
356 presym.recentred_molecule.threshold,
357 improper_kind.contains_time_reversal()
358 ),
359 "Expected improper element not added."
360 );
361 ensure!(
362 self.add_improper(
363 ORDER_2,
364 &Vector3::z(),
365 true,
366 SIG,
367 None,
368 presym.recentred_molecule.threshold,
369 improper_kind.contains_time_reversal()
370 ),
371 "Expected improper generator not added."
372 );
373 } else {
374 self.set_group_name("I".to_owned());
376 }
377 } _ => return Err(format_err!("Invalid number of C2 axes.")),
379 } let mut count_c3 = 0;
383 let mut found_consistent_c3 = false;
384 let sea_groups = &presym.sea_groups;
385 let order_3 = ElementOrder::Int(3);
386 for sea_group in sea_groups.iter() {
387 if sea_group.len() < 3 {
388 continue;
389 }
390 if found_consistent_c3 {
391 break;
392 };
393 for atom3s in sea_group.iter().combinations(3) {
394 let atom_i = atom3s[0];
395 let atom_j = atom3s[1];
396 let atom_k = atom3s[2];
397 if !geometry::check_regular_polygon(&[atom_i, atom_j, atom_k]) {
398 continue;
399 }
400 let vec_ij = atom_j.coordinates - atom_i.coordinates;
401 let vec_ik = atom_k.coordinates - atom_i.coordinates;
402 let vec_normal = vec_ij.cross(&vec_ik);
403 ensure!(
404 vec_normal.norm() > presym.recentred_molecule.threshold,
405 "Unexpected zero-norm vector."
406 );
407 if let Some(proper_kind) = presym.check_proper(&order_3, &vec_normal, tr) {
408 count_c3 += i32::from(self.add_proper(
409 order_3,
410 &vec_normal,
411 false,
412 presym.recentred_molecule.threshold,
413 proper_kind.contains_time_reversal(),
414 ));
415 }
416 if count_c2 == 3 && count_c3 == 4 {
417 found_consistent_c3 = true;
419 break;
420 }
421 if count_c2 == 9 && count_c3 == 4 {
422 found_consistent_c3 = true;
424 break;
425 }
426 if count_c2 == 15 && count_c3 == 10 {
427 found_consistent_c3 = true;
429 break;
430 }
431 }
432 }
433 ensure!(
434 found_consistent_c3,
435 "Unexpected number of C3 axes: {count_c3}."
436 );
437
438 if count_c3 == 4 {
439 let c3s = self
441 .get_proper(&order_3)
442 .ok_or_else(|| format_err!(" No C3 elements found."))?
443 .into_iter()
444 .cloned()
445 .collect_vec();
446 for c3 in &c3s {
447 self.add_proper(
448 order_3,
449 c3.raw_axis(),
450 true,
451 presym.recentred_molecule.threshold,
452 c3.contains_time_reversal(),
453 );
454 }
455 }
456
457 if count_c2 == 9 {
459 let mut count_c4 = 0;
460 let mut found_consistent_c4 = false;
461 let sea_groups = &presym.sea_groups;
462 let order_4 = ElementOrder::Int(4);
463 for sea_group in sea_groups.iter() {
464 if sea_group.len() < 4 {
465 continue;
466 }
467 if found_consistent_c4 {
468 break;
469 };
470 for atom4s in sea_group.iter().combinations(4) {
471 let atom_i = atom4s[0];
472 let atom_j = atom4s[1];
473 let atom_k = atom4s[2];
474 let atom_l = atom4s[3];
475 if !geometry::check_regular_polygon(&[atom_i, atom_j, atom_k, atom_l]) {
476 continue;
477 }
478 let vec_ij = atom_j.coordinates - atom_i.coordinates;
479 let vec_ik = atom_k.coordinates - atom_i.coordinates;
480 let vec_normal = vec_ij.cross(&vec_ik);
481 ensure!(
482 vec_normal.norm() > presym.recentred_molecule.threshold,
483 "Unexpected zero-norm vector."
484 );
485 if let Some(proper_kind) = presym.check_proper(&order_4, &vec_normal, tr) {
486 count_c4 += i32::from(self.add_proper(
487 order_4,
488 &vec_normal,
489 false,
490 presym.recentred_molecule.threshold,
491 proper_kind.contains_time_reversal(),
492 ));
493 }
494 if count_c4 == 3 {
495 found_consistent_c4 = true;
496 break;
497 }
498 }
499 }
500 ensure!(
501 found_consistent_c4,
502 "Unexpected number of C4 axes: {count_c4}."
503 );
504
505 let c4 = *self
507 .get_proper(&order_4)
508 .ok_or_else(|| format_err!(" No C4 elements found."))?
509 .iter()
510 .next()
511 .ok_or_else(|| format_err!("Expected C4 not found."))?;
512 let c4_axis = *c4.raw_axis();
513 self.add_proper(
514 order_4,
515 &c4_axis,
516 true,
517 presym.recentred_molecule.threshold,
518 c4.contains_time_reversal(),
519 );
520 } if count_c2 == 15 {
524 let mut count_c5 = 0;
525 let mut found_consistent_c5 = false;
526 let sea_groups = &presym.sea_groups;
527 let order_5 = ElementOrder::Int(5);
528 for sea_group in sea_groups.iter() {
529 if sea_group.len() < 5 {
530 continue;
531 }
532 if found_consistent_c5 {
533 break;
534 };
535 for atom5s in sea_group.iter().combinations(5) {
536 let atom_i = atom5s[0];
537 let atom_j = atom5s[1];
538 let atom_k = atom5s[2];
539 let atom_l = atom5s[3];
540 let atom_m = atom5s[4];
541 if !geometry::check_regular_polygon(&[atom_i, atom_j, atom_k, atom_l, atom_m]) {
542 continue;
543 }
544 let vec_ij = atom_j.coordinates - atom_i.coordinates;
545 let vec_ik = atom_k.coordinates - atom_i.coordinates;
546 let vec_normal = vec_ij.cross(&vec_ik);
547 ensure!(
548 vec_normal.norm() > presym.recentred_molecule.threshold,
549 "Unexpected zero-norm vector."
550 );
551 if let Some(proper_kind) = presym.check_proper(&order_5, &vec_normal, tr) {
552 count_c5 += i32::from(self.add_proper(
553 order_5,
554 &vec_normal,
555 false,
556 presym.recentred_molecule.threshold,
557 proper_kind.contains_time_reversal(),
558 ));
559 self.add_proper(
560 order_5,
561 &vec_normal,
562 true,
563 presym.recentred_molecule.threshold,
564 proper_kind.contains_time_reversal(),
565 );
566 }
567 if count_c5 == 6 {
568 found_consistent_c5 = true;
569 break;
570 }
571 }
572 }
573 ensure!(
574 found_consistent_c5,
575 "Unexpected number of C5 axes: {count_c5}."
576 );
577 } if *self
581 .group_name
582 .as_ref()
583 .ok_or_else(|| format_err!("No point groups found."))?
584 == "Td"
585 {
586 let order_4 = ElementOrder::Int(4);
588 let improper_s4_axes: Vec<(Vector3<f64>, bool)> = {
589 self.get_proper(&ORDER_2)
590 .ok_or_else(|| format_err!("Expected C2 elements not found."))?
591 .iter()
592 .filter_map(|c2_ele| {
593 presym
594 .check_improper(&order_4, c2_ele.raw_axis(), &SIG, tr)
595 .map(|improper_kind| {
596 (*c2_ele.raw_axis(), improper_kind.contains_time_reversal())
597 })
598 })
599 .collect()
600 };
601 let mut count_s4 = 0;
602 for (s4_axis, s4_axis_tr) in improper_s4_axes {
603 count_s4 += i32::from(self.add_improper(
604 order_4,
605 &s4_axis,
606 false,
607 SIG,
608 None,
609 presym.recentred_molecule.threshold,
610 s4_axis_tr,
611 ));
612 }
613 ensure!(count_s4 == 3, "Unexpected number of S4 axes: {count_s4}.");
614 }
615 else if *self
617 .group_name
618 .as_ref()
619 .ok_or_else(|| format_err!("No point groups found."))?
620 == "Th"
621 {
622 let sigmah_normals: Vec<(Vector3<f64>, bool)> = {
624 self.get_proper(&ORDER_2)
625 .ok_or_else(|| format_err!("Expected C2 elements not found."))?
626 .iter()
627 .filter_map(|c2_ele| {
628 presym
629 .check_improper(&ORDER_1, c2_ele.raw_axis(), &SIG, tr)
630 .map(|improper_kind| {
631 (*c2_ele.raw_axis(), improper_kind.contains_time_reversal())
632 })
633 })
634 .collect()
635 };
636 let mut count_sigmah = 0;
637 for (sigmah_normal, sigmah_normal_tr) in sigmah_normals {
638 count_sigmah += i32::from(self.add_improper(
639 ORDER_1,
640 &sigmah_normal,
641 false,
642 SIG,
643 Some("h".to_owned()),
644 presym.recentred_molecule.threshold,
645 sigmah_normal_tr,
646 ));
647 }
648 ensure!(
649 count_sigmah == 3,
650 "Unexpected number of σh mirror planes: {count_sigmah}."
651 );
652
653 let order_6 = ElementOrder::Int(6);
655 let s6_axes: Vec<(Vector3<f64>, bool)> = {
656 self.get_proper(&order_3)
657 .ok_or_else(|| format_err!("Expected C3 elements not found."))?
658 .iter()
659 .filter_map(|c3_ele| {
660 presym
661 .check_improper(&order_6, c3_ele.raw_axis(), &SIG, tr)
662 .map(|improper_kind| {
663 (*c3_ele.raw_axis(), improper_kind.contains_time_reversal())
664 })
665 })
666 .collect()
667 };
668 let mut count_s6 = 0;
669 for (s6_axis, s6_axis_tr) in s6_axes {
670 count_s6 += i32::from(self.add_improper(
671 order_6,
672 &s6_axis,
673 false,
674 SIG,
675 None,
676 presym.recentred_molecule.threshold,
677 s6_axis_tr,
678 ));
679 }
680 ensure!(count_s6 == 4, "Unexpected number of S6 axes: {count_s6}.");
681 }
682 else if *self
684 .group_name
685 .as_ref()
686 .ok_or_else(|| format_err!("No point groups found."))?
687 == "Oh"
688 {
689 let order_4 = ElementOrder::Int(4);
691 let s4_axes: Vec<(Vector3<f64>, bool)> = {
692 self.get_proper(&ORDER_2)
693 .ok_or_else(|| format_err!("Expected C2 elements not found."))?
694 .iter()
695 .filter_map(|c2_ele| {
696 presym
697 .check_improper(&order_4, c2_ele.raw_axis(), &SIG, tr)
698 .map(|improper_kind| {
699 (*c2_ele.raw_axis(), improper_kind.contains_time_reversal())
700 })
701 })
702 .collect()
703 };
704 let mut count_s4 = 0;
705 for (s4_axis, s4_axis_tr) in &s4_axes {
706 count_s4 += i32::from(self.add_improper(
707 order_4,
708 s4_axis,
709 false,
710 SIG,
711 None,
712 presym.recentred_molecule.threshold,
713 *s4_axis_tr,
714 ));
715 }
716 ensure!(count_s4 == 3, "Unexpected number of S4 axes: {count_s4}.");
717
718 let sigmah_axes: Vec<(Vector3<f64>, bool)> = {
719 s4_axes
720 .iter()
721 .filter_map(|(sigmah_axis, _)| {
722 presym.check_improper(&ORDER_1, sigmah_axis, &SIG, tr).map(
723 |improper_kind| (*sigmah_axis, improper_kind.contains_time_reversal()),
724 )
725 })
726 .collect()
727 };
728 let mut count_sigmah = 0;
729 for (sigmah_axis, sigmah_axis_tr) in sigmah_axes {
730 count_sigmah += i32::from(self.add_improper(
731 ORDER_1,
732 &sigmah_axis,
733 false,
734 SIG,
735 Some("h".to_owned()),
736 presym.recentred_molecule.threshold,
737 sigmah_axis_tr,
738 ));
739 }
740 ensure!(
741 count_sigmah == 3,
742 "Unexpected number of σh mirror planes: {count_sigmah}."
743 );
744
745 let sigmad_normals: Vec<(Vector3<f64>, bool)> = {
747 self.get_proper(&ORDER_2)
748 .ok_or_else(|| format_err!("Expected C2 elements not found."))?
749 .iter()
750 .filter_map(|c2_ele| {
751 if presym
752 .check_improper(&order_4, c2_ele.raw_axis(), &SIG, tr)
753 .is_none()
754 {
755 presym
756 .check_improper(&ORDER_1, c2_ele.raw_axis(), &SIG, tr)
757 .map(|improper_kind| {
758 (*c2_ele.raw_axis(), improper_kind.contains_time_reversal())
759 })
760 } else {
761 None
762 }
763 })
764 .collect()
765 };
766 let mut count_sigmad = 0;
767 for (sigmad_normal, sigmad_normal_tr) in sigmad_normals {
768 count_sigmad += i32::from(self.add_improper(
769 ORDER_1,
770 &sigmad_normal,
771 false,
772 SIG,
773 Some("d".to_owned()),
774 presym.recentred_molecule.threshold,
775 sigmad_normal_tr,
776 ));
777 }
778 ensure!(
779 count_sigmad == 6,
780 "Unexpected number of σd mirror planes: {count_sigmad}."
781 );
782
783 let order_6 = ElementOrder::Int(6);
785 let s6_axes: Vec<(Vector3<f64>, bool)> = {
786 self.get_proper(&order_3)
787 .ok_or_else(|| format_err!("Expected C3 elements not found."))?
788 .iter()
789 .filter_map(|c3_ele| {
790 presym
791 .check_improper(&order_6, c3_ele.raw_axis(), &SIG, tr)
792 .map(|improper_kind| {
793 (*c3_ele.raw_axis(), improper_kind.contains_time_reversal())
794 })
795 })
796 .collect()
797 };
798 let mut count_s6 = 0;
799 for (s6_axis, s6_axis_tr) in s6_axes {
800 count_s6 += i32::from(self.add_improper(
801 order_6,
802 &s6_axis,
803 false,
804 SIG,
805 None,
806 presym.recentred_molecule.threshold,
807 s6_axis_tr,
808 ));
809 }
810 ensure!(count_s6 == 4, "Unexpected number of S6 axes: {count_s6}.");
811 }
812 else if *self
814 .group_name
815 .as_ref()
816 .ok_or_else(|| format_err!("No point groups found."))?
817 == "Ih"
818 {
819 let order_5 = ElementOrder::Int(5);
821 let order_10 = ElementOrder::Int(10);
822 let s10_axes: Vec<(Vector3<f64>, bool)> = {
823 self.get_proper(&order_5)
824 .ok_or_else(|| format_err!("Expected C5 elements not found."))?
825 .iter()
826 .filter_map(|c5_ele| {
827 presym
828 .check_improper(&order_10, c5_ele.raw_axis(), &SIG, tr)
829 .map(|improper_kind| {
830 (*c5_ele.raw_axis(), improper_kind.contains_time_reversal())
831 })
832 })
833 .collect()
834 };
835 let mut count_s10 = 0;
836 for (s10_axis, s10_axis_tr) in s10_axes {
837 count_s10 += i32::from(self.add_improper(
838 order_10,
839 &s10_axis,
840 false,
841 SIG,
842 None,
843 presym.recentred_molecule.threshold,
844 s10_axis_tr,
845 ));
846 }
847 ensure!(
848 count_s10 == 6,
849 "Unexpected number of S10 axes: {count_s10}."
850 );
851
852 let order_6 = ElementOrder::Int(6);
854 let s6_axes: Vec<(Vector3<f64>, bool)> = {
855 self.get_proper(&order_3)
856 .ok_or_else(|| format_err!("Expected C3 elements not found."))?
857 .iter()
858 .filter_map(|c3_ele| {
859 presym
860 .check_improper(&order_6, c3_ele.raw_axis(), &SIG, tr)
861 .map(|improper_kind| {
862 (*c3_ele.raw_axis(), improper_kind.contains_time_reversal())
863 })
864 })
865 .collect()
866 };
867 let mut count_s6 = 0;
868 for (s6_axis, s6_axis_tr) in s6_axes {
869 count_s6 += i32::from(self.add_improper(
870 order_6,
871 &s6_axis,
872 false,
873 SIG,
874 None,
875 presym.recentred_molecule.threshold,
876 s6_axis_tr,
877 ));
878 }
879 ensure!(count_s6 == 10, "Unexpected number of S6 axes: {count_s6}.");
880
881 let sigma_normals: Vec<(Vector3<f64>, bool)> = {
883 self.get_proper(&ORDER_2)
884 .ok_or_else(|| format_err!("Expected C2 elements not found."))?
885 .iter()
886 .filter_map(|c2_ele| {
887 presym
888 .check_improper(&ORDER_1, c2_ele.raw_axis(), &SIG, tr)
889 .map(|improper_kind| {
890 (*c2_ele.raw_axis(), improper_kind.contains_time_reversal())
891 })
892 })
893 .collect()
894 };
895 let mut count_sigma = 0;
896 for (sigma_normal, sigma_normal_tr) in sigma_normals {
897 count_sigma += i32::from(self.add_improper(
898 ORDER_1,
899 &sigma_normal,
900 false,
901 SIG,
902 None,
903 presym.recentred_molecule.threshold,
904 sigma_normal_tr,
905 ));
906 }
907 ensure!(
908 count_sigma == 15,
909 "Unexpected number of σ mirror planes: {count_sigma}."
910 );
911 } Ok(())
914 }
915}