Skip to main content

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

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