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)]
257#[pyfunction]
258#[pyo3(signature = (
259 inp_sym,
260 pydens,
261 pybao,
262 integrality_threshold,
263 linear_independence_threshold,
264 use_magnetic_group,
265 use_double_group,
266 use_cayley_table,
267 symmetry_transformation_kind,
268 eigenvalue_comparison_mode,
269 sao_spatial_4c,
270 sao_spatial_4c_h=None,
271 write_character_table=true,
272 infinite_order_to_finite=None,
273 angular_function_integrality_threshold=1e-7,
274 angular_function_linear_independence_threshold=1e-7,
275 angular_function_max_angular_momentum=2
276))]
277pub fn rep_analyse_densities(
278 py: Python<'_>,
279 inp_sym: PathBuf,
280 pydens: Vec<(String, PyDensity)>,
281 pybao: &PyBasisAngularOrder,
282 integrality_threshold: f64,
283 linear_independence_threshold: f64,
284 use_magnetic_group: Option<MagneticSymmetryAnalysisKind>,
285 use_double_group: bool,
286 use_cayley_table: bool,
287 symmetry_transformation_kind: SymmetryTransformationKind,
288 eigenvalue_comparison_mode: EigenvalueComparisonMode,
289 sao_spatial_4c: PyArray4RC,
290 sao_spatial_4c_h: Option<PyArray4RC>,
291 write_character_table: bool,
292 infinite_order_to_finite: Option<u32>,
293 angular_function_integrality_threshold: f64,
294 angular_function_linear_independence_threshold: f64,
295 angular_function_max_angular_momentum: u32,
296) -> PyResult<()> {
297 let pd_res: SymmetryGroupDetectionResult =
298 read_qsym2_binary(inp_sym.clone(), QSym2FileType::Sym)
299 .map_err(|err| PyIOError::new_err(err.to_string()))?;
300
301 let mut file_name = inp_sym.to_path_buf();
302 file_name.set_extension(QSym2FileType::Sym.ext());
303 qsym2_output!(
304 "Symmetry-group detection results read in from {}.",
305 file_name.display(),
306 );
307 qsym2_output!("");
308
309 let mol = &pd_res.pre_symmetry.recentred_molecule;
310 let bao = pybao
311 .to_qsym2(mol)
312 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
313 let afa_params = AngularFunctionRepAnalysisParams::builder()
314 .integrality_threshold(angular_function_integrality_threshold)
315 .linear_independence_threshold(angular_function_linear_independence_threshold)
316 .max_angular_momentum(angular_function_max_angular_momentum)
317 .build()
318 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
319 let sda_params = DensityRepAnalysisParams::<f64>::builder()
320 .integrality_threshold(integrality_threshold)
321 .linear_independence_threshold(linear_independence_threshold)
322 .use_magnetic_group(use_magnetic_group.clone())
323 .use_double_group(use_double_group)
324 .use_cayley_table(use_cayley_table)
325 .symmetry_transformation_kind(symmetry_transformation_kind)
326 .eigenvalue_comparison_mode(eigenvalue_comparison_mode)
327 .write_character_table(if write_character_table {
328 Some(CharacterTableDisplay::Symbolic)
329 } else {
330 None
331 })
332 .infinite_order_to_finite(infinite_order_to_finite)
333 .build()
334 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
335
336 let any_complex = pydens
337 .iter()
338 .any(|(_, pyden)| matches!(pyden, PyDensity::Complex(_)));
339
340 match (any_complex, &sao_spatial_4c) {
341 (false, PyArray4RC::Real(pysao4c_r)) => {
342 let dens = pydens
344 .iter()
345 .map(|(_, pyden)| match pyden {
346 PyDensity::Real(pyden_r) => pyden_r.to_qsym2(&bao, mol),
347 PyDensity::Complex(_) => panic!("Unexpected complex density."),
348 })
349 .collect::<Result<Vec<_>, _>>()
350 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
351 let dens_ref = dens
352 .iter()
353 .zip(pydens.iter())
354 .map(|(den, (desc, _))| (desc.clone(), den))
355 .collect::<Vec<_>>();
356
357 let sao_spatial_4c = pysao4c_r.to_owned_array();
358
359 match &use_magnetic_group {
360 Some(MagneticSymmetryAnalysisKind::Corepresentation) => {
361 let mut da_driver =
362 DensityRepAnalysisDriver::<MagneticRepresentedSymmetryGroup, f64>::builder(
363 )
364 .parameters(&sda_params)
365 .angular_function_parameters(&afa_params)
366 .densities(dens_ref)
367 .sao_spatial_4c(&sao_spatial_4c)
368 .sao_spatial_4c_h(None)
369 .symmetry_group(&pd_res)
370 .build()
371 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
372 py.detach(|| {
373 da_driver
374 .run()
375 .map_err(|err| PyRuntimeError::new_err(err.to_string()))
376 })?
377 }
378 Some(MagneticSymmetryAnalysisKind::Representation) | None => {
379 let mut da_driver =
380 DensityRepAnalysisDriver::<UnitaryRepresentedSymmetryGroup, f64>::builder()
381 .parameters(&sda_params)
382 .angular_function_parameters(&afa_params)
383 .densities(dens_ref)
384 .sao_spatial_4c(&sao_spatial_4c)
385 .sao_spatial_4c_h(None)
386 .symmetry_group(&pd_res)
387 .build()
388 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
389 py.detach(|| {
390 da_driver
391 .run()
392 .map_err(|err| PyRuntimeError::new_err(err.to_string()))
393 })?
394 }
395 };
396 }
397 (_, _) => {
398 let dens: Vec<Density<C128>> = pydens
400 .iter()
401 .map(|(_, pyden)| match pyden {
402 PyDensity::Real(pyden_r) => {
403 pyden_r.to_qsym2(&bao, mol).map(|den_r| den_r.into())
404 }
405 PyDensity::Complex(pyden_c) => pyden_c.to_qsym2(&bao, mol),
406 })
407 .collect::<Result<Vec<_>, _>>()
408 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
409 let dens_ref = dens
410 .iter()
411 .zip(pydens.iter())
412 .map(|(den, (desc, _))| (desc.clone(), den))
413 .collect::<Vec<_>>();
414
415 let (sao_spatial_4c_c, sao_spatial_4c_h_c): (Array4<C128>, Option<Array4<C128>>) =
416 match sao_spatial_4c {
417 PyArray4RC::Real(pysao4c_r) => {
418 (pysao4c_r.to_owned_array().mapv(Complex::from), None)
419 }
420 PyArray4RC::Complex(pysao4c_c) => (
421 pysao4c_c.to_owned_array(),
422 sao_spatial_4c_h.map(|v| match v {
423 PyArray4RC::Real(pysao4c_h_r) => {
424 pysao4c_h_r.to_owned_array().mapv(Complex::from)
425 }
426 PyArray4RC::Complex(pysao4c_h_c) => pysao4c_h_c.to_owned_array(),
427 }),
428 ),
429 };
430
431 match &use_magnetic_group {
432 Some(MagneticSymmetryAnalysisKind::Corepresentation) => {
433 let mut da_driver = DensityRepAnalysisDriver::<
434 MagneticRepresentedSymmetryGroup,
435 C128,
436 >::builder()
437 .parameters(&sda_params)
438 .angular_function_parameters(&afa_params)
439 .densities(dens_ref)
440 .sao_spatial_4c(&sao_spatial_4c_c)
441 .sao_spatial_4c_h(sao_spatial_4c_h_c.as_ref())
442 .symmetry_group(&pd_res)
443 .build()
444 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
445 py.detach(|| {
446 da_driver
447 .run()
448 .map_err(|err| PyRuntimeError::new_err(err.to_string()))
449 })?
450 }
451 Some(MagneticSymmetryAnalysisKind::Representation) | None => {
452 let mut da_driver =
453 DensityRepAnalysisDriver::<UnitaryRepresentedSymmetryGroup, C128>::builder(
454 )
455 .parameters(&sda_params)
456 .angular_function_parameters(&afa_params)
457 .densities(dens_ref)
458 .sao_spatial_4c(&sao_spatial_4c_c)
459 .sao_spatial_4c_h(sao_spatial_4c_h_c.as_ref())
460 .symmetry_group(&pd_res)
461 .build()
462 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
463 py.detach(|| {
464 da_driver
465 .run()
466 .map_err(|err| PyRuntimeError::new_err(err.to_string()))
467 })?
468 }
469 };
470 }
471 }
472 Ok(())
473}