qsym2/target/determinant/
mod.rs

1//! Slater determinants.
2
3use std::collections::HashSet;
4use std::fmt;
5use std::iter::Sum;
6
7use anyhow::{self, format_err};
8use approx;
9use derive_builder::Builder;
10use itertools::Itertools;
11use log;
12use ndarray::{Array1, Array2, Ix2, s};
13use ndarray_einsum::*;
14use ndarray_linalg::types::Lapack;
15use num::ToPrimitive;
16use num_complex::{Complex, ComplexFloat};
17use num_traits::float::{Float, FloatConst};
18
19use crate::angmom::spinor_rotation_3d::{SpinConstraint, SpinOrbitCoupled, StructureConstraint};
20use crate::auxiliary::molecule::Molecule;
21use crate::basis::ao::BasisAngularOrder;
22use crate::target::density::{DensitiesOwned, Density};
23use crate::target::orbital::MolecularOrbital;
24
25#[cfg(test)]
26mod determinant_tests;
27
28#[cfg(test)]
29mod determinant_jadapted_tests;
30
31pub mod determinant_analysis;
32pub mod determinant_projection;
33mod determinant_transformation;
34
35// ==================
36// Struct definitions
37// ==================
38
39/// Structure to manage single-determinantal wavefunctions.
40#[derive(Builder, Clone)]
41#[builder(build_fn(validate = "Self::validate"))]
42pub struct SlaterDeterminant<'a, T, SC>
43where
44    T: ComplexFloat + Lapack,
45    SC: StructureConstraint + fmt::Display,
46{
47    /// The structure constraint associated with the coefficients describing this determinant.
48    structure_constraint: SC,
49
50    /// The angular order of the basis functions with respect to which the coefficients are
51    /// expressed. Each [`BasisAngularOrder`] corresponds to one explicit component in the
52    /// coefficient matrix (see [`StructureConstraint::n_explicit_comps_per_coefficient_matrix`]).
53    baos: Vec<&'a BasisAngularOrder<'a>>,
54
55    /// A boolean indicating if inner products involving this determinant should be the
56    /// complex-symmetric bilinear form, rather than the conventional Hermitian sesquilinear form.
57    complex_symmetric: bool,
58
59    /// A boolean indicating if the determinant has been acted on by an antiunitary operation. This
60    /// is so that the correct metric can be used during overlap evaluation.
61    #[builder(default = "false")]
62    complex_conjugated: bool,
63
64    /// The associated molecule.
65    mol: &'a Molecule,
66
67    /// The coefficients describing this determinant.
68    #[builder(setter(custom))]
69    coefficients: Vec<Array2<T>>,
70
71    /// The occupation patterns of the molecular orbitals in [`Self::coefficients`].
72    #[builder(setter(custom))]
73    occupations: Vec<Array1<<T as ComplexFloat>::Real>>,
74
75    /// The energies of the molecular orbitals in [`Self::coefficients`].
76    #[builder(default = "None")]
77    mo_energies: Option<Vec<Array1<T>>>,
78
79    /// The energy of this determinant.
80    #[builder(default = "Err(\"Determinant energy not yet set.\".to_string())")]
81    energy: Result<T, String>,
82
83    /// The threshold for comparing determinants.
84    threshold: <T as ComplexFloat>::Real,
85}
86
87impl<'a, T, SC> SlaterDeterminantBuilder<'a, T, SC>
88where
89    T: ComplexFloat + Lapack,
90    SC: StructureConstraint + fmt::Display,
91{
92    pub fn coefficients(&mut self, cs: &[Array2<T>]) -> &mut Self {
93        self.coefficients = Some(cs.to_vec());
94        self
95    }
96
97    pub fn occupations(&mut self, occs: &[Array1<<T as ComplexFloat>::Real>]) -> &mut Self {
98        self.occupations = Some(occs.to_vec());
99        self
100    }
101
102    fn validate(&self) -> Result<(), String> {
103        let structcons = self
104            .structure_constraint
105            .as_ref()
106            .ok_or("No structure constraints found.".to_string())?;
107        let baos = self
108            .baos
109            .as_ref()
110            .ok_or("No `BasisAngularOrder`s found.".to_string())?;
111        let baos_length_check = baos.len() == structcons.n_explicit_comps_per_coefficient_matrix();
112        if !baos_length_check {
113            log::error!(
114                "The number of `BasisAngularOrder`s provided does not match the number of explicit components per coefficient matrix."
115            );
116        }
117
118        let nbas_tot = baos.iter().map(|bao| bao.n_funcs()).sum::<usize>();
119        let coefficients = self
120            .coefficients
121            .as_ref()
122            .ok_or("No coefficients found.".to_string())?;
123        let coefficients_length_check = if coefficients.len() != structcons.n_coefficient_matrices()
124        {
125            log::error!(
126                "Unexpected number of coefficient matrices: {} found, but {} expected for the structure constraint {}.",
127                coefficients.len(),
128                structcons.n_coefficient_matrices(),
129                structcons
130            );
131            false
132        } else {
133            true
134        };
135        let coefficients_shape_check = {
136            let nrows = nbas_tot;
137            if !coefficients.iter().all(|c| c.shape()[0] == nrows) {
138                log::error!(
139                    "Unexpected shapes of coefficient matrices: {} {} expected for all coefficient matrices, but {} found.",
140                    nrows,
141                    if nrows == 1 { "row" } else { "rows" },
142                    coefficients
143                        .iter()
144                        .map(|c| c.shape()[0].to_string())
145                        .join(", ")
146                );
147                false
148            } else {
149                true
150            }
151        };
152
153        let occupations = self
154            .occupations
155            .as_ref()
156            .ok_or("No occupations found.".to_string())?;
157        let occupations_length_check = if occupations.len() != structcons.n_coefficient_matrices() {
158            log::error!(
159                "Unexpected number of occupation vectors: {} found, but {} expected for the structure constraint {}.",
160                occupations.len(),
161                structcons.n_coefficient_matrices(),
162                structcons
163            );
164            false
165        } else {
166            true
167        };
168        let occupations_shape_check = if !occupations
169            .iter()
170            .zip(coefficients.iter())
171            .all(|(o, c)| o.len() == c.shape()[1])
172        {
173            log::error!(
174                "Mismatched occupations and numbers of orbitals: {}",
175                occupations
176                    .iter()
177                    .zip(coefficients.iter())
178                    .map(|(o, c)| format!("{} vs. {}", o.len(), c.shape()[1]))
179                    .join(", ")
180            );
181            false
182        } else {
183            true
184        };
185
186        let mol = self.mol.ok_or("No molecule found.".to_string())?;
187        let natoms_set = baos.iter().map(|bao| bao.n_atoms()).collect::<HashSet<_>>();
188        if natoms_set.len() != 1 {
189            return Err("Inconsistent numbers of atoms between `BasisAngularOrder`s of different explicit components.".to_string());
190        };
191        let n_atoms = natoms_set.iter().next().ok_or_else(|| {
192            "Unable to retrieve the number of atoms from the `BasisAngularOrder`s.".to_string()
193        })?;
194        let natoms_check = mol.atoms.len() == *n_atoms;
195        if !natoms_check {
196            log::error!(
197                "The number of atoms in the molecule does not match the number of local sites in the basis."
198            );
199        }
200
201        if baos_length_check
202            && coefficients_length_check
203            && coefficients_shape_check
204            && occupations_length_check
205            && occupations_shape_check
206            && natoms_check
207        {
208            Ok(())
209        } else {
210            Err(format!(
211                "Slater determinant validation failed:
212                    baos_length ({baos_length_check}),
213                    coefficients_length ({coefficients_length_check}),
214                    coefficients_shape ({coefficients_shape_check}),
215                    occupations_length ({occupations_length_check}),
216                    occupations_shape ({occupations_shape_check}),
217                    natoms ({natoms_check})."
218            ))
219        }
220    }
221}
222
223impl<'a, T, SC> SlaterDeterminant<'a, T, SC>
224where
225    T: ComplexFloat + Clone + Lapack,
226    SC: StructureConstraint + Clone + fmt::Display,
227{
228    /// Returns a builder to construct a new [`SlaterDeterminant`].
229    pub fn builder() -> SlaterDeterminantBuilder<'a, T, SC> {
230        SlaterDeterminantBuilder::default()
231    }
232}
233
234impl<'a, T, SC> SlaterDeterminant<'a, T, SC>
235where
236    T: ComplexFloat + Clone + Lapack,
237    SC: StructureConstraint + Clone + fmt::Display,
238{
239    /// Extracts the molecular orbitals in this Slater determinant.
240    ///
241    /// # Returns
242    ///
243    /// A vector of the molecular orbitals constituting this Slater determinant. In the restricted
244    /// spin constraint, the identical molecular orbitals across different spin spaces are only
245    /// given once. Each molecular orbital does contain an index of the spin space it is in.
246    pub fn to_orbitals(&self) -> Vec<Vec<MolecularOrbital<'a, T, SC>>> {
247        self.coefficients
248            .iter()
249            .enumerate()
250            .map(|(spini, cs_spini)| {
251                cs_spini
252                    .columns()
253                    .into_iter()
254                    .enumerate()
255                    .map(move |(i, c)| {
256                        MolecularOrbital::builder()
257                            .coefficients(c.to_owned())
258                            .energy(self.mo_energies.as_ref().map(|moes| moes[spini][i]))
259                            .baos(self.baos.clone())
260                            .mol(self.mol)
261                            .structure_constraint(self.structure_constraint.clone())
262                            .component_index(spini)
263                            .complex_symmetric(self.complex_symmetric)
264                            .threshold(self.threshold)
265                            .build()
266                            .expect("Unable to construct a molecular orbital.")
267                    })
268                    .collect::<Vec<_>>()
269            })
270            .collect::<Vec<_>>()
271    }
272}
273
274impl<'a, T, SC> SlaterDeterminant<'a, T, SC>
275where
276    T: ComplexFloat + Clone + Lapack,
277    SC: StructureConstraint + fmt::Display,
278{
279    /// Returns the complex-symmetric flag of the determinant.
280    pub fn complex_symmetric(&self) -> bool {
281        self.complex_symmetric
282    }
283
284    /// Returns the complex-conjugated flag of the determinant.
285    pub(crate) fn complex_conjugated(&self) -> bool {
286        self.complex_conjugated
287    }
288
289    /// Returns the constraint imposed on the coefficients.
290    pub fn structure_constraint(&self) -> &SC {
291        &self.structure_constraint
292    }
293
294    /// Returns the basis angular order information of the basis sets in which the coefficients are
295    /// expressed.
296    pub fn baos(&'_ self) -> &Vec<&'_ BasisAngularOrder<'_>> {
297        &self.baos
298    }
299
300    /// Returns the molecule associated with this Slater determinant.
301    pub fn mol(&self) -> &Molecule {
302        self.mol
303    }
304
305    /// Returns the determinantal energy.
306    pub fn energy(&self) -> Result<&T, &String> {
307        self.energy.as_ref()
308    }
309
310    /// Returns the molecular-orbital energies.
311    pub fn mo_energies(&self) -> Option<&Vec<Array1<T>>> {
312        self.mo_energies.as_ref()
313    }
314
315    /// Returns the occupation patterns of the molecular orbitals.
316    pub fn occupations(&self) -> &Vec<Array1<<T as ComplexFloat>::Real>> {
317        &self.occupations
318    }
319
320    /// Returns a shared reference to a vector of coefficient arrays.
321    pub fn coefficients(&self) -> &Vec<Array2<T>> {
322        &self.coefficients
323    }
324
325    /// Returns the threshold with which determinants are compared.
326    pub fn threshold(&self) -> <T as ComplexFloat>::Real {
327        self.threshold
328    }
329
330    /// Returns the total number of electrons in the determinant.
331    pub fn nelectrons(&self) -> <T as ComplexFloat>::Real
332    where
333        <T as ComplexFloat>::Real: Sum + From<u16>,
334    {
335        let implicit_factor = self
336            .structure_constraint
337            .implicit_factor()
338            .expect("Unable to retrieve the implicit factor from the structure constraint.");
339        <T as ComplexFloat>::Real::from(
340            implicit_factor
341                .to_u16()
342                .expect("Unable to convert the implicit factor to `u16`."),
343        ) * self
344            .occupations
345            .iter()
346            .map(|occ| occ.iter().copied().sum())
347            .sum()
348    }
349}
350
351impl<'a, T> SlaterDeterminant<'a, T, SpinConstraint>
352where
353    T: ComplexFloat + Clone + Lapack,
354{
355    /// Augments the encoding of coefficients in this Slater determinant to that in the
356    /// corresponding generalised spin constraint.
357    ///
358    /// # Returns
359    ///
360    /// The equivalent Slater determinant with the coefficients encoded in the generalised spin
361    /// constraint.
362    pub fn to_generalised(&self) -> Self {
363        match self.structure_constraint {
364            SpinConstraint::Restricted(n) => {
365                log::debug!(
366                    "Restricted Slater determinant will be augmented to generalised Slater determinant."
367                );
368                let bao = self.baos[0];
369                let nbas = bao.n_funcs();
370
371                let cr = &self.coefficients[0];
372                let occr = &self.occupations[0];
373                let norb = cr.ncols();
374                let mut cg = Array2::<T>::zeros((nbas * usize::from(n), norb * usize::from(n)));
375                let mut occg = Array1::<<T as ComplexFloat>::Real>::zeros((norb * usize::from(n),));
376                (0..usize::from(n)).for_each(|i| {
377                    let row_start = nbas * i;
378                    let row_end = nbas * (i + 1);
379                    let col_start = norb * i;
380                    let col_end = norb * (i + 1);
381                    cg.slice_mut(s![row_start..row_end, col_start..col_end])
382                        .assign(cr);
383                    occg.slice_mut(s![col_start..col_end]).assign(occr);
384                });
385                let moeg_opt = self.mo_energies.as_ref().map(|moer| {
386                    let mut moeg = Array1::<T>::zeros((norb * usize::from(n),));
387                    (0..usize::from(n)).for_each(|i| {
388                        let col_start = norb * i;
389                        let col_end = norb * (i + 1);
390                        moeg.slice_mut(s![col_start..col_end]).assign(&moer[0]);
391                    });
392                    vec![moeg]
393                });
394                Self::builder()
395                    .coefficients(&[cg])
396                    .occupations(&[occg])
397                    .mo_energies(moeg_opt)
398                    .energy(self.energy.clone())
399                    .baos((0..n).map(|_| bao).collect::<Vec<_>>())
400                    .mol(self.mol)
401                    .structure_constraint(SpinConstraint::Generalised(n, false))
402                    .complex_symmetric(self.complex_symmetric)
403                    .threshold(self.threshold)
404                    .build()
405                    .expect("Unable to spin-generalise a `SlaterDeterminant`.")
406            }
407            SpinConstraint::Unrestricted(n, increasingm) => {
408                log::debug!(
409                    "Unrestricted Slater determinant will be augmented to generalised Slater determinant."
410                );
411                let bao = self.baos[0];
412                let nbas = bao.n_funcs();
413                let norb_tot = self.coefficients.iter().map(|c| c.ncols()).sum();
414                let mut cg = Array2::<T>::zeros((nbas * usize::from(n), norb_tot));
415                let mut occg = Array1::<<T as ComplexFloat>::Real>::zeros((norb_tot,));
416
417                let col_boundary_indices = (0..usize::from(n))
418                    .scan(0, |acc, ispin| {
419                        let start_index = *acc;
420                        *acc += self.coefficients[ispin].shape()[1];
421                        Some((start_index, *acc))
422                    })
423                    .collect::<Vec<_>>();
424                (0..usize::from(n)).for_each(|i| {
425                    let row_start = nbas * i;
426                    let row_end = nbas * (i + 1);
427                    let (col_start, col_end) = col_boundary_indices[i];
428                    cg.slice_mut(s![row_start..row_end, col_start..col_end])
429                        .assign(&self.coefficients[i]);
430                    occg.slice_mut(s![col_start..col_end])
431                        .assign(&self.occupations[i]);
432                });
433
434                let moeg_opt = self.mo_energies.as_ref().map(|moer| {
435                    let mut moeg = Array1::<T>::zeros((norb_tot,));
436                    (0..usize::from(n)).for_each(|i| {
437                        let (col_start, col_end) = col_boundary_indices[i];
438                        moeg.slice_mut(s![col_start..col_end]).assign(&moer[i]);
439                    });
440                    vec![moeg]
441                });
442
443                Self::builder()
444                    .coefficients(&[cg])
445                    .occupations(&[occg])
446                    .mo_energies(moeg_opt)
447                    .energy(self.energy.clone())
448                    .baos((0..n).map(|_| bao).collect::<Vec<_>>())
449                    .mol(self.mol)
450                    .structure_constraint(SpinConstraint::Generalised(n, increasingm))
451                    .complex_symmetric(self.complex_symmetric)
452                    .threshold(self.threshold)
453                    .build()
454                    .expect("Unable to spin-generalise a `SlaterDeterminant`.")
455            }
456            SpinConstraint::Generalised(_, _) => self.clone(),
457        }
458    }
459}
460
461impl<'a> SlaterDeterminant<'a, f64, SpinConstraint> {
462    /// Constructs a vector of real densities, one for each spin space in a Slater determinant.
463    ///
464    /// For restricted and unrestricted spin constraints, spin spaces are well-defined. For
465    /// generalised spin constraints, each spin-space density is constructed from the corresponding
466    /// diagonal block of the overall density matrix.
467    ///
468    /// Occupation numbers are also incorporated in the formation of density matrices.
469    ///
470    /// # Returns
471    ///
472    /// A vector of real densities, one for each spin space.
473    pub fn to_densities(
474        &'a self,
475    ) -> Result<DensitiesOwned<'a, f64, SpinConstraint>, anyhow::Error> {
476        let densities = match self.structure_constraint {
477            SpinConstraint::Restricted(_) | SpinConstraint::Unrestricted(_, _) => {
478                self.coefficients().iter().zip(self.occupations().iter()).map(|(c, o)| {
479                    let denmat = einsum(
480                        "i,mi,ni->mn",
481                        &[&o.view(), &c.view(), &c.view()]
482                    )
483                    .expect("Unable to construct a density matrix from a determinant coefficient matrix.")
484                    .into_dimensionality::<Ix2>()
485                    .expect("Unable to convert the resultant density matrix to two dimensions.");
486                    Density::<f64>::builder()
487                        .density_matrix(denmat)
488                        .bao(self.baos()[0])
489                        .mol(self.mol())
490                        .complex_symmetric(self.complex_symmetric())
491                        .threshold(self.threshold())
492                        .build()
493                    })
494                    .collect::<Result<Vec<_>, _>>()?
495            }
496            SpinConstraint::Generalised(nspins, _) => {
497                let denmat = einsum(
498                    "i,mi,ni->mn",
499                    &[&self.occupations[0].view(), &self.coefficients[0].view(), &self.coefficients[0].view()]
500                )
501                .expect("Unable to construct a density matrix from a determinant coefficient matrix.")
502                .into_dimensionality::<Ix2>()
503                .expect("Unable to convert the resultant density matrix to two dimensions.");
504                let component_boundary_indices = self.baos
505                    .iter()
506                    .scan(0, |acc, bao| {
507                        let start_index = *acc;
508                        *acc += bao.n_funcs();
509                        Some((start_index, *acc))
510                    }).collect::<Vec<_>>();
511                (0..usize::from(nspins)).map(|ispin| {
512                    let (start, end) = component_boundary_indices[ispin];
513                    let ispin_denmat = denmat.slice(s![start..end, start..end]).to_owned();
514                    Density::<f64>::builder()
515                        .density_matrix(ispin_denmat)
516                        .bao(self.baos()[ispin])
517                        .mol(self.mol())
518                        .complex_symmetric(self.complex_symmetric())
519                        .threshold(self.threshold())
520                        .build()
521                }).collect::<Result<Vec<_>, _>>()?
522            }
523        };
524        DensitiesOwned::builder()
525            .structure_constraint(self.structure_constraint.clone())
526            .densities(densities)
527            .build()
528            .map_err(|err| format_err!(err))
529    }
530}
531
532impl<'a, T> SlaterDeterminant<'a, Complex<T>, SpinConstraint>
533where
534    T: Float + FloatConst + Lapack,
535    Complex<T>: Lapack,
536{
537    /// Constructs a vector of complex densities, one from each coefficient matrix in a Slater
538    /// determinant.
539    ///
540    /// For restricted and unrestricted spin constraints, spin spaces are well-defined. For
541    /// generalised spin constraints, each spin-space density is constructed from the corresponding
542    /// diagonal block of the overall density matrix.
543    ///
544    /// Occupation numbers are also incorporated in the formation of density matrices.
545    ///
546    /// # Arguments
547    ///
548    /// * `sd` - A Slater determinant.
549    ///
550    /// # Returns
551    ///
552    /// A vector of complex densities, one for each spin space.
553    pub fn to_densities(
554        &'a self,
555    ) -> Result<DensitiesOwned<'a, Complex<T>, SpinConstraint>, anyhow::Error> {
556        let densities = match self.structure_constraint {
557            SpinConstraint::Restricted(_) | SpinConstraint::Unrestricted(_, _) => {
558                self.coefficients().iter().zip(self.occupations().iter()).map(|(c, o)| {
559                    let denmat = einsum(
560                        "i,mi,ni->mn",
561                        &[&o.map(Complex::<T>::from).view(), &c.view(), &c.map(Complex::conj).view()]
562                    )
563                    .expect("Unable to construct a density matrix from a determinant coefficient matrix.")
564                    .into_dimensionality::<Ix2>()
565                    .expect("Unable to convert the resultant density matrix to two dimensions.");
566                    Density::<Complex<T>>::builder()
567                        .density_matrix(denmat)
568                        .bao(self.baos()[0])
569                        .mol(self.mol())
570                        .complex_symmetric(self.complex_symmetric())
571                        .threshold(self.threshold())
572                        .build()
573                    })
574                    .collect::<Result<Vec<_>, _>>()?
575            }
576            SpinConstraint::Generalised(nspins, _) => {
577                let denmat = einsum(
578                    "i,mi,ni->mn",
579                    &[
580                        &self.occupations[0].map(Complex::<T>::from).view(),
581                        &self.coefficients[0].view(),
582                        &self.coefficients[0].map(Complex::conj).view()
583                    ]
584                )
585                .expect("Unable to construct a density matrix from a determinant coefficient matrix.")
586                .into_dimensionality::<Ix2>()
587                .expect("Unable to convert the resultant density matrix to two dimensions.");
588                let component_boundary_indices = self.baos
589                    .iter()
590                    .scan(0, |acc, bao| {
591                        let start_index = *acc;
592                        *acc += bao.n_funcs();
593                        Some((start_index, *acc))
594                    }).collect::<Vec<_>>();
595                (0..usize::from(nspins)).map(|ispin| {
596                    let (start, end) = component_boundary_indices[ispin];
597                    let ispin_denmat = denmat.slice(s![start..end, start..end]).to_owned();
598                    Density::<Complex<T>>::builder()
599                        .density_matrix(ispin_denmat)
600                        .bao(self.baos()[ispin])
601                        .mol(self.mol())
602                        .complex_symmetric(self.complex_symmetric())
603                        .threshold(self.threshold())
604                        .build()
605                }).collect::<Result<Vec<_>, _>>()?
606            }
607        };
608        DensitiesOwned::builder()
609            .structure_constraint(self.structure_constraint.clone())
610            .densities(densities)
611            .build()
612            .map_err(|err| format_err!(err))
613    }
614}
615
616impl<'a, T> SlaterDeterminant<'a, Complex<T>, SpinOrbitCoupled>
617where
618    T: Float + FloatConst + Lapack,
619    Complex<T>: Lapack,
620{
621    /// Constructs a vector of complex densities, one for each component in the multi-component
622    /// j-adapted basis.
623    ///
624    /// Occupation numbers are also incorporated in the formation of density matrices.
625    ///
626    /// # Arguments
627    ///
628    /// * `sd` - A Slater determinant.
629    ///
630    /// # Returns
631    ///
632    /// A vector of complex densities.
633    pub fn to_densities(
634        &'a self,
635    ) -> Result<DensitiesOwned<'a, Complex<T>, SpinOrbitCoupled>, anyhow::Error> {
636        let densities = match self.structure_constraint {
637            SpinOrbitCoupled::JAdapted(ncomps) => {
638                let denmat = einsum(
639                    "i,mi,ni->mn",
640                    &[
641                        &self.occupations[0].map(Complex::<T>::from).view(),
642                        &self.coefficients[0].view(),
643                        &self.coefficients[0].map(Complex::conj).view(),
644                    ],
645                )
646                .expect(
647                    "Unable to construct a density matrix from a determinant coefficient matrix.",
648                )
649                .into_dimensionality::<Ix2>()
650                .expect("Unable to convert the resultant density matrix to two dimensions.");
651                let component_boundary_indices = self
652                    .baos
653                    .iter()
654                    .scan(0, |acc, bao| {
655                        let start_index = *acc;
656                        *acc += bao.n_funcs();
657                        Some((start_index, *acc))
658                    })
659                    .collect::<Vec<_>>();
660                (0..usize::from(ncomps))
661                    .map(|icomp| {
662                        let (start, end) = component_boundary_indices[icomp];
663                        let icomp_denmat = denmat.slice(s![start..end, start..end]).to_owned();
664                        Density::<Complex<T>>::builder()
665                            .density_matrix(icomp_denmat)
666                            .bao(self.baos()[icomp])
667                            .mol(self.mol())
668                            .complex_symmetric(self.complex_symmetric())
669                            .threshold(self.threshold())
670                            .build()
671                    })
672                    .collect::<Result<Vec<_>, _>>()?
673            }
674        };
675        DensitiesOwned::builder()
676            .structure_constraint(self.structure_constraint.clone())
677            .densities(densities)
678            .build()
679            .map_err(|err| format_err!(err))
680    }
681}
682
683// =====================
684// Trait implementations
685// =====================
686
687// ----
688// From
689// ----
690impl<'a, T, SC> From<SlaterDeterminant<'a, T, SC>> for SlaterDeterminant<'a, Complex<T>, SC>
691where
692    T: Float + FloatConst + Lapack,
693    Complex<T>: Lapack,
694    SC: StructureConstraint + Clone + fmt::Display,
695{
696    fn from(value: SlaterDeterminant<'a, T, SC>) -> Self {
697        SlaterDeterminant::<'a, Complex<T>, SC>::builder()
698            .coefficients(
699                &value
700                    .coefficients
701                    .into_iter()
702                    .map(|coeffs| coeffs.map(Complex::from))
703                    .collect::<Vec<_>>(),
704            )
705            .occupations(&value.occupations)
706            .mo_energies(value.mo_energies.map(|moes| {
707                moes.iter()
708                    .map(|moe| moe.map(Complex::from))
709                    .collect::<Vec<_>>()
710            }))
711            .baos(value.baos.clone())
712            .mol(value.mol)
713            .structure_constraint(value.structure_constraint)
714            .complex_symmetric(value.complex_symmetric)
715            .threshold(value.threshold)
716            .build()
717            .expect("Unable to complexify a `SlaterDeterminant`.")
718    }
719}
720
721// ---------
722// PartialEq
723// ---------
724impl<'a, T, SC> PartialEq for SlaterDeterminant<'a, T, SC>
725where
726    T: ComplexFloat<Real = f64> + Lapack,
727    SC: StructureConstraint + PartialEq + fmt::Display,
728{
729    fn eq(&self, other: &Self) -> bool {
730        let thresh = (self.threshold * other.threshold).sqrt();
731        let coefficients_eq =
732            self.coefficients.len() == other.coefficients.len()
733                && self.coefficients.iter().zip(other.coefficients.iter()).all(
734                    |(scoeffs, ocoeffs)| {
735                        approx::relative_eq!(
736                            (scoeffs - ocoeffs)
737                                .map(|x| ComplexFloat::abs(*x).powi(2))
738                                .sum()
739                                .sqrt(),
740                            0.0,
741                            epsilon = thresh,
742                            max_relative = thresh,
743                        )
744                    },
745                );
746        let occs_eq = self.occupations.len() == other.occupations.len()
747            && self
748                .occupations
749                .iter()
750                .zip(other.occupations.iter())
751                .all(|(soccs, ooccs)| {
752                    approx::relative_eq!(
753                        (soccs - ooccs).map(|x| x.abs().powi(2)).sum().sqrt(),
754                        0.0,
755                        epsilon = thresh,
756                        max_relative = thresh,
757                    )
758                });
759        self.structure_constraint == other.structure_constraint
760            && self.baos == other.baos
761            && self.mol == other.mol
762            && coefficients_eq
763            && occs_eq
764    }
765}
766
767// --
768// Eq
769// --
770impl<'a, T, SC> Eq for SlaterDeterminant<'a, T, SC>
771where
772    T: ComplexFloat<Real = f64> + Lapack,
773    SC: StructureConstraint + Eq + fmt::Display,
774{
775}
776
777// -----
778// Debug
779// -----
780impl<'a, T, SC> fmt::Debug for SlaterDeterminant<'a, T, SC>
781where
782    T: fmt::Debug + ComplexFloat + Lapack,
783    <T as ComplexFloat>::Real: Sum + From<u16> + fmt::Debug,
784    SC: StructureConstraint + fmt::Debug + fmt::Display,
785{
786    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
787        write!(
788            f,
789            "SlaterDeterminant[{:?}: {:?} electrons, {} coefficient {} of dimensions {}]",
790            self.structure_constraint,
791            self.nelectrons(),
792            self.coefficients.len(),
793            if self.coefficients.len() != 1 {
794                "matrices"
795            } else {
796                "matrix"
797            },
798            self.coefficients
799                .iter()
800                .map(|coeff| coeff
801                    .shape()
802                    .iter()
803                    .map(|x| x.to_string())
804                    .collect::<Vec<_>>()
805                    .join("×"))
806                .collect::<Vec<_>>()
807                .join(", ")
808        )?;
809        Ok(())
810    }
811}
812
813// -------
814// Display
815// -------
816impl<'a, T, SC> fmt::Display for SlaterDeterminant<'a, T, SC>
817where
818    T: fmt::Display + ComplexFloat + Lapack,
819    <T as ComplexFloat>::Real: Sum + From<u16> + fmt::Display,
820    SC: StructureConstraint + fmt::Display,
821{
822    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
823        write!(
824            f,
825            "SlaterDeterminant[{}: {} electrons, {} coefficient {} of dimensions {}]",
826            self.structure_constraint,
827            self.nelectrons(),
828            self.coefficients.len(),
829            if self.coefficients.len() != 1 {
830                "matrices"
831            } else {
832                "matrix"
833            },
834            self.coefficients
835                .iter()
836                .map(|coeff| coeff
837                    .shape()
838                    .iter()
839                    .map(|x| x.to_string())
840                    .collect::<Vec<_>>()
841                    .join("×"))
842                .collect::<Vec<_>>()
843                .join(", ")
844        )?;
845        Ok(())
846    }
847}