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

1//! Python bindings for QSym² symmetry analysis of multi-determinants with eager bases.
2
3use std::collections::HashSet;
4use std::path::PathBuf;
5
6use anyhow::bail;
7use num_complex::Complex;
8use numpy::PyArrayMethods;
9use pyo3::exceptions::{PyIOError, PyRuntimeError};
10use pyo3::prelude::*;
11
12use crate::analysis::EigenvalueComparisonMode;
13use crate::angmom::spinor_rotation_3d::{SpinConstraint, SpinOrbitCoupled};
14use crate::bindings::python::integrals::{PyBasisAngularOrder, PyStructureConstraint};
15use crate::bindings::python::representation_analysis::slater_determinant::PySlaterDeterminant;
16use crate::bindings::python::representation_analysis::{PyArray1RC, PyArray2RC};
17use crate::drivers::representation_analysis::angular_function::AngularFunctionRepAnalysisParams;
18use crate::drivers::representation_analysis::multideterminant::{
19    MultiDeterminantRepAnalysisDriver, MultiDeterminantRepAnalysisParams,
20};
21use crate::drivers::representation_analysis::{
22    CharacterTableDisplay, MagneticSymmetryAnalysisKind,
23};
24use crate::drivers::symmetry_group_detection::SymmetryGroupDetectionResult;
25use crate::drivers::QSym2Driver;
26use crate::io::format::qsym2_output;
27use crate::io::{read_qsym2_binary, QSym2FileType};
28use crate::symmetry::symmetry_group::{
29    MagneticRepresentedSymmetryGroup, UnitaryRepresentedSymmetryGroup,
30};
31use crate::symmetry::symmetry_transformation::SymmetryTransformationKind;
32use crate::target::determinant::SlaterDeterminant;
33use crate::target::noci::basis::EagerBasis;
34use crate::target::noci::multideterminant::MultiDeterminant;
35
36type C128 = Complex<f64>;
37
38/// Python-exposed function to perform representation symmetry analysis for real and complex
39/// multi-determinantal wavefunctions constructed from an eager basis of Slater determinants and log
40/// the result via the `qsym2-output` logger at the `INFO` level.
41///
42/// If `symmetry_transformation_kind` includes spin transformation, the provided
43/// multi-determinantal wavefunctions will be augmented to generalised spin constraint
44/// automatically.
45///
46/// # Arguments
47///
48/// * `inp_sym` - A path to the [`QSym2FileType::Sym`] file containing the symmetry-group detection
49/// result for the system. This will be used to construct abstract groups and character tables for
50/// representation analysis. Python type: `str`.
51/// * `pydets` - A list of Python-exposed Slater determinants whose coefficients are of type
52/// `float64` or `complex128`. These determinants serve as basis states for non-orthogonal
53/// configuration interaction to yield multi-determinantal wavefunctions, the symmetry of which will
54/// be analysed by this function.
55/// Python type: `list[PySlaterDeterminantReal | PySlaterDeterminantComplex]`.
56/// * `coefficients` - The coefficient matrix where each column gives the linear combination
57/// coefficients for one multi-determinantal wavefunction. The number of rows must match the number
58/// of determinants specified in `pydets`. The elements are of type `float64` or `complex128`.
59/// Python type: `numpy.2darray[float] | numpy.2darray[complex]`.
60/// * `energies` - The `float64` or `complex128` energies of the multi-determinantal wavefunctions.
61/// The number of terms must match the number of columns of `coefficients`.
62/// Python type: `numpy.1darray[float] | numpy.1darray[complex]`.
63/// * `pybao` - A Python-exposed Python-exposed structure containing basis angular order information.
64/// Python type: `PyBasisAngularOrder`.
65/// * `integrality_threshold` - The threshold for verifying if subspace multiplicities are
66/// integral. Python type: `float`.
67/// * `linear_independence_threshold` - The threshold for determining the linear independence
68/// subspace via the non-zero eigenvalues of the orbit overlap matrix. Python type: `float`.
69/// * `use_magnetic_group` - An option indicating if the magnetic group is to be used for symmetry
70/// analysis, and if so, whether unitary representations or unitary-antiunitary corepresentations
71/// should be used. Python type: `None | MagneticSymmetryAnalysisKind`.
72/// * `use_double_group` - A boolean indicating if the double group of the prevailing symmetry
73/// group is to be used for representation analysis instead. Python type: `bool`.
74/// * `use_cayley_table` - A boolean indicating if the Cayley table for the group, if available,
75/// should be used to speed up the calculation of orbit overlap matrices. Python type: `bool`.
76/// * `symmetry_transformation_kind` - An enumerated type indicating the type of symmetry
77/// transformations to be performed on the origin determinant to generate the orbit. If this
78/// contains spin transformation, the determinant will be augmented to generalised spin constraint
79/// automatically. Python type: `SymmetryTransformationKind`.
80/// * `eigenvalue_comparison_mode` - An enumerated type indicating the mode of comparison of orbit
81/// overlap eigenvalues with the specified `linear_independence_threshold`.
82/// Python type: `EigenvalueComparisonMode`.
83/// * `sao` - The atomic-orbital overlap matrix whose elements are of type `float64` or
84/// `complex128`. Python type: `numpy.2darray[float] | numpy.2darray[complex]`.
85/// * `sao_h` - The optional complex-symmetric atomic-orbital overlap matrix whose elements
86/// are of type `float64` or `complex128`. This is required if antiunitary symmetry operations are
87/// involved. Python type: `None | numpy.2darray[float] | numpy.2darray[complex]`.
88/// * `write_overlap_eigenvalues` - A boolean indicating if the eigenvalues of the determinant
89/// orbit overlap matrix are to be written to the output. Python type: `bool`.
90/// * `write_character_table` - A boolean indicating if the character table of the prevailing
91/// symmetry group is to be printed out. Python type: `bool`.
92/// * `infinite_order_to_finite` - The finite order with which infinite-order generators are to be
93/// interpreted to form a finite subgroup of the prevailing infinite group. This finite subgroup
94/// will be used for symmetry analysis. Python type: `Optional[int]`.
95/// * `angular_function_integrality_threshold` - The threshold for verifying if subspace
96/// multiplicities are integral for the symmetry analysis of angular functions. Python type:
97/// `float`.
98/// * `angular_function_linear_independence_threshold` - The threshold for determining the linear
99/// independence subspace via the non-zero eigenvalues of the orbit overlap matrix for the symmetry
100/// analysis of angular functions. Python type: `float`.
101/// * `angular_function_max_angular_momentum` - The maximum angular momentum order to be used in
102/// angular function symmetry analysis. Python type: `int`.
103#[pyfunction]
104#[pyo3(signature = (
105    inp_sym,
106    pydets,
107    coefficients,
108    energies,
109    pybao,
110    integrality_threshold,
111    linear_independence_threshold,
112    use_magnetic_group,
113    use_double_group,
114    use_cayley_table,
115    symmetry_transformation_kind,
116    eigenvalue_comparison_mode,
117    sao,
118    sao_h=None,
119    write_overlap_eigenvalues=true,
120    write_character_table=true,
121    infinite_order_to_finite=None,
122    angular_function_integrality_threshold=1e-7,
123    angular_function_linear_independence_threshold=1e-7,
124    angular_function_max_angular_momentum=2
125))]
126pub fn rep_analyse_multideterminants_eager_basis(
127    py: Python<'_>,
128    inp_sym: PathBuf,
129    pydets: Vec<PySlaterDeterminant>,
130    coefficients: PyArray2RC,
131    energies: PyArray1RC,
132    pybao: &PyBasisAngularOrder,
133    integrality_threshold: f64,
134    linear_independence_threshold: f64,
135    use_magnetic_group: Option<MagneticSymmetryAnalysisKind>,
136    use_double_group: bool,
137    use_cayley_table: bool,
138    symmetry_transformation_kind: SymmetryTransformationKind,
139    eigenvalue_comparison_mode: EigenvalueComparisonMode,
140    sao: PyArray2RC,
141    sao_h: Option<PyArray2RC>,
142    write_overlap_eigenvalues: bool,
143    write_character_table: bool,
144    infinite_order_to_finite: Option<u32>,
145    angular_function_integrality_threshold: f64,
146    angular_function_linear_independence_threshold: f64,
147    angular_function_max_angular_momentum: u32,
148) -> PyResult<()> {
149    // Read in point-group detection results
150    let pd_res: SymmetryGroupDetectionResult =
151        read_qsym2_binary(inp_sym.clone(), QSym2FileType::Sym)
152            .map_err(|err| PyIOError::new_err(err.to_string()))?;
153
154    let mut file_name = inp_sym.to_path_buf();
155    file_name.set_extension(QSym2FileType::Sym.ext());
156    qsym2_output!(
157        "Symmetry-group detection results read in from {}.",
158        file_name.display(),
159    );
160    qsym2_output!("");
161
162    // Set up basic parameters
163    let mol = &pd_res.pre_symmetry.recentred_molecule;
164    let bao = pybao
165        .to_qsym2(mol)
166        .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
167    let augment_to_generalised = match symmetry_transformation_kind {
168        SymmetryTransformationKind::SpatialWithSpinTimeReversal
169        | SymmetryTransformationKind::Spin
170        | SymmetryTransformationKind::SpinSpatial => true,
171        SymmetryTransformationKind::Spatial => false,
172    };
173    let afa_params = AngularFunctionRepAnalysisParams::builder()
174        .integrality_threshold(angular_function_integrality_threshold)
175        .linear_independence_threshold(angular_function_linear_independence_threshold)
176        .max_angular_momentum(angular_function_max_angular_momentum)
177        .build()
178        .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
179    let mda_params = MultiDeterminantRepAnalysisParams::<f64>::builder()
180        .integrality_threshold(integrality_threshold)
181        .linear_independence_threshold(linear_independence_threshold)
182        .use_magnetic_group(use_magnetic_group.clone())
183        .use_double_group(use_double_group)
184        .use_cayley_table(use_cayley_table)
185        .symmetry_transformation_kind(symmetry_transformation_kind.clone())
186        .eigenvalue_comparison_mode(eigenvalue_comparison_mode)
187        .write_overlap_eigenvalues(write_overlap_eigenvalues)
188        .write_character_table(if write_character_table {
189            Some(CharacterTableDisplay::Symbolic)
190        } else {
191            None
192        })
193        .infinite_order_to_finite(infinite_order_to_finite)
194        .build()
195        .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
196
197    let all_real = pydets
198        .iter()
199        .all(|pydet| matches!(pydet, PySlaterDeterminant::Real(_)));
200
201    let structure_constraints_set = pydets
202        .iter()
203        .map(|pydet| match pydet {
204            PySlaterDeterminant::Real(pydet) => pydet.structure_constraint().clone(),
205            PySlaterDeterminant::Complex(pydet) => pydet.structure_constraint().clone(),
206        })
207        .collect::<HashSet<_>>();
208    if structure_constraints_set.len() != 1 {
209        return Err(PyRuntimeError::new_err(
210            "Inconsistent structure constraints across origin determinants.`",
211        ));
212    };
213    let structure_constraint = structure_constraints_set
214        .iter()
215        .next()
216        .ok_or_else(|| PyRuntimeError::new_err("Unable to retrieve the structure constraint."))?;
217
218    // Decision tree:
219    // - all real numerical data?
220    //   + yes:
221    //     - structure_constraint:
222    //       + SpinConstraint:
223    //         - use_magnetic_group:
224    //           + Some(Corepresentation)
225    //           + Some(Representation) | None
226    //       + SpinOrbitCoupled: not supported
227    //   - no:
228    //     - structure_constraint:
229    //       + SpinConstraint:
230    //         - use_magnetic_group:
231    //           + Some(Corepresentation)
232    //           + Some(Representation) | None
233    //       + SpinOrbitCoupled:
234    //         - use_magnetic_group:
235    //           + Some(Corepresentation)
236    //           + Some(Representation) | None
237    match (all_real, &coefficients, &energies, &sao) {
238        (
239            true,
240            PyArray2RC::Real(pycoefficients_r),
241            PyArray1RC::Real(pyenergies_r),
242            PyArray2RC::Real(pysao_r),
243        ) => {
244            // Real numeric data type
245
246            if matches!(
247                structure_constraint,
248                PyStructureConstraint::SpinOrbitCoupled(_)
249            ) {
250                return Err(PyRuntimeError::new_err(
251                    "Real determinants cannot support spin--orbit-coupled structure constraint.",
252                ));
253            }
254
255            // Preparation
256            let sao = pysao_r.to_owned_array();
257            let coefficients_r = pycoefficients_r.to_owned_array();
258            let energies_r = pyenergies_r.to_owned_array();
259            let dets_r = if augment_to_generalised {
260                pydets
261                    .iter()
262                    .map(|pydet| {
263                        if let PySlaterDeterminant::Real(pydet_r) = pydet {
264                            pydet_r
265                                .to_qsym2(&bao, mol)
266                                .map(|det_r| det_r.to_generalised())
267                        } else {
268                            bail!("Unexpected complex type for a Slater determinant.")
269                        }
270                    })
271                    .collect::<Result<Vec<_>, _>>()
272            } else {
273                pydets
274                    .iter()
275                    .map(|pydet| {
276                        if let PySlaterDeterminant::Real(pydet_r) = pydet {
277                            pydet_r.to_qsym2(&bao, mol)
278                        } else {
279                            bail!("Unexpected complex type for a Slater determinant.")
280                        }
281                    })
282                    .collect::<Result<Vec<_>, _>>()
283            }
284            .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
285
286            let n_energies = energies_r.len();
287            if coefficients_r.shape()[1] != n_energies {
288                return Err(PyRuntimeError::new_err(
289                    "Mismatched number of NOCI energies and number of NOCI states.",
290                ));
291            }
292
293            // Construct the eager basis
294            let eager_basis = EagerBasis::builder()
295                .elements(dets_r)
296                .build()
297                .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
298
299            // Construct the multi-determinantal wavefunctions
300            let multidets = energies_r
301                .iter()
302                .zip(coefficients_r.columns())
303                .map(|(energy, coeffs)| {
304                    MultiDeterminant::builder()
305                        .basis(eager_basis.clone())
306                        .coefficients(coeffs.to_owned())
307                        .threshold(1e-7)
308                        .energy(Ok(*energy))
309                        .build()
310                })
311                .collect::<Result<Vec<_>, _>>()
312                .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
313
314            // Construct the driver
315            match &use_magnetic_group {
316                Some(MagneticSymmetryAnalysisKind::Corepresentation) => {
317                    let mut mda_driver = MultiDeterminantRepAnalysisDriver::<
318                        MagneticRepresentedSymmetryGroup,
319                        f64,
320                        _,
321                        SpinConstraint,
322                    >::builder()
323                    .parameters(&mda_params)
324                    .angular_function_parameters(&afa_params)
325                    .multidets(multidets.iter().collect::<Vec<_>>())
326                    .sao(&sao)
327                    .sao_h(None) // Real SAO.
328                    .symmetry_group(&pd_res)
329                    .build()
330                    .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
331
332                    // Run the driver
333                    py.allow_threads(|| {
334                        mda_driver
335                            .run()
336                            .map_err(|err| PyRuntimeError::new_err(err.to_string()))
337                    })?
338                }
339                Some(MagneticSymmetryAnalysisKind::Representation) | None => {
340                    let mut mda_driver = MultiDeterminantRepAnalysisDriver::<
341                        UnitaryRepresentedSymmetryGroup,
342                        f64,
343                        _,
344                        SpinConstraint,
345                    >::builder()
346                    .parameters(&mda_params)
347                    .angular_function_parameters(&afa_params)
348                    .multidets(multidets.iter().collect::<Vec<_>>())
349                    .sao(&sao)
350                    .sao_h(None) // Real SAO.
351                    .symmetry_group(&pd_res)
352                    .build()
353                    .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
354
355                    // Run the driver
356                    py.allow_threads(|| {
357                        mda_driver
358                            .run()
359                            .map_err(|err| PyRuntimeError::new_err(err.to_string()))
360                    })?
361                }
362            }
363        }
364        (_, _, _, _) => {
365            // Complex numeric data type
366
367            // Preparation
368            let sao_c = match sao {
369                PyArray2RC::Real(pysao_r) => pysao_r.to_owned_array().mapv(Complex::from),
370                PyArray2RC::Complex(pysao_c) => pysao_c.to_owned_array(),
371            };
372            let sao_h_c = sao_h.and_then(|pysao_h| match pysao_h {
373                // sao_spatial_h must have the same reality as sao_spatial.
374                PyArray2RC::Real(pysao_h_r) => Some(pysao_h_r.to_owned_array().mapv(Complex::from)),
375                PyArray2RC::Complex(pysao_h_c) => Some(pysao_h_c.to_owned_array()),
376            });
377            let coefficients_c = match coefficients {
378                PyArray2RC::Real(pycoefficients_r) => {
379                    pycoefficients_r.to_owned_array().mapv(Complex::from)
380                }
381                PyArray2RC::Complex(pycoefficients_c) => pycoefficients_c.to_owned_array(),
382            };
383            let energies_c = match energies {
384                PyArray1RC::Real(pyenergies_r) => pyenergies_r.to_owned_array().mapv(Complex::from),
385                PyArray1RC::Complex(pyenergies_c) => pyenergies_c.to_owned_array(),
386            };
387
388            match structure_constraint {
389                PyStructureConstraint::SpinConstraint(_) => {
390                    let dets_c = if augment_to_generalised {
391                        pydets
392                            .iter()
393                            .map(|pydet| match pydet {
394                                PySlaterDeterminant::Real(pydet_r) => {
395                                    pydet_r.to_qsym2::<SpinConstraint>(&bao, mol).map(|det_r| {
396                                        SlaterDeterminant::<C128, SpinConstraint>::from(det_r)
397                                            .to_generalised()
398                                    })
399                                }
400                                PySlaterDeterminant::Complex(pydet_c) => pydet_c
401                                    .to_qsym2::<SpinConstraint>(&bao, mol)
402                                    .map(|det_c| det_c.to_generalised()),
403                            })
404                            .collect::<Result<Vec<_>, _>>()
405                    } else {
406                        pydets
407                            .iter()
408                            .map(|pydet| match pydet {
409                                PySlaterDeterminant::Real(pydet_r) => {
410                                    pydet_r.to_qsym2::<SpinConstraint>(&bao, mol).map(|det_r| {
411                                        SlaterDeterminant::<C128, SpinConstraint>::from(det_r)
412                                    })
413                                }
414                                PySlaterDeterminant::Complex(pydet_c) => {
415                                    pydet_c.to_qsym2::<SpinConstraint>(&bao, mol)
416                                }
417                            })
418                            .collect::<Result<Vec<_>, _>>()
419                    }
420                    .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
421
422                    let n_energies = energies_c.len();
423                    if coefficients_c.shape()[1] != n_energies {
424                        return Err(PyRuntimeError::new_err(
425                            "Mismatched number of NOCI energies and number of NOCI states.",
426                        ));
427                    }
428
429                    // Construct the eager basis
430                    let eager_basis = EagerBasis::builder()
431                        .elements(dets_c)
432                        .build()
433                        .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
434
435                    // Construct the multi-determinantal wavefunctions
436                    let multidets = energies_c
437                        .iter()
438                        .zip(coefficients_c.columns())
439                        .map(|(energy, coeffs)| {
440                            MultiDeterminant::builder()
441                                .basis(eager_basis.clone())
442                                .coefficients(coeffs.to_owned())
443                                .threshold(1e-7)
444                                .energy(Ok(*energy))
445                                .build()
446                        })
447                        .collect::<Result<Vec<_>, _>>()
448                        .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
449
450                    // Construct the driver
451                    match &use_magnetic_group {
452                        Some(MagneticSymmetryAnalysisKind::Corepresentation) => {
453                            let mut mda_driver = MultiDeterminantRepAnalysisDriver::<
454                                MagneticRepresentedSymmetryGroup,
455                                C128,
456                                _,
457                                SpinConstraint,
458                            >::builder()
459                            .parameters(&mda_params)
460                            .angular_function_parameters(&afa_params)
461                            .multidets(multidets.iter().collect::<Vec<_>>())
462                            .sao(&sao_c)
463                            .sao_h(sao_h_c.as_ref())
464                            .symmetry_group(&pd_res)
465                            .build()
466                            .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
467
468                            // Run the driver
469                            py.allow_threads(|| {
470                                mda_driver
471                                    .run()
472                                    .map_err(|err| PyRuntimeError::new_err(err.to_string()))
473                            })?
474                        }
475                        Some(MagneticSymmetryAnalysisKind::Representation) | None => {
476                            let mut mda_driver = MultiDeterminantRepAnalysisDriver::<
477                                UnitaryRepresentedSymmetryGroup,
478                                C128,
479                                _,
480                                SpinConstraint,
481                            >::builder()
482                            .parameters(&mda_params)
483                            .angular_function_parameters(&afa_params)
484                            .multidets(multidets.iter().collect::<Vec<_>>())
485                            .sao(&sao_c)
486                            .sao_h(sao_h_c.as_ref())
487                            .symmetry_group(&pd_res)
488                            .build()
489                            .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
490
491                            // Run the driver
492                            py.allow_threads(|| {
493                                mda_driver
494                                    .run()
495                                    .map_err(|err| PyRuntimeError::new_err(err.to_string()))
496                            })?
497                        }
498                    }
499                }
500                PyStructureConstraint::SpinOrbitCoupled(_) => {
501                    let dets_c = pydets
502                        .iter()
503                        .map(|pydet| match pydet {
504                            PySlaterDeterminant::Real(pydet_r) => pydet_r
505                                .to_qsym2::<SpinOrbitCoupled>(&bao, mol)
506                                .map(|det_r| {
507                                    SlaterDeterminant::<C128, SpinOrbitCoupled>::from(det_r)
508                                }),
509                            PySlaterDeterminant::Complex(pydet_c) => {
510                                pydet_c.to_qsym2::<SpinOrbitCoupled>(&bao, mol)
511                            }
512                        })
513                        .collect::<Result<Vec<_>, _>>()
514                        .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
515
516                    let n_energies = energies_c.len();
517                    if coefficients_c.shape()[1] != n_energies {
518                        return Err(PyRuntimeError::new_err(
519                            "Mismatched number of NOCI energies and number of NOCI states.",
520                        ));
521                    }
522
523                    // Construct the eager basis
524                    let eager_basis = EagerBasis::builder()
525                        .elements(dets_c)
526                        .build()
527                        .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
528
529                    // Construct the multi-determinantal wavefunctions
530                    let multidets = energies_c
531                        .iter()
532                        .zip(coefficients_c.columns())
533                        .map(|(energy, coeffs)| {
534                            MultiDeterminant::builder()
535                                .basis(eager_basis.clone())
536                                .coefficients(coeffs.to_owned())
537                                .threshold(1e-7)
538                                .energy(Ok(*energy))
539                                .build()
540                        })
541                        .collect::<Result<Vec<_>, _>>()
542                        .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
543
544                    // Construct the driver
545                    match &use_magnetic_group {
546                        Some(MagneticSymmetryAnalysisKind::Corepresentation) => {
547                            let mut mda_driver = MultiDeterminantRepAnalysisDriver::<
548                                MagneticRepresentedSymmetryGroup,
549                                C128,
550                                _,
551                                SpinOrbitCoupled,
552                            >::builder()
553                            .parameters(&mda_params)
554                            .angular_function_parameters(&afa_params)
555                            .multidets(multidets.iter().collect::<Vec<_>>())
556                            .sao(&sao_c)
557                            .sao_h(sao_h_c.as_ref())
558                            .symmetry_group(&pd_res)
559                            .build()
560                            .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
561
562                            // Run the driver
563                            py.allow_threads(|| {
564                                mda_driver
565                                    .run()
566                                    .map_err(|err| PyRuntimeError::new_err(err.to_string()))
567                            })?
568                        }
569                        Some(MagneticSymmetryAnalysisKind::Representation) | None => {
570                            let mut mda_driver = MultiDeterminantRepAnalysisDriver::<
571                                UnitaryRepresentedSymmetryGroup,
572                                C128,
573                                _,
574                                SpinOrbitCoupled,
575                            >::builder()
576                            .parameters(&mda_params)
577                            .angular_function_parameters(&afa_params)
578                            .multidets(multidets.iter().collect::<Vec<_>>())
579                            .sao(&sao_c)
580                            .sao_h(sao_h_c.as_ref())
581                            .symmetry_group(&pd_res)
582                            .build()
583                            .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
584
585                            // Run the driver
586                            py.allow_threads(|| {
587                                mda_driver
588                                    .run()
589                                    .map_err(|err| PyRuntimeError::new_err(err.to_string()))
590                            })?
591                        }
592                    }
593                }
594            }
595        }
596    }
597    Ok(())
598}