Skip to main content

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 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
56// ==================
57// Struct definitions
58// ==================
59
60// ----------
61// Parameters
62// ----------
63
64const fn default_true() -> bool {
65    true
66}
67const fn default_symbolic() -> Option<CharacterTableDisplay> {
68    Some(CharacterTableDisplay::Symbolic)
69}
70
71/// Structure containing control parameters for multi-determinantal wavefunction representation
72/// analysis.
73#[derive(Clone, Builder, Debug, Serialize, Deserialize)]
74pub struct MultiDeterminantRepAnalysisParams<T: From<f64>> {
75    /// Threshold for checking if subspace multiplicities are integral.
76    pub integrality_threshold: T,
77
78    /// Threshold for determining zero eigenvalues in the orbit overlap matrix.
79    pub linear_independence_threshold: T,
80
81    /// Threshold for checking symmetry of the orbit overlap matrix.
82    pub orbit_overlap_symmetry_threshold: T,
83
84    /// Option indicating if the magnetic group is to be used for symmetry analysis, and if so,
85    /// whether unitary representations or unitary-antiunitary corepresentations should be used.
86    #[builder(default = "None")]
87    #[serde(default)]
88    pub use_magnetic_group: Option<MagneticSymmetryAnalysisKind>,
89
90    /// Boolean indicating if the double group is to be used for symmetry analysis.
91    #[builder(default = "false")]
92    #[serde(default)]
93    pub use_double_group: bool,
94
95    /// Boolean indicating if the Cayley table of the group, if available, should be used to speed
96    /// up the computation of orbit overlap matrices.
97    #[builder(default = "true")]
98    #[serde(default = "default_true")]
99    pub use_cayley_table: bool,
100
101    /// The kind of symmetry transformation to be applied on the reference multi-determinantal
102    /// wavefunction to generate the orbit for symmetry analysis.
103    #[builder(default = "SymmetryTransformationKind::Spatial")]
104    #[serde(default)]
105    pub symmetry_transformation_kind: SymmetryTransformationKind,
106
107    /// Option indicating if the character table of the group used for symmetry analysis is to be
108    /// printed out.
109    #[builder(default = "Some(CharacterTableDisplay::Symbolic)")]
110    #[serde(default = "default_symbolic")]
111    pub write_character_table: Option<CharacterTableDisplay>,
112
113    /// Boolean indicating if the eigenvalues of the orbit overlap matrix are to be printed out.
114    #[builder(default = "true")]
115    #[serde(default = "default_true")]
116    pub write_overlap_eigenvalues: bool,
117
118    /// The comparison mode for filtering out orbit overlap eigenvalues.
119    #[builder(default = "EigenvalueComparisonMode::Modulus")]
120    #[serde(default)]
121    pub eigenvalue_comparison_mode: EigenvalueComparisonMode,
122
123    /// The finite order to which any infinite-order symmetry element is reduced, so that a finite
124    /// subgroup of an infinite group can be used for the symmetry analysis.
125    #[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    /// Returns a builder to construct a [`MultiDeterminantRepAnalysisParams`] structure.
135    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// ------
226// Result
227// ------
228
229/// Structure to contain multi-determinantal representation analysis results.
230#[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    /// The control parameters used to obtain this set of multi-determinantal wavefunction
241    /// representation analysis results.
242    parameters: &'a MultiDeterminantRepAnalysisParams<<T as ComplexFloat>::Real>,
243
244    /// The multi-determinantal wavefunctions being analysed.
245    multidets: &'a MultiDeterminants<'a, T, B, SC>,
246
247    /// The group used for the representation analysis.
248    group: G,
249
250    /// The deduced symmetries of the multi-determinantal wavefunctions.
251    multidet_symmetries:
252        Vec<Result<<G::CharTab as SubspaceDecomposable<T>>::Decomposition, String>>,
253
254    /// The overlap eigenvalues above and below the linear independence threshold for each
255    /// multi-determinantal wavefunction symmetry deduction.
256    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    /// Returns a builder to construct a new [`MultiDeterminantRepAnalysisResultBuilder`]
269    /// structure.
270    pub fn builder() -> MultiDeterminantRepAnalysisResultBuilder<'a, G, T, B, SC> {
271        MultiDeterminantRepAnalysisResultBuilder::default()
272    }
273
274    /// Returns the control parameters used to obtain this set of multi-determinantal wavefunction
275    /// representation analysis results.
276    pub fn parameters(&self) -> &MultiDeterminantRepAnalysisParams<<T as ComplexFloat>::Real> {
277        self.parameters
278    }
279
280    /// Returns the multi-determinantal wavefunctions being analysed.
281    pub fn multidets(&self) -> &'a MultiDeterminants<'a, T, B, SC> {
282        self.multidets
283    }
284
285    /// Returns the multi-determinantal wavefunction symmetries obtained from the analysis result.
286    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// ------
476// Driver
477// ------
478
479// ~~~~~~~~~~~~~~~~~
480// Struct definition
481// ~~~~~~~~~~~~~~~~~
482
483/// Driver structure for performing representation analysis on multi-determinantal wavefunctions.
484#[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    /// The control parameters for multi-determinantal wavefunction representation analysis.
496    parameters: &'a MultiDeterminantRepAnalysisParams<<T as ComplexFloat>::Real>,
497
498    /// The multi-determinantal wavefunctions to be analysed.
499    multidets: &'a MultiDeterminants<'a, T, B, SC>,
500
501    /// The result from symmetry-group detection on the underlying molecular structure of the
502    /// multi-determinantal wavefunctions.
503    symmetry_group: &'a SymmetryGroupDetectionResult,
504
505    /// The atomic-orbital overlap matrix of the underlying basis set used to describe the
506    /// wavefunctions. This is either for a single component corresponding to the basis functions
507    /// specified by the basis angular order structure in the determinants in [`Self::multidets`],
508    /// or for *all* explicit components specified by the coefficients in the determinants.
509    sao: &'a Array2<T>,
510
511    /// The complex-symmetric atomic-orbital overlap matrix of the underlying basis set used to
512    /// describe the wavefunctions. This is either for a single component corresponding to the basis
513    /// functions specified by the basis angular order structure in the determinants, or for *all*
514    /// explicit components specified by the coefficients in the determinants. This is required if
515    /// antiunitary symmetry operations are involved. If none is provided, this will be assumed to
516    /// be the same as [`Self::sao`].
517    #[builder(default = "None")]
518    sao_h: Option<&'a Array2<T>>,
519
520    /// The control parameters for symmetry analysis of angular functions.
521    angular_function_parameters: &'a AngularFunctionRepAnalysisParams,
522
523    /// The result of the multi-determinantal wavefunction representation analysis.
524    #[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
622// ~~~~~~~~~~~~~~~~~~~~~~
623// Struct implementations
624// ~~~~~~~~~~~~~~~~~~~~~~
625
626// Generic for all symmetry groups G and wavefunction numeric type T
627// '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
628
629impl<'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    /// Returns a builder to construct a [`MultiDeterminantRepAnalysisDriver`] structure.
639    pub fn builder() -> MultiDeterminantRepAnalysisDriverBuilder<'a, G, T, B, SC> {
640        MultiDeterminantRepAnalysisDriverBuilder::default()
641    }
642
643    /// Constructs the appropriate atomic-orbital overlap matrix based on the structure constraint of
644    /// the multi-determinantal wavefunctions and the provided overlap matrix.
645    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
702// Specific for unitary-represented symmetry groups, but generic for wavefunction numeric type T
703// '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
704
705impl<'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        /// Constructs the unitary-represented group (which itself can be unitary or magnetic) ready
715        /// for Slater determinant representation analysis.
716        construct_unitary_group
717    );
718}
719
720// Specific for magnetic-represented symmetry groups, but generic for wavefunction numeric type T
721// ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
722
723impl<'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        /// Constructs the magnetic-represented group (which itself can only be magnetic) ready for
733        /// Slater determinant corepresentation analysis.
734        construct_magnetic_group
735    );
736}
737
738// Specific for unitary-represented and magnetic-represented symmetry groups and wavefunction numeric types f64 and C128
739// '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
740
741#[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                                &params.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
965// ~~~~~~~~~~~~~~~~~~~~~
966// Trait implementations
967// ~~~~~~~~~~~~~~~~~~~~~
968
969// Generic for all symmetry groups G, basis B, and wavefunction numeric type T
970// '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
971
972impl<'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// Specific for unitary/magnetic-represented groups and determinant numeric type f64/Complex<f64>
1004// ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
1005
1006#[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}