``Reidemeister Schreier`` algorithm implementation in SymPy #11295

Merged
merged 38 commits into from Jul 8, 2016

Projects

None yet

2 participants

@gxyd
Member
gxyd commented Jun 25, 2016

No description provided.

@gxyd gxyd added the GSoC label Jun 25, 2016
@gxyd gxyd commented on an outdated diff Jun 27, 2016
sympy/combinatorics/fp_groups.py
+ for i, j in product(range(len(C.P)), range(len(C.A))):
+ # if equals "<identity>", replace by identity element
+ if C.P[i][j] == "<identity>":
+ C.P[i][j] = C.schreier_free_group.identity
+ elif isinstance(C.P[i][j], str):
+ r = C.schreier_generators[y.index(C.P[i][j])]
+ C.P[i][j] = r
+ beta = C.table[i][j]
+ C.P[beta][j + 1] = r**-1
+
+
+def reidemeister_relators(C):
+ R = C.fp_group.relators()
+ rels = set([rewrite(C, coset, word) for word in R for coset in range(C.n)])
+ identity = C.schreier_free_group.identity
+ order_1_gens = set([i for i in rels if len(i) == 1])
@gxyd
gxyd Jun 27, 2016 edited Member

I am not sure, whether simplifications like these should be kept just here or moved under the Tietze transformation program since I have haven't studied the "presentation by generators", if "presentation by generators" also expects such simplification then we move this out.

@gxyd
Member
gxyd commented Jun 27, 2016

I believe we should start coding the "presentation on user generators", since coding that would give an idea about which "simplification techniques" are common between them.

@jksuom jksuom commented on an outdated diff Jun 28, 2016
sympy/combinatorics/fp_groups.py
+ x_i = w[i]
+ v = v*C.P[alpha][C.A_dict[x_i]]
+ alpha = C.table[alpha][C.A_dict[x_i]]
+ return v
+
+
+# routines for modified Todd Coxeter procedure
+# Section 5.3.2 from [1]
+def modified_define(C, alpha, x):
+ """
+ see also
+ ========
+ CosetTable.define
+
+ """
+ C.define(alpha, x)
@jksuom
jksuom Jun 28, 2016 Member

Perhaps define could return beta.

@jksuom jksuom commented on an outdated diff Jun 28, 2016
sympy/combinatorics/fp_groups.py
+
+ """
+ table = C.table
+ f = alpha
+ # f_p and b_p are words with coset numbers "f" and "b".
+ # f_p when we scan forward from beginning, while b_p when we scan backward
+ # from end.
+ f_p = "<identity>"
+ b_p = y
+ i = 0
+ r = len(word)
+ b = alpha
+ j = r - 1
+ while i <= j and table[f][C.A_dict[word[i]]] is not None:
+ f_p = f_p*C.P[f][C.A_dict[word[i]]]
+ i += 1
@jksuom
jksuom Jun 28, 2016 Member

Next f should also be assigned before this.

@jksuom jksuom commented on an outdated diff Jun 29, 2016
sympy/combinatorics/fp_groups.py
- while i < len(rels):
- j = 0
- while j < len(gens):
- if rels[i].generator_exponent_sum(gen) == 1:
- gen_index = rels[i].index(gen)
- bk = rels[i].subword(gen_index + 1)
- fw = rels[i].subword(0, gen_index)
- redundant_gen[rels[i]] = (bk*fw)**-1
- del rels[i]; gens.remove(gen)
- j += 1
- i += 1
+ redundant_gens = {}
+ # examine each relator in relator list for any generator occuring exactly
+ # once
+ l1, l2 = len(rels), len(gens)
+ for i, j in product(range(l1 -1, -1, -1), range(l2 - 1, -1, -1)):
@jksuom
jksuom Jun 29, 2016 Member

The loop over j should probably be initiated anew for each round of the i-loop since len(gens) may decrease. The product may be too big.

@gxyd gxyd commented on an outdated diff Jun 29, 2016
sympy/combinatorics/fp_groups.py
+
+
+def reidemeister_presentation(fp_grp, H):
+ """
+ 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`
+
+ Examples
+ ========
+
+ >>> from sympy.combinatorics.free_group import free_group, FpGroup
+ >>> 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]
+ >>> reidemeister_relators(f, H)
@gxyd
gxyd Jun 29, 2016 Member

Will change this to reidemeister_presentation (left by mistake)

@gxyd gxyd commented on an outdated diff Jun 30, 2016
sympy/combinatorics/fp_groups.py
+ >>> H = [x]
+ >>> reidemeister_presentation(f, H)
+ ([x_1], [x_1**-4, x_1**-8])
+
+ >>> f = FpGroup(F, [x**3*y**-3, (x*y)**3, (x*y**-1)**2])
+ >>> H = [x]
+ >>> reidemeister_presentation(f, H)
+ ([x_0], [<identity>, x_0**-4, x_0**6, x_0**-12])
+
+ """
+ C = coset_enumeration_r(fp_grp, H)
+ C.compress(); C.standardize()
+ define_schreier_generators(C)
+ reidemeister_relators(C)
+ elimination_technique_1(C)
+ elimination_technique_1(C)
@gxyd
gxyd Jun 30, 2016 Member

Perhaps the number of times, any elimination technique should be used is better answered in the Handbook. Will see that later on.

@gxyd gxyd and 1 other commented on an outdated diff Jun 30, 2016
sympy/combinatorics/fp_groups.py
+
+ >>> f = FpGroup(F, [x**3, y**3, (x*y)**3])
+ >>> H = [x*y, x*y**-1]
+ >>> reidemeister_presentation(f, H)
+ ([x_0, y_0], [x_0**3, y_0**3, x_0*y_0*x_0*y_0*x_0*y_0])
+
+ # Exercises Q2. Pg 187 from [1]
+ >>> f = FpGroup(F, [x**2*y**2, y**-1*x*y*x**-3])
+ >>> H = [x]
+ >>> reidemeister_presentation(f, H)
+ ([x_1], [x_1**-4, x_1**-8])
+
+ >>> f = FpGroup(F, [x**3*y**-3, (x*y)**3, (x*y**-1)**2])
+ >>> H = [x]
+ >>> reidemeister_presentation(f, H)
+ ([x_0], [<identity>, x_0**-4, x_0**6, x_0**-12])
@gxyd
gxyd Jun 30, 2016 Member

This occurs, since of the presence of "removal of dependent relator" in the relator list, I don't exactly understand the definition of "independent relators", I have only have an idea about it (since no official definition, AFAIK).

@jksuom
jksuom Jun 30, 2016 Member

Where are those 'dependent relators' mentioned?

@gxyd
gxyd Jul 1, 2016 Member

In the Havas paper, in the elimination technique 1, second paragraph beginning in brackets. This line

(provided the relator is independent of previously discovered but not yet eliminated redundant generators).

Sorry for late reply, I somehow skipped the mail.

@jksuom
jksuom Jul 1, 2016 Member

I think that 'independent' simply means that the relator does not involve those generators that have already been marked redundant.

@gxyd gxyd and 1 other commented on an outdated diff Jun 30, 2016
sympy/combinatorics/free_group.py
@@ -806,6 +831,58 @@ def subword(self, from_i, to_j):
array_form = letter_form_to_array_form(letter_form, group)
return group.dtype(array_form)
+ def is_independent(self, word):
@gxyd
gxyd Jun 30, 2016 Member

This method is to be used to eliminate a few of the "relators" from the relator list. But I am not sure, about at what stage should this be used in the elimination_technique_1.

@gxyd
gxyd Jun 30, 2016 Member

Perhaps this is the line from Havas. From second paragraph beginning (elimination_technique_1)

provided the relator is independent of previously discovered but not yet eliminated redundant generators).

I am not fully sure that I even understood its meaning correctly.

@jksuom
jksuom Jun 30, 2016 Member

It seems that the relators are scanned and generators that appear only once are marked redundant. Those relators that contain such generators are skipped over. After all relators have been scanned redundant generators are replaced in the relators by their representations in terms of other generators and deleted from the list. Then the process can be repeated. (Relators containing generators marked redundant cannot be used to decide if a generator is redundant. Even if it appears only once, it may reappear when another generator is replaced.)

@gxyd
gxyd Jun 30, 2016 Member

That is fine. I think I already understood everything you mentioned in
your comment, (even your comment has been executed in code
successfully). But the problem is about the removal of independent
relator from the relator list. See for example see this doctest example
https://github.com/sympy/sympy/pull/11295/files#diff-2bca05fcc1cad2237c5589d92187d25cR1288
. Did I missed something?

On 06/30/2016 06:04 PM, Kalevi Suominen wrote:

In sympy/combinatorics/free_group.py
#11295 (comment):

@@ -806,6 +831,58 @@ def subword(self, from_i, to_j):
array_form = letter_form_to_array_form(letter_form, group)
return group.dtype(array_form)

  • def is_independent(self, word):

It seems that the relators are scanned and generators that appear only
once are marked redundant. Those relators that contain such generators
are skipped over. After all relators have been scanned redundant
generators are replaced in the relators by their representations in
terms of other generators and deleted from the list. Then the process
can be repeated. (Relators containing generators marked redundant
cannot be used to decide if a generator is redundant. Even if it
appears only once, it may reappear when another generator is replaced.)


You are receiving this because you authored the thread.
Reply to this email directly, view it on GitHub
https://github.com/sympy/sympy/pull/11295/files/bb2622cc696920381b63f2d02e4a5e928dc221e5#r69123414,
or mute the thread
https://github.com/notifications/unsubscribe/AHWt8f3hKesJ76PVUKgenMpwxGtQN7jSks5qQ7fmgaJpZM4I-QZs.

@gxyd gxyd commented on an outdated diff Jun 30, 2016
sympy/combinatorics/fp_groups.py
+ redundant_gens[gen] = (bk*fw)**(-1*k)
+ contained_gens.extend((bk*fw).contains_generators())
+ del rels[i]; del gens[j]
+ break
+ j -= 1
+ # eliminate the redundant generator from remaing relators
+ for i, gen in product(range(len(rels)), redundant_gens):
+ rels[i] = rels[i].eliminate_word(gen, redundant_gens[gen])
+ rels.sort()
+ C.reidemeister_relators = rels
+ C.schreier_generators = gens
+
+def elimination_technique_2(C):
+ pass
+
+def simplification_technique_1(C):
@gxyd
gxyd Jun 30, 2016 Member

Will probably see a few things for this, in the morning. It wouldn't be hard to implement this technique.

@gxyd
Member
gxyd commented Jul 1, 2016

I think the simplifiication technique mentioned in Havas paper section 2.6.1 is fine now. I have added the doctests as well.

@jksuom jksuom commented on an outdated diff Jul 2, 2016
sympy/combinatorics/fp_groups.py
+# Pg 350, section 2.5.1 from [2]
+def elimination_technique_1(C):
+ rels = list(C.reidemeister_relators)
+ # the shorter relators are examined first so that generators selected for
+ # elimination will have shorter strings as equivalent
+ rels.sort(reverse=True)
+ gens = list(C.schreier_generators)
+ redundant_gens = {}
+ contained_gens = []
+ # examine each relator in relator list for any generator occuring exactly
+ # once
+ next_i = False
+ for i in range(len(rels) -1, -1, -1):
+ rel = rels[i]
+ for gen in redundant_gens:
+ if any([gen.array_form[0][0] == r[0] for r in rel.array_form]):
@jksuom
jksuom Jul 2, 2016 Member

This is a loop. It would be better to assign gen.array_form[0][0] to a local variable.

@jksuom jksuom commented on an outdated diff Jul 2, 2016
sympy/combinatorics/free_group.py
if len(gen) != 1:
raise ValueError("gen must be a generator or inverse of a generator")
- n = 0
- g = abs(gen[0])
- for i in w:
- if i == g:
- n = n + 1
- elif i == -g:
- n = n - 1
-
- if gen[0] < 0:
- n = -n
- return n
+ s = gen.array_form[0]
+ return s[1]*sum([i[1] for i in self.array_form if i[0] == s[0]])
+
+ def generator_exponent_sum(self, gen):
@jksuom
jksuom Jul 2, 2016 Member

This name may be misleading since there are also negative exponents. Maybe something like generator_count (with no reference to addition) would be better. It could be implicitly interpreted as 'count with multiplicities'.

@jksuom jksuom commented on an outdated diff Jul 2, 2016
sympy/combinatorics/free_group.py
- if i == g:
- n = n + 1
- elif i == -g:
- n = n - 1
-
- if gen[0] < 0:
- n = -n
- return n
+ s = gen.array_form[0]
+ return s[1]*sum([i[1] for i in self.array_form if i[0] == s[0]])
+
+ def generator_exponent_sum(self, gen):
+ """
+ For an associative word `self` and a generator `gen`,
+ ``generator_exponent_sum`` returns the number of times `gen` or
+ `gen**-1` appears in `self` If both `gen` and its inverse do not
@jksuom
jksuom Jul 2, 2016 Member

Perhaps "If neither gen nor its inverse"

@jksuom jksuom commented on an outdated diff Jul 2, 2016
sympy/combinatorics/free_group.py
@@ -753,31 +756,56 @@ def __ge__(self, other):
def exponent_sum_word(self, gen):
@jksuom
jksuom Jul 2, 2016 Member

Could this be just exponent_sum?

@jksuom jksuom commented on an outdated diff Jul 2, 2016
sympy/combinatorics/fp_groups.py
+ redundant_gens = {}
+ contained_gens = []
+ # examine each relator in relator list for any generator occuring exactly
+ # once
+ next_i = False
+ for i in range(len(rels) -1, -1, -1):
+ rel = rels[i]
+ for gen in redundant_gens:
+ if any([gen.array_form[0][0] == r[0] for r in rel.array_form]):
+ next_i = True
+ break
+ if next_i:
+ next_i = False
+ continue
+ j = len(gens) - 1
+ while j >= 0:
@jksuom
jksuom Jul 2, 2016 Member

Could this also be a for loop with a backwards range?

@gxyd
Member
gxyd commented Jul 2, 2016

Seems like, github is not updating my commit that I have pushed successfully (neither showing the commit, nor the related changes).

gaurav@stallman:~/Public/sympy$ git push github implementation_reidemeister_schreier 
Username for 'https://github.com': gxyd
Password for 'https://gxyd@github.com': 
Everything up-to-date

Though the suggested changes were all valid, I have made those (atleast locally).

@jksuom
Member
jksuom commented Jul 2, 2016

There is a short queue, but that should not cause the problem. Perhaps you could wait until the next commit. (Or else close and reopen.)

@gxyd
Member
gxyd commented Jul 2, 2016 edited

Now the following things need to be done:

  1. Removing the "dependent relators". For example
>>> f = FpGroup(F, [x**3*y**-3, (x*y)**3, (x*y**-1)**2]); H = [x]
>>> reidemeister_presentation(f, H)
({x_0}, {<identity>, x_0**6, x_0**18})

Relator list should shouldn't contain x_0**18. (I mentioned this in blog as well)
2. Removal of -ve sign from the single syllable relators. For example

>>> f = FpGroup(F, [x**3, y**5, (x*y)**2]); H = [x*y, x**-1*y**-1*x*y*x]
>>> reidemeister_presentation(f, H)
({y_1, y_2}, {y_1**2, y_2**-3, y_2*y_1*y_2*y_1*y_2*y_1})

Change y_2**-3 -> y_2**3 in relator list.
3. Add tests.
4. Obviously to speed things up.

Just a TODO list for myself, not for you. :)

@gxyd gxyd closed this Jul 3, 2016
@gxyd gxyd reopened this Jul 3, 2016
@gxyd
Member
gxyd commented Jul 3, 2016

I think, perhaps there seems to be no use of "is_dependent". That functioning is infact to be done by _simplification_technique_1.

@jksuom jksuom and 1 other commented on an outdated diff Jul 4, 2016
sympy/combinatorics/fp_groups.py
+ >>> from sympy.combinatorics.free_group import free_group
+ >>> from sympy.combinatorics.fp_groups import FpGroup, coset_enumeration_r, reidemeister_relators, define_schreier_generators, elimination_technique_2
+ >>> 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]
+ >>> C = coset_enumeration_r(f, H)
+ >>> C.compress(); C.standardize()
+ >>> define_schreier_generators(C)
+ >>> reidemeister_relators(C)
+ >>> elimination_technique_2(C)
+ ([y_1, y_2], [y_2**-3, y_2*y_1*y_2*y_1*y_2*y_1, y_1**2])
+
+ """
+ rels = list(C.reidemeister_relators)
+ rels.sort(reverse=True)
+ gens = list(C.schreier_generators)
+ for i in range(len(gens) - 1, -1, -1):
@jksuom
jksuom Jul 4, 2016 Member

Maybe len(rels) - 1?

@gxyd
gxyd Jul 4, 2016 Member

Yes.

On 07/04/2016 10:27 AM, Kalevi Suominen wrote:

In sympy/combinatorics/fp_groups.py
#11295 (comment):

  • from sympy.combinatorics.free_group import free_group

  • from sympy.combinatorics.fp_groups import FpGroup, coset_enumeration_r, reidemeister_relators, define_schreier_generators, elimination_technique_2

  • F, x, y = free_group("x, y")

  • f = FpGroup(F, [x3, y5, (x_y)__2]); H = [x_y, x**-1y**-1_x_yx]

  • C = coset_enumeration_r(f, H)

  • C.compress(); C.standardize()

  • define_schreier_generators(C)

  • reidemeister_relators(C)

  • elimination_technique_2(C)

  • ([y_1, y_2], [y_2**-3, y_2_y_1_y_2_y_1_y_2*y_1, y_1**2])
  • """
  • rels = list(C.reidemeister_relators)
  • rels.sort(reverse=True)
  • gens = list(C.schreier_generators)
  • for i in range(len(gens) - 1, -1, -1):

Maybe |len(rels) - 1|?


You are receiving this because you modified the open/close state.
Reply to this email directly, view it on GitHub
https://github.com/sympy/sympy/pull/11295/files/1327a1b039a327c531e305bc3663f54362dd9c90#r69408413,
or mute the thread
https://github.com/notifications/unsubscribe/AHWt8bHZu1Ch3b70raGsuvmq62bJAqt9ks5qSJKmgaJpZM4I-QZs.

@gxyd
Member
gxyd commented Jul 6, 2016

With the latest code, one example is giving a wrong presentation. I have a made a gist file giving output at different stages.
The correct output should be a presentation of the form a^11, b^2, (a*b)^3, (a^4*b*a^-5*b)^2. (this is the presentation of PSL_2(11) group, given in Implementation and Analysis of the Todd-Coxeter Algorithm paper)

@gxyd
Member
gxyd commented Jul 6, 2016

Can you please into the current issue. Though I am trying, though I have liked to mention one line mentioned as an assumption in both "Handbook" and "Havas paper"

We shall assume that all relators are always cyclically reduced; that is, that whenever a relator is changed, it is immediately replaced by its cyclic reduction.

Also, even the latest commit introduced a bug for the "doctest" example.

@gxyd
Member
gxyd commented Jul 7, 2016

I think this is now ready for review.

@jksuom
Member
jksuom commented Jul 7, 2016

Looks good. I'll take some more time with the details tomorrow.

@gxyd gxyd changed the title from [WIP] ``Reidemeister Schreier`` algorithm implementation in SymPy to ``Reidemeister Schreier`` algorithm implementation in SymPy Jul 8, 2016
@jksuom
Member
jksuom commented Jul 8, 2016

This is ready to be merged.

@jksuom jksuom merged commit 040151c into sympy:master Jul 8, 2016

1 check passed

continuous-integration/travis-ci/pr The Travis CI build passed
Details
@gxyd gxyd deleted the gxyd:implementation_reidemeister_schreier branch Jul 8, 2016
@gxyd gxyd restored the gxyd:implementation_reidemeister_schreier branch Aug 16, 2016
@gxyd gxyd deleted the gxyd:implementation_reidemeister_schreier branch Aug 24, 2016
@skirpichev skirpichev referenced this pull request in diofant/diofant Dec 7, 2016
Open

[wip] Backport some sympy fixes #390

81 of 99 tasks complete
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment