1use std::cmp::Ordering;
4use std::fmt;
5use std::ops::Mul;
6
7use anyhow::{self, format_err, Context};
8use duplicate::duplicate_item;
9use itertools::Itertools;
10use log;
11use ndarray::{s, Array, Array1, Array2, Axis, Dimension, Ix0, Ix2};
12use ndarray_einsum_beta::*;
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::chartab::chartab_group::CharacterProperties;
22use crate::chartab::{CharacterTable, DecompositionError, SubspaceDecomposable};
23use crate::group::{class::ClassProperties, GroupProperties};
24use crate::io::format::{log_subtitle, qsym2_output};
25use crate::symmetry::symmetry_group::UnitaryRepresentedSymmetryGroup;
26
27#[derive(Clone)]
33pub(crate) struct Metric<'a, T, D: Dimension> {
34 hermitian: &'a Array<T, D>,
39
40 complex_symmetric: Option<&'a Array<T, D>>,
46}
47
48
49pub trait Overlap<T, D>
62where
63 T: ComplexFloat + fmt::Debug + Lapack,
64 D: Dimension,
65{
66 fn complex_symmetric(&self) -> bool;
69
70 fn overlap(
74 &self,
75 other: &Self,
76 metric: Option<&Array<T, D>>,
77 metric_h: Option<&Array<T, D>>,
78 ) -> Result<T, anyhow::Error>;
79
80 fn overlap_definition(&self) -> String;
83}
84
85pub struct OrbitIterator<'a, G, I>
95where
96 G: GroupProperties,
97{
98 group_iter: <<G as GroupProperties>::ElementCollection as IntoIterator>::IntoIter,
101
102 origin: &'a I,
104
105 action: fn(&G::GroupElement, &I) -> Result<I, anyhow::Error>,
107}
108
109impl<'a, G, I> OrbitIterator<'a, G, I>
110where
111 G: GroupProperties,
112{
113 pub fn new(
125 group: &G,
126 origin: &'a I,
127 action: fn(&G::GroupElement, &I) -> Result<I, anyhow::Error>,
128 ) -> Self {
129 Self {
130 group_iter: group.elements().clone().into_iter(),
131 origin,
132 action,
133 }
134 }
135}
136
137impl<'a, G, I> Iterator for OrbitIterator<'a, G, I>
138where
139 G: GroupProperties,
140{
141 type Item = Result<I, anyhow::Error>;
142
143 fn next(&mut self) -> Option<Self::Item> {
144 self.group_iter
145 .next()
146 .map(|op| (self.action)(&op, self.origin))
147 }
148}
149
150pub trait Orbit<G, I>
156where
157 G: GroupProperties,
158{
159 type OrbitIter: Iterator<Item = Result<I, anyhow::Error>>;
161
162 fn group(&self) -> &G;
164
165 fn origin(&self) -> &I;
167
168 fn iter(&self) -> Self::OrbitIter;
170}
171
172#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
183#[cfg_attr(feature = "python", pyclass(eq, eq_int))]
184pub enum EigenvalueComparisonMode {
185 Real,
187
188 Modulus,
190}
191
192impl Default for EigenvalueComparisonMode {
193 fn default() -> Self {
194 Self::Modulus
195 }
196}
197
198impl fmt::Display for EigenvalueComparisonMode {
199 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
200 match self {
201 EigenvalueComparisonMode::Real => write!(f, "Real part"),
202 EigenvalueComparisonMode::Modulus => write!(f, "Modulus"),
203 }
204 }
205}
206
207pub trait RepAnalysis<G, I, T, D>: Orbit<G, I>
218where
219 T: ComplexFloat + Lapack + fmt::Debug,
220 <T as ComplexFloat>::Real: ToPrimitive,
221 G: GroupProperties + ClassProperties + CharacterProperties,
222 G::GroupElement: fmt::Display,
223 G::CharTab: SubspaceDecomposable<T>,
224 D: Dimension,
225 I: Overlap<T, D> + Clone,
226 Self::OrbitIter: Iterator<Item = Result<I, anyhow::Error>>,
227{
228 fn set_smat(&mut self, smat: Array2<T>);
238
239 #[must_use]
241 fn smat(&self) -> Option<&Array2<T>>;
242
243 #[must_use]
262 fn xmat(&self) -> &Array2<T>;
263
264 #[must_use]
280 fn norm_preserving_scalar_map(&self, i: usize) -> Result<fn(T) -> T, anyhow::Error>;
281
282 #[must_use]
285 fn integrality_threshold(&self) -> <T as ComplexFloat>::Real;
286
287 #[must_use]
290 fn eigenvalue_comparison_mode(&self) -> &EigenvalueComparisonMode;
291
292 fn calc_smat(
307 &mut self,
308 metric: Option<&Array<T, D>>,
309 metric_h: Option<&Array<T, D>>,
310 use_cayley_table: bool,
311 ) -> Result<&mut Self, anyhow::Error> {
312 let order = self.group().order();
313 let mut smat = Array2::<T>::zeros((order, order));
314 let item_0 = self.origin();
315 if let (Some(ctb), true) = (self.group().cayley_table(), use_cayley_table) {
316 log::debug!("Cayley table available. Group closure will be used to speed up overlap matrix computation.");
317 let ovs = self
318 .iter()
319 .map(|item_res| {
320 let item = item_res?;
321 item.overlap(item_0, metric, metric_h)
322 })
323 .collect::<Result<Vec<_>, _>>()?;
324 for (i, j) in (0..order).cartesian_product(0..order) {
325 let jinv = ctb
326 .slice(s![.., j])
327 .iter()
328 .position(|&x| x == 0)
329 .ok_or(format_err!(
330 "Unable to find the inverse of group element `{j}`."
331 ))?;
332 let jinv_i = ctb[(jinv, i)];
333 smat[(i, j)] = self.norm_preserving_scalar_map(jinv)?(ovs[jinv_i]);
334 }
335 } else {
336 log::debug!("Cayley table not available or the use of Cayley table not requested. Overlap matrix will be constructed without group-closure speed-up.");
337 for pair in self
338 .iter()
339 .map(|item_res| item_res.map_err(|err| err.to_string()))
340 .enumerate()
341 .combinations_with_replacement(2)
342 {
343 let (w, item_w_res) = &pair[0];
344 let (x, item_x_res) = &pair[1];
345 let item_w = item_w_res
346 .as_ref()
347 .map_err(|err| format_err!(err.clone()))
348 .with_context(|| "One of the items in the orbit is not available")?;
349 let item_x = item_x_res
350 .as_ref()
351 .map_err(|err| format_err!(err.clone()))
352 .with_context(|| "One of the items in the orbit is not available")?;
353 smat[(*w, *x)] = item_w.overlap(item_x, metric, metric_h).map_err(|err| {
354 log::warn!("{err}");
355 log::warn!(
356 "Unable to calculate the overlap between items `{w}` and `{x}` in the orbit."
357 );
358 err
359 })?;
360 if *w != *x {
361 smat[(*x, *w)] = item_x.overlap(item_w, metric, metric_h).map_err(|err| {
362 log::warn!("{err}");
363 log::warn!(
364 "Unable to calculate the overlap between items `{x}` and `{w}` in the orbit."
365 );
366 err
367 })?;
368 }
369 }
370 }
371 if self.origin().complex_symmetric() {
372 self.set_smat((smat.clone() + smat.t().to_owned()).mapv(|x| x / (T::one() + T::one())))
373 } else {
374 self.set_smat(
375 (smat.clone() + smat.t().to_owned().mapv(|x| x.conj()))
376 .mapv(|x| x / (T::one() + T::one())),
377 )
378 }
379 Ok(self)
380 }
381
382 fn normalise_smat(&mut self) -> Result<&mut Self, anyhow::Error> {
390 let smat = self
391 .smat()
392 .ok_or(format_err!("No orbit overlap matrix to normalise."))?;
393 let norm = smat.diag().mapv(|x| <T as ComplexFloat>::sqrt(x));
394 let nspatial = norm.len();
395 let norm_col = norm
396 .clone()
397 .into_shape_with_order([nspatial, 1])
398 .map_err(|err| format_err!(err))?;
399 let norm_row = norm
400 .into_shape_with_order([1, nspatial])
401 .map_err(|err| format_err!(err))?;
402 let norm_mat = norm_col.dot(&norm_row);
403 let normalised_smat = smat / norm_mat;
404 self.set_smat(normalised_smat);
405 Ok(self)
406 }
407
408 #[must_use]
430 fn calc_tmat(&self, op: &G::GroupElement) -> Result<Array2<T>, anyhow::Error> {
431 let ctb = self
432 .group()
433 .cayley_table()
434 .expect("The Cayley table for the group cannot be found.");
435 let i = self.group().get_index_of(op).unwrap_or_else(|| {
436 panic!("Unable to retrieve the index of element `{op}` in the group.")
437 });
438 let ix = ctb.slice(s![i, ..]).iter().cloned().collect::<Vec<_>>();
439 let twx = self
440 .smat()
441 .ok_or(format_err!("No orbit overlap matrix found."))?
442 .select(Axis(1), &ix);
443 Ok(twx)
444 }
445
446 #[must_use]
466 fn calc_dmat(&self, op: &G::GroupElement) -> Result<Array2<T>, anyhow::Error> {
467 let complex_symmetric = self.origin().complex_symmetric();
468 let xmath = if complex_symmetric {
469 self.xmat().t().to_owned()
470 } else {
471 self.xmat().t().mapv(|x| x.conj())
472 };
473 let smattilde = xmath
474 .dot(
475 self.smat()
476 .ok_or(format_err!("No orbit overlap matrix found."))?,
477 )
478 .dot(self.xmat());
479 let smattilde_inv = smattilde
480 .inv()
481 .expect("The inverse of S~ could not be found.");
482 let dmat = einsum(
483 "ij,jk,kl,lm->im",
484 &[&smattilde_inv, &xmath, &self.calc_tmat(op)?, self.xmat()],
485 )
486 .map_err(|err| format_err!(err))
487 .with_context(|| "Unable to compute the matrix product [(S~)^(-1) X† T X].")?
488 .into_dimensionality::<Ix2>()
489 .map_err(|err| format_err!(err))
490 .with_context(|| {
491 "Unable to convert the matrix product [(S~)^(-1) X† T X] to two dimensions."
492 });
493 dmat
494 }
495
496 #[must_use]
509 fn calc_character(&self, op: &G::GroupElement) -> Result<T, anyhow::Error> {
510 let complex_symmetric = self.origin().complex_symmetric();
511 let xmath = if complex_symmetric {
512 self.xmat().t().to_owned()
513 } else {
514 self.xmat().t().mapv(|x| x.conj())
515 };
516 let smattilde = xmath
517 .dot(
518 self.smat()
519 .ok_or(format_err!("No orbit overlap matrix found."))?,
520 )
521 .dot(self.xmat());
522 let smattilde_inv = smattilde
523 .inv()
524 .expect("The inverse of S~ could not be found.");
525 let chi = einsum(
526 "ij,jk,kl,li",
527 &[&smattilde_inv, &xmath, &self.calc_tmat(op)?, self.xmat()],
528 )
529 .map_err(|err| format_err!(err))
530 .with_context(|| "Unable to compute the trace of the matrix product [(S~)^(-1) X† T X].")?
531 .into_dimensionality::<Ix0>()
532 .map_err(|err| format_err!(err))
533 .with_context(|| "Unable to convert the trace of the matrix product [(S~)^(-1) X† T X] to zero dimensions.")?;
534 chi.into_iter().next().ok_or(format_err!(
535 "Unable to extract the character from the representation matrix."
536 ))
537 }
538
539 #[must_use]
548 fn calc_characters(
549 &self,
550 ) -> Result<Vec<(<G as ClassProperties>::ClassSymbol, T)>, anyhow::Error> {
551 let complex_symmetric = self.origin().complex_symmetric();
552 let xmath = if complex_symmetric {
553 self.xmat().t().to_owned()
554 } else {
555 self.xmat().t().mapv(|x| x.conj())
556 };
557 let smattilde = xmath
558 .dot(
559 self.smat()
560 .ok_or(format_err!("No orbit overlap matrix found."))?,
561 )
562 .dot(self.xmat());
563 let smattilde_inv = smattilde
564 .inv()
565 .expect("The inverse of S~ could not be found.");
566 let chis = (0..self.group().class_number()).map(|cc_i| {
567 let cc = self.group().get_cc_symbol_of_index(cc_i).unwrap();
568 let op = self.group().get_cc_transversal(cc_i).unwrap();
569 let chi = einsum(
570 "ij,jk,kl,li",
571 &[&smattilde_inv, &xmath, &self.calc_tmat(&op)?, self.xmat()],
572 )
573 .map_err(|err| format_err!(err))
574 .with_context(|| "Unable to compute the trace of the matrix product [(S~)^(-1) X† T X].")?
575 .into_dimensionality::<Ix0>()
576 .map_err(|err| format_err!(err))
577 .with_context(|| "Unable to convert the trace of the matrix product [(S~)^(-1) X† T X] to zero dimensions.")?;
578 let chi_val = chi.into_iter().next().ok_or(format_err!(
579 "Unable to extract the character from the representation matrix."
580 ))?;
581 Ok((cc, chi_val))
582 }).collect::<Result<Vec<_>, _>>();
583 chis
584 }
585
586 fn analyse_rep(
598 &self,
599 ) -> Result<
600 <<G as CharacterProperties>::CharTab as SubspaceDecomposable<T>>::Decomposition,
601 DecompositionError,
602 > {
603 let chis = self
604 .calc_characters()
605 .map_err(|err| DecompositionError(err.to_string()))?;
606 let res = self.group().character_table().reduce_characters(
607 &chis.iter().map(|(cc, chi)| (cc, *chi)).collect::<Vec<_>>(),
608 self.integrality_threshold(),
609 );
610 res
611 }
612
613 fn characters_to_string(
624 &self,
625 chis: &[(<G as ClassProperties>::ClassSymbol, T)],
626 integrality_threshold: <T as ComplexFloat>::Real,
627 ) -> String
628 where
629 T: ComplexFloat + Lapack + fmt::Debug,
630 <T as ComplexFloat>::Real: ToPrimitive,
631 {
632 let ndigits = (-integrality_threshold.log10()).to_usize().unwrap_or(6) + 1;
633 let (ccs, characters): (Vec<_>, Vec<_>) = chis
634 .iter()
635 .map(|(cc, chi)| (cc.to_string(), format!("{chi:+.ndigits$}")))
636 .unzip();
637 let cc_width = ccs
638 .iter()
639 .map(|cc| cc.chars().count())
640 .max()
641 .unwrap_or(5)
642 .max(5);
643 let characters_width = characters
644 .iter()
645 .map(|chi| chi.chars().count())
646 .max()
647 .unwrap_or(9)
648 .max(9);
649
650 let divider = "┈".repeat(cc_width + characters_width + 4);
651 let header = format!(" {:<cc_width$} {:<}", "Class", "Character");
652 let body = Itertools::intersperse(
653 chis.iter()
654 .map(|(cc, chi)| format!(" {:<cc_width$} {:<+.ndigits$}", cc.to_string(), chi)),
655 "\n".to_string(),
656 )
657 .collect::<String>();
658
659 Itertools::intersperse(
660 [divider.clone(), header, divider.clone(), body, divider].into_iter(),
661 "\n".to_string(),
662 )
663 .collect::<String>()
664 }
665}
666
667pub trait ProjectionDecomposition<G, I, T, D>: RepAnalysis<G, I, T, D>
674where
675 T: ComplexFloat + Lapack + fmt::Debug,
676 <T as ComplexFloat>::Real: ToPrimitive,
677 G: GroupProperties + ClassProperties + CharacterProperties + Sync + Send,
678 <G as CharacterProperties>::RowSymbol: Sync + Send,
679 G::GroupElement: fmt::Display,
680 G::CharTab: SubspaceDecomposable<T>,
681 D: Dimension,
682 I: Overlap<T, D> + Clone,
683 Self: Sync + Send,
684 Self::OrbitIter: Iterator<Item = Result<I, anyhow::Error>>,
685 for<'a> Complex<f64>: Mul<&'a T, Output = Complex<f64>>,
686{
687 fn calc_projection_compositions(
707 &self,
708 ) -> Result<
709 Vec<(
710 <<G as CharacterProperties>::CharTab as CharacterTable>::RowSymbol,
711 Complex<f64>,
712 )>,
713 anyhow::Error,
714 > {
715 self.group()
716 .character_table()
717 .get_all_rows()
718 .iter()
719 .map(|row_label| {
720 let group = self.group();
721 let chartab = group.character_table();
722 let id_class = group
723 .get_cc_symbol_of_index(0)
724 .ok_or(format_err!("Unable to retrieve the identity class."))?;
725 let dim = chartab
726 .get_character(&row_label, &id_class).complex_value();
727 let group_order = group.order().to_f64().ok_or(
728 DecompositionError("The group order cannot be converted to `f64`.".to_string())
729 )?;
730 let projection: Complex<f64> = (0..group.order())
731 .into_par_iter()
732 .try_fold(|| Complex::<f64>::zero(), |acc, i| {
733 let cc_i = group
734 .get_cc_of_element_index(i)
735 .ok_or(format_err!("Unable to retrieve the conjugacy class index of element index {i}."))?;
736 let cc = group
737 .get_cc_symbol_of_index(cc_i)
738 .ok_or(format_err!("Unable to retrieve the conjugacy class symbol of conjugacy class index {cc_i}."))?;
739 let chi_i_star = group.character_table().get_character(&row_label, &cc).complex_conjugate();
740 let s_0i = self
741 .smat()
742 .ok_or(format_err!("No orbit overlap matrix found."))?
743 .get((0, i))
744 .ok_or(format_err!("Unable to retrieve the overlap matrix element with index `(0, {i})`."))?;
745 Ok::<_, anyhow::Error>(acc + chi_i_star.complex_value() * s_0i)
746 })
747 .try_reduce(|| Complex::<f64>::zero(), |a, s| Ok(a + s))?;
748 Ok::<_, anyhow::Error>((row_label.clone(), &dim * projection / group_order))
749 }).collect::<Result<Vec<_>, anyhow::Error>>()
750 }
751
752 fn projections_to_string(
764 &self,
765 projections: &[(
766 <<G as CharacterProperties>::CharTab as CharacterTable>::RowSymbol,
767 Complex<f64>,
768 )],
769 integrality_threshold: <T as ComplexFloat>::Real,
770 ) -> String {
771 let ndigits = (-integrality_threshold.log10()).to_usize().unwrap_or(6) + 1;
772 let (row_labels, projection_values): (Vec<_>, Vec<_>) = projections
773 .iter()
774 .map(|(row, proj)| (row.to_string(), format!("{proj:+.ndigits$}")))
775 .unzip();
776 let row_width = row_labels
777 .iter()
778 .map(|row| row.chars().count())
779 .max()
780 .unwrap_or(9)
781 .max(9);
782 let proj_width = projection_values
783 .iter()
784 .map(|proj| proj.chars().count())
785 .max()
786 .unwrap_or(10)
787 .max(10);
788
789 let divider = "┈".repeat(row_width + proj_width + 4);
790 let header = format!(" {:<row_width$} {:<}", "Component", "Projection");
791 let body = Itertools::intersperse(
792 projections.iter().map(|(row, proj)| {
793 format!(" {:<row_width$} {:<+.ndigits$}", row.to_string(), proj)
794 }),
795 "\n".to_string(),
796 )
797 .collect::<String>();
798
799 Itertools::intersperse(
800 [divider.clone(), header, divider.clone(), body, divider].into_iter(),
801 "\n".to_string(),
802 )
803 .collect::<String>()
804 }
805}
806
807#[duplicate_item(
810 duplicate!{
811 [ dtype_nested; [f64]; [Complex<f64>] ]
812 [
813 gtype_ [ UnitaryRepresentedSymmetryGroup ]
814 dtype_ [ dtype_nested ]
815 ]
816 }
817)]
818impl<O, I, D> ProjectionDecomposition<gtype_, I, dtype_, D> for O
819where
820 O: RepAnalysis<gtype_, I, dtype_, D>,
821 D: Dimension,
822 I: Overlap<dtype_, D> + Clone,
823 Self: Sync + Send,
824 Self::OrbitIter: Iterator<Item = Result<I, anyhow::Error>>,
825{
826}
827
828macro_rules! fn_calc_xmat_real {
833 ( $(#[$meta:meta])* $vis:vis $func:ident ) => {
834 $(#[$meta])*
835 $vis fn $func(&mut self, preserves_full_rank: bool) -> Result<&mut Self, anyhow::Error> {
836 let thresh = self.linear_independence_threshold;
838 let smat = self
839 .smat
840 .as_ref()
841 .ok_or(format_err!("No overlap matrix found for this orbit."))?;
842 use ndarray_linalg::norm::Norm;
843 if (smat.to_owned() - smat.t()).norm_l2() > thresh {
844 Err(format_err!("Overlap matrix is not symmetric."))
845 } else {
846 let (s_eig, umat) = smat.eigh(UPLO::Lower).map_err(|err| format_err!(err))?;
847 let nonzero_s_indices = match self.eigenvalue_comparison_mode {
848 EigenvalueComparisonMode::Modulus => {
849 s_eig.iter().positions(|x| x.abs() > thresh).collect_vec()
850 }
851 EigenvalueComparisonMode::Real => {
852 s_eig.iter().positions(|x| *x > thresh).collect_vec()
853 }
854 };
855 let nonzero_s_eig = s_eig.select(Axis(0), &nonzero_s_indices);
856 let nonzero_umat = umat.select(Axis(1), &nonzero_s_indices);
857 let nullity = smat.shape()[0] - nonzero_s_indices.len();
858 let xmat = if nullity == 0 && preserves_full_rank {
859 Array2::eye(smat.shape()[0])
860 } else {
861 let s_s = Array2::<f64>::from_diag(&nonzero_s_eig.mapv(|x| 1.0 / x.sqrt()));
862 nonzero_umat.dot(&s_s)
863 };
864 self.smat_eigvals = Some(s_eig);
865 self.xmat = Some(xmat);
866 Ok(self)
867 }
868 }
869 }
870}
871
872macro_rules! fn_calc_xmat_complex {
873 ( $(#[$meta:meta])* $vis:vis $func:ident ) => {
874 $(#[$meta])*
875 $vis fn $func(&mut self, preserves_full_rank: bool) -> Result<&mut Self, anyhow::Error> {
876 let thresh = self.linear_independence_threshold;
878 let smat = self
879 .smat
880 .as_ref()
881 .ok_or(format_err!("No overlap matrix found for this orbit."))?;
882 let (s_eig, umat_nonortho) = smat.eig().map_err(|err| format_err!(err))?;
883
884 let nonzero_s_indices = match self.eigenvalue_comparison_mode {
885 EigenvalueComparisonMode::Modulus => s_eig
886 .iter()
887 .positions(|x| ComplexFloat::abs(*x) > thresh)
888 .collect_vec(),
889 EigenvalueComparisonMode::Real => {
890 if s_eig
891 .iter()
892 .any(|x| Float::abs(ComplexFloat::im(*x)) > thresh)
893 {
894 log::warn!("Comparing eigenvalues using the real parts, but not all eigenvalues are real.");
895 }
896 s_eig
897 .iter()
898 .positions(|x| ComplexFloat::re(*x) > thresh)
899 .collect_vec()
900 }
901 };
902 let nonzero_s_eig = s_eig.select(Axis(0), &nonzero_s_indices);
903 let nonzero_umat_nonortho = umat_nonortho.select(Axis(1), &nonzero_s_indices);
904
905 let nonzero_umat = complex_modified_gram_schmidt(
908 &nonzero_umat_nonortho,
909 self.origin.complex_symmetric(),
910 thresh,
911 )
912 .map_err(
913 |_| format_err!("Unable to orthonormalise the linearly-independent eigenvectors of the overlap matrix.")
914 )?;
915
916 let nullity = smat.shape()[0] - nonzero_s_indices.len();
917 let xmat = if nullity == 0 && preserves_full_rank {
918 Array2::<Complex<T>>::eye(smat.shape()[0])
919 } else {
920 let s_s = Array2::<Complex<T>>::from_diag(
921 &nonzero_s_eig.mapv(|x| Complex::<T>::from(T::one()) / x.sqrt()),
922 );
923 nonzero_umat.dot(&s_s)
924 };
925 self.smat_eigvals = Some(s_eig);
926 self.xmat = Some(xmat);
927 Ok(self)
928 }
929 }
930}
931
932pub(crate) use fn_calc_xmat_complex;
933pub(crate) use fn_calc_xmat_real;
934
935pub(crate) fn log_overlap_eigenvalues<T>(
946 title: &str,
947 eigvals: &Array1<T>,
948 thresh: <T as ComplexFloat>::Real,
949 eigenvalue_comparison_mode: &EigenvalueComparisonMode,
950) where
951 T: std::fmt::LowerExp + ComplexFloat,
952 <T as ComplexFloat>::Real: std::fmt::LowerExp,
953{
954 let mut eigvals_sorted = eigvals.iter().collect::<Vec<_>>();
955 match eigenvalue_comparison_mode {
956 EigenvalueComparisonMode::Modulus => {
957 eigvals_sorted.sort_by(|a, b| a.abs().partial_cmp(&b.abs()).unwrap());
958 }
959 EigenvalueComparisonMode::Real => {
960 eigvals_sorted.sort_by(|a, b| a.re().partial_cmp(&b.re()).unwrap());
961 }
962 }
963 eigvals_sorted.reverse();
964 let eigvals_str = eigvals_sorted
965 .iter()
966 .map(|v| format!("{v:+.3e}"))
967 .collect::<Vec<_>>();
968 log_subtitle(title);
969 qsym2_output!("");
970
971 match eigenvalue_comparison_mode {
972 EigenvalueComparisonMode::Modulus => {
973 qsym2_output!("Eigenvalues are sorted in decreasing order of their moduli.");
974 }
975 EigenvalueComparisonMode::Real => {
976 qsym2_output!("Eigenvalues are sorted in decreasing order of their real parts.");
977 }
978 }
979 let count_length = usize::try_from(eigvals.len().ilog10() + 2).unwrap_or(2);
980 let eigval_length = eigvals_str
981 .iter()
982 .map(|v| v.chars().count())
983 .max()
984 .unwrap_or(20);
985 qsym2_output!("{}", "┈".repeat(count_length + 3 + eigval_length));
986 qsym2_output!("{:>count_length$} Eigenvalue", "#");
987 qsym2_output!("{}", "┈".repeat(count_length + 3 + eigval_length));
988 let mut write_thresh = false;
989 for (i, eigval) in eigvals_str.iter().enumerate() {
990 let cmp = match eigenvalue_comparison_mode {
991 EigenvalueComparisonMode::Modulus => {
992 eigvals_sorted[i].abs().partial_cmp(&thresh).expect(
993 "Unable to compare the modulus of an eigenvalue with the specified threshold.",
994 )
995 }
996 EigenvalueComparisonMode::Real => eigvals_sorted[i].re().partial_cmp(&thresh).expect(
997 "Unable to compare the real part of an eigenvalue with the specified threshold.",
998 ),
999 };
1000 if cmp == Ordering::Less && !write_thresh {
1001 let comparison_mode_str = match eigenvalue_comparison_mode {
1002 EigenvalueComparisonMode::Modulus => "modulus-based",
1003 EigenvalueComparisonMode::Real => "real-part-based",
1004 };
1005 qsym2_output!(
1006 "{} <-- linear independence threshold ({comparison_mode_str}): {:+.3e}",
1007 "-".repeat(count_length + 3 + eigval_length),
1008 thresh
1009 );
1010 write_thresh = true;
1011 }
1012 qsym2_output!("{i:>count_length$} {eigval}",);
1013 }
1014 qsym2_output!("{}", "┈".repeat(count_length + 3 + eigval_length));
1015}