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 if self.ident >= self.n {
27 return Err("bad ident elem");
28 }
29 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 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 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 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 #[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 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 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 fn subgroups_impl(&'_ self, only_normal: bool) -> Vec<(Subgroup<'_>, Subset<'_>)> {
299 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 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 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 #[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 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 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), (examples::symmetric_group_structure(2), 2), (examples::symmetric_group_structure(3), 3), (examples::symmetric_group_structure(4), 5), (examples::symmetric_group_structure(5), 7), ] {
531 assert_eq!(grp.conjugacy_classes().size(), num_ccls);
532 }
533 }
534}