Spaces:
Sleeping
Sleeping
from sympy.combinatorics.permutations import Permutation, _af_invert, _af_rmul | |
from sympy.ntheory import isprime | |
rmul = Permutation.rmul | |
_af_new = Permutation._af_new | |
############################################ | |
# | |
# Utilities for computational group theory | |
# | |
############################################ | |
def _base_ordering(base, degree): | |
r""" | |
Order `\{0, 1, \dots, n-1\}` so that base points come first and in order. | |
Parameters | |
========== | |
base : the base | |
degree : the degree of the associated permutation group | |
Returns | |
======= | |
A list ``base_ordering`` such that ``base_ordering[point]`` is the | |
number of ``point`` in the ordering. | |
Examples | |
======== | |
>>> from sympy.combinatorics import SymmetricGroup | |
>>> from sympy.combinatorics.util import _base_ordering | |
>>> S = SymmetricGroup(4) | |
>>> S.schreier_sims() | |
>>> _base_ordering(S.base, S.degree) | |
[0, 1, 2, 3] | |
Notes | |
===== | |
This is used in backtrack searches, when we define a relation `\ll` on | |
the underlying set for a permutation group of degree `n`, | |
`\{0, 1, \dots, n-1\}`, so that if `(b_1, b_2, \dots, b_k)` is a base we | |
have `b_i \ll b_j` whenever `i<j` and `b_i \ll a` for all | |
`i\in\{1,2, \dots, k\}` and `a` is not in the base. The idea is developed | |
and applied to backtracking algorithms in [1], pp.108-132. The points | |
that are not in the base are taken in increasing order. | |
References | |
========== | |
.. [1] Holt, D., Eick, B., O'Brien, E. | |
"Handbook of computational group theory" | |
""" | |
base_len = len(base) | |
ordering = [0]*degree | |
for i in range(base_len): | |
ordering[base[i]] = i | |
current = base_len | |
for i in range(degree): | |
if i not in base: | |
ordering[i] = current | |
current += 1 | |
return ordering | |
def _check_cycles_alt_sym(perm): | |
""" | |
Checks for cycles of prime length p with n/2 < p < n-2. | |
Explanation | |
=========== | |
Here `n` is the degree of the permutation. This is a helper function for | |
the function is_alt_sym from sympy.combinatorics.perm_groups. | |
Examples | |
======== | |
>>> from sympy.combinatorics.util import _check_cycles_alt_sym | |
>>> from sympy.combinatorics import Permutation | |
>>> a = Permutation([[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10], [11, 12]]) | |
>>> _check_cycles_alt_sym(a) | |
False | |
>>> b = Permutation([[0, 1, 2, 3, 4, 5, 6], [7, 8, 9, 10]]) | |
>>> _check_cycles_alt_sym(b) | |
True | |
See Also | |
======== | |
sympy.combinatorics.perm_groups.PermutationGroup.is_alt_sym | |
""" | |
n = perm.size | |
af = perm.array_form | |
current_len = 0 | |
total_len = 0 | |
used = set() | |
for i in range(n//2): | |
if i not in used and i < n//2 - total_len: | |
current_len = 1 | |
used.add(i) | |
j = i | |
while af[j] != i: | |
current_len += 1 | |
j = af[j] | |
used.add(j) | |
total_len += current_len | |
if current_len > n//2 and current_len < n - 2 and isprime(current_len): | |
return True | |
return False | |
def _distribute_gens_by_base(base, gens): | |
r""" | |
Distribute the group elements ``gens`` by membership in basic stabilizers. | |
Explanation | |
=========== | |
Notice that for a base `(b_1, b_2, \dots, b_k)`, the basic stabilizers | |
are defined as `G^{(i)} = G_{b_1, \dots, b_{i-1}}` for | |
`i \in\{1, 2, \dots, k\}`. | |
Parameters | |
========== | |
base : a sequence of points in `\{0, 1, \dots, n-1\}` | |
gens : a list of elements of a permutation group of degree `n`. | |
Returns | |
======= | |
list | |
List of length `k`, where `k` is the length of *base*. The `i`-th entry | |
contains those elements in *gens* which fix the first `i` elements of | |
*base* (so that the `0`-th entry is equal to *gens* itself). If no | |
element fixes the first `i` elements of *base*, the `i`-th element is | |
set to a list containing the identity element. | |
Examples | |
======== | |
>>> from sympy.combinatorics.named_groups import DihedralGroup | |
>>> from sympy.combinatorics.util import _distribute_gens_by_base | |
>>> D = DihedralGroup(3) | |
>>> D.schreier_sims() | |
>>> D.strong_gens | |
[(0 1 2), (0 2), (1 2)] | |
>>> D.base | |
[0, 1] | |
>>> _distribute_gens_by_base(D.base, D.strong_gens) | |
[[(0 1 2), (0 2), (1 2)], | |
[(1 2)]] | |
See Also | |
======== | |
_strong_gens_from_distr, _orbits_transversals_from_bsgs, | |
_handle_precomputed_bsgs | |
""" | |
base_len = len(base) | |
degree = gens[0].size | |
stabs = [[] for _ in range(base_len)] | |
max_stab_index = 0 | |
for gen in gens: | |
j = 0 | |
while j < base_len - 1 and gen._array_form[base[j]] == base[j]: | |
j += 1 | |
if j > max_stab_index: | |
max_stab_index = j | |
for k in range(j + 1): | |
stabs[k].append(gen) | |
for i in range(max_stab_index + 1, base_len): | |
stabs[i].append(_af_new(list(range(degree)))) | |
return stabs | |
def _handle_precomputed_bsgs(base, strong_gens, transversals=None, | |
basic_orbits=None, strong_gens_distr=None): | |
""" | |
Calculate BSGS-related structures from those present. | |
Explanation | |
=========== | |
The base and strong generating set must be provided; if any of the | |
transversals, basic orbits or distributed strong generators are not | |
provided, they will be calculated from the base and strong generating set. | |
Parameters | |
========== | |
base : the base | |
strong_gens : the strong generators | |
transversals : basic transversals | |
basic_orbits : basic orbits | |
strong_gens_distr : strong generators distributed by membership in basic stabilizers | |
Returns | |
======= | |
(transversals, basic_orbits, strong_gens_distr) | |
where *transversals* are the basic transversals, *basic_orbits* are the | |
basic orbits, and *strong_gens_distr* are the strong generators distributed | |
by membership in basic stabilizers. | |
Examples | |
======== | |
>>> from sympy.combinatorics.named_groups import DihedralGroup | |
>>> from sympy.combinatorics.util import _handle_precomputed_bsgs | |
>>> D = DihedralGroup(3) | |
>>> D.schreier_sims() | |
>>> _handle_precomputed_bsgs(D.base, D.strong_gens, | |
... basic_orbits=D.basic_orbits) | |
([{0: (2), 1: (0 1 2), 2: (0 2)}, {1: (2), 2: (1 2)}], [[0, 1, 2], [1, 2]], [[(0 1 2), (0 2), (1 2)], [(1 2)]]) | |
See Also | |
======== | |
_orbits_transversals_from_bsgs, _distribute_gens_by_base | |
""" | |
if strong_gens_distr is None: | |
strong_gens_distr = _distribute_gens_by_base(base, strong_gens) | |
if transversals is None: | |
if basic_orbits is None: | |
basic_orbits, transversals = \ | |
_orbits_transversals_from_bsgs(base, strong_gens_distr) | |
else: | |
transversals = \ | |
_orbits_transversals_from_bsgs(base, strong_gens_distr, | |
transversals_only=True) | |
else: | |
if basic_orbits is None: | |
base_len = len(base) | |
basic_orbits = [None]*base_len | |
for i in range(base_len): | |
basic_orbits[i] = list(transversals[i].keys()) | |
return transversals, basic_orbits, strong_gens_distr | |
def _orbits_transversals_from_bsgs(base, strong_gens_distr, | |
transversals_only=False, slp=False): | |
""" | |
Compute basic orbits and transversals from a base and strong generating set. | |
Explanation | |
=========== | |
The generators are provided as distributed across the basic stabilizers. | |
If the optional argument ``transversals_only`` is set to True, only the | |
transversals are returned. | |
Parameters | |
========== | |
base : The base. | |
strong_gens_distr : Strong generators distributed by membership in basic stabilizers. | |
transversals_only : bool, default: False | |
A flag switching between returning only the | |
transversals and both orbits and transversals. | |
slp : bool, default: False | |
If ``True``, return a list of dictionaries containing the | |
generator presentations of the elements of the transversals, | |
i.e. the list of indices of generators from ``strong_gens_distr[i]`` | |
such that their product is the relevant transversal element. | |
Examples | |
======== | |
>>> from sympy.combinatorics import SymmetricGroup | |
>>> from sympy.combinatorics.util import _distribute_gens_by_base | |
>>> S = SymmetricGroup(3) | |
>>> S.schreier_sims() | |
>>> strong_gens_distr = _distribute_gens_by_base(S.base, S.strong_gens) | |
>>> (S.base, strong_gens_distr) | |
([0, 1], [[(0 1 2), (2)(0 1), (1 2)], [(1 2)]]) | |
See Also | |
======== | |
_distribute_gens_by_base, _handle_precomputed_bsgs | |
""" | |
from sympy.combinatorics.perm_groups import _orbit_transversal | |
base_len = len(base) | |
degree = strong_gens_distr[0][0].size | |
transversals = [None]*base_len | |
slps = [None]*base_len | |
if transversals_only is False: | |
basic_orbits = [None]*base_len | |
for i in range(base_len): | |
transversals[i], slps[i] = _orbit_transversal(degree, strong_gens_distr[i], | |
base[i], pairs=True, slp=True) | |
transversals[i] = dict(transversals[i]) | |
if transversals_only is False: | |
basic_orbits[i] = list(transversals[i].keys()) | |
if transversals_only: | |
return transversals | |
else: | |
if not slp: | |
return basic_orbits, transversals | |
return basic_orbits, transversals, slps | |
def _remove_gens(base, strong_gens, basic_orbits=None, strong_gens_distr=None): | |
""" | |
Remove redundant generators from a strong generating set. | |
Parameters | |
========== | |
base : a base | |
strong_gens : a strong generating set relative to *base* | |
basic_orbits : basic orbits | |
strong_gens_distr : strong generators distributed by membership in basic stabilizers | |
Returns | |
======= | |
A strong generating set with respect to ``base`` which is a subset of | |
``strong_gens``. | |
Examples | |
======== | |
>>> from sympy.combinatorics import SymmetricGroup | |
>>> from sympy.combinatorics.util import _remove_gens | |
>>> from sympy.combinatorics.testutil import _verify_bsgs | |
>>> S = SymmetricGroup(15) | |
>>> base, strong_gens = S.schreier_sims_incremental() | |
>>> new_gens = _remove_gens(base, strong_gens) | |
>>> len(new_gens) | |
14 | |
>>> _verify_bsgs(S, base, new_gens) | |
True | |
Notes | |
===== | |
This procedure is outlined in [1],p.95. | |
References | |
========== | |
.. [1] Holt, D., Eick, B., O'Brien, E. | |
"Handbook of computational group theory" | |
""" | |
from sympy.combinatorics.perm_groups import _orbit | |
base_len = len(base) | |
degree = strong_gens[0].size | |
if strong_gens_distr is None: | |
strong_gens_distr = _distribute_gens_by_base(base, strong_gens) | |
if basic_orbits is None: | |
basic_orbits = [] | |
for i in range(base_len): | |
basic_orbit = _orbit(degree, strong_gens_distr[i], base[i]) | |
basic_orbits.append(basic_orbit) | |
strong_gens_distr.append([]) | |
res = strong_gens[:] | |
for i in range(base_len - 1, -1, -1): | |
gens_copy = strong_gens_distr[i][:] | |
for gen in strong_gens_distr[i]: | |
if gen not in strong_gens_distr[i + 1]: | |
temp_gens = gens_copy[:] | |
temp_gens.remove(gen) | |
if temp_gens == []: | |
continue | |
temp_orbit = _orbit(degree, temp_gens, base[i]) | |
if temp_orbit == basic_orbits[i]: | |
gens_copy.remove(gen) | |
res.remove(gen) | |
return res | |
def _strip(g, base, orbits, transversals): | |
""" | |
Attempt to decompose a permutation using a (possibly partial) BSGS | |
structure. | |
Explanation | |
=========== | |
This is done by treating the sequence ``base`` as an actual base, and | |
the orbits ``orbits`` and transversals ``transversals`` as basic orbits and | |
transversals relative to it. | |
This process is called "sifting". A sift is unsuccessful when a certain | |
orbit element is not found or when after the sift the decomposition | |
does not end with the identity element. | |
The argument ``transversals`` is a list of dictionaries that provides | |
transversal elements for the orbits ``orbits``. | |
Parameters | |
========== | |
g : permutation to be decomposed | |
base : sequence of points | |
orbits : list | |
A list in which the ``i``-th entry is an orbit of ``base[i]`` | |
under some subgroup of the pointwise stabilizer of ` | |
`base[0], base[1], ..., base[i - 1]``. The groups themselves are implicit | |
in this function since the only information we need is encoded in the orbits | |
and transversals | |
transversals : list | |
A list of orbit transversals associated with the orbits *orbits*. | |
Examples | |
======== | |
>>> from sympy.combinatorics import Permutation, SymmetricGroup | |
>>> from sympy.combinatorics.util import _strip | |
>>> S = SymmetricGroup(5) | |
>>> S.schreier_sims() | |
>>> g = Permutation([0, 2, 3, 1, 4]) | |
>>> _strip(g, S.base, S.basic_orbits, S.basic_transversals) | |
((4), 5) | |
Notes | |
===== | |
The algorithm is described in [1],pp.89-90. The reason for returning | |
both the current state of the element being decomposed and the level | |
at which the sifting ends is that they provide important information for | |
the randomized version of the Schreier-Sims algorithm. | |
References | |
========== | |
.. [1] Holt, D., Eick, B., O'Brien, E."Handbook of computational group theory" | |
See Also | |
======== | |
sympy.combinatorics.perm_groups.PermutationGroup.schreier_sims | |
sympy.combinatorics.perm_groups.PermutationGroup.schreier_sims_random | |
""" | |
h = g._array_form | |
base_len = len(base) | |
for i in range(base_len): | |
beta = h[base[i]] | |
if beta == base[i]: | |
continue | |
if beta not in orbits[i]: | |
return _af_new(h), i + 1 | |
u = transversals[i][beta]._array_form | |
h = _af_rmul(_af_invert(u), h) | |
return _af_new(h), base_len + 1 | |
def _strip_af(h, base, orbits, transversals, j, slp=[], slps={}): | |
""" | |
optimized _strip, with h, transversals and result in array form | |
if the stripped elements is the identity, it returns False, base_len + 1 | |
j h[base[i]] == base[i] for i <= j | |
""" | |
base_len = len(base) | |
for i in range(j+1, base_len): | |
beta = h[base[i]] | |
if beta == base[i]: | |
continue | |
if beta not in orbits[i]: | |
if not slp: | |
return h, i + 1 | |
return h, i + 1, slp | |
u = transversals[i][beta] | |
if h == u: | |
if not slp: | |
return False, base_len + 1 | |
return False, base_len + 1, slp | |
h = _af_rmul(_af_invert(u), h) | |
if slp: | |
u_slp = slps[i][beta][:] | |
u_slp.reverse() | |
u_slp = [(i, (g,)) for g in u_slp] | |
slp = u_slp + slp | |
if not slp: | |
return h, base_len + 1 | |
return h, base_len + 1, slp | |
def _strong_gens_from_distr(strong_gens_distr): | |
""" | |
Retrieve strong generating set from generators of basic stabilizers. | |
This is just the union of the generators of the first and second basic | |
stabilizers. | |
Parameters | |
========== | |
strong_gens_distr : strong generators distributed by membership in basic stabilizers | |
Examples | |
======== | |
>>> from sympy.combinatorics import SymmetricGroup | |
>>> from sympy.combinatorics.util import (_strong_gens_from_distr, | |
... _distribute_gens_by_base) | |
>>> S = SymmetricGroup(3) | |
>>> S.schreier_sims() | |
>>> S.strong_gens | |
[(0 1 2), (2)(0 1), (1 2)] | |
>>> strong_gens_distr = _distribute_gens_by_base(S.base, S.strong_gens) | |
>>> _strong_gens_from_distr(strong_gens_distr) | |
[(0 1 2), (2)(0 1), (1 2)] | |
See Also | |
======== | |
_distribute_gens_by_base | |
""" | |
if len(strong_gens_distr) == 1: | |
return strong_gens_distr[0][:] | |
else: | |
result = strong_gens_distr[0] | |
for gen in strong_gens_distr[1]: | |
if gen not in result: | |
result.append(gen) | |
return result | |