Spaces:
Sleeping
Sleeping
from sympy.ntheory.primetest import isprime | |
from sympy.combinatorics.perm_groups import PermutationGroup | |
from sympy.printing.defaults import DefaultPrinting | |
from sympy.combinatorics.free_groups import free_group | |
class PolycyclicGroup(DefaultPrinting): | |
is_group = True | |
is_solvable = True | |
def __init__(self, pc_sequence, pc_series, relative_order, collector=None): | |
""" | |
Parameters | |
========== | |
pc_sequence : list | |
A sequence of elements whose classes generate the cyclic factor | |
groups of pc_series. | |
pc_series : list | |
A subnormal sequence of subgroups where each factor group is cyclic. | |
relative_order : list | |
The orders of factor groups of pc_series. | |
collector : Collector | |
By default, it is None. Collector class provides the | |
polycyclic presentation with various other functionalities. | |
""" | |
self.pcgs = pc_sequence | |
self.pc_series = pc_series | |
self.relative_order = relative_order | |
self.collector = Collector(self.pcgs, pc_series, relative_order) if not collector else collector | |
def is_prime_order(self): | |
return all(isprime(order) for order in self.relative_order) | |
def length(self): | |
return len(self.pcgs) | |
class Collector(DefaultPrinting): | |
""" | |
References | |
========== | |
.. [1] Holt, D., Eick, B., O'Brien, E. | |
"Handbook of Computational Group Theory" | |
Section 8.1.3 | |
""" | |
def __init__(self, pcgs, pc_series, relative_order, free_group_=None, pc_presentation=None): | |
""" | |
Most of the parameters for the Collector class are the same as for PolycyclicGroup. | |
Others are described below. | |
Parameters | |
========== | |
free_group_ : tuple | |
free_group_ provides the mapping of polycyclic generating | |
sequence with the free group elements. | |
pc_presentation : dict | |
Provides the presentation of polycyclic groups with the | |
help of power and conjugate relators. | |
See Also | |
======== | |
PolycyclicGroup | |
""" | |
self.pcgs = pcgs | |
self.pc_series = pc_series | |
self.relative_order = relative_order | |
self.free_group = free_group('x:{}'.format(len(pcgs)))[0] if not free_group_ else free_group_ | |
self.index = {s: i for i, s in enumerate(self.free_group.symbols)} | |
self.pc_presentation = self.pc_relators() | |
def minimal_uncollected_subword(self, word): | |
r""" | |
Returns the minimal uncollected subwords. | |
Explanation | |
=========== | |
A word ``v`` defined on generators in ``X`` is a minimal | |
uncollected subword of the word ``w`` if ``v`` is a subword | |
of ``w`` and it has one of the following form | |
* `v = {x_{i+1}}^{a_j}x_i` | |
* `v = {x_{i+1}}^{a_j}{x_i}^{-1}` | |
* `v = {x_i}^{a_j}` | |
for `a_j` not in `\{1, \ldots, s-1\}`. Where, ``s`` is the power | |
exponent of the corresponding generator. | |
Examples | |
======== | |
>>> from sympy.combinatorics.named_groups import SymmetricGroup | |
>>> from sympy.combinatorics import free_group | |
>>> G = SymmetricGroup(4) | |
>>> PcGroup = G.polycyclic_group() | |
>>> collector = PcGroup.collector | |
>>> F, x1, x2 = free_group("x1, x2") | |
>>> word = x2**2*x1**7 | |
>>> collector.minimal_uncollected_subword(word) | |
((x2, 2),) | |
""" | |
# To handle the case word = <identity> | |
if not word: | |
return None | |
array = word.array_form | |
re = self.relative_order | |
index = self.index | |
for i in range(len(array)): | |
s1, e1 = array[i] | |
if re[index[s1]] and (e1 < 0 or e1 > re[index[s1]]-1): | |
return ((s1, e1), ) | |
for i in range(len(array)-1): | |
s1, e1 = array[i] | |
s2, e2 = array[i+1] | |
if index[s1] > index[s2]: | |
e = 1 if e2 > 0 else -1 | |
return ((s1, e1), (s2, e)) | |
return None | |
def relations(self): | |
""" | |
Separates the given relators of pc presentation in power and | |
conjugate relations. | |
Returns | |
======= | |
(power_rel, conj_rel) | |
Separates pc presentation into power and conjugate relations. | |
Examples | |
======== | |
>>> from sympy.combinatorics.named_groups import SymmetricGroup | |
>>> G = SymmetricGroup(3) | |
>>> PcGroup = G.polycyclic_group() | |
>>> collector = PcGroup.collector | |
>>> power_rel, conj_rel = collector.relations() | |
>>> power_rel | |
{x0**2: (), x1**3: ()} | |
>>> conj_rel | |
{x0**-1*x1*x0: x1**2} | |
See Also | |
======== | |
pc_relators | |
""" | |
power_relators = {} | |
conjugate_relators = {} | |
for key, value in self.pc_presentation.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. | |
Parameters | |
========== | |
word : FreeGroupElement | |
word defined on free group elements for a | |
polycyclic group. | |
w : FreeGroupElement | |
subword of a given word, whose starting and | |
ending index to be computed. | |
Returns | |
======= | |
(i, j) | |
A tuple containing starting and ending index of ``w`` | |
in the given word. | |
Examples | |
======== | |
>>> from sympy.combinatorics.named_groups import SymmetricGroup | |
>>> from sympy.combinatorics import free_group | |
>>> G = SymmetricGroup(4) | |
>>> PcGroup = G.polycyclic_group() | |
>>> collector = PcGroup.collector | |
>>> F, x1, x2 = free_group("x1, x2") | |
>>> word = x2**2*x1**7 | |
>>> 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): | |
""" | |
Return a conjugate relation. | |
Explanation | |
=========== | |
Given a word formed by two free group elements, the | |
corresponding conjugate relation with those free | |
group elements is formed and mapped with the collected | |
word in the polycyclic presentation. | |
Examples | |
======== | |
>>> from sympy.combinatorics.named_groups import SymmetricGroup | |
>>> from sympy.combinatorics import free_group | |
>>> G = SymmetricGroup(3) | |
>>> PcGroup = G.polycyclic_group() | |
>>> collector = PcGroup.collector | |
>>> F, x0, x1 = free_group("x0, x1") | |
>>> w = x1*x0 | |
>>> collector.map_relation(w) | |
x1**2 | |
See Also | |
======== | |
pc_presentation | |
""" | |
array = w.array_form | |
s1 = array[0][0] | |
s2 = array[1][0] | |
key = ((s2, -1), (s1, 1), (s2, 1)) | |
key = self.free_group.dtype(key) | |
return self.pc_presentation[key] | |
def collected_word(self, word): | |
r""" | |
Return the collected form of a word. | |
Explanation | |
=========== | |
A word ``w`` is called collected, if `w = {x_{i_1}}^{a_1} * \ldots * | |
{x_{i_r}}^{a_r}` with `i_1 < i_2< \ldots < i_r` and `a_j` is in | |
`\{1, \ldots, {s_j}-1\}`. | |
Otherwise w is uncollected. | |
Parameters | |
========== | |
word : FreeGroupElement | |
An uncollected word. | |
Returns | |
======= | |
word | |
A collected word of form `w = {x_{i_1}}^{a_1}, \ldots, | |
{x_{i_r}}^{a_r}` with `i_1, i_2, \ldots, i_r` and `a_j \in | |
\{1, \ldots, {s_j}-1\}`. | |
Examples | |
======== | |
>>> from sympy.combinatorics.named_groups import SymmetricGroup | |
>>> from sympy.combinatorics.perm_groups import PermutationGroup | |
>>> from sympy.combinatorics import free_group | |
>>> G = SymmetricGroup(4) | |
>>> PcGroup = G.polycyclic_group() | |
>>> collector = PcGroup.collector | |
>>> F, x0, x1, x2, x3 = free_group("x0, x1, x2, x3") | |
>>> word = x3*x2*x1*x0 | |
>>> collected_word = collector.collected_word(word) | |
>>> free_to_perm = {} | |
>>> free_group = collector.free_group | |
>>> for sym, gen in zip(free_group.symbols, collector.pcgs): | |
... free_to_perm[sym] = gen | |
>>> G1 = PermutationGroup() | |
>>> for w in word: | |
... sym = w[0] | |
... perm = free_to_perm[sym] | |
... G1 = PermutationGroup([perm] + G1.generators) | |
>>> G2 = PermutationGroup() | |
>>> for w in collected_word: | |
... sym = w[0] | |
... perm = free_to_perm[sym] | |
... G2 = PermutationGroup([perm] + G2.generators) | |
The two are not identical, but they are equivalent: | |
>>> G1.equals(G2), G1 == G2 | |
(True, False) | |
See Also | |
======== | |
minimal_uncollected_subword | |
""" | |
free_group = self.free_group | |
while True: | |
w = self.minimal_uncollected_subword(word) | |
if not w: | |
break | |
low, high = self.subword_index(word, free_group.dtype(w)) | |
if low == -1: | |
continue | |
s1, e1 = w[0] | |
if len(w) == 1: | |
re = self.relative_order[self.index[s1]] | |
q = e1 // re | |
r = e1-q*re | |
key = ((w[0][0], re), ) | |
key = free_group.dtype(key) | |
if self.pc_presentation[key]: | |
presentation = self.pc_presentation[key].array_form | |
sym, exp = presentation[0] | |
word_ = ((w[0][0], r), (sym, q*exp)) | |
word_ = free_group.dtype(word_) | |
else: | |
if r != 0: | |
word_ = ((w[0][0], r), ) | |
word_ = free_group.dtype(word_) | |
else: | |
word_ = None | |
word = word.eliminate_word(free_group.dtype(w), word_) | |
if len(w) == 2 and w[1][1] > 0: | |
s2, e2 = w[1] | |
s2 = ((s2, 1), ) | |
s2 = free_group.dtype(s2) | |
word_ = self.map_relation(free_group.dtype(w)) | |
word_ = s2*word_**e1 | |
word_ = free_group.dtype(word_) | |
word = word.substituted_word(low, high, word_) | |
elif len(w) == 2 and w[1][1] < 0: | |
s2, e2 = w[1] | |
s2 = ((s2, 1), ) | |
s2 = free_group.dtype(s2) | |
word_ = self.map_relation(free_group.dtype(w)) | |
word_ = s2**-1*word_**e1 | |
word_ = free_group.dtype(word_) | |
word = word.substituted_word(low, high, word_) | |
return word | |
def pc_relators(self): | |
r""" | |
Return the polycyclic presentation. | |
Explanation | |
=========== | |
There are two types of relations used in polycyclic | |
presentation. | |
* Power relations : Power relators are of the form `x_i^{re_i}`, | |
where `i \in \{0, \ldots, \mathrm{len(pcgs)}\}`, ``x`` represents polycyclic | |
generator and ``re`` is the corresponding relative order. | |
* Conjugate relations : Conjugate relators are of the form `x_j^-1x_ix_j`, | |
where `j < i \in \{0, \ldots, \mathrm{len(pcgs)}\}`. | |
Returns | |
======= | |
A dictionary with power and conjugate relations as key and | |
their collected form as corresponding values. | |
Notes | |
===== | |
Identity Permutation is mapped with empty ``()``. | |
Examples | |
======== | |
>>> from sympy.combinatorics.named_groups import SymmetricGroup | |
>>> from sympy.combinatorics.permutations import Permutation | |
>>> S = SymmetricGroup(49).sylow_subgroup(7) | |
>>> der = S.derived_series() | |
>>> G = der[len(der)-2] | |
>>> PcGroup = G.polycyclic_group() | |
>>> collector = PcGroup.collector | |
>>> pcgs = PcGroup.pcgs | |
>>> len(pcgs) | |
6 | |
>>> free_group = collector.free_group | |
>>> pc_resentation = collector.pc_presentation | |
>>> free_to_perm = {} | |
>>> for s, g in zip(free_group.symbols, pcgs): | |
... free_to_perm[s] = g | |
>>> for k, v in pc_resentation.items(): | |
... k_array = k.array_form | |
... if v != (): | |
... v_array = v.array_form | |
... lhs = Permutation() | |
... for gen in k_array: | |
... s = gen[0] | |
... e = gen[1] | |
... lhs = lhs*free_to_perm[s]**e | |
... if v == (): | |
... assert lhs.is_identity | |
... continue | |
... rhs = Permutation() | |
... for gen in v_array: | |
... s = gen[0] | |
... e = gen[1] | |
... rhs = rhs*free_to_perm[s]**e | |
... assert lhs == rhs | |
""" | |
free_group = self.free_group | |
rel_order = self.relative_order | |
pc_relators = {} | |
perm_to_free = {} | |
pcgs = self.pcgs | |
for gen, s in zip(pcgs, free_group.generators): | |
perm_to_free[gen**-1] = s**-1 | |
perm_to_free[gen] = s | |
pcgs = pcgs[::-1] | |
series = self.pc_series[::-1] | |
rel_order = rel_order[::-1] | |
collected_gens = [] | |
for i, gen in enumerate(pcgs): | |
re = rel_order[i] | |
relation = perm_to_free[gen]**re | |
G = series[i] | |
l = G.generator_product(gen**re, original = True) | |
l.reverse() | |
word = free_group.identity | |
for g in l: | |
word = word*perm_to_free[g] | |
word = self.collected_word(word) | |
pc_relators[relation] = word if word else () | |
self.pc_presentation = pc_relators | |
collected_gens.append(gen) | |
if len(collected_gens) > 1: | |
conj = collected_gens[len(collected_gens)-1] | |
conjugator = perm_to_free[conj] | |
for j in range(len(collected_gens)-1): | |
conjugated = perm_to_free[collected_gens[j]] | |
relation = conjugator**-1*conjugated*conjugator | |
gens = conj**-1*collected_gens[j]*conj | |
l = G.generator_product(gens, original = True) | |
l.reverse() | |
word = free_group.identity | |
for g in l: | |
word = word*perm_to_free[g] | |
word = self.collected_word(word) | |
pc_relators[relation] = word if word else () | |
self.pc_presentation = pc_relators | |
return pc_relators | |
def exponent_vector(self, element): | |
r""" | |
Return the exponent vector of length equal to the | |
length of polycyclic generating sequence. | |
Explanation | |
=========== | |
For a given generator/element ``g`` of the polycyclic group, | |
it can be represented as `g = {x_1}^{e_1}, \ldots, {x_n}^{e_n}`, | |
where `x_i` represents polycyclic generators and ``n`` is | |
the number of generators in the free_group equal to the length | |
of pcgs. | |
Parameters | |
========== | |
element : Permutation | |
Generator of a polycyclic group. | |
Examples | |
======== | |
>>> from sympy.combinatorics.named_groups import SymmetricGroup | |
>>> from sympy.combinatorics.permutations import Permutation | |
>>> G = SymmetricGroup(4) | |
>>> PcGroup = G.polycyclic_group() | |
>>> collector = PcGroup.collector | |
>>> pcgs = PcGroup.pcgs | |
>>> collector.exponent_vector(G[0]) | |
[1, 0, 0, 0] | |
>>> exp = collector.exponent_vector(G[1]) | |
>>> g = Permutation() | |
>>> for i in range(len(exp)): | |
... g = g*pcgs[i]**exp[i] if exp[i] else g | |
>>> assert g == G[1] | |
References | |
========== | |
.. [1] Holt, D., Eick, B., O'Brien, E. | |
"Handbook of Computational Group Theory" | |
Section 8.1.1, Definition 8.4 | |
""" | |
free_group = self.free_group | |
G = PermutationGroup() | |
for g in self.pcgs: | |
G = PermutationGroup([g] + G.generators) | |
gens = G.generator_product(element, original = True) | |
gens.reverse() | |
perm_to_free = {} | |
for sym, g in zip(free_group.generators, self.pcgs): | |
perm_to_free[g**-1] = sym**-1 | |
perm_to_free[g] = sym | |
w = free_group.identity | |
for g in gens: | |
w = w*perm_to_free[g] | |
word = self.collected_word(w) | |
index = self.index | |
exp_vector = [0]*len(free_group) | |
word = word.array_form | |
for t in word: | |
exp_vector[index[t[0]]] = t[1] | |
return exp_vector | |
def depth(self, element): | |
r""" | |
Return the depth of a given element. | |
Explanation | |
=========== | |
The depth of a given element ``g`` is defined by | |
`\mathrm{dep}[g] = i` if `e_1 = e_2 = \ldots = e_{i-1} = 0` | |
and `e_i != 0`, where ``e`` represents the exponent-vector. | |
Examples | |
======== | |
>>> from sympy.combinatorics.named_groups import SymmetricGroup | |
>>> G = SymmetricGroup(3) | |
>>> PcGroup = G.polycyclic_group() | |
>>> collector = PcGroup.collector | |
>>> collector.depth(G[0]) | |
2 | |
>>> collector.depth(G[1]) | |
1 | |
References | |
========== | |
.. [1] Holt, D., Eick, B., O'Brien, E. | |
"Handbook of Computational Group Theory" | |
Section 8.1.1, Definition 8.5 | |
""" | |
exp_vector = self.exponent_vector(element) | |
return next((i+1 for i, x in enumerate(exp_vector) if x), len(self.pcgs)+1) | |
def leading_exponent(self, element): | |
r""" | |
Return the leading non-zero exponent. | |
Explanation | |
=========== | |
The leading exponent for a given element `g` is defined | |
by `\mathrm{leading\_exponent}[g]` `= e_i`, if `\mathrm{depth}[g] = i`. | |
Examples | |
======== | |
>>> from sympy.combinatorics.named_groups import SymmetricGroup | |
>>> G = SymmetricGroup(3) | |
>>> PcGroup = G.polycyclic_group() | |
>>> collector = PcGroup.collector | |
>>> collector.leading_exponent(G[1]) | |
1 | |
""" | |
exp_vector = self.exponent_vector(element) | |
depth = self.depth(element) | |
if depth != len(self.pcgs)+1: | |
return exp_vector[depth-1] | |
return None | |
def _sift(self, z, g): | |
h = g | |
d = self.depth(h) | |
while d < len(self.pcgs) and z[d-1] != 1: | |
k = z[d-1] | |
e = self.leading_exponent(h)*(self.leading_exponent(k))**-1 | |
e = e % self.relative_order[d-1] | |
h = k**-e*h | |
d = self.depth(h) | |
return h | |
def induced_pcgs(self, gens): | |
""" | |
Parameters | |
========== | |
gens : list | |
A list of generators on which polycyclic subgroup | |
is to be defined. | |
Examples | |
======== | |
>>> from sympy.combinatorics.named_groups import SymmetricGroup | |
>>> S = SymmetricGroup(8) | |
>>> G = S.sylow_subgroup(2) | |
>>> PcGroup = G.polycyclic_group() | |
>>> collector = PcGroup.collector | |
>>> gens = [G[0], G[1]] | |
>>> ipcgs = collector.induced_pcgs(gens) | |
>>> [gen.order() for gen in ipcgs] | |
[2, 2, 2] | |
>>> G = S.sylow_subgroup(3) | |
>>> PcGroup = G.polycyclic_group() | |
>>> collector = PcGroup.collector | |
>>> gens = [G[0], G[1]] | |
>>> ipcgs = collector.induced_pcgs(gens) | |
>>> [gen.order() for gen in ipcgs] | |
[3] | |
""" | |
z = [1]*len(self.pcgs) | |
G = gens | |
while G: | |
g = G.pop(0) | |
h = self._sift(z, g) | |
d = self.depth(h) | |
if d < len(self.pcgs): | |
for gen in z: | |
if gen != 1: | |
G.append(h**-1*gen**-1*h*gen) | |
z[d-1] = h; | |
z = [gen for gen in z if gen != 1] | |
return z | |
def constructive_membership_test(self, ipcgs, g): | |
""" | |
Return the exponent vector for induced pcgs. | |
""" | |
e = [0]*len(ipcgs) | |
h = g | |
d = self.depth(h) | |
for i, gen in enumerate(ipcgs): | |
while self.depth(gen) == d: | |
f = self.leading_exponent(h)*self.leading_exponent(gen) | |
f = f % self.relative_order[d-1] | |
h = gen**(-f)*h | |
e[i] = f | |
d = self.depth(h) | |
if h == 1: | |
return e | |
return False | |