Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Compute map from subgroup to parent group #14968

Merged
merged 6 commits into from Jul 28, 2018
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
29 changes: 29 additions & 0 deletions sympy/combinatorics/coset_table.py
Expand Up @@ -816,6 +816,35 @@ def conjugates(self, R):
R_set.difference_update(r)
return R_c_list

def coset_representative(self, coset):
'''
Compute the coset representative of a given coset.

Examples
========
>>> from sympy.combinatorics.free_groups import free_group
>>> from sympy.combinatorics.fp_groups import FpGroup, coset_enumeration_r
>>> F, x, y = free_group("x, y")
>>> f = FpGroup(F, [x**3, y**3, x**-1*y**-1*x*y])
>>> C = coset_enumeration_r(f, [x])
>>> C.compress()
>>> C.table
[[0, 0, 1, 2], [1, 1, 2, 0], [2, 2, 0, 1]]
>>> C.coset_representative(0)
<identity>
>>> C.coset_representative(1)
y
>>> C.coset_representative(2)
y**-1

'''
for x in self.A:
gamma = self.table[coset][self.A_dict[x]]
if coset == 0:
return self.fp_group.identity
if gamma < coset:
return self.coset_representative(gamma)*x**-1

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are not using this anywhere else. I suppose it could be good to have this sort of a function but it can be more efficient: for example, you could start in row coset, find the first x s.t. gamma < coset where gamma = self.table[coset][self.A_dict[x]] and return coset_representative(gamma)*x**-1

##############################
# Modified Methods #
##############################
Expand Down
59 changes: 54 additions & 5 deletions sympy/combinatorics/fp_groups.py
Expand Up @@ -118,21 +118,41 @@ def identity(self):
def __contains__(self, g):
return g in self.free_group

def subgroup(self, gens, C=None):
def subgroup(self, gens, C=None, homomorphism=False):
'''
Return the subgroup generated by `gens` using the
Reidemeister-Schreier algorithm
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should have an explanation of homomorphism and actually an example of homomorphism=True could be useful (just to demonstrate that the homomorphism returns an image of an element of subgroup(...) in the parent group)

homomorphism -- When set to True, return a dictionary containing the images
of the presentation generators in the original group.

Examples
========
>>> from sympy.combinatorics.fp_groups import (FpGroup, FpSubgroup)
>>> from sympy.combinatorics.free_groups import free_group
>>> F, x, y = free_group("x, y")
>>> f = FpGroup(F, [x**3, y**5, (x*y)**2])
>>> H = [x*y, x**-1*y**-1*x*y*x]
>>> K, T = f.subgroup(H, homomorphism=True)
>>> T(K.generators)
[x*y, x**-1*y**2*x**-1]

'''

if not all([isinstance(g, FreeGroupElement) for g in gens]):
raise ValueError("Generators must be `FreeGroupElement`s")
if not all([g.group == self.free_group for g in gens]):
raise ValueError("Given generators are not members of the group")
g, rels = reidemeister_presentation(self, gens, C=C)
if homomorphism:
g, rels, _gens = reidemeister_presentation(self, gens, C=C, homomorphism=True)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You end up running reidemeister_presentation twice if homomorphism=True, so better move the first one into an "else".

else:
g, rels = reidemeister_presentation(self, gens, C=C)
if g:
g = FpGroup(g[0].group, rels)
else:
g = FpGroup(free_group('')[0], [])
if homomorphism:
from sympy.combinatorics.homomorphisms import homomorphism
return g, homomorphism(g, self, g.generators, _gens, check=False)
return g

def coset_enumeration(self, H, strategy="relator_based", max_cosets=None,
Expand Down Expand Up @@ -1068,25 +1088,44 @@ def _simplification_technique_1(rels):
###############################################################################

# Pg 175 [1]
def define_schreier_generators(C):
def define_schreier_generators(C, homomorphism=False):
'''
Arguments:
C -- Coset table.
homomorphism -- When set to True, return a dictionary containing the images
of the presentation generators in the original group.
'''
y = []
gamma = 1
f = C.fp_group
X = f.generators
if homomorphism:
# `_gens` stores the elements of the parent group to
# to which the schreier generators correspond to.
_gens = {}
# compute the schreier Traversal
tau = {}
tau[0] = f.identity
C.P = [[None]*len(C.A) for i in range(C.n)]
for alpha, x in product(C.omega, C.A):
beta = C.table[alpha][C.A_dict[x]]
if beta == gamma:
C.P[alpha][C.A_dict[x]] = "<identity>"
C.P[beta][C.A_dict_inv[x]] = "<identity>"
gamma += 1
if homomorphism:
tau[beta] = tau[alpha]*x
elif x in X and C.P[alpha][C.A_dict[x]] is None:
y_alpha_x = '%s_%s' % (x, alpha)
y.append(y_alpha_x)
C.P[alpha][C.A_dict[x]] = y_alpha_x
if homomorphism:
_gens[y_alpha_x] = tau[alpha]*x*tau[beta]**-1
grp_gens = list(free_group(', '.join(y)))
C._schreier_free_group = grp_gens.pop(0)
C._schreier_generators = grp_gens
if homomorphism:
C._schreier_gen_elem = _gens
# replace all elements of P by, free group elements
for i, j in product(range(len(C.P)), range(len(C.A))):
# if equals "<identity>", replace by identity element
Expand Down Expand Up @@ -1209,11 +1248,13 @@ def elimination_technique_2(C):
C._schreier_generators = gens
return C._schreier_generators, C._reidemeister_relators

def reidemeister_presentation(fp_grp, H, C=None):
def reidemeister_presentation(fp_grp, H, C=None, homomorphism=False, subgroup=False):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just noticed that you didn't remove the subgroup keyword - it doesn't do anything anymore

"""
fp_group: A finitely presented group, an instance of FpGroup
H: A subgroup whose presentation is to be found, given as a list
of words in generators of `fp_grp`
homomorphism: When set to True, return a homomorphism from the subgroup
to the parent group

Examples
========
Expand Down Expand Up @@ -1250,14 +1291,22 @@ def reidemeister_presentation(fp_grp, H, C=None):
if not C:
C = coset_enumeration_r(fp_grp, H)
C.compress(); C.standardize()
define_schreier_generators(C)
define_schreier_generators(C, homomorphism=homomorphism)
reidemeister_relators(C)
gens, rels = C._schreier_generators, C._reidemeister_relators
gens, rels = simplify_presentation(gens, rels, change_gens=True)

C.schreier_generators = tuple(gens)
C.reidemeister_relators = tuple(rels)

if homomorphism:
_gens = []
C.schreier_gen_elem = {}
for gen in gens:
C.schreier_gen_elem[gen] = C._schreier_gen_elem[str(gen)]
_gens.append(C.schreier_gen_elem[gen])
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does C.schreier_gen_elem actually serve a purpose? It seems enough to just compute _gens. You could even do _gens = [C._schreier_gen_elem[str(gen)] for gen in gens] in just one line

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That could be removed. Thought I might be useful elsewhere by making it an attribute, but, I don't think it's necessary.

return C.schreier_generators, C.reidemeister_relators, _gens

return C.schreier_generators, C.reidemeister_relators


Expand Down
21 changes: 20 additions & 1 deletion sympy/combinatorics/tests/test_fp_groups.py
Expand Up @@ -166,15 +166,34 @@ def test_order():
f = FpGroup(free_group('')[0], [])
assert f.order() == 1

def _test_subgroup(K, T, S):
_gens = T(K.generators)
for elem in _gens:
assert elem in S
Copy link
Contributor

@valglad valglad Jul 28, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or you could say assert all(elem in S for elem in _gens)

assert T.is_injective()
assert T.image().order() == S.order()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe this sort of thing usually goes inside the unit test for which it is relevant, because it itself isn't an independent test. So this should go after def test_fp_subgroup() - a purely stylistic thing but it seems that it's usually done that way.


def test_fp_subgroup():
F, x, y = free_group("x, y")
f = FpGroup(F, [x**4, y**2, x*y*x**-1*y])
S = FpSubgroup(f, [x*y])
assert (x*y)**-3 in S
K, T = f.subgroup([x*y], homomorphism=True)
assert T(K.generators) == [y*x**-1]
_test_subgroup(K, T, S)

S = FpSubgroup(F, [x**-1*y*x])
S = FpSubgroup(f, [x**-1*y*x])
assert x**-1*y**4*x in S
assert x**-1*y**4*x**2 not in S
K, T = f.subgroup([x**-1*y*x], homomorphism=True)
assert T(K.generators[0]**3) == y**3
_test_subgroup(K, T, S)

f = FpGroup(F, [x**3, y**5, (x*y)**2])
H = [x*y, x**-1*y**-1*x*y*x]
K, T = f.subgroup(H, homomorphism=True)
S = FpSubgroup(f, H)
_test_subgroup(K, T, S)

def test_permutation_methods():
from sympy.combinatorics.fp_groups import FpSubgroup
Expand Down