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