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/// * `use_magnetic_group` - An option indicating if the magnetic group is to be used for symmetry
530/// analysis, and if so, whether unitary representations or unitary-antiunitary corepresentations
531/// should be used.
532/// * `use_double_group` - A boolean indicating if the double group of the prevailing symmetry
533/// group is to be used for representation analysis instead.
534/// * `use_cayley_table` - A boolean indicating if the Cayley table for the group, if available,
535/// should be used to speed up the calculation of orbit overlap matrices.
536/// * `symmetry_transformation_kind` - An enumerated type indicating the type of symmetry
537/// transformations to be performed on the origin determinant to generate the orbit. If this
538/// contains spin transformation, the determinant will be augmented to generalised spin constraint
539/// automatically.
540/// * `eigenvalue_comparison_mode` - An enumerated type indicating the mode of comparison of orbit
541/// overlap eigenvalues with the specified `linear_independence_threshold`.
542/// * `sao` - The atomic-orbital overlap matrix whose elements are of type `float64` or
543/// `complex128`.
544/// * `sao_h` - The optional complex-symmetric atomic-orbital overlap matrix whose elements
545/// are of type `float64` or `complex128`. This is required if antiunitary symmetry operations are
546/// involved.
547/// * `sao_spatial_4c` - The optional atomic-orbital four-centre overlap matrix whose elements are
548/// of type `float64` or `complex128`.
549/// * `sao_spatial_4c_h` - The optional complex-symmetric atomic-orbital four-centre overlap matrix
550/// whose elements are of type `float64` or `complex128`. This is required if antiunitary symmetry
551/// operations are involved.
552/// * `analyse_mo_symmetries` - A boolean indicating if the symmetries of individual molecular
553/// orbitals are to be analysed.
554/// * `analyse_mo_symmetry_projections` - A boolean indicating if the symmetry projections of
555/// individual molecular orbitals are to be analysed.
556/// * `analyse_mo_mirror_parities` - A boolean indicating if the mirror parities of individual
557/// molecular orbitals are to be printed.
558/// * `analyse_density_symmetries` - A boolean indicating if the symmetries of densities are to be
559/// analysed.
560/// * `write_overlap_eigenvalues` - A boolean indicating if the eigenvalues of the determinant
561/// orbit overlap matrix are to be written to the output.
562/// * `write_character_table` - A boolean indicating if the character table of the prevailing
563/// symmetry group is to be printed out.
564/// * `infinite_order_to_finite` - The finite order with which infinite-order generators are to be
565/// interpreted to form a finite subgroup of the prevailing infinite group. This finite subgroup
566/// will be used for symmetry analysis.
567/// * `angular_function_integrality_threshold` - The threshold for verifying if subspace
568/// multiplicities are integral for the symmetry analysis of angular functions.
569/// * `angular_function_linear_independence_threshold` - The threshold for determining the linear
570/// independence subspace via the non-zero eigenvalues of the orbit overlap matrix for the symmetry
571/// analysis of angular functions.
572/// * `angular_function_max_angular_momentum` - The maximum angular momentum order to be used in
573/// angular function symmetry analysis.
574///
575/// # Returns
576///
577/// A Python-exposed [`PySlaterDeterminantRepAnalysisResult`] structure containing the results of the
578/// representation analysis.
579#[allow(clippy::too_many_arguments)]
580#[pyfunction]
581#[pyo3(signature = (
582 inp_sym,
583 pydet,
584 pybaos,
585 integrality_threshold,
586 linear_independence_threshold,
587 use_magnetic_group,
588 use_double_group,
589 use_cayley_table,
590 symmetry_transformation_kind,
591 eigenvalue_comparison_mode,
592 sao,
593 sao_h=None,
594 sao_spatial_4c=None,
595 sao_spatial_4c_h=None,
596 analyse_mo_symmetries=true,
597 analyse_mo_symmetry_projections=true,
598 analyse_mo_mirror_parities=false,
599 analyse_density_symmetries=false,
600 write_overlap_eigenvalues=true,
601 write_character_table=true,
602 infinite_order_to_finite=None,
603 angular_function_integrality_threshold=1e-7,
604 angular_function_linear_independence_threshold=1e-7,
605 angular_function_max_angular_momentum=2
606))]
607pub fn rep_analyse_slater_determinant(
608 py: Python<'_>,
609 inp_sym: PathBuf,
610 pydet: PySlaterDeterminant,
611 pybaos: Vec<PyBasisAngularOrder>,
612 integrality_threshold: f64,
613 linear_independence_threshold: f64,
614 use_magnetic_group: Option<MagneticSymmetryAnalysisKind>,
615 use_double_group: bool,
616 use_cayley_table: bool,
617 symmetry_transformation_kind: SymmetryTransformationKind,
618 eigenvalue_comparison_mode: EigenvalueComparisonMode,
619 sao: PyArray2RC,
620 sao_h: Option<PyArray2RC>,
621 sao_spatial_4c: Option<PyArray4RC>,
622 sao_spatial_4c_h: Option<PyArray4RC>,
623 analyse_mo_symmetries: bool,
624 analyse_mo_symmetry_projections: bool,
625 analyse_mo_mirror_parities: bool,
626 analyse_density_symmetries: bool,
627 write_overlap_eigenvalues: bool,
628 write_character_table: bool,
629 infinite_order_to_finite: Option<u32>,
630 angular_function_integrality_threshold: f64,
631 angular_function_linear_independence_threshold: f64,
632 angular_function_max_angular_momentum: u32,
633) -> PyResult<PySlaterDeterminantRepAnalysisResult> {
634 let pd_res: SymmetryGroupDetectionResult =
635 read_qsym2_binary(inp_sym.clone(), QSym2FileType::Sym)
636 .map_err(|err| PyIOError::new_err(err.to_string()))?;
637
638 let mut file_name = inp_sym.to_path_buf();
639 file_name.set_extension(QSym2FileType::Sym.ext());
640 qsym2_output!(
641 "Symmetry-group detection results read in from {}.",
642 file_name.display(),
643 );
644 qsym2_output!("");
645
646 let mol = &pd_res.pre_symmetry.recentred_molecule;
647
648 let baos = pybaos
649 .iter()
650 .map(|bao| {
651 bao.to_qsym2(mol)
652 .map_err(|err| PyRuntimeError::new_err(err.to_string()))
653 })
654 .collect::<Result<Vec<_>, _>>()?;
655 let baos_ref = baos.iter().collect::<Vec<_>>();
656 let augment_to_generalised = match symmetry_transformation_kind {
657 SymmetryTransformationKind::SpatialWithSpinTimeReversal
658 | SymmetryTransformationKind::Spin
659 | SymmetryTransformationKind::SpinSpatial => true,
660 SymmetryTransformationKind::Spatial => false,
661 };
662 let afa_params = AngularFunctionRepAnalysisParams::builder()
663 .integrality_threshold(angular_function_integrality_threshold)
664 .linear_independence_threshold(angular_function_linear_independence_threshold)
665 .max_angular_momentum(angular_function_max_angular_momentum)
666 .build()
667 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
668 let sda_params = SlaterDeterminantRepAnalysisParams::<f64>::builder()
669 .integrality_threshold(integrality_threshold)
670 .linear_independence_threshold(linear_independence_threshold)
671 .use_magnetic_group(use_magnetic_group.clone())
672 .use_double_group(use_double_group)
673 .use_cayley_table(use_cayley_table)
674 .symmetry_transformation_kind(symmetry_transformation_kind)
675 .eigenvalue_comparison_mode(eigenvalue_comparison_mode)
676 .analyse_mo_symmetries(analyse_mo_symmetries)
677 .analyse_mo_symmetry_projections(analyse_mo_symmetry_projections)
678 .analyse_mo_mirror_parities(analyse_mo_mirror_parities)
679 .analyse_density_symmetries(analyse_density_symmetries)
680 .write_overlap_eigenvalues(write_overlap_eigenvalues)
681 .write_character_table(if write_character_table {
682 Some(CharacterTableDisplay::Symbolic)
683 } else {
684 None
685 })
686 .infinite_order_to_finite(infinite_order_to_finite)
687 .build()
688 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
689 let pysda_res: PySlaterDeterminantRepAnalysisResult = match (&pydet, &sao) {
690 (PySlaterDeterminant::Real(pydet_r), PyArray2RC::Real(pysao_r)) => {
691 if matches!(
692 pydet_r.structure_constraint,
693 PyStructureConstraint::SpinOrbitCoupled(_)
694 ) {
695 return Err(PyRuntimeError::new_err("Real determinants are not compatible with spin--orbit-coupled structure constraint.".to_string()));
696 }
697
698 let sao = pysao_r.to_owned_array();
699 let sao_spatial_4c = sao_spatial_4c.and_then(|pysao4c| match pysao4c {
700 // sao_spatial_4c must have the same reality as sao_spatial.
701 PyArray4RC::Real(pysao4c_r) => Some(pysao4c_r.to_owned_array()),
702 PyArray4RC::Complex(_) => None,
703 });
704
705 let det_r = if augment_to_generalised {
706 pydet_r
707 .to_qsym2::<SpinConstraint>(&baos_ref, mol)
708 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?
709 .to_generalised()
710 } else {
711 pydet_r
712 .to_qsym2::<SpinConstraint>(&baos_ref, mol)
713 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?
714 };
715 match &use_magnetic_group {
716 Some(MagneticSymmetryAnalysisKind::Corepresentation) => {
717 let mut sda_driver = SlaterDeterminantRepAnalysisDriver::<
718 MagneticRepresentedSymmetryGroup,
719 f64,
720 SpinConstraint,
721 >::builder()
722 .parameters(&sda_params)
723 .angular_function_parameters(&afa_params)
724 .determinant(&det_r)
725 .sao(&sao)
726 .sao_h(None) // Real SAO.
727 .sao_spatial_4c(sao_spatial_4c.as_ref())
728 .sao_spatial_4c_h(None) // Real SAO.
729 .symmetry_group(&pd_res)
730 .build()
731 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
732 py.detach(|| {
733 sda_driver
734 .run()
735 .map_err(|err| PyRuntimeError::new_err(err.to_string()))
736 })?;
737 let sda_res = sda_driver
738 .result()
739 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
740 PySlaterDeterminantRepAnalysisResult {
741 group: sda_res.group().name().clone(),
742 determinant_symmetry: sda_res
743 .determinant_symmetry()
744 .as_ref()
745 .ok()
746 .map(|sym| sym.to_string()),
747 mo_symmetries: sda_res.mo_symmetries().as_ref().map(|mo_symss| {
748 mo_symss
749 .iter()
750 .map(|mo_syms| {
751 mo_syms
752 .iter()
753 .map(|mo_sym_opt| {
754 mo_sym_opt.as_ref().map(|mo_sym| mo_sym.to_string())
755 })
756 .collect::<Vec<_>>()
757 })
758 .collect::<Vec<_>>()
759 }),
760 determinant_density_symmetries: sda_res
761 .determinant_density_symmetries()
762 .as_ref()
763 .map(|den_syms| {
764 den_syms
765 .iter()
766 .map(|(den_name, den_sym_res)| {
767 (
768 den_name.clone(),
769 den_sym_res
770 .as_ref()
771 .ok()
772 .map(|den_sym| den_sym.to_string()),
773 )
774 })
775 .collect::<Vec<_>>()
776 }),
777 mo_density_symmetries: sda_res.mo_density_symmetries().as_ref().map(
778 |mo_den_symss| {
779 mo_den_symss
780 .iter()
781 .map(|mo_den_syms| {
782 mo_den_syms
783 .iter()
784 .map(|mo_den_sym_opt| {
785 mo_den_sym_opt
786 .as_ref()
787 .map(|mo_den_sym| mo_den_sym.to_string())
788 })
789 .collect::<Vec<_>>()
790 })
791 .collect::<Vec<_>>()
792 },
793 ),
794 }
795 }
796 Some(MagneticSymmetryAnalysisKind::Representation) | None => {
797 let mut sda_driver = SlaterDeterminantRepAnalysisDriver::<
798 UnitaryRepresentedSymmetryGroup,
799 f64,
800 SpinConstraint,
801 >::builder()
802 .parameters(&sda_params)
803 .angular_function_parameters(&afa_params)
804 .determinant(&det_r)
805 .sao(&sao)
806 .sao_h(None) // Real SAO.
807 .sao_spatial_4c(sao_spatial_4c.as_ref())
808 .sao_spatial_4c_h(None) // Real SAO.
809 .symmetry_group(&pd_res)
810 .build()
811 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
812 py.detach(|| {
813 sda_driver
814 .run()
815 .map_err(|err| PyRuntimeError::new_err(err.to_string()))
816 })?;
817 let sda_res = sda_driver
818 .result()
819 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
820 PySlaterDeterminantRepAnalysisResult {
821 group: sda_res.group().name().clone(),
822 determinant_symmetry: sda_res
823 .determinant_symmetry()
824 .as_ref()
825 .ok()
826 .map(|sym| sym.to_string()),
827 mo_symmetries: sda_res.mo_symmetries().as_ref().map(|mo_symss| {
828 mo_symss
829 .iter()
830 .map(|mo_syms| {
831 mo_syms
832 .iter()
833 .map(|mo_sym_opt| {
834 mo_sym_opt.as_ref().map(|mo_sym| mo_sym.to_string())
835 })
836 .collect::<Vec<_>>()
837 })
838 .collect::<Vec<_>>()
839 }),
840 determinant_density_symmetries: sda_res
841 .determinant_density_symmetries()
842 .as_ref()
843 .map(|den_syms| {
844 den_syms
845 .iter()
846 .map(|(den_name, den_sym_res)| {
847 (
848 den_name.clone(),
849 den_sym_res
850 .as_ref()
851 .ok()
852 .map(|den_sym| den_sym.to_string()),
853 )
854 })
855 .collect::<Vec<_>>()
856 }),
857 mo_density_symmetries: sda_res.mo_density_symmetries().as_ref().map(
858 |mo_den_symss| {
859 mo_den_symss
860 .iter()
861 .map(|mo_den_syms| {
862 mo_den_syms
863 .iter()
864 .map(|mo_den_sym_opt| {
865 mo_den_sym_opt
866 .as_ref()
867 .map(|mo_den_sym| mo_den_sym.to_string())
868 })
869 .collect::<Vec<_>>()
870 })
871 .collect::<Vec<_>>()
872 },
873 ),
874 }
875 }
876 }
877 }
878 (PySlaterDeterminant::Real(pydet_r), PyArray2RC::Complex(pysao_c)) => {
879 match pydet_r.structure_constraint {
880 PyStructureConstraint::SpinConstraint(_) => {
881 let det_r = if augment_to_generalised {
882 pydet_r
883 .to_qsym2(&baos_ref, mol)
884 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?
885 .to_generalised()
886 } else {
887 pydet_r
888 .to_qsym2(&baos_ref, mol)
889 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?
890 };
891 let det_c: SlaterDeterminant<C128, SpinConstraint> = det_r.into();
892 let sao_c = pysao_c.to_owned_array();
893 let sao_h_c = sao_h.and_then(|pysao_h| match pysao_h {
894 // sao_spatial_h must have the same reality as sao_spatial.
895 PyArray2RC::Real(_) => None,
896 PyArray2RC::Complex(pysao_h_c) => Some(pysao_h_c.to_owned_array()),
897 });
898 let sao_spatial_4c_c = sao_spatial_4c.and_then(|pysao4c| match pysao4c {
899 // sao_spatial_4c must have the same reality as sao_spatial.
900 PyArray4RC::Real(_) => None,
901 PyArray4RC::Complex(pysao4c_c) => Some(pysao4c_c.to_owned_array()),
902 });
903 let sao_spatial_4c_h_c =
904 sao_spatial_4c_h.and_then(|pysao4c_h| match pysao4c_h {
905 // sao_spatial_4c_h must have the same reality as sao_spatial.
906 PyArray4RC::Real(_) => None,
907 PyArray4RC::Complex(pysao4c_h_c) => Some(pysao4c_h_c.to_owned_array()),
908 });
909 match &use_magnetic_group {
910 Some(MagneticSymmetryAnalysisKind::Corepresentation) => {
911 let mut sda_driver = SlaterDeterminantRepAnalysisDriver::<
912 MagneticRepresentedSymmetryGroup,
913 C128,
914 SpinConstraint,
915 >::builder()
916 .parameters(&sda_params)
917 .angular_function_parameters(&afa_params)
918 .determinant(&det_c)
919 .sao(&sao_c)
920 .sao_h(sao_h_c.as_ref())
921 .sao_spatial_4c(sao_spatial_4c_c.as_ref())
922 .sao_spatial_4c_h(sao_spatial_4c_h_c.as_ref())
923 .symmetry_group(&pd_res)
924 .build()
925 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
926 py.detach(|| {
927 sda_driver
928 .run()
929 .map_err(|err| PyRuntimeError::new_err(err.to_string()))
930 })?;
931 let sda_res = sda_driver
932 .result()
933 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
934 PySlaterDeterminantRepAnalysisResult {
935 group: sda_res.group().name().clone(),
936 determinant_symmetry: sda_res
937 .determinant_symmetry()
938 .as_ref()
939 .ok()
940 .map(|sym| sym.to_string()),
941 mo_symmetries: sda_res.mo_symmetries().as_ref().map(|mo_symss| {
942 mo_symss
943 .iter()
944 .map(|mo_syms| {
945 mo_syms
946 .iter()
947 .map(|mo_sym_opt| {
948 mo_sym_opt
949 .as_ref()
950 .map(|mo_sym| mo_sym.to_string())
951 })
952 .collect::<Vec<_>>()
953 })
954 .collect::<Vec<_>>()
955 }),
956 determinant_density_symmetries: sda_res
957 .determinant_density_symmetries()
958 .as_ref()
959 .map(|den_syms| {
960 den_syms
961 .iter()
962 .map(|(den_name, den_sym_res)| {
963 (
964 den_name.clone(),
965 den_sym_res
966 .as_ref()
967 .ok()
968 .map(|den_sym| den_sym.to_string()),
969 )
970 })
971 .collect::<Vec<_>>()
972 }),
973 mo_density_symmetries: sda_res
974 .mo_density_symmetries()
975 .as_ref()
976 .map(|mo_den_symss| {
977 mo_den_symss
978 .iter()
979 .map(|mo_den_syms| {
980 mo_den_syms
981 .iter()
982 .map(|mo_den_sym_opt| {
983 mo_den_sym_opt.as_ref().map(|mo_den_sym| {
984 mo_den_sym.to_string()
985 })
986 })
987 .collect::<Vec<_>>()
988 })
989 .collect::<Vec<_>>()
990 }),
991 }
992 }
993 Some(MagneticSymmetryAnalysisKind::Representation) | None => {
994 let mut sda_driver = SlaterDeterminantRepAnalysisDriver::<
995 UnitaryRepresentedSymmetryGroup,
996 C128,
997 SpinConstraint,
998 >::builder()
999 .parameters(&sda_params)
1000 .angular_function_parameters(&afa_params)
1001 .determinant(&det_c)
1002 .sao(&sao_c)
1003 .sao_h(sao_h_c.as_ref())
1004 .sao_spatial_4c(sao_spatial_4c_c.as_ref())
1005 .sao_spatial_4c_h(sao_spatial_4c_h_c.as_ref())
1006 .symmetry_group(&pd_res)
1007 .build()
1008 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
1009 py.detach(|| {
1010 sda_driver
1011 .run()
1012 .map_err(|err| PyRuntimeError::new_err(err.to_string()))
1013 })?;
1014 let sda_res = sda_driver
1015 .result()
1016 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
1017 PySlaterDeterminantRepAnalysisResult {
1018 group: sda_res.group().name().clone(),
1019 determinant_symmetry: sda_res
1020 .determinant_symmetry()
1021 .as_ref()
1022 .ok()
1023 .map(|sym| sym.to_string()),
1024 mo_symmetries: sda_res.mo_symmetries().as_ref().map(|mo_symss| {
1025 mo_symss
1026 .iter()
1027 .map(|mo_syms| {
1028 mo_syms
1029 .iter()
1030 .map(|mo_sym_opt| {
1031 mo_sym_opt
1032 .as_ref()
1033 .map(|mo_sym| mo_sym.to_string())
1034 })
1035 .collect::<Vec<_>>()
1036 })
1037 .collect::<Vec<_>>()
1038 }),
1039 determinant_density_symmetries: sda_res
1040 .determinant_density_symmetries()
1041 .as_ref()
1042 .map(|den_syms| {
1043 den_syms
1044 .iter()
1045 .map(|(den_name, den_sym_res)| {
1046 (
1047 den_name.clone(),
1048 den_sym_res
1049 .as_ref()
1050 .ok()
1051 .map(|den_sym| den_sym.to_string()),
1052 )
1053 })
1054 .collect::<Vec<_>>()
1055 }),
1056 mo_density_symmetries: sda_res
1057 .mo_density_symmetries()
1058 .as_ref()
1059 .map(|mo_den_symss| {
1060 mo_den_symss
1061 .iter()
1062 .map(|mo_den_syms| {
1063 mo_den_syms
1064 .iter()
1065 .map(|mo_den_sym_opt| {
1066 mo_den_sym_opt.as_ref().map(|mo_den_sym| {
1067 mo_den_sym.to_string()
1068 })
1069 })
1070 .collect::<Vec<_>>()
1071 })
1072 .collect::<Vec<_>>()
1073 }),
1074 }
1075 }
1076 }
1077 }
1078 PyStructureConstraint::SpinOrbitCoupled(_) => {
1079 let det_r = pydet_r
1080 .to_qsym2::<SpinOrbitCoupled>(&baos_ref, mol)
1081 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
1082 let det_c: SlaterDeterminant<C128, SpinOrbitCoupled> = det_r.into();
1083 let sao_c = pysao_c.to_owned_array();
1084 let sao_h_c = sao_h.and_then(|pysao_h| match pysao_h {
1085 // sao_h must have the same reality as sao.
1086 PyArray2RC::Real(_) => None,
1087 PyArray2RC::Complex(pysao_h_c) => Some(pysao_h_c.to_owned_array()),
1088 });
1089 let sao_spatial_4c_c = sao_spatial_4c.and_then(|pysao4c| match pysao4c {
1090 // sao_spatial_4c must have the same reality as sao.
1091 PyArray4RC::Real(_) => None,
1092 PyArray4RC::Complex(pysao4c_c) => Some(pysao4c_c.to_owned_array()),
1093 });
1094 let sao_spatial_4c_h_c =
1095 sao_spatial_4c_h.and_then(|pysao4c_h| match pysao4c_h {
1096 // sao_spatial_4c_h must have the same reality as sao_spatial.
1097 PyArray4RC::Real(_) => None,
1098 PyArray4RC::Complex(pysao4c_h_c) => Some(pysao4c_h_c.to_owned_array()),
1099 });
1100 match &use_magnetic_group {
1101 Some(MagneticSymmetryAnalysisKind::Corepresentation) => {
1102 let mut sda_driver = SlaterDeterminantRepAnalysisDriver::<
1103 MagneticRepresentedSymmetryGroup,
1104 C128,
1105 SpinOrbitCoupled,
1106 >::builder()
1107 .parameters(&sda_params)
1108 .angular_function_parameters(&afa_params)
1109 .determinant(&det_c)
1110 .sao(&sao_c)
1111 .sao_h(sao_h_c.as_ref())
1112 .sao_spatial_4c(sao_spatial_4c_c.as_ref())
1113 .sao_spatial_4c_h(sao_spatial_4c_h_c.as_ref())
1114 .symmetry_group(&pd_res)
1115 .build()
1116 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
1117 py.detach(|| {
1118 sda_driver
1119 .run()
1120 .map_err(|err| PyRuntimeError::new_err(err.to_string()))
1121 })?;
1122 let sda_res = sda_driver
1123 .result()
1124 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
1125 PySlaterDeterminantRepAnalysisResult {
1126 group: sda_res.group().name().clone(),
1127 determinant_symmetry: sda_res
1128 .determinant_symmetry()
1129 .as_ref()
1130 .ok()
1131 .map(|sym| sym.to_string()),
1132 mo_symmetries: sda_res.mo_symmetries().as_ref().map(|mo_symss| {
1133 mo_symss
1134 .iter()
1135 .map(|mo_syms| {
1136 mo_syms
1137 .iter()
1138 .map(|mo_sym_opt| {
1139 mo_sym_opt
1140 .as_ref()
1141 .map(|mo_sym| mo_sym.to_string())
1142 })
1143 .collect::<Vec<_>>()
1144 })
1145 .collect::<Vec<_>>()
1146 }),
1147 determinant_density_symmetries: sda_res
1148 .determinant_density_symmetries()
1149 .as_ref()
1150 .map(|den_syms| {
1151 den_syms
1152 .iter()
1153 .map(|(den_name, den_sym_res)| {
1154 (
1155 den_name.clone(),
1156 den_sym_res
1157 .as_ref()
1158 .ok()
1159 .map(|den_sym| den_sym.to_string()),
1160 )
1161 })
1162 .collect::<Vec<_>>()
1163 }),
1164 mo_density_symmetries: sda_res
1165 .mo_density_symmetries()
1166 .as_ref()
1167 .map(|mo_den_symss| {
1168 mo_den_symss
1169 .iter()
1170 .map(|mo_den_syms| {
1171 mo_den_syms
1172 .iter()
1173 .map(|mo_den_sym_opt| {
1174 mo_den_sym_opt.as_ref().map(|mo_den_sym| {
1175 mo_den_sym.to_string()
1176 })
1177 })
1178 .collect::<Vec<_>>()
1179 })
1180 .collect::<Vec<_>>()
1181 }),
1182 }
1183 }
1184 Some(MagneticSymmetryAnalysisKind::Representation) | None => {
1185 let mut sda_driver = SlaterDeterminantRepAnalysisDriver::<
1186 UnitaryRepresentedSymmetryGroup,
1187 C128,
1188 SpinOrbitCoupled,
1189 >::builder()
1190 .parameters(&sda_params)
1191 .angular_function_parameters(&afa_params)
1192 .determinant(&det_c)
1193 .sao(&sao_c)
1194 .sao_h(sao_h_c.as_ref())
1195 .sao_spatial_4c(sao_spatial_4c_c.as_ref())
1196 .sao_spatial_4c_h(sao_spatial_4c_h_c.as_ref())
1197 .symmetry_group(&pd_res)
1198 .build()
1199 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
1200 py.detach(|| {
1201 sda_driver
1202 .run()
1203 .map_err(|err| PyRuntimeError::new_err(err.to_string()))
1204 })?;
1205 let sda_res = sda_driver
1206 .result()
1207 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
1208 PySlaterDeterminantRepAnalysisResult {
1209 group: sda_res.group().name().clone(),
1210 determinant_symmetry: sda_res
1211 .determinant_symmetry()
1212 .as_ref()
1213 .ok()
1214 .map(|sym| sym.to_string()),
1215 mo_symmetries: sda_res.mo_symmetries().as_ref().map(|mo_symss| {
1216 mo_symss
1217 .iter()
1218 .map(|mo_syms| {
1219 mo_syms
1220 .iter()
1221 .map(|mo_sym_opt| {
1222 mo_sym_opt
1223 .as_ref()
1224 .map(|mo_sym| mo_sym.to_string())
1225 })
1226 .collect::<Vec<_>>()
1227 })
1228 .collect::<Vec<_>>()
1229 }),
1230 determinant_density_symmetries: sda_res
1231 .determinant_density_symmetries()
1232 .as_ref()
1233 .map(|den_syms| {
1234 den_syms
1235 .iter()
1236 .map(|(den_name, den_sym_res)| {
1237 (
1238 den_name.clone(),
1239 den_sym_res
1240 .as_ref()
1241 .ok()
1242 .map(|den_sym| den_sym.to_string()),
1243 )
1244 })
1245 .collect::<Vec<_>>()
1246 }),
1247 mo_density_symmetries: sda_res
1248 .mo_density_symmetries()
1249 .as_ref()
1250 .map(|mo_den_symss| {
1251 mo_den_symss
1252 .iter()
1253 .map(|mo_den_syms| {
1254 mo_den_syms
1255 .iter()
1256 .map(|mo_den_sym_opt| {
1257 mo_den_sym_opt.as_ref().map(|mo_den_sym| {
1258 mo_den_sym.to_string()
1259 })
1260 })
1261 .collect::<Vec<_>>()
1262 })
1263 .collect::<Vec<_>>()
1264 }),
1265 }
1266 }
1267 }
1268 }
1269 }
1270 }
1271 (PySlaterDeterminant::Complex(pydet_c), _) => {
1272 match pydet_c.structure_constraint {
1273 PyStructureConstraint::SpinConstraint(_) => {
1274 let det_c = if augment_to_generalised {
1275 pydet_c
1276 .to_qsym2::<SpinConstraint>(&baos_ref, mol)
1277 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?
1278 .to_generalised()
1279 } else {
1280 pydet_c
1281 .to_qsym2::<SpinConstraint>(&baos_ref, mol)
1282 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?
1283 };
1284 let sao_c = match sao {
1285 PyArray2RC::Real(pysao_r) => pysao_r.to_owned_array().mapv(Complex::from),
1286 PyArray2RC::Complex(pysao_c) => pysao_c.to_owned_array(),
1287 };
1288 let sao_h_c = sao_h.map(|pysao_h| match pysao_h {
1289 // sao_h must have the same reality as sao.
1290 PyArray2RC::Real(pysao_h_r) => {
1291 pysao_h_r.to_owned_array().mapv(Complex::from)
1292 }
1293 PyArray2RC::Complex(pysao_h_c) => pysao_h_c.to_owned_array(),
1294 });
1295 let sao_spatial_4c_c = sao_spatial_4c.map(|pysao4c| match pysao4c {
1296 // sao_spatial_4c must have the same reality as sao.
1297 PyArray4RC::Real(pysao4c_r) => {
1298 pysao4c_r.to_owned_array().mapv(Complex::from)
1299 }
1300 PyArray4RC::Complex(pysao4c_c) => pysao4c_c.to_owned_array(),
1301 });
1302 let sao_spatial_4c_h_c = sao_spatial_4c_h.map(|pysao4c_h| match pysao4c_h {
1303 // sao_spatial_4c_h must have the same reality as sao.
1304 PyArray4RC::Real(pysao4c_h_r) => {
1305 pysao4c_h_r.to_owned_array().mapv(Complex::from)
1306 }
1307 PyArray4RC::Complex(pysao4c_h_c) => pysao4c_h_c.to_owned_array(),
1308 });
1309 match &use_magnetic_group {
1310 Some(MagneticSymmetryAnalysisKind::Corepresentation) => {
1311 let mut sda_driver = SlaterDeterminantRepAnalysisDriver::<
1312 MagneticRepresentedSymmetryGroup,
1313 C128,
1314 SpinConstraint,
1315 >::builder()
1316 .parameters(&sda_params)
1317 .angular_function_parameters(&afa_params)
1318 .determinant(&det_c)
1319 .sao(&sao_c)
1320 .sao_h(sao_h_c.as_ref())
1321 .sao_spatial_4c(sao_spatial_4c_c.as_ref())
1322 .sao_spatial_4c_h(sao_spatial_4c_h_c.as_ref())
1323 .symmetry_group(&pd_res)
1324 .build()
1325 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
1326 py.detach(|| {
1327 sda_driver
1328 .run()
1329 .map_err(|err| PyRuntimeError::new_err(err.to_string()))
1330 })?;
1331 let sda_res = sda_driver
1332 .result()
1333 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
1334 PySlaterDeterminantRepAnalysisResult {
1335 group: sda_res.group().name().clone(),
1336 determinant_symmetry: sda_res
1337 .determinant_symmetry()
1338 .as_ref()
1339 .ok()
1340 .map(|sym| sym.to_string()),
1341 mo_symmetries: sda_res.mo_symmetries().as_ref().map(|mo_symss| {
1342 mo_symss
1343 .iter()
1344 .map(|mo_syms| {
1345 mo_syms
1346 .iter()
1347 .map(|mo_sym_opt| {
1348 mo_sym_opt
1349 .as_ref()
1350 .map(|mo_sym| mo_sym.to_string())
1351 })
1352 .collect::<Vec<_>>()
1353 })
1354 .collect::<Vec<_>>()
1355 }),
1356 determinant_density_symmetries: sda_res
1357 .determinant_density_symmetries()
1358 .as_ref()
1359 .map(|den_syms| {
1360 den_syms
1361 .iter()
1362 .map(|(den_name, den_sym_res)| {
1363 (
1364 den_name.clone(),
1365 den_sym_res
1366 .as_ref()
1367 .ok()
1368 .map(|den_sym| den_sym.to_string()),
1369 )
1370 })
1371 .collect::<Vec<_>>()
1372 }),
1373 mo_density_symmetries: sda_res
1374 .mo_density_symmetries()
1375 .as_ref()
1376 .map(|mo_den_symss| {
1377 mo_den_symss
1378 .iter()
1379 .map(|mo_den_syms| {
1380 mo_den_syms
1381 .iter()
1382 .map(|mo_den_sym_opt| {
1383 mo_den_sym_opt.as_ref().map(|mo_den_sym| {
1384 mo_den_sym.to_string()
1385 })
1386 })
1387 .collect::<Vec<_>>()
1388 })
1389 .collect::<Vec<_>>()
1390 }),
1391 }
1392 }
1393 Some(MagneticSymmetryAnalysisKind::Representation) | None => {
1394 let mut sda_driver = SlaterDeterminantRepAnalysisDriver::<
1395 UnitaryRepresentedSymmetryGroup,
1396 C128,
1397 SpinConstraint,
1398 >::builder()
1399 .parameters(&sda_params)
1400 .angular_function_parameters(&afa_params)
1401 .determinant(&det_c)
1402 .sao(&sao_c)
1403 .sao_h(sao_h_c.as_ref())
1404 .sao_spatial_4c(sao_spatial_4c_c.as_ref())
1405 .sao_spatial_4c_h(sao_spatial_4c_h_c.as_ref())
1406 .symmetry_group(&pd_res)
1407 .build()
1408 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
1409 py.detach(|| {
1410 sda_driver
1411 .run()
1412 .map_err(|err| PyRuntimeError::new_err(err.to_string()))
1413 })?;
1414 let sda_res = sda_driver
1415 .result()
1416 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
1417 PySlaterDeterminantRepAnalysisResult {
1418 group: sda_res.group().name().clone(),
1419 determinant_symmetry: sda_res
1420 .determinant_symmetry()
1421 .as_ref()
1422 .ok()
1423 .map(|sym| sym.to_string()),
1424 mo_symmetries: sda_res.mo_symmetries().as_ref().map(|mo_symss| {
1425 mo_symss
1426 .iter()
1427 .map(|mo_syms| {
1428 mo_syms
1429 .iter()
1430 .map(|mo_sym_opt| {
1431 mo_sym_opt
1432 .as_ref()
1433 .map(|mo_sym| mo_sym.to_string())
1434 })
1435 .collect::<Vec<_>>()
1436 })
1437 .collect::<Vec<_>>()
1438 }),
1439 determinant_density_symmetries: sda_res
1440 .determinant_density_symmetries()
1441 .as_ref()
1442 .map(|den_syms| {
1443 den_syms
1444 .iter()
1445 .map(|(den_name, den_sym_res)| {
1446 (
1447 den_name.clone(),
1448 den_sym_res
1449 .as_ref()
1450 .ok()
1451 .map(|den_sym| den_sym.to_string()),
1452 )
1453 })
1454 .collect::<Vec<_>>()
1455 }),
1456 mo_density_symmetries: sda_res
1457 .mo_density_symmetries()
1458 .as_ref()
1459 .map(|mo_den_symss| {
1460 mo_den_symss
1461 .iter()
1462 .map(|mo_den_syms| {
1463 mo_den_syms
1464 .iter()
1465 .map(|mo_den_sym_opt| {
1466 mo_den_sym_opt.as_ref().map(|mo_den_sym| {
1467 mo_den_sym.to_string()
1468 })
1469 })
1470 .collect::<Vec<_>>()
1471 })
1472 .collect::<Vec<_>>()
1473 }),
1474 }
1475 }
1476 }
1477 }
1478 PyStructureConstraint::SpinOrbitCoupled(_) => {
1479 let det_c = pydet_c
1480 .to_qsym2::<SpinOrbitCoupled>(&baos_ref, mol)
1481 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
1482 let sao_c = match sao {
1483 PyArray2RC::Real(pysao_r) => pysao_r.to_owned_array().mapv(Complex::from),
1484 PyArray2RC::Complex(pysao_c) => pysao_c.to_owned_array(),
1485 };
1486 let sao_h_c = sao_h.map(|pysao_h| match pysao_h {
1487 // sao_spatial_h must have the same reality as sao.
1488 PyArray2RC::Real(pysao_h_r) => {
1489 pysao_h_r.to_owned_array().mapv(Complex::from)
1490 }
1491 PyArray2RC::Complex(pysao_h_c) => pysao_h_c.to_owned_array(),
1492 });
1493 let sao_spatial_4c_c = sao_spatial_4c.map(|pysao4c| match pysao4c {
1494 // sao_spatial_4c must have the same reality as sao.
1495 PyArray4RC::Real(pysao4c_r) => {
1496 pysao4c_r.to_owned_array().mapv(Complex::from)
1497 }
1498 PyArray4RC::Complex(pysao4c_c) => pysao4c_c.to_owned_array(),
1499 });
1500 let sao_spatial_4c_h_c = sao_spatial_4c_h.map(|pysao4c_h| match pysao4c_h {
1501 // sao_spatial_4c_h must have the same reality as sao.
1502 PyArray4RC::Real(pysao4c_h_r) => {
1503 pysao4c_h_r.to_owned_array().mapv(Complex::from)
1504 }
1505 PyArray4RC::Complex(pysao4c_h_c) => pysao4c_h_c.to_owned_array(),
1506 });
1507 match &use_magnetic_group {
1508 Some(MagneticSymmetryAnalysisKind::Corepresentation) => {
1509 let mut sda_driver = SlaterDeterminantRepAnalysisDriver::<
1510 MagneticRepresentedSymmetryGroup,
1511 C128,
1512 SpinOrbitCoupled,
1513 >::builder()
1514 .parameters(&sda_params)
1515 .angular_function_parameters(&afa_params)
1516 .determinant(&det_c)
1517 .sao(&sao_c)
1518 .sao_h(sao_h_c.as_ref())
1519 .sao_spatial_4c(sao_spatial_4c_c.as_ref())
1520 .sao_spatial_4c_h(sao_spatial_4c_h_c.as_ref())
1521 .symmetry_group(&pd_res)
1522 .build()
1523 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
1524 py.detach(|| {
1525 sda_driver
1526 .run()
1527 .map_err(|err| PyRuntimeError::new_err(err.to_string()))
1528 })?;
1529 let sda_res = sda_driver
1530 .result()
1531 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
1532 PySlaterDeterminantRepAnalysisResult {
1533 group: sda_res.group().name().clone(),
1534 determinant_symmetry: sda_res
1535 .determinant_symmetry()
1536 .as_ref()
1537 .ok()
1538 .map(|sym| sym.to_string()),
1539 mo_symmetries: sda_res.mo_symmetries().as_ref().map(|mo_symss| {
1540 mo_symss
1541 .iter()
1542 .map(|mo_syms| {
1543 mo_syms
1544 .iter()
1545 .map(|mo_sym_opt| {
1546 mo_sym_opt
1547 .as_ref()
1548 .map(|mo_sym| mo_sym.to_string())
1549 })
1550 .collect::<Vec<_>>()
1551 })
1552 .collect::<Vec<_>>()
1553 }),
1554 determinant_density_symmetries: sda_res
1555 .determinant_density_symmetries()
1556 .as_ref()
1557 .map(|den_syms| {
1558 den_syms
1559 .iter()
1560 .map(|(den_name, den_sym_res)| {
1561 (
1562 den_name.clone(),
1563 den_sym_res
1564 .as_ref()
1565 .ok()
1566 .map(|den_sym| den_sym.to_string()),
1567 )
1568 })
1569 .collect::<Vec<_>>()
1570 }),
1571 mo_density_symmetries: sda_res
1572 .mo_density_symmetries()
1573 .as_ref()
1574 .map(|mo_den_symss| {
1575 mo_den_symss
1576 .iter()
1577 .map(|mo_den_syms| {
1578 mo_den_syms
1579 .iter()
1580 .map(|mo_den_sym_opt| {
1581 mo_den_sym_opt.as_ref().map(|mo_den_sym| {
1582 mo_den_sym.to_string()
1583 })
1584 })
1585 .collect::<Vec<_>>()
1586 })
1587 .collect::<Vec<_>>()
1588 }),
1589 }
1590 }
1591 Some(MagneticSymmetryAnalysisKind::Representation) | None => {
1592 let mut sda_driver = SlaterDeterminantRepAnalysisDriver::<
1593 UnitaryRepresentedSymmetryGroup,
1594 C128,
1595 SpinOrbitCoupled,
1596 >::builder()
1597 .parameters(&sda_params)
1598 .angular_function_parameters(&afa_params)
1599 .determinant(&det_c)
1600 .sao(&sao_c)
1601 .sao_h(sao_h_c.as_ref())
1602 .sao_spatial_4c(sao_spatial_4c_c.as_ref())
1603 .sao_spatial_4c_h(sao_spatial_4c_h_c.as_ref())
1604 .symmetry_group(&pd_res)
1605 .build()
1606 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
1607 py.detach(|| {
1608 sda_driver
1609 .run()
1610 .map_err(|err| PyRuntimeError::new_err(err.to_string()))
1611 })?;
1612 let sda_res = sda_driver
1613 .result()
1614 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
1615 PySlaterDeterminantRepAnalysisResult {
1616 group: sda_res.group().name().clone(),
1617 determinant_symmetry: sda_res
1618 .determinant_symmetry()
1619 .as_ref()
1620 .ok()
1621 .map(|sym| sym.to_string()),
1622 mo_symmetries: sda_res.mo_symmetries().as_ref().map(|mo_symss| {
1623 mo_symss
1624 .iter()
1625 .map(|mo_syms| {
1626 mo_syms
1627 .iter()
1628 .map(|mo_sym_opt| {
1629 mo_sym_opt
1630 .as_ref()
1631 .map(|mo_sym| mo_sym.to_string())
1632 })
1633 .collect::<Vec<_>>()
1634 })
1635 .collect::<Vec<_>>()
1636 }),
1637 determinant_density_symmetries: sda_res
1638 .determinant_density_symmetries()
1639 .as_ref()
1640 .map(|den_syms| {
1641 den_syms
1642 .iter()
1643 .map(|(den_name, den_sym_res)| {
1644 (
1645 den_name.clone(),
1646 den_sym_res
1647 .as_ref()
1648 .ok()
1649 .map(|den_sym| den_sym.to_string()),
1650 )
1651 })
1652 .collect::<Vec<_>>()
1653 }),
1654 mo_density_symmetries: sda_res
1655 .mo_density_symmetries()
1656 .as_ref()
1657 .map(|mo_den_symss| {
1658 mo_den_symss
1659 .iter()
1660 .map(|mo_den_syms| {
1661 mo_den_syms
1662 .iter()
1663 .map(|mo_den_sym_opt| {
1664 mo_den_sym_opt.as_ref().map(|mo_den_sym| {
1665 mo_den_sym.to_string()
1666 })
1667 })
1668 .collect::<Vec<_>>()
1669 })
1670 .collect::<Vec<_>>()
1671 }),
1672 }
1673 }
1674 }
1675 }
1676 }
1677 }
1678 };
1679 Ok(pysda_res)
1680}