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