1use std::collections::HashSet;
4use std::fmt;
5use std::hash::Hash;
6use std::ops::Mul;
7
8use anyhow::{self, bail, ensure, format_err};
9use derive_builder::Builder;
10use duplicate::duplicate_item;
11use ndarray::{Array2, s};
12use ndarray_linalg::types::Lapack;
13use num_complex::{Complex, ComplexFloat};
14use num_traits::Float;
15use serde::{Deserialize, Serialize};
16
17use crate::analysis::{
18 EigenvalueComparisonMode, Overlap, ProjectionDecomposition, RepAnalysis,
19 log_overlap_eigenvalues,
20};
21use crate::angmom::spinor_rotation_3d::{SpinConstraint, SpinOrbitCoupled, StructureConstraint};
22use crate::chartab::SubspaceDecomposable;
23use crate::chartab::chartab_group::CharacterProperties;
24use crate::drivers::QSym2Driver;
25use crate::drivers::representation_analysis::angular_function::{
26 AngularFunctionRepAnalysisParams, find_angular_function_representation,
27 find_spinor_function_representation,
28};
29use crate::drivers::representation_analysis::{
30 CharacterTableDisplay, MagneticSymmetryAnalysisKind, fn_construct_magnetic_group,
31 fn_construct_unitary_group, log_bao, log_cc_transversal,
32};
33use crate::drivers::symmetry_group_detection::SymmetryGroupDetectionResult;
34use crate::group::{GroupProperties, MagneticRepresentedGroup, UnitaryRepresentedGroup};
35use crate::io::format::{
36 QSym2Output, log_micsec_begin, log_micsec_end, log_subtitle, nice_bool, qsym2_output,
37 write_subtitle, write_title,
38};
39use crate::symmetry::symmetry_group::{
40 MagneticRepresentedSymmetryGroup, SymmetryGroupProperties, UnitaryRepresentedSymmetryGroup,
41};
42use crate::symmetry::symmetry_transformation::SymmetryTransformationKind;
43use crate::target::determinant::SlaterDeterminant;
44use crate::target::noci::basis::{Basis, EagerBasis, OrbitBasis};
45use crate::target::noci::multideterminant::MultiDeterminant;
46use crate::target::noci::multideterminant::multideterminant_analysis::MultiDeterminantSymmetryOrbit;
47
48#[cfg(test)]
49#[path = "multideterminant_tests.rs"]
50mod multideterminant_tests;
51
52const fn default_true() -> bool {
61 true
62}
63const fn default_symbolic() -> Option<CharacterTableDisplay> {
64 Some(CharacterTableDisplay::Symbolic)
65}
66
67#[derive(Clone, Builder, Debug, Serialize, Deserialize)]
70pub struct MultiDeterminantRepAnalysisParams<T: From<f64>> {
71 pub integrality_threshold: T,
73
74 pub linear_independence_threshold: T,
76
77 #[builder(default = "None")]
80 #[serde(default)]
81 pub use_magnetic_group: Option<MagneticSymmetryAnalysisKind>,
82
83 #[builder(default = "false")]
85 #[serde(default)]
86 pub use_double_group: bool,
87
88 #[builder(default = "true")]
91 #[serde(default = "default_true")]
92 pub use_cayley_table: bool,
93
94 #[builder(default = "SymmetryTransformationKind::Spatial")]
97 #[serde(default)]
98 pub symmetry_transformation_kind: SymmetryTransformationKind,
99
100 #[builder(default = "Some(CharacterTableDisplay::Symbolic)")]
103 #[serde(default = "default_symbolic")]
104 pub write_character_table: Option<CharacterTableDisplay>,
105
106 #[builder(default = "true")]
108 #[serde(default = "default_true")]
109 pub write_overlap_eigenvalues: bool,
110
111 #[builder(default = "EigenvalueComparisonMode::Modulus")]
113 #[serde(default)]
114 pub eigenvalue_comparison_mode: EigenvalueComparisonMode,
115
116 #[builder(default = "None")]
119 #[serde(default)]
120 pub infinite_order_to_finite: Option<u32>,
121}
122
123impl<T> MultiDeterminantRepAnalysisParams<T>
124where
125 T: Float + From<f64>,
126{
127 pub fn builder() -> MultiDeterminantRepAnalysisParamsBuilder<T> {
129 MultiDeterminantRepAnalysisParamsBuilder::default()
130 }
131}
132
133impl Default for MultiDeterminantRepAnalysisParams<f64> {
134 fn default() -> Self {
135 Self::builder()
136 .integrality_threshold(1e-7)
137 .linear_independence_threshold(1e-7)
138 .build()
139 .expect("Unable to construct a default `MultiDeterminantRepAnalysisParams<f64>`.")
140 }
141}
142
143impl<T> fmt::Display for MultiDeterminantRepAnalysisParams<T>
144where
145 T: From<f64> + fmt::LowerExp + fmt::Debug,
146{
147 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
148 writeln!(
149 f,
150 "Integrality threshold: {:.3e}",
151 self.integrality_threshold
152 )?;
153 writeln!(
154 f,
155 "Linear independence threshold: {:.3e}",
156 self.linear_independence_threshold
157 )?;
158 writeln!(
159 f,
160 "Orbit eigenvalue comparison mode: {}",
161 self.eigenvalue_comparison_mode
162 )?;
163 writeln!(
164 f,
165 "Write overlap eigenvalues: {}",
166 nice_bool(self.write_overlap_eigenvalues)
167 )?;
168 writeln!(f)?;
169 writeln!(
170 f,
171 "Use magnetic group for analysis: {}",
172 match self.use_magnetic_group {
173 None => "no",
174 Some(MagneticSymmetryAnalysisKind::Representation) =>
175 "yes, using unitary representations",
176 Some(MagneticSymmetryAnalysisKind::Corepresentation) =>
177 "yes, using magnetic corepresentations",
178 }
179 )?;
180 writeln!(
181 f,
182 "Use double group for analysis: {}",
183 nice_bool(self.use_double_group)
184 )?;
185 writeln!(
186 f,
187 "Use Cayley table for orbit overlap matrices: {}",
188 nice_bool(self.use_cayley_table)
189 )?;
190 if let Some(finite_order) = self.infinite_order_to_finite {
191 writeln!(f, "Infinite order to finite: {finite_order}")?;
192 }
193 writeln!(
194 f,
195 "Symmetry transformation kind: {}",
196 self.symmetry_transformation_kind
197 )?;
198 writeln!(f)?;
199 writeln!(
200 f,
201 "Write character table: {}",
202 if let Some(chartab_display) = self.write_character_table.as_ref() {
203 format!("yes, {}", chartab_display.to_string().to_lowercase())
204 } else {
205 "no".to_string()
206 }
207 )?;
208
209 Ok(())
210 }
211}
212
213#[derive(Clone, Builder)]
219pub struct MultiDeterminantRepAnalysisResult<'a, G, T, B, SC>
220where
221 G: SymmetryGroupProperties + Clone,
222 G::CharTab: SubspaceDecomposable<T>,
223 T: ComplexFloat + Lapack,
224 <T as ComplexFloat>::Real: From<f64> + fmt::LowerExp + fmt::Debug,
225 B: Basis<SlaterDeterminant<'a, T, SC>> + Clone,
226 SC: StructureConstraint + Hash + Eq + fmt::Display,
227{
228 parameters: &'a MultiDeterminantRepAnalysisParams<<T as ComplexFloat>::Real>,
231
232 multidets: Vec<&'a MultiDeterminant<'a, T, B, SC>>,
234
235 group: G,
237
238 multidet_symmetries:
240 Vec<Result<<G::CharTab as SubspaceDecomposable<T>>::Decomposition, String>>,
241
242 multidet_symmetries_thresholds: Vec<(Option<T>, Option<T>)>,
245}
246
247impl<'a, G, T, B, SC> MultiDeterminantRepAnalysisResult<'a, G, T, B, SC>
248where
249 G: SymmetryGroupProperties + Clone,
250 G::CharTab: SubspaceDecomposable<T>,
251 T: ComplexFloat + Lapack,
252 <T as ComplexFloat>::Real: From<f64> + fmt::LowerExp + fmt::Debug,
253 B: Basis<SlaterDeterminant<'a, T, SC>> + Clone,
254 SC: StructureConstraint + Clone + Hash + Eq + fmt::Display,
255{
256 fn builder() -> MultiDeterminantRepAnalysisResultBuilder<'a, G, T, B, SC> {
259 MultiDeterminantRepAnalysisResultBuilder::default()
260 }
261
262 pub fn multidet_symmetries(
264 &self,
265 ) -> &Vec<Result<<G::CharTab as SubspaceDecomposable<T>>::Decomposition, String>> {
266 &self.multidet_symmetries
267 }
268}
269
270impl<'a, G, T, B, SC> fmt::Display for MultiDeterminantRepAnalysisResult<'a, G, T, B, SC>
271where
272 G: SymmetryGroupProperties + Clone,
273 G::CharTab: SubspaceDecomposable<T>,
274 T: ComplexFloat + Lapack,
275 <T as ComplexFloat>::Real: From<f64> + fmt::LowerExp + fmt::Debug + fmt::Display,
276 B: Basis<SlaterDeterminant<'a, T, SC>> + Clone,
277 SC: StructureConstraint + Clone + Hash + Eq + fmt::Display,
278{
279 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
280 write_subtitle(f, "Orbit-based symmetry analysis results")?;
281 writeln!(f)?;
282 writeln!(
283 f,
284 "> Group: {} ({})",
285 self.group
286 .finite_subgroup_name()
287 .map(|subgroup_name| format!("{} > {}", self.group.name(), subgroup_name))
288 .unwrap_or(self.group.name()),
289 self.group.group_type().to_string().to_lowercase()
290 )?;
291 writeln!(f)?;
292
293 let multidet_index_length = usize::try_from(self.multidets.len().ilog10() + 1).unwrap_or(4);
294 let multidet_symmetry_length = self
295 .multidet_symmetries
296 .iter()
297 .map(|multidet_sym| {
298 multidet_sym
299 .as_ref()
300 .map(|sym| sym.to_string())
301 .unwrap_or_else(|err| err.clone())
302 .chars()
303 .count()
304 })
305 .max()
306 .unwrap_or(0)
307 .max(8);
308 let multidet_energy_length = self
309 .multidets
310 .iter()
311 .map(|multidet| {
312 multidet
313 .energy()
314 .map(|v| format!("{v:+.7}").chars().count())
315 .unwrap_or(2)
316 })
317 .max()
318 .unwrap_or(6)
319 .max(6);
320
321 let multidet_eig_above_length: usize = self
322 .multidet_symmetries_thresholds
323 .iter()
324 .map(|(above, _)| {
325 above
326 .as_ref()
327 .map(|eig| format!("{eig:+.3e}"))
328 .unwrap_or("--".to_string())
329 .chars()
330 .count()
331 })
332 .max()
333 .unwrap_or(10)
334 .max(10);
335
336 let multidet_eig_below_length: usize = self
337 .multidet_symmetries_thresholds
338 .iter()
339 .map(|(_, below)| {
340 below
341 .as_ref()
342 .map(|eig| format!("{eig:+.3e}"))
343 .unwrap_or("--".to_string())
344 .chars()
345 .count()
346 })
347 .max()
348 .unwrap_or(10)
349 .max(10);
350
351 let table_width = 10
352 + multidet_index_length
353 + multidet_energy_length
354 + multidet_symmetry_length
355 + multidet_eig_above_length
356 + multidet_eig_below_length;
357
358 writeln!(f, "> Multi-determinantal results")?;
359 writeln!(
360 f,
361 " Structure constraint: {}",
362 self.multidets
363 .get(0)
364 .map(|multidet_0| multidet_0.structure_constraint().to_string().to_lowercase())
365 .unwrap_or("--".to_string())
366 )?;
367 writeln!(f, "{}", "┈".repeat(table_width))?;
368 writeln!(
369 f,
370 " {:>multidet_index_length$} {:<multidet_energy_length$} {:<multidet_symmetry_length$} {:<multidet_eig_above_length$} Eig. below",
371 "#", "Energy", "Symmetry", "Eig. above",
372 )?;
373 writeln!(f, "{}", "┈".repeat(table_width))?;
374
375 for (multidet_i, multidet) in self.multidets.iter().enumerate() {
376 let multidet_energy_str = multidet
377 .energy()
378 .map(|multidet_energy| format!("{multidet_energy:>+multidet_energy_length$.7}"))
379 .unwrap_or("--".to_string());
380 let multidet_sym_str = self
381 .multidet_symmetries
382 .get(multidet_i)
383 .ok_or_else(|| format!("Unable to retrieve the symmetry of multideterminantal wavefunction index `{multidet_i}`."))
384 .and_then(|sym_res| sym_res.as_ref().map(|sym| sym.to_string()).map_err(|err| err.to_string()))
385 .unwrap_or_else(|err| err);
386
387 let (eig_above_str, eig_below_str) = self
388 .multidet_symmetries_thresholds
389 .get(multidet_i)
390 .map(|(eig_above_opt, eig_below_opt)| {
391 (
392 eig_above_opt
393 .map(|eig_above| format!("{eig_above:>+.3e}"))
394 .unwrap_or("--".to_string()),
395 eig_below_opt
396 .map(|eig_below| format!("{eig_below:>+.3e}"))
397 .unwrap_or("--".to_string()),
398 )
399 })
400 .unwrap_or(("--".to_string(), "--".to_string()));
401 writeln!(
402 f,
403 " {multidet_i:>multidet_index_length$} \
404 {multidet_energy_str:<multidet_energy_length$} \
405 {multidet_sym_str:<multidet_symmetry_length$} \
406 {eig_above_str:<multidet_eig_above_length$} \
407 {eig_below_str}"
408 )?;
409 }
410
411 writeln!(f, "{}", "┈".repeat(table_width))?;
412 writeln!(f)?;
413
414 Ok(())
415 }
416}
417
418impl<'a, G, T, B, SC> fmt::Debug for MultiDeterminantRepAnalysisResult<'a, G, T, B, SC>
419where
420 G: SymmetryGroupProperties + Clone,
421 G::CharTab: SubspaceDecomposable<T>,
422 T: ComplexFloat + Lapack,
423 <T as ComplexFloat>::Real: From<f64> + fmt::LowerExp + fmt::Debug + fmt::Display,
424 B: Basis<SlaterDeterminant<'a, T, SC>> + Clone,
425 SC: StructureConstraint + Clone + Hash + Eq + fmt::Display,
426{
427 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
428 writeln!(f, "{self}")
429 }
430}
431
432#[derive(Clone, Builder)]
442#[builder(build_fn(validate = "Self::validate"))]
443pub struct MultiDeterminantRepAnalysisDriver<'a, G, T, B, SC>
444where
445 G: SymmetryGroupProperties + Clone,
446 G::CharTab: SubspaceDecomposable<T>,
447 T: ComplexFloat + Lapack,
448 <T as ComplexFloat>::Real: From<f64> + fmt::LowerExp + fmt::Debug,
449 B: Basis<SlaterDeterminant<'a, T, SC>> + Clone,
450 SC: StructureConstraint + Hash + Eq + fmt::Display,
451{
452 parameters: &'a MultiDeterminantRepAnalysisParams<<T as ComplexFloat>::Real>,
454
455 multidets: Vec<&'a MultiDeterminant<'a, T, B, SC>>,
457
458 symmetry_group: &'a SymmetryGroupDetectionResult,
461
462 sao: &'a Array2<T>,
467
468 #[builder(default = "None")]
475 sao_h: Option<&'a Array2<T>>,
476
477 angular_function_parameters: &'a AngularFunctionRepAnalysisParams,
479
480 #[builder(setter(skip), default = "None")]
482 result: Option<MultiDeterminantRepAnalysisResult<'a, G, T, B, SC>>,
483}
484
485impl<'a, G, T, B, SC> MultiDeterminantRepAnalysisDriverBuilder<'a, G, T, B, SC>
486where
487 G: SymmetryGroupProperties + Clone,
488 G::CharTab: SubspaceDecomposable<T>,
489 T: ComplexFloat + Lapack,
490 <T as ComplexFloat>::Real: From<f64> + fmt::LowerExp + fmt::Debug,
491 B: Basis<SlaterDeterminant<'a, T, SC>> + Clone,
492 SC: StructureConstraint + Hash + Eq + fmt::Display,
493{
494 fn validate(&self) -> Result<(), String> {
495 let params = self.parameters.ok_or(
496 "No multi-determinantal wavefunction representation analysis parameters found."
497 .to_string(),
498 )?;
499
500 let sym_res = self
501 .symmetry_group
502 .ok_or("No symmetry group information found.".to_string())?;
503
504 let sao = self.sao.ok_or("No SAO matrix found.".to_string())?;
505
506 if let Some(sao_h) = self.sao_h.flatten() {
507 if sao_h.shape() != sao.shape() {
508 return Err("Mismatched shapes between `sao` and `sao_h`.".to_string());
509 }
510 }
511
512 let multidets = self
513 .multidets
514 .as_ref()
515 .ok_or("No multi-determinantal wavefunctions found.".to_string())?;
516 let mut nfuncs_ncomps_set = multidets
517 .iter()
518 .flat_map(|multidet| {
519 multidet.basis().iter().map(|det_res| {
520 det_res.map(|det| {
521 (
522 det.baos()
523 .iter()
524 .map(|bao| bao.n_funcs())
525 .collect::<Vec<_>>(),
526 det.structure_constraint()
527 .n_explicit_comps_per_coefficient_matrix(),
528 )
529 })
530 })
531 })
532 .collect::<Result<HashSet<(Vec<usize>, usize)>, _>>()
533 .map_err(|err| err.to_string())?;
534 let (nfuncs, _) = if nfuncs_ncomps_set.len() == 1 {
535 nfuncs_ncomps_set
536 .drain()
537 .next()
538 .ok_or("Unable to retrieve the number of AO basis functions and the number of explicit components.".to_string())
539 } else {
540 Err("Inconsistent numbers of AO basis functions and/or explicit components across multi-determinantal wavefunctions.".to_string())
541 }?;
542
543 let sym = if params.use_magnetic_group.is_some() {
544 sym_res
545 .magnetic_symmetry
546 .as_ref()
547 .ok_or("Magnetic symmetry requested for representation analysis, but no magnetic symmetry found.")?
548 } else {
549 &sym_res.unitary_symmetry
550 };
551
552 if sym.is_infinite() && params.infinite_order_to_finite.is_none() {
553 Err(format!(
554 "Representation analysis cannot be performed using the entirety of the infinite group `{}`. \
555 Consider setting the parameter `infinite_order_to_finite` to restrict to a finite subgroup instead.",
556 sym.group_name
557 .as_ref()
558 .expect("No symmetry group name found.")
559 ))
560 } else {
561 let total_component_check = {
562 let nfuncs_tot = nfuncs.iter().sum::<usize>();
563 sao.nrows() == nfuncs_tot && sao.ncols() == nfuncs_tot
564 };
565 let nfuncs_set = nfuncs.into_iter().collect::<HashSet<_>>();
566 let uniform_component_check = nfuncs_set.len() == 1
567 && {
568 let nfuncs = nfuncs_set.iter().next().ok_or_else(|| "Unable to extract the uniform number of basis functions per explicit component.".to_string())?;
569 sao.nrows() == *nfuncs && sao.ncols() == *nfuncs
570 };
571 if !uniform_component_check && !total_component_check {
572 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 of the basis determinants.".to_string())
573 } else {
574 Ok(())
575 }
576 }
577 }
578}
579
580impl<'a, G, T, B, SC> MultiDeterminantRepAnalysisDriver<'a, G, T, B, SC>
588where
589 G: SymmetryGroupProperties + Clone,
590 G::CharTab: SubspaceDecomposable<T>,
591 T: ComplexFloat + Lapack,
592 <T as ComplexFloat>::Real: From<f64> + fmt::LowerExp + fmt::Debug,
593 B: Basis<SlaterDeterminant<'a, T, SC>> + Clone,
594 SC: StructureConstraint + Clone + Hash + Eq + fmt::Display,
595{
596 pub fn builder() -> MultiDeterminantRepAnalysisDriverBuilder<'a, G, T, B, SC> {
598 MultiDeterminantRepAnalysisDriverBuilder::default()
599 }
600
601 fn construct_sao(&self) -> Result<(Array2<T>, Option<Array2<T>>), anyhow::Error> {
604 let mut structure_constraint_set = self
605 .multidets
606 .iter()
607 .map(|multidet| multidet.structure_constraint())
608 .collect::<HashSet<_>>();
609 let structure_constraint = if structure_constraint_set.len() == 1 {
610 structure_constraint_set.drain().next().ok_or(format_err!(
611 "Unable to retrieve the structure constraint of the multi-determinantal wavefunctions."
612 ))
613 } else {
614 Err(format_err!(
615 "Inconsistent structure constraints across multi-determinantal wavefunctions."
616 ))
617 }?;
618
619 let mut nfuncss_set = self
620 .multidets
621 .iter()
622 .map(|multidet| {
623 multidet
624 .basis()
625 .first()
626 .expect("Unable to obtain the first determinant in the basis.")
627 .baos()
628 .iter()
629 .map(|bao| bao.n_funcs())
630 .collect::<Vec<_>>()
631 })
632 .collect::<HashSet<Vec<usize>>>();
633 let nfuncs_vec = if nfuncss_set.len() == 1 {
634 nfuncss_set.drain().next().ok_or(format_err!(
635 "Unable to retrieve the number of basis functions describing the multi-determinantal wavefunctions."
636 ))
637 } else {
638 Err(format_err!(
639 "Inconsistent numbers of basis functions across multi-determinantal wavefunctions."
640 ))
641 }?;
642 let nfuncs_set = nfuncs_vec.iter().cloned().collect::<HashSet<_>>();
652 let uniform_component = nfuncs_set.len() == 1;
653 let ncomps = structure_constraint.n_explicit_comps_per_coefficient_matrix();
654 let provided_dim = self.sao.nrows();
655
656 if uniform_component {
657 let nfuncs = *nfuncs_set.iter().next().ok_or_else(|| format_err!("Unable to extract the uniform number of basis functions per explicit component."))?;
658 if provided_dim == nfuncs {
659 let sao = {
660 let mut sao_mut = Array2::zeros((ncomps * nfuncs, ncomps * nfuncs));
661 (0..ncomps).for_each(|icomp| {
662 let start = icomp * nfuncs;
663 let end = (icomp + 1) * nfuncs;
664 sao_mut
665 .slice_mut(s![start..end, start..end])
666 .assign(self.sao);
667 });
668 sao_mut
669 };
670
671 let sao_h = self.sao_h.map(|sao_h| {
672 let mut sao_h_mut = Array2::zeros((ncomps * nfuncs, ncomps * nfuncs));
673 (0..ncomps).for_each(|icomp| {
674 let start = icomp * nfuncs;
675 let end = (icomp + 1) * nfuncs;
676 sao_h_mut
677 .slice_mut(s![start..end, start..end])
678 .assign(sao_h);
679 });
680 sao_h_mut
681 });
682
683 Ok((sao, sao_h))
684 } else {
685 ensure!(provided_dim == nfuncs * ncomps);
686 Ok((self.sao.clone(), self.sao_h.cloned()))
687 }
688 } else {
689 let nfuncs_tot = nfuncs_vec.iter().sum::<usize>();
690 ensure!(provided_dim == nfuncs_tot);
691 Ok((self.sao.clone(), self.sao_h.cloned()))
692 }
693 }
694}
695
696impl<'a, T, B, SC> MultiDeterminantRepAnalysisDriver<'a, UnitaryRepresentedSymmetryGroup, T, B, SC>
700where
701 T: ComplexFloat + Lapack + Sync + Send,
702 <T as ComplexFloat>::Real: From<f64> + fmt::LowerExp + fmt::Debug + Sync + Send,
703 for<'b> Complex<f64>: Mul<&'b T, Output = Complex<f64>>,
704 B: Basis<SlaterDeterminant<'a, T, SC>> + Clone,
705 SC: StructureConstraint + Hash + Eq + fmt::Display,
706{
707 fn_construct_unitary_group!(
708 construct_unitary_group
711 );
712}
713
714impl<'a, T, B, SC> MultiDeterminantRepAnalysisDriver<'a, MagneticRepresentedSymmetryGroup, T, B, SC>
718where
719 T: ComplexFloat + Lapack + Sync + Send,
720 <T as ComplexFloat>::Real: From<f64> + Sync + Send + fmt::LowerExp + fmt::Debug,
721 for<'b> Complex<f64>: Mul<&'b T, Output = Complex<f64>>,
722 B: Basis<SlaterDeterminant<'a, T, SC>> + Clone,
723 SC: StructureConstraint + Hash + Eq + fmt::Display,
724{
725 fn_construct_magnetic_group!(
726 construct_magnetic_group
729 );
730}
731
732#[duplicate_item(
736 duplicate!{
737 [
738 dtype_nested sctype_nested;
739 [ f64 ] [ SpinConstraint ];
740 [ Complex<f64> ] [ SpinConstraint ];
741 [ Complex<f64> ] [ SpinOrbitCoupled ];
742 ]
743 duplicate!{
744 [
745 [
746 btype_nested [OrbitBasis<'a, UnitaryRepresentedSymmetryGroup, SlaterDeterminant<'a, dtype_nested, sctype_nested>>]
747 calc_smat_nested [calc_smat_optimised]
748 ]
749 [
750 btype_nested [EagerBasis<SlaterDeterminant<'a, dtype_nested, sctype_nested>>]
751 calc_smat_nested [calc_smat]
752 ]
753 ]
754 [
755 gtype_ [ UnitaryRepresentedSymmetryGroup ]
756 dtype_ [ dtype_nested ]
757 btype_ [ btype_nested ]
758 sctype_ [ sctype_nested ]
759 doc_sub_ [ "Performs representation analysis using a unitary-represented group and stores the result." ]
760 analyse_fn_ [ analyse_representation ]
761 construct_group_ [ self.construct_unitary_group()? ]
762 calc_smat_ [ calc_smat_nested ]
763 calc_projections_ [
764 log_subtitle("Multi-determinantal wavefunction projection decompositions");
765 qsym2_output!("");
766 qsym2_output!(" Projections are defined w.r.t. the following inner product:");
767 qsym2_output!(" {}", multidet_orbit.origin().overlap_definition());
768 qsym2_output!("");
769 multidet_orbit
770 .projections_to_string(
771 &multidet_orbit.calc_projection_compositions()?,
772 params.integrality_threshold,
773 )
774 .log_output_display();
775 qsym2_output!("");
776 ]
777 ]
778 }
779 }
780 duplicate!{
781 [
782 dtype_nested sctype_nested;
783 [ f64 ] [ SpinConstraint ];
784 [ Complex<f64> ] [ SpinConstraint ];
785 [ Complex<f64> ] [ SpinOrbitCoupled ];
786 ]
787 duplicate!{
788 [
789 [
790 btype_nested [OrbitBasis<'a, MagneticRepresentedSymmetryGroup, SlaterDeterminant<'a, dtype_nested, sctype_nested>>]
791 calc_smat_nested [calc_smat_optimised]
792 ]
793 [
794 btype_nested [EagerBasis<SlaterDeterminant<'a, dtype_nested, sctype_nested>>]
795 calc_smat_nested [calc_smat]
796 ]
797 ]
798 [
799 gtype_ [ MagneticRepresentedSymmetryGroup ]
800 dtype_ [ dtype_nested ]
801 btype_ [ btype_nested ]
802 sctype_ [ sctype_nested ]
803 doc_sub_ [ "Performs corepresentation analysis using a magnetic-represented group and stores the result." ]
804 analyse_fn_ [ analyse_corepresentation ]
805 construct_group_ [ self.construct_magnetic_group()? ]
806 calc_smat_ [ calc_smat_nested ]
807 calc_projections_ [ ]
808 ]
809 }
810 }
811)]
812impl<'a> MultiDeterminantRepAnalysisDriver<'a, gtype_, dtype_, btype_, sctype_> {
813 #[doc = doc_sub_]
814 fn analyse_fn_(&mut self) -> Result<(), anyhow::Error> {
815 let params = self.parameters;
816 let (sao, sao_h) = self.construct_sao()?;
817 let group = construct_group_;
818 log_cc_transversal(&group);
819 let _ = find_angular_function_representation(&group, self.angular_function_parameters);
820 if group.is_double_group() {
821 let _ = find_spinor_function_representation(&group, self.angular_function_parameters);
822 }
823 if let Some(det) = self
824 .multidets
825 .get(0)
826 .and_then(|multidet| multidet.basis().first())
827 {
828 for (bao_i, bao) in det.baos().iter().enumerate() {
829 log_bao(bao, Some(bao_i));
830 }
831 }
832
833 let (multidet_symmetries, multidet_symmetries_thresholds): (Vec<_>, Vec<_>) = self.multidets
834 .iter()
835 .enumerate()
836 .map(|(i, multidet)| {
837 log_micsec_begin(&format!("Multi-determinantal wavefunction {i}"));
838 qsym2_output!("");
839 let res = MultiDeterminantSymmetryOrbit::builder()
840 .group(&group)
841 .origin(multidet)
842 .integrality_threshold(params.integrality_threshold)
843 .linear_independence_threshold(params.linear_independence_threshold)
844 .symmetry_transformation_kind(params.symmetry_transformation_kind.clone())
845 .eigenvalue_comparison_mode(params.eigenvalue_comparison_mode.clone())
846 .build()
847 .map_err(|err| format_err!(err))
848 .and_then(|mut multidet_orbit| {
849 multidet_orbit
850 .calc_smat_(Some(&sao), sao_h.as_ref(), params.use_cayley_table)?
851 .normalise_smat()?
852 .calc_xmat(false)?;
853 log_overlap_eigenvalues(
854 "Overlap eigenvalues",
855 multidet_orbit.smat_eigvals.as_ref().ok_or(format_err!("Orbit overlap eigenvalues not found."))?,
856 params.linear_independence_threshold,
857 ¶ms.eigenvalue_comparison_mode
858 );
859 qsym2_output!("");
860 let multidet_symmetry_thresholds = multidet_orbit
861 .smat_eigvals
862 .as_ref()
863 .map(|eigvals| {
864 let mut eigvals_vec = eigvals.iter().collect::<Vec<_>>();
865 match multidet_orbit.eigenvalue_comparison_mode() {
866 EigenvalueComparisonMode::Modulus => {
867 eigvals_vec.sort_by(|a, b| {
868 a.abs().partial_cmp(&b.abs()).expect("Unable to compare two eigenvalues based on their moduli.")
869 });
870 }
871 EigenvalueComparisonMode::Real => {
872 eigvals_vec.sort_by(|a, b| {
873 a.re().partial_cmp(&b.re()).expect("Unable to compare two eigenvalues based on their real parts.")
874 });
875 }
876 }
877 let eigval_above = match multidet_orbit.eigenvalue_comparison_mode() {
878 EigenvalueComparisonMode::Modulus => eigvals_vec
879 .iter()
880 .find(|val| {
881 val.abs() >= multidet_orbit.linear_independence_threshold
882 })
883 .copied()
884 .copied(),
885 EigenvalueComparisonMode::Real => eigvals_vec
886 .iter()
887 .find(|val| {
888 val.re() >= multidet_orbit.linear_independence_threshold
889 })
890 .copied()
891 .copied(),
892 };
893 eigvals_vec.reverse();
894 let eigval_below = match multidet_orbit.eigenvalue_comparison_mode() {
895 EigenvalueComparisonMode::Modulus => eigvals_vec
896 .iter()
897 .find(|val| {
898 val.abs() < multidet_orbit.linear_independence_threshold
899 })
900 .copied()
901 .copied(),
902 EigenvalueComparisonMode::Real => eigvals_vec
903 .iter()
904 .find(|val| {
905 val.re() < multidet_orbit.linear_independence_threshold
906 })
907 .copied()
908 .copied(),
909 };
910 (eigval_above, eigval_below)
911 })
912 .unwrap_or((None, None));
913 let multidet_sym = multidet_orbit.analyse_rep().map_err(|err| err.to_string());
914 { calc_projections_ }
915 Ok((multidet_sym, multidet_symmetry_thresholds))
916 })
917 .unwrap_or_else(|err| (Err(err.to_string()), (None, None)));
918 log_micsec_end(&format!("Multi-determinantal wavefunction {i}"));
919 qsym2_output!("");
920 res
921 }).unzip();
922
923 let result = MultiDeterminantRepAnalysisResult::builder()
924 .parameters(params)
925 .multidets(self.multidets.clone())
926 .group(group)
927 .multidet_symmetries(multidet_symmetries)
928 .multidet_symmetries_thresholds(multidet_symmetries_thresholds)
929 .build()?;
930 self.result = Some(result);
931
932 Ok(())
933 }
934}
935
936impl<'a, G, T, B, SC> fmt::Display for MultiDeterminantRepAnalysisDriver<'a, G, T, B, SC>
944where
945 G: SymmetryGroupProperties + Clone,
946 G::CharTab: SubspaceDecomposable<T>,
947 T: ComplexFloat + Lapack,
948 <T as ComplexFloat>::Real: From<f64> + fmt::LowerExp + fmt::Debug,
949 B: Basis<SlaterDeterminant<'a, T, SC>> + Clone,
950 SC: StructureConstraint + Hash + Eq + fmt::Display,
951{
952 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
953 write_title(f, "Multi-determinantal Wavefunction Symmetry Analysis")?;
954 writeln!(f)?;
955 writeln!(f, "{}", self.parameters)?;
956 Ok(())
957 }
958}
959
960impl<'a, G, T, B, SC> fmt::Debug for MultiDeterminantRepAnalysisDriver<'a, G, T, B, SC>
961where
962 G: SymmetryGroupProperties + Clone,
963 G::CharTab: SubspaceDecomposable<T>,
964 T: ComplexFloat + Lapack,
965 <T as ComplexFloat>::Real: From<f64> + fmt::LowerExp + fmt::Debug,
966 B: Basis<SlaterDeterminant<'a, T, SC>> + Clone,
967 SC: StructureConstraint + Hash + Eq + fmt::Display,
968{
969 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
970 writeln!(f, "{self}")
971 }
972}
973
974#[duplicate_item(
978 duplicate!{
979 [
980 dtype_nested sctype_nested;
981 [ f64 ] [ SpinConstraint ];
982 [ Complex<f64> ] [ SpinConstraint ];
983 [ Complex<f64> ] [ SpinOrbitCoupled ];
984 ]
985 duplicate!{
986 [
987 btype_nested;
988 [OrbitBasis<'a, UnitaryRepresentedSymmetryGroup, SlaterDeterminant<'a, dtype_nested, sctype_nested>>];
989 [EagerBasis<SlaterDeterminant<'a, dtype_nested, sctype_nested>>]
990 ]
991 [
992 gtype_ [ UnitaryRepresentedSymmetryGroup ]
993 dtype_ [ dtype_nested ]
994 btype_ [ btype_nested ]
995 sctype_ [ sctype_nested ]
996 analyse_fn_ [ analyse_representation ]
997 ]
998 }
999 }
1000 duplicate!{
1001 [
1002 dtype_nested sctype_nested;
1003 [ f64 ] [ SpinConstraint ];
1004 [ Complex<f64> ] [ SpinConstraint ];
1005 [ Complex<f64> ] [ SpinOrbitCoupled ];
1006 ]
1007 duplicate!{
1008 [
1009 btype_nested;
1010 [OrbitBasis<'a, MagneticRepresentedSymmetryGroup, SlaterDeterminant<'a, dtype_nested, sctype_nested>>];
1011 [EagerBasis<SlaterDeterminant<'a, dtype_nested, sctype_nested>>]
1012 ]
1013 [
1014 gtype_ [ MagneticRepresentedSymmetryGroup ]
1015 dtype_ [ dtype_nested ]
1016 btype_ [ btype_nested ]
1017 sctype_ [ sctype_nested ]
1018 analyse_fn_ [ analyse_corepresentation ]
1019 ]
1020 }
1021 }
1022)]
1023impl<'a> QSym2Driver for MultiDeterminantRepAnalysisDriver<'a, gtype_, dtype_, btype_, sctype_> {
1024 type Params = MultiDeterminantRepAnalysisParams<f64>;
1025
1026 type Outcome = MultiDeterminantRepAnalysisResult<'a, gtype_, dtype_, btype_, sctype_>;
1027
1028 fn result(&self) -> Result<&Self::Outcome, anyhow::Error> {
1029 self.result.as_ref().ok_or_else(|| {
1030 format_err!(
1031 "No multi-determinantal wavefunction representation analysis results found."
1032 )
1033 })
1034 }
1035
1036 fn run(&mut self) -> Result<(), anyhow::Error> {
1037 self.log_output_display();
1038 self.analyse_fn_()?;
1039 self.result()?.log_output_display();
1040 Ok(())
1041 }
1042}