qsym2/target/vibration/
vibration_transformation.rs

1//! Implementation of symmetry transformations for vibrational coordinates.
2
3use ndarray::{Array2, Axis, LinalgScalar, ScalarOperand, concatenate, s};
4use ndarray_linalg::types::Lapack;
5use num_complex::{Complex, ComplexFloat};
6
7use crate::auxiliary::molecule::Molecule;
8use crate::basis::ao::{BasisAngularOrder, BasisAtom, BasisShell, CartOrder, ShellOrder};
9use crate::permutation::{IntoPermutation, PermutableCollection, Permutation};
10use crate::symmetry::symmetry_element::SymmetryOperation;
11use crate::symmetry::symmetry_transformation::{
12    ComplexConjugationTransformable, SpatialUnitaryTransformable, SpinUnitaryTransformable,
13    SymmetryTransformable, TimeReversalTransformable, TransformationError,
14    assemble_sh_rotation_3d_matrices, permute_array_by_atoms,
15};
16use crate::target::vibration::VibrationalCoordinate;
17
18// ---------------------------
19// SpatialUnitaryTransformable
20// ---------------------------
21impl<'a, T> SpatialUnitaryTransformable for VibrationalCoordinate<'a, T>
22where
23    T: ComplexFloat + LinalgScalar + ScalarOperand + Copy + Lapack,
24    f64: Into<T>,
25{
26    fn transform_spatial_mut(
27        &mut self,
28        rmat: &Array2<f64>,
29        perm: Option<&Permutation<usize>>,
30    ) -> Result<&mut Self, TransformationError> {
31        let vib_bao = construct_vibration_bao(self.mol);
32        let tmats: Vec<Array2<T>> = assemble_sh_rotation_3d_matrices(&[&vib_bao], rmat, perm)
33            .map_err(|err| TransformationError(err.to_string()))?
34            .iter()
35            .map(|tmats| {
36                tmats
37                    .iter()
38                    .map(|tmat| tmat.mapv(|x| x.into()))
39                    .collect::<Vec<_>>()
40            })
41            .next()
42            .ok_or_else(|| {
43                TransformationError(
44                    "Unable to obtain the spherical-harmonic transformation matrices.".to_string(),
45                )
46            })?;
47        let pbao = if let Some(p) = perm {
48            vib_bao
49                .permute(p)
50                .map_err(|err| TransformationError(err.to_string()))?
51        } else {
52            vib_bao.clone()
53        };
54        let old_coeff = &self.coefficients;
55        let p_coeff = if let Some(p) = perm {
56            permute_array_by_atoms(old_coeff, p, &[Axis(0)], &vib_bao)
57        } else {
58            old_coeff.clone()
59        };
60        let t_p_blocks = pbao
61            .shell_boundary_indices()
62            .into_iter()
63            .zip(tmats.iter())
64            .map(|((shl_start, shl_end), tmat)| tmat.dot(&p_coeff.slice(s![shl_start..shl_end])))
65            .collect::<Vec<_>>();
66        let new_coefficients = concatenate(
67            Axis(0),
68            &t_p_blocks
69                .iter()
70                .map(|t_p_block| t_p_block.view())
71                .collect::<Vec<_>>(),
72        )
73        .expect("Unable to concatenate the transformed rows for the various atoms.");
74        self.coefficients = new_coefficients;
75        Ok(self)
76    }
77}
78
79// ------------------------
80// SpinUnitaryTransformable
81// ------------------------
82
83impl<'a, T> SpinUnitaryTransformable for VibrationalCoordinate<'a, T>
84where
85    T: ComplexFloat + Lapack,
86{
87    /// Performs a spin transformation in-place.
88    ///
89    /// This has no effects on the vibrational coordinate as vibrational coordinates are entirely
90    /// spatial.
91    fn transform_spin_mut(
92        &mut self,
93        _dmat: &Array2<Complex<f64>>,
94    ) -> Result<&mut Self, TransformationError> {
95        Ok(self)
96    }
97}
98
99// -------------------------------
100// ComplexConjugationTransformable
101// -------------------------------
102
103impl<'a, T> ComplexConjugationTransformable for VibrationalCoordinate<'a, T>
104where
105    T: ComplexFloat + Lapack,
106{
107    /// Performs a complex conjugation in-place.
108    fn transform_cc_mut(&mut self) -> Result<&mut Self, TransformationError> {
109        self.coefficients.mapv_inplace(|x| x.conj());
110        Ok(self)
111    }
112}
113
114// -------------------------
115// TimeReversalTransformable
116// -------------------------
117impl<'a, T> TimeReversalTransformable for VibrationalCoordinate<'a, T>
118where
119    T: ComplexFloat + Lapack,
120{
121    /// Provides a custom implementation of time reversal where the components of the vibrational
122    /// coordinates are complex-conjugated to respect the antiunitarity of time reversal.
123    fn transform_timerev_mut(&mut self) -> Result<&mut Self, TransformationError> {
124        self.transform_cc_mut()
125    }
126}
127
128// ---------------------
129// SymmetryTransformable
130// ---------------------
131impl<'a, T> SymmetryTransformable for VibrationalCoordinate<'a, T>
132where
133    T: ComplexFloat + Lapack,
134    VibrationalCoordinate<'a, T>: SpatialUnitaryTransformable + TimeReversalTransformable,
135{
136    fn sym_permute_sites_spatial(
137        &self,
138        symop: &SymmetryOperation,
139    ) -> Result<Permutation<usize>, TransformationError> {
140        symop
141            .act_permute(&self.mol.molecule_ordinary_atoms())
142            .ok_or(TransformationError(format!(
143                "Unable to determine the atom permutation corresponding to the operation `{symop}`."
144            )))
145    }
146}
147
148// ---------
149// Functions
150// ---------
151
152fn construct_vibration_bao(mol: &'_ Molecule) -> BasisAngularOrder<'_> {
153    let bsp_c = BasisShell::new(1, ShellOrder::Cart(CartOrder::lex(1)));
154    let batms = mol
155        .atoms
156        .iter()
157        .map(|atom| BasisAtom::new(atom, &[bsp_c.clone()]))
158        .collect::<Vec<_>>();
159    BasisAngularOrder::new(&batms)
160}