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