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
96fn enumerate_cosets_impl(
102 num_gens: usize,
103 rels: Vec<Vec<usize>>,
104 subgens: Vec<Vec<usize>>,
105) -> (usize, Vec<Permutation>) {
106 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 for word in &rels {
115 for a in word {
116 assert!(*a < 2 * num_gens);
117 }
118 }
119 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 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#[derive(Debug, Clone)]
250pub struct FinitelyGeneratedGroupPresentation {
251 generators: HashMap<usize, usize>,
253 relations: Vec<Vec<usize>>,
259}
260
261impl FinitelyGeneratedGroupPresentation {
262 #[allow(clippy::new_without_default)]
264 pub fn new() -> Self {
265 Self {
266 generators: HashMap::new(),
267 relations: vec![],
268 }
269 }
270
271 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 pub fn add_relation(&mut self, rel: FinitelyGeneratedGroupElement) {
314 self.relations
315 .push(self.translate_generator_expression(rel));
316 }
317
318 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 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 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 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#[derive(Debug, Clone)]
430pub struct FinitelyGeneratedGroupCosetEnumerator {
431 group: FinitelyGeneratedGroupPresentation,
432 subgroup_generators: Vec<Vec<usize>>,
433}
434
435impl FinitelyGeneratedGroupCosetEnumerator {
436 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 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 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 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 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 let a = g.add_generator();
538 let b = g.add_generator();
539 let c = g.add_generator();
540 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 let (n, _) = g.enumerate_elements();
549 assert_eq!(n, 120);
550
551 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}