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 mo_symmetries: Option<Vec<Vec<Option<<G::CharTab as SubspaceDecomposable<T>>::Decomposition>>>>,
296
297 mo_symmetry_projections: Option<
299 Vec<
300 Vec<
301 Option<
302 Vec<(
303 <<G as CharacterProperties>::CharTab as CharacterTable>::RowSymbol,
304 Complex<f64>,
305 )>,
306 >,
307 >,
308 >,
309 >,
310
311 mo_mirror_parities:
313 Option<Vec<Vec<Option<IndexMap<SymmetryClassSymbol<SymmetryOperation>, MirrorParity>>>>>,
314
315 mo_symmetries_thresholds: Option<Vec<Vec<(Option<T>, Option<T>)>>>,
318
319 determinant_density_symmetries: Option<
323 Vec<(
324 String,
325 Result<<G::CharTab as SubspaceDecomposable<T>>::Decomposition, String>,
326 )>,
327 >,
328
329 mo_density_symmetries:
332 Option<Vec<Vec<Option<<G::CharTab as SubspaceDecomposable<T>>::Decomposition>>>>,
333}
334
335impl<'a, G, T, SC> SlaterDeterminantRepAnalysisResult<'a, G, T, SC>
336where
337 G: SymmetryGroupProperties + Clone,
338 G::CharTab: SubspaceDecomposable<T>,
339 T: ComplexFloat + Lapack,
340 SC: StructureConstraint + Clone + fmt::Display,
341 <T as ComplexFloat>::Real: From<f64> + fmt::LowerExp + fmt::Debug,
342{
343 fn builder() -> SlaterDeterminantRepAnalysisResultBuilder<'a, G, T, SC> {
346 SlaterDeterminantRepAnalysisResultBuilder::default()
347 }
348}
349
350impl<'a, G, T, SC> SlaterDeterminantRepAnalysisResult<'a, G, T, SC>
351where
352 G: SymmetryGroupProperties + Clone,
353 G::CharTab: SubspaceDecomposable<T>,
354 T: ComplexFloat + Lapack,
355 SC: StructureConstraint + fmt::Display,
356 <T as ComplexFloat>::Real: From<f64> + fmt::LowerExp + fmt::Debug,
357{
358 pub fn group(&self) -> &G {
360 &self.group
361 }
362
363 pub fn determinant_symmetry(
365 &self,
366 ) -> &Result<<G::CharTab as SubspaceDecomposable<T>>::Decomposition, String> {
367 &self.determinant_symmetry
368 }
369
370 pub fn mo_symmetries(
372 &self,
373 ) -> &Option<Vec<Vec<Option<<G::CharTab as SubspaceDecomposable<T>>::Decomposition>>>> {
374 &self.mo_symmetries
375 }
376
377 pub fn mo_symmetry_projections(
379 &self,
380 ) -> &Option<
381 Vec<
382 Vec<
383 Option<
384 Vec<(
385 <<G as CharacterProperties>::CharTab as CharacterTable>::RowSymbol,
386 Complex<f64>,
387 )>,
388 >,
389 >,
390 >,
391 > {
392 &self.mo_symmetry_projections
393 }
394
395 pub fn determinant_density_symmetries(
399 &self,
400 ) -> &Option<
401 Vec<(
402 String,
403 Result<<G::CharTab as SubspaceDecomposable<T>>::Decomposition, String>,
404 )>,
405 > {
406 &self.determinant_density_symmetries
407 }
408
409 pub fn mo_density_symmetries(
412 &self,
413 ) -> &Option<Vec<Vec<Option<<G::CharTab as SubspaceDecomposable<T>>::Decomposition>>>> {
414 &self.mo_density_symmetries
415 }
416}
417
418impl<'a, G, T, SC> fmt::Display for SlaterDeterminantRepAnalysisResult<'a, G, T, SC>
419where
420 G: SymmetryGroupProperties + Clone,
421 G::CharTab: SubspaceDecomposable<T>,
422 T: ComplexFloat + Lapack,
423 SC: StructureConstraint + fmt::Display,
424 <T as ComplexFloat>::Real: From<f64> + fmt::LowerExp + fmt::Debug + fmt::Display,
425{
426 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
427 write_subtitle(f, "Orbit-based symmetry analysis results")?;
428 writeln!(f)?;
429 writeln!(
430 f,
431 "> Group: {} ({})",
432 self.group
433 .finite_subgroup_name()
434 .map(|subgroup_name| format!("{} > {}", self.group.name(), subgroup_name))
435 .unwrap_or(self.group.name()),
436 self.group.group_type().to_string().to_lowercase()
437 )?;
438 writeln!(f)?;
439 writeln!(f, "> Overall determinantal result")?;
440 writeln!(
441 f,
442 " Energy : {}",
443 self.determinant
444 .energy()
445 .map(|e| e.to_string())
446 .unwrap_or_else(|err| format!("-- ({err})"))
447 )?;
448 writeln!(
449 f,
450 " Symmetry: {}",
451 self.determinant_symmetry
452 .as_ref()
453 .map(|s| s.to_string())
454 .unwrap_or_else(|err| format!("-- ({err})"))
455 )?;
456 writeln!(f)?;
457
458 if let Some(den_syms) = self.determinant_density_symmetries.as_ref() {
459 writeln!(f, "> Overall determinantal density result")?;
460 let den_type_width = den_syms
461 .iter()
462 .map(|(den_type, _)| den_type.chars().count())
463 .max()
464 .unwrap_or(7)
465 .max(7);
466 for (den_type, den_sym_res) in den_syms.iter() {
467 writeln!(
468 f,
469 " {den_type:<den_type_width$}: {}",
470 den_sym_res
471 .as_ref()
472 .map(|e| e.to_string())
473 .unwrap_or_else(|err| format!("-- ({err})"))
474 )?;
475 }
476 writeln!(f)?;
477 }
478
479 if let Some(mo_symmetries) = self.mo_symmetries.as_ref() {
480 let mo_spin_index_length = 4;
481 let mo_index_length = mo_symmetries
482 .iter()
483 .map(|spin_mo_symmetries| spin_mo_symmetries.len())
484 .max()
485 .and_then(|max_mo_length| usize::try_from(max_mo_length.ilog10() + 2).ok())
486 .unwrap_or(4);
487 let mo_occ_length = 5;
488 let mo_symmetry_length = mo_symmetries
489 .iter()
490 .flat_map(|spin_mo_symmetries| {
491 spin_mo_symmetries.iter().map(|mo_sym| {
492 mo_sym
493 .as_ref()
494 .map(|sym| sym.to_string())
495 .unwrap_or("--".to_string())
496 .chars()
497 .count()
498 })
499 })
500 .max()
501 .unwrap_or(0)
502 .max(8);
503 let mo_energies_opt = self.determinant.mo_energies();
504 let mo_energy_length = mo_energies_opt
505 .map(|mo_energies| {
506 mo_energies
507 .iter()
508 .flat_map(|spin_mo_energies| {
509 spin_mo_energies.map(|v| format!("{v:+.7}").chars().count())
510 })
511 .max()
512 .unwrap_or(0)
513 })
514 .unwrap_or(0)
515 .max(6);
516
517 let mo_eig_above_length: usize = self
518 .mo_symmetries_thresholds
519 .as_ref()
520 .map(|mo_symmetries_thresholds| {
521 mo_symmetries_thresholds
522 .iter()
523 .flat_map(|spin_mo_symmetries_thresholds| {
524 spin_mo_symmetries_thresholds.iter().map(|(above, _)| {
525 above
526 .as_ref()
527 .map(|eig| format!("{eig:+.3e}"))
528 .unwrap_or("--".to_string())
529 .chars()
530 .count()
531 })
532 })
533 .max()
534 .unwrap_or(10)
535 .max(10)
536 })
537 .unwrap_or(10);
538 let mo_eig_below_length: usize = self
539 .mo_symmetries_thresholds
540 .as_ref()
541 .map(|mo_symmetries_thresholds| {
542 mo_symmetries_thresholds
543 .iter()
544 .flat_map(|spin_mo_symmetries_thresholds| {
545 spin_mo_symmetries_thresholds.iter().map(|(_, below)| {
546 below
547 .as_ref()
548 .map(|eig| format!("{eig:+.3e}"))
549 .unwrap_or("--".to_string())
550 .chars()
551 .count()
552 })
553 })
554 .max()
555 .unwrap_or(10)
556 .max(10)
557 })
558 .unwrap_or(10);
559
560 let precision = Real::ceil(ComplexFloat::abs(ComplexFloat::log10(
561 self.parameters.linear_independence_threshold,
562 )))
563 .to_usize()
564 .expect("Unable to convert the linear independence threshold exponent to `usize`.");
565 let mo_sym_projections_str_opt =
566 self.mo_symmetry_projections
567 .as_ref()
568 .map(|mo_sym_projectionss| {
569 mo_sym_projectionss
570 .iter()
571 .enumerate()
572 .map(|(ispin, mo_sym_projections)| {
573 mo_sym_projections
574 .iter()
575 .enumerate()
576 .map(|(imo, mo_sym_projection)| {
577 mo_sym_projection
578 .as_ref()
579 .map(|sym_proj| {
580 if let Some(mo_symmetriess) = self.mo_symmetries() {
581 if let Some(sym) = &mo_symmetriess[ispin][imo] {
582 let sym_proj_hashmap = sym_proj
583 .iter()
584 .cloned()
585 .collect::<HashMap<_, _>>();
586 sym.subspaces()
587 .iter()
588 .map(|(subspace, _)| {
589 format!(
590 "{subspace}: {}",
591 sym_proj_hashmap
592 .get(subspace)
593 .map(|composition| {
594 if abs_diff_eq!(
595 composition.im,
596 0.0,
597 epsilon = self.parameters.linear_independence_threshold.to_f64().expect("Unable to convert the linear independence threshold to `f64`.")
598 ) {
599 format!(
600 "{:+.precision$}",
601 composition.re
602 )
603 } else {
604 format!(
605 "{composition:+.precision$}")
606 }
607 })
608 .unwrap_or(
609 "--".to_string()
610 )
611 )
612 })
613 .join(", ")
614 } else {
615 "--".to_string()
616 }
617 } else {
618 "--".to_string()
619 }
620 })
621 .unwrap_or("--".to_string())
622 })
623 .collect::<Vec<_>>()
624 })
625 .collect::<Vec<_>>()
626 });
627 let mo_sym_projections_length_opt =
628 mo_sym_projections_str_opt
629 .as_ref()
630 .map(|mo_sym_projectionss| {
631 mo_sym_projectionss
632 .iter()
633 .flat_map(|mo_sym_projections| {
634 mo_sym_projections
635 .iter()
636 .map(|mo_sym_projection| mo_sym_projection.chars().count())
637 })
638 .max()
639 .unwrap_or(18)
640 .max(18)
641 });
642 let mo_sym_projections_length = mo_sym_projections_length_opt.unwrap_or(0);
643 let mo_sym_projections_gap = mo_sym_projections_length_opt.map(|_| 2).unwrap_or(0);
644 let mo_sym_projections_heading = mo_sym_projections_length_opt
645 .map(|_| "MO sym. projection")
646 .unwrap_or("");
647
648 let mirrors = self
649 .group
650 .filter_cc_symbols(|cc| cc.is_spatial_reflection());
651 let mo_mirror_parities_length_opt = self.mo_mirror_parities.as_ref().map(|_| {
652 let mirror_heading = mirrors.iter().map(|sigma| format!("p[{sigma}]")).join(" ");
653 let length = mirror_heading.chars().count();
654 (mirror_heading, length)
655 });
656 let mo_mirror_parities_gap = mo_mirror_parities_length_opt
657 .as_ref()
658 .map(|_| 2)
659 .unwrap_or(0);
660 let (mo_mirror_parities_heading, mo_mirror_parities_length) =
661 mo_mirror_parities_length_opt.unwrap_or((String::new(), 0));
662
663 let mo_den_symss_str_opt = self.mo_density_symmetries.as_ref().map(|mo_den_symss| {
664 mo_den_symss
665 .iter()
666 .map(|mo_den_syms| {
667 mo_den_syms
668 .iter()
669 .map(|mo_den_sym| {
670 mo_den_sym
671 .as_ref()
672 .map(|sym| sym.to_string())
673 .unwrap_or("--".to_string())
674 })
675 .collect::<Vec<_>>()
676 })
677 .collect::<Vec<_>>()
678 });
679 let mo_density_length_opt = mo_den_symss_str_opt.as_ref().map(|mo_den_symss| {
680 mo_den_symss
681 .iter()
682 .flat_map(|mo_den_syms| {
683 mo_den_syms
684 .iter()
685 .map(|mo_den_sym| mo_den_sym.chars().count())
686 })
687 .max()
688 .unwrap_or(12)
689 .max(12)
690 });
691 let mo_density_length = mo_density_length_opt.unwrap_or(0);
692 let mo_density_gap = mo_density_length_opt.map(|_| 2).unwrap_or(0);
693 let mo_density_heading = mo_density_length_opt.map(|_| "Density sym.").unwrap_or("");
694
695 let table_width = 14
696 + mo_spin_index_length
697 + mo_index_length
698 + mo_occ_length
699 + mo_energy_length
700 + mo_symmetry_length
701 + mo_mirror_parities_gap
702 + mo_mirror_parities_length
703 + mo_eig_above_length
704 + mo_eig_below_length
705 + mo_sym_projections_gap
706 + mo_sym_projections_length
707 + mo_density_gap
708 + mo_density_length;
709
710 writeln!(f, "> Molecular orbital results")?;
711 writeln!(
712 f,
713 " Structure constraint: {}",
714 self.determinant
715 .structure_constraint()
716 .to_string()
717 .to_lowercase()
718 )?;
719 if self.mo_mirror_parities.as_ref().is_some() {
720 writeln!(f, "")?;
721 writeln!(
722 f,
723 "Column p[σ] gives the parity under the reflection class σ: {} => even, {} => odd, {} => neither.",
724 MirrorParity::Even,
725 MirrorParity::Odd,
726 MirrorParity::Neither
727 )?;
728 }
729 writeln!(f, "{}", "┈".repeat(table_width))?;
730 if mo_density_length > 0 {
731 writeln!(
732 f,
733 " {:>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$}{}{}",
734 "Spin",
735 "MO",
736 "Occ.",
737 "Energy",
738 "Symmetry",
739 " ".repeat(mo_mirror_parities_gap),
740 mo_mirror_parities_heading,
741 "Eig. above",
742 "Eig. below",
743 " ".repeat(mo_sym_projections_gap),
744 mo_sym_projections_heading,
745 " ".repeat(mo_density_gap),
746 mo_density_heading
747 )?;
748 } else if mo_sym_projections_length > 0 {
749 writeln!(
750 f,
751 " {:>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$}{}{}",
752 "Spin",
753 "MO",
754 "Occ.",
755 "Energy",
756 "Symmetry",
757 " ".repeat(mo_mirror_parities_gap),
758 mo_mirror_parities_heading,
759 "Eig. above",
760 "Eig. below",
761 " ".repeat(mo_sym_projections_gap),
762 mo_sym_projections_heading,
763 )?;
764 } else {
765 writeln!(
766 f,
767 " {:>mo_spin_index_length$} {:>mo_index_length$} {:<mo_occ_length$} {:<mo_energy_length$} {:<mo_symmetry_length$}{}{:mo_mirror_parities_length$} {:<mo_eig_above_length$} {}",
768 "Spin",
769 "MO",
770 "Occ.",
771 "Energy",
772 "Symmetry",
773 " ".repeat(mo_mirror_parities_gap),
774 mo_mirror_parities_heading,
775 "Eig. above",
776 "Eig. below",
777 )?;
778 };
779 writeln!(f, "{}", "┈".repeat(table_width))?;
780
781 let empty_string = String::new();
782 for (spini, spin_mo_symmetries) in mo_symmetries.iter().enumerate() {
783 writeln!(f, " Spin {spini}")?;
784 for (moi, mo_sym) in spin_mo_symmetries.iter().enumerate() {
785 let occ_str = self
786 .determinant
787 .occupations()
788 .get(spini)
789 .and_then(|spin_occs| spin_occs.get(moi))
790 .map(|occ| format!("{occ:>.3}"))
791 .unwrap_or("--".to_string());
792 let mo_energy_str = mo_energies_opt
793 .and_then(|mo_energies| mo_energies.get(spini))
794 .and_then(|spin_mo_energies| spin_mo_energies.get(moi))
795 .map(|mo_energy| format!("{mo_energy:>+mo_energy_length$.7}"))
796 .unwrap_or("--".to_string());
797 let mo_sym_str = mo_sym
798 .as_ref()
799 .map(|sym| sym.to_string())
800 .unwrap_or("--".to_string());
801
802 let mo_mirror_parities_str = self
803 .mo_mirror_parities
804 .as_ref()
805 .and_then(|mo_mirror_paritiess| {
806 mo_mirror_paritiess
807 .get(spini)
808 .and_then(|spin_mo_mirror_parities| {
809 spin_mo_mirror_parities
810 .get(moi)
811 .map(|mo_mirror_parities_opt| {
812 mo_mirror_parities_opt
813 .as_ref()
814 .map(|mo_mirror_parities| {
815 mirrors
816 .iter()
817 .map(|sigma| {
818 let sigma_length =
819 sigma.to_string().chars().count()
820 + 3;
821 mo_mirror_parities
822 .get(sigma)
823 .map(|parity| {
824 format!(
825 "{:^sigma_length$}",
826 parity.to_string()
827 )
828 })
829 .unwrap_or_else(|| {
830 format!(
831 "{:^sigma_length$}",
832 "--"
833 )
834 })
835 })
836 .join(" ")
837 })
838 .unwrap_or(String::new())
839 })
840 })
841 })
842 .unwrap_or(String::new());
843
844 let (eig_above_str, eig_below_str) = self
845 .mo_symmetries_thresholds
846 .as_ref()
847 .map(|mo_symmetries_thresholds| {
848 mo_symmetries_thresholds
849 .get(spini)
850 .and_then(|spin_mo_symmetries_thresholds| {
851 spin_mo_symmetries_thresholds.get(moi)
852 })
853 .map(|(eig_above_opt, eig_below_opt)| {
854 (
855 eig_above_opt
856 .map(|eig_above| format!("{eig_above:>+.3e}"))
857 .unwrap_or("--".to_string()),
858 eig_below_opt
859 .map(|eig_below| format!("{eig_below:>+.3e}"))
860 .unwrap_or("--".to_string()),
861 )
862 })
863 .unwrap_or(("--".to_string(), "--".to_string()))
864 })
865 .unwrap_or(("--".to_string(), "--".to_string()));
866
867 let mo_symmetry_projections_str = mo_sym_projections_str_opt
868 .as_ref()
869 .and_then(|mo_symmetry_projectionss| {
870 mo_symmetry_projectionss.get(spini).and_then(
871 |spin_mo_symmetry_projections| {
872 spin_mo_symmetry_projections.get(moi)
873 },
874 )
875 })
876 .unwrap_or(&empty_string);
877
878 let mo_density_symmetries_str = mo_den_symss_str_opt
879 .as_ref()
880 .and_then(|mo_density_symmetriess| {
881 mo_density_symmetriess.get(spini).and_then(
882 |spin_mo_density_symmetries| spin_mo_density_symmetries.get(moi),
883 )
884 })
885 .unwrap_or(&empty_string);
886
887 match (mo_density_length, mo_sym_projections_length) {
888 (0, 0) => {
889 writeln!(
890 f,
891 " {spini:>mo_spin_index_length$} \
892 {moi:>mo_index_length$} \
893 {occ_str:<mo_occ_length$} \
894 {mo_energy_str:<mo_energy_length$} \
895 {mo_sym_str:<mo_symmetry_length$}\
896 {}{:mo_mirror_parities_length$} \
897 {eig_above_str:<mo_eig_above_length$} \
898 {eig_below_str}",
899 " ".repeat(mo_mirror_parities_gap),
900 mo_mirror_parities_str,
901 )?;
902 }
903 (_, 0) => {
904 writeln!(
905 f,
906 " {spini:>mo_spin_index_length$} \
907 {moi:>mo_index_length$} \
908 {occ_str:<mo_occ_length$} \
909 {mo_energy_str:<mo_energy_length$} \
910 {mo_sym_str:<mo_symmetry_length$}\
911 {}{:mo_mirror_parities_length$} \
912 {eig_above_str:<mo_eig_above_length$} \
913 {eig_below_str:<mo_eig_below_length$} \
914 {mo_density_symmetries_str}",
915 " ".repeat(mo_mirror_parities_gap),
916 mo_mirror_parities_str,
917 )?;
918 }
919 (0, _) => {
920 writeln!(
921 f,
922 " {spini:>mo_spin_index_length$} \
923 {moi:>mo_index_length$} \
924 {occ_str:<mo_occ_length$} \
925 {mo_energy_str:<mo_energy_length$} \
926 {mo_sym_str:<mo_symmetry_length$}\
927 {}{:mo_mirror_parities_length$} \
928 {eig_above_str:<mo_eig_above_length$} \
929 {eig_below_str:<mo_eig_below_length$} \
930 {mo_symmetry_projections_str}",
931 " ".repeat(mo_mirror_parities_gap),
932 mo_mirror_parities_str,
933 )?;
934 }
935 (_, _) => {
936 writeln!(
937 f,
938 " {spini:>mo_spin_index_length$} \
939 {moi:>mo_index_length$} \
940 {occ_str:<mo_occ_length$} \
941 {mo_energy_str:<mo_energy_length$} \
942 {mo_sym_str:<mo_symmetry_length$}\
943 {}{:mo_mirror_parities_length$} \
944 {eig_above_str:<mo_eig_above_length$} \
945 {eig_below_str:<mo_eig_below_length$} \
946 {mo_symmetry_projections_str:<mo_sym_projections_length$} \
947 {mo_density_symmetries_str}",
948 " ".repeat(mo_mirror_parities_gap),
949 mo_mirror_parities_str,
950 )?;
951 }
952 }
953 }
954 }
955
956 writeln!(f, "{}", "┈".repeat(table_width))?;
957 writeln!(f)?;
958 }
959
960 Ok(())
961 }
962}
963
964impl<'a, G, T, SC> fmt::Debug for SlaterDeterminantRepAnalysisResult<'a, G, T, SC>
965where
966 G: SymmetryGroupProperties + Clone,
967 G::CharTab: SubspaceDecomposable<T>,
968 T: ComplexFloat + Lapack,
969 SC: StructureConstraint + fmt::Display,
970 <T as ComplexFloat>::Real: From<f64> + fmt::LowerExp + fmt::Debug + fmt::Display,
971{
972 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
973 writeln!(f, "{self}")
974 }
975}
976
977#[derive(Clone, Builder)]
987#[builder(build_fn(validate = "Self::validate"))]
988pub struct SlaterDeterminantRepAnalysisDriver<'a, G, T, SC>
989where
990 G: SymmetryGroupProperties + Clone,
991 G::CharTab: SubspaceDecomposable<T>,
992 T: ComplexFloat + Lapack,
993 SC: StructureConstraint + fmt::Display,
994 <T as ComplexFloat>::Real: From<f64> + fmt::LowerExp + fmt::Debug,
995{
996 parameters: &'a SlaterDeterminantRepAnalysisParams<<T as ComplexFloat>::Real>,
998
999 determinant: &'a SlaterDeterminant<'a, T, SC>,
1001
1002 symmetry_group: &'a SymmetryGroupDetectionResult,
1005
1006 sao: &'a Array2<T>,
1011
1012 #[builder(default = "None")]
1019 sao_h: Option<&'a Array2<T>>,
1020
1021 #[builder(default = "None")]
1024 sao_spatial_4c: Option<&'a Array4<T>>,
1025
1026 #[builder(default = "None")]
1031 sao_spatial_4c_h: Option<&'a Array4<T>>,
1032
1033 angular_function_parameters: &'a AngularFunctionRepAnalysisParams,
1035
1036 #[builder(setter(skip), default = "None")]
1038 result: Option<SlaterDeterminantRepAnalysisResult<'a, G, T, SC>>,
1039}
1040
1041impl<'a, G, T, SC> SlaterDeterminantRepAnalysisDriverBuilder<'a, G, T, SC>
1042where
1043 G: SymmetryGroupProperties + Clone,
1044 G::CharTab: SubspaceDecomposable<T>,
1045 T: ComplexFloat + Lapack,
1046 SC: StructureConstraint + fmt::Display,
1047 <T as ComplexFloat>::Real: From<f64> + fmt::LowerExp + fmt::Debug,
1048{
1049 fn validate(&self) -> Result<(), String> {
1050 let params = self
1051 .parameters
1052 .ok_or("No Slater determinant representation analysis parameters found.".to_string())?;
1053
1054 let sym_res = self
1055 .symmetry_group
1056 .ok_or("No symmetry group information found.".to_string())?;
1057
1058 let sao = self.sao.ok_or("No SAO matrix found.".to_string())?;
1059
1060 if let Some(sao_h) = self.sao_h.flatten() {
1061 if sao_h.shape() != sao.shape() {
1062 return Err("Mismatched shapes between `sao` and `sao_h`.".to_string());
1063 }
1064 }
1065
1066 match (
1067 self.sao_spatial_4c.flatten(),
1068 self.sao_spatial_4c_h.flatten(),
1069 ) {
1070 (Some(sao_spatial_4c), Some(sao_spatial_4c_h)) => {
1071 if sao_spatial_4c_h.shape() != sao_spatial_4c.shape() {
1072 return Err(
1073 "Mismatched shapes between `sao_spatial_4c` and `sao_spatial_4c_h`."
1074 .to_string(),
1075 );
1076 }
1077 }
1078 (None, Some(_)) => {
1079 return Err("`sao_spatial_4c_h` is provided without `sao_spatial_4c`.".to_string());
1080 }
1081 _ => {}
1082 }
1083
1084 let det = self
1085 .determinant
1086 .ok_or("No Slater determinant found.".to_string())?;
1087
1088 let sym = if params.use_magnetic_group.is_some() {
1089 sym_res
1090 .magnetic_symmetry
1091 .as_ref()
1092 .ok_or("Magnetic symmetry requested for representation analysis, but no magnetic symmetry found.")?
1093 } else {
1094 &sym_res.unitary_symmetry
1095 };
1096
1097 if sym.is_infinite() && params.infinite_order_to_finite.is_none() {
1098 Err(format!(
1099 "Representation analysis cannot be performed using the entirety of the infinite group `{}`. \
1100 Consider setting the parameter `infinite_order_to_finite` to restrict to a finite subgroup instead.",
1101 sym.group_name
1102 .as_ref()
1103 .expect("No symmetry group name found.")
1104 ))
1105 } else {
1106 let nfuncs_set = det
1107 .baos()
1108 .iter()
1109 .map(|bao| bao.n_funcs())
1110 .collect::<HashSet<_>>();
1111 let uniform_component_check = nfuncs_set.len() == 1
1112 && {
1113 let nfuncs = nfuncs_set.iter().next().ok_or_else(|| "Unable to extract the uniform number of basis functions per explicit component.".to_string())?;
1114 sao.nrows() == *nfuncs && sao.ncols() == *nfuncs
1115 };
1116 let total_component_check = {
1117 let nfuncs_tot = det.baos().iter().map(|bao| bao.n_funcs()).sum::<usize>();
1118 sao.nrows() == nfuncs_tot && sao.ncols() == nfuncs_tot
1119 };
1120 if !uniform_component_check && !total_component_check {
1121 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())
1122 } else {
1123 Ok(())
1124 }
1125 }
1126 }
1127}
1128
1129impl<'a, G, T, SC> SlaterDeterminantRepAnalysisDriver<'a, G, T, SC>
1137where
1138 G: SymmetryGroupProperties + Clone,
1139 G::CharTab: SubspaceDecomposable<T>,
1140 T: ComplexFloat + Lapack,
1141 SC: StructureConstraint + Clone + fmt::Display,
1142 <T as ComplexFloat>::Real: From<f64> + fmt::LowerExp + fmt::Debug,
1143{
1144 pub fn builder() -> SlaterDeterminantRepAnalysisDriverBuilder<'a, G, T, SC> {
1146 SlaterDeterminantRepAnalysisDriverBuilder::default()
1147 }
1148
1149 fn construct_sao(&self) -> Result<(Array2<T>, Option<Array2<T>>), anyhow::Error> {
1152 let nbas_set = self
1153 .determinant
1154 .baos()
1155 .iter()
1156 .map(|bao| bao.n_funcs())
1157 .collect::<HashSet<_>>();
1158 let uniform_component = nbas_set.len() == 1;
1159 let ncomps = self
1160 .determinant
1161 .structure_constraint()
1162 .n_explicit_comps_per_coefficient_matrix();
1163 let provided_dim = self.sao.nrows();
1164
1165 if uniform_component {
1166 let nbas = nbas_set.iter().next().ok_or_else(|| format_err!("Unable to extract the uniform number of basis functions per explicit component."))?;
1167 if provided_dim == *nbas {
1168 let sao = {
1169 let mut sao_mut = Array2::zeros((ncomps * nbas, ncomps * nbas));
1170 (0..ncomps).for_each(|icomp| {
1171 let start = icomp * nbas;
1172 let end = (icomp + 1) * nbas;
1173 sao_mut
1174 .slice_mut(s![start..end, start..end])
1175 .assign(self.sao);
1176 });
1177 sao_mut
1178 };
1179
1180 let sao_h = self.sao_h.map(|sao_h| {
1181 let mut sao_h_mut = Array2::zeros((ncomps * nbas, ncomps * nbas));
1182 (0..ncomps).for_each(|icomp| {
1183 let start = icomp * nbas;
1184 let end = (icomp + 1) * nbas;
1185 sao_h_mut
1186 .slice_mut(s![start..end, start..end])
1187 .assign(sao_h);
1188 });
1189 sao_h_mut
1190 });
1191
1192 Ok((sao, sao_h))
1193 } else {
1194 ensure!(provided_dim == nbas * ncomps);
1195 Ok((self.sao.clone(), self.sao_h.cloned()))
1196 }
1197 } else {
1198 let nbas_tot = self
1199 .determinant
1200 .baos()
1201 .iter()
1202 .map(|bao| bao.n_funcs())
1203 .sum::<usize>();
1204 ensure!(provided_dim == nbas_tot);
1205 Ok((self.sao.clone(), self.sao_h.cloned()))
1206 }
1207 }
1208}
1209
1210impl<'a, T, SC> SlaterDeterminantRepAnalysisDriver<'a, UnitaryRepresentedSymmetryGroup, T, SC>
1214where
1215 T: ComplexFloat + Lapack + Sync + Send,
1216 <T as ComplexFloat>::Real: From<f64> + fmt::LowerExp + fmt::Debug + Sync + Send,
1217 SC: StructureConstraint + fmt::Display,
1218 for<'b> Complex<f64>: Mul<&'b T, Output = Complex<f64>>,
1219{
1220 fn_construct_unitary_group!(
1221 construct_unitary_group
1224 );
1225}
1226
1227impl<'a, T, SC> SlaterDeterminantRepAnalysisDriver<'a, MagneticRepresentedSymmetryGroup, T, SC>
1231where
1232 T: ComplexFloat + Lapack + Sync + Send,
1233 <T as ComplexFloat>::Real: From<f64> + Sync + Send + fmt::LowerExp + fmt::Debug,
1234 SC: StructureConstraint + fmt::Display,
1235 for<'b> Complex<f64>: Mul<&'b T, Output = Complex<f64>>,
1236{
1237 fn_construct_magnetic_group!(
1238 construct_magnetic_group
1241 );
1242}
1243
1244#[duplicate_item(
1248 duplicate!{
1249 [
1250 dtype_nested sctype_nested;
1251 [ f64 ] [ SpinConstraint ];
1252 [ Complex<f64> ] [ SpinConstraint ];
1253 [ Complex<f64> ] [ SpinOrbitCoupled ];
1254 ]
1255 [
1256 gtype_ [ UnitaryRepresentedSymmetryGroup ]
1257 dtype_ [ dtype_nested ]
1258 sctype_ [ sctype_nested ]
1259 doc_sub_ [ "Performs representation analysis using a unitary-represented group and stores the result." ]
1260 analyse_fn_ [ analyse_representation ]
1261 construct_group_ [ self.construct_unitary_group()? ]
1262 calc_projections_ [
1263 log_subtitle("Slater determinant projection decompositions");
1264 qsym2_output!("");
1265 qsym2_output!(" Projections are defined w.r.t. the following inner product:");
1266 qsym2_output!(" {}", det_orbit.origin().overlap_definition());
1267 qsym2_output!("");
1268 det_orbit
1269 .projections_to_string(
1270 &det_orbit.calc_projection_compositions()?,
1271 params.integrality_threshold,
1272 )
1273 .log_output_display();
1274 qsym2_output!("");
1275 ]
1276 calc_mo_projections_ [
1277 let mo_projectionss = mo_orbitss.iter().map(|mo_orbits| {
1278 mo_orbits
1279 .iter()
1280 .map(|mo_orbit| mo_orbit.calc_projection_compositions().ok())
1281 .collect::<Vec<_>>()
1282 }).collect::<Vec<_>>();
1283 Some(mo_projectionss)
1284 ]
1285 ]
1286 }
1287 duplicate!{
1288 [
1289 dtype_nested sctype_nested;
1290 [ f64 ] [ SpinConstraint ];
1291 [ Complex<f64> ] [ SpinConstraint ];
1292 [ Complex<f64> ] [ SpinOrbitCoupled ];
1293 ]
1294 [
1295 gtype_ [ MagneticRepresentedSymmetryGroup ]
1296 dtype_ [ dtype_nested ]
1297 sctype_ [ sctype_nested ]
1298 doc_sub_ [ "Performs corepresentation analysis using a magnetic-represented group and stores the result." ]
1299 analyse_fn_ [ analyse_corepresentation ]
1300 construct_group_ [ self.construct_magnetic_group()? ]
1301 calc_projections_ [ ]
1302 calc_mo_projections_ [
1303 None::<Vec<Vec<Option<Vec<(MullikenIrcorepSymbol, Complex<f64>)>>>>>
1304 ]
1305 ]
1306 }
1307)]
1308impl<'a> SlaterDeterminantRepAnalysisDriver<'a, gtype_, dtype_, sctype_> {
1309 #[doc = doc_sub_]
1310 fn analyse_fn_(&mut self) -> Result<(), anyhow::Error> {
1311 let params = self.parameters;
1312 let (sao, sao_h) = self.construct_sao()?;
1313 let group = construct_group_;
1314 log_cc_transversal(&group);
1315 let _ = find_angular_function_representation(&group, self.angular_function_parameters);
1316 if group.is_double_group() {
1317 let _ = find_spinor_function_representation(&group, self.angular_function_parameters);
1318 }
1319 for (bao_i, bao) in self.determinant.baos().iter().enumerate() {
1320 log_bao(bao, Some(bao_i));
1321 }
1322
1323 let (
1325 det_symmetry,
1326 mo_symmetries,
1327 mo_symmetry_projections,
1328 mo_mirror_parities,
1329 mo_symmetries_thresholds,
1330 ) = if params.analyse_mo_symmetries {
1331 let mos = self.determinant.to_orbitals();
1332 let (mut det_orbit, mut mo_orbitss) = generate_det_mo_orbits(
1333 self.determinant,
1334 &mos,
1335 &group,
1336 &sao,
1337 sao_h.as_ref(),
1338 params.integrality_threshold,
1339 params.linear_independence_threshold,
1340 params.symmetry_transformation_kind.clone(),
1341 params.eigenvalue_comparison_mode.clone(),
1342 params.use_cayley_table,
1343 )?;
1344 det_orbit.calc_xmat(false)?;
1345 if params.write_overlap_eigenvalues {
1346 if let Some(smat_eigvals) = det_orbit.smat_eigvals.as_ref() {
1347 log_overlap_eigenvalues(
1348 "Determinant orbit overlap eigenvalues",
1349 smat_eigvals,
1350 params.linear_independence_threshold,
1351 ¶ms.eigenvalue_comparison_mode,
1352 );
1353 qsym2_output!("");
1354 }
1355 }
1356
1357 let det_symmetry = det_orbit.analyse_rep().map_err(|err| err.to_string());
1358
1359 {
1360 calc_projections_
1361 }
1362
1363 let mo_symmetries = mo_orbitss
1364 .iter_mut()
1365 .map(|mo_orbits| {
1366 mo_orbits
1367 .par_iter_mut()
1368 .map(|mo_orbit| {
1369 mo_orbit.calc_xmat(false).ok()?;
1370 mo_orbit.analyse_rep().ok()
1371 })
1372 .collect::<Vec<_>>()
1373 })
1374 .collect::<Vec<_>>();
1375
1376 let mo_symmetry_projections_opt = if params.analyse_mo_symmetry_projections {
1377 calc_mo_projections_
1378 } else {
1379 None
1380 };
1381
1382 let mo_mirror_parities_opt = if params.analyse_mo_mirror_parities {
1383 Some(
1384 mo_symmetries
1385 .iter()
1386 .map(|spin_mo_symmetries| {
1387 spin_mo_symmetries
1388 .iter()
1389 .map(|mo_sym_opt| {
1390 mo_sym_opt.as_ref().map(|mo_sym| {
1391 deduce_mirror_parities(det_orbit.group(), mo_sym)
1392 })
1393 })
1394 .collect::<Vec<_>>()
1395 })
1396 .collect::<Vec<_>>(),
1397 )
1398 } else {
1399 None
1400 };
1401
1402 let mo_symmetries_thresholds = mo_orbitss
1403 .iter_mut()
1404 .map(|mo_orbits| {
1405 mo_orbits
1406 .par_iter_mut()
1407 .map(|mo_orbit| {
1408 mo_orbit
1409 .smat_eigvals
1410 .as_ref()
1411 .map(|eigvals| {
1412 let mut eigvals_vec = eigvals.iter().collect::<Vec<_>>();
1413 match mo_orbit.eigenvalue_comparison_mode {
1414 EigenvalueComparisonMode::Modulus => {
1415 eigvals_vec.sort_by(|a, b| {
1416 a.abs().partial_cmp(&b.abs()).expect("Unable to compare two eigenvalues based on their moduli.")
1417 });
1418 }
1419 EigenvalueComparisonMode::Real => {
1420 eigvals_vec.sort_by(|a, b| {
1421 a.re().partial_cmp(&b.re()).expect("Unable to compare two eigenvalues based on their real parts.")
1422 });
1423 }
1424 }
1425 let eigval_above = match mo_orbit.eigenvalue_comparison_mode {
1426 EigenvalueComparisonMode::Modulus => eigvals_vec
1427 .iter()
1428 .find(|val| {
1429 val.abs() >= mo_orbit.linear_independence_threshold
1430 })
1431 .copied()
1432 .copied(),
1433 EigenvalueComparisonMode::Real => eigvals_vec
1434 .iter()
1435 .find(|val| {
1436 val.re() >= mo_orbit.linear_independence_threshold
1437 })
1438 .copied()
1439 .copied(),
1440 };
1441 eigvals_vec.reverse();
1442 let eigval_below = match mo_orbit.eigenvalue_comparison_mode {
1443 EigenvalueComparisonMode::Modulus => eigvals_vec
1444 .iter()
1445 .find(|val| {
1446 val.abs() < mo_orbit.linear_independence_threshold
1447 })
1448 .copied()
1449 .copied(),
1450 EigenvalueComparisonMode::Real => eigvals_vec
1451 .iter()
1452 .find(|val| {
1453 val.re() < mo_orbit.linear_independence_threshold
1454 })
1455 .copied()
1456 .copied(),
1457 };
1458 (eigval_above, eigval_below)
1459 })
1460 .unwrap_or((None, None))
1461 })
1462 .collect::<Vec<_>>()
1463 })
1464 .collect::<Vec<_>>();
1465 (
1466 det_symmetry,
1467 Some(mo_symmetries),
1468 mo_symmetry_projections_opt,
1469 mo_mirror_parities_opt,
1470 Some(mo_symmetries_thresholds),
1471 ) } else {
1473 let mut det_orbit = SlaterDeterminantSymmetryOrbit::builder()
1474 .group(&group)
1475 .origin(self.determinant)
1476 .integrality_threshold(params.integrality_threshold)
1477 .linear_independence_threshold(params.linear_independence_threshold)
1478 .symmetry_transformation_kind(params.symmetry_transformation_kind.clone())
1479 .eigenvalue_comparison_mode(params.eigenvalue_comparison_mode.clone())
1480 .build()?;
1481 let det_symmetry = det_orbit
1482 .calc_smat(Some(&sao), sao_h.as_ref(), params.use_cayley_table)
1483 .and_then(|det_orb| det_orb.normalise_smat())
1484 .map_err(|err| err.to_string())
1485 .and_then(|det_orb| {
1486 det_orb.calc_xmat(false).map_err(|err| err.to_string())?;
1487 if params.write_overlap_eigenvalues {
1488 if let Some(smat_eigvals) = det_orb.smat_eigvals.as_ref() {
1489 log_overlap_eigenvalues(
1490 "Determinant orbit overlap eigenvalues",
1491 smat_eigvals,
1492 params.linear_independence_threshold,
1493 ¶ms.eigenvalue_comparison_mode,
1494 );
1495 qsym2_output!("");
1496 }
1497 }
1498 det_orb.analyse_rep().map_err(|err| err.to_string())
1499 });
1500
1501 {
1502 calc_projections_
1503 }
1504
1505 (det_symmetry, None, None, None, None) };
1507
1508 let (den_symmetries, mo_den_symmetries) = if params.analyse_density_symmetries {
1510 let den_syms = self.determinant.to_densities().map(|densities| {
1511 let mut spin_den_syms = densities
1512 .iter()
1513 .enumerate()
1514 .map(|(ispin, den)| {
1515 let den_sym_res = || {
1516 let mut den_orbit = DensitySymmetryOrbit::builder()
1517 .group(&group)
1518 .origin(den)
1519 .integrality_threshold(params.integrality_threshold)
1520 .linear_independence_threshold(params.linear_independence_threshold)
1521 .symmetry_transformation_kind(
1522 params.symmetry_transformation_kind.clone(),
1523 )
1524 .eigenvalue_comparison_mode(
1525 params.eigenvalue_comparison_mode.clone(),
1526 )
1527 .build()?;
1528 den_orbit
1529 .calc_smat(
1530 self.sao_spatial_4c,
1531 self.sao_spatial_4c_h,
1532 params.use_cayley_table,
1533 )?
1534 .normalise_smat()?
1535 .calc_xmat(false)?;
1536 den_orbit.analyse_rep().map_err(|err| format_err!(err))
1537 };
1538 (
1539 format!("Spin-{ispin} density"),
1540 den_sym_res().map_err(|err| err.to_string()),
1541 )
1542 })
1543 .collect::<Vec<_>>();
1544 let mut extra_den_syms = densities
1545 .calc_extra_densities()
1546 .expect("Unable to calculate extra densities.")
1547 .iter()
1548 .map(|(label, den)| {
1549 let den_sym_res = || {
1550 let mut den_orbit = DensitySymmetryOrbit::builder()
1551 .group(&group)
1552 .origin(den)
1553 .integrality_threshold(params.integrality_threshold)
1554 .linear_independence_threshold(params.linear_independence_threshold)
1555 .symmetry_transformation_kind(
1556 params.symmetry_transformation_kind.clone(),
1557 )
1558 .eigenvalue_comparison_mode(
1559 params.eigenvalue_comparison_mode.clone(),
1560 )
1561 .build()?;
1562 den_orbit
1563 .calc_smat(
1564 self.sao_spatial_4c,
1565 self.sao_spatial_4c_h,
1566 params.use_cayley_table,
1567 )?
1568 .calc_xmat(false)?;
1569 den_orbit.analyse_rep().map_err(|err| format_err!(err))
1570 };
1571 (label.clone(), den_sym_res().map_err(|err| err.to_string()))
1572 })
1573 .collect_vec();
1574 spin_den_syms.append(&mut extra_den_syms);
1575 spin_den_syms
1576 });
1577
1578 let mo_den_syms = if params.analyse_mo_symmetries {
1579 let mo_den_symmetries = self
1580 .determinant
1581 .to_orbitals()
1582 .iter()
1583 .map(|mos| {
1584 mos.par_iter()
1585 .map(|mo| {
1586 let mo_den = mo.to_total_density().ok()?;
1587 let mut mo_den_orbit = DensitySymmetryOrbit::builder()
1588 .group(&group)
1589 .origin(&mo_den)
1590 .integrality_threshold(params.integrality_threshold)
1591 .linear_independence_threshold(
1592 params.linear_independence_threshold,
1593 )
1594 .symmetry_transformation_kind(
1595 params.symmetry_transformation_kind.clone(),
1596 )
1597 .eigenvalue_comparison_mode(
1598 params.eigenvalue_comparison_mode.clone(),
1599 )
1600 .build()
1601 .ok()?;
1602 log::debug!("Computing overlap matrix for an MO density orbit...");
1603 mo_den_orbit
1604 .calc_smat(
1605 self.sao_spatial_4c,
1606 self.sao_spatial_4c_h,
1607 params.use_cayley_table,
1608 )
1609 .ok()?
1610 .normalise_smat()
1611 .ok()?
1612 .calc_xmat(false)
1613 .ok()?;
1614 log::debug!(
1615 "Computing overlap matrix for an MO density orbit... Done."
1616 );
1617 mo_den_orbit.analyse_rep().ok()
1618 })
1619 .collect::<Vec<_>>()
1620 })
1621 .collect::<Vec<_>>();
1622 Some(mo_den_symmetries)
1623 } else {
1624 None
1625 };
1626
1627 (den_syms.ok(), mo_den_syms)
1628 } else {
1629 (None, None)
1630 };
1631
1632 let result = SlaterDeterminantRepAnalysisResult::builder()
1633 .parameters(params)
1634 .determinant(self.determinant)
1635 .group(group)
1636 .determinant_symmetry(det_symmetry)
1637 .determinant_density_symmetries(den_symmetries)
1638 .mo_symmetries(mo_symmetries)
1639 .mo_symmetry_projections(mo_symmetry_projections)
1640 .mo_mirror_parities(mo_mirror_parities)
1641 .mo_symmetries_thresholds(mo_symmetries_thresholds)
1642 .mo_density_symmetries(mo_den_symmetries)
1643 .build()?;
1644 self.result = Some(result);
1645
1646 Ok(())
1647 }
1648}
1649
1650impl<'a, G, T, SC> fmt::Display for SlaterDeterminantRepAnalysisDriver<'a, G, T, SC>
1658where
1659 G: SymmetryGroupProperties + Clone,
1660 G::CharTab: SubspaceDecomposable<T>,
1661 T: ComplexFloat + Lapack,
1662 SC: StructureConstraint + fmt::Display,
1663 <T as ComplexFloat>::Real: From<f64> + fmt::LowerExp + fmt::Debug,
1664{
1665 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1666 write_title(f, "Slater Determinant Symmetry Analysis")?;
1667 writeln!(f)?;
1668 writeln!(f, "{}", self.parameters)?;
1669 Ok(())
1670 }
1671}
1672
1673impl<'a, G, T, SC> fmt::Debug for SlaterDeterminantRepAnalysisDriver<'a, G, T, SC>
1674where
1675 G: SymmetryGroupProperties + Clone,
1676 G::CharTab: SubspaceDecomposable<T>,
1677 T: ComplexFloat + Lapack,
1678 SC: StructureConstraint + fmt::Display,
1679 <T as ComplexFloat>::Real: From<f64> + fmt::LowerExp + fmt::Debug,
1680{
1681 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1682 writeln!(f, "{self}")
1683 }
1684}
1685
1686#[duplicate_item(
1690 duplicate!{
1691 [
1692 dtype_nested sctype_nested;
1693 [ f64 ] [ SpinConstraint ];
1694 [ Complex<f64> ] [ SpinConstraint ];
1695 [ Complex<f64> ] [ SpinOrbitCoupled ];
1696 ]
1697 [
1698 gtype_ [ UnitaryRepresentedSymmetryGroup ]
1699 dtype_ [ dtype_nested ]
1700 sctype_ [ sctype_nested ]
1701 analyse_fn_ [ analyse_representation ]
1702 ]
1703 }
1704 duplicate!{
1705 [
1706 dtype_nested sctype_nested;
1707 [ f64 ] [ SpinConstraint ];
1708 [ Complex<f64> ] [ SpinConstraint ];
1709 [ Complex<f64> ] [ SpinOrbitCoupled ];
1710 ]
1711 [
1712 gtype_ [ MagneticRepresentedSymmetryGroup ]
1713 dtype_ [ dtype_nested ]
1714 sctype_ [ sctype_nested ]
1715 analyse_fn_ [ analyse_corepresentation ]
1716 ]
1717 }
1718)]
1719impl<'a> QSym2Driver for SlaterDeterminantRepAnalysisDriver<'a, gtype_, dtype_, sctype_> {
1720 type Params = SlaterDeterminantRepAnalysisParams<f64>;
1721
1722 type Outcome = SlaterDeterminantRepAnalysisResult<'a, gtype_, dtype_, sctype_>;
1723
1724 fn result(&self) -> Result<&Self::Outcome, anyhow::Error> {
1725 self.result.as_ref().ok_or_else(|| {
1726 format_err!("No Slater determinant representation analysis results found.")
1727 })
1728 }
1729
1730 fn run(&mut self) -> Result<(), anyhow::Error> {
1731 self.log_output_display();
1732 self.analyse_fn_()?;
1733 self.result()?.log_output_display();
1734 Ok(())
1735 }
1736}