Skip to main content

qsym2/drivers/projection/
slater_determinant.rs

1//! Driver for symmetry projection of electron densities.
2
3use std::collections::HashSet;
4use std::fmt;
5use std::hash::Hash;
6use std::str::FromStr;
7
8use anyhow::{bail, ensure, format_err};
9use derive_builder::Builder;
10use duplicate::duplicate_item;
11use indexmap::IndexMap;
12use ndarray::{Array1, Array2, Array4, Ix2, s};
13use ndarray_linalg::Lapack;
14use num::Complex;
15use num_complex::ComplexFloat;
16use num_traits::ToPrimitive;
17use num_traits::real::Real;
18use serde::{Deserialize, Serialize, de::DeserializeOwned};
19
20use crate::analysis::{EigenvalueComparisonMode, Overlap};
21use crate::angmom::spinor_rotation_3d::{SpinConstraint, SpinOrbitCoupled, StructureConstraint};
22use crate::chartab::CharacterTable;
23use crate::chartab::chartab_group::CharacterProperties;
24use crate::drivers::QSym2Driver;
25use crate::drivers::representation_analysis::{
26    CharacterTableDisplay, MagneticSymmetryAnalysisKind, fn_construct_unitary_group, log_bao,
27    log_cc_transversal,
28};
29use crate::drivers::symmetry_group_detection::SymmetryGroupDetectionResult;
30use crate::group::{GroupProperties, UnitaryRepresentedGroup};
31use crate::io::format::{
32    QSym2Output, log_subtitle, nice_bool, qsym2_output, write_subtitle, write_title,
33};
34use crate::projection::Projectable;
35use crate::symmetry::symmetry_group::{SymmetryGroupProperties, UnitaryRepresentedSymmetryGroup};
36use crate::symmetry::symmetry_symbols::MullikenIrrepSymbol;
37use crate::symmetry::symmetry_transformation::{SymmetryTransformable, SymmetryTransformationKind};
38use crate::target::determinant::SlaterDeterminant;
39use crate::target::determinant::determinant_analysis::SlaterDeterminantSymmetryOrbit;
40use crate::target::noci::basis::OrbitBasis;
41use crate::target::noci::multideterminant::MultiDeterminant;
42use crate::target::noci::multideterminants::MultiDeterminants;
43
44#[cfg(test)]
45#[path = "slater_determinant_tests.rs"]
46mod slater_determinant_tests;
47
48// ----------
49// Parameters
50// ----------
51
52const fn default_symbolic() -> Option<CharacterTableDisplay> {
53    Some(CharacterTableDisplay::Symbolic)
54}
55
56/// Structure containing control parameters for Slater determinant projection.
57#[derive(Clone, Builder, Debug, Serialize, Deserialize)]
58pub struct SlaterDeterminantProjectionParams {
59    /// Option indicating if the magnetic group is to be used for projection, and if so,
60    /// whether unitary representations or unitary-antiunitary corepresentations should be used.
61    #[builder(default = "None")]
62    #[serde(default)]
63    pub use_magnetic_group: Option<MagneticSymmetryAnalysisKind>,
64
65    /// Boolean indicating if the double group is to be used for symmetry projection.
66    #[builder(default = "false")]
67    #[serde(default)]
68    pub use_double_group: bool,
69
70    /// Option indicating if the character table of the group used for symmetry projection is to be
71    /// printed out.
72    #[builder(default = "Some(CharacterTableDisplay::Symbolic)")]
73    #[serde(default = "default_symbolic")]
74    pub write_character_table: Option<CharacterTableDisplay>,
75
76    /// The kind of symmetry transformation to be applied on the reference Slater determinant to
77    /// generate the orbit for symmetry projection.
78    #[builder(default = "SymmetryTransformationKind::Spatial")]
79    #[serde(default)]
80    pub symmetry_transformation_kind: SymmetryTransformationKind,
81
82    /// The finite order to which any infinite-order symmetry element is reduced, so that a finite
83    /// subgroup of an infinite group can be used for the symmetry projection.
84    #[builder(default = "None")]
85    #[serde(default)]
86    pub infinite_order_to_finite: Option<u32>,
87
88    /// The projection targets supplied symbolically.
89    #[builder(default = "None")]
90    #[serde(default)]
91    pub symbolic_projection_targets: Option<Vec<String>>,
92
93    /// The projection targets supplied numerically, where each value gives the index of the
94    /// projection subspace based on the group's character table.
95    #[builder(default = "None")]
96    #[serde(default)]
97    pub numeric_projection_targets: Option<Vec<usize>>,
98}
99
100impl SlaterDeterminantProjectionParams {
101    /// Returns a builder to construct a [`SlaterDeterminantProjectionParams`] structure.
102    pub fn builder() -> SlaterDeterminantProjectionParamsBuilder {
103        SlaterDeterminantProjectionParamsBuilder::default()
104    }
105}
106
107impl fmt::Display for SlaterDeterminantProjectionParams {
108    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
109        if let Some(symbolic_projection_targets) = self.symbolic_projection_targets.as_ref() {
110            writeln!(f, "Projection subspaces (symbolic):")?;
111            for projection_target in symbolic_projection_targets.iter() {
112                writeln!(f, "  {projection_target}")?;
113            }
114            writeln!(f)?;
115        }
116        if let Some(numeric_projection_targets) = self.numeric_projection_targets.as_ref() {
117            writeln!(f, "Projection subspaces (numeric):")?;
118            for projection_target in numeric_projection_targets.iter() {
119                writeln!(f, "  {projection_target}")?;
120            }
121            writeln!(f)?;
122        }
123        writeln!(
124            f,
125            "Use magnetic group for projection: {}",
126            match self.use_magnetic_group {
127                None => "no",
128                Some(MagneticSymmetryAnalysisKind::Representation) =>
129                    "yes, using unitary representations",
130                Some(MagneticSymmetryAnalysisKind::Corepresentation) =>
131                    "yes, using magnetic corepresentations",
132            }
133        )?;
134        writeln!(
135            f,
136            "Use double group for projection: {}",
137            nice_bool(self.use_double_group)
138        )?;
139        if let Some(finite_order) = self.infinite_order_to_finite {
140            writeln!(f, "Infinite order to finite: {finite_order}")?;
141        }
142        writeln!(
143            f,
144            "Symmetry transformation kind: {}",
145            self.symmetry_transformation_kind
146        )?;
147        writeln!(f)?;
148        writeln!(
149            f,
150            "Write character table: {}",
151            if let Some(chartab_display) = self.write_character_table.as_ref() {
152                format!("yes, {}", chartab_display.to_string().to_lowercase())
153            } else {
154                "no".to_string()
155            }
156        )?;
157
158        Ok(())
159    }
160}
161
162// ------
163// Result
164// ------
165
166/// Structure to contain Slater determinant projection results.
167#[derive(Clone, Builder)]
168pub struct SlaterDeterminantProjectionResult<'a, G, T, SC>
169where
170    G: SymmetryGroupProperties + Clone,
171    T: ComplexFloat + Lapack,
172    SC: StructureConstraint + Hash + Eq + fmt::Display + 'a,
173    SlaterDeterminant<'a, T, SC>: SymmetryTransformable,
174    for<'g> SlaterDeterminantSymmetryOrbit<'g, 'a, G, T, SC>:
175        Projectable<G, SlaterDeterminant<'a, T, SC>>,
176{
177    /// The control parameters used to obtain this set of Slater determinant projection results.
178    parameters: &'a SlaterDeterminantProjectionParams,
179
180    /// The Slater determinant being projected.
181    determinant: &'a SlaterDeterminant<'a, T, SC>,
182
183    /// The group used for the projection.
184    group: G,
185
186    /// The projected Slater determinants given as an indexmap containing the projected Slater
187    /// determinant indexed by the requested subspace labels.
188    #[allow(clippy::type_complexity)]
189    projected_determinants: IndexMap<
190        G::RowSymbol,
191        Result<MultiDeterminant<'a, T, OrbitBasis<G, SlaterDeterminant<'a, T, SC>>, SC>, String>,
192    >,
193
194    /// The optional atomic-orbital overlap matrix of the underlying basis set used to describe the
195    /// wavefunctions. This is either for a single component corresponding to the basis functions
196    /// specified by the basis angular order structure in the determinant in
197    /// [`Self::determinant`], or for *all* explicit components specified by the coefficients
198    /// in the determinant. This is not required for projection, and only used to compute the norm
199    /// of the resulting multi-determinants.
200    #[builder(default = "None")]
201    sao: Option<Array2<T>>,
202
203    /// The complex-symmetric atomic-orbital overlap matrix of the underlying basis set used to
204    /// describe the wavefunctions. This is either for a single component corresponding to the basis
205    /// functions specified by the basis angular order structure in the determinant, or for *all*
206    /// explicit components specified by the coefficients in the determinant. If none is provided,
207    /// this will be assumed to be the same as [`Self::sao`]. This is not required for projection,
208    /// and only used to compute the norm of the resulting multi-determinants.
209    #[builder(default = "None")]
210    sao_h: Option<Array2<T>>,
211}
212
213impl<'a, G, T, SC> SlaterDeterminantProjectionResult<'a, G, T, SC>
214where
215    G: SymmetryGroupProperties + Clone,
216    G::RowSymbol: Serialize + DeserializeOwned,
217    T: ComplexFloat + Lapack,
218    SC: StructureConstraint + Hash + Eq + Clone + fmt::Display,
219    SlaterDeterminant<'a, T, SC>: SymmetryTransformable,
220    for<'g> SlaterDeterminantSymmetryOrbit<'g, 'a, G, T, SC>:
221        Projectable<G, SlaterDeterminant<'a, T, SC>>,
222{
223    pub fn builder() -> SlaterDeterminantProjectionResultBuilder<'a, G, T, SC> {
224        SlaterDeterminantProjectionResultBuilder::default()
225    }
226
227    /// Returns the control parameters used to obtain this set of Slater determinant projection results.
228    pub fn parameters(&self) -> &SlaterDeterminantProjectionParams {
229        self.parameters
230    }
231
232    /// Returns the Slater determinant being projected.
233    pub fn determinant(&self) -> &SlaterDeterminant<'a, T, SC> {
234        self.determinant
235    }
236
237    /// Returns the projected Slater determinants.
238    #[allow(clippy::type_complexity)]
239    pub fn projected_determinants(
240        &self,
241    ) -> &IndexMap<
242        G::RowSymbol,
243        Result<MultiDeterminant<'a, T, OrbitBasis<G, SlaterDeterminant<'a, T, SC>>, SC>, String>,
244    > {
245        &self.projected_determinants
246    }
247}
248
249impl<'a, G, T, SC> fmt::Display for SlaterDeterminantProjectionResult<'a, G, T, SC>
250where
251    G: SymmetryGroupProperties + Clone,
252    T: ComplexFloat + Lapack,
253    SC: StructureConstraint + Hash + Eq + fmt::Display,
254    SlaterDeterminant<'a, T, SC>: SymmetryTransformable,
255    for<'g> SlaterDeterminantSymmetryOrbit<'g, 'a, G, T, SC>:
256        Projectable<G, SlaterDeterminant<'a, T, SC>>,
257    MultiDeterminant<'a, T, OrbitBasis<G, SlaterDeterminant<'a, T, SC>>, SC>: Overlap<T, Ix2>,
258{
259    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
260        write_subtitle(f, "Orbit-based symmetry projection summary")?;
261        writeln!(f)?;
262        writeln!(
263            f,
264            "> Group: {} ({})",
265            self.group
266                .finite_subgroup_name()
267                .map(|subgroup_name| format!("{} > {}", self.group.name(), subgroup_name))
268                .unwrap_or(self.group.name()),
269            self.group.group_type().to_string().to_lowercase()
270        )?;
271        writeln!(f)?;
272
273        let energy_precision = 7 + Real::ceil(ComplexFloat::abs(ComplexFloat::log10(
274            self.determinant.threshold(),
275        )))
276        .to_usize()
277        .expect("Unable to convert the linear independence threshold exponent to `usize`.");
278
279        let (rows, (sq_norms, energies)): (Vec<_>, (Vec<_>, Vec<_>)) = self
280            .projected_determinants
281            .iter()
282            .map(|(row, psd_res)| {
283                let sq_norm = psd_res
284                    .as_ref()
285                    .map_err(|err| err.to_string())
286                    .and_then(|psd| {
287                        psd.overlap(psd, self.sao.as_ref(), self.sao_h.as_ref())
288                            .map(|norm_sq| format!("{norm_sq:+.3e}"))
289                            .map_err(|err| err.to_string())
290                    })
291                    .unwrap_or_else(|err| err);
292                let energy = psd_res
293                    .as_ref()
294                    .map_err(|err| err.to_string())
295                    .and_then(|psd| {
296                        psd.energy()
297                            .map(|energy| format!("{energy:+.energy_precision$}"))
298                            .map_err(|err| err.to_string())
299                    })
300                    .unwrap_or_else(|err| err);
301                (row.to_string(), (sq_norm, energy))
302            })
303            .unzip();
304
305        let overlap_definition = self
306            .projected_determinants
307            .iter()
308            .next()
309            .and_then(|(_, psd_res)| psd_res.as_ref().ok().map(|psd| psd.overlap_definition()))
310            .unwrap_or("--".to_string());
311
312        writeln!(
313            f,
314            "  Squared norms are computed w.r.t. the following inner product:"
315        )?;
316        writeln!(f, "    {overlap_definition}")?;
317        writeln!(f)?;
318
319        let row_length = rows
320            .iter()
321            .map(|row| row.chars().count())
322            .max()
323            .unwrap_or(8)
324            .max(8);
325        let sq_norm_length = sq_norms
326            .iter()
327            .map(|sq_norm| sq_norm.chars().count())
328            .max()
329            .unwrap_or(12)
330            .max(12);
331        let energy_length = energies
332            .iter()
333            .map(|energy| energy.chars().count())
334            .max()
335            .unwrap_or(6)
336            .max(6);
337        let table_width = 6 + row_length + sq_norm_length + energy_length;
338        writeln!(f, "{}", "┈".repeat(table_width))?;
339        writeln!(
340            f,
341            " {:<row_length$}  {:<sq_norm_length$}  Energy",
342            "Subspace", "Squared norm"
343        )?;
344        writeln!(f, "{}", "┈".repeat(table_width))?;
345        for (row, (sq_norm, energy)) in rows.iter().zip(sq_norms.iter().zip(energies)) {
346            writeln!(
347                f,
348                " {:<row_length$}  {:<sq_norm_length$}  {:<}",
349                row, sq_norm, energy
350            )?;
351        }
352        writeln!(f, "{}", "┈".repeat(table_width))?;
353
354        writeln!(f)?;
355        Ok(())
356    }
357}
358
359impl<'a, G, T, SC> fmt::Debug for SlaterDeterminantProjectionResult<'a, G, T, SC>
360where
361    G: SymmetryGroupProperties + Clone,
362    T: ComplexFloat + Lapack,
363    SC: StructureConstraint + Hash + Eq + fmt::Display,
364    SlaterDeterminant<'a, T, SC>: SymmetryTransformable,
365    for<'g> SlaterDeterminantSymmetryOrbit<'g, 'a, G, T, SC>:
366        Projectable<G, SlaterDeterminant<'a, T, SC>>,
367    MultiDeterminant<'a, T, OrbitBasis<G, SlaterDeterminant<'a, T, SC>>, SC>: Overlap<T, Ix2>,
368{
369    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
370        writeln!(f, "{self}")
371    }
372}
373
374// ------
375// Driver
376// ------
377
378// ~~~~~~~~~~~~~~~~~
379// Struct definition
380// ~~~~~~~~~~~~~~~~~
381//
382/// Driver structure for performing projection on Slater determinants.
383#[derive(Clone, Builder)]
384#[builder(build_fn(validate = "Self::validate"))]
385pub struct SlaterDeterminantProjectionDriver<'a, G, T, SC, F>
386where
387    G: SymmetryGroupProperties + Clone,
388    G::RowSymbol: Serialize + DeserializeOwned,
389    T: ComplexFloat + Lapack,
390    SC: StructureConstraint + Hash + Eq + fmt::Display,
391    F: Fn(&Array2<T>) -> Result<(Array2<T>, Array2<T>), anyhow::Error> + Clone + Sync,
392    SlaterDeterminant<'a, T, SC>: SymmetryTransformable,
393    for<'g> SlaterDeterminantSymmetryOrbit<'g, 'a, G, T, SC>:
394        Projectable<G, SlaterDeterminant<'a, T, SC>>,
395{
396    /// The control parameters used to obtain this set of Slater determinant projection results.
397    parameters: &'a SlaterDeterminantProjectionParams,
398
399    /// The Slater determinant being projected.
400    determinant: &'a SlaterDeterminant<'a, T, SC>,
401
402    /// The result from symmetry-group detection on the underlying molecular structure of the
403    /// Slater determinant. Only the unitary symmetry group will be used for projection, since
404    /// magnetic-group projection is not yet formulated.
405    symmetry_group: &'a SymmetryGroupDetectionResult,
406
407    /// The result of the Slater determinant projection.
408    #[builder(setter(skip), default = "None")]
409    result: Option<SlaterDeterminantProjectionResult<'a, G, T, SC>>,
410
411    /// The optional atomic-orbital overlap matrix of the underlying basis set used to describe the
412    /// wavefunctions. This is either for a single component corresponding to the basis functions
413    /// specified by the basis angular order structure in the determinant in
414    /// [`Self::determinant`], or for *all* explicit components specified by the coefficients
415    /// in the determinant. This is not required for projection, and only used to compute the norm
416    /// of the resulting multi-determinants.
417    #[builder(default = "None")]
418    sao: Option<&'a Array2<T>>,
419
420    /// The complex-symmetric atomic-orbital overlap matrix of the underlying basis set used to
421    /// describe the wavefunctions. This is either for a single component corresponding to the basis
422    /// functions specified by the basis angular order structure in the determinant, or for *all*
423    /// explicit components specified by the coefficients in the determinant. If none is provided,
424    /// this will be assumed to be the same as [`Self::sao`]. This is not required for projection,
425    /// and only used to compute the norm of the resulting multi-determinants.
426    #[builder(default = "None")]
427    sao_h: Option<&'a Array2<T>>,
428
429    /// The optional nuclear repulsion energy. This is required for the computation of the energies
430    /// of the resulting multi-determinantal wavefunctions.
431    #[builder(default = "None")]
432    enuc: Option<T>,
433
434    /// The optional one-electron integral matrix. This is required for the computation of the
435    /// energies of the resulting multi-determinantal wavefunctions.
436    #[builder(default = "None")]
437    onee: Option<&'a Array2<T>>,
438
439    /// The optional two-electron integral tensor in physicists' ordering. Either this or `get_jk`
440    /// is required for the computation of the energies of the resulting multi-determinantal
441    /// wavefunctions.
442    #[builder(default = "None")]
443    twoe: Option<&'a Array4<T>>,
444
445    /// The optional function callable on a density matrix $`\mathbf{D}`$ to calculate the
446    /// corresponding $`\mathbf{J}`$ and $`\mathbf{K}`$ matrices by contracting with appropriate
447    /// two-electron integrals calculated on-the-fly. Either this or `twoe` is required for the
448    /// computation of the energies of the resulting multi-determinantal wavefunctions.
449    #[builder(default = "None")]
450    get_jk: Option<F>,
451
452    /// The optional threshold for identifying non-zero off-diagonal elements in Löwdin pairing.
453    /// This is required for the computation of the energies of the resulting multi-determinantal
454    /// wavefunctions.
455    #[builder(default = "None")]
456    thresh_offdiag: Option<<T as ComplexFloat>::Real>,
457
458    /// The optional threshold for identifying non-zero overlaps in Löwdin pairing. This is required
459    /// for the computation of the energies of the resulting multi-determinantal wavefunctions.
460    #[builder(default = "None")]
461    thresh_zeroov: Option<<T as ComplexFloat>::Real>,
462}
463
464impl<'a, G, T, SC, F> SlaterDeterminantProjectionDriverBuilder<'a, G, T, SC, F>
465where
466    G: SymmetryGroupProperties + Clone,
467    G::RowSymbol: Serialize + DeserializeOwned,
468    T: ComplexFloat + Lapack,
469    SC: StructureConstraint + Hash + Eq + fmt::Display,
470    F: Fn(&Array2<T>) -> Result<(Array2<T>, Array2<T>), anyhow::Error> + Clone + Sync,
471    SlaterDeterminant<'a, T, SC>: SymmetryTransformable,
472    for<'g> SlaterDeterminantSymmetryOrbit<'g, 'a, G, T, SC>:
473        Projectable<G, SlaterDeterminant<'a, T, SC>>,
474{
475    fn validate(&self) -> Result<(), String> {
476        let params = self
477            .parameters
478            .ok_or("No Slater determinant projection parameters found.".to_string())?;
479
480        let sym_res = self
481            .symmetry_group
482            .ok_or("No symmetry group information found.".to_string())?;
483
484        let _sd = self
485            .determinant
486            .as_ref()
487            .ok_or("No Slater determinant found.".to_string())?;
488
489        let sym = if params.use_magnetic_group.is_some() {
490            sym_res
491                .magnetic_symmetry
492                .as_ref()
493                .ok_or("Magnetic symmetry requested for symmetry projection, but no magnetic symmetry found.")?
494        } else {
495            &sym_res.unitary_symmetry
496        };
497
498        if sym.is_infinite() && params.infinite_order_to_finite.is_none() {
499            Err(format!(
500                "Projection cannot be performed using the entirety of the infinite group `{}`. \
501                    Consider setting the parameter `infinite_order_to_finite` to restrict to a finite subgroup instead.",
502                sym.group_name
503                    .as_ref()
504                    .expect("No symmetry group name found.")
505            ))
506        } else {
507            Ok(())
508        }
509    }
510}
511
512// ~~~~~~~~~~~~~~~~~~~~~~
513// Struct implementations
514// ~~~~~~~~~~~~~~~~~~~~~~
515
516// Generic for all symmetry groups G and density numeric type T
517// ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
518
519impl<'a, G, T, SC, F> SlaterDeterminantProjectionDriver<'a, G, T, SC, F>
520where
521    G: SymmetryGroupProperties + Clone,
522    G::RowSymbol: Serialize + DeserializeOwned,
523    T: ComplexFloat + Lapack,
524    SC: StructureConstraint + Hash + Eq + Clone + fmt::Display,
525    F: Fn(&Array2<T>) -> Result<(Array2<T>, Array2<T>), anyhow::Error> + Clone + Sync,
526    SlaterDeterminant<'a, T, SC>: SymmetryTransformable,
527    for<'g> SlaterDeterminantSymmetryOrbit<'g, 'a, G, T, SC>:
528        Projectable<G, SlaterDeterminant<'a, T, SC>>,
529{
530    /// Returns a builder to construct a [`SlaterDeterminantProjectionDriver`] structure.
531    pub fn builder() -> SlaterDeterminantProjectionDriverBuilder<'a, G, T, SC, F> {
532        SlaterDeterminantProjectionDriverBuilder::default()
533    }
534
535    /// Constructs the appropriate atomic-orbital overlap matrix based on the structure constraint
536    /// of the determinant and the specified overlap matrix.
537    #[allow(clippy::type_complexity)]
538    fn construct_sao(&self) -> Result<(Option<Array2<T>>, Option<Array2<T>>), anyhow::Error> {
539        if let Some(provided_sao) = self.sao {
540            let nbas_set = self
541                .determinant
542                .baos()
543                .iter()
544                .map(|bao| bao.n_funcs())
545                .collect::<HashSet<_>>();
546            let uniform_component = nbas_set.len() == 1;
547            let ncomps = self
548                .determinant
549                .structure_constraint()
550                .n_explicit_comps_per_coefficient_matrix();
551            let provided_dim = provided_sao.nrows();
552
553            if uniform_component {
554                let nbas = nbas_set.iter().next().ok_or_else(|| format_err!("Unable to extract the uniform number of basis functions per explicit component."))?;
555                if provided_dim == *nbas {
556                    let sao = {
557                        let mut sao_mut = Array2::zeros((ncomps * nbas, ncomps * nbas));
558                        (0..ncomps).for_each(|icomp| {
559                            let start = icomp * nbas;
560                            let end = (icomp + 1) * nbas;
561                            sao_mut
562                                .slice_mut(s![start..end, start..end])
563                                .assign(provided_sao);
564                        });
565                        sao_mut
566                    };
567
568                    let sao_h_opt = self.sao_h.map(|sao_h| {
569                        let mut sao_h_mut = Array2::zeros((ncomps * nbas, ncomps * nbas));
570                        (0..ncomps).for_each(|icomp| {
571                            let start = icomp * nbas;
572                            let end = (icomp + 1) * nbas;
573                            sao_h_mut
574                                .slice_mut(s![start..end, start..end])
575                                .assign(sao_h);
576                        });
577                        sao_h_mut
578                    });
579
580                    Ok((Some(sao), sao_h_opt))
581                } else {
582                    ensure!(provided_dim == nbas * ncomps);
583                    Ok((self.sao.cloned(), self.sao_h.cloned()))
584                }
585            } else {
586                let nbas_tot = self
587                    .determinant
588                    .baos()
589                    .iter()
590                    .map(|bao| bao.n_funcs())
591                    .sum::<usize>();
592                ensure!(provided_dim == nbas_tot);
593                Ok((self.sao.cloned(), self.sao_h.cloned()))
594            }
595        } else {
596            Ok((None, None))
597        }
598    }
599}
600
601// Specific for unitary-represented symmetry groups, but generic for density numeric type T
602// ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
603
604impl<'a, T, SC, F> SlaterDeterminantProjectionDriver<'a, UnitaryRepresentedSymmetryGroup, T, SC, F>
605where
606    T: ComplexFloat + Lapack,
607    SC: StructureConstraint + Hash + Eq + Clone + fmt::Display,
608    F: Fn(&Array2<T>) -> Result<(Array2<T>, Array2<T>), anyhow::Error> + Clone + Sync,
609    SlaterDeterminant<'a, T, SC>: SymmetryTransformable,
610    for<'g> SlaterDeterminantSymmetryOrbit<'g, 'a, UnitaryRepresentedSymmetryGroup, T, SC>:
611        Projectable<UnitaryRepresentedSymmetryGroup, SlaterDeterminant<'a, T, SC>>,
612{
613    fn_construct_unitary_group!(
614        /// Constructs the unitary-represented group (which itself can be unitary or magnetic) ready
615        /// for Slater determinant projection.
616        construct_unitary_group
617    );
618}
619
620// Specific for unitary-represented symmetry groups and density numeric types f64 and C128
621// '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
622
623#[duplicate_item(
624    duplicate!{
625        [
626            dtype_nested sctype_nested;
627            [ f64 ] [ SpinConstraint ];
628            [ Complex<f64> ] [ SpinConstraint ];
629            [ Complex<f64> ] [ SpinOrbitCoupled ];
630        ]
631        [
632            gtype_ [ UnitaryRepresentedSymmetryGroup ]
633            dtype_ [ dtype_nested ]
634            sctype_ [ sctype_nested ]
635            doc_sub_ [ "Performs projection using a unitary-represented group and stores the result." ]
636            projection_fn_ [ projection_representation ]
637            construct_group_ [ self.construct_unitary_group()? ]
638        ]
639    }
640)]
641impl<'a, F> SlaterDeterminantProjectionDriver<'a, gtype_, dtype_, sctype_, F>
642where
643    F: Fn(&Array2<dtype_>) -> Result<(Array2<dtype_>, Array2<dtype_>), anyhow::Error>
644        + Clone
645        + Sync,
646{
647    #[doc = doc_sub_]
648    fn projection_fn_(&mut self) -> Result<(), anyhow::Error> {
649        let params = self.parameters;
650        let group = construct_group_;
651        let original_sd = self.determinant;
652        log_cc_transversal(&group);
653        let baos = original_sd.baos();
654        for (bao_i, bao) in baos.iter().enumerate() {
655            log_bao(bao, Some(bao_i));
656        }
657
658        let all_rows = group.character_table().get_all_rows();
659        let rows = params
660            .symbolic_projection_targets
661            .as_ref()
662            .unwrap_or(&vec![])
663            .iter()
664            .map(|row_str| MullikenIrrepSymbol::from_str(row_str).map_err(|err| format_err!(err)))
665            .chain(
666                params
667                    .numeric_projection_targets
668                    .as_ref()
669                    .unwrap_or(&vec![])
670                    .iter()
671                    .map(|row_index| {
672                        all_rows.get_index(*row_index).cloned().ok_or_else(|| {
673                            format_err!(
674                                "Unable to retrieve the subspace label with index {row_index}."
675                            )
676                        })
677                    }),
678            )
679            .collect::<Result<Vec<_>, _>>()?;
680
681        let mut projected_determinants = SlaterDeterminantSymmetryOrbit::builder()
682            .group(&group)
683            .origin(original_sd)
684            .symmetry_transformation_kind(params.symmetry_transformation_kind.clone())
685            .integrality_threshold(1e-14)
686            .linear_independence_threshold(1e-14)
687            .orbit_overlap_symmetry_threshold(1e-14)
688            .eigenvalue_comparison_mode(EigenvalueComparisonMode::Modulus)
689            .build()
690            .map_err(|err| format_err!(err))
691            .and_then(|sd_orbit| {
692                rows.iter()
693                    .map(move |row| {
694                        sd_orbit.project_onto(row).map(move |temp_projected_sd| {
695                            let basis = temp_projected_sd.basis().clone();
696                            (
697                                row.clone(),
698                                MultiDeterminant::builder()
699                                    .basis(basis)
700                                    .complex_conjugated(temp_projected_sd.complex_conjugated())
701                                    .coefficients(temp_projected_sd.coefficients().clone())
702                                    .threshold(temp_projected_sd.threshold())
703                                    .build()
704                                    .map_err(|err| err.to_string()),
705                            )
706                        })
707                    })
708                    .collect::<Result<IndexMap<_, _>, _>>()
709            })?;
710
711        let multidets_vec = projected_determinants
712            .values()
713            .cloned()
714            .collect::<Result<Vec<_>, _>>()
715            .map_err(|err| format_err!(err))?;
716        let multidets = MultiDeterminants::from_multideterminant_vec(
717            &multidets_vec.iter().collect::<Vec<_>>(),
718        )?;
719
720        let energies = match (
721            self.enuc,
722            self.onee,
723            self.thresh_offdiag,
724            self.thresh_zeroov,
725            self.sao,
726        ) {
727            (Some(enuc), Some(onee), Some(thresh_offdiag), Some(thresh_zeroov), Some(sao)) => {
728                multidets.compute_energies(
729                    sao.into(),
730                    enuc,
731                    onee.into(),
732                    self.twoe.map(|twoe| twoe.into()),
733                    self.get_jk.as_ref(),
734                    thresh_offdiag,
735                    thresh_zeroov,
736                    true,
737                )?
738            }
739            _ => (0..multidets.n_states())
740                .map(|_| Err("No multi-determinantal energies calculated.".to_string()))
741                .collect::<Array1<_>>(),
742        };
743        projected_determinants
744            .values_mut()
745            .zip(energies)
746            .for_each(|(det_res, energy)| {
747                let _ = det_res.as_mut().map(|det| det.set_energy(energy));
748            });
749
750        let (sao_opt, sao_h_opt) = self.construct_sao()?;
751        let result = SlaterDeterminantProjectionResult::builder()
752            .parameters(params)
753            .determinant(self.determinant)
754            .group(group.clone())
755            .projected_determinants(projected_determinants)
756            .sao(sao_opt)
757            .sao_h(sao_h_opt)
758            .build()?;
759        self.result = Some(result);
760
761        Ok(())
762    }
763}
764
765// ~~~~~~~~~~~~~~~~~~~~~
766// Trait implementations
767// ~~~~~~~~~~~~~~~~~~~~~
768
769// Generic for all symmetry groups G and density numeric type T
770// ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
771
772impl<'a, G, T, SC, F> fmt::Display for SlaterDeterminantProjectionDriver<'a, G, T, SC, F>
773where
774    G: SymmetryGroupProperties + Clone,
775    G::RowSymbol: Serialize + DeserializeOwned,
776    T: ComplexFloat + Lapack,
777    SC: StructureConstraint + Hash + Eq + fmt::Display,
778    F: Fn(&Array2<T>) -> Result<(Array2<T>, Array2<T>), anyhow::Error> + Clone + Sync,
779    SlaterDeterminant<'a, T, SC>: SymmetryTransformable,
780    for<'g> SlaterDeterminantSymmetryOrbit<'g, 'a, G, T, SC>:
781        Projectable<G, SlaterDeterminant<'a, T, SC>>,
782{
783    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
784        write_title(f, "Slater Determinant Symmetry Projection")?;
785        writeln!(f)?;
786        writeln!(f, "{}", self.parameters)?;
787        Ok(())
788    }
789}
790
791impl<'a, G, T, SC, F> fmt::Debug for SlaterDeterminantProjectionDriver<'a, G, T, SC, F>
792where
793    G: SymmetryGroupProperties + Clone,
794    G::RowSymbol: Serialize + DeserializeOwned,
795    T: ComplexFloat + Lapack,
796    SC: StructureConstraint + Hash + Eq + fmt::Display,
797    F: Fn(&Array2<T>) -> Result<(Array2<T>, Array2<T>), anyhow::Error> + Clone + Sync,
798    SlaterDeterminant<'a, T, SC>: SymmetryTransformable,
799    for<'g> SlaterDeterminantSymmetryOrbit<'g, 'a, G, T, SC>:
800        Projectable<G, SlaterDeterminant<'a, T, SC>>,
801{
802    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
803        writeln!(f, "{self}")
804    }
805}
806
807// Specific for unitary-represented symmetry groups and density numeric types f64 and C128
808// '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
809
810#[duplicate_item(
811    duplicate!{
812        [
813            dtype_nested sctype_nested;
814            [ f64 ] [ SpinConstraint ];
815            [ Complex<f64> ] [ SpinConstraint ];
816            [ Complex<f64> ] [ SpinOrbitCoupled ];
817        ]
818        [
819            gtype_ [ UnitaryRepresentedSymmetryGroup ]
820            dtype_ [ dtype_nested ]
821            sctype_ [ sctype_nested ]
822            doc_sub_ [ "Performs projection using a unitary-represented group and stores the result." ]
823            projection_fn_ [ projection_representation ]
824            construct_group_ [ self.construct_unitary_group()? ]
825        ]
826    }
827)]
828impl<'a, F> QSym2Driver for SlaterDeterminantProjectionDriver<'a, gtype_, dtype_, sctype_, F>
829where
830    F: Fn(&Array2<dtype_>) -> Result<(Array2<dtype_>, Array2<dtype_>), anyhow::Error>
831        + Clone
832        + Sync,
833{
834    type Params = SlaterDeterminantProjectionParams;
835
836    type Outcome = SlaterDeterminantProjectionResult<'a, gtype_, dtype_, sctype_>;
837
838    fn result(&self) -> Result<&Self::Outcome, anyhow::Error> {
839        self.result
840            .as_ref()
841            .ok_or_else(|| format_err!("No Slater determinant projection results found."))
842    }
843
844    fn run(&mut self) -> Result<(), anyhow::Error> {
845        self.log_output_display();
846        self.projection_fn_()?;
847        self.result()?.log_output_display();
848        Ok(())
849    }
850}