1use 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
43const fn default_true() -> bool {
52 true
53}
54const fn default_symbolic() -> Option<CharacterTableDisplay> {
55 Some(CharacterTableDisplay::Symbolic)
56}
57
58#[derive(Clone, Builder, Debug, Serialize, Deserialize)]
60pub struct VibrationalCoordinateRepAnalysisParams<T: From<f64>> {
61 pub integrality_threshold: T,
63
64 pub linear_independence_threshold: T,
66
67 #[builder(default = "None")]
70 #[serde(default)]
71 pub use_magnetic_group: Option<MagneticSymmetryAnalysisKind>,
72
73 #[builder(default = "false")]
75 #[serde(default)]
76 pub use_double_group: bool,
77
78 #[builder(default = "true")]
81 #[serde(default = "default_true")]
82 pub use_cayley_table: bool,
83
84 #[builder(default = "SymmetryTransformationKind::Spatial")]
87 #[serde(default)]
88 pub symmetry_transformation_kind: SymmetryTransformationKind,
89
90 #[builder(default = "Some(CharacterTableDisplay::Symbolic)")]
93 #[serde(default = "default_symbolic")]
94 pub write_character_table: Option<CharacterTableDisplay>,
95
96 #[builder(default = "EigenvalueComparisonMode::Modulus")]
98 #[serde(default)]
99 pub eigenvalue_comparison_mode: EigenvalueComparisonMode,
100
101 #[builder(default = "None")]
104 #[serde(default)]
105 pub infinite_order_to_finite: Option<u32>,
106}
107
108impl<T> VibrationalCoordinateRepAnalysisParams<T>
109where
110 T: Float + From<f64>,
111{
112 pub fn builder() -> VibrationalCoordinateRepAnalysisParamsBuilder<T> {
114 VibrationalCoordinateRepAnalysisParamsBuilder::default()
115 }
116}
117
118impl Default for VibrationalCoordinateRepAnalysisParams<f64> {
119 fn default() -> Self {
120 Self::builder()
121 .integrality_threshold(1e-7)
122 .linear_independence_threshold(1e-7)
123 .build()
124 .expect("Unable to construct a default `VibrationalCoordinateRepAnalysisParams<f64>`.")
125 }
126}
127
128impl<T> fmt::Display for VibrationalCoordinateRepAnalysisParams<T>
129where
130 T: From<f64> + fmt::LowerExp + fmt::Debug,
131{
132 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
133 writeln!(
134 f,
135 "Integrality threshold: {:.3e}",
136 self.integrality_threshold
137 )?;
138 writeln!(
139 f,
140 "Linear independence threshold: {:.3e}",
141 self.linear_independence_threshold
142 )?;
143 writeln!(
144 f,
145 "Orbit eigenvalue comparison mode: {}",
146 self.eigenvalue_comparison_mode
147 )?;
148 writeln!(f)?;
149 writeln!(
150 f,
151 "Use magnetic group for analysis: {}",
152 match self.use_magnetic_group {
153 None => "no",
154 Some(MagneticSymmetryAnalysisKind::Representation) =>
155 "yes, using unitary representations",
156 Some(MagneticSymmetryAnalysisKind::Corepresentation) =>
157 "yes, using magnetic corepresentations",
158 }
159 )?;
160 writeln!(
161 f,
162 "Use double group for analysis: {}",
163 nice_bool(self.use_double_group)
164 )?;
165 writeln!(
166 f,
167 "Use Cayley table for orbit overlap matrices: {}",
168 nice_bool(self.use_cayley_table)
169 )?;
170 if let Some(finite_order) = self.infinite_order_to_finite {
171 writeln!(f, "Infinite order to finite: {finite_order}")?;
172 }
173 writeln!(
174 f,
175 "Symmetry transformation kind: {}",
176 self.symmetry_transformation_kind
177 )?;
178 writeln!(f)?;
179 writeln!(
180 f,
181 "Write character table: {}",
182 if let Some(chartab_display) = self.write_character_table.as_ref() {
183 format!("yes, {}", chartab_display.to_string().to_lowercase())
184 } else {
185 "no".to_string()
186 }
187 )?;
188
189 Ok(())
190 }
191}
192
193#[derive(Clone, Builder)]
199pub struct VibrationalCoordinateRepAnalysisResult<'a, G, T>
200where
201 G: SymmetryGroupProperties + Clone,
202 G::CharTab: SubspaceDecomposable<T>,
203 T: ComplexFloat + Lapack,
204 <T as ComplexFloat>::Real: From<f64> + fmt::LowerExp + fmt::Debug,
205{
206 parameters: &'a VibrationalCoordinateRepAnalysisParams<<T as ComplexFloat>::Real>,
209
210 vibrational_coordinate_collection: &'a VibrationalCoordinateCollection<'a, T>,
212
213 group: G,
215
216 vib_symmetries: Vec<Result<<G::CharTab as SubspaceDecomposable<T>>::Decomposition, String>>,
218
219 vib_symmetries_thresholds: Option<Vec<(Option<T>, Option<T>)>>,
222}
223
224impl<'a, G, T> VibrationalCoordinateRepAnalysisResult<'a, G, T>
225where
226 G: SymmetryGroupProperties + Clone,
227 G::CharTab: SubspaceDecomposable<T>,
228 T: ComplexFloat + Lapack,
229 <T as ComplexFloat>::Real: From<f64> + fmt::LowerExp + fmt::Debug,
230{
231 pub fn builder() -> VibrationalCoordinateRepAnalysisResultBuilder<'a, G, T> {
234 VibrationalCoordinateRepAnalysisResultBuilder::default()
235 }
236
237 pub fn parameters(&self) -> &VibrationalCoordinateRepAnalysisParams<<T as ComplexFloat>::Real> {
240 self.parameters
241 }
242
243 pub fn vibrational_coordinate_collection(&self) -> &VibrationalCoordinateCollection<'a, T> {
245 self.vibrational_coordinate_collection
246 }
247}
248
249impl<'a, G, T> fmt::Display for VibrationalCoordinateRepAnalysisResult<'a, G, T>
250where
251 G: SymmetryGroupProperties + Clone,
252 G::CharTab: SubspaceDecomposable<T>,
253 T: ComplexFloat + Lapack,
254 <T as ComplexFloat>::Real: From<f64> + fmt::LowerExp + fmt::Debug + fmt::Display,
255{
256 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
257 write_subtitle(f, "Orbit-based symmetry analysis results")?;
258 writeln!(f)?;
259 writeln!(
260 f,
261 "> Group: {} ({})",
262 self.group
263 .finite_subgroup_name()
264 .map(|subgroup_name| format!("{} > {}", self.group.name(), subgroup_name))
265 .unwrap_or(self.group.name()),
266 self.group.group_type().to_string().to_lowercase()
267 )?;
268 writeln!(f)?;
269
270 let vib_index_length =
271 (usize::try_from(self.vibrational_coordinate_collection.n_modes().ilog10())
272 .unwrap_or(4)
273 + 2)
274 .max(4);
275 let vib_frequency_length = self
276 .vibrational_coordinate_collection
277 .frequencies()
278 .iter()
279 .map(|freq| format!("{freq:+.3}").chars().count())
280 .max()
281 .unwrap_or(9)
282 .max(9);
283 let vib_symmetry_length = self
284 .vib_symmetries
285 .iter()
286 .map(|vib_sym_res| {
287 vib_sym_res
288 .as_ref()
289 .map(|sym| sym.to_string())
290 .unwrap_or("--".to_string())
291 .chars()
292 .count()
293 })
294 .max()
295 .unwrap_or(8)
296 .max(8);
297
298 let vib_eig_above_length: usize = self
299 .vib_symmetries_thresholds
300 .as_ref()
301 .map(|vib_symmetries_thresholds| {
302 vib_symmetries_thresholds
303 .iter()
304 .map(|(above, _)| {
305 above
306 .as_ref()
307 .map(|eig| format!("{eig:+.3e}"))
308 .unwrap_or("--".to_string())
309 .chars()
310 .count()
311 })
312 .max()
313 .unwrap_or(10)
314 .max(10)
315 })
316 .unwrap_or(10);
317 let vib_eig_below_length: usize = self
318 .vib_symmetries_thresholds
319 .as_ref()
320 .map(|vib_symmetries_thresholds| {
321 vib_symmetries_thresholds
322 .iter()
323 .map(|(_, below)| {
324 below
325 .as_ref()
326 .map(|eig| format!("{eig:+.3e}"))
327 .unwrap_or("--".to_string())
328 .chars()
329 .count()
330 })
331 .max()
332 .unwrap_or(10)
333 .max(10)
334 })
335 .unwrap_or(10);
336
337 writeln!(f, "> Vibrational coordinate results")?;
338
339 writeln!(
340 f,
341 "{}",
342 "┈".repeat(
343 10 + vib_index_length
344 + vib_frequency_length
345 + vib_symmetry_length
346 + vib_eig_above_length
347 + vib_eig_below_length
348 )
349 )?;
350 writeln!(
351 f,
352 " {:>vib_index_length$} {:<vib_frequency_length$} {:<vib_symmetry_length$} {:<vib_eig_above_length$} Eig. below",
353 "Mode", "Frequency", "Symmetry", "Eig. above"
354 )?;
355 writeln!(
356 f,
357 "{}",
358 "┈".repeat(
359 10 + vib_index_length
360 + vib_frequency_length
361 + vib_symmetry_length
362 + vib_eig_above_length
363 + vib_eig_below_length
364 )
365 )?;
366
367 for (vibi, vib_sym) in self.vib_symmetries.iter().enumerate() {
368 let vib_frequency_str = self
369 .vibrational_coordinate_collection
370 .frequencies()
371 .get(vibi)
372 .map(|freq| format!("{freq:>+vib_frequency_length$.3}"))
373 .unwrap_or("--".to_string());
374 let vib_sym_str = vib_sym
375 .as_ref()
376 .map(|sym| sym.to_string())
377 .unwrap_or("--".to_string());
378 let (eig_above_str, eig_below_str) = self
379 .vib_symmetries_thresholds
380 .as_ref()
381 .map(|vib_symmetries_thresholds| {
382 vib_symmetries_thresholds
383 .get(vibi)
384 .map(|(eig_above_opt, eig_below_opt)| {
385 (
386 eig_above_opt
387 .map(|eig_above| format!("{eig_above:>+.3e}"))
388 .unwrap_or("--".to_string()),
389 eig_below_opt
390 .map(|eig_below| format!("{eig_below:>+.3e}"))
391 .unwrap_or("--".to_string()),
392 )
393 })
394 .unwrap_or(("--".to_string(), "--".to_string()))
395 })
396 .unwrap_or(("--".to_string(), "--".to_string()));
397 writeln!(
398 f,
399 " {vibi:>vib_index_length$} \
400 {vib_frequency_str:<vib_frequency_length$} \
401 {vib_sym_str:<vib_symmetry_length$} \
402 {eig_above_str:<vib_eig_above_length$} \
403 {eig_below_str}"
404 )?;
405 }
406 writeln!(
407 f,
408 "{}",
409 "┈".repeat(
410 10 + vib_index_length
411 + vib_frequency_length
412 + vib_symmetry_length
413 + vib_eig_above_length
414 + vib_eig_below_length
415 )
416 )?;
417 writeln!(f)?;
418
419 Ok(())
420 }
421}
422
423impl<'a, G, T> fmt::Debug for VibrationalCoordinateRepAnalysisResult<'a, G, T>
424where
425 G: SymmetryGroupProperties + Clone,
426 G::CharTab: SubspaceDecomposable<T>,
427 T: ComplexFloat + Lapack,
428 <T as ComplexFloat>::Real: From<f64> + fmt::LowerExp + fmt::Debug + fmt::Display,
429{
430 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
431 writeln!(f, "{self}")
432 }
433}
434
435impl<'a, G, T> VibrationalCoordinateRepAnalysisResult<'a, G, T>
436where
437 G: SymmetryGroupProperties + Clone,
438 G::CharTab: SubspaceDecomposable<T>,
439 T: ComplexFloat + Lapack,
440 <T as ComplexFloat>::Real: From<f64> + fmt::LowerExp + fmt::Debug + fmt::Display,
441{
442 pub fn vibrational_coordinate_symmetries(
444 &self,
445 ) -> &Vec<Result<<G::CharTab as SubspaceDecomposable<T>>::Decomposition, String>> {
446 &self.vib_symmetries
447 }
448}
449
450#[derive(Clone, Builder)]
460#[builder(build_fn(validate = "Self::validate"))]
461pub struct VibrationalCoordinateRepAnalysisDriver<'a, G, T>
462where
463 G: SymmetryGroupProperties + Clone,
464 G::CharTab: SubspaceDecomposable<T>,
465 T: ComplexFloat + Lapack,
466 <T as ComplexFloat>::Real: From<f64> + fmt::LowerExp + fmt::Debug,
467{
468 parameters: &'a VibrationalCoordinateRepAnalysisParams<<T as ComplexFloat>::Real>,
470
471 vibrational_coordinate_collection: &'a VibrationalCoordinateCollection<'a, T>,
473
474 symmetry_group: &'a SymmetryGroupDetectionResult,
477
478 angular_function_parameters: &'a AngularFunctionRepAnalysisParams,
480
481 #[builder(setter(skip), default = "None")]
483 result: Option<VibrationalCoordinateRepAnalysisResult<'a, G, T>>,
484}
485
486impl<'a, G, T> VibrationalCoordinateRepAnalysisDriverBuilder<'a, G, T>
487where
488 G: SymmetryGroupProperties + Clone,
489 G::CharTab: SubspaceDecomposable<T>,
490 T: ComplexFloat + Lapack,
491 <T as ComplexFloat>::Real: From<f64> + fmt::LowerExp + fmt::Debug,
492{
493 fn validate(&self) -> Result<(), String> {
494 let params = self.parameters.ok_or(
495 "No vibrational coordinate representation analysis parameters found.".to_string(),
496 )?;
497
498 let sym_res = self
499 .symmetry_group
500 .ok_or("No symmetry group information found.".to_string())?;
501
502 let sym = if params.use_magnetic_group.is_some() {
503 sym_res
504 .magnetic_symmetry
505 .as_ref()
506 .ok_or("Magnetic symmetry requested for representation analysis, but no magnetic symmetry found.")?
507 } else {
508 &sym_res.unitary_symmetry
509 };
510
511 if sym.is_infinite() && params.infinite_order_to_finite.is_none() {
512 Err(format!(
513 "Representation analysis cannot be performed using the entirety of the infinite group `{}`. \
514 Consider setting the parameter `infinite_order_to_finite` to restrict to a finite subgroup instead.",
515 sym.group_name
516 .as_ref()
517 .expect("No symmetry group name found.")
518 ))
519 } else {
520 Ok(())
521 }
522 }
523}
524
525impl<'a, G, T> VibrationalCoordinateRepAnalysisDriver<'a, G, T>
533where
534 G: SymmetryGroupProperties + Clone,
535 G::CharTab: SubspaceDecomposable<T>,
536 T: ComplexFloat + Lapack,
537 <T as ComplexFloat>::Real: From<f64> + fmt::LowerExp + fmt::Debug,
538{
539 pub fn builder() -> VibrationalCoordinateRepAnalysisDriverBuilder<'a, G, T> {
541 VibrationalCoordinateRepAnalysisDriverBuilder::default()
542 }
543}
544
545impl<'a, T> VibrationalCoordinateRepAnalysisDriver<'a, UnitaryRepresentedSymmetryGroup, T>
549where
550 T: ComplexFloat + Lapack + Sync + Send,
551 <T as ComplexFloat>::Real: From<f64> + fmt::LowerExp + fmt::Debug + Sync + Send,
552 for<'b> Complex<f64>: Mul<&'b T, Output = Complex<f64>>,
553{
554 fn_construct_unitary_group!(
555 construct_unitary_group
558 );
559}
560
561impl<'a, T> VibrationalCoordinateRepAnalysisDriver<'a, MagneticRepresentedSymmetryGroup, T>
565where
566 T: ComplexFloat + Lapack + Sync + Send,
567 <T as ComplexFloat>::Real: From<f64> + Sync + Send + fmt::LowerExp + fmt::Debug,
568 for<'b> Complex<f64>: Mul<&'b T, Output = Complex<f64>>,
569{
570 fn_construct_magnetic_group!(
571 construct_magnetic_group
574 );
575}
576
577#[duplicate_item(
581 duplicate!{
582 [ dtype_nested; [f64]; [Complex<f64>] ]
583 [
584 gtype_ [ UnitaryRepresentedSymmetryGroup ]
585 dtype_ [ dtype_nested ]
586 doc_sub_ [ "Performs representation analysis using a unitary-represented group and stores the result." ]
587 analyse_fn_ [ analyse_representation ]
588 construct_group_ [ self.construct_unitary_group()? ]
589 ]
590 }
591 duplicate!{
592 [ dtype_nested; [f64]; [Complex<f64>] ]
593 [
594 gtype_ [ MagneticRepresentedSymmetryGroup ]
595 dtype_ [ dtype_nested ]
596 doc_sub_ [ "Performs corepresentation analysis using a magnetic-represented group and stores the result." ]
597 analyse_fn_ [ analyse_corepresentation ]
598 construct_group_ [ self.construct_magnetic_group()? ]
599 ]
600 }
601)]
602impl<'a> VibrationalCoordinateRepAnalysisDriver<'a, gtype_, dtype_> {
603 #[doc = doc_sub_]
604 fn analyse_fn_(&mut self) -> Result<(), anyhow::Error> {
605 let params = self.parameters;
606 let group = construct_group_;
607 log_cc_transversal(&group);
608 let _ = find_angular_function_representation(&group, self.angular_function_parameters);
609 if group.is_double_group() {
610 let _ = find_spinor_function_representation(&group, self.angular_function_parameters);
611 }
612
613 let (vib_symmetries, vib_symmetries_thresholds) = {
614 let vibs = self
615 .vibrational_coordinate_collection
616 .to_vibrational_coordinates();
617 let vib_orbits = vibs.par_iter().map(|vib| {
618 let mut vib_orbit = VibrationalCoordinateSymmetryOrbit::builder()
619 .group(&group)
620 .origin(vib)
621 .integrality_threshold(params.integrality_threshold)
622 .linear_independence_threshold(params.linear_independence_threshold)
623 .symmetry_transformation_kind(params.symmetry_transformation_kind.clone())
624 .eigenvalue_comparison_mode(params.eigenvalue_comparison_mode.clone())
625 .build()?;
626 vib_orbit
627 .calc_smat(None, None, params.use_cayley_table)
628 .and_then(|orbit| orbit.calc_xmat(false))?;
629 Ok::<_, anyhow::Error>(vib_orbit)
630 });
631 let (vib_symmetries, vib_symmetries_thresholds): (Vec<_>, Vec<_>) = vib_orbits
632 .map(|vib_orbit_res| {
633 vib_orbit_res
634 .as_ref()
635 .map(|vib_orbit| {
636 let sym_res = vib_orbit.analyse_rep().map_err(|err| err.to_string());
637 let eigs = vib_orbit
638 .smat_eigvals
639 .as_ref()
640 .map(|eigvals| {
641 let mut eigvals_vec = eigvals.iter().collect::<Vec<_>>();
642 match vib_orbit.eigenvalue_comparison_mode {
643 EigenvalueComparisonMode::Modulus => {
644 eigvals_vec.sort_by(|a, b| {
645 a.abs().partial_cmp(&b.abs()).expect("Unable to compare two eigenvalues based on their moduli.")
646 });
647 }
648 EigenvalueComparisonMode::Real => {
649 eigvals_vec.sort_by(|a, b| {
650 a.re().partial_cmp(&b.re()).expect("Unable to compare two eigenvalues based on their real parts.")
651 });
652 }
653 }
654 let eigval_above = match vib_orbit.eigenvalue_comparison_mode {
655 EigenvalueComparisonMode::Modulus => eigvals_vec
656 .iter()
657 .find(|val| {
658 val.abs() >= vib_orbit.linear_independence_threshold
659 })
660 .copied()
661 .copied(),
662 EigenvalueComparisonMode::Real => eigvals_vec
663 .iter()
664 .find(|val| {
665 val.re() >= vib_orbit.linear_independence_threshold
666 })
667 .copied()
668 .copied(),
669 };
670 eigvals_vec.reverse();
671 let eigval_below = match vib_orbit.eigenvalue_comparison_mode {
672 EigenvalueComparisonMode::Modulus => eigvals_vec
673 .iter()
674 .find(|val| {
675 val.abs() < vib_orbit.linear_independence_threshold
676 })
677 .copied()
678 .copied(),
679 EigenvalueComparisonMode::Real => eigvals_vec
680 .iter()
681 .find(|val| {
682 val.re() < vib_orbit.linear_independence_threshold
683 })
684 .copied()
685 .copied(),
686 };
687 (eigval_above, eigval_below)
688 })
689 .unwrap_or_else(|| (None, None));
690 (sym_res, eigs)
691 })
692 .unwrap_or((Err("--".to_string()), (None, None)))
693 })
694 .unzip();
695 (vib_symmetries, vib_symmetries_thresholds)
696 };
697
698 let result = VibrationalCoordinateRepAnalysisResult::builder()
699 .parameters(params)
700 .vibrational_coordinate_collection(self.vibrational_coordinate_collection)
701 .group(group)
702 .vib_symmetries(vib_symmetries)
703 .vib_symmetries_thresholds(Some(vib_symmetries_thresholds))
704 .build()?;
705 self.result = Some(result);
706
707 Ok(())
708 }
709}
710
711impl<'a, G, T> fmt::Display for VibrationalCoordinateRepAnalysisDriver<'a, G, T>
719where
720 G: SymmetryGroupProperties + Clone,
721 G::CharTab: SubspaceDecomposable<T>,
722 T: ComplexFloat + Lapack,
723 <T as ComplexFloat>::Real: From<f64> + fmt::LowerExp + fmt::Debug,
724{
725 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
726 write_title(f, "Vibrational Coordinate Symmetry Analysis")?;
727 writeln!(f)?;
728 writeln!(f, "{}", self.parameters)?;
729 Ok(())
730 }
731}
732
733impl<'a, G, T> fmt::Debug for VibrationalCoordinateRepAnalysisDriver<'a, G, T>
734where
735 G: SymmetryGroupProperties + Clone,
736 G::CharTab: SubspaceDecomposable<T>,
737 T: ComplexFloat + Lapack,
738 <T as ComplexFloat>::Real: From<f64> + fmt::LowerExp + fmt::Debug,
739{
740 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
741 writeln!(f, "{self}")
742 }
743}
744
745#[duplicate_item(
749 duplicate!{
750 [ dtype_nested; [f64]; [Complex<f64>] ]
751 [
752 gtype_ [ UnitaryRepresentedSymmetryGroup ]
753 dtype_ [ dtype_nested ]
754 analyse_fn_ [ analyse_representation ]
755 ]
756 }
757 duplicate!{
758 [ dtype_nested; [f64]; [Complex<f64>] ]
759 [
760 gtype_ [ MagneticRepresentedSymmetryGroup ]
761 dtype_ [ dtype_nested ]
762 analyse_fn_ [ analyse_corepresentation ]
763 ]
764 }
765)]
766impl<'a> QSym2Driver for VibrationalCoordinateRepAnalysisDriver<'a, gtype_, dtype_> {
767 type Params = VibrationalCoordinateRepAnalysisParams<f64>;
768
769 type Outcome = VibrationalCoordinateRepAnalysisResult<'a, gtype_, dtype_>;
770
771 fn result(&self) -> Result<&Self::Outcome, anyhow::Error> {
772 self.result
773 .as_ref()
774 .ok_or_else(|| format_err!("No vibrational representation analysis results found."))
775 }
776
777 fn run(&mut self) -> Result<(), anyhow::Error> {
778 self.log_output_display();
779 self.analyse_fn_()?;
780 self.result()?.log_output_display();
781 Ok(())
782 }
783}