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