qsym2/target/density/
density_transformation.rs1use ndarray::{Array2, Axis, LinalgScalar, ScalarOperand, concatenate, s};
4use ndarray_linalg::types::Lapack;
5use num_complex::{Complex, ComplexFloat};
6
7use crate::permutation::{IntoPermutation, PermutableCollection, Permutation};
8use crate::symmetry::symmetry_element::SymmetryOperation;
9use crate::symmetry::symmetry_transformation::{
10 ComplexConjugationTransformable, DefaultTimeReversalTransformable, SpatialUnitaryTransformable,
11 SpinUnitaryTransformable, SymmetryTransformable, TimeReversalTransformable,
12 TransformationError, assemble_sh_rotation_3d_matrices, permute_array_by_atoms,
13};
14use crate::target::density::Density;
15
16impl<'a, T> SpatialUnitaryTransformable for Density<'a, T>
20where
21 T: ComplexFloat + LinalgScalar + ScalarOperand + Copy + Lapack,
22 f64: Into<T>,
23{
24 fn transform_spatial_mut(
25 &mut self,
26 rmat: &Array2<f64>,
27 perm: Option<&Permutation<usize>>,
28 ) -> Result<&mut Self, TransformationError> {
29 let tmats: Vec<Array2<T>> = assemble_sh_rotation_3d_matrices(&[self.bao], rmat, perm)
30 .map_err(|err| TransformationError(err.to_string()))?
31 .iter()
32 .map(|tmats| {
33 tmats
34 .iter()
35 .map(|tmat| tmat.mapv(|x| x.into()))
36 .collect::<Vec<_>>()
37 })
38 .next()
39 .ok_or_else(|| {
40 TransformationError(
41 "Unable to obtain the spherical-harmonic transformation matrices.".to_string(),
42 )
43 })?;
44 let pbao = if let Some(p) = perm {
45 self.bao
46 .permute(p)
47 .map_err(|err| TransformationError(err.to_string()))?
48 } else {
49 self.bao.clone()
50 };
51 let old_denmat = &self.density_matrix;
52 let p_coeff = if let Some(p) = perm {
53 permute_array_by_atoms(old_denmat, p, &[Axis(0), Axis(1)], self.bao)
54 } else {
55 old_denmat.clone()
56 };
57 let trow_p_blocks = pbao
58 .shell_boundary_indices()
59 .into_iter()
60 .zip(tmats.iter())
61 .map(|((shl_start, shl_end), tmat)| {
62 tmat.dot(&p_coeff.slice(s![shl_start..shl_end, ..]))
63 })
64 .collect::<Vec<Array2<T>>>();
65 let trow_p_coeff = concatenate(
66 Axis(0),
67 &trow_p_blocks
68 .iter()
69 .map(|trow_p_block| trow_p_block.view())
70 .collect::<Vec<_>>(),
71 )
72 .map_err(|err| {
73 TransformationError(format!(
74 "Unable to concatenate the transformed rows for the various shells: {err}."
75 ))
76 })?;
77
78 let tcol_trow_p_blocks = pbao
79 .shell_boundary_indices()
80 .into_iter()
81 .zip(tmats.iter())
82 .map(|((shl_start, shl_end), tmat)| {
83 trow_p_coeff
85 .slice(s![.., shl_start..shl_end])
86 .dot(&tmat.t())
87 })
88 .collect::<Vec<_>>();
89 let new_denmat = concatenate(
90 Axis(1),
91 &tcol_trow_p_blocks
92 .iter()
93 .map(|tcol_trow_p_block| tcol_trow_p_block.view())
94 .collect::<Vec<_>>(),
95 )
96 .map_err(|err| {
97 TransformationError(format!(
98 "Unable to concatenate the transformed columns for the various shells: {err}."
99 ))
100 })?;
101 self.density_matrix = new_denmat;
102 Ok(self)
103 }
104}
105
106impl<'a, T> SpinUnitaryTransformable for Density<'a, T>
111where
112 T: ComplexFloat + Lapack,
113{
114 fn transform_spin_mut(
119 &mut self,
120 _: &Array2<Complex<f64>>,
121 ) -> Result<&mut Self, TransformationError> {
122 Ok(self)
123 }
124}
125
126impl<'a, T> ComplexConjugationTransformable for Density<'a, T>
131where
132 T: ComplexFloat + Lapack,
133{
134 fn transform_cc_mut(&mut self) -> Result<&mut Self, TransformationError> {
135 self.density_matrix.mapv_inplace(|x| x.conj());
136 self.complex_conjugated = !self.complex_conjugated;
137 Ok(self)
138 }
139}
140
141impl<'a, T> DefaultTimeReversalTransformable for Density<'a, T> where T: ComplexFloat + Lapack {}
145
146impl<'a, T> SymmetryTransformable for Density<'a, T>
150where
151 T: ComplexFloat + Lapack,
152 Density<'a, T>: SpatialUnitaryTransformable + TimeReversalTransformable,
153{
154 fn sym_permute_sites_spatial(
155 &self,
156 symop: &SymmetryOperation,
157 ) -> Result<Permutation<usize>, TransformationError> {
158 if (symop.generating_element.threshold().log10() - self.mol.threshold.log10()).abs() >= 3.0
159 {
160 log::warn!(
161 "Symmetry operation threshold ({:.3e}) and molecule threshold ({:.3e}) \
162 differ by more than three orders of magnitudes.",
163 symop.generating_element.threshold(),
164 self.mol.threshold
165 )
166 }
167 symop
168 .act_permute(&self.mol.molecule_ordinary_atoms())
169 .ok_or(TransformationError(format!(
170 "Unable to determine the atom permutation corresponding to the operation `{symop}`.",
171 )))
172 }
173}