Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

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

Merged
merged 13 commits into from

5 participants

@amakelov

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.

@travisbot

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

@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.

@travisbot

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

@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
@amakelov 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
@amakelov amakelov Tests for incremental Schreier-Sims algorithm. 2090a19
@amakelov amakelov Remove redundant strong generators from a strong generating set.
Via the function _remove_gens in sympy/combinatorics/util.py
b852d89
@amakelov 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
@amakelov amakelov Added tests for subgroup search. be08509
@amakelov 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
@amakelov amakelov Removed unnecessary code from subgroup_search. 1ad8444
@amakelov amakelov Added docstrings for the new functions. fabddb8
@amakelov amakelov Minor style improvements. 8820277
@amakelov amakelov Got rid of lines longer than 80 characters. ea648ae
@amakelov 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
@amakelov amakelov Fixed some issues with the docs. b4cc733
@amakelov amakelov Removed side effects from schreier_sims_incremental. 4dffbde
@travisbot

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

@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.

@wdjoyner

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

@asmeurer
Owner

OK, I'll merge it then.

@asmeurer asmeurer merged commit c7a4a4a into sympy:master
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Aug 6, 2012
  1. @amakelov

    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. @amakelov
  3. @amakelov

    Remove redundant strong generators from a strong generating set.

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

    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. @amakelov
  6. @amakelov

    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. @amakelov
  8. @amakelov
  9. @amakelov

    Minor style improvements.

    amakelov authored
  10. @amakelov
  11. @amakelov

    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. @amakelov
  13. @amakelov
This page is out of date. Refresh to see the latest.
View
1  doc/src/modules/combinatorics/index.rst
@@ -16,3 +16,4 @@ Contents
subsets.rst
graycode.rst
named_groups.rst
+ util.rst
View
12 doc/src/modules/combinatorics/util.txt → doc/src/modules/combinatorics/util.rst
@@ -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
View
4 sympy/combinatorics/named_groups.py
@@ -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)
View
441 sympy/combinatorics/perm_groups.py
@@ -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):
"""
View
55 sympy/combinatorics/tests/test_perm_groups.py
@@ -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]
View
28 sympy/combinatorics/tests/test_util.py
@@ -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
View
125 sympy/combinatorics/util.py
@@ -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.