Skip to main content

qsym2/drivers/representation_analysis/
vibrational_coordinate.rs

1//! Driver for symmetry analysis of vibrational coordinates.
2
3use std::fmt;
4use std::ops::Mul;
5
6use anyhow::{self, bail, format_err};
7use derive_builder::Builder;
8use duplicate::duplicate_item;
9use ndarray_linalg::types::Lapack;
10use num_complex::{Complex, ComplexFloat};
11use num_traits::Float;
12use rayon::prelude::*;
13use serde::{Deserialize, Serialize};
14
15use crate::analysis::{EigenvalueComparisonMode, RepAnalysis};
16use crate::chartab::SubspaceDecomposable;
17use crate::chartab::chartab_group::CharacterProperties;
18use crate::drivers::QSym2Driver;
19use crate::drivers::representation_analysis::angular_function::{
20    AngularFunctionRepAnalysisParams, find_angular_function_representation,
21    find_spinor_function_representation,
22};
23use crate::drivers::representation_analysis::{
24    CharacterTableDisplay, MagneticSymmetryAnalysisKind, fn_construct_magnetic_group,
25    fn_construct_unitary_group, log_cc_transversal,
26};
27use crate::drivers::symmetry_group_detection::SymmetryGroupDetectionResult;
28use crate::group::{GroupProperties, MagneticRepresentedGroup, UnitaryRepresentedGroup};
29use crate::io::format::{
30    QSym2Output, log_subtitle, nice_bool, qsym2_output, write_subtitle, write_title,
31};
32use crate::symmetry::symmetry_group::{
33    MagneticRepresentedSymmetryGroup, SymmetryGroupProperties, UnitaryRepresentedSymmetryGroup,
34};
35use crate::symmetry::symmetry_transformation::SymmetryTransformationKind;
36use crate::target::vibration::VibrationalCoordinateCollection;
37use crate::target::vibration::vibration_analysis::VibrationalCoordinateSymmetryOrbit;
38
39#[cfg(test)]
40#[path = "vibrational_coordinate_tests.rs"]
41mod vibrational_coordinate_tests;
42
43// ==================
44// Struct definitions
45// ==================
46
47// ----------
48// Parameters
49// ----------
50
51const fn default_true() -> bool {
52    true
53}
54const fn default_symbolic() -> Option<CharacterTableDisplay> {
55    Some(CharacterTableDisplay::Symbolic)
56}
57
58/// Structure containing control parameters for vibrational coordinate representation analysis.
59#[derive(Clone, Builder, Debug, Serialize, Deserialize)]
60pub struct VibrationalCoordinateRepAnalysisParams<T: From<f64>> {
61    /// Threshold for checking if subspace multiplicities are integral.
62    pub integrality_threshold: T,
63
64    /// Threshold for determining zero eigenvalues in the orbit overlap matrix.
65    pub linear_independence_threshold: T,
66
67    /// Threshold for checking symmetry of the orbit overlap matrix.
68    pub orbit_overlap_symmetry_threshold: f64,
69
70    /// Option indicating if the magnetic group is to be used for symmetry analysis, and if so,
71    /// whether unitary representations or unitary-antiunitary corepresentations should be used.
72    #[builder(default = "None")]
73    #[serde(default)]
74    pub use_magnetic_group: Option<MagneticSymmetryAnalysisKind>,
75
76    /// Boolean indicating if the double group is to be used for symmetry analysis.
77    #[builder(default = "false")]
78    #[serde(default)]
79    pub use_double_group: bool,
80
81    /// Boolean indicating if the Cayley table of the group, if available, should be used to speed
82    /// up the computation of orbit overlap matrices.
83    #[builder(default = "true")]
84    #[serde(default = "default_true")]
85    pub use_cayley_table: bool,
86
87    /// The kind of symmetry transformation to be applied on the reference vibrational coordinate to
88    /// generate the orbit for symmetry analysis.
89    #[builder(default = "SymmetryTransformationKind::Spatial")]
90    #[serde(default)]
91    pub symmetry_transformation_kind: SymmetryTransformationKind,
92
93    /// Option indicating if the character table of the group used for symmetry analysis is to be
94    /// printed out.
95    #[builder(default = "Some(CharacterTableDisplay::Symbolic)")]
96    #[serde(default = "default_symbolic")]
97    pub write_character_table: Option<CharacterTableDisplay>,
98
99    /// The comparison mode for filtering out orbit overlap eigenvalues.
100    #[builder(default = "EigenvalueComparisonMode::Modulus")]
101    #[serde(default)]
102    pub eigenvalue_comparison_mode: EigenvalueComparisonMode,
103
104    /// The finite order to which any infinite-order symmetry element is reduced, so that a finite
105    /// subgroup of an infinite group can be used for the symmetry analysis.
106    #[builder(default = "None")]
107    #[serde(default)]
108    pub infinite_order_to_finite: Option<u32>,
109}
110
111impl<T> VibrationalCoordinateRepAnalysisParams<T>
112where
113    T: Float + From<f64>,
114{
115    /// Returns a builder to construct a [`VibrationalCoordinateRepAnalysisParams`] structure.
116    pub fn builder() -> VibrationalCoordinateRepAnalysisParamsBuilder<T> {
117        VibrationalCoordinateRepAnalysisParamsBuilder::default()
118    }
119}
120
121impl Default for VibrationalCoordinateRepAnalysisParams<f64> {
122    fn default() -> Self {
123        Self::builder()
124            .integrality_threshold(1e-7)
125            .linear_independence_threshold(1e-7)
126            .orbit_overlap_symmetry_threshold(1e-7)
127            .build()
128            .expect("Unable to construct a default `VibrationalCoordinateRepAnalysisParams<f64>`.")
129    }
130}
131
132impl<T> fmt::Display for VibrationalCoordinateRepAnalysisParams<T>
133where
134    T: From<f64> + fmt::LowerExp + fmt::Debug,
135{
136    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
137        writeln!(
138            f,
139            "Integrality threshold: {:.3e}",
140            self.integrality_threshold
141        )?;
142        writeln!(
143            f,
144            "Linear independence threshold: {:.3e}",
145            self.linear_independence_threshold
146        )?;
147        writeln!(
148            f,
149            "Orbit-overlap symmetry threshold: {:.3e}",
150            self.orbit_overlap_symmetry_threshold
151        )?;
152        writeln!(
153            f,
154            "Orbit eigenvalue comparison mode: {}",
155            self.eigenvalue_comparison_mode
156        )?;
157        writeln!(f)?;
158        writeln!(
159            f,
160            "Use magnetic group for analysis: {}",
161            match self.use_magnetic_group {
162                None => "no",
163                Some(MagneticSymmetryAnalysisKind::Representation) =>
164                    "yes, using unitary representations",
165                Some(MagneticSymmetryAnalysisKind::Corepresentation) =>
166                    "yes, using magnetic corepresentations",
167            }
168        )?;
169        writeln!(
170            f,
171            "Use double group for analysis: {}",
172            nice_bool(self.use_double_group)
173        )?;
174        writeln!(
175            f,
176            "Use Cayley table for orbit overlap matrices: {}",
177            nice_bool(self.use_cayley_table)
178        )?;
179        if let Some(finite_order) = self.infinite_order_to_finite {
180            writeln!(f, "Infinite order to finite: {finite_order}")?;
181        }
182        writeln!(
183            f,
184            "Symmetry transformation kind: {}",
185            self.symmetry_transformation_kind
186        )?;
187        writeln!(f)?;
188        writeln!(
189            f,
190            "Write character table: {}",
191            if let Some(chartab_display) = self.write_character_table.as_ref() {
192                format!("yes, {}", chartab_display.to_string().to_lowercase())
193            } else {
194                "no".to_string()
195            }
196        )?;
197
198        Ok(())
199    }
200}
201
202// ------
203// Result
204// ------
205
206/// Structure to contain vibrational coordinate representation analysis results.
207#[derive(Clone, Builder)]
208pub struct VibrationalCoordinateRepAnalysisResult<'a, G, T>
209where
210    G: SymmetryGroupProperties + Clone,
211    G::CharTab: SubspaceDecomposable<T>,
212    T: ComplexFloat + Lapack,
213    <T as ComplexFloat>::Real: From<f64> + fmt::LowerExp + fmt::Debug,
214{
215    /// The control parameters used to obtain this set of vibrational coordinate representation
216    /// analysis results.
217    parameters: &'a VibrationalCoordinateRepAnalysisParams<<T as ComplexFloat>::Real>,
218
219    /// The collection of vibrational coordinates being analysed.
220    vibrational_coordinate_collection: &'a VibrationalCoordinateCollection<'a, T>,
221
222    /// The group used for the representation analysis.
223    group: G,
224
225    /// The deduced symmetries of the vibrational coordinates.
226    vib_symmetries: Vec<Result<<G::CharTab as SubspaceDecomposable<T>>::Decomposition, String>>,
227
228    /// The overlap eigenvalues above and below the linear independence threshold for each
229    /// vibrational coordinate symmetry deduction.
230    vib_symmetries_thresholds: Option<Vec<(Option<T>, Option<T>)>>,
231}
232
233impl<'a, G, T> VibrationalCoordinateRepAnalysisResult<'a, G, T>
234where
235    G: SymmetryGroupProperties + Clone,
236    G::CharTab: SubspaceDecomposable<T>,
237    T: ComplexFloat + Lapack,
238    <T as ComplexFloat>::Real: From<f64> + fmt::LowerExp + fmt::Debug,
239{
240    /// Returns a builder to construct a new [`VibrationalCoordinateRepAnalysisResultBuilder`]
241    /// structure.
242    pub fn builder() -> VibrationalCoordinateRepAnalysisResultBuilder<'a, G, T> {
243        VibrationalCoordinateRepAnalysisResultBuilder::default()
244    }
245
246    /// Returns the control parameters used to obtain this set of vibrational coordinate
247    /// representation analysis results.
248    pub fn parameters(&self) -> &VibrationalCoordinateRepAnalysisParams<<T as ComplexFloat>::Real> {
249        self.parameters
250    }
251
252    /// Returns the collection of vibrational coordinates being analysed.
253    pub fn vibrational_coordinate_collection(&self) -> &VibrationalCoordinateCollection<'a, T> {
254        self.vibrational_coordinate_collection
255    }
256}
257
258impl<'a, G, T> fmt::Display for VibrationalCoordinateRepAnalysisResult<'a, G, T>
259where
260    G: SymmetryGroupProperties + Clone,
261    G::CharTab: SubspaceDecomposable<T>,
262    T: ComplexFloat + Lapack,
263    <T as ComplexFloat>::Real: From<f64> + fmt::LowerExp + fmt::Debug + fmt::Display,
264{
265    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
266        write_subtitle(f, "Orbit-based symmetry analysis results")?;
267        writeln!(f)?;
268        writeln!(
269            f,
270            "> Group: {} ({})",
271            self.group
272                .finite_subgroup_name()
273                .map(|subgroup_name| format!("{} > {}", self.group.name(), subgroup_name))
274                .unwrap_or(self.group.name()),
275            self.group.group_type().to_string().to_lowercase()
276        )?;
277        writeln!(f)?;
278
279        let vib_index_length =
280            (usize::try_from(self.vibrational_coordinate_collection.n_modes().ilog10())
281                .unwrap_or(4)
282                + 2)
283            .max(4);
284        let vib_frequency_length = self
285            .vibrational_coordinate_collection
286            .frequencies()
287            .iter()
288            .map(|freq| format!("{freq:+.3}").chars().count())
289            .max()
290            .unwrap_or(9)
291            .max(9);
292        let vib_symmetry_length = self
293            .vib_symmetries
294            .iter()
295            .map(|vib_sym_res| {
296                vib_sym_res
297                    .as_ref()
298                    .map(|sym| sym.to_string())
299                    .unwrap_or("--".to_string())
300                    .chars()
301                    .count()
302            })
303            .max()
304            .unwrap_or(8)
305            .max(8);
306
307        let vib_eig_above_length: usize = self
308            .vib_symmetries_thresholds
309            .as_ref()
310            .map(|vib_symmetries_thresholds| {
311                vib_symmetries_thresholds
312                    .iter()
313                    .map(|(above, _)| {
314                        above
315                            .as_ref()
316                            .map(|eig| format!("{eig:+.3e}"))
317                            .unwrap_or("--".to_string())
318                            .chars()
319                            .count()
320                    })
321                    .max()
322                    .unwrap_or(10)
323                    .max(10)
324            })
325            .unwrap_or(10);
326        let vib_eig_below_length: usize = self
327            .vib_symmetries_thresholds
328            .as_ref()
329            .map(|vib_symmetries_thresholds| {
330                vib_symmetries_thresholds
331                    .iter()
332                    .map(|(_, below)| {
333                        below
334                            .as_ref()
335                            .map(|eig| format!("{eig:+.3e}"))
336                            .unwrap_or("--".to_string())
337                            .chars()
338                            .count()
339                    })
340                    .max()
341                    .unwrap_or(10)
342                    .max(10)
343            })
344            .unwrap_or(10);
345
346        writeln!(f, "> Vibrational coordinate results")?;
347
348        writeln!(
349            f,
350            "{}",
351            "┈".repeat(
352                10 + vib_index_length
353                    + vib_frequency_length
354                    + vib_symmetry_length
355                    + vib_eig_above_length
356                    + vib_eig_below_length
357            )
358        )?;
359        writeln!(
360            f,
361            " {:>vib_index_length$}  {:<vib_frequency_length$}  {:<vib_symmetry_length$}  {:<vib_eig_above_length$}  Eig. below",
362            "Mode", "Frequency", "Symmetry", "Eig. above"
363        )?;
364        writeln!(
365            f,
366            "{}",
367            "┈".repeat(
368                10 + vib_index_length
369                    + vib_frequency_length
370                    + vib_symmetry_length
371                    + vib_eig_above_length
372                    + vib_eig_below_length
373            )
374        )?;
375
376        for (vibi, vib_sym) in self.vib_symmetries.iter().enumerate() {
377            let vib_frequency_str = self
378                .vibrational_coordinate_collection
379                .frequencies()
380                .get(vibi)
381                .map(|freq| format!("{freq:>+vib_frequency_length$.3}"))
382                .unwrap_or("--".to_string());
383            let vib_sym_str = vib_sym
384                .as_ref()
385                .map(|sym| sym.to_string())
386                .unwrap_or("--".to_string());
387            let (eig_above_str, eig_below_str) = self
388                .vib_symmetries_thresholds
389                .as_ref()
390                .map(|vib_symmetries_thresholds| {
391                    vib_symmetries_thresholds
392                        .get(vibi)
393                        .map(|(eig_above_opt, eig_below_opt)| {
394                            (
395                                eig_above_opt
396                                    .map(|eig_above| format!("{eig_above:>+.3e}"))
397                                    .unwrap_or("--".to_string()),
398                                eig_below_opt
399                                    .map(|eig_below| format!("{eig_below:>+.3e}"))
400                                    .unwrap_or("--".to_string()),
401                            )
402                        })
403                        .unwrap_or(("--".to_string(), "--".to_string()))
404                })
405                .unwrap_or(("--".to_string(), "--".to_string()));
406            writeln!(
407                f,
408                " {vibi:>vib_index_length$}  \
409                {vib_frequency_str:<vib_frequency_length$}  \
410                {vib_sym_str:<vib_symmetry_length$}  \
411                {eig_above_str:<vib_eig_above_length$}  \
412                {eig_below_str}"
413            )?;
414        }
415        writeln!(
416            f,
417            "{}",
418            "┈".repeat(
419                10 + vib_index_length
420                    + vib_frequency_length
421                    + vib_symmetry_length
422                    + vib_eig_above_length
423                    + vib_eig_below_length
424            )
425        )?;
426        writeln!(f)?;
427
428        Ok(())
429    }
430}
431
432impl<'a, G, T> fmt::Debug for VibrationalCoordinateRepAnalysisResult<'a, G, T>
433where
434    G: SymmetryGroupProperties + Clone,
435    G::CharTab: SubspaceDecomposable<T>,
436    T: ComplexFloat + Lapack,
437    <T as ComplexFloat>::Real: From<f64> + fmt::LowerExp + fmt::Debug + fmt::Display,
438{
439    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
440        writeln!(f, "{self}")
441    }
442}
443
444impl<'a, G, T> VibrationalCoordinateRepAnalysisResult<'a, G, T>
445where
446    G: SymmetryGroupProperties + Clone,
447    G::CharTab: SubspaceDecomposable<T>,
448    T: ComplexFloat + Lapack,
449    <T as ComplexFloat>::Real: From<f64> + fmt::LowerExp + fmt::Debug + fmt::Display,
450{
451    /// Returns the vibrational coordinate symmetries obtained from the analysis result.
452    pub fn vibrational_coordinate_symmetries(
453        &self,
454    ) -> &Vec<Result<<G::CharTab as SubspaceDecomposable<T>>::Decomposition, String>> {
455        &self.vib_symmetries
456    }
457}
458
459// ------
460// Driver
461// ------
462
463// ~~~~~~~~~~~~~~~~~
464// Struct definition
465// ~~~~~~~~~~~~~~~~~
466
467/// Driver structure for performing representation analysis on vibrational coordinates.
468#[derive(Clone, Builder)]
469#[builder(build_fn(validate = "Self::validate"))]
470pub struct VibrationalCoordinateRepAnalysisDriver<'a, G, T>
471where
472    G: SymmetryGroupProperties + Clone,
473    G::CharTab: SubspaceDecomposable<T>,
474    T: ComplexFloat + Lapack,
475    <T as ComplexFloat>::Real: From<f64> + fmt::LowerExp + fmt::Debug,
476{
477    /// The control parameters for vibrational coordinate representation analysis.
478    parameters: &'a VibrationalCoordinateRepAnalysisParams<<T as ComplexFloat>::Real>,
479
480    /// The collection of vibrational coordinates to be analysed.
481    vibrational_coordinate_collection: &'a VibrationalCoordinateCollection<'a, T>,
482
483    /// The result from symmetry-group detection on the underlying molecular structure of the
484    /// vibrational coordinates.
485    symmetry_group: &'a SymmetryGroupDetectionResult,
486
487    /// The control parameters for symmetry analysis of angular functions.
488    angular_function_parameters: &'a AngularFunctionRepAnalysisParams,
489
490    /// The result of the vibrational coordinate representation analysis.
491    #[builder(setter(skip), default = "None")]
492    result: Option<VibrationalCoordinateRepAnalysisResult<'a, G, T>>,
493}
494
495impl<'a, G, T> VibrationalCoordinateRepAnalysisDriverBuilder<'a, G, T>
496where
497    G: SymmetryGroupProperties + Clone,
498    G::CharTab: SubspaceDecomposable<T>,
499    T: ComplexFloat + Lapack,
500    <T as ComplexFloat>::Real: From<f64> + fmt::LowerExp + fmt::Debug,
501{
502    fn validate(&self) -> Result<(), String> {
503        let params = self.parameters.ok_or(
504            "No vibrational coordinate representation analysis parameters found.".to_string(),
505        )?;
506
507        let sym_res = self
508            .symmetry_group
509            .ok_or("No symmetry group information found.".to_string())?;
510
511        let sym = if params.use_magnetic_group.is_some() {
512            sym_res
513                .magnetic_symmetry
514                .as_ref()
515                .ok_or("Magnetic symmetry requested for representation analysis, but no magnetic symmetry found.")?
516        } else {
517            &sym_res.unitary_symmetry
518        };
519
520        if sym.is_infinite() && params.infinite_order_to_finite.is_none() {
521            Err(format!(
522                "Representation analysis cannot be performed using the entirety of the infinite group `{}`. \
523                    Consider setting the parameter `infinite_order_to_finite` to restrict to a finite subgroup instead.",
524                sym.group_name
525                    .as_ref()
526                    .expect("No symmetry group name found.")
527            ))
528        } else {
529            Ok(())
530        }
531    }
532}
533
534// ~~~~~~~~~~~~~~~~~~~~~~
535// Struct implementations
536// ~~~~~~~~~~~~~~~~~~~~~~
537
538// Generic for all symmetry groups G and determinant numeric type T
539// ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
540
541impl<'a, G, T> VibrationalCoordinateRepAnalysisDriver<'a, G, T>
542where
543    G: SymmetryGroupProperties + Clone,
544    G::CharTab: SubspaceDecomposable<T>,
545    T: ComplexFloat + Lapack,
546    <T as ComplexFloat>::Real: From<f64> + fmt::LowerExp + fmt::Debug,
547{
548    /// Returns a builder to construct a [`VibrationalCoordinateRepAnalysisDriver`] structure.
549    pub fn builder() -> VibrationalCoordinateRepAnalysisDriverBuilder<'a, G, T> {
550        VibrationalCoordinateRepAnalysisDriverBuilder::default()
551    }
552}
553
554// Specific for unitary-represented symmetry groups, but generic for determinant numeric type T
555// ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
556
557impl<'a, T> VibrationalCoordinateRepAnalysisDriver<'a, UnitaryRepresentedSymmetryGroup, T>
558where
559    T: ComplexFloat + Lapack + Sync + Send,
560    <T as ComplexFloat>::Real: From<f64> + fmt::LowerExp + fmt::Debug + Sync + Send,
561    for<'b> Complex<f64>: Mul<&'b T, Output = Complex<f64>>,
562{
563    fn_construct_unitary_group!(
564        /// Constructs the unitary-represented group (which itself can be unitary or magnetic) ready
565        /// for vibrational coordinate representation analysis.
566        construct_unitary_group
567    );
568}
569
570// Specific for magnetic-represented symmetry groups, but generic for determinant numeric type T
571// ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
572
573impl<'a, T> VibrationalCoordinateRepAnalysisDriver<'a, MagneticRepresentedSymmetryGroup, T>
574where
575    T: ComplexFloat + Lapack + Sync + Send,
576    <T as ComplexFloat>::Real: From<f64> + Sync + Send + fmt::LowerExp + fmt::Debug,
577    for<'b> Complex<f64>: Mul<&'b T, Output = Complex<f64>>,
578{
579    fn_construct_magnetic_group!(
580        /// Constructs the magnetic-represented group (which itself can only be magnetic) ready for
581        /// vibrational coordinate corepresentation analysis.
582        construct_magnetic_group
583    );
584}
585
586// Specific for unitary-represented and magnetic-represented symmetry groups and determinant numeric types f64 and C128
587// ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
588
589#[duplicate_item(
590    duplicate!{
591        [ dtype_nested; [f64]; [Complex<f64>] ]
592        [
593            gtype_ [ UnitaryRepresentedSymmetryGroup ]
594            dtype_ [ dtype_nested ]
595            doc_sub_ [ "Performs representation analysis using a unitary-represented group and stores the result." ]
596            analyse_fn_ [ analyse_representation ]
597            construct_group_ [ self.construct_unitary_group()? ]
598        ]
599    }
600    duplicate!{
601        [ dtype_nested; [f64]; [Complex<f64>] ]
602        [
603            gtype_ [ MagneticRepresentedSymmetryGroup ]
604            dtype_ [ dtype_nested ]
605            doc_sub_ [ "Performs corepresentation analysis using a magnetic-represented group and stores the result." ]
606            analyse_fn_ [ analyse_corepresentation ]
607            construct_group_ [ self.construct_magnetic_group()? ]
608        ]
609    }
610)]
611impl<'a> VibrationalCoordinateRepAnalysisDriver<'a, gtype_, dtype_> {
612    #[doc = doc_sub_]
613    fn analyse_fn_(&mut self) -> Result<(), anyhow::Error> {
614        let params = self.parameters;
615        let group = construct_group_;
616        log_cc_transversal(&group);
617        let _ = find_angular_function_representation(&group, self.angular_function_parameters);
618        if group.is_double_group() {
619            let _ = find_spinor_function_representation(&group, self.angular_function_parameters);
620        }
621
622        let (vib_symmetries, vib_symmetries_thresholds) = {
623            let vibs = self
624                .vibrational_coordinate_collection
625                .to_vibrational_coordinates();
626            let vib_orbits = vibs.par_iter().map(|vib| {
627                let mut vib_orbit = VibrationalCoordinateSymmetryOrbit::builder()
628                    .group(&group)
629                    .origin(vib)
630                    .integrality_threshold(params.integrality_threshold)
631                    .linear_independence_threshold(params.linear_independence_threshold)
632                    .orbit_overlap_symmetry_threshold(params.orbit_overlap_symmetry_threshold)
633                    .symmetry_transformation_kind(params.symmetry_transformation_kind.clone())
634                    .eigenvalue_comparison_mode(params.eigenvalue_comparison_mode.clone())
635                    .build()?;
636                vib_orbit
637                    .calc_smat(None, None, params.use_cayley_table)
638                    .and_then(|orbit| orbit.calc_xmat(false))?;
639                Ok::<_, anyhow::Error>(vib_orbit)
640            });
641            let (vib_symmetries, vib_symmetries_thresholds): (Vec<_>, Vec<_>) = vib_orbits
642                .map(|vib_orbit_res| {
643                    vib_orbit_res
644                        .as_ref()
645                        .map(|vib_orbit| {
646                            let sym_res = vib_orbit.analyse_rep().map_err(|err| err.to_string());
647                            let eigs = vib_orbit
648                                .smat_eigvals
649                                .as_ref()
650                                .map(|eigvals| {
651                                    let mut eigvals_vec = eigvals.iter().collect::<Vec<_>>();
652                                    match vib_orbit.eigenvalue_comparison_mode {
653                                        EigenvalueComparisonMode::Modulus => {
654                                            eigvals_vec.sort_by(|a, b| {
655                                                a.abs().partial_cmp(&b.abs()).expect("Unable to compare two eigenvalues based on their moduli.")
656                                            });
657                                        }
658                                        EigenvalueComparisonMode::Real => {
659                                            eigvals_vec.sort_by(|a, b| {
660                                                a.re().partial_cmp(&b.re()).expect("Unable to compare two eigenvalues based on their real parts.")
661                                            });
662                                        }
663                                    }
664                                    let eigval_above = match vib_orbit.eigenvalue_comparison_mode {
665                                        EigenvalueComparisonMode::Modulus => eigvals_vec
666                                            .iter()
667                                            .find(|val| {
668                                                val.abs() >= vib_orbit.linear_independence_threshold
669                                            })
670                                            .copied()
671                                            .copied(),
672                                        EigenvalueComparisonMode::Real => eigvals_vec
673                                            .iter()
674                                            .find(|val| {
675                                                val.re() >= vib_orbit.linear_independence_threshold
676                                            })
677                                            .copied()
678                                            .copied(),
679                                    };
680                                    eigvals_vec.reverse();
681                                    let eigval_below = match vib_orbit.eigenvalue_comparison_mode {
682                                        EigenvalueComparisonMode::Modulus => eigvals_vec
683                                            .iter()
684                                            .find(|val| {
685                                                val.abs() < vib_orbit.linear_independence_threshold
686                                            })
687                                            .copied()
688                                            .copied(),
689                                        EigenvalueComparisonMode::Real => eigvals_vec
690                                            .iter()
691                                            .find(|val| {
692                                                val.re() < vib_orbit.linear_independence_threshold
693                                            })
694                                            .copied()
695                                            .copied(),
696                                    };
697                                    (eigval_above, eigval_below)
698                                })
699                                .unwrap_or_else(|| (None, None));
700                            (sym_res, eigs)
701                        })
702                        .unwrap_or((Err("--".to_string()), (None, None)))
703                })
704                .unzip();
705            (vib_symmetries, vib_symmetries_thresholds)
706        };
707
708        let result = VibrationalCoordinateRepAnalysisResult::builder()
709            .parameters(params)
710            .vibrational_coordinate_collection(self.vibrational_coordinate_collection)
711            .group(group)
712            .vib_symmetries(vib_symmetries)
713            .vib_symmetries_thresholds(Some(vib_symmetries_thresholds))
714            .build()?;
715        self.result = Some(result);
716
717        Ok(())
718    }
719}
720
721// ~~~~~~~~~~~~~~~~~~~~~
722// Trait implementations
723// ~~~~~~~~~~~~~~~~~~~~~
724
725// Generic for all symmetry groups G and determinant numeric type T
726// ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
727
728impl<'a, G, T> fmt::Display for VibrationalCoordinateRepAnalysisDriver<'a, G, T>
729where
730    G: SymmetryGroupProperties + Clone,
731    G::CharTab: SubspaceDecomposable<T>,
732    T: ComplexFloat + Lapack,
733    <T as ComplexFloat>::Real: From<f64> + fmt::LowerExp + fmt::Debug,
734{
735    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
736        write_title(f, "Vibrational Coordinate Symmetry Analysis")?;
737        writeln!(f)?;
738        writeln!(f, "{}", self.parameters)?;
739        Ok(())
740    }
741}
742
743impl<'a, G, T> fmt::Debug for VibrationalCoordinateRepAnalysisDriver<'a, G, T>
744where
745    G: SymmetryGroupProperties + Clone,
746    G::CharTab: SubspaceDecomposable<T>,
747    T: ComplexFloat + Lapack,
748    <T as ComplexFloat>::Real: From<f64> + fmt::LowerExp + fmt::Debug,
749{
750    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
751        writeln!(f, "{self}")
752    }
753}
754
755// Specific for unitary/magnetic-represented groups and vibration numeric type f64/Complex<f64>
756// ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
757
758#[duplicate_item(
759    duplicate!{
760        [ dtype_nested; [f64]; [Complex<f64>] ]
761        [
762            gtype_ [ UnitaryRepresentedSymmetryGroup ]
763            dtype_ [ dtype_nested ]
764            analyse_fn_ [ analyse_representation ]
765        ]
766    }
767    duplicate!{
768        [ dtype_nested; [f64]; [Complex<f64>] ]
769        [
770            gtype_ [ MagneticRepresentedSymmetryGroup ]
771            dtype_ [ dtype_nested ]
772            analyse_fn_ [ analyse_corepresentation ]
773        ]
774    }
775)]
776impl<'a> QSym2Driver for VibrationalCoordinateRepAnalysisDriver<'a, gtype_, dtype_> {
777    type Params = VibrationalCoordinateRepAnalysisParams<f64>;
778
779    type Outcome = VibrationalCoordinateRepAnalysisResult<'a, gtype_, dtype_>;
780
781    fn result(&self) -> Result<&Self::Outcome, anyhow::Error> {
782        self.result
783            .as_ref()
784            .ok_or_else(|| format_err!("No vibrational representation analysis results found."))
785    }
786
787    fn run(&mut self) -> Result<(), anyhow::Error> {
788        self.log_output_display();
789        self.analyse_fn_()?;
790        self.result()?.log_output_display();
791        Ok(())
792    }
793}