1use std::fmt;
4use std::ops::Mul;
5
6use anyhow::{self, Context, ensure, format_err};
7use approx;
8use derive_builder::Builder;
9use itertools::Itertools;
10use ndarray::{Array1, Array2, Axis, Ix2};
11use ndarray_linalg::{
12 UPLO,
13 eig::Eig,
14 eigh::Eigh,
15 types::{Lapack, Scalar},
16};
17use num_complex::{Complex, ComplexFloat};
18use num_traits::{Float, Zero};
19
20use crate::analysis::{
21 EigenvalueComparisonMode, Orbit, OrbitIterator, Overlap, RepAnalysis, fn_calc_xmat_complex,
22 fn_calc_xmat_real,
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 orbit_overlap_symmetry_threshold: <T as ComplexFloat>::Real,
122
123 symmetry_transformation_kind: SymmetryTransformationKind,
126
127 #[builder(setter(skip), default = "None")]
129 smat: Option<Array2<T>>,
130
131 #[builder(setter(skip), default = "None")]
134 pub(crate) smat_eigvals: Option<Array1<T>>,
135
136 #[builder(setter(skip), default = "None")]
141 xmat: Option<Array2<T>>,
142
143 pub(crate) eigenvalue_comparison_mode: EigenvalueComparisonMode,
146}
147
148impl<'a, G, T> VibrationalCoordinateSymmetryOrbit<'a, G, T>
153where
154 G: SymmetryGroupProperties + Clone,
155 T: ComplexFloat + fmt::Debug + Lapack,
156 VibrationalCoordinate<'a, T>: SymmetryTransformable,
157{
158 pub fn builder() -> VibrationalCoordinateSymmetryOrbitBuilder<'a, G, T> {
160 VibrationalCoordinateSymmetryOrbitBuilder::default()
161 }
162}
163
164impl<'a, G> VibrationalCoordinateSymmetryOrbit<'a, G, f64>
165where
166 G: SymmetryGroupProperties,
167{
168 fn_calc_xmat_real!(
169 pub calc_xmat
181 );
182}
183
184impl<'a, G, T> VibrationalCoordinateSymmetryOrbit<'a, G, Complex<T>>
185where
186 G: SymmetryGroupProperties,
187 T: Float + Scalar<Complex = Complex<T>>,
188 Complex<T>: ComplexFloat<Real = T> + Scalar<Real = T, Complex = Complex<T>> + Lapack,
189 VibrationalCoordinate<'a, Complex<T>>: SymmetryTransformable + Overlap<Complex<T>, Ix2>,
190{
191 fn_calc_xmat_complex!(
192 pub calc_xmat
205 );
206}
207
208impl<'a, G, T> Orbit<G, VibrationalCoordinate<'a, T>>
217 for VibrationalCoordinateSymmetryOrbit<'a, G, T>
218where
219 G: SymmetryGroupProperties,
220 T: ComplexFloat + fmt::Debug + Lapack,
221 VibrationalCoordinate<'a, T>: SymmetryTransformable,
222{
223 type OrbitIter = OrbitIterator<'a, G, VibrationalCoordinate<'a, T>>;
224
225 fn group(&self) -> &G {
226 self.group
227 }
228
229 fn origin(&self) -> &VibrationalCoordinate<'a, T> {
230 self.origin
231 }
232
233 fn iter(&self) -> Self::OrbitIter {
234 OrbitIterator::new(
235 self.group,
236 self.origin,
237 match self.symmetry_transformation_kind {
238 SymmetryTransformationKind::Spatial
239 | SymmetryTransformationKind::SpatialWithSpinTimeReversal => |op, vib| {
240 vib.sym_transform_spatial(op).with_context(|| {
243 format_err!(
244 "Unable to apply `{op}` spatially on the origin vibrational coordinate"
245 )
246 })
247 },
248 SymmetryTransformationKind::Spin => |op, vib| {
249 vib.sym_transform_spin(op).with_context(|| {
250 format_err!(
251 "Unable to apply `{op}` spin-wise on the origin vibrational coordinate"
252 )
253 })
254 },
255 SymmetryTransformationKind::SpinSpatial => |op, vib| {
256 vib.sym_transform_spin_spatial(op).with_context(|| {
257 format_err!(
258 "Unable to apply `{op}` spin-spatially on the origin vibrational coordinate"
259 )
260 })
261 },
262 },
263 )
264 }
265}
266
267impl<'a, G, T> RepAnalysis<G, VibrationalCoordinate<'a, T>, T, Ix2>
272 for VibrationalCoordinateSymmetryOrbit<'a, G, T>
273where
274 G: SymmetryGroupProperties,
275 G::CharTab: SubspaceDecomposable<T>,
276 T: Lapack
277 + ComplexFloat<Real = <T as Scalar>::Real>
278 + fmt::Debug
279 + Mul<<T as ComplexFloat>::Real, Output = T>,
280 <T as ComplexFloat>::Real: fmt::Debug
281 + Zero
282 + approx::RelativeEq<<T as ComplexFloat>::Real>
283 + approx::AbsDiffEq<Epsilon = <T as Scalar>::Real>,
284 VibrationalCoordinate<'a, T>: SymmetryTransformable,
285{
286 fn set_smat(&mut self, smat: Array2<T>) {
287 self.smat = Some(smat)
288 }
289
290 fn smat(&self) -> Option<&Array2<T>> {
291 self.smat.as_ref()
292 }
293
294 fn xmat(&self) -> &Array2<T> {
295 self.xmat
296 .as_ref()
297 .expect("Orbit overlap orthogonalisation matrix not found.")
298 }
299
300 fn norm_preserving_scalar_map(&self, i: usize) -> Result<fn(T) -> T, anyhow::Error> {
301 if self.origin.complex_symmetric() {
302 Err(format_err!(
303 "`norm_preserving_scalar_map` is currently not implemented for complex-symmetric overlaps. This thus precludes the use of the Cayley table to speed up the computation of the orbit overlap matrix."
304 ))
305 } else if self
306 .group
307 .get_index(i)
308 .unwrap_or_else(|| panic!("Group operation index `{i}` not found."))
309 .contains_time_reversal()
310 {
311 Ok(ComplexFloat::conj)
312 } else {
313 Ok(|x| x)
314 }
315 }
316
317 fn orbit_overlap_symmetry_threshold(&self) -> <T as ComplexFloat>::Real {
318 self.orbit_overlap_symmetry_threshold
319 }
320
321 fn integrality_threshold(&self) -> <T as ComplexFloat>::Real {
322 self.integrality_threshold
323 }
324
325 fn eigenvalue_comparison_mode(&self) -> &EigenvalueComparisonMode {
326 &self.eigenvalue_comparison_mode
327 }
328}