Skip to main content

qsym2/sandbox/target/real_space_function/
real_space_function_analysis.rs

1//! Implementation of symmetry analysis for real-space functions.
2
3use std::fmt;
4use std::ops::Mul;
5
6use anyhow::{self, Context, ensure, format_err};
7use approx;
8use derive_builder::Builder;
9use itertools::Itertools;
10use log;
11use nalgebra::Point3;
12use ndarray::{Array1, Array2, Axis, Ix1};
13use ndarray_linalg::{
14    UPLO,
15    eig::Eig,
16    eigh::Eigh,
17    types::{Lapack, Scalar},
18};
19use num_complex::{Complex, ComplexFloat};
20use num_traits::{Float, ToPrimitive, Zero};
21use rayon::prelude::*;
22
23use crate::analysis::{
24    EigenvalueComparisonMode, Orbit, OrbitIterator, Overlap, RepAnalysis, fn_calc_xmat_complex,
25    fn_calc_xmat_real,
26};
27use crate::auxiliary::misc::complex_modified_gram_schmidt;
28use crate::chartab::chartab_group::CharacterProperties;
29use crate::chartab::{DecompositionError, SubspaceDecomposable};
30use crate::io::format::{QSym2Output, log_subtitle, qsym2_output};
31use crate::sandbox::target::real_space_function::RealSpaceFunction;
32use crate::symmetry::symmetry_element::symmetry_operation::SpecialSymmetryTransformation;
33use crate::symmetry::symmetry_group::SymmetryGroupProperties;
34use crate::symmetry::symmetry_transformation::{SymmetryTransformable, SymmetryTransformationKind};
35
36// =======
37// Overlap
38// =======
39
40impl<T, F> Overlap<T, Ix1> for RealSpaceFunction<T, F>
41where
42    T: ComplexFloat + Lapack + Send + Sync,
43    F: Clone + Sync + Send + Fn(&Point3<f64>) -> T,
44{
45    fn complex_symmetric(&self) -> bool {
46        false
47    }
48
49    /// Computes the overlap between two real-space functions.
50    ///
51    /// No attempts are made to check that the grid points between the two real-space functions are
52    /// 'compatible'. The overlap is simply evaluated as
53    ///
54    /// ```math
55    ///     \sum_i [\hat{\iota} f_1(\mathbf{r}_{1i})]^* f_2(\mathbf{r}_{2i}) w_i,
56    /// ```
57    ///
58    /// where  $`w_i`$ are weight values.
59    ///
60    ///
61    /// # Errors
62    ///
63    /// Errors if `self` and `other` have mismatched numbers of grid points or if the number of
64    /// weight values does not match the number of grid points.
65    fn overlap(
66        &self,
67        other: &Self,
68        metric: Option<&Array1<T>>,
69        _: Option<&Array1<T>>,
70    ) -> Result<T, anyhow::Error> {
71        let weight = metric.ok_or_else(|| {
72            format_err!("No weights found for `RealSpaceFunction` overlap calculation.")
73        })?;
74
75        ensure!(
76            self.grid_points.len() == other.grid_points.len(),
77            "Inconsistent number of grid points between `self` and `other`."
78        );
79        ensure!(
80            self.grid_points.len() == weight.len(),
81            "Inconsistent number of weight values and grid points."
82        );
83
84        let overlap = (0..weight.len())
85            .into_par_iter()
86            .map(|i| {
87                let s_pt = self.grid_points[i];
88                let o_pt = other.grid_points[i];
89                let w = weight[i];
90                match (self.complex_conjugated, other.complex_conjugated) {
91                    (false, false) => self.function()(&s_pt).conj() * other.function()(&o_pt) * w,
92                    (false, true) => {
93                        self.function()(&s_pt).conj() * other.function()(&o_pt).conj() * w
94                    }
95                    (true, false) => self.function()(&s_pt) * other.function()(&o_pt) * w,
96                    (true, true) => self.function()(&s_pt) * other.function()(&o_pt).conj() * w,
97                }
98            })
99            .sum();
100        Ok(overlap)
101    }
102
103    /// Returns the mathematical definition of the overlap between two real-space functions.
104    fn overlap_definition(&self) -> String {
105        let k = if self.complex_symmetric() { "κ " } else { "" };
106        format!("⟨{k}f_1|f_2⟩ = ∫ [{k}f_1(r)]* w(r) f_2(r) dr    where w(r) is a required weight")
107    }
108}
109
110// ==============================
111// RealSpaceFunctionSymmetryOrbit
112// ==============================
113
114// -----------------
115// Struct definition
116// -----------------
117
118/// Structure to manage symmetry orbits (*i.e.* orbits generated by symmetry groups) of real-space
119/// functions.
120#[derive(Builder, Clone)]
121pub struct RealSpaceFunctionSymmetryOrbit<'a, G, T, F>
122where
123    G: SymmetryGroupProperties,
124    T: ComplexFloat + fmt::Debug + Lapack,
125    F: Fn(&Point3<f64>) -> T,
126    RealSpaceFunction<T, F>: SymmetryTransformable,
127{
128    /// The generating symmetry group.
129    group: &'a G,
130
131    /// The origin [`RealSpaceFunction`] of the orbit.
132    origin: &'a RealSpaceFunction<T, F>,
133
134    /// The threshold for determining zero eigenvalues in the orbit overlap matrix.
135    pub(crate) linear_independence_threshold: <T as ComplexFloat>::Real,
136
137    /// The threshold for checking symmetry of the orbit overlap matrix.
138    orbit_overlap_symmetry_threshold: <T as ComplexFloat>::Real,
139
140    /// The threshold for determining if calculated multiplicities in representation analysis are
141    /// integral.
142    integrality_threshold: <T as ComplexFloat>::Real,
143
144    /// The kind of transformation determining the way the symmetry operations in the generating
145    /// group act on [`Self::origin`].
146    symmetry_transformation_kind: SymmetryTransformationKind,
147
148    /// The overlap matrix between the symmetry-equivalent real-space functions in the orbit.
149    #[builder(setter(skip), default = "None")]
150    smat: Option<Array2<T>>,
151
152    /// The eigenvalues of the overlap matrix between the symmetry-equivalent real-space functions
153    /// in the orbit.
154    #[builder(setter(skip), default = "None")]
155    pub(crate) smat_eigvals: Option<Array1<T>>,
156
157    /// The $`\mathbf{X}`$ matrix for the overlap matrix between the symmetry-equivalent real-space
158    /// functions in the orbit.
159    ///
160    /// See [`RepAnalysis::xmat`] for further information.
161    #[builder(setter(skip), default = "None")]
162    xmat: Option<Array2<T>>,
163
164    /// An enumerated type specifying the comparison mode for filtering out orbit overlap
165    /// eigenvalues.
166    eigenvalue_comparison_mode: EigenvalueComparisonMode,
167}
168
169// ----------------------------
170// Struct method implementation
171// ----------------------------
172
173impl<'a, G, T, F> RealSpaceFunctionSymmetryOrbit<'a, G, T, F>
174where
175    G: SymmetryGroupProperties + Clone,
176    T: ComplexFloat + fmt::Debug + Lapack,
177    F: Clone + Fn(&Point3<f64>) -> T,
178    RealSpaceFunction<T, F>: SymmetryTransformable,
179{
180    /// Returns a builder for constructing a new real-space function symmetry orbit.
181    pub fn builder() -> RealSpaceFunctionSymmetryOrbitBuilder<'a, G, T, F> {
182        RealSpaceFunctionSymmetryOrbitBuilder::default()
183    }
184}
185
186impl<'a, G, F> RealSpaceFunctionSymmetryOrbit<'a, G, f64, F>
187where
188    G: SymmetryGroupProperties + Clone,
189    F: Clone + Fn(&Point3<f64>) -> f64,
190{
191    fn_calc_xmat_real!(
192        /// Calculates the $`\mathbf{X}`$ matrix for real and symmetric overlap matrix
193        /// $`\mathbf{S}`$ between the symmetry-equivalent real-space functions in the orbit.
194        ///
195        /// The resulting $`\mathbf{X}`$ is stored in the orbit.
196        ///
197        /// # Arguments
198        ///
199        /// * `preserves_full_rank` - If `true`, when $`\mathbf{S}`$ is already of full rank, then
200        /// $`\mathbf{X}`$ is set to be the identity matrix to avoid mixing the orbit real-space
201        /// functions. If `false`, $`\mathbf{X}`$ also orthogonalises $`\mathbf{S}`$ even when it is
202        /// already of full rank.
203        pub calc_xmat
204    );
205}
206
207impl<'a, G, T, F> RealSpaceFunctionSymmetryOrbit<'a, G, Complex<T>, F>
208where
209    G: SymmetryGroupProperties + Clone,
210    T: Float + Scalar<Complex = Complex<T>>,
211    Complex<T>: ComplexFloat<Real = T> + Scalar<Real = T, Complex = Complex<T>> + Lapack,
212    F: Clone + Fn(&Point3<f64>) -> Complex<T>,
213    RealSpaceFunction<Complex<T>, F>: SymmetryTransformable + Overlap<Complex<T>, Ix1>,
214{
215    fn_calc_xmat_complex!(
216        /// Calculates the $`\mathbf{X}`$ matrix for complex and symmetric or Hermitian overlap
217        /// matrix $`\mathbf{S}`$ between the symmetry-equivalent real-space functions in the orbit.
218        ///
219        /// The resulting $`\mathbf{X}`$ is stored in the orbit.
220        ///
221        /// # Arguments
222        ///
223        /// * `preserves_full_rank` - If `true`, when $`\mathbf{S}`$ is already of full rank, then
224        /// $`\mathbf{X}`$ is set to be the identity matrix to avoid mixing the orbit real-space
225        /// functions. If `false`, $`\mathbf{X}`$ also orthogonalises $`\mathbf{S}`$ even when it is
226        /// already of full rank.
227        pub calc_xmat
228    );
229}
230
231// ---------------------
232// Trait implementations
233// ---------------------
234
235// ~~~~~
236// Orbit
237// ~~~~~
238
239impl<'a, G, T, F> Orbit<G, RealSpaceFunction<T, F>> for RealSpaceFunctionSymmetryOrbit<'a, G, T, F>
240where
241    G: SymmetryGroupProperties + Clone,
242    T: ComplexFloat + fmt::Debug + Lapack,
243    F: Fn(&Point3<f64>) -> T,
244    RealSpaceFunction<T, F>: SymmetryTransformable,
245{
246    type OrbitIter = OrbitIterator<'a, G, RealSpaceFunction<T, F>>;
247
248    fn group(&self) -> &'a G {
249        self.group
250    }
251
252    fn origin(&self) -> &RealSpaceFunction<T, F> {
253        self.origin
254    }
255
256    fn iter(&self) -> Self::OrbitIter {
257        OrbitIterator::new(
258            self.group,
259            self.origin,
260            match self.symmetry_transformation_kind {
261                SymmetryTransformationKind::Spatial => {
262                    |op, real_space_function| {
263                        real_space_function.sym_transform_spatial(op).with_context(|| {
264                        format!("Unable to apply `{op}` spatially on the origin real-space function")
265                    })
266                    }
267                }
268                SymmetryTransformationKind::SpatialWithSpinTimeReversal => {
269                    |op, real_space_function| {
270                        real_space_function.sym_transform_spatial_with_spintimerev(op).with_context(|| {
271                        format!("Unable to apply `{op}` spatially (with spin-including time-reversal) on the origin real-space function")
272                    })
273                    }
274                }
275                SymmetryTransformationKind::Spin => |op, real_space_function| {
276                    real_space_function.sym_transform_spin(op).with_context(|| {
277                        format!(
278                            "Unable to apply `{op}` spin-wise on the origin real-space function"
279                        )
280                    })
281                },
282                SymmetryTransformationKind::SpinSpatial => |op, real_space_function| {
283                    real_space_function.sym_transform_spin_spatial(op).with_context(|| {
284                        format!("Unable to apply `{op}` spin-spatially on the origin real-space function")
285                    })
286                },
287            },
288        )
289    }
290}
291
292// ~~~~~~~~~~~
293// RepAnalysis
294// ~~~~~~~~~~~
295
296impl<'a, G, T, F> RepAnalysis<G, RealSpaceFunction<T, F>, T, Ix1>
297    for RealSpaceFunctionSymmetryOrbit<'a, G, T, F>
298where
299    G: SymmetryGroupProperties + Clone,
300    G::CharTab: SubspaceDecomposable<T>,
301    T: Lapack
302        + ComplexFloat<Real = <T as Scalar>::Real>
303        + fmt::Debug
304        + Send
305        + Sync
306        + Mul<<T as ComplexFloat>::Real, Output = T>,
307    <T as ComplexFloat>::Real: fmt::Debug
308        + Zero
309        + From<u16>
310        + ToPrimitive
311        + approx::RelativeEq<<T as ComplexFloat>::Real>
312        + approx::AbsDiffEq<Epsilon = <T as Scalar>::Real>,
313    F: Clone + Sync + Send + Fn(&Point3<f64>) -> T,
314    RealSpaceFunction<T, F>: SymmetryTransformable,
315{
316    fn set_smat(&mut self, smat: Array2<T>) {
317        self.smat = Some(smat)
318    }
319
320    fn smat(&self) -> Option<&Array2<T>> {
321        self.smat.as_ref()
322    }
323
324    fn xmat(&self) -> &Array2<T> {
325        self.xmat
326            .as_ref()
327            .expect("Orbit overlap orthogonalisation matrix not found.")
328    }
329
330    fn norm_preserving_scalar_map(&self, i: usize) -> Result<fn(T) -> T, anyhow::Error> {
331        if self.origin.complex_symmetric() {
332            Err(format_err!(
333                "`norm_preserving_scalar_map` is currently not implemented for complex symmetric overlaps."
334            ))
335        } else if self
336            .group
337            .get_index(i)
338            .unwrap_or_else(|| panic!("Group operation index `{i}` not found."))
339            .contains_time_reversal()
340        {
341            Ok(ComplexFloat::conj)
342        } else {
343            Ok(|x| x)
344        }
345    }
346
347    fn orbit_overlap_symmetry_threshold(&self) -> <T as ComplexFloat>::Real {
348        self.orbit_overlap_symmetry_threshold
349    }
350
351    fn integrality_threshold(&self) -> <T as ComplexFloat>::Real {
352        self.integrality_threshold
353    }
354
355    fn eigenvalue_comparison_mode(&self) -> &EigenvalueComparisonMode {
356        &self.eigenvalue_comparison_mode
357    }
358
359    /// Reduces the representation or corepresentation spanned by the real-space functions in the
360    /// orbit to a direct sum of the irreducible representations or corepresentations of the
361    /// generating symmetry group.
362    ///
363    /// # Returns
364    ///
365    /// The decomposed result.
366    ///
367    /// # Errors
368    ///
369    /// Errors if the decomposition fails, *e.g.* because one or more calculated multiplicities
370    /// are non-integral.
371    fn analyse_rep(
372        &self,
373    ) -> Result<
374        <<G as CharacterProperties>::CharTab as SubspaceDecomposable<T>>::Decomposition,
375        DecompositionError,
376    > {
377        log::debug!("Analysing representation symmetry for a real-space function...");
378        let chis = self
379            .calc_characters()
380            .map_err(|err| DecompositionError(err.to_string()))?;
381        log::debug!("Characters calculated.");
382        log_subtitle("Real-space function orbit characters");
383        qsym2_output!("");
384        self.characters_to_string(&chis, self.integrality_threshold)
385            .log_output_display();
386        qsym2_output!("");
387
388        let res = self.group().character_table().reduce_characters(
389            &chis.iter().map(|(cc, chi)| (cc, *chi)).collect::<Vec<_>>(),
390            self.integrality_threshold(),
391        );
392        log::debug!("Characters reduced.");
393        log::debug!("Analysing representation symmetry for a real-space function... Done.");
394        res
395    }
396}