1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
//! Drivers for symmetry analysis via representation and corepresentation theories.

use std::fmt;

#[cfg(feature = "python")]
use pyo3::prelude::*;
use serde::{Deserialize, Serialize};

use crate::basis::ao::BasisAngularOrder;
use crate::group::class::ClassPropertiesSummary;
use crate::io::format::{log_subtitle, qsym2_output, QSym2Output};

pub mod angular_function;
pub mod density;
pub mod multideterminant;
pub mod slater_determinant;
pub mod vibrational_coordinate;

// ================
// Enum definitions
// ================

/// Enumerated type indicating the format of character table print-out.
#[derive(Clone, Debug, Deserialize, Serialize)]
pub enum CharacterTableDisplay {
    /// Prints the character table symbolically showing explicitly the roots of unity.
    Symbolic,

    /// Prints the character table numerically where each character is a complex number.
    Numerical,
}

impl fmt::Display for CharacterTableDisplay {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            CharacterTableDisplay::Symbolic => write!(f, "Symbolic"),
            CharacterTableDisplay::Numerical => write!(f, "Numerical"),
        }
    }
}

/// Enumerated type indicating the type of magnetic symmetry to be used for representation
/// analysis.
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "python", pyclass)]
pub enum MagneticSymmetryAnalysisKind {
    /// Variant indicating that unitary representations should be used for magnetic symmetry
    /// analysis.
    Representation,

    /// Variant indicating that magnetic corepresentations should be used for magnetic symmetry
    /// analysis.
    Corepresentation,
}

impl fmt::Display for MagneticSymmetryAnalysisKind {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            MagneticSymmetryAnalysisKind::Representation => write!(f, "Unitary representations"),
            MagneticSymmetryAnalysisKind::Corepresentation => {
                write!(f, "Magnetic corepresentations")
            }
        }
    }
}

// =================
// Utility functions
// =================

/// Logs basis angular order information nicely.
///
/// # Arguments
///
/// * `bao` - The basis angular order information structure.
fn log_bao(bao: &BasisAngularOrder) {
    log_subtitle("Basis angular order");
    qsym2_output!("");
    "The basis angular order information dictates how basis functions in each basis shell are transformed.\n\
    It is important to check that this is consistent with the basis set being used, otherwise incorrect\n\
    symmetry results will be obtained.".log_output_display();
    bao.log_output_display();
    qsym2_output!("");
}

/// Logs a conjugacy class transversal of a group nicely.
///
/// # Arguments
///
/// * `group` - A group for which a conjugacy class transversal should be logged nicely.
pub(crate) fn log_cc_transversal<G>(group: &G)
where
    G: ClassPropertiesSummary,
    G::GroupElement: fmt::Display,
{
    log_subtitle("Conjugacy class transversal");
    qsym2_output!("");
    group.class_transversal_to_string().log_output_display();
    qsym2_output!("");
}

// =================
// Macro definitions
// =================

macro_rules! fn_construct_unitary_group {
    ( $(#[$meta:meta])* $vis:vis $func:ident ) => {
        $(#[$meta])*
        $vis fn $func(&self) -> Result<UnitaryRepresentedSymmetryGroup, anyhow::Error> {
            let params = self.parameters;
            let sym = match params.use_magnetic_group {
                Some(MagneticSymmetryAnalysisKind::Representation) => self.symmetry_group
                    .magnetic_symmetry
                    .as_ref()
                    .ok_or_else(|| {
                        format_err!(
                            "Magnetic symmetry requested for analysis, but no magnetic symmetry found."
                        )
                    })?,
                Some(MagneticSymmetryAnalysisKind::Corepresentation) => bail!("Magnetic corepresentations requested, but unitary-represented group is being constructed."),
                None => &self.symmetry_group.unitary_symmetry
            };
            let group = if params.use_double_group {
                UnitaryRepresentedGroup::from_molecular_symmetry(sym, params.infinite_order_to_finite)?
                    .to_double_group()?
            } else {
                UnitaryRepresentedGroup::from_molecular_symmetry(sym, params.infinite_order_to_finite)?
            };

            qsym2_output!(
                "Unitary-represented group for representation analysis: {}",
                group.name()
            );
            qsym2_output!("");
            if let Some(chartab_display) = params.write_character_table.as_ref() {
                log_subtitle("Character table of irreducible representations");
                qsym2_output!("");
                match chartab_display {
                    CharacterTableDisplay::Symbolic => {
                        group.character_table().log_output_debug();
                        "Any `En` in a character value denotes the first primitive n-th root of unity:\n  \
                        En = exp(2πi/n)".log_output_display();
                    }
                    CharacterTableDisplay::Numerical => group.character_table().log_output_display(),
                }
                qsym2_output!("");
                qsym2_output!("The symbol `◈` indicates the principal class of the group.");
                qsym2_output!("");
                "Note 1: `FS` contains the classification of the irreps using the Frobenius--Schur indicator:\n  \
                `r` = real: the irrep and its complex-conjugate partner are real and identical,\n  \
                `c` = complex: the irrep and its complex-conjugate partner are complex and inequivalent,\n  \
                `q` = quaternion: the irrep and its complex-conjugate partner are complex and equivalent.\n\n\
                Note 2: The conjugacy classes are sorted according to the following order:\n  \
                E -> C_n (n descending) -> C2 -> i -> S_n (n decending) -> σ\n  \
                Within each order and power, elements with axes close to Cartesian axes are put first.\n  \
                Within each equi-inclination from Cartesian axes, z-inclined axes are put first, then y, then x.\n\n\
                Note 3: The Mulliken labels generated for the irreps in the table above are internally consistent.\n  \
                However, certain labels might differ from those tabulated elsewhere using other conventions.\n  \
                If need be, please check with other literature to ensure external consistency.".log_output_display();
                qsym2_output!("");
            }
            Ok(group)
        }
    }
}

macro_rules! fn_construct_magnetic_group {
    ( $(#[$meta:meta])* $vis:vis $func:ident ) => {
        $(#[$meta])*
        $vis fn $func(&self) -> Result<MagneticRepresentedSymmetryGroup, anyhow::Error> {
            let params = self.parameters;
            let sym = match params.use_magnetic_group {
                Some(MagneticSymmetryAnalysisKind::Corepresentation) => self.symmetry_group
                    .magnetic_symmetry
                    .as_ref()
                    .ok_or_else(|| {
                        format_err!(
                            "Magnetic symmetry requested for analysis, but no magnetic symmetry found."
                        )
                    })?,
                Some(MagneticSymmetryAnalysisKind::Representation) => bail!("Unitary representations requested, but magnetic-represented group is being constructed."),
                None => &self.symmetry_group.unitary_symmetry
            };
            let group = if params.use_double_group {
                MagneticRepresentedGroup::from_molecular_symmetry(sym, params.infinite_order_to_finite)?
                    .to_double_group()?
            } else {
                MagneticRepresentedGroup::from_molecular_symmetry(sym, params.infinite_order_to_finite)?
            };

            qsym2_output!(
                "Magnetic-represented group for corepresentation analysis: {}",
                group.name()
            );
            qsym2_output!("");

            if let Some(chartab_display) = params.write_character_table.as_ref() {
                log_subtitle("Character table of irreducible corepresentations");
                qsym2_output!("");
                match chartab_display {
                    CharacterTableDisplay::Symbolic => {
                        group.character_table().log_output_debug();
                        "Any `En` in a character value denotes the first primitive n-th root of unity:\n  \
                        En = exp(2πi/n)".log_output_display();
                    }
                    CharacterTableDisplay::Numerical => group.character_table().log_output_display(),
                }
                qsym2_output!("");
                qsym2_output!("The symbol `◈` indicates the principal class of the group.");
                qsym2_output!("");
                "Note 1: The ircorep notation `D[Δ]` means that this ircorep is induced by the representation Δ\n  \
                of the unitary halving subgroup. The exact nature of Δ determines the kind of D[Δ].\n\n\
                Note 2: `IN` shows the intertwining numbers of the ircoreps which classify them into three kinds:\n  \
                `1` = 1st kind: the ircorep is induced by a single irrep of the unitary halving subgroup once,\n  \
                `4` = 2nd kind: the ircorep is induced by a single irrep of the unitary halving subgroup twice,\n  \
                `2` = 3rd kind: the ircorep is induced by an irrep of the unitary halving subgroup and its Wigner conjugate.\n\n\
                Note 3: Only unitary-represented elements are shown in the character table, as characters of\n  \
                antiunitary-represented elements are not invariant under a change of basis.\n\n\
                Refs:\n  \
                Newmarch, J. D. & Golding, R. M. J. Math. Phys. 23, 695–704 (1982)\n  \
                Bradley, C. J. & Davies, B. L. Rev. Mod. Phys. 40, 359–379 (1968)\n  \
                Newmarch, J. D. J. Math. Phys. 24, 742–756 (1983)".log_output_display();
                qsym2_output!("");
            }

            Ok(group)
        }
    }
}

pub(crate) use fn_construct_magnetic_group;
pub(crate) use fn_construct_unitary_group;