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