algebraeon_groups/composition_table/
group.rs

1use super::normal_subgroup::*;
2use super::partition::*;
3use super::subgroup::*;
4use super::subset::*;
5use algebraeon_sets::combinatorics::Partition;
6use rayon::prelude::*;
7use std::collections::HashMap;
8use std::collections::HashSet;
9use std::fmt::Debug;
10use std::hash::Hash;
11
12#[derive(Debug)]
13pub struct FiniteGroupMultiplicationTable {
14    n: usize,
15    ident: usize,
16    inv: Vec<usize>,
17    mul: Vec<Vec<usize>>,
18    conjugacy_classes: Option<Partition>,
19    is_abelian: Option<bool>,
20    is_simple: Option<bool>,
21}
22
23impl FiniteGroupMultiplicationTable {
24    pub fn check_state(&self) -> Result<(), &'static str> {
25        //check ident
26        if !(self.ident < self.n) {
27            return Err("bad ident elem");
28        }
29        //check inv
30        if !(self.inv.len() == self.n) {
31            return Err("bad inv len");
32        }
33        for x in &self.inv {
34            if !(*x < self.n) {
35                return Err("bad inv elem");
36            }
37        }
38        //check mul
39        if !(self.mul.len() == self.n) {
40            return Err("bad mul left len");
41        }
42        for m in &self.mul {
43            if !(m.len() == self.n) {
44                return Err("bad mul right len");
45            }
46            for x in m {
47                if !(*x < self.n) {
48                    return Err("bad mul elem");
49                }
50            }
51        }
52        //identity axiom
53        for x in 0..self.n {
54            if !(x == self.mul[x][self.ident] && x == self.mul[self.ident][x]) {
55                return Err("identity axiom failed");
56            }
57        }
58        //inv axiom
59        for x in 0..self.n {
60            if !(self.ident == self.mul[self.inv[x]][x] && self.ident == self.mul[x][self.inv[x]]) {
61                return Err("inverse axiom failed");
62            }
63        }
64        //assoc axiom
65        for x in 0..self.n {
66            for y in 0..self.n {
67                for z in 0..self.n {
68                    if !(self.mul[x][self.mul[y][z]] == self.mul[self.mul[x][y]][z]) {
69                        return Err("assoc axiom failed");
70                    }
71                }
72            }
73        }
74
75        //cached is_abelian if present
76        match self.is_abelian {
77            Some(claimed_is_abelian) => {
78                if claimed_is_abelian != self.compute_is_abelian() {
79                    return Err("incorrect is_abelian flag");
80                }
81            }
82            None => {}
83        };
84
85        //check is_simple
86        match &self.is_simple {
87            Some(is_simple) => {
88                if (self.normal_subgroups().len() == 2) != *is_simple {
89                    return Err("is_simple flag is incorrect");
90                }
91            }
92            None => {}
93        }
94
95        Ok(())
96    }
97
98    pub fn new(
99        n: usize,
100        ident: usize,
101        inv: Vec<usize>,
102        mul: Vec<Vec<usize>>,
103    ) -> Result<Self, &'static str> {
104        let grp = FiniteGroupMultiplicationTable {
105            n,
106            ident,
107            inv,
108            mul,
109            is_abelian: None,
110            conjugacy_classes: None,
111            is_simple: None,
112        };
113        match grp.check_state() {
114            Ok(()) => Ok(grp),
115            Err(msg) => Err(msg),
116        }
117    }
118
119    pub fn new_unchecked(
120        n: usize,
121        ident: usize,
122        inv: Vec<usize>,
123        mul: Vec<Vec<usize>>,
124        is_abelian: Option<bool>,
125        is_simple: Option<bool>,
126    ) -> Self {
127        Self {
128            n,
129            ident,
130            inv,
131            mul,
132            is_abelian,
133            conjugacy_classes: None,
134            is_simple,
135        }
136    }
137
138    pub fn mul(&self, x: usize, y: usize) -> usize {
139        self.mul[x][y]
140    }
141
142    pub fn inv(&self, x: usize) -> usize {
143        self.inv[x]
144    }
145
146    pub fn ident(&self) -> usize {
147        self.ident
148    }
149
150    pub fn from_raw_model<T: PartialEq + Eq + Hash + Clone + Debug>(
151        elems: Vec<T>,
152        ident: impl Fn() -> T,
153        inv: impl Fn(T) -> T,
154        mul: impl Fn(T, T) -> T,
155    ) -> Result<Self, &'static str> {
156        let grp = Self::from_raw_model_unchecked(elems, ident, inv, mul, None, None);
157        match grp.check_state() {
158            Ok(()) => Ok(grp),
159            Err(msg) => Err(msg),
160        }
161    }
162
163    pub fn from_raw_model_unchecked<T: PartialEq + Eq + Hash + Clone + Debug>(
164        elems: Vec<T>,
165        ident: impl Fn() -> T,
166        inv: impl Fn(T) -> T,
167        mul: impl Fn(T, T) -> T,
168        is_abelian: Option<bool>,
169        is_simple: Option<bool>,
170    ) -> Self {
171        let n = elems.len();
172        let mut idx_map: HashMap<T, usize> = HashMap::new();
173        for (i, x) in elems.iter().enumerate() {
174            idx_map.insert(x.clone(), i);
175        }
176
177        Self::new_unchecked(
178            n,
179            idx_map[&ident()],
180            {
181                let mut inv_lookup = vec![];
182                for x in &elems {
183                    inv_lookup.push(idx_map[&inv(x.clone())]);
184                }
185                inv_lookup
186            },
187            {
188                let mut mul_lookup = vec![];
189                for (i, x) in elems.iter().enumerate() {
190                    mul_lookup.push(vec![]);
191                    for y in &elems {
192                        mul_lookup[i].push(idx_map[&mul(x.clone(), y.clone())]);
193                    }
194                }
195                mul_lookup
196            },
197            is_abelian,
198            is_simple,
199        )
200    }
201
202    pub fn size(&self) -> usize {
203        self.n
204    }
205
206    pub fn elems(&self) -> std::ops::Range<usize> {
207        0..self.n
208    }
209
210    pub fn mul_many(&self, elems: &Vec<usize>) -> usize {
211        if elems.len() == 0 {
212            return self.ident;
213        }
214        let mut prod = elems[0];
215        for i in 1..elems.len() {
216            prod = self.mul[prod][elems[i]];
217        }
218        prod
219    }
220
221    pub fn order(&self, x: usize) -> Result<usize, ()> {
222        if !(x < self.n) {
223            return Err(());
224        }
225        let mut y = x;
226        let mut ord = 1;
227        while y != self.ident {
228            y = self.mul[x][y];
229            ord += 1;
230            debug_assert!(ord <= self.n);
231        }
232        return Ok(ord);
233    }
234
235    fn compute_is_abelian(&self) -> bool {
236        for x in 0..self.n {
237            for y in 0..x {
238                if self.mul[x][y] != self.mul[y][x] {
239                    return false;
240                }
241            }
242        }
243        true
244    }
245
246    pub fn is_abelian(&self) -> bool {
247        match &self.is_abelian {
248            Some(flag) => {
249                return *flag;
250            }
251            None => {
252                return self.compute_is_abelian();
253            }
254        }
255    }
256
257    fn compute_conjugacy_classes(&self) -> Partition {
258        let mut unclassified_elems = HashSet::<_>::from_iter(self.elems());
259        let mut classes = vec![];
260        let mut lookup = vec![0; self.n];
261        while unclassified_elems.len() > 0 {
262            let x = unclassified_elems.iter().next().unwrap();
263            let mut class = HashSet::new();
264            for g in self.elems() {
265                class.insert(self.mul[self.mul[g][*x]][self.inv[g]]);
266            }
267            for y in &class {
268                unclassified_elems.remove(y);
269                lookup[*y] = classes.len();
270            }
271            classes.push(class);
272        }
273        Partition::new_unchecked(classes, lookup)
274    }
275
276    pub fn cache_conjugacy_classes(&mut self) {
277        self.conjugacy_classes = Some(self.compute_conjugacy_classes())
278    }
279
280    pub fn conjugacy_class(&mut self, x: usize) -> Result<Subset, ()> {
281        if !(x < self.n) {
282            return Err(());
283        }
284        self.cache_conjugacy_classes();
285        match &self.conjugacy_classes {
286            Some(conj_state) => Ok(Subset::new_unchecked(
287                self,
288                conj_state.class_containing(x).clone(),
289            )),
290            None => panic!(),
291        }
292    }
293
294    pub fn conjugacy_classes(&self) -> GroupPartition {
295        GroupPartition {
296            group: self,
297            partition: match &self.conjugacy_classes {
298                Some(state) => state.clone(),
299                None => self.compute_conjugacy_classes(),
300            },
301        }
302    }
303
304    //returns a hashmap of subgroups and a minimal generating set
305    fn subgroups_impl(&self, only_normal: bool) -> Vec<(Subgroup, Subset)> {
306        //choose one generator of each cyclic subgroup
307        //The partition of elements where x~y iff <x>=<y> is such that an equivelence class is either a subset of a subgroup or doesnot intersect the subgroup
308        let mut distinguished_gens = vec![];
309        let mut subgroups: HashMap<Subgroup, Subset> = HashMap::new();
310        for x in self.elems() {
311            let singleton_x_subset = Subset::new_unchecked(&self, HashSet::from_iter(vec![x]));
312            let cyclic_sg = match only_normal {
313                false => singleton_x_subset.generated_subgroup().unwrap(),
314                true => singleton_x_subset.normal_closure().unwrap().to_subgroup(),
315            };
316            if !subgroups.contains_key(&cyclic_sg) {
317                subgroups.insert(cyclic_sg, singleton_x_subset.clone());
318                distinguished_gens.push(x);
319            }
320        }
321
322        //generate all subgroups by itteratively adding generators
323        let mut boundary = HashMap::new();
324        for (sg, gens) in subgroups.clone().into_iter() {
325            boundary.insert(sg, gens);
326        }
327        let mut next_boundary = HashMap::new();
328        while boundary.len() > 0 {
329            for (_sg, sg_gens) in &boundary {
330                //compute and sort the next subgroups in parallel threads
331                for (new_sg, new_gens) in distinguished_gens
332                    .par_iter()
333                    .map(|dgen| {
334                        let mut new_gens = sg_gens.clone();
335                        new_gens.add_elem(*dgen).unwrap();
336                        let new_sg = match only_normal {
337                            false => new_gens.generated_subgroup().unwrap(),
338                            true => new_gens.normal_closure().unwrap().to_subgroup(),
339                        };
340                        return (new_sg, new_gens);
341                    })
342                    .collect::<Vec<(Subgroup, Subset)>>()
343                {
344                    //collect the computed subgroups in the main thread
345                    if !subgroups.contains_key(&new_sg) {
346                        next_boundary.insert(new_sg.clone(), new_gens.clone());
347                        subgroups.insert(new_sg, new_gens);
348                    }
349                }
350            }
351            boundary = next_boundary;
352            next_boundary = HashMap::new();
353        }
354        return subgroups
355            .into_iter()
356            .map(|(elems, gens)| (elems, gens))
357            .collect();
358    }
359
360    pub fn subgroups(&self) -> Vec<(Subgroup, Subset)> {
361        self.subgroups_impl(false)
362    }
363
364    pub fn normal_subgroups(&self) -> Vec<(NormalSubgroup, Subset)> {
365        self.subgroups_impl(true)
366            .into_iter()
367            .map(|(subgroup, gens)| (NormalSubgroup::new_unchecked(subgroup), gens))
368            .collect()
369    }
370}
371
372pub fn direct_product_structure(
373    group_one: &FiniteGroupMultiplicationTable,
374    group_two: &FiniteGroupMultiplicationTable,
375) -> FiniteGroupMultiplicationTable {
376    let m = group_one.n;
377    let n = group_two.n;
378
379    let single_to_pair = |i: usize| -> (usize, usize) { (i % m, i / m) };
380    let pair_to_single = |i: usize, j: usize| -> usize { i + j * m };
381
382    FiniteGroupMultiplicationTable {
383        n: m * n,
384        ident: pair_to_single(group_one.ident, group_two.ident),
385        inv: (0..m * n)
386            .map(|x| {
387                let (i, j) = single_to_pair(x);
388                pair_to_single(group_one.inv[i], group_two.inv[j])
389            })
390            .collect(),
391        mul: (0..m * n)
392            .map(|x| {
393                (0..m * n)
394                    .map(|y| {
395                        let (ix, jx) = single_to_pair(x);
396                        let (iy, jy) = single_to_pair(y);
397                        pair_to_single(group_one.mul[ix][iy], group_two.mul[jx][jy])
398                    })
399                    .collect()
400            })
401            .collect(),
402        conjugacy_classes: None,
403        is_abelian: None,
404        is_simple: None,
405    }
406}
407
408pub mod examples {
409    use crate::free_group::todd_coxeter::FinitelyGeneratedGroupPresentation;
410
411    use super::*;
412
413    pub fn trivial_group_structure() -> FiniteGroupMultiplicationTable {
414        cyclic_group_structure(1)
415    }
416
417    pub fn cyclic_group_structure(n: usize) -> FiniteGroupMultiplicationTable {
418        FiniteGroupMultiplicationTable::from_raw_model_unchecked(
419            (0..n).collect(),
420            || 0,
421            |x: usize| (n - x) % n,
422            |x: usize, y: usize| (x + y) % n,
423            Some(true),
424            None,
425        )
426    }
427
428    pub fn klein_four_structure() -> FiniteGroupMultiplicationTable {
429        direct_product_structure(&cyclic_group_structure(2), &cyclic_group_structure(2))
430    }
431
432    pub fn dihedral_group_structure(n: usize) -> FiniteGroupMultiplicationTable {
433        // dihedral group using the presentation
434        // <a b : a^2 = b^2 = (ab)^n = e>
435        assert!(1 <= n);
436
437        let mut grp = FinitelyGeneratedGroupPresentation::new();
438        let a = grp.add_generator();
439        let b = grp.add_generator();
440        grp.add_relation(a.pow(2));
441        grp.add_relation(b.pow(2));
442        grp.add_relation((&a * &b).pow(n as isize));
443        let mut grp = grp.into_finite_group();
444        grp.is_abelian = Some(n <= 2);
445        grp.is_simple = Some(n <= 1);
446        grp
447    }
448
449    pub fn quaternion_group_structure() -> FiniteGroupMultiplicationTable {
450        // quaternion group using the presentation
451        // <-1 i j k : (-1)^2 = 1  i^2 = j^2 = k^2 = ijk = -1>
452        let mut grp = FinitelyGeneratedGroupPresentation::new();
453        let a = grp.add_generator();
454        let i = grp.add_generator();
455        let j = grp.add_generator();
456        let k = grp.add_generator();
457        grp.add_relation(a.pow(2));
458        grp.add_two_sided_relation(i.pow(2), a.clone());
459        grp.add_two_sided_relation(j.pow(2), a.clone());
460        grp.add_two_sided_relation(k.pow(2), a.clone());
461        grp.add_two_sided_relation(&i * &j * &k, a.clone());
462        let mut grp = grp.into_finite_group();
463        grp.is_abelian = Some(false);
464        grp.is_simple = Some(false);
465        grp
466    }
467
468    pub fn symmetric_group_structure(n: usize) -> FiniteGroupMultiplicationTable {
469        super::super::super::permutation::Permutation::symmetric_composition_table(n).0
470    }
471
472    pub fn alternating_group_structure(n: usize) -> FiniteGroupMultiplicationTable {
473        super::super::super::permutation::Permutation::alternating_composition_table(n).0
474    }
475}
476
477#[cfg(test)]
478mod group_tests {
479    use super::*;
480
481    #[test]
482    fn test_cyclic() {
483        for k in vec![1, 2, 3, 81, 91, 97, 100, 128] {
484            let mut grp = examples::cyclic_group_structure(k);
485            grp.cache_conjugacy_classes();
486            grp.check_state().unwrap();
487            assert_eq!(grp.elems().len(), k);
488        }
489    }
490
491    #[test]
492    fn test_dihedral() {
493        for k in vec![1, 2, 3, 16, 17, 18, 50] {
494            let mut grp = examples::dihedral_group_structure(k);
495            grp.cache_conjugacy_classes();
496            grp.check_state().unwrap();
497            assert_eq!(grp.elems().len(), 2 * k);
498        }
499    }
500
501    #[test]
502    fn test_quaternion() {
503        let mut grp = examples::quaternion_group_structure();
504        assert_eq!(grp.size(), 8);
505        grp.cache_conjugacy_classes();
506        grp.check_state().unwrap();
507    }
508
509    #[test]
510    fn test_direct_product() {
511        let grp1 = examples::dihedral_group_structure(5);
512        let grp2 = examples::dihedral_group_structure(4);
513        let grp3 = direct_product_structure(&grp1, &grp2);
514        grp3.check_state().unwrap();
515    }
516
517    #[test]
518    fn test_conjugacy_class_count() {
519        for (grp, num_ccls) in vec![
520            (examples::cyclic_group_structure(12), 12),
521            (examples::cyclic_group_structure(13), 13),
522            (examples::dihedral_group_structure(1), 2),
523            (examples::dihedral_group_structure(12), 9),
524            (examples::dihedral_group_structure(13), 8),
525            (examples::symmetric_group_structure(1), 1), //only the identity
526            (examples::symmetric_group_structure(2), 2), //ident, 2-cycle
527            (examples::symmetric_group_structure(3), 3), //ident, 2-cycle, 3-cycle
528            (examples::symmetric_group_structure(4), 5), //ident, 2-cycle, 3-cycle, 4-cycle, (2, 2)-cycle
529            (examples::symmetric_group_structure(5), 7), //ident, 2-cycle, 3-cycle, 4-cycle, 5-cycle, (2, 2)-cycle, (2, 3)-cycle
530                                                         // (examples::symmetric_group_structure(6), 11), //ident, 2, 3, 4, 5, 6, (2, 2), (2, 3), (2, 4), (3, 3), (2, 2, 2)
531        ] {
532            assert_eq!(grp.conjugacy_classes().size(), num_ccls);
533        }
534    }
535}