Skip to main content

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