1use std::fmt;
4use std::ops::Mul;
5
6use anyhow::{self, Context, ensure, format_err};
7use approx;
8use derive_builder::Builder;
9use itertools::{Itertools, izip};
10use log;
11use ndarray::{Array1, Array2, Axis, Ix2};
12use ndarray_linalg::{
13 UPLO,
14 eig::Eig,
15 eigh::Eigh,
16 solve::Determinant,
17 types::{Lapack, Scalar},
18};
19use num_complex::{Complex, ComplexFloat};
20use num_traits::{Float, ToPrimitive, Zero};
21
22use crate::analysis::{
23 EigenvalueComparisonMode, Orbit, OrbitIterator, Overlap, RepAnalysis, fn_calc_xmat_complex,
24 fn_calc_xmat_real,
25};
26use crate::angmom::spinor_rotation_3d::StructureConstraint;
27use crate::auxiliary::misc::complex_modified_gram_schmidt;
28use crate::chartab::chartab_group::CharacterProperties;
29use crate::chartab::{DecompositionError, SubspaceDecomposable};
30use crate::group::GroupType;
31use crate::io::format::{QSym2Output, log_subtitle, qsym2_output};
32use crate::symmetry::symmetry_element::symmetry_operation::SpecialSymmetryTransformation;
33use crate::symmetry::symmetry_group::SymmetryGroupProperties;
34use crate::symmetry::symmetry_transformation::{SymmetryTransformable, SymmetryTransformationKind};
35use crate::target::determinant::SlaterDeterminant;
36
37impl<'a, T, SC> Overlap<T, Ix2> for SlaterDeterminant<'a, T, SC>
42where
43 T: Lapack
44 + ComplexFloat<Real = <T as Scalar>::Real>
45 + fmt::Debug
46 + Mul<<T as ComplexFloat>::Real, Output = T>,
47 <T as ComplexFloat>::Real: fmt::Debug
48 + approx::RelativeEq<<T as ComplexFloat>::Real>
49 + approx::AbsDiffEq<Epsilon = <T as Scalar>::Real>,
50 SC: StructureConstraint + Eq + fmt::Display,
51{
52 fn complex_symmetric(&self) -> bool {
53 self.complex_symmetric
54 }
55
56 fn overlap(
75 &self,
76 other: &Self,
77 metric: Option<&Array2<T>>,
78 metric_h: Option<&Array2<T>>,
79 ) -> Result<T, anyhow::Error> {
80 ensure!(
81 self.structure_constraint == other.structure_constraint,
82 "Inconsistent structure constraints between `self` and `other`."
83 );
84 ensure!(
85 self.coefficients.len() == other.coefficients.len(),
86 "Inconsistent numbers of coefficient matrices between `self` and `other`."
87 );
88 ensure!(
89 self.baos == other.baos,
90 "Inconsistent basis angular order between `self` and `other`."
91 );
92 ensure!(
93 self.complex_symmetric == other.complex_symmetric,
94 "Inconsistent `complex_symmetric` between `self` and `other`."
95 );
96
97 let thresh = Float::sqrt(self.threshold * other.threshold);
98 ensure!(
99 self.occupations
100 .iter()
101 .chain(other.occupations.iter())
102 .all(|occs| occs.iter().all(|&occ| approx::relative_eq!(
103 occ,
104 occ.round(),
105 epsilon = thresh,
106 max_relative = thresh
107 ))),
108 "Overlaps between determinants with fractional occupation numbers are currently not supported."
109 );
110
111 let sao = metric.ok_or_else(|| format_err!("No atomic-orbital metric found."))?;
112 let sao_h = metric_h.unwrap_or(sao);
113
114 let ov = izip!(
115 &self.coefficients,
116 &self.occupations,
117 &other.coefficients,
118 &other.occupations
119 )
120 .map(|(cw, occw, cx, occx)| {
121 let nonzero_occ_w = occw.iter().positions(|&occ| occ > thresh).collect_vec();
122 let cw_o = cw.select(Axis(1), &nonzero_occ_w);
123 let nonzero_occ_x = occx.iter().positions(|&occ| occ > thresh).collect_vec();
124 let cx_o = cx.select(Axis(1), &nonzero_occ_x);
125
126 let mo_ov_mat = if self.complex_symmetric() {
127 match (self.complex_conjugated, other.complex_conjugated) {
128 (false, false) => cw_o.t().dot(sao_h).dot(&cx_o),
129 (true, false) => cw_o.t().dot(sao).dot(&cx_o),
130 (false, true) => cx_o.t().dot(sao).dot(&cw_o),
131 (true, true) => cw_o.t().dot(&sao_h.t()).dot(&cx_o),
132 }
133 } else {
134 match (self.complex_conjugated, other.complex_conjugated) {
135 (false, false) => cw_o.t().mapv(|x| x.conj()).dot(sao).dot(&cx_o),
136 (true, false) => cw_o.t().mapv(|x| x.conj()).dot(sao_h).dot(&cx_o),
137 (false, true) => cx_o
138 .t()
139 .mapv(|x| x.conj())
140 .dot(sao_h)
141 .dot(&cw_o)
142 .mapv(|x| x.conj()),
143 (true, true) => cw_o.t().mapv(|x| x.conj()).dot(&sao.t()).dot(&cx_o),
144 }
145 };
146 mo_ov_mat
147 .det()
148 .expect("The determinant of the MO overlap matrix could not be found.")
149 })
150 .fold(T::one(), |acc, x| acc * x);
151
152 let implicit_factor = self.structure_constraint.implicit_factor()?;
153 if implicit_factor > 1 {
154 let p_i32 = i32::try_from(implicit_factor)?;
155 Ok(ComplexFloat::powi(ov, p_i32))
156 } else {
157 Ok(ov)
158 }
159 }
160
161 fn overlap_definition(&self) -> String {
163 let k = if self.complex_symmetric() { "κ " } else { "" };
164 format!("⟨{k}Ψ_1|Ψ_2⟩ = ∫ [{k}Ψ_1(x^Ne)]* Ψ_2(x^Ne) dx^Ne")
165 }
166}
167
168#[derive(Builder, Clone)]
179pub struct SlaterDeterminantSymmetryOrbit<'g, 'a, G, T, SC>
180where
181 G: SymmetryGroupProperties,
182 T: ComplexFloat + fmt::Debug + Lapack,
183 SC: StructureConstraint + fmt::Display,
184 SlaterDeterminant<'a, T, SC>: SymmetryTransformable,
185{
186 group: &'g G,
188
189 origin: &'a SlaterDeterminant<'a, T, SC>,
191
192 linear_independence_threshold: <T as ComplexFloat>::Real,
194
195 orbit_overlap_symmetry_threshold: <T as ComplexFloat>::Real,
197
198 integrality_threshold: <T as ComplexFloat>::Real,
201
202 symmetry_transformation_kind: SymmetryTransformationKind,
205
206 #[builder(setter(skip), default = "None")]
208 smat: Option<Array2<T>>,
209
210 #[builder(setter(skip), default = "None")]
213 pub(crate) smat_eigvals: Option<Array1<T>>,
214
215 #[builder(setter(skip), default = "None")]
220 xmat: Option<Array2<T>>,
221
222 eigenvalue_comparison_mode: EigenvalueComparisonMode,
225}
226
227impl<'g, 'a, G, T, SC> SlaterDeterminantSymmetryOrbit<'g, 'a, G, T, SC>
232where
233 G: SymmetryGroupProperties + Clone,
234 T: ComplexFloat + fmt::Debug + Lapack,
235 SC: StructureConstraint + Clone + fmt::Display,
236 SlaterDeterminant<'a, T, SC>: SymmetryTransformable,
237{
238 pub fn builder() -> SlaterDeterminantSymmetryOrbitBuilder<'g, 'a, G, T, SC> {
240 SlaterDeterminantSymmetryOrbitBuilder::default()
241 }
242}
243
244impl<'g, 'a, G, T, SC> SlaterDeterminantSymmetryOrbit<'g, 'a, G, T, SC>
245where
246 G: SymmetryGroupProperties,
247 T: ComplexFloat + fmt::Debug + Lapack,
248 SC: StructureConstraint + fmt::Display,
249 SlaterDeterminant<'a, T, SC>: SymmetryTransformable,
250{
251 #[allow(clippy::type_complexity)]
252 pub fn action(
253 &self,
254 ) -> fn(
255 &G::GroupElement,
256 &SlaterDeterminant<'a, T, SC>,
257 ) -> Result<SlaterDeterminant<'a, T, SC>, anyhow::Error> {
258 match self.symmetry_transformation_kind {
259 SymmetryTransformationKind::Spatial => |op, det| {
260 det.sym_transform_spatial(op).with_context(|| {
261 format!("Unable to apply `{op}` spatially on the origin determinant")
262 })
263 },
264 SymmetryTransformationKind::SpatialWithSpinTimeReversal => |op, det| {
265 det.sym_transform_spatial_with_spintimerev(op).with_context(|| {
266 format!("Unable to apply `{op}` spatially (with spin-including time reversal) on the origin determinant")
267 })
268 },
269 SymmetryTransformationKind::Spin => |op, det| {
270 det.sym_transform_spin(op).with_context(|| {
271 format!("Unable to apply `{op}` spin-wise on the origin determinant")
272 })
273 },
274 SymmetryTransformationKind::SpinSpatial => |op, det| {
275 det.sym_transform_spin_spatial(op).with_context(|| {
276 format!("Unable to apply `{op}` spin-spatially on the origin determinant")
277 })
278 },
279 }
280 }
281}
282
283impl<'g, 'a, G, SC> SlaterDeterminantSymmetryOrbit<'g, 'a, G, f64, SC>
284where
285 G: SymmetryGroupProperties,
286 SC: StructureConstraint + fmt::Display,
287 SlaterDeterminant<'a, f64, SC>: SymmetryTransformable,
288{
289 fn_calc_xmat_real!(
290 pub calc_xmat
302 );
303}
304
305impl<'g, 'a, G, T, SC> SlaterDeterminantSymmetryOrbit<'g, 'a, G, Complex<T>, SC>
306where
307 G: SymmetryGroupProperties,
308 T: Float + Scalar<Complex = Complex<T>>,
309 Complex<T>: ComplexFloat<Real = T> + Scalar<Real = T, Complex = Complex<T>> + Lapack,
310 SC: StructureConstraint + fmt::Display,
311 SlaterDeterminant<'a, Complex<T>, SC>: SymmetryTransformable + Overlap<Complex<T>, Ix2>,
312{
313 fn_calc_xmat_complex!(
314 pub calc_xmat
326 );
327}
328
329impl<'g, 'a, G, T, SC> Orbit<G, SlaterDeterminant<'a, T, SC>>
338 for SlaterDeterminantSymmetryOrbit<'g, 'a, G, T, SC>
339where
340 G: SymmetryGroupProperties,
341 T: ComplexFloat + fmt::Debug + Lapack,
342 SC: StructureConstraint + fmt::Display,
343 SlaterDeterminant<'a, T, SC>: SymmetryTransformable,
344{
345 type OrbitIter = OrbitIterator<'a, G, SlaterDeterminant<'a, T, SC>>;
346
347 fn group(&self) -> &G {
348 self.group
349 }
350
351 fn origin(&self) -> &SlaterDeterminant<'a, T, SC> {
352 self.origin
353 }
354
355 fn iter(&self) -> Self::OrbitIter {
356 OrbitIterator::new(
357 self.group,
358 self.origin,
359 self.action(),
360 )
383 }
384}
385
386impl<'g, 'a, G, T, SC> RepAnalysis<G, SlaterDeterminant<'a, T, SC>, T, Ix2>
391 for SlaterDeterminantSymmetryOrbit<'g, 'a, G, T, SC>
392where
393 G: SymmetryGroupProperties,
394 G::CharTab: SubspaceDecomposable<T>,
395 T: Lapack
396 + ComplexFloat<Real = <T as Scalar>::Real>
397 + fmt::Debug
398 + Mul<<T as ComplexFloat>::Real, Output = T>,
399 <T as ComplexFloat>::Real: fmt::Debug
400 + Zero
401 + From<u16>
402 + ToPrimitive
403 + approx::RelativeEq<<T as ComplexFloat>::Real>
404 + approx::AbsDiffEq<Epsilon = <T as Scalar>::Real>,
405 SC: StructureConstraint + Eq + fmt::Display,
406 SlaterDeterminant<'a, T, SC>: SymmetryTransformable,
407{
408 fn set_smat(&mut self, smat: Array2<T>) {
409 self.smat = Some(smat)
410 }
411
412 fn smat(&self) -> Option<&Array2<T>> {
413 self.smat.as_ref()
414 }
415
416 fn xmat(&self) -> &Array2<T> {
417 self.xmat
418 .as_ref()
419 .expect("Orbit overlap orthogonalisation matrix not found.")
420 }
421
422 fn norm_preserving_scalar_map(&self, i: usize) -> Result<fn(T) -> T, anyhow::Error> {
423 if self.origin.complex_symmetric {
424 Err(format_err!(
425 "`norm_preserving_scalar_map` is currently not implemented for complex-symmetric overlaps. This thus precludes the use of the Cayley table to speed up the computation of the orbit overlap matrix."
426 ))
427 } else if self
428 .group
429 .get_index(i)
430 .unwrap_or_else(|| panic!("Group operation index `{i}` not found."))
431 .contains_time_reversal()
432 {
433 Ok(ComplexFloat::conj)
434 } else {
435 Ok(|x| x)
436 }
437 }
438
439 fn orbit_overlap_symmetry_threshold(&self) -> <T as ComplexFloat>::Real {
440 self.orbit_overlap_symmetry_threshold
441 }
442
443 fn integrality_threshold(&self) -> <T as ComplexFloat>::Real {
444 self.integrality_threshold
445 }
446
447 fn eigenvalue_comparison_mode(&self) -> &EigenvalueComparisonMode {
448 &self.eigenvalue_comparison_mode
449 }
450
451 fn analyse_rep(
467 &self,
468 ) -> Result<
469 <<G as CharacterProperties>::CharTab as SubspaceDecomposable<T>>::Decomposition,
470 DecompositionError,
471 > {
472 log::debug!("Analysing representation symmetry for a Slater determinant...");
473 let nelectrons_float = self.origin().nelectrons();
474 if approx::relative_eq!(
475 nelectrons_float.round(),
476 nelectrons_float,
477 epsilon = self.integrality_threshold,
478 max_relative = self.integrality_threshold
479 ) {
480 let nelectrons_usize = nelectrons_float.round().to_usize().unwrap_or_else(|| {
481 panic!(
482 "Unable to convert the number of electrons `{nelectrons_float:.7}` to `usize`."
483 );
484 });
485 let (valid_symmetry, err_str) = if nelectrons_usize.rem_euclid(2) == 0 {
486 (true, String::new())
488 } else {
489 match self.symmetry_transformation_kind {
491 SymmetryTransformationKind::Spatial => (true, String::new()),
492 SymmetryTransformationKind::SpatialWithSpinTimeReversal
493 | SymmetryTransformationKind::Spin
494 | SymmetryTransformationKind::SpinSpatial => {
495 match self.group().group_type() {
496 GroupType::Ordinary(_) => (true, String::new()),
497 GroupType::MagneticGrey(_) | GroupType::MagneticBlackWhite(_) => {
498 (!self.group().unitary_represented(),
499 "Unitary-represented magnetic groups cannot be used for symmetry analysis of odd-electron systems where spin is treated explicitly.".to_string())
500 }
501 }
502 }
503 }
504 };
505
506 if valid_symmetry {
507 let chis = self
508 .calc_characters()
509 .map_err(|err| DecompositionError(err.to_string()))?;
510 log::debug!("Characters calculated.");
511
512 log_subtitle("Determinant orbit characters");
513 qsym2_output!("");
514 self.characters_to_string(&chis, self.integrality_threshold)
515 .log_output_display();
516 qsym2_output!("");
517
518 let res = self.group().character_table().reduce_characters(
519 &chis.iter().map(|(cc, chi)| (cc, *chi)).collect::<Vec<_>>(),
520 self.integrality_threshold(),
521 );
522 log::debug!("Characters reduced.");
523 log::debug!("Analysing representation symmetry for a Slater determinant... Done.");
524 res
525 } else {
526 Err(DecompositionError(err_str))
527 }
528 } else {
529 Err(DecompositionError(format!(
530 "Symmetry analysis for determinant with non-integer number of electrons `{nelectrons_float:.7}` (threshold = {:.3e}) not supported.",
531 self.integrality_threshold
532 )))
533 }
534 }
535}