Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Incremental Schreier-Sims algorithm, and subgroup searching. #1454

Merged
merged 13 commits into from

5 participants

Aleksandar Makelov Don't Add Me To Your Organization a.k.a The Travis Bot Stefan Krastanov david joyner Aaron Meurer
Aleksandar Makelov

Implemented a version of the Schreier-Sims algorithm that takes a sequence of points and a generating set for a group and extends them to a base and strong generating set.

Implemented a procedure to remove redundant generators from a strong generating set.

Implemented the procedure subgroup_search which finds generators for the subgroup of all elements satisfying a given property in a larger group.

Don't Add Me To Your Organization a.k.a The Travis Bot

This pull request passes (merged a6253a17 into 02daa7d).

Stefan Krastanov
Collaborator

SymPy Bot Summary: :red_circle: There were test failures.

@amakelov: Please fix the test failures.

Test command: setup.py test
master hash: 02daa7d
branch hash: a6253a17db5652b928f21db5b05a45d837016786

Interpreter 1: :red_circle: There were test failures.

Interpreter: /usr/local/bin/python2.5 (2.5.6-final-0)
Architecture: Linux (64-bit)
Cache: yes

Test results html report: http://reviews.sympy.org/report/agZzeW1weTNyDAsSBFRhc2sYnbYiDA

Interpreter 2: :red_circle: There were test failures.

Interpreter: /usr/bin/python2.7 (2.7.3-candidate-2)
Architecture: Linux (64-bit)
Cache: yes

Test results html report: http://reviews.sympy.org/report/agZzeW1weTNyDAsSBFRhc2sY3PQiDA

Interpreter 3: :eight_spoked_asterisk: All tests have passed.

Interpreter: /usr/bin/python3.2 (3.2.3-candidate-2)
Architecture: Linux (64-bit)
Cache: yes

Test results html report: http://reviews.sympy.org/report/agZzeW1weTNyDAsSBFRhc2sYmI8iDA

Build HTML Docs: :red_circle: There were test failures.

Docs build command: make html-errors
Sphinx version: 1.1.3

Test results html report: http://reviews.sympy.org/report/agZzeW1weTNyDAsSBFRhc2sYn90iDA

Automatic review by SymPy Bot.

Don't Add Me To Your Organization a.k.a The Travis Bot

This pull request passes (merged 02198cca into 02daa7d).

Stefan Krastanov
Collaborator

SymPy Bot Summary: :red_circle: There were test failures.

@amakelov: Please fix the test failures.

Test command: setup.py test
master hash: bff3595
branch hash: 02198ccac5a5838a94afb875d5f23470da9a9fce

Interpreter 1: :red_circle: There were test failures.

Interpreter: /usr/local/bin/python2.5 (2.5.6-final-0)
Architecture: Linux (64-bit)
Cache: yes

Test results html report: http://reviews.sympy.org/report/agZzeW1weTNyDAsSBFRhc2sY8MUiDA

Interpreter 2: :red_circle: There were test failures.

Interpreter: /usr/bin/python2.7 (2.7.3-candidate-2)
Architecture: Linux (64-bit)
Cache: yes

Test results html report: http://reviews.sympy.org/report/agZzeW1weTNyDAsSBFRhc2sYiIwjDA

Interpreter 3: :eight_spoked_asterisk: All tests have passed.

Interpreter: /usr/bin/python3.2 (3.2.3-candidate-2)
Architecture: Linux (64-bit)
Cache: yes

Test results html report: http://reviews.sympy.org/report/agZzeW1weTNyDAsSBFRhc2sY4fQiDA

Build HTML Docs: :red_circle: There were test failures.

Docs build command: make html-errors
Sphinx version: 1.1.3

Test results html report: http://reviews.sympy.org/report/agZzeW1weTNyDAsSBFRhc2sY1M0iDA

Automatic review by SymPy Bot.

amakelov added some commits
Aleksandar Makelov amakelov Incremental Schreier-Sims algorithm.
It is used to extend a sequence of points and a generating set
to a base and strong generating set relative to it.
c592584
Aleksandar Makelov amakelov Tests for incremental Schreier-Sims algorithm. 2090a19
Aleksandar Makelov amakelov Remove redundant strong generators from a strong generating set.
Via the function _remove_gens in sympy/combinatorics/util.py
b852d89
Aleksandar Makelov amakelov Subgroup search and tests for _remove_gens.
The function subgroup_search is used to find a strong generating
set for the subgroup of all elements satisfying a given property
within a group.
454268c
Aleksandar Makelov amakelov Added tests for subgroup search. be08509
Aleksandar Makelov amakelov Removed the use of baseswap from subgroup search; minor changes.
Due to a bug in the version of subgroup_search using baseswap
to perform the base changes, the way basic stabilizers are
obtained is now via the stabilizer() function. The function
_insert_point_in_base was remove from sympy.combinatorics.util
since it's not needed right now.

Also, made the incremental Schreier-Sims algorithm exclude the
identity element as a generator and wrote some more tests
for subgroup_search
0cdee22
Aleksandar Makelov amakelov Removed unnecessary code from subgroup_search. 1ad8444
Aleksandar Makelov amakelov Added docstrings for the new functions. fabddb8
Aleksandar Makelov amakelov Minor style improvements. 8820277
Aleksandar Makelov amakelov Got rid of lines longer than 80 characters. ea648ae
Aleksandar Makelov amakelov Renamed all variables `distr_gens` to `strong_gens_distr`.
For more clarity, as suggested by my GSoC mentor David. This
variable is usually used to denote a list of strong generators
distributed by membership in basic stabilizers.
03f2c95
Aleksandar Makelov amakelov Fixed some issues with the docs. b4cc733
Aleksandar Makelov amakelov Removed side effects from schreier_sims_incremental. 4dffbde
Don't Add Me To Your Organization a.k.a The Travis Bot

This pull request passes (merged 4dffbde into 65b6582).

Stefan Krastanov
Collaborator

SymPy Bot Summary: :eight_spoked_asterisk: All tests have passed.

Test command: setup.py test
master hash: 3c2c3c7
branch hash: 4dffbde

Interpreter 1: :eight_spoked_asterisk: All tests have passed.

Interpreter: /usr/local/bin/python2.5 (2.5.6-final-0)
Architecture: Linux (64-bit)
Cache: yes

Test results html report: http://reviews.sympy.org/report/agZzeW1weTNyDAsSBFRhc2sYhsYiDA

Interpreter 2: :eight_spoked_asterisk: All tests have passed.

Interpreter: /usr/bin/python2.7 (2.7.3-candidate-2)
Architecture: Linux (64-bit)
Cache: yes

Test results html report: http://reviews.sympy.org/report/agZzeW1weTNyDAsSBFRhc2sY9JsjDA

Interpreter 3: :eight_spoked_asterisk: All tests have passed.

Interpreter: /usr/bin/python3.2 (3.2.3-candidate-2)
Architecture: Linux (64-bit)
Cache: yes

Test results html report: http://reviews.sympy.org/report/agZzeW1weTNyDAsSBFRhc2sY8f8hDA

Build HTML Docs: :eight_spoked_asterisk: All tests have passed.

Docs build command: make html-errors
Sphinx version: 1.1.3

Test results html report: http://reviews.sympy.org/report/agZzeW1weTNyDAsSBFRhc2sYp4wjDA

Automatic review by SymPy Bot.

david joyner

This patch looks good to me and it passes all tests.

Aaron Meurer
Owner

OK, I'll merge it then.

Aaron Meurer asmeurer merged commit c7a4a4a into from
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Aug 6, 2012
  1. Aleksandar Makelov

    Incremental Schreier-Sims algorithm.

    amakelov authored
    It is used to extend a sequence of points and a generating set
    to a base and strong generating set relative to it.
  2. Aleksandar Makelov
  3. Aleksandar Makelov

    Remove redundant strong generators from a strong generating set.

    amakelov authored
    Via the function _remove_gens in sympy/combinatorics/util.py
  4. Aleksandar Makelov

    Subgroup search and tests for _remove_gens.

    amakelov authored
    The function subgroup_search is used to find a strong generating
    set for the subgroup of all elements satisfying a given property
    within a group.
  5. Aleksandar Makelov
  6. Aleksandar Makelov

    Removed the use of baseswap from subgroup search; minor changes.

    amakelov authored
    Due to a bug in the version of subgroup_search using baseswap
    to perform the base changes, the way basic stabilizers are
    obtained is now via the stabilizer() function. The function
    _insert_point_in_base was remove from sympy.combinatorics.util
    since it's not needed right now.
    
    Also, made the incremental Schreier-Sims algorithm exclude the
    identity element as a generator and wrote some more tests
    for subgroup_search
  7. Aleksandar Makelov
  8. Aleksandar Makelov
  9. Aleksandar Makelov

    Minor style improvements.

    amakelov authored
  10. Aleksandar Makelov
  11. Aleksandar Makelov

    Renamed all variables `distr_gens` to `strong_gens_distr`.

    amakelov authored
    For more clarity, as suggested by my GSoC mentor David. This
    variable is usually used to denote a list of strong generators
    distributed by membership in basic stabilizers.
  12. Aleksandar Makelov
  13. Aleksandar Makelov
This page is out of date. Refresh to see the latest.
1  doc/src/modules/combinatorics/index.rst
View
@@ -16,3 +16,4 @@ Contents
subsets.rst
graycode.rst
named_groups.rst
+ util.rst
12 doc/src/modules/combinatorics/util.txt → doc/src/modules/combinatorics/util.rst
View
@@ -5,18 +5,20 @@ Utilities
.. module:: sympy.combinatorics.util
-.. autofunction:: _check_cycles_alt_sym
+.. autofunction:: _base_ordering
-.. autofunction:: _strip
+.. autofunction:: _check_cycles_alt_sym
.. autofunction:: _distribute_gens_by_base
-.. autofunction:: _strong_gens_from_distr
+.. autofunction:: _handle_precomputed_bsgs
.. autofunction:: _orbits_transversals_from_bsgs
-.. autofunction:: _handle_precomputed_bsgs
+.. autofunction:: _remove_gens
-.. autofunction:: _base_ordering
+.. autofunction:: _strip
+
+.. autofunction:: _strong_gens_from_distr
.. autofunction:: _verify_bsgs
4 sympy/combinatorics/named_groups.py
View
@@ -70,13 +70,13 @@ def AlternatingGroup(n):
"""
# small cases are special
- if n == 1 or n == 2:
+ if n in (1, 2):
return PermutationGroup([Permutation([0])])
a = range(n)
a[0], a[1], a[2] = a[1], a[2], a[0]
gen1 = _new_from_array_form(a)
- if n % 2 == 1:
+ if n % 2:
a = range(1, n)
a.append(0)
gen2 = _new_from_array_form(a)
441 sympy/combinatorics/perm_groups.py
View
@@ -475,7 +475,7 @@ def _random_pr_init(self, r, n, _random_prec_n=None):
Notes
=====
- THIS FUNCTION HAS SIDE EFFECTS: it changes the attribute
+ XXX THIS FUNCTION HAS SIDE EFFECTS: it changes the attribute
self._random_gens
See Also
@@ -619,8 +619,8 @@ def base(self):
self.schreier_sims()
return self._base
- def baseswap(self, base, strong_gens, pos, randomized=True,\
- transversals=None, basic_orbits=None, distr_gens=None):
+ def baseswap(self, base, strong_gens, pos, randomized=False,\
+ transversals=None, basic_orbits=None, strong_gens_distr=None):
r"""
Swap two consecutive base points in a base and strong generating set.
@@ -639,7 +639,7 @@ def baseswap(self, base, strong_gens, pos, randomized=True,\
``randomized`` - switch between randomized and deterministic version
``transversals`` - transversals for the basic orbits, if known
``basic_orbits`` - basic orbits, if known
- ``distr_gens`` - strong generators distributed by basic stabilizers,
+ ``strong_gens_distr`` - strong generators distributed by basic stabilizers,
if known
Returns
@@ -682,12 +682,12 @@ def baseswap(self, base, strong_gens, pos, randomized=True,\
"""
# construct the basic orbits, generators for the stabilizer chain
# and transversal elements from whatever was provided
- transversals, basic_orbits, distr_gens =\
+ transversals, basic_orbits, strong_gens_distr =\
_handle_precomputed_bsgs(base, strong_gens, transversals,\
- basic_orbits, distr_gens)
+ basic_orbits, strong_gens_distr)
base_len = len(base)
degree = self.degree
- stab_pos = PermutationGroup(distr_gens[pos])
+ stab_pos = PermutationGroup(strong_gens_distr[pos])
# size of orbit of base[pos] under the stabilizer we seek to insert
# in the stabilizer chain at position pos + 1
size = len(basic_orbits[pos])*len(basic_orbits[pos + 1])\
@@ -696,7 +696,7 @@ def baseswap(self, base, strong_gens, pos, randomized=True,\
if pos + 2 > base_len - 1:
T = []
else:
- T = distr_gens[pos + 2][:]
+ T = strong_gens_distr[pos + 2][:]
if T == []:
current_group = PermGroup([_new_from_array_form(range(degree))])
else:
@@ -733,7 +733,7 @@ def baseswap(self, base, strong_gens, pos, randomized=True,\
current_group = PermutationGroup(T)
Gamma = Gamma - current_group.orbit(base[pos])
# build the new base and strong generating set
- strong_gens_new_distr = distr_gens[:]
+ strong_gens_new_distr = strong_gens_distr[:]
strong_gens_new_distr[pos + 1] = T
base_new = base[:]
base_new[pos], base_new[pos + 1] = base_new[pos + 1], base_new[pos]
@@ -803,9 +803,9 @@ def basic_stabilizers(self):
self.schreier_sims()
strong_gens = self._strong_gens
base = self._base
- distr_gens = _distribute_gens_by_base(base, strong_gens)
+ strong_gens_distr = _distribute_gens_by_base(base, strong_gens)
basic_stabilizers = []
- for gens in distr_gens:
+ for gens in strong_gens_distr:
basic_stabilizers.append(PermutationGroup(gens))
return basic_stabilizers
@@ -1732,7 +1732,7 @@ def orbit(self, alpha, action='tuples'):
orb.append(temp)
used[temp] = True
return set(orb)
- if action == 'tuples':
+ elif action == 'tuples':
alpha = tuple(alpha)
orb = [alpha]
used = set([alpha])
@@ -1744,7 +1744,7 @@ def orbit(self, alpha, action='tuples'):
orb.append(temp)
used.add(temp)
return set(orb)
- if action == 'sets':
+ elif action == 'sets':
alpha = frozenset(alpha)
orb = [alpha]
used = set([alpha])
@@ -2081,6 +2081,150 @@ def schreier_sims(self):
self._transversals = transversals
self._basic_orbits = basic_orbits
+ def schreier_sims_incremental(self, base=None, gens=None):
+ """
+ Extend a sequence of points and generating set to a base and strong
+ generating set.
+
+ Parameters
+ ==========
+
+ ``base`` - the sequence of points to be extended to a base. Optional
+ parameter with default value ``[]``
+ ``gens`` - generating set to be extended to a strong generating set
+ relative to the base obtained. Optional parameter with default value
+ ``self.generators``
+
+ Returns
+ =======
+
+ ``(base, strong_gens)`` where ``base`` is the base obtained, and
+ ``strong_gens`` is the strong generating set relative to it. The
+ original parameters ``base``, ``gens`` remain unchanged.
+
+ Examples
+ ========
+
+ >>> from sympy.combinatorics.named_groups import AlternatingGroup
+ >>> from sympy.combinatorics.perm_groups import PermutationGroup
+ >>> from sympy.combinatorics.util import _verify_bsgs
+ >>> A = AlternatingGroup(7)
+ >>> base = [2, 3]
+ >>> seq = [2, 3]
+ >>> base, strong_gens = A.schreier_sims_incremental(base=seq)
+ >>> _verify_bsgs(A, base, strong_gens)
+ True
+ >>> base[:2]
+ [2, 3]
+
+ Notes
+ =====
+
+ This version of the Schreier-Sims algorithm runs in polynomial time.
+ There are certain assumptions in the implementation - if the trivial
+ group is provided, ``base`` and ``gens`` are returned immediately,
+ as any sequence of points is a base for the trivial group. If the
+ identity is present in the generators ``gens``, it is removed as
+ it is a redundant generator.
+ The implementation is described in [1],pp.90-93.
+
+ See Also
+ ========
+
+ schreier_sims, schreier_sims_random
+
+ """
+ if base is None:
+ base = []
+ if gens is None:
+ gens = self.generators[:]
+ base_len = len(base)
+ degree = self.degree
+ identity = _new_from_array_form(range(degree))
+ # handle the trivial group
+ if gens == [identity]:
+ return base, gens
+ # prevent side effects
+ _base, _gens = base[:], gens[:]
+ # remove the identity as a generator
+ _gens = [x for x in _gens if x != identity]
+ # make sure no generator fixes all base points
+ for gen in _gens:
+ if [gen(x) for x in _base] == [x for x in _base]:
+ new = 0
+ while gen(new) == new:
+ new += 1
+ _base.append(new)
+ base_len += 1
+ # distribute generators according to basic stabilizers
+ strong_gens_distr = _distribute_gens_by_base(_base, _gens)
+ # initialize the basic stabilizers, basic orbits and basic transversals
+ stabs = {}
+ orbs = {}
+ transversals = {}
+ for i in xrange(base_len):
+ stabs[i] = PermutationGroup(strong_gens_distr[i])
+ transversals[i] = dict(stabs[i].orbit_transversal(_base[i],\
+ pairs=True))
+ orbs[i] = transversals[i].keys()
+ # main loop: amend the stabilizer chain until we have generators
+ # for all stabilizers
+ i = base_len - 1
+ while i >= 0:
+ # this flag is used to continue with the main loop from inside
+ # a nested loop
+ continue_i = False
+ # test the generators for being a strong generating set
+ for beta in orbs[i]:
+ u_beta = transversals[i][beta]
+ for gen in strong_gens_distr[i]:
+ u_beta_gen = transversals[i][gen(beta)]
+ if gen*u_beta != u_beta_gen:
+ # test if the schreier generator is in the i+1-th
+ # would-be basic stabilizer
+ y = True
+ schreier_gen = (~u_beta_gen)*gen*u_beta
+ h, j = _strip(schreier_gen, _base, orbs, transversals)
+ if j <= base_len:
+ # new strong generator h at level j
+ y = False
+ elif h != _new_from_array_form(range(degree)):
+ # h fixes all base points
+ y = False
+ moved = 0
+ while h(moved) == moved:
+ moved += 1
+ _base.append(moved)
+ base_len += 1
+ strong_gens_distr.append([])
+ if y == False:
+ # if a new strong generator is found, update the
+ # data structures and start over
+ for l in range(i + 1, j):
+ strong_gens_distr[l].append(h)
+ stabs[l] = PermutationGroup(strong_gens_distr[l])
+ transversals[l] =\
+ dict(stabs[l].orbit_transversal(_base[l],\
+ pairs=True))
+ orbs[l] = transversals[l].keys()
+ i = j - 1
+ # continue main loop using the flag
+ continue_i = True
+ if continue_i == True:
+ break
+ if continue_i == True:
+ break
+ if continue_i == True:
+ continue
+ i -= 1
+ # build the strong generating set
+ strong_gens = []
+ for gens in strong_gens_distr:
+ for gen in gens:
+ if gen not in strong_gens:
+ strong_gens.append(gen)
+ return _base, strong_gens
+
def schreier_sims_random(self, base=None, gens=None, consec_succ=10,\
_random_prec=None):
r"""
@@ -2158,13 +2302,13 @@ def schreier_sims_random(self, base=None, gens=None, consec_succ=10,\
base.append(new)
base_len += 1
# distribute generators according to basic stabilizers
- distr_gens = _distribute_gens_by_base(base, gens)
+ strong_gens_distr = _distribute_gens_by_base(base, gens)
# initialize the basic stabilizers, basic transversals and basic orbits
stabs = {}
transversals = {}
orbs = {}
for i in xrange(base_len):
- stabs[i] = PermutationGroup(distr_gens[i])
+ stabs[i] = PermutationGroup(strong_gens_distr[i])
transversals[i] = dict(stabs[i].orbit_transversal(base[i],\
pairs=True))
orbs[i] = transversals[i].keys()
@@ -2189,13 +2333,13 @@ def schreier_sims_random(self, base=None, gens=None, consec_succ=10,\
moved += 1
base.append(moved)
base_len += 1
- distr_gens.append([])
+ strong_gens_distr.append([])
# if the element doesn't sift, amend the strong generators and
# associated stabilizers and orbits
if y == False:
for l in range(1, j):
- distr_gens[l].append(h)
- stabs[l] = PermutationGroup(distr_gens[l])
+ strong_gens_distr[l].append(h)
+ stabs[l] = PermutationGroup(strong_gens_distr[l])
transversals[l] = dict(stabs[l].orbit_transversal(base[l],\
pairs=True))
orbs[l] = transversals[l].keys()
@@ -2203,8 +2347,8 @@ def schreier_sims_random(self, base=None, gens=None, consec_succ=10,\
else:
c += 1
# build the strong generating set
- strong_gens = distr_gens[0][:]
- for gen in distr_gens[1]:
+ strong_gens = strong_gens_distr[0][:]
+ for gen in strong_gens_distr[1]:
if gen not in strong_gens:
strong_gens.append(gen)
return base, strong_gens
@@ -2361,6 +2505,263 @@ def strong_gens(self):
self.schreier_sims()
return self._strong_gens
+ def subgroup_search(self, prop, base=None, strong_gens=None, tests=None,\
+ init_subgroup=None):
+ """
+ Find the subgroup of all elements satisfying the property ``prop``.
+
+ This is done by a depth-first search with respect to base images that
+ uses several tests to prune the search tree.
+
+ Parameters
+ ==========
+
+ ``prop`` - the property to be used. Has to be callable on group
+ elements and always return ``True`` or ``False``. It is assumed that
+ all group elements satisfying ``prop`` indeed form a subgroup.
+ ``base`` - a base for the supergroup.
+ ``strong_gens`` - a strong generating set for the supergroup.
+ ``tests`` - list of callables of length equal to the length of ``base``.
+ These are used to rule out group elements by partial base images, so
+ that ``tests[l](g)`` returns False if the element ``g`` is known not
+ to satisfy prop base on where g sends the first ``l + 1`` base points.
+ ``init_subgroup`` - if a subgroup of the saught group is known in
+ advance, it can be passed to the function as this parameter.
+
+ Returns
+ =======
+
+ The subgroup of all elements satisfying ``prop``. The generating set
+ for this group is guaranteed to be a strong generating set relative to
+ the base ``base``.
+
+ Examples
+ ========
+
+ >>> from sympy.combinatorics.named_groups import (SymmetricGroup,
+ ... AlternatingGroup)
+ >>> from sympy.combinatorics.perm_groups import PermutationGroup
+ >>> from sympy.combinatorics.util import _verify_bsgs
+ >>> S = SymmetricGroup(7)
+ >>> prop_even = lambda x: x.is_even
+ >>> base, strong_gens = S.schreier_sims_incremental()
+ >>> G = S.subgroup_search(prop_even, base=base, strong_gens=strong_gens)
+ >>> G == AlternatingGroup(7)
+ True
+ >>> _verify_bsgs(G, base, G.generators)
+ True
+
+ Notes
+ =====
+
+ This function is extremely lenghty and complicated and will require
+ some careful attention. The implementation is described in
+ [1],pp.114-117, and the comments for the code here follow the lines
+ of the pseudocode in the book for clarity.
+ The complexity is exponential in general, since the search process by
+ itself visits all members of the supergroup. However, there are a lot
+ of tests which are used to prune the search tree, and users can define
+ their own tests via the ``tests`` parameter, so in practice, and for
+ some computations, it's not terrible.
+ A crucial part in the procedure is the frequent base change performed
+ (this is line 11 in the pseudocode) in order to obtain a new basic
+ stabilizer. The book mentiones that this can be done by using
+ ``.baseswap(...)``, however the current imlementation uses a more
+ straightforward way to find the next basic stabilizer - calling the
+ function ``.stabilizer(...)`` on the previous basic stabilizer.
+
+ """
+ # initialize BSGS and basic group properties
+ if base is None:
+ base, strong_gens = self.schreier_sims_incremental()
+ base_len = len(base)
+ degree = self.degree
+ identity = _new_from_array_form(range(degree))
+ base_ordering = _base_ordering(base, degree)
+ # add an element larger than all points
+ base_ordering.append(degree)
+ # add an element smaller than all points
+ base_ordering.append(-1)
+ # compute BSGS-related structures
+ strong_gens_distr = _distribute_gens_by_base(base, strong_gens)
+ basic_orbits, transversals = _orbits_transversals_from_bsgs(base,\
+ strong_gens_distr)
+ # handle subgroup initialization and tests
+ if init_subgroup is None:
+ init_subgroup = PermutationGroup([identity])
+ if tests is None:
+ trivial_test = lambda x: True
+ tests = []
+ for i in xrange(base_len):
+ tests.append(trivial_test)
+ # line 1: more initializations.
+ res = init_subgroup
+ f = base_len - 1
+ l = base_len - 1
+ # line 2: set the base for K to the base for G
+ res_base = base[:]
+ # line 3: compute BSGS and related structures for K
+ res_base, res_strong_gens = res.schreier_sims_incremental(base=res_base)
+ res_strong_gens_distr = _distribute_gens_by_base(res_base, res_strong_gens)
+ res_basic_orbits_init_base =\
+ [PermutationGroup(res_strong_gens_distr[i]).orbit(res_base[i])\
+ for i in range(base_len)]
+ # initialize orbit representatives
+ orbit_reps = [None]*base_len
+ # line 4: orbit representatives for f-th basic stabilizer of K
+ stab_f = PermutationGroup(res_strong_gens_distr[f])
+ orbits = stab_f.orbits()
+ reps = []
+ for orbit in orbits:
+ # get the minimal element in the base ordering
+ rep = min(orbit, key = lambda point: base_ordering[point])
+ reps.append(rep)
+ orbit_reps[f] = reps
+ # line 5: remove the base point from the representatives to avoid
+ # getting the identity element as a generator for K
+ orbit_reps[f].remove(base[f])
+ # line 6: more initializations
+ c = [0]*base_len
+ u = [identity]*base_len
+ sorted_orbits = [None]*base_len
+ for i in range(base_len):
+ sorted_orbits[i] = basic_orbits[i][:]
+ sorted_orbits[i].sort(key = lambda point: base_ordering[point])
+ # line 7: initializations
+ mu = [None]*base_len
+ nu = [None]*base_len
+ # this corresponds to the element smaller than all points
+ mu[l] = degree + 1
+ temp_index = len(basic_orbits[l])+1-len(res_basic_orbits_init_base[l])
+ if temp_index >= len(basic_orbits[l]):
+ # this corresponds to the element larger than all points
+ nu[l] = base_ordering[degree]
+ else:
+ nu[l] = sorted_orbits[l][temp_index]
+ # initialize computed words
+ computed_words = [identity]*base_len
+ # line 8: main loop
+ while True:
+ # apply all the tests
+ while l < base_len - 1 and\
+ computed_words[l](base[l]) in orbit_reps[l] and\
+ base_ordering[computed_words[l](base[l])] >\
+ base_ordering[mu[l]] and\
+ base_ordering[computed_words[l](base[l])] <\
+ base_ordering[nu[l]] and\
+ tests[l](computed_words[base_len - 1]):
+ # line 11: change the (partial) base of K
+ new_point = computed_words[l](base[l])
+ res_base[l] = new_point
+ temp_group = PermutationGroup(res_strong_gens_distr[l])
+ new_stab = temp_group.stabilizer(new_point)
+ res_strong_gens_distr[l + 1] = new_stab.generators
+ # line 12: calculate minimal orbit representatives for the
+ # l+1-th basic stabilizer
+ orbits = new_stab.orbits()
+ reps = []
+ for orbit in orbits:
+ rep = min(orbit, key = lambda point: base_ordering[point])
+ reps.append(rep)
+ orbit_reps[l + 1] = reps
+ # line 13: amend sorted orbits
+ l += 1
+ temp_orbit = [computed_words[l-1](point) for point\
+ in basic_orbits[l]]
+ temp_orbit.sort(key = lambda point: base_ordering[point])
+ sorted_orbits[l] = temp_orbit
+ # lines 14 and 15: update variables used minimality tests
+ new_mu = degree + 1
+ for i in range(l):
+ if base[l] in res_basic_orbits_init_base[i]:
+ candidate = computed_words[i](base[i])
+ if base_ordering[candidate] > base_ordering[new_mu]:
+ new_mu = candidate
+ mu[l] = new_mu
+ temp_index = len(basic_orbits[l]) + 1 -\
+ len(res_basic_orbits_init_base[l])
+ if temp_index >= len(sorted_orbits[l]):
+ nu[l] = base_ordering[degree]
+ else:
+ nu[l] = sorted_orbits[l][temp_index]
+ # line 16: determine the new transversal element
+ c[l] = 0
+ temp_point = sorted_orbits[l][c[l]]
+ temp_element = ~(computed_words[l - 1])
+ gamma = temp_element(temp_point)
+ u[l] = transversals[l][gamma]
+ # update computed words
+ computed_words[l] = computed_words[l-1] * u[l]
+ # lines 17 & 18: apply the tests to the group element found
+ g = computed_words[l]
+ temp_point = g(base[l])
+ if l == base_len - 1 and\
+ base_ordering[temp_point] > base_ordering[mu[l]] and\
+ base_ordering[temp_point] < base_ordering[nu[l]] and\
+ temp_point in orbit_reps[l] and\
+ tests[l](g) and\
+ prop(g):
+ # line 19: reset the base of K
+ gens = res.generators[:]
+ gens.append(g)
+ res = PermutationGroup(gens)
+ res_base = base[:]
+ # line 20: recalculate basic orbits (and transversals)
+ res_strong_gens.append(g)
+ res_strong_gens_distr = _distribute_gens_by_base(res_base,\
+ res_strong_gens)
+ res_basic_orbits_init_base =\
+ [PermutationGroup(res_strong_gens_distr[i]).orbit(res_base[i])\
+ for i in range(base_len)]
+ # line 21: recalculate orbit representatives
+ stab_f = PermutationGroup(res_strong_gens_distr[f])
+ temp_orbits = stab_f.orbits()
+ reps = []
+ for orbit in orbits:
+ rep = min(orbit, key = lambda point: base_ordering[point])
+ reps.append(rep)
+ orbit_reps[f] = reps
+ # line 22: reset the search depth
+ l = f
+ # line 23: go up the tree until in the first branch not fully
+ # searched
+ while l >= 0 and c[l] == len(basic_orbits[l]) - 1:
+ l = l - 1
+ # line 24: if the entire tree is traversed, return K
+ if l == -1:
+ return res
+ # lines 25-27: update orbit representatives
+ if l < f:
+ # line 26
+ f = l
+ c[l] = 0
+ # line 27
+ stab_f = PermutationGroup(res_strong_gens_distr[f])
+ temp_orbits = stab_f.orbits()
+ reps = []
+ for orbit in orbits:
+ rep = min(orbit, key = lambda point: base_ordering[point])
+ reps.append(rep)
+ orbit_reps[f] = reps
+ # line 28: update variables used for minimality testing
+ mu[l] = degree + 1
+ temp_index = len(basic_orbits[l]) + 1 -\
+ len(res_basic_orbits_init_base[l])
+ if temp_index >= len(sorted_orbits[l]):
+ nu[l] = base_ordering[degree]
+ else:
+ nu[l] = sorted_orbits[l][temp_index]
+ # line 29: set the next element from the current branch and update
+ # accorndingly
+ c[l] += 1
+ element = ~(computed_words[l - 1])
+ gamma = element(sorted_orbits[l][c[l]])
+ u[l] = transversals[l][gamma]
+ if l == 0:
+ computed_words[l] = u[l]
+ else:
+ computed_words[l] = computed_words[l - 1]*u[l]
+
@property
def transitivity_degree(self):
"""
55 sympy/combinatorics/tests/test_perm_groups.py
View
@@ -376,3 +376,58 @@ def test_direct_product_n():
H = DirectProduct(D, C)
assert H.order() == 32
assert H.is_abelian == False
+
+def test_schreier_sims_incremental():
+ identity = Permutation([0, 1, 2, 3, 4])
+ TrivialGroup = PermutationGroup([identity])
+ base, strong_gens = TrivialGroup.schreier_sims_incremental(base=[0, 1, 2])
+ assert _verify_bsgs(TrivialGroup, base, strong_gens) == True
+ S = SymmetricGroup(5)
+ base, strong_gens = S.schreier_sims_incremental(base=[0,1,2])
+ assert _verify_bsgs(S, base, strong_gens) == True
+ D = DihedralGroup(2)
+ base, strong_gens = D.schreier_sims_incremental(base=[1])
+ assert _verify_bsgs(D, base, strong_gens) == True
+ A = AlternatingGroup(7)
+ gens = A.generators[:]
+ gen0 = gens[0]
+ gen1 = gens[1]
+ gen1 = gen1*(~gen0)
+ gen0 = gen0*gen1
+ gen1 = gen0*gen1
+ base, strong_gens = A.schreier_sims_incremental(base=[0,1], gens=gens)
+ assert _verify_bsgs(A, base, strong_gens) == True
+ C = CyclicGroup(11)
+ gen = C.generators[0]
+ base, strong_gens = C.schreier_sims_incremental(gens=[gen**3])
+ assert _verify_bsgs(C, base, strong_gens) == True
+
+def test_subgroup_search():
+ prop_true = lambda x: True
+ prop_fix_points = lambda x: [x(point) for point in points] == points
+ prop_comm_g = lambda x: x*g == g*x
+ prop_even = lambda x: x.is_even
+ for i in range(10, 17, 2):
+ S = SymmetricGroup(i)
+ A = AlternatingGroup(i)
+ C = CyclicGroup(i)
+ Sym = S.subgroup_search(prop_true)
+ assert Sym == S
+ Alt = S.subgroup_search(prop_even)
+ assert Alt == A
+ Sym = S.subgroup_search(prop_true, init_subgroup=C)
+ assert Sym == S
+ points = [7]
+ assert S.stabilizer(7) == S.subgroup_search(prop_fix_points)
+ points = [3, 4]
+ assert S.stabilizer(3).stabilizer(4) == S.subgroup_search(prop_fix_points)
+ points = [3, 5]
+ fix35 = A.subgroup_search(prop_fix_points)
+ points = [5]
+ fix5 = A.subgroup_search(prop_fix_points)
+ assert A.subgroup_search(prop_fix_points, init_subgroup=fix35) == fix5
+ base, strong_gens = A.schreier_sims_incremental()
+ g = A.generators[0]
+ comm_g = A.subgroup_search(prop_comm_g, base=base, strong_gens=strong_gens)
+ assert _verify_bsgs(comm_g, base, comm_g.generators) == True
+ assert [prop_comm_g(gen) == True for gen in comm_g.generators]
28 sympy/combinatorics/tests/test_util.py
View
@@ -5,7 +5,7 @@
from sympy.combinatorics.util import _check_cycles_alt_sym, _strip,\
_distribute_gens_by_base, _strong_gens_from_distr,\
_orbits_transversals_from_bsgs, _handle_precomputed_bsgs, _base_ordering,\
-_verify_bsgs
+_verify_bsgs, _remove_gens
def test_check_cycles_alt_sym():
perm1 = Permutation([[0, 1, 2, 3, 4, 5, 6], [7], [8], [9]])
@@ -44,9 +44,9 @@ def test_distribute_gens_by_base():
Permutation([0, 1, 3, 2])]]
def test_strong_gens_from_distr():
- distr_gens = [[Permutation([0, 2, 1]), Permutation([1, 2, 0]),\
+ strong_gens_distr = [[Permutation([0, 2, 1]), Permutation([1, 2, 0]),\
Permutation([1, 0, 2])], [Permutation([0, 2, 1])]]
- assert _strong_gens_from_distr(distr_gens) ==\
+ assert _strong_gens_from_distr(strong_gens_distr) ==\
[Permutation([0, 2, 1]),\
Permutation([1, 2, 0]),\
Permutation([1, 0, 2])]
@@ -56,8 +56,8 @@ def test_orbits_transversals_from_bsgs():
S.schreier_sims()
base = S.base
strong_gens = S.strong_gens
- distr_gens = _distribute_gens_by_base(base, strong_gens)
- result = _orbits_transversals_from_bsgs(base, distr_gens)
+ strong_gens_distr = _distribute_gens_by_base(base, strong_gens)
+ result = _orbits_transversals_from_bsgs(base, strong_gens_distr)
orbits = result[0]
transversals = result[1]
base_len = len(base)
@@ -77,8 +77,8 @@ def test_handle_precomputed_bsgs():
base = A.base
strong_gens = A.strong_gens
result = _handle_precomputed_bsgs(base, strong_gens)
- distr_gens = _distribute_gens_by_base(base, strong_gens)
- assert distr_gens == result[2]
+ strong_gens_distr = _distribute_gens_by_base(base, strong_gens)
+ assert strong_gens_distr == result[2]
transversals = result[0]
orbits = result[1]
base_len = len(base)
@@ -106,3 +106,17 @@ def test_verify_bsgs():
assert _verify_bsgs(S, base, strong_gens) == True
assert _verify_bsgs(S, base[:-1], strong_gens) == False
assert _verify_bsgs(S, base, S.generators) == False
+
+def test_remove_gens():
+ S = SymmetricGroup(10)
+ base, strong_gens = S.schreier_sims_incremental()
+ new_gens = _remove_gens(base, strong_gens)
+ assert _verify_bsgs(S, base, new_gens) == True
+ A = AlternatingGroup(7)
+ base, strong_gens = A.schreier_sims_incremental()
+ new_gens = _remove_gens(base, strong_gens)
+ assert _verify_bsgs(A, base, new_gens) == True
+ D = DihedralGroup(2)
+ base, strong_gens = D.schreier_sims_incremental()
+ new_gens = _remove_gens(base, strong_gens)
+ assert _verify_bsgs(D, base, new_gens) == True
125 sympy/combinatorics/util.py
View
@@ -172,7 +172,7 @@ def _distribute_gens_by_base(base, gens):
return stabs
def _handle_precomputed_bsgs(base, strong_gens, transversals=None,\
- basic_orbits=None, distr_gens=None):
+ basic_orbits=None, strong_gens_distr=None):
"""
Calculate BSGS-related structures from those present.
@@ -187,15 +187,15 @@ def _handle_precomputed_bsgs(base, strong_gens, transversals=None,\
``strong_gens`` - the strong generators
``transversals`` - basic transversals
``basic_orbits`` - basic orbits
- ``distr_gens`` - strong generators distributed by membership in basic
+ ``strong_gens_distr`` - strong generators distributed by membership in basic
stabilizers
Returns
=======
- ``(transversals, basic_orbits, distr_gens)`` where ``transversals`` are the
+ ``(transversals, basic_orbits, strong_gens_distr)`` where ``transversals`` are the
basic transversals, ``basic_orbits`` are the basic orbits, and
- ``distr_gens`` are the strong generators distributed by membership in basic
+ ``strong_gens_distr`` are the strong generators distributed by membership in basic
stabilizers.
Examples
@@ -218,15 +218,15 @@ def _handle_precomputed_bsgs(base, strong_gens, transversals=None,\
_orbits_transversals_from_bsgs, distribute_gens_by_base
"""
- if distr_gens is None:
- distr_gens = _distribute_gens_by_base(base, strong_gens)
+ 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, distr_gens)
+ _orbits_transversals_from_bsgs(base, strong_gens_distr)
else:
transversals =\
- _orbits_transversals_from_bsgs(base, distr_gens,
+ _orbits_transversals_from_bsgs(base, strong_gens_distr,
transversals_only=True)
else:
if basic_orbits is None:
@@ -234,9 +234,9 @@ def _handle_precomputed_bsgs(base, strong_gens, transversals=None,\
basic_orbits = [None]*base_len
for i in xrange(base_len):
basic_orbits[i] = transversals[i].keys()
- return transversals, basic_orbits, distr_gens
+ return transversals, basic_orbits, strong_gens_distr
-def _orbits_transversals_from_bsgs(base, distr_gens,\
+def _orbits_transversals_from_bsgs(base, strong_gens_distr,\
transversals_only=False):
"""
Compute basic orbits and transversals from a base and strong generating set.
@@ -249,7 +249,7 @@ def _orbits_transversals_from_bsgs(base, distr_gens,\
==========
``base`` - the base
- ``distr_gens`` - strong generators distributed by membership in basic
+ ``strong_gens_distr`` - strong generators distributed by membership in basic
stabilizers
``transversals_only`` - a flag swithing between returning only the
transversals/ both orbits and transversals
@@ -263,8 +263,8 @@ def _orbits_transversals_from_bsgs(base, distr_gens,\
... _distribute_gens_by_base)
>>> S = SymmetricGroup(3)
>>> S.schreier_sims()
- >>> distr_gens = _distribute_gens_by_base(S.base, S.strong_gens)
- >>> _orbits_transversals_from_bsgs(S.base, distr_gens)
+ >>> strong_gens_distr = _distribute_gens_by_base(S.base, S.strong_gens)
+ >>> _orbits_transversals_from_bsgs(S.base, strong_gens_distr)
([[0, 1, 2], [1, 2]], [{0: Permutation([0, 1, 2]),\
1: Permutation([1, 2, 0]), 2: Permutation([2, 0, 1])},\
{1: Permutation([0, 1, 2]), 2: Permutation([0, 2, 1])}])
@@ -281,7 +281,7 @@ def _orbits_transversals_from_bsgs(base, distr_gens,\
if transversals_only is False:
basic_orbits = [None]*base_len
for i in xrange(base_len):
- group = PermutationGroup(distr_gens[i])
+ group = PermutationGroup(strong_gens_distr[i])
transversals[i] = dict(group.orbit_transversal(base[i], pairs=True))
if transversals_only is False:
basic_orbits[i] = transversals[i].keys()
@@ -290,6 +290,83 @@ def _orbits_transversals_from_bsgs(base, distr_gens,\
else:
return basic_orbits, transversals
+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.named_groups import SymmetricGroup
+ >>> from sympy.combinatorics.perm_groups import PermutationGroup
+ >>> from sympy.combinatorics.util import _remove_gens, _verify_bsgs
+ >>> S = SymmetricGroup(15)
+ >>> base, strong_gens = S.schreier_sims_incremental()
+ >>> len(strong_gens)
+ 26
+ >>> 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 PermutationGroup
+ base_len = len(base)
+ degree = strong_gens[0].size
+ identity = _new_from_array_form(range(degree))
+ if strong_gens_distr is None:
+ strong_gens_distr = _distribute_gens_by_base(base, strong_gens)
+ temp = strong_gens_distr[:]
+ if basic_orbits is None:
+ basic_orbits = []
+ for i in range(base_len):
+ stab = PermutationGroup(strong_gens_distr[i])
+ basic_orbit = stab.orbit(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_group = PermutationGroup(temp_gens)
+ temp_orbit = temp_group.orbit(base[i])
+ if temp_orbit == basic_orbits[i]:
+ gens_copy.remove(gen)
+ res.remove(gen)
+ return res
+
def _strip(g, base, orbs, transversals):
"""
Attempt to decompose a permutation using a (possibly partial) BSGS
@@ -363,7 +440,7 @@ def _strip(g, base, orbs, transversals):
h = ~u*h
return h, base_len + 1
-def _strong_gens_from_distr(distr_gens):
+def _strong_gens_from_distr(strong_gens_distr):
"""
Retrieve strong generating set from generators of basic stabilizers.
@@ -373,7 +450,7 @@ def _strong_gens_from_distr(distr_gens):
Parameters
==========
- ``distr_gens`` - strong generators distributed by membership in basic
+ ``strong_gens_distr`` - strong generators distributed by membership in basic
stabilizers
Examples
@@ -386,8 +463,8 @@ def _strong_gens_from_distr(distr_gens):
>>> S.schreier_sims()
>>> S.strong_gens
[Permutation([1, 2, 0]), Permutation([1, 0, 2]), Permutation([0, 2, 1])]
- >>> distr_gens = _distribute_gens_by_base(S.base, S.strong_gens)
- >>> _strong_gens_from_distr(distr_gens)
+ >>> strong_gens_distr = _distribute_gens_by_base(S.base, S.strong_gens)
+ >>> _strong_gens_from_distr(strong_gens_distr)
[Permutation([1, 2, 0]), Permutation([1, 0, 2]), Permutation([0, 2, 1])]
See Also
@@ -396,11 +473,11 @@ def _strong_gens_from_distr(distr_gens):
_distribute_gens_by_base
"""
- if len(distr_gens) == 1:
- return distr_gens[0][:]
+ if len(strong_gens_distr) == 1:
+ return strong_gens_distr[0][:]
else:
- result = distr_gens[0]
- for gen in distr_gens[1]:
+ result = strong_gens_distr[0]
+ for gen in strong_gens_distr[1]:
if gen not in result:
result.append(gen)
return result
@@ -431,12 +508,12 @@ def _verify_bsgs(group, base, gens):
"""
from sympy.combinatorics.perm_groups import PermutationGroup
- distr_gens = _distribute_gens_by_base(base, gens)
+ strong_gens_distr = _distribute_gens_by_base(base, gens)
base_len = len(base)
degree = group.degree
current_stabilizer = group
for i in range(base_len):
- candidate = PermutationGroup(distr_gens[i])
+ candidate = PermutationGroup(strong_gens_distr[i])
if current_stabilizer.order() != candidate.order():
return False
current_stabilizer = current_stabilizer.stabilizer(base[i])
Something went wrong with that request. Please try again.