qsym2/target/noci/backend/matelem/
mod.rs

1use std::collections::HashSet;
2use std::fmt::{self, LowerExp};
3
4use anyhow::{self, ensure, format_err};
5use itertools::Itertools;
6use log;
7use ndarray::{Array2, Array3, ArrayView2, s};
8use ndarray_linalg::types::Lapack;
9use num_complex::ComplexFloat;
10use rayon::iter::{ParallelBridge, ParallelIterator};
11
12use crate::angmom::spinor_rotation_3d::StructureConstraint;
13use crate::symmetry::symmetry_element::SpecialSymmetryTransformation;
14use crate::symmetry::symmetry_group::SymmetryGroupProperties;
15use crate::symmetry::symmetry_transformation::SymmetryTransformable;
16use crate::target::determinant::SlaterDeterminant;
17use crate::target::noci::basis::{Basis, OrbitBasis};
18
19pub mod hamiltonian;
20pub mod overlap;
21
22pub trait OrbitMatrix<'a, T, SC>
23where
24    T: Lapack + ComplexFloat,
25    SC: StructureConstraint + Clone + fmt::Display,
26    SlaterDeterminant<'a, T, SC>: SymmetryTransformable,
27{
28    /// The type of the matrix elements.
29    type MatrixElement;
30
31    // ----------------
32    // Required methods
33    // ----------------
34    /// Calculates the matrix element between two Slater determinants.
35    ///
36    /// # Arguments
37    ///
38    /// * `det_w` - The determinant $`^{w}\Psi`$.
39    /// * `det_x` - The determinant $`^{x}\Psi`$.
40    /// * `sao` - The atomic-orbital overlap matrix.
41    /// * `thresh_offdiag` - Threshold for determining non-zero off-diagonal elements in the
42    ///   orbital overlap matrix between $`^{w}\Psi`$ and $`^{x}\Psi`$ during Löwdin pairing.
43    /// * `thresh_zeroov` - Threshold for identifying zero Löwdin overlaps.
44    ///
45    /// # Returns
46    ///
47    /// The resulting matrix element.
48    fn calc_matrix_element(
49        &self,
50        det_w: &SlaterDeterminant<T, SC>,
51        det_x: &SlaterDeterminant<T, SC>,
52        sao: &ArrayView2<T>,
53        thresh_offdiag: <T as ComplexFloat>::Real,
54        thresh_zeroov: <T as ComplexFloat>::Real,
55    ) -> Result<Self::MatrixElement, anyhow::Error>;
56
57    /// Computes the transpose of a matrix element.
58    fn t(x: &Self::MatrixElement) -> Self::MatrixElement;
59
60    /// Computes the complex conjugation of a matrix element.
61    fn conj(x: &Self::MatrixElement) -> Self::MatrixElement;
62
63    /// Returns the zero matrix element.
64    fn zero(&self) -> Self::MatrixElement;
65
66    // ----------------
67    // Provided methods
68    // ----------------
69
70    /// Returns the norm-presearving scalar map connecting diagonally-symmetric elements in the
71    /// matrix.
72    #[allow(clippy::type_complexity)]
73    fn norm_preserving_scalar_map<'b, G>(
74        &self,
75        i: usize,
76        orbit_basis: &'b OrbitBasis<'b, G, SlaterDeterminant<'a, T, SC>>,
77    ) -> Result<fn(&Self::MatrixElement) -> Self::MatrixElement, anyhow::Error>
78    where
79        G: SymmetryGroupProperties + Clone,
80        'a: 'b,
81    {
82        let group = orbit_basis.group();
83        let complex_symmetric_set = orbit_basis
84            .origins()
85            .iter()
86            .map(|det| det.complex_symmetric())
87            .collect::<HashSet<_>>();
88        ensure!(
89            complex_symmetric_set.len() == 1,
90            "Inconsistent complex-symmetric flags across origin determinants."
91        );
92        let complex_symmetric = *complex_symmetric_set
93            .iter()
94            .next()
95            .ok_or(format_err!("Unable to obtain the complex-symmetric flag."))?;
96        if complex_symmetric {
97            Err(format_err!(
98                "`norm_preserving_scalar_map` is currently not implemented for complex-symmetric inner products. This thus precludes the use of the Cayley table to speed up the computation of orbit matrices."
99            ))
100        } else if group
101            .get_index(i)
102            .unwrap_or_else(|| panic!("Group operation index `{i}` not found."))
103            .contains_time_reversal()
104        {
105            Ok(Self::conj)
106        } else {
107            Ok(Self::t)
108        }
109    }
110
111    /// Computes the entire matrix of matrix elements in an orbit basis, making use of group
112    /// closure for optimisation.
113    ///
114    /// # Arguments
115    ///
116    /// * `orbit_basis` - The orbit basis in which the matrix elements are to be computed.
117    /// * `use_cayley_table` - Boolean indicating whether group closure should be used to speed up
118    ///   the computation.
119    /// * `sao` - The atomic-orbital overlap matrix.
120    /// * `thresh_offdiag` - Threshold for determining non-zero off-diagonal elements in the
121    ///   orbital overlap matrix between two Slater determinants during Löwdin pairing.
122    /// * `thresh_zeroov` - Threshold for identifying zero Löwdin overlaps.
123    fn calc_orbit_matrix<'g, G>(
124        &self,
125        orbit_basis: &'g OrbitBasis<'g, G, SlaterDeterminant<'a, T, SC>>,
126        use_cayley_table: bool,
127        sao: &ArrayView2<T>,
128        thresh_offdiag: <T as ComplexFloat>::Real,
129        thresh_zeroov: <T as ComplexFloat>::Real,
130    ) -> Result<Array2<Self::MatrixElement>, anyhow::Error>
131    where
132        G: SymmetryGroupProperties + Clone,
133        T: Sync + Send,
134        <T as ComplexFloat>::Real: Sync,
135        SlaterDeterminant<'a, T, SC>: Sync,
136        Self: Sync,
137        Self::MatrixElement: Send + LowerExp,
138        'a: 'g,
139        Self::MatrixElement: Clone,
140    {
141        let group = orbit_basis.group();
142        let order = group.order();
143        let det_origins = orbit_basis.origins();
144        let n_det_origins = det_origins.len();
145        let mut mat = Array2::<Self::MatrixElement>::from_elem(
146            (n_det_origins * order, n_det_origins * order),
147            self.zero(),
148        );
149
150        if let (Some(ctb), true) = (group.cayley_table(), use_cayley_table) {
151            log::debug!(
152                "Cayley table available and its use requested. Group closure will be used to speed up orbit matrix computation."
153            );
154            // Compute unique matrix elements
155            let mut ov_elems = orbit_basis
156                .iter()
157                .collect::<Result<Vec<_>, _>>()?
158                .iter()
159                .enumerate()
160                .cartesian_product(orbit_basis.origins().iter().enumerate())
161                // .par_bridge()
162                .map(|((k_ii, k_ii_det), (jj, jj_det))| {
163                    let k = k_ii.div_euclid(n_det_origins);
164                    let ii = k_ii.rem_euclid(n_det_origins);
165                    (
166                        ii,
167                        jj,
168                        k,
169                        self.calc_matrix_element(
170                            k_ii_det,
171                            jj_det,
172                            sao,
173                            thresh_offdiag,
174                            thresh_zeroov,
175                        ),
176                    )
177                })
178                .collect::<Vec<_>>();
179            ov_elems.sort_by_key(|v| (v.0, v.1, v.2));
180            let mut ov_ii_jj_k =
181                Array3::from_elem((n_det_origins, n_det_origins, order), self.zero());
182            for (ii, jj, k, elem_res) in ov_elems {
183                log::debug!(
184                    "⟨g_{k} Ψ_{ii} | Ψ_{jj}⟩ = ⟨{} Ψ_{ii} | Ψ_{jj}⟩ = {}",
185                    group
186                        .get_index(k)
187                        .map(|g| g.to_string())
188                        .unwrap_or_else(|| format!("g_{k}")),
189                    elem_res
190                        .as_ref()
191                        .map(|v| format!("{v:+.8e}"))
192                        .unwrap_or_else(|err| err.to_string())
193                );
194                ov_ii_jj_k[(ii, jj, k)] = elem_res?;
195            }
196
197            // Populate all matrix elements
198            for v in [
199                (0..order),
200                (0..n_det_origins),
201                (0..order),
202                (0..n_det_origins),
203            ]
204            .into_iter()
205            .multi_cartesian_product()
206            {
207                let i = v[0];
208                let ii = v[1];
209                let j = v[2];
210                let jj = v[3];
211
212                let jinv = ctb
213                    .slice(s![.., j])
214                    .iter()
215                    .position(|&x| x == 0)
216                    .ok_or(format_err!(
217                        "Unable to find the inverse of group element `{j}`."
218                    ))?;
219                let k = ctb[(jinv, i)];
220                log::debug!(
221                    "{}^(-1) = {} ⇒ ⟨g_{i} Ψ_{ii} | g_{j} Ψ_{jj}⟩ = ⟨{} Ψ_{ii} | {} Ψ_{jj}⟩ = ⟨{} Ψ_{ii} | Ψ_{jj}⟩ = {:+8e}",
222                    group
223                        .get_index(j)
224                        .map(|g| g.to_string())
225                        .unwrap_or_else(|| format!("g_{j}")),
226                    group
227                        .get_index(jinv)
228                        .map(|g| g.to_string())
229                        .unwrap_or_else(|| format!("g_{jinv}")),
230                    group
231                        .get_index(i)
232                        .map(|g| g.to_string())
233                        .unwrap_or_else(|| format!("g_{i}")),
234                    group
235                        .get_index(j)
236                        .map(|g| g.to_string())
237                        .unwrap_or_else(|| format!("g_{j}")),
238                    group
239                        .get_index(k)
240                        .map(|g| g.to_string())
241                        .unwrap_or_else(|| format!("g_{k}")),
242                    ov_ii_jj_k[(ii, jj, k)],
243                );
244                mat[(i + ii * order, j + jj * order)] =
245                    self.norm_preserving_scalar_map(jinv, orbit_basis)?(&ov_ii_jj_k[(ii, jj, k)]);
246            }
247        } else {
248            log::debug!(
249                "Cayley table not available or its use not requested. Group closure will not be used for orbit matrix computation."
250            );
251            let orbit_basis_vec = orbit_basis.iter().collect::<Result<Vec<_>, _>>()?;
252            let mut elems = orbit_basis_vec
253                .iter()
254                .enumerate()
255                .cartesian_product(orbit_basis_vec.iter().enumerate())
256                .par_bridge()
257                .map(|((i_ii, i_ii_det), (j_jj, j_jj_det))| {
258                    let i = i_ii.div_euclid(n_det_origins);
259                    let ii = i_ii.rem_euclid(n_det_origins);
260                    let j = j_jj.div_euclid(n_det_origins);
261                    let jj = j_jj.rem_euclid(n_det_origins);
262                    let elem_res = self.calc_matrix_element(
263                        i_ii_det,
264                        j_jj_det,
265                        sao,
266                        thresh_offdiag,
267                        thresh_zeroov,
268                    );
269                    (i, ii, j, jj, elem_res)
270                })
271                .collect::<Vec<_>>();
272            elems.sort_by_key(|v| (v.1, v.0, v.3, v.2));
273            for (i, ii, j, jj, elem_res) in elems {
274                log::debug!(
275                    "⟨g_{i} Ψ_{ii} | g_{j} Ψ_{jj}⟩ = ⟨{} Ψ_{ii} | {} Ψ_{jj}⟩ = {}",
276                    group
277                        .get_index(i)
278                        .map(|g| g.to_string())
279                        .unwrap_or_else(|| format!("g_{i}")),
280                    group
281                        .get_index(j)
282                        .map(|g| g.to_string())
283                        .unwrap_or_else(|| format!("g_{j}")),
284                    elem_res
285                        .as_ref()
286                        .map(|v| format!("{v:+.8e}"))
287                        .unwrap_or_else(|err| err.to_string())
288                );
289                mat[(i + ii * order, j + jj * order)] = elem_res?;
290            }
291        }
292        Ok(mat)
293    }
294}