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 pub fn builder() -> MultiDeterminantRepAnalysisResultBuilder<'a, G, T, B, SC> {
259 MultiDeterminantRepAnalysisResultBuilder::default()
260 }
261
262 pub fn parameters(&self) -> &MultiDeterminantRepAnalysisParams<<T as ComplexFloat>::Real> {
265 self.parameters
266 }
267
268 pub fn multidets(&self) -> &Vec<&'a MultiDeterminant<'a, T, B, SC>> {
270 &self.multidets
271 }
272
273 pub fn multidet_symmetries(
275 &self,
276 ) -> &Vec<Result<<G::CharTab as SubspaceDecomposable<T>>::Decomposition, String>> {
277 &self.multidet_symmetries
278 }
279}
280
281impl<'a, G, T, B, SC> fmt::Display for MultiDeterminantRepAnalysisResult<'a, G, T, B, SC>
282where
283 G: SymmetryGroupProperties + Clone,
284 G::CharTab: SubspaceDecomposable<T>,
285 T: ComplexFloat + Lapack,
286 <T as ComplexFloat>::Real: From<f64> + fmt::LowerExp + fmt::Debug + fmt::Display,
287 B: Basis<SlaterDeterminant<'a, T, SC>> + Clone,
288 SC: StructureConstraint + Clone + Hash + Eq + fmt::Display,
289{
290 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
291 write_subtitle(f, "Orbit-based symmetry analysis results")?;
292 writeln!(f)?;
293 writeln!(
294 f,
295 "> Group: {} ({})",
296 self.group
297 .finite_subgroup_name()
298 .map(|subgroup_name| format!("{} > {}", self.group.name(), subgroup_name))
299 .unwrap_or(self.group.name()),
300 self.group.group_type().to_string().to_lowercase()
301 )?;
302 writeln!(f)?;
303
304 let multidet_index_length = usize::try_from(self.multidets.len().ilog10() + 1).unwrap_or(4);
305 let multidet_symmetry_length = self
306 .multidet_symmetries
307 .iter()
308 .map(|multidet_sym| {
309 multidet_sym
310 .as_ref()
311 .map(|sym| sym.to_string())
312 .unwrap_or_else(|err| err.clone())
313 .chars()
314 .count()
315 })
316 .max()
317 .unwrap_or(0)
318 .max(8);
319 let multidet_energy_length = self
320 .multidets
321 .iter()
322 .map(|multidet| {
323 multidet
324 .energy()
325 .map(|v| format!("{v:+.7}").chars().count())
326 .unwrap_or(2)
327 })
328 .max()
329 .unwrap_or(6)
330 .max(6);
331
332 let multidet_eig_above_length: usize = self
333 .multidet_symmetries_thresholds
334 .iter()
335 .map(|(above, _)| {
336 above
337 .as_ref()
338 .map(|eig| format!("{eig:+.3e}"))
339 .unwrap_or("--".to_string())
340 .chars()
341 .count()
342 })
343 .max()
344 .unwrap_or(10)
345 .max(10);
346
347 let multidet_eig_below_length: usize = self
348 .multidet_symmetries_thresholds
349 .iter()
350 .map(|(_, below)| {
351 below
352 .as_ref()
353 .map(|eig| format!("{eig:+.3e}"))
354 .unwrap_or("--".to_string())
355 .chars()
356 .count()
357 })
358 .max()
359 .unwrap_or(10)
360 .max(10);
361
362 let table_width = 10
363 + multidet_index_length
364 + multidet_energy_length
365 + multidet_symmetry_length
366 + multidet_eig_above_length
367 + multidet_eig_below_length;
368
369 writeln!(f, "> Multi-determinantal results")?;
370 writeln!(
371 f,
372 " Structure constraint: {}",
373 self.multidets
374 .first()
375 .map(|multidet_0| multidet_0.structure_constraint().to_string().to_lowercase())
376 .unwrap_or("--".to_string())
377 )?;
378 writeln!(f, "{}", "┈".repeat(table_width))?;
379 writeln!(
380 f,
381 " {:>multidet_index_length$} {:<multidet_energy_length$} {:<multidet_symmetry_length$} {:<multidet_eig_above_length$} Eig. below",
382 "#", "Energy", "Symmetry", "Eig. above",
383 )?;
384 writeln!(f, "{}", "┈".repeat(table_width))?;
385
386 for (multidet_i, multidet) in self.multidets.iter().enumerate() {
387 let multidet_energy_str = multidet
388 .energy()
389 .map(|multidet_energy| format!("{multidet_energy:>+multidet_energy_length$.7}"))
390 .unwrap_or("--".to_string());
391 let multidet_sym_str = self
392 .multidet_symmetries
393 .get(multidet_i)
394 .ok_or_else(|| format!("Unable to retrieve the symmetry of multideterminantal wavefunction index `{multidet_i}`."))
395 .and_then(|sym_res| sym_res.as_ref().map(|sym| sym.to_string()).map_err(|err| err.to_string()))
396 .unwrap_or_else(|err| err);
397
398 let (eig_above_str, eig_below_str) = self
399 .multidet_symmetries_thresholds
400 .get(multidet_i)
401 .map(|(eig_above_opt, eig_below_opt)| {
402 (
403 eig_above_opt
404 .map(|eig_above| format!("{eig_above:>+.3e}"))
405 .unwrap_or("--".to_string()),
406 eig_below_opt
407 .map(|eig_below| format!("{eig_below:>+.3e}"))
408 .unwrap_or("--".to_string()),
409 )
410 })
411 .unwrap_or(("--".to_string(), "--".to_string()));
412 writeln!(
413 f,
414 " {multidet_i:>multidet_index_length$} \
415 {multidet_energy_str:<multidet_energy_length$} \
416 {multidet_sym_str:<multidet_symmetry_length$} \
417 {eig_above_str:<multidet_eig_above_length$} \
418 {eig_below_str}"
419 )?;
420 }
421
422 writeln!(f, "{}", "┈".repeat(table_width))?;
423 writeln!(f)?;
424
425 Ok(())
426 }
427}
428
429impl<'a, G, T, B, SC> fmt::Debug for MultiDeterminantRepAnalysisResult<'a, G, T, B, SC>
430where
431 G: SymmetryGroupProperties + Clone,
432 G::CharTab: SubspaceDecomposable<T>,
433 T: ComplexFloat + Lapack,
434 <T as ComplexFloat>::Real: From<f64> + fmt::LowerExp + fmt::Debug + fmt::Display,
435 B: Basis<SlaterDeterminant<'a, T, SC>> + Clone,
436 SC: StructureConstraint + Clone + Hash + Eq + fmt::Display,
437{
438 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
439 writeln!(f, "{self}")
440 }
441}
442
443#[derive(Clone, Builder)]
453#[builder(build_fn(validate = "Self::validate"))]
454pub struct MultiDeterminantRepAnalysisDriver<'a, G, T, B, SC>
455where
456 G: SymmetryGroupProperties + Clone,
457 G::CharTab: SubspaceDecomposable<T>,
458 T: ComplexFloat + Lapack,
459 <T as ComplexFloat>::Real: From<f64> + fmt::LowerExp + fmt::Debug,
460 B: Basis<SlaterDeterminant<'a, T, SC>> + Clone,
461 SC: StructureConstraint + Hash + Eq + fmt::Display,
462{
463 parameters: &'a MultiDeterminantRepAnalysisParams<<T as ComplexFloat>::Real>,
465
466 multidets: Vec<&'a MultiDeterminant<'a, T, B, SC>>,
468
469 symmetry_group: &'a SymmetryGroupDetectionResult,
472
473 sao: &'a Array2<T>,
478
479 #[builder(default = "None")]
486 sao_h: Option<&'a Array2<T>>,
487
488 angular_function_parameters: &'a AngularFunctionRepAnalysisParams,
490
491 #[builder(setter(skip), default = "None")]
493 result: Option<MultiDeterminantRepAnalysisResult<'a, G, T, B, SC>>,
494}
495
496impl<'a, G, T, B, SC> MultiDeterminantRepAnalysisDriverBuilder<'a, G, T, B, SC>
497where
498 G: SymmetryGroupProperties + Clone,
499 G::CharTab: SubspaceDecomposable<T>,
500 T: ComplexFloat + Lapack,
501 <T as ComplexFloat>::Real: From<f64> + fmt::LowerExp + fmt::Debug,
502 B: Basis<SlaterDeterminant<'a, T, SC>> + Clone,
503 SC: StructureConstraint + Hash + Eq + fmt::Display,
504{
505 fn validate(&self) -> Result<(), String> {
506 let params = self.parameters.ok_or(
507 "No multi-determinantal wavefunction representation analysis parameters found."
508 .to_string(),
509 )?;
510
511 let sym_res = self
512 .symmetry_group
513 .ok_or("No symmetry group information found.".to_string())?;
514
515 let sao = self.sao.ok_or("No SAO matrix found.".to_string())?;
516
517 if let Some(sao_h) = self.sao_h.flatten()
518 && sao_h.shape() != sao.shape()
519 {
520 return Err("Mismatched shapes between `sao` and `sao_h`.".to_string());
521 }
522
523 let multidets = self
524 .multidets
525 .as_ref()
526 .ok_or("No multi-determinantal wavefunctions found.".to_string())?;
527 let mut nfuncs_ncomps_set = multidets
528 .iter()
529 .flat_map(|multidet| {
530 multidet.basis().iter().map(|det_res| {
531 det_res.map(|det| {
532 (
533 det.baos()
534 .iter()
535 .map(|bao| bao.n_funcs())
536 .collect::<Vec<_>>(),
537 det.structure_constraint()
538 .n_explicit_comps_per_coefficient_matrix(),
539 )
540 })
541 })
542 })
543 .collect::<Result<HashSet<(Vec<usize>, usize)>, _>>()
544 .map_err(|err| err.to_string())?;
545 let (nfuncs, _) = if nfuncs_ncomps_set.len() == 1 {
546 nfuncs_ncomps_set
547 .drain()
548 .next()
549 .ok_or("Unable to retrieve the number of AO basis functions and the number of explicit components.".to_string())
550 } else {
551 Err("Inconsistent numbers of AO basis functions and/or explicit components across multi-determinantal wavefunctions.".to_string())
552 }?;
553
554 let sym = if params.use_magnetic_group.is_some() {
555 sym_res
556 .magnetic_symmetry
557 .as_ref()
558 .ok_or("Magnetic symmetry requested for representation analysis, but no magnetic symmetry found.")?
559 } else {
560 &sym_res.unitary_symmetry
561 };
562
563 if sym.is_infinite() && params.infinite_order_to_finite.is_none() {
564 Err(format!(
565 "Representation analysis cannot be performed using the entirety of the infinite group `{}`. \
566 Consider setting the parameter `infinite_order_to_finite` to restrict to a finite subgroup instead.",
567 sym.group_name
568 .as_ref()
569 .expect("No symmetry group name found.")
570 ))
571 } else {
572 let total_component_check = {
573 let nfuncs_tot = nfuncs.iter().sum::<usize>();
574 sao.nrows() == nfuncs_tot && sao.ncols() == nfuncs_tot
575 };
576 let nfuncs_set = nfuncs.into_iter().collect::<HashSet<_>>();
577 let uniform_component_check = nfuncs_set.len() == 1
578 && {
579 let nfuncs = nfuncs_set.iter().next().ok_or_else(|| "Unable to extract the uniform number of basis functions per explicit component.".to_string())?;
580 sao.nrows() == *nfuncs && sao.ncols() == *nfuncs
581 };
582 if !uniform_component_check && !total_component_check {
583 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())
584 } else {
585 Ok(())
586 }
587 }
588 }
589}
590
591impl<'a, G, T, B, SC> MultiDeterminantRepAnalysisDriver<'a, G, T, B, SC>
599where
600 G: SymmetryGroupProperties + Clone,
601 G::CharTab: SubspaceDecomposable<T>,
602 T: ComplexFloat + Lapack,
603 <T as ComplexFloat>::Real: From<f64> + fmt::LowerExp + fmt::Debug,
604 B: Basis<SlaterDeterminant<'a, T, SC>> + Clone,
605 SC: StructureConstraint + Clone + Hash + Eq + fmt::Display,
606{
607 pub fn builder() -> MultiDeterminantRepAnalysisDriverBuilder<'a, G, T, B, SC> {
609 MultiDeterminantRepAnalysisDriverBuilder::default()
610 }
611
612 fn construct_sao(&self) -> Result<(Array2<T>, Option<Array2<T>>), anyhow::Error> {
615 let mut structure_constraint_set = self
616 .multidets
617 .iter()
618 .map(|multidet| multidet.structure_constraint())
619 .collect::<HashSet<_>>();
620 let structure_constraint = if structure_constraint_set.len() == 1 {
621 structure_constraint_set.drain().next().ok_or(format_err!(
622 "Unable to retrieve the structure constraint of the multi-determinantal wavefunctions."
623 ))
624 } else {
625 Err(format_err!(
626 "Inconsistent structure constraints across multi-determinantal wavefunctions."
627 ))
628 }?;
629
630 let mut nfuncss_set = self
631 .multidets
632 .iter()
633 .map(|multidet| {
634 multidet
635 .basis()
636 .first()
637 .expect("Unable to obtain the first determinant in the basis.")
638 .baos()
639 .iter()
640 .map(|bao| bao.n_funcs())
641 .collect::<Vec<_>>()
642 })
643 .collect::<HashSet<Vec<usize>>>();
644 let nfuncs_vec = if nfuncss_set.len() == 1 {
645 nfuncss_set.drain().next().ok_or(format_err!(
646 "Unable to retrieve the number of basis functions describing the multi-determinantal wavefunctions."
647 ))
648 } else {
649 Err(format_err!(
650 "Inconsistent numbers of basis functions across multi-determinantal wavefunctions."
651 ))
652 }?;
653 let nfuncs_set = nfuncs_vec.iter().cloned().collect::<HashSet<_>>();
663 let uniform_component = nfuncs_set.len() == 1;
664 let ncomps = structure_constraint.n_explicit_comps_per_coefficient_matrix();
665 let provided_dim = self.sao.nrows();
666
667 if uniform_component {
668 let nfuncs = *nfuncs_set.iter().next().ok_or_else(|| format_err!("Unable to extract the uniform number of basis functions per explicit component."))?;
669 if provided_dim == nfuncs {
670 let sao = {
671 let mut sao_mut = Array2::zeros((ncomps * nfuncs, ncomps * nfuncs));
672 (0..ncomps).for_each(|icomp| {
673 let start = icomp * nfuncs;
674 let end = (icomp + 1) * nfuncs;
675 sao_mut
676 .slice_mut(s![start..end, start..end])
677 .assign(self.sao);
678 });
679 sao_mut
680 };
681
682 let sao_h = self.sao_h.map(|sao_h| {
683 let mut sao_h_mut = Array2::zeros((ncomps * nfuncs, ncomps * nfuncs));
684 (0..ncomps).for_each(|icomp| {
685 let start = icomp * nfuncs;
686 let end = (icomp + 1) * nfuncs;
687 sao_h_mut
688 .slice_mut(s![start..end, start..end])
689 .assign(sao_h);
690 });
691 sao_h_mut
692 });
693
694 Ok((sao, sao_h))
695 } else {
696 ensure!(provided_dim == nfuncs * ncomps);
697 Ok((self.sao.clone(), self.sao_h.cloned()))
698 }
699 } else {
700 let nfuncs_tot = nfuncs_vec.iter().sum::<usize>();
701 ensure!(provided_dim == nfuncs_tot);
702 Ok((self.sao.clone(), self.sao_h.cloned()))
703 }
704 }
705}
706
707impl<'a, T, B, SC> MultiDeterminantRepAnalysisDriver<'a, UnitaryRepresentedSymmetryGroup, T, B, SC>
711where
712 T: ComplexFloat + Lapack + Sync + Send,
713 <T as ComplexFloat>::Real: From<f64> + fmt::LowerExp + fmt::Debug + Sync + Send,
714 for<'b> Complex<f64>: Mul<&'b T, Output = Complex<f64>>,
715 B: Basis<SlaterDeterminant<'a, T, SC>> + Clone,
716 SC: StructureConstraint + Hash + Eq + fmt::Display,
717{
718 fn_construct_unitary_group!(
719 construct_unitary_group
722 );
723}
724
725impl<'a, T, B, SC> MultiDeterminantRepAnalysisDriver<'a, MagneticRepresentedSymmetryGroup, T, B, SC>
729where
730 T: ComplexFloat + Lapack + Sync + Send,
731 <T as ComplexFloat>::Real: From<f64> + Sync + Send + fmt::LowerExp + fmt::Debug,
732 for<'b> Complex<f64>: Mul<&'b T, Output = Complex<f64>>,
733 B: Basis<SlaterDeterminant<'a, T, SC>> + Clone,
734 SC: StructureConstraint + Hash + Eq + fmt::Display,
735{
736 fn_construct_magnetic_group!(
737 construct_magnetic_group
740 );
741}
742
743#[duplicate_item(
747 duplicate!{
748 [
749 dtype_nested sctype_nested;
750 [ f64 ] [ SpinConstraint ];
751 [ Complex<f64> ] [ SpinConstraint ];
752 [ Complex<f64> ] [ SpinOrbitCoupled ];
753 ]
754 duplicate!{
755 [
756 [
757 btype_nested [OrbitBasis<'a, UnitaryRepresentedSymmetryGroup, SlaterDeterminant<'a, dtype_nested, sctype_nested>>]
758 calc_smat_nested [calc_smat_optimised]
759 ]
760 [
761 btype_nested [EagerBasis<SlaterDeterminant<'a, dtype_nested, sctype_nested>>]
762 calc_smat_nested [calc_smat]
763 ]
764 ]
765 [
766 gtype_ [ UnitaryRepresentedSymmetryGroup ]
767 dtype_ [ dtype_nested ]
768 btype_ [ btype_nested ]
769 sctype_ [ sctype_nested ]
770 doc_sub_ [ "Performs representation analysis using a unitary-represented group and stores the result." ]
771 analyse_fn_ [ analyse_representation ]
772 construct_group_ [ self.construct_unitary_group()? ]
773 calc_smat_ [ calc_smat_nested ]
774 calc_projections_ [
775 log_subtitle("Multi-determinantal wavefunction projection decompositions");
776 qsym2_output!("");
777 qsym2_output!(" Projections are defined w.r.t. the following inner product:");
778 qsym2_output!(" {}", multidet_orbit.origin().overlap_definition());
779 qsym2_output!("");
780 multidet_orbit
781 .projections_to_string(
782 &multidet_orbit.calc_projection_compositions()?,
783 params.integrality_threshold,
784 )
785 .log_output_display();
786 qsym2_output!("");
787 ]
788 ]
789 }
790 }
791 duplicate!{
792 [
793 dtype_nested sctype_nested;
794 [ f64 ] [ SpinConstraint ];
795 [ Complex<f64> ] [ SpinConstraint ];
796 [ Complex<f64> ] [ SpinOrbitCoupled ];
797 ]
798 duplicate!{
799 [
800 [
801 btype_nested [OrbitBasis<'a, MagneticRepresentedSymmetryGroup, SlaterDeterminant<'a, dtype_nested, sctype_nested>>]
802 calc_smat_nested [calc_smat_optimised]
803 ]
804 [
805 btype_nested [EagerBasis<SlaterDeterminant<'a, dtype_nested, sctype_nested>>]
806 calc_smat_nested [calc_smat]
807 ]
808 ]
809 [
810 gtype_ [ MagneticRepresentedSymmetryGroup ]
811 dtype_ [ dtype_nested ]
812 btype_ [ btype_nested ]
813 sctype_ [ sctype_nested ]
814 doc_sub_ [ "Performs corepresentation analysis using a magnetic-represented group and stores the result." ]
815 analyse_fn_ [ analyse_corepresentation ]
816 construct_group_ [ self.construct_magnetic_group()? ]
817 calc_smat_ [ calc_smat_nested ]
818 calc_projections_ [ ]
819 ]
820 }
821 }
822)]
823impl<'a> MultiDeterminantRepAnalysisDriver<'a, gtype_, dtype_, btype_, sctype_> {
824 #[doc = doc_sub_]
825 fn analyse_fn_(&mut self) -> Result<(), anyhow::Error> {
826 let params = self.parameters;
827 let (sao, sao_h) = self.construct_sao()?;
828 let group = construct_group_;
829 log_cc_transversal(&group);
830 let _ = find_angular_function_representation(&group, self.angular_function_parameters);
831 if group.is_double_group() {
832 let _ = find_spinor_function_representation(&group, self.angular_function_parameters);
833 }
834 if let Some(det) = self
835 .multidets
836 .first()
837 .and_then(|multidet| multidet.basis().first())
838 {
839 for (bao_i, bao) in det.baos().iter().enumerate() {
840 log_bao(bao, Some(bao_i));
841 }
842 }
843
844 let (multidet_symmetries, multidet_symmetries_thresholds): (Vec<_>, Vec<_>) = self.multidets
845 .iter()
846 .enumerate()
847 .map(|(i, multidet)| {
848 log_micsec_begin(&format!("Multi-determinantal wavefunction {i}"));
849 qsym2_output!("");
850 let res = MultiDeterminantSymmetryOrbit::builder()
851 .group(&group)
852 .origin(multidet)
853 .integrality_threshold(params.integrality_threshold)
854 .linear_independence_threshold(params.linear_independence_threshold)
855 .symmetry_transformation_kind(params.symmetry_transformation_kind.clone())
856 .eigenvalue_comparison_mode(params.eigenvalue_comparison_mode.clone())
857 .build()
858 .map_err(|err| format_err!(err))
859 .and_then(|mut multidet_orbit| {
860 multidet_orbit
861 .calc_smat_(Some(&sao), sao_h.as_ref(), params.use_cayley_table)?
862 .normalise_smat()?
863 .calc_xmat(false)?;
864 log_overlap_eigenvalues(
865 "Overlap eigenvalues",
866 multidet_orbit.smat_eigvals.as_ref().ok_or(format_err!("Orbit overlap eigenvalues not found."))?,
867 params.linear_independence_threshold,
868 ¶ms.eigenvalue_comparison_mode
869 );
870 qsym2_output!("");
871 let multidet_symmetry_thresholds = multidet_orbit
872 .smat_eigvals
873 .as_ref()
874 .map(|eigvals| {
875 let mut eigvals_vec = eigvals.iter().collect::<Vec<_>>();
876 match multidet_orbit.eigenvalue_comparison_mode() {
877 EigenvalueComparisonMode::Modulus => {
878 eigvals_vec.sort_by(|a, b| {
879 a.abs().partial_cmp(&b.abs()).expect("Unable to compare two eigenvalues based on their moduli.")
880 });
881 }
882 EigenvalueComparisonMode::Real => {
883 eigvals_vec.sort_by(|a, b| {
884 a.re().partial_cmp(&b.re()).expect("Unable to compare two eigenvalues based on their real parts.")
885 });
886 }
887 }
888 let eigval_above = match multidet_orbit.eigenvalue_comparison_mode() {
889 EigenvalueComparisonMode::Modulus => eigvals_vec
890 .iter()
891 .find(|val| {
892 val.abs() >= multidet_orbit.linear_independence_threshold
893 })
894 .copied()
895 .copied(),
896 EigenvalueComparisonMode::Real => eigvals_vec
897 .iter()
898 .find(|val| {
899 val.re() >= multidet_orbit.linear_independence_threshold
900 })
901 .copied()
902 .copied(),
903 };
904 eigvals_vec.reverse();
905 let eigval_below = 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 (eigval_above, eigval_below)
922 })
923 .unwrap_or((None, None));
924 let multidet_sym = multidet_orbit.analyse_rep().map_err(|err| err.to_string());
925 { calc_projections_ }
926 Ok((multidet_sym, multidet_symmetry_thresholds))
927 })
928 .unwrap_or_else(|err| (Err(err.to_string()), (None, None)));
929 log_micsec_end(&format!("Multi-determinantal wavefunction {i}"));
930 qsym2_output!("");
931 res
932 }).unzip();
933
934 let result = MultiDeterminantRepAnalysisResult::builder()
935 .parameters(params)
936 .multidets(self.multidets.clone())
937 .group(group)
938 .multidet_symmetries(multidet_symmetries)
939 .multidet_symmetries_thresholds(multidet_symmetries_thresholds)
940 .build()?;
941 self.result = Some(result);
942
943 Ok(())
944 }
945}
946
947impl<'a, G, T, B, SC> fmt::Display for MultiDeterminantRepAnalysisDriver<'a, G, T, B, SC>
955where
956 G: SymmetryGroupProperties + Clone,
957 G::CharTab: SubspaceDecomposable<T>,
958 T: ComplexFloat + Lapack,
959 <T as ComplexFloat>::Real: From<f64> + fmt::LowerExp + fmt::Debug,
960 B: Basis<SlaterDeterminant<'a, T, SC>> + Clone,
961 SC: StructureConstraint + Hash + Eq + fmt::Display,
962{
963 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
964 write_title(f, "Multi-determinantal Wavefunction Symmetry Analysis")?;
965 writeln!(f)?;
966 writeln!(f, "{}", self.parameters)?;
967 Ok(())
968 }
969}
970
971impl<'a, G, T, B, SC> fmt::Debug for MultiDeterminantRepAnalysisDriver<'a, G, T, B, SC>
972where
973 G: SymmetryGroupProperties + Clone,
974 G::CharTab: SubspaceDecomposable<T>,
975 T: ComplexFloat + Lapack,
976 <T as ComplexFloat>::Real: From<f64> + fmt::LowerExp + fmt::Debug,
977 B: Basis<SlaterDeterminant<'a, T, SC>> + Clone,
978 SC: StructureConstraint + Hash + Eq + fmt::Display,
979{
980 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
981 writeln!(f, "{self}")
982 }
983}
984
985#[duplicate_item(
989 duplicate!{
990 [
991 dtype_nested sctype_nested;
992 [ f64 ] [ SpinConstraint ];
993 [ Complex<f64> ] [ SpinConstraint ];
994 [ Complex<f64> ] [ SpinOrbitCoupled ];
995 ]
996 duplicate!{
997 [
998 btype_nested;
999 [OrbitBasis<'a, UnitaryRepresentedSymmetryGroup, SlaterDeterminant<'a, dtype_nested, sctype_nested>>];
1000 [EagerBasis<SlaterDeterminant<'a, dtype_nested, sctype_nested>>]
1001 ]
1002 [
1003 gtype_ [ UnitaryRepresentedSymmetryGroup ]
1004 dtype_ [ dtype_nested ]
1005 btype_ [ btype_nested ]
1006 sctype_ [ sctype_nested ]
1007 analyse_fn_ [ analyse_representation ]
1008 ]
1009 }
1010 }
1011 duplicate!{
1012 [
1013 dtype_nested sctype_nested;
1014 [ f64 ] [ SpinConstraint ];
1015 [ Complex<f64> ] [ SpinConstraint ];
1016 [ Complex<f64> ] [ SpinOrbitCoupled ];
1017 ]
1018 duplicate!{
1019 [
1020 btype_nested;
1021 [OrbitBasis<'a, MagneticRepresentedSymmetryGroup, SlaterDeterminant<'a, dtype_nested, sctype_nested>>];
1022 [EagerBasis<SlaterDeterminant<'a, dtype_nested, sctype_nested>>]
1023 ]
1024 [
1025 gtype_ [ MagneticRepresentedSymmetryGroup ]
1026 dtype_ [ dtype_nested ]
1027 btype_ [ btype_nested ]
1028 sctype_ [ sctype_nested ]
1029 analyse_fn_ [ analyse_corepresentation ]
1030 ]
1031 }
1032 }
1033)]
1034impl<'a> QSym2Driver for MultiDeterminantRepAnalysisDriver<'a, gtype_, dtype_, btype_, sctype_> {
1035 type Params = MultiDeterminantRepAnalysisParams<f64>;
1036
1037 type Outcome = MultiDeterminantRepAnalysisResult<'a, gtype_, dtype_, btype_, sctype_>;
1038
1039 fn result(&self) -> Result<&Self::Outcome, anyhow::Error> {
1040 self.result.as_ref().ok_or_else(|| {
1041 format_err!(
1042 "No multi-determinantal wavefunction representation analysis results found."
1043 )
1044 })
1045 }
1046
1047 fn run(&mut self) -> Result<(), anyhow::Error> {
1048 self.log_output_display();
1049 self.analyse_fn_()?;
1050 self.result()?.log_output_display();
1051 Ok(())
1052 }
1053}