1use std::collections::HashSet;
4use std::fmt;
5use std::hash::Hash;
6use std::ops::Mul;
7
8use anyhow::{self, Context, ensure, format_err};
9use approx;
10use derive_builder::Builder;
11use itertools::Itertools;
12use log;
13use ndarray::{Array1, Array2, Array3, Axis, Ix2, s};
14use ndarray_linalg::{
15 UPLO,
16 eig::Eig,
17 eigh::Eigh,
18 types::{Lapack, Scalar},
19};
20use num_complex::{Complex, ComplexFloat};
21use num_traits::{Float, ToPrimitive, Zero};
22
23use crate::analysis::{
24 EigenvalueComparisonMode, Orbit, OrbitIterator, Overlap, RepAnalysis, fn_calc_xmat_complex,
25 fn_calc_xmat_real,
26};
27use crate::angmom::spinor_rotation_3d::StructureConstraint;
28use crate::auxiliary::misc::complex_modified_gram_schmidt;
29use crate::chartab::chartab_group::CharacterProperties;
30use crate::chartab::{DecompositionError, SubspaceDecomposable};
31use crate::group::GroupType;
32use crate::io::format::{QSym2Output, log_subtitle, qsym2_output};
33use crate::symmetry::symmetry_element::symmetry_operation::SpecialSymmetryTransformation;
34use crate::symmetry::symmetry_group::SymmetryGroupProperties;
35use crate::symmetry::symmetry_transformation::{SymmetryTransformable, SymmetryTransformationKind};
36use crate::target::determinant::SlaterDeterminant;
37use crate::target::noci::backend::solver::check_complex_matrix_symmetry;
38use crate::target::noci::basis::{Basis, OrbitBasis};
39use crate::target::noci::multideterminant::MultiDeterminant;
40
41impl<'a, T, B, SC> Overlap<T, Ix2> for MultiDeterminant<'a, T, B, SC>
46where
47 T: Lapack
48 + ComplexFloat<Real = <T as Scalar>::Real>
49 + fmt::Debug
50 + Mul<<T as ComplexFloat>::Real, Output = T>,
51 <T as ComplexFloat>::Real: fmt::Debug
52 + approx::RelativeEq<<T as ComplexFloat>::Real>
53 + approx::AbsDiffEq<Epsilon = <T as Scalar>::Real>,
54 SC: StructureConstraint + Hash + Eq + Clone + fmt::Display,
55 B: Basis<SlaterDeterminant<'a, T, SC>> + Clone,
56{
57 fn complex_symmetric(&self) -> bool {
58 self.complex_symmetric
59 }
60
61 fn overlap(
80 &self,
81 other: &Self,
82 metric: Option<&Array2<T>>,
83 metric_h: Option<&Array2<T>>,
84 ) -> Result<T, anyhow::Error> {
85 ensure!(
86 self.structure_constraint() == other.structure_constraint(),
87 "Inconsistent structure constraints between `self` and `other`."
88 );
89 ensure!(
90 self.coefficients.len() == other.coefficients.len(),
91 "Inconsistent numbers of coefficients between `self` and `other`."
92 );
93
94 let s_dets = self.basis.iter().collect::<Result<Vec<_>, _>>()?;
95 let o_dets = other.basis.iter().collect::<Result<Vec<_>, _>>()?;
96 let swx_vec = s_dets
97 .iter()
98 .cartesian_product(o_dets.iter())
99 .map(|(w, x)| w.overlap(x, metric, metric_h))
100 .collect::<Result<Vec<_>, _>>()?;
101 let d = self.coefficients.len();
102 let swx = Array2::from_shape_vec((d, d), swx_vec)?;
103
104 if self.complex_symmetric {
105 Ok(self.coefficients.t().dot(&swx).dot(&other.coefficients))
106 } else {
107 Ok(self
108 .coefficients
109 .t()
110 .mapv(|x| x.conj())
111 .dot(&swx)
112 .dot(&other.coefficients))
113 }
114 }
115
116 fn overlap_definition(&self) -> String {
119 let k = if self.complex_symmetric() { "κ " } else { "" };
120 format!("⟨{k}Ψ_1|Ψ_2⟩ = ∫ [{k}Ψ_1(x^Ne)]* Ψ_2(x^Ne) dx^Ne")
121 }
122}
123
124#[derive(Builder, Clone)]
135pub struct MultiDeterminantSymmetryOrbit<'a, 'g, G, T, B, SC>
136where
137 G: SymmetryGroupProperties,
138 T: ComplexFloat + fmt::Debug + Lapack,
139 SC: StructureConstraint + Hash + Eq + fmt::Display,
140 B: 'a + Basis<SlaterDeterminant<'a, T, SC>> + Clone,
141 MultiDeterminant<'a, T, B, SC>: SymmetryTransformable,
142{
143 group: &'g G,
145
146 origin: &'a MultiDeterminant<'a, T, B, SC>,
148
149 pub(crate) linear_independence_threshold: <T as ComplexFloat>::Real,
151
152 integrality_threshold: <T as ComplexFloat>::Real,
155
156 symmetry_transformation_kind: SymmetryTransformationKind,
159
160 #[builder(setter(skip), default = "None")]
163 smat: Option<Array2<T>>,
164
165 #[builder(setter(skip), default = "None")]
168 pub(crate) smat_eigvals: Option<Array1<T>>,
169
170 #[builder(setter(skip), default = "None")]
175 xmat: Option<Array2<T>>,
176
177 eigenvalue_comparison_mode: EigenvalueComparisonMode,
180}
181
182impl<'a, 'g, G, T, B, SC> MultiDeterminantSymmetryOrbit<'a, 'g, G, T, B, SC>
187where
188 G: SymmetryGroupProperties + Clone,
189 T: ComplexFloat + fmt::Debug + Lapack,
190 SC: StructureConstraint + Hash + Eq + Clone + fmt::Display,
191 B: 'a + Basis<SlaterDeterminant<'a, T, SC>> + Clone,
192 MultiDeterminant<'a, T, B, SC>: SymmetryTransformable,
193{
194 pub fn builder() -> MultiDeterminantSymmetryOrbitBuilder<'a, 'g, G, T, B, SC> {
196 MultiDeterminantSymmetryOrbitBuilder::default()
197 }
198
199 pub fn origin(&self) -> &MultiDeterminant<'a, T, B, SC> {
201 self.origin
202 }
203}
204
205impl<'a, 'g, G, B, SC> MultiDeterminantSymmetryOrbit<'a, 'g, G, f64, B, SC>
206where
207 G: SymmetryGroupProperties,
208 SC: StructureConstraint + Hash + Eq + fmt::Display,
209 B: 'a + Basis<SlaterDeterminant<'a, f64, SC>> + Clone,
210 MultiDeterminant<'a, f64, B, SC>: SymmetryTransformable,
211{
212 fn_calc_xmat_real!(
213 pub calc_xmat
225 );
226}
227
228impl<'a, 'g, G, T, B, SC> MultiDeterminantSymmetryOrbit<'a, 'g, G, Complex<T>, B, SC>
229where
230 G: SymmetryGroupProperties,
231 T: Float + Scalar<Complex = Complex<T>>,
232 Complex<T>: ComplexFloat<Real = T> + Scalar<Real = T, Complex = Complex<T>> + Lapack,
233 SC: StructureConstraint + Hash + Eq + fmt::Display,
234 B: 'a + Basis<SlaterDeterminant<'a, Complex<T>, SC>> + Clone,
235 MultiDeterminant<'a, Complex<T>, B, SC>: SymmetryTransformable + Overlap<Complex<T>, Ix2>,
236{
237 fn_calc_xmat_complex!(
238 pub calc_xmat
250 );
251}
252
253impl<'a, 'g, G, T, B, SC> Orbit<G, MultiDeterminant<'a, T, B, SC>>
262 for MultiDeterminantSymmetryOrbit<'a, 'g, G, T, B, SC>
263where
264 G: SymmetryGroupProperties,
265 T: ComplexFloat + fmt::Debug + Lapack,
266 SC: StructureConstraint + Hash + Eq + fmt::Display,
267 B: 'a + Basis<SlaterDeterminant<'a, T, SC>> + Clone,
268 MultiDeterminant<'a, T, B, SC>: SymmetryTransformable,
269{
270 type OrbitIter = OrbitIterator<'a, G, MultiDeterminant<'a, T, B, SC>>;
271
272 fn group(&self) -> &G {
273 self.group
274 }
275
276 fn origin(&self) -> &MultiDeterminant<'a, T, B, SC> {
277 self.origin
278 }
279
280 fn iter(&self) -> Self::OrbitIter {
281 OrbitIterator::new(
282 self.group,
283 self.origin,
284 match self.symmetry_transformation_kind {
285 SymmetryTransformationKind::Spatial => |op, multidet| {
286 multidet.sym_transform_spatial(op).with_context(|| {
287 format!("Unable to apply `{op}` spatially on the origin multi-determinantal wavefunction")
288 })
289 },
290 SymmetryTransformationKind::SpatialWithSpinTimeReversal => |op, multidet| {
291 multidet.sym_transform_spatial_with_spintimerev(op).with_context(|| {
292 format!("Unable to apply `{op}` spatially (with spin-including time reversal) on the origin multi-determinantal wavefunction")
293 })
294 },
295 SymmetryTransformationKind::Spin => |op, multidet| {
296 multidet.sym_transform_spin(op).with_context(|| {
297 format!("Unable to apply `{op}` spin-wise on the origin multi-determinantal wavefunction")
298 })
299 },
300 SymmetryTransformationKind::SpinSpatial => |op, multidet| {
301 multidet.sym_transform_spin_spatial(op).with_context(|| {
302 format!("Unable to apply `{op}` spin-spatially on the origin multi-determinantal wavefunction")
303 })
304 },
305 },
306 )
307 }
308}
309
310impl<'a, 'g, G, T, B, SC> RepAnalysis<G, MultiDeterminant<'a, T, B, SC>, T, Ix2>
315 for MultiDeterminantSymmetryOrbit<'a, 'g, G, T, B, SC>
316where
317 G: SymmetryGroupProperties,
318 G::CharTab: SubspaceDecomposable<T>,
319 T: Lapack
320 + ComplexFloat<Real = <T as Scalar>::Real>
321 + fmt::Debug
322 + Mul<<T as ComplexFloat>::Real, Output = T>,
323 <T as ComplexFloat>::Real: fmt::Debug
324 + Zero
325 + From<u16>
326 + ToPrimitive
327 + approx::RelativeEq<<T as ComplexFloat>::Real>
328 + approx::AbsDiffEq<Epsilon = <T as Scalar>::Real>,
329 SC: StructureConstraint + Hash + Eq + Clone + fmt::Display,
330 B: 'a + Basis<SlaterDeterminant<'a, T, SC>> + Clone,
331 MultiDeterminant<'a, T, B, SC>: SymmetryTransformable,
332{
333 fn set_smat(&mut self, smat: Array2<T>) {
334 self.smat = Some(smat)
335 }
336
337 fn smat(&self) -> Option<&Array2<T>> {
338 self.smat.as_ref()
339 }
340
341 fn xmat(&self) -> &Array2<T> {
342 self.xmat
343 .as_ref()
344 .expect("Orbit overlap orthogonalisation matrix not found.")
345 }
346
347 fn norm_preserving_scalar_map(&self, i: usize) -> Result<fn(T) -> T, anyhow::Error> {
348 if self.origin.complex_symmetric {
349 Err(format_err!(
350 "`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."
351 ))
352 } else if self
353 .group
354 .get_index(i)
355 .unwrap_or_else(|| panic!("Group operation index `{i}` not found."))
356 .contains_time_reversal()
357 {
358 Ok(ComplexFloat::conj)
359 } else {
360 Ok(|x| x)
361 }
362 }
363
364 fn integrality_threshold(&self) -> <T as ComplexFloat>::Real {
365 self.integrality_threshold
366 }
367
368 fn eigenvalue_comparison_mode(&self) -> &EigenvalueComparisonMode {
369 &self.eigenvalue_comparison_mode
370 }
371
372 fn analyse_rep(
388 &self,
389 ) -> Result<
390 <<G as CharacterProperties>::CharTab as SubspaceDecomposable<T>>::Decomposition,
391 DecompositionError,
392 > {
393 log::debug!("Analysing representation symmetry for a multi-determinantal wavefunction...");
394 let mut nelectrons_set = self
395 .origin()
396 .basis
397 .iter()
398 .map(|det_res| det_res.and_then(|det| {
399 let det_nelectrons_float = det.nelectrons();
400 if approx::relative_eq!(
401 det_nelectrons_float.round(),
402 det_nelectrons_float,
403 epsilon = self.integrality_threshold,
404 max_relative = self.integrality_threshold
405 ) {
406 det_nelectrons_float.round().to_usize().ok_or(format_err!("Unable to convert the number of electrons `{det_nelectrons_float:.7}` to `usize`."))
407 } else {
408 Err(format_err!("Fractional number of electrons encountered: `{det_nelectrons_float:.7}`"))
409 }
410 }))
411 .collect::<Result<HashSet<_>, _>>()
412 .map_err(|err| DecompositionError(err.to_string()))?;
413 if nelectrons_set.len() != 1 {
414 Err(DecompositionError("Symmetry analysis for multi-determinantal wavefunctions with multiple numbers of electrons is not yet supported.".to_string()))
415 } else {
416 let nelectrons = nelectrons_set.drain().next().ok_or(DecompositionError(
417 "Unable to obtain the number of electrons.".to_string(),
418 ))?;
419 let (valid_symmetry, err_str) = if nelectrons.rem_euclid(2) == 0 {
420 (true, String::new())
422 } else {
423 match self.symmetry_transformation_kind {
425 SymmetryTransformationKind::Spatial => (true, String::new()),
426 SymmetryTransformationKind::SpatialWithSpinTimeReversal
427 | SymmetryTransformationKind::Spin
428 | SymmetryTransformationKind::SpinSpatial => {
429 match self.group().group_type() {
430 GroupType::Ordinary(_) => (true, String::new()),
431 GroupType::MagneticGrey(_) | GroupType::MagneticBlackWhite(_) => {
432 (!self.group().unitary_represented(),
433 "Unitary-represented magnetic groups cannot be used for symmetry analysis of odd-electron systems where spin is treated explicitly.".to_string())
434 }
435 }
436 }
437 }
438 };
439
440 if valid_symmetry {
441 let chis = self
442 .calc_characters()
443 .map_err(|err| DecompositionError(err.to_string()))?;
444 log::debug!("Characters calculated.");
445
446 log_subtitle("Multi-determinantal wavefunction orbit characters");
447 qsym2_output!("");
448 self.characters_to_string(&chis, self.integrality_threshold)
449 .log_output_display();
450 qsym2_output!("");
451
452 let res = self.group().character_table().reduce_characters(
453 &chis.iter().map(|(cc, chi)| (cc, *chi)).collect::<Vec<_>>(),
454 self.integrality_threshold(),
455 );
456 log::debug!("Characters reduced.");
457 log::debug!(
458 "Analysing representation symmetry for a multi-determinantal wavefunction... Done."
459 );
460 res
461 } else {
462 Err(DecompositionError(err_str))
463 }
464 }
465 }
466}
467
468impl<'a, 'go, 'g, G, T, SC>
472 MultiDeterminantSymmetryOrbit<
473 'a,
474 'go,
475 G,
476 T,
477 OrbitBasis<'g, G, SlaterDeterminant<'a, T, SC>>,
478 SC,
479 >
480where
481 G: SymmetryGroupProperties + Clone,
482 G::CharTab: SubspaceDecomposable<T>,
483 T: Lapack
484 + ComplexFloat<Real = <T as Scalar>::Real>
485 + fmt::Debug
486 + Mul<<T as ComplexFloat>::Real, Output = T>,
487 <T as ComplexFloat>::Real: fmt::Debug
488 + Zero
489 + From<u16>
490 + ToPrimitive
491 + approx::RelativeEq<<T as ComplexFloat>::Real>
492 + approx::AbsDiffEq<Epsilon = <T as Scalar>::Real>,
493 SC: StructureConstraint + Hash + Eq + Clone + fmt::Display,
494 MultiDeterminant<'a, T, OrbitBasis<'g, G, SlaterDeterminant<'a, T, SC>>, SC>:
495 SymmetryTransformable,
496{
497 pub(crate) fn calc_smat_optimised(
514 &mut self,
515 metric: Option<&Array2<T>>,
516 metric_h: Option<&Array2<T>>,
517 use_cayley_table: bool,
518 ) -> Result<&mut Self, anyhow::Error> {
519 ensure!(
520 self.group.name() == self.origin.basis.group().name(),
521 "Multi-determinantal wavefunction orbit-generating group does not match symmetry analysis group."
522 );
523
524 if let (Some(ctb), true) = (self.group().cayley_table(), use_cayley_table) {
525 log::debug!(
526 "Cayley table available. Group closure will be used to speed up overlap matrix computation."
527 );
528
529 let order = self.group.order();
530 let mut smat = Array2::<T>::zeros((order, order));
531 let multidet_0 = self.origin();
532 let det_origins = multidet_0.basis.origins();
533 let n_det_origins = det_origins.len();
534
535 let detov_kpwx_vec = multidet_0
536 .basis
537 .iter()
538 .collect::<Result<Vec<_>, _>>()?
539 .into_iter()
540 .cartesian_product(det_origins.iter())
541 .map(|(w, x)| w.overlap(x, metric, metric_h))
542 .collect::<Result<Vec<_>, _>>()?;
543 let detov_kpwx =
544 Array3::from_shape_vec((order, n_det_origins, n_det_origins), detov_kpwx_vec)?;
545 let ovs = (0..order)
546 .map(|k| {
547 [
548 (0..order),
549 (0..order),
550 (0..n_det_origins),
551 (0..n_det_origins),
552 ]
553 .into_iter()
554 .multi_cartesian_product()
555 .try_fold(T::zero(), |acc, v| {
556 let ip = v[0];
557 let jp = v[1];
558 let w = v[2];
559 let x = v[3];
560 let aipw = multidet_0.coefficients[ip * n_det_origins + w];
561 let ajpx = multidet_0.coefficients[jp * n_det_origins + x];
562
563 let jpinv = ctb.slice(s![.., jp]).iter().position(|&x| x == 0).ok_or(
564 format_err!("Unable to find the inverse of group element `{jp}`."),
565 )?;
566 let kp = ctb[(jpinv, ctb[(k, ip)])];
567 let ukipwjpx = self.norm_preserving_scalar_map(jp)?(detov_kpwx[(kp, w, x)]);
568
569 Ok::<_, anyhow::Error>(
570 acc + self.norm_preserving_scalar_map(k)?(aipw.conj())
571 * ukipwjpx
572 * ajpx,
573 )
574 })
575 })
576 .collect::<Result<Vec<_>, _>>()?;
577 for (i, j) in (0..order).cartesian_product(0..order) {
578 let jinv = ctb
579 .slice(s![.., j])
580 .iter()
581 .position(|&x| x == 0)
582 .ok_or(format_err!(
583 "Unable to find the inverse of group element `{j}`."
584 ))?;
585 let jinv_i = ctb[(jinv, i)];
586 smat[(i, j)] = self.norm_preserving_scalar_map(jinv)?(ovs[jinv_i]);
587 }
588 if self.origin().complex_symmetric() {
589 let _ = check_complex_matrix_symmetry(
590 &smat.view(),
591 true,
592 self.linear_independence_threshold,
593 "Orbit overlap",
594 "S_orbit",
595 );
596 self.set_smat(
597 (smat.clone() + smat.t().to_owned()).mapv(|x| x / (T::one() + T::one())),
598 )
599 } else {
600 let _ = check_complex_matrix_symmetry(
601 &smat.view(),
602 false,
603 self.linear_independence_threshold,
604 "Orbit overlap",
605 "S_orbit",
606 );
607 self.set_smat(
608 (smat.clone() + smat.t().to_owned().mapv(|x| x.conj()))
609 .mapv(|x| x / (T::one() + T::one())),
610 )
611 }
612 Ok(self)
613 } else {
614 self.calc_smat(metric, metric_h, use_cayley_table)
615 }
616 }
617}