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::target::noci::backend::solver::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 integrality_threshold: <T as ComplexFloat>::Real,
159
160 symmetry_transformation_kind: SymmetryTransformationKind,
163
164 #[builder(setter(skip), default = "None")]
167 smat: Option<Array2<T>>,
168
169 #[builder(setter(skip), default = "None")]
172 pub(crate) smat_eigvals: Option<Array1<T>>,
173
174 #[builder(setter(skip), default = "None")]
179 xmat: Option<Array2<T>>,
180
181 eigenvalue_comparison_mode: EigenvalueComparisonMode,
184}
185
186impl<'a, 'g, G, T, B, SC> MultiDeterminantSymmetryOrbit<'a, 'g, G, T, B, SC>
191where
192 G: SymmetryGroupProperties + Clone,
193 T: ComplexFloat + fmt::Debug + Lapack,
194 SC: StructureConstraint + Hash + Eq + Clone + fmt::Display,
195 B: 'a + Basis<Item = SlaterDeterminant<'a, T, SC>> + Clone,
196 MultiDeterminant<'a, T, B, SC>: SymmetryTransformable,
197{
198 pub fn builder() -> MultiDeterminantSymmetryOrbitBuilder<'a, 'g, G, T, B, SC> {
200 MultiDeterminantSymmetryOrbitBuilder::default()
201 }
202
203 pub fn origin(&self) -> &MultiDeterminant<'a, T, B, SC> {
205 self.origin
206 }
207}
208
209impl<'a, 'g, G, B, SC> MultiDeterminantSymmetryOrbit<'a, 'g, G, f64, B, SC>
210where
211 G: SymmetryGroupProperties,
212 SC: StructureConstraint + Hash + Eq + fmt::Display,
213 B: 'a + Basis<Item = SlaterDeterminant<'a, f64, SC>> + Clone,
214 MultiDeterminant<'a, f64, B, SC>: SymmetryTransformable,
215{
216 fn_calc_xmat_real!(
217 pub calc_xmat
229 );
230}
231
232impl<'a, 'g, G, T, B, SC> MultiDeterminantSymmetryOrbit<'a, 'g, G, Complex<T>, B, SC>
233where
234 G: SymmetryGroupProperties,
235 T: Float + Scalar<Complex = Complex<T>>,
236 Complex<T>: ComplexFloat<Real = T> + Scalar<Real = T, Complex = Complex<T>> + Lapack,
237 SC: StructureConstraint + Hash + Eq + fmt::Display,
238 B: 'a + Basis<Item = SlaterDeterminant<'a, Complex<T>, SC>> + Clone,
239 MultiDeterminant<'a, Complex<T>, B, SC>: SymmetryTransformable + Overlap<Complex<T>, Ix2>,
240{
241 fn_calc_xmat_complex!(
242 pub calc_xmat
254 );
255}
256
257impl<'a, 'o, G, T, B, SC> Orbit<G, MultiDeterminant<'a, T, B, SC>>
266 for MultiDeterminantSymmetryOrbit<'a, 'o, G, T, B, SC>
267where
268 G: SymmetryGroupProperties,
269 T: ComplexFloat + fmt::Debug + Lapack,
270 SC: 'a + StructureConstraint + Hash + Eq + fmt::Display,
271 B: 'a + Basis<Item = SlaterDeterminant<'a, T, SC>> + Clone,
272 MultiDeterminant<'a, T, B, SC>: SymmetryTransformable,
273{
274 type OrbitIter = OrbitIterator<'o, G, MultiDeterminant<'a, T, B, SC>>;
275
276 fn group(&self) -> &G {
277 self.group
278 }
279
280 fn origin(&self) -> &MultiDeterminant<'a, T, B, SC> {
281 self.origin
282 }
283
284 fn iter(&self) -> Self::OrbitIter {
285 OrbitIterator::new(
286 self.group,
287 self.origin,
288 match self.symmetry_transformation_kind {
289 SymmetryTransformationKind::Spatial => |op, multidet| {
290 multidet.sym_transform_spatial(op).with_context(|| {
291 format!("Unable to apply `{op}` spatially on the origin multi-determinantal wavefunction")
292 })
293 },
294 SymmetryTransformationKind::SpatialWithSpinTimeReversal => |op, multidet| {
295 multidet.sym_transform_spatial_with_spintimerev(op).with_context(|| {
296 format!("Unable to apply `{op}` spatially (with spin-including time reversal) on the origin multi-determinantal wavefunction")
297 })
298 },
299 SymmetryTransformationKind::Spin => |op, multidet| {
300 multidet.sym_transform_spin(op).with_context(|| {
301 format!("Unable to apply `{op}` spin-wise on the origin multi-determinantal wavefunction")
302 })
303 },
304 SymmetryTransformationKind::SpinSpatial => |op, multidet| {
305 multidet.sym_transform_spin_spatial(op).with_context(|| {
306 format!("Unable to apply `{op}` spin-spatially on the origin multi-determinantal wavefunction")
307 })
308 },
309 },
310 )
311 }
312}
313
314impl<'a, 'o, G, T, B, SC> RepAnalysis<G, MultiDeterminant<'a, T, B, SC>, T, Ix2>
319 for MultiDeterminantSymmetryOrbit<'a, 'o, G, T, B, SC>
320where
321 G: SymmetryGroupProperties,
322 G::CharTab: SubspaceDecomposable<T>,
323 T: Lapack
324 + ComplexFloat<Real = <T as Scalar>::Real>
325 + fmt::Debug
326 + Mul<<T as ComplexFloat>::Real, Output = T>,
327 <T as ComplexFloat>::Real: fmt::Debug
328 + Zero
329 + From<u16>
330 + ToPrimitive
331 + approx::RelativeEq<<T as ComplexFloat>::Real>
332 + approx::AbsDiffEq<Epsilon = <T as Scalar>::Real>,
333 SC: 'a + StructureConstraint + Hash + Eq + Clone + fmt::Display,
334 B: 'a + Basis<Item = SlaterDeterminant<'a, T, SC>> + Clone,
335 MultiDeterminant<'a, T, B, SC>: SymmetryTransformable,
336{
337 fn set_smat(&mut self, smat: Array2<T>) {
338 self.smat = Some(smat)
339 }
340
341 fn smat(&self) -> Option<&Array2<T>> {
342 self.smat.as_ref()
343 }
344
345 fn xmat(&self) -> &Array2<T> {
346 self.xmat
347 .as_ref()
348 .expect("Orbit overlap orthogonalisation matrix not found.")
349 }
350
351 fn norm_preserving_scalar_map(&self, i: usize) -> Result<fn(T) -> T, anyhow::Error> {
352 if self.origin.complex_symmetric {
353 Err(format_err!(
354 "`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."
355 ))
356 } else if self
357 .group
358 .get_index(i)
359 .unwrap_or_else(|| panic!("Group operation index `{i}` not found."))
360 .contains_time_reversal()
361 {
362 Ok(ComplexFloat::conj)
363 } else {
364 Ok(|x| x)
365 }
366 }
367
368 fn integrality_threshold(&self) -> <T as ComplexFloat>::Real {
369 self.integrality_threshold
370 }
371
372 fn eigenvalue_comparison_mode(&self) -> &EigenvalueComparisonMode {
373 &self.eigenvalue_comparison_mode
374 }
375
376 fn analyse_rep(
392 &self,
393 ) -> Result<
394 <<G as CharacterProperties>::CharTab as SubspaceDecomposable<T>>::Decomposition,
395 DecompositionError,
396 > {
397 log::debug!("Analysing representation symmetry for a multi-determinantal wavefunction...");
398 let mut nelectrons_set = self
399 .origin()
400 .basis
401 .iter()
402 .map(|det_res| det_res.and_then(|det| {
403 let det_nelectrons_float = det.nelectrons();
404 if approx::relative_eq!(
405 det_nelectrons_float.round(),
406 det_nelectrons_float,
407 epsilon = self.integrality_threshold,
408 max_relative = self.integrality_threshold
409 ) {
410 det_nelectrons_float.round().to_usize().ok_or(format_err!("Unable to convert the number of electrons `{det_nelectrons_float:.7}` to `usize`."))
411 } else {
412 Err(format_err!("Fractional number of electrons encountered: `{det_nelectrons_float:.7}`"))
413 }
414 }))
415 .collect::<Result<HashSet<_>, _>>()
416 .map_err(|err| DecompositionError(err.to_string()))?;
417 if nelectrons_set.len() != 1 {
418 Err(DecompositionError("Symmetry analysis for multi-determinantal wavefunctions with multiple numbers of electrons is not yet supported.".to_string()))
419 } else {
420 let nelectrons = nelectrons_set.drain().next().ok_or(DecompositionError(
421 "Unable to obtain the number of electrons.".to_string(),
422 ))?;
423 let (valid_symmetry, err_str) = if nelectrons.rem_euclid(2) == 0 {
424 (true, String::new())
426 } else {
427 match self.symmetry_transformation_kind {
429 SymmetryTransformationKind::Spatial => (true, String::new()),
430 SymmetryTransformationKind::SpatialWithSpinTimeReversal
431 | SymmetryTransformationKind::Spin
432 | SymmetryTransformationKind::SpinSpatial => {
433 match self.group().group_type() {
434 GroupType::Ordinary(_) => (true, String::new()),
435 GroupType::MagneticGrey(_) | GroupType::MagneticBlackWhite(_) => {
436 (!self.group().unitary_represented(),
437 "Unitary-represented magnetic groups cannot be used for symmetry analysis of odd-electron systems where spin is treated explicitly.".to_string())
438 }
439 }
440 }
441 }
442 };
443
444 if valid_symmetry {
445 let chis = self
446 .calc_characters()
447 .map_err(|err| DecompositionError(err.to_string()))?;
448 log::debug!("Characters calculated.");
449
450 log_subtitle("Multi-determinantal wavefunction orbit characters");
451 qsym2_output!("");
452 self.characters_to_string(&chis, self.integrality_threshold)
453 .log_output_display();
454 qsym2_output!("");
455
456 let res = self.group().character_table().reduce_characters(
457 &chis.iter().map(|(cc, chi)| (cc, *chi)).collect::<Vec<_>>(),
458 self.integrality_threshold(),
459 );
460 log::debug!("Characters reduced.");
461 log::debug!(
462 "Analysing representation symmetry for a multi-determinantal wavefunction... Done."
463 );
464 res
465 } else {
466 Err(DecompositionError(err_str))
467 }
468 }
469 }
470}
471
472impl<'a, 'o, 'g, G, T, SC>
476 MultiDeterminantSymmetryOrbit<
477 'a,
478 'o,
479 G,
480 T,
481 OrbitBasis<'g, G, SlaterDeterminant<'a, T, SC>>,
482 SC,
483 >
484where
485 G: SymmetryGroupProperties + Clone,
486 G::CharTab: SubspaceDecomposable<T>,
487 T: Lapack
488 + ComplexFloat<Real = <T as Scalar>::Real>
489 + fmt::Debug
490 + Mul<<T as ComplexFloat>::Real, Output = T>,
491 <T as ComplexFloat>::Real: fmt::Debug
492 + Zero
493 + From<u16>
494 + ToPrimitive
495 + approx::RelativeEq<<T as ComplexFloat>::Real>
496 + approx::AbsDiffEq<Epsilon = <T as Scalar>::Real>,
497 SC: StructureConstraint + Hash + Eq + Clone + fmt::Display,
498 MultiDeterminant<'a, T, OrbitBasis<'g, G, SlaterDeterminant<'a, T, SC>>, SC>:
499 SymmetryTransformable,
500{
501 #[allow(dead_code)]
518 pub(crate) fn calc_smat_optimised(
519 &mut self,
520 metric: Option<&Array2<T>>,
521 metric_h: Option<&Array2<T>>,
522 use_cayley_table: bool,
523 ) -> Result<&mut Self, anyhow::Error> {
524 ensure!(
525 self.group.name() == self.origin.basis.group().name(),
526 "Multi-determinantal wavefunction orbit-generating group does not match symmetry analysis group."
527 );
528
529 if let (Some(ctb), true) = (self.group().cayley_table(), use_cayley_table) {
530 log::debug!(
531 "Cayley table available. Group closure will be used to speed up overlap matrix computation."
532 );
533
534 let order = self.group.order();
535 let mut smat = Array2::<T>::zeros((order, order));
536 let multidet_0 = self.origin();
537 let det_origins = multidet_0.basis.origins();
538 let n_det_origins = det_origins.len();
539
540 let detov_kpwx_vec = multidet_0
541 .basis
542 .iter()
543 .collect::<Result<Vec<_>, _>>()?
544 .into_iter()
545 .cartesian_product(det_origins.iter())
546 .map(|(w, x)| w.overlap(x, metric, metric_h))
547 .collect::<Result<Vec<_>, _>>()?;
548 let detov_kpwx =
549 Array3::from_shape_vec((order, n_det_origins, n_det_origins), detov_kpwx_vec)?;
550 let ovs = (0..order)
551 .map(|k| {
552 [
553 (0..order),
554 (0..order),
555 (0..n_det_origins),
556 (0..n_det_origins),
557 ]
558 .into_iter()
559 .multi_cartesian_product()
560 .try_fold(T::zero(), |acc, v| {
561 let ip = v[0];
562 let jp = v[1];
563 let w = v[2];
564 let x = v[3];
565 let aipw = multidet_0.coefficients[ip * n_det_origins + w];
566 let ajpx = multidet_0.coefficients[jp * n_det_origins + x];
567
568 let jpinv = ctb.slice(s![.., jp]).iter().position(|&x| x == 0).ok_or(
569 format_err!("Unable to find the inverse of group element `{jp}`."),
570 )?;
571 let kp = ctb[(jpinv, ctb[(k, ip)])];
572 let ukipwjpx = self.norm_preserving_scalar_map(jp)?(detov_kpwx[(kp, w, x)]);
573
574 Ok::<_, anyhow::Error>(
575 acc + self.norm_preserving_scalar_map(k)?(aipw.conj())
576 * ukipwjpx
577 * ajpx,
578 )
579 })
580 })
581 .collect::<Result<Vec<_>, _>>()?;
582 for (i, j) in (0..order).cartesian_product(0..order) {
583 let jinv = ctb
584 .slice(s![.., j])
585 .iter()
586 .position(|&x| x == 0)
587 .ok_or(format_err!(
588 "Unable to find the inverse of group element `{j}`."
589 ))?;
590 let jinv_i = ctb[(jinv, i)];
591 smat[(i, j)] = self.norm_preserving_scalar_map(jinv)?(ovs[jinv_i]);
592 }
593 if self.origin().complex_symmetric() {
594 let _ = check_complex_matrix_symmetry(
595 &smat.view(),
596 true,
597 self.linear_independence_threshold,
598 "Orbit overlap",
599 "S_orbit",
600 );
601 self.set_smat(
602 (smat.clone() + smat.t().to_owned()).mapv(|x| x / (T::one() + T::one())),
603 )
604 } else {
605 let _ = check_complex_matrix_symmetry(
606 &smat.view(),
607 false,
608 self.linear_independence_threshold,
609 "Orbit overlap",
610 "S_orbit",
611 );
612 self.set_smat(
613 (smat.clone() + smat.t().to_owned().mapv(|x| x.conj()))
614 .mapv(|x| x / (T::one() + T::one())),
615 )
616 }
617 Ok(self)
618 } else {
619 self.calc_smat(metric, metric_h, use_cayley_table)
620 }
621 }
622}
623
624impl<'a, 'o, G, T, SC> MultiDeterminantSymmetryOrbit<'a, 'o, G, T, FCIBasis<'a, T, SC>, SC>
628where
629 G: SymmetryGroupProperties + Clone,
630 G::CharTab: SubspaceDecomposable<T>,
631 T: Lapack
632 + ComplexFloat<Real = <T as Scalar>::Real>
633 + fmt::Debug
634 + Mul<<T as ComplexFloat>::Real, Output = T>
635 + Sync
636 + Send,
637 <T as ComplexFloat>::Real: fmt::Debug
638 + Zero
639 + From<u16>
640 + ToPrimitive
641 + approx::RelativeEq<<T as ComplexFloat>::Real>
642 + approx::AbsDiffEq<Epsilon = <T as Scalar>::Real>
643 + Sync
644 + Send,
645 SC: StructureConstraint + Hash + Eq + Clone + fmt::Display + Sync + Send,
646 MultiDeterminant<'a, T, FCIBasis<'a, T, SC>, SC>: SymmetryTransformable,
647 FCIBasis<'a, T, SC>: SymmetryTransformable,
648{
649 #[allow(dead_code)]
665 pub(crate) fn calc_smat_optimised(
666 &mut self,
667 metric: Option<&Array2<T>>,
668 metric_h: Option<&Array2<T>>,
669 use_cayley_table: bool,
670 ) -> Result<&mut Self, anyhow::Error> {
671 if let (Some(ctb), true) = (self.group().cayley_table(), use_cayley_table) {
672 log::debug!(
673 "Cayley table available. Group closure will be used to speed up overlap matrix computation."
674 );
675
676 let order = self.group.order();
677 let mut smat = Array2::<T>::zeros((order, order));
678
679 let multidet_0 = self.origin();
680 let fci_basis_0 = multidet_0.basis();
681
682 #[allow(non_snake_case)]
685 let ov_iI_0J = fci_basis_0.basis_symmetry_orbit_metric(
686 self.group(),
687 &self.symmetry_transformation_kind,
688 metric,
689 metric_h,
690 )?;
691
692 #[allow(non_snake_case)]
693 let cJ = multidet_0.coefficients();
694 #[allow(non_snake_case)]
695 let ov_i_vec = ov_iI_0J
696 .axis_iter(Axis(0))
697 .enumerate()
698 .map(|(i, ov_I_0J)| {
699 let cI = multidet_0
700 .coefficients()
701 .mapv(self.norm_preserving_scalar_map(i)?);
702 if multidet_0.complex_symmetric() {
703 einsum("ij,i,j->", &[&ov_I_0J, &cI, cJ])
704 .map_err(|err| format_err!(err))?
705 .into_dimensionality::<Ix0>()
706 .map_err(|err| format_err!(err))?
707 .into_iter()
708 .next()
709 .ok_or(format_err!(
710 "Unable to extract the overlap value between `gΦ0` and `Φ0`."
711 ))
712 } else {
713 einsum("ij,i,j->", &[&ov_I_0J, &cI.mapv(ComplexFloat::conj), cJ])
714 .map_err(|err| format_err!(err))?
715 .into_dimensionality::<Ix0>()
716 .map_err(|err| format_err!(err))?
717 .into_iter()
718 .next()
719 .ok_or(format_err!(
720 "Unable to extract the overlap value between `gΦ0` and `Φ0`."
721 ))
722 }
723 })
724 .collect::<Result<Vec<_>, _>>()?;
725
726 for (i, j) in (0..order).cartesian_product(0..order) {
727 let jinv = ctb
728 .slice(s![.., j])
729 .iter()
730 .position(|&x| x == 0)
731 .ok_or(format_err!(
732 "Unable to find the inverse of group element `{j}`."
733 ))?;
734 let jinv_i = ctb[(jinv, i)];
735 smat[(i, j)] = self.norm_preserving_scalar_map(jinv)?(ov_i_vec[jinv_i]);
736 }
737 if self.origin().complex_symmetric() {
738 let _ = check_complex_matrix_symmetry(
739 &smat.view(),
740 true,
741 self.linear_independence_threshold,
742 "Orbit overlap",
743 "S_orbit",
744 );
745 self.set_smat(
746 (smat.clone() + smat.t().to_owned()).mapv(|x| x / (T::one() + T::one())),
747 )
748 } else {
749 let _ = check_complex_matrix_symmetry(
750 &smat.view(),
751 false,
752 self.linear_independence_threshold,
753 "Orbit overlap",
754 "S_orbit",
755 );
756 self.set_smat(
757 (smat.clone() + smat.t().to_owned().mapv(|x| x.conj()))
758 .mapv(|x| x / (T::one() + T::one())),
759 )
760 }
761 Ok(self)
762 } else {
763 self.calc_smat(metric, metric_h, use_cayley_table)
764 }
765 }
766}