1use std::collections::{HashMap, HashSet};
4use std::fmt;
5use std::ops::Mul;
6
7use anyhow::{self, bail, ensure, format_err};
8use approx::abs_diff_eq;
9use derive_builder::Builder;
10use duplicate::duplicate_item;
11use indexmap::IndexMap;
12use itertools::Itertools;
13use ndarray::{Array2, Array4, s};
14use ndarray_linalg::types::Lapack;
15use num::ToPrimitive;
16use num_complex::{Complex, ComplexFloat};
17use num_traits::Float;
18use num_traits::real::Real;
19use rayon::prelude::*;
20use serde::{Deserialize, Serialize};
21
22use crate::analysis::{
23 EigenvalueComparisonMode, Orbit, Overlap, ProjectionDecomposition, RepAnalysis,
24 log_overlap_eigenvalues,
25};
26use crate::angmom::spinor_rotation_3d::{SpinConstraint, SpinOrbitCoupled, StructureConstraint};
27use crate::chartab::chartab_group::CharacterProperties;
28use crate::chartab::chartab_symbols::ReducibleLinearSpaceSymbol;
29use crate::chartab::{CharacterTable, SubspaceDecomposable};
30use crate::drivers::QSym2Driver;
31use crate::drivers::representation_analysis::angular_function::{
32 AngularFunctionRepAnalysisParams, find_angular_function_representation,
33 find_spinor_function_representation,
34};
35use crate::drivers::representation_analysis::{
36 CharacterTableDisplay, MagneticSymmetryAnalysisKind, fn_construct_magnetic_group,
37 fn_construct_unitary_group, log_bao, log_cc_transversal,
38};
39use crate::drivers::symmetry_group_detection::SymmetryGroupDetectionResult;
40use crate::group::{GroupProperties, MagneticRepresentedGroup, UnitaryRepresentedGroup};
41use crate::io::format::{
42 QSym2Output, log_subtitle, nice_bool, qsym2_output, write_subtitle, write_title,
43};
44use crate::symmetry::symmetry_element::symmetry_operation::{
45 SpecialSymmetryTransformation, SymmetryOperation,
46};
47use crate::symmetry::symmetry_group::{
48 MagneticRepresentedSymmetryGroup, SymmetryGroupProperties, UnitaryRepresentedSymmetryGroup,
49};
50use crate::symmetry::symmetry_symbols::{
51 MirrorParity, MullikenIrcorepSymbol, SymmetryClassSymbol, deduce_mirror_parities,
52};
53use crate::symmetry::symmetry_transformation::SymmetryTransformationKind;
54use crate::target::density::density_analysis::DensitySymmetryOrbit;
55use crate::target::determinant::SlaterDeterminant;
56use crate::target::determinant::determinant_analysis::SlaterDeterminantSymmetryOrbit;
57use crate::target::orbital::orbital_analysis::generate_det_mo_orbits;
58
59#[cfg(test)]
60#[path = "slater_determinant_tests.rs"]
61mod slater_determinant_tests;
62
63const fn default_true() -> bool {
72 true
73}
74const fn default_symbolic() -> Option<CharacterTableDisplay> {
75 Some(CharacterTableDisplay::Symbolic)
76}
77
78#[derive(Clone, Builder, Debug, Serialize, Deserialize)]
80pub struct SlaterDeterminantRepAnalysisParams<T: From<f64>> {
81 pub integrality_threshold: T,
83
84 pub linear_independence_threshold: T,
86
87 #[builder(default = "true")]
90 #[serde(default = "default_true")]
91 pub analyse_mo_symmetries: bool,
92
93 #[builder(default = "false")]
96 #[serde(default)]
97 pub analyse_mo_mirror_parities: bool,
98
99 #[builder(default = "false")]
101 #[serde(default)]
102 pub analyse_mo_symmetry_projections: bool,
103
104 #[builder(default = "false")]
107 #[serde(default)]
108 pub analyse_density_symmetries: bool,
109
110 #[builder(default = "None")]
113 #[serde(default)]
114 pub use_magnetic_group: Option<MagneticSymmetryAnalysisKind>,
115
116 #[builder(default = "false")]
118 #[serde(default)]
119 pub use_double_group: bool,
120
121 #[builder(default = "true")]
124 #[serde(default = "default_true")]
125 pub use_cayley_table: bool,
126
127 #[builder(default = "SymmetryTransformationKind::Spatial")]
130 #[serde(default)]
131 pub symmetry_transformation_kind: SymmetryTransformationKind,
132
133 #[builder(default = "Some(CharacterTableDisplay::Symbolic)")]
136 #[serde(default = "default_symbolic")]
137 pub write_character_table: Option<CharacterTableDisplay>,
138
139 #[builder(default = "true")]
141 #[serde(default = "default_true")]
142 pub write_overlap_eigenvalues: bool,
143
144 #[builder(default = "EigenvalueComparisonMode::Modulus")]
146 #[serde(default)]
147 pub eigenvalue_comparison_mode: EigenvalueComparisonMode,
148
149 #[builder(default = "None")]
152 #[serde(default)]
153 pub infinite_order_to_finite: Option<u32>,
154}
155
156impl<T> SlaterDeterminantRepAnalysisParams<T>
157where
158 T: Float + From<f64>,
159{
160 pub fn builder() -> SlaterDeterminantRepAnalysisParamsBuilder<T> {
162 SlaterDeterminantRepAnalysisParamsBuilder::default()
163 }
164}
165
166impl Default for SlaterDeterminantRepAnalysisParams<f64> {
167 fn default() -> Self {
168 Self::builder()
169 .integrality_threshold(1e-7)
170 .linear_independence_threshold(1e-7)
171 .build()
172 .expect("Unable to construct a default `SlaterDeterminantRepAnalysisParams<f64>`.")
173 }
174}
175
176impl<T> fmt::Display for SlaterDeterminantRepAnalysisParams<T>
177where
178 T: From<f64> + fmt::LowerExp + fmt::Debug,
179{
180 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
181 writeln!(
182 f,
183 "Integrality threshold: {:.3e}",
184 self.integrality_threshold
185 )?;
186 writeln!(
187 f,
188 "Linear independence threshold: {:.3e}",
189 self.linear_independence_threshold
190 )?;
191 writeln!(
192 f,
193 "Orbit eigenvalue comparison mode: {}",
194 self.eigenvalue_comparison_mode
195 )?;
196 writeln!(
197 f,
198 "Write overlap eigenvalues: {}",
199 nice_bool(self.write_overlap_eigenvalues)
200 )?;
201 writeln!(f)?;
202 writeln!(
203 f,
204 "Analyse molecular orbital symmetry: {}",
205 nice_bool(self.analyse_mo_symmetries)
206 )?;
207 writeln!(
208 f,
209 "Analyse molecular orbital symmetry projection: {}",
210 nice_bool(self.analyse_mo_symmetry_projections)
211 )?;
212 writeln!(
213 f,
214 "Analyse molecular orbital mirror parity: {}",
215 nice_bool(self.analyse_mo_mirror_parities)
216 )?;
217 writeln!(
218 f,
219 "Analyse density symmetry: {}",
220 nice_bool(self.analyse_density_symmetries)
221 )?;
222 writeln!(f)?;
223 writeln!(
224 f,
225 "Use magnetic group for analysis: {}",
226 match self.use_magnetic_group {
227 None => "no",
228 Some(MagneticSymmetryAnalysisKind::Representation) =>
229 "yes, using unitary representations",
230 Some(MagneticSymmetryAnalysisKind::Corepresentation) =>
231 "yes, using magnetic corepresentations",
232 }
233 )?;
234 writeln!(
235 f,
236 "Use double group for analysis: {}",
237 nice_bool(self.use_double_group)
238 )?;
239 writeln!(
240 f,
241 "Use Cayley table for orbit overlap matrices: {}",
242 nice_bool(self.use_cayley_table)
243 )?;
244 if let Some(finite_order) = self.infinite_order_to_finite {
245 writeln!(f, "Infinite order to finite: {finite_order}")?;
246 }
247 writeln!(
248 f,
249 "Symmetry transformation kind: {}",
250 self.symmetry_transformation_kind
251 )?;
252 writeln!(f)?;
253 writeln!(
254 f,
255 "Write character table: {}",
256 if let Some(chartab_display) = self.write_character_table.as_ref() {
257 format!("yes, {}", chartab_display.to_string().to_lowercase())
258 } else {
259 "no".to_string()
260 }
261 )?;
262
263 Ok(())
264 }
265}
266
267#[derive(Clone, Builder)]
273pub struct SlaterDeterminantRepAnalysisResult<'a, G, T, SC>
274where
275 G: SymmetryGroupProperties + Clone,
276 G::CharTab: SubspaceDecomposable<T>,
277 T: ComplexFloat + Lapack,
278 SC: StructureConstraint + fmt::Display,
279 <T as ComplexFloat>::Real: From<f64> + fmt::LowerExp + fmt::Debug,
280{
281 parameters: &'a SlaterDeterminantRepAnalysisParams<<T as ComplexFloat>::Real>,
284
285 determinant: &'a SlaterDeterminant<'a, T, SC>,
287
288 group: G,
290
291 determinant_symmetry: Result<<G::CharTab as SubspaceDecomposable<T>>::Decomposition, String>,
293
294 #[allow(clippy::type_complexity)]
296 mo_symmetries: Option<Vec<Vec<Option<<G::CharTab as SubspaceDecomposable<T>>::Decomposition>>>>,
297
298 #[allow(clippy::type_complexity)]
300 mo_symmetry_projections: Option<
301 Vec<
302 Vec<
303 Option<
304 Vec<(
305 <<G as CharacterProperties>::CharTab as CharacterTable>::RowSymbol,
306 Complex<f64>,
307 )>,
308 >,
309 >,
310 >,
311 >,
312
313 #[allow(clippy::type_complexity)]
315 mo_mirror_parities:
316 Option<Vec<Vec<Option<IndexMap<SymmetryClassSymbol<SymmetryOperation>, MirrorParity>>>>>,
317
318 #[allow(clippy::type_complexity)]
321 mo_symmetries_thresholds: Option<Vec<Vec<(Option<T>, Option<T>)>>>,
322
323 #[allow(clippy::type_complexity)]
327 determinant_density_symmetries: Option<
328 Vec<(
329 String,
330 Result<<G::CharTab as SubspaceDecomposable<T>>::Decomposition, String>,
331 )>,
332 >,
333
334 #[allow(clippy::type_complexity)]
337 mo_density_symmetries:
338 Option<Vec<Vec<Option<<G::CharTab as SubspaceDecomposable<T>>::Decomposition>>>>,
339}
340
341impl<'a, G, T, SC> SlaterDeterminantRepAnalysisResult<'a, G, T, SC>
342where
343 G: SymmetryGroupProperties + Clone,
344 G::CharTab: SubspaceDecomposable<T>,
345 T: ComplexFloat + Lapack,
346 SC: StructureConstraint + Clone + fmt::Display,
347 <T as ComplexFloat>::Real: From<f64> + fmt::LowerExp + fmt::Debug,
348{
349 fn builder() -> SlaterDeterminantRepAnalysisResultBuilder<'a, G, T, SC> {
352 SlaterDeterminantRepAnalysisResultBuilder::default()
353 }
354}
355
356impl<'a, G, T, SC> SlaterDeterminantRepAnalysisResult<'a, G, T, SC>
357where
358 G: SymmetryGroupProperties + Clone,
359 G::CharTab: SubspaceDecomposable<T>,
360 T: ComplexFloat + Lapack,
361 SC: StructureConstraint + fmt::Display,
362 <T as ComplexFloat>::Real: From<f64> + fmt::LowerExp + fmt::Debug,
363{
364 pub fn group(&self) -> &G {
366 &self.group
367 }
368
369 pub fn determinant_symmetry(
371 &self,
372 ) -> &Result<<G::CharTab as SubspaceDecomposable<T>>::Decomposition, String> {
373 &self.determinant_symmetry
374 }
375
376 #[allow(clippy::type_complexity)]
378 pub fn mo_symmetries(
379 &self,
380 ) -> &Option<Vec<Vec<Option<<G::CharTab as SubspaceDecomposable<T>>::Decomposition>>>> {
381 &self.mo_symmetries
382 }
383
384 #[allow(clippy::type_complexity)]
386 pub fn mo_symmetry_projections(
387 &self,
388 ) -> &Option<
389 Vec<
390 Vec<
391 Option<
392 Vec<(
393 <<G as CharacterProperties>::CharTab as CharacterTable>::RowSymbol,
394 Complex<f64>,
395 )>,
396 >,
397 >,
398 >,
399 > {
400 &self.mo_symmetry_projections
401 }
402
403 #[allow(clippy::type_complexity)]
407 pub fn determinant_density_symmetries(
408 &self,
409 ) -> &Option<
410 Vec<(
411 String,
412 Result<<G::CharTab as SubspaceDecomposable<T>>::Decomposition, String>,
413 )>,
414 > {
415 &self.determinant_density_symmetries
416 }
417
418 #[allow(clippy::type_complexity)]
421 pub fn mo_density_symmetries(
422 &self,
423 ) -> &Option<Vec<Vec<Option<<G::CharTab as SubspaceDecomposable<T>>::Decomposition>>>> {
424 &self.mo_density_symmetries
425 }
426}
427
428impl<'a, G, T, SC> fmt::Display for SlaterDeterminantRepAnalysisResult<'a, G, T, SC>
429where
430 G: SymmetryGroupProperties + Clone,
431 G::CharTab: SubspaceDecomposable<T>,
432 T: ComplexFloat + Lapack,
433 SC: StructureConstraint + fmt::Display,
434 <T as ComplexFloat>::Real: From<f64> + fmt::LowerExp + fmt::Debug + fmt::Display,
435{
436 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
437 write_subtitle(f, "Orbit-based symmetry analysis results")?;
438 writeln!(f)?;
439 writeln!(
440 f,
441 "> Group: {} ({})",
442 self.group
443 .finite_subgroup_name()
444 .map(|subgroup_name| format!("{} > {}", self.group.name(), subgroup_name))
445 .unwrap_or(self.group.name()),
446 self.group.group_type().to_string().to_lowercase()
447 )?;
448 writeln!(f)?;
449 writeln!(f, "> Overall determinantal result")?;
450 writeln!(
451 f,
452 " Energy : {}",
453 self.determinant
454 .energy()
455 .map(|e| e.to_string())
456 .unwrap_or_else(|err| format!("-- ({err})"))
457 )?;
458 writeln!(
459 f,
460 " Symmetry: {}",
461 self.determinant_symmetry
462 .as_ref()
463 .map(|s| s.to_string())
464 .unwrap_or_else(|err| format!("-- ({err})"))
465 )?;
466 writeln!(f)?;
467
468 if let Some(den_syms) = self.determinant_density_symmetries.as_ref() {
469 writeln!(f, "> Overall determinantal density result")?;
470 let den_type_width = den_syms
471 .iter()
472 .map(|(den_type, _)| den_type.chars().count())
473 .max()
474 .unwrap_or(7)
475 .max(7);
476 for (den_type, den_sym_res) in den_syms.iter() {
477 writeln!(
478 f,
479 " {den_type:<den_type_width$}: {}",
480 den_sym_res
481 .as_ref()
482 .map(|e| e.to_string())
483 .unwrap_or_else(|err| format!("-- ({err})"))
484 )?;
485 }
486 writeln!(f)?;
487 }
488
489 if let Some(mo_symmetries) = self.mo_symmetries.as_ref() {
490 let mo_spin_index_length = 4;
491 let mo_index_length = mo_symmetries
492 .iter()
493 .map(|spin_mo_symmetries| spin_mo_symmetries.len())
494 .max()
495 .and_then(|max_mo_length| usize::try_from(max_mo_length.ilog10() + 2).ok())
496 .unwrap_or(4);
497 let mo_occ_length = 5;
498 let mo_symmetry_length = mo_symmetries
499 .iter()
500 .flat_map(|spin_mo_symmetries| {
501 spin_mo_symmetries.iter().map(|mo_sym| {
502 mo_sym
503 .as_ref()
504 .map(|sym| sym.to_string())
505 .unwrap_or("--".to_string())
506 .chars()
507 .count()
508 })
509 })
510 .max()
511 .unwrap_or(0)
512 .max(8);
513 let mo_energies_opt = self.determinant.mo_energies();
514 let mo_energy_length = mo_energies_opt
515 .map(|mo_energies| {
516 mo_energies
517 .iter()
518 .flat_map(|spin_mo_energies| {
519 spin_mo_energies.map(|v| format!("{v:+.7}").chars().count())
520 })
521 .max()
522 .unwrap_or(0)
523 })
524 .unwrap_or(0)
525 .max(6);
526
527 let mo_eig_above_length: usize = self
528 .mo_symmetries_thresholds
529 .as_ref()
530 .map(|mo_symmetries_thresholds| {
531 mo_symmetries_thresholds
532 .iter()
533 .flat_map(|spin_mo_symmetries_thresholds| {
534 spin_mo_symmetries_thresholds.iter().map(|(above, _)| {
535 above
536 .as_ref()
537 .map(|eig| format!("{eig:+.3e}"))
538 .unwrap_or("--".to_string())
539 .chars()
540 .count()
541 })
542 })
543 .max()
544 .unwrap_or(10)
545 .max(10)
546 })
547 .unwrap_or(10);
548 let mo_eig_below_length: usize = self
549 .mo_symmetries_thresholds
550 .as_ref()
551 .map(|mo_symmetries_thresholds| {
552 mo_symmetries_thresholds
553 .iter()
554 .flat_map(|spin_mo_symmetries_thresholds| {
555 spin_mo_symmetries_thresholds.iter().map(|(_, below)| {
556 below
557 .as_ref()
558 .map(|eig| format!("{eig:+.3e}"))
559 .unwrap_or("--".to_string())
560 .chars()
561 .count()
562 })
563 })
564 .max()
565 .unwrap_or(10)
566 .max(10)
567 })
568 .unwrap_or(10);
569
570 let precision = Real::ceil(ComplexFloat::abs(ComplexFloat::log10(
571 self.parameters.linear_independence_threshold,
572 )))
573 .to_usize()
574 .expect("Unable to convert the linear independence threshold exponent to `usize`.");
575 let mo_sym_projections_str_opt =
576 self.mo_symmetry_projections
577 .as_ref()
578 .map(|mo_sym_projectionss| {
579 mo_sym_projectionss
580 .iter()
581 .enumerate()
582 .map(|(ispin, mo_sym_projections)| {
583 mo_sym_projections
584 .iter()
585 .enumerate()
586 .map(|(imo, mo_sym_projection)| {
587 mo_sym_projection
588 .as_ref()
589 .map(|sym_proj| {
590 if let Some(mo_symmetriess) = self.mo_symmetries() {
591 if let Some(sym) = &mo_symmetriess[ispin][imo] {
592 let sym_proj_hashmap = sym_proj
593 .iter()
594 .cloned()
595 .collect::<HashMap<_, _>>();
596 sym.subspaces()
597 .iter()
598 .map(|(subspace, _)| {
599 format!(
600 "{subspace}: {}",
601 sym_proj_hashmap
602 .get(subspace)
603 .map(|composition| {
604 if abs_diff_eq!(
605 composition.im,
606 0.0,
607 epsilon = self.parameters.linear_independence_threshold.to_f64().expect("Unable to convert the linear independence threshold to `f64`.")
608 ) {
609 format!(
610 "{:+.precision$}",
611 composition.re
612 )
613 } else {
614 format!(
615 "{composition:+.precision$}")
616 }
617 })
618 .unwrap_or(
619 "--".to_string()
620 )
621 )
622 })
623 .join(", ")
624 } else {
625 "--".to_string()
626 }
627 } else {
628 "--".to_string()
629 }
630 })
631 .unwrap_or("--".to_string())
632 })
633 .collect::<Vec<_>>()
634 })
635 .collect::<Vec<_>>()
636 });
637 let mo_sym_projections_length_opt =
638 mo_sym_projections_str_opt
639 .as_ref()
640 .map(|mo_sym_projectionss| {
641 mo_sym_projectionss
642 .iter()
643 .flat_map(|mo_sym_projections| {
644 mo_sym_projections
645 .iter()
646 .map(|mo_sym_projection| mo_sym_projection.chars().count())
647 })
648 .max()
649 .unwrap_or(18)
650 .max(18)
651 });
652 let mo_sym_projections_length = mo_sym_projections_length_opt.unwrap_or(0);
653 let mo_sym_projections_gap = mo_sym_projections_length_opt.map(|_| 2).unwrap_or(0);
654 let mo_sym_projections_heading = mo_sym_projections_length_opt
655 .map(|_| "MO sym. projection")
656 .unwrap_or("");
657
658 let mirrors = self
659 .group
660 .filter_cc_symbols(|cc| cc.is_spatial_reflection());
661 let mo_mirror_parities_length_opt = self.mo_mirror_parities.as_ref().map(|_| {
662 let mirror_heading = mirrors.iter().map(|sigma| format!("p[{sigma}]")).join(" ");
663 let length = mirror_heading.chars().count();
664 (mirror_heading, length)
665 });
666 let mo_mirror_parities_gap = mo_mirror_parities_length_opt
667 .as_ref()
668 .map(|_| 2)
669 .unwrap_or(0);
670 let (mo_mirror_parities_heading, mo_mirror_parities_length) =
671 mo_mirror_parities_length_opt.unwrap_or((String::new(), 0));
672
673 let mo_den_symss_str_opt = self.mo_density_symmetries.as_ref().map(|mo_den_symss| {
674 mo_den_symss
675 .iter()
676 .map(|mo_den_syms| {
677 mo_den_syms
678 .iter()
679 .map(|mo_den_sym| {
680 mo_den_sym
681 .as_ref()
682 .map(|sym| sym.to_string())
683 .unwrap_or("--".to_string())
684 })
685 .collect::<Vec<_>>()
686 })
687 .collect::<Vec<_>>()
688 });
689 let mo_density_length_opt = mo_den_symss_str_opt.as_ref().map(|mo_den_symss| {
690 mo_den_symss
691 .iter()
692 .flat_map(|mo_den_syms| {
693 mo_den_syms
694 .iter()
695 .map(|mo_den_sym| mo_den_sym.chars().count())
696 })
697 .max()
698 .unwrap_or(12)
699 .max(12)
700 });
701 let mo_density_length = mo_density_length_opt.unwrap_or(0);
702 let mo_density_gap = mo_density_length_opt.map(|_| 2).unwrap_or(0);
703 let mo_density_heading = mo_density_length_opt.map(|_| "Density sym.").unwrap_or("");
704
705 let table_width = 14
706 + mo_spin_index_length
707 + mo_index_length
708 + mo_occ_length
709 + mo_energy_length
710 + mo_symmetry_length
711 + mo_mirror_parities_gap
712 + mo_mirror_parities_length
713 + mo_eig_above_length
714 + mo_eig_below_length
715 + mo_sym_projections_gap
716 + mo_sym_projections_length
717 + mo_density_gap
718 + mo_density_length;
719
720 writeln!(f, "> Molecular orbital results")?;
721 writeln!(
722 f,
723 " Structure constraint: {}",
724 self.determinant
725 .structure_constraint()
726 .to_string()
727 .to_lowercase()
728 )?;
729 if self.mo_mirror_parities.as_ref().is_some() {
730 writeln!(f)?;
731 writeln!(
732 f,
733 "Column p[σ] gives the parity under the reflection class σ: {} => even, {} => odd, {} => neither.",
734 MirrorParity::Even,
735 MirrorParity::Odd,
736 MirrorParity::Neither
737 )?;
738 }
739 writeln!(f, "{}", "┈".repeat(table_width))?;
740 if mo_density_length > 0 {
741 writeln!(
742 f,
743 " {:>mo_spin_index_length$} {:>mo_index_length$} {:<mo_occ_length$} {:<mo_energy_length$} {:<mo_symmetry_length$}{}{:mo_mirror_parities_length$} {:<mo_eig_above_length$} {:<mo_eig_below_length$}{}{:mo_sym_projections_length$}{}{}",
744 "Spin",
745 "MO",
746 "Occ.",
747 "Energy",
748 "Symmetry",
749 " ".repeat(mo_mirror_parities_gap),
750 mo_mirror_parities_heading,
751 "Eig. above",
752 "Eig. below",
753 " ".repeat(mo_sym_projections_gap),
754 mo_sym_projections_heading,
755 " ".repeat(mo_density_gap),
756 mo_density_heading
757 )?;
758 } else if mo_sym_projections_length > 0 {
759 writeln!(
760 f,
761 " {:>mo_spin_index_length$} {:>mo_index_length$} {:<mo_occ_length$} {:<mo_energy_length$} {:<mo_symmetry_length$}{}{:mo_mirror_parities_length$} {:<mo_eig_above_length$} {:<mo_eig_below_length$}{}{}",
762 "Spin",
763 "MO",
764 "Occ.",
765 "Energy",
766 "Symmetry",
767 " ".repeat(mo_mirror_parities_gap),
768 mo_mirror_parities_heading,
769 "Eig. above",
770 "Eig. below",
771 " ".repeat(mo_sym_projections_gap),
772 mo_sym_projections_heading,
773 )?;
774 } else {
775 writeln!(
776 f,
777 " {:>mo_spin_index_length$} {:>mo_index_length$} {:<mo_occ_length$} {:<mo_energy_length$} {:<mo_symmetry_length$}{}{:mo_mirror_parities_length$} {:<mo_eig_above_length$} Eig. below",
778 "Spin",
779 "MO",
780 "Occ.",
781 "Energy",
782 "Symmetry",
783 " ".repeat(mo_mirror_parities_gap),
784 mo_mirror_parities_heading,
785 "Eig. above",
786 )?;
787 };
788 writeln!(f, "{}", "┈".repeat(table_width))?;
789
790 let empty_string = String::new();
791 for (spini, spin_mo_symmetries) in mo_symmetries.iter().enumerate() {
792 writeln!(f, " Spin {spini}")?;
793 for (moi, mo_sym) in spin_mo_symmetries.iter().enumerate() {
794 let occ_str = self
795 .determinant
796 .occupations()
797 .get(spini)
798 .and_then(|spin_occs| spin_occs.get(moi))
799 .map(|occ| format!("{occ:>.3}"))
800 .unwrap_or("--".to_string());
801 let mo_energy_str = mo_energies_opt
802 .and_then(|mo_energies| mo_energies.get(spini))
803 .and_then(|spin_mo_energies| spin_mo_energies.get(moi))
804 .map(|mo_energy| format!("{mo_energy:>+mo_energy_length$.7}"))
805 .unwrap_or("--".to_string());
806 let mo_sym_str = mo_sym
807 .as_ref()
808 .map(|sym| sym.to_string())
809 .unwrap_or("--".to_string());
810
811 let mo_mirror_parities_str = self
812 .mo_mirror_parities
813 .as_ref()
814 .and_then(|mo_mirror_paritiess| {
815 mo_mirror_paritiess
816 .get(spini)
817 .and_then(|spin_mo_mirror_parities| {
818 spin_mo_mirror_parities
819 .get(moi)
820 .map(|mo_mirror_parities_opt| {
821 mo_mirror_parities_opt
822 .as_ref()
823 .map(|mo_mirror_parities| {
824 mirrors
825 .iter()
826 .map(|sigma| {
827 let sigma_length =
828 sigma.to_string().chars().count()
829 + 3;
830 mo_mirror_parities
831 .get(sigma)
832 .map(|parity| {
833 format!(
834 "{:^sigma_length$}",
835 parity.to_string()
836 )
837 })
838 .unwrap_or_else(|| {
839 format!(
840 "{:^sigma_length$}",
841 "--"
842 )
843 })
844 })
845 .join(" ")
846 })
847 .unwrap_or(String::new())
848 })
849 })
850 })
851 .unwrap_or_default();
852
853 let (eig_above_str, eig_below_str) = self
854 .mo_symmetries_thresholds
855 .as_ref()
856 .map(|mo_symmetries_thresholds| {
857 mo_symmetries_thresholds
858 .get(spini)
859 .and_then(|spin_mo_symmetries_thresholds| {
860 spin_mo_symmetries_thresholds.get(moi)
861 })
862 .map(|(eig_above_opt, eig_below_opt)| {
863 (
864 eig_above_opt
865 .map(|eig_above| format!("{eig_above:>+.3e}"))
866 .unwrap_or("--".to_string()),
867 eig_below_opt
868 .map(|eig_below| format!("{eig_below:>+.3e}"))
869 .unwrap_or("--".to_string()),
870 )
871 })
872 .unwrap_or(("--".to_string(), "--".to_string()))
873 })
874 .unwrap_or(("--".to_string(), "--".to_string()));
875
876 let mo_symmetry_projections_str = mo_sym_projections_str_opt
877 .as_ref()
878 .and_then(|mo_symmetry_projectionss| {
879 mo_symmetry_projectionss.get(spini).and_then(
880 |spin_mo_symmetry_projections| {
881 spin_mo_symmetry_projections.get(moi)
882 },
883 )
884 })
885 .unwrap_or(&empty_string);
886
887 let mo_density_symmetries_str = mo_den_symss_str_opt
888 .as_ref()
889 .and_then(|mo_density_symmetriess| {
890 mo_density_symmetriess.get(spini).and_then(
891 |spin_mo_density_symmetries| spin_mo_density_symmetries.get(moi),
892 )
893 })
894 .unwrap_or(&empty_string);
895
896 match (mo_density_length, mo_sym_projections_length) {
897 (0, 0) => {
898 writeln!(
899 f,
900 " {spini:>mo_spin_index_length$} \
901 {moi:>mo_index_length$} \
902 {occ_str:<mo_occ_length$} \
903 {mo_energy_str:<mo_energy_length$} \
904 {mo_sym_str:<mo_symmetry_length$}\
905 {}{:mo_mirror_parities_length$} \
906 {eig_above_str:<mo_eig_above_length$} \
907 {eig_below_str}",
908 " ".repeat(mo_mirror_parities_gap),
909 mo_mirror_parities_str,
910 )?;
911 }
912 (_, 0) => {
913 writeln!(
914 f,
915 " {spini:>mo_spin_index_length$} \
916 {moi:>mo_index_length$} \
917 {occ_str:<mo_occ_length$} \
918 {mo_energy_str:<mo_energy_length$} \
919 {mo_sym_str:<mo_symmetry_length$}\
920 {}{:mo_mirror_parities_length$} \
921 {eig_above_str:<mo_eig_above_length$} \
922 {eig_below_str:<mo_eig_below_length$} \
923 {mo_density_symmetries_str}",
924 " ".repeat(mo_mirror_parities_gap),
925 mo_mirror_parities_str,
926 )?;
927 }
928 (0, _) => {
929 writeln!(
930 f,
931 " {spini:>mo_spin_index_length$} \
932 {moi:>mo_index_length$} \
933 {occ_str:<mo_occ_length$} \
934 {mo_energy_str:<mo_energy_length$} \
935 {mo_sym_str:<mo_symmetry_length$}\
936 {}{:mo_mirror_parities_length$} \
937 {eig_above_str:<mo_eig_above_length$} \
938 {eig_below_str:<mo_eig_below_length$} \
939 {mo_symmetry_projections_str}",
940 " ".repeat(mo_mirror_parities_gap),
941 mo_mirror_parities_str,
942 )?;
943 }
944 (_, _) => {
945 writeln!(
946 f,
947 " {spini:>mo_spin_index_length$} \
948 {moi:>mo_index_length$} \
949 {occ_str:<mo_occ_length$} \
950 {mo_energy_str:<mo_energy_length$} \
951 {mo_sym_str:<mo_symmetry_length$}\
952 {}{:mo_mirror_parities_length$} \
953 {eig_above_str:<mo_eig_above_length$} \
954 {eig_below_str:<mo_eig_below_length$} \
955 {mo_symmetry_projections_str:<mo_sym_projections_length$} \
956 {mo_density_symmetries_str}",
957 " ".repeat(mo_mirror_parities_gap),
958 mo_mirror_parities_str,
959 )?;
960 }
961 }
962 }
963 }
964
965 writeln!(f, "{}", "┈".repeat(table_width))?;
966 writeln!(f)?;
967 }
968
969 Ok(())
970 }
971}
972
973impl<'a, G, T, SC> fmt::Debug for SlaterDeterminantRepAnalysisResult<'a, G, T, SC>
974where
975 G: SymmetryGroupProperties + Clone,
976 G::CharTab: SubspaceDecomposable<T>,
977 T: ComplexFloat + Lapack,
978 SC: StructureConstraint + fmt::Display,
979 <T as ComplexFloat>::Real: From<f64> + fmt::LowerExp + fmt::Debug + fmt::Display,
980{
981 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
982 writeln!(f, "{self}")
983 }
984}
985
986#[derive(Clone, Builder)]
996#[builder(build_fn(validate = "Self::validate"))]
997pub struct SlaterDeterminantRepAnalysisDriver<'a, G, T, SC>
998where
999 G: SymmetryGroupProperties + Clone,
1000 G::CharTab: SubspaceDecomposable<T>,
1001 T: ComplexFloat + Lapack,
1002 SC: StructureConstraint + fmt::Display,
1003 <T as ComplexFloat>::Real: From<f64> + fmt::LowerExp + fmt::Debug,
1004{
1005 parameters: &'a SlaterDeterminantRepAnalysisParams<<T as ComplexFloat>::Real>,
1007
1008 determinant: &'a SlaterDeterminant<'a, T, SC>,
1010
1011 symmetry_group: &'a SymmetryGroupDetectionResult,
1014
1015 sao: &'a Array2<T>,
1020
1021 #[builder(default = "None")]
1028 sao_h: Option<&'a Array2<T>>,
1029
1030 #[builder(default = "None")]
1033 sao_spatial_4c: Option<&'a Array4<T>>,
1034
1035 #[builder(default = "None")]
1040 sao_spatial_4c_h: Option<&'a Array4<T>>,
1041
1042 angular_function_parameters: &'a AngularFunctionRepAnalysisParams,
1044
1045 #[builder(setter(skip), default = "None")]
1047 result: Option<SlaterDeterminantRepAnalysisResult<'a, G, T, SC>>,
1048}
1049
1050impl<'a, G, T, SC> SlaterDeterminantRepAnalysisDriverBuilder<'a, G, T, SC>
1051where
1052 G: SymmetryGroupProperties + Clone,
1053 G::CharTab: SubspaceDecomposable<T>,
1054 T: ComplexFloat + Lapack,
1055 SC: StructureConstraint + fmt::Display,
1056 <T as ComplexFloat>::Real: From<f64> + fmt::LowerExp + fmt::Debug,
1057{
1058 fn validate(&self) -> Result<(), String> {
1059 let params = self
1060 .parameters
1061 .ok_or("No Slater determinant representation analysis parameters found.".to_string())?;
1062
1063 let sym_res = self
1064 .symmetry_group
1065 .ok_or("No symmetry group information found.".to_string())?;
1066
1067 let sao = self.sao.ok_or("No SAO matrix found.".to_string())?;
1068
1069 if let Some(sao_h) = self.sao_h.flatten()
1070 && sao_h.shape() != sao.shape()
1071 {
1072 return Err("Mismatched shapes between `sao` and `sao_h`.".to_string());
1073 }
1074
1075 match (
1076 self.sao_spatial_4c.flatten(),
1077 self.sao_spatial_4c_h.flatten(),
1078 ) {
1079 (Some(sao_spatial_4c), Some(sao_spatial_4c_h)) => {
1080 if sao_spatial_4c_h.shape() != sao_spatial_4c.shape() {
1081 return Err(
1082 "Mismatched shapes between `sao_spatial_4c` and `sao_spatial_4c_h`."
1083 .to_string(),
1084 );
1085 }
1086 }
1087 (None, Some(_)) => {
1088 return Err("`sao_spatial_4c_h` is provided without `sao_spatial_4c`.".to_string());
1089 }
1090 _ => {}
1091 }
1092
1093 let det = self
1094 .determinant
1095 .ok_or("No Slater determinant found.".to_string())?;
1096
1097 let sym = if params.use_magnetic_group.is_some() {
1098 sym_res
1099 .magnetic_symmetry
1100 .as_ref()
1101 .ok_or("Magnetic symmetry requested for representation analysis, but no magnetic symmetry found.")?
1102 } else {
1103 &sym_res.unitary_symmetry
1104 };
1105
1106 if sym.is_infinite() && params.infinite_order_to_finite.is_none() {
1107 Err(format!(
1108 "Representation analysis cannot be performed using the entirety of the infinite group `{}`. \
1109 Consider setting the parameter `infinite_order_to_finite` to restrict to a finite subgroup instead.",
1110 sym.group_name
1111 .as_ref()
1112 .expect("No symmetry group name found.")
1113 ))
1114 } else {
1115 let nfuncs_set = det
1116 .baos()
1117 .iter()
1118 .map(|bao| bao.n_funcs())
1119 .collect::<HashSet<_>>();
1120 let uniform_component_check = nfuncs_set.len() == 1
1121 && {
1122 let nfuncs = nfuncs_set.iter().next().ok_or_else(|| "Unable to extract the uniform number of basis functions per explicit component.".to_string())?;
1123 sao.nrows() == *nfuncs && sao.ncols() == *nfuncs
1124 };
1125 let total_component_check = {
1126 let nfuncs_tot = det.baos().iter().map(|bao| bao.n_funcs()).sum::<usize>();
1127 sao.nrows() == nfuncs_tot && sao.ncols() == nfuncs_tot
1128 };
1129 if !uniform_component_check && !total_component_check {
1130 Err("The dimensions of the SAO matrix do not match either the uniform number of AO basis functions per explicit component or the total number of AO basis functions across all explicit components.".to_string())
1131 } else {
1132 Ok(())
1133 }
1134 }
1135 }
1136}
1137
1138impl<'a, G, T, SC> SlaterDeterminantRepAnalysisDriver<'a, G, T, SC>
1146where
1147 G: SymmetryGroupProperties + Clone,
1148 G::CharTab: SubspaceDecomposable<T>,
1149 T: ComplexFloat + Lapack,
1150 SC: StructureConstraint + Clone + fmt::Display,
1151 <T as ComplexFloat>::Real: From<f64> + fmt::LowerExp + fmt::Debug,
1152{
1153 pub fn builder() -> SlaterDeterminantRepAnalysisDriverBuilder<'a, G, T, SC> {
1155 SlaterDeterminantRepAnalysisDriverBuilder::default()
1156 }
1157
1158 fn construct_sao(&self) -> Result<(Array2<T>, Option<Array2<T>>), anyhow::Error> {
1161 let nbas_set = self
1162 .determinant
1163 .baos()
1164 .iter()
1165 .map(|bao| bao.n_funcs())
1166 .collect::<HashSet<_>>();
1167 let uniform_component = nbas_set.len() == 1;
1168 let ncomps = self
1169 .determinant
1170 .structure_constraint()
1171 .n_explicit_comps_per_coefficient_matrix();
1172 let provided_dim = self.sao.nrows();
1173
1174 if uniform_component {
1175 let nbas = nbas_set.iter().next().ok_or_else(|| format_err!("Unable to extract the uniform number of basis functions per explicit component."))?;
1176 if provided_dim == *nbas {
1177 let sao = {
1178 let mut sao_mut = Array2::zeros((ncomps * nbas, ncomps * nbas));
1179 (0..ncomps).for_each(|icomp| {
1180 let start = icomp * nbas;
1181 let end = (icomp + 1) * nbas;
1182 sao_mut
1183 .slice_mut(s![start..end, start..end])
1184 .assign(self.sao);
1185 });
1186 sao_mut
1187 };
1188
1189 let sao_h = self.sao_h.map(|sao_h| {
1190 let mut sao_h_mut = Array2::zeros((ncomps * nbas, ncomps * nbas));
1191 (0..ncomps).for_each(|icomp| {
1192 let start = icomp * nbas;
1193 let end = (icomp + 1) * nbas;
1194 sao_h_mut
1195 .slice_mut(s![start..end, start..end])
1196 .assign(sao_h);
1197 });
1198 sao_h_mut
1199 });
1200
1201 Ok((sao, sao_h))
1202 } else {
1203 ensure!(provided_dim == nbas * ncomps);
1204 Ok((self.sao.clone(), self.sao_h.cloned()))
1205 }
1206 } else {
1207 let nbas_tot = self
1208 .determinant
1209 .baos()
1210 .iter()
1211 .map(|bao| bao.n_funcs())
1212 .sum::<usize>();
1213 ensure!(provided_dim == nbas_tot);
1214 Ok((self.sao.clone(), self.sao_h.cloned()))
1215 }
1216 }
1217}
1218
1219impl<'a, T, SC> SlaterDeterminantRepAnalysisDriver<'a, UnitaryRepresentedSymmetryGroup, T, SC>
1223where
1224 T: ComplexFloat + Lapack + Sync + Send,
1225 <T as ComplexFloat>::Real: From<f64> + fmt::LowerExp + fmt::Debug + Sync + Send,
1226 SC: StructureConstraint + fmt::Display,
1227 for<'b> Complex<f64>: Mul<&'b T, Output = Complex<f64>>,
1228{
1229 fn_construct_unitary_group!(
1230 construct_unitary_group
1233 );
1234}
1235
1236impl<'a, T, SC> SlaterDeterminantRepAnalysisDriver<'a, MagneticRepresentedSymmetryGroup, T, SC>
1240where
1241 T: ComplexFloat + Lapack + Sync + Send,
1242 <T as ComplexFloat>::Real: From<f64> + Sync + Send + fmt::LowerExp + fmt::Debug,
1243 SC: StructureConstraint + fmt::Display,
1244 for<'b> Complex<f64>: Mul<&'b T, Output = Complex<f64>>,
1245{
1246 fn_construct_magnetic_group!(
1247 construct_magnetic_group
1250 );
1251}
1252
1253#[duplicate_item(
1257 duplicate!{
1258 [
1259 dtype_nested sctype_nested;
1260 [ f64 ] [ SpinConstraint ];
1261 [ Complex<f64> ] [ SpinConstraint ];
1262 [ Complex<f64> ] [ SpinOrbitCoupled ];
1263 ]
1264 [
1265 gtype_ [ UnitaryRepresentedSymmetryGroup ]
1266 dtype_ [ dtype_nested ]
1267 sctype_ [ sctype_nested ]
1268 doc_sub_ [ "Performs representation analysis using a unitary-represented group and stores the result." ]
1269 analyse_fn_ [ analyse_representation ]
1270 construct_group_ [ self.construct_unitary_group()? ]
1271 calc_projections_ [
1272 log_subtitle("Slater determinant projection decompositions");
1273 qsym2_output!("");
1274 qsym2_output!(" Projections are defined w.r.t. the following inner product:");
1275 qsym2_output!(" {}", det_orbit.origin().overlap_definition());
1276 qsym2_output!("");
1277 det_orbit
1278 .projections_to_string(
1279 &det_orbit.calc_projection_compositions()?,
1280 params.integrality_threshold,
1281 )
1282 .log_output_display();
1283 qsym2_output!("");
1284 ]
1285 calc_mo_projections_ [
1286 let mo_projectionss = mo_orbitss.iter().map(|mo_orbits| {
1287 mo_orbits
1288 .iter()
1289 .map(|mo_orbit| mo_orbit.calc_projection_compositions().ok())
1290 .collect::<Vec<_>>()
1291 }).collect::<Vec<_>>();
1292 Some(mo_projectionss)
1293 ]
1294 ]
1295 }
1296 duplicate!{
1297 [
1298 dtype_nested sctype_nested;
1299 [ f64 ] [ SpinConstraint ];
1300 [ Complex<f64> ] [ SpinConstraint ];
1301 [ Complex<f64> ] [ SpinOrbitCoupled ];
1302 ]
1303 [
1304 gtype_ [ MagneticRepresentedSymmetryGroup ]
1305 dtype_ [ dtype_nested ]
1306 sctype_ [ sctype_nested ]
1307 doc_sub_ [ "Performs corepresentation analysis using a magnetic-represented group and stores the result." ]
1308 analyse_fn_ [ analyse_corepresentation ]
1309 construct_group_ [ self.construct_magnetic_group()? ]
1310 calc_projections_ [ ]
1311 calc_mo_projections_ [
1312 None::<Vec<Vec<Option<Vec<(MullikenIrcorepSymbol, Complex<f64>)>>>>>
1313 ]
1314 ]
1315 }
1316)]
1317impl<'a> SlaterDeterminantRepAnalysisDriver<'a, gtype_, dtype_, sctype_> {
1318 #[doc = doc_sub_]
1319 fn analyse_fn_(&mut self) -> Result<(), anyhow::Error> {
1320 let params = self.parameters;
1321 let (sao, sao_h) = self.construct_sao()?;
1322 let group = construct_group_;
1323 log_cc_transversal(&group);
1324 let _ = find_angular_function_representation(&group, self.angular_function_parameters);
1325 if group.is_double_group() {
1326 let _ = find_spinor_function_representation(&group, self.angular_function_parameters);
1327 }
1328 for (bao_i, bao) in self.determinant.baos().iter().enumerate() {
1329 log_bao(bao, Some(bao_i));
1330 }
1331
1332 let (
1334 det_symmetry,
1335 mo_symmetries,
1336 mo_symmetry_projections,
1337 mo_mirror_parities,
1338 mo_symmetries_thresholds,
1339 ) = if params.analyse_mo_symmetries {
1340 let mos = self.determinant.to_orbitals();
1341 let (mut det_orbit, mut mo_orbitss) = generate_det_mo_orbits(
1342 self.determinant,
1343 &mos,
1344 &group,
1345 &sao,
1346 sao_h.as_ref(),
1347 params.integrality_threshold,
1348 params.linear_independence_threshold,
1349 params.symmetry_transformation_kind.clone(),
1350 params.eigenvalue_comparison_mode.clone(),
1351 params.use_cayley_table,
1352 )?;
1353 det_orbit.calc_xmat(false)?;
1354 if params.write_overlap_eigenvalues
1355 && let Some(smat_eigvals) = det_orbit.smat_eigvals.as_ref()
1356 {
1357 log_overlap_eigenvalues(
1358 "Determinant orbit overlap eigenvalues",
1359 smat_eigvals,
1360 params.linear_independence_threshold,
1361 ¶ms.eigenvalue_comparison_mode,
1362 );
1363 qsym2_output!("");
1364 }
1365
1366 let det_symmetry = det_orbit.analyse_rep().map_err(|err| err.to_string());
1367
1368 {
1369 calc_projections_
1370 }
1371
1372 let mo_symmetries = mo_orbitss
1373 .iter_mut()
1374 .map(|mo_orbits| {
1375 mo_orbits
1376 .par_iter_mut()
1377 .map(|mo_orbit| {
1378 mo_orbit.calc_xmat(false).ok()?;
1379 mo_orbit.analyse_rep().ok()
1380 })
1381 .collect::<Vec<_>>()
1382 })
1383 .collect::<Vec<_>>();
1384
1385 let mo_symmetry_projections_opt = if params.analyse_mo_symmetry_projections {
1386 calc_mo_projections_
1387 } else {
1388 None
1389 };
1390
1391 let mo_mirror_parities_opt = if params.analyse_mo_mirror_parities {
1392 Some(
1393 mo_symmetries
1394 .iter()
1395 .map(|spin_mo_symmetries| {
1396 spin_mo_symmetries
1397 .iter()
1398 .map(|mo_sym_opt| {
1399 mo_sym_opt.as_ref().map(|mo_sym| {
1400 deduce_mirror_parities(det_orbit.group(), mo_sym)
1401 })
1402 })
1403 .collect::<Vec<_>>()
1404 })
1405 .collect::<Vec<_>>(),
1406 )
1407 } else {
1408 None
1409 };
1410
1411 let mo_symmetries_thresholds = mo_orbitss
1412 .iter_mut()
1413 .map(|mo_orbits| {
1414 mo_orbits
1415 .par_iter_mut()
1416 .map(|mo_orbit| {
1417 mo_orbit
1418 .smat_eigvals
1419 .as_ref()
1420 .map(|eigvals| {
1421 let mut eigvals_vec = eigvals.iter().collect::<Vec<_>>();
1422 match mo_orbit.eigenvalue_comparison_mode {
1423 EigenvalueComparisonMode::Modulus => {
1424 eigvals_vec.sort_by(|a, b| {
1425 a.abs().partial_cmp(&b.abs()).expect("Unable to compare two eigenvalues based on their moduli.")
1426 });
1427 }
1428 EigenvalueComparisonMode::Real => {
1429 eigvals_vec.sort_by(|a, b| {
1430 a.re().partial_cmp(&b.re()).expect("Unable to compare two eigenvalues based on their real parts.")
1431 });
1432 }
1433 }
1434 let eigval_above = match mo_orbit.eigenvalue_comparison_mode {
1435 EigenvalueComparisonMode::Modulus => eigvals_vec
1436 .iter()
1437 .find(|val| {
1438 val.abs() >= mo_orbit.linear_independence_threshold
1439 })
1440 .copied()
1441 .copied(),
1442 EigenvalueComparisonMode::Real => eigvals_vec
1443 .iter()
1444 .find(|val| {
1445 val.re() >= mo_orbit.linear_independence_threshold
1446 })
1447 .copied()
1448 .copied(),
1449 };
1450 eigvals_vec.reverse();
1451 let eigval_below = match mo_orbit.eigenvalue_comparison_mode {
1452 EigenvalueComparisonMode::Modulus => eigvals_vec
1453 .iter()
1454 .find(|val| {
1455 val.abs() < mo_orbit.linear_independence_threshold
1456 })
1457 .copied()
1458 .copied(),
1459 EigenvalueComparisonMode::Real => eigvals_vec
1460 .iter()
1461 .find(|val| {
1462 val.re() < mo_orbit.linear_independence_threshold
1463 })
1464 .copied()
1465 .copied(),
1466 };
1467 (eigval_above, eigval_below)
1468 })
1469 .unwrap_or((None, None))
1470 })
1471 .collect::<Vec<_>>()
1472 })
1473 .collect::<Vec<_>>();
1474 (
1475 det_symmetry,
1476 Some(mo_symmetries),
1477 mo_symmetry_projections_opt,
1478 mo_mirror_parities_opt,
1479 Some(mo_symmetries_thresholds),
1480 ) } else {
1482 let mut det_orbit = SlaterDeterminantSymmetryOrbit::builder()
1483 .group(&group)
1484 .origin(self.determinant)
1485 .integrality_threshold(params.integrality_threshold)
1486 .linear_independence_threshold(params.linear_independence_threshold)
1487 .symmetry_transformation_kind(params.symmetry_transformation_kind.clone())
1488 .eigenvalue_comparison_mode(params.eigenvalue_comparison_mode.clone())
1489 .build()?;
1490 let det_symmetry = det_orbit
1491 .calc_smat(Some(&sao), sao_h.as_ref(), params.use_cayley_table)
1492 .and_then(|det_orb| det_orb.normalise_smat())
1493 .map_err(|err| err.to_string())
1494 .and_then(|det_orb| {
1495 det_orb.calc_xmat(false).map_err(|err| err.to_string())?;
1496 if params.write_overlap_eigenvalues
1497 && let Some(smat_eigvals) = det_orb.smat_eigvals.as_ref()
1498 {
1499 log_overlap_eigenvalues(
1500 "Determinant orbit overlap eigenvalues",
1501 smat_eigvals,
1502 params.linear_independence_threshold,
1503 ¶ms.eigenvalue_comparison_mode,
1504 );
1505 qsym2_output!("");
1506 }
1507 det_orb.analyse_rep().map_err(|err| err.to_string())
1508 });
1509
1510 {
1511 calc_projections_
1512 }
1513
1514 (det_symmetry, None, None, None, None) };
1516
1517 let (den_symmetries, mo_den_symmetries) = if params.analyse_density_symmetries {
1519 let den_syms = self.determinant.to_densities().map(|densities| {
1520 let mut spin_den_syms = densities
1521 .iter()
1522 .enumerate()
1523 .map(|(ispin, den)| {
1524 let den_sym_res = || {
1525 let mut den_orbit = DensitySymmetryOrbit::builder()
1526 .group(&group)
1527 .origin(den)
1528 .integrality_threshold(params.integrality_threshold)
1529 .linear_independence_threshold(params.linear_independence_threshold)
1530 .symmetry_transformation_kind(
1531 params.symmetry_transformation_kind.clone(),
1532 )
1533 .eigenvalue_comparison_mode(
1534 params.eigenvalue_comparison_mode.clone(),
1535 )
1536 .build()?;
1537 den_orbit
1538 .calc_smat(
1539 self.sao_spatial_4c,
1540 self.sao_spatial_4c_h,
1541 params.use_cayley_table,
1542 )?
1543 .normalise_smat()?
1544 .calc_xmat(false)?;
1545 den_orbit.analyse_rep().map_err(|err| format_err!(err))
1546 };
1547 (
1548 format!("Spin-{ispin} density"),
1549 den_sym_res().map_err(|err| err.to_string()),
1550 )
1551 })
1552 .collect::<Vec<_>>();
1553 let mut extra_den_syms = densities
1554 .calc_extra_densities()
1555 .expect("Unable to calculate extra densities.")
1556 .iter()
1557 .map(|(label, den)| {
1558 let den_sym_res = || {
1559 let mut den_orbit = DensitySymmetryOrbit::builder()
1560 .group(&group)
1561 .origin(den)
1562 .integrality_threshold(params.integrality_threshold)
1563 .linear_independence_threshold(params.linear_independence_threshold)
1564 .symmetry_transformation_kind(
1565 params.symmetry_transformation_kind.clone(),
1566 )
1567 .eigenvalue_comparison_mode(
1568 params.eigenvalue_comparison_mode.clone(),
1569 )
1570 .build()?;
1571 den_orbit
1572 .calc_smat(
1573 self.sao_spatial_4c,
1574 self.sao_spatial_4c_h,
1575 params.use_cayley_table,
1576 )?
1577 .calc_xmat(false)?;
1578 den_orbit.analyse_rep().map_err(|err| format_err!(err))
1579 };
1580 (label.clone(), den_sym_res().map_err(|err| err.to_string()))
1581 })
1582 .collect_vec();
1583 spin_den_syms.append(&mut extra_den_syms);
1584 spin_den_syms
1585 });
1586
1587 let mo_den_syms = if params.analyse_mo_symmetries {
1588 let mo_den_symmetries = self
1589 .determinant
1590 .to_orbitals()
1591 .iter()
1592 .map(|mos| {
1593 mos.par_iter()
1594 .map(|mo| {
1595 let mo_den = mo.to_total_density().ok()?;
1596 let mut mo_den_orbit = DensitySymmetryOrbit::builder()
1597 .group(&group)
1598 .origin(&mo_den)
1599 .integrality_threshold(params.integrality_threshold)
1600 .linear_independence_threshold(
1601 params.linear_independence_threshold,
1602 )
1603 .symmetry_transformation_kind(
1604 params.symmetry_transformation_kind.clone(),
1605 )
1606 .eigenvalue_comparison_mode(
1607 params.eigenvalue_comparison_mode.clone(),
1608 )
1609 .build()
1610 .ok()?;
1611 log::debug!("Computing overlap matrix for an MO density orbit...");
1612 mo_den_orbit
1613 .calc_smat(
1614 self.sao_spatial_4c,
1615 self.sao_spatial_4c_h,
1616 params.use_cayley_table,
1617 )
1618 .ok()?
1619 .normalise_smat()
1620 .ok()?
1621 .calc_xmat(false)
1622 .ok()?;
1623 log::debug!(
1624 "Computing overlap matrix for an MO density orbit... Done."
1625 );
1626 mo_den_orbit.analyse_rep().ok()
1627 })
1628 .collect::<Vec<_>>()
1629 })
1630 .collect::<Vec<_>>();
1631 Some(mo_den_symmetries)
1632 } else {
1633 None
1634 };
1635
1636 (den_syms.ok(), mo_den_syms)
1637 } else {
1638 (None, None)
1639 };
1640
1641 let result = SlaterDeterminantRepAnalysisResult::builder()
1642 .parameters(params)
1643 .determinant(self.determinant)
1644 .group(group)
1645 .determinant_symmetry(det_symmetry)
1646 .determinant_density_symmetries(den_symmetries)
1647 .mo_symmetries(mo_symmetries)
1648 .mo_symmetry_projections(mo_symmetry_projections)
1649 .mo_mirror_parities(mo_mirror_parities)
1650 .mo_symmetries_thresholds(mo_symmetries_thresholds)
1651 .mo_density_symmetries(mo_den_symmetries)
1652 .build()?;
1653 self.result = Some(result);
1654
1655 Ok(())
1656 }
1657}
1658
1659impl<'a, G, T, SC> fmt::Display for SlaterDeterminantRepAnalysisDriver<'a, G, T, SC>
1667where
1668 G: SymmetryGroupProperties + Clone,
1669 G::CharTab: SubspaceDecomposable<T>,
1670 T: ComplexFloat + Lapack,
1671 SC: StructureConstraint + fmt::Display,
1672 <T as ComplexFloat>::Real: From<f64> + fmt::LowerExp + fmt::Debug,
1673{
1674 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1675 write_title(f, "Slater Determinant Symmetry Analysis")?;
1676 writeln!(f)?;
1677 writeln!(f, "{}", self.parameters)?;
1678 Ok(())
1679 }
1680}
1681
1682impl<'a, G, T, SC> fmt::Debug for SlaterDeterminantRepAnalysisDriver<'a, G, T, SC>
1683where
1684 G: SymmetryGroupProperties + Clone,
1685 G::CharTab: SubspaceDecomposable<T>,
1686 T: ComplexFloat + Lapack,
1687 SC: StructureConstraint + fmt::Display,
1688 <T as ComplexFloat>::Real: From<f64> + fmt::LowerExp + fmt::Debug,
1689{
1690 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1691 writeln!(f, "{self}")
1692 }
1693}
1694
1695#[duplicate_item(
1699 duplicate!{
1700 [
1701 dtype_nested sctype_nested;
1702 [ f64 ] [ SpinConstraint ];
1703 [ Complex<f64> ] [ SpinConstraint ];
1704 [ Complex<f64> ] [ SpinOrbitCoupled ];
1705 ]
1706 [
1707 gtype_ [ UnitaryRepresentedSymmetryGroup ]
1708 dtype_ [ dtype_nested ]
1709 sctype_ [ sctype_nested ]
1710 analyse_fn_ [ analyse_representation ]
1711 ]
1712 }
1713 duplicate!{
1714 [
1715 dtype_nested sctype_nested;
1716 [ f64 ] [ SpinConstraint ];
1717 [ Complex<f64> ] [ SpinConstraint ];
1718 [ Complex<f64> ] [ SpinOrbitCoupled ];
1719 ]
1720 [
1721 gtype_ [ MagneticRepresentedSymmetryGroup ]
1722 dtype_ [ dtype_nested ]
1723 sctype_ [ sctype_nested ]
1724 analyse_fn_ [ analyse_corepresentation ]
1725 ]
1726 }
1727)]
1728impl<'a> QSym2Driver for SlaterDeterminantRepAnalysisDriver<'a, gtype_, dtype_, sctype_> {
1729 type Params = SlaterDeterminantRepAnalysisParams<f64>;
1730
1731 type Outcome = SlaterDeterminantRepAnalysisResult<'a, gtype_, dtype_, sctype_>;
1732
1733 fn result(&self) -> Result<&Self::Outcome, anyhow::Error> {
1734 self.result.as_ref().ok_or_else(|| {
1735 format_err!("No Slater determinant representation analysis results found.")
1736 })
1737 }
1738
1739 fn run(&mut self) -> Result<(), anyhow::Error> {
1740 self.log_output_display();
1741 self.analyse_fn_()?;
1742 self.result()?.log_output_display();
1743 Ok(())
1744 }
1745}