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