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