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