1use std::fmt;
4use std::hash::Hash;
5use std::ops::Mul;
6use std::str::FromStr;
7
8use approx;
9use log;
10use ndarray::{Array1, Array2, Zip, array, s};
11use num::integer::lcm;
12use num_modular::{ModularInteger, MontgomeryInt};
13use num_ord::NumOrd;
14use num_traits::{Inv, One, Pow, ToPrimitive, Zero};
15use primes::is_prime;
16use rayon::prelude::*;
17use serde::{Serialize, de::DeserializeOwned};
18
19use crate::chartab::character::Character;
20use crate::chartab::chartab_symbols::{
21 CollectionSymbol, LinearSpaceSymbol, ReducibleLinearSpaceSymbol,
22};
23use crate::chartab::modular_linalg::{
24 modular_eig, split_2d_space, split_space, weighted_hermitian_inprod,
25};
26use crate::chartab::reducedint::{IntoLinAlgReducedInt, LinAlgMontgomeryInt};
27use crate::chartab::unityroot::UnityRoot;
28use crate::chartab::{CharacterTable, CorepCharacterTable, RepCharacterTable};
29use crate::group::class::ClassProperties;
30use crate::group::{
31 FiniteOrder, GroupProperties, HasUnitarySubgroup, MagneticRepresentedGroup,
32 UnitaryRepresentedGroup,
33};
34
35pub trait CharacterProperties: ClassProperties
42where
43 Self::RowSymbol: LinearSpaceSymbol,
44 Self::CharTab: CharacterTable<RowSymbol = Self::RowSymbol, ColSymbol = Self::ClassSymbol>,
45{
46 type RowSymbol;
48
49 type CharTab;
52
53 fn character_table(&self) -> &Self::CharTab;
55
56 fn unitary_represented(&self) -> bool;
62}
63
64pub trait IrrepCharTabConstruction:
69 CharacterProperties<
70 CharTab = RepCharacterTable<
71 <Self as CharacterProperties>::RowSymbol,
72 <Self as ClassProperties>::ClassSymbol,
73 >,
74>
75where
76 Self: Sync,
77 Self::GroupElement: Mul<Output = Self::GroupElement>
78 + Hash
79 + Eq
80 + Clone
81 + Sync
82 + fmt::Debug
83 + FiniteOrder<Int = u32>
84 + Pow<i32, Output = Self::GroupElement>,
85{
86 fn set_irrep_character_table(&mut self, chartab: Self::CharTab);
88
89 #[allow(clippy::too_many_lines)]
103 fn construct_irrep_character_table(&mut self) {
104 log::debug!("=============================================");
116 log::debug!("Construction of irrep character table begins.");
117 log::debug!(" *** Burnside--Dixon algorithm *** ");
118 log::debug!(" ** with Schneider optimisation ** ");
119 log::debug!("=============================================");
120
121 let m = (0..self.class_number())
123 .map(|i| {
124 self.get_cc_transversal(i)
125 .unwrap_or_else(|| panic!("No representative of class index `{i}` found."))
126 .order()
127 })
128 .reduce(lcm)
129 .expect("Unable to find the LCM for the orders of the elements in this group.");
130 let zeta = UnityRoot::new(1, m);
131 log::debug!("Found group exponent m = {}.", m);
132 log::debug!("Chosen primitive unity root ζ = {}.", zeta);
133
134 let rf64 = (2.0
135 * self
136 .order()
137 .to_f64()
138 .unwrap_or_else(|| panic!("Unable to convert `{}` to `f64`.", self.order()))
139 .sqrt()
140 / (f64::from(m)))
141 .ceil();
142 assert!(rf64.is_sign_positive());
143 assert!(rf64 <= f64::from(u32::MAX));
144 #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
145 let mut r = rf64 as u32;
146 if r == 0 {
147 r = 1;
148 };
149 let mut p = r * m + 1;
150 while !is_prime(u64::from(p)) {
151 log::debug!("Trying {}: not prime.", p);
152 r += 1;
153 p = r * m + 1;
154 }
155 log::debug!("Found r = {}.", r);
156 log::debug!("Found prime number p = r * m + 1 = {}.", p);
157 log::debug!("All arithmetic will now be carried out in GF({}).", p);
158
159 let modp = MontgomeryInt::<u32>::new(1, &p).linalg();
160 let mut i = 1u32;
162 while modp.convert(i).multiplicative_order().unwrap_or_else(|| {
163 panic!(
164 "Unable to find multiplicative order for `{}`",
165 modp.convert(i)
166 )
167 }) != m
168 && i < p
169 {
170 i += 1;
171 }
172 let z = modp.convert(i);
173 assert_eq!(
174 z.multiplicative_order()
175 .unwrap_or_else(|| panic!("Unable to find multiplicative order for `{z}`.")),
176 m
177 );
178 log::debug!("Found integer z = {} with multiplicative order {}.", z, m);
179
180 let class_sizes: Vec<_> = (0..self.class_number())
182 .map(|i| {
183 self.class_size(i)
184 .expect("Not all class sizes can be found.")
185 })
186 .collect();
187 log::debug!("Class sizes: {:?}", class_sizes);
188 let sq_indices: Vec<usize> = (0..self.class_number())
189 .map(|i| {
190 let ele = self
191 .get_cc_transversal(i)
192 .unwrap_or_else(|| panic!("No representative of class index `{i}` found."));
193 let elep2 = ele.pow(2);
194 let elep2_i = self
195 .get_index_of(&elep2)
196 .unwrap_or_else(|| panic!("Element {elep2:?} not found."));
197 self.get_cc_of_element_index(elep2_i)
198 .unwrap_or_else(|| panic!("Conjugacy class for element {elep2:?} not found."))
199 })
200 .collect();
201 let inverse_conjugacy_classes = Some(
202 (0..self.class_number())
203 .map(|i| {
204 self.get_inverse_cc(i)
205 .expect("Not all class inverses could be obtained.")
206 })
207 .collect::<Vec<_>>(),
208 );
209 log::debug!(
210 "Inverse conjugacy classes: {:?}",
211 inverse_conjugacy_classes.as_ref().unwrap()
212 );
213
214 let mut eigvecs_1d: Vec<Array1<LinAlgMontgomeryInt<u32>>> = vec![];
215 let mut success = false;
216 let mut rotate_count = 0;
217 let rs_original = (1..self.class_number()).collect::<Vec<_>>();
218
219 while !success && rotate_count < self.class_number() {
220 let mut rs = rs_original.clone();
221 rs.rotate_left(rotate_count);
222 log::debug!("Class matrix consideration order: {rs:?}");
223 rotate_count += 1;
224 let mut r_iter = rs.into_iter();
225 eigvecs_1d = vec![];
226
227 if self.class_number() == 1 {
228 eigvecs_1d.push(array![modp.convert(1)]);
229 success = true;
230 } else {
231 let mut degenerate_subspaces: Vec<Vec<Array1<LinAlgMontgomeryInt<u32>>>> = vec![];
232 let ctb = self.cayley_table();
233 let r = r_iter
234 .next()
235 .expect("Unable to obtain any `r` indices for class matrices.");
236 log::debug!("Considering class matrix N{r}...");
237 let nmat_1 = self.class_matrix(ctb, r).map(|&i| {
238 modp.convert(
239 u32::try_from(i)
240 .unwrap_or_else(|_| panic!("Unable to convert `{i}` to `u32`.")),
241 )
242 });
243 let eigs_1 = modular_eig(&nmat_1).unwrap_or_else(|err| {
244 log::error!("{err}");
245 panic!("{err}");
246 });
247 eigvecs_1d.extend(eigs_1.values().filter_map(|eigvecs| {
248 if eigvecs.len() == 1 {
249 Some(eigvecs[0].clone())
250 } else {
251 None
252 }
253 }));
254 degenerate_subspaces
255 .extend(eigs_1.values().filter(|eigvecs| eigvecs.len() > 1).cloned());
256
257 while !degenerate_subspaces.is_empty() {
258 log::debug!(
259 "Number of 1-D eigenvectors found: {} / {}.",
260 eigvecs_1d.len(),
261 self.class_number()
262 );
263 log::debug!(
264 "Number of degenerate subspaces found: {}.",
265 degenerate_subspaces.len(),
266 );
267
268 let mut degenerate_2d_subspaces = degenerate_subspaces
269 .iter()
270 .filter(|subspace| subspace.len() == 2)
271 .cloned()
272 .collect::<Vec<_>>();
273 if !degenerate_2d_subspaces.is_empty() {
274 degenerate_subspaces.retain(|subspace| subspace.len() > 2);
275 log::debug!(
276 "Number of 2-D degenerate subspaces found: {}",
277 degenerate_2d_subspaces.len()
278 );
279 log::debug!(
280 "Schneider's greedy algorithm for splitting 2-D spaces will be attempted."
281 );
282 while let Some(subspace) = degenerate_2d_subspaces.pop() {
283 if let Ok(subsubspaces) = split_2d_space(
284 &subspace,
285 &class_sizes,
286 &sq_indices,
287 inverse_conjugacy_classes.as_ref(),
288 ) {
289 eigvecs_1d.extend(subsubspaces.iter().filter_map(|subsubspace| {
290 if subsubspace.len() == 1 {
291 Some(subsubspace[0].clone())
292 } else {
293 None
294 }
295 }));
296 log::debug!(
297 "2-D subspace index {} successfully split.",
298 degenerate_2d_subspaces.len()
299 );
300 } else {
301 log::debug!(
302 "2-D subspace index {} cannot be split greedily.",
303 degenerate_2d_subspaces.len()
304 );
305 log::debug!(
306 "Stashing this 2-D subspace for splitting with class matrices..."
307 );
308 degenerate_subspaces.push(subspace);
309 }
310 }
311 }
312
313 if !degenerate_subspaces.is_empty() {
314 if let Some(r) = r_iter.next() {
315 log::debug!("Considering class matrix N{}...", r);
316 let nmat_r = self.class_matrix(ctb, r).map(|&i| {
317 modp.convert(u32::try_from(i).unwrap_or_else(|_| {
318 panic!("Unable to convert `{i}` to `u32`.")
319 }))
320 });
321
322 let mut remaining_degenerate_subspaces: Vec<
323 Vec<Array1<LinAlgMontgomeryInt<u32>>>,
324 > = vec![];
325 while let Some(subspace) = degenerate_subspaces.pop() {
326 if let Ok(subsubspaces) = split_space(
327 &nmat_r,
328 &subspace,
329 &class_sizes,
330 inverse_conjugacy_classes.as_ref(),
331 ) {
332 eigvecs_1d.extend(subsubspaces.iter().filter_map(
333 |subsubspace| {
334 if subsubspace.len() == 1 {
335 Some(subsubspace[0].clone())
336 } else {
337 None
338 }
339 },
340 ));
341 remaining_degenerate_subspaces.extend(
342 subsubspaces
343 .iter()
344 .filter(|subsubspace| subsubspace.len() > 1)
345 .cloned(),
346 );
347 } else {
348 log::warn!(
349 "Class matrix N{} failed to split degenerate subspace {}.",
350 r,
351 degenerate_subspaces.len()
352 );
353 log::warn!(
354 "Stashing this subspace for the next class matrices..."
355 );
356 remaining_degenerate_subspaces.push(subspace);
357 }
358 }
359 degenerate_subspaces = remaining_degenerate_subspaces;
360 } else {
361 log::warn!(
362 "Class matrices exhausted before degenerate subspaces are fully resolved."
363 );
364 log::warn!(
365 "Restarting the solver using a different class matrix order..."
366 );
367 break;
368 }
369 }
370 }
371 }
372 if eigvecs_1d.len() == self.class_number() {
373 success = true;
374 }
375 }
376
377 if eigvecs_1d.len() != self.class_number() {
378 log::error!(
379 "Degenerate subspaces failed to be fully resolved for all cyclic permutations of class matrices N1, ..., N{}.",
380 self.class_number()
381 );
382 }
383 assert_eq!(
384 eigvecs_1d.len(),
385 self.class_number(),
386 "Degenerate subspaces failed to be fully resolved."
387 );
388 log::debug!(
389 "Successfully found {} / {} one-dimensional eigenvectors for the class matrices.",
390 eigvecs_1d.len(),
391 self.class_number()
392 );
393 for (i, vec) in eigvecs_1d.iter().enumerate() {
394 log::debug!("Eigenvector {}: {}", i, vec);
395 }
396
397 log::debug!("Lifting characters from GF({p}) back to the complex field...",);
399
400 let chars: Vec<_> = eigvecs_1d
401 .par_iter()
402 .flat_map(|vec_i| {
403 let vec_i_inprod = weighted_hermitian_inprod(
404 (vec_i, vec_i),
405 &class_sizes,
406 inverse_conjugacy_classes.as_ref(),
407 );
408 let dim_i = (1..=p.div_euclid(2))
409 .map(|d| vec_i_inprod.convert(d))
410 .find(|d_modp| {
411 Some(vec_i_inprod) == d_modp.square().inv()
412 })
413 .unwrap_or_else(|| {
414 log::error!("Unable to deduce the irrep dimensionality from ⟨θvi, θvi⟩ = {vec_i_inprod} where vi = {vec_i}.");
415 panic!("Unable to deduce the irrep dimensionality from ⟨θvi, θvi⟩ = {vec_i_inprod} where vi = {vec_i}.");
416 });
417 log::debug!("⟨θvi, θvi⟩ = {vec_i_inprod} yields irrep dimensionality {}.", dim_i.residue());
418
419 let tchar_i =
420 Zip::from(vec_i)
421 .and(class_sizes.as_slice())
422 .par_map_collect(|&v, &k| {
423 v * dim_i
424 / modp.convert(u32::try_from(k).unwrap_or_else(|_| {
425 panic!("Unable to convert `{k}` to `u32`.")
426 }))
427 });
428 let char_i: Vec<_> = (0..self.class_number())
429 .into_par_iter()
430 .map(|cc_i| {
431 let x = self
432 .get_cc_transversal(cc_i)
433 .unwrap_or_else(|| {
434 panic!("Representative of class index `{cc_i}` not found.")
435 });
436 let k = x.order();
437 let xi = zeta.clone().pow(
438 i32::try_from(m.checked_div_euclid(k).unwrap_or_else(|| {
439 panic!("`{m}` cannot be Euclid-divided by `{k}`.")
440 }))
441 .unwrap_or_else(|_| {
442 panic!(
443 "The Euclid division `{m} / {k}` cannot be converted to `i32`."
444 )
445 }),
446 );
447 let char_ij_terms: Vec<_> = (0..k)
448 .into_par_iter()
449 .map(|s| {
450 let mu_s = (0..k).fold(modp.convert(0), |acc, l| {
451 let x_l =
452 x.clone().pow(i32::try_from(l).unwrap_or_else(|_| {
453 panic!("Unable to convert `{l}` to `i32`.")
454 }));
455 let x_l_idx = self
456 .get_index_of(&x_l)
457 .unwrap_or_else(|| panic!("Element {x_l:?} not found."));
458 let x_l_class_idx =
459 self.get_cc_of_element_index(x_l_idx).unwrap_or_else(|| {
460 panic!("Element `{x_l_idx}` does not have a conjugacy class.")
461 });
462 let tchar_i_x_l = tchar_i[x_l_class_idx];
463 acc + tchar_i_x_l
464 * z.pow(
465 s * l
466 * m.checked_div_euclid(k).unwrap_or_else(|| {
467 panic!(
468 "`{m}` cannot be Euclid-divided by `{k}`."
469 )
470 }),
471 )
472 .inv()
473 .expect("Unable to find the modular inverse of z^(slm//k).")
474 }) / k;
475 (
476 xi.pow(i32::try_from(s).unwrap_or_else(|_| {
477 panic!("Unable to convert `{s}` to `i32`.")
478 })),
479 usize::try_from(mu_s.residue()).unwrap_or_else(|_| {
480 panic!("Unable to convert `{}` to `usize`.", mu_s.residue())
481 }),
482 )
483 })
484 .collect();
485 Character::new(&char_ij_terms)
490 })
491 .collect();
492 char_i
493 })
494 .collect();
495
496 let char_arr = Array2::from_shape_vec((self.class_number(), self.class_number()), chars)
497 .expect("Unable to construct the two-dimensional array of characters.");
498 log::debug!("Lifting characters from GF({p}) back to the complex field... Done.",);
499
500 let default_irrep_symbols = char_arr
501 .rows()
502 .into_iter()
503 .enumerate()
504 .map(|(irrep_i, _)| {
505 Self::RowSymbol::from_str(&format!("||Λ|_({irrep_i})|"))
506 .ok()
507 .expect("Unable to construct default irrep symbols.")
508 })
509 .collect::<Vec<_>>();
510 let default_principal_classes = vec![
511 self.get_cc_symbol_of_index(0)
512 .expect("No conjugacy class symbols found."),
513 ];
514
515 log::debug!("Computing the Frobenius--Schur indicators in GF({p})...");
516 let group_order = class_sizes.iter().sum::<usize>();
517 let group_order_u32 = u32::try_from(group_order).unwrap_or_else(|_| {
518 panic!("Unable to convert the group order {group_order} to `u32`.")
519 });
520 log::debug!("Indices of squared conjugacy classes: {sq_indices:?}");
521 let frobenius_schur_indicators: Vec<i8> = eigvecs_1d
522 .par_iter()
523 .map(|vec_i| {
524 let vec_i_inprod = weighted_hermitian_inprod(
525 (vec_i, vec_i),
526 &class_sizes,
527 inverse_conjugacy_classes.as_ref(),
528 );
529 let dim_i = (1..=p.div_euclid(2))
530 .map(|d| vec_i_inprod.convert(d))
531 .find(|d_modp| {
532 Some(vec_i_inprod) == d_modp.square().inv()
533 })
534 .unwrap_or_else(|| {
535 log::error!("Unable to deduce the irrep dimensionality from ⟨θvi, θvi⟩ = {vec_i_inprod} where vi = {vec_i}.");
536 panic!("Unable to deduce the irrep dimensionality from ⟨θvi, θvi⟩ = {vec_i_inprod} where vi = {vec_i}.");
537 });
538
539 let tchar_i =
540 Zip::from(vec_i)
541 .and(class_sizes.as_slice())
542 .par_map_collect(|&v, &k| {
543 v * dim_i
544 / modp.convert(u32::try_from(k).unwrap_or_else(|_| {
545 panic!("Unable to convert `{k}` to `u32`.")
546 }))
547 });
548 let fs_i = sq_indices
549 .iter()
550 .zip(class_sizes.iter())
551 .fold(modp.convert(0), |acc, (&sq_idx, &k)| {
552 let k_u32 = u32::try_from(k).unwrap_or_else(|_| {
553 panic!("Unable to convert the class size {k} to `u32`.");
554 });
555 acc + modp.convert(k_u32) * tchar_i[sq_idx]
556 }) / modp.convert(group_order_u32);
557
558 if fs_i.is_one() {
559 1i8
560 } else if Zero::is_zero(&fs_i) {
561 0i8
562 } else if fs_i == modp.convert(modp.modulus() - 1) {
563 -1i8
564 } else {
565 log::error!("Invalid Frobenius -- Schur indicator: `{fs_i}`.");
566 panic!("Invalid Frobenius -- Schur indicator: `{fs_i}`.");
567 }
568 }).collect();
569 log::debug!("Computing the Frobenius--Schur indicators in GF({p})... Done.");
570
571 let chartab_name = if let Some(finite_name) = self.finite_subgroup_name().as_ref() {
572 format!("{} > {finite_name}", self.name())
573 } else {
574 self.name()
575 };
576 let ccsyms = (0..self.class_number())
577 .map(|i| {
578 self.get_cc_symbol_of_index(i)
579 .unwrap_or_else(|| {
580 let rep = self
581 .get_cc_transversal(i)
582 .unwrap_or_else(||
583 panic!("Unable to obtain a representative for conjugacy class `{i}`.")
584 );
585 panic!("Class symbol for conjugacy class `{i}` with representative element `{rep:?}` cannot be found.")
586 })
587 })
588 .collect::<Vec<_>>();
589 self.set_irrep_character_table(RepCharacterTable::new(
590 chartab_name.as_str(),
591 &default_irrep_symbols,
592 &ccsyms,
593 &default_principal_classes,
594 char_arr,
595 &frobenius_schur_indicators,
596 ));
597
598 log::debug!("===========================================");
599 log::debug!(" *** Burnside--Dixon algorithm *** ");
600 log::debug!(" ** with Schneider optimisation ** ");
601 log::debug!("Construction of irrep character table ends.");
602 log::debug!("===========================================");
603 }
604}
605
606pub trait IrcorepCharTabConstruction: HasUnitarySubgroup + CharacterProperties<
611 CharTab = CorepCharacterTable<
612 <Self as CharacterProperties>::RowSymbol,
613 <<Self as HasUnitarySubgroup>::UnitarySubgroup as CharacterProperties>::CharTab,
614 >
615>
616where
617 Self: ClassProperties<ClassSymbol = <<Self as HasUnitarySubgroup>::UnitarySubgroup as ClassProperties>::ClassSymbol>,
618 Self::RowSymbol: ReducibleLinearSpaceSymbol<
619 Subspace = <
620 <Self as HasUnitarySubgroup>::UnitarySubgroup as CharacterProperties
621 >::RowSymbol
622 >,
623 <<Self as HasUnitarySubgroup>::UnitarySubgroup as ClassProperties>::ClassSymbol: Serialize + DeserializeOwned,
624{
625 fn set_ircorep_character_table(&mut self, chartab: Self::CharTab);
627
628 fn construct_ircorep_character_table(&mut self) {
644 log::debug!("===============================================");
645 log::debug!("Construction of ircorep character table begins.");
646 log::debug!("===============================================");
647
648 if self.unitary_subgroup().order() == self.order() {
649 log::debug!(
650 "The unitary subgroup order and the full group order are both {}. This is not a magnetic group.", self.order()
651 );
652 return;
654 }
655
656 debug_assert_eq!(self.order() % 2, 0);
657 debug_assert_eq!(self.order().div_euclid(2), self.unitary_subgroup().order());
658 let unitary_order: i32 = self
659 .order()
660 .div_euclid(2)
661 .try_into()
662 .expect("Unable to convert the unitary group order to `i32`.");
663 let unitary_chartab = self.unitary_subgroup().character_table();
664
665 let mag_ctb = self.cayley_table().expect("Cayley table not found for the magnetic group.");
666
667 let mag = &self;
668 let uni = self.unitary_subgroup();
669 let mag_ccsyms = (0..mag.class_number()) .map(|i| {
670 mag.get_cc_symbol_of_index(i).expect("Unable to retrieve all class symbols of the magnetic group.")
671 })
672 .collect::<Vec<_>>();
673 let uni_ccsyms = (0..uni.class_number()).map(|i| {
674 uni.get_cc_symbol_of_index(i).expect("Unable to retrieve all class symbols of the unitary subgroup.")
675 }).collect::<Vec<_>>();
676 let (a0_mag_idx, _) = self
677 .elements()
678 .clone()
679 .into_iter()
680 .enumerate()
681 .find(|(_, op)| self.check_elem_antiunitary(op))
682 .expect("No antiunitary elements found in the magnetic group.");
683
684 let mut remaining_irreps = unitary_chartab.get_all_rows();
685 remaining_irreps.reverse();
686
687 let mut ircoreps_ins: Vec<(Self::RowSymbol, u8)> = Vec::new();
688 while let Some(irrep) = remaining_irreps.pop() {
689 log::debug!("Considering irrep {irrep} of the unitary subgroup...");
690 let char_sum = self
691 .elements()
692 .clone()
693 .into_iter()
694 .enumerate()
695 .filter(|(_, op)| self.check_elem_antiunitary(op))
696 .fold(Character::zero(), |acc, (a_mag_idx, _)| {
697 let a2_mag_idx = mag_ctb[(a_mag_idx, a_mag_idx)];
698 let a2 = self.get_index(a2_mag_idx).unwrap_or_else(|| {
699 panic!("Element index `{a2_mag_idx}` not found in the magnetic group.")
700 });
701 let a2_uni_idx = self.unitary_subgroup().get_index_of(&a2).unwrap_or_else(|| {
702 panic!("Element `{a2:?}` not found in the unitary subgroup.")
703 });
704 let a2_uni_class = &uni_ccsyms[
705 uni.get_cc_of_element_index(a2_uni_idx).unwrap_or_else(|| {
706 panic!("Conjugacy class for `{a2:?}` not found in the unitary subgroup.")
707 })
708 ];
709 acc + unitary_chartab.get_character(&irrep, a2_uni_class)
710 })
711 .simplify();
712 log::debug!(" Dimmock--Wheeler indicator for {irrep}: {char_sum}");
713 let char_sum_c128 = char_sum.complex_value();
714 approx::assert_relative_eq!(
715 char_sum_c128.im,
716 0.0,
717 max_relative = char_sum.threshold()
718 * unitary_order
719 .to_f64()
720 .expect("Unable to convert the unitary order to `f64`.")
721 .sqrt(),
722 epsilon = char_sum.threshold()
723 * unitary_order
724 .to_f64()
725 .expect("Unable to convert the unitary order to `f64`.")
726 .sqrt(),
727 );
728 approx::assert_relative_eq!(
729 char_sum_c128.re,
730 char_sum_c128.re.round(),
731 max_relative = char_sum.threshold()
732 * unitary_order
733 .to_f64()
734 .expect("Unable to convert the unitary order to `f64`.")
735 .sqrt(),
736 epsilon = char_sum.threshold()
737 * unitary_order
738 .to_f64()
739 .expect("Unable to convert the unitary order to `f64`.")
740 .sqrt(),
741 );
742 let char_sum = char_sum_c128.re.round();
743
744 let (intertwining_number, ircorep) = if NumOrd(char_sum) == NumOrd(unitary_order) {
745 log::debug!(
749 " Ircorep induced by {irrep} is of type (a) with intertwining number 1."
750 );
751 (1u8, Self::RowSymbol::from_subspaces(&[(irrep, 1)]))
752 } else if NumOrd(char_sum) == NumOrd(-unitary_order) {
753 log::debug!(
757 " Ircorep induced by {irrep} is of type (b) with intertwining number 4."
758 );
759 (4u8, Self::RowSymbol::from_subspaces(&[(irrep, 2)]))
760 } else if NumOrd(char_sum) == NumOrd(0i8) {
761 let irrep_conj_chars: Vec<Character> = unitary_chartab
765 .get_all_cols()
766 .iter()
767 .enumerate()
768 .map(|(cc_idx, cc)| {
769 let u_cc = self
770 .unitary_subgroup()
771 .get_cc_index(cc_idx)
772 .unwrap_or_else(|| panic!("Conjugacy class `{cc}` not found."));
773 let u_unitary_idx = u_cc
774 .iter()
775 .next()
776 .unwrap_or_else(|| panic!("No unitary elements found for conjugacy class `{cc}`."));
777 let u = self.unitary_subgroup()
778 .get_index(*u_unitary_idx)
779 .unwrap_or_else(|| panic!("Unitary element with index `{u_unitary_idx}` cannot be retrieved."));
780 let u_mag_idx = self
781 .get_index_of(&u)
782 .unwrap_or_else(|| panic!("Unable to retrieve the index of unitary element `{u:?}` in the magnetic group."));
783 let ua0_mag_idx = mag_ctb[(u_mag_idx, a0_mag_idx)];
784 let mag_ctb_a0x = mag_ctb.slice(s![a0_mag_idx, ..]);
785 let a0invua0_mag_idx = mag_ctb_a0x.iter().position(|&x| x == ua0_mag_idx).unwrap_or_else(|| {
786 panic!("No element `{ua0_mag_idx}` can be found in row `{a0_mag_idx}` of the magnetic group Cayley table.")
787 });
788 let a0invua0 = self
789 .get_index(a0invua0_mag_idx)
790 .unwrap_or_else(|| {
791 panic!("Unable to retrieve element with index `{a0invua0_mag_idx}` in the magnetic group.")
792 });
793 let a0invua0_unitary_idx = self.unitary_subgroup()
794 .get_index_of(&a0invua0)
795 .unwrap_or_else(|| {
796 panic!("Unable to retrieve the index of element `{a0invua0:?}` in the unitary subgroup.")
797 });
798 let a0invua0_unitary_class = &uni_ccsyms[
799 uni.get_cc_of_element_index(a0invua0_unitary_idx).unwrap_or_else(|| {
800 panic!("Unable to retrieve the class for `{a0invua0:?}` in the unitary subgroup.")
801 })
802 ];
803 unitary_chartab.get_character(&irrep, a0invua0_unitary_class).complex_conjugate()
804 }).collect();
805 let all_irreps = unitary_chartab.get_all_rows();
806 let (_, conj_irrep) = all_irreps
807 .iter()
808 .enumerate()
809 .find(|(irrep_idx, _)| {
810 unitary_chartab.array().row(*irrep_idx).to_vec() == irrep_conj_chars
811 })
812 .unwrap_or_else(|| panic!("Conjugate irrep for {irrep} not found."));
813 assert!(remaining_irreps.shift_remove(conj_irrep));
814
815 log::debug!(" The Wigner-conjugate irrep of {irrep} is {conj_irrep}.");
816 log::debug!(" Ircorep induced by {irrep} and {conj_irrep} is of type (c) with intertwining number 2.");
817 (
818 2u8,
819 Self::RowSymbol::from_subspaces(&[(irrep, 1), (conj_irrep.to_owned(), 1)]),
820 )
821 } else {
822 log::error!(
823 "Unexpected `char_sum`: {char_sum}. This can only be ±{unitary_order} or 0."
824 );
825 panic!("Unexpected `char_sum`: {char_sum}. This can only be ±{unitary_order} or 0.")
826 };
827 ircoreps_ins.push((ircorep, intertwining_number));
828 }
829
830 let mut char_arr: Array2<Character> =
831 Array2::zeros((ircoreps_ins.len(), self.class_number()));
832 for (i, (ircorep, intertwining_number)) in ircoreps_ins.iter().enumerate() {
833 for cc_idx in 0..mag.class_number() {
834 let mag_cc = &mag_ccsyms[cc_idx];
835 let mag_cc_rep = mag_cc.representative().unwrap_or_else(|| {
836 panic!(
837 "No representative element found for magnetic conjugacy class {mag_cc}."
838 );
839 });
840 let mag_cc_uni_idx = self
841 .unitary_subgroup()
842 .get_index_of(mag_cc_rep)
843 .unwrap_or_else(|| {
844 panic!(
845 "Index for element {mag_cc_rep:?} not found in the unitary subgroup."
846 );
847 });
848 let uni_cc = &uni_ccsyms[
849 uni.get_cc_of_element_index(mag_cc_uni_idx).unwrap_or_else(|| {
850 panic!("Unable to find the conjugacy class of element {mag_cc_rep:?} in the unitary subgroup.");
851 })
852 ];
853
854 char_arr[(i, cc_idx)] = ircorep
855 .subspaces()
856 .iter()
857 .fold(Character::zero(), |acc, (irrep, _)| {
858 acc + unitary_chartab.get_character(irrep, uni_cc)
859 });
860 if *intertwining_number == 4 {
861 char_arr[(i, cc_idx)] *= 2;
864 }
865 }
866 }
867
868 let principal_classes = (0..mag.class_number())
869 .filter_map(|i| {
870 let mag_cc = &mag_ccsyms[i];
871 let mag_cc_rep = mag_cc.representative().unwrap_or_else(|| {
872 panic!("No representative element found for magnetic conjugacy class {mag_cc}.");
873 });
874 let mag_cc_uni_idx = self.unitary_subgroup().get_index_of(mag_cc_rep).unwrap_or_else(|| {
875 panic!("Index for element {mag_cc_rep:?} not found in the unitary subgroup.");
876 });
877 let uni_cc = uni.get_cc_symbol_of_index(
878 uni.get_cc_of_element_index(mag_cc_uni_idx).unwrap_or_else(|| {
879 panic!("Unable to find the conjugacy class of element {mag_cc_rep:?} in the unitary subgroup.");
880 })
881 ).unwrap_or_else(|| {
882 panic!("Unable to find the conjugacy class symbol of element {mag_cc_rep:?} in the unitary subgroup.");
883 });
884 if unitary_chartab.get_principal_cols().contains(&uni_cc) {
885 Some(mag_cc.clone())
886 } else {
887 None
888 }
889 }).collect::<Vec<_>>();
890
891 let (ircoreps, ins): (Vec<Self::RowSymbol>, Vec<u8>) = ircoreps_ins.into_iter().unzip();
892
893 let chartab_name = if let Some(finite_name) = self.finite_subgroup_name().as_ref() {
894 format!("{} > {finite_name}", self.name())
895 } else {
896 self.name()
897 };
898 self.set_ircorep_character_table(Self::CharTab::new(
899 chartab_name.as_str(),
900 unitary_chartab.clone(),
901 &ircoreps,
902 &mag_ccsyms,
903 &principal_classes,
904 char_arr,
905 &ins,
906 ));
907
908 log::debug!("=============================================");
909 log::debug!("Construction of ircorep character table ends.");
910 log::debug!("=============================================");
911 }
912}
913
914impl<T, RowSymbol, ColSymbol> CharacterProperties
923 for UnitaryRepresentedGroup<T, RowSymbol, ColSymbol>
924where
925 RowSymbol: LinearSpaceSymbol + Sync,
926 ColSymbol: CollectionSymbol<CollectionElement = T> + Sync,
927 T: Mul<Output = T>
928 + Inv<Output = T>
929 + Hash
930 + Eq
931 + Clone
932 + Sync
933 + fmt::Debug
934 + FiniteOrder<Int = u32>
935 + Pow<i32, Output = T>,
936 for<'a, 'b> &'b T: Mul<&'a T, Output = T>,
937{
938 type RowSymbol = RowSymbol;
939 type CharTab = RepCharacterTable<RowSymbol, ColSymbol>;
940
941 fn character_table(&self) -> &Self::CharTab {
942 self.irrep_character_table
943 .as_ref()
944 .expect("Irrep character table not found for this group.")
945 }
946
947 fn unitary_represented(&self) -> bool {
948 true
949 }
950}
951
952impl<T, RowSymbol, ColSymbol> IrrepCharTabConstruction
953 for UnitaryRepresentedGroup<T, RowSymbol, ColSymbol>
954where
955 RowSymbol: LinearSpaceSymbol + Sync,
956 ColSymbol: CollectionSymbol<CollectionElement = T> + Sync,
957 T: Mul<Output = T>
958 + Inv<Output = T>
959 + Hash
960 + Eq
961 + Clone
962 + Sync
963 + fmt::Debug
964 + FiniteOrder<Int = u32>
965 + Pow<i32, Output = T>,
966 for<'a, 'b> &'b T: Mul<&'a T, Output = T>,
967{
968 fn set_irrep_character_table(&mut self, chartab: Self::CharTab) {
969 self.irrep_character_table = Some(chartab)
970 }
971}
972
973impl<T, RowSymbol, UG> CharacterProperties for MagneticRepresentedGroup<T, UG, RowSymbol>
978where
979 RowSymbol: ReducibleLinearSpaceSymbol<Subspace = UG::RowSymbol> + Serialize + DeserializeOwned,
980 T: Mul<Output = T>
981 + Inv<Output = T>
982 + Hash
983 + Eq
984 + Clone
985 + Sync
986 + fmt::Debug
987 + FiniteOrder<Int = u32>
988 + Pow<i32, Output = T>,
989 for<'a, 'b> &'b T: Mul<&'a T, Output = T>,
990 UG: Clone
991 + GroupProperties<GroupElement = T>
992 + ClassProperties<GroupElement = T>
993 + CharacterProperties,
994 <UG as ClassProperties>::ClassSymbol: Serialize + DeserializeOwned,
995 <UG as CharacterProperties>::CharTab: Serialize + DeserializeOwned,
996 CorepCharacterTable<RowSymbol, <UG as CharacterProperties>::CharTab>:
997 Serialize + DeserializeOwned,
998{
999 type RowSymbol = RowSymbol;
1000 type CharTab = CorepCharacterTable<Self::RowSymbol, UG::CharTab>;
1001
1002 fn character_table(&self) -> &Self::CharTab {
1003 self.ircorep_character_table
1004 .as_ref()
1005 .expect("Ircorep character table not found for this group.")
1006 }
1007
1008 fn unitary_represented(&self) -> bool {
1009 false
1010 }
1011}
1012
1013impl<T, RowSymbol, UG> IrcorepCharTabConstruction for MagneticRepresentedGroup<T, UG, RowSymbol>
1014where
1015 RowSymbol: ReducibleLinearSpaceSymbol<Subspace = UG::RowSymbol> + Serialize + DeserializeOwned,
1016 T: Mul<Output = T>
1017 + Inv<Output = T>
1018 + Hash
1019 + Eq
1020 + Clone
1021 + Sync
1022 + fmt::Debug
1023 + FiniteOrder<Int = u32>
1024 + Pow<i32, Output = T>,
1025 for<'a, 'b> &'b T: Mul<&'a T, Output = T>,
1026 UG: Clone
1027 + GroupProperties<GroupElement = T>
1028 + ClassProperties<GroupElement = T>
1029 + CharacterProperties,
1030 <UG as ClassProperties>::ClassSymbol: Serialize + DeserializeOwned,
1031 <UG as CharacterProperties>::CharTab: Serialize + DeserializeOwned,
1032 CorepCharacterTable<RowSymbol, <UG as CharacterProperties>::CharTab>:
1033 Serialize + DeserializeOwned,
1034{
1035 fn set_ircorep_character_table(&mut self, chartab: Self::CharTab) {
1036 self.ircorep_character_table = Some(chartab);
1037 }
1038}