Skip to main content

qsym2/target/noci/
multideterminant_analysis.rs

1//! Implementation of symmetry analysis for individual multi-determinantal wavefunctions.
2
3use std::collections::HashSet;
4use std::fmt;
5use std::hash::Hash;
6use std::ops::Mul;
7
8use anyhow::{self, Context, ensure, format_err};
9use approx;
10use derive_builder::Builder;
11use itertools::Itertools;
12use log;
13use ndarray::{Array1, Array2, Array3, Axis, Ix0, Ix2, s};
14use ndarray_einsum::*;
15use ndarray_linalg::{
16    UPLO,
17    eig::Eig,
18    eigh::Eigh,
19    types::{Lapack, Scalar},
20};
21use num_complex::{Complex, ComplexFloat};
22use num_traits::{Float, ToPrimitive, Zero};
23
24use crate::angmom::spinor_rotation_3d::StructureConstraint;
25use crate::auxiliary::misc::complex_modified_gram_schmidt;
26use crate::chartab::chartab_group::CharacterProperties;
27use crate::chartab::{DecompositionError, SubspaceDecomposable};
28use crate::group::GroupType;
29use crate::io::format::{QSym2Output, log_subtitle, qsym2_output};
30use crate::symmetry::symmetry_element::symmetry_operation::SpecialSymmetryTransformation;
31use crate::symmetry::symmetry_group::SymmetryGroupProperties;
32use crate::symmetry::symmetry_transformation::{SymmetryTransformable, SymmetryTransformationKind};
33use crate::target::determinant::SlaterDeterminant;
34use crate::auxiliary::misc::check_complex_matrix_symmetry;
35use crate::target::noci::basis::{Basis, FCIBasis, OrbitBasis};
36use crate::target::noci::multideterminant::MultiDeterminant;
37use crate::{
38    analysis::{
39        EigenvalueComparisonMode, Orbit, OrbitIterator, Overlap, RepAnalysis, fn_calc_xmat_complex,
40        fn_calc_xmat_real,
41    },
42    target::noci::basis::basis_metric::BasisSymmetryOrbitMetric,
43};
44
45// =======
46// Overlap
47// =======
48
49impl<'a, T, B, SC> Overlap<T, Ix2> for MultiDeterminant<'a, T, B, SC>
50where
51    T: Lapack
52        + ComplexFloat<Real = <T as Scalar>::Real>
53        + fmt::Debug
54        + Mul<<T as ComplexFloat>::Real, Output = T>,
55    <T as ComplexFloat>::Real: fmt::Debug
56        + approx::RelativeEq<<T as ComplexFloat>::Real>
57        + approx::AbsDiffEq<Epsilon = <T as Scalar>::Real>,
58    SC: StructureConstraint + Hash + Eq + Clone + fmt::Display,
59    B: Basis<Item = SlaterDeterminant<'a, T, SC>> + Clone,
60{
61    fn complex_symmetric(&self) -> bool {
62        self.complex_symmetric
63    }
64
65    /// Computes the overlap between two multi-determinantal wavefunctions.
66    ///
67    /// Determinants with fractional electrons are currently not supported.
68    ///
69    /// When one or both of the multi-determinantal wavefunctions have been acted on by an
70    /// antiunitary operation, the correct Hermitian or complex-symmetric metric will be chosen in
71    /// the evalulation of the overlap.
72    ///
73    /// # Arguments
74    ///
75    /// * `metric` - The atomic-orbital overlap matrix with respect to the conventional sesquilinear
76    ///   inner product.
77    /// * `metric_h` - The atomic-orbital overlap matrix with respect to the bilinear inner product.
78    ///
79    /// # Panics
80    ///
81    /// Panics if `self` and `other` have mismatched spin constraints or numbers of coefficient
82    /// matrices, or if fractional occupation numbers are detected.
83    fn overlap(
84        &self,
85        other: &Self,
86        metric: Option<&Array2<T>>,
87        metric_h: Option<&Array2<T>>,
88    ) -> Result<T, anyhow::Error> {
89        ensure!(
90            self.structure_constraint() == other.structure_constraint(),
91            "Inconsistent structure constraints between `self` and `other`."
92        );
93        ensure!(
94            self.coefficients.len() == other.coefficients.len(),
95            "Inconsistent numbers of coefficients between `self` and `other`."
96        );
97
98        let s_dets = self.basis.iter().collect::<Result<Vec<_>, _>>()?;
99        let o_dets = other.basis.iter().collect::<Result<Vec<_>, _>>()?;
100        let d = self.coefficients.len();
101        let swx_vec = s_dets
102            .iter()
103            .cartesian_product(o_dets.iter())
104            .map(|(w, x)| w.overlap(x, metric, metric_h))
105            .collect::<Result<Vec<_>, _>>()?;
106        let swx = Array2::from_shape_vec((d, d), swx_vec)?;
107
108        if self.complex_symmetric {
109            Ok(self.coefficients.t().dot(&swx).dot(&other.coefficients))
110        } else {
111            Ok(self
112                .coefficients
113                .t()
114                .mapv(|x| x.conj())
115                .dot(&swx)
116                .dot(&other.coefficients))
117        }
118    }
119
120    /// Returns the mathematical definition of the overlap between two multi-determinantal
121    /// wavefunctions.
122    fn overlap_definition(&self) -> String {
123        let k = if self.complex_symmetric() { "κ " } else { "" };
124        format!("⟨{k}Ψ_1|Ψ_2⟩ = ∫ [{k}Ψ_1(x^Ne)]* Ψ_2(x^Ne) dx^Ne")
125    }
126}
127
128// =============================
129// MultiDeterminantSymmetryOrbit
130// =============================
131
132// -----------------
133// Struct definition
134// -----------------
135
136/// Structure to manage symmetry orbits (*i.e.* orbits generated by symmetry groups) of
137/// individual multi-determinantal wavefunctions.
138#[derive(Builder, Clone)]
139pub struct MultiDeterminantSymmetryOrbit<'a, 'o, G, T, B, SC>
140where
141    G: SymmetryGroupProperties,
142    T: ComplexFloat + fmt::Debug + Lapack,
143    SC: StructureConstraint + Hash + Eq + fmt::Display,
144    B: 'a + Basis<Item = SlaterDeterminant<'a, T, SC>> + Clone,
145    MultiDeterminant<'a, T, B, SC>: SymmetryTransformable,
146{
147    /// The generating symmetry group.
148    group: &'o G,
149
150    /// The origin multi-determinantal wavefunction of the orbit.
151    origin: &'o MultiDeterminant<'a, T, B, SC>,
152
153    /// The threshold for determining linear independence from orbit overlap matrices.
154    pub(crate) linear_independence_threshold: <T as ComplexFloat>::Real,
155
156    /// The threshold for checking symmetry of the orbit overlap matrix.
157    orbit_overlap_symmetry_threshold: <T as ComplexFloat>::Real,
158
159    /// The threshold for determining if calculated multiplicities in representation analysis are
160    /// integral.
161    integrality_threshold: <T as ComplexFloat>::Real,
162
163    /// The kind of transformation determining the way the symmetry operations in `group` act on
164    /// [`Self::origin`].
165    symmetry_transformation_kind: SymmetryTransformationKind,
166
167    /// The overlap matrix between the symmetry-equivalent multi-determinantal wavefunctions in the
168    /// orbit.
169    #[builder(setter(skip), default = "None")]
170    smat: Option<Array2<T>>,
171
172    /// The eigenvalues of the overlap matrix between the symmetry-equivalent multi-determinantal
173    /// wavefunctions in the orbit.
174    #[builder(setter(skip), default = "None")]
175    pub(crate) smat_eigvals: Option<Array1<T>>,
176
177    /// The $`\mathbf{X}`$ matrix for the overlap matrix between the symmetry-equivalent
178    /// multi-determinantal wavefunctions in the orbit.
179    ///
180    /// See [`RepAnalysis::xmat`] for further information.
181    #[builder(setter(skip), default = "None")]
182    xmat: Option<Array2<T>>,
183
184    /// An enumerated type specifying the comparison mode for filtering out orbit overlap
185    /// eigenvalues.
186    eigenvalue_comparison_mode: EigenvalueComparisonMode,
187}
188
189// ----------------------------
190// Struct method implementation
191// ----------------------------
192
193impl<'a, 'g, G, T, B, SC> MultiDeterminantSymmetryOrbit<'a, 'g, G, T, B, SC>
194where
195    G: SymmetryGroupProperties + Clone,
196    T: ComplexFloat + fmt::Debug + Lapack,
197    SC: StructureConstraint + Hash + Eq + Clone + fmt::Display,
198    B: 'a + Basis<Item = SlaterDeterminant<'a, T, SC>> + Clone,
199    MultiDeterminant<'a, T, B, SC>: SymmetryTransformable,
200{
201    /// Returns a builder for constructing a new multi-determinantal wavefunction symmetry orbit.
202    pub fn builder() -> MultiDeterminantSymmetryOrbitBuilder<'a, 'g, G, T, B, SC> {
203        MultiDeterminantSymmetryOrbitBuilder::default()
204    }
205
206    /// Returns the origin of the multi-determinantal wavefunction symmetry orbit.
207    pub fn origin(&self) -> &MultiDeterminant<'a, T, B, SC> {
208        self.origin
209    }
210}
211
212impl<'a, 'g, G, B, SC> MultiDeterminantSymmetryOrbit<'a, 'g, G, f64, B, SC>
213where
214    G: SymmetryGroupProperties,
215    SC: StructureConstraint + Hash + Eq + fmt::Display,
216    B: 'a + Basis<Item = SlaterDeterminant<'a, f64, SC>> + Clone,
217    MultiDeterminant<'a, f64, B, SC>: SymmetryTransformable,
218{
219    fn_calc_xmat_real!(
220        /// Calculates the $`\mathbf{X}`$ matrix for real and symmetric overlap matrix
221        /// $`\mathbf{S}`$ between the symmetry-equivalent Slater determinants in the orbit.
222        ///
223        /// The resulting $`\mathbf{X}`$ is stored in the orbit.
224        ///
225        /// # Arguments
226        ///
227        /// * `preserves_full_rank` - If `true`, when $`\mathbf{S}`$ is already of full rank, then
228        /// $`\mathbf{X}`$ is set to be the identity matrix to avoid mixing the orbit determinants.
229        /// If `false`, $`\mathbf{X}`$ also orthogonalises $`\mathbf{S}`$ even when it is already of
230        /// full rank.
231        pub calc_xmat
232    );
233}
234
235impl<'a, 'g, G, T, B, SC> MultiDeterminantSymmetryOrbit<'a, 'g, G, Complex<T>, B, SC>
236where
237    G: SymmetryGroupProperties,
238    T: Float + Scalar<Complex = Complex<T>>,
239    Complex<T>: ComplexFloat<Real = T> + Scalar<Real = T, Complex = Complex<T>> + Lapack,
240    SC: StructureConstraint + Hash + Eq + fmt::Display,
241    B: 'a + Basis<Item = SlaterDeterminant<'a, Complex<T>, SC>> + Clone,
242    MultiDeterminant<'a, Complex<T>, B, SC>: SymmetryTransformable + Overlap<Complex<T>, Ix2>,
243{
244    fn_calc_xmat_complex!(
245        /// Calculates the $`\mathbf{X}`$ matrix for complex and symmetric or Hermitian overlap
246        /// matrix $`\mathbf{S}`$ between the symmetry-equivalent Slater determinants in the orbit.
247        ///
248        /// The resulting $`\mathbf{X}`$ is stored in the orbit.
249        ///
250        /// # Arguments
251        ///
252        /// * `preserves_full_rank` - If `true`, when $`\mathbf{S}`$ is already of full rank, then
253        /// $`\mathbf{X}`$ is set to be the identity matrix to avoid mixing the orbit determinants.
254        /// If `false`, $`\mathbf{X}`$ also orthogonalises $`\mathbf{S}`$ even when it is already of
255        /// full rank.
256        pub calc_xmat
257    );
258}
259
260// ---------------------
261// Trait implementations
262// ---------------------
263
264// ~~~~~
265// Orbit
266// ~~~~~
267
268impl<'a, 'o, G, T, B, SC> Orbit<G, MultiDeterminant<'a, T, B, SC>>
269    for MultiDeterminantSymmetryOrbit<'a, 'o, G, T, B, SC>
270where
271    G: SymmetryGroupProperties,
272    T: ComplexFloat + fmt::Debug + Lapack,
273    SC: 'a + StructureConstraint + Hash + Eq + fmt::Display,
274    B: 'a + Basis<Item = SlaterDeterminant<'a, T, SC>> + Clone,
275    MultiDeterminant<'a, T, B, SC>: SymmetryTransformable,
276{
277    type OrbitIter = OrbitIterator<'o, G, MultiDeterminant<'a, T, B, SC>>;
278
279    fn group(&self) -> &G {
280        self.group
281    }
282
283    fn origin(&self) -> &MultiDeterminant<'a, T, B, SC> {
284        self.origin
285    }
286
287    fn iter(&self) -> Self::OrbitIter {
288        OrbitIterator::new(
289            self.group,
290            self.origin,
291            match self.symmetry_transformation_kind {
292                SymmetryTransformationKind::Spatial => |op, multidet| {
293                    multidet.sym_transform_spatial(op).with_context(|| {
294                        format!("Unable to apply `{op}` spatially on the origin multi-determinantal wavefunction")
295                    })
296                },
297                SymmetryTransformationKind::SpatialWithSpinTimeReversal => |op, multidet| {
298                    multidet.sym_transform_spatial_with_spintimerev(op).with_context(|| {
299                        format!("Unable to apply `{op}` spatially (with spin-including time reversal) on the origin multi-determinantal wavefunction")
300                    })
301                },
302                SymmetryTransformationKind::Spin => |op, multidet| {
303                    multidet.sym_transform_spin(op).with_context(|| {
304                        format!("Unable to apply `{op}` spin-wise on the origin multi-determinantal wavefunction")
305                    })
306                },
307                SymmetryTransformationKind::SpinSpatial => |op, multidet| {
308                    multidet.sym_transform_spin_spatial(op).with_context(|| {
309                        format!("Unable to apply `{op}` spin-spatially on the origin multi-determinantal wavefunction")
310                    })
311                },
312            },
313        )
314    }
315}
316
317// ~~~~~~~~~~~
318// RepAnalysis
319// ~~~~~~~~~~~
320
321impl<'a, 'o, G, T, B, SC> RepAnalysis<G, MultiDeterminant<'a, T, B, SC>, T, Ix2>
322    for MultiDeterminantSymmetryOrbit<'a, 'o, G, T, B, SC>
323where
324    G: SymmetryGroupProperties,
325    G::CharTab: SubspaceDecomposable<T>,
326    T: Lapack
327        + ComplexFloat<Real = <T as Scalar>::Real>
328        + fmt::Debug
329        + Mul<<T as ComplexFloat>::Real, Output = T>,
330    <T as ComplexFloat>::Real: fmt::Debug
331        + Zero
332        + From<u16>
333        + ToPrimitive
334        + approx::RelativeEq<<T as ComplexFloat>::Real>
335        + approx::AbsDiffEq<Epsilon = <T as Scalar>::Real>,
336    SC: 'a + StructureConstraint + Hash + Eq + Clone + fmt::Display,
337    B: 'a + Basis<Item = SlaterDeterminant<'a, T, SC>> + Clone,
338    MultiDeterminant<'a, T, B, SC>: SymmetryTransformable,
339{
340    fn set_smat(&mut self, smat: Array2<T>) {
341        self.smat = Some(smat)
342    }
343
344    fn smat(&self) -> Option<&Array2<T>> {
345        self.smat.as_ref()
346    }
347
348    fn xmat(&self) -> &Array2<T> {
349        self.xmat
350            .as_ref()
351            .expect("Orbit overlap orthogonalisation matrix not found.")
352    }
353
354    fn norm_preserving_scalar_map(&self, i: usize) -> Result<fn(T) -> T, anyhow::Error> {
355        if self.origin.complex_symmetric {
356            Err(format_err!(
357                "`norm_preserving_scalar_map` is currently not implemented for complex-symmetric overlaps. This thus precludes the use of the Cayley table to speed up the computation of the orbit overlap matrix."
358            ))
359        } else if self
360            .group
361            .get_index(i)
362            .unwrap_or_else(|| panic!("Group operation index `{i}` not found."))
363            .contains_time_reversal()
364        {
365            Ok(ComplexFloat::conj)
366        } else {
367            Ok(|x| x)
368        }
369    }
370
371    fn orbit_overlap_symmetry_threshold(&self) -> <T as ComplexFloat>::Real {
372        self.orbit_overlap_symmetry_threshold
373    }
374
375    fn integrality_threshold(&self) -> <T as ComplexFloat>::Real {
376        self.integrality_threshold
377    }
378
379    fn eigenvalue_comparison_mode(&self) -> &EigenvalueComparisonMode {
380        &self.eigenvalue_comparison_mode
381    }
382
383    /// Reduces the representation or corepresentation spanned by the multi-determinantal
384    /// wavefunctions in the orbit to a direct sum of the irreducible representations or
385    /// corepresentations of the generating symmetry group.
386    ///
387    /// # Returns
388    ///
389    /// The decomposed result.
390    ///
391    /// # Errors
392    ///
393    /// Errors if the decomposition fails, *e.g.* because one or more calculated multiplicities
394    /// are non-integral, or also because the combination of group type, transformation type, and
395    /// oddity of the number of electrons would not give sensible symmetry results. In particular,
396    /// spin or spin-spatial symmetry analysis of odd-electron systems in unitary-represented
397    /// magnetic groups is not valid.
398    fn analyse_rep(
399        &self,
400    ) -> Result<
401        <<G as CharacterProperties>::CharTab as SubspaceDecomposable<T>>::Decomposition,
402        DecompositionError,
403    > {
404        log::debug!("Analysing representation symmetry for a multi-determinantal wavefunction...");
405        let mut nelectrons_set = self
406            .origin()
407            .basis
408            .iter()
409            .map(|det_res| det_res.and_then(|det| {
410                let det_nelectrons_float = det.nelectrons();
411                if approx::relative_eq!(
412                    det_nelectrons_float.round(),
413                    det_nelectrons_float,
414                    epsilon = self.integrality_threshold,
415                    max_relative = self.integrality_threshold
416                ) {
417                    det_nelectrons_float.round().to_usize().ok_or(format_err!("Unable to convert the number of electrons `{det_nelectrons_float:.7}` to `usize`."))
418                } else {
419                    Err(format_err!("Fractional number of electrons encountered: `{det_nelectrons_float:.7}`"))
420                }
421            }))
422            .collect::<Result<HashSet<_>, _>>()
423            .map_err(|err| DecompositionError(err.to_string()))?;
424        if nelectrons_set.len() != 1 {
425            Err(DecompositionError("Symmetry analysis for multi-determinantal wavefunctions with multiple numbers of electrons is not yet supported.".to_string()))
426        } else {
427            let nelectrons = nelectrons_set.drain().next().ok_or(DecompositionError(
428                "Unable to obtain the number of electrons.".to_string(),
429            ))?;
430            let (valid_symmetry, err_str) = if nelectrons.rem_euclid(2) == 0 {
431                // Even number of electrons; always valid
432                (true, String::new())
433            } else {
434                // Odd number of electrons; validity depends on group and orbit type
435                match self.symmetry_transformation_kind {
436                    SymmetryTransformationKind::Spatial => (true, String::new()),
437                    SymmetryTransformationKind::SpatialWithSpinTimeReversal
438                        | SymmetryTransformationKind::Spin
439                        | SymmetryTransformationKind::SpinSpatial => {
440                        match self.group().group_type() {
441                            GroupType::Ordinary(_) => (true, String::new()),
442                            GroupType::MagneticGrey(_) | GroupType::MagneticBlackWhite(_) => {
443                                (!self.group().unitary_represented(),
444                                "Unitary-represented magnetic groups cannot be used for symmetry analysis of odd-electron systems where spin is treated explicitly.".to_string())
445                            }
446                        }
447                    }
448                }
449            };
450
451            if valid_symmetry {
452                let chis = self
453                    .calc_characters()
454                    .map_err(|err| DecompositionError(err.to_string()))?;
455                log::debug!("Characters calculated.");
456
457                log_subtitle("Multi-determinantal wavefunction orbit characters");
458                qsym2_output!("");
459                self.characters_to_string(&chis, self.integrality_threshold)
460                    .log_output_display();
461                qsym2_output!("");
462
463                let res = self.group().character_table().reduce_characters(
464                    &chis.iter().map(|(cc, chi)| (cc, *chi)).collect::<Vec<_>>(),
465                    self.integrality_threshold(),
466                );
467                log::debug!("Characters reduced.");
468                log::debug!(
469                    "Analysing representation symmetry for a multi-determinantal wavefunction... Done."
470                );
471                res
472            } else {
473                Err(DecompositionError(err_str))
474            }
475        }
476    }
477}
478
479// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
480// Optimised implementation for multi-determinantal wavefunctions constructed from orbits
481// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
482impl<'a, 'o, G, T, SC>
483    MultiDeterminantSymmetryOrbit<
484        'a,
485        'o,
486        G,
487        T,
488        OrbitBasis<G, SlaterDeterminant<'a, T, SC>>,
489        SC,
490    >
491where
492    G: SymmetryGroupProperties + Clone,
493    G::CharTab: SubspaceDecomposable<T>,
494    T: Lapack
495        + ComplexFloat<Real = <T as Scalar>::Real>
496        + fmt::Debug
497        + Mul<<T as ComplexFloat>::Real, Output = T>,
498    <T as ComplexFloat>::Real: fmt::Debug
499        + Zero
500        + From<u16>
501        + ToPrimitive
502        + approx::RelativeEq<<T as ComplexFloat>::Real>
503        + approx::AbsDiffEq<Epsilon = <T as Scalar>::Real>,
504    SC: StructureConstraint + Hash + Eq + Clone + fmt::Display,
505    MultiDeterminant<'a, T, OrbitBasis<G, SlaterDeterminant<'a, T, SC>>, SC>:
506        SymmetryTransformable,
507{
508    /// Calculates and stores the overlap matrix between multi-determinantal wavefunctions in the
509    /// orbit, with respect to a metric of the basis in which the constituting Slater determinants
510    /// are expressed.
511    ///
512    /// This function is particularly optimised for multi-determinantal wavefunctions constructed
513    /// from orbits of origin Slater determinants such that the multi-determinantal wavefunctions
514    /// are never explicitly transformed by group operations.
515    ///
516    /// # Arguments
517    ///
518    /// * `metric` - The metric of the basis in which the orbit items are expressed.
519    /// * `metric_h` - The complex-symmetric metric of the basis in which the orbit items are
520    ///   expressed. This is required if antiunitary operations are involved.
521    /// * `use_cayley_table` - A boolean indicating if the Cayley table of the group should be used
522    ///   to speed up the computation of the overlap matrix. If `false`, this will revert back to the
523    ///   non-optimised overlap matrix calculation.
524    #[allow(dead_code)]
525    pub(crate) fn calc_smat_optimised(
526        &mut self,
527        metric: Option<&Array2<T>>,
528        metric_h: Option<&Array2<T>>,
529        use_cayley_table: bool,
530    ) -> Result<&mut Self, anyhow::Error> {
531        ensure!(
532            self.group.name() == self.origin.basis.group().name(),
533            "Multi-determinantal wavefunction orbit-generating group does not match symmetry analysis group."
534        );
535
536        if let (Some(ctb), true) = (self.group().cayley_table(), use_cayley_table) {
537            log::debug!(
538                "Cayley table available. Group closure will be used to speed up overlap matrix computation."
539            );
540
541            let order = self.group.order();
542            let mut smat = Array2::<T>::zeros((order, order));
543            let multidet_0 = self.origin();
544            let det_origins = multidet_0.basis.origins();
545            let n_det_origins = det_origins.len();
546
547            let detov_kpwx_vec = multidet_0
548                .basis
549                .iter()
550                .collect::<Result<Vec<_>, _>>()?
551                .into_iter()
552                .cartesian_product(det_origins.iter())
553                .map(|(w, x)| w.overlap(x, metric, metric_h))
554                .collect::<Result<Vec<_>, _>>()?;
555            let detov_kpwx =
556                Array3::from_shape_vec((order, n_det_origins, n_det_origins), detov_kpwx_vec)?;
557            let ovs = (0..order)
558                .map(|k| {
559                    [
560                        (0..order),
561                        (0..order),
562                        (0..n_det_origins),
563                        (0..n_det_origins),
564                    ]
565                    .into_iter()
566                    .multi_cartesian_product()
567                    .try_fold(T::zero(), |acc, v| {
568                        let ip = v[0];
569                        let jp = v[1];
570                        let w = v[2];
571                        let x = v[3];
572                        let aipw = multidet_0.coefficients[ip * n_det_origins + w];
573                        let ajpx = multidet_0.coefficients[jp * n_det_origins + x];
574
575                        let jpinv = ctb.slice(s![.., jp]).iter().position(|&x| x == 0).ok_or(
576                            format_err!("Unable to find the inverse of group element `{jp}`."),
577                        )?;
578                        let kp = ctb[(jpinv, ctb[(k, ip)])];
579                        let ukipwjpx = self.norm_preserving_scalar_map(jp)?(detov_kpwx[(kp, w, x)]);
580
581                        Ok::<_, anyhow::Error>(
582                            acc + self.norm_preserving_scalar_map(k)?(aipw.conj())
583                                * ukipwjpx
584                                * ajpx,
585                        )
586                    })
587                })
588                .collect::<Result<Vec<_>, _>>()?;
589            for (i, j) in (0..order).cartesian_product(0..order) {
590                let jinv = ctb
591                    .slice(s![.., j])
592                    .iter()
593                    .position(|&x| x == 0)
594                    .ok_or(format_err!(
595                        "Unable to find the inverse of group element `{j}`."
596                    ))?;
597                let jinv_i = ctb[(jinv, i)];
598                smat[(i, j)] = self.norm_preserving_scalar_map(jinv)?(ovs[jinv_i]);
599            }
600            if self.origin().complex_symmetric() {
601                let _ = check_complex_matrix_symmetry(
602                    &smat.view(),
603                    true,
604                    self.orbit_overlap_symmetry_threshold,
605                    "Orbit overlap",
606                    "S_orbit",
607                );
608                self.set_smat(
609                    (smat.clone() + smat.t().to_owned()).mapv(|x| x / (T::one() + T::one())),
610                )
611            } else {
612                let _ = check_complex_matrix_symmetry(
613                    &smat.view(),
614                    false,
615                    self.orbit_overlap_symmetry_threshold,
616                    "Orbit overlap",
617                    "S_orbit",
618                );
619                self.set_smat(
620                    (smat.clone() + smat.t().to_owned().mapv(|x| x.conj()))
621                        .mapv(|x| x / (T::one() + T::one())),
622                )
623            }
624            Ok(self)
625        } else {
626            self.calc_smat(metric, metric_h, use_cayley_table)
627        }
628    }
629}
630
631// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
632// Optimised implementation for multi-determinantal wavefunctions constructed from FCI bases
633// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
634impl<'a, 'o, G, T, SC> MultiDeterminantSymmetryOrbit<'a, 'o, G, T, FCIBasis<'a, T, SC>, SC>
635where
636    G: SymmetryGroupProperties + Clone,
637    G::CharTab: SubspaceDecomposable<T>,
638    T: Lapack
639        + ComplexFloat<Real = <T as Scalar>::Real>
640        + fmt::Debug
641        + Mul<<T as ComplexFloat>::Real, Output = T>
642        + Sync
643        + Send,
644    <T as ComplexFloat>::Real: fmt::Debug
645        + Zero
646        + From<u16>
647        + ToPrimitive
648        + approx::RelativeEq<<T as ComplexFloat>::Real>
649        + approx::AbsDiffEq<Epsilon = <T as Scalar>::Real>
650        + Sync
651        + Send,
652    SC: StructureConstraint + Hash + Eq + Clone + fmt::Display + Sync + Send,
653    MultiDeterminant<'a, T, FCIBasis<'a, T, SC>, SC>: SymmetryTransformable,
654    FCIBasis<'a, T, SC>: SymmetryTransformable,
655{
656    /// Calculates and stores the overlap matrix between multi-determinantal wavefunctions in the
657    /// orbit, with respect to a metric of the basis in which the constituting Slater determinants
658    /// are expressed.
659    ///
660    /// This function is particularly optimised for full-configuration-interaction
661    /// multi-determinantal wavefunctions.
662    ///
663    /// # Arguments
664    ///
665    /// * `metric` - The metric of the basis in which the orbit items are expressed.
666    /// * `metric_h` - The complex-symmetric metric of the basis in which the orbit items are
667    ///   expressed. This is required if antiunitary operations are involved.
668    /// * `use_cayley_table` - A boolean indicating if the Cayley table of the group should be used
669    ///   to speed up the computation of the overlap matrix. If `false`, this will revert back to the
670    ///   non-optimised overlap matrix calculation.
671    #[allow(dead_code)]
672    pub(crate) fn calc_smat_optimised(
673        &mut self,
674        metric: Option<&Array2<T>>,
675        metric_h: Option<&Array2<T>>,
676        use_cayley_table: bool,
677    ) -> Result<&mut Self, anyhow::Error> {
678        if let (Some(ctb), true) = (self.group().cayley_table(), use_cayley_table) {
679            log::debug!(
680                "Cayley table available. Group closure will be used to speed up overlap matrix computation."
681            );
682
683            let order = self.group.order();
684            let mut smat = Array2::<T>::zeros((order, order));
685
686            let multidet_0 = self.origin();
687            let fci_basis_0 = multidet_0.basis();
688
689            // i: group element index
690            // I, J: FCI indices
691            #[allow(non_snake_case)]
692            let ov_iI_0J = fci_basis_0.basis_symmetry_orbit_metric(
693                self.group(),
694                &self.symmetry_transformation_kind,
695                metric,
696                metric_h,
697            )?;
698
699            #[allow(non_snake_case)]
700            let cJ = multidet_0.coefficients();
701            #[allow(non_snake_case)]
702            let ov_i_vec = ov_iI_0J
703                .axis_iter(Axis(0))
704                .enumerate()
705                .map(|(i, ov_I_0J)| {
706                    let cI = multidet_0
707                        .coefficients()
708                        .mapv(self.norm_preserving_scalar_map(i)?);
709                    if multidet_0.complex_symmetric() {
710                        einsum("ij,i,j->", &[&ov_I_0J, &cI, cJ])
711                            .map_err(|err| format_err!(err))?
712                            .into_dimensionality::<Ix0>()
713                            .map_err(|err| format_err!(err))?
714                            .into_iter()
715                            .next()
716                            .ok_or(format_err!(
717                                "Unable to extract the overlap value between `gΦ0` and `Φ0`."
718                            ))
719                    } else {
720                        einsum("ij,i,j->", &[&ov_I_0J, &cI.mapv(ComplexFloat::conj), cJ])
721                            .map_err(|err| format_err!(err))?
722                            .into_dimensionality::<Ix0>()
723                            .map_err(|err| format_err!(err))?
724                            .into_iter()
725                            .next()
726                            .ok_or(format_err!(
727                                "Unable to extract the overlap value between `gΦ0` and `Φ0`."
728                            ))
729                    }
730                })
731                .collect::<Result<Vec<_>, _>>()?;
732
733            for (i, j) in (0..order).cartesian_product(0..order) {
734                let jinv = ctb
735                    .slice(s![.., j])
736                    .iter()
737                    .position(|&x| x == 0)
738                    .ok_or(format_err!(
739                        "Unable to find the inverse of group element `{j}`."
740                    ))?;
741                let jinv_i = ctb[(jinv, i)];
742                smat[(i, j)] = self.norm_preserving_scalar_map(jinv)?(ov_i_vec[jinv_i]);
743            }
744            if self.origin().complex_symmetric() {
745                let _ = check_complex_matrix_symmetry(
746                    &smat.view(),
747                    true,
748                    self.orbit_overlap_symmetry_threshold,
749                    "Orbit overlap",
750                    "S_orbit",
751                );
752                self.set_smat(
753                    (smat.clone() + smat.t().to_owned()).mapv(|x| x / (T::one() + T::one())),
754                )
755            } else {
756                let _ = check_complex_matrix_symmetry(
757                    &smat.view(),
758                    false,
759                    self.orbit_overlap_symmetry_threshold,
760                    "Orbit overlap",
761                    "S_orbit",
762                );
763                self.set_smat(
764                    (smat.clone() + smat.t().to_owned().mapv(|x| x.conj()))
765                        .mapv(|x| x / (T::one() + T::one())),
766                )
767            }
768            Ok(self)
769        } else {
770            self.calc_smat(metric, metric_h, use_cayley_table)
771        }
772    }
773}