qsym2/bindings/python/representation_analysis/
density.rs1use std::path::PathBuf;
4
5use anyhow::format_err;
6use ndarray::{Array2, Array4};
7use num_complex::Complex;
8use numpy::{PyArray2, PyArrayMethods, ToPyArray};
9use pyo3::exceptions::{PyIOError, PyRuntimeError};
10use pyo3::prelude::*;
11
12use crate::analysis::EigenvalueComparisonMode;
13use crate::auxiliary::molecule::Molecule;
14use crate::basis::ao::BasisAngularOrder;
15use crate::bindings::python::integrals::PyBasisAngularOrder;
16use crate::bindings::python::representation_analysis::PyArray4RC;
17use crate::drivers::QSym2Driver;
18use crate::drivers::representation_analysis::angular_function::AngularFunctionRepAnalysisParams;
19use crate::drivers::representation_analysis::density::{
20 DensityRepAnalysisDriver, DensityRepAnalysisParams,
21};
22use crate::drivers::representation_analysis::{
23 CharacterTableDisplay, MagneticSymmetryAnalysisKind,
24};
25use crate::drivers::symmetry_group_detection::SymmetryGroupDetectionResult;
26use crate::io::format::qsym2_output;
27use crate::io::{QSym2FileType, read_qsym2_binary};
28use crate::symmetry::symmetry_group::{
29 MagneticRepresentedSymmetryGroup, UnitaryRepresentedSymmetryGroup,
30};
31use crate::symmetry::symmetry_transformation::SymmetryTransformationKind;
32use crate::target::density::Density;
33
34type C128 = Complex<f64>;
35
36#[pyclass]
42#[derive(Clone)]
43pub struct PyDensityReal {
44 #[pyo3(get)]
47 pub complex_symmetric: bool,
48
49 pub density_matrix: Array2<f64>,
51
52 #[pyo3(get)]
54 pub threshold: f64,
55}
56
57#[pymethods]
58impl PyDensityReal {
59 #[new]
66 fn new(
67 complex_symmetric: bool,
68 density_matrix: Bound<'_, PyArray2<f64>>,
69 threshold: f64,
70 ) -> Self {
71 Self {
72 complex_symmetric,
73 density_matrix: density_matrix.to_owned_array(),
74 threshold,
75 }
76 }
77
78 #[getter]
79 fn density_matrix<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyArray2<f64>>> {
80 Ok(self.density_matrix.to_pyarray(py))
81 }
82}
83
84impl PyDensityReal {
85 pub(crate) fn to_qsym2<'b, 'a: 'b>(
101 &'b self,
102 bao: &'a BasisAngularOrder,
103 mol: &'a Molecule,
104 ) -> Result<Density<'b, f64>, anyhow::Error> {
105 Density::<f64>::builder()
106 .bao(bao)
107 .complex_symmetric(self.complex_symmetric)
108 .mol(mol)
109 .density_matrix(self.density_matrix.clone())
110 .threshold(self.threshold)
111 .build()
112 .map_err(|err| format_err!(err))
113 }
114}
115
116#[pyclass]
118#[derive(Clone)]
119pub struct PyDensityComplex {
120 #[pyo3(get)]
123 pub complex_symmetric: bool,
124
125 pub density_matrix: Array2<C128>,
127
128 #[pyo3(get)]
130 pub threshold: f64,
131}
132
133#[pymethods]
134impl PyDensityComplex {
135 #[new]
144 fn new(
145 complex_symmetric: bool,
146 density_matrix: Bound<'_, PyArray2<C128>>,
147 threshold: f64,
148 ) -> Self {
149 Self {
150 complex_symmetric,
151 density_matrix: density_matrix.to_owned_array(),
152 threshold,
153 }
154 }
155
156 #[getter]
157 fn density_matrix<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyArray2<C128>>> {
158 Ok(self.density_matrix.to_pyarray(py))
159 }
160}
161
162impl PyDensityComplex {
163 pub(crate) fn to_qsym2<'b, 'a: 'b>(
179 &'b self,
180 bao: &'a BasisAngularOrder,
181 mol: &'a Molecule,
182 ) -> Result<Density<'b, C128>, anyhow::Error> {
183 Density::<C128>::builder()
184 .bao(bao)
185 .complex_symmetric(self.complex_symmetric)
186 .mol(mol)
187 .density_matrix(self.density_matrix.clone())
188 .threshold(self.threshold)
189 .build()
190 .map_err(|err| format_err!(err))
191 }
192}
193
194#[derive(FromPyObject)]
201pub enum PyDensity {
202 Real(PyDensityReal),
204
205 Complex(PyDensityComplex),
207}
208
209#[allow(clippy::too_many_arguments)]
261#[pyfunction]
262#[pyo3(signature = (
263 inp_sym,
264 pydens,
265 pybao,
266 integrality_threshold,
267 linear_independence_threshold,
268 orbit_overlap_symmetry_threshold,
269 use_magnetic_group,
270 use_double_group,
271 use_cayley_table,
272 symmetry_transformation_kind,
273 eigenvalue_comparison_mode,
274 sao_spatial_4c,
275 sao_spatial_4c_h=None,
276 write_character_table=true,
277 infinite_order_to_finite=None,
278 angular_function_integrality_threshold=1e-7,
279 angular_function_linear_independence_threshold=1e-7,
280 angular_function_orbit_overlap_symmetry_threshold=1e-7,
281 angular_function_max_angular_momentum=2
282))]
283pub fn rep_analyse_densities(
284 py: Python<'_>,
285 inp_sym: PathBuf,
286 pydens: Vec<(String, PyDensity)>,
287 pybao: &PyBasisAngularOrder,
288 integrality_threshold: f64,
289 linear_independence_threshold: f64,
290 orbit_overlap_symmetry_threshold: f64,
291 use_magnetic_group: Option<MagneticSymmetryAnalysisKind>,
292 use_double_group: bool,
293 use_cayley_table: bool,
294 symmetry_transformation_kind: SymmetryTransformationKind,
295 eigenvalue_comparison_mode: EigenvalueComparisonMode,
296 sao_spatial_4c: PyArray4RC,
297 sao_spatial_4c_h: Option<PyArray4RC>,
298 write_character_table: bool,
299 infinite_order_to_finite: Option<u32>,
300 angular_function_integrality_threshold: f64,
301 angular_function_linear_independence_threshold: f64,
302 angular_function_orbit_overlap_symmetry_threshold: f64,
303 angular_function_max_angular_momentum: u32,
304) -> PyResult<()> {
305 let pd_res: SymmetryGroupDetectionResult =
306 read_qsym2_binary(inp_sym.clone(), QSym2FileType::Sym)
307 .map_err(|err| PyIOError::new_err(err.to_string()))?;
308
309 let mut file_name = inp_sym.to_path_buf();
310 file_name.set_extension(QSym2FileType::Sym.ext());
311 qsym2_output!(
312 "Symmetry-group detection results read in from {}.",
313 file_name.display(),
314 );
315 qsym2_output!("");
316
317 let mol = &pd_res.pre_symmetry.recentred_molecule;
318 let bao = pybao
319 .to_qsym2(mol)
320 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
321 let afa_params = AngularFunctionRepAnalysisParams::builder()
322 .integrality_threshold(angular_function_integrality_threshold)
323 .linear_independence_threshold(angular_function_linear_independence_threshold)
324 .orbit_overlap_symmetry_threshold(angular_function_orbit_overlap_symmetry_threshold)
325 .max_angular_momentum(angular_function_max_angular_momentum)
326 .build()
327 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
328 let sda_params = DensityRepAnalysisParams::<f64>::builder()
329 .integrality_threshold(integrality_threshold)
330 .linear_independence_threshold(linear_independence_threshold)
331 .orbit_overlap_symmetry_threshold(orbit_overlap_symmetry_threshold)
332 .use_magnetic_group(use_magnetic_group.clone())
333 .use_double_group(use_double_group)
334 .use_cayley_table(use_cayley_table)
335 .symmetry_transformation_kind(symmetry_transformation_kind)
336 .eigenvalue_comparison_mode(eigenvalue_comparison_mode)
337 .write_character_table(if write_character_table {
338 Some(CharacterTableDisplay::Symbolic)
339 } else {
340 None
341 })
342 .infinite_order_to_finite(infinite_order_to_finite)
343 .build()
344 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
345
346 let any_complex = pydens
347 .iter()
348 .any(|(_, pyden)| matches!(pyden, PyDensity::Complex(_)));
349
350 match (any_complex, &sao_spatial_4c) {
351 (false, PyArray4RC::Real(pysao4c_r)) => {
352 let dens = pydens
354 .iter()
355 .map(|(_, pyden)| match pyden {
356 PyDensity::Real(pyden_r) => pyden_r.to_qsym2(&bao, mol),
357 PyDensity::Complex(_) => panic!("Unexpected complex density."),
358 })
359 .collect::<Result<Vec<_>, _>>()
360 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
361 let dens_ref = dens
362 .iter()
363 .zip(pydens.iter())
364 .map(|(den, (desc, _))| (desc.clone(), den))
365 .collect::<Vec<_>>();
366
367 let sao_spatial_4c = pysao4c_r.to_owned_array();
368
369 match &use_magnetic_group {
370 Some(MagneticSymmetryAnalysisKind::Corepresentation) => {
371 let mut da_driver =
372 DensityRepAnalysisDriver::<MagneticRepresentedSymmetryGroup, f64>::builder(
373 )
374 .parameters(&sda_params)
375 .angular_function_parameters(&afa_params)
376 .densities(dens_ref)
377 .sao_spatial_4c(&sao_spatial_4c)
378 .sao_spatial_4c_h(None)
379 .symmetry_group(&pd_res)
380 .build()
381 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
382 py.detach(|| {
383 da_driver
384 .run()
385 .map_err(|err| PyRuntimeError::new_err(err.to_string()))
386 })?
387 }
388 Some(MagneticSymmetryAnalysisKind::Representation) | None => {
389 let mut da_driver =
390 DensityRepAnalysisDriver::<UnitaryRepresentedSymmetryGroup, f64>::builder()
391 .parameters(&sda_params)
392 .angular_function_parameters(&afa_params)
393 .densities(dens_ref)
394 .sao_spatial_4c(&sao_spatial_4c)
395 .sao_spatial_4c_h(None)
396 .symmetry_group(&pd_res)
397 .build()
398 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
399 py.detach(|| {
400 da_driver
401 .run()
402 .map_err(|err| PyRuntimeError::new_err(err.to_string()))
403 })?
404 }
405 };
406 }
407 (_, _) => {
408 let dens: Vec<Density<C128>> = pydens
410 .iter()
411 .map(|(_, pyden)| match pyden {
412 PyDensity::Real(pyden_r) => {
413 pyden_r.to_qsym2(&bao, mol).map(|den_r| den_r.into())
414 }
415 PyDensity::Complex(pyden_c) => pyden_c.to_qsym2(&bao, mol),
416 })
417 .collect::<Result<Vec<_>, _>>()
418 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
419 let dens_ref = dens
420 .iter()
421 .zip(pydens.iter())
422 .map(|(den, (desc, _))| (desc.clone(), den))
423 .collect::<Vec<_>>();
424
425 let (sao_spatial_4c_c, sao_spatial_4c_h_c): (Array4<C128>, Option<Array4<C128>>) =
426 match sao_spatial_4c {
427 PyArray4RC::Real(pysao4c_r) => {
428 (pysao4c_r.to_owned_array().mapv(Complex::from), None)
429 }
430 PyArray4RC::Complex(pysao4c_c) => (
431 pysao4c_c.to_owned_array(),
432 sao_spatial_4c_h.map(|v| match v {
433 PyArray4RC::Real(pysao4c_h_r) => {
434 pysao4c_h_r.to_owned_array().mapv(Complex::from)
435 }
436 PyArray4RC::Complex(pysao4c_h_c) => pysao4c_h_c.to_owned_array(),
437 }),
438 ),
439 };
440
441 match &use_magnetic_group {
442 Some(MagneticSymmetryAnalysisKind::Corepresentation) => {
443 let mut da_driver = DensityRepAnalysisDriver::<
444 MagneticRepresentedSymmetryGroup,
445 C128,
446 >::builder()
447 .parameters(&sda_params)
448 .angular_function_parameters(&afa_params)
449 .densities(dens_ref)
450 .sao_spatial_4c(&sao_spatial_4c_c)
451 .sao_spatial_4c_h(sao_spatial_4c_h_c.as_ref())
452 .symmetry_group(&pd_res)
453 .build()
454 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
455 py.detach(|| {
456 da_driver
457 .run()
458 .map_err(|err| PyRuntimeError::new_err(err.to_string()))
459 })?
460 }
461 Some(MagneticSymmetryAnalysisKind::Representation) | None => {
462 let mut da_driver =
463 DensityRepAnalysisDriver::<UnitaryRepresentedSymmetryGroup, C128>::builder(
464 )
465 .parameters(&sda_params)
466 .angular_function_parameters(&afa_params)
467 .densities(dens_ref)
468 .sao_spatial_4c(&sao_spatial_4c_c)
469 .sao_spatial_4c_h(sao_spatial_4c_h_c.as_ref())
470 .symmetry_group(&pd_res)
471 .build()
472 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
473 py.detach(|| {
474 da_driver
475 .run()
476 .map_err(|err| PyRuntimeError::new_err(err.to_string()))
477 })?
478 }
479 };
480 }
481 }
482 Ok(())
483}