1use std::collections::hash_map::Entry::Vacant;
4use std::collections::{HashMap, HashSet};
5use std::error::Error;
6use std::fmt;
7
8use anyhow::{self, ensure, format_err};
9use derive_builder::Builder;
10use indexmap::IndexSet;
11use itertools::Itertools;
12use log;
13use nalgebra::{Point3, Vector3};
14use rayon::prelude::*;
15use serde::{Deserialize, Serialize};
16
17use crate::auxiliary::atom::Atom;
18use crate::auxiliary::geometry::{self, Transform};
19use crate::auxiliary::molecule::Molecule;
20use crate::rotsym::{self, RotationalSymmetry};
21use crate::symmetry::symmetry_element::symmetry_operation::{
22 SpecialSymmetryTransformation, SymmetryOperation, sort_operations,
23};
24use crate::symmetry::symmetry_element::{
25 AntiunitaryKind, ROT, SIG, SO3, SymmetryElement, SymmetryElementKind, TRROT, TRSIG,
26};
27use crate::symmetry::symmetry_element_order::{ElementOrder, ORDER_1, ORDER_2};
28use crate::symmetry::symmetry_symbols::deduce_sigma_symbol;
29
30#[cfg(test)]
31mod symmetry_core_tests;
32
33#[cfg(test)]
34mod symmetry_group_detection_tests;
35
36#[derive(Debug)]
37pub struct PointGroupDetectionError(pub String);
38
39impl fmt::Display for PointGroupDetectionError {
40 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
41 write!(f, "Point-group detection error: {}.", self.0)
42 }
43}
44
45impl Error for PointGroupDetectionError {}
46
47#[derive(Clone, Builder, Debug, Serialize, Deserialize)]
49pub struct PreSymmetry {
50 #[builder(setter(custom))]
52 pub original_molecule: Molecule,
53
54 #[builder(setter(custom))]
56 pub recentred_molecule: Molecule,
57
58 #[builder(setter(custom))]
60 pub moi_threshold: f64,
61
62 #[builder(setter(skip), default = "self.calc_rotational_symmetry()")]
65 pub rotational_symmetry: RotationalSymmetry,
66
67 #[builder(setter(skip), default = "self.calc_sea_groups()")]
69 pub sea_groups: Vec<Vec<Atom>>,
70
71 #[builder(setter(skip), default = "self.get_dist_threshold()")]
73 pub dist_threshold: f64,
74}
75
76impl PreSymmetryBuilder {
77 pub fn molecule(&mut self, molecule: &Molecule) -> &mut Self {
89 self.recentred_molecule = Some(molecule.recentre());
90 self.original_molecule = Some(molecule.clone());
91 self
92 }
93
94 pub fn moi_threshold(&mut self, thresh: f64) -> &mut Self {
103 if thresh >= f64::EPSILON {
104 self.moi_threshold = Some(thresh);
105 } else {
106 log::error!(
107 "Threshold value {} is invalid. Threshold must be at least the machine epsilon.",
108 thresh
109 );
110 self.moi_threshold = None;
111 }
112 self
113 }
114
115 fn calc_rotational_symmetry(&self) -> RotationalSymmetry {
117 let com = self
118 .recentred_molecule
119 .as_ref()
120 .expect("A molecule has not been set.")
121 .calc_com();
122 let inertia = self
123 .recentred_molecule
124 .as_ref()
125 .expect("A molecule has not been set.")
126 .calc_inertia_tensor(&com);
127 approx::assert_relative_eq!(
128 com,
129 Point3::origin(),
130 epsilon = self
131 .recentred_molecule
132 .as_ref()
133 .expect("A molecule has not been set.")
134 .threshold,
135 max_relative = self
136 .recentred_molecule
137 .as_ref()
138 .expect("A molecule has not been set.")
139 .threshold
140 );
141 rotsym::calc_rotational_symmetry(
142 &inertia,
143 self.moi_threshold.expect("MoI threshold has not been set."),
144 )
145 }
146
147 fn calc_sea_groups(&self) -> Vec<Vec<Atom>> {
149 self.recentred_molecule
150 .as_ref()
151 .expect("A molecule has not been set.")
152 .calc_sea_groups()
153 }
154
155 fn get_dist_threshold(&self) -> f64 {
157 self.recentred_molecule
158 .as_ref()
159 .expect("A molecule has not been set.")
160 .threshold
161 }
162}
163
164impl PreSymmetry {
165 #[must_use]
171 pub(crate) fn builder() -> PreSymmetryBuilder {
172 PreSymmetryBuilder::default()
173 }
174
175 #[allow(clippy::trivially_copy_pass_by_ref)]
193 fn check_proper(
194 &self,
195 order: &ElementOrder,
196 axis: &Vector3<f64>,
197 tr: bool,
198 ) -> Option<SymmetryElementKind> {
199 assert_ne!(
200 *order,
201 ElementOrder::Inf,
202 "This method does not work for infinite-order elements."
203 );
204 let angle = 2.0 * std::f64::consts::PI / order.to_float();
205 let rotated_mol = self.recentred_molecule.rotate(angle, axis);
206 if rotated_mol == self.recentred_molecule {
207 Some(ROT)
208 } else if tr {
209 let tr_rotated_mol = rotated_mol.reverse_time();
210 if tr_rotated_mol == self.recentred_molecule {
211 Some(TRROT)
212 } else {
213 None
214 }
215 } else {
216 None
217 }
218 }
219
220 #[allow(clippy::trivially_copy_pass_by_ref)]
241 fn check_improper(
242 &self,
243 order: &ElementOrder,
244 axis: &Vector3<f64>,
245 kind: &SymmetryElementKind,
246 tr: bool,
247 ) -> Option<SymmetryElementKind> {
248 assert_ne!(
249 *order,
250 ElementOrder::Inf,
251 "This method does not work for infinite-order elements."
252 );
253 let angle = 2.0 * std::f64::consts::PI / order.to_float();
254 let transformed_mol = self.recentred_molecule.improper_rotate(
255 angle,
256 axis,
257 &kind.to_tr(false).try_into().unwrap_or_else(|err| {
258 log::error!("Error detected: {err}.");
259 panic!("Error detected: {err}.")
260 }),
261 );
262 if transformed_mol == self.recentred_molecule {
263 Some(kind.to_tr(false))
264 } else if tr {
265 let tr_transformed_mol = transformed_mol.reverse_time();
266 if tr_transformed_mol == self.recentred_molecule {
267 Some(kind.to_tr(true))
268 } else {
269 None
270 }
271 } else {
272 None
273 }
274 }
275}
276
277#[derive(Builder, Clone, Debug, Serialize, Deserialize)]
279pub struct Symmetry {
280 #[builder(setter(skip, strip_option), default = "None")]
282 pub group_name: Option<String>,
283
284 #[builder(setter(skip), default = "HashMap::new()")]
292 pub(crate) elements:
293 HashMap<SymmetryElementKind, HashMap<ElementOrder, IndexSet<SymmetryElement>>>,
294
295 #[builder(setter(skip), default = "HashMap::new()")]
303 pub(crate) generators:
304 HashMap<SymmetryElementKind, HashMap<ElementOrder, IndexSet<SymmetryElement>>>,
305}
306
307impl Symmetry {
308 #[must_use]
314 fn builder() -> SymmetryBuilder {
315 SymmetryBuilder::default()
316 }
317
318 #[must_use]
320 pub fn new() -> Self {
321 Symmetry::builder()
322 .build()
323 .expect("Unable to construct a `Symmetry` structure.")
324 }
325
326 pub fn analyse(&mut self, presym: &PreSymmetry, tr: bool) -> Result<&mut Self, anyhow::Error> {
337 log::debug!("Rotational symmetry found: {}", presym.rotational_symmetry);
338
339 if tr {
340 log::debug!("Antiunitary symmetry generated by time reversal will be considered.");
341 };
342
343 let c1 = SymmetryElement::builder()
345 .threshold(presym.dist_threshold)
346 .proper_order(ORDER_1)
347 .proper_power(1)
348 .raw_axis(Vector3::new(0.0, 0.0, 1.0))
349 .kind(ROT)
350 .rotation_group(SO3)
351 .build()
352 .map_err(|err| format_err!(err))?;
353 self.add_proper(ORDER_1, c1.raw_axis(), false, presym.dist_threshold, false);
354
355 match &presym.rotational_symmetry {
357 RotationalSymmetry::Spherical => self.analyse_spherical(presym, tr)?,
358 RotationalSymmetry::ProlateLinear => self.analyse_linear(presym, tr)?,
359 RotationalSymmetry::OblatePlanar
360 | RotationalSymmetry::OblateNonPlanar
361 | RotationalSymmetry::ProlateNonLinear => self.analyse_symmetric(presym, tr)?,
362 RotationalSymmetry::AsymmetricPlanar | RotationalSymmetry::AsymmetricNonPlanar => {
363 self.analyse_asymmetric(presym, tr)?
364 }
365 }
366
367 if tr {
368 if self.get_elements(&TRROT).is_none()
369 && self.get_elements(&TRSIG).is_none()
370 && self.get_generators(&TRROT).is_none()
371 && self.get_generators(&TRSIG).is_none()
372 {
373 log::debug!(
374 "Antiunitary symmetry requested, but so far only non-time-reversed elements found."
375 );
376 if presym.recentred_molecule == presym.recentred_molecule.reverse_time() {
380 log::debug!("Time reversal is a symmetry element. This is a grey group.");
381 self.elements.insert(
383 TRROT,
384 self.get_elements(&ROT)
385 .ok_or_else(|| format_err!("No proper elements found."))?
386 .iter()
387 .map(|(order, proper_elements)| {
388 let tr_proper_elements = proper_elements
389 .iter()
390 .map(|proper_element| {
391 proper_element.to_tr(true)
392 })
396 .collect::<IndexSet<_>>();
397 (*order, tr_proper_elements)
398 })
399 .collect::<HashMap<_, _>>(),
400 );
401 log::debug!("Time-reversed copies of all proper elements added.");
402
403 self.add_proper(ORDER_1, c1.raw_axis(), true, presym.dist_threshold, true);
405
406 if self.get_elements(&SIG).is_some() {
408 self.elements.insert(
409 TRSIG,
410 self.get_elements(&SIG)
411 .ok_or_else(|| format_err!("No improper elements found."))?
412 .iter()
413 .map(|(order, improper_elements)| {
414 let tr_improper_elements = improper_elements
415 .iter()
416 .map(|improper_element| improper_element.to_tr(true))
417 .collect::<IndexSet<_>>();
418 (*order, tr_improper_elements)
419 })
420 .collect::<HashMap<_, _>>(),
421 );
422 log::debug!("Time-reversed copies of all improper elements added.");
423 }
424
425 let unitary_group = self
427 .group_name
428 .as_ref()
429 .expect("No point groups found.")
430 .clone();
431 self.set_group_name(format!("{unitary_group} + θ·{unitary_group}"));
432 } else {
433 log::debug!(
434 "Time reversal is not a symmetry element. This is an ordinary group."
435 );
436 }
437 } else {
438 log::debug!(
439 "Antiunitary symmetry requested and some non-time-reversed elements found."
440 );
441 log::debug!(
442 "Time reversal is not a symmetry element. This is a black-and-white group."
443 );
444 }
445 }
446 Ok(self)
447 }
448
449 fn add_proper(
464 &mut self,
465 order: ElementOrder,
466 axis: &Vector3<f64>,
467 generator: bool,
468 threshold: f64,
469 tr: bool,
470 ) -> bool {
471 let positive_axis = geometry::get_standard_positive_pole(axis, threshold).normalize();
472 let proper_kind = if tr { TRROT } else { ROT };
473 let element = SymmetryElement::builder()
474 .threshold(threshold)
475 .proper_order(order)
476 .proper_power(1)
477 .raw_axis(positive_axis)
478 .kind(proper_kind)
479 .rotation_group(SO3)
480 .generator(generator)
481 .build()
482 .expect("Unable to construct a proper element.");
483 let simplified_symbol = element.get_simplified_symbol();
484 let full_symbol = element.get_full_symbol();
485 let result = if generator {
486 if let Vacant(proper_generators) = self.generators.entry(proper_kind) {
487 proper_generators.insert(HashMap::from([(order, IndexSet::from([element]))]));
488 true
489 } else {
490 let proper_generators =
491 self.generators.get_mut(&proper_kind).unwrap_or_else(|| {
492 panic!(
493 "{} generators not found.",
494 if tr { "Time-reversed proper" } else { "Proper" }
495 )
496 });
497
498 if let Vacant(proper_generators_order) = proper_generators.entry(order) {
499 proper_generators_order.insert(IndexSet::from([element]));
500 true
501 } else {
502 proper_generators
503 .get_mut(&order)
504 .unwrap_or_else(|| {
505 panic!(
506 "Proper generators {}C{order} not found.",
507 if tr { "θ" } else { "" }
508 )
509 })
510 .insert(element)
511 }
512 }
513 } else if let Vacant(proper_elements) = self.elements.entry(proper_kind) {
514 proper_elements.insert(HashMap::from([(order, IndexSet::from([element]))]));
515 true
516 } else {
517 let proper_elements = self.elements.get_mut(&proper_kind).unwrap_or_else(|| {
518 panic!(
519 "{} elements not found.",
520 if tr { "Time-reversed proper" } else { "Proper" }
521 )
522 });
523
524 if let Vacant(e) = proper_elements.entry(order) {
525 e.insert(IndexSet::from([element]));
526 true
527 } else {
528 proper_elements
529 .get_mut(&order)
530 .unwrap_or_else(|| {
531 panic!(
532 "Proper elements {}C{order} not found.",
533 if tr { "θ" } else { "" }
534 )
535 })
536 .insert(element)
537 }
538 };
539
540 let dest_str = if generator {
541 "generator".to_owned()
542 } else {
543 "element".to_owned()
544 };
545 if result {
546 log::debug!(
547 "Proper rotation {} ({}): {} axis along ({:+.3}, {:+.3}, {:+.3}) added.",
548 dest_str,
549 simplified_symbol,
550 full_symbol,
551 positive_axis[0] + 0.0,
552 positive_axis[1] + 0.0,
553 positive_axis[2] + 0.0,
554 );
555 }
556 result
557 }
558
559 #[allow(clippy::too_many_lines, clippy::too_many_arguments)]
582 fn add_improper(
583 &mut self,
584 order: ElementOrder,
585 axis: &Vector3<f64>,
586 generator: bool,
587 kind: SymmetryElementKind,
588 sigma: Option<String>,
589 threshold: f64,
590 tr: bool,
591 ) -> bool {
592 let positive_axis = geometry::get_standard_positive_pole(axis, threshold).normalize();
593 let mirror_kind = if tr { TRSIG } else { SIG };
594 let element = if let Some(sigma_str) = sigma {
595 assert!(sigma_str == "d" || sigma_str == "v" || sigma_str == "h");
596 let mut sym_ele = SymmetryElement::builder()
597 .threshold(threshold)
598 .proper_order(order)
599 .proper_power(1)
600 .raw_axis(positive_axis)
601 .kind(kind.to_tr(tr))
602 .rotation_group(SO3)
603 .generator(generator)
604 .build()
605 .expect("Unable to construct an improper symmetry element.")
606 .convert_to_improper_kind(&mirror_kind, false);
607 if *sym_ele.raw_proper_order() == ElementOrder::Int(1) {
608 sym_ele.additional_subscript = sigma_str;
609 }
610 sym_ele
611 } else {
612 SymmetryElement::builder()
613 .threshold(threshold)
614 .proper_order(order)
615 .proper_power(1)
616 .raw_axis(positive_axis)
617 .kind(kind.to_tr(tr))
618 .rotation_group(SO3)
619 .generator(generator)
620 .build()
621 .expect("Unable to construct an improper symmetry element.")
622 .convert_to_improper_kind(&mirror_kind, false)
623 };
624 let order = *element.raw_proper_order();
625 let simplified_symbol = element.get_simplified_symbol();
626 let full_symbol = element.get_full_symbol();
627 let au = if tr {
628 Some(AntiunitaryKind::TimeReversal)
629 } else {
630 None
631 };
632 let is_o3_mirror_plane = element.is_o3_mirror_plane(au);
633 let is_o3_inversion_centre = element.is_o3_inversion_centre(au);
634 let improper_kind = if tr { TRSIG } else { SIG };
635 let result = if generator {
636 if let Vacant(improper_generators) = self.generators.entry(improper_kind) {
637 improper_generators.insert(HashMap::from([(order, IndexSet::from([element]))]));
638 true
639 } else {
640 let improper_generators =
641 self.generators.get_mut(&improper_kind).unwrap_or_else(|| {
642 panic!(
643 "{} generators not found.",
644 if tr {
645 "Time-reversed improper"
646 } else {
647 "Improper"
648 }
649 )
650 });
651
652 if let Vacant(e) = improper_generators.entry(order) {
653 e.insert(IndexSet::from([element]));
654 true
655 } else {
656 improper_generators
657 .get_mut(&order)
658 .unwrap_or_else(|| {
659 panic!(
660 "Improper generators {}S{order} not found.",
661 if tr { "θ" } else { "" }
662 )
663 })
664 .insert(element)
665 }
666 }
667 } else if let Vacant(improper_elements) = self.elements.entry(improper_kind) {
668 improper_elements.insert(HashMap::from([(order, IndexSet::from([element]))]));
669 true
670 } else {
671 let improper_elements = self.elements.get_mut(&improper_kind).unwrap_or_else(|| {
672 panic!(
673 "{} elements not found.",
674 if tr {
675 "Time-reversed improper"
676 } else {
677 "Improper"
678 }
679 )
680 });
681
682 if let Vacant(e) = improper_elements.entry(order) {
683 e.insert(IndexSet::from([element]));
684 true
685 } else {
686 improper_elements
687 .get_mut(&order)
688 .unwrap_or_else(|| {
689 panic!(
690 "Improper elements {}S{order} not found.",
691 if tr { "θ" } else { "" }
692 )
693 })
694 .insert(element)
695 }
696 };
697 let dest_str = if generator {
698 "generator".to_owned()
699 } else {
700 "element".to_owned()
701 };
702 if result {
703 if is_o3_mirror_plane {
704 log::debug!(
705 "Mirror plane {} ({}): {} axis along ({:+.3}, {:+.3}, {:+.3}) added.",
706 dest_str,
707 simplified_symbol,
708 full_symbol,
709 positive_axis[0] + 0.0,
710 positive_axis[1] + 0.0,
711 positive_axis[2] + 0.0,
712 );
713 } else if is_o3_inversion_centre {
714 log::debug!(
715 "Inversion centre {} ({}): {} axis along ({:+.3}, {:+.3}, {:+.3}) added.",
716 dest_str,
717 simplified_symbol,
718 full_symbol,
719 positive_axis[0] + 0.0,
720 positive_axis[1] + 0.0,
721 positive_axis[2] + 0.0,
722 );
723 } else {
724 log::debug!(
725 "Improper rotation {} ({}): {} axis along ({:+.3}, {:+.3}, {:+.3}) added.",
726 dest_str,
727 simplified_symbol,
728 full_symbol,
729 positive_axis[0] + 0.0,
730 positive_axis[1] + 0.0,
731 positive_axis[2] + 0.0,
732 );
733 }
734 }
735 result
736 }
737
738 fn set_group_name(&mut self, name: String) {
744 self.group_name = Some(name);
745 log::debug!(
746 "Symmetry group determined: {}",
747 self.group_name.as_ref().expect("No symmetry groups found.")
748 );
749 }
750
751 #[must_use]
761 pub fn get_elements(
762 &self,
763 kind: &SymmetryElementKind,
764 ) -> Option<&HashMap<ElementOrder, IndexSet<SymmetryElement>>> {
765 self.elements.get(kind)
766 }
767
768 #[must_use]
778 pub fn get_elements_mut(
779 &mut self,
780 kind: &SymmetryElementKind,
781 ) -> Option<&mut HashMap<ElementOrder, IndexSet<SymmetryElement>>> {
782 self.elements.get_mut(kind)
783 }
784
785 #[must_use]
795 pub fn get_generators(
796 &self,
797 kind: &SymmetryElementKind,
798 ) -> Option<&HashMap<ElementOrder, IndexSet<SymmetryElement>>> {
799 self.generators.get(kind)
800 }
801
802 #[must_use]
812 pub fn get_generators_mut(
813 &mut self,
814 kind: &SymmetryElementKind,
815 ) -> Option<&mut HashMap<ElementOrder, IndexSet<SymmetryElement>>> {
816 self.generators.get_mut(kind)
817 }
818
819 #[must_use]
831 pub fn get_sigma_elements(&self, sigma: &str) -> Option<HashSet<&SymmetryElement>> {
832 self.get_improper(&ORDER_1)
833 .map(|sigma_elements| {
834 sigma_elements
835 .iter()
836 .filter_map(|ele| {
837 if ele.additional_subscript == sigma {
838 Some(*ele)
839 } else {
840 None
841 }
842 })
843 .collect::<HashSet<_>>()
844 })
845 .filter(|sigma_elements| !sigma_elements.is_empty())
846 }
847
848 #[must_use]
859 pub fn get_sigma_generators(&self, sigma: &str) -> Option<HashSet<&SymmetryElement>> {
860 let mut sigma_generators: HashSet<&SymmetryElement> = HashSet::new();
861 if let Some(improper_generators) = self.get_generators(&SIG)
862 && let Some(sigmas) = improper_generators.get(&ORDER_1)
863 {
864 sigma_generators.extend(
865 sigmas
866 .iter()
867 .filter(|ele| ele.additional_subscript == sigma),
868 );
869 }
870 if let Some(tr_improper_generators) = self.get_generators(&TRSIG)
871 && let Some(sigmas) = tr_improper_generators.get(&ORDER_1)
872 {
873 sigma_generators.extend(
874 sigmas
875 .iter()
876 .filter(|ele| ele.additional_subscript == sigma),
877 );
878 }
879 if sigma_generators.is_empty() {
880 None
881 } else {
882 Some(sigma_generators)
883 }
884 }
885
886 #[must_use]
892 pub fn get_max_proper_order(&self) -> ElementOrder {
893 *self
894 .get_generators(&ROT)
895 .unwrap_or(&HashMap::new())
896 .keys()
897 .chain(self.get_elements(&ROT).unwrap_or(&HashMap::new()).keys())
898 .chain(
899 self.get_generators(&TRROT)
900 .unwrap_or(&HashMap::new())
901 .keys(),
902 )
903 .chain(self.get_elements(&TRROT).unwrap_or(&HashMap::new()).keys())
904 .max()
905 .expect("No highest proper rotation order could be obtained.")
906 }
907
908 #[must_use]
919 pub fn get_proper(&self, order: &ElementOrder) -> Option<HashSet<&SymmetryElement>> {
920 let opt_proper_elements = self
921 .get_elements(&ROT)
922 .map(|proper_elements| proper_elements.get(order))
923 .unwrap_or_default();
924 let opt_tr_proper_elements = self
925 .get_elements(&TRROT)
926 .map(|tr_proper_elements| tr_proper_elements.get(order))
927 .unwrap_or_default();
928
929 match (opt_proper_elements, opt_tr_proper_elements) {
930 (None, None) => None,
931 (Some(proper_elements), None) => Some(proper_elements.iter().collect::<HashSet<_>>()),
932 (None, Some(tr_proper_elements)) => {
933 Some(tr_proper_elements.iter().collect::<HashSet<_>>())
934 }
935 (Some(proper_elements), Some(tr_proper_elements)) => Some(
936 proper_elements
937 .iter()
938 .chain(tr_proper_elements.iter())
939 .collect::<HashSet<_>>(),
940 ),
941 }
942 }
943
944 #[must_use]
955 pub fn get_improper(&self, order: &ElementOrder) -> Option<HashSet<&SymmetryElement>> {
956 let opt_improper_elements = self
957 .get_elements(&SIG)
958 .map(|improper_elements| improper_elements.get(order))
959 .unwrap_or_default();
960 let opt_tr_improper_elements = self
961 .get_elements(&TRSIG)
962 .map(|tr_improper_elements| tr_improper_elements.get(order))
963 .unwrap_or_default();
964
965 match (opt_improper_elements, opt_tr_improper_elements) {
966 (None, None) => None,
967 (Some(improper_elements), None) => {
968 Some(improper_elements.iter().collect::<HashSet<_>>())
969 }
970 (None, Some(tr_improper_elements)) => {
971 Some(tr_improper_elements.iter().collect::<HashSet<_>>())
972 }
973 (Some(improper_elements), Some(tr_improper_elements)) => Some(
974 improper_elements
975 .iter()
976 .chain(tr_improper_elements.iter())
977 .collect::<HashSet<_>>(),
978 ),
979 }
980 }
981
982 #[must_use]
996 pub fn get_proper_principal_element(&self) -> &SymmetryElement {
997 let max_ord = self.get_max_proper_order();
998 let principal_elements = self.get_proper(&max_ord).unwrap_or_else(|| {
999 let opt_proper_generators = self
1000 .get_generators(&ROT)
1001 .map(|proper_generators| proper_generators.get(&max_ord))
1002 .unwrap_or_default();
1003 let opt_tr_proper_generators = self
1004 .get_elements(&TRROT)
1005 .map(|tr_proper_generators| tr_proper_generators.get(&max_ord))
1006 .unwrap_or_default();
1007
1008 match (opt_proper_generators, opt_tr_proper_generators) {
1009 (None, None) => panic!("No proper elements found."),
1010 (Some(proper_generators), None) => proper_generators.iter().collect::<HashSet<_>>(),
1011 (None, Some(tr_proper_generators)) => {
1012 tr_proper_generators.iter().collect::<HashSet<_>>()
1013 }
1014 (Some(proper_generators), Some(tr_proper_generators)) => proper_generators
1015 .iter()
1016 .chain(tr_proper_generators.iter())
1017 .collect::<HashSet<_>>(),
1018 }
1019 });
1020 principal_elements
1021 .iter()
1022 .find(|ele| !ele.contains_time_reversal())
1023 .unwrap_or_else(|| {
1024 principal_elements
1025 .iter()
1026 .next()
1027 .expect("No proper principal elements found.")
1028 })
1029 }
1030
1031 #[must_use]
1037 pub fn is_infinite(&self) -> bool {
1038 self.get_max_proper_order() == ElementOrder::Inf
1039 || *self
1040 .get_generators(&ROT)
1041 .unwrap_or(&HashMap::new())
1042 .keys()
1043 .chain(self.get_elements(&ROT).unwrap_or(&HashMap::new()).keys())
1044 .chain(
1045 self.get_generators(&TRROT)
1046 .unwrap_or(&HashMap::new())
1047 .keys(),
1048 )
1049 .chain(self.get_elements(&TRROT).unwrap_or(&HashMap::new()).keys())
1050 .max()
1051 .unwrap_or(&ElementOrder::Int(0))
1052 == ElementOrder::Inf
1053 }
1054
1055 pub fn n_elements(&self) -> usize {
1059 let n_elements = self
1060 .elements
1061 .values()
1062 .flat_map(|kind_elements| kind_elements.values())
1063 .flatten()
1064 .count();
1065 if self.is_infinite() {
1066 n_elements
1067 + self
1068 .generators
1069 .values()
1070 .flat_map(|kind_elements| kind_elements.values())
1071 .flatten()
1072 .count()
1073 } else {
1074 n_elements
1075 }
1076 }
1077
1078 #[allow(clippy::too_many_lines)]
1094 pub fn generate_all_operations(
1095 &self,
1096 infinite_order_to_finite: Option<u32>,
1097 ) -> Vec<SymmetryOperation> {
1098 let handles_infinite_group = if self.is_infinite() {
1099 assert!(infinite_order_to_finite.is_some());
1100 infinite_order_to_finite
1101 } else {
1102 None
1103 };
1104
1105 if let Some(finite_order) = handles_infinite_group {
1106 let group_name = self.group_name.as_ref().expect("Group name not found.");
1107 if group_name.contains("O(3)") {
1108 if !matches!(finite_order, 2 | 4) {
1109 log::error!(
1110 "Finite order of `{finite_order}` is not yet supported for `{group_name}`."
1111 );
1112 }
1113 assert!(
1114 matches!(finite_order, 2 | 4),
1115 "Finite order of `{finite_order}` is not yet supported for `{group_name}`."
1116 );
1117 }
1118 }
1119
1120 let id_element = self
1121 .get_elements(&ROT)
1122 .unwrap_or(&HashMap::new())
1123 .get(&ORDER_1)
1124 .expect("No identity elements found.")
1125 .iter()
1126 .next()
1127 .expect("No identity elements found.")
1128 .clone();
1129
1130 let id_operation = SymmetryOperation::builder()
1131 .generating_element(id_element)
1132 .power(1)
1133 .build()
1134 .expect("Unable to construct an identity operation.");
1135
1136 let empty_elements: HashMap<ElementOrder, IndexSet<SymmetryElement>> = HashMap::new();
1137
1138 let mut proper_orders = self
1140 .get_elements(&ROT)
1141 .unwrap_or(&empty_elements)
1142 .keys()
1143 .collect::<Vec<_>>();
1144 proper_orders.sort_by(|a, b| {
1145 a.partial_cmp(b)
1146 .unwrap_or_else(|| panic!("`{a}` and `{b}` cannot be compared."))
1147 });
1148 let proper_operations =
1149 proper_orders
1150 .iter()
1151 .fold(vec![id_operation], |mut acc, proper_order| {
1152 self.get_elements(&ROT)
1153 .unwrap_or(&empty_elements)
1154 .get(proper_order)
1155 .unwrap_or_else(|| panic!("Proper elements C{proper_order} not found."))
1156 .iter()
1157 .for_each(|proper_element| {
1158 if let ElementOrder::Int(io) = proper_order {
1159 acc.extend((1..*io).map(|power| {
1160 SymmetryOperation::builder()
1161 .generating_element(proper_element.clone())
1162 .power(power.try_into().unwrap_or_else(|_| {
1163 panic!("Unable to convert `{power}` to `i32`.")
1164 }))
1165 .build()
1166 .expect("Unable to construct a symmetry operation.")
1167 }));
1168 }
1169 });
1170 acc
1171 });
1172
1173 let proper_operations_from_generators = if let Some(fin_ord) = handles_infinite_group {
1175 self.get_generators(&ROT)
1176 .unwrap_or(&empty_elements)
1177 .par_iter()
1178 .fold(std::vec::Vec::new, |mut acc, (order, proper_generators)| {
1179 for proper_generator in proper_generators.iter() {
1180 let finite_order = match order {
1181 ElementOrder::Int(io) => *io,
1182 ElementOrder::Inf => fin_ord,
1183 };
1184 let finite_proper_element = SymmetryElement::builder()
1185 .threshold(proper_generator.threshold())
1186 .proper_order(ElementOrder::Int(finite_order))
1187 .proper_power(1)
1188 .raw_axis(*proper_generator.raw_axis())
1189 .kind(*proper_generator.kind())
1190 .rotation_group(proper_generator.rotation_group().clone())
1191 .additional_superscript(proper_generator.additional_superscript.clone())
1192 .additional_subscript(proper_generator.additional_subscript.clone())
1193 .build()
1194 .expect("Unable to construct a symmetry element.");
1195 acc.extend((1..finite_order).map(|power| {
1196 SymmetryOperation::builder()
1197 .generating_element(finite_proper_element.clone())
1198 .power(power.try_into().unwrap_or_else(|_| {
1199 panic!("Unable to convert `{power}` to `i32`.")
1200 }))
1201 .build()
1202 .expect("Unable to construct a symmetry operation.")
1203 }));
1204 }
1205 acc
1206 })
1207 .reduce(std::vec::Vec::new, |mut acc, vec| {
1208 acc.extend(vec);
1209 acc
1210 })
1211 } else {
1212 vec![]
1213 };
1214
1215 let mut tr_proper_orders = self
1217 .get_elements(&TRROT)
1218 .unwrap_or(&empty_elements)
1219 .keys()
1220 .collect::<Vec<_>>();
1221 tr_proper_orders.sort_by(|a, b| {
1222 a.partial_cmp(b)
1223 .unwrap_or_else(|| panic!("`{a}` and `{b}` cannot be compared."))
1224 });
1225 let tr_proper_operations =
1226 tr_proper_orders
1227 .iter()
1228 .fold(vec![], |mut acc, tr_proper_order| {
1229 self.get_elements(&TRROT)
1230 .unwrap_or(&empty_elements)
1231 .get(tr_proper_order)
1232 .unwrap_or_else(|| {
1233 panic!("Proper elements θ·C{tr_proper_order} not found.")
1234 })
1235 .iter()
1236 .for_each(|tr_proper_element| {
1237 if let ElementOrder::Int(io) = tr_proper_order {
1238 acc.extend((1..(2 * *io)).step_by(2).map(|power| {
1239 SymmetryOperation::builder()
1240 .generating_element(tr_proper_element.clone())
1241 .power(power.try_into().unwrap_or_else(|_| {
1242 panic!("Unable to convert `{power}` to `i32`.")
1243 }))
1244 .build()
1245 .expect("Unable to construct a symmetry operation.")
1246 }));
1247 }
1248 });
1249 acc
1250 });
1251
1252 let tr_proper_operations_from_generators = if let Some(fin_ord) = handles_infinite_group {
1254 self.get_generators(&TRROT)
1255 .unwrap_or(&empty_elements)
1256 .par_iter()
1257 .fold(
1258 std::vec::Vec::new,
1259 |mut acc, (order, tr_proper_generators)| {
1260 for tr_proper_generator in tr_proper_generators.iter() {
1261 let finite_order = match order {
1262 ElementOrder::Int(io) => *io,
1263 ElementOrder::Inf => fin_ord,
1264 };
1265 let finite_tr_proper_element = SymmetryElement::builder()
1266 .threshold(tr_proper_generator.threshold())
1267 .proper_order(ElementOrder::Int(finite_order))
1268 .proper_power(1)
1269 .raw_axis(*tr_proper_generator.raw_axis())
1270 .kind(*tr_proper_generator.kind())
1271 .rotation_group(tr_proper_generator.rotation_group().clone())
1272 .additional_superscript(
1273 tr_proper_generator.additional_superscript.clone(),
1274 )
1275 .additional_subscript(
1276 tr_proper_generator.additional_subscript.clone(),
1277 )
1278 .build()
1279 .expect("Unable to construct a symmetry element.");
1280 acc.extend((1..finite_order).map(|power| {
1281 SymmetryOperation::builder()
1282 .generating_element(finite_tr_proper_element.clone())
1283 .power(power.try_into().unwrap_or_else(|_| {
1284 panic!("Unable to convert `{power}` to `i32`.")
1285 }))
1286 .build()
1287 .expect("Unable to construct a symmetry operation.")
1288 }));
1289 }
1290 acc
1291 },
1292 )
1293 .reduce(std::vec::Vec::new, |mut acc, vec| {
1294 acc.extend(vec);
1295 acc
1296 })
1297 } else {
1298 vec![]
1299 };
1300
1301 let mut improper_orders = self
1303 .get_elements(&SIG)
1304 .unwrap_or(&empty_elements)
1305 .keys()
1306 .collect::<Vec<_>>();
1307 improper_orders.sort_by(|a, b| {
1308 a.partial_cmp(b)
1309 .unwrap_or_else(|| panic!("`{a}` and `{b}` cannot be compared."))
1310 });
1311 let improper_operations = improper_orders
1312 .iter()
1313 .fold(vec![], |mut acc, improper_order| {
1314 self.get_elements(&SIG)
1315 .unwrap_or(&empty_elements)
1316 .get(improper_order)
1317 .unwrap_or_else(|| panic!("Improper elements S{improper_order} not found."))
1318 .iter()
1319 .for_each(|improper_element| {
1320 if let ElementOrder::Int(io) = improper_order {
1321 acc.extend((1..(2 * *io)).step_by(2).map(|power| {
1322 SymmetryOperation::builder()
1323 .generating_element(improper_element.clone())
1324 .power(power.try_into().unwrap_or_else(|_| {
1325 panic!("Unable to convert `{power}` to `i32`.")
1326 }))
1327 .build()
1328 .expect("Unable to construct a symmetry operation.")
1329 }));
1330 }
1331 });
1332 acc
1333 });
1334
1335 let improper_operations_from_generators = if let Some(fin_ord) = handles_infinite_group {
1337 self.get_generators(&SIG)
1338 .unwrap_or(&empty_elements)
1339 .par_iter()
1340 .fold(
1341 std::vec::Vec::new,
1342 |mut acc, (order, improper_generators)| {
1343 for improper_generator in improper_generators.iter() {
1344 let finite_order = match order {
1345 ElementOrder::Int(io) => *io,
1346 ElementOrder::Inf => fin_ord,
1347 };
1348 let finite_improper_element = SymmetryElement::builder()
1349 .threshold(improper_generator.threshold())
1350 .proper_order(ElementOrder::Int(finite_order))
1351 .proper_power(1)
1352 .raw_axis(*improper_generator.raw_axis())
1353 .kind(*improper_generator.kind())
1354 .rotation_group(improper_generator.rotation_group().clone())
1355 .additional_superscript(
1356 improper_generator.additional_superscript.clone(),
1357 )
1358 .additional_subscript(
1359 improper_generator.additional_subscript.clone(),
1360 )
1361 .build()
1362 .expect("Unable to construct a symmetry element.");
1363 acc.extend((1..(2 * finite_order)).step_by(2).map(|power| {
1364 SymmetryOperation::builder()
1365 .generating_element(finite_improper_element.clone())
1366 .power(power.try_into().unwrap_or_else(|_| {
1367 panic!("Unable to convert `{power}` to `i32`.")
1368 }))
1369 .build()
1370 .expect("Unable to construct a symmetry operation.")
1371 }));
1372 }
1373 acc
1374 },
1375 )
1376 .reduce(std::vec::Vec::new, |mut acc, vec| {
1377 acc.extend(vec);
1378 acc
1379 })
1380 } else {
1381 vec![]
1382 };
1383
1384 let mut tr_improper_orders = self
1386 .get_elements(&TRSIG)
1387 .unwrap_or(&empty_elements)
1388 .keys()
1389 .collect::<Vec<_>>();
1390 tr_improper_orders.sort_by(|a, b| {
1391 a.partial_cmp(b)
1392 .unwrap_or_else(|| panic!("`{a}` and `{b}` cannot be compared."))
1393 });
1394 let tr_improper_operations =
1395 tr_improper_orders
1396 .iter()
1397 .fold(vec![], |mut acc, tr_improper_order| {
1398 self.get_elements(&TRSIG)
1399 .unwrap_or(&empty_elements)
1400 .get(tr_improper_order)
1401 .unwrap_or_else(|| {
1402 panic!("Improper elements θ·S{tr_improper_order} not found.")
1403 })
1404 .iter()
1405 .for_each(|tr_improper_element| {
1406 if let ElementOrder::Int(io) = tr_improper_order {
1407 acc.extend((1..(2 * *io)).step_by(2).map(|power| {
1408 SymmetryOperation::builder()
1409 .generating_element(tr_improper_element.clone())
1410 .power(power.try_into().unwrap_or_else(|_| {
1411 panic!("Unable to convert `{power}` to `i32`.")
1412 }))
1413 .build()
1414 .expect("Unable to construct a symmetry operation.")
1415 }));
1416 }
1417 });
1418 acc
1419 });
1420
1421 let tr_improper_operations_from_generators = if let Some(fin_ord) = handles_infinite_group {
1423 self.get_generators(&TRSIG)
1424 .unwrap_or(&empty_elements)
1425 .par_iter()
1426 .fold(
1427 std::vec::Vec::new,
1428 |mut acc, (order, tr_improper_generators)| {
1429 for tr_improper_generator in tr_improper_generators.iter() {
1430 let finite_order = match order {
1431 ElementOrder::Int(io) => *io,
1432 ElementOrder::Inf => fin_ord,
1433 };
1434 let finite_tr_improper_element = SymmetryElement::builder()
1435 .threshold(tr_improper_generator.threshold())
1436 .proper_order(ElementOrder::Int(finite_order))
1437 .proper_power(1)
1438 .raw_axis(*tr_improper_generator.raw_axis())
1439 .kind(*tr_improper_generator.kind())
1440 .rotation_group(tr_improper_generator.rotation_group().clone())
1441 .additional_superscript(
1442 tr_improper_generator.additional_superscript.clone(),
1443 )
1444 .additional_subscript(
1445 tr_improper_generator.additional_subscript.clone(),
1446 )
1447 .build()
1448 .expect("Unable to construct a symmetry element.");
1449 acc.extend((1..(2 * finite_order)).step_by(2).map(|power| {
1450 SymmetryOperation::builder()
1451 .generating_element(finite_tr_improper_element.clone())
1452 .power(power.try_into().unwrap_or_else(|_| {
1453 panic!("Unable to convert `{power}` to `i32`.")
1454 }))
1455 .build()
1456 .expect("Unable to construct a symmetry operation.")
1457 }));
1458 }
1459 acc
1460 },
1461 )
1462 .reduce(std::vec::Vec::new, |mut acc, vec| {
1463 acc.extend(vec);
1464 acc
1465 })
1466 } else {
1467 vec![]
1468 };
1469
1470 let operations: IndexSet<_> = if handles_infinite_group.is_none() {
1471 proper_operations
1472 .into_iter()
1473 .chain(proper_operations_from_generators)
1474 .chain(improper_operations)
1475 .chain(improper_operations_from_generators)
1476 .chain(tr_proper_operations)
1477 .chain(tr_proper_operations_from_generators)
1478 .chain(tr_improper_operations)
1479 .chain(tr_improper_operations_from_generators)
1480 .collect()
1481 } else {
1482 log::debug!("Fulfilling closure for a finite subgroup of an infinite group...");
1484 let mut existing_operations: IndexSet<_> = proper_operations
1485 .into_iter()
1486 .chain(proper_operations_from_generators)
1487 .chain(improper_operations)
1488 .chain(improper_operations_from_generators)
1489 .chain(tr_proper_operations)
1490 .chain(tr_proper_operations_from_generators)
1491 .chain(tr_improper_operations)
1492 .chain(tr_improper_operations_from_generators)
1493 .collect();
1494 let mut extra_operations = HashSet::<SymmetryOperation>::new();
1495 let mut npasses = 0;
1496 let mut nstable = 0;
1497
1498 let principal_element = self.get_proper_principal_element();
1499 while nstable < 2 || npasses == 0 {
1500 let n_extra_operations = extra_operations.len();
1501 existing_operations.extend(extra_operations);
1502
1503 npasses += 1;
1504 log::debug!(
1505 "Generating all group elements: {} pass{}, {} element{} (of which {} {} new)",
1506 npasses,
1507 { if npasses > 1 { "es" } else { "" } },
1508 existing_operations.len(),
1509 {
1510 if existing_operations.len() > 1 {
1511 "s"
1512 } else {
1513 ""
1514 }
1515 },
1516 n_extra_operations,
1517 { if n_extra_operations == 1 { "is" } else { "are" } },
1518 );
1519
1520 extra_operations = existing_operations
1521 .iter()
1522 .combinations_with_replacement(2)
1523 .par_bridge()
1524 .filter_map(|op_pairs| {
1525 let op_i_ref = op_pairs[0];
1526 let op_j_ref = op_pairs[1];
1527 let op_k = op_i_ref * op_j_ref;
1528 if existing_operations.contains(&op_k) {
1529 None
1530 } else if op_k.is_proper() {
1531 Some(op_k)
1532 } else if op_k.is_spatial_reflection()
1533 && op_k.generating_element.additional_subscript.is_empty()
1534 {
1535 if let Some(sigma_symbol) = deduce_sigma_symbol(
1536 op_k.generating_element.raw_axis(),
1537 principal_element,
1538 op_k.generating_element.threshold(),
1539 false,
1540 ) {
1541 let mut op_k_sym = op_k.convert_to_improper_kind(&SIG);
1542 op_k_sym.generating_element.additional_subscript = sigma_symbol;
1543 Some(op_k_sym)
1544 } else {
1545 Some(op_k.convert_to_improper_kind(&SIG))
1546 }
1547 } else {
1548 Some(op_k.convert_to_improper_kind(&SIG))
1549 }
1550 })
1551 .collect();
1552 if extra_operations.is_empty() {
1553 nstable += 1;
1554 } else {
1555 nstable = 0;
1556 }
1557 }
1558
1559 assert_eq!(extra_operations.len(), 0);
1560 log::debug!(
1561 "Group closure reached with {} elements.",
1562 existing_operations.len()
1563 );
1564 existing_operations
1565 };
1566
1567 let mut sorted_operations: Vec<SymmetryOperation> = operations.into_iter().collect();
1568 sort_operations(&mut sorted_operations);
1569 sorted_operations
1570 }
1571}
1572
1573impl Default for Symmetry {
1574 fn default() -> Self {
1575 Self::new()
1576 }
1577}
1578
1579#[allow(clippy::too_many_lines)]
1591fn _search_proper_rotations(
1592 presym: &PreSymmetry,
1593 sym: &mut Symmetry,
1594 asymmetric: bool,
1595 tr: bool,
1596) -> Result<(), anyhow::Error> {
1597 log::debug!("==============================");
1598 log::debug!("Proper rotation search begins.");
1599 log::debug!("==============================");
1600 let mut linear_sea_groups: Vec<&Vec<Atom>> = vec![];
1601 let mut count_c2: usize = 0;
1602 log::debug!("++++++++++++++++++++++++++");
1603 log::debug!("SEA group analysis begins.");
1604 log::debug!("++++++++++++++++++++++++++");
1605 for sea_group in &presym.sea_groups {
1606 if asymmetric && count_c2 == 3 {
1607 break;
1608 }
1609 let k_sea = sea_group.len();
1610 match k_sea {
1611 1 => {
1612 continue;
1613 }
1614 2 => {
1615 log::debug!("A linear SEA set detected: {:?}.", sea_group);
1616 linear_sea_groups.push(sea_group);
1617 }
1618 _ => {
1619 let sea_mol = Molecule::from_atoms(sea_group, presym.dist_threshold);
1620 let (sea_mois, sea_axes) = sea_mol.calc_moi();
1621 if approx::relative_eq!(
1623 sea_mois[0] + sea_mois[1],
1624 sea_mois[2],
1625 epsilon = presym.moi_threshold,
1626 max_relative = presym.moi_threshold,
1627 ) {
1628 let k_fac_range: Vec<_> = if approx::relative_eq!(
1630 sea_mois[0],
1631 sea_mois[1],
1632 epsilon = presym.moi_threshold,
1633 max_relative = presym.moi_threshold,
1634 ) {
1635 log::debug!(
1637 "A regular {}-sided polygon SEA set detected: {:?}.",
1638 k_sea,
1639 sea_group
1640 );
1641 let mut divisors = divisors::get_divisors(k_sea);
1642 divisors.push(k_sea);
1643 divisors
1644 } else {
1645 log::debug!(
1647 "An irregular {}-sided polygon SEA set detected: {:?}.",
1648 k_sea,
1649 sea_group
1650 );
1651 divisors::get_divisors(k_sea)
1652 };
1653 for k_fac in &k_fac_range {
1654 if let Some(proper_kind) =
1655 presym.check_proper(
1656 &ElementOrder::Int((*k_fac).try_into().unwrap_or_else(|_| {
1657 panic!("Unable to convert {k_fac} to `u32`.")
1658 })),
1659 &sea_axes[2],
1660 tr,
1661 )
1662 {
1663 match *k_fac {
1664 2 => {
1665 count_c2 += usize::from(sym.add_proper(
1666 ElementOrder::Int((*k_fac).try_into().unwrap_or_else(
1667 |_| panic!("Unable to convert {k_fac} to `u32`."),
1668 )),
1669 &sea_axes[2],
1670 false,
1671 presym.dist_threshold,
1672 proper_kind.contains_time_reversal(),
1673 ));
1674 }
1675 _ => {
1676 sym.add_proper(
1677 ElementOrder::Int((*k_fac).try_into().unwrap_or_else(
1678 |_| panic!("Unable to convert {k_fac} to `u32`."),
1679 )),
1680 &sea_axes[2],
1681 false,
1682 presym.dist_threshold,
1683 proper_kind.contains_time_reversal(),
1684 );
1685 }
1686 }
1687 }
1688 }
1689 } else {
1690 if approx::relative_eq!(
1692 sea_mois[1],
1693 sea_mois[2],
1694 epsilon = presym.moi_threshold,
1695 max_relative = presym.moi_threshold,
1696 ) {
1697 ensure!(
1699 k_sea % 2 == 0,
1700 "Unexpected odd number of atoms in this SEA group."
1701 );
1702 if approx::relative_eq!(
1703 sea_mois[0],
1704 sea_mois[1],
1705 epsilon = presym.moi_threshold,
1706 max_relative = presym.moi_threshold,
1707 ) {
1708 log::debug!("A spherical top SEA set detected: {:?}", sea_group);
1710 let sea_presym = PreSymmetry::builder()
1711 .moi_threshold(presym.moi_threshold)
1712 .molecule(&sea_mol)
1713 .build()
1714 .expect("Unable to construct a `PreSymmetry` structure.");
1715 let mut sea_sym = Symmetry::builder()
1716 .build()
1717 .expect("Unable to construct a default `Symmetry` structure.");
1718 log::debug!("-----------------------------------------------");
1719 log::debug!("Symmetry analysis for spherical top SEA begins.");
1720 log::debug!("-----------------------------------------------");
1721 sea_sym.analyse(&sea_presym, tr)?;
1722 log::debug!("---------------------------------------------");
1723 log::debug!("Symmetry analysis for spherical top SEA ends.");
1724 log::debug!("---------------------------------------------");
1725 for (order, proper_elements) in sea_sym
1726 .get_elements(&ROT)
1727 .unwrap_or(&HashMap::new())
1728 .iter()
1729 .chain(
1730 sea_sym
1731 .get_elements(&TRROT)
1732 .unwrap_or(&HashMap::new())
1733 .iter(),
1734 )
1735 {
1736 for proper_element in proper_elements {
1737 if let Some(proper_kind) =
1738 presym.check_proper(order, proper_element.raw_axis(), tr)
1739 {
1740 sym.add_proper(
1741 *order,
1742 proper_element.raw_axis(),
1743 false,
1744 presym.dist_threshold,
1745 proper_kind.contains_time_reversal(),
1746 );
1747 }
1748 }
1749 }
1750 } else {
1751 log::debug!("A prolate symmetric top SEA set detected.");
1753 for k_fac in divisors::get_divisors(k_sea / 2)
1754 .iter()
1755 .chain([k_sea / 2].iter())
1756 {
1757 let k_fac_order =
1758 ElementOrder::Int((*k_fac).try_into().unwrap_or_else(|_| {
1759 panic!("Unable to convert {k_fac} to u32.")
1760 }));
1761 if let Some(proper_kind) =
1762 presym.check_proper(&k_fac_order, &sea_axes[0], tr)
1763 {
1764 if *k_fac == 2 {
1765 count_c2 += usize::from(sym.add_proper(
1766 k_fac_order,
1767 &sea_axes[0],
1768 false,
1769 presym.dist_threshold,
1770 proper_kind.contains_time_reversal(),
1771 ));
1772 } else {
1773 sym.add_proper(
1774 k_fac_order,
1775 &sea_axes[0],
1776 false,
1777 presym.dist_threshold,
1778 proper_kind.contains_time_reversal(),
1779 );
1780 }
1781 }
1782 }
1783 }
1784 } else if approx::relative_eq!(
1785 sea_mois[0],
1786 sea_mois[1],
1787 epsilon = presym.moi_threshold,
1788 max_relative = presym.moi_threshold,
1789 ) {
1790 log::debug!("An oblate symmetric top SEA set detected.");
1792 ensure!(
1793 k_sea % 2 == 0,
1794 "Unexpected odd number of atoms in this SEA group."
1795 );
1796 for k_fac in divisors::get_divisors(k_sea / 2)
1797 .iter()
1798 .chain([k_sea / 2].iter())
1799 {
1800 let k_fac_order =
1801 ElementOrder::Int((*k_fac).try_into().map_err(|_| {
1802 format_err!("Unable to convert `{k_fac}` to `u32`.")
1803 })?);
1804 if let Some(proper_kind) =
1805 presym.check_proper(&k_fac_order, &sea_axes[2], tr)
1806 {
1807 if *k_fac == 2 {
1808 count_c2 += usize::from(sym.add_proper(
1809 k_fac_order,
1810 &sea_axes[2],
1811 false,
1812 presym.dist_threshold,
1813 proper_kind.contains_time_reversal(),
1814 ));
1815 } else {
1816 sym.add_proper(
1817 k_fac_order,
1818 &sea_axes[2],
1819 false,
1820 presym.dist_threshold,
1821 proper_kind.contains_time_reversal(),
1822 );
1823 }
1824 }
1825 }
1826 } else {
1827 log::debug!("An asymmetric top SEA set detected.");
1829 for sea_axis in &sea_axes {
1830 if let Some(proper_kind) = presym.check_proper(&ORDER_2, sea_axis, tr) {
1831 count_c2 += usize::from(sym.add_proper(
1832 ORDER_2,
1833 sea_axis,
1834 false,
1835 presym.dist_threshold,
1836 proper_kind.contains_time_reversal(),
1837 ));
1838 }
1839 }
1840 }
1841 }
1842 }
1843 } log::debug!("Searching for any remaining C2 axes in this SEA group...");
1847 for atom2s in sea_group.iter().combinations(2) {
1848 if asymmetric && count_c2 == 3 {
1849 log::debug!(
1850 "Three C2 axes have been found for an asymmetric top. No more C2 axes will be found. The C2 search has completed."
1851 );
1852 break;
1853 }
1854 let atom_i_pos = atom2s[0].coordinates;
1855 let atom_j_pos = atom2s[1].coordinates;
1856
1857 if let Some(proper_kind) = presym.check_proper(&ORDER_2, &atom_i_pos.coords, tr) {
1859 count_c2 += usize::from(sym.add_proper(
1860 ORDER_2,
1861 &atom_i_pos.coords,
1862 false,
1863 presym.dist_threshold,
1864 proper_kind.contains_time_reversal(),
1865 ));
1866 }
1867
1868 let midvec = 0.5 * (atom_i_pos.coords + atom_j_pos.coords);
1874 let c2_check = presym.check_proper(&ORDER_2, &midvec, tr);
1875 if midvec.norm() > presym.dist_threshold
1876 && let Some(c2) = c2_check
1877 {
1878 count_c2 += usize::from(sym.add_proper(
1879 ORDER_2,
1880 &midvec,
1881 false,
1882 presym.dist_threshold,
1883 c2.contains_time_reversal(),
1884 ));
1885 } else if let Some(electric_atoms) = &presym.recentred_molecule.electric_atoms {
1886 let com = presym.recentred_molecule.calc_com();
1887 let e_vector = electric_atoms[0].coordinates - com;
1888 if let Some(proper_kind) = presym.check_proper(&ORDER_2, &e_vector, tr) {
1889 count_c2 += usize::from(sym.add_proper(
1890 ORDER_2,
1891 &e_vector,
1892 false,
1893 presym.dist_threshold,
1894 proper_kind.contains_time_reversal(),
1895 ));
1896 }
1897 } else if midvec.norm() <= presym.dist_threshold {
1898 for principal_axis in &presym.recentred_molecule.calc_moi().1 {
1899 if let Some(proper_kind) = presym.check_proper(&ORDER_2, principal_axis, tr) {
1900 count_c2 += usize::from(sym.add_proper(
1901 ORDER_2,
1902 principal_axis,
1903 false,
1904 presym.dist_threshold,
1905 proper_kind.contains_time_reversal(),
1906 ));
1907 }
1908 }
1909 }
1910 }
1911 log::debug!("Searching for any remaining C2 axes in this SEA group... Done.");
1912 } log::debug!("++++++++++++++++++++++++");
1914 log::debug!("SEA group analysis ends.");
1915 log::debug!("++++++++++++++++++++++++");
1916
1917 if asymmetric && count_c2 == 3 {
1919 } else {
1920 if linear_sea_groups.len() >= 2 {
1922 let normal_option = linear_sea_groups.iter().combinations(2).find_map(|pair| {
1923 let vec_0 = pair[0][1].coordinates - pair[0][0].coordinates;
1924 let vec_1 = pair[1][1].coordinates - pair[1][0].coordinates;
1925 let trial_normal = vec_0.cross(&vec_1);
1926 if trial_normal.norm() > presym.dist_threshold {
1927 Some(trial_normal)
1928 } else {
1929 None
1930 }
1931 });
1932 if let Some(normal) = normal_option
1933 && let Some(proper_kind) = presym.check_proper(&ORDER_2, &normal, tr)
1934 {
1935 sym.add_proper(
1936 ORDER_2,
1937 &normal,
1938 false,
1939 presym.dist_threshold,
1940 proper_kind.contains_time_reversal(),
1941 );
1942 }
1943 }
1944 }
1945 log::debug!("============================");
1946 log::debug!("Proper rotation search ends.");
1947 log::debug!("============================");
1948
1949 Ok(())
1950}
1951
1952mod symmetry_core_asymmetric;
1953mod symmetry_core_linear;
1954mod symmetry_core_spherical;
1955mod symmetry_core_symmetric;