algebraeon_groups/composition_table/
group.rs

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