Skip to main content

qsym2/target/orbital/
orbital_analysis.rs

1//! Implementation of symmetry analysis for orbitals.
2
3use std::fmt;
4use std::ops::Mul;
5
6use anyhow::{self, Context, ensure, format_err};
7use approx;
8use derive_builder::Builder;
9use itertools::{Itertools, izip};
10use log;
11use ndarray::{Array1, Array2, Axis, Ix2, s};
12use ndarray_linalg::{
13    UPLO,
14    eig::Eig,
15    eigh::Eigh,
16    solve::Determinant,
17    types::{Lapack, Scalar},
18};
19use num_complex::{Complex, ComplexFloat};
20use num_traits::{Float, ToPrimitive, Zero};
21
22use crate::analysis::{
23    EigenvalueComparisonMode, Orbit, OrbitIterator, Overlap, RepAnalysis, fn_calc_xmat_complex,
24    fn_calc_xmat_real,
25};
26use crate::angmom::spinor_rotation_3d::StructureConstraint;
27use crate::auxiliary::misc::{ProductRepeat, complex_modified_gram_schmidt};
28use crate::chartab::chartab_group::CharacterProperties;
29use crate::chartab::{DecompositionError, SubspaceDecomposable};
30use crate::group::GroupType;
31use crate::symmetry::symmetry_element::symmetry_operation::SpecialSymmetryTransformation;
32use crate::symmetry::symmetry_group::SymmetryGroupProperties;
33use crate::symmetry::symmetry_transformation::{SymmetryTransformable, SymmetryTransformationKind};
34use crate::target::determinant::SlaterDeterminant;
35use crate::target::determinant::determinant_analysis::SlaterDeterminantSymmetryOrbit;
36use crate::target::orbital::MolecularOrbital;
37
38// -------
39// Overlap
40// -------
41
42impl<'a, T, SC> Overlap<T, Ix2> for MolecularOrbital<'a, T, SC>
43where
44    T: Lapack
45        + ComplexFloat<Real = <T as Scalar>::Real>
46        + fmt::Debug
47        + Mul<<T as ComplexFloat>::Real, Output = T>,
48    <T as ComplexFloat>::Real: fmt::Debug
49        + approx::RelativeEq<<T as ComplexFloat>::Real>
50        + approx::AbsDiffEq<Epsilon = <T as Scalar>::Real>,
51    SC: StructureConstraint + Eq + fmt::Display,
52{
53    fn complex_symmetric(&self) -> bool {
54        self.complex_symmetric
55    }
56
57    /// Computes the overlap between two molecular orbitals.
58    ///
59    /// When one or both of the orbitals have been acted on by an antiunitary operation, the correct
60    /// Hermitian or complex-symmetric metric will be chosen in the evalulation of the overlap.
61    ///
62    /// # Panics
63    ///
64    /// Panics if `self` and `other` have mismatched spin constraints or coefficient array lengths.
65    fn overlap(
66        &self,
67        other: &Self,
68        metric: Option<&Array2<T>>,
69        metric_h: Option<&Array2<T>>,
70    ) -> Result<T, anyhow::Error> {
71        ensure!(
72            self.structure_constraint == other.structure_constraint,
73            "Inconsistent spin constraints between `self` and `other`."
74        );
75        ensure!(
76            self.coefficients.len() == other.coefficients.len(),
77            "Inconsistent numbers of coefficient matrices between `self` and `other`."
78        );
79
80        let sao = metric.ok_or_else(|| format_err!("No atomic-orbital metric found."))?;
81        let sao_h = metric_h.unwrap_or(sao);
82        let ov = if self.component_index != other.component_index {
83            T::zero()
84        } else if self.complex_symmetric() {
85            match (self.complex_conjugated, other.complex_conjugated) {
86                (false, false) => self.coefficients.t().dot(sao_h).dot(&other.coefficients),
87                (true, false) => self.coefficients.t().dot(sao).dot(&other.coefficients),
88                (false, true) => other.coefficients.t().dot(sao).dot(&self.coefficients),
89                (true, true) => self
90                    .coefficients
91                    .t()
92                    .dot(&sao_h.t())
93                    .dot(&other.coefficients),
94            }
95        } else {
96            match (self.complex_conjugated, other.complex_conjugated) {
97                (false, false) => self
98                    .coefficients
99                    .t()
100                    .mapv(|x| x.conj()) // This conjugation is still needed as it comes from the sesquilinear form.
101                    .dot(sao)
102                    .dot(&other.coefficients),
103                (true, false) => self
104                    .coefficients
105                    .t()
106                    .mapv(|x| x.conj()) // This conjugation is still needed as it comes from the sesquilinear form.
107                    .dot(sao_h)
108                    .dot(&other.coefficients),
109                (false, true) => other
110                    .coefficients
111                    .t()
112                    .mapv(|x| x.conj()) // This conjugation is still needed as it comes from the sesquilinear form.
113                    .dot(sao_h)
114                    .dot(&self.coefficients)
115                    .conj(),
116                (true, true) => self
117                    .coefficients
118                    .t()
119                    .mapv(|x| x.conj()) // This conjugation is still needed as it comes from the sesquilinear form.
120                    .dot(&sao.t())
121                    .dot(&other.coefficients),
122            }
123        };
124        Ok(ov)
125    }
126
127    /// Returns the mathematical definition of the overlap between two orbitals.
128    fn overlap_definition(&self) -> String {
129        let k = if self.complex_symmetric() { "κ " } else { "" };
130        format!("⟨{k}ψ_1|ψ_2⟩ = ∫ [{k}ψ_1(x)]* ψ_2(x) dx")
131    }
132}
133
134// =============================
135// MolecularOrbitalSymmetryOrbit
136// =============================
137
138// -----------------
139// Struct definition
140// -----------------
141
142/// Structure to manage symmetry orbits (*i.e.* orbits generated by symmetry groups) of molecular
143/// orbitals.
144#[derive(Builder, Clone)]
145pub struct MolecularOrbitalSymmetryOrbit<'a, G, T, SC>
146where
147    G: SymmetryGroupProperties,
148    T: ComplexFloat + fmt::Debug + Lapack,
149    SC: StructureConstraint + fmt::Display,
150    MolecularOrbital<'a, T, SC>: SymmetryTransformable,
151{
152    /// The generating symmetry group.
153    group: &'a G,
154
155    /// The origin molecular orbital of the orbit.
156    origin: &'a MolecularOrbital<'a, T, SC>,
157
158    /// The threshold for determining if calculated multiplicities in representation analysis are
159    /// integral.
160    integrality_threshold: <T as ComplexFloat>::Real,
161
162    /// The threshold for determining zero eigenvalues in the orbit overlap matrix.
163    pub(crate) linear_independence_threshold: <T as ComplexFloat>::Real,
164
165    /// The threshold for checking symmetry of the orbit overlap matrix.
166    orbit_overlap_symmetry_threshold: <T as ComplexFloat>::Real,
167
168    /// The kind of transformation determining the way the symmetry operations in `group` act on
169    /// [`Self::origin`].
170    symmetry_transformation_kind: SymmetryTransformationKind,
171
172    /// The overlap matrix between the symmetry-equivalent molecular orbitals in the orbit.
173    #[builder(setter(skip), default = "None")]
174    smat: Option<Array2<T>>,
175
176    /// The eigenvalues of the overlap matrix between the symmetry-equivalent molecular orbitals in
177    /// the orbit.
178    #[builder(setter(skip), default = "None")]
179    pub(crate) smat_eigvals: Option<Array1<T>>,
180
181    /// The $`\mathbf{X}`$ matrix for the overlap matrix between the symmetry-equivalent molecular
182    /// orbitals in the orbit.
183    ///
184    /// See [`RepAnalysis::xmat`] for further information.
185    #[builder(setter(skip), default = "None")]
186    xmat: Option<Array2<T>>,
187
188    /// An enumerated type specifying the comparison mode for filtering out orbit overlap
189    /// eigenvalues.
190    pub(crate) eigenvalue_comparison_mode: EigenvalueComparisonMode,
191}
192
193// ----------------------------
194// Struct method implementation
195// ----------------------------
196
197impl<'a, G, T, SC> MolecularOrbitalSymmetryOrbit<'a, G, T, SC>
198where
199    G: SymmetryGroupProperties + Clone,
200    T: ComplexFloat + fmt::Debug + Lapack,
201    SC: StructureConstraint + Clone + fmt::Display,
202    MolecularOrbital<'a, T, SC>: SymmetryTransformable,
203{
204    /// Returns a builder to construct a new [`MolecularOrbitalSymmetryOrbit`] structure.
205    pub fn builder() -> MolecularOrbitalSymmetryOrbitBuilder<'a, G, T, SC> {
206        MolecularOrbitalSymmetryOrbitBuilder::default()
207    }
208
209    /// Constructs multiple molecular orbital orbits, each from one of the supplied orbitals.
210    ///
211    /// # Arguments
212    ///
213    /// * `group` - The orbit-generating group.
214    /// * `orbitals` - The origin orbitals, each of which generates its own orbit.
215    /// * `sym_kind` - The symmetry transformation kind.
216    /// * `integrality_thresh` - The threshold of integrality check of multiplicity coefficients in
217    ///   each orbit.
218    /// * `linear_independence_thresh` - The threshold of linear independence for each orbit.
219    ///
220    /// # Returns
221    ///
222    /// A vector of molecular orbital orbits.
223    pub fn from_orbitals(
224        group: &'a G,
225        orbitals: &'a [Vec<MolecularOrbital<'a, T, SC>>],
226        sym_kind: SymmetryTransformationKind,
227        eig_comp_mode: EigenvalueComparisonMode,
228        integrality_thresh: <T as ComplexFloat>::Real,
229        linear_independence_thresh: <T as ComplexFloat>::Real,
230        orbit_overlap_symmetry_threshold: <T as ComplexFloat>::Real,
231    ) -> Vec<Vec<Self>> {
232        orbitals
233            .iter()
234            .map(|orbs_spin| {
235                orbs_spin
236                    .iter()
237                    .map(|orb| {
238                        MolecularOrbitalSymmetryOrbit::builder()
239                            .group(group)
240                            .origin(orb)
241                            .integrality_threshold(integrality_thresh)
242                            .linear_independence_threshold(linear_independence_thresh)
243                            .orbit_overlap_symmetry_threshold(orbit_overlap_symmetry_threshold)
244                            .symmetry_transformation_kind(sym_kind.clone())
245                            .eigenvalue_comparison_mode(eig_comp_mode.clone())
246                            .build()
247                            .expect("Unable to construct a molecular orbital symmetry orbit.")
248                    })
249                    .collect_vec()
250            })
251            .collect_vec()
252    }
253}
254
255impl<'a, G, SC> MolecularOrbitalSymmetryOrbit<'a, G, f64, SC>
256where
257    G: SymmetryGroupProperties,
258    SC: StructureConstraint + fmt::Display,
259    MolecularOrbital<'a, f64, SC>: SymmetryTransformable,
260{
261    fn_calc_xmat_real!(
262        /// Calculates the $`\mathbf{X}`$ matrix for real and symmetric overlap matrix
263        /// $`\mathbf{S}`$ between the symmetry-equivalent molecular orbitals in the orbit.
264        ///
265        /// The resulting $`\mathbf{X}`$ is stored in the orbit.
266        ///
267        /// # Arguments
268        ///
269        /// * `preserves_full_rank` - If `true`, when $`\mathbf{S}`$ is already of full rank, then
270        /// $`\mathbf{X}`$ is set to be the identity matrix to avoid mixing the orbit molecular
271        /// orbitals. If `false`, $`\mathbf{X}`$ also orthogonalises $`\mathbf{S}`$ even when it is
272        /// already of full rank.
273        pub calc_xmat
274    );
275}
276
277impl<'a, G, T, SC> MolecularOrbitalSymmetryOrbit<'a, G, Complex<T>, SC>
278where
279    G: SymmetryGroupProperties,
280    T: Float + Scalar<Complex = Complex<T>>,
281    Complex<T>: ComplexFloat<Real = T> + Scalar<Real = T, Complex = Complex<T>> + Lapack,
282    SC: StructureConstraint + fmt::Display,
283    MolecularOrbital<'a, Complex<T>, SC>: SymmetryTransformable + Overlap<Complex<T>, Ix2>,
284{
285    fn_calc_xmat_complex!(
286        /// Calculates the $`\mathbf{X}`$ matrix for complex and symmetric or Hermitian overlap
287        /// matrix $`\mathbf{S}`$ between the symmetry-equivalent molecular orbitals in the orbit.
288        ///
289        /// The resulting $`\mathbf{X}`$ is stored in the orbit.
290        ///
291        /// # Arguments
292        ///
293        /// * `preserves_full_rank` - If `true`, when $`\mathbf{S}`$ is already of full rank, then
294        /// $`\mathbf{X}`$ is set to be the identity matrix to avoid mixing the orbit molecular
295        /// orbitals. If `false`, $`\mathbf{X}`$ also orthogonalises $`\mathbf{S}`$ even when it is
296        /// already of full rank.
297        pub calc_xmat
298    );
299}
300
301// ---------------------
302// Trait implementations
303// ---------------------
304
305// ~~~~~
306// Orbit
307// ~~~~~
308
309impl<'a, G, T, SC> Orbit<G, MolecularOrbital<'a, T, SC>>
310    for MolecularOrbitalSymmetryOrbit<'a, G, T, SC>
311where
312    G: SymmetryGroupProperties,
313    T: ComplexFloat + fmt::Debug + Lapack,
314    SC: StructureConstraint + fmt::Display,
315    MolecularOrbital<'a, T, SC>: SymmetryTransformable,
316{
317    type OrbitIter = OrbitIterator<'a, G, MolecularOrbital<'a, T, SC>>;
318
319    fn group(&self) -> &G {
320        self.group
321    }
322
323    fn origin(&self) -> &MolecularOrbital<'a, T, SC> {
324        self.origin
325    }
326
327    fn iter(&self) -> Self::OrbitIter {
328        OrbitIterator::new(
329            self.group,
330            self.origin,
331            match self.symmetry_transformation_kind {
332                SymmetryTransformationKind::Spatial => |op, orb| {
333                    orb.sym_transform_spatial(op).with_context(|| {
334                        format!("Unable to apply `{op}` spatially on the origin orbital")
335                    })
336                },
337                SymmetryTransformationKind::SpatialWithSpinTimeReversal => |op, orb| {
338                    orb.sym_transform_spatial_with_spintimerev(op).with_context(|| {
339                        format!("Unable to apply `{op}` spatially (with spin-including time reversal) on the origin orbital")
340                    })
341                },
342                SymmetryTransformationKind::Spin => |op, orb| {
343                    orb.sym_transform_spin(op).with_context(|| {
344                        format!("Unable to apply `{op}` spin-wise on the origin orbital")
345                    })
346                },
347                SymmetryTransformationKind::SpinSpatial => |op, orb| {
348                    orb.sym_transform_spin_spatial(op).with_context(|| {
349                        format!("Unable to apply `{op}` spin-spatially on the origin orbital",)
350                    })
351                },
352            },
353        )
354    }
355}
356
357// ~~~~~~~~~~~
358// RepAnalysis
359// ~~~~~~~~~~~
360
361impl<'a, G, T, SC> RepAnalysis<G, MolecularOrbital<'a, T, SC>, T, Ix2>
362    for MolecularOrbitalSymmetryOrbit<'a, G, T, SC>
363where
364    G: SymmetryGroupProperties,
365    G::CharTab: SubspaceDecomposable<T>,
366    T: Lapack
367        + ComplexFloat<Real = <T as Scalar>::Real>
368        + fmt::Debug
369        + Mul<<T as ComplexFloat>::Real, Output = T>,
370    <T as ComplexFloat>::Real: fmt::Debug
371        + Zero
372        + approx::RelativeEq<<T as ComplexFloat>::Real>
373        + approx::AbsDiffEq<Epsilon = <T as Scalar>::Real>,
374    SC: StructureConstraint + Eq + fmt::Display,
375    MolecularOrbital<'a, T, SC>: SymmetryTransformable,
376{
377    fn set_smat(&mut self, smat: Array2<T>) {
378        self.smat = Some(smat)
379    }
380
381    fn smat(&self) -> Option<&Array2<T>> {
382        self.smat.as_ref()
383    }
384
385    fn xmat(&self) -> &Array2<T> {
386        self.xmat
387            .as_ref()
388            .expect("Orbit overlap orthogonalisation matrix not found.")
389    }
390
391    fn norm_preserving_scalar_map(&self, i: usize) -> Result<fn(T) -> T, anyhow::Error> {
392        if self.origin.complex_symmetric {
393            Err(format_err!(
394                "`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."
395            ))
396        } else if self
397            .group
398            .get_index(i)
399            .unwrap_or_else(|| panic!("Group operation index `{i}` not found."))
400            .contains_time_reversal()
401        {
402            Ok(ComplexFloat::conj)
403        } else {
404            Ok(|x| x)
405        }
406    }
407
408    fn orbit_overlap_symmetry_threshold(&self) -> <T as ComplexFloat>::Real {
409        self.orbit_overlap_symmetry_threshold
410    }
411
412    fn integrality_threshold(&self) -> <T as ComplexFloat>::Real {
413        self.integrality_threshold
414    }
415
416    fn eigenvalue_comparison_mode(&self) -> &EigenvalueComparisonMode {
417        &self.eigenvalue_comparison_mode
418    }
419
420    /// Reduces the representation or corepresentation spanned by the molecular orbitals in the
421    /// orbit to a direct sum of the irreducible representations or corepresentations of the
422    /// generating symmetry group.
423    ///
424    /// # Returns
425    ///
426    /// The decomposed result.
427    ///
428    /// # Errors
429    ///
430    /// Errors if the decomposition fails, *e.g.* because one or more calculated multiplicities
431    /// are non-integral, or also because the combination of group type and transformation type
432    /// would not give sensible symmetry results for a single-electron orbital. In particular, spin
433    /// or spin-spatial symmetry analysis in unitary-represented magnetic groups is not valid for
434    /// one-electron orbitals.
435    fn analyse_rep(
436        &self,
437    ) -> Result<
438        <<G as CharacterProperties>::CharTab as SubspaceDecomposable<T>>::Decomposition,
439        DecompositionError,
440    > {
441        // A single electron; validity depends on group and orbit type
442        let (valid_symmetry, err_str) = match self.symmetry_transformation_kind {
443                SymmetryTransformationKind::Spatial => (true, String::new()),
444                SymmetryTransformationKind::SpatialWithSpinTimeReversal
445                    | SymmetryTransformationKind::Spin
446                    | SymmetryTransformationKind::SpinSpatial => {
447                    match self.group().group_type() {
448                        GroupType::Ordinary(_) => (true, String::new()),
449                        GroupType::MagneticGrey(_) | GroupType::MagneticBlackWhite(_) => {
450                            (!self.group().unitary_represented(),
451                            "Unitary-represented magnetic groups cannot be used for symmetry analysis of a one-electron molecular orbital where spin is treated explicitly.".to_string())
452                        }
453                    }
454                }
455        };
456        if valid_symmetry {
457            log::debug!("Analysing representation symmetry for an MO...");
458            let chis = self
459                .calc_characters()
460                .map_err(|err| DecompositionError(err.to_string()))?;
461            let res = self.group().character_table().reduce_characters(
462                &chis.iter().map(|(cc, chi)| (cc, *chi)).collect::<Vec<_>>(),
463                self.integrality_threshold(),
464            );
465            log::debug!("Analysing representation symmetry for an MO... Done.");
466            res
467        } else {
468            Err(DecompositionError(err_str))
469        }
470    }
471}
472
473// ---------
474// Functions
475// ---------
476
477/// Given an origin determinant, generates the determinant orbit and all molecular-orbital orbits
478/// in tandem while populating their $`\mathbf{S}`$ matrices at the same time.
479///
480/// The evaluation of the $`\mathbf{S}`$ matrix for the determinant orbit passes through
481/// intermediate values that can be used to populate the $`\mathbf{S}`$ matrices of the
482/// molecular-orbital orbits, so it saves time significantly to construct all orbits together.
483///
484/// # Arguments
485///
486/// * `det` - An origin determinant.
487/// * `mos` - The molecular orbitals of `det`.
488/// * `group` - An orbit-generating symmetry group.
489/// * `metric` - The metric of the basis in which the coefficients of `det` and `mos` are written.
490/// * `integrality_threshold` - The threshold of integrality check of multiplicity coefficients in
491///   each orbit.
492/// * `linear_independence_threshold` - The threshold of linear independence for each orbit.
493/// * `orbit_overlap_symmetry_threshold` - The threshold for checking the symmetry of each orbit
494///   overlap matrix.
495/// * `symmetry_transformation_kind` - The kind of symmetry transformation to be applied to the
496/// * `eigenvalue_comparison_mode` - The mode of comparing the overlap eigenvalues to the specified
497///   `linear_independence_threshold`.
498/// * `use_cayley_table` - A boolean indicating if the Cayley table of the group, if available,
499///   should be used to speed up the computation of the orbit overlap matrix.
500///
501/// # Returns
502///
503/// A tuple consisting of:
504/// * the determinant orbit, and
505/// * a vector of vectors of molecular-orbital orbits, where each element of the outer vector is
506///   for one spin space, and each element of an inner vector is for one molecular orbital.
507#[allow(clippy::too_many_arguments, clippy::type_complexity)]
508pub fn generate_det_mo_orbits<'a, G, T, SC>(
509    det: &'a SlaterDeterminant<'a, T, SC>,
510    mos: &'a [Vec<MolecularOrbital<'a, T, SC>>],
511    group: &'a G,
512    metric: &Array2<T>,
513    metric_h: Option<&Array2<T>>,
514    integrality_threshold: <T as ComplexFloat>::Real,
515    linear_independence_threshold: <T as ComplexFloat>::Real,
516    orbit_overlap_symmetry_threshold: <T as ComplexFloat>::Real,
517    symmetry_transformation_kind: SymmetryTransformationKind,
518    eigenvalue_comparison_mode: EigenvalueComparisonMode,
519    use_cayley_table: bool,
520) -> Result<
521    (
522        SlaterDeterminantSymmetryOrbit<'a, 'a, G, T, SC>,
523        Vec<Vec<MolecularOrbitalSymmetryOrbit<'a, G, T, SC>>>,
524    ),
525    anyhow::Error,
526>
527where
528    G: SymmetryGroupProperties + Clone,
529    G::CharTab: SubspaceDecomposable<T>,
530    T: Lapack
531        + ComplexFloat<Real = <T as Scalar>::Real>
532        + fmt::Debug
533        + Mul<<T as ComplexFloat>::Real, Output = T>,
534    <T as ComplexFloat>::Real: fmt::Debug
535        + Zero
536        + From<u16>
537        + ToPrimitive
538        + approx::RelativeEq<<T as ComplexFloat>::Real>
539        + approx::AbsDiffEq<Epsilon = <T as Scalar>::Real>,
540    SC: StructureConstraint + Clone + Eq + fmt::Display,
541    SlaterDeterminant<'a, T, SC>: SymmetryTransformable,
542    MolecularOrbital<'a, T, SC>: SymmetryTransformable,
543{
544    log::debug!("Constructing determinant and MO orbits in tandem...");
545    let order = group.order();
546    let mut det_orbit = SlaterDeterminantSymmetryOrbit::<G, T, SC>::builder()
547        .group(group)
548        .origin(det)
549        .integrality_threshold(integrality_threshold)
550        .linear_independence_threshold(linear_independence_threshold)
551        .orbit_overlap_symmetry_threshold(orbit_overlap_symmetry_threshold)
552        .symmetry_transformation_kind(symmetry_transformation_kind.clone())
553        .eigenvalue_comparison_mode(eigenvalue_comparison_mode.clone())
554        .build()
555        .map_err(|err| format_err!(err))?;
556    let mut mo_orbitss = MolecularOrbitalSymmetryOrbit::from_orbitals(
557        group,
558        mos,
559        symmetry_transformation_kind,
560        eigenvalue_comparison_mode,
561        integrality_threshold,
562        linear_independence_threshold,
563        orbit_overlap_symmetry_threshold
564    );
565
566    let mut det_smat = Array2::<T>::zeros((order, order));
567    let mut mo_smatss = mo_orbitss
568        .iter()
569        .map(|mo_orbits| {
570            mo_orbits
571                .iter()
572                .map(|_| Array2::<T>::zeros((order, order)))
573                .collect::<Vec<_>>()
574        })
575        .collect::<Vec<_>>();
576
577    let thresh = det.threshold();
578
579    let sao = metric;
580    let sao_h = metric_h.unwrap_or(sao);
581
582    if let (Some(ctb), true) = (group.cayley_table(), use_cayley_table) {
583        log::debug!(
584            "Cayley table available. Group closure will be used to speed up overlap matrix computation."
585        );
586        let mut det_smatw0 = Array1::<T>::zeros(order);
587        let mut mo_smatw0ss = mo_orbitss
588            .iter()
589            .map(|mo_orbits| {
590                mo_orbits
591                    .iter()
592                    .map(|_| Array1::<T>::zeros(order))
593                    .collect::<Vec<_>>()
594            })
595            .collect::<Vec<_>>();
596        let det_0 = det_orbit.origin();
597        for (w, det_w_res) in det_orbit.iter().enumerate() {
598            let det_w = det_w_res?;
599            let w0_ov = izip!(
600                det_w.coefficients(),
601                det_w.occupations(),
602                det_0.coefficients(),
603                det_0.occupations(),
604            )
605            .enumerate()
606            .map(|(ispin, (cw, occw, c0, occ0))| {
607                let nonzero_occ_w = occw.iter().positions(|&occ| occ > thresh).collect_vec();
608                let nonzero_occ_0 = occ0.iter().positions(|&occ| occ > thresh).collect_vec();
609
610                let all_mo_w0_ov_mat = if det.complex_symmetric() {
611                    match (det_w.complex_conjugated(), det_0.complex_conjugated()) {
612                        (false, false) => cw.t().dot(sao_h).dot(c0),
613                        (true, false) => cw.t().dot(sao).dot(c0),
614                        (false, true) => c0.t().dot(sao).dot(cw),
615                        (true, true) => cw.t().dot(&sao_h.t()).dot(c0),
616                    }
617                } else {
618                    match (det_w.complex_conjugated(), det_0.complex_conjugated()) {
619                        (false, false) => cw.t().mapv(|x| x.conj()).dot(sao).dot(c0),
620                        (true, false) => cw.t().mapv(|x| x.conj()).dot(sao_h).dot(c0),
621                        (false, true) => c0
622                            .t()
623                            .mapv(|x| x.conj())
624                            .dot(sao_h)
625                            .dot(cw)
626                            .mapv(|x| x.conj()),
627                        (true, true) => cw.t().mapv(|x| x.conj()).dot(&sao.t()).dot(c0),
628                    }
629                };
630
631                all_mo_w0_ov_mat
632                    .diag()
633                    .iter()
634                    .enumerate()
635                    .for_each(|(imo, mo_w0_ov)| {
636                        mo_smatw0ss[ispin][imo][w] = *mo_w0_ov;
637                    });
638
639                let occ_mo_w0_ov_mat = all_mo_w0_ov_mat
640                    .select(Axis(0), &nonzero_occ_w)
641                    .select(Axis(1), &nonzero_occ_0);
642
643                occ_mo_w0_ov_mat
644                    .det()
645                    .expect("The determinant of the MO overlap matrix `w0` could not be found.")
646            })
647            .fold(T::one(), |acc, x| acc * x);
648
649            let implicit_factor = det.structure_constraint().implicit_factor()?;
650            let w0_ov = if implicit_factor > 1 {
651                let p_i32 = i32::try_from(implicit_factor)?;
652                ComplexFloat::powi(w0_ov, p_i32)
653            } else {
654                w0_ov
655            };
656            det_smatw0[w] = w0_ov;
657        }
658
659        for (i, j) in (0..order).cartesian_product(0..order) {
660            let jinv = ctb
661                .slice(s![.., j])
662                .iter()
663                .position(|&x| x == 0)
664                .ok_or(format_err!(
665                    "Unable to find the inverse of group element `{j}`."
666                ))?;
667            let jinv_i = ctb[(jinv, i)];
668
669            for (ispin, mo_smatw0s) in mo_smatw0ss.iter().enumerate() {
670                for (imo, mo_smat_w0) in mo_smatw0s.iter().enumerate() {
671                    mo_smatss[ispin][imo][(i, j)] =
672                        mo_orbitss[ispin][imo].norm_preserving_scalar_map(jinv)?(mo_smat_w0[jinv_i])
673                }
674            }
675
676            det_smat[(i, j)] = det_orbit.norm_preserving_scalar_map(jinv)?(det_smatw0[jinv_i]);
677        }
678    } else {
679        log::debug!(
680            "Cayley table not available or the use of Cayley table not requested. Overlap matrix will be constructed without group-closure speed-up."
681        );
682        let indexed_dets = det_orbit
683            .iter()
684            .map(|det_res| det_res.map_err(|err| err.to_string()))
685            .enumerate()
686            .collect::<Vec<_>>();
687        for det_pair in indexed_dets.iter().product_repeat(2) {
688            let (w, det_w_res) = &det_pair[0];
689            let (x, det_x_res) = &det_pair[1];
690            let det_w = det_w_res
691                .as_ref()
692                .map_err(|err| format_err!(err.to_owned()))
693                .with_context(|| "One of the determinants in the orbit is not available")?;
694            let det_x = det_x_res
695                .as_ref()
696                .map_err(|err| format_err!(err.to_owned()))
697                .with_context(|| "One of the determinants in the orbit is not available")?;
698
699            let wx_ov = izip!(
700                det_w.coefficients(),
701                det_w.occupations(),
702                det_x.coefficients(),
703                det_x.occupations(),
704            )
705            .enumerate()
706            .map(|(ispin, (cw, occw, cx, occx))| {
707                let nonzero_occ_w = occw.iter().positions(|&occ| occ > thresh).collect_vec();
708                let nonzero_occ_x = occx.iter().positions(|&occ| occ > thresh).collect_vec();
709
710                // let all_mo_wx_ov_mat = if det.complex_symmetric() {
711                //     cw.t().dot(metric).dot(cx)
712                // } else {
713                //     cw.t().mapv(|x| x.conj()).dot(metric).dot(cx)
714                // };
715                let all_mo_wx_ov_mat = if det.complex_symmetric() {
716                    match (det_w.complex_conjugated(), det_x.complex_conjugated()) {
717                        (false, false) => cw.t().dot(sao_h).dot(cx),
718                        (true, false) => cw.t().dot(sao).dot(cx),
719                        (false, true) => cx.t().dot(sao).dot(cw),
720                        (true, true) => cw.t().dot(&sao_h.t()).dot(cx),
721                    }
722                } else {
723                    match (det_w.complex_conjugated(), det_x.complex_conjugated()) {
724                        (false, false) => cw.t().mapv(|x| x.conj()).dot(sao).dot(cx),
725                        (true, false) => cw.t().mapv(|x| x.conj()).dot(sao_h).dot(cx),
726                        (false, true) => cx
727                            .t()
728                            .mapv(|x| x.conj())
729                            .dot(sao_h)
730                            .dot(cw)
731                            .mapv(|x| x.conj()),
732                        (true, true) => cw.t().mapv(|x| x.conj()).dot(&sao.t()).dot(cx),
733                    }
734                };
735
736                all_mo_wx_ov_mat
737                    .diag()
738                    .iter()
739                    .enumerate()
740                    .for_each(|(imo, mo_wx_ov)| {
741                        mo_smatss[ispin][imo][(*w, *x)] = *mo_wx_ov;
742                    });
743
744                let occ_mo_wx_ov_mat = all_mo_wx_ov_mat
745                    .select(Axis(0), &nonzero_occ_w)
746                    .select(Axis(1), &nonzero_occ_x);
747
748                occ_mo_wx_ov_mat
749                    .det()
750                    .expect("The determinant of the MO overlap matrix could not be found.")
751            })
752            .fold(T::one(), |acc, x| acc * x);
753
754            let implicit_factor = det.structure_constraint().implicit_factor()?;
755            let wx_ov = if implicit_factor > 1 {
756                let p_i32 = i32::try_from(implicit_factor)?;
757                ComplexFloat::powi(wx_ov, p_i32)
758            } else {
759                wx_ov
760            };
761            det_smat[(*w, *x)] = wx_ov;
762        }
763    }
764
765    if det_orbit.origin().complex_symmetric() {
766        det_orbit.set_smat(
767            (det_smat.clone() + det_smat.t().to_owned()).mapv(|x| x / (T::one() + T::one())),
768        );
769    } else {
770        det_orbit.set_smat(
771            (det_smat.clone() + det_smat.t().to_owned().mapv(|x| x.conj()))
772                .mapv(|x| x / (T::one() + T::one())),
773        );
774    };
775
776    mo_orbitss
777        .iter_mut()
778        .enumerate()
779        .for_each(|(ispin, mo_orbits)| {
780            mo_orbits
781                .iter_mut()
782                .enumerate()
783                .for_each(|(imo, mo_orbit)| {
784                    if mo_orbit.origin().complex_symmetric() {
785                        mo_orbit.set_smat(
786                            (mo_smatss[ispin][imo].clone() + mo_smatss[ispin][imo].t().to_owned())
787                                .mapv(|x| x / (T::one() + T::one())),
788                        )
789                    } else {
790                        mo_orbit.set_smat(
791                            (mo_smatss[ispin][imo].clone()
792                                + mo_smatss[ispin][imo].t().to_owned().mapv(|x| x.conj()))
793                            .mapv(|x| x / (T::one() + T::one())),
794                        )
795                    }
796                })
797        });
798
799    log::debug!("Constructing determinant and MO orbits in tandem... Done.");
800    Ok((det_orbit, mo_orbitss))
801}