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, Ix0, Ix2, s};
14use ndarray_einsum::*;
15use ndarray_linalg::{
16 UPLO,
17 eig::Eig,
18 eigh::Eigh,
19 types::{Lapack, Scalar},
20};
21use num_complex::{Complex, ComplexFloat};
22use num_traits::{Float, ToPrimitive, Zero};
23
24use crate::angmom::spinor_rotation_3d::StructureConstraint;
25use crate::auxiliary::misc::complex_modified_gram_schmidt;
26use crate::chartab::chartab_group::CharacterProperties;
27use crate::chartab::{DecompositionError, SubspaceDecomposable};
28use crate::group::GroupType;
29use crate::io::format::{QSym2Output, log_subtitle, qsym2_output};
30use crate::symmetry::symmetry_element::symmetry_operation::SpecialSymmetryTransformation;
31use crate::symmetry::symmetry_group::SymmetryGroupProperties;
32use crate::symmetry::symmetry_transformation::{SymmetryTransformable, SymmetryTransformationKind};
33use crate::target::determinant::SlaterDeterminant;
34use crate::auxiliary::misc::check_complex_matrix_symmetry;
35use crate::target::noci::basis::{Basis, FCIBasis, OrbitBasis};
36use crate::target::noci::multideterminant::MultiDeterminant;
37use crate::{
38 analysis::{
39 EigenvalueComparisonMode, Orbit, OrbitIterator, Overlap, RepAnalysis, fn_calc_xmat_complex,
40 fn_calc_xmat_real,
41 },
42 target::noci::basis::basis_metric::BasisSymmetryOrbitMetric,
43};
44
45impl<'a, T, B, SC> Overlap<T, Ix2> for MultiDeterminant<'a, T, B, SC>
50where
51 T: Lapack
52 + ComplexFloat<Real = <T as Scalar>::Real>
53 + fmt::Debug
54 + Mul<<T as ComplexFloat>::Real, Output = T>,
55 <T as ComplexFloat>::Real: fmt::Debug
56 + approx::RelativeEq<<T as ComplexFloat>::Real>
57 + approx::AbsDiffEq<Epsilon = <T as Scalar>::Real>,
58 SC: StructureConstraint + Hash + Eq + Clone + fmt::Display,
59 B: Basis<Item = SlaterDeterminant<'a, T, SC>> + Clone,
60{
61 fn complex_symmetric(&self) -> bool {
62 self.complex_symmetric
63 }
64
65 fn overlap(
84 &self,
85 other: &Self,
86 metric: Option<&Array2<T>>,
87 metric_h: Option<&Array2<T>>,
88 ) -> Result<T, anyhow::Error> {
89 ensure!(
90 self.structure_constraint() == other.structure_constraint(),
91 "Inconsistent structure constraints between `self` and `other`."
92 );
93 ensure!(
94 self.coefficients.len() == other.coefficients.len(),
95 "Inconsistent numbers of coefficients between `self` and `other`."
96 );
97
98 let s_dets = self.basis.iter().collect::<Result<Vec<_>, _>>()?;
99 let o_dets = other.basis.iter().collect::<Result<Vec<_>, _>>()?;
100 let d = self.coefficients.len();
101 let swx_vec = s_dets
102 .iter()
103 .cartesian_product(o_dets.iter())
104 .map(|(w, x)| w.overlap(x, metric, metric_h))
105 .collect::<Result<Vec<_>, _>>()?;
106 let swx = Array2::from_shape_vec((d, d), swx_vec)?;
107
108 if self.complex_symmetric {
109 Ok(self.coefficients.t().dot(&swx).dot(&other.coefficients))
110 } else {
111 Ok(self
112 .coefficients
113 .t()
114 .mapv(|x| x.conj())
115 .dot(&swx)
116 .dot(&other.coefficients))
117 }
118 }
119
120 fn overlap_definition(&self) -> String {
123 let k = if self.complex_symmetric() { "κ " } else { "" };
124 format!("⟨{k}Ψ_1|Ψ_2⟩ = ∫ [{k}Ψ_1(x^Ne)]* Ψ_2(x^Ne) dx^Ne")
125 }
126}
127
128#[derive(Builder, Clone)]
139pub struct MultiDeterminantSymmetryOrbit<'a, 'o, G, T, B, SC>
140where
141 G: SymmetryGroupProperties,
142 T: ComplexFloat + fmt::Debug + Lapack,
143 SC: StructureConstraint + Hash + Eq + fmt::Display,
144 B: 'a + Basis<Item = SlaterDeterminant<'a, T, SC>> + Clone,
145 MultiDeterminant<'a, T, B, SC>: SymmetryTransformable,
146{
147 group: &'o G,
149
150 origin: &'o MultiDeterminant<'a, T, B, SC>,
152
153 pub(crate) linear_independence_threshold: <T as ComplexFloat>::Real,
155
156 orbit_overlap_symmetry_threshold: <T as ComplexFloat>::Real,
158
159 integrality_threshold: <T as ComplexFloat>::Real,
162
163 symmetry_transformation_kind: SymmetryTransformationKind,
166
167 #[builder(setter(skip), default = "None")]
170 smat: Option<Array2<T>>,
171
172 #[builder(setter(skip), default = "None")]
175 pub(crate) smat_eigvals: Option<Array1<T>>,
176
177 #[builder(setter(skip), default = "None")]
182 xmat: Option<Array2<T>>,
183
184 eigenvalue_comparison_mode: EigenvalueComparisonMode,
187}
188
189impl<'a, 'g, G, T, B, SC> MultiDeterminantSymmetryOrbit<'a, 'g, G, T, B, SC>
194where
195 G: SymmetryGroupProperties + Clone,
196 T: ComplexFloat + fmt::Debug + Lapack,
197 SC: StructureConstraint + Hash + Eq + Clone + fmt::Display,
198 B: 'a + Basis<Item = SlaterDeterminant<'a, T, SC>> + Clone,
199 MultiDeterminant<'a, T, B, SC>: SymmetryTransformable,
200{
201 pub fn builder() -> MultiDeterminantSymmetryOrbitBuilder<'a, 'g, G, T, B, SC> {
203 MultiDeterminantSymmetryOrbitBuilder::default()
204 }
205
206 pub fn origin(&self) -> &MultiDeterminant<'a, T, B, SC> {
208 self.origin
209 }
210}
211
212impl<'a, 'g, G, B, SC> MultiDeterminantSymmetryOrbit<'a, 'g, G, f64, B, SC>
213where
214 G: SymmetryGroupProperties,
215 SC: StructureConstraint + Hash + Eq + fmt::Display,
216 B: 'a + Basis<Item = SlaterDeterminant<'a, f64, SC>> + Clone,
217 MultiDeterminant<'a, f64, B, SC>: SymmetryTransformable,
218{
219 fn_calc_xmat_real!(
220 pub calc_xmat
232 );
233}
234
235impl<'a, 'g, G, T, B, SC> MultiDeterminantSymmetryOrbit<'a, 'g, G, Complex<T>, B, SC>
236where
237 G: SymmetryGroupProperties,
238 T: Float + Scalar<Complex = Complex<T>>,
239 Complex<T>: ComplexFloat<Real = T> + Scalar<Real = T, Complex = Complex<T>> + Lapack,
240 SC: StructureConstraint + Hash + Eq + fmt::Display,
241 B: 'a + Basis<Item = SlaterDeterminant<'a, Complex<T>, SC>> + Clone,
242 MultiDeterminant<'a, Complex<T>, B, SC>: SymmetryTransformable + Overlap<Complex<T>, Ix2>,
243{
244 fn_calc_xmat_complex!(
245 pub calc_xmat
257 );
258}
259
260impl<'a, 'o, G, T, B, SC> Orbit<G, MultiDeterminant<'a, T, B, SC>>
269 for MultiDeterminantSymmetryOrbit<'a, 'o, G, T, B, SC>
270where
271 G: SymmetryGroupProperties,
272 T: ComplexFloat + fmt::Debug + Lapack,
273 SC: 'a + StructureConstraint + Hash + Eq + fmt::Display,
274 B: 'a + Basis<Item = SlaterDeterminant<'a, T, SC>> + Clone,
275 MultiDeterminant<'a, T, B, SC>: SymmetryTransformable,
276{
277 type OrbitIter = OrbitIterator<'o, G, MultiDeterminant<'a, T, B, SC>>;
278
279 fn group(&self) -> &G {
280 self.group
281 }
282
283 fn origin(&self) -> &MultiDeterminant<'a, T, B, SC> {
284 self.origin
285 }
286
287 fn iter(&self) -> Self::OrbitIter {
288 OrbitIterator::new(
289 self.group,
290 self.origin,
291 match self.symmetry_transformation_kind {
292 SymmetryTransformationKind::Spatial => |op, multidet| {
293 multidet.sym_transform_spatial(op).with_context(|| {
294 format!("Unable to apply `{op}` spatially on the origin multi-determinantal wavefunction")
295 })
296 },
297 SymmetryTransformationKind::SpatialWithSpinTimeReversal => |op, multidet| {
298 multidet.sym_transform_spatial_with_spintimerev(op).with_context(|| {
299 format!("Unable to apply `{op}` spatially (with spin-including time reversal) on the origin multi-determinantal wavefunction")
300 })
301 },
302 SymmetryTransformationKind::Spin => |op, multidet| {
303 multidet.sym_transform_spin(op).with_context(|| {
304 format!("Unable to apply `{op}` spin-wise on the origin multi-determinantal wavefunction")
305 })
306 },
307 SymmetryTransformationKind::SpinSpatial => |op, multidet| {
308 multidet.sym_transform_spin_spatial(op).with_context(|| {
309 format!("Unable to apply `{op}` spin-spatially on the origin multi-determinantal wavefunction")
310 })
311 },
312 },
313 )
314 }
315}
316
317impl<'a, 'o, G, T, B, SC> RepAnalysis<G, MultiDeterminant<'a, T, B, SC>, T, Ix2>
322 for MultiDeterminantSymmetryOrbit<'a, 'o, G, T, B, SC>
323where
324 G: SymmetryGroupProperties,
325 G::CharTab: SubspaceDecomposable<T>,
326 T: Lapack
327 + ComplexFloat<Real = <T as Scalar>::Real>
328 + fmt::Debug
329 + Mul<<T as ComplexFloat>::Real, Output = T>,
330 <T as ComplexFloat>::Real: fmt::Debug
331 + Zero
332 + From<u16>
333 + ToPrimitive
334 + approx::RelativeEq<<T as ComplexFloat>::Real>
335 + approx::AbsDiffEq<Epsilon = <T as Scalar>::Real>,
336 SC: 'a + StructureConstraint + Hash + Eq + Clone + fmt::Display,
337 B: 'a + Basis<Item = SlaterDeterminant<'a, T, SC>> + Clone,
338 MultiDeterminant<'a, T, B, SC>: SymmetryTransformable,
339{
340 fn set_smat(&mut self, smat: Array2<T>) {
341 self.smat = Some(smat)
342 }
343
344 fn smat(&self) -> Option<&Array2<T>> {
345 self.smat.as_ref()
346 }
347
348 fn xmat(&self) -> &Array2<T> {
349 self.xmat
350 .as_ref()
351 .expect("Orbit overlap orthogonalisation matrix not found.")
352 }
353
354 fn norm_preserving_scalar_map(&self, i: usize) -> Result<fn(T) -> T, anyhow::Error> {
355 if self.origin.complex_symmetric {
356 Err(format_err!(
357 "`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."
358 ))
359 } else if self
360 .group
361 .get_index(i)
362 .unwrap_or_else(|| panic!("Group operation index `{i}` not found."))
363 .contains_time_reversal()
364 {
365 Ok(ComplexFloat::conj)
366 } else {
367 Ok(|x| x)
368 }
369 }
370
371 fn orbit_overlap_symmetry_threshold(&self) -> <T as ComplexFloat>::Real {
372 self.orbit_overlap_symmetry_threshold
373 }
374
375 fn integrality_threshold(&self) -> <T as ComplexFloat>::Real {
376 self.integrality_threshold
377 }
378
379 fn eigenvalue_comparison_mode(&self) -> &EigenvalueComparisonMode {
380 &self.eigenvalue_comparison_mode
381 }
382
383 fn analyse_rep(
399 &self,
400 ) -> Result<
401 <<G as CharacterProperties>::CharTab as SubspaceDecomposable<T>>::Decomposition,
402 DecompositionError,
403 > {
404 log::debug!("Analysing representation symmetry for a multi-determinantal wavefunction...");
405 let mut nelectrons_set = self
406 .origin()
407 .basis
408 .iter()
409 .map(|det_res| det_res.and_then(|det| {
410 let det_nelectrons_float = det.nelectrons();
411 if approx::relative_eq!(
412 det_nelectrons_float.round(),
413 det_nelectrons_float,
414 epsilon = self.integrality_threshold,
415 max_relative = self.integrality_threshold
416 ) {
417 det_nelectrons_float.round().to_usize().ok_or(format_err!("Unable to convert the number of electrons `{det_nelectrons_float:.7}` to `usize`."))
418 } else {
419 Err(format_err!("Fractional number of electrons encountered: `{det_nelectrons_float:.7}`"))
420 }
421 }))
422 .collect::<Result<HashSet<_>, _>>()
423 .map_err(|err| DecompositionError(err.to_string()))?;
424 if nelectrons_set.len() != 1 {
425 Err(DecompositionError("Symmetry analysis for multi-determinantal wavefunctions with multiple numbers of electrons is not yet supported.".to_string()))
426 } else {
427 let nelectrons = nelectrons_set.drain().next().ok_or(DecompositionError(
428 "Unable to obtain the number of electrons.".to_string(),
429 ))?;
430 let (valid_symmetry, err_str) = if nelectrons.rem_euclid(2) == 0 {
431 (true, String::new())
433 } else {
434 match self.symmetry_transformation_kind {
436 SymmetryTransformationKind::Spatial => (true, String::new()),
437 SymmetryTransformationKind::SpatialWithSpinTimeReversal
438 | SymmetryTransformationKind::Spin
439 | SymmetryTransformationKind::SpinSpatial => {
440 match self.group().group_type() {
441 GroupType::Ordinary(_) => (true, String::new()),
442 GroupType::MagneticGrey(_) | GroupType::MagneticBlackWhite(_) => {
443 (!self.group().unitary_represented(),
444 "Unitary-represented magnetic groups cannot be used for symmetry analysis of odd-electron systems where spin is treated explicitly.".to_string())
445 }
446 }
447 }
448 }
449 };
450
451 if valid_symmetry {
452 let chis = self
453 .calc_characters()
454 .map_err(|err| DecompositionError(err.to_string()))?;
455 log::debug!("Characters calculated.");
456
457 log_subtitle("Multi-determinantal wavefunction orbit characters");
458 qsym2_output!("");
459 self.characters_to_string(&chis, self.integrality_threshold)
460 .log_output_display();
461 qsym2_output!("");
462
463 let res = self.group().character_table().reduce_characters(
464 &chis.iter().map(|(cc, chi)| (cc, *chi)).collect::<Vec<_>>(),
465 self.integrality_threshold(),
466 );
467 log::debug!("Characters reduced.");
468 log::debug!(
469 "Analysing representation symmetry for a multi-determinantal wavefunction... Done."
470 );
471 res
472 } else {
473 Err(DecompositionError(err_str))
474 }
475 }
476 }
477}
478
479impl<'a, 'o, G, T, SC>
483 MultiDeterminantSymmetryOrbit<
484 'a,
485 'o,
486 G,
487 T,
488 OrbitBasis<G, SlaterDeterminant<'a, T, SC>>,
489 SC,
490 >
491where
492 G: SymmetryGroupProperties + Clone,
493 G::CharTab: SubspaceDecomposable<T>,
494 T: Lapack
495 + ComplexFloat<Real = <T as Scalar>::Real>
496 + fmt::Debug
497 + Mul<<T as ComplexFloat>::Real, Output = T>,
498 <T as ComplexFloat>::Real: fmt::Debug
499 + Zero
500 + From<u16>
501 + ToPrimitive
502 + approx::RelativeEq<<T as ComplexFloat>::Real>
503 + approx::AbsDiffEq<Epsilon = <T as Scalar>::Real>,
504 SC: StructureConstraint + Hash + Eq + Clone + fmt::Display,
505 MultiDeterminant<'a, T, OrbitBasis<G, SlaterDeterminant<'a, T, SC>>, SC>:
506 SymmetryTransformable,
507{
508 #[allow(dead_code)]
525 pub(crate) fn calc_smat_optimised(
526 &mut self,
527 metric: Option<&Array2<T>>,
528 metric_h: Option<&Array2<T>>,
529 use_cayley_table: bool,
530 ) -> Result<&mut Self, anyhow::Error> {
531 ensure!(
532 self.group.name() == self.origin.basis.group().name(),
533 "Multi-determinantal wavefunction orbit-generating group does not match symmetry analysis group."
534 );
535
536 if let (Some(ctb), true) = (self.group().cayley_table(), use_cayley_table) {
537 log::debug!(
538 "Cayley table available. Group closure will be used to speed up overlap matrix computation."
539 );
540
541 let order = self.group.order();
542 let mut smat = Array2::<T>::zeros((order, order));
543 let multidet_0 = self.origin();
544 let det_origins = multidet_0.basis.origins();
545 let n_det_origins = det_origins.len();
546
547 let detov_kpwx_vec = multidet_0
548 .basis
549 .iter()
550 .collect::<Result<Vec<_>, _>>()?
551 .into_iter()
552 .cartesian_product(det_origins.iter())
553 .map(|(w, x)| w.overlap(x, metric, metric_h))
554 .collect::<Result<Vec<_>, _>>()?;
555 let detov_kpwx =
556 Array3::from_shape_vec((order, n_det_origins, n_det_origins), detov_kpwx_vec)?;
557 let ovs = (0..order)
558 .map(|k| {
559 [
560 (0..order),
561 (0..order),
562 (0..n_det_origins),
563 (0..n_det_origins),
564 ]
565 .into_iter()
566 .multi_cartesian_product()
567 .try_fold(T::zero(), |acc, v| {
568 let ip = v[0];
569 let jp = v[1];
570 let w = v[2];
571 let x = v[3];
572 let aipw = multidet_0.coefficients[ip * n_det_origins + w];
573 let ajpx = multidet_0.coefficients[jp * n_det_origins + x];
574
575 let jpinv = ctb.slice(s![.., jp]).iter().position(|&x| x == 0).ok_or(
576 format_err!("Unable to find the inverse of group element `{jp}`."),
577 )?;
578 let kp = ctb[(jpinv, ctb[(k, ip)])];
579 let ukipwjpx = self.norm_preserving_scalar_map(jp)?(detov_kpwx[(kp, w, x)]);
580
581 Ok::<_, anyhow::Error>(
582 acc + self.norm_preserving_scalar_map(k)?(aipw.conj())
583 * ukipwjpx
584 * ajpx,
585 )
586 })
587 })
588 .collect::<Result<Vec<_>, _>>()?;
589 for (i, j) in (0..order).cartesian_product(0..order) {
590 let jinv = ctb
591 .slice(s![.., j])
592 .iter()
593 .position(|&x| x == 0)
594 .ok_or(format_err!(
595 "Unable to find the inverse of group element `{j}`."
596 ))?;
597 let jinv_i = ctb[(jinv, i)];
598 smat[(i, j)] = self.norm_preserving_scalar_map(jinv)?(ovs[jinv_i]);
599 }
600 if self.origin().complex_symmetric() {
601 let _ = check_complex_matrix_symmetry(
602 &smat.view(),
603 true,
604 self.orbit_overlap_symmetry_threshold,
605 "Orbit overlap",
606 "S_orbit",
607 );
608 self.set_smat(
609 (smat.clone() + smat.t().to_owned()).mapv(|x| x / (T::one() + T::one())),
610 )
611 } else {
612 let _ = check_complex_matrix_symmetry(
613 &smat.view(),
614 false,
615 self.orbit_overlap_symmetry_threshold,
616 "Orbit overlap",
617 "S_orbit",
618 );
619 self.set_smat(
620 (smat.clone() + smat.t().to_owned().mapv(|x| x.conj()))
621 .mapv(|x| x / (T::one() + T::one())),
622 )
623 }
624 Ok(self)
625 } else {
626 self.calc_smat(metric, metric_h, use_cayley_table)
627 }
628 }
629}
630
631impl<'a, 'o, G, T, SC> MultiDeterminantSymmetryOrbit<'a, 'o, G, T, FCIBasis<'a, T, SC>, SC>
635where
636 G: SymmetryGroupProperties + Clone,
637 G::CharTab: SubspaceDecomposable<T>,
638 T: Lapack
639 + ComplexFloat<Real = <T as Scalar>::Real>
640 + fmt::Debug
641 + Mul<<T as ComplexFloat>::Real, Output = T>
642 + Sync
643 + Send,
644 <T as ComplexFloat>::Real: fmt::Debug
645 + Zero
646 + From<u16>
647 + ToPrimitive
648 + approx::RelativeEq<<T as ComplexFloat>::Real>
649 + approx::AbsDiffEq<Epsilon = <T as Scalar>::Real>
650 + Sync
651 + Send,
652 SC: StructureConstraint + Hash + Eq + Clone + fmt::Display + Sync + Send,
653 MultiDeterminant<'a, T, FCIBasis<'a, T, SC>, SC>: SymmetryTransformable,
654 FCIBasis<'a, T, SC>: SymmetryTransformable,
655{
656 #[allow(dead_code)]
672 pub(crate) fn calc_smat_optimised(
673 &mut self,
674 metric: Option<&Array2<T>>,
675 metric_h: Option<&Array2<T>>,
676 use_cayley_table: bool,
677 ) -> Result<&mut Self, anyhow::Error> {
678 if let (Some(ctb), true) = (self.group().cayley_table(), use_cayley_table) {
679 log::debug!(
680 "Cayley table available. Group closure will be used to speed up overlap matrix computation."
681 );
682
683 let order = self.group.order();
684 let mut smat = Array2::<T>::zeros((order, order));
685
686 let multidet_0 = self.origin();
687 let fci_basis_0 = multidet_0.basis();
688
689 #[allow(non_snake_case)]
692 let ov_iI_0J = fci_basis_0.basis_symmetry_orbit_metric(
693 self.group(),
694 &self.symmetry_transformation_kind,
695 metric,
696 metric_h,
697 )?;
698
699 #[allow(non_snake_case)]
700 let cJ = multidet_0.coefficients();
701 #[allow(non_snake_case)]
702 let ov_i_vec = ov_iI_0J
703 .axis_iter(Axis(0))
704 .enumerate()
705 .map(|(i, ov_I_0J)| {
706 let cI = multidet_0
707 .coefficients()
708 .mapv(self.norm_preserving_scalar_map(i)?);
709 if multidet_0.complex_symmetric() {
710 einsum("ij,i,j->", &[&ov_I_0J, &cI, cJ])
711 .map_err(|err| format_err!(err))?
712 .into_dimensionality::<Ix0>()
713 .map_err(|err| format_err!(err))?
714 .into_iter()
715 .next()
716 .ok_or(format_err!(
717 "Unable to extract the overlap value between `gΦ0` and `Φ0`."
718 ))
719 } else {
720 einsum("ij,i,j->", &[&ov_I_0J, &cI.mapv(ComplexFloat::conj), cJ])
721 .map_err(|err| format_err!(err))?
722 .into_dimensionality::<Ix0>()
723 .map_err(|err| format_err!(err))?
724 .into_iter()
725 .next()
726 .ok_or(format_err!(
727 "Unable to extract the overlap value between `gΦ0` and `Φ0`."
728 ))
729 }
730 })
731 .collect::<Result<Vec<_>, _>>()?;
732
733 for (i, j) in (0..order).cartesian_product(0..order) {
734 let jinv = ctb
735 .slice(s![.., j])
736 .iter()
737 .position(|&x| x == 0)
738 .ok_or(format_err!(
739 "Unable to find the inverse of group element `{j}`."
740 ))?;
741 let jinv_i = ctb[(jinv, i)];
742 smat[(i, j)] = self.norm_preserving_scalar_map(jinv)?(ov_i_vec[jinv_i]);
743 }
744 if self.origin().complex_symmetric() {
745 let _ = check_complex_matrix_symmetry(
746 &smat.view(),
747 true,
748 self.orbit_overlap_symmetry_threshold,
749 "Orbit overlap",
750 "S_orbit",
751 );
752 self.set_smat(
753 (smat.clone() + smat.t().to_owned()).mapv(|x| x / (T::one() + T::one())),
754 )
755 } else {
756 let _ = check_complex_matrix_symmetry(
757 &smat.view(),
758 false,
759 self.orbit_overlap_symmetry_threshold,
760 "Orbit overlap",
761 "S_orbit",
762 );
763 self.set_smat(
764 (smat.clone() + smat.t().to_owned().mapv(|x| x.conj()))
765 .mapv(|x| x / (T::one() + T::one())),
766 )
767 }
768 Ok(self)
769 } else {
770 self.calc_smat(metric, metric_h, use_cayley_table)
771 }
772 }
773}