qsym2/target/noci/backend/matelem/
mod.rs1use 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 type MatrixElement;
30
31 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 fn t(x: &Self::MatrixElement) -> Self::MatrixElement;
59
60 fn conj(x: &Self::MatrixElement) -> Self::MatrixElement;
62
63 fn zero(&self) -> Self::MatrixElement;
65
66 #[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 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 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 .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 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}