qsym2/group/
class.rs

1//! Conjugacy class structures.
2
3use std::collections::HashSet;
4use std::fmt;
5use std::hash::Hash;
6use std::ops::Mul;
7
8use anyhow::{self, ensure, format_err};
9use derive_builder::Builder;
10use indexmap::IndexMap;
11use itertools::Itertools;
12use ndarray::{s, Array2};
13use num_traits::Inv;
14use serde::{de::DeserializeOwned, Deserialize, Serialize};
15
16use crate::chartab::chartab_group::CharacterProperties;
17use crate::chartab::chartab_symbols::{
18    CollectionSymbol, LinearSpaceSymbol, ReducibleLinearSpaceSymbol,
19};
20use crate::group::{
21    FiniteOrder, GroupProperties, HasUnitarySubgroup, MagneticRepresentedGroup,
22    UnitaryRepresentedGroup,
23};
24
25// =================
26// Trait definitions
27// =================
28
29/// Trait for conjugacy class properties of a finite group.
30pub trait ClassProperties: GroupProperties
31where
32    Self::ClassSymbol: CollectionSymbol<CollectionElement = Self::GroupElement>,
33    <Self as GroupProperties>::GroupElement: Inv<Output = <Self as GroupProperties>::GroupElement>,
34{
35    /// The type of class symbols.
36    type ClassSymbol;
37
38    // ----------------
39    // Required methods
40    // ----------------
41
42    /// Computes the class structure of the group and store the result.
43    fn compute_class_structure(&mut self) -> Result<(), anyhow::Error>;
44
45    /// Given a class index, returns an optional shared reference to the set containing the indices
46    /// of all elements in that class.
47    ///
48    /// # Arguments
49    ///
50    /// * `cc_idx` - A class index.
51    ///
52    /// # Returns
53    ///
54    /// Returns a shared reference to the set containing the indices of all elements in that class, or
55    /// `None` if `cc_idx` is not a valid class index of the group.
56    #[must_use]
57    fn get_cc_index(&self, cc_idx: usize) -> Option<&HashSet<usize>>;
58
59    /// Given an element index, returns an optional index of the conjugacy class to which the
60    /// element belongs.
61    ///
62    /// # Arguments
63    ///
64    /// * `e_idx` - An element index.
65    ///
66    /// # Returns
67    ///
68    /// Returns an index of the conjugacy class to which the element belongs, or `None` if either
69    /// the element does not have a conjugacy class, or the index is out of range.
70    #[must_use]
71    fn get_cc_of_element_index(&self, e_idx: usize) -> Option<usize>;
72
73    /// Given a class index, returns an optional representative element of that conjugacy class.
74    ///
75    /// # Arguments
76    ///
77    /// * `cc_idx` - A class index.
78    ///
79    /// # Returns
80    ///
81    /// Returns a representative element of the class, or `None` if the class index is out of
82    /// range.
83    #[must_use]
84    fn get_cc_transversal(&self, cc_idx: usize) -> Option<Self::GroupElement>;
85
86    /// Given a conjugacy class symbol, returns the index of the corresponding conjugacy class.
87    ///
88    /// # Arguments
89    ///
90    /// * `cc_sym` - A conjugacy class symbol.
91    ///
92    /// # Returns
93    ///
94    /// Returns an index corresponding to the conjugacy class of `cc_sym`, or `None` if `cc_sym`
95    /// does not exist in the group.
96    #[must_use]
97    fn get_index_of_cc_symbol(&self, cc_sym: &Self::ClassSymbol) -> Option<usize>;
98
99    /// Given a class index, returns its conjugacy class symbol, if any.
100    ///
101    /// # Arguments
102    ///
103    /// * `cc_idx` - A class index.
104    ///
105    /// # Returns
106    ///
107    /// Returns a conjugacy class symbol, or `None` if such a symbol does not exist for the class,
108    /// or if the class index is out of range.
109    #[must_use]
110    fn get_cc_symbol_of_index(&self, cc_idx: usize) -> Option<Self::ClassSymbol>;
111
112    /// Given a predicate, returns conjugacy class symbols satisfying it.
113    ///
114    /// # Arguments
115    ///
116    /// * `predicate` - A predicate to filter conjugacy class symbols.
117    ///
118    /// # Returns
119    ///
120    /// Returns conjugacy class symbols satisfying `predicate`, or `None` if such a symbol does not
121    /// exist for the class.
122    #[must_use]
123    fn filter_cc_symbols<P: FnMut(&Self::ClassSymbol) -> bool>(
124        &self,
125        predicate: P,
126    ) -> Vec<Self::ClassSymbol>;
127
128    /// Sets the conjugacy class symbols for this group.
129    ///
130    /// # Arguments
131    ///
132    /// `cc_symbols` - A sliced of owned conjugacy class symbols.
133    fn set_class_symbols(&mut self, cc_symbols: &[Self::ClassSymbol]);
134
135    /// Given a class index, returns an index for its inverse.
136    ///
137    /// The inverse of a class contains the inverses of its elements.
138    ///
139    /// # Arguments
140    ///
141    /// `cc_idx` - A class index.
142    ///
143    /// # Returns
144    ///
145    /// The index of the inverse of `cc_idx`, or `None` if the class index is out of range.
146    #[must_use]
147    fn get_inverse_cc(&self, cc_idx: usize) -> Option<usize>;
148
149    /// Returns the number of conjugacy classes in the group.
150    #[must_use]
151    fn class_number(&self) -> usize;
152
153    /// Given a class index, returns its size.
154    ///
155    /// # Arguments
156    ///
157    /// `cc_idx` - A class index.
158    ///
159    /// # Returns
160    ///
161    /// The size of the class with index `cc_idx`, or `None` if the class index is out of range.
162    #[must_use]
163    fn class_size(&self, cc_idx: usize) -> Option<usize>;
164
165    // ----------------
166    // Provided methods
167    // ----------------
168
169    /// The class matrix $`\mathbf{N}_r`$ for the conjugacy classes in the group.
170    ///
171    /// Let $`K_i`$ be the $`i^{\textrm{th}}`$ conjugacy class of the group. The
172    /// elements of the class matrix $`\mathbf{N}_r`$ are given by
173    ///
174    /// ```math
175    ///     N_{r, st} = \lvert \{ (x, y) \in K_r \times K_s : xy = z \in K_t \} \rvert,
176    /// ```
177    ///
178    /// independent of any $`z \in K_t`$.
179    ///
180    /// # Arguments
181    ///
182    /// * `ctb_opt` - An optional Cayley table.
183    /// * `r` - The index $`r`$.
184    ///
185    /// # Returns
186    ///
187    /// The class matrix $`\mathbf{N}_r`$.
188    #[must_use]
189    fn class_matrix(&self, ctb_opt: Option<&Array2<usize>>, r: usize) -> Array2<usize> {
190        let class_number = self.class_number();
191        let mut nmat_r = Array2::<usize>::zeros((class_number, class_number));
192        let class_r = &self
193            .get_cc_index(r)
194            .unwrap_or_else(|| panic!("Conjugacy class index `{r}` not found."));
195
196        if let Some(ctb) = ctb_opt {
197            log::debug!("Computing class matrix N{r} using the Cayley table...");
198            (0..class_number).for_each(|t| {
199                let class_t = self
200                    .get_cc_index(t)
201                    .unwrap_or_else(|| panic!("Conjugacy class index `{t}` not found."));
202                let rep_z_idx = *class_t
203                    .iter()
204                    .next()
205                    .expect("No conjugacy classes can be empty.");
206                for &x_idx in class_r.iter() {
207                    let x_inv_idx = ctb
208                        .slice(s![.., x_idx])
209                        .iter()
210                        .position(|&x| x == 0)
211                        .unwrap_or_else(|| {
212                            panic!("The inverse of element index `{x_idx}` cannot be found.")
213                        });
214                    let y_idx = ctb[[x_inv_idx, rep_z_idx]];
215                    let s = self.get_cc_of_element_index(y_idx).unwrap_or_else(|| {
216                        panic!("Conjugacy class of element index `{y_idx}` not found.")
217                    });
218                    nmat_r[[s, t]] += 1;
219                }
220            });
221        } else {
222            log::debug!("Computing class matrix N{r} without the Cayley table...");
223            (0..class_number).for_each(|t| {
224                let class_t = self
225                    .get_cc_index(t)
226                    .unwrap_or_else(|| panic!("Conjugacy class index `{t}` not found."));
227                let rep_z_idx = *class_t
228                    .iter()
229                    .next()
230                    .expect("No conjugacy classes can be empty.");
231                let z = self
232                    .get_index(rep_z_idx)
233                    .unwrap_or_else(|| panic!("No element with index `{rep_z_idx}` found."));
234                for &x_idx in class_r.iter() {
235                    let x = self
236                        .get_index(x_idx)
237                        .unwrap_or_else(|| panic!("No element with index `{x_idx}` found."));
238                    let y = x.clone().inv() * z.clone();
239                    let y_idx = self
240                        .get_index_of(&y)
241                        .unwrap_or_else(|| panic!("Element `{y:?}` not found in this group."));
242                    let s = self
243                        .get_cc_of_element_index(y_idx)
244                        .unwrap_or_else(|| panic!("Conjugacy class of element `{y:?}` not found."));
245                    nmat_r[[s, t]] += 1;
246                }
247            });
248        };
249
250        log::debug!("Computing class matrix N{r}... Done.");
251        nmat_r
252    }
253}
254
255/// Trait for outputting summaries of conjugacy class properties.
256pub trait ClassPropertiesSummary: ClassProperties
257where
258    <Self as GroupProperties>::GroupElement: fmt::Display,
259{
260    /// Outputs a class transversal as a nicely formatted table.
261    fn class_transversal_to_string(&self) -> String {
262        let cc_transversal = (0..self.class_number())
263            .filter_map(|i| {
264                let cc_opt = self.get_cc_symbol_of_index(i);
265                let op_opt = self.get_cc_transversal(i);
266                match (cc_opt, op_opt) {
267                    (Some(cc), Some(op)) => Some((cc.to_string(), op.to_string())),
268                    _ => None,
269                }
270            })
271            .collect::<Vec<_>>();
272        let cc_width = cc_transversal
273            .iter()
274            .map(|(cc, _)| cc.chars().count())
275            .max()
276            .unwrap_or(5)
277            .max(5);
278        let op_width = cc_transversal
279            .iter()
280            .map(|(_, op)| op.chars().count())
281            .max()
282            .unwrap_or(14)
283            .max(14);
284
285        let divider = "┈".repeat(cc_width + op_width + 4);
286        let header = format!(" {:<cc_width$}  {:<}", "Class", "Representative");
287        let body = Itertools::intersperse(
288            cc_transversal
289                .iter()
290                .map(|(cc, op)| format!(" {:<cc_width$}  {:<}", cc, op)),
291            "\n".to_string(),
292        )
293        .collect::<String>();
294
295        Itertools::intersperse(
296            [divider.clone(), header, divider.clone(), body, divider].into_iter(),
297            "\n".to_string(),
298        )
299        .collect::<String>()
300    }
301}
302
303// Blanket implementation
304impl<G> ClassPropertiesSummary for G
305where
306    G: ClassProperties,
307    G::GroupElement: fmt::Display,
308{
309}
310
311// ======================================
312// Struct definitions and implementations
313// ======================================
314
315/// Structure for managing class structures eagerly, *i.e.* all elements and their class maps are
316/// stored.
317#[derive(Builder, Clone, Serialize, Deserialize)]
318pub(super) struct EagerClassStructure<T, ClassSymbol>
319where
320    T: Mul<Output = T> + Inv<Output = T> + Hash + Eq + Clone + Sync + fmt::Debug + FiniteOrder,
321    ClassSymbol: CollectionSymbol<CollectionElement = T>,
322{
323    /// A vector of conjugacy classes.
324    ///
325    /// Each element in the vector is a hashset containing the indices of the
326    /// elements in a certain ordered collection of group elements for a particular conjugacy
327    /// class. This thus defines a multi-valued map from each conjugacy class index to one
328    /// or more element indices in said collection.
329    conjugacy_classes: Vec<HashSet<usize>>,
330
331    /// The conjugacy class index of the elements in a certain ordered collection of group
332    /// elements.
333    ///
334    /// This is the so-called inverse map of [`Self::conjugacy_classes`]. This maps
335    /// each element index in said collection to its corresponding conjugacy class index.
336    element_to_conjugacy_classes: Vec<Option<usize>>,
337
338    /// The conjugacy class representatives of the group.
339    ///
340    /// Each element in the vector is an index for a representative element of the corresponding
341    /// conjugacy class in a certain ordered collection of group elements.
342    #[builder(setter(custom))]
343    conjugacy_class_transversal: Vec<usize>,
344
345    /// An index map of symbols for the conjugacy classes in this group.
346    ///
347    /// Each key in the index map is a class symbol, and the associated value is the index of
348    /// the corresponding conjugacy class in [`Self::conjugacy_classes`].
349    #[builder(setter(custom))]
350    conjugacy_class_symbols: IndexMap<ClassSymbol, usize>,
351
352    /// A vector containing the indices of inverse conjugacy classes.
353    ///
354    /// Each index gives the inverse conjugacy class for the corresponding
355    /// conjugacy class.
356    #[builder(setter(custom))]
357    inverse_conjugacy_classes: Vec<usize>,
358}
359
360impl<T, ClassSymbol> EagerClassStructureBuilder<T, ClassSymbol>
361where
362    T: Mul<Output = T> + Inv<Output = T> + Hash + Eq + Clone + Sync + fmt::Debug + FiniteOrder,
363    ClassSymbol: CollectionSymbol<CollectionElement = T>,
364{
365    fn conjugacy_class_transversal(&mut self) -> &mut Self {
366        self.conjugacy_class_transversal = Some(
367            self.conjugacy_classes
368                .as_ref()
369                .expect("Conjugacy classes have not been found.")
370                .iter()
371                .map(|cc| *cc.iter().min().expect("No conjugacy classes can be empty."))
372                .collect::<Vec<usize>>(),
373        );
374        self
375    }
376
377    fn conjugacy_class_symbols(
378        &mut self,
379        group: &impl GroupProperties<GroupElement = T>,
380    ) -> &mut Self {
381        log::debug!("Assigning generic class symbols...");
382        let class_sizes: Vec<_> = self
383            .conjugacy_classes
384            .as_ref()
385            .expect("Conjugacy classes have not been found.")
386            .iter()
387            .map(HashSet::len)
388            .collect();
389        let class_symbols_iter = self
390            .conjugacy_class_transversal
391            .as_ref()
392            .expect("A conjugacy class transversal has not been found.")
393            .iter()
394            .enumerate()
395            .map(|(i, &rep_ele_index)| {
396                let rep_ele = group.get_index(rep_ele_index).unwrap_or_else(|| {
397                    panic!("Element with index {rep_ele_index} cannot be retrieved.")
398                });
399                (
400                    ClassSymbol::from_reps(
401                        format!("{}||K{i}||", class_sizes[i]).as_str(),
402                        Some(vec![rep_ele]),
403                    )
404                    .unwrap_or_else(|_| {
405                        panic!(
406                            "Unable to construct a class symbol from `{}||K{i}||`.",
407                            class_sizes[i]
408                        )
409                    }),
410                    i,
411                )
412            });
413        self.conjugacy_class_symbols = Some(class_symbols_iter.collect::<IndexMap<_, _>>());
414        log::debug!("Assigning generic class symbols... Done.");
415        self
416    }
417
418    fn inverse_conjugacy_classes(&mut self, ctb: &Array2<usize>) -> &mut Self {
419        log::debug!("Finding inverse conjugacy classes...");
420        let mut iccs: Vec<_> = self
421            .conjugacy_classes
422            .as_ref()
423            .expect("Conjugacy classes have not been found.")
424            .iter()
425            .map(|_| 0usize)
426            .collect();
427        let class_number = self
428            .conjugacy_classes
429            .as_ref()
430            .expect("Conjugacy classes have not been found.")
431            .len();
432        let mut remaining_classes: HashSet<_> = (1..class_number).collect();
433        while !remaining_classes.is_empty() {
434            let class_index = *remaining_classes
435                .iter()
436                .next()
437                .expect("Unexpected empty `remaining_classes`.");
438            remaining_classes.remove(&class_index);
439            let g = *self
440                .conjugacy_classes
441                .as_ref()
442                .expect("Conjugacy classes have not been found.")[class_index]
443                .iter()
444                .next()
445                .expect("No conjugacy classes can be empty.");
446            let g_inv = ctb
447                .slice(s![g, ..])
448                .iter()
449                .position(|&x| x == 0)
450                .unwrap_or_else(|| {
451                    panic!("No identity element can be found in row `{g}` of Cayley table.")
452                });
453            let inv_class_index = self
454                .element_to_conjugacy_classes
455                .as_ref()
456                .expect("Map from element to conjugacy class has not been found.")[g_inv]
457                .unwrap_or_else(|| {
458                    panic!("Element index `{g_inv}` does not have a conjugacy class.",)
459                });
460            iccs[class_index] = inv_class_index;
461            if remaining_classes.contains(&inv_class_index) {
462                remaining_classes.remove(&inv_class_index);
463                iccs[inv_class_index] = class_index;
464            }
465        }
466        assert!(iccs.iter().skip(1).all(|&x| x > 0));
467        self.inverse_conjugacy_classes = Some(iccs);
468        log::debug!("Finding inverse conjugacy classes... Done.");
469        self
470    }
471
472    fn custom_inverse_conjugacy_classes(&mut self, iccs: Vec<usize>) -> &mut Self {
473        log::debug!("Setting custom inverse conjugacy classes...");
474        assert_eq!(
475            iccs.len(),
476            self.conjugacy_classes
477                .as_ref()
478                .expect("Conjugacy classes have not been set.")
479                .len(),
480            "The provided inverse conjugacy class structure does not have the correct class number."
481        );
482        self.inverse_conjugacy_classes = Some(iccs);
483        log::debug!("Setting custom inverse conjugacy classes... Done.");
484        self
485    }
486}
487
488impl<T, ClassSymbol> EagerClassStructure<T, ClassSymbol>
489where
490    T: Mul<Output = T> + Inv<Output = T> + Hash + Eq + Clone + Sync + fmt::Debug + FiniteOrder,
491    ClassSymbol: CollectionSymbol<CollectionElement = T>,
492{
493    /// Returns a builder to construct a new class structure.
494    fn builder() -> EagerClassStructureBuilder<T, ClassSymbol> {
495        EagerClassStructureBuilder::<T, ClassSymbol>::default()
496    }
497
498    /// Constructs a new eager class structure.
499    ///
500    /// # Arguments
501    ///
502    /// * `group` - A group with its Cayley table computed.
503    /// * `conjugacy_classes` - A vector of hashsets, each of which contains the indices of the
504    /// elements in `group` that are in the same conjugacy class.
505    /// * `element_to_conjugacy_classes` - A vector containing the conjugacy class indices for the
506    /// elements in `group`. An element might not have a conjugacy class (*e.g.*
507    /// antiunitary-represented elements in magnetic-represented groups).
508    ///
509    /// # Returns
510    ///
511    /// A new class structure.
512    fn new(
513        group: &impl GroupProperties<GroupElement = T>,
514        conjugacy_classes: Vec<HashSet<usize>>,
515        element_to_conjugacy_classes: Vec<Option<usize>>,
516    ) -> Self {
517        let ctb_opt = group.cayley_table();
518        let ctb = ctb_opt
519            .as_ref()
520            .expect("Cayley table not found for this group.");
521        Self::builder()
522            .conjugacy_classes(conjugacy_classes)
523            .element_to_conjugacy_classes(element_to_conjugacy_classes)
524            .conjugacy_class_transversal()
525            .conjugacy_class_symbols(group)
526            .inverse_conjugacy_classes(ctb)
527            .build()
528            .expect("Unable to construct a `EagerClassStructure`.")
529    }
530
531    /// Constructs a new eager class structure without using any information from any Cayley table.
532    ///
533    /// # Arguments
534    ///
535    /// * `conjugacy_classes` - A vector of hashsets, each of which contains the indices of the
536    /// elements in `group` that are in the same conjugacy class.
537    /// * `element_to_conjugacy_classes` - A vector containing the conjugacy class indices for the
538    /// elements in `group`. An element might not have a conjugacy class (*e.g.*
539    /// antiunitary-represented elements in magnetic-represented groups).
540    ///
541    /// # Returns
542    ///
543    /// A new class structure.
544    fn new_no_ctb(
545        group: &impl GroupProperties<GroupElement = T>,
546        conjugacy_classes: Vec<HashSet<usize>>,
547        element_to_conjugacy_classes: Vec<Option<usize>>,
548        inverse_conjugacy_classes: Vec<usize>,
549    ) -> Self {
550        Self::builder()
551            .conjugacy_classes(conjugacy_classes)
552            .element_to_conjugacy_classes(element_to_conjugacy_classes)
553            .conjugacy_class_transversal()
554            .conjugacy_class_symbols(group)
555            .custom_inverse_conjugacy_classes(inverse_conjugacy_classes)
556            .build()
557            .expect("Unable to construct a `EagerClassStructure`.")
558    }
559
560    /// Returns the number of conjugacy classes in the class structure.
561    #[must_use]
562    fn class_number(&self) -> usize {
563        self.conjugacy_classes.len()
564    }
565
566    /// Sets the symbols of the conjugacy classes.
567    ///
568    /// # Arguments
569    ///
570    /// `csyms` - A slice of class symbols.
571    ///
572    /// # Panics
573    ///
574    /// Panics if the length of `csyms` does not match that of [`Self::conjugacy_classes`].
575    fn set_class_symbols(&mut self, csyms: &[ClassSymbol]) {
576        assert_eq!(csyms.len(), self.conjugacy_classes.len());
577        self.conjugacy_class_symbols = csyms
578            .iter()
579            .enumerate()
580            .map(|(i, cc)| (cc.clone(), i))
581            .collect::<IndexMap<_, _>>();
582    }
583}
584
585// =====================
586// Trait implementations
587// =====================
588
589// ---------------------------------------------
590// UnitaryRepresentedGroup trait implementations
591// ---------------------------------------------
592
593impl<T, RowSymbol, ColSymbol> ClassProperties for UnitaryRepresentedGroup<T, RowSymbol, ColSymbol>
594where
595    T: Mul<Output = T> + Inv<Output = T> + Hash + Eq + Clone + Sync + fmt::Debug + FiniteOrder,
596    for<'a, 'b> &'b T: Mul<&'a T, Output = T>,
597    <Self as GroupProperties>::GroupElement: Inv,
598    RowSymbol: LinearSpaceSymbol,
599    ColSymbol: CollectionSymbol<CollectionElement = T>,
600{
601    type ClassSymbol = ColSymbol;
602
603    /// Compute the class structure of this unitary-represented group that is induced by the
604    /// following equivalence relation:
605    ///
606    /// ```math
607    ///     g \sim h \Leftrightarrow \exists u : h = u g u^{-1}.
608    /// ```
609    fn compute_class_structure(&mut self) -> Result<(), anyhow::Error> {
610        log::debug!("Finding unitary conjugacy classes...");
611        let order = self.abstract_group.order();
612        let (ccs, e2ccs) = if self.abstract_group.is_abelian() {
613            log::debug!("Abelian group found.");
614            // Abelian group; each element is in its own conjugacy class.
615            (
616                (0usize..order)
617                    .map(|i| HashSet::from([i]))
618                    .collect::<Vec<_>>(),
619                (0usize..order).map(Some).collect::<Vec<_>>(),
620            )
621        } else {
622            // Non-Abelian group.
623            log::debug!("Non-Abelian group found.");
624            let mut ccs: Vec<HashSet<usize>> = vec![HashSet::from([0usize])];
625            let mut e2ccs = vec![0usize; order];
626            let mut remaining_elements: HashSet<usize> = (1usize..order).collect();
627            let ctb = self.abstract_group.cayley_table.as_ref().expect(
628                "Cayley table required for computing unitary class structure, but not found.",
629            );
630
631            while !remaining_elements.is_empty() {
632                // For a fixed g, find all h such that sg = hs for all s in the group.
633                let g = *remaining_elements
634                    .iter()
635                    .next()
636                    .expect("Unexpected empty `remaining_elements`.");
637                let mut cur_cc = HashSet::from([g]);
638                for s in 0usize..order {
639                    let sg = ctb[[s, g]];
640                    let ctb_xs = ctb.slice(s![.., s]);
641                    let h = ctb_xs.iter().position(|&x| x == sg).ok_or_else(|| {
642                        format_err!(
643                            "No element `{sg}` can be found in column `{s}` of Cayley table."
644                        )
645                    })?;
646                    if remaining_elements.contains(&h) {
647                        remaining_elements.remove(&h);
648                        cur_cc.insert(h);
649                    }
650                }
651                ccs.push(cur_cc);
652            }
653            ccs.sort_by_key(|cc| {
654                *cc.iter()
655                    .min()
656                    .expect("Unable to find the minimum element index in one conjugacy class.")
657            });
658            ccs.iter().enumerate().for_each(|(i, cc)| {
659                cc.iter().for_each(|&j| e2ccs[j] = i);
660            });
661            assert!(e2ccs.iter().skip(1).all(|&x| x > 0));
662            (ccs, e2ccs.iter().map(|&i| Some(i)).collect::<Vec<_>>())
663        };
664
665        let class_structure =
666            EagerClassStructure::<T, Self::ClassSymbol>::new(&self.abstract_group, ccs, e2ccs);
667        self.class_structure = Some(class_structure);
668        log::debug!("Finding unitary conjugacy classes... Done.");
669        Ok(())
670    }
671
672    fn get_cc_index(&self, cc_idx: usize) -> Option<&HashSet<usize>> {
673        self.class_structure
674            .as_ref()
675            .expect("No class structure found.")
676            .conjugacy_classes
677            .get(cc_idx)
678    }
679
680    fn get_cc_of_element_index(&self, e_idx: usize) -> Option<usize> {
681        self.class_structure
682            .as_ref()
683            .expect("No class structure found.")
684            .element_to_conjugacy_classes[e_idx]
685    }
686
687    fn get_cc_transversal(&self, cc_idx: usize) -> Option<Self::GroupElement> {
688        self.class_structure
689            .as_ref()
690            .expect("No class structure found.")
691            .conjugacy_class_transversal
692            .get(cc_idx)
693            .and_then(|&i| self.get_index(i))
694    }
695
696    fn get_index_of_cc_symbol(&self, cc_sym: &Self::ClassSymbol) -> Option<usize> {
697        self.class_structure
698            .as_ref()
699            .expect("No class structure found.")
700            .conjugacy_class_symbols
701            .get_index_of(cc_sym)
702    }
703
704    fn get_cc_symbol_of_index(&self, cc_idx: usize) -> Option<Self::ClassSymbol> {
705        self.class_structure
706            .as_ref()
707            .expect("No class structure found.")
708            .conjugacy_class_symbols
709            .get_index(cc_idx)
710            .map(|(cc_sym, _)| cc_sym.clone())
711    }
712
713    fn filter_cc_symbols<P: FnMut(&Self::ClassSymbol) -> bool>(
714        &self,
715        predicate: P,
716    ) -> Vec<Self::ClassSymbol> {
717        self.class_structure
718            .as_ref()
719            .expect("No class structure found.")
720            .conjugacy_class_symbols
721            .keys()
722            .cloned()
723            .filter(predicate)
724            .collect::<Vec<_>>()
725    }
726
727    fn set_class_symbols(&mut self, cc_symbols: &[Self::ClassSymbol]) {
728        self.class_structure
729            .as_mut()
730            .unwrap()
731            .set_class_symbols(cc_symbols);
732    }
733
734    fn get_inverse_cc(&self, cc_idx: usize) -> Option<usize> {
735        self.class_structure
736            .as_ref()
737            .expect("No class structure found.")
738            .inverse_conjugacy_classes
739            .get(cc_idx)
740            .cloned()
741    }
742
743    fn class_number(&self) -> usize {
744        self.class_structure
745            .as_ref()
746            .expect("No class structure found.")
747            .class_number()
748    }
749
750    fn class_size(&self, cc_idx: usize) -> Option<usize> {
751        self.class_structure
752            .as_ref()
753            .expect("No class structure found.")
754            .conjugacy_classes
755            .get(cc_idx)
756            .map(|cc| cc.len())
757    }
758}
759
760// ----------------------------------------------
761// MagneticRepresentedGroup trait implementations
762// ----------------------------------------------
763
764impl<T, UG, RowSymbol> ClassProperties for MagneticRepresentedGroup<T, UG, RowSymbol>
765where
766    T: Mul<Output = T> + Inv<Output = T> + Hash + Eq + Clone + Sync + fmt::Debug + FiniteOrder,
767    for<'a, 'b> &'b T: Mul<&'a T, Output = T>,
768    <Self as GroupProperties>::GroupElement: Inv,
769    UG: Clone + GroupProperties<GroupElement = T> + CharacterProperties,
770    RowSymbol: ReducibleLinearSpaceSymbol<Subspace = UG::RowSymbol> + Serialize + DeserializeOwned,
771    <UG as ClassProperties>::ClassSymbol: Serialize + DeserializeOwned,
772    <UG as CharacterProperties>::CharTab: Serialize + DeserializeOwned,
773{
774    type ClassSymbol = UG::ClassSymbol;
775
776    /// Compute the class structure of this magnetic-represented group that is induced by the
777    /// following equivalence relation:
778    ///
779    /// ```math
780    ///     g \sim h \Leftrightarrow
781    ///     \exists u : h = u g u^{-1}
782    ///     \quad \textrm{or} \quad
783    ///     \exists a : h = a g^{-1} a^{-1},
784    /// ```
785    ///
786    /// where $`u`$ is unitary-represented and $`a`$ is antiunitary-represented in the group.
787    fn compute_class_structure(&mut self) -> Result<(), anyhow::Error> {
788        log::debug!("Finding magnetic conjugacy classes...");
789        let order = self.abstract_group.order();
790        let mut ccs: Vec<HashSet<usize>> = vec![HashSet::from([0usize])];
791        let mut e2ccs = vec![None; order];
792        let mut remaining_unitary_elements = self
793            .elements()
794            .iter()
795            .enumerate()
796            .skip(1)
797            .filter_map(|(i, op)| {
798                if self.check_elem_antiunitary(op) {
799                    None
800                } else {
801                    Some(i)
802                }
803            })
804            .collect::<HashSet<usize>>();
805        let ctb =
806            self.abstract_group.cayley_table.as_ref().expect(
807                "Cayley table required for computing magnetic class structure, but not found.",
808            );
809
810        while !remaining_unitary_elements.is_empty() {
811            // For a fixed unitary g, find all unitary h such that ug = hu for all unitary u
812            // in the group, and all unitary h such that ag^(-1) = ha for all antiunitary a in
813            // the group.
814            let g = *remaining_unitary_elements
815                .iter()
816                .next()
817                .expect("Unexpected empty `remaining_elements`.");
818            let ctb_xg = ctb.slice(s![.., g]);
819            let ginv = ctb_xg
820                .iter()
821                .position(|&x| x == 0)
822                .unwrap_or_else(|| panic!("The inverse of `{g}` cannot be found."));
823            let mut cur_cc = HashSet::from([g]);
824            for (s, op) in self.elements().iter().enumerate() {
825                let h = if self.check_elem_antiunitary(op) {
826                    // s denotes a.
827                    let sginv = ctb[[s, ginv]];
828                    let ctb_xs = ctb.slice(s![.., s]);
829                    ctb_xs.iter().position(|&x| x == sginv).ok_or_else(|| {
830                        format_err!("No element `{sginv}` can be found in column `{s}` of Cayley table.")
831                    })?
832                } else {
833                    // s denotes u.
834                    let sg = ctb[[s, g]];
835                    let ctb_xs = ctb.slice(s![.., s]);
836                    ctb_xs.iter().position(|&x| x == sg).ok_or_else(|| {
837                        format_err!("No element `{sg}` can be found in column `{s}` of Cayley table.")
838                    })?
839                };
840                if remaining_unitary_elements.contains(&h) {
841                    remaining_unitary_elements.remove(&h);
842                    cur_cc.insert(h);
843                }
844            }
845            ccs.push(cur_cc);
846        }
847        ccs.sort_by_key(|cc| {
848            *cc.iter()
849                .min()
850                .expect("Unable to find the minimum element index in one conjugacy class.")
851        });
852        ccs.iter().enumerate().for_each(|(i, cc)| {
853            cc.iter().for_each(|&j| e2ccs[j] = Some(i));
854        });
855        ensure!(e2ccs
856            .iter()
857            .skip(1)
858            .all(|x_opt| if let Some(x) = x_opt { *x > 0 } else { true }));
859
860        let class_structure =
861            EagerClassStructure::<T, Self::ClassSymbol>::new(&self.abstract_group, ccs, e2ccs);
862        self.class_structure = Some(class_structure);
863        log::debug!("Finding magnetic conjugacy classes... Done.");
864        Ok(())
865    }
866
867    fn get_cc_index(&self, cc_idx: usize) -> Option<&HashSet<usize>> {
868        self.class_structure
869            .as_ref()
870            .expect("No class structure found.")
871            .conjugacy_classes
872            .get(cc_idx)
873    }
874
875    fn get_cc_of_element_index(&self, e_idx: usize) -> Option<usize> {
876        self.class_structure
877            .as_ref()
878            .expect("No class structure found.")
879            .element_to_conjugacy_classes[e_idx]
880    }
881
882    fn get_cc_transversal(&self, cc_idx: usize) -> Option<Self::GroupElement> {
883        self.class_structure
884            .as_ref()
885            .expect("No class structure found.")
886            .conjugacy_class_transversal
887            .get(cc_idx)
888            .and_then(|&i| self.get_index(i))
889    }
890
891    fn get_index_of_cc_symbol(&self, cc_sym: &Self::ClassSymbol) -> Option<usize> {
892        self.class_structure
893            .as_ref()
894            .expect("No class structure found.")
895            .conjugacy_class_symbols
896            .get_index_of(cc_sym)
897    }
898
899    fn get_cc_symbol_of_index(&self, cc_idx: usize) -> Option<Self::ClassSymbol> {
900        self.class_structure
901            .as_ref()
902            .expect("No class structure found.")
903            .conjugacy_class_symbols
904            .get_index(cc_idx)
905            .map(|(cc_sym, _)| cc_sym.clone())
906    }
907
908    fn filter_cc_symbols<P: FnMut(&Self::ClassSymbol) -> bool>(
909        &self,
910        predicate: P,
911    ) -> Vec<Self::ClassSymbol> {
912        self.class_structure
913            .as_ref()
914            .expect("No class structure found.")
915            .conjugacy_class_symbols
916            .keys()
917            .cloned()
918            .filter(predicate)
919            .collect::<Vec<_>>()
920    }
921
922    fn set_class_symbols(&mut self, cc_symbols: &[Self::ClassSymbol]) {
923        self.class_structure
924            .as_mut()
925            .unwrap()
926            .set_class_symbols(cc_symbols);
927    }
928
929    fn get_inverse_cc(&self, cc_idx: usize) -> Option<usize> {
930        self.class_structure
931            .as_ref()
932            .expect("No class structure found.")
933            .inverse_conjugacy_classes
934            .get(cc_idx)
935            .cloned()
936    }
937
938    fn class_number(&self) -> usize {
939        self.class_structure
940            .as_ref()
941            .expect("No class structure found.")
942            .class_number()
943    }
944
945    fn class_size(&self, cc_idx: usize) -> Option<usize> {
946        self.class_structure
947            .as_ref()
948            .expect("No class structure found.")
949            .conjugacy_classes
950            .get(cc_idx)
951            .map(|cc| cc.len())
952    }
953}