qsym2/bindings/python/representation_analysis/
vibrational_coordinate.rs1use std::path::PathBuf;
4
5use anyhow::format_err;
6use ndarray::{Array1, Array2};
7use num_complex::Complex;
8use numpy::{PyArray1, PyArray2, PyArrayMethods, ToPyArray};
9use pyo3::exceptions::{PyIOError, PyRuntimeError};
10use pyo3::prelude::*;
11
12use crate::analysis::EigenvalueComparisonMode;
13use crate::auxiliary::molecule::Molecule;
14use crate::drivers::representation_analysis::angular_function::AngularFunctionRepAnalysisParams;
15use crate::drivers::representation_analysis::vibrational_coordinate::{
16 VibrationalCoordinateRepAnalysisDriver, VibrationalCoordinateRepAnalysisParams,
17};
18use crate::drivers::representation_analysis::{
19 CharacterTableDisplay, MagneticSymmetryAnalysisKind,
20};
21use crate::drivers::symmetry_group_detection::SymmetryGroupDetectionResult;
22use crate::drivers::QSym2Driver;
23use crate::io::format::qsym2_output;
24use crate::io::{read_qsym2_binary, QSym2FileType};
25use crate::symmetry::symmetry_group::{
26 MagneticRepresentedSymmetryGroup, UnitaryRepresentedSymmetryGroup,
27};
28use crate::symmetry::symmetry_transformation::SymmetryTransformationKind;
29use crate::target::vibration::VibrationalCoordinateCollection;
30
31type C128 = Complex<f64>;
32
33#[pyclass]
40#[derive(Clone)]
41pub struct PyVibrationalCoordinateCollectionReal {
42 coefficients: Array2<f64>,
44
45 frequencies: Array1<f64>,
47
48 #[pyo3(get)]
50 threshold: f64,
51}
52
53#[pymethods]
54impl PyVibrationalCoordinateCollectionReal {
55 #[new]
63 fn new(
64 coefficients: Bound<'_, PyArray2<f64>>,
65 frequencies: Bound<'_, PyArray1<f64>>,
66 threshold: f64,
67 ) -> Self {
68 let vibs = Self {
69 coefficients: coefficients.to_owned_array(),
70 frequencies: frequencies.to_owned_array(),
71 threshold,
72 };
73 vibs
74 }
75
76 #[getter]
77 fn coefficients<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyArray2<f64>>> {
78 Ok(self.coefficients.to_pyarray(py))
79 }
80
81 #[getter]
82 fn frequencies<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyArray1<f64>>> {
83 Ok(self.frequencies.to_pyarray(py))
84 }
85}
86
87impl PyVibrationalCoordinateCollectionReal {
88 fn to_qsym2<'b, 'a: 'b>(
103 &'b self,
104 mol: &'a Molecule,
105 ) -> Result<VibrationalCoordinateCollection<'b, f64>, anyhow::Error> {
106 let vibs = VibrationalCoordinateCollection::<f64>::builder()
107 .mol(mol)
108 .coefficients(self.coefficients.clone())
109 .frequencies(self.frequencies.clone())
110 .threshold(self.threshold)
111 .build()
112 .map_err(|err| format_err!(err));
113 vibs
114 }
115}
116
117#[pyclass]
126#[derive(Clone)]
127pub struct PyVibrationalCoordinateCollectionComplex {
128 coefficients: Array2<C128>,
130
131 frequencies: Array1<C128>,
133
134 #[pyo3(get)]
136 threshold: f64,
137}
138
139#[pymethods]
140impl PyVibrationalCoordinateCollectionComplex {
141 #[new]
150 fn new(
151 coefficients: Bound<'_, PyArray2<C128>>,
152 frequencies: Bound<'_, PyArray1<C128>>,
153 threshold: f64,
154 ) -> Self {
155 let vibs = Self {
156 coefficients: coefficients.to_owned_array(),
157 frequencies: frequencies.to_owned_array(),
158 threshold,
159 };
160 vibs
161 }
162
163 #[getter]
164 fn coefficients<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyArray2<C128>>> {
165 Ok(self.coefficients.to_pyarray(py))
166 }
167
168 #[getter]
169 fn frequencies<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyArray1<C128>>> {
170 Ok(self.frequencies.to_pyarray(py))
171 }
172}
173
174impl PyVibrationalCoordinateCollectionComplex {
175 fn to_qsym2<'b, 'a: 'b>(
190 &'b self,
191 mol: &'a Molecule,
192 ) -> Result<VibrationalCoordinateCollection<'b, C128>, anyhow::Error> {
193 let vibs = VibrationalCoordinateCollection::<C128>::builder()
194 .mol(mol)
195 .coefficients(self.coefficients.clone())
196 .frequencies(self.frequencies.clone())
197 .threshold(self.threshold)
198 .build()
199 .map_err(|err| format_err!(err));
200 vibs
201 }
202}
203
204#[derive(FromPyObject)]
211pub enum PyVibrationalCoordinateCollection {
212 Real(PyVibrationalCoordinateCollectionReal),
214
215 Complex(PyVibrationalCoordinateCollectionComplex),
217}
218
219#[pyfunction]
262#[pyo3(signature = (
263 inp_sym,
264 pyvibs,
265 integrality_threshold,
266 linear_independence_threshold,
267 use_magnetic_group,
268 use_double_group,
269 use_cayley_table,
270 symmetry_transformation_kind,
271 eigenvalue_comparison_mode,
272 write_character_table=true,
273 infinite_order_to_finite=None,
274 angular_function_integrality_threshold=1e-7,
275 angular_function_linear_independence_threshold=1e-7,
276 angular_function_max_angular_momentum=2
277))]
278pub fn rep_analyse_vibrational_coordinate_collection(
279 py: Python<'_>,
280 inp_sym: PathBuf,
281 pyvibs: PyVibrationalCoordinateCollection,
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 write_character_table: bool,
290 infinite_order_to_finite: Option<u32>,
291 angular_function_integrality_threshold: f64,
292 angular_function_linear_independence_threshold: f64,
293 angular_function_max_angular_momentum: u32,
294) -> PyResult<()> {
295 py.detach(|| {
296 let pd_res: SymmetryGroupDetectionResult =
297 read_qsym2_binary(inp_sym.clone(), QSym2FileType::Sym)
298 .map_err(|err| PyIOError::new_err(err.to_string()))?;
299
300 let mut file_name = inp_sym.to_path_buf();
301 file_name.set_extension(QSym2FileType::Sym.ext());
302 qsym2_output!(
303 "Symmetry-group detection results read in from {}.",
304 file_name.display(),
305 );
306 qsym2_output!("");
307
308 let mol = &pd_res.pre_symmetry.recentred_molecule;
309
310 let afa_params = AngularFunctionRepAnalysisParams::builder()
311 .integrality_threshold(angular_function_integrality_threshold)
312 .linear_independence_threshold(angular_function_linear_independence_threshold)
313 .max_angular_momentum(angular_function_max_angular_momentum)
314 .build()
315 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
316 match &pyvibs {
317 PyVibrationalCoordinateCollection::Real(pyvibs_r) => {
318 let vibs_r = pyvibs_r
319 .to_qsym2(mol)
320 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
321 let vca_params = VibrationalCoordinateRepAnalysisParams::<f64>::builder()
322 .integrality_threshold(integrality_threshold)
323 .linear_independence_threshold(linear_independence_threshold)
324 .use_magnetic_group(use_magnetic_group.clone())
325 .use_double_group(use_double_group)
326 .use_cayley_table(use_cayley_table)
327 .symmetry_transformation_kind(symmetry_transformation_kind)
328 .eigenvalue_comparison_mode(eigenvalue_comparison_mode)
329 .write_character_table(if write_character_table {
330 Some(CharacterTableDisplay::Symbolic)
331 } else {
332 None
333 })
334 .infinite_order_to_finite(infinite_order_to_finite)
335 .build()
336 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
337 match &use_magnetic_group {
338 Some(MagneticSymmetryAnalysisKind::Corepresentation) => {
339 let mut vca_driver = VibrationalCoordinateRepAnalysisDriver::<
340 MagneticRepresentedSymmetryGroup,
341 f64,
342 >::builder()
343 .parameters(&vca_params)
344 .angular_function_parameters(&afa_params)
345 .vibrational_coordinate_collection(&vibs_r)
346 .symmetry_group(&pd_res)
347 .build()
348 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
349 vca_driver
350 .run()
351 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?
352 }
353 Some(MagneticSymmetryAnalysisKind::Representation) | None => {
354 let mut vca_driver = VibrationalCoordinateRepAnalysisDriver::<
355 UnitaryRepresentedSymmetryGroup,
356 f64,
357 >::builder()
358 .parameters(&vca_params)
359 .angular_function_parameters(&afa_params)
360 .vibrational_coordinate_collection(&vibs_r)
361 .symmetry_group(&pd_res)
362 .build()
363 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
364 vca_driver
365 .run()
366 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?
367 }
368 };
369 }
370 PyVibrationalCoordinateCollection::Complex(pyvibs_c) => {
371 let vibs_c = pyvibs_c
372 .to_qsym2(mol)
373 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
374 let vca_params = VibrationalCoordinateRepAnalysisParams::<f64>::builder()
375 .integrality_threshold(integrality_threshold)
376 .linear_independence_threshold(linear_independence_threshold)
377 .use_magnetic_group(use_magnetic_group.clone())
378 .use_double_group(use_double_group)
379 .use_cayley_table(use_cayley_table)
380 .symmetry_transformation_kind(symmetry_transformation_kind)
381 .eigenvalue_comparison_mode(eigenvalue_comparison_mode)
382 .write_character_table(if write_character_table {
383 Some(CharacterTableDisplay::Symbolic)
384 } else {
385 None
386 })
387 .infinite_order_to_finite(infinite_order_to_finite)
388 .build()
389 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
390 match &use_magnetic_group {
391 Some(MagneticSymmetryAnalysisKind::Corepresentation) => {
392 let mut vca_driver = VibrationalCoordinateRepAnalysisDriver::<
393 MagneticRepresentedSymmetryGroup,
394 C128,
395 >::builder()
396 .parameters(&vca_params)
397 .angular_function_parameters(&afa_params)
398 .vibrational_coordinate_collection(&vibs_c)
399 .symmetry_group(&pd_res)
400 .build()
401 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
402 vca_driver
403 .run()
404 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?
405 }
406 Some(MagneticSymmetryAnalysisKind::Representation) | None => {
407 let mut vca_driver = VibrationalCoordinateRepAnalysisDriver::<
408 UnitaryRepresentedSymmetryGroup,
409 C128,
410 >::builder()
411 .parameters(&vca_params)
412 .angular_function_parameters(&afa_params)
413 .vibrational_coordinate_collection(&vibs_c)
414 .symmetry_group(&pd_res)
415 .build()
416 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
417 vca_driver
418 .run()
419 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?
420 }
421 };
422 }
423 }
424 Ok(())
425 })
426}