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