1use std::cmp::Ordering;
4use std::fmt;
5use std::ops::Mul;
6
7use anyhow::{self, Context, format_err};
8use duplicate::duplicate_item;
9use itertools::Itertools;
10use log;
11use ndarray::{Array, Array1, Array2, Axis, Dimension, Ix0, Ix2, s};
12use ndarray_einsum::*;
13use ndarray_linalg::{solve::Inverse, types::Lapack};
14use num_complex::{Complex, ComplexFloat};
15use num_traits::{ToPrimitive, Zero};
16#[cfg(feature = "python")]
17use pyo3::prelude::*;
18use rayon::prelude::*;
19use serde::{Deserialize, Serialize};
20
21use crate::auxiliary::misc::check_complex_matrix_symmetry;
22use crate::chartab::chartab_group::CharacterProperties;
23use crate::chartab::{CharacterTable, DecompositionError, SubspaceDecomposable};
24use crate::group::{GroupProperties, class::ClassProperties};
25use crate::io::format::{log_subtitle, qsym2_output};
26use crate::symmetry::symmetry_group::UnitaryRepresentedSymmetryGroup;
27
28pub trait Overlap<T, D>
41where
42 T: ComplexFloat + fmt::Debug + Lapack,
43 D: Dimension,
44{
45 fn complex_symmetric(&self) -> bool;
48
49 fn overlap(
53 &self,
54 other: &Self,
55 metric: Option<&Array<T, D>>,
56 metric_h: Option<&Array<T, D>>,
57 ) -> Result<T, anyhow::Error>;
58
59 fn overlap_definition(&self) -> String;
62}
63
64pub struct OrbitIterator<'a, G, I>
74where
75 G: GroupProperties,
76{
77 group_iter: <<G as GroupProperties>::ElementCollection as IntoIterator>::IntoIter,
80
81 origin: &'a I,
83
84 action: fn(&G::GroupElement, &I) -> Result<I, anyhow::Error>,
86}
87
88impl<'a, G, I> OrbitIterator<'a, G, I>
89where
90 G: GroupProperties,
91{
92 pub fn new(
104 group: &G,
105 origin: &'a I,
106 action: fn(&G::GroupElement, &I) -> Result<I, anyhow::Error>,
107 ) -> Self {
108 Self {
109 group_iter: group.elements().clone().into_iter(),
110 origin,
111 action,
112 }
113 }
114}
115
116impl<'a, G, I> Iterator for OrbitIterator<'a, G, I>
117where
118 G: GroupProperties,
119{
120 type Item = Result<I, anyhow::Error>;
121
122 fn next(&mut self) -> Option<Self::Item> {
123 self.group_iter
124 .next()
125 .map(|op| (self.action)(&op, self.origin))
126 }
127}
128
129pub trait Orbit<G, I>
135where
136 G: GroupProperties,
137{
138 type OrbitIter: Iterator<Item = Result<I, anyhow::Error>>;
140
141 fn group(&self) -> &G;
143
144 fn origin(&self) -> &I;
146
147 fn iter(&self) -> Self::OrbitIter;
149}
150
151#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Default)]
162#[cfg_attr(feature = "python", pyclass(eq, eq_int))]
163pub enum EigenvalueComparisonMode {
164 Real,
166
167 #[default]
169 Modulus,
170}
171
172impl fmt::Display for EigenvalueComparisonMode {
173 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
174 match self {
175 EigenvalueComparisonMode::Real => write!(f, "Real part"),
176 EigenvalueComparisonMode::Modulus => write!(f, "Modulus"),
177 }
178 }
179}
180
181pub trait RepAnalysis<G, I, T, D>: Orbit<G, I>
192where
193 T: ComplexFloat + Lapack + fmt::Debug,
194 <T as ComplexFloat>::Real: ToPrimitive + fmt::LowerExp + fmt::Display,
195 G: GroupProperties + ClassProperties + CharacterProperties,
196 G::GroupElement: fmt::Display,
197 G::CharTab: SubspaceDecomposable<T>,
198 D: Dimension,
199 I: Overlap<T, D> + Clone,
200 Self::OrbitIter: Iterator<Item = Result<I, anyhow::Error>>,
201{
202 fn set_smat(&mut self, smat: Array2<T>);
212
213 #[must_use]
215 fn smat(&self) -> Option<&Array2<T>>;
216
217 #[must_use]
236 fn xmat(&self) -> &Array2<T>;
237
238 fn norm_preserving_scalar_map(&self, i: usize) -> Result<fn(T) -> T, anyhow::Error>;
256
257 #[must_use]
260 fn integrality_threshold(&self) -> <T as ComplexFloat>::Real;
261
262 #[must_use]
264 fn orbit_overlap_symmetry_threshold(&self) -> <T as ComplexFloat>::Real;
265
266 #[must_use]
269 fn eigenvalue_comparison_mode(&self) -> &EigenvalueComparisonMode;
270
271 fn calc_smat(
286 &mut self,
287 metric: Option<&Array<T, D>>,
288 metric_h: Option<&Array<T, D>>,
289 use_cayley_table: bool,
290 ) -> Result<&mut Self, anyhow::Error> {
291 let order = self.group().order();
292 let mut smat = Array2::<T>::zeros((order, order));
293 let item_0 = self.origin();
294 if let (Some(ctb), true) = (self.group().cayley_table(), use_cayley_table) {
295 log::debug!(
296 "Cayley table available. Group closure will be used to speed up overlap matrix computation."
297 );
298 let ovs = self
299 .iter()
300 .map(|item_res| {
301 let item = item_res?;
302 item.overlap(item_0, metric, metric_h)
303 })
304 .collect::<Result<Vec<_>, _>>()?;
305 for (i, j) in (0..order).cartesian_product(0..order) {
306 let jinv = ctb
307 .slice(s![.., j])
308 .iter()
309 .position(|&x| x == 0)
310 .ok_or(format_err!(
311 "Unable to find the inverse of group element `{j}`."
312 ))?;
313 let jinv_i = ctb[(jinv, i)];
314 smat[(i, j)] = self.norm_preserving_scalar_map(jinv)?(ovs[jinv_i]);
315 }
316 } else {
317 log::debug!(
318 "Cayley table not available or the use of Cayley table not requested. Overlap matrix will be constructed without group-closure speed-up."
319 );
320 for pair in self
321 .iter()
322 .map(|item_res| item_res.map_err(|err| err.to_string()))
323 .enumerate()
324 .combinations_with_replacement(2)
325 {
326 let (w, item_w_res) = &pair[0];
327 let (x, item_x_res) = &pair[1];
328 let item_w = item_w_res
329 .as_ref()
330 .map_err(|err| format_err!(err.clone()))
331 .with_context(|| "One of the items in the orbit is not available")?;
332 let item_x = item_x_res
333 .as_ref()
334 .map_err(|err| format_err!(err.clone()))
335 .with_context(|| "One of the items in the orbit is not available")?;
336 smat[(*w, *x)] = item_w.overlap(item_x, metric, metric_h).map_err(|err| {
337 log::warn!("{err}");
338 log::warn!(
339 "Unable to calculate the overlap between items `{w}` and `{x}` in the orbit."
340 );
341 err
342 })?;
343 if *w != *x {
344 smat[(*x, *w)] = item_x.overlap(item_w, metric, metric_h).map_err(|err| {
345 log::warn!("{err}");
346 log::warn!(
347 "Unable to calculate the overlap between items `{x}` and `{w}` in the orbit."
348 );
349 err
350 })?;
351 }
352 }
353 }
354 if self.origin().complex_symmetric() {
355 let _ = check_complex_matrix_symmetry(
356 &smat.view(),
357 true,
358 self.orbit_overlap_symmetry_threshold(),
359 "Orbit overlap",
360 "S_orbit",
361 );
362 self.set_smat((smat.clone() + smat.t().to_owned()).mapv(|x| x / (T::one() + T::one())))
363 } else {
364 let _ = check_complex_matrix_symmetry(
365 &smat.view(),
366 false,
367 self.orbit_overlap_symmetry_threshold(),
368 "Orbit overlap",
369 "S_orbit",
370 );
371 self.set_smat(
372 (smat.clone() + smat.t().to_owned().mapv(|x| x.conj()))
373 .mapv(|x| x / (T::one() + T::one())),
374 )
375 }
376 Ok(self)
377 }
378
379 fn normalise_smat(&mut self) -> Result<&mut Self, anyhow::Error> {
387 let smat = self
388 .smat()
389 .ok_or(format_err!("No orbit overlap matrix to normalise."))?;
390 let norm = smat.diag().mapv(|x| <T as ComplexFloat>::sqrt(x));
391 let nspatial = norm.len();
392 let norm_col = norm
393 .clone()
394 .into_shape_with_order([nspatial, 1])
395 .map_err(|err| format_err!(err))?;
396 let norm_row = norm
397 .into_shape_with_order([1, nspatial])
398 .map_err(|err| format_err!(err))?;
399 let norm_mat = norm_col.dot(&norm_row);
400 let normalised_smat = smat / norm_mat;
401 self.set_smat(normalised_smat);
402 Ok(self)
403 }
404
405 fn calc_tmat(&self, op: &G::GroupElement) -> Result<Array2<T>, anyhow::Error> {
427 let ctb = self
428 .group()
429 .cayley_table()
430 .expect("The Cayley table for the group cannot be found.");
431 let i = self.group().get_index_of(op).unwrap_or_else(|| {
432 panic!("Unable to retrieve the index of element `{op}` in the group.")
433 });
434 let ix = ctb.slice(s![i, ..]).iter().cloned().collect::<Vec<_>>();
435 let twx = self
436 .smat()
437 .ok_or(format_err!("No orbit overlap matrix found."))?
438 .select(Axis(1), &ix);
439 Ok(twx)
440 }
441
442 fn calc_dmat(&self, op: &G::GroupElement) -> Result<Array2<T>, anyhow::Error> {
462 let complex_symmetric = self.origin().complex_symmetric();
463 let xmath = if complex_symmetric {
464 self.xmat().t().to_owned()
465 } else {
466 self.xmat().t().mapv(|x| x.conj())
467 };
468 let smattilde = xmath
469 .dot(
470 self.smat()
471 .ok_or(format_err!("No orbit overlap matrix found."))?,
472 )
473 .dot(self.xmat());
474 let smattilde_inv = smattilde
475 .inv()
476 .expect("The inverse of S~ could not be found.");
477 einsum(
478 "ij,jk,kl,lm->im",
479 &[&smattilde_inv, &xmath, &self.calc_tmat(op)?, self.xmat()],
480 )
481 .map_err(|err| format_err!(err))
482 .with_context(|| "Unable to compute the matrix product [(S~)^(-1) X† T X].")?
483 .into_dimensionality::<Ix2>()
484 .map_err(|err| format_err!(err))
485 .with_context(
486 || "Unable to convert the matrix product [(S~)^(-1) X† T X] to two dimensions.",
487 )
488 }
489
490 fn calc_character(&self, op: &G::GroupElement) -> Result<T, anyhow::Error> {
503 let complex_symmetric = self.origin().complex_symmetric();
504 let xmath = if complex_symmetric {
505 self.xmat().t().to_owned()
506 } else {
507 self.xmat().t().mapv(|x| x.conj())
508 };
509 let smattilde = xmath
510 .dot(
511 self.smat()
512 .ok_or(format_err!("No orbit overlap matrix found."))?,
513 )
514 .dot(self.xmat());
515 let smattilde_inv = smattilde
516 .inv()
517 .expect("The inverse of S~ could not be found.");
518 let chi = einsum(
519 "ij,jk,kl,li",
520 &[&smattilde_inv, &xmath, &self.calc_tmat(op)?, self.xmat()],
521 )
522 .map_err(|err| format_err!(err))
523 .with_context(|| "Unable to compute the trace of the matrix product [(S~)^(-1) X† T X].")?
524 .into_dimensionality::<Ix0>()
525 .map_err(|err| format_err!(err))
526 .with_context(|| "Unable to convert the trace of the matrix product [(S~)^(-1) X† T X] to zero dimensions.")?;
527 chi.into_iter().next().ok_or(format_err!(
528 "Unable to extract the character from the representation matrix."
529 ))
530 }
531
532 fn calc_characters(
541 &self,
542 ) -> Result<Vec<(<G as ClassProperties>::ClassSymbol, T)>, anyhow::Error> {
543 let complex_symmetric = self.origin().complex_symmetric();
544 let xmath = if complex_symmetric {
545 self.xmat().t().to_owned()
546 } else {
547 self.xmat().t().mapv(|x| x.conj())
548 };
549 let smattilde = xmath
550 .dot(
551 self.smat()
552 .ok_or(format_err!("No orbit overlap matrix found."))?,
553 )
554 .dot(self.xmat());
555 let smattilde_inv = smattilde
556 .inv()
557 .expect("The inverse of S~ could not be found.");
558 (0..self.group().class_number()).map(|cc_i| {
559 let cc = self.group().get_cc_symbol_of_index(cc_i).unwrap();
560 let op = self.group().get_cc_transversal(cc_i).unwrap();
561 let chi = einsum(
562 "ij,jk,kl,li",
563 &[&smattilde_inv, &xmath, &self.calc_tmat(&op)?, self.xmat()],
564 )
565 .map_err(|err| format_err!(err))
566 .with_context(|| "Unable to compute the trace of the matrix product [(S~)^(-1) X† T X].")?
567 .into_dimensionality::<Ix0>()
568 .map_err(|err| format_err!(err))
569 .with_context(|| "Unable to convert the trace of the matrix product [(S~)^(-1) X† T X] to zero dimensions.")?;
570 let chi_val = chi.into_iter().next().ok_or(format_err!(
571 "Unable to extract the character from the representation matrix."
572 ))?;
573 Ok((cc, chi_val))
574 }).collect::<Result<Vec<_>, _>>()
575 }
576
577 fn analyse_rep(
589 &self,
590 ) -> Result<
591 <<G as CharacterProperties>::CharTab as SubspaceDecomposable<T>>::Decomposition,
592 DecompositionError,
593 > {
594 let chis = self
595 .calc_characters()
596 .map_err(|err| DecompositionError(err.to_string()))?;
597 self.group().character_table().reduce_characters(
598 &chis.iter().map(|(cc, chi)| (cc, *chi)).collect::<Vec<_>>(),
599 self.integrality_threshold(),
600 )
601 }
602
603 fn characters_to_string(
614 &self,
615 chis: &[(<G as ClassProperties>::ClassSymbol, T)],
616 integrality_threshold: <T as ComplexFloat>::Real,
617 ) -> String
618 where
619 T: ComplexFloat + Lapack + fmt::Debug,
620 <T as ComplexFloat>::Real: ToPrimitive,
621 {
622 let ndigits = (-integrality_threshold.log10()).to_usize().unwrap_or(6) + 1;
623 let (ccs, characters): (Vec<_>, Vec<_>) = chis
624 .iter()
625 .map(|(cc, chi)| (cc.to_string(), format!("{chi:+.ndigits$}")))
626 .unzip();
627 let cc_width = ccs
628 .iter()
629 .map(|cc| cc.chars().count())
630 .max()
631 .unwrap_or(5)
632 .max(5);
633 let characters_width = characters
634 .iter()
635 .map(|chi| chi.chars().count())
636 .max()
637 .unwrap_or(9)
638 .max(9);
639
640 let divider = "┈".repeat(cc_width + characters_width + 4);
641 let header = format!(" {:<cc_width$} {:<}", "Class", "Character");
642 let body = Itertools::intersperse(
643 chis.iter()
644 .map(|(cc, chi)| format!(" {:<cc_width$} {:<+.ndigits$}", cc.to_string(), chi)),
645 "\n".to_string(),
646 )
647 .collect::<String>();
648
649 Itertools::intersperse(
650 [divider.clone(), header, divider.clone(), body, divider].into_iter(),
651 "\n".to_string(),
652 )
653 .collect::<String>()
654 }
655}
656
657pub trait ProjectionDecomposition<G, I, T, D>: RepAnalysis<G, I, T, D>
664where
665 T: ComplexFloat + Lapack + fmt::Debug,
666 <T as ComplexFloat>::Real: ToPrimitive + fmt::LowerExp + fmt::Display,
667 G: GroupProperties + ClassProperties + CharacterProperties + Sync + Send,
668 <G as CharacterProperties>::RowSymbol: Sync + Send,
669 G::GroupElement: fmt::Display,
670 G::CharTab: SubspaceDecomposable<T>,
671 D: Dimension,
672 I: Overlap<T, D> + Clone,
673 Self: Sync + Send,
674 Self::OrbitIter: Iterator<Item = Result<I, anyhow::Error>>,
675 for<'a> Complex<f64>: Mul<&'a T, Output = Complex<f64>>,
676{
677 #[allow(clippy::type_complexity)]
697 fn calc_projection_compositions(
698 &self,
699 ) -> Result<
700 Vec<(
701 <<G as CharacterProperties>::CharTab as CharacterTable>::RowSymbol,
702 Complex<f64>,
703 )>,
704 anyhow::Error,
705 > {
706 self.group()
707 .character_table()
708 .get_all_rows()
709 .iter()
710 .map(|row_label| {
711 let group = self.group();
712 let chartab = group.character_table();
713 let id_class = group
714 .get_cc_symbol_of_index(0)
715 .ok_or(format_err!("Unable to retrieve the identity class."))?;
716 let dim = chartab
717 .get_character(row_label, &id_class).complex_value();
718 let group_order = group.order().to_f64().ok_or(
719 DecompositionError("The group order cannot be converted to `f64`.".to_string())
720 )?;
721 let projection: Complex<f64> = (0..group.order())
722 .into_par_iter()
723 .try_fold(Complex::<f64>::zero, |acc, i| {
724 let cc_i = group
725 .get_cc_of_element_index(i)
726 .ok_or(format_err!("Unable to retrieve the conjugacy class index of element index {i}."))?;
727 let cc = group
728 .get_cc_symbol_of_index(cc_i)
729 .ok_or(format_err!("Unable to retrieve the conjugacy class symbol of conjugacy class index {cc_i}."))?;
730 let chi_i_star = group.character_table().get_character(row_label, &cc).complex_conjugate();
731 let s_0i = self
732 .smat()
733 .ok_or(format_err!("No orbit overlap matrix found."))?
734 .get((0, i))
735 .ok_or(format_err!("Unable to retrieve the overlap matrix element with index `(0, {i})`."))?;
736 Ok::<_, anyhow::Error>(acc + chi_i_star.complex_value() * s_0i)
737 })
738 .try_reduce(Complex::<f64>::zero, |a, s| Ok(a + s))?;
739 #[allow(clippy::op_ref)]
740 Ok::<_, anyhow::Error>((row_label.clone(), &dim * projection / group_order))
741 }).collect::<Result<Vec<_>, anyhow::Error>>()
742 }
743
744 fn projections_to_string(
756 &self,
757 projections: &[(
758 <<G as CharacterProperties>::CharTab as CharacterTable>::RowSymbol,
759 Complex<f64>,
760 )],
761 integrality_threshold: <T as ComplexFloat>::Real,
762 ) -> String {
763 let ndigits = (-integrality_threshold.log10()).to_usize().unwrap_or(6) + 1;
764 let (row_labels, projection_values): (Vec<_>, Vec<_>) = projections
765 .iter()
766 .map(|(row, proj)| (row.to_string(), format!("{proj:+.ndigits$}")))
767 .unzip();
768 let row_width = row_labels
769 .iter()
770 .map(|row| row.chars().count())
771 .max()
772 .unwrap_or(9)
773 .max(9);
774 let proj_width = projection_values
775 .iter()
776 .map(|proj| proj.chars().count())
777 .max()
778 .unwrap_or(10)
779 .max(10);
780
781 let divider = "┈".repeat(row_width + proj_width + 4);
782 let header = format!(" {:<row_width$} {:<}", "Component", "Projection");
783 let body = Itertools::intersperse(
784 projections.iter().map(|(row, proj)| {
785 format!(" {:<row_width$} {:<+.ndigits$}", row.to_string(), proj)
786 }),
787 "\n".to_string(),
788 )
789 .collect::<String>();
790
791 Itertools::intersperse(
792 [divider.clone(), header, divider.clone(), body, divider].into_iter(),
793 "\n".to_string(),
794 )
795 .collect::<String>()
796 }
797}
798
799#[duplicate_item(
802 duplicate!{
803 [ dtype_nested; [f64]; [Complex<f64>] ]
804 [
805 gtype_ [ UnitaryRepresentedSymmetryGroup ]
806 dtype_ [ dtype_nested ]
807 ]
808 }
809)]
810impl<O, I, D> ProjectionDecomposition<gtype_, I, dtype_, D> for O
811where
812 O: RepAnalysis<gtype_, I, dtype_, D>,
813 D: Dimension,
814 I: Overlap<dtype_, D> + Clone,
815 Self: Sync + Send,
816 Self::OrbitIter: Iterator<Item = Result<I, anyhow::Error>>,
817{
818}
819
820macro_rules! fn_calc_xmat_real {
825 ( $(#[$meta:meta])* $vis:vis $func:ident ) => {
826 $(#[$meta])*
827 $vis fn $func(&mut self, preserves_full_rank: bool) -> Result<&mut Self, anyhow::Error> {
828 let thresh = self.linear_independence_threshold;
830 let smat = self
831 .smat
832 .as_ref()
833 .ok_or(format_err!("No overlap matrix found for this orbit."))?;
834 use ndarray_linalg::norm::Norm;
835 if (smat.to_owned() - smat.t()).norm_l2() > thresh {
836 Err(format_err!("Overlap matrix is not symmetric."))
837 } else {
838 let (s_eig, umat) = smat.eigh(UPLO::Lower).map_err(|err| format_err!(err))?;
839 let nonzero_s_indices = match self.eigenvalue_comparison_mode {
840 EigenvalueComparisonMode::Modulus => {
841 s_eig.iter().positions(|x| x.abs() > thresh).collect_vec()
842 }
843 EigenvalueComparisonMode::Real => {
844 s_eig.iter().positions(|x| *x > thresh).collect_vec()
845 }
846 };
847 let nonzero_s_eig = s_eig.select(Axis(0), &nonzero_s_indices);
848 let nonzero_umat = umat.select(Axis(1), &nonzero_s_indices);
849 let nullity = smat.shape()[0] - nonzero_s_indices.len();
850 let xmat = if nullity == 0 && preserves_full_rank {
851 Array2::eye(smat.shape()[0])
852 } else {
853 let s_s = Array2::<f64>::from_diag(&nonzero_s_eig.mapv(|x| 1.0 / x.sqrt()));
854 nonzero_umat.dot(&s_s)
855 };
856 self.smat_eigvals = Some(s_eig);
857 self.xmat = Some(xmat);
858 Ok(self)
859 }
860 }
861 }
862}
863
864macro_rules! fn_calc_xmat_complex {
865 ( $(#[$meta:meta])* $vis:vis $func:ident ) => {
866 $(#[$meta])*
867 $vis fn $func(&mut self, preserves_full_rank: bool) -> Result<&mut Self, anyhow::Error> {
868 let thresh = self.linear_independence_threshold;
870 let smat = self
871 .smat
872 .as_ref()
873 .ok_or(format_err!("No overlap matrix found for this orbit."))?;
874 let (s_eig, umat_nonortho) = smat.eig().map_err(|err| format_err!(err))?;
875
876 let nonzero_s_indices = match self.eigenvalue_comparison_mode {
877 EigenvalueComparisonMode::Modulus => s_eig
878 .iter()
879 .positions(|x| ComplexFloat::abs(*x) > thresh)
880 .collect_vec(),
881 EigenvalueComparisonMode::Real => {
882 if s_eig
883 .iter()
884 .any(|x| Float::abs(ComplexFloat::im(*x)) > thresh)
885 {
886 log::warn!("Comparing eigenvalues using the real parts, but not all eigenvalues are real.");
887 }
888 s_eig
889 .iter()
890 .positions(|x| ComplexFloat::re(*x) > thresh)
891 .collect_vec()
892 }
893 };
894 let nonzero_s_eig = s_eig.select(Axis(0), &nonzero_s_indices);
895 let nonzero_umat_nonortho = umat_nonortho.select(Axis(1), &nonzero_s_indices);
896
897 let nonzero_umat = complex_modified_gram_schmidt(
900 &nonzero_umat_nonortho,
901 self.origin.complex_symmetric(),
902 thresh,
903 )
904 .map_err(
905 |_| format_err!("Unable to Gram--Schmidt orthonormalise the linearly-independent eigenvectors of the overlap matrix.")
906 )?;
907
908 let nullity = smat.shape()[0] - nonzero_s_indices.len();
909 let xmat = if nullity == 0 && preserves_full_rank {
910 Array2::<Complex<T>>::eye(smat.shape()[0])
911 } else {
912 let s_s = Array2::<Complex<T>>::from_diag(
913 &nonzero_s_eig.mapv(|x| Complex::<T>::from(T::one()) / x.sqrt()),
914 );
915 nonzero_umat.dot(&s_s)
916 };
917 self.smat_eigvals = Some(s_eig);
918 self.xmat = Some(xmat);
919 Ok(self)
920 }
921 }
922}
923
924pub(crate) use fn_calc_xmat_complex;
925pub(crate) use fn_calc_xmat_real;
926
927pub(crate) fn log_overlap_eigenvalues<T>(
938 title: &str,
939 eigvals: &Array1<T>,
940 thresh: <T as ComplexFloat>::Real,
941 eigenvalue_comparison_mode: &EigenvalueComparisonMode,
942) where
943 T: std::fmt::LowerExp + ComplexFloat,
944 <T as ComplexFloat>::Real: std::fmt::LowerExp,
945{
946 let mut eigvals_sorted = eigvals.iter().collect::<Vec<_>>();
947 match eigenvalue_comparison_mode {
948 EigenvalueComparisonMode::Modulus => {
949 eigvals_sorted.sort_by(|a, b| a.abs().partial_cmp(&b.abs()).unwrap());
950 }
951 EigenvalueComparisonMode::Real => {
952 eigvals_sorted.sort_by(|a, b| a.re().partial_cmp(&b.re()).unwrap());
953 }
954 }
955 eigvals_sorted.reverse();
956 let eigvals_str = eigvals_sorted
957 .iter()
958 .map(|v| format!("{v:+.3e}"))
959 .collect::<Vec<_>>();
960 log_subtitle(title);
961 qsym2_output!("");
962
963 match eigenvalue_comparison_mode {
964 EigenvalueComparisonMode::Modulus => {
965 qsym2_output!("Eigenvalues are sorted in decreasing order of their moduli.");
966 }
967 EigenvalueComparisonMode::Real => {
968 qsym2_output!("Eigenvalues are sorted in decreasing order of their real parts.");
969 }
970 }
971 let count_length = usize::try_from(eigvals.len().ilog10() + 2).unwrap_or(2);
972 let eigval_length = eigvals_str
973 .iter()
974 .map(|v| v.chars().count())
975 .max()
976 .unwrap_or(20);
977 qsym2_output!("{}", "┈".repeat(count_length + 3 + eigval_length));
978 qsym2_output!("{:>count_length$} Eigenvalue", "#");
979 qsym2_output!("{}", "┈".repeat(count_length + 3 + eigval_length));
980 let mut write_thresh = false;
981 for (i, eigval) in eigvals_str.iter().enumerate() {
982 let cmp = match eigenvalue_comparison_mode {
983 EigenvalueComparisonMode::Modulus => {
984 eigvals_sorted[i].abs().partial_cmp(&thresh).expect(
985 "Unable to compare the modulus of an eigenvalue with the specified threshold.",
986 )
987 }
988 EigenvalueComparisonMode::Real => eigvals_sorted[i].re().partial_cmp(&thresh).expect(
989 "Unable to compare the real part of an eigenvalue with the specified threshold.",
990 ),
991 };
992 if cmp == Ordering::Less && !write_thresh {
993 let comparison_mode_str = match eigenvalue_comparison_mode {
994 EigenvalueComparisonMode::Modulus => "modulus-based",
995 EigenvalueComparisonMode::Real => "real-part-based",
996 };
997 qsym2_output!(
998 "{} <-- linear independence threshold ({comparison_mode_str}): {:+.3e}",
999 "-".repeat(count_length + 3 + eigval_length),
1000 thresh
1001 );
1002 write_thresh = true;
1003 }
1004 qsym2_output!("{i:>count_length$} {eigval}",);
1005 }
1006 qsym2_output!("{}", "┈".repeat(count_length + 3 + eigval_length));
1007}