Skip to main content

qsym2/bindings/python/representation_analysis/multideterminant/
multideterminant_orbit_basis_internal_solver.rs

1//! Python bindings for QSym² symmetry analysis of multi-determinants with orbit bases using
2//! the internal NOCI solver.
3
4use std::collections::HashSet;
5use std::path::PathBuf;
6
7use anyhow::{bail, format_err};
8use itertools::Itertools;
9use log;
10use ndarray::{Array1, Array2, Axis, ShapeBuilder};
11use num_complex::Complex;
12use numpy::{PyArrayMethods, ToPyArray};
13use pyo3::exceptions::{PyIOError, PyRuntimeError, PyTypeError};
14use pyo3::types::PyFunction;
15use pyo3::{IntoPyObjectExt, prelude::*};
16
17use crate::analysis::EigenvalueComparisonMode;
18use crate::angmom::spinor_rotation_3d::{SpinConstraint, SpinOrbitCoupled};
19use crate::bindings::python::integrals::{PyBasisAngularOrder, PyStructureConstraint};
20use crate::bindings::python::representation_analysis::multideterminant::{
21    PyMultiDeterminantsComplex, PyMultiDeterminantsReal,
22};
23use crate::bindings::python::representation_analysis::slater_determinant::PySlaterDeterminant;
24use crate::bindings::python::representation_analysis::{PyArray2RC, PyArray4RC, PyScalarRC};
25use crate::drivers::QSym2Driver;
26use crate::drivers::representation_analysis::angular_function::AngularFunctionRepAnalysisParams;
27use crate::drivers::representation_analysis::multideterminant::{
28    MultiDeterminantRepAnalysisDriver, MultiDeterminantRepAnalysisParams,
29};
30use crate::drivers::representation_analysis::{
31    CharacterTableDisplay, MagneticSymmetryAnalysisKind,
32};
33use crate::drivers::symmetry_group_detection::SymmetryGroupDetectionResult;
34use crate::io::format::qsym2_output;
35use crate::io::{QSym2FileType, read_qsym2_binary};
36use crate::symmetry::symmetry_group::{
37    MagneticRepresentedSymmetryGroup, SymmetryGroupProperties, UnitaryRepresentedSymmetryGroup,
38};
39use crate::symmetry::symmetry_transformation::SymmetryTransformationKind;
40use crate::target::noci::backend::matelem::hamiltonian::HamiltonianAO;
41use crate::target::noci::backend::matelem::overlap::OverlapAO;
42use crate::target::noci::backend::solver::noci::SymmetryOrbitNOCISolvable;
43use crate::target::noci::basis::Basis;
44use crate::target::noci::multideterminants::MultiDeterminants;
45
46// ~~~~~~~~~~~~~
47// Macro helpers
48// ~~~~~~~~~~~~~
49#[macro_export]
50macro_rules! generate_get_jk {
51    ($get_jk_name:ident, $py_get_jk_func:ident, $t:ty) => {
52        let $get_jk_name = move |dm: &Array2<$t>| {
53            Python::attach(|py_inner| {
54                let py_dm = dm.to_pyarray(py_inner);
55                $py_get_jk_func
56                    .call1(py_inner, (py_dm,))
57                    .and_then(|res| res.extract::<(Vec<Vec<$t>>, Vec<Vec<$t>>)>(py_inner))
58                    .and_then(|(jss, kss)| {
59                        let nrows = jss.len();
60                        let ncols_set = jss.iter().map(|js| js.len()).collect::<HashSet<usize>>();
61                        if ncols_set.len() != 1 {
62                            return Err(PyRuntimeError::new_err(
63                                "Inconsistent numbers of columns.".to_string(),
64                            ));
65                        }
66                        let ncols = *ncols_set
67                            .iter()
68                            .next()
69                            .unwrap_or_else(|| panic!("Unable to extract the number of columns."));
70
71                        let j = Array2::<$t>::from_shape_vec(
72                            (nrows, ncols),
73                            jss.into_iter().flatten().collect::<Vec<_>>(),
74                        )
75                        .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
76                        let k = Array2::<$t>::from_shape_vec(
77                            (nrows, ncols),
78                            kss.into_iter().flatten().collect::<Vec<_>>(),
79                        )
80                        .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
81                        Ok((j, k))
82                    })
83                    .map_err(|err| format_err!(err))
84            })
85        };
86    };
87}
88
89/// Python-exposed function to run non-orthogonal configuration interaction using the internal
90/// solver and then perform representation symmetry analysis on the resulting real and complex
91/// multi-determinantal wavefunctions constructed from group-generated orbits and log the result via
92/// the `qsym2-output` logger at the `INFO` level.
93///
94/// If `symmetry_transformation_kind` includes spin transformation, the provided
95/// multi-determinantal wavefunctions with spin constraint structure will be augmented to
96/// the generalised spin constraint automatically.
97///
98/// # Arguments
99///
100/// * `inp_sym` - A path to the [`QSym2FileType::Sym`] file containing the symmetry-group detection
101/// result for the system. This will be used to construct abstract groups and character tables for
102/// representation analysis.
103/// * `pyorigins` - A list of Python-exposed Slater determinants whose coefficients are of type
104/// `float64` or `complex128`. These determinants serve as origins for group-generated orbits which
105/// serve as basis states for non-orthogonal configuration interaction to yield multi-determinantal
106/// wavefunctions, the symmetry of which will be analysed by this function.
107/// * `pybaos` - Python-exposed structures containing basis angular order information, one for each
108/// explicit component per coefficient matrix.
109/// * `sao` - The atomic-orbital overlap matrix whose elements are of type `float64` or
110/// `complex128`.
111/// * `enuc` - The nuclear repulsion energy.
112/// * `onee` - The one-electron integral matrix whose elements are of type `float64` or
113/// `complex128`.
114/// * `twoe` - The two-electron integral tensor in physicists' ordering whose elements are of type
115/// `float64` or `complex128`. Either this or `py_get_jk` must be specified, but not both.
116/// * `py_get_jk` - A Python function callable on a density matrix $`\mathbf{D}`$ to calculate the
117/// corresponding $`\mathbf{J}`$ and $`\mathbf{K}`$ matrices by contracting with appropriate
118/// two-electron integrals calculated on-the-fly. Either this or `twoe` must be specified, but not
119/// both.
120/// Python type: `Callable[[numpy.2darray[float] | numpy.2darray[complex]], tuple[numpy.2darray[float], numpy.2darray[float]] | tuple[numpy.2darray[complex], numpy.2darray[complex]]] | None`.
121/// * `thresh_offdiag` - Threshold for identifying non-zero off-diagonal elements in Löwdin
122/// pairing.
123/// * `thresh_zeroov` - Threshold for identifying non-zero overlaps in Löwdin pairing.
124/// * `calculate_density_matrices` - Boolean indicating if the density matrices for the resulting
125/// multi-determinants should be computed.
126/// * `integrality_threshold` - The threshold for verifying if subspace multiplicities are
127/// integral.
128/// * `linear_independence_threshold` - The threshold for determining the linear independence
129/// subspace via the non-zero eigenvalues of the orbit overlap matrix.
130/// * `orbit_overlap_symmetry_threshold` - The threshold for checking the symmetry of the orbit
131/// overlap matrix.
132/// * `use_magnetic_group` - An option indicating if the magnetic group is to be used for symmetry
133/// analysis, and if so, whether unitary representations or unitary-antiunitary corepresentations
134/// should be used.
135/// * `use_double_group` - A boolean indicating if the double group of the prevailing symmetry
136/// group is to be used for representation analysis instead.
137/// * `use_cayley_table` - A boolean indicating if the Cayley table for the group, if available,
138/// should be used to speed up the calculation of orbit overlap matrices.
139/// * `symmetry_transformation_kind` - An enumerated type indicating the type of symmetry
140/// transformations to be performed on the origin determinant to generate the orbit. If this
141/// contains spin transformation, the multi-determinant will be augmented to generalised spin
142/// constraint automatically.
143/// * `eigenvalue_comparison_mode` - An enumerated type indicating the mode of comparison of orbit
144/// overlap eigenvalues with the specified `linear_independence_threshold`.
145/// * `sao_h` - The optional complex-symmetric atomic-orbital overlap matrix whose elements
146/// are of type `float64` or `complex128`. This is required if antiunitary symmetry operations are
147/// involved.
148/// * `write_overlap_eigenvalues` - A boolean indicating if the eigenvalues of the determinant
149/// orbit overlap matrix are to be written to the output.
150/// * `write_character_table` - A boolean indicating if the character table of the prevailing
151/// symmetry group is to be printed out.
152/// * `infinite_order_to_finite` - The finite order with which infinite-order generators are to be
153/// interpreted to form a finite subgroup of the prevailing infinite group. This finite subgroup
154/// will be used for symmetry analysis.
155/// * `angular_function_integrality_threshold` - The threshold for verifying if subspace
156/// multiplicities are integral for the symmetry analysis of angular functions.
157/// * `angular_function_linear_independence_threshold` - The threshold for determining the linear
158/// independence subspace via the non-zero eigenvalues of the orbit overlap matrix for the symmetry
159/// analysis of angular functions.
160/// * `angular_function_orbit_overlap_symmetry_threshold` - The threshold for checking the symmetry
161/// of the orbit overlap matrices for the symmetry analysis of angular functions.
162/// * `angular_function_max_angular_momentum` - The maximum angular momentum order to be used in
163/// angular function symmetry analysis.
164///
165/// # Returns
166///
167/// The result will be returned as an object containing the Slater determinant basis and the linear
168/// combination coefficients as a two-dimensional array with each column corresponding to one
169/// computed multi-determinantal state.
170#[allow(clippy::too_many_arguments)]
171#[pyfunction]
172#[pyo3(signature = (
173    inp_sym,
174    pyorigins,
175    pybaos,
176    sao,
177    enuc,
178    onee,
179    twoe,
180    py_get_jk,
181    thresh_offdiag,
182    thresh_zeroov,
183    calculate_density_matrices,
184    integrality_threshold,
185    linear_independence_threshold,
186    orbit_overlap_symmetry_threshold,
187    use_magnetic_group,
188    use_double_group,
189    use_cayley_table,
190    symmetry_transformation_kind,
191    eigenvalue_comparison_mode,
192    sao_h=None,
193    write_overlap_eigenvalues=true,
194    write_character_table=true,
195    infinite_order_to_finite=None,
196    angular_function_integrality_threshold=1e-7,
197    angular_function_linear_independence_threshold=1e-7,
198    angular_function_orbit_overlap_symmetry_threshold=1e-7,
199    angular_function_max_angular_momentum=2
200))]
201pub fn rep_analyse_multideterminants_orbit_basis_internal_solver(
202    py: Python<'_>,
203    inp_sym: PathBuf,
204    pyorigins: Vec<PySlaterDeterminant>,
205    pybaos: Vec<PyBasisAngularOrder>,
206    sao: PyArray2RC,
207    enuc: PyScalarRC,
208    onee: PyArray2RC,
209    twoe: Option<PyArray4RC>,
210    py_get_jk: Option<Py<PyFunction>>,
211    thresh_offdiag: f64,
212    thresh_zeroov: f64,
213    calculate_density_matrices: bool,
214    integrality_threshold: f64,
215    linear_independence_threshold: f64,
216    orbit_overlap_symmetry_threshold: f64,
217    use_magnetic_group: Option<MagneticSymmetryAnalysisKind>,
218    use_double_group: bool,
219    use_cayley_table: bool,
220    symmetry_transformation_kind: SymmetryTransformationKind,
221    eigenvalue_comparison_mode: EigenvalueComparisonMode,
222    sao_h: Option<PyArray2RC>,
223    write_overlap_eigenvalues: bool,
224    write_character_table: bool,
225    infinite_order_to_finite: Option<u32>,
226    angular_function_integrality_threshold: f64,
227    angular_function_linear_independence_threshold: f64,
228    angular_function_orbit_overlap_symmetry_threshold: f64,
229    angular_function_max_angular_momentum: u32,
230) -> PyResult<Py<PyAny>> {
231    // Read in point-group detection results
232    let pd_res: SymmetryGroupDetectionResult =
233        read_qsym2_binary(inp_sym.clone(), QSym2FileType::Sym)
234            .map_err(|err| PyIOError::new_err(err.to_string()))?;
235
236    let mut file_name = inp_sym.to_path_buf();
237    file_name.set_extension(QSym2FileType::Sym.ext());
238    qsym2_output!(
239        "Symmetry-group detection results read in from {}.",
240        file_name.display(),
241    );
242    qsym2_output!("");
243
244    // Set up basic parameters
245    let mol = &pd_res.pre_symmetry.recentred_molecule;
246
247    let baos = pybaos
248        .iter()
249        .map(|bao| {
250            bao.to_qsym2(mol)
251                .map_err(|err| PyRuntimeError::new_err(err.to_string()))
252        })
253        .collect::<Result<Vec<_>, _>>()?;
254    let baos_ref = baos.iter().collect::<Vec<_>>();
255    let augment_to_generalised = match symmetry_transformation_kind {
256        SymmetryTransformationKind::SpatialWithSpinTimeReversal
257        | SymmetryTransformationKind::Spin
258        | SymmetryTransformationKind::SpinSpatial => true,
259        SymmetryTransformationKind::Spatial => false,
260    };
261    let afa_params = AngularFunctionRepAnalysisParams::builder()
262        .integrality_threshold(angular_function_integrality_threshold)
263        .linear_independence_threshold(angular_function_linear_independence_threshold)
264        .orbit_overlap_symmetry_threshold(angular_function_orbit_overlap_symmetry_threshold)
265        .max_angular_momentum(angular_function_max_angular_momentum)
266        .build()
267        .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
268    let mda_params = MultiDeterminantRepAnalysisParams::<f64>::builder()
269        .integrality_threshold(integrality_threshold)
270        .linear_independence_threshold(linear_independence_threshold)
271        .orbit_overlap_symmetry_threshold(orbit_overlap_symmetry_threshold)
272        .use_magnetic_group(use_magnetic_group.clone())
273        .use_double_group(use_double_group)
274        .use_cayley_table(use_cayley_table)
275        .symmetry_transformation_kind(symmetry_transformation_kind.clone())
276        .eigenvalue_comparison_mode(eigenvalue_comparison_mode)
277        .write_overlap_eigenvalues(write_overlap_eigenvalues)
278        .write_character_table(if write_character_table {
279            Some(CharacterTableDisplay::Symbolic)
280        } else {
281            None
282        })
283        .infinite_order_to_finite(infinite_order_to_finite)
284        .build()
285        .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
286
287    let all_real_origins = pyorigins
288        .iter()
289        .all(|pyorigin| matches!(pyorigin, PySlaterDeterminant::Real(_)));
290    let all_real_integrals = matches!(sao, PyArray2RC::Real(_))
291        && matches!(enuc, PyScalarRC::Real(_))
292        && matches!(onee, PyArray2RC::Real(_))
293        && matches!(twoe, Some(PyArray4RC::Real(_)) | None);
294    let all_real = all_real_origins && all_real_integrals;
295
296    let structure_constraints_set = pyorigins
297        .iter()
298        .map(|pyorigin| match pyorigin {
299            PySlaterDeterminant::Real(pydet) => pydet.structure_constraint().clone(),
300            PySlaterDeterminant::Complex(pydet) => pydet.structure_constraint().clone(),
301        })
302        .collect::<HashSet<_>>();
303    if structure_constraints_set.len() != 1 {
304        return Err(PyRuntimeError::new_err(
305            "Inconsistent structure constraints across origin determinants.`",
306        ));
307    };
308    let structure_constraint = structure_constraints_set
309        .iter()
310        .next()
311        .ok_or_else(|| PyRuntimeError::new_err("Unable to retrieve the structure constraint."))?;
312
313    // Decision tree:
314    // - all_real?
315    //   + yes:
316    //     - structure_constraint:
317    //       + SpinConstraint:
318    //         - use_magnetic_group:
319    //           + Some(Corepresentation)
320    //           + Some(Representation) | None
321    //       + SpinOrbitCoupled: not supported
322    //   - no:
323    //     - structure_constraint:
324    //       + SpinConstraint:
325    //         - use_magnetic_group:
326    //           + Some(Corepresentation)
327    //           + Some(Representation) | None
328    //       + SpinOrbitCoupled:
329    //         - use_magnetic_group:
330    //           + Some(Corepresentation)
331    //           + Some(Representation) | None
332    if all_real {
333        // Real numeric data type
334        // Only SpinConstraint is supported for real numeric data type.
335
336        if matches!(
337            structure_constraint,
338            PyStructureConstraint::SpinOrbitCoupled(_)
339        ) {
340            return Err(PyRuntimeError::new_err(
341                "Real determinants cannot support spin--orbit-coupled structure constraint.",
342            ));
343        }
344
345        // Preparation
346        let sao_r = match sao {
347            PyArray2RC::Real(pysao_r) => Ok(pysao_r.to_owned_array()),
348            PyArray2RC::Complex(_) => Err(PyTypeError::new_err(
349                "Unexpected complex type for the SAO matrix.",
350            )),
351        }?;
352        let enuc_r = match enuc {
353            PyScalarRC::Real(enuc_r) => Ok(enuc_r),
354            PyScalarRC::Complex(_) => Err(PyTypeError::new_err(
355                "Unexpected complex type for the nuclear repulsion energy.",
356            )),
357        }?;
358        let onee_r = match onee {
359            PyArray2RC::Real(pyonee_r) => Ok(pyonee_r.to_owned_array()),
360            PyArray2RC::Complex(_) => Err(PyTypeError::new_err(
361                "Unexpected complex type for the one-electron integral matrix.",
362            )),
363        }?;
364        let twoe_r = match twoe {
365            Some(PyArray4RC::Real(pytwoe_r)) => Ok(Some(pytwoe_r.to_owned_array())),
366            Some(PyArray4RC::Complex(_)) => Err(PyTypeError::new_err(
367                "Unexpected complex type for the two-electron integral tensor.",
368            )),
369            None => Ok(None),
370        }?;
371        let get_jk_r = py_get_jk.map(|py_get_jk_inner| {
372            generate_get_jk!(get_jk_r, py_get_jk_inner, f64);
373            get_jk_r
374        });
375        let overlap_ao = OverlapAO::<f64, SpinConstraint>::builder()
376            .sao(sao_r.view())
377            .build()
378            .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
379        let hamiltonian_ao = HamiltonianAO::<f64, SpinConstraint, _>::builder()
380            .enuc(enuc_r)
381            .onee(onee_r.view())
382            .twoe(twoe_r.as_ref().map(|m| m.view()))
383            .get_jk(get_jk_r)
384            .build()
385            .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
386
387        let origins_r = if augment_to_generalised {
388            pyorigins
389                .iter()
390                .map(|pydet| {
391                    if let PySlaterDeterminant::Real(pydet_r) = pydet {
392                        pydet_r
393                            .to_qsym2::<SpinConstraint>(&baos_ref, mol)
394                            .map(|det_r| det_r.to_generalised())
395                    } else {
396                        bail!("Unexpected complex type for an origin Slater determinant.")
397                    }
398                })
399                .collect::<Result<Vec<_>, _>>()
400        } else {
401            pyorigins
402                .iter()
403                .map(|pydet| {
404                    if let PySlaterDeterminant::Real(pydet_r) = pydet {
405                        pydet_r.to_qsym2::<SpinConstraint>(&baos_ref, mol)
406                    } else {
407                        bail!("Unexpected complex type for an origin Slater determinant.")
408                    }
409                })
410                .collect::<Result<Vec<_>, _>>()
411        }
412        .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
413
414        match &use_magnetic_group {
415            Some(MagneticSymmetryAnalysisKind::Corepresentation) => {
416                // Magnetic groups with corepresentations
417                let group = py
418                    .detach(|| {
419                        let magsym = pd_res
420                            .magnetic_symmetry
421                            .as_ref()
422                            .ok_or(format_err!("Magnetic group required for orbit construction, but no magnetic symmetry found."))?;
423                        if use_double_group {
424                            MagneticRepresentedSymmetryGroup::from_molecular_symmetry(
425                                magsym,
426                                infinite_order_to_finite,
427                            )
428                            .and_then(|grp| grp.to_double_group())
429                        } else {
430                            MagneticRepresentedSymmetryGroup::from_molecular_symmetry(
431                                magsym,
432                                infinite_order_to_finite,
433                            )
434                        }
435                    })
436                    .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
437
438                // Run NOCI
439                let system = (&hamiltonian_ao, &overlap_ao);
440                let multidets_vec = system
441                    .solve_symmetry_orbit_noci(
442                        &origins_r.iter().collect_vec(),
443                        &group,
444                        symmetry_transformation_kind,
445                        use_cayley_table,
446                        thresh_offdiag,
447                        thresh_zeroov,
448                    )
449                    .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
450                let multidets = MultiDeterminants::from_multideterminant_vec(
451                    &multidets_vec.iter().collect::<Vec<_>>(),
452                )
453                .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
454
455                // Symmetry analysis for NOCI states
456                let mut mda_driver = MultiDeterminantRepAnalysisDriver::<
457                    MagneticRepresentedSymmetryGroup,
458                    f64,
459                    _,
460                    SpinConstraint,
461                >::builder()
462                .parameters(&mda_params)
463                .angular_function_parameters(&afa_params)
464                .multidets(&multidets)
465                .sao(&sao_r)
466                .sao_h(None) // Real SAO.
467                .symmetry_group(&pd_res)
468                .build()
469                .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
470                py.detach(|| {
471                    mda_driver
472                        .run()
473                        .map_err(|err| PyRuntimeError::new_err(err.to_string()))
474                })?;
475
476                // Collect multi-determinantal wavefunctions for returning
477                let basis = multidets
478                    .basis()
479                    .iter()
480                    .map(|det_res| det_res.and_then(|det| det.to_python(py)))
481                    .collect::<Result<Vec<_>, _>>()
482                    .map_err(|_| {
483                        PyRuntimeError::new_err(
484                            "Unable to obtain the basis of Slater determinants.".to_string(),
485                        )
486                    })?;
487                let (coefficientss, energies): (Vec<_>, Vec<_>) = multidets_vec
488                    .iter()
489                    .map(|multidet| {
490                        let coefficients = multidet.coefficients().iter().cloned().collect_vec();
491                        let energy = *multidet.energy().unwrap_or(&f64::NAN);
492                        Ok::<_, PyErr>((coefficients, energy))
493                    })
494                    .collect::<Result<Vec<_>, _>>()?
495                    .into_iter()
496                    .unzip();
497                let coefficientss_arr = Array2::from_shape_vec(
498                    (basis.len(), coefficientss.len()).f(),
499                    coefficientss.into_iter().flatten().collect_vec(),
500                )
501                .map_err(|err| PyRuntimeError::new_err(err.to_string()))?
502                .to_pyarray(py);
503                let energies_arr = Array1::from_vec(energies).to_pyarray(py);
504                let density_matrices = if calculate_density_matrices {
505                    log::debug!("Calculating density matrices...");
506                    let denmats_opt = multidets
507                        .density_matrices(&sao_r.view(), thresh_offdiag, thresh_zeroov, true)
508                        .map(|denmats| {
509                            denmats
510                                .axis_iter(Axis(0))
511                                .map(|denmat| denmat.to_pyarray(py))
512                                .collect_vec()
513                        })
514                        .ok();
515                    log::debug!("Calculating density matrices... Done.");
516                    denmats_opt
517                } else {
518                    None
519                };
520                let pymultidet = PyMultiDeterminantsReal::new(
521                    basis,
522                    coefficientss_arr,
523                    energies_arr,
524                    density_matrices,
525                    multidets.threshold(),
526                )
527                .into_py_any(py)?;
528                Ok(pymultidet)
529            }
530            Some(MagneticSymmetryAnalysisKind::Representation) | None => {
531                // Unitary groups or magnetic groups with representations
532                let group = py
533                    .detach(|| {
534                        let sym = if use_magnetic_group.is_some() {
535                            pd_res
536                                .magnetic_symmetry
537                                .as_ref()
538                                .ok_or(format_err!("Magnetic group required for orbit construction, but no magnetic symmetry found."))?
539                        } else {
540                            &pd_res.unitary_symmetry
541                        };
542                        if use_double_group {
543                            UnitaryRepresentedSymmetryGroup::from_molecular_symmetry(
544                                sym,
545                                infinite_order_to_finite,
546                            )
547                            .and_then(|grp| grp.to_double_group())
548                        } else {
549                            UnitaryRepresentedSymmetryGroup::from_molecular_symmetry(
550                                sym,
551                                infinite_order_to_finite,
552                            )
553                        }
554                    })
555                    .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
556
557                // Run NOCI
558                let system = (&hamiltonian_ao, &overlap_ao);
559                let multidets_vec = system
560                    .solve_symmetry_orbit_noci(
561                        &origins_r.iter().collect_vec(),
562                        &group,
563                        symmetry_transformation_kind,
564                        use_cayley_table,
565                        thresh_offdiag,
566                        thresh_zeroov,
567                    )
568                    .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
569                let multidets = MultiDeterminants::from_multideterminant_vec(
570                    &multidets_vec.iter().collect::<Vec<_>>(),
571                )
572                .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
573
574                // Symmetry analysis for NOCI states
575                let mut mda_driver = MultiDeterminantRepAnalysisDriver::<
576                    UnitaryRepresentedSymmetryGroup,
577                    f64,
578                    _,
579                    SpinConstraint,
580                >::builder()
581                .parameters(&mda_params)
582                .angular_function_parameters(&afa_params)
583                .multidets(&multidets)
584                .sao(&sao_r)
585                .sao_h(None) // Real SAO.
586                .symmetry_group(&pd_res)
587                .build()
588                .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
589                py.detach(|| {
590                    mda_driver
591                        .run()
592                        .map_err(|err| PyRuntimeError::new_err(err.to_string()))
593                })?;
594
595                // Collect multi-determinantal wavefunctions for returning
596                let basis = multidets
597                    .basis()
598                    .iter()
599                    .map(|det_res| det_res.and_then(|det| det.to_python(py)))
600                    .collect::<Result<Vec<_>, _>>()
601                    .map_err(|_| {
602                        PyRuntimeError::new_err(
603                            "Unable to obtain the basis of Slater determinants.".to_string(),
604                        )
605                    })?;
606                let (coefficientss, energies): (Vec<_>, Vec<_>) = multidets_vec
607                    .iter()
608                    .map(|multidet| {
609                        let coefficients = multidet.coefficients().iter().cloned().collect_vec();
610                        let energy = *multidet.energy().unwrap_or(&f64::NAN);
611                        Ok::<_, PyErr>((coefficients, energy))
612                    })
613                    .collect::<Result<Vec<_>, _>>()?
614                    .into_iter()
615                    .unzip();
616                let coefficientss_arr = Array2::from_shape_vec(
617                    (basis.len(), coefficientss.len()).f(),
618                    coefficientss.into_iter().flatten().collect_vec(),
619                )
620                .map_err(|err| PyRuntimeError::new_err(err.to_string()))?
621                .to_pyarray(py);
622                let energies_arr = Array1::from_vec(energies).to_pyarray(py);
623                let density_matrices = if calculate_density_matrices {
624                    log::debug!("Calculating density matrices...");
625                    let denmats_opt = multidets
626                        .density_matrices(&sao_r.view(), thresh_offdiag, thresh_zeroov, true)
627                        .map(|denmats| {
628                            denmats
629                                .axis_iter(Axis(0))
630                                .map(|denmat| denmat.to_pyarray(py))
631                                .collect_vec()
632                        })
633                        .ok();
634                    log::debug!("Calculating density matrices... Done.");
635                    denmats_opt
636                } else {
637                    None
638                };
639                let pymultidet = PyMultiDeterminantsReal::new(
640                    basis,
641                    coefficientss_arr,
642                    energies_arr,
643                    density_matrices,
644                    multidets.threshold(),
645                )
646                .into_py_any(py)?;
647                Ok(pymultidet)
648            }
649        }
650    } else {
651        // Some complex numeric data type
652
653        // Preparation
654        let sao_c = match sao {
655            PyArray2RC::Real(pysao_r) => pysao_r.to_owned_array().mapv(Complex::from),
656            PyArray2RC::Complex(pysao_c) => pysao_c.to_owned_array(),
657        };
658        let sao_h_c = match sao_h {
659            Some(PyArray2RC::Real(pysao_r)) => Some(pysao_r.to_owned_array().mapv(Complex::from)),
660            Some(PyArray2RC::Complex(pysao_c)) => Some(pysao_c.to_owned_array()),
661            None => None,
662        };
663        let enuc_c = match enuc {
664            PyScalarRC::Real(enuc_r) => Complex::from(enuc_r),
665            PyScalarRC::Complex(enuc_c) => enuc_c,
666        };
667        let onee_c = match onee {
668            PyArray2RC::Real(pyonee_r) => pyonee_r.to_owned_array().mapv(Complex::from),
669            PyArray2RC::Complex(pyonee_c) => pyonee_c.to_owned_array(),
670        };
671        let twoe_c = match twoe {
672            Some(PyArray4RC::Real(pytwoe_r)) => Some(pytwoe_r.to_owned_array().mapv(Complex::from)),
673            Some(PyArray4RC::Complex(pytwoe_c)) => Some(pytwoe_c.to_owned_array()),
674            None => None,
675        };
676        let get_jk_c = py_get_jk.map(|py_get_jk_inner| {
677            generate_get_jk!(get_jk_c, py_get_jk_inner, Complex<f64>);
678            get_jk_c
679        });
680
681        match structure_constraint {
682            PyStructureConstraint::SpinConstraint(_) => {
683                let overlap_ao = OverlapAO::<Complex<f64>, SpinConstraint>::builder()
684                    .sao(sao_c.view())
685                    .build()
686                    .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
687                let hamiltonian_ao = HamiltonianAO::<Complex<f64>, SpinConstraint, _>::builder()
688                    .enuc(enuc_c)
689                    .onee(onee_c.view())
690                    .twoe(twoe_c.as_ref().map(|m| m.view()))
691                    .get_jk(get_jk_c.as_ref())
692                    .build()
693                    .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
694
695                let origins_c = if augment_to_generalised {
696                    pyorigins
697                        .iter()
698                        .map(|pydet| match pydet {
699                            PySlaterDeterminant::Real(pydet_r) => pydet_r
700                                .to_qsym2::<SpinConstraint>(&baos_ref, mol)
701                                .map(|det_r| det_r.to_generalised().into()),
702                            PySlaterDeterminant::Complex(pydet_c) => pydet_c
703                                .to_qsym2::<SpinConstraint>(&baos_ref, mol)
704                                .map(|det_c| det_c.to_generalised()),
705                        })
706                        .collect::<Result<Vec<_>, _>>()
707                } else {
708                    pyorigins
709                        .iter()
710                        .map(|pydet| match pydet {
711                            PySlaterDeterminant::Real(pydet_r) => pydet_r
712                                .to_qsym2::<SpinConstraint>(&baos_ref, mol)
713                                .map(|det_r| det_r.into()),
714                            PySlaterDeterminant::Complex(pydet_c) => {
715                                pydet_c.to_qsym2::<SpinConstraint>(&baos_ref, mol)
716                            }
717                        })
718                        .collect::<Result<Vec<_>, _>>()
719                }
720                .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
721
722                match &use_magnetic_group {
723                    Some(MagneticSymmetryAnalysisKind::Corepresentation) => {
724                        // Magnetic groups with corepresentations
725                        let group = py
726                            .detach(|| {
727                                let magsym = pd_res
728                                    .magnetic_symmetry
729                                    .as_ref()
730                                    .ok_or(format_err!("Magnetic group required for orbit construction, but no magnetic symmetry found."))?;
731                                if use_double_group {
732                                    MagneticRepresentedSymmetryGroup::from_molecular_symmetry(
733                                        magsym,
734                                        infinite_order_to_finite,
735                                    )
736                                    .and_then(|grp| grp.to_double_group())
737                                } else {
738                                    MagneticRepresentedSymmetryGroup::from_molecular_symmetry(
739                                        magsym,
740                                        infinite_order_to_finite,
741                                    )
742                                }
743                            })
744                            .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
745
746                        // Run NOCI
747                        let system = (&hamiltonian_ao, &overlap_ao);
748                        let multidets_vec = system
749                            .solve_symmetry_orbit_noci(
750                                &origins_c.iter().collect_vec(),
751                                &group,
752                                symmetry_transformation_kind,
753                                use_cayley_table,
754                                thresh_offdiag,
755                                thresh_zeroov,
756                            )
757                            .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
758                        let multidets = MultiDeterminants::from_multideterminant_vec(
759                            &multidets_vec.iter().collect::<Vec<_>>(),
760                        )
761                        .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
762
763                        // Symmetry analysis for NOCI states
764                        let mut mda_driver = MultiDeterminantRepAnalysisDriver::<
765                            MagneticRepresentedSymmetryGroup,
766                            Complex<f64>,
767                            _,
768                            SpinConstraint,
769                        >::builder()
770                        .parameters(&mda_params)
771                        .angular_function_parameters(&afa_params)
772                        .multidets(&multidets)
773                        .sao(&sao_c)
774                        .sao_h(sao_h_c.as_ref())
775                        .symmetry_group(&pd_res)
776                        .build()
777                        .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
778                        py.detach(|| {
779                            mda_driver
780                                .run()
781                                .map_err(|err| PyRuntimeError::new_err(err.to_string()))
782                        })?;
783
784                        // Collect multi-determinantal wavefunctions for returning
785                        let basis = multidets
786                            .basis()
787                            .iter()
788                            .map(|det_res| det_res.and_then(|det| det.to_python(py)))
789                            .collect::<Result<Vec<_>, _>>()
790                            .map_err(|_| {
791                                PyRuntimeError::new_err(
792                                    "Unable to obtain the basis of Slater determinants."
793                                        .to_string(),
794                                )
795                            })?;
796                        let (coefficientss, energies): (Vec<_>, Vec<_>) = multidets_vec
797                            .iter()
798                            .map(|multidet| {
799                                let coefficients =
800                                    multidet.coefficients().iter().cloned().collect_vec();
801                                let energy = *multidet.energy().unwrap_or(&Complex::from(f64::NAN));
802                                Ok::<_, PyErr>((coefficients, energy))
803                            })
804                            .collect::<Result<Vec<_>, _>>()?
805                            .into_iter()
806                            .unzip();
807                        let coefficientss_arr = Array2::from_shape_vec(
808                            (basis.len(), coefficientss.len()).f(),
809                            coefficientss.into_iter().flatten().collect_vec(),
810                        )
811                        .map_err(|err| PyRuntimeError::new_err(err.to_string()))?
812                        .to_pyarray(py);
813                        let energies_arr = Array1::from_vec(energies).to_pyarray(py);
814                        let density_matrices = if calculate_density_matrices {
815                            log::debug!("Calculating density matrices...");
816                            let denmats_opt = multidets
817                                .density_matrices(
818                                    &sao_c.view(),
819                                    thresh_offdiag,
820                                    thresh_zeroov,
821                                    true,
822                                )
823                                .map(|denmats| {
824                                    denmats
825                                        .axis_iter(Axis(0))
826                                        .map(|denmat| denmat.to_pyarray(py))
827                                        .collect_vec()
828                                })
829                                .ok();
830                            log::debug!("Calculating density matrices... Done.");
831                            denmats_opt
832                        } else {
833                            None
834                        };
835                        let pymultidet = PyMultiDeterminantsComplex::new(
836                            basis,
837                            coefficientss_arr,
838                            energies_arr,
839                            density_matrices,
840                            multidets.threshold(),
841                        )
842                        .into_py_any(py)?;
843                        Ok(pymultidet)
844                    }
845                    Some(MagneticSymmetryAnalysisKind::Representation) | None => {
846                        // Unitary groups or magnetic groups with representations
847                        let group = py
848                            .detach(|| {
849                                let sym = if use_magnetic_group.is_some() {
850                                    pd_res
851                                        .magnetic_symmetry
852                                        .as_ref()
853                                        .ok_or(format_err!("Magnetic group required for orbit construction, but no magnetic symmetry found."))?
854                                } else {
855                                    &pd_res.unitary_symmetry
856                                };
857                                if use_double_group {
858                                    UnitaryRepresentedSymmetryGroup::from_molecular_symmetry(
859                                        sym,
860                                        infinite_order_to_finite,
861                                    )
862                                    .and_then(|grp| grp.to_double_group())
863                                } else {
864                                    UnitaryRepresentedSymmetryGroup::from_molecular_symmetry(
865                                        sym,
866                                        infinite_order_to_finite,
867                                    )
868                                }
869                            })
870                            .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
871
872                        // Run NOCI
873                        let system = (&hamiltonian_ao, &overlap_ao);
874                        let multidets_vec = system
875                            .solve_symmetry_orbit_noci(
876                                &origins_c.iter().collect_vec(),
877                                &group,
878                                symmetry_transformation_kind,
879                                use_cayley_table,
880                                thresh_offdiag,
881                                thresh_zeroov,
882                            )
883                            .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
884                        let multidets = MultiDeterminants::from_multideterminant_vec(
885                            &multidets_vec.iter().collect::<Vec<_>>(),
886                        )
887                        .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
888
889                        // Symmetry analysis for NOCI states
890                        let mut mda_driver = MultiDeterminantRepAnalysisDriver::<
891                            UnitaryRepresentedSymmetryGroup,
892                            Complex<f64>,
893                            _,
894                            SpinConstraint,
895                        >::builder()
896                        .parameters(&mda_params)
897                        .angular_function_parameters(&afa_params)
898                        .multidets(&multidets)
899                        .sao(&sao_c)
900                        .sao_h(sao_h_c.as_ref())
901                        .symmetry_group(&pd_res)
902                        .build()
903                        .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
904                        py.detach(|| {
905                            mda_driver
906                                .run()
907                                .map_err(|err| PyRuntimeError::new_err(err.to_string()))
908                        })?;
909
910                        // Collect multi-determinantal wavefunctions for returning
911                        let basis = multidets
912                            .basis()
913                            .iter()
914                            .map(|det_res| det_res.and_then(|det| det.to_python(py)))
915                            .collect::<Result<Vec<_>, _>>()
916                            .map_err(|_| {
917                                PyRuntimeError::new_err(
918                                    "Unable to obtain the basis of Slater determinants."
919                                        .to_string(),
920                                )
921                            })?;
922                        let (coefficientss, energies): (Vec<_>, Vec<_>) = multidets_vec
923                            .iter()
924                            .map(|multidet| {
925                                let coefficients =
926                                    multidet.coefficients().iter().cloned().collect_vec();
927                                let energy = *multidet.energy().unwrap_or(&Complex::from(f64::NAN));
928                                Ok::<_, PyErr>((coefficients, energy))
929                            })
930                            .collect::<Result<Vec<_>, _>>()?
931                            .into_iter()
932                            .unzip();
933                        let coefficientss_arr = Array2::from_shape_vec(
934                            (basis.len(), coefficientss.len()).f(),
935                            coefficientss.into_iter().flatten().collect_vec(),
936                        )
937                        .map_err(|err| PyRuntimeError::new_err(err.to_string()))?
938                        .to_pyarray(py);
939                        let energies_arr = Array1::from_vec(energies).to_pyarray(py);
940                        let density_matrices = if calculate_density_matrices {
941                            log::debug!("Calculating density matrices...");
942                            let denmats_opt = multidets
943                                .density_matrices(
944                                    &sao_c.view(),
945                                    thresh_offdiag,
946                                    thresh_zeroov,
947                                    true,
948                                )
949                                .map(|denmats| {
950                                    denmats
951                                        .axis_iter(Axis(0))
952                                        .map(|denmat| denmat.to_pyarray(py))
953                                        .collect_vec()
954                                })
955                                .ok();
956                            log::debug!("Calculating density matrices... Done.");
957                            denmats_opt
958                        } else {
959                            None
960                        };
961                        let pymultidet = PyMultiDeterminantsComplex::new(
962                            basis,
963                            coefficientss_arr,
964                            energies_arr,
965                            density_matrices,
966                            multidets.threshold(),
967                        )
968                        .into_py_any(py)?;
969                        Ok(pymultidet)
970                    }
971                }
972            }
973            PyStructureConstraint::SpinOrbitCoupled(_) => {
974                let overlap_ao = OverlapAO::<Complex<f64>, SpinOrbitCoupled>::builder()
975                    .sao(sao_c.view())
976                    .build()
977                    .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
978                let hamiltonian_ao = HamiltonianAO::<Complex<f64>, SpinOrbitCoupled, _>::builder()
979                    .enuc(enuc_c)
980                    .onee(onee_c.view())
981                    .twoe(twoe_c.as_ref().map(|m| m.view()))
982                    .get_jk(get_jk_c.as_ref())
983                    .build()
984                    .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
985
986                let origins_c = pyorigins
987                    .iter()
988                    .map(|pydet| match pydet {
989                        PySlaterDeterminant::Real(pydet_r) => pydet_r
990                            .to_qsym2::<SpinOrbitCoupled>(&baos_ref, mol)
991                            .map(|det_r| det_r.into()),
992                        PySlaterDeterminant::Complex(pydet_c) => {
993                            pydet_c.to_qsym2::<SpinOrbitCoupled>(&baos_ref, mol)
994                        }
995                    })
996                    .collect::<Result<Vec<_>, _>>()
997                    .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
998
999                match &use_magnetic_group {
1000                    Some(MagneticSymmetryAnalysisKind::Corepresentation) => {
1001                        // Magnetic groups with corepresentations
1002                        let group = py
1003                            .detach(|| {
1004                                let magsym = pd_res
1005                                    .magnetic_symmetry
1006                                    .as_ref()
1007                                    .ok_or(format_err!("Magnetic group required for orbit construction, but no magnetic symmetry found."))?;
1008                                if use_double_group {
1009                                    MagneticRepresentedSymmetryGroup::from_molecular_symmetry(
1010                                        magsym,
1011                                        infinite_order_to_finite,
1012                                    )
1013                                    .and_then(|grp| grp.to_double_group())
1014                                } else {
1015                                    MagneticRepresentedSymmetryGroup::from_molecular_symmetry(
1016                                        magsym,
1017                                        infinite_order_to_finite,
1018                                    )
1019                                }
1020                            })
1021                            .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
1022
1023                        // Run NOCI
1024                        let system = (&hamiltonian_ao, &overlap_ao);
1025                        let multidets_vec = system
1026                            .solve_symmetry_orbit_noci(
1027                                &origins_c.iter().collect_vec(),
1028                                &group,
1029                                symmetry_transformation_kind,
1030                                use_cayley_table,
1031                                thresh_offdiag,
1032                                thresh_zeroov,
1033                            )
1034                            .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
1035                        let multidets = MultiDeterminants::from_multideterminant_vec(
1036                            &multidets_vec.iter().collect::<Vec<_>>(),
1037                        )
1038                        .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
1039
1040                        // Symmetry analysis for NOCI states
1041                        let mut mda_driver = MultiDeterminantRepAnalysisDriver::<
1042                            MagneticRepresentedSymmetryGroup,
1043                            Complex<f64>,
1044                            _,
1045                            SpinOrbitCoupled,
1046                        >::builder()
1047                        .parameters(&mda_params)
1048                        .angular_function_parameters(&afa_params)
1049                        .multidets(&multidets)
1050                        .sao(&sao_c)
1051                        .sao_h(sao_h_c.as_ref())
1052                        .symmetry_group(&pd_res)
1053                        .build()
1054                        .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
1055                        py.detach(|| {
1056                            mda_driver
1057                                .run()
1058                                .map_err(|err| PyRuntimeError::new_err(err.to_string()))
1059                        })?;
1060
1061                        // Collect multi-determinantal wavefunctions for returning
1062                        let basis = multidets
1063                            .basis()
1064                            .iter()
1065                            .map(|det_res| det_res.and_then(|det| det.to_python(py)))
1066                            .collect::<Result<Vec<_>, _>>()
1067                            .map_err(|_| {
1068                                PyRuntimeError::new_err(
1069                                    "Unable to obtain the basis of Slater determinants."
1070                                        .to_string(),
1071                                )
1072                            })?;
1073                        let (coefficientss, energies): (Vec<_>, Vec<_>) = multidets_vec
1074                            .iter()
1075                            .map(|multidet| {
1076                                let coefficients =
1077                                    multidet.coefficients().iter().cloned().collect_vec();
1078                                let energy = *multidet.energy().unwrap_or(&Complex::from(f64::NAN));
1079                                Ok::<_, PyErr>((coefficients, energy))
1080                            })
1081                            .collect::<Result<Vec<_>, _>>()?
1082                            .into_iter()
1083                            .unzip();
1084                        let coefficientss_arr = Array2::from_shape_vec(
1085                            (basis.len(), coefficientss.len()).f(),
1086                            coefficientss.into_iter().flatten().collect_vec(),
1087                        )
1088                        .map_err(|err| PyRuntimeError::new_err(err.to_string()))?
1089                        .to_pyarray(py);
1090                        let energies_arr = Array1::from_vec(energies).to_pyarray(py);
1091                        let density_matrices = if calculate_density_matrices {
1092                            log::debug!("Calculating density matrices...");
1093                            let denmats_opt = multidets
1094                                .density_matrices(
1095                                    &sao_c.view(),
1096                                    thresh_offdiag,
1097                                    thresh_zeroov,
1098                                    true,
1099                                )
1100                                .map(|denmats| {
1101                                    denmats
1102                                        .axis_iter(Axis(0))
1103                                        .map(|denmat| denmat.to_pyarray(py))
1104                                        .collect_vec()
1105                                })
1106                                .ok();
1107                            log::debug!("Calculating density matrices... Done.");
1108                            denmats_opt
1109                        } else {
1110                            None
1111                        };
1112                        let pymultidet = PyMultiDeterminantsComplex::new(
1113                            basis,
1114                            coefficientss_arr,
1115                            energies_arr,
1116                            density_matrices,
1117                            multidets.threshold(),
1118                        )
1119                        .into_py_any(py)?;
1120                        Ok(pymultidet)
1121                    }
1122                    Some(MagneticSymmetryAnalysisKind::Representation) | None => {
1123                        // Unitary groups or magnetic groups with representations
1124                        let group = py
1125                            .detach(|| {
1126                                let sym = if use_magnetic_group.is_some() {
1127                                    pd_res
1128                                        .magnetic_symmetry
1129                                        .as_ref()
1130                                        .ok_or(format_err!("Magnetic group required for orbit construction, but no magnetic symmetry found."))?
1131                                } else {
1132                                    &pd_res.unitary_symmetry
1133                                };
1134                                if use_double_group {
1135                                    UnitaryRepresentedSymmetryGroup::from_molecular_symmetry(
1136                                        sym,
1137                                        infinite_order_to_finite,
1138                                    )
1139                                    .and_then(|grp| grp.to_double_group())
1140                                } else {
1141                                    UnitaryRepresentedSymmetryGroup::from_molecular_symmetry(
1142                                        sym,
1143                                        infinite_order_to_finite,
1144                                    )
1145                                }
1146                            })
1147                            .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
1148
1149                        // Run NOCI
1150                        let system = (&hamiltonian_ao, &overlap_ao);
1151                        let multidets_vec = system
1152                            .solve_symmetry_orbit_noci(
1153                                &origins_c.iter().collect_vec(),
1154                                &group,
1155                                symmetry_transformation_kind,
1156                                use_cayley_table,
1157                                thresh_offdiag,
1158                                thresh_zeroov,
1159                            )
1160                            .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
1161                        let multidets = MultiDeterminants::from_multideterminant_vec(
1162                            &multidets_vec.iter().collect::<Vec<_>>(),
1163                        )
1164                        .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
1165
1166                        // Symmetry analysis for NOCI states
1167                        let mut mda_driver = MultiDeterminantRepAnalysisDriver::<
1168                            UnitaryRepresentedSymmetryGroup,
1169                            Complex<f64>,
1170                            _,
1171                            SpinOrbitCoupled,
1172                        >::builder()
1173                        .parameters(&mda_params)
1174                        .angular_function_parameters(&afa_params)
1175                        .multidets(&multidets)
1176                        .sao(&sao_c)
1177                        .sao_h(sao_h_c.as_ref())
1178                        .symmetry_group(&pd_res)
1179                        .build()
1180                        .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
1181                        py.detach(|| {
1182                            mda_driver
1183                                .run()
1184                                .map_err(|err| PyRuntimeError::new_err(err.to_string()))
1185                        })?;
1186
1187                        // Collect multi-determinantal wavefunctions for returning
1188                        let basis = multidets
1189                            .basis()
1190                            .iter()
1191                            .map(|det_res| det_res.and_then(|det| det.to_python(py)))
1192                            .collect::<Result<Vec<_>, _>>()
1193                            .map_err(|_| {
1194                                PyRuntimeError::new_err(
1195                                    "Unable to obtain the basis of Slater determinants."
1196                                        .to_string(),
1197                                )
1198                            })?;
1199                        let (coefficientss, energies): (Vec<_>, Vec<_>) = multidets_vec
1200                            .iter()
1201                            .map(|multidet| {
1202                                let coefficients =
1203                                    multidet.coefficients().iter().cloned().collect_vec();
1204                                let energy = *multidet.energy().unwrap_or(&Complex::from(f64::NAN));
1205                                Ok::<_, PyErr>((coefficients, energy))
1206                            })
1207                            .collect::<Result<Vec<_>, _>>()?
1208                            .into_iter()
1209                            .unzip();
1210                        let coefficientss_arr = Array2::from_shape_vec(
1211                            (basis.len(), coefficientss.len()).f(),
1212                            coefficientss.into_iter().flatten().collect_vec(),
1213                        )
1214                        .map_err(|err| PyRuntimeError::new_err(err.to_string()))?
1215                        .to_pyarray(py);
1216                        let energies_arr = Array1::from_vec(energies).to_pyarray(py);
1217                        let density_matrices = if calculate_density_matrices {
1218                            log::debug!("Calculating density matrices...");
1219                            let denmats_opt = multidets
1220                                .density_matrices(
1221                                    &sao_c.view(),
1222                                    thresh_offdiag,
1223                                    thresh_zeroov,
1224                                    true,
1225                                )
1226                                .map(|denmats| {
1227                                    denmats
1228                                        .axis_iter(Axis(0))
1229                                        .map(|denmat| denmat.to_pyarray(py))
1230                                        .collect_vec()
1231                                })
1232                                .ok();
1233                            log::debug!("Calculating density matrices... Done.");
1234                            denmats_opt
1235                        } else {
1236                            None
1237                        };
1238                        let pymultidet = PyMultiDeterminantsComplex::new(
1239                            basis,
1240                            coefficientss_arr,
1241                            energies_arr,
1242                            density_matrices,
1243                            multidets.threshold(),
1244                        )
1245                        .into_py_any(py)?;
1246                        Ok(pymultidet)
1247                    }
1248                }
1249            }
1250        }
1251    }
1252}