1use std::cmp::max;
4use std::error::Error;
5use std::fmt;
6use std::iter;
7use std::ops::Mul;
8
9use derive_builder::Builder;
10use indexmap::{IndexMap, IndexSet};
11use ndarray::{Array2, ArrayView1};
12use num_complex::{Complex, ComplexFloat};
13use num_traits::{ToPrimitive, Zero};
14use rayon::prelude::*;
15use serde::{de::DeserializeOwned, Deserialize, Serialize};
16
17use crate::chartab::character::Character;
18use crate::chartab::chartab_symbols::{
19 CollectionSymbol, DecomposedSymbol, LinearSpaceSymbol, ReducibleLinearSpaceSymbol,
20 FROBENIUS_SCHUR_SYMBOLS,
21};
22
23pub mod character;
24pub mod chartab_group;
25pub mod chartab_symbols;
26pub(crate) mod modular_linalg;
27pub(crate) mod reducedint;
28pub mod unityroot;
29
30pub trait CharacterTable: Clone
36where
37 Self::RowSymbol: LinearSpaceSymbol,
38 Self::ColSymbol: CollectionSymbol,
39{
40 type RowSymbol;
42
43 type ColSymbol;
45
46 fn get_character(&self, irrep: &Self::RowSymbol, class: &Self::ColSymbol) -> &Character;
62
63 fn get_row(&self, row: &Self::RowSymbol) -> ArrayView1<Character>;
73
74 fn get_col(&self, col: &Self::ColSymbol) -> ArrayView1<Character>;
84
85 fn get_all_rows(&self) -> IndexSet<Self::RowSymbol>;
87
88 fn get_all_cols(&self) -> IndexSet<Self::ColSymbol>;
90
91 fn array(&self) -> &Array2<Character>;
93
94 fn get_order(&self) -> usize;
96
97 fn get_principal_cols(&self) -> &IndexSet<Self::ColSymbol>;
99
100 fn write_nice_table(
118 &self,
119 f: &mut fmt::Formatter,
120 compact: bool,
121 numerical: Option<usize>,
122 ) -> fmt::Result;
123}
124
125pub trait SubspaceDecomposable<T>: CharacterTable
128where
129 T: ComplexFloat,
130 <T as ComplexFloat>::Real: ToPrimitive,
131 Self::Decomposition: ReducibleLinearSpaceSymbol<Subspace = Self::RowSymbol>,
132{
133 type Decomposition;
135
136 fn reduce_characters(
148 &self,
149 characters: &[(&Self::ColSymbol, T)],
150 thresh: T::Real,
151 ) -> Result<Self::Decomposition, DecompositionError>;
152}
153
154#[derive(Debug, Clone)]
155pub struct DecompositionError(pub String);
156
157impl fmt::Display for DecompositionError {
158 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
159 write!(f, "Subspace decomposition error: {}", self.0)
160 }
161}
162
163impl Error for DecompositionError {}
164
165#[derive(Builder, Clone, Serialize, Deserialize)]
175pub struct RepCharacterTable<RowSymbol, ColSymbol>
176where
177 RowSymbol: LinearSpaceSymbol,
178 ColSymbol: CollectionSymbol,
179{
180 pub name: String,
182
183 pub(crate) irreps: IndexMap<RowSymbol, usize>,
186
187 pub(crate) classes: IndexMap<ColSymbol, usize>,
189
190 principal_classes: IndexSet<ColSymbol>,
192
193 pub(crate) characters: Array2<Character>,
195
196 pub(crate) frobenius_schurs: IndexMap<RowSymbol, i8>,
198}
199
200impl<RowSymbol, ColSymbol> RepCharacterTable<RowSymbol, ColSymbol>
201where
202 RowSymbol: LinearSpaceSymbol,
203 ColSymbol: CollectionSymbol,
204{
205 fn builder() -> RepCharacterTableBuilder<RowSymbol, ColSymbol> {
207 RepCharacterTableBuilder::default()
208 }
209
210 pub(crate) fn new(
230 name: &str,
231 irreps: &[RowSymbol],
232 classes: &[ColSymbol],
233 principal_classes: &[ColSymbol],
234 char_arr: Array2<Character>,
235 frobenius_schurs: &[i8],
236 ) -> Self {
237 assert_eq!(irreps.len(), char_arr.dim().0);
238 assert_eq!(frobenius_schurs.len(), char_arr.dim().0);
239 assert_eq!(classes.len(), char_arr.dim().1);
240 assert_eq!(char_arr.dim().0, char_arr.dim().1);
241
242 let irreps_indexmap: IndexMap<RowSymbol, usize> = irreps
243 .iter()
244 .cloned()
245 .enumerate()
246 .map(|(i, irrep)| (irrep, i))
247 .collect();
248
249 let classes_indexmap: IndexMap<ColSymbol, usize> = classes
250 .iter()
251 .cloned()
252 .enumerate()
253 .map(|(i, class)| (class, i))
254 .collect();
255
256 let principal_classes_indexset: IndexSet<ColSymbol> =
257 principal_classes.iter().cloned().collect();
258
259 let frobenius_schurs_indexmap = iter::zip(irreps, frobenius_schurs)
260 .map(|(irrep, &fsi)| (irrep.clone(), fsi))
261 .collect::<IndexMap<_, _>>();
262
263 Self::builder()
264 .name(name.to_string())
265 .irreps(irreps_indexmap)
266 .classes(classes_indexmap)
267 .principal_classes(principal_classes_indexset)
268 .characters(char_arr)
269 .frobenius_schurs(frobenius_schurs_indexmap)
270 .build()
271 .expect("Unable to construct a character table.")
272 }
273}
274
275impl<RowSymbol, ColSymbol> CharacterTable for RepCharacterTable<RowSymbol, ColSymbol>
276where
277 RowSymbol: LinearSpaceSymbol,
278 ColSymbol: CollectionSymbol,
279{
280 type RowSymbol = RowSymbol;
281 type ColSymbol = ColSymbol;
282
283 fn get_character(&self, irrep: &Self::RowSymbol, class: &Self::ColSymbol) -> &Character {
299 let row = self
300 .irreps
301 .get(irrep)
302 .unwrap_or_else(|| panic!("Irrep `{irrep}` not found."));
303 let col = self
304 .classes
305 .get(class)
306 .unwrap_or_else(|| panic!("Conjugacy class `{class}` not found."));
307 &self.characters[(*row, *col)]
308 }
309
310 fn get_row(&self, irrep: &Self::RowSymbol) -> ArrayView1<Character> {
321 let row = self
322 .irreps
323 .get(irrep)
324 .unwrap_or_else(|| panic!("Irrep `{irrep}` not found."));
325 self.characters.row(*row)
326 }
327
328 fn get_col(&self, class: &Self::ColSymbol) -> ArrayView1<Character> {
339 let col = self
340 .classes
341 .get(class)
342 .unwrap_or_else(|| panic!("Conjugacy class `{class}` not found."));
343 self.characters.column(*col)
344 }
345
346 fn get_all_rows(&self) -> IndexSet<Self::RowSymbol> {
348 self.irreps.keys().cloned().collect::<IndexSet<_>>()
349 }
350
351 fn get_all_cols(&self) -> IndexSet<Self::ColSymbol> {
353 self.classes.keys().cloned().collect::<IndexSet<_>>()
354 }
355
356 fn array(&self) -> &Array2<Character> {
358 &self.characters
359 }
360
361 fn get_order(&self) -> usize {
363 self.classes.keys().map(|cc| cc.size()).sum()
364 }
365
366 #[allow(clippy::too_many_lines)]
389 fn write_nice_table(
390 &self,
391 f: &mut fmt::Formatter,
392 compact: bool,
393 numerical: Option<usize>,
394 ) -> fmt::Result {
395 let group_order = self.get_order();
396
397 let name = format!("u {} ({group_order})", self.name);
398 let chars_str = self.characters.map(|character| {
399 if let Some(precision) = numerical {
400 let real_only = self.characters.iter().all(|character| {
401 approx::relative_eq!(
402 character.complex_value().im,
403 0.0,
404 epsilon = character.threshold(),
405 max_relative = character.threshold()
406 )
407 });
408 character.get_numerical(real_only, precision)
409 } else {
410 character.to_string()
411 }
412 });
413 let irreps_str: Vec<_> = self
414 .irreps
415 .keys()
416 .map(std::string::ToString::to_string)
417 .collect();
418 let ccs_str: Vec<_> = self
419 .classes
420 .keys()
421 .map(|cc| {
422 if self.principal_classes.contains(cc) {
423 format!("◈{cc}")
424 } else {
425 cc.to_string()
426 }
427 })
428 .collect();
429
430 let first_width = max(
431 irreps_str
432 .iter()
433 .map(|irrep_str| irrep_str.chars().count())
434 .max()
435 .expect("Unable to find the maximum length for the irrep symbols."),
436 name.chars().count(),
437 ) + 1;
438
439 let digit_widths: Vec<_> = if compact {
440 iter::zip(chars_str.columns(), &ccs_str)
441 .map(|(chars_col_str, cc_str)| {
442 let char_width = chars_col_str
443 .iter()
444 .map(|c| c.chars().count())
445 .max()
446 .expect("Unable to find the maximum length for the characters.");
447 let cc_width = cc_str.chars().count();
448 max(char_width, cc_width) + 1
449 })
450 .collect()
451 } else {
452 let char_width = chars_str
453 .iter()
454 .map(|c| c.chars().count())
455 .max()
456 .expect("Unable to find the maximum length for the characters.");
457 let cc_width = ccs_str
458 .iter()
459 .map(|cc| cc.chars().count())
460 .max()
461 .expect("Unable to find the maximum length for the conjugacy class symbols.");
462 let fixed_width = max(char_width, cc_width) + 1;
463 iter::repeat(fixed_width).take(ccs_str.len()).collect()
464 };
465
466 let mut heading = format!(" {name:^first_width$} ┆ FS ║");
468 ccs_str.iter().enumerate().for_each(|(i, cc)| {
469 heading.push_str(&format!("{cc:>width$} │", width = digit_widths[i]));
470 });
471 heading.pop();
472 let tab_width = heading.chars().count();
473 heading = format!(
474 "{}\n{}\n{}\n",
475 "━".repeat(tab_width),
476 heading,
477 "┈".repeat(tab_width),
478 );
479 write!(f, "{heading}")?;
480
481 let rows =
483 iter::zip(self.irreps.keys(), irreps_str)
484 .enumerate()
485 .map(|(i, (irrep, irrep_str))| {
486 let ind = self.frobenius_schurs.get(irrep).unwrap_or_else(|| {
487 panic!(
488 "Unable to obtain the Frobenius--Schur indicator for irrep `{irrep}`."
489 )
490 });
491 let fs = FROBENIUS_SCHUR_SYMBOLS.get(ind).unwrap_or_else(|| {
492 panic!("Unknown Frobenius--Schur symbol for indicator {ind}.")
493 });
494 let mut line = format!(" {irrep_str:<first_width$} ┆ {fs:>2} ║");
495
496 let line_chars: String = itertools::Itertools::intersperse(
497 ccs_str.iter().enumerate().map(|(j, _)| {
498 format!("{:>width$}", chars_str[[i, j]], width = digit_widths[j])
499 }),
500 " │".to_string(),
501 )
502 .collect();
503
504 line.push_str(&line_chars);
505 line
506 });
507
508 write!(
509 f,
510 "{}",
511 &itertools::Itertools::intersperse(rows, "\n".to_string()).collect::<String>(),
512 )?;
513
514 write!(f, "\n{}\n", &"━".repeat(tab_width))
516 }
517
518 fn get_principal_cols(&self) -> &IndexSet<Self::ColSymbol> {
519 &self.principal_classes
520 }
521}
522
523impl<RowSymbol, ColSymbol, T> SubspaceDecomposable<T> for RepCharacterTable<RowSymbol, ColSymbol>
524where
525 RowSymbol: LinearSpaceSymbol + PartialOrd + Sync + Send,
526 ColSymbol: CollectionSymbol + Sync + Send,
527 T: ComplexFloat + Sync + Send,
528 <T as ComplexFloat>::Real: ToPrimitive + Sync + Send,
529 for<'a> Complex<f64>: Mul<&'a T, Output = Complex<f64>>,
530{
531 type Decomposition = DecomposedSymbol<RowSymbol>;
532
533 fn reduce_characters(
545 &self,
546 characters: &[(&Self::ColSymbol, T)],
547 thresh: T::Real,
548 ) -> Result<Self::Decomposition, DecompositionError> {
549 assert_eq!(characters.len(), self.classes.len());
550 let rep_syms: Result<Vec<Option<(RowSymbol, usize)>>, _> = self
551 .irreps
552 .par_iter()
553 .map(|(irrep_symbol, &i)| {
554 let c = characters
555 .par_iter()
556 .try_fold(|| Complex::<f64>::zero(), |acc, (cc_symbol, character)| {
557 let j = self.classes.get_index_of(*cc_symbol).ok_or(DecompositionError(
558 format!(
559 "The conjugacy class `{cc_symbol}` cannot be found in this group."
560 )
561 ))?;
562 Ok(
563 acc + cc_symbol.size().to_f64().ok_or(DecompositionError(
564 format!(
565 "The size of conjugacy class `{cc_symbol}` cannot be converted to `f64`."
566 )
567 ))?
568 * self.characters[(i, j)].complex_conjugate().complex_value()
569 * character
570 )
571 })
572 .try_reduce(|| Complex::<f64>::zero(), |a, s| Ok(a + s))? / self.get_order().to_f64().ok_or(
573 DecompositionError("The group order cannot be converted to `f64`.".to_string())
574 )?;
575
576 let thresh_f64 = thresh.to_f64().expect("Unable to convert the threshold to `f64`.");
577 if approx::relative_ne!(c.im, 0.0, epsilon = thresh_f64, max_relative = thresh_f64) {
578 Err(
579 DecompositionError(
580 format!(
581 "Non-negligible imaginary part for irrep multiplicity: {:.3e}",
582 c.im
583 )
584 )
585 )
586 } else if c.re < -thresh_f64 {
587 Err(
588 DecompositionError(
589 format!(
590 "Negative irrep multiplicity: {:.3e}",
591 c.re
592 )
593 )
594 )
595 } else if approx::relative_ne!(
596 c.re, c.re.round(), epsilon = thresh_f64, max_relative = thresh_f64
597 ) {
598 let ndigits = (-thresh_f64.log10())
599 .ceil()
600 .to_usize()
601 .expect("Unable to convert the number of digits to `usize`.");
602 Err(
603 DecompositionError(
604 format!(
605 "Non-integer coefficient: {:.ndigits$e}",
606 c.re
607 )
608 )
609 )
610 } else {
611 let mult = c.re.round().to_usize().ok_or(DecompositionError(
612 format!(
613 "Unable to convert the rounded coefficient `{}` to `usize`.",
614 c.re.round()
615 )
616 ))?;
617 if mult != 0 {
618 Ok(Some((irrep_symbol.clone(), mult)))
619 } else {
620 Ok(None)
621 }
622 }
623 })
624 .collect();
625
626 rep_syms.map(|syms| {
627 DecomposedSymbol::<RowSymbol>::from_subspaces(
628 &syms.into_iter().flatten().collect::<Vec<_>>(),
629 )
630 })
631 }
632}
633
634impl<RowSymbol, ColSymbol> fmt::Display for RepCharacterTable<RowSymbol, ColSymbol>
635where
636 RowSymbol: LinearSpaceSymbol,
637 ColSymbol: CollectionSymbol,
638{
639 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
640 self.write_nice_table(f, true, Some(3))
641 }
642}
643
644impl<RowSymbol, ColSymbol> fmt::Debug for RepCharacterTable<RowSymbol, ColSymbol>
645where
646 RowSymbol: LinearSpaceSymbol,
647 ColSymbol: CollectionSymbol,
648{
649 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
650 self.write_nice_table(f, true, None)
651 }
652}
653
654#[derive(Builder, Clone, Serialize, Deserialize)]
660pub struct CorepCharacterTable<RowSymbol, UC>
661where
662 <UC as CharacterTable>::ColSymbol: Serialize + DeserializeOwned,
663 RowSymbol: ReducibleLinearSpaceSymbol,
664 UC: CharacterTable,
665{
666 pub name: String,
668
669 pub(crate) unitary_character_table: UC,
672
673 pub(crate) ircoreps: IndexMap<RowSymbol, usize>,
676
677 classes: IndexMap<UC::ColSymbol, usize>,
679
680 principal_classes: IndexSet<UC::ColSymbol>,
682
683 characters: Array2<Character>,
685
686 pub(crate) intertwining_numbers: IndexMap<RowSymbol, u8>,
688}
689
690impl<RowSymbol, UC> CorepCharacterTable<RowSymbol, UC>
691where
692 <UC as CharacterTable>::ColSymbol: Serialize + DeserializeOwned,
693 RowSymbol: ReducibleLinearSpaceSymbol,
694 UC: CharacterTable,
695{
696 fn builder() -> CorepCharacterTableBuilder<RowSymbol, UC> {
698 CorepCharacterTableBuilder::default()
699 }
700
701 pub(crate) fn new(
724 name: &str,
725 unitary_chartab: UC,
726 ircoreps: &[RowSymbol],
727 classes: &[UC::ColSymbol],
728 principal_classes: &[UC::ColSymbol],
729 char_arr: Array2<Character>,
730 intertwining_numbers: &[u8],
731 ) -> Self {
732 assert_eq!(ircoreps.len(), char_arr.dim().0);
733 assert_eq!(intertwining_numbers.len(), char_arr.dim().0);
734
735 let ircoreps_indexmap: IndexMap<RowSymbol, usize> = ircoreps
736 .iter()
737 .cloned()
738 .enumerate()
739 .map(|(i, ircorep)| (ircorep, i))
740 .collect();
741
742 let classes_indexmap: IndexMap<UC::ColSymbol, usize> = classes
743 .iter()
744 .cloned()
745 .enumerate()
746 .map(|(i, class)| (class, i))
747 .collect();
748
749 let principal_classes_indexset: IndexSet<UC::ColSymbol> =
750 principal_classes.iter().cloned().collect();
751
752 let intertwining_numbers_indexmap = iter::zip(ircoreps, intertwining_numbers)
753 .map(|(ircorep, &ini)| (ircorep.clone(), ini))
754 .collect::<IndexMap<_, _>>();
755
756 Self::builder()
757 .name(name.to_string())
758 .unitary_character_table(unitary_chartab)
759 .ircoreps(ircoreps_indexmap)
760 .classes(classes_indexmap)
761 .principal_classes(principal_classes_indexset)
762 .characters(char_arr)
763 .intertwining_numbers(intertwining_numbers_indexmap)
764 .build()
765 .expect("Unable to construct a character table.")
766 }
767}
768
769impl<RowSymbol, UC> CharacterTable for CorepCharacterTable<RowSymbol, UC>
770where
771 <UC as CharacterTable>::ColSymbol: Serialize + DeserializeOwned,
772 RowSymbol: ReducibleLinearSpaceSymbol,
773 UC: CharacterTable,
774{
775 type RowSymbol = RowSymbol;
776 type ColSymbol = UC::ColSymbol;
777
778 fn get_character(&self, ircorep: &Self::RowSymbol, class: &Self::ColSymbol) -> &Character {
794 let row = self
795 .ircoreps
796 .get(ircorep)
797 .unwrap_or_else(|| panic!("Ircorep `{ircorep}` not found."));
798 let col = self
799 .classes
800 .get(class)
801 .unwrap_or_else(|| panic!("Conjugacy class `{class}` not found."));
802 &self.characters[(*row, *col)]
803 }
804
805 fn get_row(&self, ircorep: &Self::RowSymbol) -> ArrayView1<Character> {
816 let row = self
817 .ircoreps
818 .get(ircorep)
819 .unwrap_or_else(|| panic!("Ircorep `{ircorep}` not found."));
820 self.characters.row(*row)
821 }
822
823 fn get_col(&self, class: &Self::ColSymbol) -> ArrayView1<Character> {
834 let col = self
835 .classes
836 .get(class)
837 .unwrap_or_else(|| panic!("Conjugacy class `{class}` not found."));
838 self.characters.column(*col)
839 }
840
841 fn get_all_rows(&self) -> IndexSet<Self::RowSymbol> {
843 self.ircoreps.keys().cloned().collect::<IndexSet<_>>()
844 }
845
846 fn get_all_cols(&self) -> IndexSet<Self::ColSymbol> {
848 self.classes.keys().cloned().collect::<IndexSet<_>>()
849 }
850
851 fn array(&self) -> &Array2<Character> {
853 &self.characters
854 }
855
856 fn get_order(&self) -> usize {
858 2 * self.unitary_character_table.get_order()
859 }
860
861 #[allow(clippy::too_many_lines)]
884 fn write_nice_table(
885 &self,
886 f: &mut fmt::Formatter,
887 compact: bool,
888 numerical: Option<usize>,
889 ) -> fmt::Result {
890 let group_order = self.get_order();
891
892 let name = format!("m {} ({})", self.name, group_order);
893 let chars_str = self.characters.map(|character| {
894 if let Some(precision) = numerical {
895 let real_only = self.characters.iter().all(|character| {
896 approx::relative_eq!(
897 character.complex_value().im,
898 0.0,
899 epsilon = character.threshold(),
900 max_relative = character.threshold()
901 )
902 });
903 character.get_numerical(real_only, precision)
904 } else {
905 character.to_string()
906 }
907 });
908 let ircoreps_str: Vec<_> = self
909 .ircoreps
910 .keys()
911 .map(std::string::ToString::to_string)
912 .collect();
913 let ccs_str: Vec<_> = self
914 .classes
915 .keys()
916 .map(|cc| {
917 if self.principal_classes.contains(cc) {
918 format!("◈{cc}")
919 } else {
920 cc.to_string()
921 }
922 })
923 .collect();
924
925 let first_width = max(
926 ircoreps_str
927 .iter()
928 .map(|ircorep_str| ircorep_str.chars().count())
929 .max()
930 .expect("Unable to find the maximum length for the ircorep symbols."),
931 name.chars().count(),
932 ) + 1;
933
934 let digit_widths: Vec<_> = if compact {
935 iter::zip(chars_str.columns(), &ccs_str)
936 .map(|(chars_col_str, cc_str)| {
937 let char_width = chars_col_str
938 .iter()
939 .map(|c| c.chars().count())
940 .max()
941 .expect("Unable to find the maximum length for the characters.");
942 let cc_width = cc_str.chars().count();
943 max(char_width, cc_width) + 1
944 })
945 .collect()
946 } else {
947 let char_width = chars_str
948 .iter()
949 .map(|c| c.chars().count())
950 .max()
951 .expect("Unable to find the maximum length for the characters.");
952 let cc_width = ccs_str
953 .iter()
954 .map(|cc| cc.chars().count())
955 .max()
956 .expect("Unable to find the maximum length for the conjugacy class symbols.");
957 let fixed_width = max(char_width, cc_width) + 1;
958 iter::repeat(fixed_width).take(ccs_str.len()).collect()
959 };
960
961 let mut heading = format!(" {name:^first_width$} ┆ IN ║");
963 ccs_str.iter().enumerate().for_each(|(i, cc)| {
964 heading.push_str(&format!("{cc:>width$} │", width = digit_widths[i]));
965 });
966 heading.pop();
967 let tab_width = heading.chars().count();
968 heading = format!(
969 "{}\n{}\n{}\n",
970 "━".repeat(tab_width),
971 heading,
972 "┈".repeat(tab_width),
973 );
974 write!(f, "{heading}")?;
975
976 let rows = iter::zip(self.ircoreps.keys(), ircoreps_str)
978 .enumerate()
979 .map(|(i, (ircorep, ircorep_str))| {
980 let intertwining_number =
981 self.intertwining_numbers.get(ircorep).unwrap_or_else(|| {
982 panic!("Unable to obtain the intertwining_number for ircorep `{ircorep}`.")
983 });
984 let mut line = format!(" {ircorep_str:<first_width$} ┆ {intertwining_number:>2} ║");
985
986 let line_chars: String = itertools::Itertools::intersperse(
987 ccs_str.iter().enumerate().map(|(j, _)| {
988 format!("{:>width$}", chars_str[[i, j]], width = digit_widths[j])
989 }),
990 " │".to_string(),
991 )
992 .collect();
993
994 line.push_str(&line_chars);
995 line
996 });
997
998 write!(
999 f,
1000 "{}",
1001 &itertools::Itertools::intersperse(rows, "\n".to_string()).collect::<String>(),
1002 )?;
1003
1004 write!(f, "\n{}\n", &"━".repeat(tab_width))
1006 }
1007
1008 fn get_principal_cols(&self) -> &IndexSet<Self::ColSymbol> {
1009 &self.principal_classes
1010 }
1011}
1012
1013impl<RowSymbol, UC, T> SubspaceDecomposable<T> for CorepCharacterTable<RowSymbol, UC>
1014where
1015 RowSymbol: ReducibleLinearSpaceSymbol + PartialOrd + Sync + Send,
1016 UC: CharacterTable + Sync + Send,
1017 <UC as CharacterTable>::ColSymbol: Serialize + DeserializeOwned + Sync + Send,
1018 T: ComplexFloat + Sync + Send,
1019 <T as ComplexFloat>::Real: ToPrimitive + Sync + Send,
1020 for<'a> Complex<f64>: Mul<&'a T, Output = Complex<f64>>,
1021{
1022 type Decomposition = DecomposedSymbol<RowSymbol>;
1023
1024 fn reduce_characters(
1036 &self,
1037 characters: &[(&Self::ColSymbol, T)],
1038 thresh: T::Real,
1039 ) -> Result<Self::Decomposition, DecompositionError> {
1040 assert_eq!(characters.len(), self.classes.len());
1041 let rep_syms: Result<Vec<Option<(RowSymbol, usize)>>, _> = self
1042 .ircoreps
1043 .par_iter()
1044 .map(|(ircorep_symbol, &i)| {
1045 let c = characters
1046 .par_iter()
1047 .try_fold(|| Complex::<f64>::zero(), |acc, (cc_symbol, character)| {
1048 let j = self.classes.get_index_of(*cc_symbol).ok_or(DecompositionError(
1049 format!(
1050 "The conjugacy class `{cc_symbol}` cannot be found in this group."
1051 )
1052 ))?;
1053 Ok(
1054 acc + cc_symbol.size().to_f64().ok_or(DecompositionError(
1055 format!(
1056 "The size of conjugacy class `{cc_symbol}` cannot be converted to `f64`."
1057 )
1058 ))?
1059 * self.characters[(i, j)].complex_conjugate().complex_value()
1060 * character
1061 )
1062 })
1063 .try_reduce(|| Complex::<f64>::zero(), |a, s| Ok(a + s))? / (self.unitary_character_table.get_order().to_f64().ok_or(
1064 DecompositionError("The unitary subgroup order cannot be converted to `f64`.".to_string())
1065 )? * self.intertwining_numbers.get(ircorep_symbol).and_then(|x| x.to_f64()).ok_or(
1066 DecompositionError(
1067 format!(
1068 "The intertwining number of `{ircorep_symbol}` cannot be retrieved and/or converted to `f64`."
1069 )
1070 )
1071 )?);
1072
1073 let thresh_f64 = thresh.to_f64().expect("Unable to convert the threshold to `f64`.");
1074 if approx::relative_ne!(c.im, 0.0, epsilon = thresh_f64, max_relative = thresh_f64) {
1075 Err(
1076 DecompositionError(
1077 format!(
1078 "Non-negligible imaginary part for ircorep multiplicity: {:.3e}",
1079 c.im
1080 )
1081 )
1082 )
1083 } else if c.re < -thresh_f64 {
1084 Err(
1085 DecompositionError(
1086 format!(
1087 "Negative ircorep multiplicity: {:.3e}",
1088 c.re
1089 )
1090 )
1091 )
1092 } else if approx::relative_ne!(
1093 c.re, c.re.round(), epsilon = thresh_f64, max_relative = thresh_f64
1094 ) {
1095 let ndigits = (-thresh_f64.log10())
1096 .ceil()
1097 .to_usize()
1098 .expect("Unable to convert the number of digits to `usize`.");
1099 Err(
1100 DecompositionError(
1101 format!(
1102 "Non-integer coefficient: {:.ndigits$e}",
1103 c.re
1104 )
1105 )
1106 )
1107 } else {
1108 let mult = c.re.round().to_usize().ok_or(DecompositionError(
1109 format!(
1110 "Unable to convert the rounded coefficient `{}` to `usize`.",
1111 c.re.round()
1112 )
1113 ))?;
1114 if mult != 0 {
1115 Ok(Some((ircorep_symbol.clone(), mult)))
1116 } else {
1117 Ok(None)
1118 }
1119 }
1120 })
1121 .collect();
1122
1123 rep_syms.map(|syms| {
1124 DecomposedSymbol::<RowSymbol>::from_subspaces(
1125 &syms.into_iter().flatten().collect::<Vec<_>>(),
1126 )
1127 })
1128 }
1129}
1130
1131impl<RowSymbol, UC> fmt::Display for CorepCharacterTable<RowSymbol, UC>
1132where
1133 RowSymbol: ReducibleLinearSpaceSymbol,
1134 UC: CharacterTable,
1135 <UC as CharacterTable>::ColSymbol: Serialize + DeserializeOwned,
1136{
1137 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1138 self.write_nice_table(f, true, Some(3))
1139 }
1140}
1141
1142impl<RowSymbol, UC> fmt::Debug for CorepCharacterTable<RowSymbol, UC>
1143where
1144 RowSymbol: ReducibleLinearSpaceSymbol,
1145 UC: CharacterTable,
1146 <UC as CharacterTable>::ColSymbol: Serialize + DeserializeOwned,
1147{
1148 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1149 self.write_nice_table(f, true, None)
1150 }
1151}