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 pub orbit_overlap_symmetry_threshold: f64,
69
70 #[builder(default = "None")]
73 #[serde(default)]
74 pub use_magnetic_group: Option<MagneticSymmetryAnalysisKind>,
75
76 #[builder(default = "false")]
78 #[serde(default)]
79 pub use_double_group: bool,
80
81 #[builder(default = "true")]
84 #[serde(default = "default_true")]
85 pub use_cayley_table: bool,
86
87 #[builder(default = "SymmetryTransformationKind::Spatial")]
90 #[serde(default)]
91 pub symmetry_transformation_kind: SymmetryTransformationKind,
92
93 #[builder(default = "Some(CharacterTableDisplay::Symbolic)")]
96 #[serde(default = "default_symbolic")]
97 pub write_character_table: Option<CharacterTableDisplay>,
98
99 #[builder(default = "EigenvalueComparisonMode::Modulus")]
101 #[serde(default)]
102 pub eigenvalue_comparison_mode: EigenvalueComparisonMode,
103
104 #[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 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#[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 parameters: &'a VibrationalCoordinateRepAnalysisParams<<T as ComplexFloat>::Real>,
218
219 vibrational_coordinate_collection: &'a VibrationalCoordinateCollection<'a, T>,
221
222 group: G,
224
225 vib_symmetries: Vec<Result<<G::CharTab as SubspaceDecomposable<T>>::Decomposition, String>>,
227
228 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 pub fn builder() -> VibrationalCoordinateRepAnalysisResultBuilder<'a, G, T> {
243 VibrationalCoordinateRepAnalysisResultBuilder::default()
244 }
245
246 pub fn parameters(&self) -> &VibrationalCoordinateRepAnalysisParams<<T as ComplexFloat>::Real> {
249 self.parameters
250 }
251
252 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 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#[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 parameters: &'a VibrationalCoordinateRepAnalysisParams<<T as ComplexFloat>::Real>,
479
480 vibrational_coordinate_collection: &'a VibrationalCoordinateCollection<'a, T>,
482
483 symmetry_group: &'a SymmetryGroupDetectionResult,
486
487 angular_function_parameters: &'a AngularFunctionRepAnalysisParams,
489
490 #[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
534impl<'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 pub fn builder() -> VibrationalCoordinateRepAnalysisDriverBuilder<'a, G, T> {
550 VibrationalCoordinateRepAnalysisDriverBuilder::default()
551 }
552}
553
554impl<'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 construct_unitary_group
567 );
568}
569
570impl<'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 construct_magnetic_group
583 );
584}
585
586#[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
721impl<'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#[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}