Skip to main content

algebraeon_groups/free_group/
todd_coxeter.rs

1use crate::{permutation::Permutation, structure::MetaGroupSignature};
2use std::{
3    collections::HashMap,
4    ops::Mul,
5    sync::atomic::{AtomicUsize, Ordering},
6};
7
8#[derive(Clone, Copy)]
9enum Neighbor {
10    None(),
11    Coset(usize),
12}
13
14struct SchreierGraph {
15    num_gens: usize,
16    idents: Vec<usize>,
17    neighbors: Vec<Vec<Neighbor>>,
18}
19
20impl SchreierGraph {
21    fn new(num_gens: usize) -> Self {
22        Self {
23            num_gens,
24            idents: vec![],
25            neighbors: vec![],
26        }
27    }
28
29    fn find_coset(&self, mut c: usize) -> usize {
30        while self.idents[c] != c {
31            c = self.idents[c];
32        }
33        c
34    }
35
36    fn new_coset(&mut self) -> usize {
37        let c = self.idents.len();
38        self.idents.push(c);
39        let mut new_nbs = vec![];
40        for _ in 0..2 * self.num_gens {
41            new_nbs.push(Neighbor::None());
42        }
43        self.neighbors.push(new_nbs);
44        c
45    }
46
47    fn unify(&mut self, c1: usize, c2: usize) {
48        let mut c1 = self.find_coset(c1);
49        let mut c2 = self.find_coset(c2);
50        if c1 == c2 {
51            return;
52        }
53        if c2 < c1 {
54            (c1, c2) = (c2, c1);
55        }
56        self.idents[c2] = c1;
57        for d in 0..2 * self.num_gens {
58            let n1 = self.neighbors[c1][d];
59            let n2 = self.neighbors[c2][d];
60            match n1 {
61                Neighbor::None() => {
62                    self.neighbors[c1][d] = n2;
63                }
64                Neighbor::Coset(n1c) => match n2 {
65                    Neighbor::None() => {}
66                    Neighbor::Coset(n2c) => {
67                        self.unify(n1c, n2c);
68                    }
69                },
70            }
71        }
72    }
73
74    fn follow(&mut self, mut c: usize, d: usize) -> usize {
75        c = self.find_coset(c);
76        #[allow(clippy::indexing_slicing)]
77        match self.neighbors[c][d] {
78            Neighbor::None() => {
79                let nc = self.new_coset();
80                self.neighbors[c][d] = Neighbor::Coset(nc);
81                nc
82            }
83            Neighbor::Coset(nc) => self.find_coset(nc),
84        }
85    }
86
87    fn follow_path(&mut self, mut c: usize, ds: &[usize]) -> usize {
88        c = self.find_coset(c);
89        for d in ds.iter().rev() {
90            c = self.follow(c, *d);
91        }
92        c
93    }
94}
95
96/*
97n = num_gens
98now a generator is represented by a signed integer.
99The generators are 0, 2, 4, ..., 2n-2 with inverses 1, 3, 5, ..., 2n-1
100 */
101fn enumerate_cosets_impl(
102    num_gens: usize,
103    rels: Vec<Vec<usize>>,
104    subgens: Vec<Vec<usize>>,
105) -> (usize, Vec<Permutation>) {
106    //impose inverse relations
107    let mut full_rels = rels.clone();
108    for i in 0..num_gens {
109        full_rels.push(vec![2 * i + 1, 2 * i]);
110    }
111    let rels = full_rels;
112
113    //check rels
114    for word in &rels {
115        for a in word {
116            assert!(*a < 2 * num_gens);
117        }
118    }
119    //check subgens
120    for word in &subgens {
121        for a in word {
122            assert!(*a < 2 * num_gens);
123        }
124    }
125
126    let mut scg = SchreierGraph::new(num_gens);
127    let start = scg.new_coset();
128
129    for subgen in subgens {
130        let end = scg.follow_path(start, &subgen);
131        scg.unify(end, start);
132    }
133
134    let mut to_visit = 0;
135    while to_visit < scg.idents.len() {
136        let c = scg.find_coset(to_visit);
137        if c == to_visit {
138            for rel in &rels {
139                let b = scg.follow_path(c, rel);
140                scg.unify(b, c);
141            }
142        }
143        to_visit += 1;
144    }
145
146    #[allow(clippy::items_after_statements)]
147    enum CosetIndexEntry {
148        None,
149        Index(usize),
150    }
151
152    let mut coset_index_lookup = vec![];
153    let mut cosets = vec![];
154    for (i, c) in scg.idents.iter().enumerate() {
155        if i == *c {
156            coset_index_lookup.push(CosetIndexEntry::Index(cosets.len()));
157            cosets.push(*c);
158        } else {
159            coset_index_lookup.push(CosetIndexEntry::None);
160        }
161    }
162
163    let mut perms = vec![];
164    #[allow(clippy::indexing_slicing)]
165    for g in 0..num_gens {
166        perms.push(
167            Permutation::new(
168                cosets
169                    .iter()
170                    .map(|c| match coset_index_lookup[scg.follow(*c, 2 * g)] {
171                        CosetIndexEntry::None => {
172                            panic!()
173                        }
174                        CosetIndexEntry::Index(i) => {
175                            debug_assert_eq!(cosets[i], scg.follow(*c, 2 * g));
176                            i
177                        }
178                    })
179                    .collect(),
180            )
181            .unwrap(),
182        );
183    }
184
185    (cosets.len(), perms)
186}
187
188#[derive(Debug, Clone, PartialEq, Eq, Hash)]
189pub struct FinitelyGeneratedGroupElement {
190    // Positive entries are idents of generators
191    // Negative entries are inverses of the generator
192    product: Vec<isize>,
193}
194impl FinitelyGeneratedGroupElement {
195    pub fn identity() -> Self {
196        Self { product: vec![] }
197    }
198    pub fn inv(&self) -> Self {
199        Self {
200            product: self.product.iter().rev().map(|ident| -ident).collect(),
201        }
202    }
203    pub fn pow(&self, n: isize) -> Self {
204        match n.cmp(&0) {
205            std::cmp::Ordering::Equal => Self::identity(),
206            std::cmp::Ordering::Less => self.pow(-n).inv(),
207            std::cmp::Ordering::Greater => {
208                let mut pow_product = vec![];
209                for _ in 0..n {
210                    for x in &self.product {
211                        pow_product.push(*x);
212                    }
213                }
214                Self {
215                    product: pow_product,
216                }
217            }
218        }
219    }
220}
221impl Mul<FinitelyGeneratedGroupElement> for FinitelyGeneratedGroupElement {
222    type Output = FinitelyGeneratedGroupElement;
223    fn mul(self, other: FinitelyGeneratedGroupElement) -> Self::Output {
224        Self {
225            product: self.product.into_iter().chain(other.product).collect(),
226        }
227    }
228}
229impl Mul<&FinitelyGeneratedGroupElement> for FinitelyGeneratedGroupElement {
230    type Output = FinitelyGeneratedGroupElement;
231    fn mul(self, other: &FinitelyGeneratedGroupElement) -> Self::Output {
232        self * other.clone()
233    }
234}
235impl Mul<FinitelyGeneratedGroupElement> for &FinitelyGeneratedGroupElement {
236    type Output = FinitelyGeneratedGroupElement;
237    fn mul(self, other: FinitelyGeneratedGroupElement) -> Self::Output {
238        self.clone() * other
239    }
240}
241impl Mul<&FinitelyGeneratedGroupElement> for &FinitelyGeneratedGroupElement {
242    type Output = FinitelyGeneratedGroupElement;
243    fn mul(self, other: &FinitelyGeneratedGroupElement) -> Self::Output {
244        self.clone() * other.clone()
245    }
246}
247
248/// A struct used to help build finitely generated group presentations.
249#[derive(Debug, Clone)]
250pub struct FinitelyGeneratedGroupPresentation {
251    // A vector of generator idents pointing at the order in which they were added
252    generators: HashMap<usize, usize>,
253    // Vectors of generator expressions
254    // Each Vec<usize> represents a product of generators or their inverses
255    // For each usize i:
256    //  If even: represents the generator at index i/2 in self.generators
257    //  If odd:represents the inverse of the generator at index (i-1)/2 in self.generators
258    relations: Vec<Vec<usize>>,
259}
260
261impl FinitelyGeneratedGroupPresentation {
262    /// Create a new empty group presentation.
263    #[allow(clippy::new_without_default)]
264    pub fn new() -> Self {
265        Self {
266            generators: HashMap::new(),
267            relations: vec![],
268        }
269    }
270
271    /// Add a new generator for the finitely generated group.
272    pub fn add_generator(&mut self) -> FinitelyGeneratedGroupElement {
273        static COUNTER: AtomicUsize = AtomicUsize::new(1);
274        let ident = COUNTER.fetch_add(1, Ordering::Relaxed);
275        let idx = self.generators.len();
276        self.generators.insert(ident, idx);
277        FinitelyGeneratedGroupElement {
278            product: vec![ident as isize],
279        }
280    }
281
282    fn translate_generator_expression(&self, expr: FinitelyGeneratedGroupElement) -> Vec<usize> {
283        expr.product
284            .into_iter()
285            .map(|mut ident| {
286                debug_assert!(ident != 0);
287                let mut sign = false;
288                if ident < 0 {
289                    sign = true;
290                    ident = -ident;
291                }
292                debug_assert!(ident > 0);
293                let ident = ident as usize;
294                match self.generators.get(&ident) {
295                    None => panic!(
296                        "\
297A generator in the expression does not belong to \
298the list of generators for this finitely generated group"
299                    ),
300                    Some(index) => {
301                        let mut val = 2 * index;
302                        if sign {
303                            val += 1;
304                        }
305                        val
306                    }
307                }
308            })
309            .collect()
310    }
311
312    /// Add a new relation among the generators of the finitely generated group of the form rel=identity
313    pub fn add_relation(&mut self, rel: FinitelyGeneratedGroupElement) {
314        self.relations
315            .push(self.translate_generator_expression(rel));
316    }
317
318    /// Add a new relation among the generators of the finitely generated group of the form rel1=rel2
319    pub fn add_two_sided_relation(
320        &mut self,
321        rel1: FinitelyGeneratedGroupElement,
322        rel2: FinitelyGeneratedGroupElement,
323    ) {
324        self.add_relation(rel1 * rel2.inv());
325    }
326
327    /// If finite, return the number of elements of the finitely generated group
328    /// and return a vector, in the order each generator was added, of the action of each generator on the elements
329    /// If the number of elements is infinite, a call to this function will never halt.
330    /// The identity element is always labelled by 0
331    pub fn enumerate_elements(&self) -> (usize, Vec<Permutation>) {
332        enumerate_cosets_impl(self.generators.len(), self.relations.clone(), vec![])
333    }
334
335    pub fn into_coset_enumerator(self) -> FinitelyGeneratedGroupCosetEnumerator {
336        FinitelyGeneratedGroupCosetEnumerator {
337            group: self,
338            subgroup_generators: vec![],
339        }
340    }
341
342    pub fn into_finite_group(
343        &self,
344    ) -> super::super::composition_table::group::FiniteGroupMultiplicationTable {
345        let num_gens = self.generators.len();
346        let (n, gen_perms) = self.enumerate_elements();
347        #[allow(clippy::redundant_closure_for_method_calls)]
348        let inv_gen_perms = gen_perms
349            .iter()
350            .map(|perm| perm.inverse())
351            .collect::<Vec<Permutation>>();
352
353        //write each element as a word in the n generators
354        let mut paths: Vec<(bool, Vec<usize>)> = vec![];
355        for i in 0..n {
356            paths.push((i == 0, vec![]));
357        }
358
359        let mut boundary = vec![0];
360        let mut new_boundary = vec![];
361        while !boundary.is_empty() {
362            for b_idx in boundary {
363                let (b_done, b_path) = paths[b_idx].clone();
364                debug_assert!(b_done);
365                #[allow(clippy::needless_range_loop)]
366                for g in 0..num_gens {
367                    let c = gen_perms[g].call(b_idx);
368                    if !paths[c].0 {
369                        let mut c_path = b_path.clone();
370                        c_path.push(g);
371                        paths[c] = (true, c_path);
372                        new_boundary.push(c);
373                    }
374                }
375            }
376            boundary = new_boundary;
377            new_boundary = vec![];
378        }
379
380        //remove the done flags from paths and check that the values make sense
381        for p in &paths {
382            assert!(p.0);
383        }
384        let paths = paths
385            .into_iter()
386            .map(|(_done, path)| path)
387            .collect::<Vec<Vec<usize>>>();
388        for p in &paths {
389            for g in p {
390                debug_assert!(*g < num_gens);
391            }
392        }
393
394        super::super::composition_table::group::FiniteGroupMultiplicationTable::new_unchecked(
395            n,
396            0,
397            (0..n)
398                .map(|x| {
399                    let mut y = 0;
400                    for g in paths[x].iter().rev() {
401                        y = inv_gen_perms[*g].call(y);
402                    }
403                    y
404                })
405                .collect(),
406            (0..n)
407                .map(|x| {
408                    (0..n)
409                        .map(|y| {
410                            let mut z = 0;
411                            for g in &paths[x] {
412                                z = gen_perms[*g].call(z);
413                            }
414                            for g in &paths[y] {
415                                z = gen_perms[*g].call(z);
416                            }
417                            z
418                        })
419                        .collect()
420                })
421                .collect(),
422            None,
423            None,
424        )
425    }
426}
427
428/// A struct used to help enumerate cosets of a subgroup in a finitely generated group.
429#[derive(Debug, Clone)]
430pub struct FinitelyGeneratedGroupCosetEnumerator {
431    group: FinitelyGeneratedGroupPresentation,
432    subgroup_generators: Vec<Vec<usize>>,
433}
434
435impl FinitelyGeneratedGroupCosetEnumerator {
436    /// Add a new generator to the subgroup whose cosets are to be enumerated.
437    pub fn add_subgroup_generator(&mut self, subgroup_generator: FinitelyGeneratedGroupElement) {
438        self.subgroup_generators.push(
439            self.group
440                .translate_generator_expression(subgroup_generator),
441        );
442    }
443
444    /// If finite, return the number of cosets of the subgroup inside the finitely generated group
445    /// and return a vector, in the order each generator was added, of the action of each generator on the set of enumerated cosets
446    /// If the number of cosets is infinite, a call to this function will never halt.
447    pub fn enumerate_cosets(&self) -> (usize, Vec<Permutation>) {
448        enumerate_cosets_impl(
449            self.group.generators.len(),
450            self.group.relations.clone(),
451            self.subgroup_generators.clone(),
452        )
453    }
454}
455
456#[cfg(test)]
457mod tests {
458    use super::*;
459
460    #[test]
461    fn trivial() {
462        let (n, perms) = enumerate_cosets_impl(0, vec![], vec![]);
463        assert_eq!(n, 1);
464        assert_eq!(perms.len(), 0);
465    }
466
467    #[test]
468    fn alternating_5() {
469        let (n, _perms) = enumerate_cosets_impl(
470            2,
471            vec![vec![0, 0, 0, 0, 0], vec![2, 2], vec![0, 2, 0, 2, 0, 2]],
472            vec![],
473        );
474        assert_eq!(n, 60);
475
476        let (n, _perms) = enumerate_cosets_impl(
477            2,
478            vec![vec![0, 0, 0, 0, 0], vec![2, 2], vec![0, 2, 0, 2, 0, 2]],
479            vec![vec![0, 2]],
480        );
481        assert_eq!(n, 20);
482    }
483
484    #[test]
485    fn small_coxeter() {
486        //icosahedral symmetry (3, 5)
487        let (n, _perms) = enumerate_cosets_impl(
488            3,
489            vec![
490                vec![0, 0],
491                vec![2, 2],
492                vec![4, 4],
493                vec![0, 2, 0, 2, 0, 2, 0, 2, 0, 2],
494                vec![2, 4, 2, 4, 2, 4],
495                vec![0, 4, 0, 4],
496            ],
497            vec![],
498        );
499        assert_eq!(n, 120);
500    }
501
502    #[test]
503    fn large_coxeter() {
504        //600-cell symmetry (3, 3, 5)
505        let (n, _perms) = enumerate_cosets_impl(
506            4,
507            vec![
508                vec![0, 0],
509                vec![2, 2],
510                vec![4, 4],
511                vec![6, 6],
512                vec![0, 2, 0, 2, 0, 2, 0, 2, 0, 2],
513                vec![2, 4, 2, 4, 2, 4],
514                vec![4, 6, 4, 6, 4, 6],
515                vec![0, 4, 0, 4],
516                vec![0, 6, 0, 6],
517                vec![2, 6, 2, 6],
518            ],
519            vec![],
520        );
521        assert_eq!(n, 14400);
522    }
523
524    #[test]
525    fn unexpected_trivial() {
526        // <a, b | bab^-1=a^2, aba=bab> is the trivial group
527        let (n, _perms) =
528            enumerate_cosets_impl(2, vec![vec![2, 0, 3, 1, 1], vec![0, 2, 0, 3, 1, 3]], vec![]);
529        assert_eq!(n, 1);
530    }
531
532    #[allow(clippy::many_single_char_names)]
533    #[test]
534    fn test_ergonomic_todd_coxeter() {
535        let mut g = FinitelyGeneratedGroupPresentation::new();
536        // Add the 3 generators
537        let a = g.add_generator();
538        let b = g.add_generator();
539        let c = g.add_generator();
540        // Add the relations
541        g.add_relation(a.pow(2));
542        g.add_relation(b.pow(2));
543        g.add_relation(c.pow(2));
544        g.add_relation((&a * &b).pow(3));
545        g.add_relation((&b * &c).pow(5));
546        g.add_relation((&a * &c).pow(2));
547        // Count elements
548        let (n, _) = g.enumerate_elements();
549        assert_eq!(n, 120);
550
551        // Count cosets
552        let mut s = g.into_coset_enumerator();
553        s.add_subgroup_generator(a.clone());
554        let (n, _) = s.enumerate_cosets();
555        assert_eq!(n, 60);
556    }
557}