1use std::collections::{BTreeMap, BTreeSet, HashMap};
19
20pub type Perm = Vec<usize>;
22
23fn identity(n: usize) -> Perm {
24 (0..n).collect()
25}
26fn is_identity(p: &[usize]) -> bool {
27 p.iter().enumerate().all(|(i, &v)| i == v)
28}
29fn compose(g: &[usize], h: &[usize]) -> Perm {
31 g.iter().map(|&x| h[x]).collect()
32}
33fn invert(g: &[usize]) -> Perm {
34 let mut inv = vec![0usize; g.len()];
35 for (x, &gx) in g.iter().enumerate() {
36 inv[gx] = x;
37 }
38 inv
39}
40
41fn orbit_transversal(base: &[usize], strong: &[Perm], level: usize) -> HashMap<usize, Perm> {
44 let degree = strong.first().map(|p| p.len()).unwrap_or(base.len());
45 let stab: Vec<&Perm> = strong
46 .iter()
47 .filter(|g| (0..level).all(|j| g[base[j]] == base[j]))
48 .collect();
49 let mut trans: HashMap<usize, Perm> = HashMap::new();
50 trans.insert(base[level], identity(degree));
51 let mut queue = vec![base[level]];
52 while let Some(p) = queue.pop() {
53 let up = trans[&p].clone();
54 for s in &stab {
55 let q = s[p];
56 if !trans.contains_key(&q) {
57 trans.insert(q, compose(&up, s)); queue.push(q);
59 }
60 }
61 }
62 trans
63}
64
65fn sift(base: &[usize], strong: &[Perm], mut g: Perm) -> (Perm, usize) {
69 for (i, &beta) in base.iter().enumerate() {
70 let trans = orbit_transversal(base, strong, i);
71 let img = g[beta];
72 match trans.get(&img) {
73 None => return (g, i),
74 Some(t) => g = compose(&g, &invert(t)), }
76 }
77 (g, base.len())
78}
79
80fn extend_with(base: &mut Vec<usize>, strong: &mut Vec<Perm>, g: Perm) -> bool {
83 let (res, lvl) = sift(base, strong, g);
84 if is_identity(&res) {
85 return false;
86 }
87 if lvl == base.len() {
88 let moved = (0..res.len())
89 .find(|&x| res[x] != x)
90 .expect("a non-identity moves a point");
91 base.push(moved);
92 }
93 strong.push(res);
94 true
95}
96
97pub fn orbits(degree: usize, generators: &[Perm]) -> Vec<Vec<usize>> {
100 let mut seen = vec![false; degree];
101 let mut out = Vec::new();
102 for start in 0..degree {
103 if seen[start] {
104 continue;
105 }
106 seen[start] = true;
107 let mut orbit = vec![start];
108 let mut i = 0;
109 while i < orbit.len() {
110 let p = orbit[i];
111 i += 1;
112 for g in generators {
113 let q = g[p];
114 if !seen[q] {
115 seen[q] = true;
116 orbit.push(q);
117 }
118 }
119 }
120 orbit.sort_unstable();
121 out.push(orbit);
122 }
123 out
124}
125
126fn uf_find(parent: &mut [usize], mut x: usize) -> usize {
127 while parent[x] != x {
128 parent[x] = parent[parent[x]];
129 x = parent[x];
130 }
131 x
132}
133
134fn block_containing(degree: usize, gens: &[Perm], alpha: usize, beta: usize) -> Vec<usize> {
139 let mut parent: Vec<usize> = (0..degree).collect();
140 let mut queue: Vec<(usize, usize)> = Vec::new();
141 let (ra, rb) = (uf_find(&mut parent, alpha), uf_find(&mut parent, beta));
142 if ra != rb {
143 parent[ra] = rb;
144 queue.push((alpha, beta));
145 }
146 while let Some((x, y)) = queue.pop() {
147 for g in gens {
148 let (gx, gy) = (g[x], g[y]);
149 let (rx, ry) = (uf_find(&mut parent, gx), uf_find(&mut parent, gy));
150 if rx != ry {
151 parent[rx] = ry;
152 queue.push((gx, gy));
153 }
154 }
155 }
156 (0..degree).map(|x| uf_find(&mut parent, x)).collect()
157}
158
159pub fn minimal_block_system(degree: usize, gens: &[Perm]) -> Option<Vec<Vec<usize>>> {
165 if degree < 2 || orbits(degree, gens).len() != 1 {
166 return None; }
168 let mut best: Option<Vec<usize>> = None;
169 let mut best_size = degree;
170 for beta in 1..degree {
171 let ids = block_containing(degree, gens, 0, beta);
172 let size = ids.iter().filter(|&&b| b == ids[0]).count();
173 if 1 < size && size < degree && size < best_size {
174 best_size = size;
175 best = Some(ids);
176 }
177 }
178 best.map(|ids| {
179 let mut by_block: BTreeMap<usize, Vec<usize>> = BTreeMap::new();
180 for (x, &b) in ids.iter().enumerate() {
181 by_block.entry(b).or_default().push(x);
182 }
183 by_block.into_values().collect()
184 })
185}
186
187pub fn is_primitive(degree: usize, gens: &[Perm]) -> bool {
191 degree >= 2 && orbits(degree, gens).len() == 1 && minimal_block_system(degree, gens).is_none()
192}
193
194pub fn orbitals(degree: usize, gens: &[Perm]) -> Vec<Vec<(usize, usize)>> {
199 let mut seen = vec![false; degree * degree];
200 let idx = |i: usize, j: usize| i * degree + j;
201 let mut out = Vec::new();
202 for i in 0..degree {
203 for j in 0..degree {
204 if seen[idx(i, j)] {
205 continue;
206 }
207 seen[idx(i, j)] = true;
208 let mut orbit = vec![(i, j)];
209 let mut k = 0;
210 while k < orbit.len() {
211 let (a, b) = orbit[k];
212 k += 1;
213 for g in gens {
214 let (ga, gb) = (g[a], g[b]);
215 if !seen[idx(ga, gb)] {
216 seen[idx(ga, gb)] = true;
217 orbit.push((ga, gb));
218 }
219 }
220 }
221 out.push(orbit);
222 }
223 }
224 out
225}
226
227pub fn rank(degree: usize, gens: &[Perm]) -> usize {
230 orbitals(degree, gens).len()
231}
232
233fn orbital_graph_connected(degree: usize, orbital: &[(usize, usize)]) -> bool {
235 let mut parent: Vec<usize> = (0..degree).collect();
236 for &(i, j) in orbital {
237 let (ri, rj) = (uf_find(&mut parent, i), uf_find(&mut parent, j));
238 parent[ri] = rj;
239 }
240 let r0 = uf_find(&mut parent, 0);
241 (0..degree).all(|v| uf_find(&mut parent, v) == r0)
242}
243
244pub fn is_primitive_via_orbitals(degree: usize, gens: &[Perm]) -> bool {
248 if degree < 2 || orbits(degree, gens).len() != 1 {
249 return false;
250 }
251 orbitals(degree, gens)
252 .iter()
253 .filter(|orb| orb.iter().any(|&(i, j)| i != j)) .all(|orb| orbital_graph_connected(degree, orb))
255}
256
257fn distinct_tuples(degree: usize, k: usize) -> Vec<Vec<usize>> {
259 let mut out = Vec::new();
260 let mut cur = Vec::with_capacity(k);
261 let mut used = vec![false; degree];
262 fn rec(
263 degree: usize,
264 k: usize,
265 cur: &mut Vec<usize>,
266 used: &mut [bool],
267 out: &mut Vec<Vec<usize>>,
268 ) {
269 if cur.len() == k {
270 out.push(cur.clone());
271 return;
272 }
273 for x in 0..degree {
274 if !used[x] {
275 used[x] = true;
276 cur.push(x);
277 rec(degree, k, cur, used, out);
278 cur.pop();
279 used[x] = false;
280 }
281 }
282 }
283 rec(degree, k, &mut cur, &mut used, &mut out);
284 out
285}
286
287pub fn orbits_on_tuples(degree: usize, gens: &[Perm], k: usize) -> Vec<Vec<Vec<usize>>> {
291 if k == 0 || k > degree {
292 return Vec::new();
293 }
294 let tuples = distinct_tuples(degree, k);
295 let index: HashMap<Vec<usize>, usize> = tuples
296 .iter()
297 .enumerate()
298 .map(|(i, t)| (t.clone(), i))
299 .collect();
300 let mut seen = vec![false; tuples.len()];
301 let mut out = Vec::new();
302 for start in 0..tuples.len() {
303 if seen[start] {
304 continue;
305 }
306 seen[start] = true;
307 let mut orbit = vec![tuples[start].clone()];
308 let mut i = 0;
309 while i < orbit.len() {
310 let cur = orbit[i].clone();
311 i += 1;
312 for g in gens {
313 let img: Vec<usize> = cur.iter().map(|&x| g[x]).collect();
314 let idx = index[&img];
315 if !seen[idx] {
316 seen[idx] = true;
317 orbit.push(img);
318 }
319 }
320 }
321 out.push(orbit);
322 }
323 out
324}
325
326pub fn transitivity_degree(degree: usize, gens: &[Perm], max_t: usize) -> usize {
331 let mut t = 0;
332 for k in 1..=max_t.min(degree) {
333 if orbits_on_tuples(degree, gens, k).len() == 1 {
334 t = k;
335 } else {
336 break; }
338 }
339 t
340}
341
342fn commutator(g: &[usize], h: &[usize]) -> Perm {
344 compose(&compose(&invert(g), &invert(h)), &compose(g, h))
345}
346
347fn normal_closure(degree: usize, sub: &[Perm], gens: &[Perm]) -> Vec<Perm> {
351 let mut closure: Vec<Perm> = sub.iter().filter(|p| !is_identity(p)).cloned().collect();
352 if closure.is_empty() {
353 return closure;
354 }
355 let mut bsgs = schreier_sims(degree, &closure);
356 let mut i = 0;
357 while i < closure.len() {
358 let s = closure[i].clone();
359 i += 1;
360 for g in gens {
361 for conj in [
362 compose(&compose(&invert(g), &s), g),
363 compose(&compose(g, &s), &invert(g)),
364 ] {
365 if !is_identity(&conj) && !bsgs.contains(&conj) {
366 closure.push(conj);
367 bsgs = schreier_sims(degree, &closure);
368 }
369 }
370 }
371 }
372 closure
373}
374
375fn commutator_subgroup(degree: usize, gens_a: &[Perm], gens_b: &[Perm]) -> Vec<Perm> {
379 let mut comms = Vec::new();
380 for a in gens_a {
381 for b in gens_b {
382 let c = commutator(a, b);
383 if !is_identity(&c) {
384 comms.push(c);
385 }
386 }
387 }
388 normal_closure(degree, &comms, gens_a)
389}
390
391pub fn derived_subgroup(degree: usize, gens: &[Perm]) -> Vec<Perm> {
395 commutator_subgroup(degree, gens, gens)
396}
397
398pub fn is_nilpotent(degree: usize, gens: &[Perm]) -> bool {
401 nilpotency_class(degree, gens).is_some()
402}
403
404pub fn is_abelian(_degree: usize, gens: &[Perm]) -> bool {
406 gens.iter()
407 .all(|g| gens.iter().all(|h| compose(g, h) == compose(h, g)))
408}
409
410pub fn derived_length(degree: usize, gens: &[Perm]) -> Option<usize> {
414 let mut cur: Vec<Perm> = gens.to_vec();
415 let mut len = 0;
416 loop {
417 let order = schreier_sims(degree, &cur).order();
418 if order == 1 {
419 return Some(len);
420 }
421 let d = derived_subgroup(degree, &cur);
422 if schreier_sims(degree, &d).order() == order {
423 return None; }
425 cur = d;
426 len += 1;
427 }
428}
429
430pub fn is_solvable(degree: usize, gens: &[Perm]) -> bool {
432 derived_length(degree, gens).is_some()
433}
434
435pub fn nilpotency_class(degree: usize, gens: &[Perm]) -> Option<usize> {
439 let mut gamma: Vec<Perm> = gens.to_vec();
440 let mut class = 0;
441 loop {
442 let order = schreier_sims(degree, &gamma).order();
443 if order == 1 {
444 return Some(class);
445 }
446 let next = commutator_subgroup(degree, gens, &gamma);
447 if schreier_sims(degree, &next).order() == order {
448 return None; }
450 gamma = next;
451 class += 1;
452 }
453}
454
455pub fn conjugacy_classes(degree: usize, gens: &[Perm], cap: usize) -> Option<Vec<Vec<Perm>>> {
460 let elements = schreier_sims(degree, gens).elements(cap)?;
461 let mut remaining: BTreeSet<Perm> = elements.iter().cloned().collect();
462 let mut classes = Vec::new();
463 while let Some(g) = remaining.iter().next().cloned() {
464 let mut class: BTreeSet<Perm> = BTreeSet::new();
465 for x in &elements {
466 class.insert(compose(&compose(&invert(x), &g), x)); }
468 for c in &class {
469 remaining.remove(c);
470 }
471 classes.push(class.into_iter().collect::<Vec<_>>());
472 }
473 Some(classes)
474}
475
476pub fn center_order(degree: usize, gens: &[Perm], cap: usize) -> Option<u128> {
479 conjugacy_classes(degree, gens, cap)
480 .map(|classes| classes.iter().filter(|c| c.len() == 1).count() as u128)
481}
482
483fn element_order(g: &[usize]) -> usize {
485 if is_identity(g) {
486 return 1;
487 }
488 let mut p = compose(g, g);
489 let mut k = 2;
490 while !is_identity(&p) {
491 p = compose(&p, g);
492 k += 1;
493 }
494 k
495}
496
497fn gcd(mut a: u128, mut b: u128) -> u128 {
498 while b != 0 {
499 (a, b) = (b, a % b);
500 }
501 a
502}
503
504fn lcm(a: u128, b: u128) -> u128 {
505 if a == 0 || b == 0 {
506 0
507 } else {
508 a / gcd(a, b) * b
509 }
510}
511
512pub fn element_orders(degree: usize, gens: &[Perm], cap: usize) -> Option<BTreeSet<usize>> {
514 let elements = schreier_sims(degree, gens).elements(cap)?;
515 Some(elements.iter().map(|g| element_order(g)).collect())
516}
517
518pub fn exponent(degree: usize, gens: &[Perm], cap: usize) -> Option<u128> {
521 let elements = schreier_sims(degree, gens).elements(cap)?;
522 Some(
523 elements
524 .iter()
525 .fold(1u128, |e, g| lcm(e, element_order(g) as u128)),
526 )
527}
528
529pub fn upper_central_series(degree: usize, gens: &[Perm], cap: usize) -> Option<Vec<u128>> {
533 let elements = schreier_sims(degree, gens).elements(cap)?;
534 let mut z: BTreeSet<Perm> = BTreeSet::from([identity(degree)]);
535 let mut orders = vec![1u128];
536 loop {
537 let next: BTreeSet<Perm> = elements
538 .iter()
539 .filter(|g| elements.iter().all(|x| z.contains(&commutator(g, x))))
540 .cloned()
541 .collect();
542 if next.len() == z.len() {
543 break; }
545 orders.push(next.len() as u128);
546 z = next;
547 }
548 Some(orders)
549}
550
551pub fn upper_central_length(degree: usize, gens: &[Perm], cap: usize) -> Option<usize> {
555 let orders = upper_central_series(degree, gens, cap)?;
556 (orders.last() == Some(&schreier_sims(degree, gens).order())).then_some(orders.len() - 1)
557}
558
559fn cycle_type(g: &[usize]) -> Vec<usize> {
562 let mut seen = vec![false; g.len()];
563 let mut lengths = Vec::new();
564 for start in 0..g.len() {
565 if seen[start] {
566 continue;
567 }
568 let mut len = 0;
569 let mut x = start;
570 while !seen[x] {
571 seen[x] = true;
572 x = g[x];
573 len += 1;
574 }
575 lengths.push(len);
576 }
577 lengths.sort_unstable();
578 lengths
579}
580
581pub fn cycle_index(degree: usize, gens: &[Perm], cap: usize) -> Option<BTreeMap<Vec<usize>, u128>> {
585 let elements = schreier_sims(degree, gens).elements(cap)?;
586 let mut dist: BTreeMap<Vec<usize>, u128> = BTreeMap::new();
587 for g in &elements {
588 *dist.entry(cycle_type(g)).or_insert(0) += 1;
589 }
590 Some(dist)
591}
592
593pub fn polya_count(degree: usize, gens: &[Perm], m: usize, cap: usize) -> Option<u128> {
598 let elements = schreier_sims(degree, gens).elements(cap)?;
599 let order = elements.len() as u128;
600 let total: u128 = elements
601 .iter()
602 .map(|g| (m as u128).pow(cycle_type(g).len() as u32))
603 .sum();
604 Some(total / order)
605}
606
607pub fn pattern_inventory(degree: usize, gens: &[Perm], cap: usize) -> Option<Vec<u128>> {
612 let elements = schreier_sims(degree, gens).elements(cap)?;
613 let order = elements.len() as u128;
614 let mut total = vec![0u128; degree + 1];
615 for g in &elements {
616 let mut poly = vec![0u128; degree + 1];
618 poly[0] = 1;
619 for &len in &cycle_type(g) {
620 let prev = poly.clone();
621 for i in 0..=degree {
622 poly[i] = prev[i] + if i >= len { prev[i - len] } else { 0 };
623 }
624 }
625 for i in 0..=degree {
626 total[i] += poly[i];
627 }
628 }
629 Some(total.iter().map(|&c| c / order).collect())
630}
631
632pub fn abelianization(degree: usize, gens: &[Perm], cap: usize) -> Option<(u128, u128)> {
636 let elements = schreier_sims(degree, gens).elements(cap)?;
637 let derived: BTreeSet<Perm> = schreier_sims(degree, &derived_subgroup(degree, gens))
638 .elements(cap)?
639 .into_iter()
640 .collect();
641 let coset_rep = |g: &Perm| -> Perm { derived.iter().map(|x| compose(g, x)).min().unwrap() };
643 let cosets: BTreeSet<Perm> = elements.iter().map(|g| coset_rep(g)).collect();
644 let order = cosets.len() as u128;
645 let coset_order = |r: &Perm| -> usize {
647 let mut p = r.clone();
648 let mut k = 1;
649 while !derived.contains(&p) {
650 p = compose(&p, r);
651 k += 1;
652 }
653 k
654 };
655 let exponent = cosets
656 .iter()
657 .fold(1u128, |e, r| lcm(e, coset_order(r) as u128));
658 Some((order, exponent))
659}
660
661fn subgroup_closure(degree: usize, seed: &BTreeSet<Perm>) -> BTreeSet<Perm> {
663 let mut set = seed.clone();
664 set.insert(identity(degree));
665 loop {
666 let snapshot: Vec<Perm> = set.iter().cloned().collect();
667 let before = set.len();
668 for a in &snapshot {
669 for b in &snapshot {
670 set.insert(compose(a, b));
671 }
672 }
673 if set.len() == before {
674 break;
675 }
676 }
677 set
678}
679
680fn all_subgroups(degree: usize, gens: &[Perm], cap: usize) -> Option<BTreeSet<BTreeSet<Perm>>> {
685 let elements = schreier_sims(degree, gens).elements(cap)?;
686 let trivial: BTreeSet<Perm> = BTreeSet::from([identity(degree)]);
687 let mut subgroups: BTreeSet<BTreeSet<Perm>> = BTreeSet::from([trivial.clone()]);
688 let mut queue = vec![trivial];
689 while let Some(h) = queue.pop() {
690 for g in &elements {
691 if h.contains(g) {
692 continue;
693 }
694 let mut seed = h.clone();
695 seed.insert(g.clone());
696 let sub = subgroup_closure(degree, &seed);
697 if subgroups.insert(sub.clone()) {
698 queue.push(sub);
699 }
700 }
701 }
702 Some(subgroups)
703}
704
705pub fn subgroup_count(degree: usize, gens: &[Perm], cap: usize) -> Option<usize> {
706 all_subgroups(degree, gens, cap).map(|s| s.len())
707}
708
709fn is_normal_set(h: &BTreeSet<Perm>, gens: &[Perm]) -> bool {
711 gens.iter().all(|g| {
712 h.iter()
713 .all(|x| h.contains(&compose(&compose(&invert(g), x), g)))
714 })
715}
716
717fn maximal_normal_subgroup(degree: usize, gens: &[Perm], cap: usize) -> Option<Vec<Perm>> {
720 let order = schreier_sims(degree, gens).order();
721 let subgroups = all_subgroups(degree, gens, cap)?;
722 subgroups
723 .iter()
724 .filter(|h| (h.len() as u128) < order && is_normal_set(h, gens))
725 .max_by_key(|h| h.len())
726 .map(|h| h.iter().cloned().collect())
727}
728
729pub fn composition_factor_orders(degree: usize, gens: &[Perm], cap: usize) -> Option<Vec<u128>> {
734 let order = schreier_sims(degree, gens).order();
735 if order == 1 {
736 return Some(Vec::new());
737 }
738 if is_simple(degree, gens, cap)? {
739 return Some(vec![order]);
740 }
741 let n = maximal_normal_subgroup(degree, gens, cap)?;
742 let n_order = schreier_sims(degree, &n).order();
743 let mut factors = composition_factor_orders(degree, &n, cap)?;
744 factors.push(order / n_order); factors.sort_unstable();
746 Some(factors)
747}
748
749fn distinct_primes(mut n: u128) -> Vec<u128> {
751 let mut primes = Vec::new();
752 let mut p = 2u128;
753 while p * p <= n {
754 if n % p == 0 {
755 primes.push(p);
756 while n % p == 0 {
757 n /= p;
758 }
759 }
760 p += 1;
761 }
762 if n > 1 {
763 primes.push(n);
764 }
765 primes
766}
767
768fn prime_power_part(n: u128, p: u128) -> u128 {
770 let mut pa = 1;
771 let mut m = n;
772 while m % p == 0 {
773 pa *= p;
774 m /= p;
775 }
776 pa
777}
778
779pub fn sylow_counts(degree: usize, gens: &[Perm], cap: usize) -> Option<Vec<(u128, usize)>> {
784 let order = schreier_sims(degree, gens).order();
785 let subgroups = all_subgroups(degree, gens, cap)?;
786 Some(
787 distinct_primes(order)
788 .into_iter()
789 .map(|p| {
790 let pa = prime_power_part(order, p);
791 let n_p = subgroups.iter().filter(|h| h.len() as u128 == pa).count();
792 (p, n_p)
793 })
794 .collect(),
795 )
796}
797
798fn class_index_map(classes: &[Vec<Perm>]) -> BTreeMap<Perm, usize> {
800 let mut idx = BTreeMap::new();
801 for (i, class) in classes.iter().enumerate() {
802 for g in class {
803 idx.insert(g.clone(), i);
804 }
805 }
806 idx
807}
808
809pub fn class_multiplication_coefficients(
815 degree: usize,
816 gens: &[Perm],
817 cap: usize,
818) -> Option<Vec<Vec<Vec<u128>>>> {
819 let classes = conjugacy_classes(degree, gens, cap)?;
820 let idx = class_index_map(&classes);
821 let k = classes.len();
822 let mut a = vec![vec![vec![0u128; k]; k]; k];
823 for (kk, class_k) in classes.iter().enumerate() {
824 let z = &class_k[0];
825 for (i, class_i) in classes.iter().enumerate() {
826 for x in class_i {
827 let j = idx[&compose(&invert(x), z)]; a[i][j][kk] += 1;
829 }
830 }
831 }
832 Some(a)
833}
834
835pub fn real_class_count(degree: usize, gens: &[Perm], cap: usize) -> Option<usize> {
838 let classes = conjugacy_classes(degree, gens, cap)?;
839 let idx = class_index_map(&classes);
840 Some(
841 classes
842 .iter()
843 .enumerate()
844 .filter(|(i, c)| idx[&invert(&c[0])] == *i)
845 .count(),
846 )
847}
848
849fn perm_pow(g: &[usize], mut t: usize) -> Perm {
851 let mut result = identity(g.len());
852 let mut base = g.to_vec();
853 while t > 0 {
854 if t & 1 == 1 {
855 result = compose(&result, &base);
856 }
857 base = compose(&base, &base);
858 t >>= 1;
859 }
860 result
861}
862
863pub fn galois_class_orbits(degree: usize, gens: &[Perm], cap: usize) -> Option<Vec<Vec<usize>>> {
868 let classes = conjugacy_classes(degree, gens, cap)?;
869 let idx = class_index_map(&classes);
870 let e = exponent(degree, gens, cap)? as usize;
871 let k = classes.len();
872 let mut parent: Vec<usize> = (0..k).collect();
873 fn find(parent: &mut [usize], mut x: usize) -> usize {
874 while parent[x] != x {
875 parent[x] = parent[parent[x]];
876 x = parent[x];
877 }
878 x
879 }
880 for t in 1..e.max(2) {
881 if gcd(t as u128, e as u128) != 1 {
882 continue;
883 }
884 for r in 0..k {
885 let img = idx[&perm_pow(&classes[r][0], t)];
886 let (a, b) = (find(&mut parent, r), find(&mut parent, img));
887 parent[a] = b;
888 }
889 }
890 let mut groups: BTreeMap<usize, Vec<usize>> = BTreeMap::new();
891 for r in 0..k {
892 let root = find(&mut parent, r);
893 groups.entry(root).or_default().push(r);
894 }
895 Some(groups.into_values().collect())
896}
897
898pub fn rational_class_count(degree: usize, gens: &[Perm], cap: usize) -> Option<usize> {
905 Some(
906 galois_class_orbits(degree, gens, cap)?
907 .iter()
908 .filter(|o| o.len() == 1)
909 .count(),
910 )
911}
912
913pub fn automorphism_group_order(degree: usize, gens: &[Perm], cap: usize) -> Option<u128> {
920 let seed: BTreeSet<Perm> = gens.iter().cloned().collect();
921 let elements: Vec<Perm> = subgroup_closure(degree, &seed).into_iter().collect();
922 let n = elements.len();
923 if n > cap {
924 return None;
925 }
926 let idx: BTreeMap<Perm, usize> = elements
927 .iter()
928 .enumerate()
929 .map(|(i, e)| (e.clone(), i))
930 .collect();
931 let id_idx = idx[&identity(degree)];
932 let mul: Vec<Vec<usize>> = (0..n)
933 .map(|i| {
934 (0..n)
935 .map(|j| idx[&compose(&elements[i], &elements[j])])
936 .collect()
937 })
938 .collect();
939 let ord: Vec<usize> = elements.iter().map(|e| element_order(e)).collect();
940 let mut gen_idx: Vec<usize> = Vec::new();
942 for g in gens {
943 let gi = idx[g];
944 if gi != id_idx && !gen_idx.contains(&gi) {
945 gen_idx.push(gi);
946 }
947 }
948 if gen_idx.is_empty() {
949 return Some(1); }
951 let candidates: Vec<Vec<usize>> = gen_idx
953 .iter()
954 .map(|&gi| (0..n).filter(|&e| ord[e] == ord[gi]).collect::<Vec<_>>())
955 .collect();
956 if candidates.iter().map(|c| c.len() as u128).product::<u128>() > 2_000_000 {
957 return None;
958 }
959 let m = gen_idx.len();
960 let mut count = 0u128;
961 let mut choice = vec![0usize; m];
962 loop {
963 let img: Vec<usize> = (0..m).map(|t| candidates[t][choice[t]]).collect();
964 let mut phi = vec![usize::MAX; n];
966 phi[id_idx] = id_idx;
967 let mut queue = vec![id_idx];
968 let mut head = 0;
969 let mut ok = true;
970 'bfs: while head < queue.len() {
971 let u = queue[head];
972 head += 1;
973 for t in 0..m {
974 let ug = mul[u][gen_idx[t]];
975 let target = mul[phi[u]][img[t]];
976 if phi[ug] == usize::MAX {
977 phi[ug] = target;
978 queue.push(ug);
979 } else if phi[ug] != target {
980 ok = false;
981 break 'bfs;
982 }
983 }
984 }
985 if ok && phi.iter().all(|&x| x != usize::MAX) {
987 let mut seen = vec![false; n];
988 if phi.iter().all(|&x| !std::mem::replace(&mut seen[x], true)) {
989 count += 1;
990 }
991 }
992 let mut t = 0;
993 while t < m {
994 choice[t] += 1;
995 if choice[t] < candidates[t].len() {
996 break;
997 }
998 choice[t] = 0;
999 t += 1;
1000 }
1001 if t == m {
1002 break;
1003 }
1004 }
1005 Some(count)
1006}
1007
1008pub fn outer_automorphism_order(degree: usize, gens: &[Perm], cap: usize) -> Option<u128> {
1012 let aut = automorphism_group_order(degree, gens, cap)?;
1013 let order = schreier_sims(degree, gens).order();
1014 let center = center_order(degree, gens, cap)?;
1015 Some(aut / (order / center)) }
1017
1018pub fn table_of_marks(
1029 degree: usize,
1030 gens: &[Perm],
1031 cap: usize,
1032) -> Option<(Vec<u128>, Vec<Vec<u128>>)> {
1033 let elements: Vec<Perm> = subgroup_closure(degree, &gens.iter().cloned().collect())
1034 .into_iter()
1035 .collect();
1036 let subs = all_subgroups(degree, gens, cap)?;
1037 let conjugate = |h: &BTreeSet<Perm>, g: &Perm| -> BTreeSet<Perm> {
1039 let gi = invert(g);
1040 h.iter().map(|x| compose(&compose(&gi, x), g)).collect()
1041 };
1042 let mut reps: Vec<BTreeSet<Perm>> = Vec::new();
1044 let mut seen: BTreeSet<BTreeSet<Perm>> = BTreeSet::new();
1045 for h in &subs {
1046 if seen.contains(h) {
1047 continue;
1048 }
1049 for g in &elements {
1050 seen.insert(conjugate(h, g));
1051 }
1052 reps.push(h.clone());
1053 }
1054 reps.sort_by(|a, b| a.len().cmp(&b.len()).then_with(|| a.cmp(b)));
1056 let k = reps.len();
1057 let orders: Vec<u128> = reps.iter().map(|h| h.len() as u128).collect();
1058 let mut marks = vec![vec![0u128; k]; k];
1059 for i in 0..k {
1060 for j in 0..k {
1061 if reps[i].len() > reps[j].len() {
1062 continue; }
1064 let count = elements
1065 .iter()
1066 .filter(|g| conjugate(&reps[i], g).iter().all(|x| reps[j].contains(x)))
1067 .count() as u128;
1068 marks[i][j] = count / reps[j].len() as u128;
1069 }
1070 }
1071 Some((orders, marks))
1072}
1073
1074pub fn burnside_ring_product(
1085 degree: usize,
1086 gens: &[Perm],
1087 cap: usize,
1088) -> Option<Vec<Vec<Vec<i128>>>> {
1089 let (_orders, marks) = table_of_marks(degree, gens, cap)?;
1090 let k = marks.len();
1091 let solve = |p: &[u128]| -> Option<Vec<i128>> {
1094 let mut c = vec![0i128; k];
1095 for i in (0..k).rev() {
1096 let mut acc = p[i] as i128;
1097 for l in (i + 1)..k {
1098 acc -= marks[i][l] as i128 * c[l];
1099 }
1100 let diag = marks[i][i] as i128;
1101 if diag == 0 || acc % diag != 0 {
1102 return None; }
1104 c[i] = acc / diag;
1105 }
1106 Some(c)
1107 };
1108 let mut n = vec![vec![vec![0i128; k]; k]; k];
1109 for a in 0..k {
1110 for b in 0..k {
1111 let p: Vec<u128> = (0..k).map(|i| marks[i][a] * marks[i][b]).collect();
1112 let c = solve(&p)?;
1113 for l in 0..k {
1114 n[a][b][l] = c[l];
1115 }
1116 }
1117 }
1118 Some(n)
1119}
1120
1121fn lattice_mobius_to_top(
1125 degree: usize,
1126 gens: &[Perm],
1127 cap: usize,
1128) -> Option<(Vec<u128>, Vec<i128>)> {
1129 let subs: Vec<BTreeSet<Perm>> = all_subgroups(degree, gens, cap)?.into_iter().collect();
1130 let n = subs.len();
1131 let top_size = subs.iter().map(|h| h.len()).max().unwrap_or(0); let mut order: Vec<usize> = (0..n).collect();
1133 order.sort_by_key(|&i| std::cmp::Reverse(subs[i].len())); let mut mu = vec![0i128; n];
1135 for &i in &order {
1136 if subs[i].len() == top_size {
1137 mu[i] = 1; } else {
1139 let s: i128 = (0..n)
1140 .filter(|&j| subs[i].len() < subs[j].len() && subs[i].is_subset(&subs[j]))
1141 .map(|j| mu[j])
1142 .sum();
1143 mu[i] = -s;
1144 }
1145 }
1146 Some((subs.iter().map(|h| h.len() as u128).collect(), mu))
1147}
1148
1149pub fn mobius_number(degree: usize, gens: &[Perm], cap: usize) -> Option<i128> {
1154 let (orders, mu) = lattice_mobius_to_top(degree, gens, cap)?;
1155 (0..orders.len()).find(|&i| orders[i] == 1).map(|i| mu[i])
1156}
1157
1158pub fn generating_tuple_count(degree: usize, gens: &[Perm], cap: usize, k: u32) -> Option<i128> {
1163 let (orders, mu) = lattice_mobius_to_top(degree, gens, cap)?;
1164 Some(
1165 (0..orders.len())
1166 .map(|i| mu[i] * (orders[i] as i128).pow(k))
1167 .sum(),
1168 )
1169}
1170
1171fn subgroup_class_reps(degree: usize, gens: &[Perm], cap: usize) -> Option<Vec<BTreeSet<Perm>>> {
1174 let elements: Vec<Perm> = subgroup_closure(degree, &gens.iter().cloned().collect())
1175 .into_iter()
1176 .collect();
1177 let subs = all_subgroups(degree, gens, cap)?;
1178 let conjugate = |h: &BTreeSet<Perm>, g: &Perm| -> BTreeSet<Perm> {
1179 let gi = invert(g);
1180 h.iter().map(|x| compose(&compose(&gi, x), g)).collect()
1181 };
1182 let mut reps: Vec<BTreeSet<Perm>> = Vec::new();
1183 let mut seen: BTreeSet<BTreeSet<Perm>> = BTreeSet::new();
1184 for h in &subs {
1185 if seen.contains(h) {
1186 continue;
1187 }
1188 for g in &elements {
1189 seen.insert(conjugate(h, g));
1190 }
1191 reps.push(h.clone());
1192 }
1193 reps.sort_by(|a, b| a.len().cmp(&b.len()).then_with(|| a.cmp(b)));
1194 Some(reps)
1195}
1196
1197pub fn permutation_character_decomposition(
1210 degree: usize,
1211 gens: &[Perm],
1212 cap: usize,
1213) -> Option<(Vec<u128>, Vec<u128>, Vec<Vec<u128>>)> {
1214 let ct = character_table(degree, gens, cap)?;
1215 let p = ct.prime;
1216 let k = ct.degrees.len();
1217 let classes = conjugacy_classes(degree, gens, cap)?;
1218 let idx = class_index_map(&classes);
1219 let reps = subgroup_class_reps(degree, gens, cap)?;
1220 let order: u128 = ct.degrees.iter().map(|d| d * d).sum();
1221
1222 let mut m = vec![vec![0u128; k]; reps.len()];
1223 for (i, h) in reps.iter().enumerate() {
1224 let mut inter = vec![0u128; k];
1226 for x in h {
1227 inter[idx[x]] += 1;
1228 }
1229 let inv_h = mod_inv((h.len() as u128 % p as u128) as u64, p);
1230 for s in 0..k {
1231 let mut acc = 0u64;
1232 for r in 0..k {
1233 let term = (inter[r] % p as u128) as u64;
1234 acc = ((acc as u128 + term as u128 * ct.values[s][r] as u128) % p as u128) as u64;
1235 }
1236 m[i][s] = (acc as u128 * inv_h as u128 % p as u128) as u128;
1237 }
1238 }
1239
1240 let trivial_irr = ct
1242 .values
1243 .iter()
1244 .position(|row| row.iter().all(|&x| x == 1))?;
1245 for (i, h) in reps.iter().enumerate() {
1246 if m[i][trivial_irr] != 1 {
1247 return None; }
1249 let dim: u128 = (0..k).map(|s| m[i][s] * ct.degrees[s]).sum();
1250 if dim != order / h.len() as u128 {
1251 return None; }
1253 for s in 0..k {
1254 if m[i][s] > ct.degrees[s] {
1255 return None; }
1257 }
1258 }
1259 if m[0] != ct.degrees {
1260 return None; }
1262 let mut e_triv = vec![0u128; k];
1263 e_triv[trivial_irr] = 1;
1264 if *m.last()? != e_triv {
1265 return None; }
1267 let orders: Vec<u128> = reps.iter().map(|h| h.len() as u128).collect();
1268 Some((orders, ct.degrees, m))
1269}
1270
1271fn mod_pow(mut base: u64, mut exp: u64, p: u64) -> u64 {
1283 let mut r = 1u128;
1284 let mut b = (base % p) as u128;
1285 base = 0; let _ = base;
1287 while exp > 0 {
1288 if exp & 1 == 1 {
1289 r = (r * b) % p as u128;
1290 }
1291 b = (b * b) % p as u128;
1292 exp >>= 1;
1293 }
1294 r as u64
1295}
1296
1297pub(crate) fn mod_inv(a: u64, p: u64) -> u64 {
1299 mod_pow(a % p, p - 2, p)
1300}
1301
1302pub(crate) fn is_prime(n: u64) -> bool {
1304 if n < 2 {
1305 return false;
1306 }
1307 if n % 2 == 0 {
1308 return n == 2;
1309 }
1310 let mut d = 3u64;
1311 while d * d <= n {
1312 if n % d == 0 {
1313 return false;
1314 }
1315 d += 2;
1316 }
1317 true
1318}
1319
1320fn isqrt(n: u128) -> u128 {
1322 if n < 2 {
1323 return n;
1324 }
1325 let mut x = (n as f64).sqrt() as u128;
1326 while x * x > n {
1327 x -= 1;
1328 }
1329 while (x + 1) * (x + 1) <= n {
1330 x += 1;
1331 }
1332 x
1333}
1334
1335pub(crate) fn gf_mat_vec(m: &[Vec<u64>], v: &[u64], p: u64) -> Vec<u64> {
1337 m.iter()
1338 .map(|row| {
1339 let mut acc = 0u128;
1340 for (a, b) in row.iter().zip(v) {
1341 acc += (*a as u128) * (*b as u128);
1342 }
1343 (acc % p as u128) as u64
1344 })
1345 .collect()
1346}
1347
1348pub(crate) fn gf_nullspace(mut a: Vec<Vec<u64>>, ncols: usize, p: u64) -> Vec<Vec<u64>> {
1350 let nrows = a.len();
1351 let mut where_pivot = vec![usize::MAX; ncols]; let mut row = 0usize;
1353 for col in 0..ncols {
1354 if row >= nrows {
1355 break;
1356 }
1357 let Some(sel) = (row..nrows).find(|&r| a[r][col] % p != 0) else {
1358 continue;
1359 };
1360 a.swap(row, sel);
1361 let inv = mod_inv(a[row][col], p);
1362 for c in 0..ncols {
1363 a[row][c] = ((a[row][c] as u128 * inv as u128) % p as u128) as u64;
1364 }
1365 for r in 0..nrows {
1366 if r != row && a[r][col] != 0 {
1367 let f = a[r][col] as u128;
1368 for c in 0..ncols {
1369 let sub = (f * a[row][c] as u128) % p as u128;
1370 a[r][c] = ((a[r][c] as u128 + p as u128 - sub) % p as u128) as u64;
1371 }
1372 }
1373 }
1374 where_pivot[col] = row;
1375 row += 1;
1376 }
1377 let mut basis = Vec::new();
1378 for fc in 0..ncols {
1379 if where_pivot[fc] != usize::MAX {
1380 continue; }
1382 let mut x = vec![0u64; ncols];
1383 x[fc] = 1;
1384 for (col, &pr) in where_pivot.iter().enumerate() {
1385 if pr != usize::MAX {
1386 x[col] = (p - a[pr][fc] % p) % p; }
1388 }
1389 basis.push(x);
1390 }
1391 basis
1392}
1393
1394#[derive(Clone, Debug, PartialEq, Eq)]
1396pub struct CharacterTable {
1397 pub prime: u64,
1399 pub class_sizes: Vec<u128>,
1401 pub inverse_class: Vec<usize>,
1403 pub power_map2: Vec<usize>,
1406 pub identity_class: usize,
1408 pub class_reps: Vec<Perm>,
1411 pub degrees: Vec<u128>,
1413 pub values: Vec<Vec<u64>>,
1415}
1416
1417pub fn character_table(degree: usize, gens: &[Perm], cap: usize) -> Option<CharacterTable> {
1426 let classes = conjugacy_classes(degree, gens, cap)?;
1427 let k = classes.len();
1428 if k == 0 || k > 64 {
1430 return None;
1431 }
1432 let order = schreier_sims(degree, gens).order();
1433 if order == 0 || order > 100_000 {
1434 return None;
1435 }
1436 let idx = class_index_map(&classes);
1437 let class_sizes: Vec<u128> = classes.iter().map(|c| c.len() as u128).collect();
1438 let inverse_class: Vec<usize> = classes.iter().map(|c| idx[&invert(&c[0])]).collect();
1439 let power_map2: Vec<usize> = classes
1440 .iter()
1441 .map(|c| idx[&compose(&c[0], &c[0])])
1442 .collect();
1443 let class_reps: Vec<Perm> = classes.iter().map(|c| c[0].clone()).collect();
1444 let id_perm = identity(degree);
1445 let identity_class = classes
1446 .iter()
1447 .position(|c| c.iter().any(|g| *g == id_perm))?;
1448
1449 let e = exponent(degree, gens, cap)? as u64;
1452 let order_u = order as u64;
1453 let p = {
1454 let mut m = order_u / e + 1;
1455 let mut found = None;
1456 for _ in 0..1_000_000 {
1457 let cand = e.checked_mul(m)?.checked_add(1)?;
1458 if cand > order_u && is_prime(cand) {
1459 found = Some(cand);
1460 break;
1461 }
1462 m += 1;
1463 }
1464 found?
1465 };
1466
1467 let a = class_multiplication_coefficients(degree, gens, cap)?;
1469 let mmats: Vec<Vec<Vec<u64>>> = (0..k)
1470 .map(|i| {
1471 let mut m = vec![vec![0u64; k]; k];
1472 for j in 0..k {
1473 for kk in 0..k {
1474 m[kk][j] = (a[i][j][kk] % p as u128) as u64;
1475 }
1476 }
1477 m
1478 })
1479 .collect();
1480
1481 let mut subspaces: Vec<Vec<Vec<u64>>> = vec![(0..k)
1483 .map(|i| {
1484 let mut ei = vec![0u64; k];
1485 ei[i] = 1;
1486 ei
1487 })
1488 .collect()];
1489 for mi in &mmats {
1490 if subspaces.iter().all(|s| s.len() == 1) {
1491 break;
1492 }
1493 let mut next: Vec<Vec<Vec<u64>>> = Vec::new();
1494 for s in &subspaces {
1495 if s.len() == 1 {
1496 next.push(s.clone());
1497 continue;
1498 }
1499 let bn = s.len();
1500 let mb: Vec<Vec<u64>> = s.iter().map(|b| gf_mat_vec(mi, b, p)).collect();
1501 let mut pieces: Vec<Vec<Vec<u64>>> = Vec::new();
1502 let mut covered = 0usize;
1503 for lam in 0..p {
1504 let mut rows = vec![vec![0u64; bn]; k];
1506 for r in 0..k {
1507 for (j, bj) in s.iter().enumerate() {
1508 let shifted = (lam as u128 * bj[r] as u128) % p as u128;
1509 rows[r][j] = ((mb[j][r] as u128 + p as u128 - shifted) % p as u128) as u64;
1510 }
1511 }
1512 let ns = gf_nullspace(rows, bn, p);
1513 if ns.is_empty() {
1514 continue;
1515 }
1516 let eig: Vec<Vec<u64>> = ns
1517 .iter()
1518 .map(|c| {
1519 let mut x = vec![0u64; k];
1520 for (j, &cj) in c.iter().enumerate() {
1521 if cj != 0 {
1522 for r in 0..k {
1523 x[r] = ((x[r] as u128 + cj as u128 * s[j][r] as u128)
1524 % p as u128)
1525 as u64;
1526 }
1527 }
1528 }
1529 x
1530 })
1531 .collect();
1532 covered += eig.len();
1533 pieces.push(eig);
1534 if covered == bn {
1535 break;
1536 }
1537 }
1538 if covered == bn {
1539 next.extend(pieces);
1540 } else {
1541 next.push(s.clone()); }
1543 }
1544 subspaces = next;
1545 }
1546 if subspaces.iter().any(|s| s.len() != 1) {
1547 return None; }
1549
1550 let order_p = (order % p as u128) as u64;
1552 let max_deg = isqrt(order);
1553 let mut rows: Vec<(u128, Vec<u64>)> = Vec::with_capacity(k);
1554 for s in &subspaces {
1555 let v = &s[0];
1556 let t = v.iter().position(|&x| x != 0)?;
1557 let inv_vt = mod_inv(v[t], p);
1558 let omega: Vec<u64> = mmats
1559 .iter()
1560 .map(|mi| {
1561 let mv = gf_mat_vec(mi, v, p);
1562 ((mv[t] as u128 * inv_vt as u128) % p as u128) as u64
1563 })
1564 .collect();
1565 let mut denom = 0u64;
1567 for r in 0..k {
1568 let hr = (class_sizes[r] % p as u128) as u64;
1569 let term = (omega[r] as u128 * omega[inverse_class[r]] as u128 % p as u128) as u64;
1570 let contrib = (term as u128 * mod_inv(hr, p) as u128 % p as u128) as u64;
1571 denom = (denom + contrib) % p;
1572 }
1573 if denom == 0 {
1574 return None;
1575 }
1576 let d2 = (order_p as u128 * mod_inv(denom, p) as u128 % p as u128) as u64;
1577 let mut deg = None;
1579 let mut d = 1u128;
1580 while d <= max_deg {
1581 if order % d == 0 && ((d * d) % p as u128) as u64 == d2 {
1582 deg = Some(d);
1583 break;
1584 }
1585 d += 1;
1586 }
1587 let deg = deg?;
1588 let vals: Vec<u64> = (0..k)
1589 .map(|r| {
1590 let hr = (class_sizes[r] % p as u128) as u64;
1591 let num = (deg % p as u128) as u64 as u128 * omega[r] as u128 % p as u128;
1592 (num * mod_inv(hr, p) as u128 % p as u128) as u64
1593 })
1594 .collect();
1595 rows.push((deg, vals));
1596 }
1597 rows.sort();
1598 let degrees: Vec<u128> = rows.iter().map(|(d, _)| *d).collect();
1599 let values: Vec<Vec<u64>> = rows.into_iter().map(|(_, v)| v).collect();
1600
1601 if degrees.iter().map(|d| d * d).sum::<u128>() != order {
1603 return None;
1604 }
1605 if !values.iter().any(|row| row.iter().all(|&x| x == 1)) {
1606 return None; }
1608 for s in 0..k {
1609 for t in 0..k {
1610 let mut acc = 0u64;
1611 for r in 0..k {
1612 let hr = (class_sizes[r] % p as u128) as u64;
1613 let prod = values[s][r] as u128 * values[t][inverse_class[r]] as u128 % p as u128;
1614 acc = ((acc as u128 + hr as u128 * prod) % p as u128) as u64;
1615 }
1616 let want = if s == t { order_p } else { 0 };
1617 if acc != want {
1618 return None;
1619 }
1620 }
1621 }
1622
1623 Some(CharacterTable {
1624 prime: p,
1625 class_sizes,
1626 inverse_class,
1627 power_map2,
1628 identity_class,
1629 class_reps,
1630 degrees,
1631 values,
1632 })
1633}
1634
1635pub fn frobenius_schur_from_table(t: &CharacterTable) -> Option<Vec<i8>> {
1643 let p = t.prime;
1644 let order: u128 = t.degrees.iter().map(|d| d * d).sum();
1645 let inv_order = mod_inv((order % p as u128) as u64, p);
1646 let k = t.degrees.len();
1647 let involutions_plus_id: u128 = (0..k)
1649 .filter(|&r| t.power_map2[r] == t.identity_class)
1650 .map(|r| t.class_sizes[r])
1651 .sum();
1652 let mut nu = Vec::with_capacity(k);
1653 for s in 0..k {
1654 let mut acc = 0u64;
1655 for r in 0..k {
1656 let hr = (t.class_sizes[r] % p as u128) as u64;
1657 let chi_sq = t.values[s][t.power_map2[r]];
1658 acc = ((acc as u128 + hr as u128 * chi_sq as u128) % p as u128) as u64;
1659 }
1660 let val = ((acc as u128 * inv_order as u128) % p as u128) as u64;
1661 let ind: i8 = if val == 0 {
1662 0
1663 } else if val == 1 {
1664 1
1665 } else if val == p - 1 {
1666 -1
1667 } else {
1668 return None; };
1670 nu.push(ind);
1671 }
1672 let sum: i128 = nu
1674 .iter()
1675 .zip(&t.degrees)
1676 .map(|(&v, &d)| v as i128 * d as i128)
1677 .sum();
1678 if sum != involutions_plus_id as i128 {
1679 return None;
1680 }
1681 Some(nu)
1682}
1683
1684pub fn frobenius_schur_indicators(degree: usize, gens: &[Perm], cap: usize) -> Option<Vec<i8>> {
1688 frobenius_schur_from_table(&character_table(degree, gens, cap)?)
1689}
1690
1691pub fn permutation_character(degree: usize, gens: &[Perm], cap: usize) -> Option<Vec<u128>> {
1696 let classes = conjugacy_classes(degree, gens, cap)?;
1697 Some(
1698 classes
1699 .iter()
1700 .map(|c| c[0].iter().enumerate().filter(|(i, &x)| *i == x).count() as u128)
1701 .collect(),
1702 )
1703}
1704
1705pub fn isotypic_from_table(degree: usize, gens: &[Perm], t: &CharacterTable) -> Option<Vec<u128>> {
1713 let p = t.prime;
1714 if p as u128 <= degree as u128 {
1715 return None; }
1717 let order: u128 = t.degrees.iter().map(|d| d * d).sum();
1718 let inv_order = mod_inv((order % p as u128) as u64, p);
1719 let k = t.degrees.len();
1720 let pi: Vec<u64> = t
1722 .class_reps
1723 .iter()
1724 .map(|g| (g.iter().enumerate().filter(|(i, &x)| *i == x).count() as u64) % p)
1725 .collect();
1726 let mut mult = Vec::with_capacity(k);
1727 for s in 0..k {
1728 let mut acc = 0u64;
1729 for r in 0..k {
1730 let hr = (t.class_sizes[r] % p as u128) as u64;
1731 let term = (hr as u128 * pi[r] as u128 % p as u128) as u64;
1732 let contrib = (term as u128 * t.values[s][t.inverse_class[r]] as u128) % p as u128;
1733 acc = ((acc as u128 + contrib) % p as u128) as u64;
1734 }
1735 let m = ((acc as u128 * inv_order as u128) % p as u128) as u128;
1736 mult.push(m);
1737 }
1738 if mult
1740 .iter()
1741 .zip(&t.degrees)
1742 .map(|(m, d)| m * d)
1743 .sum::<u128>()
1744 != degree as u128
1745 {
1746 return None; }
1748 if mult.iter().map(|m| m * m).sum::<u128>() != rank(degree, gens) as u128 {
1749 return None; }
1751 let num_orbits = orbits(degree, gens).len() as u128;
1752 let trivial_row = t
1753 .values
1754 .iter()
1755 .position(|row| row.iter().all(|&x| x == 1))?;
1756 if mult[trivial_row] != num_orbits {
1757 return None; }
1759 Some(mult)
1760}
1761
1762pub fn isotypic_multiplicities(degree: usize, gens: &[Perm], cap: usize) -> Option<Vec<u128>> {
1766 isotypic_from_table(degree, gens, &character_table(degree, gens, cap)?)
1767}
1768
1769pub fn tensor_from_table(t: &CharacterTable) -> Option<Vec<Vec<Vec<u128>>>> {
1779 let p = t.prime;
1780 let order: u128 = t.degrees.iter().map(|d| d * d).sum();
1781 let inv_order = mod_inv((order % p as u128) as u64, p);
1782 let k = t.degrees.len();
1783 let mut n = vec![vec![vec![0u128; k]; k]; k];
1784 for i in 0..k {
1785 for j in 0..k {
1786 for kk in 0..k {
1787 let mut acc = 0u64;
1788 for r in 0..k {
1789 let hr = (t.class_sizes[r] % p as u128) as u64;
1790 let mut prod = hr as u128;
1791 prod = prod * t.values[i][r] as u128 % p as u128;
1792 prod = prod * t.values[j][r] as u128 % p as u128;
1793 prod = prod * t.values[kk][t.inverse_class[r]] as u128 % p as u128;
1794 acc = ((acc as u128 + prod) % p as u128) as u64;
1795 }
1796 n[i][j][kk] = (acc as u128 * inv_order as u128 % p as u128) as u128;
1797 }
1798 }
1799 }
1800 let trivial = t
1802 .values
1803 .iter()
1804 .position(|row| row.iter().all(|&x| x == 1))?;
1805 for i in 0..k {
1806 for j in 0..k {
1807 if (0..k).map(|kk| n[i][j][kk] * t.degrees[kk]).sum::<u128>()
1809 != t.degrees[i] * t.degrees[j]
1810 {
1811 return None;
1812 }
1813 for kk in 0..k {
1815 if n[i][j][kk] != n[j][i][kk] {
1816 return None;
1817 }
1818 }
1819 }
1820 for kk in 0..k {
1822 let expect = u128::from(kk == i);
1823 if n[trivial][i][kk] != expect {
1824 return None;
1825 }
1826 }
1827 }
1828 Some(n)
1829}
1830
1831pub fn tensor_decomposition(
1834 degree: usize,
1835 gens: &[Perm],
1836 cap: usize,
1837) -> Option<Vec<Vec<Vec<u128>>>> {
1838 tensor_from_table(&character_table(degree, gens, cap)?)
1839}
1840
1841pub fn irreducible_degrees(degree: usize, gens: &[Perm], cap: usize) -> Option<Vec<u128>> {
1845 character_table(degree, gens, cap).map(|t| t.degrees)
1846}
1847
1848pub fn is_simple(degree: usize, gens: &[Perm], cap: usize) -> Option<bool> {
1854 let order = schreier_sims(degree, gens).order();
1855 if order <= 1 {
1856 return Some(false); }
1858 let classes = conjugacy_classes(degree, gens, cap)?;
1859 for class in &classes {
1860 let rep = &class[0];
1861 if is_identity(rep) {
1862 continue;
1863 }
1864 let ncl = normal_closure(degree, std::slice::from_ref(rep), gens);
1865 if schreier_sims(degree, &ncl).order() < order {
1866 return Some(false); }
1868 }
1869 Some(true)
1870}
1871
1872#[derive(Clone, Debug)]
1874pub struct Bsgs {
1875 pub degree: usize,
1876 pub base: Vec<usize>,
1877 transversals: Vec<HashMap<usize, Perm>>,
1878}
1879
1880impl Bsgs {
1881 pub fn order(&self) -> u128 {
1883 self.transversals.iter().map(|t| t.len() as u128).product()
1884 }
1885
1886 pub fn elements(&self, cap: usize) -> Option<Vec<Perm>> {
1892 if self.order() > cap as u128 {
1893 return None;
1894 }
1895 let mut elems = vec![identity(self.degree)];
1896 for trans in self.transversals.iter().rev() {
1897 let reps: Vec<&Perm> = trans.values().collect();
1898 let mut next = Vec::with_capacity(elems.len() * reps.len());
1899 for e in &elems {
1900 for r in &reps {
1901 next.push(compose(e, r));
1902 }
1903 }
1904 elems = next;
1905 }
1906 Some(elems)
1907 }
1908
1909 pub fn transversal_elements(&self) -> Vec<Perm> {
1915 self.transversals
1916 .iter()
1917 .flat_map(|t| t.values().cloned())
1918 .collect()
1919 }
1920
1921 pub fn contains(&self, g: &[usize]) -> bool {
1924 if g.len() != self.degree {
1925 return false;
1926 }
1927 let mut g = g.to_vec();
1928 for (i, &beta) in self.base.iter().enumerate() {
1929 let img = g[beta];
1930 match self.transversals[i].get(&img) {
1931 None => return false,
1932 Some(t) => g = compose(&g, &invert(t)),
1933 }
1934 }
1935 is_identity(&g)
1936 }
1937}
1938
1939pub fn schreier_sims(degree: usize, generators: &[Perm]) -> Bsgs {
1946 let mut base: Vec<usize> = Vec::new();
1947 let mut strong: Vec<Perm> = Vec::new();
1948 for g in generators {
1949 if !is_identity(g) {
1950 extend_with(&mut base, &mut strong, g.clone());
1951 }
1952 }
1953 loop {
1954 let mut changed = false;
1955 'scan: for i in 0..base.len() {
1956 let trans = orbit_transversal(&base, &strong, i);
1957 let stab: Vec<Perm> = strong
1958 .iter()
1959 .filter(|g| (0..i).all(|j| g[base[j]] == base[j]))
1960 .cloned()
1961 .collect();
1962 for u in trans.values() {
1963 for s in &stab {
1964 let us = compose(u, s);
1965 let img = us[base[i]];
1966 let schreier = compose(&us, &invert(&trans[&img])); if !is_identity(&schreier) && extend_with(&mut base, &mut strong, schreier) {
1968 changed = true;
1969 break 'scan; }
1971 }
1972 }
1973 }
1974 if !changed {
1975 break;
1976 }
1977 }
1978 let transversals = (0..base.len())
1979 .map(|i| orbit_transversal(&base, &strong, i))
1980 .collect();
1981 Bsgs {
1982 degree,
1983 base,
1984 transversals,
1985 }
1986}
1987
1988#[cfg(test)]
1989#[path = "../../logicaffeine_tests/unit/logicaffeine_proof/permgroup/tests.rs"]
1990mod tests;