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::QSym2Driver;
15use crate::drivers::representation_analysis::angular_function::AngularFunctionRepAnalysisParams;
16use crate::drivers::representation_analysis::vibrational_coordinate::{
17 VibrationalCoordinateRepAnalysisDriver, VibrationalCoordinateRepAnalysisParams,
18};
19use crate::drivers::representation_analysis::{
20 CharacterTableDisplay, MagneticSymmetryAnalysisKind,
21};
22use crate::drivers::symmetry_group_detection::SymmetryGroupDetectionResult;
23use crate::io::format::qsym2_output;
24use crate::io::{QSym2FileType, read_qsym2_binary};
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 Self {
69 coefficients: coefficients.to_owned_array(),
70 frequencies: frequencies.to_owned_array(),
71 threshold,
72 }
73 }
74
75 #[getter]
76 fn coefficients<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyArray2<f64>>> {
77 Ok(self.coefficients.to_pyarray(py))
78 }
79
80 #[getter]
81 fn frequencies<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyArray1<f64>>> {
82 Ok(self.frequencies.to_pyarray(py))
83 }
84}
85
86impl PyVibrationalCoordinateCollectionReal {
87 fn to_qsym2<'b, 'a: 'b>(
102 &'b self,
103 mol: &'a Molecule,
104 ) -> Result<VibrationalCoordinateCollection<'b, f64>, anyhow::Error> {
105 VibrationalCoordinateCollection::<f64>::builder()
106 .mol(mol)
107 .coefficients(self.coefficients.clone())
108 .frequencies(self.frequencies.clone())
109 .threshold(self.threshold)
110 .build()
111 .map_err(|err| format_err!(err))
112 }
113}
114
115#[pyclass]
124#[derive(Clone)]
125pub struct PyVibrationalCoordinateCollectionComplex {
126 coefficients: Array2<C128>,
128
129 frequencies: Array1<C128>,
131
132 #[pyo3(get)]
134 threshold: f64,
135}
136
137#[pymethods]
138impl PyVibrationalCoordinateCollectionComplex {
139 #[new]
148 fn new(
149 coefficients: Bound<'_, PyArray2<C128>>,
150 frequencies: Bound<'_, PyArray1<C128>>,
151 threshold: f64,
152 ) -> Self {
153 Self {
154 coefficients: coefficients.to_owned_array(),
155 frequencies: frequencies.to_owned_array(),
156 threshold,
157 }
158 }
159
160 #[getter]
161 fn coefficients<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyArray2<C128>>> {
162 Ok(self.coefficients.to_pyarray(py))
163 }
164
165 #[getter]
166 fn frequencies<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyArray1<C128>>> {
167 Ok(self.frequencies.to_pyarray(py))
168 }
169}
170
171impl PyVibrationalCoordinateCollectionComplex {
172 fn to_qsym2<'b, 'a: 'b>(
187 &'b self,
188 mol: &'a Molecule,
189 ) -> Result<VibrationalCoordinateCollection<'b, C128>, anyhow::Error> {
190 VibrationalCoordinateCollection::<C128>::builder()
191 .mol(mol)
192 .coefficients(self.coefficients.clone())
193 .frequencies(self.frequencies.clone())
194 .threshold(self.threshold)
195 .build()
196 .map_err(|err| format_err!(err))
197 }
198}
199
200#[derive(FromPyObject)]
207pub enum PyVibrationalCoordinateCollection {
208 Real(PyVibrationalCoordinateCollectionReal),
210
211 Complex(PyVibrationalCoordinateCollectionComplex),
213}
214
215#[allow(clippy::too_many_arguments)]
258#[pyfunction]
259#[pyo3(signature = (
260 inp_sym,
261 pyvibs,
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 write_character_table=true,
270 infinite_order_to_finite=None,
271 angular_function_integrality_threshold=1e-7,
272 angular_function_linear_independence_threshold=1e-7,
273 angular_function_max_angular_momentum=2
274))]
275pub fn rep_analyse_vibrational_coordinate_collection(
276 py: Python<'_>,
277 inp_sym: PathBuf,
278 pyvibs: PyVibrationalCoordinateCollection,
279 integrality_threshold: f64,
280 linear_independence_threshold: f64,
281 use_magnetic_group: Option<MagneticSymmetryAnalysisKind>,
282 use_double_group: bool,
283 use_cayley_table: bool,
284 symmetry_transformation_kind: SymmetryTransformationKind,
285 eigenvalue_comparison_mode: EigenvalueComparisonMode,
286 write_character_table: bool,
287 infinite_order_to_finite: Option<u32>,
288 angular_function_integrality_threshold: f64,
289 angular_function_linear_independence_threshold: f64,
290 angular_function_max_angular_momentum: u32,
291) -> PyResult<()> {
292 py.detach(|| {
293 let pd_res: SymmetryGroupDetectionResult =
294 read_qsym2_binary(inp_sym.clone(), QSym2FileType::Sym)
295 .map_err(|err| PyIOError::new_err(err.to_string()))?;
296
297 let mut file_name = inp_sym.to_path_buf();
298 file_name.set_extension(QSym2FileType::Sym.ext());
299 qsym2_output!(
300 "Symmetry-group detection results read in from {}.",
301 file_name.display(),
302 );
303 qsym2_output!("");
304
305 let mol = &pd_res.pre_symmetry.recentred_molecule;
306
307 let afa_params = AngularFunctionRepAnalysisParams::builder()
308 .integrality_threshold(angular_function_integrality_threshold)
309 .linear_independence_threshold(angular_function_linear_independence_threshold)
310 .max_angular_momentum(angular_function_max_angular_momentum)
311 .build()
312 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
313 match &pyvibs {
314 PyVibrationalCoordinateCollection::Real(pyvibs_r) => {
315 let vibs_r = pyvibs_r
316 .to_qsym2(mol)
317 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
318 let vca_params = VibrationalCoordinateRepAnalysisParams::<f64>::builder()
319 .integrality_threshold(integrality_threshold)
320 .linear_independence_threshold(linear_independence_threshold)
321 .use_magnetic_group(use_magnetic_group.clone())
322 .use_double_group(use_double_group)
323 .use_cayley_table(use_cayley_table)
324 .symmetry_transformation_kind(symmetry_transformation_kind)
325 .eigenvalue_comparison_mode(eigenvalue_comparison_mode)
326 .write_character_table(if write_character_table {
327 Some(CharacterTableDisplay::Symbolic)
328 } else {
329 None
330 })
331 .infinite_order_to_finite(infinite_order_to_finite)
332 .build()
333 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
334 match &use_magnetic_group {
335 Some(MagneticSymmetryAnalysisKind::Corepresentation) => {
336 let mut vca_driver = VibrationalCoordinateRepAnalysisDriver::<
337 MagneticRepresentedSymmetryGroup,
338 f64,
339 >::builder()
340 .parameters(&vca_params)
341 .angular_function_parameters(&afa_params)
342 .vibrational_coordinate_collection(&vibs_r)
343 .symmetry_group(&pd_res)
344 .build()
345 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
346 vca_driver
347 .run()
348 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?
349 }
350 Some(MagneticSymmetryAnalysisKind::Representation) | None => {
351 let mut vca_driver = VibrationalCoordinateRepAnalysisDriver::<
352 UnitaryRepresentedSymmetryGroup,
353 f64,
354 >::builder()
355 .parameters(&vca_params)
356 .angular_function_parameters(&afa_params)
357 .vibrational_coordinate_collection(&vibs_r)
358 .symmetry_group(&pd_res)
359 .build()
360 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
361 vca_driver
362 .run()
363 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?
364 }
365 };
366 }
367 PyVibrationalCoordinateCollection::Complex(pyvibs_c) => {
368 let vibs_c = pyvibs_c
369 .to_qsym2(mol)
370 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
371 let vca_params = VibrationalCoordinateRepAnalysisParams::<f64>::builder()
372 .integrality_threshold(integrality_threshold)
373 .linear_independence_threshold(linear_independence_threshold)
374 .use_magnetic_group(use_magnetic_group.clone())
375 .use_double_group(use_double_group)
376 .use_cayley_table(use_cayley_table)
377 .symmetry_transformation_kind(symmetry_transformation_kind)
378 .eigenvalue_comparison_mode(eigenvalue_comparison_mode)
379 .write_character_table(if write_character_table {
380 Some(CharacterTableDisplay::Symbolic)
381 } else {
382 None
383 })
384 .infinite_order_to_finite(infinite_order_to_finite)
385 .build()
386 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
387 match &use_magnetic_group {
388 Some(MagneticSymmetryAnalysisKind::Corepresentation) => {
389 let mut vca_driver = VibrationalCoordinateRepAnalysisDriver::<
390 MagneticRepresentedSymmetryGroup,
391 C128,
392 >::builder()
393 .parameters(&vca_params)
394 .angular_function_parameters(&afa_params)
395 .vibrational_coordinate_collection(&vibs_c)
396 .symmetry_group(&pd_res)
397 .build()
398 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
399 vca_driver
400 .run()
401 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?
402 }
403 Some(MagneticSymmetryAnalysisKind::Representation) | None => {
404 let mut vca_driver = VibrationalCoordinateRepAnalysisDriver::<
405 UnitaryRepresentedSymmetryGroup,
406 C128,
407 >::builder()
408 .parameters(&vca_params)
409 .angular_function_parameters(&afa_params)
410 .vibrational_coordinate_collection(&vibs_c)
411 .symmetry_group(&pd_res)
412 .build()
413 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?;
414 vca_driver
415 .run()
416 .map_err(|err| PyRuntimeError::new_err(err.to_string()))?
417 }
418 };
419 }
420 }
421 Ok(())
422 })
423}