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