qsym2/drivers/representation_analysis/
multideterminant.rs

1//! Driver for symmetry analysis of multi-determinantal wavefunctions.
2
3use 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
52// ==================
53// Struct definitions
54// ==================
55
56// ----------
57// Parameters
58// ----------
59
60const fn default_true() -> bool {
61    true
62}
63const fn default_symbolic() -> Option<CharacterTableDisplay> {
64    Some(CharacterTableDisplay::Symbolic)
65}
66
67/// Structure containing control parameters for multi-determinantal wavefunction representation
68/// analysis.
69#[derive(Clone, Builder, Debug, Serialize, Deserialize)]
70pub struct MultiDeterminantRepAnalysisParams<T: From<f64>> {
71    /// Threshold for checking if subspace multiplicities are integral.
72    pub integrality_threshold: T,
73
74    /// Threshold for determining zero eigenvalues in the orbit overlap matrix.
75    pub linear_independence_threshold: T,
76
77    /// Option indicating if the magnetic group is to be used for symmetry analysis, and if so,
78    /// whether unitary representations or unitary-antiunitary corepresentations should be used.
79    #[builder(default = "None")]
80    #[serde(default)]
81    pub use_magnetic_group: Option<MagneticSymmetryAnalysisKind>,
82
83    /// Boolean indicating if the double group is to be used for symmetry analysis.
84    #[builder(default = "false")]
85    #[serde(default)]
86    pub use_double_group: bool,
87
88    /// Boolean indicating if the Cayley table of the group, if available, should be used to speed
89    /// up the computation of orbit overlap matrices.
90    #[builder(default = "true")]
91    #[serde(default = "default_true")]
92    pub use_cayley_table: bool,
93
94    /// The kind of symmetry transformation to be applied on the reference multi-determinantal
95    /// wavefunction to generate the orbit for symmetry analysis.
96    #[builder(default = "SymmetryTransformationKind::Spatial")]
97    #[serde(default)]
98    pub symmetry_transformation_kind: SymmetryTransformationKind,
99
100    /// Option indicating if the character table of the group used for symmetry analysis is to be
101    /// printed out.
102    #[builder(default = "Some(CharacterTableDisplay::Symbolic)")]
103    #[serde(default = "default_symbolic")]
104    pub write_character_table: Option<CharacterTableDisplay>,
105
106    /// Boolean indicating if the eigenvalues of the orbit overlap matrix are to be printed out.
107    #[builder(default = "true")]
108    #[serde(default = "default_true")]
109    pub write_overlap_eigenvalues: bool,
110
111    /// The comparison mode for filtering out orbit overlap eigenvalues.
112    #[builder(default = "EigenvalueComparisonMode::Modulus")]
113    #[serde(default)]
114    pub eigenvalue_comparison_mode: EigenvalueComparisonMode,
115
116    /// The finite order to which any infinite-order symmetry element is reduced, so that a finite
117    /// subgroup of an infinite group can be used for the symmetry analysis.
118    #[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    /// Returns a builder to construct a [`MultiDeterminantRepAnalysisParams`] structure.
128    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// ------
214// Result
215// ------
216
217/// Structure to contain multi-determinantal representation analysis results.
218#[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    /// The control parameters used to obtain this set of multi-determinantal wavefunction
229    /// representation analysis results.
230    parameters: &'a MultiDeterminantRepAnalysisParams<<T as ComplexFloat>::Real>,
231
232    /// The multi-determinantal wavefunctions being analysed.
233    multidets: Vec<&'a MultiDeterminant<'a, T, B, SC>>,
234
235    /// The group used for the representation analysis.
236    group: G,
237
238    /// The deduced symmetries of the multi-determinantal wavefunctions.
239    multidet_symmetries:
240        Vec<Result<<G::CharTab as SubspaceDecomposable<T>>::Decomposition, String>>,
241
242    /// The overlap eigenvalues above and below the linear independence threshold for each
243    /// multi-determinantal wavefunction symmetry deduction.
244    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    /// Returns a builder to construct a new [`MultiDeterminantRepAnalysisResultBuilder`]
257    /// structure.
258    pub fn builder() -> MultiDeterminantRepAnalysisResultBuilder<'a, G, T, B, SC> {
259        MultiDeterminantRepAnalysisResultBuilder::default()
260    }
261
262    /// Returns the control parameters used to obtain this set of multi-determinantal wavefunction
263    /// representation analysis results.
264    pub fn parameters(&self) -> &MultiDeterminantRepAnalysisParams<<T as ComplexFloat>::Real> {
265        self.parameters
266    }
267
268    /// Returns the multi-determinantal wavefunctions being analysed.
269    pub fn multidets(&self) -> &Vec<&'a MultiDeterminant<'a, T, B, SC>> {
270        &self.multidets
271    }
272
273    /// Returns the multi-determinantal wavefunction symmetries obtained from the analysis result.
274    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// ------
444// Driver
445// ------
446
447// ~~~~~~~~~~~~~~~~~
448// Struct definition
449// ~~~~~~~~~~~~~~~~~
450
451/// Driver structure for performing representation analysis on multi-determinantal wavefunctions.
452#[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    /// The control parameters for multi-determinantal wavefunction representation analysis.
464    parameters: &'a MultiDeterminantRepAnalysisParams<<T as ComplexFloat>::Real>,
465
466    /// The multi-determinantal wavefunctions to be analysed.
467    multidets: Vec<&'a MultiDeterminant<'a, T, B, SC>>,
468
469    /// The result from symmetry-group detection on the underlying molecular structure of the
470    /// multi-determinantal wavefunctions.
471    symmetry_group: &'a SymmetryGroupDetectionResult,
472
473    /// The atomic-orbital overlap matrix of the underlying basis set used to describe the
474    /// wavefunctions. This is either for a single component corresponding to the basis functions
475    /// specified by the basis angular order structure in the determinants in [`Self::multidets`],
476    /// or for *all* explicit components specified by the coefficients in the determinants.
477    sao: &'a Array2<T>,
478
479    /// The complex-symmetric atomic-orbital overlap matrix of the underlying basis set used to
480    /// describe the wavefunctions. This is either for a single component corresponding to the basis
481    /// functions specified by the basis angular order structure in the determinants, or for *all*
482    /// explicit components specified by the coefficients in the determinants. This is required if
483    /// antiunitary symmetry operations are involved. If none is provided, this will be assumed to
484    /// be the same as [`Self::sao`].
485    #[builder(default = "None")]
486    sao_h: Option<&'a Array2<T>>,
487
488    /// The control parameters for symmetry analysis of angular functions.
489    angular_function_parameters: &'a AngularFunctionRepAnalysisParams,
490
491    /// The result of the multi-determinantal wavefunction representation analysis.
492    #[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
591// ~~~~~~~~~~~~~~~~~~~~~~
592// Struct implementations
593// ~~~~~~~~~~~~~~~~~~~~~~
594
595// Generic for all symmetry groups G and wavefunction numeric type T
596// '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
597
598impl<'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    /// Returns a builder to construct a [`MultiDeterminantRepAnalysisDriver`] structure.
608    pub fn builder() -> MultiDeterminantRepAnalysisDriverBuilder<'a, G, T, B, SC> {
609        MultiDeterminantRepAnalysisDriverBuilder::default()
610    }
611
612    /// Constructs the appropriate atomic-orbital overlap matrix based on the structure constraint of
613    /// the multi-determinantal wavefunctions and the provided overlap matrix.
614    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 nbas_set = self
654        //     .multidets
655        //     .iter()
656        //     .next()
657        //     .ok_or_else(|| format_err!("Unable to retrieve "))
658        //     .baos()
659        //     .iter()
660        //     .map(|bao| bao.n_funcs())
661        //     .collect::<HashSet<_>>();
662        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
707// Specific for unitary-represented symmetry groups, but generic for wavefunction numeric type T
708// '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
709
710impl<'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        /// Constructs the unitary-represented group (which itself can be unitary or magnetic) ready
720        /// for Slater determinant representation analysis.
721        construct_unitary_group
722    );
723}
724
725// Specific for magnetic-represented symmetry groups, but generic for wavefunction numeric type T
726// ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
727
728impl<'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        /// Constructs the magnetic-represented group (which itself can only be magnetic) ready for
738        /// Slater determinant corepresentation analysis.
739        construct_magnetic_group
740    );
741}
742
743// Specific for unitary-represented and magnetic-represented symmetry groups and wavefunction numeric types f64 and C128
744// '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
745
746#[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                            &params.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
947// ~~~~~~~~~~~~~~~~~~~~~
948// Trait implementations
949// ~~~~~~~~~~~~~~~~~~~~~
950
951// Generic for all symmetry groups G, basis B, and wavefunction numeric type T
952// '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
953
954impl<'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// Specific for unitary/magnetic-represented groups and determinant numeric type f64/Complex<f64>
986// ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
987
988#[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}