| 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%282,5%29 |
| |
| """ |
| 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) |
|
|
| |
| |
| 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) |
|
|
| |
|
|
| 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) |
|
|
| |
| |
| |
|
|
| 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 |
|
|