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 projection: {}",
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    #[allow(clippy::type_complexity)]
185    projected_determinants: IndexMap<
186        G::RowSymbol,
187        Result<MultiDeterminant<'a, T, EagerBasis<SlaterDeterminant<'a, T, SC>>, SC>, String>,
188    >,
189
190    /// The optional atomic-orbital overlap matrix of the underlying basis set used to describe the
191    /// wavefunctions. This is either for a single component corresponding to the basis functions
192    /// specified by the basis angular order structure in the determinant in
193    /// [`Self::determinant`], or for *all* explicit components specified by the coefficients
194    /// in the determinant. This is not required for projection, and only used to compute the norm
195    /// of the resulting multi-determinants.
196    #[builder(default = "None")]
197    sao: Option<Array2<T>>,
198
199    /// The complex-symmetric atomic-orbital overlap matrix of the underlying basis set used to
200    /// describe the wavefunctions. This is either for a single component corresponding to the basis
201    /// functions specified by the basis angular order structure in the determinant, or for *all*
202    /// explicit components specified by the coefficients in the determinant. If none is provided,
203    /// this will be assumed to be the same as [`Self::sao`]. This is not required for projection,
204    /// and only used to compute the norm of the resulting multi-determinants.
205    #[builder(default = "None")]
206    sao_h: Option<Array2<T>>,
207}
208
209impl<'a, G, T, SC> SlaterDeterminantProjectionResult<'a, G, T, SC>
210where
211    G: SymmetryGroupProperties + Clone,
212    G::RowSymbol: Serialize + DeserializeOwned,
213    T: ComplexFloat + Lapack,
214    SC: StructureConstraint + Hash + Eq + Clone + fmt::Display,
215    SlaterDeterminant<'a, T, SC>: SymmetryTransformable,
216    SlaterDeterminantSymmetryOrbit<'a, G, T, SC>: Projectable<G, SlaterDeterminant<'a, T, SC>>,
217{
218    pub fn builder() -> SlaterDeterminantProjectionResultBuilder<'a, G, T, SC> {
219        SlaterDeterminantProjectionResultBuilder::default()
220    }
221
222    /// Returns the projected densities.
223    #[allow(clippy::type_complexity)]
224    pub fn projected_determinants(
225        &self,
226    ) -> &IndexMap<
227        G::RowSymbol,
228        Result<MultiDeterminant<'a, T, EagerBasis<SlaterDeterminant<'a, T, SC>>, SC>, String>,
229    > {
230        &self.projected_determinants
231    }
232}
233
234impl<'a, G, T, SC> fmt::Display for SlaterDeterminantProjectionResult<'a, G, T, SC>
235where
236    G: SymmetryGroupProperties + Clone,
237    T: ComplexFloat + Lapack,
238    SC: StructureConstraint + Hash + Eq + fmt::Display,
239    SlaterDeterminant<'a, T, SC>: SymmetryTransformable,
240    SlaterDeterminantSymmetryOrbit<'a, G, T, SC>: Projectable<G, SlaterDeterminant<'a, T, SC>>,
241    MultiDeterminant<'a, T, EagerBasis<SlaterDeterminant<'a, T, SC>>, SC>: Overlap<T, Ix2>,
242{
243    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
244        write_subtitle(f, "Orbit-based symmetry projection summary")?;
245        writeln!(f)?;
246        writeln!(
247            f,
248            "> Group: {} ({})",
249            self.group
250                .finite_subgroup_name()
251                .map(|subgroup_name| format!("{} > {}", self.group.name(), subgroup_name))
252                .unwrap_or(self.group.name()),
253            self.group.group_type().to_string().to_lowercase()
254        )?;
255        writeln!(f)?;
256
257        let (rows, sq_norms): (Vec<_>, Vec<_>) = self
258            .projected_determinants
259            .iter()
260            .map(|(row, psd_res)| {
261                let sq_norm = psd_res
262                    .as_ref()
263                    .map_err(|err| err.to_string())
264                    .and_then(|psd| {
265                        psd.overlap(psd, self.sao.as_ref(), self.sao_h.as_ref())
266                            .map(|norm_sq| format!("{norm_sq:+.3e}"))
267                            .map_err(|err| err.to_string())
268                    })
269                    .unwrap_or_else(|err| err);
270                (row.to_string(), sq_norm)
271            })
272            .unzip();
273
274        let overlap_definition = self
275            .projected_determinants
276            .iter()
277            .next()
278            .and_then(|(_, psd_res)| psd_res.as_ref().ok().map(|psd| psd.overlap_definition()))
279            .unwrap_or("--".to_string());
280
281        writeln!(
282            f,
283            "  Squared norms are computed w.r.t. the following inner product:"
284        )?;
285        writeln!(f, "    {overlap_definition}")?;
286        writeln!(f)?;
287
288        let row_length = rows
289            .iter()
290            .map(|row| row.chars().count())
291            .max()
292            .unwrap_or(8)
293            .max(8);
294        let sq_norm_length = sq_norms
295            .iter()
296            .map(|sq_norm| sq_norm.chars().count())
297            .max()
298            .unwrap_or(12)
299            .max(12);
300        let table_width = 4 + row_length + sq_norm_length;
301        writeln!(f, "{}", "┈".repeat(table_width))?;
302        writeln!(f, " {:<row_length$}  Squared norm", "Subspace",)?;
303        writeln!(f, "{}", "┈".repeat(table_width))?;
304        for (row, sq_norm) in rows.iter().zip(sq_norms) {
305            writeln!(f, " {:<row_length$}  {:<}", row, sq_norm)?;
306        }
307        writeln!(f, "{}", "┈".repeat(table_width))?;
308
309        writeln!(f)?;
310        Ok(())
311    }
312}
313
314impl<'a, G, T, SC> fmt::Debug for SlaterDeterminantProjectionResult<'a, G, T, SC>
315where
316    G: SymmetryGroupProperties + Clone,
317    T: ComplexFloat + Lapack,
318    SC: StructureConstraint + Hash + Eq + fmt::Display,
319    SlaterDeterminant<'a, T, SC>: SymmetryTransformable,
320    SlaterDeterminantSymmetryOrbit<'a, G, T, SC>: Projectable<G, SlaterDeterminant<'a, T, SC>>,
321    MultiDeterminant<'a, T, EagerBasis<SlaterDeterminant<'a, T, SC>>, SC>: Overlap<T, Ix2>,
322{
323    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
324        writeln!(f, "{self}")
325    }
326}
327
328// ------
329// Driver
330// ------
331
332// ~~~~~~~~~~~~~~~~~
333// Struct definition
334// ~~~~~~~~~~~~~~~~~
335//
336/// Driver structure for performing projection on Slater determinants.
337#[derive(Clone, Builder)]
338#[builder(build_fn(validate = "Self::validate"))]
339pub struct SlaterDeterminantProjectionDriver<'a, G, T, SC>
340where
341    G: SymmetryGroupProperties + Clone,
342    G::RowSymbol: Serialize + DeserializeOwned,
343    T: ComplexFloat + Lapack,
344    SC: StructureConstraint + Hash + Eq + fmt::Display,
345    SlaterDeterminant<'a, T, SC>: SymmetryTransformable,
346    SlaterDeterminantSymmetryOrbit<'a, G, T, SC>: Projectable<G, SlaterDeterminant<'a, T, SC>>,
347{
348    /// The control parameters used to obtain this set of Slater determinant projection results.
349    parameters: &'a SlaterDeterminantProjectionParams,
350
351    /// The Slater determinant being projected.
352    determinant: &'a SlaterDeterminant<'a, T, SC>,
353
354    /// The result from symmetry-group detection on the underlying molecular structure of the
355    /// Slater determinant. Only the unitary symmetry group will be used for projection, since
356    /// magnetic-group projection is not yet formulated.
357    symmetry_group: &'a SymmetryGroupDetectionResult,
358
359    /// The result of the Slater determinant projection.
360    #[builder(setter(skip), default = "None")]
361    result: Option<SlaterDeterminantProjectionResult<'a, G, T, SC>>,
362
363    /// The optional atomic-orbital overlap matrix of the underlying basis set used to describe the
364    /// wavefunctions. This is either for a single component corresponding to the basis functions
365    /// specified by the basis angular order structure in the determinant in
366    /// [`Self::determinant`], or for *all* explicit components specified by the coefficients
367    /// in the determinant. This is not required for projection, and only used to compute the norm
368    /// of the resulting multi-determinants.
369    #[builder(default = "None")]
370    sao: Option<&'a Array2<T>>,
371
372    /// The complex-symmetric atomic-orbital overlap matrix of the underlying basis set used to
373    /// describe the wavefunctions. This is either for a single component corresponding to the basis
374    /// functions specified by the basis angular order structure in the determinant, or for *all*
375    /// explicit components specified by the coefficients in the determinant. If none is provided,
376    /// this will be assumed to be the same as [`Self::sao`]. This is not required for projection,
377    /// and only used to compute the norm of the resulting multi-determinants.
378    #[builder(default = "None")]
379    sao_h: Option<&'a Array2<T>>,
380}
381
382impl<'a, G, T, SC> SlaterDeterminantProjectionDriverBuilder<'a, G, T, SC>
383where
384    G: SymmetryGroupProperties + Clone,
385    G::RowSymbol: Serialize + DeserializeOwned,
386    T: ComplexFloat + Lapack,
387    SC: StructureConstraint + Hash + Eq + fmt::Display,
388    SlaterDeterminant<'a, T, SC>: SymmetryTransformable,
389    SlaterDeterminantSymmetryOrbit<'a, G, T, SC>: Projectable<G, SlaterDeterminant<'a, T, SC>>,
390{
391    fn validate(&self) -> Result<(), String> {
392        let params = self
393            .parameters
394            .ok_or("No Slater determinant projection parameters found.".to_string())?;
395
396        let sym_res = self
397            .symmetry_group
398            .ok_or("No symmetry group information found.".to_string())?;
399
400        let _sd = self
401            .determinant
402            .as_ref()
403            .ok_or("No Slater determinant found.".to_string())?;
404
405        let sym = if params.use_magnetic_group.is_some() {
406            sym_res
407                .magnetic_symmetry
408                .as_ref()
409                .ok_or("Magnetic symmetry requested for symmetry projection, but no magnetic symmetry found.")?
410        } else {
411            &sym_res.unitary_symmetry
412        };
413
414        if sym.is_infinite() && params.infinite_order_to_finite.is_none() {
415            Err(format!(
416                "Projection cannot be performed using the entirety of the infinite group `{}`. \
417                    Consider setting the parameter `infinite_order_to_finite` to restrict to a finite subgroup instead.",
418                sym.group_name
419                    .as_ref()
420                    .expect("No symmetry group name found.")
421            ))
422        } else {
423            Ok(())
424        }
425    }
426}
427
428// ~~~~~~~~~~~~~~~~~~~~~~
429// Struct implementations
430// ~~~~~~~~~~~~~~~~~~~~~~
431
432// Generic for all symmetry groups G and density numeric type T
433// ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
434
435impl<'a, G, T, SC> SlaterDeterminantProjectionDriver<'a, G, T, SC>
436where
437    G: SymmetryGroupProperties + Clone,
438    G::RowSymbol: Serialize + DeserializeOwned,
439    T: ComplexFloat + Lapack,
440    SC: StructureConstraint + Hash + Eq + Clone + fmt::Display,
441    SlaterDeterminant<'a, T, SC>: SymmetryTransformable,
442    SlaterDeterminantSymmetryOrbit<'a, G, T, SC>: Projectable<G, SlaterDeterminant<'a, T, SC>>,
443{
444    /// Returns a builder to construct a [`SlaterDeterminantProjectionDriver`] structure.
445    pub fn builder() -> SlaterDeterminantProjectionDriverBuilder<'a, G, T, SC> {
446        SlaterDeterminantProjectionDriverBuilder::default()
447    }
448
449    /// Constructs the appropriate atomic-orbital overlap matrix based on the structure constraint
450    /// of the determinant and the specified overlap matrix.
451    #[allow(clippy::type_complexity)]
452    fn construct_sao(&self) -> Result<(Option<Array2<T>>, Option<Array2<T>>), anyhow::Error> {
453        if let Some(provided_sao) = self.sao {
454            let nbas_set = self
455                .determinant
456                .baos()
457                .iter()
458                .map(|bao| bao.n_funcs())
459                .collect::<HashSet<_>>();
460            let uniform_component = nbas_set.len() == 1;
461            let ncomps = self
462                .determinant
463                .structure_constraint()
464                .n_explicit_comps_per_coefficient_matrix();
465            let provided_dim = provided_sao.nrows();
466
467            if uniform_component {
468                let nbas = nbas_set.iter().next().ok_or_else(|| format_err!("Unable to extract the uniform number of basis functions per explicit component."))?;
469                if provided_dim == *nbas {
470                    let sao = {
471                        let mut sao_mut = Array2::zeros((ncomps * nbas, ncomps * nbas));
472                        (0..ncomps).for_each(|icomp| {
473                            let start = icomp * nbas;
474                            let end = (icomp + 1) * nbas;
475                            sao_mut
476                                .slice_mut(s![start..end, start..end])
477                                .assign(provided_sao);
478                        });
479                        sao_mut
480                    };
481
482                    let sao_h_opt = self.sao_h.map(|sao_h| {
483                        let mut sao_h_mut = Array2::zeros((ncomps * nbas, ncomps * nbas));
484                        (0..ncomps).for_each(|icomp| {
485                            let start = icomp * nbas;
486                            let end = (icomp + 1) * nbas;
487                            sao_h_mut
488                                .slice_mut(s![start..end, start..end])
489                                .assign(sao_h);
490                        });
491                        sao_h_mut
492                    });
493
494                    Ok((Some(sao), sao_h_opt))
495                } else {
496                    ensure!(provided_dim == nbas * ncomps);
497                    Ok((self.sao.cloned(), self.sao_h.cloned()))
498                }
499            } else {
500                let nbas_tot = self
501                    .determinant
502                    .baos()
503                    .iter()
504                    .map(|bao| bao.n_funcs())
505                    .sum::<usize>();
506                ensure!(provided_dim == nbas_tot);
507                Ok((self.sao.cloned(), self.sao_h.cloned()))
508            }
509        } else {
510            Ok((None, None))
511        }
512    }
513}
514
515// Specific for unitary-represented symmetry groups, but generic for density numeric type T
516// ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
517
518impl<'a, T, SC> SlaterDeterminantProjectionDriver<'a, UnitaryRepresentedSymmetryGroup, T, SC>
519where
520    T: ComplexFloat + Lapack,
521    SC: StructureConstraint + Hash + Eq + Clone + fmt::Display,
522    SlaterDeterminant<'a, T, SC>: SymmetryTransformable,
523    SlaterDeterminantSymmetryOrbit<'a, UnitaryRepresentedSymmetryGroup, T, SC>:
524        Projectable<UnitaryRepresentedSymmetryGroup, SlaterDeterminant<'a, T, SC>>,
525{
526    fn_construct_unitary_group!(
527        /// Constructs the unitary-represented group (which itself can be unitary or magnetic) ready
528        /// for Slater determinant projection.
529        construct_unitary_group
530    );
531}
532
533// Specific for unitary-represented symmetry groups and density numeric types f64 and C128
534// '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
535
536#[duplicate_item(
537    duplicate!{
538        [
539            dtype_nested sctype_nested;
540            [ f64 ] [ SpinConstraint ];
541            [ Complex<f64> ] [ SpinConstraint ];
542            [ Complex<f64> ] [ SpinOrbitCoupled ];
543        ]
544        [
545            gtype_ [ UnitaryRepresentedSymmetryGroup ]
546            dtype_ [ dtype_nested ]
547            sctype_ [ sctype_nested ]
548            doc_sub_ [ "Performs projection using a unitary-represented group and stores the result." ]
549            projection_fn_ [ projection_representation ]
550            construct_group_ [ self.construct_unitary_group()? ]
551        ]
552    }
553)]
554impl<'a> SlaterDeterminantProjectionDriver<'a, gtype_, dtype_, sctype_> {
555    #[doc = doc_sub_]
556    fn projection_fn_(&mut self) -> Result<(), anyhow::Error> {
557        let params = self.parameters;
558        let group = construct_group_;
559        let original_sd = self.determinant;
560        log_cc_transversal(&group);
561        let baos = original_sd.baos();
562        for (bao_i, bao) in baos.iter().enumerate() {
563            log_bao(bao, Some(bao_i));
564        }
565
566        let all_rows = group.character_table().get_all_rows();
567        let rows = params
568            .symbolic_projection_targets
569            .as_ref()
570            .unwrap_or(&vec![])
571            .iter()
572            .map(|row_str| MullikenIrrepSymbol::from_str(row_str).map_err(|err| format_err!(err)))
573            .chain(
574                params
575                    .numeric_projection_targets
576                    .as_ref()
577                    .unwrap_or(&vec![])
578                    .iter()
579                    .map(|row_index| {
580                        all_rows.get_index(*row_index).cloned().ok_or_else(|| {
581                            format_err!(
582                                "Unable to retrieve the subspace label with index {row_index}."
583                            )
584                        })
585                    }),
586            )
587            .collect::<Result<Vec<_>, _>>()?;
588
589        let projected_determinants = SlaterDeterminantSymmetryOrbit::builder()
590            .group(&group)
591            .origin(original_sd)
592            .symmetry_transformation_kind(params.symmetry_transformation_kind.clone())
593            .integrality_threshold(1e-14)
594            .linear_independence_threshold(1e-14)
595            .eigenvalue_comparison_mode(EigenvalueComparisonMode::Modulus)
596            .build()
597            .map_err(|err| format_err!(err))
598            .and_then(|sd_orbit| {
599                rows.iter()
600                    .map(|row| {
601                        sd_orbit.project_onto(row).and_then(|temp_projected_sd| {
602                            let basis_dets = temp_projected_sd
603                                .basis()
604                                .iter()
605                                .map(|det_res| {
606                                    det_res.and_then(|det| {
607                                        SlaterDeterminant::builder()
608                                            .structure_constraint(
609                                                det.structure_constraint().clone(),
610                                            )
611                                            .baos(baos.clone())
612                                            .complex_symmetric(det.complex_symmetric())
613                                            .complex_conjugated(det.complex_conjugated())
614                                            .mol(original_sd.mol())
615                                            .coefficients(&det.coefficients().clone())
616                                            .occupations(&det.occupations().clone())
617                                            .mo_energies(det.mo_energies().cloned())
618                                            .energy(
619                                                det.energy()
620                                                    .cloned()
621                                                    .map_err(|err| err.to_string()),
622                                            )
623                                            .threshold(original_sd.threshold())
624                                            .build()
625                                            .map_err(|err| format_err!(err))
626                                    })
627                                })
628                                .collect::<Result<Vec<_>, _>>()?;
629                            let basis = EagerBasis::builder()
630                                .elements(basis_dets)
631                                .build()
632                                .map_err(|err| format_err!(err))?;
633                            Ok((
634                                row.clone(),
635                                MultiDeterminant::builder()
636                                    .basis(basis)
637                                    .complex_conjugated(temp_projected_sd.complex_conjugated())
638                                    .coefficients(temp_projected_sd.coefficients().clone())
639                                    .threshold(temp_projected_sd.threshold())
640                                    .build()
641                                    .map_err(|err| err.to_string()),
642                            ))
643                        })
644                    })
645                    .collect::<Result<IndexMap<_, _>, _>>()
646            })?;
647
648        let (sao_opt, sao_h_opt) = self.construct_sao()?;
649        let result = SlaterDeterminantProjectionResult::builder()
650            .parameters(params)
651            .determinant(self.determinant)
652            .group(group.clone())
653            .projected_determinants(projected_determinants)
654            .sao(sao_opt)
655            .sao_h(sao_h_opt)
656            .build()?;
657        self.result = Some(result);
658
659        Ok(())
660    }
661}
662
663// ~~~~~~~~~~~~~~~~~~~~~
664// Trait implementations
665// ~~~~~~~~~~~~~~~~~~~~~
666
667// Generic for all symmetry groups G and density numeric type T
668// ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
669
670impl<'a, G, T, SC> fmt::Display for SlaterDeterminantProjectionDriver<'a, G, T, SC>
671where
672    G: SymmetryGroupProperties + Clone,
673    G::RowSymbol: Serialize + DeserializeOwned,
674    T: ComplexFloat + Lapack,
675    SC: StructureConstraint + Hash + Eq + fmt::Display,
676    SlaterDeterminant<'a, T, SC>: SymmetryTransformable,
677    SlaterDeterminantSymmetryOrbit<'a, G, T, SC>: Projectable<G, SlaterDeterminant<'a, T, SC>>,
678{
679    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
680        write_title(f, "Slater Determinant Symmetry Projection")?;
681        writeln!(f)?;
682        writeln!(f, "{}", self.parameters)?;
683        Ok(())
684    }
685}
686
687impl<'a, G, T, SC> fmt::Debug for SlaterDeterminantProjectionDriver<'a, G, T, SC>
688where
689    G: SymmetryGroupProperties + Clone,
690    G::RowSymbol: Serialize + DeserializeOwned,
691    T: ComplexFloat + Lapack,
692    SC: StructureConstraint + Hash + Eq + fmt::Display,
693    SlaterDeterminant<'a, T, SC>: SymmetryTransformable,
694    SlaterDeterminantSymmetryOrbit<'a, G, T, SC>: Projectable<G, SlaterDeterminant<'a, T, SC>>,
695{
696    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
697        writeln!(f, "{self}")
698    }
699}
700
701// Specific for unitary-represented symmetry groups and density numeric types f64 and C128
702// '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
703
704#[duplicate_item(
705    duplicate!{
706        [
707            dtype_nested sctype_nested;
708            [ f64 ] [ SpinConstraint ];
709            [ Complex<f64> ] [ SpinConstraint ];
710            [ Complex<f64> ] [ SpinOrbitCoupled ];
711        ]
712        [
713            gtype_ [ UnitaryRepresentedSymmetryGroup ]
714            dtype_ [ dtype_nested ]
715            sctype_ [ sctype_nested ]
716            doc_sub_ [ "Performs projection using a unitary-represented group and stores the result." ]
717            projection_fn_ [ projection_representation ]
718            construct_group_ [ self.construct_unitary_group()? ]
719        ]
720    }
721)]
722impl<'a> QSym2Driver for SlaterDeterminantProjectionDriver<'a, gtype_, dtype_, sctype_> {
723    type Params = SlaterDeterminantProjectionParams;
724
725    type Outcome = SlaterDeterminantProjectionResult<'a, gtype_, dtype_, sctype_>;
726
727    fn result(&self) -> Result<&Self::Outcome, anyhow::Error> {
728        self.result
729            .as_ref()
730            .ok_or_else(|| format_err!("No Slater determinant projection results found."))
731    }
732
733    fn run(&mut self) -> Result<(), anyhow::Error> {
734        self.log_output_display();
735        self.projection_fn_()?;
736        self.result()?.log_output_display();
737        Ok(())
738    }
739}