Skip to content

Commit

Permalink
Merge pull request #13150 from valglad/alt_sym_sylow
Browse files Browse the repository at this point in the history
group theory: more efficient Sylow subgroups for Sym and Alt groups
  • Loading branch information
jksuom committed Aug 27, 2017
2 parents e166e1c + c87e6ca commit 6d502a6
Show file tree
Hide file tree
Showing 2 changed files with 123 additions and 1 deletion.
114 changes: 113 additions & 1 deletion sympy/combinatorics/perm_groups.py
Expand Up @@ -1680,6 +1680,9 @@ def is_alt_sym(self, eps=0.05, _random_prec=None):
answer True (i.e., G is symmetric/alternating) guaranteed to be
correct, and the answer False being incorrect with probability eps.
For degree < 8, the order of the group is checked so the test
is deterministic.
Notes
=====
Expand Down Expand Up @@ -1712,7 +1715,11 @@ def is_alt_sym(self, eps=0.05, _random_prec=None):
if _random_prec is None:
n = self.degree
if n < 8:
return False
sym_order = 1
for i in range(2, n+1):
sym_order *= i
order = self.order()
return order == sym_order or 2*order == sym_order
if not self.is_transitive():
return False
if n < 17:
Expand Down Expand Up @@ -3556,6 +3563,108 @@ def _p_elements_group(G, p):
gens_p = gens_p[:j] + [x] + gens_p[j:]
return PermutationGroup(gens_r)

def _sylow_alt_sym(self, p):
'''
Return a p-Sylow subgroup of a symmetric or an
alternating group.
The algorithm for this is hinted at in [1], Chapter 4,
Exercise 4.
For Sym(n) with n = p^i, the idea is as follows. Partition
the interval [0..n-1] into p equal parts, each of length p^(i-1):
[0..p^(i-1)-1], [p^(i-1)..2*p^(i-1)-1]...[(p-1)*p^(i-1)..p^i-1].
Find a p-Sylow subgroup of Sym(p^(i-1)) (treated as a subgroup
of `self`) acting on each of the parts. Call the subgroups
P_1, P_2...P_p. The generators for the subgroups P_2...P_p
can be obtained from those of P_1 by applying a "shifting"
permutation to them, that is, a permutation mapping [0..p^(i-1)-1]
to the second part (the other parts are obtained by using the shift
multiple times). The union of this permutation and the generators
of P_1 is a p-Sylow subgroup of `self`.
For n not equal to a power of p, partition
[0..n-1] in accordance with how n would be written in base p.
E.g. for p=2 and n=11, 11 = 2^3 + 2^2 + 1 so the partition
is [[0..7], [8..9], {10}]. To generate a p-Sylow subgroup,
take the union of the generators for each of the parts.
For the above example, {(0 1), (0 2)(1 3), (0 4), (1 5)(2 7)}
from the first part, {(8 9)} from the second part and
nothing from the third. This gives 4 generators in total, and
the subgroup they generate is p-Sylow.
Alternating groups are treated the same except when p=2. In this
case, (0 1)(s s+1) should be added for an appropriate s (the start
of a part) for each part in the partitions.
See Also
========
sylow_subgroup, is_alt_sym
'''
n = self.degree
gens = []
identity = Permutation(n-1)
# the case of 2-sylow subgroups of alternating groups
# needs special treatment
alt = p == 2 and all(g.is_even for g in self.generators)

# find the presentation of n in base p
coeffs = []
m = n
while m > 0:
coeffs.append(m % p)
m = m // p

power = len(coeffs)-1
# for a symmetric group, gens[:i] is the generating
# set for a p-Sylow subgroup on [0..p**(i-1)-1]. For
# alternating groups, the same is given by gens[:2*(i-1)]
for i in range(1, power+1):
if i == 1 and alt:
# (0 1) shouldn't be added for alternating groups
continue
gen = Permutation([(j + p**(i-1)) % p**i for j in range(p**i)])
gens.append(identity*gen)
if alt:
gen = Permutation(0, 1)*gen*Permutation(0, 1)*gen
gens.append(gen)

# the first point in the current part (see the algorithm
# description in the docstring)
start = 0

while power > 0:
a = coeffs[power]

# make the permutation shifting the start of the first
# part ([0..p^i-1] for some i) to the current one
for s in range(a):
shift = Permutation()
if start > 0:
for i in range(p**power):
shift = shift(i, start + i)

if alt:
gen = Permutation(0, 1)*shift*Permutation(0, 1)*shift
gens.append(gen)
j = 2*(power - 1)
else:
j = power

for i, gen in enumerate(gens[:j]):
if alt and i % 2 == 1:
continue
# shift the generator to the start of the
# partition part
gen = shift*gen*shift
gens.append(gen)

start += p**power
power = power-1

return gens

def sylow_subgroup(self, p):
'''
Return a p-Sylow subgroup of the group.
Expand Down Expand Up @@ -3631,6 +3740,9 @@ def _sylow_reduce(mu, nu):
if p_group:
return self

if self.is_alt_sym():
return PermutationGroup(self._sylow_alt_sym(p))

# if there is a non-trivial orbit with size not divisible
# by p, the sylow subgroup is contained in its stabilizer
# (by orbit-stabilizer theorem)
Expand Down
10 changes: 10 additions & 0 deletions sympy/combinatorics/tests/test_perm_groups.py
Expand Up @@ -828,6 +828,16 @@ def test_sylow_subgroup():
else:
assert len(ls) == length

G = SymmetricGroup(100)
S = G.sylow_subgroup(3)
assert G.order() % S.order() == 0
assert G.order()/S.order() % 3 > 0

G = AlternatingGroup(100)
S = G.sylow_subgroup(2)
assert G.order() % S.order() == 0
assert G.order()/S.order() % 2 > 0

def test_presentation():
def _test(P):
G = P.presentation()
Expand Down

0 comments on commit 6d502a6

Please sign in to comment.