qsym2/permutation/
permutation_group.rs

1//! Symmetric groups of permutations.
2
3use std::collections::{HashSet, VecDeque};
4use std::fmt;
5use std::ops::Range;
6
7use anyhow::{self, format_err};
8use derive_builder::Builder;
9use factorial::Factorial;
10use indexmap::IndexSet;
11use itertools::Itertools;
12use ndarray::Array2;
13use rayon::prelude::*;
14
15use crate::chartab::chartab_group::{CharacterProperties, IrrepCharTabConstruction};
16use crate::chartab::{CharacterTable, RepCharacterTable};
17use crate::group::class::ClassProperties;
18use crate::group::{GroupProperties, UnitaryRepresentedGroup};
19use crate::permutation::permutation_symbols::{
20    deduce_permutation_irrep_symbols, sort_perm_irreps, PermutationClassSymbol,
21    PermutationIrrepSymbol,
22};
23use crate::permutation::Permutation;
24use crate::permutation::PermutationRank;
25
26#[cfg(test)]
27#[path = "permutation_group_tests.rs"]
28mod permutation_group_tests;
29
30// ==================
31// Struct definitions
32// ==================
33
34/// Lazy iterator for permutations of a particular rank enumerated by their Lehmer encoding
35/// integers.
36#[derive(Clone)]
37pub struct PermutationIterator<T: PermutationRank> {
38    rank: T,
39    raw_perm_indices: Range<usize>,
40}
41
42impl<T: PermutationRank> Iterator for PermutationIterator<T>
43where
44    std::ops::Range<T>: Iterator + DoubleEndedIterator,
45    Vec<T>: FromIterator<<std::ops::Range<T> as Iterator>::Item>,
46    VecDeque<T>: FromIterator<<std::ops::Range<T> as Iterator>::Item>,
47    <T as TryFrom<usize>>::Error: fmt::Debug,
48    IndexSet<T>: FromIterator<<std::ops::Range<T> as Iterator>::Item>,
49{
50    type Item = Permutation<T>;
51
52    fn next(&mut self) -> Option<Self::Item> {
53        self.raw_perm_indices
54            .next()
55            .and_then(|index| Permutation::<T>::from_lehmer_index(index, self.rank).ok())
56    }
57}
58
59/// Dedicated structure for managing permutation groups efficiently. Only permutation groups of
60/// ranks up to 20 are supported, as higher-rank permutation groups have too large orders not
61/// representable with `usize` which is `u64` on most modern machines.
62#[derive(Clone, Builder)]
63pub struct PermutationGroup {
64    /// The rank of the permutation group.
65    rank: u8,
66
67    /// The lazy iterator yielding all permutations in this group in their Lehmer-encoding-integer
68    /// order.
69    perms_iter: PermutationIterator<u8>,
70
71    /// All possible cycle patterns of this group. These are the possible partitions of
72    /// [`Self::rank`].
73    #[builder(setter(skip), default = "None")]
74    cycle_patterns: Option<IndexSet<Vec<u8>>>,
75
76    /// A vector of hashsets, each of which contains the indices of the elements having the
77    /// corresponding cycle pattern in [`Self::cycle_patterns`]. These elements are also in the
78    /// same conjugacy class.
79    #[builder(setter(skip), default = "None")]
80    conjugacy_classes: Option<Vec<HashSet<usize>>>,
81
82    /// The symbols for the conjugacy classes.
83    #[builder(setter(skip), default = "None")]
84    conjugacy_class_symbols: Option<IndexSet<PermutationClassSymbol<u8>>>,
85
86    /// The character table for the irreducible representations of this permutation group.
87    #[builder(setter(skip), default = "None")]
88    irrep_character_table:
89        Option<RepCharacterTable<PermutationIrrepSymbol, PermutationClassSymbol<u8>>>,
90}
91
92impl PermutationGroup {
93    fn builder() -> PermutationGroupBuilder {
94        PermutationGroupBuilder::default()
95    }
96}
97
98// =================
99// Trait definitions
100// =================
101
102/// Trait for permutation groups. Only permutation groups of ranks up to 20 are supported, as
103/// higher-rank permutation groups have too large orders not representable with `usize` which is
104/// `u64` on most modern machines.
105pub trait PermutationGroupProperties:
106    ClassProperties<GroupElement = Permutation<u8>, ClassSymbol = PermutationClassSymbol<u8>>
107    + CharacterProperties
108    + Sized
109{
110    /// Constructs a permutation group $`Sym(n)`$ from a given rank $`n`$ (*i.e.* the number of
111    /// elements in the set to be permuted).
112    ///
113    /// # Arguments
114    ///
115    /// * `rank` - The permutation rank.
116    ///
117    /// # Returns
118    ///
119    /// A finite group of permutations.
120    fn from_rank(rank: u8) -> Result<Self, anyhow::Error>;
121
122    /// Sets class symbols from cycle patterns.
123    ///
124    /// Classes in permutation groups are determined by the cycle patterns of their elements. The
125    /// number of classes for $`Sym(n)`$ is the number of integer partitions of $`n`$.
126    fn set_class_symbols_from_cycle_patterns(&mut self) {
127        log::debug!("Assigning class symbols from cycle patterns...");
128        let class_symbols = (0..self.class_number())
129            .map(|cc_i| {
130                let rep_ele = self.get_cc_transversal(cc_i).unwrap_or_else(|| {
131                    panic!("No representative element found for conjugacy class index `{cc_i}`.")
132                });
133                let cycle_pattern = rep_ele.cycle_pattern();
134                let mut cycle_pattern_count: Vec<(u8, u8)> =
135                    Vec::with_capacity(cycle_pattern.len());
136                let mut i = 0u8;
137                while i < u8::try_from(cycle_pattern.len()).unwrap() {
138                    let mut j = i + 1;
139                    while j < u8::try_from(cycle_pattern.len()).unwrap()
140                        && cycle_pattern[usize::from(j)] == cycle_pattern[usize::from(i)]
141                    {
142                        j += 1;
143                    }
144                    cycle_pattern_count.push((cycle_pattern[usize::from(i)], j - i));
145                    i = j;
146                }
147                let cycle_pattern_str = cycle_pattern_count
148                    .iter()
149                    .map(|(length, count)| {
150                        if *count > 1 {
151                            format!("{length}^{count}")
152                        } else {
153                            length.to_string()
154                        }
155                    })
156                    .collect_vec()
157                    .join("ยท");
158                let size = self
159                    .class_size(cc_i)
160                    .unwrap_or_else(|| panic!("Unknown size for conjugacy class index `{i}`."));
161                PermutationClassSymbol::new(
162                    format!("{size}||{cycle_pattern_str}||").as_str(),
163                    Some(vec![rep_ele]),
164                )
165                .unwrap_or_else(|_| {
166                    panic!(
167                        "Unable to construct a class symbol from `{size}||{cycle_pattern_str}||`"
168                    )
169                })
170            })
171            .collect_vec();
172        self.set_class_symbols(&class_symbols);
173        log::debug!("Assigning class symbols from cycle patterns... Done.");
174    }
175
176    /// Reorders and relabels the rows and columns of the constructed character table using
177    /// permutation-specific rules and conventions.
178    fn canonicalise_character_table(&mut self);
179}
180
181// =====================
182// Trait implementations
183// =====================
184
185// -----------------------
186// UnitaryRepresentedGroup
187// -----------------------
188
189impl PermutationGroupProperties
190    for UnitaryRepresentedGroup<Permutation<u8>, PermutationIrrepSymbol, PermutationClassSymbol<u8>>
191{
192    fn from_rank(rank: u8) -> Result<Self, anyhow::Error> {
193        assert!(rank > 0, "A permutation rank must be a positive integer.");
194        assert!(
195            rank <= 20,
196            "Permutations of rank more than 20 will not be representable."
197        );
198        log::debug!("Generating all permutations of rank {rank}...");
199        let perms = (0..rank)
200            .permutations(usize::from(rank))
201            .map(Permutation::from_image)
202            .collect::<Result<Vec<_>, _>>()?;
203        log::debug!("Generating all permutations of rank {rank}... Done.");
204        log::debug!("Collecting all permutations into a unitary-represented group...");
205        let mut group = UnitaryRepresentedGroup::<
206            Permutation<u8>,
207            PermutationIrrepSymbol,
208            PermutationClassSymbol<u8>,
209        >::new(format!("Sym({rank})").as_str(), perms)?;
210        log::debug!("Collecting all permutations into a unitary-represented group... Done.");
211        group.set_class_symbols_from_cycle_patterns();
212        group.construct_irrep_character_table();
213        group.canonicalise_character_table();
214        Ok(group)
215    }
216
217    fn canonicalise_character_table(&mut self) {
218        let old_chartab = self.character_table();
219        let class_symbols = (0..self.class_number())
220            .map(|i| {
221                self.get_cc_symbol_of_index(i)
222                    .expect("Unable to retrieve all class symbols.")
223            })
224            .collect_vec();
225        let (char_arr, sorted_fs) = sort_perm_irreps(
226            &old_chartab.array().view(),
227            &old_chartab.frobenius_schurs.values().copied().collect_vec(),
228        );
229        let ordered_irreps = deduce_permutation_irrep_symbols(&char_arr.view());
230        self.set_irrep_character_table(RepCharacterTable::new(
231            &old_chartab.name,
232            &ordered_irreps,
233            &class_symbols,
234            &[],
235            char_arr,
236            &sorted_fs,
237        ));
238    }
239}
240
241// ----------------
242// PermutationGroup
243// ----------------
244
245impl GroupProperties for PermutationGroup {
246    type GroupElement = Permutation<u8>;
247    type ElementCollection = PermutationIterator<u8>;
248
249    fn name(&self) -> String {
250        format!("Sym({})", self.rank)
251    }
252
253    fn finite_subgroup_name(&self) -> Option<&String> {
254        None
255    }
256
257    fn get_index(&self, index: usize) -> Option<Self::GroupElement> {
258        Permutation::from_lehmer_index(index, self.rank).ok()
259    }
260
261    fn get_index_of(&self, g: &Self::GroupElement) -> Option<usize> {
262        if g.rank() != self.rank {
263            None
264        } else {
265            Some(g.lehmer_index(None))
266        }
267    }
268
269    fn contains(&self, g: &Self::GroupElement) -> bool {
270        g.rank() == self.rank
271    }
272
273    fn elements(&self) -> &Self::ElementCollection {
274        &self.perms_iter
275    }
276
277    fn is_abelian(&self) -> bool {
278        self.perms_iter.clone().enumerate().all(|(i, gi)| {
279            (0..i).all(|j| {
280                let gj = self
281                    .get_index(j)
282                    .unwrap_or_else(|| panic!("Element with index `{j}` not found."));
283                (&gi) * (&gj) == (&gj) * (&gi)
284            })
285        })
286    }
287
288    fn order(&self) -> usize {
289        usize::from(self.rank).checked_factorial().unwrap()
290    }
291
292    fn cayley_table(&self) -> Option<&Array2<usize>> {
293        None
294    }
295}
296
297/// Returns all partitions of an integer.
298///
299/// The partitions are generated in lexicographic order, then, within each partition, the
300/// sub-partitions are arranged in decreasing order.
301///
302/// See [here](https://jeromekelleher.net/generating-integer-partitions.html) for the algorithm.
303///
304/// # Arguments
305///
306/// * `n` - An integer.
307///
308/// # Returns
309///
310/// An indexset containing the partition vectors.
311fn partitions(n: u8) -> IndexSet<Vec<u8>> {
312    if n == 0 {
313        IndexSet::from([vec![0]])
314    } else {
315        let mut res: IndexSet<Vec<u8>> = IndexSet::new();
316        let mut a = vec![0; usize::from(n) + 1];
317        let mut k = 1;
318        let mut y = n - 1;
319        while k != 0 {
320            let mut x = a[k - 1] + 1;
321            k -= 1;
322            while 2 * x <= y {
323                a[k] = x;
324                y -= x;
325                k += 1;
326            }
327            let l = k + 1;
328            while x <= y {
329                a[k] = x;
330                a[l] = y;
331                let mut cycle = a[0..k + 2].to_vec();
332                cycle.reverse();
333                res.insert(cycle);
334                x += 1;
335                y -= 1;
336            }
337            a[k] = x + y;
338            y = x + y - 1;
339            let mut cycle = a[0..k + 1].to_vec();
340            cycle.reverse();
341            res.insert(cycle);
342        }
343        res
344    }
345}
346
347impl ClassProperties for PermutationGroup {
348    type ClassSymbol = PermutationClassSymbol<u8>;
349
350    /// Computes the class structure of this permutation group based on cycle patterns.
351    fn compute_class_structure(&mut self) -> Result<(), anyhow::Error> {
352        log::debug!("Finding all partitions of {}...", self.rank);
353        self.cycle_patterns = Some(partitions(self.rank));
354        log::debug!("Finding all partitions of {}... Done.", self.rank);
355
356        log::debug!("Finding conjugacy classes based on cycle patterns in parallel...");
357        let mut conjugacy_classes = vec![HashSet::<usize>::new(); self.class_number()];
358
359        // For ranks up to 20, the number of classes is at most 627. `u16` is then plenty to store
360        // each class index. This helps reduce memory cost a little bit.
361        let e2ccs: Vec<(usize, u16)> = (0..self.order())
362            .into_par_iter()
363            .map(|i| {
364                let p_i = Permutation::from_lehmer_index(i, self.rank).map_err(|_| {
365                    format_err!(
366                        "Unable to construct a permutation of rank {} with Lehmer index {i}.",
367                        self.rank
368                    )
369                })?;
370                let cycle_pattern = p_i.cycle_pattern();
371                let c_i = self
372                    .cycle_patterns
373                    .as_ref()
374                    .expect("Cycle patterns not found.")
375                    .get_index_of(&cycle_pattern)
376                    .ok_or_else(|| {
377                        format_err!("Cycle pattern {cycle_pattern:?} is not valid in this group.")
378                    })
379                    .and_then(|index| u16::try_from(index).map_err(|err| format_err!(err)))?;
380                Ok::<_, anyhow::Error>((i, c_i))
381            })
382            .collect::<Result<Vec<_>, _>>()?;
383        e2ccs.into_iter().for_each(|(i, c_i)| {
384            conjugacy_classes[usize::from(c_i)].insert(i);
385        });
386        self.conjugacy_classes = Some(conjugacy_classes);
387        log::debug!("Finding conjugacy classes based on cycle patterns in parallel... Done.");
388        Ok(())
389    }
390
391    fn get_cc_index(&self, cc_idx: usize) -> Option<&HashSet<usize>> {
392        self.conjugacy_classes
393            .as_ref()
394            .map(|conjugacy_classes| &conjugacy_classes[cc_idx])
395    }
396
397    fn filter_cc_symbols<P: FnMut(&Self::ClassSymbol) -> bool>(
398        &self,
399        predicate: P,
400    ) -> Vec<Self::ClassSymbol> {
401        self.conjugacy_class_symbols
402            .as_ref()
403            .expect("No class structure found.")
404            .iter()
405            .cloned()
406            .filter(predicate)
407            .collect::<Vec<_>>()
408    }
409
410    fn get_cc_of_element_index(&self, e_idx: usize) -> Option<usize> {
411        let perm = Permutation::from_lehmer_index(e_idx, self.rank).ok()?;
412        self.cycle_patterns
413            .as_ref()
414            .expect("Cycle patterns not found.")
415            .get_index_of(&perm.cycle_pattern())
416    }
417
418    fn get_cc_transversal(&self, cc_idx: usize) -> Option<Self::GroupElement> {
419        let cycle_pattern = self
420            .cycle_patterns
421            .as_ref()
422            .expect("Cycle patterns not found.")
423            .get_index(cc_idx)?;
424        let cycles = cycle_pattern
425            .iter()
426            .scan(0u8, |start, &l| {
427                let cycle = (*start..*start + l).collect::<Vec<u8>>();
428                *start += l;
429                Some(cycle)
430            })
431            .collect_vec();
432        Permutation::from_cycles(&cycles).ok()
433    }
434
435    fn get_index_of_cc_symbol(&self, cc_sym: &Self::ClassSymbol) -> Option<usize> {
436        self.conjugacy_class_symbols
437            .as_ref()
438            .expect("Conjugacy class symbols not found.")
439            .get_index_of(cc_sym)
440    }
441
442    fn get_cc_symbol_of_index(&self, cc_idx: usize) -> Option<Self::ClassSymbol> {
443        self.conjugacy_class_symbols
444            .as_ref()
445            .expect("Conjugacy class symbols not found.")
446            .get_index(cc_idx)
447            .cloned()
448    }
449
450    fn set_class_symbols(&mut self, cc_symbols: &[Self::ClassSymbol]) {
451        self.conjugacy_class_symbols = Some(cc_symbols.iter().cloned().collect());
452    }
453
454    fn get_inverse_cc(&self, cc_idx: usize) -> Option<usize> {
455        Some(cc_idx)
456    }
457
458    fn class_number(&self) -> usize {
459        self.cycle_patterns
460            .as_ref()
461            .expect("Cycle patterns not found.")
462            .len()
463    }
464
465    /// Computes the class size given a class index.
466    ///
467    /// The closed form for a conjugacy class of a particular cycle pattern is given
468    /// [here](https://math.stackexchange.com/questions/140311/number-of-permutations-for-a-cycle-type).
469    fn class_size(&self, cc_idx: usize) -> Option<usize> {
470        let cycle_pattern = self
471            .cycle_patterns
472            .as_ref()
473            .expect("Cycle patterns not found.")
474            .get_index(cc_idx)?;
475        let mut cycle_pattern_count: Vec<(u8, u8)> = Vec::with_capacity(cycle_pattern.len());
476        let mut i = 0u8;
477        while i < u8::try_from(cycle_pattern.len()).unwrap() {
478            let mut j = i + 1;
479            while j < u8::try_from(cycle_pattern.len()).unwrap()
480                && cycle_pattern[usize::from(j)] == cycle_pattern[usize::from(i)]
481            {
482                j += 1;
483            }
484            cycle_pattern_count.push((cycle_pattern[usize::from(i)], j - i));
485            i = j;
486        }
487        let denom = cycle_pattern_count
488            .into_iter()
489            .map(|(l, m)| {
490                usize::from(l).pow(u32::from(m)) * usize::from(m).checked_factorial().unwrap()
491            })
492            .product();
493        Some(
494            usize::from(self.rank)
495                .checked_factorial()
496                .unwrap()
497                .div_euclid(denom),
498        )
499    }
500}
501
502impl CharacterProperties for PermutationGroup {
503    type RowSymbol = PermutationIrrepSymbol;
504    type CharTab = RepCharacterTable<PermutationIrrepSymbol, PermutationClassSymbol<u8>>;
505
506    fn character_table(&self) -> &Self::CharTab {
507        self.irrep_character_table
508            .as_ref()
509            .expect("Irrep character table not found for this group.")
510    }
511
512    fn unitary_represented(&self) -> bool {
513        true
514    }
515}
516
517impl IrrepCharTabConstruction for PermutationGroup {
518    fn set_irrep_character_table(&mut self, chartab: Self::CharTab) {
519        self.irrep_character_table = Some(chartab)
520    }
521}
522
523impl PermutationGroupProperties for PermutationGroup {
524    fn from_rank(rank: u8) -> Result<Self, anyhow::Error> {
525        assert!(rank > 0, "A permutation rank must be a positive integer.");
526        assert!(
527            rank <= 20,
528            "Permutations of rank more than 20 will not be representable."
529        );
530        log::debug!("Initialising lazy iterator for permutations of rank {rank}...");
531        let perms_iter = PermutationIterator {
532            rank,
533            raw_perm_indices: (0..usize::from(rank).checked_factorial().unwrap()),
534        };
535        let mut group = PermutationGroup::builder()
536            .rank(rank)
537            .perms_iter(perms_iter)
538            .build()
539            .expect("Unable to construct a `PermutationGroup`.");
540        log::debug!("Initialising lazy iterator for permutations of rank {rank}... Done.");
541        group.compute_class_structure()?;
542        group.set_class_symbols_from_cycle_patterns();
543        group.construct_irrep_character_table();
544        group.canonicalise_character_table();
545        Ok(group)
546    }
547
548    fn canonicalise_character_table(&mut self) {
549        let old_chartab = self.character_table();
550        let class_symbols = (0..self.class_number())
551            .map(|i| {
552                self.get_cc_symbol_of_index(i)
553                    .expect("Unable to retrieve all class symbols.")
554            })
555            .collect_vec();
556        let (char_arr, sorted_fs) = sort_perm_irreps(
557            &old_chartab.array().view(),
558            &old_chartab.frobenius_schurs.values().copied().collect_vec(),
559        );
560        let ordered_irreps = deduce_permutation_irrep_symbols(&char_arr.view());
561        self.set_irrep_character_table(RepCharacterTable::new(
562            &old_chartab.name,
563            &ordered_irreps,
564            &class_symbols,
565            &[],
566            char_arr,
567            &sorted_fs,
568        ));
569    }
570}