123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611 |
- r"""
- Construct transitive subgroups of symmetric groups, useful in Galois theory.
- Besides constructing instances of the :py:class:`~.PermutationGroup` class to
- represent the transitive subgroups of $S_n$ for small $n$, this module provides
- *names* for these groups.
- In some applications, it may be preferable to know the name of a group,
- rather than receive an instance of the :py:class:`~.PermutationGroup`
- class, and then have to do extra work to determine which group it is, by
- checking various properties.
- Names are instances of ``Enum`` classes defined in this module. With a name in
- hand, the name's ``get_perm_group`` method can then be used to retrieve a
- :py:class:`~.PermutationGroup`.
- The names used for groups in this module are taken from [1].
- References
- ==========
- .. [1] Cohen, H. *A Course in Computational Algebraic Number Theory*.
- """
- from collections import defaultdict
- from enum import Enum
- import itertools
- from sympy.combinatorics.named_groups import (
- SymmetricGroup, AlternatingGroup, CyclicGroup, DihedralGroup,
- set_symmetric_group_properties, set_alternating_group_properties,
- )
- from sympy.combinatorics.perm_groups import PermutationGroup
- from sympy.combinatorics.permutations import Permutation
- class S1TransitiveSubgroups(Enum):
- """
- Names for the transitive subgroups of S1.
- """
- S1 = "S1"
- def get_perm_group(self):
- return SymmetricGroup(1)
- class S2TransitiveSubgroups(Enum):
- """
- Names for the transitive subgroups of S2.
- """
- S2 = "S2"
- def get_perm_group(self):
- return SymmetricGroup(2)
- class S3TransitiveSubgroups(Enum):
- """
- Names for the transitive subgroups of S3.
- """
- A3 = "A3"
- S3 = "S3"
- def get_perm_group(self):
- if self == S3TransitiveSubgroups.A3:
- return AlternatingGroup(3)
- elif self == S3TransitiveSubgroups.S3:
- return SymmetricGroup(3)
- class S4TransitiveSubgroups(Enum):
- """
- Names for the transitive subgroups of S4.
- """
- C4 = "C4"
- V = "V"
- D4 = "D4"
- A4 = "A4"
- S4 = "S4"
- def get_perm_group(self):
- if self == S4TransitiveSubgroups.C4:
- return CyclicGroup(4)
- elif self == S4TransitiveSubgroups.V:
- return four_group()
- elif self == S4TransitiveSubgroups.D4:
- return DihedralGroup(4)
- elif self == S4TransitiveSubgroups.A4:
- return AlternatingGroup(4)
- elif self == S4TransitiveSubgroups.S4:
- return SymmetricGroup(4)
- class S5TransitiveSubgroups(Enum):
- """
- Names for the transitive subgroups of S5.
- """
- C5 = "C5"
- D5 = "D5"
- M20 = "M20"
- A5 = "A5"
- S5 = "S5"
- def get_perm_group(self):
- if self == S5TransitiveSubgroups.C5:
- return CyclicGroup(5)
- elif self == S5TransitiveSubgroups.D5:
- return DihedralGroup(5)
- elif self == S5TransitiveSubgroups.M20:
- return M20()
- elif self == S5TransitiveSubgroups.A5:
- return AlternatingGroup(5)
- elif self == S5TransitiveSubgroups.S5:
- return SymmetricGroup(5)
- class S6TransitiveSubgroups(Enum):
- """
- Names for the transitive subgroups of S6.
- """
- C6 = "C6"
- S3 = "S3"
- D6 = "D6"
- A4 = "A4"
- G18 = "G18"
- A4xC2 = "A4 x C2"
- S4m = "S4-"
- S4p = "S4+"
- G36m = "G36-"
- G36p = "G36+"
- S4xC2 = "S4 x C2"
- PSL2F5 = "PSL2(F5)"
- G72 = "G72"
- PGL2F5 = "PGL2(F5)"
- A6 = "A6"
- S6 = "S6"
- def get_perm_group(self):
- if self == S6TransitiveSubgroups.C6:
- return CyclicGroup(6)
- elif self == S6TransitiveSubgroups.S3:
- return S3_in_S6()
- elif self == S6TransitiveSubgroups.D6:
- return DihedralGroup(6)
- elif self == S6TransitiveSubgroups.A4:
- return A4_in_S6()
- elif self == S6TransitiveSubgroups.G18:
- return G18()
- elif self == S6TransitiveSubgroups.A4xC2:
- return A4xC2()
- elif self == S6TransitiveSubgroups.S4m:
- return S4m()
- elif self == S6TransitiveSubgroups.S4p:
- return S4p()
- elif self == S6TransitiveSubgroups.G36m:
- return G36m()
- elif self == S6TransitiveSubgroups.G36p:
- return G36p()
- elif self == S6TransitiveSubgroups.S4xC2:
- return S4xC2()
- elif self == S6TransitiveSubgroups.PSL2F5:
- return PSL2F5()
- elif self == S6TransitiveSubgroups.G72:
- return G72()
- elif self == S6TransitiveSubgroups.PGL2F5:
- return PGL2F5()
- elif self == S6TransitiveSubgroups.A6:
- return AlternatingGroup(6)
- elif self == S6TransitiveSubgroups.S6:
- return SymmetricGroup(6)
- def four_group():
- """
- Return a representation of the Klein four-group as a transitive subgroup
- of S4.
- """
- return PermutationGroup(
- Permutation(0, 1)(2, 3),
- Permutation(0, 2)(1, 3)
- )
- def M20():
- """
- Return a representation of the metacyclic group M20, a transitive subgroup
- of S5 that is one of the possible Galois groups for polys of degree 5.
- Notes
- =====
- See [1], Page 323.
- """
- G = PermutationGroup(Permutation(0, 1, 2, 3, 4), Permutation(1, 2, 4, 3))
- G._degree = 5
- G._order = 20
- G._is_transitive = True
- G._is_sym = False
- G._is_alt = False
- G._is_cyclic = False
- G._is_dihedral = False
- return G
- def S3_in_S6():
- """
- Return a representation of S3 as a transitive subgroup of S6.
- Notes
- =====
- The representation is found by viewing the group as the symmetries of a
- triangular prism.
- """
- G = PermutationGroup(Permutation(0, 1, 2)(3, 4, 5), Permutation(0, 3)(2, 4)(1, 5))
- set_symmetric_group_properties(G, 3, 6)
- return G
- def A4_in_S6():
- """
- Return a representation of A4 as a transitive subgroup of S6.
- Notes
- =====
- This was computed using :py:func:`~.find_transitive_subgroups_of_S6`.
- """
- G = PermutationGroup(Permutation(0, 4, 5)(1, 3, 2), Permutation(0, 1, 2)(3, 5, 4))
- set_alternating_group_properties(G, 4, 6)
- return G
- def S4m():
- """
- Return a representation of the S4- transitive subgroup of S6.
- Notes
- =====
- This was computed using :py:func:`~.find_transitive_subgroups_of_S6`.
- """
- G = PermutationGroup(Permutation(1, 4, 5, 3), Permutation(0, 4)(1, 5)(2, 3))
- set_symmetric_group_properties(G, 4, 6)
- return G
- def S4p():
- """
- Return a representation of the S4+ transitive subgroup of S6.
- Notes
- =====
- This was computed using :py:func:`~.find_transitive_subgroups_of_S6`.
- """
- G = PermutationGroup(Permutation(0, 2, 4, 1)(3, 5), Permutation(0, 3)(4, 5))
- set_symmetric_group_properties(G, 4, 6)
- return G
- def A4xC2():
- """
- Return a representation of the (A4 x C2) transitive subgroup of S6.
- Notes
- =====
- This was computed using :py:func:`~.find_transitive_subgroups_of_S6`.
- """
- return PermutationGroup(
- Permutation(0, 4, 5)(1, 3, 2), Permutation(0, 1, 2)(3, 5, 4),
- Permutation(5)(2, 4))
- def S4xC2():
- """
- Return a representation of the (S4 x C2) transitive subgroup of S6.
- Notes
- =====
- This was computed using :py:func:`~.find_transitive_subgroups_of_S6`.
- """
- return PermutationGroup(
- Permutation(1, 4, 5, 3), Permutation(0, 4)(1, 5)(2, 3),
- Permutation(1, 4)(3, 5))
- def G18():
- """
- Return a representation of the group G18, a transitive subgroup of S6
- isomorphic to the semidirect product of C3^2 with C2.
- Notes
- =====
- This was computed using :py:func:`~.find_transitive_subgroups_of_S6`.
- """
- return PermutationGroup(
- Permutation(5)(0, 1, 2), Permutation(3, 4, 5),
- Permutation(0, 4)(1, 5)(2, 3))
- def G36m():
- """
- Return a representation of the group G36-, a transitive subgroup of S6
- isomorphic to the semidirect product of C3^2 with C2^2.
- Notes
- =====
- This was computed using :py:func:`~.find_transitive_subgroups_of_S6`.
- """
- return PermutationGroup(
- Permutation(5)(0, 1, 2), Permutation(3, 4, 5),
- Permutation(1, 2)(3, 5), Permutation(0, 4)(1, 5)(2, 3))
- def G36p():
- """
- Return a representation of the group G36+, a transitive subgroup of S6
- isomorphic to the semidirect product of C3^2 with C4.
- Notes
- =====
- This was computed using :py:func:`~.find_transitive_subgroups_of_S6`.
- """
- return PermutationGroup(
- Permutation(5)(0, 1, 2), Permutation(3, 4, 5),
- Permutation(0, 5, 2, 3)(1, 4))
- def G72():
- """
- Return a representation of the group G72, a transitive subgroup of S6
- isomorphic to the semidirect product of C3^2 with D4.
- Notes
- =====
- See [1], Page 325.
- """
- return PermutationGroup(
- Permutation(5)(0, 1, 2),
- Permutation(0, 4, 1, 3)(2, 5), Permutation(0, 3)(1, 4)(2, 5))
- def PSL2F5():
- r"""
- Return a representation of the group $PSL_2(\mathbb{F}_5)$, as a transitive
- subgroup of S6, isomorphic to $A_5$.
- Notes
- =====
- This was computed using :py:func:`~.find_transitive_subgroups_of_S6`.
- """
- G = PermutationGroup(
- Permutation(0, 4, 5)(1, 3, 2), Permutation(0, 4, 3, 1, 5))
- set_alternating_group_properties(G, 5, 6)
- return G
- def PGL2F5():
- r"""
- Return a representation of the group $PGL_2(\mathbb{F}_5)$, as a transitive
- subgroup of S6, isomorphic to $S_5$.
- Notes
- =====
- See [1], Page 325.
- """
- G = PermutationGroup(
- Permutation(0, 1, 2, 3, 4), Permutation(0, 5)(1, 2)(3, 4))
- set_symmetric_group_properties(G, 5, 6)
- return G
- def find_transitive_subgroups_of_S6(*targets, print_report=False):
- r"""
- Search for certain transitive subgroups of $S_6$.
- The symmetric group $S_6$ has 16 different transitive subgroups, up to
- conjugacy. Some are more easily constructed than others. For example, the
- dihedral group $D_6$ is immediately found, but it is not at all obvious how
- to realize $S_4$ or $S_5$ *transitively* within $S_6$.
- In some cases there are well-known constructions that can be used. For
- example, $S_5$ is isomorphic to $PGL_2(\mathbb{F}_5)$, which acts in a
- natural way on the projective line $P^1(\mathbb{F}_5)$, a set of order 6.
- In absence of such special constructions however, we can simply search for
- generators. For example, transitive instances of $A_4$ and $S_4$ can be
- found within $S_6$ in this way.
- Once we are engaged in such searches, it may then be easier (if less
- elegant) to find even those groups like $S_5$ that do have special
- constructions, by mere search.
- This function locates generators for transitive instances in $S_6$ of the
- following subgroups:
- * $A_4$
- * $S_4^-$ ($S_4$ not contained within $A_6$)
- * $S_4^+$ ($S_4$ contained within $A_6$)
- * $A_4 \times C_2$
- * $S_4 \times C_2$
- * $G_{18} = C_3^2 \rtimes C_2$
- * $G_{36}^- = C_3^2 \rtimes C_2^2$
- * $G_{36}^+ = C_3^2 \rtimes C_4$
- * $G_{72} = C_3^2 \rtimes D_4$
- * $A_5$
- * $S_5$
- Note: Each of these groups also has a dedicated function in this module
- that returns the group immediately, using generators that were found by
- this search procedure.
- The search procedure serves as a record of how these generators were
- found. Also, due to randomness in the generation of the elements of
- permutation groups, it can be called again, in order to (probably) get
- different generators for the same groups.
- Parameters
- ==========
- targets : list of :py:class:`~.S6TransitiveSubgroups` values
- The groups you want to find.
- print_report : bool (default False)
- If True, print to stdout the generators found for each group.
- Returns
- =======
- dict
- mapping each name in *targets* to the :py:class:`~.PermutationGroup`
- that was found
- References
- ==========
- .. [2] https://en.wikipedia.org/wiki/Projective_linear_group#Exceptional_isomorphisms
- .. [3] https://en.wikipedia.org/wiki/Automorphisms_of_the_symmetric_and_alternating_groups#PGL(2,5)
- """
- def elts_by_order(G):
- """Sort the elements of a group by their order. """
- elts = defaultdict(list)
- for g in G.elements:
- elts[g.order()].append(g)
- return elts
- def order_profile(G, name=None):
- """Determine how many elements a group has, of each order. """
- elts = elts_by_order(G)
- profile = {o:len(e) for o, e in elts.items()}
- if name:
- print(f'{name}: ' + ' '.join(f'{len(profile[r])}@{r}' for r in sorted(profile.keys())))
- return profile
- S6 = SymmetricGroup(6)
- A6 = AlternatingGroup(6)
- S6_by_order = elts_by_order(S6)
- def search(existing_gens, needed_gen_orders, order, alt=None, profile=None, anti_profile=None):
- """
- Find a transitive subgroup of S6.
- Parameters
- ==========
- existing_gens : list of Permutation
- Optionally empty list of generators that must be in the group.
- needed_gen_orders : list of positive int
- Nonempty list of the orders of the additional generators that are
- to be found.
- order: int
- The order of the group being sought.
- alt: bool, None
- If True, require the group to be contained in A6.
- If False, require the group not to be contained in A6.
- profile : dict
- If given, the group's order profile must equal this.
- anti_profile : dict
- If given, the group's order profile must *not* equal this.
- """
- for gens in itertools.product(*[S6_by_order[n] for n in needed_gen_orders]):
- if len(set(gens)) < len(gens):
- continue
- G = PermutationGroup(existing_gens + list(gens))
- if G.order() == order and G.is_transitive():
- if alt is not None and G.is_subgroup(A6) != alt:
- continue
- if profile and order_profile(G) != profile:
- continue
- if anti_profile and order_profile(G) == anti_profile:
- continue
- return G
- def match_known_group(G, alt=None):
- needed = [g.order() for g in G.generators]
- return search([], needed, G.order(), alt=alt, profile=order_profile(G))
- found = {}
- def finish_up(name, G):
- found[name] = G
- if print_report:
- print("=" * 40)
- print(f"{name}:")
- print(G.generators)
- if S6TransitiveSubgroups.A4 in targets or S6TransitiveSubgroups.A4xC2 in targets:
- A4_in_S6 = match_known_group(AlternatingGroup(4))
- finish_up(S6TransitiveSubgroups.A4, A4_in_S6)
- if S6TransitiveSubgroups.S4m in targets or S6TransitiveSubgroups.S4xC2 in targets:
- S4m_in_S6 = match_known_group(SymmetricGroup(4), alt=False)
- finish_up(S6TransitiveSubgroups.S4m, S4m_in_S6)
- if S6TransitiveSubgroups.S4p in targets:
- S4p_in_S6 = match_known_group(SymmetricGroup(4), alt=True)
- finish_up(S6TransitiveSubgroups.S4p, S4p_in_S6)
- if S6TransitiveSubgroups.A4xC2 in targets:
- A4xC2_in_S6 = search(A4_in_S6.generators, [2], 24, anti_profile=order_profile(SymmetricGroup(4)))
- finish_up(S6TransitiveSubgroups.A4xC2, A4xC2_in_S6)
- if S6TransitiveSubgroups.S4xC2 in targets:
- S4xC2_in_S6 = search(S4m_in_S6.generators, [2], 48)
- finish_up(S6TransitiveSubgroups.S4xC2, S4xC2_in_S6)
- # For the normal factor N = C3^2 in any of the G_n subgroups, we take one
- # obvious instance of C3^2 in S6:
- N_gens = [Permutation(5)(0, 1, 2), Permutation(5)(3, 4, 5)]
- if S6TransitiveSubgroups.G18 in targets:
- G18_in_S6 = search(N_gens, [2], 18)
- finish_up(S6TransitiveSubgroups.G18, G18_in_S6)
- if S6TransitiveSubgroups.G36m in targets:
- G36m_in_S6 = search(N_gens, [2, 2], 36, alt=False)
- finish_up(S6TransitiveSubgroups.G36m, G36m_in_S6)
- if S6TransitiveSubgroups.G36p in targets:
- G36p_in_S6 = search(N_gens, [4], 36, alt=True)
- finish_up(S6TransitiveSubgroups.G36p, G36p_in_S6)
- if S6TransitiveSubgroups.G72 in targets:
- G72_in_S6 = search(N_gens, [4, 2], 72)
- finish_up(S6TransitiveSubgroups.G72, G72_in_S6)
- # The PSL2(F5) and PGL2(F5) subgroups are isomorphic to A5 and S5, resp.
- if S6TransitiveSubgroups.PSL2F5 in targets:
- PSL2F5_in_S6 = match_known_group(AlternatingGroup(5))
- finish_up(S6TransitiveSubgroups.PSL2F5, PSL2F5_in_S6)
- if S6TransitiveSubgroups.PGL2F5 in targets:
- PGL2F5_in_S6 = match_known_group(SymmetricGroup(5))
- finish_up(S6TransitiveSubgroups.PGL2F5, PGL2F5_in_S6)
- # There is little need to "search" for any of the groups C6, S3, D6, A6,
- # or S6, since they all have obvious realizations within S6. However, we
- # support them here just in case a random representation is desired.
- if S6TransitiveSubgroups.C6 in targets:
- C6 = match_known_group(CyclicGroup(6))
- finish_up(S6TransitiveSubgroups.C6, C6)
- if S6TransitiveSubgroups.S3 in targets:
- S3 = match_known_group(SymmetricGroup(3))
- finish_up(S6TransitiveSubgroups.S3, S3)
- if S6TransitiveSubgroups.D6 in targets:
- D6 = match_known_group(DihedralGroup(6))
- finish_up(S6TransitiveSubgroups.D6, D6)
- if S6TransitiveSubgroups.A6 in targets:
- A6 = match_known_group(A6)
- finish_up(S6TransitiveSubgroups.A6, A6)
- if S6TransitiveSubgroups.S6 in targets:
- S6 = match_known_group(S6)
- finish_up(S6TransitiveSubgroups.S6, S6)
- return found
|