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/// * `use_magnetic_group` - An option indicating if the magnetic group is to be used for symmetry
530/// analysis, and if so, whether unitary representations or unitary-antiunitary corepresentations
531/// should be used.
532/// * `use_double_group` - A boolean indicating if the double group of the prevailing symmetry
533/// group is to be used for representation analysis instead.
534/// * `use_cayley_table` - A boolean indicating if the Cayley table for the group, if available,
535/// should be used to speed up the calculation of orbit overlap matrices.
536/// * `symmetry_transformation_kind` - An enumerated type indicating the type of symmetry
537/// transformations to be performed on the origin determinant to generate the orbit. If this
538/// contains spin transformation, the determinant will be augmented to generalised spin constraint
539/// automatically.
540/// * `eigenvalue_comparison_mode` - An enumerated type indicating the mode of comparison of orbit
541/// overlap eigenvalues with the specified `linear_independence_threshold`.
542/// * `sao` - The atomic-orbital overlap matrix whose elements are of type `float64` or
543/// `complex128`.
544/// * `sao_h` - The optional complex-symmetric atomic-orbital overlap matrix whose elements
545/// are of type `float64` or `complex128`. This is required if antiunitary symmetry operations are
546/// involved.
547/// * `sao_spatial_4c` - The optional atomic-orbital four-centre overlap matrix whose elements are
548/// of type `float64` or `complex128`.
549/// * `sao_spatial_4c_h` - The optional complex-symmetric atomic-orbital four-centre overlap matrix
550/// whose elements are of type `float64` or `complex128`. This is required if antiunitary symmetry
551/// operations are involved.
552/// * `analyse_mo_symmetries` - A boolean indicating if the symmetries of individual molecular
553/// orbitals are to be analysed.
554/// * `analyse_mo_symmetry_projections` - A boolean indicating if the symmetry projections of
555/// individual molecular orbitals are to be analysed.
556/// * `analyse_mo_mirror_parities` - A boolean indicating if the mirror parities of individual
557/// molecular orbitals are to be printed.
558/// * `analyse_density_symmetries` - A boolean indicating if the symmetries of densities are to be
559/// analysed.
560/// * `write_overlap_eigenvalues` - A boolean indicating if the eigenvalues of the determinant
561/// orbit overlap matrix are to be written to the output.
562/// * `write_character_table` - A boolean indicating if the character table of the prevailing
563/// symmetry group is to be printed out.
564/// * `infinite_order_to_finite` - The finite order with which infinite-order generators are to be
565/// interpreted to form a finite subgroup of the prevailing infinite group. This finite subgroup
566/// will be used for symmetry analysis.
567/// * `angular_function_integrality_threshold` - The threshold for verifying if subspace
568/// multiplicities are integral for the symmetry analysis of angular functions.
569/// * `angular_function_linear_independence_threshold` - The threshold for determining the linear
570/// independence subspace via the non-zero eigenvalues of the orbit overlap matrix for the symmetry
571/// analysis of angular functions.
572/// * `angular_function_max_angular_momentum` - The maximum angular momentum order to be used in
573/// angular function symmetry analysis.
574///
575/// # Returns
576///
577/// A Python-exposed [`PySlaterDeterminantRepAnalysisResult`] structure containing the results of the
578/// representation analysis.
579#[allow(clippy::too_many_arguments)]
580#[pyfunction]
581#[pyo3(signature = (
582    inp_sym,
583    pydet,
584    pybaos,
585    integrality_threshold,
586    linear_independence_threshold,
587    use_magnetic_group,
588    use_double_group,
589    use_cayley_table,
590    symmetry_transformation_kind,
591    eigenvalue_comparison_mode,
592    sao,
593    sao_h=None,
594    sao_spatial_4c=None,
595    sao_spatial_4c_h=None,
596    analyse_mo_symmetries=true,
597    analyse_mo_symmetry_projections=true,
598    analyse_mo_mirror_parities=false,
599    analyse_density_symmetries=false,
600    write_overlap_eigenvalues=true,
601    write_character_table=true,
602    infinite_order_to_finite=None,
603    angular_function_integrality_threshold=1e-7,
604    angular_function_linear_independence_threshold=1e-7,
605    angular_function_max_angular_momentum=2
606))]
607pub fn rep_analyse_slater_determinant(
608    py: Python<'_>,
609    inp_sym: PathBuf,
610    pydet: PySlaterDeterminant,
611    pybaos: Vec<PyBasisAngularOrder>,
612    integrality_threshold: f64,
613    linear_independence_threshold: f64,
614    use_magnetic_group: Option<MagneticSymmetryAnalysisKind>,
615    use_double_group: bool,
616    use_cayley_table: bool,
617    symmetry_transformation_kind: SymmetryTransformationKind,
618    eigenvalue_comparison_mode: EigenvalueComparisonMode,
619    sao: PyArray2RC,
620    sao_h: Option<PyArray2RC>,
621    sao_spatial_4c: Option<PyArray4RC>,
622    sao_spatial_4c_h: Option<PyArray4RC>,
623    analyse_mo_symmetries: bool,
624    analyse_mo_symmetry_projections: bool,
625    analyse_mo_mirror_parities: bool,
626    analyse_density_symmetries: bool,
627    write_overlap_eigenvalues: bool,
628    write_character_table: bool,
629    infinite_order_to_finite: Option<u32>,
630    angular_function_integrality_threshold: f64,
631    angular_function_linear_independence_threshold: f64,
632    angular_function_max_angular_momentum: u32,
633) -> PyResult<PySlaterDeterminantRepAnalysisResult> {
634    let pd_res: SymmetryGroupDetectionResult =
635        read_qsym2_binary(inp_sym.clone(), QSym2FileType::Sym)
636            .map_err(|err| PyIOError::new_err(err.to_string()))?;
637
638    let mut file_name = inp_sym.to_path_buf();
639    file_name.set_extension(QSym2FileType::Sym.ext());
640    qsym2_output!(
641        "Symmetry-group detection results read in from {}.",
642        file_name.display(),
643    );
644    qsym2_output!("");
645
646    let mol = &pd_res.pre_symmetry.recentred_molecule;
647
648    let baos = pybaos
649        .iter()
650        .map(|bao| {
651            bao.to_qsym2(mol)
652                .map_err(|err| PyRuntimeError::new_err(err.to_string()))
653        })
654        .collect::<Result<Vec<_>, _>>()?;
655    let baos_ref = baos.iter().collect::<Vec<_>>();
656    let augment_to_generalised = match symmetry_transformation_kind {
657        SymmetryTransformationKind::SpatialWithSpinTimeReversal
658        | SymmetryTransformationKind::Spin
659        | SymmetryTransformationKind::SpinSpatial => true,
660        SymmetryTransformationKind::Spatial => false,
661    };
662    let afa_params = AngularFunctionRepAnalysisParams::builder()
663        .integrality_threshold(angular_function_integrality_threshold)
664        .linear_independence_threshold(angular_function_linear_independence_threshold)
665        .max_angular_momentum(angular_function_max_angular_momentum)
666        .build()
667        .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
668    let sda_params = SlaterDeterminantRepAnalysisParams::<f64>::builder()
669        .integrality_threshold(integrality_threshold)
670        .linear_independence_threshold(linear_independence_threshold)
671        .use_magnetic_group(use_magnetic_group.clone())
672        .use_double_group(use_double_group)
673        .use_cayley_table(use_cayley_table)
674        .symmetry_transformation_kind(symmetry_transformation_kind)
675        .eigenvalue_comparison_mode(eigenvalue_comparison_mode)
676        .analyse_mo_symmetries(analyse_mo_symmetries)
677        .analyse_mo_symmetry_projections(analyse_mo_symmetry_projections)
678        .analyse_mo_mirror_parities(analyse_mo_mirror_parities)
679        .analyse_density_symmetries(analyse_density_symmetries)
680        .write_overlap_eigenvalues(write_overlap_eigenvalues)
681        .write_character_table(if write_character_table {
682            Some(CharacterTableDisplay::Symbolic)
683        } else {
684            None
685        })
686        .infinite_order_to_finite(infinite_order_to_finite)
687        .build()
688        .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
689    let pysda_res: PySlaterDeterminantRepAnalysisResult = match (&pydet, &sao) {
690        (PySlaterDeterminant::Real(pydet_r), PyArray2RC::Real(pysao_r)) => {
691            if matches!(
692                pydet_r.structure_constraint,
693                PyStructureConstraint::SpinOrbitCoupled(_)
694            ) {
695                return Err(PyRuntimeError::new_err("Real determinants are not compatible with spin--orbit-coupled structure constraint.".to_string()));
696            }
697
698            let sao = pysao_r.to_owned_array();
699            let sao_spatial_4c = sao_spatial_4c.and_then(|pysao4c| match pysao4c {
700                // sao_spatial_4c must have the same reality as sao_spatial.
701                PyArray4RC::Real(pysao4c_r) => Some(pysao4c_r.to_owned_array()),
702                PyArray4RC::Complex(_) => None,
703            });
704
705            let det_r = if augment_to_generalised {
706                pydet_r
707                    .to_qsym2::<SpinConstraint>(&baos_ref, mol)
708                    .map_err(|err| PyRuntimeError::new_err(err.to_string()))?
709                    .to_generalised()
710            } else {
711                pydet_r
712                    .to_qsym2::<SpinConstraint>(&baos_ref, mol)
713                    .map_err(|err| PyRuntimeError::new_err(err.to_string()))?
714            };
715            match &use_magnetic_group {
716                Some(MagneticSymmetryAnalysisKind::Corepresentation) => {
717                    let mut sda_driver = SlaterDeterminantRepAnalysisDriver::<
718                        MagneticRepresentedSymmetryGroup,
719                        f64,
720                        SpinConstraint,
721                    >::builder()
722                    .parameters(&sda_params)
723                    .angular_function_parameters(&afa_params)
724                    .determinant(&det_r)
725                    .sao(&sao)
726                    .sao_h(None) // Real SAO.
727                    .sao_spatial_4c(sao_spatial_4c.as_ref())
728                    .sao_spatial_4c_h(None) // Real SAO.
729                    .symmetry_group(&pd_res)
730                    .build()
731                    .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
732                    py.detach(|| {
733                        sda_driver
734                            .run()
735                            .map_err(|err| PyRuntimeError::new_err(err.to_string()))
736                    })?;
737                    let sda_res = sda_driver
738                        .result()
739                        .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
740                    PySlaterDeterminantRepAnalysisResult {
741                        group: sda_res.group().name().clone(),
742                        determinant_symmetry: sda_res
743                            .determinant_symmetry()
744                            .as_ref()
745                            .ok()
746                            .map(|sym| sym.to_string()),
747                        mo_symmetries: sda_res.mo_symmetries().as_ref().map(|mo_symss| {
748                            mo_symss
749                                .iter()
750                                .map(|mo_syms| {
751                                    mo_syms
752                                        .iter()
753                                        .map(|mo_sym_opt| {
754                                            mo_sym_opt.as_ref().map(|mo_sym| mo_sym.to_string())
755                                        })
756                                        .collect::<Vec<_>>()
757                                })
758                                .collect::<Vec<_>>()
759                        }),
760                        determinant_density_symmetries: sda_res
761                            .determinant_density_symmetries()
762                            .as_ref()
763                            .map(|den_syms| {
764                                den_syms
765                                    .iter()
766                                    .map(|(den_name, den_sym_res)| {
767                                        (
768                                            den_name.clone(),
769                                            den_sym_res
770                                                .as_ref()
771                                                .ok()
772                                                .map(|den_sym| den_sym.to_string()),
773                                        )
774                                    })
775                                    .collect::<Vec<_>>()
776                            }),
777                        mo_density_symmetries: sda_res.mo_density_symmetries().as_ref().map(
778                            |mo_den_symss| {
779                                mo_den_symss
780                                    .iter()
781                                    .map(|mo_den_syms| {
782                                        mo_den_syms
783                                            .iter()
784                                            .map(|mo_den_sym_opt| {
785                                                mo_den_sym_opt
786                                                    .as_ref()
787                                                    .map(|mo_den_sym| mo_den_sym.to_string())
788                                            })
789                                            .collect::<Vec<_>>()
790                                    })
791                                    .collect::<Vec<_>>()
792                            },
793                        ),
794                    }
795                }
796                Some(MagneticSymmetryAnalysisKind::Representation) | None => {
797                    let mut sda_driver = SlaterDeterminantRepAnalysisDriver::<
798                        UnitaryRepresentedSymmetryGroup,
799                        f64,
800                        SpinConstraint,
801                    >::builder()
802                    .parameters(&sda_params)
803                    .angular_function_parameters(&afa_params)
804                    .determinant(&det_r)
805                    .sao(&sao)
806                    .sao_h(None) // Real SAO.
807                    .sao_spatial_4c(sao_spatial_4c.as_ref())
808                    .sao_spatial_4c_h(None) // Real SAO.
809                    .symmetry_group(&pd_res)
810                    .build()
811                    .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
812                    py.detach(|| {
813                        sda_driver
814                            .run()
815                            .map_err(|err| PyRuntimeError::new_err(err.to_string()))
816                    })?;
817                    let sda_res = sda_driver
818                        .result()
819                        .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
820                    PySlaterDeterminantRepAnalysisResult {
821                        group: sda_res.group().name().clone(),
822                        determinant_symmetry: sda_res
823                            .determinant_symmetry()
824                            .as_ref()
825                            .ok()
826                            .map(|sym| sym.to_string()),
827                        mo_symmetries: sda_res.mo_symmetries().as_ref().map(|mo_symss| {
828                            mo_symss
829                                .iter()
830                                .map(|mo_syms| {
831                                    mo_syms
832                                        .iter()
833                                        .map(|mo_sym_opt| {
834                                            mo_sym_opt.as_ref().map(|mo_sym| mo_sym.to_string())
835                                        })
836                                        .collect::<Vec<_>>()
837                                })
838                                .collect::<Vec<_>>()
839                        }),
840                        determinant_density_symmetries: sda_res
841                            .determinant_density_symmetries()
842                            .as_ref()
843                            .map(|den_syms| {
844                                den_syms
845                                    .iter()
846                                    .map(|(den_name, den_sym_res)| {
847                                        (
848                                            den_name.clone(),
849                                            den_sym_res
850                                                .as_ref()
851                                                .ok()
852                                                .map(|den_sym| den_sym.to_string()),
853                                        )
854                                    })
855                                    .collect::<Vec<_>>()
856                            }),
857                        mo_density_symmetries: sda_res.mo_density_symmetries().as_ref().map(
858                            |mo_den_symss| {
859                                mo_den_symss
860                                    .iter()
861                                    .map(|mo_den_syms| {
862                                        mo_den_syms
863                                            .iter()
864                                            .map(|mo_den_sym_opt| {
865                                                mo_den_sym_opt
866                                                    .as_ref()
867                                                    .map(|mo_den_sym| mo_den_sym.to_string())
868                                            })
869                                            .collect::<Vec<_>>()
870                                    })
871                                    .collect::<Vec<_>>()
872                            },
873                        ),
874                    }
875                }
876            }
877        }
878        (PySlaterDeterminant::Real(pydet_r), PyArray2RC::Complex(pysao_c)) => {
879            match pydet_r.structure_constraint {
880                PyStructureConstraint::SpinConstraint(_) => {
881                    let det_r = if augment_to_generalised {
882                        pydet_r
883                            .to_qsym2(&baos_ref, mol)
884                            .map_err(|err| PyRuntimeError::new_err(err.to_string()))?
885                            .to_generalised()
886                    } else {
887                        pydet_r
888                            .to_qsym2(&baos_ref, mol)
889                            .map_err(|err| PyRuntimeError::new_err(err.to_string()))?
890                    };
891                    let det_c: SlaterDeterminant<C128, SpinConstraint> = det_r.into();
892                    let sao_c = pysao_c.to_owned_array();
893                    let sao_h_c = sao_h.and_then(|pysao_h| match pysao_h {
894                        // sao_spatial_h must have the same reality as sao_spatial.
895                        PyArray2RC::Real(_) => None,
896                        PyArray2RC::Complex(pysao_h_c) => Some(pysao_h_c.to_owned_array()),
897                    });
898                    let sao_spatial_4c_c = sao_spatial_4c.and_then(|pysao4c| match pysao4c {
899                        // sao_spatial_4c must have the same reality as sao_spatial.
900                        PyArray4RC::Real(_) => None,
901                        PyArray4RC::Complex(pysao4c_c) => Some(pysao4c_c.to_owned_array()),
902                    });
903                    let sao_spatial_4c_h_c =
904                        sao_spatial_4c_h.and_then(|pysao4c_h| match pysao4c_h {
905                            // sao_spatial_4c_h must have the same reality as sao_spatial.
906                            PyArray4RC::Real(_) => None,
907                            PyArray4RC::Complex(pysao4c_h_c) => Some(pysao4c_h_c.to_owned_array()),
908                        });
909                    match &use_magnetic_group {
910                        Some(MagneticSymmetryAnalysisKind::Corepresentation) => {
911                            let mut sda_driver = SlaterDeterminantRepAnalysisDriver::<
912                                MagneticRepresentedSymmetryGroup,
913                                C128,
914                                SpinConstraint,
915                            >::builder()
916                            .parameters(&sda_params)
917                            .angular_function_parameters(&afa_params)
918                            .determinant(&det_c)
919                            .sao(&sao_c)
920                            .sao_h(sao_h_c.as_ref())
921                            .sao_spatial_4c(sao_spatial_4c_c.as_ref())
922                            .sao_spatial_4c_h(sao_spatial_4c_h_c.as_ref())
923                            .symmetry_group(&pd_res)
924                            .build()
925                            .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
926                            py.detach(|| {
927                                sda_driver
928                                    .run()
929                                    .map_err(|err| PyRuntimeError::new_err(err.to_string()))
930                            })?;
931                            let sda_res = sda_driver
932                                .result()
933                                .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
934                            PySlaterDeterminantRepAnalysisResult {
935                                group: sda_res.group().name().clone(),
936                                determinant_symmetry: sda_res
937                                    .determinant_symmetry()
938                                    .as_ref()
939                                    .ok()
940                                    .map(|sym| sym.to_string()),
941                                mo_symmetries: sda_res.mo_symmetries().as_ref().map(|mo_symss| {
942                                    mo_symss
943                                        .iter()
944                                        .map(|mo_syms| {
945                                            mo_syms
946                                                .iter()
947                                                .map(|mo_sym_opt| {
948                                                    mo_sym_opt
949                                                        .as_ref()
950                                                        .map(|mo_sym| mo_sym.to_string())
951                                                })
952                                                .collect::<Vec<_>>()
953                                        })
954                                        .collect::<Vec<_>>()
955                                }),
956                                determinant_density_symmetries: sda_res
957                                    .determinant_density_symmetries()
958                                    .as_ref()
959                                    .map(|den_syms| {
960                                        den_syms
961                                            .iter()
962                                            .map(|(den_name, den_sym_res)| {
963                                                (
964                                                    den_name.clone(),
965                                                    den_sym_res
966                                                        .as_ref()
967                                                        .ok()
968                                                        .map(|den_sym| den_sym.to_string()),
969                                                )
970                                            })
971                                            .collect::<Vec<_>>()
972                                    }),
973                                mo_density_symmetries: sda_res
974                                    .mo_density_symmetries()
975                                    .as_ref()
976                                    .map(|mo_den_symss| {
977                                        mo_den_symss
978                                            .iter()
979                                            .map(|mo_den_syms| {
980                                                mo_den_syms
981                                                    .iter()
982                                                    .map(|mo_den_sym_opt| {
983                                                        mo_den_sym_opt.as_ref().map(|mo_den_sym| {
984                                                            mo_den_sym.to_string()
985                                                        })
986                                                    })
987                                                    .collect::<Vec<_>>()
988                                            })
989                                            .collect::<Vec<_>>()
990                                    }),
991                            }
992                        }
993                        Some(MagneticSymmetryAnalysisKind::Representation) | None => {
994                            let mut sda_driver = SlaterDeterminantRepAnalysisDriver::<
995                                UnitaryRepresentedSymmetryGroup,
996                                C128,
997                                SpinConstraint,
998                            >::builder()
999                            .parameters(&sda_params)
1000                            .angular_function_parameters(&afa_params)
1001                            .determinant(&det_c)
1002                            .sao(&sao_c)
1003                            .sao_h(sao_h_c.as_ref())
1004                            .sao_spatial_4c(sao_spatial_4c_c.as_ref())
1005                            .sao_spatial_4c_h(sao_spatial_4c_h_c.as_ref())
1006                            .symmetry_group(&pd_res)
1007                            .build()
1008                            .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
1009                            py.detach(|| {
1010                                sda_driver
1011                                    .run()
1012                                    .map_err(|err| PyRuntimeError::new_err(err.to_string()))
1013                            })?;
1014                            let sda_res = sda_driver
1015                                .result()
1016                                .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
1017                            PySlaterDeterminantRepAnalysisResult {
1018                                group: sda_res.group().name().clone(),
1019                                determinant_symmetry: sda_res
1020                                    .determinant_symmetry()
1021                                    .as_ref()
1022                                    .ok()
1023                                    .map(|sym| sym.to_string()),
1024                                mo_symmetries: sda_res.mo_symmetries().as_ref().map(|mo_symss| {
1025                                    mo_symss
1026                                        .iter()
1027                                        .map(|mo_syms| {
1028                                            mo_syms
1029                                                .iter()
1030                                                .map(|mo_sym_opt| {
1031                                                    mo_sym_opt
1032                                                        .as_ref()
1033                                                        .map(|mo_sym| mo_sym.to_string())
1034                                                })
1035                                                .collect::<Vec<_>>()
1036                                        })
1037                                        .collect::<Vec<_>>()
1038                                }),
1039                                determinant_density_symmetries: sda_res
1040                                    .determinant_density_symmetries()
1041                                    .as_ref()
1042                                    .map(|den_syms| {
1043                                        den_syms
1044                                            .iter()
1045                                            .map(|(den_name, den_sym_res)| {
1046                                                (
1047                                                    den_name.clone(),
1048                                                    den_sym_res
1049                                                        .as_ref()
1050                                                        .ok()
1051                                                        .map(|den_sym| den_sym.to_string()),
1052                                                )
1053                                            })
1054                                            .collect::<Vec<_>>()
1055                                    }),
1056                                mo_density_symmetries: sda_res
1057                                    .mo_density_symmetries()
1058                                    .as_ref()
1059                                    .map(|mo_den_symss| {
1060                                        mo_den_symss
1061                                            .iter()
1062                                            .map(|mo_den_syms| {
1063                                                mo_den_syms
1064                                                    .iter()
1065                                                    .map(|mo_den_sym_opt| {
1066                                                        mo_den_sym_opt.as_ref().map(|mo_den_sym| {
1067                                                            mo_den_sym.to_string()
1068                                                        })
1069                                                    })
1070                                                    .collect::<Vec<_>>()
1071                                            })
1072                                            .collect::<Vec<_>>()
1073                                    }),
1074                            }
1075                        }
1076                    }
1077                }
1078                PyStructureConstraint::SpinOrbitCoupled(_) => {
1079                    let det_r = pydet_r
1080                        .to_qsym2::<SpinOrbitCoupled>(&baos_ref, mol)
1081                        .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
1082                    let det_c: SlaterDeterminant<C128, SpinOrbitCoupled> = det_r.into();
1083                    let sao_c = pysao_c.to_owned_array();
1084                    let sao_h_c = sao_h.and_then(|pysao_h| match pysao_h {
1085                        // sao_h must have the same reality as sao.
1086                        PyArray2RC::Real(_) => None,
1087                        PyArray2RC::Complex(pysao_h_c) => Some(pysao_h_c.to_owned_array()),
1088                    });
1089                    let sao_spatial_4c_c = sao_spatial_4c.and_then(|pysao4c| match pysao4c {
1090                        // sao_spatial_4c must have the same reality as sao.
1091                        PyArray4RC::Real(_) => None,
1092                        PyArray4RC::Complex(pysao4c_c) => Some(pysao4c_c.to_owned_array()),
1093                    });
1094                    let sao_spatial_4c_h_c =
1095                        sao_spatial_4c_h.and_then(|pysao4c_h| match pysao4c_h {
1096                            // sao_spatial_4c_h must have the same reality as sao_spatial.
1097                            PyArray4RC::Real(_) => None,
1098                            PyArray4RC::Complex(pysao4c_h_c) => Some(pysao4c_h_c.to_owned_array()),
1099                        });
1100                    match &use_magnetic_group {
1101                        Some(MagneticSymmetryAnalysisKind::Corepresentation) => {
1102                            let mut sda_driver = SlaterDeterminantRepAnalysisDriver::<
1103                                MagneticRepresentedSymmetryGroup,
1104                                C128,
1105                                SpinOrbitCoupled,
1106                            >::builder()
1107                            .parameters(&sda_params)
1108                            .angular_function_parameters(&afa_params)
1109                            .determinant(&det_c)
1110                            .sao(&sao_c)
1111                            .sao_h(sao_h_c.as_ref())
1112                            .sao_spatial_4c(sao_spatial_4c_c.as_ref())
1113                            .sao_spatial_4c_h(sao_spatial_4c_h_c.as_ref())
1114                            .symmetry_group(&pd_res)
1115                            .build()
1116                            .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
1117                            py.detach(|| {
1118                                sda_driver
1119                                    .run()
1120                                    .map_err(|err| PyRuntimeError::new_err(err.to_string()))
1121                            })?;
1122                            let sda_res = sda_driver
1123                                .result()
1124                                .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
1125                            PySlaterDeterminantRepAnalysisResult {
1126                                group: sda_res.group().name().clone(),
1127                                determinant_symmetry: sda_res
1128                                    .determinant_symmetry()
1129                                    .as_ref()
1130                                    .ok()
1131                                    .map(|sym| sym.to_string()),
1132                                mo_symmetries: sda_res.mo_symmetries().as_ref().map(|mo_symss| {
1133                                    mo_symss
1134                                        .iter()
1135                                        .map(|mo_syms| {
1136                                            mo_syms
1137                                                .iter()
1138                                                .map(|mo_sym_opt| {
1139                                                    mo_sym_opt
1140                                                        .as_ref()
1141                                                        .map(|mo_sym| mo_sym.to_string())
1142                                                })
1143                                                .collect::<Vec<_>>()
1144                                        })
1145                                        .collect::<Vec<_>>()
1146                                }),
1147                                determinant_density_symmetries: sda_res
1148                                    .determinant_density_symmetries()
1149                                    .as_ref()
1150                                    .map(|den_syms| {
1151                                        den_syms
1152                                            .iter()
1153                                            .map(|(den_name, den_sym_res)| {
1154                                                (
1155                                                    den_name.clone(),
1156                                                    den_sym_res
1157                                                        .as_ref()
1158                                                        .ok()
1159                                                        .map(|den_sym| den_sym.to_string()),
1160                                                )
1161                                            })
1162                                            .collect::<Vec<_>>()
1163                                    }),
1164                                mo_density_symmetries: sda_res
1165                                    .mo_density_symmetries()
1166                                    .as_ref()
1167                                    .map(|mo_den_symss| {
1168                                        mo_den_symss
1169                                            .iter()
1170                                            .map(|mo_den_syms| {
1171                                                mo_den_syms
1172                                                    .iter()
1173                                                    .map(|mo_den_sym_opt| {
1174                                                        mo_den_sym_opt.as_ref().map(|mo_den_sym| {
1175                                                            mo_den_sym.to_string()
1176                                                        })
1177                                                    })
1178                                                    .collect::<Vec<_>>()
1179                                            })
1180                                            .collect::<Vec<_>>()
1181                                    }),
1182                            }
1183                        }
1184                        Some(MagneticSymmetryAnalysisKind::Representation) | None => {
1185                            let mut sda_driver = SlaterDeterminantRepAnalysisDriver::<
1186                                UnitaryRepresentedSymmetryGroup,
1187                                C128,
1188                                SpinOrbitCoupled,
1189                            >::builder()
1190                            .parameters(&sda_params)
1191                            .angular_function_parameters(&afa_params)
1192                            .determinant(&det_c)
1193                            .sao(&sao_c)
1194                            .sao_h(sao_h_c.as_ref())
1195                            .sao_spatial_4c(sao_spatial_4c_c.as_ref())
1196                            .sao_spatial_4c_h(sao_spatial_4c_h_c.as_ref())
1197                            .symmetry_group(&pd_res)
1198                            .build()
1199                            .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
1200                            py.detach(|| {
1201                                sda_driver
1202                                    .run()
1203                                    .map_err(|err| PyRuntimeError::new_err(err.to_string()))
1204                            })?;
1205                            let sda_res = sda_driver
1206                                .result()
1207                                .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
1208                            PySlaterDeterminantRepAnalysisResult {
1209                                group: sda_res.group().name().clone(),
1210                                determinant_symmetry: sda_res
1211                                    .determinant_symmetry()
1212                                    .as_ref()
1213                                    .ok()
1214                                    .map(|sym| sym.to_string()),
1215                                mo_symmetries: sda_res.mo_symmetries().as_ref().map(|mo_symss| {
1216                                    mo_symss
1217                                        .iter()
1218                                        .map(|mo_syms| {
1219                                            mo_syms
1220                                                .iter()
1221                                                .map(|mo_sym_opt| {
1222                                                    mo_sym_opt
1223                                                        .as_ref()
1224                                                        .map(|mo_sym| mo_sym.to_string())
1225                                                })
1226                                                .collect::<Vec<_>>()
1227                                        })
1228                                        .collect::<Vec<_>>()
1229                                }),
1230                                determinant_density_symmetries: sda_res
1231                                    .determinant_density_symmetries()
1232                                    .as_ref()
1233                                    .map(|den_syms| {
1234                                        den_syms
1235                                            .iter()
1236                                            .map(|(den_name, den_sym_res)| {
1237                                                (
1238                                                    den_name.clone(),
1239                                                    den_sym_res
1240                                                        .as_ref()
1241                                                        .ok()
1242                                                        .map(|den_sym| den_sym.to_string()),
1243                                                )
1244                                            })
1245                                            .collect::<Vec<_>>()
1246                                    }),
1247                                mo_density_symmetries: sda_res
1248                                    .mo_density_symmetries()
1249                                    .as_ref()
1250                                    .map(|mo_den_symss| {
1251                                        mo_den_symss
1252                                            .iter()
1253                                            .map(|mo_den_syms| {
1254                                                mo_den_syms
1255                                                    .iter()
1256                                                    .map(|mo_den_sym_opt| {
1257                                                        mo_den_sym_opt.as_ref().map(|mo_den_sym| {
1258                                                            mo_den_sym.to_string()
1259                                                        })
1260                                                    })
1261                                                    .collect::<Vec<_>>()
1262                                            })
1263                                            .collect::<Vec<_>>()
1264                                    }),
1265                            }
1266                        }
1267                    }
1268                }
1269            }
1270        }
1271        (PySlaterDeterminant::Complex(pydet_c), _) => {
1272            match pydet_c.structure_constraint {
1273                PyStructureConstraint::SpinConstraint(_) => {
1274                    let det_c = if augment_to_generalised {
1275                        pydet_c
1276                            .to_qsym2::<SpinConstraint>(&baos_ref, mol)
1277                            .map_err(|err| PyRuntimeError::new_err(err.to_string()))?
1278                            .to_generalised()
1279                    } else {
1280                        pydet_c
1281                            .to_qsym2::<SpinConstraint>(&baos_ref, mol)
1282                            .map_err(|err| PyRuntimeError::new_err(err.to_string()))?
1283                    };
1284                    let sao_c = match sao {
1285                        PyArray2RC::Real(pysao_r) => pysao_r.to_owned_array().mapv(Complex::from),
1286                        PyArray2RC::Complex(pysao_c) => pysao_c.to_owned_array(),
1287                    };
1288                    let sao_h_c = sao_h.map(|pysao_h| match pysao_h {
1289                        // sao_h must have the same reality as sao.
1290                        PyArray2RC::Real(pysao_h_r) => {
1291                            pysao_h_r.to_owned_array().mapv(Complex::from)
1292                        }
1293                        PyArray2RC::Complex(pysao_h_c) => pysao_h_c.to_owned_array(),
1294                    });
1295                    let sao_spatial_4c_c = sao_spatial_4c.map(|pysao4c| match pysao4c {
1296                        // sao_spatial_4c must have the same reality as sao.
1297                        PyArray4RC::Real(pysao4c_r) => {
1298                            pysao4c_r.to_owned_array().mapv(Complex::from)
1299                        }
1300                        PyArray4RC::Complex(pysao4c_c) => pysao4c_c.to_owned_array(),
1301                    });
1302                    let sao_spatial_4c_h_c = sao_spatial_4c_h.map(|pysao4c_h| match pysao4c_h {
1303                        // sao_spatial_4c_h must have the same reality as sao.
1304                        PyArray4RC::Real(pysao4c_h_r) => {
1305                            pysao4c_h_r.to_owned_array().mapv(Complex::from)
1306                        }
1307                        PyArray4RC::Complex(pysao4c_h_c) => pysao4c_h_c.to_owned_array(),
1308                    });
1309                    match &use_magnetic_group {
1310                        Some(MagneticSymmetryAnalysisKind::Corepresentation) => {
1311                            let mut sda_driver = SlaterDeterminantRepAnalysisDriver::<
1312                                MagneticRepresentedSymmetryGroup,
1313                                C128,
1314                                SpinConstraint,
1315                            >::builder()
1316                            .parameters(&sda_params)
1317                            .angular_function_parameters(&afa_params)
1318                            .determinant(&det_c)
1319                            .sao(&sao_c)
1320                            .sao_h(sao_h_c.as_ref())
1321                            .sao_spatial_4c(sao_spatial_4c_c.as_ref())
1322                            .sao_spatial_4c_h(sao_spatial_4c_h_c.as_ref())
1323                            .symmetry_group(&pd_res)
1324                            .build()
1325                            .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
1326                            py.detach(|| {
1327                                sda_driver
1328                                    .run()
1329                                    .map_err(|err| PyRuntimeError::new_err(err.to_string()))
1330                            })?;
1331                            let sda_res = sda_driver
1332                                .result()
1333                                .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
1334                            PySlaterDeterminantRepAnalysisResult {
1335                                group: sda_res.group().name().clone(),
1336                                determinant_symmetry: sda_res
1337                                    .determinant_symmetry()
1338                                    .as_ref()
1339                                    .ok()
1340                                    .map(|sym| sym.to_string()),
1341                                mo_symmetries: sda_res.mo_symmetries().as_ref().map(|mo_symss| {
1342                                    mo_symss
1343                                        .iter()
1344                                        .map(|mo_syms| {
1345                                            mo_syms
1346                                                .iter()
1347                                                .map(|mo_sym_opt| {
1348                                                    mo_sym_opt
1349                                                        .as_ref()
1350                                                        .map(|mo_sym| mo_sym.to_string())
1351                                                })
1352                                                .collect::<Vec<_>>()
1353                                        })
1354                                        .collect::<Vec<_>>()
1355                                }),
1356                                determinant_density_symmetries: sda_res
1357                                    .determinant_density_symmetries()
1358                                    .as_ref()
1359                                    .map(|den_syms| {
1360                                        den_syms
1361                                            .iter()
1362                                            .map(|(den_name, den_sym_res)| {
1363                                                (
1364                                                    den_name.clone(),
1365                                                    den_sym_res
1366                                                        .as_ref()
1367                                                        .ok()
1368                                                        .map(|den_sym| den_sym.to_string()),
1369                                                )
1370                                            })
1371                                            .collect::<Vec<_>>()
1372                                    }),
1373                                mo_density_symmetries: sda_res
1374                                    .mo_density_symmetries()
1375                                    .as_ref()
1376                                    .map(|mo_den_symss| {
1377                                        mo_den_symss
1378                                            .iter()
1379                                            .map(|mo_den_syms| {
1380                                                mo_den_syms
1381                                                    .iter()
1382                                                    .map(|mo_den_sym_opt| {
1383                                                        mo_den_sym_opt.as_ref().map(|mo_den_sym| {
1384                                                            mo_den_sym.to_string()
1385                                                        })
1386                                                    })
1387                                                    .collect::<Vec<_>>()
1388                                            })
1389                                            .collect::<Vec<_>>()
1390                                    }),
1391                            }
1392                        }
1393                        Some(MagneticSymmetryAnalysisKind::Representation) | None => {
1394                            let mut sda_driver = SlaterDeterminantRepAnalysisDriver::<
1395                                UnitaryRepresentedSymmetryGroup,
1396                                C128,
1397                                SpinConstraint,
1398                            >::builder()
1399                            .parameters(&sda_params)
1400                            .angular_function_parameters(&afa_params)
1401                            .determinant(&det_c)
1402                            .sao(&sao_c)
1403                            .sao_h(sao_h_c.as_ref())
1404                            .sao_spatial_4c(sao_spatial_4c_c.as_ref())
1405                            .sao_spatial_4c_h(sao_spatial_4c_h_c.as_ref())
1406                            .symmetry_group(&pd_res)
1407                            .build()
1408                            .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
1409                            py.detach(|| {
1410                                sda_driver
1411                                    .run()
1412                                    .map_err(|err| PyRuntimeError::new_err(err.to_string()))
1413                            })?;
1414                            let sda_res = sda_driver
1415                                .result()
1416                                .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
1417                            PySlaterDeterminantRepAnalysisResult {
1418                                group: sda_res.group().name().clone(),
1419                                determinant_symmetry: sda_res
1420                                    .determinant_symmetry()
1421                                    .as_ref()
1422                                    .ok()
1423                                    .map(|sym| sym.to_string()),
1424                                mo_symmetries: sda_res.mo_symmetries().as_ref().map(|mo_symss| {
1425                                    mo_symss
1426                                        .iter()
1427                                        .map(|mo_syms| {
1428                                            mo_syms
1429                                                .iter()
1430                                                .map(|mo_sym_opt| {
1431                                                    mo_sym_opt
1432                                                        .as_ref()
1433                                                        .map(|mo_sym| mo_sym.to_string())
1434                                                })
1435                                                .collect::<Vec<_>>()
1436                                        })
1437                                        .collect::<Vec<_>>()
1438                                }),
1439                                determinant_density_symmetries: sda_res
1440                                    .determinant_density_symmetries()
1441                                    .as_ref()
1442                                    .map(|den_syms| {
1443                                        den_syms
1444                                            .iter()
1445                                            .map(|(den_name, den_sym_res)| {
1446                                                (
1447                                                    den_name.clone(),
1448                                                    den_sym_res
1449                                                        .as_ref()
1450                                                        .ok()
1451                                                        .map(|den_sym| den_sym.to_string()),
1452                                                )
1453                                            })
1454                                            .collect::<Vec<_>>()
1455                                    }),
1456                                mo_density_symmetries: sda_res
1457                                    .mo_density_symmetries()
1458                                    .as_ref()
1459                                    .map(|mo_den_symss| {
1460                                        mo_den_symss
1461                                            .iter()
1462                                            .map(|mo_den_syms| {
1463                                                mo_den_syms
1464                                                    .iter()
1465                                                    .map(|mo_den_sym_opt| {
1466                                                        mo_den_sym_opt.as_ref().map(|mo_den_sym| {
1467                                                            mo_den_sym.to_string()
1468                                                        })
1469                                                    })
1470                                                    .collect::<Vec<_>>()
1471                                            })
1472                                            .collect::<Vec<_>>()
1473                                    }),
1474                            }
1475                        }
1476                    }
1477                }
1478                PyStructureConstraint::SpinOrbitCoupled(_) => {
1479                    let det_c = pydet_c
1480                        .to_qsym2::<SpinOrbitCoupled>(&baos_ref, mol)
1481                        .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
1482                    let sao_c = match sao {
1483                        PyArray2RC::Real(pysao_r) => pysao_r.to_owned_array().mapv(Complex::from),
1484                        PyArray2RC::Complex(pysao_c) => pysao_c.to_owned_array(),
1485                    };
1486                    let sao_h_c = sao_h.map(|pysao_h| match pysao_h {
1487                        // sao_spatial_h must have the same reality as sao.
1488                        PyArray2RC::Real(pysao_h_r) => {
1489                            pysao_h_r.to_owned_array().mapv(Complex::from)
1490                        }
1491                        PyArray2RC::Complex(pysao_h_c) => pysao_h_c.to_owned_array(),
1492                    });
1493                    let sao_spatial_4c_c = sao_spatial_4c.map(|pysao4c| match pysao4c {
1494                        // sao_spatial_4c must have the same reality as sao.
1495                        PyArray4RC::Real(pysao4c_r) => {
1496                            pysao4c_r.to_owned_array().mapv(Complex::from)
1497                        }
1498                        PyArray4RC::Complex(pysao4c_c) => pysao4c_c.to_owned_array(),
1499                    });
1500                    let sao_spatial_4c_h_c = sao_spatial_4c_h.map(|pysao4c_h| match pysao4c_h {
1501                        // sao_spatial_4c_h must have the same reality as sao.
1502                        PyArray4RC::Real(pysao4c_h_r) => {
1503                            pysao4c_h_r.to_owned_array().mapv(Complex::from)
1504                        }
1505                        PyArray4RC::Complex(pysao4c_h_c) => pysao4c_h_c.to_owned_array(),
1506                    });
1507                    match &use_magnetic_group {
1508                        Some(MagneticSymmetryAnalysisKind::Corepresentation) => {
1509                            let mut sda_driver = SlaterDeterminantRepAnalysisDriver::<
1510                                MagneticRepresentedSymmetryGroup,
1511                                C128,
1512                                SpinOrbitCoupled,
1513                            >::builder()
1514                            .parameters(&sda_params)
1515                            .angular_function_parameters(&afa_params)
1516                            .determinant(&det_c)
1517                            .sao(&sao_c)
1518                            .sao_h(sao_h_c.as_ref())
1519                            .sao_spatial_4c(sao_spatial_4c_c.as_ref())
1520                            .sao_spatial_4c_h(sao_spatial_4c_h_c.as_ref())
1521                            .symmetry_group(&pd_res)
1522                            .build()
1523                            .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
1524                            py.detach(|| {
1525                                sda_driver
1526                                    .run()
1527                                    .map_err(|err| PyRuntimeError::new_err(err.to_string()))
1528                            })?;
1529                            let sda_res = sda_driver
1530                                .result()
1531                                .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
1532                            PySlaterDeterminantRepAnalysisResult {
1533                                group: sda_res.group().name().clone(),
1534                                determinant_symmetry: sda_res
1535                                    .determinant_symmetry()
1536                                    .as_ref()
1537                                    .ok()
1538                                    .map(|sym| sym.to_string()),
1539                                mo_symmetries: sda_res.mo_symmetries().as_ref().map(|mo_symss| {
1540                                    mo_symss
1541                                        .iter()
1542                                        .map(|mo_syms| {
1543                                            mo_syms
1544                                                .iter()
1545                                                .map(|mo_sym_opt| {
1546                                                    mo_sym_opt
1547                                                        .as_ref()
1548                                                        .map(|mo_sym| mo_sym.to_string())
1549                                                })
1550                                                .collect::<Vec<_>>()
1551                                        })
1552                                        .collect::<Vec<_>>()
1553                                }),
1554                                determinant_density_symmetries: sda_res
1555                                    .determinant_density_symmetries()
1556                                    .as_ref()
1557                                    .map(|den_syms| {
1558                                        den_syms
1559                                            .iter()
1560                                            .map(|(den_name, den_sym_res)| {
1561                                                (
1562                                                    den_name.clone(),
1563                                                    den_sym_res
1564                                                        .as_ref()
1565                                                        .ok()
1566                                                        .map(|den_sym| den_sym.to_string()),
1567                                                )
1568                                            })
1569                                            .collect::<Vec<_>>()
1570                                    }),
1571                                mo_density_symmetries: sda_res
1572                                    .mo_density_symmetries()
1573                                    .as_ref()
1574                                    .map(|mo_den_symss| {
1575                                        mo_den_symss
1576                                            .iter()
1577                                            .map(|mo_den_syms| {
1578                                                mo_den_syms
1579                                                    .iter()
1580                                                    .map(|mo_den_sym_opt| {
1581                                                        mo_den_sym_opt.as_ref().map(|mo_den_sym| {
1582                                                            mo_den_sym.to_string()
1583                                                        })
1584                                                    })
1585                                                    .collect::<Vec<_>>()
1586                                            })
1587                                            .collect::<Vec<_>>()
1588                                    }),
1589                            }
1590                        }
1591                        Some(MagneticSymmetryAnalysisKind::Representation) | None => {
1592                            let mut sda_driver = SlaterDeterminantRepAnalysisDriver::<
1593                                UnitaryRepresentedSymmetryGroup,
1594                                C128,
1595                                SpinOrbitCoupled,
1596                            >::builder()
1597                            .parameters(&sda_params)
1598                            .angular_function_parameters(&afa_params)
1599                            .determinant(&det_c)
1600                            .sao(&sao_c)
1601                            .sao_h(sao_h_c.as_ref())
1602                            .sao_spatial_4c(sao_spatial_4c_c.as_ref())
1603                            .sao_spatial_4c_h(sao_spatial_4c_h_c.as_ref())
1604                            .symmetry_group(&pd_res)
1605                            .build()
1606                            .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
1607                            py.detach(|| {
1608                                sda_driver
1609                                    .run()
1610                                    .map_err(|err| PyRuntimeError::new_err(err.to_string()))
1611                            })?;
1612                            let sda_res = sda_driver
1613                                .result()
1614                                .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
1615                            PySlaterDeterminantRepAnalysisResult {
1616                                group: sda_res.group().name().clone(),
1617                                determinant_symmetry: sda_res
1618                                    .determinant_symmetry()
1619                                    .as_ref()
1620                                    .ok()
1621                                    .map(|sym| sym.to_string()),
1622                                mo_symmetries: sda_res.mo_symmetries().as_ref().map(|mo_symss| {
1623                                    mo_symss
1624                                        .iter()
1625                                        .map(|mo_syms| {
1626                                            mo_syms
1627                                                .iter()
1628                                                .map(|mo_sym_opt| {
1629                                                    mo_sym_opt
1630                                                        .as_ref()
1631                                                        .map(|mo_sym| mo_sym.to_string())
1632                                                })
1633                                                .collect::<Vec<_>>()
1634                                        })
1635                                        .collect::<Vec<_>>()
1636                                }),
1637                                determinant_density_symmetries: sda_res
1638                                    .determinant_density_symmetries()
1639                                    .as_ref()
1640                                    .map(|den_syms| {
1641                                        den_syms
1642                                            .iter()
1643                                            .map(|(den_name, den_sym_res)| {
1644                                                (
1645                                                    den_name.clone(),
1646                                                    den_sym_res
1647                                                        .as_ref()
1648                                                        .ok()
1649                                                        .map(|den_sym| den_sym.to_string()),
1650                                                )
1651                                            })
1652                                            .collect::<Vec<_>>()
1653                                    }),
1654                                mo_density_symmetries: sda_res
1655                                    .mo_density_symmetries()
1656                                    .as_ref()
1657                                    .map(|mo_den_symss| {
1658                                        mo_den_symss
1659                                            .iter()
1660                                            .map(|mo_den_syms| {
1661                                                mo_den_syms
1662                                                    .iter()
1663                                                    .map(|mo_den_sym_opt| {
1664                                                        mo_den_sym_opt.as_ref().map(|mo_den_sym| {
1665                                                            mo_den_sym.to_string()
1666                                                        })
1667                                                    })
1668                                                    .collect::<Vec<_>>()
1669                                            })
1670                                            .collect::<Vec<_>>()
1671                                    }),
1672                            }
1673                        }
1674                    }
1675                }
1676            }
1677        }
1678    };
1679    Ok(pysda_res)
1680}