qsym2/bindings/python/representation_analysis/
slater_determinant.rs

1//! Python bindings for QSym² symmetry analysis of Slater determinants.
2
3use std::fmt;
4use std::path::PathBuf;
5
6use anyhow::format_err;
7use ndarray::{Array1, Array2};
8use num_complex::Complex;
9use numpy::{PyArray1, PyArray2, PyArrayMethods, ToPyArray};
10use pyo3::exceptions::{PyIOError, PyRuntimeError};
11use pyo3::prelude::*;
12
13use crate::analysis::EigenvalueComparisonMode;
14use crate::angmom::spinor_rotation_3d::{SpinConstraint, SpinOrbitCoupled, StructureConstraint};
15use crate::auxiliary::molecule::Molecule;
16use crate::basis::ao::BasisAngularOrder;
17use crate::bindings::python::integrals::{PyBasisAngularOrder, PyStructureConstraint};
18use crate::bindings::python::representation_analysis::{PyArray2RC, PyArray4RC};
19use crate::drivers::representation_analysis::angular_function::AngularFunctionRepAnalysisParams;
20use crate::drivers::representation_analysis::slater_determinant::{
21    SlaterDeterminantRepAnalysisDriver, SlaterDeterminantRepAnalysisParams,
22};
23use crate::drivers::representation_analysis::{
24    CharacterTableDisplay, MagneticSymmetryAnalysisKind,
25};
26use crate::drivers::symmetry_group_detection::SymmetryGroupDetectionResult;
27use crate::drivers::QSym2Driver;
28use crate::group::GroupProperties;
29use crate::io::format::qsym2_output;
30use crate::io::{read_qsym2_binary, QSym2FileType};
31use crate::symmetry::symmetry_group::{
32    MagneticRepresentedSymmetryGroup, UnitaryRepresentedSymmetryGroup,
33};
34use crate::symmetry::symmetry_transformation::SymmetryTransformationKind;
35use crate::target::determinant::SlaterDeterminant;
36
37type C128 = Complex<f64>;
38
39// ==================
40// Struct definitions
41// ==================
42
43// ------------------
44// Slater determinant
45// ------------------
46
47// ~~~~~~~~~~~~
48// Real, no SOC
49// ~~~~~~~~~~~~
50
51/// Python-exposed structure to marshall real Slater determinant information between Rust and
52/// Python.
53///
54/// # Constructor arguments
55///
56/// * `structure_constraint` - The structure constraint applied to the coefficients of the determinant.
57/// Python type: `PySpinConstraint | PySpinOrbitCoupled`.
58/// * `complex_symmetric` - A boolean indicating if inner products involving this determinant
59/// are complex-symmetric. Python type: `bool`.
60/// * `coefficients` - The real coefficients for the molecular orbitals of this determinant.
61/// Python type: `list[numpy.2darray[float]]`.
62/// * `occupations` - The occupation patterns for the molecular orbitals. Python type:
63/// `list[numpy.1darray[float]]`.
64/// * `threshold` - The threshold for comparisons. Python type: `float`.
65/// * `mo_energies` - The optional real molecular orbital energies. Python type:
66/// `Optional[list[numpy.1darray[float]]]`.
67/// * `energy` - The optional real determinantal energy. Python type: `Optional[float]`.
68#[pyclass]
69#[derive(Clone)]
70pub struct PySlaterDeterminantReal {
71    /// The structure constraint applied to the coefficients of the determinant.
72    ///
73    /// Python type: `PySpinConstraint | PySpinOrbitCoupled`.
74    structure_constraint: PyStructureConstraint,
75
76    /// A boolean indicating if inner products involving this determinant are complex-symmetric.
77    ///
78    /// Python type: `bool`.
79    #[pyo3(get)]
80    complex_symmetric: bool,
81
82    /// The real coefficients for the molecular orbitals of this determinant.
83    ///
84    /// Python type: `list[numpy.2darray[float]]`.
85    coefficients: Vec<Array2<f64>>,
86
87    /// The occupation patterns for the molecular orbitals.
88    ///
89    /// Python type: `list[numpy.1darray[float]]`.
90    occupations: Vec<Array1<f64>>,
91
92    /// The threshold for comparisons.
93    ///
94    /// Python type: `float`.
95    #[pyo3(get)]
96    threshold: f64,
97
98    /// The optional real molecular orbital energies.
99    ///
100    /// Python type: `Optional[list[numpy.1darray[float]]]`.
101    mo_energies: Option<Vec<Array1<f64>>>,
102
103    /// The optional real determinantal energy.
104    ///
105    /// Python type: `Optional[float]`.
106    #[pyo3(get)]
107    energy: Option<f64>,
108}
109
110#[pymethods]
111impl PySlaterDeterminantReal {
112    /// Constructs a real Python-exposed Slater determinant.
113    ///
114    /// # Arguments
115    ///
116    /// * `structure_constraint` - The structure constraint applied to the coefficients of the determinant.
117    /// Python type: `PySpinConstraint | PySpinOrbitCoupled`.
118    /// * `complex_symmetric` - A boolean indicating if inner products involving this determinant
119    /// are complex-symmetric. Python type: `bool`.
120    /// * `coefficients` - The real coefficients for the molecular orbitals of this determinant.
121    /// Python type: `list[numpy.2darray[float]]`.
122    /// * `occupations` - The occupation patterns for the molecular orbitals. Python type:
123    /// `list[numpy.1darray[float]]`.
124    /// * `threshold` - The threshold for comparisons. Python type: `float`.
125    /// * `mo_energies` - The optional real molecular orbital energies. Python type:
126    /// `Optional[list[numpy.1darray[float]]]`.
127    /// * `energy` - The optional real determinantal energy. Python type: `Optional[float]`.
128    #[new]
129    #[pyo3(signature = (structure_constraint, complex_symmetric, coefficients, occupations, threshold, mo_energies=None, energy=None))]
130    pub(crate) fn new(
131        structure_constraint: PyStructureConstraint,
132        complex_symmetric: bool,
133        coefficients: Vec<Bound<'_, PyArray2<f64>>>,
134        occupations: Vec<Bound<'_, PyArray1<f64>>>,
135        threshold: f64,
136        mo_energies: Option<Vec<Bound<'_, PyArray1<f64>>>>,
137        energy: Option<f64>,
138    ) -> Self {
139        let det = Self {
140            structure_constraint,
141            complex_symmetric,
142            coefficients: coefficients
143                .iter()
144                .map(|pyarr| pyarr.to_owned_array())
145                .collect::<Vec<_>>(),
146            occupations: occupations
147                .iter()
148                .map(|pyarr| pyarr.to_owned_array())
149                .collect::<Vec<_>>(),
150            threshold,
151            mo_energies: mo_energies.map(|energies| {
152                energies
153                    .iter()
154                    .map(|pyarr| pyarr.to_owned_array())
155                    .collect::<Vec<_>>()
156            }),
157            energy,
158        };
159        det
160    }
161
162    #[getter]
163    fn occupations<'py>(&self, py: Python<'py>) -> PyResult<Vec<Bound<'py, PyArray1<f64>>>> {
164        Ok(self
165            .occupations
166            .iter()
167            .map(|occ| occ.to_pyarray(py))
168            .collect::<Vec<_>>())
169    }
170
171    #[getter]
172    fn coefficients<'py>(&self, py: Python<'py>) -> PyResult<Vec<Bound<'py, PyArray2<f64>>>> {
173        Ok(self
174            .coefficients
175            .iter()
176            .map(|occ| occ.to_pyarray(py))
177            .collect::<Vec<_>>())
178    }
179}
180
181impl PySlaterDeterminantReal {
182    /// Extracts the information in the [`PySlaterDeterminantReal`] structure into `QSym2`'s native
183    /// [`SlaterDeterminant`] structure.
184    ///
185    /// # Arguments
186    ///
187    /// * `bao` - The [`BasisAngularOrder`] for the basis set in which the Slater determinant is
188    /// given.
189    /// * `mol` - The molecule with which the Slater determinant is associated.
190    ///
191    /// # Returns
192    ///
193    /// The [`SlaterDeterminant`] structure with the same information.
194    ///
195    /// # Errors
196    ///
197    /// Errors if the [`SlaterDeterminant`] fails to build.
198    pub(crate) fn to_qsym2<'b, 'a: 'b, SC>(
199        &'b self,
200        bao: &'a BasisAngularOrder,
201        mol: &'a Molecule,
202    ) -> Result<SlaterDeterminant<'b, f64, SC>, anyhow::Error>
203    where
204        SC: StructureConstraint
205            + Clone
206            + fmt::Display
207            + TryFrom<PyStructureConstraint, Error = anyhow::Error>,
208    {
209        let det = SlaterDeterminant::<f64, SC>::builder()
210            .structure_constraint(self.structure_constraint.clone().try_into()?)
211            .bao(bao)
212            .complex_symmetric(self.complex_symmetric)
213            .mol(mol)
214            .coefficients(&self.coefficients)
215            .occupations(&self.occupations)
216            .mo_energies(self.mo_energies.clone())
217            .energy(
218                self.energy
219                    .ok_or_else(|| "No determinantal energy set.".to_string()),
220            )
221            .threshold(self.threshold)
222            .build()
223            .map_err(|err| format_err!(err));
224        det
225    }
226}
227
228/// Python-exposed structure to marshall complex Slater determinant information between Rust and
229/// Python.
230///
231/// # Constructor arguments
232///
233/// * `structure_constraint` - The spin constraint applied to the coefficients of the determinant.
234/// Python type: `PySpinConstraint | PySpinOrbitCoupled`.
235/// * `complex_symmetric` - A boolean indicating if inner products involving this determinant
236/// are complex-symmetric. Python type: `bool`.
237/// * `coefficients` - The complex coefficients for the molecular orbitals of this determinant.
238/// Python type: `list[numpy.2darray[float]]`.
239/// * `occupations` - The occupation patterns for the molecular orbitals. Python type:
240/// `list[numpy.1darray[float]]`.
241/// * `threshold` - The threshold for comparisons. Python type: `float`.
242/// * `mo_energies` - The optional complex molecular orbital energies. Python type:
243/// `Optional[list[numpy.1darray[complex]]]`.
244/// * `energy` - The optional complex determinantal energy. Python type: `Optional[complex]`.
245#[pyclass]
246#[derive(Clone)]
247pub struct PySlaterDeterminantComplex {
248    /// The structure constraint applied to the coefficients of the determinant.
249    ///
250    /// Python type: `PySpinConstraint | PySpinOrbitCoupled`.
251    structure_constraint: PyStructureConstraint,
252
253    /// A boolean indicating if inner products involving this determinant are complex-symmetric.
254    ///
255    /// Python type: `bool`.
256    #[pyo3(get)]
257    complex_symmetric: bool,
258
259    /// The complex coefficients for the molecular orbitals of this determinant.
260    ///
261    /// Python type: `list[numpy.2darray[complex]]`.
262    coefficients: Vec<Array2<C128>>,
263
264    /// The occupation patterns for the molecular orbitals.
265    ///
266    /// Python type: `list[numpy.1darray[float]]`.
267    occupations: Vec<Array1<f64>>,
268
269    /// The threshold for comparisons.
270    ///
271    /// Python type: `float`.
272    #[pyo3(get)]
273    threshold: f64,
274
275    /// The optional complex molecular orbital energies.
276    ///
277    /// Python type: `Optional[list[numpy.1darray[complex]]]`.
278    mo_energies: Option<Vec<Array1<C128>>>,
279
280    /// The optional complex determinantal energy.
281    ///
282    /// Python type: `Optional[complex]`.
283    #[pyo3(get)]
284    energy: Option<C128>,
285}
286
287#[pymethods]
288impl PySlaterDeterminantComplex {
289    /// Constructs a complex Python-exposed Slater determinant.
290    ///
291    /// # Arguments
292    ///
293    /// * `structure_constraint` - The structure constraint applied to the coefficients of the
294    /// determinant. Python type: `PySpinConstraint | PySpinOrbitCoupled`.
295    /// * `complex_symmetric` - A boolean indicating if inner products involving this determinant
296    /// are complex-symmetric. Python type: `bool`.
297    /// * `coefficients` - The complex coefficients for the molecular orbitals of this determinant.
298    /// Python type: `list[numpy.2darray[float]]`.
299    /// * `occupations` - The occupation patterns for the molecular orbitals. Python type:
300    /// `list[numpy.1darray[float]]`.
301    /// * `threshold` - The threshold for comparisons. Python type: `float`.
302    /// * `mo_energies` - The optional complex molecular orbital energies. Python type:
303    /// `Optional[list[numpy.1darray[complex]]]`.
304    /// * `energy` - The optional complex determinantal energy. Python type: `Optional[complex]`.
305    #[new]
306    #[pyo3(signature = (structure_constraint, complex_symmetric, coefficients, occupations, threshold, mo_energies=None, energy=None))]
307    pub(crate) fn new(
308        structure_constraint: PyStructureConstraint,
309        complex_symmetric: bool,
310        coefficients: Vec<Bound<'_, PyArray2<C128>>>,
311        occupations: Vec<Bound<'_, PyArray1<f64>>>,
312        threshold: f64,
313        mo_energies: Option<Vec<Bound<'_, PyArray1<C128>>>>,
314        energy: Option<C128>,
315    ) -> Self {
316        let det = Self {
317            structure_constraint,
318            complex_symmetric,
319            coefficients: coefficients
320                .iter()
321                .map(|pyarr| pyarr.to_owned_array())
322                .collect::<Vec<_>>(),
323            occupations: occupations
324                .iter()
325                .map(|pyarr| pyarr.to_owned_array())
326                .collect::<Vec<_>>(),
327            threshold,
328            mo_energies: mo_energies.map(|energies| {
329                energies
330                    .iter()
331                    .map(|pyarr| pyarr.to_owned_array())
332                    .collect::<Vec<_>>()
333            }),
334            energy,
335        };
336        det
337    }
338
339    #[getter]
340    fn occupations<'py>(&self, py: Python<'py>) -> PyResult<Vec<Bound<'py, PyArray1<f64>>>> {
341        Ok(self
342            .occupations
343            .iter()
344            .map(|occ| occ.to_pyarray(py))
345            .collect::<Vec<_>>())
346    }
347
348    #[getter]
349    fn coefficients<'py>(&self, py: Python<'py>) -> PyResult<Vec<Bound<'py, PyArray2<C128>>>> {
350        Ok(self
351            .coefficients
352            .iter()
353            .map(|occ| occ.to_pyarray(py))
354            .collect::<Vec<_>>())
355    }
356}
357
358impl PySlaterDeterminantComplex {
359    /// Extracts the information in the [`PySlaterDeterminantComplex`] structure into `QSym2`'s native
360    /// [`SlaterDeterminant`] structure.
361    ///
362    /// # Arguments
363    ///
364    /// * `bao` - The [`BasisAngularOrder`] for the basis set in which the Slater determinant is
365    /// given.
366    /// * `mol` - The molecule with which the Slater determinant is associated.
367    ///
368    /// # Returns
369    ///
370    /// The [`SlaterDeterminant`] structure with the same information.
371    ///
372    /// # Errors
373    ///
374    /// Errors if the [`SlaterDeterminant`] fails to build.
375    pub(crate) fn to_qsym2<'b, 'a: 'b, SC>(
376        &'b self,
377        bao: &'a BasisAngularOrder,
378        mol: &'a Molecule,
379    ) -> Result<SlaterDeterminant<'b, C128, SC>, anyhow::Error>
380    where
381        SC: StructureConstraint
382            + Clone
383            + fmt::Display
384            + TryFrom<PyStructureConstraint, Error = anyhow::Error>,
385    {
386        let det = SlaterDeterminant::<C128, SC>::builder()
387            .structure_constraint(self.structure_constraint.clone().try_into()?)
388            .bao(bao)
389            .complex_symmetric(self.complex_symmetric)
390            .mol(mol)
391            .coefficients(&self.coefficients)
392            .occupations(&self.occupations)
393            .mo_energies(self.mo_energies.clone())
394            .energy(
395                self.energy
396                    .ok_or_else(|| "No determinantal energy set.".to_string()),
397            )
398            .threshold(self.threshold)
399            .build()
400            .map_err(|err| format_err!(err));
401        det
402    }
403}
404
405// --------------------------------------------
406// Slater determinant symmetry analysis results
407// --------------------------------------------
408
409/// Python-exposed structure storing the results of Slater determinant representation analysis.
410#[pyclass]
411#[derive(Clone)]
412pub struct PySlaterDeterminantRepAnalysisResult {
413    /// The group used for the representation analysis.
414    #[pyo3(get)]
415    group: String,
416
417    /// The deduced overall symmetry of the determinant.
418    #[pyo3(get)]
419    determinant_symmetry: Option<String>,
420
421    /// The deduced symmetries of the molecular orbitals constituting the determinant, if required.
422    #[pyo3(get)]
423    mo_symmetries: Option<Vec<Vec<Option<String>>>>,
424
425    /// The deduced symmetries of the various densities constructible from the determinant, if
426    /// required. In each tuple, the first element gives a description of the density corresponding
427    /// to the symmetry result.
428    #[pyo3(get)]
429    determinant_density_symmetries: Option<Vec<(String, Option<String>)>>,
430
431    /// The deduced symmetries of the total densities of the molecular orbitals constituting the
432    /// determinant, if required.
433    #[pyo3(get)]
434    mo_density_symmetries: Option<Vec<Vec<Option<String>>>>,
435}
436
437// ================
438// Enum definitions
439// ================
440
441/// Python-exposed enumerated type to handle the union type
442/// `PySlaterDeterminantReal | PySlaterDeterminantComplex` in Python.
443#[derive(FromPyObject)]
444pub enum PySlaterDeterminant {
445    /// Variant for real Python-exposed Slater determinant.
446    Real(PySlaterDeterminantReal),
447
448    /// Variant for complex Python-exposed Slater determinant.
449    Complex(PySlaterDeterminantComplex),
450}
451
452// =====================
453// Functions definitions
454// =====================
455
456/// Python-exposed function to perform representation symmetry analysis for real and complex
457/// Slater determinants and log the result via the `qsym2-output` logger at the `INFO` level.
458///
459/// If `symmetry_transformation_kind` includes spin transformation, the provided determinant will
460/// be augmented to generalised spin constraint automatically.
461///
462/// # Arguments
463///
464/// * `inp_sym` - A path to the [`QSym2FileType::Sym`] file containing the symmetry-group detection
465/// result for the system. This will be used to construct abstract groups and character tables for
466/// representation analysis. Python type: `str`.
467/// * `pydet` - A Python-exposed Slater determinant whose coefficients are of type `float64` or
468/// `complex128`. Python type: `PySlaterDeterminantReal | PySlaterDeterminantComplex`.
469/// * `pybao` - A Python-exposed Python-exposed structure containing basis angular order information.
470/// Python type: `PyBasisAngularOrder`.
471/// * `integrality_threshold` - The threshold for verifying if subspace multiplicities are
472/// integral. Python type: `float`.
473/// * `linear_independence_threshold` - The threshold for determining the linear independence
474/// subspace via the non-zero eigenvalues of the orbit overlap matrix. Python type: `float`.
475/// * `use_magnetic_group` - An option indicating if the magnetic group is to be used for symmetry
476/// analysis, and if so, whether unitary representations or unitary-antiunitary corepresentations
477/// should be used. Python type: `None | MagneticSymmetryAnalysisKind`.
478/// * `use_double_group` - A boolean indicating if the double group of the prevailing symmetry
479/// group is to be used for representation analysis instead. Python type: `bool`.
480/// * `use_cayley_table` - A boolean indicating if the Cayley table for the group, if available,
481/// should be used to speed up the calculation of orbit overlap matrices. Python type: `bool`.
482/// * `symmetry_transformation_kind` - An enumerated type indicating the type of symmetry
483/// transformations to be performed on the origin determinant to generate the orbit. If this
484/// contains spin transformation, the determinant will be augmented to generalised spin constraint
485/// automatically. Python type: `SymmetryTransformationKind`.
486/// * `eigenvalue_comparison_mode` - An enumerated type indicating the mode of comparison of orbit
487/// overlap eigenvalues with the specified `linear_independence_threshold`.
488/// Python type: `EigenvalueComparisonMode`.
489/// * `sao` - The atomic-orbital overlap matrix whose elements are of type `float64` or
490/// `complex128`. Python type: `numpy.2darray[float] | numpy.2darray[complex]`.
491/// * `sao_h` - The optional complex-symmetric atomic-orbital overlap matrix whose elements
492/// are of type `float64` or `complex128`. This is required if antiunitary symmetry operations are
493/// involved. Python type: `None | numpy.2darray[float] | numpy.2darray[complex]`.
494/// * `sao_spatial_4c` - The optional atomic-orbital four-centre overlap matrix whose elements are
495/// of type `float64` or `complex128`.
496/// Python type: `numpy.2darray[float] | numpy.2darray[complex] | None`.
497/// * `sao_spatial_4c_h` - The optional complex-symmetric atomic-orbital four-centre overlap matrix
498/// whose elements are of type `float64` or `complex128`. This is required if antiunitary symmetry
499/// operations are involved. Python type: `numpy.2darray[float] | numpy.2darray[complex] | None`.
500/// * `analyse_mo_symmetries` - A boolean indicating if the symmetries of individual molecular
501/// orbitals are to be analysed. Python type: `bool`.
502/// * `analyse_mo_symmetry_projections` - A boolean indicating if the symmetry projections of
503/// individual molecular orbitals are to be analysed. Python type: `bool`.
504/// * `analyse_mo_mirror_parities` - A boolean indicating if the mirror parities of individual
505/// molecular orbitals are to be printed. Python type: `bool`.
506/// * `analyse_density_symmetries` - A boolean indicating if the symmetries of densities are to be
507/// analysed. Python type: `bool`.
508/// * `write_overlap_eigenvalues` - A boolean indicating if the eigenvalues of the determinant
509/// orbit overlap matrix are to be written to the output. Python type: `bool`.
510/// * `write_character_table` - A boolean indicating if the character table of the prevailing
511/// symmetry group is to be printed out. Python type: `bool`.
512/// * `infinite_order_to_finite` - The finite order with which infinite-order generators are to be
513/// interpreted to form a finite subgroup of the prevailing infinite group. This finite subgroup
514/// will be used for symmetry analysis. Python type: `Optional[int]`.
515/// * `angular_function_integrality_threshold` - The threshold for verifying if subspace
516/// multiplicities are integral for the symmetry analysis of angular functions. Python type:
517/// `float`.
518/// * `angular_function_linear_independence_threshold` - The threshold for determining the linear
519/// independence subspace via the non-zero eigenvalues of the orbit overlap matrix for the symmetry
520/// analysis of angular functions. Python type: `float`.
521/// * `angular_function_max_angular_momentum` - The maximum angular momentum order to be used in
522/// angular function symmetry analysis. Python type: `int`.
523///
524/// # Returns
525///
526/// A Python-exposed [`PySlaterDeterminantRepAnalysisResult`] structure containing the results of the
527/// representation analysis. Python type: `PySlaterDeterminantRepAnalysisResult`.
528#[pyfunction]
529#[pyo3(signature = (
530    inp_sym,
531    pydet,
532    pybao,
533    integrality_threshold,
534    linear_independence_threshold,
535    use_magnetic_group,
536    use_double_group,
537    use_cayley_table,
538    symmetry_transformation_kind,
539    eigenvalue_comparison_mode,
540    sao,
541    sao_h=None,
542    sao_spatial_4c=None,
543    sao_spatial_4c_h=None,
544    analyse_mo_symmetries=true,
545    analyse_mo_symmetry_projections=true,
546    analyse_mo_mirror_parities=false,
547    analyse_density_symmetries=false,
548    write_overlap_eigenvalues=true,
549    write_character_table=true,
550    infinite_order_to_finite=None,
551    angular_function_integrality_threshold=1e-7,
552    angular_function_linear_independence_threshold=1e-7,
553    angular_function_max_angular_momentum=2
554))]
555pub fn rep_analyse_slater_determinant(
556    py: Python<'_>,
557    inp_sym: PathBuf,
558    pydet: PySlaterDeterminant,
559    pybao: &PyBasisAngularOrder,
560    integrality_threshold: f64,
561    linear_independence_threshold: f64,
562    use_magnetic_group: Option<MagneticSymmetryAnalysisKind>,
563    use_double_group: bool,
564    use_cayley_table: bool,
565    symmetry_transformation_kind: SymmetryTransformationKind,
566    eigenvalue_comparison_mode: EigenvalueComparisonMode,
567    sao: PyArray2RC,
568    sao_h: Option<PyArray2RC>,
569    sao_spatial_4c: Option<PyArray4RC>,
570    sao_spatial_4c_h: Option<PyArray4RC>,
571    analyse_mo_symmetries: bool,
572    analyse_mo_symmetry_projections: bool,
573    analyse_mo_mirror_parities: bool,
574    analyse_density_symmetries: bool,
575    write_overlap_eigenvalues: bool,
576    write_character_table: bool,
577    infinite_order_to_finite: Option<u32>,
578    angular_function_integrality_threshold: f64,
579    angular_function_linear_independence_threshold: f64,
580    angular_function_max_angular_momentum: u32,
581) -> PyResult<PySlaterDeterminantRepAnalysisResult> {
582    let pd_res: SymmetryGroupDetectionResult =
583        read_qsym2_binary(inp_sym.clone(), QSym2FileType::Sym)
584            .map_err(|err| PyIOError::new_err(err.to_string()))?;
585
586    let mut file_name = inp_sym.to_path_buf();
587    file_name.set_extension(QSym2FileType::Sym.ext());
588    qsym2_output!(
589        "Symmetry-group detection results read in from {}.",
590        file_name.display(),
591    );
592    qsym2_output!("");
593
594    let mol = &pd_res.pre_symmetry.recentred_molecule;
595    let bao = pybao
596        .to_qsym2(mol)
597        .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
598    let augment_to_generalised = match symmetry_transformation_kind {
599        SymmetryTransformationKind::SpatialWithSpinTimeReversal
600        | SymmetryTransformationKind::Spin
601        | SymmetryTransformationKind::SpinSpatial => true,
602        SymmetryTransformationKind::Spatial => false,
603    };
604    let afa_params = AngularFunctionRepAnalysisParams::builder()
605        .integrality_threshold(angular_function_integrality_threshold)
606        .linear_independence_threshold(angular_function_linear_independence_threshold)
607        .max_angular_momentum(angular_function_max_angular_momentum)
608        .build()
609        .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
610    let sda_params = SlaterDeterminantRepAnalysisParams::<f64>::builder()
611        .integrality_threshold(integrality_threshold)
612        .linear_independence_threshold(linear_independence_threshold)
613        .use_magnetic_group(use_magnetic_group.clone())
614        .use_double_group(use_double_group)
615        .use_cayley_table(use_cayley_table)
616        .symmetry_transformation_kind(symmetry_transformation_kind)
617        .eigenvalue_comparison_mode(eigenvalue_comparison_mode)
618        .analyse_mo_symmetries(analyse_mo_symmetries)
619        .analyse_mo_symmetry_projections(analyse_mo_symmetry_projections)
620        .analyse_mo_mirror_parities(analyse_mo_mirror_parities)
621        .analyse_density_symmetries(analyse_density_symmetries)
622        .write_overlap_eigenvalues(write_overlap_eigenvalues)
623        .write_character_table(if write_character_table {
624            Some(CharacterTableDisplay::Symbolic)
625        } else {
626            None
627        })
628        .infinite_order_to_finite(infinite_order_to_finite)
629        .build()
630        .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
631    let pysda_res: PySlaterDeterminantRepAnalysisResult = match (&pydet, &sao) {
632        (PySlaterDeterminant::Real(pydet_r), PyArray2RC::Real(pysao_r)) => {
633            if matches!(
634                pydet_r.structure_constraint,
635                PyStructureConstraint::SpinOrbitCoupled(_)
636            ) {
637                return Err(PyRuntimeError::new_err("Real determinants are not compatible with spin--orbit-coupled structure constraint.".to_string()));
638            }
639
640            let sao = pysao_r.to_owned_array();
641            let sao_spatial_4c = sao_spatial_4c.and_then(|pysao4c| match pysao4c {
642                // sao_spatial_4c must have the same reality as sao_spatial.
643                PyArray4RC::Real(pysao4c_r) => Some(pysao4c_r.to_owned_array()),
644                PyArray4RC::Complex(_) => None,
645            });
646
647            let det_r = if augment_to_generalised {
648                pydet_r
649                    .to_qsym2::<SpinConstraint>(&bao, mol)
650                    .map_err(|err| PyRuntimeError::new_err(err.to_string()))?
651                    .to_generalised()
652            } else {
653                pydet_r
654                    .to_qsym2::<SpinConstraint>(&bao, mol)
655                    .map_err(|err| PyRuntimeError::new_err(err.to_string()))?
656            };
657            match &use_magnetic_group {
658                Some(MagneticSymmetryAnalysisKind::Corepresentation) => {
659                    let mut sda_driver = SlaterDeterminantRepAnalysisDriver::<
660                        MagneticRepresentedSymmetryGroup,
661                        f64,
662                        SpinConstraint,
663                    >::builder()
664                    .parameters(&sda_params)
665                    .angular_function_parameters(&afa_params)
666                    .determinant(&det_r)
667                    .sao(&sao)
668                    .sao_h(None) // Real SAO.
669                    .sao_spatial_4c(sao_spatial_4c.as_ref())
670                    .sao_spatial_4c_h(None) // Real SAO.
671                    .symmetry_group(&pd_res)
672                    .build()
673                    .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
674                    py.allow_threads(|| {
675                        sda_driver
676                            .run()
677                            .map_err(|err| PyRuntimeError::new_err(err.to_string()))
678                    })?;
679                    let sda_res = sda_driver
680                        .result()
681                        .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
682                    PySlaterDeterminantRepAnalysisResult {
683                        group: sda_res.group().name().clone(),
684                        determinant_symmetry: sda_res
685                            .determinant_symmetry()
686                            .as_ref()
687                            .ok()
688                            .map(|sym| sym.to_string()),
689                        mo_symmetries: sda_res.mo_symmetries().as_ref().map(|mo_symss| {
690                            mo_symss
691                                .iter()
692                                .map(|mo_syms| {
693                                    mo_syms
694                                        .iter()
695                                        .map(|mo_sym_opt| {
696                                            mo_sym_opt.as_ref().map(|mo_sym| mo_sym.to_string())
697                                        })
698                                        .collect::<Vec<_>>()
699                                })
700                                .collect::<Vec<_>>()
701                        }),
702                        determinant_density_symmetries: sda_res
703                            .determinant_density_symmetries()
704                            .as_ref()
705                            .map(|den_syms| {
706                                den_syms
707                                    .iter()
708                                    .map(|(den_name, den_sym_res)| {
709                                        (
710                                            den_name.clone(),
711                                            den_sym_res
712                                                .as_ref()
713                                                .ok()
714                                                .map(|den_sym| den_sym.to_string()),
715                                        )
716                                    })
717                                    .collect::<Vec<_>>()
718                            }),
719                        mo_density_symmetries: sda_res.mo_density_symmetries().as_ref().map(
720                            |mo_den_symss| {
721                                mo_den_symss
722                                    .iter()
723                                    .map(|mo_den_syms| {
724                                        mo_den_syms
725                                            .iter()
726                                            .map(|mo_den_sym_opt| {
727                                                mo_den_sym_opt
728                                                    .as_ref()
729                                                    .map(|mo_den_sym| mo_den_sym.to_string())
730                                            })
731                                            .collect::<Vec<_>>()
732                                    })
733                                    .collect::<Vec<_>>()
734                            },
735                        ),
736                    }
737                }
738                Some(MagneticSymmetryAnalysisKind::Representation) | None => {
739                    let mut sda_driver = SlaterDeterminantRepAnalysisDriver::<
740                        UnitaryRepresentedSymmetryGroup,
741                        f64,
742                        SpinConstraint,
743                    >::builder()
744                    .parameters(&sda_params)
745                    .angular_function_parameters(&afa_params)
746                    .determinant(&det_r)
747                    .sao(&sao)
748                    .sao_h(None) // Real SAO.
749                    .sao_spatial_4c(sao_spatial_4c.as_ref())
750                    .sao_spatial_4c_h(None) // Real SAO.
751                    .symmetry_group(&pd_res)
752                    .build()
753                    .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
754                    py.allow_threads(|| {
755                        sda_driver
756                            .run()
757                            .map_err(|err| PyRuntimeError::new_err(err.to_string()))
758                    })?;
759                    let sda_res = sda_driver
760                        .result()
761                        .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
762                    PySlaterDeterminantRepAnalysisResult {
763                        group: sda_res.group().name().clone(),
764                        determinant_symmetry: sda_res
765                            .determinant_symmetry()
766                            .as_ref()
767                            .ok()
768                            .map(|sym| sym.to_string()),
769                        mo_symmetries: sda_res.mo_symmetries().as_ref().map(|mo_symss| {
770                            mo_symss
771                                .iter()
772                                .map(|mo_syms| {
773                                    mo_syms
774                                        .iter()
775                                        .map(|mo_sym_opt| {
776                                            mo_sym_opt.as_ref().map(|mo_sym| mo_sym.to_string())
777                                        })
778                                        .collect::<Vec<_>>()
779                                })
780                                .collect::<Vec<_>>()
781                        }),
782                        determinant_density_symmetries: sda_res
783                            .determinant_density_symmetries()
784                            .as_ref()
785                            .map(|den_syms| {
786                                den_syms
787                                    .iter()
788                                    .map(|(den_name, den_sym_res)| {
789                                        (
790                                            den_name.clone(),
791                                            den_sym_res
792                                                .as_ref()
793                                                .ok()
794                                                .map(|den_sym| den_sym.to_string()),
795                                        )
796                                    })
797                                    .collect::<Vec<_>>()
798                            }),
799                        mo_density_symmetries: sda_res.mo_density_symmetries().as_ref().map(
800                            |mo_den_symss| {
801                                mo_den_symss
802                                    .iter()
803                                    .map(|mo_den_syms| {
804                                        mo_den_syms
805                                            .iter()
806                                            .map(|mo_den_sym_opt| {
807                                                mo_den_sym_opt
808                                                    .as_ref()
809                                                    .map(|mo_den_sym| mo_den_sym.to_string())
810                                            })
811                                            .collect::<Vec<_>>()
812                                    })
813                                    .collect::<Vec<_>>()
814                            },
815                        ),
816                    }
817                }
818            }
819        }
820        (PySlaterDeterminant::Real(pydet_r), PyArray2RC::Complex(pysao_c)) => {
821            match pydet_r.structure_constraint {
822                PyStructureConstraint::SpinConstraint(_) => {
823                    let det_r = if augment_to_generalised {
824                        pydet_r
825                            .to_qsym2(&bao, mol)
826                            .map_err(|err| PyRuntimeError::new_err(err.to_string()))?
827                            .to_generalised()
828                    } else {
829                        pydet_r
830                            .to_qsym2(&bao, mol)
831                            .map_err(|err| PyRuntimeError::new_err(err.to_string()))?
832                    };
833                    let det_c: SlaterDeterminant<C128, SpinConstraint> = det_r.into();
834                    let sao_c = pysao_c.to_owned_array();
835                    let sao_h_c = sao_h.and_then(|pysao_h| match pysao_h {
836                        // sao_spatial_h must have the same reality as sao_spatial.
837                        PyArray2RC::Real(_) => None,
838                        PyArray2RC::Complex(pysao_h_c) => Some(pysao_h_c.to_owned_array()),
839                    });
840                    let sao_spatial_4c_c = sao_spatial_4c.and_then(|pysao4c| match pysao4c {
841                        // sao_spatial_4c must have the same reality as sao_spatial.
842                        PyArray4RC::Real(_) => None,
843                        PyArray4RC::Complex(pysao4c_c) => Some(pysao4c_c.to_owned_array()),
844                    });
845                    let sao_spatial_4c_h_c =
846                        sao_spatial_4c_h.and_then(|pysao4c_h| match pysao4c_h {
847                            // sao_spatial_4c_h must have the same reality as sao_spatial.
848                            PyArray4RC::Real(_) => None,
849                            PyArray4RC::Complex(pysao4c_h_c) => Some(pysao4c_h_c.to_owned_array()),
850                        });
851                    match &use_magnetic_group {
852                        Some(MagneticSymmetryAnalysisKind::Corepresentation) => {
853                            let mut sda_driver = SlaterDeterminantRepAnalysisDriver::<
854                                MagneticRepresentedSymmetryGroup,
855                                C128,
856                                SpinConstraint,
857                            >::builder()
858                            .parameters(&sda_params)
859                            .angular_function_parameters(&afa_params)
860                            .determinant(&det_c)
861                            .sao(&sao_c)
862                            .sao_h(sao_h_c.as_ref())
863                            .sao_spatial_4c(sao_spatial_4c_c.as_ref())
864                            .sao_spatial_4c_h(sao_spatial_4c_h_c.as_ref())
865                            .symmetry_group(&pd_res)
866                            .build()
867                            .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
868                            py.allow_threads(|| {
869                                sda_driver
870                                    .run()
871                                    .map_err(|err| PyRuntimeError::new_err(err.to_string()))
872                            })?;
873                            let sda_res = sda_driver
874                                .result()
875                                .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
876                            PySlaterDeterminantRepAnalysisResult {
877                                group: sda_res.group().name().clone(),
878                                determinant_symmetry: sda_res
879                                    .determinant_symmetry()
880                                    .as_ref()
881                                    .ok()
882                                    .map(|sym| sym.to_string()),
883                                mo_symmetries: sda_res.mo_symmetries().as_ref().map(|mo_symss| {
884                                    mo_symss
885                                        .iter()
886                                        .map(|mo_syms| {
887                                            mo_syms
888                                                .iter()
889                                                .map(|mo_sym_opt| {
890                                                    mo_sym_opt
891                                                        .as_ref()
892                                                        .map(|mo_sym| mo_sym.to_string())
893                                                })
894                                                .collect::<Vec<_>>()
895                                        })
896                                        .collect::<Vec<_>>()
897                                }),
898                                determinant_density_symmetries: sda_res
899                                    .determinant_density_symmetries()
900                                    .as_ref()
901                                    .map(|den_syms| {
902                                        den_syms
903                                            .iter()
904                                            .map(|(den_name, den_sym_res)| {
905                                                (
906                                                    den_name.clone(),
907                                                    den_sym_res
908                                                        .as_ref()
909                                                        .ok()
910                                                        .map(|den_sym| den_sym.to_string()),
911                                                )
912                                            })
913                                            .collect::<Vec<_>>()
914                                    }),
915                                mo_density_symmetries: sda_res
916                                    .mo_density_symmetries()
917                                    .as_ref()
918                                    .map(|mo_den_symss| {
919                                        mo_den_symss
920                                            .iter()
921                                            .map(|mo_den_syms| {
922                                                mo_den_syms
923                                                    .iter()
924                                                    .map(|mo_den_sym_opt| {
925                                                        mo_den_sym_opt.as_ref().map(|mo_den_sym| {
926                                                            mo_den_sym.to_string()
927                                                        })
928                                                    })
929                                                    .collect::<Vec<_>>()
930                                            })
931                                            .collect::<Vec<_>>()
932                                    }),
933                            }
934                        }
935                        Some(MagneticSymmetryAnalysisKind::Representation) | None => {
936                            let mut sda_driver = SlaterDeterminantRepAnalysisDriver::<
937                                UnitaryRepresentedSymmetryGroup,
938                                C128,
939                                SpinConstraint,
940                            >::builder()
941                            .parameters(&sda_params)
942                            .angular_function_parameters(&afa_params)
943                            .determinant(&det_c)
944                            .sao(&sao_c)
945                            .sao_h(sao_h_c.as_ref())
946                            .sao_spatial_4c(sao_spatial_4c_c.as_ref())
947                            .sao_spatial_4c_h(sao_spatial_4c_h_c.as_ref())
948                            .symmetry_group(&pd_res)
949                            .build()
950                            .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
951                            py.allow_threads(|| {
952                                sda_driver
953                                    .run()
954                                    .map_err(|err| PyRuntimeError::new_err(err.to_string()))
955                            })?;
956                            let sda_res = sda_driver
957                                .result()
958                                .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
959                            PySlaterDeterminantRepAnalysisResult {
960                                group: sda_res.group().name().clone(),
961                                determinant_symmetry: sda_res
962                                    .determinant_symmetry()
963                                    .as_ref()
964                                    .ok()
965                                    .map(|sym| sym.to_string()),
966                                mo_symmetries: sda_res.mo_symmetries().as_ref().map(|mo_symss| {
967                                    mo_symss
968                                        .iter()
969                                        .map(|mo_syms| {
970                                            mo_syms
971                                                .iter()
972                                                .map(|mo_sym_opt| {
973                                                    mo_sym_opt
974                                                        .as_ref()
975                                                        .map(|mo_sym| mo_sym.to_string())
976                                                })
977                                                .collect::<Vec<_>>()
978                                        })
979                                        .collect::<Vec<_>>()
980                                }),
981                                determinant_density_symmetries: sda_res
982                                    .determinant_density_symmetries()
983                                    .as_ref()
984                                    .map(|den_syms| {
985                                        den_syms
986                                            .iter()
987                                            .map(|(den_name, den_sym_res)| {
988                                                (
989                                                    den_name.clone(),
990                                                    den_sym_res
991                                                        .as_ref()
992                                                        .ok()
993                                                        .map(|den_sym| den_sym.to_string()),
994                                                )
995                                            })
996                                            .collect::<Vec<_>>()
997                                    }),
998                                mo_density_symmetries: sda_res
999                                    .mo_density_symmetries()
1000                                    .as_ref()
1001                                    .map(|mo_den_symss| {
1002                                        mo_den_symss
1003                                            .iter()
1004                                            .map(|mo_den_syms| {
1005                                                mo_den_syms
1006                                                    .iter()
1007                                                    .map(|mo_den_sym_opt| {
1008                                                        mo_den_sym_opt.as_ref().map(|mo_den_sym| {
1009                                                            mo_den_sym.to_string()
1010                                                        })
1011                                                    })
1012                                                    .collect::<Vec<_>>()
1013                                            })
1014                                            .collect::<Vec<_>>()
1015                                    }),
1016                            }
1017                        }
1018                    }
1019                }
1020                PyStructureConstraint::SpinOrbitCoupled(_) => {
1021                    let det_r = pydet_r
1022                        .to_qsym2::<SpinOrbitCoupled>(&bao, mol)
1023                        .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
1024                    let det_c: SlaterDeterminant<C128, SpinOrbitCoupled> = det_r.into();
1025                    let sao_c = pysao_c.to_owned_array();
1026                    let sao_h_c = sao_h.and_then(|pysao_h| match pysao_h {
1027                        // sao_h must have the same reality as sao.
1028                        PyArray2RC::Real(_) => None,
1029                        PyArray2RC::Complex(pysao_h_c) => Some(pysao_h_c.to_owned_array()),
1030                    });
1031                    let sao_spatial_4c_c = sao_spatial_4c.and_then(|pysao4c| match pysao4c {
1032                        // sao_spatial_4c must have the same reality as sao.
1033                        PyArray4RC::Real(_) => None,
1034                        PyArray4RC::Complex(pysao4c_c) => Some(pysao4c_c.to_owned_array()),
1035                    });
1036                    let sao_spatial_4c_h_c =
1037                        sao_spatial_4c_h.and_then(|pysao4c_h| match pysao4c_h {
1038                            // sao_spatial_4c_h must have the same reality as sao_spatial.
1039                            PyArray4RC::Real(_) => None,
1040                            PyArray4RC::Complex(pysao4c_h_c) => Some(pysao4c_h_c.to_owned_array()),
1041                        });
1042                    match &use_magnetic_group {
1043                        Some(MagneticSymmetryAnalysisKind::Corepresentation) => {
1044                            let mut sda_driver = SlaterDeterminantRepAnalysisDriver::<
1045                                MagneticRepresentedSymmetryGroup,
1046                                C128,
1047                                SpinOrbitCoupled,
1048                            >::builder()
1049                            .parameters(&sda_params)
1050                            .angular_function_parameters(&afa_params)
1051                            .determinant(&det_c)
1052                            .sao(&sao_c)
1053                            .sao_h(sao_h_c.as_ref())
1054                            .sao_spatial_4c(sao_spatial_4c_c.as_ref())
1055                            .sao_spatial_4c_h(sao_spatial_4c_h_c.as_ref())
1056                            .symmetry_group(&pd_res)
1057                            .build()
1058                            .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
1059                            py.allow_threads(|| {
1060                                sda_driver
1061                                    .run()
1062                                    .map_err(|err| PyRuntimeError::new_err(err.to_string()))
1063                            })?;
1064                            let sda_res = sda_driver
1065                                .result()
1066                                .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
1067                            PySlaterDeterminantRepAnalysisResult {
1068                                group: sda_res.group().name().clone(),
1069                                determinant_symmetry: sda_res
1070                                    .determinant_symmetry()
1071                                    .as_ref()
1072                                    .ok()
1073                                    .map(|sym| sym.to_string()),
1074                                mo_symmetries: sda_res.mo_symmetries().as_ref().map(|mo_symss| {
1075                                    mo_symss
1076                                        .iter()
1077                                        .map(|mo_syms| {
1078                                            mo_syms
1079                                                .iter()
1080                                                .map(|mo_sym_opt| {
1081                                                    mo_sym_opt
1082                                                        .as_ref()
1083                                                        .map(|mo_sym| mo_sym.to_string())
1084                                                })
1085                                                .collect::<Vec<_>>()
1086                                        })
1087                                        .collect::<Vec<_>>()
1088                                }),
1089                                determinant_density_symmetries: sda_res
1090                                    .determinant_density_symmetries()
1091                                    .as_ref()
1092                                    .map(|den_syms| {
1093                                        den_syms
1094                                            .iter()
1095                                            .map(|(den_name, den_sym_res)| {
1096                                                (
1097                                                    den_name.clone(),
1098                                                    den_sym_res
1099                                                        .as_ref()
1100                                                        .ok()
1101                                                        .map(|den_sym| den_sym.to_string()),
1102                                                )
1103                                            })
1104                                            .collect::<Vec<_>>()
1105                                    }),
1106                                mo_density_symmetries: sda_res
1107                                    .mo_density_symmetries()
1108                                    .as_ref()
1109                                    .map(|mo_den_symss| {
1110                                        mo_den_symss
1111                                            .iter()
1112                                            .map(|mo_den_syms| {
1113                                                mo_den_syms
1114                                                    .iter()
1115                                                    .map(|mo_den_sym_opt| {
1116                                                        mo_den_sym_opt.as_ref().map(|mo_den_sym| {
1117                                                            mo_den_sym.to_string()
1118                                                        })
1119                                                    })
1120                                                    .collect::<Vec<_>>()
1121                                            })
1122                                            .collect::<Vec<_>>()
1123                                    }),
1124                            }
1125                        }
1126                        Some(MagneticSymmetryAnalysisKind::Representation) | None => {
1127                            let mut sda_driver = SlaterDeterminantRepAnalysisDriver::<
1128                                UnitaryRepresentedSymmetryGroup,
1129                                C128,
1130                                SpinOrbitCoupled,
1131                            >::builder()
1132                            .parameters(&sda_params)
1133                            .angular_function_parameters(&afa_params)
1134                            .determinant(&det_c)
1135                            .sao(&sao_c)
1136                            .sao_h(sao_h_c.as_ref())
1137                            .sao_spatial_4c(sao_spatial_4c_c.as_ref())
1138                            .sao_spatial_4c_h(sao_spatial_4c_h_c.as_ref())
1139                            .symmetry_group(&pd_res)
1140                            .build()
1141                            .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
1142                            py.allow_threads(|| {
1143                                sda_driver
1144                                    .run()
1145                                    .map_err(|err| PyRuntimeError::new_err(err.to_string()))
1146                            })?;
1147                            let sda_res = sda_driver
1148                                .result()
1149                                .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
1150                            PySlaterDeterminantRepAnalysisResult {
1151                                group: sda_res.group().name().clone(),
1152                                determinant_symmetry: sda_res
1153                                    .determinant_symmetry()
1154                                    .as_ref()
1155                                    .ok()
1156                                    .map(|sym| sym.to_string()),
1157                                mo_symmetries: sda_res.mo_symmetries().as_ref().map(|mo_symss| {
1158                                    mo_symss
1159                                        .iter()
1160                                        .map(|mo_syms| {
1161                                            mo_syms
1162                                                .iter()
1163                                                .map(|mo_sym_opt| {
1164                                                    mo_sym_opt
1165                                                        .as_ref()
1166                                                        .map(|mo_sym| mo_sym.to_string())
1167                                                })
1168                                                .collect::<Vec<_>>()
1169                                        })
1170                                        .collect::<Vec<_>>()
1171                                }),
1172                                determinant_density_symmetries: sda_res
1173                                    .determinant_density_symmetries()
1174                                    .as_ref()
1175                                    .map(|den_syms| {
1176                                        den_syms
1177                                            .iter()
1178                                            .map(|(den_name, den_sym_res)| {
1179                                                (
1180                                                    den_name.clone(),
1181                                                    den_sym_res
1182                                                        .as_ref()
1183                                                        .ok()
1184                                                        .map(|den_sym| den_sym.to_string()),
1185                                                )
1186                                            })
1187                                            .collect::<Vec<_>>()
1188                                    }),
1189                                mo_density_symmetries: sda_res
1190                                    .mo_density_symmetries()
1191                                    .as_ref()
1192                                    .map(|mo_den_symss| {
1193                                        mo_den_symss
1194                                            .iter()
1195                                            .map(|mo_den_syms| {
1196                                                mo_den_syms
1197                                                    .iter()
1198                                                    .map(|mo_den_sym_opt| {
1199                                                        mo_den_sym_opt.as_ref().map(|mo_den_sym| {
1200                                                            mo_den_sym.to_string()
1201                                                        })
1202                                                    })
1203                                                    .collect::<Vec<_>>()
1204                                            })
1205                                            .collect::<Vec<_>>()
1206                                    }),
1207                            }
1208                        }
1209                    }
1210                }
1211            }
1212        }
1213        (PySlaterDeterminant::Complex(pydet_c), _) => {
1214            match pydet_c.structure_constraint {
1215                PyStructureConstraint::SpinConstraint(_) => {
1216                    let det_c = if augment_to_generalised {
1217                        pydet_c
1218                            .to_qsym2::<SpinConstraint>(&bao, mol)
1219                            .map_err(|err| PyRuntimeError::new_err(err.to_string()))?
1220                            .to_generalised()
1221                    } else {
1222                        pydet_c
1223                            .to_qsym2::<SpinConstraint>(&bao, mol)
1224                            .map_err(|err| PyRuntimeError::new_err(err.to_string()))?
1225                    };
1226                    let sao_c = match sao {
1227                        PyArray2RC::Real(pysao_r) => pysao_r.to_owned_array().mapv(Complex::from),
1228                        PyArray2RC::Complex(pysao_c) => pysao_c.to_owned_array(),
1229                    };
1230                    let sao_h_c = sao_h.and_then(|pysao_h| match pysao_h {
1231                        // sao_h must have the same reality as sao.
1232                        PyArray2RC::Real(pysao_h_r) => {
1233                            Some(pysao_h_r.to_owned_array().mapv(Complex::from))
1234                        }
1235                        PyArray2RC::Complex(pysao_h_c) => Some(pysao_h_c.to_owned_array()),
1236                    });
1237                    let sao_spatial_4c_c = sao_spatial_4c.and_then(|pysao4c| match pysao4c {
1238                        // sao_spatial_4c must have the same reality as sao.
1239                        PyArray4RC::Real(pysao4c_r) => {
1240                            Some(pysao4c_r.to_owned_array().mapv(Complex::from))
1241                        }
1242                        PyArray4RC::Complex(pysao4c_c) => Some(pysao4c_c.to_owned_array()),
1243                    });
1244                    let sao_spatial_4c_h_c =
1245                        sao_spatial_4c_h.and_then(|pysao4c_h| match pysao4c_h {
1246                            // sao_spatial_4c_h must have the same reality as sao.
1247                            PyArray4RC::Real(pysao4c_h_r) => {
1248                                Some(pysao4c_h_r.to_owned_array().mapv(Complex::from))
1249                            }
1250                            PyArray4RC::Complex(pysao4c_h_c) => Some(pysao4c_h_c.to_owned_array()),
1251                        });
1252                    match &use_magnetic_group {
1253                        Some(MagneticSymmetryAnalysisKind::Corepresentation) => {
1254                            let mut sda_driver = SlaterDeterminantRepAnalysisDriver::<
1255                                MagneticRepresentedSymmetryGroup,
1256                                C128,
1257                                SpinConstraint,
1258                            >::builder()
1259                            .parameters(&sda_params)
1260                            .angular_function_parameters(&afa_params)
1261                            .determinant(&det_c)
1262                            .sao(&sao_c)
1263                            .sao_h(sao_h_c.as_ref())
1264                            .sao_spatial_4c(sao_spatial_4c_c.as_ref())
1265                            .sao_spatial_4c_h(sao_spatial_4c_h_c.as_ref())
1266                            .symmetry_group(&pd_res)
1267                            .build()
1268                            .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
1269                            py.allow_threads(|| {
1270                                sda_driver
1271                                    .run()
1272                                    .map_err(|err| PyRuntimeError::new_err(err.to_string()))
1273                            })?;
1274                            let sda_res = sda_driver
1275                                .result()
1276                                .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
1277                            PySlaterDeterminantRepAnalysisResult {
1278                                group: sda_res.group().name().clone(),
1279                                determinant_symmetry: sda_res
1280                                    .determinant_symmetry()
1281                                    .as_ref()
1282                                    .ok()
1283                                    .map(|sym| sym.to_string()),
1284                                mo_symmetries: sda_res.mo_symmetries().as_ref().map(|mo_symss| {
1285                                    mo_symss
1286                                        .iter()
1287                                        .map(|mo_syms| {
1288                                            mo_syms
1289                                                .iter()
1290                                                .map(|mo_sym_opt| {
1291                                                    mo_sym_opt
1292                                                        .as_ref()
1293                                                        .map(|mo_sym| mo_sym.to_string())
1294                                                })
1295                                                .collect::<Vec<_>>()
1296                                        })
1297                                        .collect::<Vec<_>>()
1298                                }),
1299                                determinant_density_symmetries: sda_res
1300                                    .determinant_density_symmetries()
1301                                    .as_ref()
1302                                    .map(|den_syms| {
1303                                        den_syms
1304                                            .iter()
1305                                            .map(|(den_name, den_sym_res)| {
1306                                                (
1307                                                    den_name.clone(),
1308                                                    den_sym_res
1309                                                        .as_ref()
1310                                                        .ok()
1311                                                        .map(|den_sym| den_sym.to_string()),
1312                                                )
1313                                            })
1314                                            .collect::<Vec<_>>()
1315                                    }),
1316                                mo_density_symmetries: sda_res
1317                                    .mo_density_symmetries()
1318                                    .as_ref()
1319                                    .map(|mo_den_symss| {
1320                                        mo_den_symss
1321                                            .iter()
1322                                            .map(|mo_den_syms| {
1323                                                mo_den_syms
1324                                                    .iter()
1325                                                    .map(|mo_den_sym_opt| {
1326                                                        mo_den_sym_opt.as_ref().map(|mo_den_sym| {
1327                                                            mo_den_sym.to_string()
1328                                                        })
1329                                                    })
1330                                                    .collect::<Vec<_>>()
1331                                            })
1332                                            .collect::<Vec<_>>()
1333                                    }),
1334                            }
1335                        }
1336                        Some(MagneticSymmetryAnalysisKind::Representation) | None => {
1337                            let mut sda_driver = SlaterDeterminantRepAnalysisDriver::<
1338                                UnitaryRepresentedSymmetryGroup,
1339                                C128,
1340                                SpinConstraint,
1341                            >::builder()
1342                            .parameters(&sda_params)
1343                            .angular_function_parameters(&afa_params)
1344                            .determinant(&det_c)
1345                            .sao(&sao_c)
1346                            .sao_h(sao_h_c.as_ref())
1347                            .sao_spatial_4c(sao_spatial_4c_c.as_ref())
1348                            .sao_spatial_4c_h(sao_spatial_4c_h_c.as_ref())
1349                            .symmetry_group(&pd_res)
1350                            .build()
1351                            .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
1352                            py.allow_threads(|| {
1353                                sda_driver
1354                                    .run()
1355                                    .map_err(|err| PyRuntimeError::new_err(err.to_string()))
1356                            })?;
1357                            let sda_res = sda_driver
1358                                .result()
1359                                .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
1360                            PySlaterDeterminantRepAnalysisResult {
1361                                group: sda_res.group().name().clone(),
1362                                determinant_symmetry: sda_res
1363                                    .determinant_symmetry()
1364                                    .as_ref()
1365                                    .ok()
1366                                    .map(|sym| sym.to_string()),
1367                                mo_symmetries: sda_res.mo_symmetries().as_ref().map(|mo_symss| {
1368                                    mo_symss
1369                                        .iter()
1370                                        .map(|mo_syms| {
1371                                            mo_syms
1372                                                .iter()
1373                                                .map(|mo_sym_opt| {
1374                                                    mo_sym_opt
1375                                                        .as_ref()
1376                                                        .map(|mo_sym| mo_sym.to_string())
1377                                                })
1378                                                .collect::<Vec<_>>()
1379                                        })
1380                                        .collect::<Vec<_>>()
1381                                }),
1382                                determinant_density_symmetries: sda_res
1383                                    .determinant_density_symmetries()
1384                                    .as_ref()
1385                                    .map(|den_syms| {
1386                                        den_syms
1387                                            .iter()
1388                                            .map(|(den_name, den_sym_res)| {
1389                                                (
1390                                                    den_name.clone(),
1391                                                    den_sym_res
1392                                                        .as_ref()
1393                                                        .ok()
1394                                                        .map(|den_sym| den_sym.to_string()),
1395                                                )
1396                                            })
1397                                            .collect::<Vec<_>>()
1398                                    }),
1399                                mo_density_symmetries: sda_res
1400                                    .mo_density_symmetries()
1401                                    .as_ref()
1402                                    .map(|mo_den_symss| {
1403                                        mo_den_symss
1404                                            .iter()
1405                                            .map(|mo_den_syms| {
1406                                                mo_den_syms
1407                                                    .iter()
1408                                                    .map(|mo_den_sym_opt| {
1409                                                        mo_den_sym_opt.as_ref().map(|mo_den_sym| {
1410                                                            mo_den_sym.to_string()
1411                                                        })
1412                                                    })
1413                                                    .collect::<Vec<_>>()
1414                                            })
1415                                            .collect::<Vec<_>>()
1416                                    }),
1417                            }
1418                        }
1419                    }
1420                }
1421                PyStructureConstraint::SpinOrbitCoupled(_) => {
1422                    let det_c = pydet_c
1423                        .to_qsym2::<SpinOrbitCoupled>(&bao, mol)
1424                        .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
1425                    let sao_c = match sao {
1426                        PyArray2RC::Real(pysao_r) => pysao_r.to_owned_array().mapv(Complex::from),
1427                        PyArray2RC::Complex(pysao_c) => pysao_c.to_owned_array(),
1428                    };
1429                    let sao_h_c = sao_h.and_then(|pysao_h| match pysao_h {
1430                        // sao_spatial_h must have the same reality as sao.
1431                        PyArray2RC::Real(pysao_h_r) => {
1432                            Some(pysao_h_r.to_owned_array().mapv(Complex::from))
1433                        }
1434                        PyArray2RC::Complex(pysao_h_c) => Some(pysao_h_c.to_owned_array()),
1435                    });
1436                    let sao_spatial_4c_c = sao_spatial_4c.and_then(|pysao4c| match pysao4c {
1437                        // sao_spatial_4c must have the same reality as sao.
1438                        PyArray4RC::Real(pysao4c_r) => {
1439                            Some(pysao4c_r.to_owned_array().mapv(Complex::from))
1440                        }
1441                        PyArray4RC::Complex(pysao4c_c) => Some(pysao4c_c.to_owned_array()),
1442                    });
1443                    let sao_spatial_4c_h_c =
1444                        sao_spatial_4c_h.and_then(|pysao4c_h| match pysao4c_h {
1445                            // sao_spatial_4c_h must have the same reality as sao.
1446                            PyArray4RC::Real(pysao4c_h_r) => {
1447                                Some(pysao4c_h_r.to_owned_array().mapv(Complex::from))
1448                            }
1449                            PyArray4RC::Complex(pysao4c_h_c) => Some(pysao4c_h_c.to_owned_array()),
1450                        });
1451                    match &use_magnetic_group {
1452                        Some(MagneticSymmetryAnalysisKind::Corepresentation) => {
1453                            let mut sda_driver = SlaterDeterminantRepAnalysisDriver::<
1454                                MagneticRepresentedSymmetryGroup,
1455                                C128,
1456                                SpinOrbitCoupled,
1457                            >::builder()
1458                            .parameters(&sda_params)
1459                            .angular_function_parameters(&afa_params)
1460                            .determinant(&det_c)
1461                            .sao(&sao_c)
1462                            .sao_h(sao_h_c.as_ref())
1463                            .sao_spatial_4c(sao_spatial_4c_c.as_ref())
1464                            .sao_spatial_4c_h(sao_spatial_4c_h_c.as_ref())
1465                            .symmetry_group(&pd_res)
1466                            .build()
1467                            .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
1468                            py.allow_threads(|| {
1469                                sda_driver
1470                                    .run()
1471                                    .map_err(|err| PyRuntimeError::new_err(err.to_string()))
1472                            })?;
1473                            let sda_res = sda_driver
1474                                .result()
1475                                .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
1476                            PySlaterDeterminantRepAnalysisResult {
1477                                group: sda_res.group().name().clone(),
1478                                determinant_symmetry: sda_res
1479                                    .determinant_symmetry()
1480                                    .as_ref()
1481                                    .ok()
1482                                    .map(|sym| sym.to_string()),
1483                                mo_symmetries: sda_res.mo_symmetries().as_ref().map(|mo_symss| {
1484                                    mo_symss
1485                                        .iter()
1486                                        .map(|mo_syms| {
1487                                            mo_syms
1488                                                .iter()
1489                                                .map(|mo_sym_opt| {
1490                                                    mo_sym_opt
1491                                                        .as_ref()
1492                                                        .map(|mo_sym| mo_sym.to_string())
1493                                                })
1494                                                .collect::<Vec<_>>()
1495                                        })
1496                                        .collect::<Vec<_>>()
1497                                }),
1498                                determinant_density_symmetries: sda_res
1499                                    .determinant_density_symmetries()
1500                                    .as_ref()
1501                                    .map(|den_syms| {
1502                                        den_syms
1503                                            .iter()
1504                                            .map(|(den_name, den_sym_res)| {
1505                                                (
1506                                                    den_name.clone(),
1507                                                    den_sym_res
1508                                                        .as_ref()
1509                                                        .ok()
1510                                                        .map(|den_sym| den_sym.to_string()),
1511                                                )
1512                                            })
1513                                            .collect::<Vec<_>>()
1514                                    }),
1515                                mo_density_symmetries: sda_res
1516                                    .mo_density_symmetries()
1517                                    .as_ref()
1518                                    .map(|mo_den_symss| {
1519                                        mo_den_symss
1520                                            .iter()
1521                                            .map(|mo_den_syms| {
1522                                                mo_den_syms
1523                                                    .iter()
1524                                                    .map(|mo_den_sym_opt| {
1525                                                        mo_den_sym_opt.as_ref().map(|mo_den_sym| {
1526                                                            mo_den_sym.to_string()
1527                                                        })
1528                                                    })
1529                                                    .collect::<Vec<_>>()
1530                                            })
1531                                            .collect::<Vec<_>>()
1532                                    }),
1533                            }
1534                        }
1535                        Some(MagneticSymmetryAnalysisKind::Representation) | None => {
1536                            let mut sda_driver = SlaterDeterminantRepAnalysisDriver::<
1537                                UnitaryRepresentedSymmetryGroup,
1538                                C128,
1539                                SpinOrbitCoupled,
1540                            >::builder()
1541                            .parameters(&sda_params)
1542                            .angular_function_parameters(&afa_params)
1543                            .determinant(&det_c)
1544                            .sao(&sao_c)
1545                            .sao_h(sao_h_c.as_ref())
1546                            .sao_spatial_4c(sao_spatial_4c_c.as_ref())
1547                            .sao_spatial_4c_h(sao_spatial_4c_h_c.as_ref())
1548                            .symmetry_group(&pd_res)
1549                            .build()
1550                            .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
1551                            py.allow_threads(|| {
1552                                sda_driver
1553                                    .run()
1554                                    .map_err(|err| PyRuntimeError::new_err(err.to_string()))
1555                            })?;
1556                            let sda_res = sda_driver
1557                                .result()
1558                                .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
1559                            PySlaterDeterminantRepAnalysisResult {
1560                                group: sda_res.group().name().clone(),
1561                                determinant_symmetry: sda_res
1562                                    .determinant_symmetry()
1563                                    .as_ref()
1564                                    .ok()
1565                                    .map(|sym| sym.to_string()),
1566                                mo_symmetries: sda_res.mo_symmetries().as_ref().map(|mo_symss| {
1567                                    mo_symss
1568                                        .iter()
1569                                        .map(|mo_syms| {
1570                                            mo_syms
1571                                                .iter()
1572                                                .map(|mo_sym_opt| {
1573                                                    mo_sym_opt
1574                                                        .as_ref()
1575                                                        .map(|mo_sym| mo_sym.to_string())
1576                                                })
1577                                                .collect::<Vec<_>>()
1578                                        })
1579                                        .collect::<Vec<_>>()
1580                                }),
1581                                determinant_density_symmetries: sda_res
1582                                    .determinant_density_symmetries()
1583                                    .as_ref()
1584                                    .map(|den_syms| {
1585                                        den_syms
1586                                            .iter()
1587                                            .map(|(den_name, den_sym_res)| {
1588                                                (
1589                                                    den_name.clone(),
1590                                                    den_sym_res
1591                                                        .as_ref()
1592                                                        .ok()
1593                                                        .map(|den_sym| den_sym.to_string()),
1594                                                )
1595                                            })
1596                                            .collect::<Vec<_>>()
1597                                    }),
1598                                mo_density_symmetries: sda_res
1599                                    .mo_density_symmetries()
1600                                    .as_ref()
1601                                    .map(|mo_den_symss| {
1602                                        mo_den_symss
1603                                            .iter()
1604                                            .map(|mo_den_syms| {
1605                                                mo_den_syms
1606                                                    .iter()
1607                                                    .map(|mo_den_sym_opt| {
1608                                                        mo_den_sym_opt.as_ref().map(|mo_den_sym| {
1609                                                            mo_den_sym.to_string()
1610                                                        })
1611                                                    })
1612                                                    .collect::<Vec<_>>()
1613                                            })
1614                                            .collect::<Vec<_>>()
1615                                    }),
1616                            }
1617                        }
1618                    }
1619                }
1620            }
1621        }
1622    };
1623    Ok(pysda_res)
1624}