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