def DihedralGroup(n): r""" Generates the dihedral group `D_n` as a permutation group. The dihedral group `D_n` is the group of symmetries of the regular ``n``-gon. The generators taken are the ``n``-cycle ``a = (0 1 2 ... n-1)`` (a rotation of the ``n``-gon) and ``b = (0 n-1)(1 n-2)...`` (a reflection of the ``n``-gon) in cycle rotation. It is easy to see that these satisfy ``a**n = b**2 = 1`` and ``bab = ~a`` so they indeed generate `D_n` (See [1]). After the group is generated, some of its basic properties are set. Examples ======== >>> from sympy.combinatorics.named_groups import DihedralGroup >>> G = DihedralGroup(5) >>> a = list(G.generate_dimino()) >>> [perm.cyclic_form for perm in a] [[[4], [3], [2], [1], [0]], [[0, 1, 2, 3, 4]], [[0, 2, 4, 1, 3]], [[0, 3, 1, 4, 2]], [[0, 4, 3, 2, 1]], [[2], [1, 3], [0, 4]], [[2, 3], [1, 4], [0]], [[3], [2, 4], [0, 1]], [[3, 4], [1], [0, 2]], [[4], [1, 2], [0, 3]]] See Also ======== SymmetricGroup, CyclicGroup, AlternatingGroup References ========== [1] http://en.wikipedia.org/wiki/Dihedral_group """ # small cases are special if n == 1: return PermutationGroup([Permutation([1, 0])]) if n == 2: return PermutationGroup([ Permutation([1, 0, 3, 2]), Permutation([2, 3, 0, 1]), Permutation([3, 2, 1, 0]) ]) a = range(1, n) a.append(0) gen1 = _new_from_array_form(a) a = range(n) a.reverse() gen2 = _new_from_array_form(a) G = PermutationGroup([gen1, gen2]) G._is_abelian = False G._degree = n G._is_transitive = True G._order = 2 * n return G
def AlternatingGroup(n): """ Generates the alternating group on ``n`` elements as a permutation group. For ``n > 2``, the generators taken are ``(0 1 2), (0 1 2 ... n-1)`` for ``n`` odd and ``(0 1 2), (1 2 ... n-1)`` for ``n`` even (See [1], p.31, ex.6.9.). After the group is generated, some of its basic properties are set. The cases ``n = 1, 2`` are handled separately. Examples ======== >>> from sympy.combinatorics.named_groups import AlternatingGroup >>> G = AlternatingGroup(4) >>> a = list(G.generate_dimino()) >>> len(a) 12 >>> [perm.is_even for perm in a] [True, True, True, True, True, True, True, True, True, True, True, True] See Also ======== SymmetricGroup, CyclicGroup, DihedralGroup References ========== [1] Armstrong, M. "Groups and Symmetry" """ # small cases are special 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: a = range(1, n) a.append(0) gen2 = _new_from_array_form(a) else: a = range(2, n) a.append(1) gen2 = _new_from_array_form([0] + a) G = PermutationGroup([gen1, gen2]) if n < 4: G._is_abelian = True else: G._is_abelian = False G._degree = n G._is_transitive = True G._is_alt = True return G
def AlternatingGroup(n): """ Generates the alternating group on ``n`` elements as a permutation group. For ``n > 2``, the generators taken are ``(0 1 2), (0 1 2 ... n-1)`` for ``n`` odd and ``(0 1 2), (1 2 ... n-1)`` for ``n`` even (See [1], p.31, ex.6.9.). After the group is generated, some of its basic properties are set. The cases ``n = 1, 2`` are handled separately. Examples ======== >>> from sympy.combinatorics.named_groups import AlternatingGroup >>> G = AlternatingGroup(4) >>> a = list(G.generate_dimino()) >>> len(a) 12 >>> [perm.is_even for perm in a] [True, True, True, True, True, True, True, True, True, True, True, True] See Also ======== SymmetricGroup, CyclicGroup, DihedralGroup References ========== [1] Armstrong, M. "Groups and Symmetry" """ # small cases are special 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: a = range(1, n) a.append(0) gen2 = _new_from_array_form(a) else: a = range(2, n) a.append(1) gen2 = _new_from_array_form([0] + a) G = PermutationGroup([gen1, gen2]) if n<4: G._is_abelian = True else: G._is_abelian = False G._degree = n G._is_transitive = True G._is_alt = True return G
def generate_dimino(self, af=False): """ yield group elements using Dimino's algorithm If af == True it yields the array form of the permutations Reference: [1] The implementation of various algorithms for Permutation Groups in the Computer Algebra System: AXIOM, N.J. Doye, M.Sc. Thesis Examples ======== >>> from sympy.combinatorics.permutations import Permutation >>> from sympy.combinatorics.perm_groups import PermutationGroup >>> a = Permutation([0, 2, 1, 3]) >>> b = Permutation([0, 2, 3, 1]) >>> g = PermutationGroup([a, b]) >>> list(g.generate_dimino(af=True)) [[0, 1, 2, 3], [0, 2, 1, 3], [0, 2, 3, 1], [0, 1, 3, 2], [0, 3, 2, 1], [0, 3, 1, 2]] """ idn = range(self.degree) order = 0 element_list = [idn] set_element_list = set([tuple(idn)]) if af: yield idn else: yield _new_from_array_form(idn) gens = [p.array_form for p in self.generators] for i in xrange(len(gens)): # D elements of the subgroup G_i generated by gens[:i] D = element_list[:] N = [idn] while N: A = N N = [] for a in A: for g in gens[:i + 1]: ag = perm_af_mul(a, g) if tuple(ag) not in set_element_list: # produce G_i*g for d in D: order += 1 ap = perm_af_mul(d, ag) if af: yield ap else: p = _new_from_array_form(ap) yield p element_list.append(ap) set_element_list.add(tuple(ap)) N.append(ap) self._order = len(element_list)
def generate_dimino(self, af=False): """ yield group elements using Dimino's algorithm If af == True it yields the array form of the permutations Reference: [1] The implementation of various algorithms for Permutation Groups in the Computer Algebra System: AXIOM, N.J. Doye, M.Sc. Thesis Examples ======== >>> from sympy.combinatorics.permutations import Permutation >>> from sympy.combinatorics.perm_groups import PermutationGroup >>> a = Permutation([0, 2, 1, 3]) >>> b = Permutation([0, 2, 3, 1]) >>> g = PermutationGroup([a, b]) >>> list(g.generate_dimino(af=True)) [[0, 1, 2, 3], [0, 2, 1, 3], [0, 2, 3, 1], [0, 1, 3, 2], [0, 3, 2, 1], [0, 3, 1, 2]] """ idn = range(self.degree) order = 0 element_list = [idn] set_element_list = set([tuple(idn)]) if af: yield idn else: yield _new_from_array_form(idn) gens = [p.array_form for p in self.generators] for i in xrange(len(gens)): # D elements of the subgroup G_i generated by gens[:i] D = element_list[:] N = [idn] while N: A = N N = [] for a in A: for g in gens[:i+1]: ag = perm_af_mul(a, g) if tuple(ag) not in set_element_list: # produce G_i*g for d in D: order += 1 ap = perm_af_mul(d, ag) if af: yield ap else: p = _new_from_array_form(ap) yield p element_list.append(ap) set_element_list.add(tuple(ap)) N.append(ap) self._order = len(element_list)
def SymmetricGroup(n): """ Generates the symmetric group on ``n`` elements as a permutation group. The generators taken are the ``n``-cycle ``(0 1 2 ... n-1)`` and the transposition ``(0 1)`` (in cycle notation). (See [1]). After the group is generated, some of its basic properties are set. Examples ======== >>> from sympy.combinatorics.named_groups import SymmetricGroup >>> G = SymmetricGroup(4) >>> G.order() 24 >>> list(G.generate_schreier_sims(af=True)) [[0, 1, 2, 3], [0, 1, 3, 2], [0, 2, 1, 3], [0, 2, 3, 1], [0, 3, 1, 2], [0, 3, 2, 1], [1, 2, 3, 0], [1, 2, 0, 3], [1, 3, 2, 0], [1, 3, 0, 2], [1, 0, 2, 3], [1, 0, 3, 2], [2, 3, 0, 1], [2, 3, 1, 0], [2, 0, 3, 1], [2, 0, 1, 3], [2, 1, 3, 0], [2, 1, 0, 3], [3, 0, 1, 2], [3, 0, 2, 1], [3, 1, 0, 2], [3, 1, 2, 0], [3, 2, 0, 1], [3, 2, 1, 0]] See Also ======== CyclicGroup, DihedralGroup, AlternatingGroup References ========== [1] http://en.wikipedia.org/wiki/Symmetric_group#Generators_and_relations """ if n == 1: G = PermutationGroup([Permutation([0])]) elif n == 2: G = PermutationGroup([Permutation([1, 0])]) else: a = range(1,n) a.append(0) gen1 = _new_from_array_form(a) a = range(n) a[0], a[1] = a[1], a[0] gen2 = _new_from_array_form(a) G = PermutationGroup([gen1, gen2]) if n<3: G._is_abelian = True else: G._is_abelian = False G._degree = n G._is_transitive = True G._is_sym = True return G
def DihedralGroup(n): r""" Generates the dihedral group `D_n` as a permutation group. The dihedral group `D_n` is the group of symmetries of the regular ``n``-gon. The generators taken are the ``n``-cycle ``a = (0 1 2 ... n-1)`` (a rotation of the ``n``-gon) and ``b = (0 n-1)(1 n-2)...`` (a reflection of the ``n``-gon) in cycle rotation. It is easy to see that these satisfy ``a**n = b**2 = 1`` and ``bab = ~a`` so they indeed generate `D_n` (See [1]). After the group is generated, some of its basic properties are set. Examples ======== >>> from sympy.combinatorics.named_groups import DihedralGroup >>> G = DihedralGroup(5) >>> a = list(G.generate_dimino()) >>> [perm.cyclic_form for perm in a] [[[4], [3], [2], [1], [0]], [[0, 1, 2, 3, 4]], [[0, 2, 4, 1, 3]], [[0, 3, 1, 4, 2]], [[0, 4, 3, 2, 1]], [[2], [1, 3], [0, 4]], [[2, 3], [1, 4], [0]], [[3], [2, 4], [0, 1]], [[3, 4], [1], [0, 2]], [[4], [1, 2], [0, 3]]] See Also ======== SymmetricGroup, CyclicGroup, AlternatingGroup References ========== [1] http://en.wikipedia.org/wiki/Dihedral_group """ # small cases are special if n == 1: return PermutationGroup([Permutation([1, 0])]) if n == 2: return PermutationGroup([Permutation([1, 0, 3, 2]), Permutation([2, 3, 0, 1]), Permutation([3, 2, 1, 0])]) a = range(1, n) a.append(0) gen1 = _new_from_array_form(a) a = range(n) a.reverse() gen2 = _new_from_array_form(a) G = PermutationGroup([gen1, gen2]) G._is_abelian = False G._degree = n G._is_transitive = True G._order = 2*n return G
def SymmetricGroup(n): """ Generates the symmetric group on ``n`` elements as a permutation group. The generators taken are the ``n``-cycle ``(0 1 2 ... n-1)`` and the transposition ``(0 1)`` (in cycle notation). (See [1]). After the group is generated, some of its basic properties are set. Examples ======== >>> from sympy.combinatorics.named_groups import SymmetricGroup >>> G = SymmetricGroup(4) >>> G.order() 24 >>> list(G.generate_schreier_sims(af=True)) [[0, 1, 2, 3], [0, 1, 3, 2], [0, 2, 1, 3], [0, 2, 3, 1], [0, 3, 1, 2], [0, 3, 2, 1], [1, 2, 3, 0], [1, 2, 0, 3], [1, 3, 2, 0], [1, 3, 0, 2], [1, 0, 2, 3], [1, 0, 3, 2], [2, 3, 0, 1], [2, 3, 1, 0], [2, 0, 3, 1], [2, 0, 1, 3], [2, 1, 3, 0], [2, 1, 0, 3], [3, 0, 1, 2], [3, 0, 2, 1], [3, 1, 0, 2], [3, 1, 2, 0], [3, 2, 0, 1], [3, 2, 1, 0]] See Also ======== CyclicGroup, DihedralGroup, AlternatingGroup References ========== [1] http://en.wikipedia.org/wiki/Symmetric_group#Generators_and_relations """ if n == 1: G = PermutationGroup([Permutation([0])]) elif n == 2: G = PermutationGroup([Permutation([1, 0])]) else: a = range(1, n) a.append(0) gen1 = _new_from_array_form(a) a = range(n) a[0], a[1] = a[1], a[0] gen2 = _new_from_array_form(a) G = PermutationGroup([gen1, gen2]) if n < 3: G._is_abelian = True else: G._is_abelian = False G._degree = n G._is_transitive = True G._is_sym = True return G
def coset_unrank(self, rank, af=False): """ unrank using Schreier-Sims representation coset_unrank is the inverse operation of coset_rank if 0 <= rank < order; otherwise it returns None. """ u = self.coset_repr() if rank < 0 or rank >= self.order(): return None un = self._coset_repr_n base = self._base m = len(u) nb = len(base) assert nb == len(un) v = [0] * m for i in range(nb - 1, -1, -1): j = base[i] rank, c = divmod(rank, un[i]) v[j] = c a = [u[i][v[i]] for i in range(m)] h = perm_af_muln(*a) if af: return h else: return _new_from_array_form(h)
def CyclicGroup(n): """ Generates the cyclic group of order ``n`` as a permutation group. The generator taken is the ``n``-cycle ``(0 1 2 ... n-1)`` (in cycle notation). After the group is generated, some of its basic properties are set. Examples ======== >>> from sympy.combinatorics.named_groups import CyclicGroup >>> G = CyclicGroup(6) >>> G.order() 6 >>> list(G.generate_schreier_sims(af=True)) [[0, 1, 2, 3, 4, 5], [1, 2, 3, 4, 5, 0], [2, 3, 4, 5, 0, 1], [3, 4, 5, 0, 1, 2], [4, 5, 0, 1, 2, 3], [5, 0, 1, 2, 3, 4]] See Also ======== SymmetricGroup, DihedralGroup, AlternatingGroup """ a = range(1, n) a.append(0) gen = _new_from_array_form(a) G = PermutationGroup([gen]) G._is_abelian = True G._degree = n G._is_transitive = True G._order = n return G
def coset_unrank(self, rank, af=False): """ unrank using Schreier-Sims representation coset_unrank is the inverse operation of coset_rank if 0 <= rank < order; otherwise it returns None. """ u = self.coset_repr() if rank < 0 or rank >= self.order(): return None un = self._coset_repr_n base = self._base m = len(u) nb = len(base) assert nb == len(un) v = [0]*m for i in range(nb-1, -1,-1): j = base[i] rank, c = divmod(rank, un[i]) v[j] = c a = [u[i][v[i]] for i in range(m)] h = perm_af_muln(*a) if af: return h else: return _new_from_array_form(h)
def generate_schreier_sims(self, af=False): """ yield group elements using the Schreier-Sims representation If af = True it yields the array form of the permutations Examples ======== >>> from sympy.combinatorics.permutations import Permutation >>> from sympy.combinatorics.perm_groups import PermutationGroup >>> a = Permutation([0, 2, 1, 3]) >>> b = Permutation([0, 2, 3, 1]) >>> g = PermutationGroup([a, b]) >>> list(g.generate_schreier_sims(af=True)) [[0, 1, 2, 3], [0, 1, 3, 2], [0, 2, 3, 1], [0, 2, 1, 3], [0, 3, 2, 1], [0, 3, 1, 2]] """ def get1(posmax): n = len(posmax) - 1 for i in range(n,-1,-1): if posmax[i] != 1: return i + 1 n = self.degree u = self.coset_repr() # stg stack of group elements stg = [range(n)] # posmax[i] = len(u[i]) posmax = [len(x) for x in u] n1 = get1(posmax) pos = [0]*n1 posmax = posmax[:n1] h = 0 while 1: # backtrack when finished iterating over coset if pos[h] >= posmax[h]: if h == 0: raise StopIteration pos[h] = 0 h -= 1 stg.pop() continue p = perm_af_mul(stg[-1], u[h][pos[h]]) pos[h] += 1 stg.append(p) h += 1 if h == n1: if af: yield p else: p1 = _new_from_array_form(p) yield p1 stg.pop() h -= 1
def generate_schreier_sims(self, af=False): """ yield group elements using the Schreier-Sims representation If af = True it yields the array form of the permutations Examples ======== >>> from sympy.combinatorics.permutations import Permutation >>> from sympy.combinatorics.perm_groups import PermutationGroup >>> a = Permutation([0, 2, 1, 3]) >>> b = Permutation([0, 2, 3, 1]) >>> g = PermutationGroup([a, b]) >>> list(g.generate_schreier_sims(af=True)) [[0, 1, 2, 3], [0, 1, 3, 2], [0, 2, 3, 1], [0, 2, 1, 3], [0, 3, 2, 1], [0, 3, 1, 2]] """ def get1(posmax): n = len(posmax) - 1 for i in range(n, -1, -1): if posmax[i] != 1: return i + 1 n = self.degree u = self.coset_repr() # stg stack of group elements stg = [range(n)] # posmax[i] = len(u[i]) posmax = [len(x) for x in u] n1 = get1(posmax) pos = [0] * n1 posmax = posmax[:n1] h = 0 while 1: # backtrack when finished iterating over coset if pos[h] >= posmax[h]: if h == 0: raise StopIteration pos[h] = 0 h -= 1 stg.pop() continue p = perm_af_mul(stg[-1], u[h][pos[h]]) pos[h] += 1 stg.append(p) h += 1 if h == n1: if af: yield p else: p1 = _new_from_array_form(p) yield p1 stg.pop() h -= 1
def DirectProduct(*groups): """ Returns the direct product of several groups as a permutation group. This is implemented much like the __mul__ procedure for taking the direct product of two permutation groups, but the idea of shifting the generators is realized in the case of an arbitrary number of groups. A call to DirectProduct(G1, G2, ..., Gn) is generally expected to be faster than a call to G1*G2*...*Gn (and thus the need for this algorithm). Examples ======== >>> from sympy.combinatorics.group_constructs import DirectProduct >>> from sympy.combinatorics.named_groups import CyclicGroup >>> C = CyclicGroup(4) >>> G = DirectProduct(C,C,C) >>> G.order() 64 See Also ======== __mul__ """ degrees = [] gens_count = [] total_degree = 0 total_gens = 0 for group in groups: current_deg = group.degree current_num_gens = len(group.generators) degrees.append(current_deg) total_degree += current_deg gens_count.append(current_num_gens) total_gens += current_num_gens array_gens = [] for i in range(total_gens): array_gens.append(range(total_degree)) current_gen = 0 current_deg = 0 for i in xrange(len(gens_count)): for j in xrange(current_gen, current_gen + gens_count[i]): gen = ((groups[i].generators)[j - current_gen]).array_form array_gens[j][current_deg:current_deg + degrees[i]] =\ [ x + current_deg for x in gen] current_gen += gens_count[i] current_deg += degrees[i] perm_gens = [_new_from_array_form(array) for array in array_gens] return PermutationGroup(perm_gens)
def stabilizer(self, alpha): """ return the stabilizer subgroup leaving alpha fixed. Examples ======== >>> from sympy.combinatorics.permutations import Permutation >>> from sympy.combinatorics.perm_groups import PermutationGroup >>> a = Permutation([0, 2, 1, 3]) >>> b = Permutation([2, 0, 1, 3]) >>> g = PermutationGroup([a, b]) >>> g2 = g.stabilizer(2) >>> g2 PermutationGroup([Permutation([1, 0, 2, 3])]) """ if alpha == 0: self.schreier_sims() gens = self._stabilizers_gens gens = [_new_from_array_form(p) for p in gens] return PermutationGroup(gens) # h[alpha] = 0; h[0] = alpha n = self.degree h = range(n) h[0] = alpha h[alpha] = 0 genv = [p.array_form for p in self.generators] # conjugate the group to breng alpha to 0 gens = [_new_from_array_form(perm_af_muln(h, p, h)) for p in genv] G = PermutationGroup(gens) G.schreier_sims() # stabilizers for 0 gens1 = G._stabilizers_gens # conjugate the group gens2 = [_new_from_array_form(perm_af_muln(h, p, h)) for p in gens1] return PermutationGroup(gens2)
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 >>> from sympy.combinatorics.testutil import _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 _distribute_gens_by_base(base, gens): """ Distribute the group elements ``gens`` by membership in basic stabilizers. Notice that for a base `(b_1, b_2, ..., b_k)`, the basic stabilizers are defined as `G^{(i)} = G_{b_1, ..., b_{i-1}}` for `i \in\{1, 2, ..., k\}`. Parameters ========== ``base`` - a sequence of points in `\{0, 1, ..., n-1\}` ``gens`` - a list of elements of a permutation group of degree `n`. Returns ======= 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 [Permutation([1, 2, 0]), Permutation([2, 1, 0]), Permutation([0, 2, 1])] >>> D.base [0, 1] >>> _distribute_gens_by_base(D.base, D.strong_gens) [[Permutation([1, 2, 0]), Permutation([2, 1, 0]), Permutation([0, 2, 1])],\ [Permutation([0, 2, 1])]] See Also ======== _strong_gens_from_distr, _orbits_transversals_from_bsgs, _handle_precomputed_bsgs """ base_len = len(base) stabs = [] degree = gens[0].size for i in xrange(base_len): stabs.append([]) num_gens = len(gens) max_stab_index = 0 for i in xrange(num_gens): j = 0 while j < base_len - 1 and gens[i](base[j]) == base[j]: j += 1 if j > max_stab_index: max_stab_index = j for k in xrange(j + 1): stabs[k].append(gens[i]) for i in range(max_stab_index + 1, base_len): stabs[i].append(_new_from_array_form(range(degree))) return stabs
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