switching to high quality piper tts and added label translations
This commit is contained in:
@@ -0,0 +1,532 @@
|
||||
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
|
||||
Reference in New Issue
Block a user