1use std::collections::HashSet;
4use std::fmt;
5use std::ops::Mul;
6
7use anyhow::{self, bail, format_err};
8use derive_builder::Builder;
9use duplicate::duplicate_item;
10use ndarray::Array4;
11use ndarray_linalg::types::Lapack;
12use num_complex::{Complex, ComplexFloat};
13use num_traits::Float;
14use serde::{Deserialize, Serialize};
15
16use crate::analysis::{
17 EigenvalueComparisonMode, Orbit, Overlap, ProjectionDecomposition, RepAnalysis,
18};
19use crate::chartab::SubspaceDecomposable;
20use crate::chartab::chartab_group::CharacterProperties;
21use crate::drivers::QSym2Driver;
22use crate::drivers::representation_analysis::angular_function::{
23 AngularFunctionRepAnalysisParams, find_angular_function_representation,
24 find_spinor_function_representation,
25};
26use crate::drivers::representation_analysis::{
27 CharacterTableDisplay, MagneticSymmetryAnalysisKind, fn_construct_magnetic_group,
28 fn_construct_unitary_group, log_bao, log_cc_transversal,
29};
30use crate::drivers::symmetry_group_detection::SymmetryGroupDetectionResult;
31use crate::group::{GroupProperties, MagneticRepresentedGroup, UnitaryRepresentedGroup};
32use crate::io::format::{
33 QSym2Output, log_subtitle, nice_bool, qsym2_output, write_subtitle, write_title,
34};
35use crate::symmetry::symmetry_group::{
36 MagneticRepresentedSymmetryGroup, SymmetryGroupProperties, UnitaryRepresentedSymmetryGroup,
37};
38use crate::symmetry::symmetry_transformation::SymmetryTransformationKind;
39use crate::target::density::Density;
40use crate::target::density::density_analysis::DensitySymmetryOrbit;
41
42#[cfg(test)]
43#[path = "density_tests.rs"]
44mod density_tests;
45
46const fn default_true() -> bool {
55 true
56}
57const fn default_symbolic() -> Option<CharacterTableDisplay> {
58 Some(CharacterTableDisplay::Symbolic)
59}
60
61#[derive(Clone, Builder, Debug, Serialize, Deserialize)]
63pub struct DensityRepAnalysisParams<T: From<f64>> {
64 pub integrality_threshold: T,
66
67 pub linear_independence_threshold: T,
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> DensityRepAnalysisParams<T>
112where
113 T: Float + From<f64>,
114{
115 pub fn builder() -> DensityRepAnalysisParamsBuilder<T> {
117 DensityRepAnalysisParamsBuilder::default()
118 }
119}
120
121impl Default for DensityRepAnalysisParams<f64> {
122 fn default() -> Self {
123 Self::builder()
124 .integrality_threshold(1e-7)
125 .linear_independence_threshold(1e-7)
126 .build()
127 .expect("Unable to construct a default `DensityRepAnalysisParams<f64>`.")
128 }
129}
130
131impl<T> fmt::Display for DensityRepAnalysisParams<T>
132where
133 T: From<f64> + fmt::LowerExp + fmt::Debug,
134{
135 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
136 writeln!(
137 f,
138 "Integrality threshold: {:.3e}",
139 self.integrality_threshold
140 )?;
141 writeln!(
142 f,
143 "Linear independence threshold: {:.3e}",
144 self.linear_independence_threshold
145 )?;
146 writeln!(
147 f,
148 "Orbit eigenvalue comparison mode: {}",
149 self.eigenvalue_comparison_mode
150 )?;
151 writeln!(f)?;
152 writeln!(
153 f,
154 "Use magnetic group for analysis: {}",
155 match self.use_magnetic_group {
156 None => "no",
157 Some(MagneticSymmetryAnalysisKind::Representation) =>
158 "yes, using unitary representations",
159 Some(MagneticSymmetryAnalysisKind::Corepresentation) =>
160 "yes, using magnetic corepresentations",
161 }
162 )?;
163 writeln!(
164 f,
165 "Use double group for analysis: {}",
166 nice_bool(self.use_double_group)
167 )?;
168 writeln!(
169 f,
170 "Use Cayley table for orbit overlap matrices: {}",
171 nice_bool(self.use_cayley_table)
172 )?;
173 if let Some(finite_order) = self.infinite_order_to_finite {
174 writeln!(f, "Infinite order to finite: {finite_order}")?;
175 }
176 writeln!(
177 f,
178 "Symmetry transformation kind: {}",
179 self.symmetry_transformation_kind
180 )?;
181 writeln!(f)?;
182 writeln!(
183 f,
184 "Write character table: {}",
185 if let Some(chartab_display) = self.write_character_table.as_ref() {
186 format!("yes, {}", chartab_display.to_string().to_lowercase())
187 } else {
188 "no".to_string()
189 }
190 )?;
191
192 Ok(())
193 }
194}
195
196#[derive(Clone, Builder)]
202pub struct DensityRepAnalysisResult<'a, G, T>
203where
204 G: SymmetryGroupProperties + Clone,
205 G::CharTab: SubspaceDecomposable<T>,
206 T: ComplexFloat + Lapack,
207 <T as ComplexFloat>::Real: From<f64> + fmt::LowerExp + fmt::Debug,
208{
209 parameters: &'a DensityRepAnalysisParams<<T as ComplexFloat>::Real>,
212
213 densities: Vec<(String, &'a Density<'a, T>)>,
215
216 group: G,
218
219 density_symmetries: Vec<Result<<G::CharTab as SubspaceDecomposable<T>>::Decomposition, String>>,
221
222 density_symmetries_thresholds: Vec<(Option<T>, Option<T>)>,
225}
226
227impl<'a, G, T> DensityRepAnalysisResult<'a, G, T>
228where
229 G: SymmetryGroupProperties + Clone,
230 G::CharTab: SubspaceDecomposable<T>,
231 T: ComplexFloat + Lapack,
232 <T as ComplexFloat>::Real: From<f64> + fmt::LowerExp + fmt::Debug,
233{
234 pub fn builder() -> DensityRepAnalysisResultBuilder<'a, G, T> {
237 DensityRepAnalysisResultBuilder::default()
238 }
239
240 pub fn parameters(&self) -> &DensityRepAnalysisParams<<T as ComplexFloat>::Real> {
243 self.parameters
244 }
245
246 pub fn densities(&self) -> &Vec<(String, &'a Density<'a, T>)> {
248 &self.densities
249 }
250}
251
252impl<'a, G, T> fmt::Display for DensityRepAnalysisResult<'a, G, T>
253where
254 G: SymmetryGroupProperties + Clone,
255 G::CharTab: SubspaceDecomposable<T>,
256 T: ComplexFloat + Lapack,
257 <T as ComplexFloat>::Real: From<f64> + fmt::LowerExp + fmt::Debug + fmt::Display,
258{
259 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
260 write_subtitle(f, "Orbit-based symmetry analysis results")?;
261 writeln!(f)?;
262 writeln!(
263 f,
264 "> Group: {} ({})",
265 self.group
266 .finite_subgroup_name()
267 .map(|subgroup_name| format!("{} > {}", self.group.name(), subgroup_name))
268 .unwrap_or(self.group.name()),
269 self.group.group_type().to_string().to_lowercase()
270 )?;
271 writeln!(f)?;
272
273 let density_index_length = usize::try_from(self.densities.len().ilog10() + 1)
274 .unwrap_or(1)
275 .max(1);
276 let density_text_length = self
277 .densities
278 .iter()
279 .map(|(desc, _)| desc.chars().count())
280 .max()
281 .unwrap_or(7)
282 .max(7);
283 let symmetry_length = self
284 .density_symmetries
285 .iter()
286 .map(|den_sym_res| {
287 den_sym_res
288 .as_ref()
289 .map(|sym| sym.to_string())
290 .unwrap_or_else(|err| err.clone())
291 .chars()
292 .count()
293 })
294 .max()
295 .unwrap_or(8)
296 .max(8);
297 let eig_above_length: usize = self
298 .density_symmetries_thresholds
299 .iter()
300 .map(|(eig_above_opt, _)| {
301 eig_above_opt
302 .as_ref()
303 .map(|eig_above| format!("{eig_above:+.3e}").chars().count())
304 .unwrap_or(10)
305 })
306 .max()
307 .unwrap_or(10)
308 .max(10);
309 let eig_below_length: usize = self
310 .density_symmetries_thresholds
311 .iter()
312 .map(|(_, eig_below_opt)| {
313 eig_below_opt
314 .as_ref()
315 .map(|eig_below| format!("{eig_below:+.3e}").chars().count())
316 .unwrap_or(10)
317 })
318 .max()
319 .unwrap_or(10)
320 .max(10);
321
322 let table_width = 10
323 + density_index_length
324 + density_text_length
325 + symmetry_length
326 + eig_above_length
327 + eig_below_length;
328
329 writeln!(f, "> Density results")?;
330 writeln!(f, "{}", "┈".repeat(table_width))?;
331 writeln!(
332 f,
333 " {:>density_index_length$} {:<density_text_length$} {:<symmetry_length$} {:<eig_above_length$} Eig. below",
334 "#", "Density", "Symmetry", "Eig. above",
335 )?;
336 writeln!(f, "{}", "┈".repeat(table_width))?;
337 for (deni, (((den, _), den_sym), (eig_above_opt, eig_below_opt))) in self
338 .densities
339 .iter()
340 .zip(self.density_symmetries.iter())
341 .zip(self.density_symmetries_thresholds.iter())
342 .enumerate()
343 {
344 writeln!(
345 f,
346 " {:>density_index_length$} {:<density_text_length$} {:<symmetry_length$} {:<eig_above_length$} {}",
347 deni,
348 den,
349 den_sym
350 .as_ref()
351 .map(|sym| sym.to_string())
352 .unwrap_or_else(|err| err.clone()),
353 eig_above_opt
354 .map(|eig_above| format!("{eig_above:>+.3e}"))
355 .unwrap_or("--".to_string()),
356 eig_below_opt
357 .map(|eig_below| format!("{eig_below:>+.3e}"))
358 .unwrap_or("--".to_string()),
359 )?;
360 }
361 writeln!(f, "{}", "┈".repeat(table_width))?;
362 writeln!(f)?;
363
364 Ok(())
365 }
366}
367
368impl<'a, G, T> fmt::Debug for DensityRepAnalysisResult<'a, G, T>
369where
370 G: SymmetryGroupProperties + Clone,
371 G::CharTab: SubspaceDecomposable<T>,
372 T: ComplexFloat + Lapack,
373 <T as ComplexFloat>::Real: From<f64> + fmt::LowerExp + fmt::Debug + fmt::Display,
374{
375 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
376 writeln!(f, "{self}")
377 }
378}
379
380impl<'a, G, T> DensityRepAnalysisResult<'a, G, T>
381where
382 G: SymmetryGroupProperties + Clone,
383 G::CharTab: SubspaceDecomposable<T>,
384 T: ComplexFloat + Lapack,
385 <T as ComplexFloat>::Real: From<f64> + fmt::LowerExp + fmt::Debug + fmt::Display,
386{
387 pub fn density_symmetries(
389 &self,
390 ) -> &Vec<Result<<G::CharTab as SubspaceDecomposable<T>>::Decomposition, String>> {
391 &self.density_symmetries
392 }
393}
394
395#[derive(Clone, Builder)]
405#[builder(build_fn(validate = "Self::validate"))]
406pub struct DensityRepAnalysisDriver<'a, G, T>
407where
408 G: SymmetryGroupProperties + Clone,
409 G::CharTab: SubspaceDecomposable<T>,
410 T: ComplexFloat + Lapack,
411 <T as ComplexFloat>::Real: From<f64> + fmt::LowerExp + fmt::Debug,
412{
413 parameters: &'a DensityRepAnalysisParams<<T as ComplexFloat>::Real>,
415
416 densities: Vec<(String, &'a Density<'a, T>)>,
418
419 symmetry_group: &'a SymmetryGroupDetectionResult,
422
423 sao_spatial_4c: &'a Array4<T>,
426
427 #[builder(default = "None")]
432 sao_spatial_4c_h: Option<&'a Array4<T>>,
433
434 angular_function_parameters: &'a AngularFunctionRepAnalysisParams,
436
437 #[builder(setter(skip), default = "None")]
439 result: Option<DensityRepAnalysisResult<'a, G, T>>,
440}
441
442impl<'a, G, T> DensityRepAnalysisDriverBuilder<'a, G, T>
443where
444 G: SymmetryGroupProperties + Clone,
445 G::CharTab: SubspaceDecomposable<T>,
446 T: ComplexFloat + Lapack,
447 <T as ComplexFloat>::Real: From<f64> + fmt::LowerExp + fmt::Debug,
448{
449 fn validate(&self) -> Result<(), String> {
450 let params = self
451 .parameters
452 .ok_or("No electron density representation analysis parameters found.".to_string())?;
453
454 let sym_res = self
455 .symmetry_group
456 .ok_or("No symmetry group information found.".to_string())?;
457
458 let sao_spatial_4c = self
459 .sao_spatial_4c
460 .ok_or("No four-centre spatial SAO matrix found.".to_string())?;
461
462 if let Some(sao_spatial_4c_h) = self.sao_spatial_4c_h.flatten()
463 && sao_spatial_4c_h.shape() != sao_spatial_4c.shape()
464 {
465 return Err(
466 "Mismatched shapes between `sao_spatial_4c` and `sao_spatial_4c_h`.".to_string(),
467 );
468 }
469
470 let dens = self
471 .densities
472 .as_ref()
473 .ok_or("No electron densities found.".to_string())?;
474
475 let sym = if params.use_magnetic_group.is_some() {
476 sym_res
477 .magnetic_symmetry
478 .as_ref()
479 .ok_or("Magnetic symmetry requested for representation analysis, but no magnetic symmetry found.")?
480 } else {
481 &sym_res.unitary_symmetry
482 };
483
484 if sym.is_infinite() && params.infinite_order_to_finite.is_none() {
485 Err(format!(
486 "Representation analysis cannot be performed using the entirety of the infinite group `{}`. \
487 Consider setting the parameter `infinite_order_to_finite` to restrict to a finite subgroup instead.",
488 sym.group_name
489 .as_ref()
490 .expect("No symmetry group name found.")
491 ))
492 } else {
493 let baos = dens
494 .iter()
495 .map(|(_, den)| den.bao())
496 .collect::<HashSet<_>>();
497 if baos.len() != 1 {
498 Err("Inconsistent basis angular order information between densities.".to_string())
499 } else {
500 let naos = sao_spatial_4c.shape().iter().collect::<HashSet<_>>();
501 if naos.len() != 1 {
502 Err("The shape of the four-centre spatial SAO tensor is invalid: all four dimensions must have the same length.".to_string())
503 } else {
504 let nao = **naos.iter().next().ok_or(
505 "Unable to extract the dimensions of the four-centre spatial SAO tensor.",
506 )?;
507 if !dens.iter().all(|(_, den)| den.bao().n_funcs() == nao) {
508 Err("The dimensions of the four-centre spatial SAO tensor do not match the number of spatial AO basis functions.".to_string())
509 } else {
510 Ok(())
511 }
512 }
513 }
514 }
515 }
516}
517
518impl<'a, G, T> DensityRepAnalysisDriver<'a, G, T>
526where
527 G: SymmetryGroupProperties + Clone,
528 G::CharTab: SubspaceDecomposable<T>,
529 T: ComplexFloat + Lapack,
530 <T as ComplexFloat>::Real: From<f64> + fmt::LowerExp + fmt::Debug,
531{
532 pub fn builder() -> DensityRepAnalysisDriverBuilder<'a, G, T> {
534 DensityRepAnalysisDriverBuilder::default()
535 }
536}
537
538impl<'a, T> DensityRepAnalysisDriver<'a, UnitaryRepresentedSymmetryGroup, T>
542where
543 T: ComplexFloat + Lapack + Sync + Send,
544 <T as ComplexFloat>::Real: From<f64> + fmt::LowerExp + fmt::Debug + Sync + Send,
545 for<'b> Complex<f64>: Mul<&'b T, Output = Complex<f64>>,
546{
547 fn_construct_unitary_group!(
548 construct_unitary_group
551 );
552}
553
554impl<'a, T> DensityRepAnalysisDriver<'a, MagneticRepresentedSymmetryGroup, T>
558where
559 T: ComplexFloat + Lapack + Sync + Send,
560 <T as ComplexFloat>::Real: From<f64> + Sync + Send + fmt::LowerExp + fmt::Debug,
561 for<'b> Complex<f64>: Mul<&'b T, Output = Complex<f64>>,
562{
563 fn_construct_magnetic_group!(
564 construct_magnetic_group
567 );
568}
569
570#[duplicate_item(
574 duplicate!{
575 [ dtype_nested; [f64]; [Complex<f64>] ]
576 [
577 gtype_ [ UnitaryRepresentedSymmetryGroup ]
578 dtype_ [ dtype_nested ]
579 doc_sub_ [ "Performs representation analysis using a unitary-represented group and stores the result." ]
580 analyse_fn_ [ analyse_representation ]
581 construct_group_ [ self.construct_unitary_group()? ]
582 calc_projections_ [
583 log_subtitle("Electron density projection decompositions");
584 qsym2_output!("");
585 qsym2_output!(" Density name: {den_name}");
586 qsym2_output!("");
587 qsym2_output!(" Projections are defined w.r.t. the following inner product:");
588 qsym2_output!(" {}", den_orbit.origin().overlap_definition());
589 qsym2_output!("");
590 den_orbit
591 .projections_to_string(
592 &den_orbit.calc_projection_compositions()?,
593 params.integrality_threshold,
594 )
595 .log_output_display();
596 qsym2_output!("");
597 ]
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 calc_projections_ [ ]
609 ]
610 }
611)]
612impl<'a> DensityRepAnalysisDriver<'a, gtype_, dtype_> {
613 #[doc = doc_sub_]
614 fn analyse_fn_(&mut self) -> Result<(), anyhow::Error> {
615 let params = self.parameters;
616 let sao_spatial_4c = self.sao_spatial_4c;
617 let sao_spatial_4c_h = self.sao_spatial_4c_h;
618 let group = construct_group_;
619 log_cc_transversal(&group);
620 let _ = find_angular_function_representation(&group, self.angular_function_parameters);
621 if group.is_double_group() {
622 let _ = find_spinor_function_representation(&group, self.angular_function_parameters);
623 }
624 let bao = self
625 .densities
626 .first()
627 .map(|(_, den)| den.bao())
628 .ok_or_else(|| {
629 format_err!("Basis angular order information could not be extracted.")
630 })?;
631 log_bao(bao, None);
632
633 let (den_symmetries, den_symmetries_thresholds): (Vec<_>, Vec<_>) =
634 self.densities.iter().map(|(den_name, den)| {
635 log::debug!("Analysing symmetry for density {den_name}...");
636 DensitySymmetryOrbit::builder()
637 .group(&group)
638 .origin(den)
639 .integrality_threshold(params.integrality_threshold)
640 .linear_independence_threshold(params.linear_independence_threshold)
641 .symmetry_transformation_kind(params.symmetry_transformation_kind.clone())
642 .eigenvalue_comparison_mode(params.eigenvalue_comparison_mode.clone())
643 .build()
644 .map_err(|err| format_err!(err))
645 .and_then(|mut den_orbit| {
646 den_orbit
647 .calc_smat(Some(sao_spatial_4c), sao_spatial_4c_h, params.use_cayley_table)?
648 .normalise_smat()?
649 .calc_xmat(false)?;
650 let density_symmetry_thresholds = den_orbit
651 .smat_eigvals
652 .as_ref()
653 .map(|eigvals| {
654 let mut eigvals_vec = eigvals.iter().collect::<Vec<_>>();
655 match den_orbit.eigenvalue_comparison_mode() {
656 EigenvalueComparisonMode::Modulus => {
657 eigvals_vec.sort_by(|a, b| {
658 a.abs().partial_cmp(&b.abs()).expect("Unable to compare two eigenvalues based on their moduli.")
659 });
660 }
661 EigenvalueComparisonMode::Real => {
662 eigvals_vec.sort_by(|a, b| {
663 a.re().partial_cmp(&b.re()).expect("Unable to compare two eigenvalues based on their real parts.")
664 });
665 }
666 }
667 let eigval_above = match den_orbit.eigenvalue_comparison_mode() {
668 EigenvalueComparisonMode::Modulus => eigvals_vec
669 .iter()
670 .find(|val| {
671 val.abs() >= den_orbit.linear_independence_threshold
672 })
673 .copied()
674 .copied(),
675 EigenvalueComparisonMode::Real => eigvals_vec
676 .iter()
677 .find(|val| {
678 val.re() >= den_orbit.linear_independence_threshold
679 })
680 .copied()
681 .copied(),
682 };
683 eigvals_vec.reverse();
684 let eigval_below = match den_orbit.eigenvalue_comparison_mode() {
685 EigenvalueComparisonMode::Modulus => eigvals_vec
686 .iter()
687 .find(|val| {
688 val.abs() < den_orbit.linear_independence_threshold
689 })
690 .copied()
691 .copied(),
692 EigenvalueComparisonMode::Real => eigvals_vec
693 .iter()
694 .find(|val| {
695 val.re() < den_orbit.linear_independence_threshold
696 })
697 .copied()
698 .copied(),
699 };
700 (eigval_above, eigval_below)
701 })
702 .unwrap_or((None, None));
703 let den_sym = den_orbit.analyse_rep().map_err(|err| err.to_string());
704
705 {
706 calc_projections_
707 }
708
709 Ok((den_sym, density_symmetry_thresholds))
710 })
711 .unwrap_or_else(|err| (Err(err.to_string()), (None, None)))
712 }).unzip();
713
714 let result = DensityRepAnalysisResult::builder()
715 .parameters(params)
716 .densities(self.densities.clone())
717 .group(group)
718 .density_symmetries(den_symmetries)
719 .density_symmetries_thresholds(den_symmetries_thresholds)
720 .build()?;
721 self.result = Some(result);
722
723 Ok(())
724 }
725}
726
727impl<'a, G, T> fmt::Display for DensityRepAnalysisDriver<'a, G, T>
735where
736 G: SymmetryGroupProperties + Clone,
737 G::CharTab: SubspaceDecomposable<T>,
738 T: ComplexFloat + Lapack,
739 <T as ComplexFloat>::Real: From<f64> + fmt::LowerExp + fmt::Debug,
740{
741 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
742 write_title(f, "Electron Density Symmetry Analysis")?;
743 writeln!(f)?;
744 writeln!(f, "{}", self.parameters)?;
745 Ok(())
746 }
747}
748
749impl<'a, G, T> fmt::Debug for DensityRepAnalysisDriver<'a, G, T>
750where
751 G: SymmetryGroupProperties + Clone,
752 G::CharTab: SubspaceDecomposable<T>,
753 T: ComplexFloat + Lapack,
754 <T as ComplexFloat>::Real: From<f64> + fmt::LowerExp + fmt::Debug,
755{
756 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
757 writeln!(f, "{self}")
758 }
759}
760
761#[duplicate_item(
765 duplicate!{
766 [ dtype_nested; [f64]; [Complex<f64>] ]
767 [
768 gtype_ [ UnitaryRepresentedSymmetryGroup ]
769 dtype_ [ dtype_nested ]
770 analyse_fn_ [ analyse_representation ]
771 ]
772 }
773 duplicate!{
774 [ dtype_nested; [f64]; [Complex<f64>] ]
775 [
776 gtype_ [ MagneticRepresentedSymmetryGroup ]
777 dtype_ [ dtype_nested ]
778 analyse_fn_ [ analyse_corepresentation ]
779 ]
780 }
781)]
782impl<'a> QSym2Driver for DensityRepAnalysisDriver<'a, gtype_, dtype_> {
783 type Params = DensityRepAnalysisParams<f64>;
784
785 type Outcome = DensityRepAnalysisResult<'a, gtype_, dtype_>;
786
787 fn result(&self) -> Result<&Self::Outcome, anyhow::Error> {
788 self.result.as_ref().ok_or_else(|| {
789 format_err!("No electron density representation analysis results found.")
790 })
791 }
792
793 fn run(&mut self) -> Result<(), anyhow::Error> {
794 self.log_output_display();
795 self.analyse_fn_()?;
796 self.result()?.log_output_display();
797 Ok(())
798 }
799}