qsym2/drivers/representation_analysis/
mod.rs

1//! Drivers for symmetry analysis via representation and corepresentation theories.
2
3use std::fmt;
4
5#[cfg(feature = "python")]
6use pyo3::prelude::*;
7use serde::{Deserialize, Serialize};
8
9use crate::basis::ao::BasisAngularOrder;
10use crate::group::class::ClassPropertiesSummary;
11use crate::io::format::{QSym2Output, log_subtitle, qsym2_output};
12
13pub mod angular_function;
14pub mod density;
15pub mod multideterminant;
16pub mod slater_determinant;
17pub mod vibrational_coordinate;
18
19// ================
20// Enum definitions
21// ================
22
23/// Enumerated type indicating the format of character table print-out.
24#[derive(Clone, Debug, Deserialize, Serialize)]
25pub enum CharacterTableDisplay {
26    /// Prints the character table symbolically showing explicitly the roots of unity.
27    Symbolic,
28
29    /// Prints the character table numerically where each character is a complex number.
30    Numerical,
31}
32
33impl fmt::Display for CharacterTableDisplay {
34    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
35        match self {
36            CharacterTableDisplay::Symbolic => write!(f, "Symbolic"),
37            CharacterTableDisplay::Numerical => write!(f, "Numerical"),
38        }
39    }
40}
41
42/// Enumerated type indicating the type of magnetic symmetry to be used for representation
43/// analysis.
44#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
45#[cfg_attr(feature = "python", pyclass(eq, eq_int))]
46pub enum MagneticSymmetryAnalysisKind {
47    /// Variant indicating that unitary representations should be used for magnetic symmetry
48    /// analysis.
49    Representation,
50
51    /// Variant indicating that magnetic corepresentations should be used for magnetic symmetry
52    /// analysis.
53    Corepresentation,
54}
55
56impl fmt::Display for MagneticSymmetryAnalysisKind {
57    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
58        match self {
59            MagneticSymmetryAnalysisKind::Representation => write!(f, "Unitary representations"),
60            MagneticSymmetryAnalysisKind::Corepresentation => {
61                write!(f, "Magnetic corepresentations")
62            }
63        }
64    }
65}
66
67// =================
68// Utility functions
69// =================
70
71/// Logs basis angular order information nicely.
72///
73/// # Arguments
74///
75/// * `bao` - The basis angular order information structure.
76/// * `index` - The optional index for the explicit component to which the basis angular
77///   information corresponds. If `None`, then it is assumed that the basis angular order information
78///   is uniform across all explicit components.
79pub(crate) fn log_bao(bao: &BasisAngularOrder, index: Option<usize>) {
80    if let Some(i) = index {
81        log_subtitle(&format!("Basis angular order (explicit component {i})"));
82    } else {
83        log_subtitle("Basis angular order (uniform across all explicit components)");
84    }
85    qsym2_output!("");
86    "The basis angular order information dictates how basis functions in each basis shell are transformed.\n\
87    It is important to check that this is consistent with the basis set being used, otherwise incorrect\n\
88    symmetry results will be obtained.".log_output_display();
89    bao.log_output_display();
90    qsym2_output!("");
91}
92
93/// Logs a conjugacy class transversal of a group nicely.
94///
95/// # Arguments
96///
97/// * `group` - A group for which a conjugacy class transversal should be logged nicely.
98pub(crate) fn log_cc_transversal<G>(group: &G)
99where
100    G: ClassPropertiesSummary,
101    G::GroupElement: fmt::Display,
102{
103    log_subtitle("Conjugacy class transversal");
104    qsym2_output!("");
105    group.class_transversal_to_string().log_output_display();
106    qsym2_output!("");
107}
108
109// =================
110// Macro definitions
111// =================
112
113macro_rules! fn_construct_unitary_group {
114    ( $(#[$meta:meta])* $vis:vis $func:ident ) => {
115        $(#[$meta])*
116        $vis fn $func(&self) -> Result<UnitaryRepresentedSymmetryGroup, anyhow::Error> {
117            let params = self.parameters;
118            let sym = match params.use_magnetic_group {
119                Some(MagneticSymmetryAnalysisKind::Representation) => self.symmetry_group
120                    .magnetic_symmetry
121                    .as_ref()
122                    .ok_or_else(|| {
123                        format_err!(
124                            "Magnetic symmetry requested for analysis, but no magnetic symmetry found."
125                        )
126                    })?,
127                Some(MagneticSymmetryAnalysisKind::Corepresentation) => bail!("Magnetic corepresentations requested, but unitary-represented group is being constructed."),
128                None => &self.symmetry_group.unitary_symmetry
129            };
130            let group = if params.use_double_group {
131                UnitaryRepresentedGroup::from_molecular_symmetry(sym, params.infinite_order_to_finite)?
132                    .to_double_group()?
133            } else {
134                UnitaryRepresentedGroup::from_molecular_symmetry(sym, params.infinite_order_to_finite)?
135            };
136
137            qsym2_output!(
138                "Unitary-represented group for representation analysis: {}",
139                group.name()
140            );
141            qsym2_output!("");
142            if let Some(chartab_display) = params.write_character_table.as_ref() {
143                log_subtitle("Character table of irreducible representations");
144                qsym2_output!("");
145                match chartab_display {
146                    CharacterTableDisplay::Symbolic => {
147                        group.character_table().log_output_debug();
148                        "Any `En` in a character value denotes the first primitive n-th root of unity:\n  \
149                        En = exp(2πi/n)".log_output_display();
150                    }
151                    CharacterTableDisplay::Numerical => group.character_table().log_output_display(),
152                }
153                qsym2_output!("");
154                qsym2_output!("The symbol `◈` indicates the principal class of the group.");
155                qsym2_output!("");
156                "Note 1: `FS` contains the classification of the irreps using the Frobenius--Schur indicator:\n  \
157                `r` = real: the irrep and its complex-conjugate partner are real and identical,\n  \
158                `c` = complex: the irrep and its complex-conjugate partner are complex and inequivalent,\n  \
159                `q` = quaternion: the irrep and its complex-conjugate partner are complex and equivalent.\n\n\
160                Note 2: The conjugacy classes are sorted according to the following order:\n  \
161                E -> C_n (n descending) -> C2 -> i -> S_n (n decending) -> σ\n  \
162                Within each order and power, elements with axes close to Cartesian axes are put first.\n  \
163                Within each equi-inclination from Cartesian axes, z-inclined axes are put first, then y, then x.\n\n\
164                Note 3: The Mulliken labels generated for the irreps in the table above are internally consistent.\n  \
165                However, certain labels might differ from those tabulated elsewhere using other conventions.\n  \
166                If need be, please check with other literature to ensure external consistency.".log_output_display();
167                qsym2_output!("");
168            }
169            Ok(group)
170        }
171    }
172}
173
174macro_rules! fn_construct_magnetic_group {
175    ( $(#[$meta:meta])* $vis:vis $func:ident ) => {
176        $(#[$meta])*
177        $vis fn $func(&self) -> Result<MagneticRepresentedSymmetryGroup, anyhow::Error> {
178            let params = self.parameters;
179            let sym = match params.use_magnetic_group {
180                Some(MagneticSymmetryAnalysisKind::Corepresentation) => self.symmetry_group
181                    .magnetic_symmetry
182                    .as_ref()
183                    .ok_or_else(|| {
184                        format_err!(
185                            "Magnetic symmetry requested for analysis, but no magnetic symmetry found."
186                        )
187                    })?,
188                Some(MagneticSymmetryAnalysisKind::Representation) => bail!("Unitary representations requested, but magnetic-represented group is being constructed."),
189                None => &self.symmetry_group.unitary_symmetry
190            };
191            let group = if params.use_double_group {
192                MagneticRepresentedGroup::from_molecular_symmetry(sym, params.infinite_order_to_finite)?
193                    .to_double_group()?
194            } else {
195                MagneticRepresentedGroup::from_molecular_symmetry(sym, params.infinite_order_to_finite)?
196            };
197
198            qsym2_output!(
199                "Magnetic-represented group for corepresentation analysis: {}",
200                group.name()
201            );
202            qsym2_output!("");
203
204            if let Some(chartab_display) = params.write_character_table.as_ref() {
205                log_subtitle("Character table of irreducible corepresentations");
206                qsym2_output!("");
207                match chartab_display {
208                    CharacterTableDisplay::Symbolic => {
209                        group.character_table().log_output_debug();
210                        "Any `En` in a character value denotes the first primitive n-th root of unity:\n  \
211                        En = exp(2πi/n)".log_output_display();
212                    }
213                    CharacterTableDisplay::Numerical => group.character_table().log_output_display(),
214                }
215                qsym2_output!("");
216                qsym2_output!("The symbol `◈` indicates the principal class of the group.");
217                qsym2_output!("");
218                "Note 1: The ircorep notation `D[Δ]` means that this ircorep is induced by the representation Δ\n  \
219                of the unitary halving subgroup. The exact nature of Δ determines the kind of D[Δ].\n\n\
220                Note 2: `IN` shows the intertwining numbers of the ircoreps which classify them into three kinds:\n  \
221                `1` = 1st kind: the ircorep is induced by a single irrep of the unitary halving subgroup once,\n  \
222                `4` = 2nd kind: the ircorep is induced by a single irrep of the unitary halving subgroup twice,\n  \
223                `2` = 3rd kind: the ircorep is induced by an irrep of the unitary halving subgroup and its Wigner conjugate.\n\n\
224                Note 3: Only unitary-represented elements are shown in the character table, as characters of\n  \
225                antiunitary-represented elements are not invariant under a change of basis.\n\n\
226                Refs:\n  \
227                Newmarch, J. D. & Golding, R. M. J. Math. Phys. 23, 695–704 (1982)\n  \
228                Bradley, C. J. & Davies, B. L. Rev. Mod. Phys. 40, 359–379 (1968)\n  \
229                Newmarch, J. D. J. Math. Phys. 24, 742–756 (1983)".log_output_display();
230                qsym2_output!("");
231            }
232
233            Ok(group)
234        }
235    }
236}
237
238pub(crate) use fn_construct_magnetic_group;
239pub(crate) use fn_construct_unitary_group;