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