qsym2/bindings/python/representation_analysis/slater_determinant.rs
1//! Python bindings for QSym² symmetry analysis of Slater determinants.
2
3use std::fmt;
4use std::path::PathBuf;
5
6use anyhow::format_err;
7use itertools::Itertools;
8use ndarray::{Array1, Array2};
9use num_complex::Complex;
10use numpy::{PyArray1, PyArray2, PyArrayMethods, ToPyArray};
11use pyo3::exceptions::{PyIOError, PyRuntimeError};
12use pyo3::prelude::*;
13
14use crate::analysis::EigenvalueComparisonMode;
15use crate::angmom::spinor_rotation_3d::{SpinConstraint, SpinOrbitCoupled, StructureConstraint};
16use crate::auxiliary::molecule::Molecule;
17use crate::basis::ao::BasisAngularOrder;
18use crate::bindings::python::integrals::{PyBasisAngularOrder, PyStructureConstraint};
19use crate::bindings::python::representation_analysis::{PyArray2RC, PyArray4RC};
20use crate::drivers::QSym2Driver;
21use crate::drivers::representation_analysis::angular_function::AngularFunctionRepAnalysisParams;
22use crate::drivers::representation_analysis::slater_determinant::{
23 SlaterDeterminantRepAnalysisDriver, SlaterDeterminantRepAnalysisParams,
24};
25use crate::drivers::representation_analysis::{
26 CharacterTableDisplay, MagneticSymmetryAnalysisKind,
27};
28use crate::drivers::symmetry_group_detection::SymmetryGroupDetectionResult;
29use crate::group::GroupProperties;
30use crate::io::format::qsym2_output;
31use crate::io::{QSym2FileType, read_qsym2_binary};
32use crate::symmetry::symmetry_group::{
33 MagneticRepresentedSymmetryGroup, UnitaryRepresentedSymmetryGroup,
34};
35use crate::symmetry::symmetry_transformation::SymmetryTransformationKind;
36use crate::target::determinant::SlaterDeterminant;
37
38type C128 = Complex<f64>;
39
40// ==================
41// Struct definitions
42// ==================
43
44// ------------------
45// Slater determinant
46// ------------------
47
48// ~~~~
49// Real
50// ~~~~
51
52/// Python-exposed structure to marshall real Slater determinant information between Rust and
53/// Python.
54#[pyclass]
55#[derive(Clone)]
56pub struct PySlaterDeterminantReal {
57 /// The structure constraint applied to the coefficients of the determinant.
58 pub(crate) structure_constraint: PyStructureConstraint,
59
60 /// A boolean indicating if inner products involving this determinant are complex-symmetric.
61 #[pyo3(get)]
62 pub(crate) complex_symmetric: bool,
63
64 /// The real coefficients for the molecular orbitals of this determinant.
65 pub(crate) coefficients: Vec<Array2<f64>>,
66
67 /// The occupation patterns for the molecular orbitals.
68 pub(crate) occupations: Vec<Array1<f64>>,
69
70 /// The threshold for comparisons.
71 #[pyo3(get)]
72 pub(crate) threshold: f64,
73
74 /// The optional real molecular orbital energies.
75 pub(crate) mo_energies: Option<Vec<Array1<f64>>>,
76
77 /// The optional real determinantal energy.
78 #[pyo3(get)]
79 pub(crate) energy: Option<f64>,
80}
81
82#[pymethods]
83impl PySlaterDeterminantReal {
84 /// Constructs a real Python-exposed Slater determinant.
85 ///
86 /// # Arguments
87 ///
88 /// * `structure_constraint` - The structure constraint applied to the coefficients of the determinant.
89 /// * `complex_symmetric` - A boolean indicating if inner products involving this determinant are complex-symmetric.
90 /// * `coefficients` - The real coefficients for the molecular orbitals of this determinant.
91 /// * `occupations` - The occupation patterns for the molecular orbitals.
92 /// * `threshold` - The threshold for comparisons.
93 /// * `mo_energies` - The optional real molecular orbital energies.
94 /// * `energy` - The optional real determinantal energy.
95 #[new]
96 #[pyo3(signature = (structure_constraint, complex_symmetric, coefficients, occupations, threshold, mo_energies=None, energy=None))]
97 pub fn new(
98 structure_constraint: PyStructureConstraint,
99 complex_symmetric: bool,
100 coefficients: Vec<Bound<'_, PyArray2<f64>>>,
101 occupations: Vec<Bound<'_, PyArray1<f64>>>,
102 threshold: f64,
103 mo_energies: Option<Vec<Bound<'_, PyArray1<f64>>>>,
104 energy: Option<f64>,
105 ) -> Self {
106 let det = Self {
107 structure_constraint,
108 complex_symmetric,
109 coefficients: coefficients
110 .iter()
111 .map(|pyarr| pyarr.to_owned_array())
112 .collect::<Vec<_>>(),
113 occupations: occupations
114 .iter()
115 .map(|pyarr| pyarr.to_owned_array())
116 .collect::<Vec<_>>(),
117 threshold,
118 mo_energies: mo_energies.map(|energies| {
119 energies
120 .iter()
121 .map(|pyarr| pyarr.to_owned_array())
122 .collect::<Vec<_>>()
123 }),
124 energy,
125 };
126 det
127 }
128
129 /// The occupation patterns for the molecular orbitals.
130 #[getter]
131 pub fn occupations<'py>(&self, py: Python<'py>) -> PyResult<Vec<Bound<'py, PyArray1<f64>>>> {
132 Ok(self
133 .occupations
134 .iter()
135 .map(|occ| occ.to_pyarray(py))
136 .collect::<Vec<_>>())
137 }
138
139 /// The real coefficients for the molecular orbitals of this determinant.
140 #[getter]
141 pub fn coefficients<'py>(&self, py: Python<'py>) -> PyResult<Vec<Bound<'py, PyArray2<f64>>>> {
142 Ok(self
143 .coefficients
144 .iter()
145 .map(|occ| occ.to_pyarray(py))
146 .collect::<Vec<_>>())
147 }
148
149 /// The real molecular orbital energies, if any.
150 #[getter]
151 pub fn mo_energies<'py>(
152 &self,
153 py: Python<'py>,
154 ) -> PyResult<Option<Vec<Bound<'py, PyArray1<f64>>>>> {
155 Ok(self.mo_energies.as_ref().map(|mo_energies| {
156 mo_energies
157 .iter()
158 .map(|mo_en| mo_en.to_pyarray(py))
159 .collect::<Vec<_>>()
160 }))
161 }
162}
163
164impl PySlaterDeterminantReal {
165 /// Extracts the information in the [`PySlaterDeterminantReal`] structure into `QSym2`'s native
166 /// [`SlaterDeterminant`] structure.
167 ///
168 /// # Arguments
169 ///
170 /// * `baos` - The [`BasisAngularOrder`]s for the basis set in which the Slater determinant is
171 /// given, one for each explicit component per coefficient matrix.
172 /// * `mol` - The molecule with which the Slater determinant is associated.
173 ///
174 /// # Returns
175 ///
176 /// The [`SlaterDeterminant`] structure with the same information.
177 ///
178 /// # Errors
179 ///
180 /// Errors if the [`SlaterDeterminant`] fails to build.
181 pub fn to_qsym2<'b, 'a: 'b, SC>(
182 &'b self,
183 baos: &[&'a BasisAngularOrder],
184 mol: &'a Molecule,
185 ) -> Result<SlaterDeterminant<'b, f64, SC>, anyhow::Error>
186 where
187 SC: StructureConstraint
188 + Clone
189 + fmt::Display
190 + TryFrom<PyStructureConstraint, Error = anyhow::Error>,
191 {
192 let det = SlaterDeterminant::<f64, SC>::builder()
193 .structure_constraint(self.structure_constraint.clone().try_into()?)
194 .baos(baos.to_vec())
195 .complex_symmetric(self.complex_symmetric)
196 .mol(mol)
197 .coefficients(&self.coefficients)
198 .occupations(&self.occupations)
199 .mo_energies(self.mo_energies.clone())
200 .energy(
201 self.energy
202 .ok_or_else(|| "No determinantal energy set.".to_string()),
203 )
204 .threshold(self.threshold)
205 .build()
206 .map_err(|err| format_err!(err));
207 det
208 }
209
210 /// Returns the Python-exposed structure constraint information.
211 pub fn structure_constraint(&self) -> &PyStructureConstraint {
212 &self.structure_constraint
213 }
214}
215
216impl<'a, SC> SlaterDeterminant<'a, f64, SC>
217where
218 SC: StructureConstraint
219 + Clone
220 + fmt::Display
221 + TryInto<PyStructureConstraint, Error = anyhow::Error>,
222{
223 /// Extracts the information in the real [`SlaterDeterminant`] structure into [`PySlaterDeterminantReal`].
224 ///
225 /// # Returns
226 ///
227 /// The [`PySlaterDeterminantReal`] structure with the same information.
228 pub fn to_python<'py>(
229 &self,
230 py: Python<'py>,
231 ) -> Result<PySlaterDeterminantReal, anyhow::Error> {
232 Ok(PySlaterDeterminantReal::new(
233 self.structure_constraint().clone().try_into()?,
234 self.complex_symmetric(),
235 self.coefficients()
236 .iter()
237 .map(|coeffs| coeffs.to_pyarray(py))
238 .collect_vec(),
239 self.occupations()
240 .iter()
241 .map(|occ| occ.to_pyarray(py))
242 .collect_vec(),
243 self.threshold(),
244 self.mo_energies().map(|mo_energies| {
245 mo_energies
246 .iter()
247 .map(|mo_e| mo_e.to_pyarray(py))
248 .collect_vec()
249 }),
250 self.energy().ok().map(|energy| *energy),
251 ))
252 }
253}
254
255// ~~~~~~~
256// Complex
257// ~~~~~~~
258
259/// Python-exposed structure to marshall complex Slater determinant information between Rust and Python.
260#[pyclass]
261#[derive(Clone)]
262pub struct PySlaterDeterminantComplex {
263 /// The structure constraint applied to the coefficients of the determinant.
264 pub(crate) structure_constraint: PyStructureConstraint,
265
266 /// A boolean indicating if inner products involving this determinant are complex-symmetric.
267 #[pyo3(get)]
268 pub(crate) complex_symmetric: bool,
269
270 /// The complex coefficients for the molecular orbitals of this determinant.
271 pub(crate) coefficients: Vec<Array2<C128>>,
272
273 /// The occupation patterns for the molecular orbitals.
274 pub(crate) occupations: Vec<Array1<f64>>,
275
276 /// The threshold for comparisons.
277 #[pyo3(get)]
278 pub(crate) threshold: f64,
279
280 /// The optional complex molecular orbital energies.
281 pub(crate) mo_energies: Option<Vec<Array1<C128>>>,
282
283 /// The optional complex determinantal energy.
284 #[pyo3(get)]
285 pub(crate) energy: Option<C128>,
286}
287
288#[pymethods]
289impl PySlaterDeterminantComplex {
290 /// Constructs a complex Python-exposed Slater determinant.
291 ///
292 /// # Arguments
293 ///
294 /// * `structure_constraint` - The structure constraint applied to the coefficients of the
295 /// determinant.
296 /// * `complex_symmetric` - A boolean indicating if inner products involving this determinant
297 /// are complex-symmetric.
298 /// * `coefficients` - The complex coefficients for the molecular orbitals of this determinant.
299 /// * `occupations` - The occupation patterns for the molecular orbitals.
300 /// * `threshold` - The threshold for comparisons.
301 /// * `mo_energies` - The optional complex molecular orbital energies.
302 /// * `energy` - The optional complex determinantal energy.
303 #[new]
304 #[pyo3(signature = (structure_constraint, complex_symmetric, coefficients, occupations, threshold, mo_energies=None, energy=None))]
305 pub fn new(
306 structure_constraint: PyStructureConstraint,
307 complex_symmetric: bool,
308 coefficients: Vec<Bound<'_, PyArray2<C128>>>,
309 occupations: Vec<Bound<'_, PyArray1<f64>>>,
310 threshold: f64,
311 mo_energies: Option<Vec<Bound<'_, PyArray1<C128>>>>,
312 energy: Option<C128>,
313 ) -> Self {
314 let det = Self {
315 structure_constraint,
316 complex_symmetric,
317 coefficients: coefficients
318 .iter()
319 .map(|pyarr| pyarr.to_owned_array())
320 .collect::<Vec<_>>(),
321 occupations: occupations
322 .iter()
323 .map(|pyarr| pyarr.to_owned_array())
324 .collect::<Vec<_>>(),
325 threshold,
326 mo_energies: mo_energies.map(|energies| {
327 energies
328 .iter()
329 .map(|pyarr| pyarr.to_owned_array())
330 .collect::<Vec<_>>()
331 }),
332 energy,
333 };
334 det
335 }
336
337 /// The occupation patterns for the molecular orbitals.
338 #[getter]
339 pub fn occupations<'py>(&self, py: Python<'py>) -> PyResult<Vec<Bound<'py, PyArray1<f64>>>> {
340 Ok(self
341 .occupations
342 .iter()
343 .map(|occ| occ.to_pyarray(py))
344 .collect::<Vec<_>>())
345 }
346
347 /// The complex coefficients for the molecular orbitals of this determinant.
348 #[getter]
349 pub fn coefficients<'py>(&self, py: Python<'py>) -> PyResult<Vec<Bound<'py, PyArray2<C128>>>> {
350 Ok(self
351 .coefficients
352 .iter()
353 .map(|occ| occ.to_pyarray(py))
354 .collect::<Vec<_>>())
355 }
356
357 /// The complex molecular orbital energies, if any.
358 #[getter]
359 pub fn mo_energies<'py>(
360 &self,
361 py: Python<'py>,
362 ) -> PyResult<Option<Vec<Bound<'py, PyArray1<C128>>>>> {
363 Ok(self.mo_energies.as_ref().map(|mo_energies| {
364 mo_energies
365 .iter()
366 .map(|mo_en| mo_en.to_pyarray(py))
367 .collect::<Vec<_>>()
368 }))
369 }
370}
371
372impl PySlaterDeterminantComplex {
373 /// Extracts the information in the [`PySlaterDeterminantComplex`] structure into `QSym2`'s native
374 /// [`SlaterDeterminant`] structure.
375 ///
376 /// # Arguments
377 ///
378 /// * `baos` - The [`BasisAngularOrder`]s for the basis set in which the Slater determinant is
379 /// given, one for each explicit component per coefficient matrix.
380 /// * `mol` - The molecule with which the Slater determinant is associated.
381 ///
382 /// # Returns
383 ///
384 /// The [`SlaterDeterminant`] structure with the same information.
385 ///
386 /// # Errors
387 ///
388 /// Errors if the [`SlaterDeterminant`] fails to build.
389 pub fn to_qsym2<'b, 'a: 'b, SC>(
390 &'b self,
391 baos: &[&'a BasisAngularOrder],
392 mol: &'a Molecule,
393 ) -> Result<SlaterDeterminant<'b, C128, SC>, anyhow::Error>
394 where
395 SC: StructureConstraint
396 + Clone
397 + fmt::Display
398 + TryFrom<PyStructureConstraint, Error = anyhow::Error>,
399 {
400 let det = SlaterDeterminant::<C128, SC>::builder()
401 .structure_constraint(self.structure_constraint.clone().try_into()?)
402 .baos(baos.to_vec())
403 .complex_symmetric(self.complex_symmetric)
404 .mol(mol)
405 .coefficients(&self.coefficients)
406 .occupations(&self.occupations)
407 .mo_energies(self.mo_energies.clone())
408 .energy(
409 self.energy
410 .ok_or_else(|| "No determinantal energy set.".to_string()),
411 )
412 .threshold(self.threshold)
413 .build()
414 .map_err(|err| format_err!(err));
415 det
416 }
417
418 pub fn structure_constraint(&self) -> &PyStructureConstraint {
419 &self.structure_constraint
420 }
421}
422
423impl<'a, SC> SlaterDeterminant<'a, C128, SC>
424where
425 SC: StructureConstraint
426 + Clone
427 + fmt::Display
428 + TryInto<PyStructureConstraint, Error = anyhow::Error>,
429{
430 /// Extracts the information in the complex [`SlaterDeterminant`] structure into [`PySlaterDeterminantComplex`].
431 ///
432 /// # Returns
433 ///
434 /// The [`PySlaterDeterminantComplex`] structure with the same information.
435 pub fn to_python<'py>(
436 &self,
437 py: Python<'py>,
438 ) -> Result<PySlaterDeterminantComplex, anyhow::Error> {
439 Ok(PySlaterDeterminantComplex::new(
440 self.structure_constraint().clone().try_into()?,
441 self.complex_symmetric(),
442 self.coefficients()
443 .iter()
444 .map(|coeffs| coeffs.to_pyarray(py))
445 .collect_vec(),
446 self.occupations()
447 .iter()
448 .map(|occ| occ.to_pyarray(py))
449 .collect_vec(),
450 self.threshold(),
451 self.mo_energies().map(|mo_energies| {
452 mo_energies
453 .iter()
454 .map(|mo_e| mo_e.to_pyarray(py))
455 .collect_vec()
456 }),
457 self.energy().ok().map(|energy| *energy),
458 ))
459 }
460}
461
462// --------------------------------------------
463// Slater determinant symmetry analysis results
464// --------------------------------------------
465
466/// Python-exposed structure storing the results of Slater determinant representation analysis.
467#[pyclass]
468#[derive(Clone)]
469pub struct PySlaterDeterminantRepAnalysisResult {
470 /// The group used for the representation analysis.
471 #[pyo3(get)]
472 group: String,
473
474 /// The deduced overall symmetry of the determinant.
475 #[pyo3(get)]
476 determinant_symmetry: Option<String>,
477
478 /// The deduced symmetries of the molecular orbitals constituting the determinant, if required.
479 #[pyo3(get)]
480 mo_symmetries: Option<Vec<Vec<Option<String>>>>,
481
482 /// The deduced symmetries of the various densities constructible from the determinant, if
483 /// required. In each tuple, the first element gives a description of the density corresponding
484 /// to the symmetry result.
485 #[pyo3(get)]
486 determinant_density_symmetries: Option<Vec<(String, Option<String>)>>,
487
488 /// The deduced symmetries of the total densities of the molecular orbitals constituting the
489 /// determinant, if required.
490 #[pyo3(get)]
491 mo_density_symmetries: Option<Vec<Vec<Option<String>>>>,
492}
493
494// ================
495// Enum definitions
496// ================
497
498/// Python-exposed enumerated type to handle the union type
499/// `PySlaterDeterminantReal | PySlaterDeterminantComplex` in Python.
500#[derive(FromPyObject)]
501pub enum PySlaterDeterminant {
502 /// Variant for real Python-exposed Slater determinant.
503 Real(PySlaterDeterminantReal),
504
505 /// Variant for complex Python-exposed Slater determinant.
506 Complex(PySlaterDeterminantComplex),
507}
508
509// =====================
510// Functions definitions
511// =====================
512
513/// Python-exposed function to perform representation symmetry analysis for real and complex
514/// Slater determinants and log the result via the `qsym2-output` logger at the `INFO` level.
515///
516/// If `symmetry_transformation_kind` includes spin transformation, the provided determinant will
517/// be augmented to generalised spin constraint automatically.
518///
519/// # Arguments
520///
521/// * `inp_sym` - A path to the [`QSym2FileType::Sym`] file containing the symmetry-group detection
522/// result for the system. This will be used to construct abstract groups and character tables for
523/// representation analysis.
524/// * `pydet` - A Python-exposed Slater determinant whose coefficients are of type `float64` or
525/// `complex128`.
526/// * `pybaos` - Python-exposed structures containing basis angular order information, one for each
527/// explicit component per coefficient matrix.
528/// * `integrality_threshold` - The threshold for verifying if subspace multiplicities are
529/// integral.
530/// * `linear_independence_threshold` - The threshold for determining the linear independence
531/// subspace via the non-zero eigenvalues of the orbit overlap matrix.
532/// * `use_magnetic_group` - An option indicating if the magnetic group is to be used for symmetry
533/// analysis, and if so, whether unitary representations or unitary-antiunitary corepresentations
534/// should be used.
535/// * `use_double_group` - A boolean indicating if the double group of the prevailing symmetry
536/// group is to be used for representation analysis instead.
537/// * `use_cayley_table` - A boolean indicating if the Cayley table for the group, if available,
538/// should be used to speed up the calculation of orbit overlap matrices.
539/// * `symmetry_transformation_kind` - An enumerated type indicating the type of symmetry
540/// transformations to be performed on the origin determinant to generate the orbit. If this
541/// contains spin transformation, the determinant will be augmented to generalised spin constraint
542/// automatically.
543/// * `eigenvalue_comparison_mode` - An enumerated type indicating the mode of comparison of orbit
544/// overlap eigenvalues with the specified `linear_independence_threshold`.
545/// * `sao` - The atomic-orbital overlap matrix whose elements are of type `float64` or
546/// `complex128`.
547/// * `sao_h` - The optional complex-symmetric atomic-orbital overlap matrix whose elements
548/// are of type `float64` or `complex128`. This is required if antiunitary symmetry operations are
549/// involved.
550/// * `sao_spatial_4c` - The optional atomic-orbital four-centre overlap matrix whose elements are
551/// of type `float64` or `complex128`.
552/// * `sao_spatial_4c_h` - The optional complex-symmetric atomic-orbital four-centre overlap matrix
553/// whose elements are of type `float64` or `complex128`. This is required if antiunitary symmetry
554/// operations are involved.
555/// * `analyse_mo_symmetries` - A boolean indicating if the symmetries of individual molecular
556/// orbitals are to be analysed.
557/// * `analyse_mo_symmetry_projections` - A boolean indicating if the symmetry projections of
558/// individual molecular orbitals are to be analysed.
559/// * `analyse_mo_mirror_parities` - A boolean indicating if the mirror parities of individual
560/// molecular orbitals are to be printed.
561/// * `analyse_density_symmetries` - A boolean indicating if the symmetries of densities are to be
562/// analysed.
563/// * `write_overlap_eigenvalues` - A boolean indicating if the eigenvalues of the determinant
564/// orbit overlap matrix are to be written to the output.
565/// * `write_character_table` - A boolean indicating if the character table of the prevailing
566/// symmetry group is to be printed out.
567/// * `infinite_order_to_finite` - The finite order with which infinite-order generators are to be
568/// interpreted to form a finite subgroup of the prevailing infinite group. This finite subgroup
569/// will be used for symmetry analysis.
570/// * `angular_function_integrality_threshold` - The threshold for verifying if subspace
571/// multiplicities are integral for the symmetry analysis of angular functions.
572/// * `angular_function_linear_independence_threshold` - The threshold for determining the linear
573/// independence subspace via the non-zero eigenvalues of the orbit overlap matrix for the symmetry
574/// analysis of angular functions.
575/// * `angular_function_max_angular_momentum` - The maximum angular momentum order to be used in
576/// angular function symmetry analysis.
577///
578/// # Returns
579///
580/// A Python-exposed [`PySlaterDeterminantRepAnalysisResult`] structure containing the results of the
581/// representation analysis.
582#[pyfunction]
583#[pyo3(signature = (
584 inp_sym,
585 pydet,
586 pybaos,
587 integrality_threshold,
588 linear_independence_threshold,
589 use_magnetic_group,
590 use_double_group,
591 use_cayley_table,
592 symmetry_transformation_kind,
593 eigenvalue_comparison_mode,
594 sao,
595 sao_h=None,
596 sao_spatial_4c=None,
597 sao_spatial_4c_h=None,
598 analyse_mo_symmetries=true,
599 analyse_mo_symmetry_projections=true,
600 analyse_mo_mirror_parities=false,
601 analyse_density_symmetries=false,
602 write_overlap_eigenvalues=true,
603 write_character_table=true,
604 infinite_order_to_finite=None,
605 angular_function_integrality_threshold=1e-7,
606 angular_function_linear_independence_threshold=1e-7,
607 angular_function_max_angular_momentum=2
608))]
609pub fn rep_analyse_slater_determinant(
610 py: Python<'_>,
611 inp_sym: PathBuf,
612 pydet: PySlaterDeterminant,
613 pybaos: Vec<PyBasisAngularOrder>,
614 integrality_threshold: f64,
615 linear_independence_threshold: f64,
616 use_magnetic_group: Option<MagneticSymmetryAnalysisKind>,
617 use_double_group: bool,
618 use_cayley_table: bool,
619 symmetry_transformation_kind: SymmetryTransformationKind,
620 eigenvalue_comparison_mode: EigenvalueComparisonMode,
621 sao: PyArray2RC,
622 sao_h: Option<PyArray2RC>,
623 sao_spatial_4c: Option<PyArray4RC>,
624 sao_spatial_4c_h: Option<PyArray4RC>,
625 analyse_mo_symmetries: bool,
626 analyse_mo_symmetry_projections: bool,
627 analyse_mo_mirror_parities: bool,
628 analyse_density_symmetries: bool,
629 write_overlap_eigenvalues: bool,
630 write_character_table: bool,
631 infinite_order_to_finite: Option<u32>,
632 angular_function_integrality_threshold: f64,
633 angular_function_linear_independence_threshold: f64,
634 angular_function_max_angular_momentum: u32,
635) -> PyResult<PySlaterDeterminantRepAnalysisResult> {
636 let pd_res: SymmetryGroupDetectionResult =
637 read_qsym2_binary(inp_sym.clone(), QSym2FileType::Sym)
638 .map_err(|err| PyIOError::new_err(err.to_string()))?;
639
640 let mut file_name = inp_sym.to_path_buf();
641 file_name.set_extension(QSym2FileType::Sym.ext());
642 qsym2_output!(
643 "Symmetry-group detection results read in from {}.",
644 file_name.display(),
645 );
646 qsym2_output!("");
647
648 let mol = &pd_res.pre_symmetry.recentred_molecule;
649
650 let baos = pybaos
651 .iter()
652 .map(|bao| {
653 bao.to_qsym2(mol)
654 .map_err(|err| PyRuntimeError::new_err(err.to_string()))
655 })
656 .collect::<Result<Vec<_>, _>>()?;
657 let baos_ref = baos.iter().collect::<Vec<_>>();
658 let augment_to_generalised = match symmetry_transformation_kind {
659 SymmetryTransformationKind::SpatialWithSpinTimeReversal
660 | SymmetryTransformationKind::Spin
661 | SymmetryTransformationKind::SpinSpatial => true,
662 SymmetryTransformationKind::Spatial => false,
663 };
664 let afa_params = AngularFunctionRepAnalysisParams::builder()
665 .integrality_threshold(angular_function_integrality_threshold)
666 .linear_independence_threshold(angular_function_linear_independence_threshold)
667 .max_angular_momentum(angular_function_max_angular_momentum)
668 .build()
669 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
670 let sda_params = SlaterDeterminantRepAnalysisParams::<f64>::builder()
671 .integrality_threshold(integrality_threshold)
672 .linear_independence_threshold(linear_independence_threshold)
673 .use_magnetic_group(use_magnetic_group.clone())
674 .use_double_group(use_double_group)
675 .use_cayley_table(use_cayley_table)
676 .symmetry_transformation_kind(symmetry_transformation_kind)
677 .eigenvalue_comparison_mode(eigenvalue_comparison_mode)
678 .analyse_mo_symmetries(analyse_mo_symmetries)
679 .analyse_mo_symmetry_projections(analyse_mo_symmetry_projections)
680 .analyse_mo_mirror_parities(analyse_mo_mirror_parities)
681 .analyse_density_symmetries(analyse_density_symmetries)
682 .write_overlap_eigenvalues(write_overlap_eigenvalues)
683 .write_character_table(if write_character_table {
684 Some(CharacterTableDisplay::Symbolic)
685 } else {
686 None
687 })
688 .infinite_order_to_finite(infinite_order_to_finite)
689 .build()
690 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
691 let pysda_res: PySlaterDeterminantRepAnalysisResult = match (&pydet, &sao) {
692 (PySlaterDeterminant::Real(pydet_r), PyArray2RC::Real(pysao_r)) => {
693 if matches!(
694 pydet_r.structure_constraint,
695 PyStructureConstraint::SpinOrbitCoupled(_)
696 ) {
697 return Err(PyRuntimeError::new_err("Real determinants are not compatible with spin--orbit-coupled structure constraint.".to_string()));
698 }
699
700 let sao = pysao_r.to_owned_array();
701 let sao_spatial_4c = sao_spatial_4c.and_then(|pysao4c| match pysao4c {
702 // sao_spatial_4c must have the same reality as sao_spatial.
703 PyArray4RC::Real(pysao4c_r) => Some(pysao4c_r.to_owned_array()),
704 PyArray4RC::Complex(_) => None,
705 });
706
707 let det_r = if augment_to_generalised {
708 pydet_r
709 .to_qsym2::<SpinConstraint>(&baos_ref, mol)
710 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?
711 .to_generalised()
712 } else {
713 pydet_r
714 .to_qsym2::<SpinConstraint>(&baos_ref, mol)
715 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?
716 };
717 match &use_magnetic_group {
718 Some(MagneticSymmetryAnalysisKind::Corepresentation) => {
719 let mut sda_driver = SlaterDeterminantRepAnalysisDriver::<
720 MagneticRepresentedSymmetryGroup,
721 f64,
722 SpinConstraint,
723 >::builder()
724 .parameters(&sda_params)
725 .angular_function_parameters(&afa_params)
726 .determinant(&det_r)
727 .sao(&sao)
728 .sao_h(None) // Real SAO.
729 .sao_spatial_4c(sao_spatial_4c.as_ref())
730 .sao_spatial_4c_h(None) // Real SAO.
731 .symmetry_group(&pd_res)
732 .build()
733 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
734 py.detach(|| {
735 sda_driver
736 .run()
737 .map_err(|err| PyRuntimeError::new_err(err.to_string()))
738 })?;
739 let sda_res = sda_driver
740 .result()
741 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
742 PySlaterDeterminantRepAnalysisResult {
743 group: sda_res.group().name().clone(),
744 determinant_symmetry: sda_res
745 .determinant_symmetry()
746 .as_ref()
747 .ok()
748 .map(|sym| sym.to_string()),
749 mo_symmetries: sda_res.mo_symmetries().as_ref().map(|mo_symss| {
750 mo_symss
751 .iter()
752 .map(|mo_syms| {
753 mo_syms
754 .iter()
755 .map(|mo_sym_opt| {
756 mo_sym_opt.as_ref().map(|mo_sym| mo_sym.to_string())
757 })
758 .collect::<Vec<_>>()
759 })
760 .collect::<Vec<_>>()
761 }),
762 determinant_density_symmetries: sda_res
763 .determinant_density_symmetries()
764 .as_ref()
765 .map(|den_syms| {
766 den_syms
767 .iter()
768 .map(|(den_name, den_sym_res)| {
769 (
770 den_name.clone(),
771 den_sym_res
772 .as_ref()
773 .ok()
774 .map(|den_sym| den_sym.to_string()),
775 )
776 })
777 .collect::<Vec<_>>()
778 }),
779 mo_density_symmetries: sda_res.mo_density_symmetries().as_ref().map(
780 |mo_den_symss| {
781 mo_den_symss
782 .iter()
783 .map(|mo_den_syms| {
784 mo_den_syms
785 .iter()
786 .map(|mo_den_sym_opt| {
787 mo_den_sym_opt
788 .as_ref()
789 .map(|mo_den_sym| mo_den_sym.to_string())
790 })
791 .collect::<Vec<_>>()
792 })
793 .collect::<Vec<_>>()
794 },
795 ),
796 }
797 }
798 Some(MagneticSymmetryAnalysisKind::Representation) | None => {
799 let mut sda_driver = SlaterDeterminantRepAnalysisDriver::<
800 UnitaryRepresentedSymmetryGroup,
801 f64,
802 SpinConstraint,
803 >::builder()
804 .parameters(&sda_params)
805 .angular_function_parameters(&afa_params)
806 .determinant(&det_r)
807 .sao(&sao)
808 .sao_h(None) // Real SAO.
809 .sao_spatial_4c(sao_spatial_4c.as_ref())
810 .sao_spatial_4c_h(None) // Real SAO.
811 .symmetry_group(&pd_res)
812 .build()
813 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
814 py.detach(|| {
815 sda_driver
816 .run()
817 .map_err(|err| PyRuntimeError::new_err(err.to_string()))
818 })?;
819 let sda_res = sda_driver
820 .result()
821 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
822 PySlaterDeterminantRepAnalysisResult {
823 group: sda_res.group().name().clone(),
824 determinant_symmetry: sda_res
825 .determinant_symmetry()
826 .as_ref()
827 .ok()
828 .map(|sym| sym.to_string()),
829 mo_symmetries: sda_res.mo_symmetries().as_ref().map(|mo_symss| {
830 mo_symss
831 .iter()
832 .map(|mo_syms| {
833 mo_syms
834 .iter()
835 .map(|mo_sym_opt| {
836 mo_sym_opt.as_ref().map(|mo_sym| mo_sym.to_string())
837 })
838 .collect::<Vec<_>>()
839 })
840 .collect::<Vec<_>>()
841 }),
842 determinant_density_symmetries: sda_res
843 .determinant_density_symmetries()
844 .as_ref()
845 .map(|den_syms| {
846 den_syms
847 .iter()
848 .map(|(den_name, den_sym_res)| {
849 (
850 den_name.clone(),
851 den_sym_res
852 .as_ref()
853 .ok()
854 .map(|den_sym| den_sym.to_string()),
855 )
856 })
857 .collect::<Vec<_>>()
858 }),
859 mo_density_symmetries: sda_res.mo_density_symmetries().as_ref().map(
860 |mo_den_symss| {
861 mo_den_symss
862 .iter()
863 .map(|mo_den_syms| {
864 mo_den_syms
865 .iter()
866 .map(|mo_den_sym_opt| {
867 mo_den_sym_opt
868 .as_ref()
869 .map(|mo_den_sym| mo_den_sym.to_string())
870 })
871 .collect::<Vec<_>>()
872 })
873 .collect::<Vec<_>>()
874 },
875 ),
876 }
877 }
878 }
879 }
880 (PySlaterDeterminant::Real(pydet_r), PyArray2RC::Complex(pysao_c)) => {
881 match pydet_r.structure_constraint {
882 PyStructureConstraint::SpinConstraint(_) => {
883 let det_r = if augment_to_generalised {
884 pydet_r
885 .to_qsym2(&baos_ref, mol)
886 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?
887 .to_generalised()
888 } else {
889 pydet_r
890 .to_qsym2(&baos_ref, mol)
891 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?
892 };
893 let det_c: SlaterDeterminant<C128, SpinConstraint> = det_r.into();
894 let sao_c = pysao_c.to_owned_array();
895 let sao_h_c = sao_h.and_then(|pysao_h| match pysao_h {
896 // sao_spatial_h must have the same reality as sao_spatial.
897 PyArray2RC::Real(_) => None,
898 PyArray2RC::Complex(pysao_h_c) => Some(pysao_h_c.to_owned_array()),
899 });
900 let sao_spatial_4c_c = sao_spatial_4c.and_then(|pysao4c| match pysao4c {
901 // sao_spatial_4c must have the same reality as sao_spatial.
902 PyArray4RC::Real(_) => None,
903 PyArray4RC::Complex(pysao4c_c) => Some(pysao4c_c.to_owned_array()),
904 });
905 let sao_spatial_4c_h_c =
906 sao_spatial_4c_h.and_then(|pysao4c_h| match pysao4c_h {
907 // sao_spatial_4c_h must have the same reality as sao_spatial.
908 PyArray4RC::Real(_) => None,
909 PyArray4RC::Complex(pysao4c_h_c) => Some(pysao4c_h_c.to_owned_array()),
910 });
911 match &use_magnetic_group {
912 Some(MagneticSymmetryAnalysisKind::Corepresentation) => {
913 let mut sda_driver = SlaterDeterminantRepAnalysisDriver::<
914 MagneticRepresentedSymmetryGroup,
915 C128,
916 SpinConstraint,
917 >::builder()
918 .parameters(&sda_params)
919 .angular_function_parameters(&afa_params)
920 .determinant(&det_c)
921 .sao(&sao_c)
922 .sao_h(sao_h_c.as_ref())
923 .sao_spatial_4c(sao_spatial_4c_c.as_ref())
924 .sao_spatial_4c_h(sao_spatial_4c_h_c.as_ref())
925 .symmetry_group(&pd_res)
926 .build()
927 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
928 py.detach(|| {
929 sda_driver
930 .run()
931 .map_err(|err| PyRuntimeError::new_err(err.to_string()))
932 })?;
933 let sda_res = sda_driver
934 .result()
935 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
936 PySlaterDeterminantRepAnalysisResult {
937 group: sda_res.group().name().clone(),
938 determinant_symmetry: sda_res
939 .determinant_symmetry()
940 .as_ref()
941 .ok()
942 .map(|sym| sym.to_string()),
943 mo_symmetries: sda_res.mo_symmetries().as_ref().map(|mo_symss| {
944 mo_symss
945 .iter()
946 .map(|mo_syms| {
947 mo_syms
948 .iter()
949 .map(|mo_sym_opt| {
950 mo_sym_opt
951 .as_ref()
952 .map(|mo_sym| mo_sym.to_string())
953 })
954 .collect::<Vec<_>>()
955 })
956 .collect::<Vec<_>>()
957 }),
958 determinant_density_symmetries: sda_res
959 .determinant_density_symmetries()
960 .as_ref()
961 .map(|den_syms| {
962 den_syms
963 .iter()
964 .map(|(den_name, den_sym_res)| {
965 (
966 den_name.clone(),
967 den_sym_res
968 .as_ref()
969 .ok()
970 .map(|den_sym| den_sym.to_string()),
971 )
972 })
973 .collect::<Vec<_>>()
974 }),
975 mo_density_symmetries: sda_res
976 .mo_density_symmetries()
977 .as_ref()
978 .map(|mo_den_symss| {
979 mo_den_symss
980 .iter()
981 .map(|mo_den_syms| {
982 mo_den_syms
983 .iter()
984 .map(|mo_den_sym_opt| {
985 mo_den_sym_opt.as_ref().map(|mo_den_sym| {
986 mo_den_sym.to_string()
987 })
988 })
989 .collect::<Vec<_>>()
990 })
991 .collect::<Vec<_>>()
992 }),
993 }
994 }
995 Some(MagneticSymmetryAnalysisKind::Representation) | None => {
996 let mut sda_driver = SlaterDeterminantRepAnalysisDriver::<
997 UnitaryRepresentedSymmetryGroup,
998 C128,
999 SpinConstraint,
1000 >::builder()
1001 .parameters(&sda_params)
1002 .angular_function_parameters(&afa_params)
1003 .determinant(&det_c)
1004 .sao(&sao_c)
1005 .sao_h(sao_h_c.as_ref())
1006 .sao_spatial_4c(sao_spatial_4c_c.as_ref())
1007 .sao_spatial_4c_h(sao_spatial_4c_h_c.as_ref())
1008 .symmetry_group(&pd_res)
1009 .build()
1010 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
1011 py.detach(|| {
1012 sda_driver
1013 .run()
1014 .map_err(|err| PyRuntimeError::new_err(err.to_string()))
1015 })?;
1016 let sda_res = sda_driver
1017 .result()
1018 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
1019 PySlaterDeterminantRepAnalysisResult {
1020 group: sda_res.group().name().clone(),
1021 determinant_symmetry: sda_res
1022 .determinant_symmetry()
1023 .as_ref()
1024 .ok()
1025 .map(|sym| sym.to_string()),
1026 mo_symmetries: sda_res.mo_symmetries().as_ref().map(|mo_symss| {
1027 mo_symss
1028 .iter()
1029 .map(|mo_syms| {
1030 mo_syms
1031 .iter()
1032 .map(|mo_sym_opt| {
1033 mo_sym_opt
1034 .as_ref()
1035 .map(|mo_sym| mo_sym.to_string())
1036 })
1037 .collect::<Vec<_>>()
1038 })
1039 .collect::<Vec<_>>()
1040 }),
1041 determinant_density_symmetries: sda_res
1042 .determinant_density_symmetries()
1043 .as_ref()
1044 .map(|den_syms| {
1045 den_syms
1046 .iter()
1047 .map(|(den_name, den_sym_res)| {
1048 (
1049 den_name.clone(),
1050 den_sym_res
1051 .as_ref()
1052 .ok()
1053 .map(|den_sym| den_sym.to_string()),
1054 )
1055 })
1056 .collect::<Vec<_>>()
1057 }),
1058 mo_density_symmetries: sda_res
1059 .mo_density_symmetries()
1060 .as_ref()
1061 .map(|mo_den_symss| {
1062 mo_den_symss
1063 .iter()
1064 .map(|mo_den_syms| {
1065 mo_den_syms
1066 .iter()
1067 .map(|mo_den_sym_opt| {
1068 mo_den_sym_opt.as_ref().map(|mo_den_sym| {
1069 mo_den_sym.to_string()
1070 })
1071 })
1072 .collect::<Vec<_>>()
1073 })
1074 .collect::<Vec<_>>()
1075 }),
1076 }
1077 }
1078 }
1079 }
1080 PyStructureConstraint::SpinOrbitCoupled(_) => {
1081 let det_r = pydet_r
1082 .to_qsym2::<SpinOrbitCoupled>(&baos_ref, mol)
1083 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
1084 let det_c: SlaterDeterminant<C128, SpinOrbitCoupled> = det_r.into();
1085 let sao_c = pysao_c.to_owned_array();
1086 let sao_h_c = sao_h.and_then(|pysao_h| match pysao_h {
1087 // sao_h must have the same reality as sao.
1088 PyArray2RC::Real(_) => None,
1089 PyArray2RC::Complex(pysao_h_c) => Some(pysao_h_c.to_owned_array()),
1090 });
1091 let sao_spatial_4c_c = sao_spatial_4c.and_then(|pysao4c| match pysao4c {
1092 // sao_spatial_4c must have the same reality as sao.
1093 PyArray4RC::Real(_) => None,
1094 PyArray4RC::Complex(pysao4c_c) => Some(pysao4c_c.to_owned_array()),
1095 });
1096 let sao_spatial_4c_h_c =
1097 sao_spatial_4c_h.and_then(|pysao4c_h| match pysao4c_h {
1098 // sao_spatial_4c_h must have the same reality as sao_spatial.
1099 PyArray4RC::Real(_) => None,
1100 PyArray4RC::Complex(pysao4c_h_c) => Some(pysao4c_h_c.to_owned_array()),
1101 });
1102 match &use_magnetic_group {
1103 Some(MagneticSymmetryAnalysisKind::Corepresentation) => {
1104 let mut sda_driver = SlaterDeterminantRepAnalysisDriver::<
1105 MagneticRepresentedSymmetryGroup,
1106 C128,
1107 SpinOrbitCoupled,
1108 >::builder()
1109 .parameters(&sda_params)
1110 .angular_function_parameters(&afa_params)
1111 .determinant(&det_c)
1112 .sao(&sao_c)
1113 .sao_h(sao_h_c.as_ref())
1114 .sao_spatial_4c(sao_spatial_4c_c.as_ref())
1115 .sao_spatial_4c_h(sao_spatial_4c_h_c.as_ref())
1116 .symmetry_group(&pd_res)
1117 .build()
1118 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
1119 py.detach(|| {
1120 sda_driver
1121 .run()
1122 .map_err(|err| PyRuntimeError::new_err(err.to_string()))
1123 })?;
1124 let sda_res = sda_driver
1125 .result()
1126 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
1127 PySlaterDeterminantRepAnalysisResult {
1128 group: sda_res.group().name().clone(),
1129 determinant_symmetry: sda_res
1130 .determinant_symmetry()
1131 .as_ref()
1132 .ok()
1133 .map(|sym| sym.to_string()),
1134 mo_symmetries: sda_res.mo_symmetries().as_ref().map(|mo_symss| {
1135 mo_symss
1136 .iter()
1137 .map(|mo_syms| {
1138 mo_syms
1139 .iter()
1140 .map(|mo_sym_opt| {
1141 mo_sym_opt
1142 .as_ref()
1143 .map(|mo_sym| mo_sym.to_string())
1144 })
1145 .collect::<Vec<_>>()
1146 })
1147 .collect::<Vec<_>>()
1148 }),
1149 determinant_density_symmetries: sda_res
1150 .determinant_density_symmetries()
1151 .as_ref()
1152 .map(|den_syms| {
1153 den_syms
1154 .iter()
1155 .map(|(den_name, den_sym_res)| {
1156 (
1157 den_name.clone(),
1158 den_sym_res
1159 .as_ref()
1160 .ok()
1161 .map(|den_sym| den_sym.to_string()),
1162 )
1163 })
1164 .collect::<Vec<_>>()
1165 }),
1166 mo_density_symmetries: sda_res
1167 .mo_density_symmetries()
1168 .as_ref()
1169 .map(|mo_den_symss| {
1170 mo_den_symss
1171 .iter()
1172 .map(|mo_den_syms| {
1173 mo_den_syms
1174 .iter()
1175 .map(|mo_den_sym_opt| {
1176 mo_den_sym_opt.as_ref().map(|mo_den_sym| {
1177 mo_den_sym.to_string()
1178 })
1179 })
1180 .collect::<Vec<_>>()
1181 })
1182 .collect::<Vec<_>>()
1183 }),
1184 }
1185 }
1186 Some(MagneticSymmetryAnalysisKind::Representation) | None => {
1187 let mut sda_driver = SlaterDeterminantRepAnalysisDriver::<
1188 UnitaryRepresentedSymmetryGroup,
1189 C128,
1190 SpinOrbitCoupled,
1191 >::builder()
1192 .parameters(&sda_params)
1193 .angular_function_parameters(&afa_params)
1194 .determinant(&det_c)
1195 .sao(&sao_c)
1196 .sao_h(sao_h_c.as_ref())
1197 .sao_spatial_4c(sao_spatial_4c_c.as_ref())
1198 .sao_spatial_4c_h(sao_spatial_4c_h_c.as_ref())
1199 .symmetry_group(&pd_res)
1200 .build()
1201 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
1202 py.detach(|| {
1203 sda_driver
1204 .run()
1205 .map_err(|err| PyRuntimeError::new_err(err.to_string()))
1206 })?;
1207 let sda_res = sda_driver
1208 .result()
1209 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
1210 PySlaterDeterminantRepAnalysisResult {
1211 group: sda_res.group().name().clone(),
1212 determinant_symmetry: sda_res
1213 .determinant_symmetry()
1214 .as_ref()
1215 .ok()
1216 .map(|sym| sym.to_string()),
1217 mo_symmetries: sda_res.mo_symmetries().as_ref().map(|mo_symss| {
1218 mo_symss
1219 .iter()
1220 .map(|mo_syms| {
1221 mo_syms
1222 .iter()
1223 .map(|mo_sym_opt| {
1224 mo_sym_opt
1225 .as_ref()
1226 .map(|mo_sym| mo_sym.to_string())
1227 })
1228 .collect::<Vec<_>>()
1229 })
1230 .collect::<Vec<_>>()
1231 }),
1232 determinant_density_symmetries: sda_res
1233 .determinant_density_symmetries()
1234 .as_ref()
1235 .map(|den_syms| {
1236 den_syms
1237 .iter()
1238 .map(|(den_name, den_sym_res)| {
1239 (
1240 den_name.clone(),
1241 den_sym_res
1242 .as_ref()
1243 .ok()
1244 .map(|den_sym| den_sym.to_string()),
1245 )
1246 })
1247 .collect::<Vec<_>>()
1248 }),
1249 mo_density_symmetries: sda_res
1250 .mo_density_symmetries()
1251 .as_ref()
1252 .map(|mo_den_symss| {
1253 mo_den_symss
1254 .iter()
1255 .map(|mo_den_syms| {
1256 mo_den_syms
1257 .iter()
1258 .map(|mo_den_sym_opt| {
1259 mo_den_sym_opt.as_ref().map(|mo_den_sym| {
1260 mo_den_sym.to_string()
1261 })
1262 })
1263 .collect::<Vec<_>>()
1264 })
1265 .collect::<Vec<_>>()
1266 }),
1267 }
1268 }
1269 }
1270 }
1271 }
1272 }
1273 (PySlaterDeterminant::Complex(pydet_c), _) => {
1274 match pydet_c.structure_constraint {
1275 PyStructureConstraint::SpinConstraint(_) => {
1276 let det_c = if augment_to_generalised {
1277 pydet_c
1278 .to_qsym2::<SpinConstraint>(&baos_ref, mol)
1279 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?
1280 .to_generalised()
1281 } else {
1282 pydet_c
1283 .to_qsym2::<SpinConstraint>(&baos_ref, mol)
1284 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?
1285 };
1286 let sao_c = match sao {
1287 PyArray2RC::Real(pysao_r) => pysao_r.to_owned_array().mapv(Complex::from),
1288 PyArray2RC::Complex(pysao_c) => pysao_c.to_owned_array(),
1289 };
1290 let sao_h_c = sao_h.and_then(|pysao_h| match pysao_h {
1291 // sao_h must have the same reality as sao.
1292 PyArray2RC::Real(pysao_h_r) => {
1293 Some(pysao_h_r.to_owned_array().mapv(Complex::from))
1294 }
1295 PyArray2RC::Complex(pysao_h_c) => Some(pysao_h_c.to_owned_array()),
1296 });
1297 let sao_spatial_4c_c = sao_spatial_4c.and_then(|pysao4c| match pysao4c {
1298 // sao_spatial_4c must have the same reality as sao.
1299 PyArray4RC::Real(pysao4c_r) => {
1300 Some(pysao4c_r.to_owned_array().mapv(Complex::from))
1301 }
1302 PyArray4RC::Complex(pysao4c_c) => Some(pysao4c_c.to_owned_array()),
1303 });
1304 let sao_spatial_4c_h_c =
1305 sao_spatial_4c_h.and_then(|pysao4c_h| match pysao4c_h {
1306 // sao_spatial_4c_h must have the same reality as sao.
1307 PyArray4RC::Real(pysao4c_h_r) => {
1308 Some(pysao4c_h_r.to_owned_array().mapv(Complex::from))
1309 }
1310 PyArray4RC::Complex(pysao4c_h_c) => Some(pysao4c_h_c.to_owned_array()),
1311 });
1312 match &use_magnetic_group {
1313 Some(MagneticSymmetryAnalysisKind::Corepresentation) => {
1314 let mut sda_driver = SlaterDeterminantRepAnalysisDriver::<
1315 MagneticRepresentedSymmetryGroup,
1316 C128,
1317 SpinConstraint,
1318 >::builder()
1319 .parameters(&sda_params)
1320 .angular_function_parameters(&afa_params)
1321 .determinant(&det_c)
1322 .sao(&sao_c)
1323 .sao_h(sao_h_c.as_ref())
1324 .sao_spatial_4c(sao_spatial_4c_c.as_ref())
1325 .sao_spatial_4c_h(sao_spatial_4c_h_c.as_ref())
1326 .symmetry_group(&pd_res)
1327 .build()
1328 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
1329 py.detach(|| {
1330 sda_driver
1331 .run()
1332 .map_err(|err| PyRuntimeError::new_err(err.to_string()))
1333 })?;
1334 let sda_res = sda_driver
1335 .result()
1336 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
1337 PySlaterDeterminantRepAnalysisResult {
1338 group: sda_res.group().name().clone(),
1339 determinant_symmetry: sda_res
1340 .determinant_symmetry()
1341 .as_ref()
1342 .ok()
1343 .map(|sym| sym.to_string()),
1344 mo_symmetries: sda_res.mo_symmetries().as_ref().map(|mo_symss| {
1345 mo_symss
1346 .iter()
1347 .map(|mo_syms| {
1348 mo_syms
1349 .iter()
1350 .map(|mo_sym_opt| {
1351 mo_sym_opt
1352 .as_ref()
1353 .map(|mo_sym| mo_sym.to_string())
1354 })
1355 .collect::<Vec<_>>()
1356 })
1357 .collect::<Vec<_>>()
1358 }),
1359 determinant_density_symmetries: sda_res
1360 .determinant_density_symmetries()
1361 .as_ref()
1362 .map(|den_syms| {
1363 den_syms
1364 .iter()
1365 .map(|(den_name, den_sym_res)| {
1366 (
1367 den_name.clone(),
1368 den_sym_res
1369 .as_ref()
1370 .ok()
1371 .map(|den_sym| den_sym.to_string()),
1372 )
1373 })
1374 .collect::<Vec<_>>()
1375 }),
1376 mo_density_symmetries: sda_res
1377 .mo_density_symmetries()
1378 .as_ref()
1379 .map(|mo_den_symss| {
1380 mo_den_symss
1381 .iter()
1382 .map(|mo_den_syms| {
1383 mo_den_syms
1384 .iter()
1385 .map(|mo_den_sym_opt| {
1386 mo_den_sym_opt.as_ref().map(|mo_den_sym| {
1387 mo_den_sym.to_string()
1388 })
1389 })
1390 .collect::<Vec<_>>()
1391 })
1392 .collect::<Vec<_>>()
1393 }),
1394 }
1395 }
1396 Some(MagneticSymmetryAnalysisKind::Representation) | None => {
1397 let mut sda_driver = SlaterDeterminantRepAnalysisDriver::<
1398 UnitaryRepresentedSymmetryGroup,
1399 C128,
1400 SpinConstraint,
1401 >::builder()
1402 .parameters(&sda_params)
1403 .angular_function_parameters(&afa_params)
1404 .determinant(&det_c)
1405 .sao(&sao_c)
1406 .sao_h(sao_h_c.as_ref())
1407 .sao_spatial_4c(sao_spatial_4c_c.as_ref())
1408 .sao_spatial_4c_h(sao_spatial_4c_h_c.as_ref())
1409 .symmetry_group(&pd_res)
1410 .build()
1411 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
1412 py.detach(|| {
1413 sda_driver
1414 .run()
1415 .map_err(|err| PyRuntimeError::new_err(err.to_string()))
1416 })?;
1417 let sda_res = sda_driver
1418 .result()
1419 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
1420 PySlaterDeterminantRepAnalysisResult {
1421 group: sda_res.group().name().clone(),
1422 determinant_symmetry: sda_res
1423 .determinant_symmetry()
1424 .as_ref()
1425 .ok()
1426 .map(|sym| sym.to_string()),
1427 mo_symmetries: sda_res.mo_symmetries().as_ref().map(|mo_symss| {
1428 mo_symss
1429 .iter()
1430 .map(|mo_syms| {
1431 mo_syms
1432 .iter()
1433 .map(|mo_sym_opt| {
1434 mo_sym_opt
1435 .as_ref()
1436 .map(|mo_sym| mo_sym.to_string())
1437 })
1438 .collect::<Vec<_>>()
1439 })
1440 .collect::<Vec<_>>()
1441 }),
1442 determinant_density_symmetries: sda_res
1443 .determinant_density_symmetries()
1444 .as_ref()
1445 .map(|den_syms| {
1446 den_syms
1447 .iter()
1448 .map(|(den_name, den_sym_res)| {
1449 (
1450 den_name.clone(),
1451 den_sym_res
1452 .as_ref()
1453 .ok()
1454 .map(|den_sym| den_sym.to_string()),
1455 )
1456 })
1457 .collect::<Vec<_>>()
1458 }),
1459 mo_density_symmetries: sda_res
1460 .mo_density_symmetries()
1461 .as_ref()
1462 .map(|mo_den_symss| {
1463 mo_den_symss
1464 .iter()
1465 .map(|mo_den_syms| {
1466 mo_den_syms
1467 .iter()
1468 .map(|mo_den_sym_opt| {
1469 mo_den_sym_opt.as_ref().map(|mo_den_sym| {
1470 mo_den_sym.to_string()
1471 })
1472 })
1473 .collect::<Vec<_>>()
1474 })
1475 .collect::<Vec<_>>()
1476 }),
1477 }
1478 }
1479 }
1480 }
1481 PyStructureConstraint::SpinOrbitCoupled(_) => {
1482 let det_c = pydet_c
1483 .to_qsym2::<SpinOrbitCoupled>(&baos_ref, mol)
1484 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
1485 let sao_c = match sao {
1486 PyArray2RC::Real(pysao_r) => pysao_r.to_owned_array().mapv(Complex::from),
1487 PyArray2RC::Complex(pysao_c) => pysao_c.to_owned_array(),
1488 };
1489 let sao_h_c = sao_h.and_then(|pysao_h| match pysao_h {
1490 // sao_spatial_h must have the same reality as sao.
1491 PyArray2RC::Real(pysao_h_r) => {
1492 Some(pysao_h_r.to_owned_array().mapv(Complex::from))
1493 }
1494 PyArray2RC::Complex(pysao_h_c) => Some(pysao_h_c.to_owned_array()),
1495 });
1496 let sao_spatial_4c_c = sao_spatial_4c.and_then(|pysao4c| match pysao4c {
1497 // sao_spatial_4c must have the same reality as sao.
1498 PyArray4RC::Real(pysao4c_r) => {
1499 Some(pysao4c_r.to_owned_array().mapv(Complex::from))
1500 }
1501 PyArray4RC::Complex(pysao4c_c) => Some(pysao4c_c.to_owned_array()),
1502 });
1503 let sao_spatial_4c_h_c =
1504 sao_spatial_4c_h.and_then(|pysao4c_h| match pysao4c_h {
1505 // sao_spatial_4c_h must have the same reality as sao.
1506 PyArray4RC::Real(pysao4c_h_r) => {
1507 Some(pysao4c_h_r.to_owned_array().mapv(Complex::from))
1508 }
1509 PyArray4RC::Complex(pysao4c_h_c) => Some(pysao4c_h_c.to_owned_array()),
1510 });
1511 match &use_magnetic_group {
1512 Some(MagneticSymmetryAnalysisKind::Corepresentation) => {
1513 let mut sda_driver = SlaterDeterminantRepAnalysisDriver::<
1514 MagneticRepresentedSymmetryGroup,
1515 C128,
1516 SpinOrbitCoupled,
1517 >::builder()
1518 .parameters(&sda_params)
1519 .angular_function_parameters(&afa_params)
1520 .determinant(&det_c)
1521 .sao(&sao_c)
1522 .sao_h(sao_h_c.as_ref())
1523 .sao_spatial_4c(sao_spatial_4c_c.as_ref())
1524 .sao_spatial_4c_h(sao_spatial_4c_h_c.as_ref())
1525 .symmetry_group(&pd_res)
1526 .build()
1527 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
1528 py.detach(|| {
1529 sda_driver
1530 .run()
1531 .map_err(|err| PyRuntimeError::new_err(err.to_string()))
1532 })?;
1533 let sda_res = sda_driver
1534 .result()
1535 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
1536 PySlaterDeterminantRepAnalysisResult {
1537 group: sda_res.group().name().clone(),
1538 determinant_symmetry: sda_res
1539 .determinant_symmetry()
1540 .as_ref()
1541 .ok()
1542 .map(|sym| sym.to_string()),
1543 mo_symmetries: sda_res.mo_symmetries().as_ref().map(|mo_symss| {
1544 mo_symss
1545 .iter()
1546 .map(|mo_syms| {
1547 mo_syms
1548 .iter()
1549 .map(|mo_sym_opt| {
1550 mo_sym_opt
1551 .as_ref()
1552 .map(|mo_sym| mo_sym.to_string())
1553 })
1554 .collect::<Vec<_>>()
1555 })
1556 .collect::<Vec<_>>()
1557 }),
1558 determinant_density_symmetries: sda_res
1559 .determinant_density_symmetries()
1560 .as_ref()
1561 .map(|den_syms| {
1562 den_syms
1563 .iter()
1564 .map(|(den_name, den_sym_res)| {
1565 (
1566 den_name.clone(),
1567 den_sym_res
1568 .as_ref()
1569 .ok()
1570 .map(|den_sym| den_sym.to_string()),
1571 )
1572 })
1573 .collect::<Vec<_>>()
1574 }),
1575 mo_density_symmetries: sda_res
1576 .mo_density_symmetries()
1577 .as_ref()
1578 .map(|mo_den_symss| {
1579 mo_den_symss
1580 .iter()
1581 .map(|mo_den_syms| {
1582 mo_den_syms
1583 .iter()
1584 .map(|mo_den_sym_opt| {
1585 mo_den_sym_opt.as_ref().map(|mo_den_sym| {
1586 mo_den_sym.to_string()
1587 })
1588 })
1589 .collect::<Vec<_>>()
1590 })
1591 .collect::<Vec<_>>()
1592 }),
1593 }
1594 }
1595 Some(MagneticSymmetryAnalysisKind::Representation) | None => {
1596 let mut sda_driver = SlaterDeterminantRepAnalysisDriver::<
1597 UnitaryRepresentedSymmetryGroup,
1598 C128,
1599 SpinOrbitCoupled,
1600 >::builder()
1601 .parameters(&sda_params)
1602 .angular_function_parameters(&afa_params)
1603 .determinant(&det_c)
1604 .sao(&sao_c)
1605 .sao_h(sao_h_c.as_ref())
1606 .sao_spatial_4c(sao_spatial_4c_c.as_ref())
1607 .sao_spatial_4c_h(sao_spatial_4c_h_c.as_ref())
1608 .symmetry_group(&pd_res)
1609 .build()
1610 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
1611 py.detach(|| {
1612 sda_driver
1613 .run()
1614 .map_err(|err| PyRuntimeError::new_err(err.to_string()))
1615 })?;
1616 let sda_res = sda_driver
1617 .result()
1618 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
1619 PySlaterDeterminantRepAnalysisResult {
1620 group: sda_res.group().name().clone(),
1621 determinant_symmetry: sda_res
1622 .determinant_symmetry()
1623 .as_ref()
1624 .ok()
1625 .map(|sym| sym.to_string()),
1626 mo_symmetries: sda_res.mo_symmetries().as_ref().map(|mo_symss| {
1627 mo_symss
1628 .iter()
1629 .map(|mo_syms| {
1630 mo_syms
1631 .iter()
1632 .map(|mo_sym_opt| {
1633 mo_sym_opt
1634 .as_ref()
1635 .map(|mo_sym| mo_sym.to_string())
1636 })
1637 .collect::<Vec<_>>()
1638 })
1639 .collect::<Vec<_>>()
1640 }),
1641 determinant_density_symmetries: sda_res
1642 .determinant_density_symmetries()
1643 .as_ref()
1644 .map(|den_syms| {
1645 den_syms
1646 .iter()
1647 .map(|(den_name, den_sym_res)| {
1648 (
1649 den_name.clone(),
1650 den_sym_res
1651 .as_ref()
1652 .ok()
1653 .map(|den_sym| den_sym.to_string()),
1654 )
1655 })
1656 .collect::<Vec<_>>()
1657 }),
1658 mo_density_symmetries: sda_res
1659 .mo_density_symmetries()
1660 .as_ref()
1661 .map(|mo_den_symss| {
1662 mo_den_symss
1663 .iter()
1664 .map(|mo_den_syms| {
1665 mo_den_syms
1666 .iter()
1667 .map(|mo_den_sym_opt| {
1668 mo_den_sym_opt.as_ref().map(|mo_den_sym| {
1669 mo_den_sym.to_string()
1670 })
1671 })
1672 .collect::<Vec<_>>()
1673 })
1674 .collect::<Vec<_>>()
1675 }),
1676 }
1677 }
1678 }
1679 }
1680 }
1681 }
1682 };
1683 Ok(pysda_res)
1684}