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
Added Polycyclic Group Class #16991
Added Polycyclic Group Class #16991
Changes from 10 commits
91f55c9
0a9b68f
1ea3055
be62f04
1f6232e
f02f9c7
5f71423
29ee81b
1aa9ba5
c82cd23
78af5dd
0033d11
f290d62
267af71
6935976
e5c9b1b
feadb8b
a72ba44
cb4ee73
ed4f142
fbdc1ad
415d476
ecfbb7d
e210b27
7d9e3e4
d80c0ed
4c9ca8a
86b8fe7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,290 @@ | ||
from sympy.core import Basic | ||
from sympy import sieve | ||
from sympy.combinatorics.perm_groups import PermutationGroup | ||
from sympy.printing.defaults import DefaultPrinting | ||
|
||
class PolycyclicGroup(Basic): | ||
|
||
is_group = True | ||
is_solvable = True | ||
|
||
def __init__(self, pcgs_): | ||
self.perm_group = PermutationGroup(pcgs_) | ||
self.pc_series = self._pc_series() | ||
self.pcgs = self._compute_pcgs() | ||
|
||
def _pc_series(self): | ||
return self.perm_group.composition_series() | ||
|
||
def _compute_pcgs(self): | ||
# computes the generating sequence for polycyclic groups. | ||
series = self.pc_series | ||
pcgs = [] | ||
for i in range(len(series)-1): | ||
for g in series[i].generators: | ||
if not g in series[i+1]: | ||
pcgs.append(g) | ||
return pcgs | ||
|
||
def relative_orders(self): | ||
rel_orders = [] | ||
for i in range(len(self.pc_series)-1): | ||
G = self.pc_series[i] | ||
H = self.pc_series[i+1] | ||
rel_orders.append(G.order()//H.order()) | ||
return rel_orders | ||
|
||
def is_prime_order(self): | ||
for order in self.relative_orders(): | ||
if order not in sieve: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How does There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For, greater length of relative_order |
||
return False | ||
return True | ||
|
||
def length(self): | ||
return len(self.pcgs) | ||
|
||
def pc_element_exponent(self, element): | ||
series = self.pc_series | ||
pcgs = self.pcgs | ||
exponent = [0]*len(series) | ||
for i in range(len(series)): | ||
exp = 0 | ||
if not element in series[i]: | ||
for j in range(len(pcgs)): | ||
element = (pcgs[j]**-1)*element | ||
exp = exp + 1 | ||
if element in series[i]: | ||
exponent[i] = exp | ||
break | ||
return exponent | ||
|
||
|
||
class Collector(DefaultPrinting): | ||
|
||
""" | ||
References | ||
========== | ||
|
||
.. [1] Holt, D., Eick, B., O'Brien, E. | ||
"Handbook of Computational Group Theory" | ||
Section 8.1.3 | ||
""" | ||
|
||
def __init__(self, pc_relators, relative_order, group): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe we could have
It the generators are needed somewhere, they could also be added to Collector:
but maybe that is not necessary. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Isn't if we are providing There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. May be this can be done with the class There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
But that also I think will not be a good choice! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It looks like there are several possibilities. If pc_relators are given (as they are now) then the group can be found from those relators. (They contain elements of the group.) Alternatively, and maybe preferably, pcgs and relative_order could be given as parameters, and then the pc_relators could be derived from them by internal methods of Collector class. (Most of the code is there already, only differently organized.) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
If not given by user then there is only one way to get the whole pc_relators via pc_presentation. Should there be an additional method to Collector to compute pc_relators given user provides group is a parameter to Collector. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think that |
||
self.pc_relators = pc_relators | ||
self.relative_order = relative_order | ||
self.group = group | ||
|
||
def minimal_uncollected_subwords(self, word): | ||
""" | ||
Returns the minimal uncollected subwords. | ||
|
||
Examples | ||
======== | ||
>>> from sympy.combinatorics.pc_groups import Collector | ||
>>> from sympy.combinatorics.free_groups import free_group | ||
|
||
Example 8.7 Pg. 281 from [1] | ||
>>> F, x1, x2 = free_group("x1, x2") | ||
>>> pc_relators = {x1**2 : (), x1*x2*x1**-1 : x2**-1, x1**-1*x2*x1 : x2**-1} | ||
>>> relative_order = {x1: 2, x2: 3} | ||
>>> word = x2**2*x1**7 | ||
>>> group = word.group | ||
>>> collector = Collector(pc_relators, relative_order, group) | ||
>>> collector.minimal_uncollected_subwords(word) | ||
{((x1, 7),): 2, ((x2, 2), (x1, 1)): 0} | ||
|
||
""" | ||
|
||
# To handle the case word = <identity> | ||
if not word: | ||
return {} | ||
group = self.group | ||
index = {s: i+1 for i, s in enumerate(group.symbols)} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Initially, we thought of treating index as relative order that's why I kept this, Now this can be changed! |
||
array = word.array_form | ||
re = self.relative_order | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could this be a list? For instance, There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe 0 or There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, we can do that! |
||
uncollected_subwords = {} | ||
|
||
for i in range(len(array)-1): | ||
s1, e1 = array[i] | ||
s2, e2 = array[i+1] | ||
s = ((s1, 1), ) | ||
s = group.dtype(s) | ||
if e1 > re[s]-1: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What about There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There is a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I doubt that There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It is uncollected if the relative order is finite (Definition 8.14 a). Someone may try to collect words with negative exponents. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. But, that will lead to error because power-relators are in form of positive exponent and have a look at example 8.7 if negative-exponents should be considered then that example should have been proceeded further. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. See the lines 7 - 9 in COLLECTED_WORD on pg. 282. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In example 8.7, the relative order of x2 is infinite. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ahh! that's right! |
||
# case-2: v = x[i]**a | ||
uncollected_subwords[((s1, e1), )] = 2 | ||
continue | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I thought we do not need to handle There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think that it is better to handle all uncollected minimal subwords as soon as we find them. They may cause extra work later if the power relation is more complicated than |
||
|
||
if e2 > 0 and index[s1] > index[s2]: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would start by checking that |
||
# case-0: v = x[i]**a*x[i+1], where index[x[i]] > index[x[i+1]] | ||
uncollected_subwords[((s1, e1), (s2, 1))] = 0 | ||
|
||
elif e2 < 0 and index[s1] > index[s2]: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The index condition is the important part. It should be checked first. Then this line and the previous one could be handled together. For example,
I don't think it would be necessary to created a dict |
||
# case-1: v = x[i]**a*x[i+1]*-1, where index[x[i]] > index[x[i+1]] | ||
uncollected_subwords[((s1, e1), (s2, -1))] = 1 | ||
|
||
i = len(array)-1 | ||
s1, e1 = array[i] | ||
s = ((s1, 1), ) | ||
s = group.dtype(s) | ||
if e1 > re[s]-1: | ||
# case-2: v = x[i]**a | ||
uncollected_subwords[((s1, e1), )] = 2 | ||
|
||
return uncollected_subwords | ||
|
||
def relations(self): | ||
""" | ||
Separates the given relators of pc presentation in power and | ||
conjugate relations. | ||
|
||
Examples | ||
======== | ||
>>> from sympy.combinatorics.pc_groups import Collector | ||
>>> from sympy.combinatorics.free_groups import free_group | ||
>>> F, x1, x2 = free_group("x1, x2") | ||
>>> pc_relators = {x1**2 : 1, x1*x2*x1**-1 : x2**-1, x1**-1*x2*x1 : x2**-1} | ||
>>> relative_order = {x1: 2, x2: 3} | ||
>>> word = x2**2*x1**7 | ||
>>> group = word.group | ||
>>> collector = Collector(pc_relators, relative_order, group) | ||
>>> power_rel, conj_rel = collector.relations() | ||
>>> power_rel | ||
{x1**2: 1} | ||
>>> conj_rel | ||
{x1**-1*x2*x1: x2**-1, x1*x2*x1**-1: x2**-1} | ||
|
||
""" | ||
power_relators = {} | ||
conjugate_relators = {} | ||
for key, value in self.pc_relators.items(): | ||
if len(key.array_form) == 1: | ||
power_relators[key] = value | ||
else: | ||
conjugate_relators[key] = value | ||
return power_relators, conjugate_relators | ||
|
||
def subword_index(self, word, w): | ||
""" | ||
Returns the start and ending index of a given | ||
subword in a word. | ||
|
||
Examples | ||
======== | ||
>>> from sympy.combinatorics.pc_groups import Collector | ||
>>> from sympy.combinatorics.free_groups import free_group | ||
>>> F, x1, x2 = free_group("x1, x2") | ||
>>> pc_relators = {x1**2 : 1, x1*x2*x1**-1 : x2**-1, x1**-1*x2*x1 : x2**-1} | ||
>>> relative_order = {x1: 2, x2: 3} | ||
>>> word = x2**2*x1**7 | ||
>>> group = word.group | ||
>>> collector = Collector(pc_relators, relative_order, group) | ||
>>> w = x2**2*x1 | ||
>>> collector.subword_index(word, w) | ||
(0, 3) | ||
>>> w = x1**7 | ||
>>> collector.subword_index(word, w) | ||
(2, 9) | ||
|
||
""" | ||
low = -1 | ||
high = -1 | ||
for i in range(len(word)-len(w)+1): | ||
if word.subword(i, i+len(w)) == w: | ||
low = i | ||
high = i+len(w) | ||
break | ||
if low == high == -1: | ||
return -1, -1 | ||
return low, high | ||
|
||
def map_relation(self, w): | ||
""" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Some kind of explanation is expected in the docstring. |
||
Examples | ||
======== | ||
>>> from sympy.combinatorics.pc_groups import Collector | ||
>>> from sympy.combinatorics.free_groups import free_group | ||
>>> F, x0, x1, x2 = free_group("x0, x1, x2") | ||
>>> pc_relators = {x0**-1*x1*x0: x1**2, x1**-1*x2*x1:x2, x0**-1*x2*x0:x2*x1} | ||
>>> relative_order = {x0: 2, x1: 2, x2: 3} | ||
>>> word = x2**2*x1**7 | ||
>>> group = word.group | ||
>>> collector = Collector(pc_relators, relative_order, group) | ||
>>> w = x2*x1 | ||
>>> collector.map_relation(w) | ||
x2 | ||
>>> w = x1*x0 | ||
>>> collector.map_relation(w) | ||
x1**2 | ||
|
||
""" | ||
group = w.group | ||
gens = list(sorted(w.contains_generators())) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, I should remove this! |
||
key = gens[0]**-1*gens[1]*gens[0] | ||
return self.pc_relators[key] | ||
|
||
|
||
def collected_word(self, word): | ||
""" | ||
Examples | ||
======== | ||
>>> from sympy.combinatorics.pc_groups import Collector | ||
>>> from sympy.combinatorics.free_groups import free_group | ||
>>> F, x1, x2 = free_group("x1, x2") | ||
>>> pc_relators = {x1**2 : (), x1*x2*x1**-1 : x2**-1, x1**-1*x2*x1 : x2**-1} | ||
>>> relative_order = {x1: 2, x2: 3} | ||
>>> word = x2**2*x1**7 | ||
>>> group = word.group | ||
>>> collector = Collector(pc_relators, relative_order, group) | ||
>>> collector.collected_word(word) | ||
x1*x2**-2 | ||
|
||
""" | ||
group = self.group | ||
while True: | ||
uncollected_subwords = self.minimal_uncollected_subwords(word) | ||
if not uncollected_subwords: | ||
break | ||
w = list(uncollected_subwords)[0] | ||
case = uncollected_subwords[w] | ||
w = group.dtype(w) | ||
low, high = self.subword_index(word, w) | ||
if low == -1: | ||
continue | ||
if case == 0: | ||
gens = list(sorted(w.contains_generators())) | ||
array = w.array_form | ||
s, e = array[0] | ||
word_ = self.map_relation(w) | ||
word_ = gens[0]*word_**e | ||
word_ = group.dtype(word_) | ||
word = word.substituted_word(low, high, word_) | ||
|
||
elif case == 1: | ||
gens = list(sorted(w.contains_generators())) | ||
array = w.array_form | ||
s, e = array[0] | ||
word_ = self.map_relation(w) | ||
word_ = (gens[0]**-1)*word_**e | ||
word_ = group.dtype(word_) | ||
word = word.substituted_word(low, high, word_) | ||
|
||
else: | ||
array = w.array_form | ||
s, e = array[0] | ||
s1 = ((s, 1), ) | ||
s = group.dtype(s1) | ||
re = self.relative_order[s] | ||
q = e//re | ||
r = e-q*re | ||
key = w[0]**re | ||
if self.pc_relators[key]: | ||
word_ = ((w[0], r), (self.pc_relators[key], re)) | ||
word_ = group.dtype(word_) | ||
else: | ||
if r != 0: | ||
word_ = w[0]**r | ||
else: | ||
word_ = None | ||
word = word.eliminate_word(w, word_) | ||
return word |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
from sympy.combinatorics.pc_groups import PolycyclicGroup, Collector | ||
from sympy.combinatorics.permutations import Permutation | ||
from sympy.combinatorics.free_groups import free_group | ||
|
||
def test_collected_word(): | ||
F, x0, x1, x2, x3 = free_group("x0, x1, x2, x3") | ||
|
||
# Polycyclic relators for SymmetricGroup(4) | ||
pc_relators = { x0**2: (), x1**3: (), x2**2: (), x3**2: (), | ||
x0**-1*x1*x0: x1**2, x0**-1*x2*x0: x2*x3, | ||
x0**-1*x3*x0: x3, x1**-1*x2*x1: x3, | ||
x1**-1*x3*x1: x2*x3, x2**-1*x3*x2: x3 | ||
} | ||
|
||
word = x3*x2*x1*x0 | ||
relative_order = {x0: 2, x1: 3, x2: 2, x3: 2} | ||
group = word.group | ||
collector = Collector(pc_relators, relative_order, group) | ||
collected_word_ = collector.collected_word(word) | ||
|
||
assert collected_word_ == x0*x1**2*x2*x3 | ||
|
||
# Polycyclic Generators of SymmetricGroup(4) | ||
x0 = Permutation(0, 1) | ||
x1 = Permutation(0, 1, 2) | ||
x2 = Permutation(0, 2)(1, 3) | ||
x3 = Permutation(0, 1)(2, 3) | ||
|
||
word = x3*x2*x1*x0 | ||
collected_word_ = x0*x1**2*x2*x3 | ||
assert word == collected_word_ | ||
|
||
|
||
|
||
F, x0, x1 = free_group("x0, x1") | ||
# polycyclic relators for Symmetricgroup(3) | ||
pc_relators = {x0**2: (), x1**3: (), x0**-1*x1*x0: x1**2} | ||
relative_order = {x0: 2, x1: 3} | ||
group = F | ||
collector = Collector(pc_relators, relative_order, group) | ||
|
||
a = Permutation(0, 1) # x0 | ||
b = Permutation(0, 1, 2) # x1 | ||
|
||
word = x1*x0 | ||
assert collector.collected_word(word) == x0*x1**2 | ||
assert b*a == a*b**2 | ||
|
||
word = x1*x0**2 | ||
assert collector.collected_word(word) == x1 | ||
assert b*a**2 == b | ||
|
||
word = x1**2*x0 | ||
assert collector.collected_word(word) == x0*x1 | ||
assert b**2*a == a*b | ||
|
||
word = x1**4*x0**6 | ||
assert collector.collected_word(word) == x1 | ||
assert b**4*a**6 == b | ||
|
||
word = x0*x1 | ||
# The word is already collected | ||
assert collector.collected_word(word) == x0*x1 | ||
assert a*b == a*b | ||
|
||
word = x0**2*x1 | ||
assert collector.collected_word(word) == x1 | ||
assert a**2*b == b | ||
|
||
word = x0**2*x1**3 | ||
# Handle Identity case | ||
assert collector.collected_word(word) == F.identity | ||
assert a**2*b**3 == Permutation(2) | ||
|
||
word = x1**-2*x0 | ||
assert collector.collected_word(word) == x0*x1**-4 | ||
assert b**-2*a == a*b**-4 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Where is this method needed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not used anywhere, this is a separate method for polycyclic groups.