1use std::fmt;
4use std::ops::Mul;
5
6use anyhow::{self, ensure, format_err, Context};
7use approx;
8use derive_builder::Builder;
9use itertools::Itertools;
10use ndarray::{Array1, Array2, Axis, Ix2};
11use ndarray_linalg::{
12 eig::Eig,
13 eigh::Eigh,
14 types::{Lapack, Scalar},
15 UPLO,
16};
17use num_complex::{Complex, ComplexFloat};
18use num_traits::{Float, Zero};
19
20use crate::analysis::{
21 fn_calc_xmat_complex, fn_calc_xmat_real, EigenvalueComparisonMode, Orbit, OrbitIterator,
22 Overlap, RepAnalysis,
23};
24use crate::auxiliary::misc::complex_modified_gram_schmidt;
25use crate::chartab::SubspaceDecomposable;
26use crate::symmetry::symmetry_element::symmetry_operation::SpecialSymmetryTransformation;
27use crate::symmetry::symmetry_group::SymmetryGroupProperties;
28use crate::symmetry::symmetry_transformation::{SymmetryTransformable, SymmetryTransformationKind};
29use crate::target::vibration::VibrationalCoordinate;
30
31impl<'a, T> Overlap<T, Ix2> for VibrationalCoordinate<'a, T>
36where
37 T: Lapack
38 + ComplexFloat<Real = <T as Scalar>::Real>
39 + fmt::Debug
40 + Mul<<T as ComplexFloat>::Real, Output = T>,
41 <T as ComplexFloat>::Real: fmt::Debug
42 + approx::RelativeEq<<T as ComplexFloat>::Real>
43 + approx::AbsDiffEq<Epsilon = <T as Scalar>::Real>,
44{
45 fn complex_symmetric(&self) -> bool {
46 false
47 }
48
49 fn overlap(
58 &self,
59 other: &Self,
60 metric: Option<&Array2<T>>,
61 _: Option<&Array2<T>>,
62 ) -> Result<T, anyhow::Error> {
63 ensure!(
64 self.coefficients.len() == other.coefficients.len(),
65 "Inconsistent numbers of coefficient matrices between `self` and `other`."
66 );
67
68 let ov = if let Some(s) = metric {
69 self.coefficients
70 .t()
71 .mapv(|x| x.conj())
72 .dot(s)
73 .dot(&other.coefficients)
74 } else {
75 self.coefficients
76 .t()
77 .mapv(|x| x.conj())
78 .dot(&other.coefficients)
79 };
80 Ok(ov)
81 }
82
83 fn overlap_definition(&self) -> String {
85 let k = if self.complex_symmetric() { "κ " } else { "" };
86 format!("⟨{k}v_1|v_2⟩ = [{k}v_1]† g v_2 where g is an optional metric")
87 }
88}
89
90#[derive(Builder, Clone)]
101pub struct VibrationalCoordinateSymmetryOrbit<'a, G, T>
102where
103 G: SymmetryGroupProperties,
104 T: ComplexFloat + fmt::Debug + Lapack,
105 VibrationalCoordinate<'a, T>: SymmetryTransformable,
106{
107 group: &'a G,
109
110 origin: &'a VibrationalCoordinate<'a, T>,
112
113 integrality_threshold: <T as ComplexFloat>::Real,
116
117 pub(crate) linear_independence_threshold: <T as ComplexFloat>::Real,
119
120 symmetry_transformation_kind: SymmetryTransformationKind,
123
124 #[builder(setter(skip), default = "None")]
126 smat: Option<Array2<T>>,
127
128 #[builder(setter(skip), default = "None")]
131 pub(crate) smat_eigvals: Option<Array1<T>>,
132
133 #[builder(setter(skip), default = "None")]
138 xmat: Option<Array2<T>>,
139
140 pub(crate) eigenvalue_comparison_mode: EigenvalueComparisonMode,
143}
144
145impl<'a, G, T> VibrationalCoordinateSymmetryOrbit<'a, G, T>
150where
151 G: SymmetryGroupProperties + Clone,
152 T: ComplexFloat + fmt::Debug + Lapack,
153 VibrationalCoordinate<'a, T>: SymmetryTransformable,
154{
155 pub fn builder() -> VibrationalCoordinateSymmetryOrbitBuilder<'a, G, T> {
157 VibrationalCoordinateSymmetryOrbitBuilder::default()
158 }
159}
160
161impl<'a, G> VibrationalCoordinateSymmetryOrbit<'a, G, f64>
162where
163 G: SymmetryGroupProperties,
164{
165 fn_calc_xmat_real!(
166 pub calc_xmat
178 );
179}
180
181impl<'a, G, T> VibrationalCoordinateSymmetryOrbit<'a, G, Complex<T>>
182where
183 G: SymmetryGroupProperties,
184 T: Float + Scalar<Complex = Complex<T>>,
185 Complex<T>: ComplexFloat<Real = T> + Scalar<Real = T, Complex = Complex<T>> + Lapack,
186 VibrationalCoordinate<'a, Complex<T>>: SymmetryTransformable + Overlap<Complex<T>, Ix2>,
187{
188 fn_calc_xmat_complex!(
189 pub calc_xmat
202 );
203}
204
205impl<'a, G, T> Orbit<G, VibrationalCoordinate<'a, T>>
214 for VibrationalCoordinateSymmetryOrbit<'a, G, T>
215where
216 G: SymmetryGroupProperties,
217 T: ComplexFloat + fmt::Debug + Lapack,
218 VibrationalCoordinate<'a, T>: SymmetryTransformable,
219{
220 type OrbitIter = OrbitIterator<'a, G, VibrationalCoordinate<'a, T>>;
221
222 fn group(&self) -> &G {
223 self.group
224 }
225
226 fn origin(&self) -> &VibrationalCoordinate<'a, T> {
227 self.origin
228 }
229
230 fn iter(&self) -> Self::OrbitIter {
231 OrbitIterator::new(
232 self.group,
233 self.origin,
234 match self.symmetry_transformation_kind {
235 SymmetryTransformationKind::Spatial
236 | SymmetryTransformationKind::SpatialWithSpinTimeReversal => |op, vib| {
237 vib.sym_transform_spatial(op).with_context(|| {
240 format_err!(
241 "Unable to apply `{op}` spatially on the origin vibrational coordinate"
242 )
243 })
244 },
245 SymmetryTransformationKind::Spin => |op, vib| {
246 vib.sym_transform_spin(op).with_context(|| {
247 format_err!(
248 "Unable to apply `{op}` spin-wise on the origin vibrational coordinate"
249 )
250 })
251 },
252 SymmetryTransformationKind::SpinSpatial => |op, vib| {
253 vib.sym_transform_spin_spatial(op).with_context(|| {
254 format_err!(
255 "Unable to apply `{op}` spin-spatially on the origin vibrational coordinate"
256 )
257 })
258 },
259 },
260 )
261 }
262}
263
264impl<'a, G, T> RepAnalysis<G, VibrationalCoordinate<'a, T>, T, Ix2>
269 for VibrationalCoordinateSymmetryOrbit<'a, G, T>
270where
271 G: SymmetryGroupProperties,
272 G::CharTab: SubspaceDecomposable<T>,
273 T: Lapack
274 + ComplexFloat<Real = <T as Scalar>::Real>
275 + fmt::Debug
276 + Mul<<T as ComplexFloat>::Real, Output = T>,
277 <T as ComplexFloat>::Real: fmt::Debug
278 + Zero
279 + approx::RelativeEq<<T as ComplexFloat>::Real>
280 + approx::AbsDiffEq<Epsilon = <T as Scalar>::Real>,
281 VibrationalCoordinate<'a, T>: SymmetryTransformable,
282{
283 fn set_smat(&mut self, smat: Array2<T>) {
284 self.smat = Some(smat)
285 }
286
287 fn smat(&self) -> Option<&Array2<T>> {
288 self.smat.as_ref()
289 }
290
291 fn xmat(&self) -> &Array2<T> {
292 self.xmat
293 .as_ref()
294 .expect("Orbit overlap orthogonalisation matrix not found.")
295 }
296
297 fn norm_preserving_scalar_map(&self, i: usize) -> Result<fn(T) -> T, anyhow::Error> {
298 if self.origin.complex_symmetric() {
299 Err(format_err!("`norm_preserving_scalar_map` is currently not implemented for complex symmetric overlaps."))
300 } else {
301 if self
302 .group
303 .get_index(i)
304 .unwrap_or_else(|| panic!("Group operation index `{i}` not found."))
305 .contains_time_reversal()
306 {
307 Ok(ComplexFloat::conj)
308 } else {
309 Ok(|x| x)
310 }
311 }
312 }
313
314 fn integrality_threshold(&self) -> <T as ComplexFloat>::Real {
315 self.integrality_threshold
316 }
317
318 fn eigenvalue_comparison_mode(&self) -> &EigenvalueComparisonMode {
319 &self.eigenvalue_comparison_mode
320 }
321}