def is_normal(self, gr): """ test if G=self is a normal subgroup of gr G is normal in gr if for each g2 in G, g1 in gr, g = g1*g2*g1**-1 belongs to G It is sufficient to check this for each g1 in gr.generator and g2 g2 in G.generator Examples ======== >>> from sympy.combinatorics.permutations import Permutation >>> from sympy.combinatorics.perm_groups import PermutationGroup >>> a = Permutation([1, 2, 0]) >>> b = Permutation([1, 0, 2]) >>> G = PermutationGroup([a, b]) >>> G1 = PermutationGroup([a, Permutation([2, 0, 1])]) >>> G1.is_normal(G) True """ gens2 = [p.array_form for p in self.generators] gens1 = [p.array_form for p in gr.generators] for g1 in gens1: for g2 in gens2: p = perm_af_muln(g1, g2, perm_af_invert(g1)) if not self.coset_decomposition(p): return False return True
def insert(self, g, alpha): """ insert permutation `g` in stabilizer chain at point alpha """ n = len(g) if not g == self.idn: vertex = self.vertex jg = self.jg i = _smallest_change(g, alpha) ig = g[i] nn = vertex[i].index_neighbor[ig] if nn >= 0: # if ig is already neighbor of i jginn = jg[vertex[i].perm[nn]] if g != jginn: # cycle consisting of two edges; # replace jginn by g and insert h = g**-1*jginn g1 = perm_af_invert(g) h = perm_af_mul(g1, jginn) jg[ vertex[i].perm[nn] ] = g self.insert(h, alpha) else: # new edge self.insert_edge(g, i, ig) self.cycle = [i] if self.find_cycle(i, i, ig, -1): cycle = self.cycle cycle.append(cycle[0]) # find the smallest point (vertex) of the cycle minn = min(cycle) cmin = cycle.index(minn) # now walk around the cycle starting from the smallest # point, and multiply around the cycle to obtain h # satisfying h[cmin] = cmin ap = [] for c in range(cmin, len(cycle)-1) + range(cmin): i = cycle[c] j = cycle[c+1] nn = vertex[i].index_neighbor[j] p = jg[ vertex[i].perm[nn] ] if i > j: p = perm_af_invert(p) ap.append(p) ap.reverse() h = perm_af_muln(*ap) self.remove_edge(cycle[cmin], cycle[cmin + 1]) self.insert(h, alpha)
def insert(self, g, alpha): """ insert permutation `g` in stabilizer chain at point alpha """ n = len(g) if not g == self.idn: vertex = self.vertex jg = self.jg i = _smallest_change(g, alpha) ig = g[i] nn = vertex[i].index_neighbor[ig] if nn >= 0: # if ig is already neighbor of i jginn = jg[vertex[i].perm[nn]] if g != jginn: # cycle consisting of two edges; # replace jginn by g and insert h = g**-1*jginn g1 = perm_af_invert(g) h = perm_af_mul(g1, jginn) jg[vertex[i].perm[nn]] = g self.insert(h, alpha) else: # new edge self.insert_edge(g, i, ig) self.cycle = [i] if self.find_cycle(i, i, ig, -1): cycle = self.cycle cycle.append(cycle[0]) # find the smallest point (vertex) of the cycle minn = min(cycle) cmin = cycle.index(minn) # now walk around the cycle starting from the smallest # point, and multiply around the cycle to obtain h # satisfying h[cmin] = cmin ap = [] for c in range(cmin, len(cycle) - 1) + range(cmin): i = cycle[c] j = cycle[c + 1] nn = vertex[i].index_neighbor[j] p = jg[vertex[i].perm[nn]] if i > j: p = perm_af_invert(p) ap.append(p) ap.reverse() h = perm_af_muln(*ap) self.remove_edge(cycle[cmin], cycle[cmin + 1]) self.insert(h, alpha)
def coset_rank(self, g): """ rank using Schreier-Sims representation The coset rank of `g` is the ordering number in which it appears in the lexicographic listing according to the coset decomposition, see coset_decomposition; the ordering is the same as in G.generate(method='coset'). If `g` does not belong to the group it returns None Examples ======== >>> from sympy.combinatorics.permutations import Permutation >>> from sympy.combinatorics.perm_groups import PermutationGroup >>> a = Permutation([[0, 1, 3, 7, 6, 4], [2, 5]]) >>> b = Permutation([[0, 1, 3, 2], [4, 5, 7, 6]]) >>> G = PermutationGroup([a, b]) >>> c = Permutation([[0, 1, 2, 3, 4], [5, 6, 7]]) >>> G.coset_rank(c) >>> c = Permutation([[0, 6], [1, 7], [2, 4], [3, 5]]) >>> G.coset_rank(c) 40 >>> G.coset_unrank(40, af=True) [6, 7, 4, 5, 2, 3, 0, 1] """ u = self.coset_repr() if isinstance(g, Permutation): g = g.array_form g1 = g m = len(u) a = [] un = self._coset_repr_n n = self.degree rank = 0 base = [1] for i in un[m:0:-1]: base.append(base[-1]*i) base.reverse() a1 = [0]*m i1 = -1 for i in self._base: i1 += 1 x = g1[i] for j, h in enumerate(u[i]): if h[i] == x: a.append(h) a1[i] = j rank += j*base[i1] p2 = perm_af_invert(h) g1 = perm_af_mul(p2, g1) break else: return None if perm_af_muln(*a) == g: return rank return None
def coset_rank(self, g): """ rank using Schreier-Sims representation The coset rank of `g` is the ordering number in which it appears in the lexicographic listing according to the coset decomposition, see coset_decomposition; the ordering is the same as in G.generate(method='coset'). If `g` does not belong to the group it returns None Examples ======== >>> from sympy.combinatorics.permutations import Permutation >>> from sympy.combinatorics.perm_groups import PermutationGroup >>> a = Permutation([[0, 1, 3, 7, 6, 4], [2, 5]]) >>> b = Permutation([[0, 1, 3, 2], [4, 5, 7, 6]]) >>> G = PermutationGroup([a, b]) >>> c = Permutation([[0, 1, 2, 3, 4], [5, 6, 7]]) >>> G.coset_rank(c) >>> c = Permutation([[0, 6], [1, 7], [2, 4], [3, 5]]) >>> G.coset_rank(c) 40 >>> G.coset_unrank(40, af=True) [6, 7, 4, 5, 2, 3, 0, 1] """ u = self.coset_repr() if isinstance(g, Permutation): g = g.array_form g1 = g m = len(u) a = [] un = self._coset_repr_n n = self.degree rank = 0 base = [1] for i in un[m:0:-1]: base.append(base[-1] * i) base.reverse() a1 = [0] * m i1 = -1 for i in self._base: i1 += 1 x = g1[i] for j, h in enumerate(u[i]): if h[i] == x: a.append(h) a1[i] = j rank += j * base[i1] p2 = perm_af_invert(h) g1 = perm_af_mul(p2, g1) break else: return None if perm_af_muln(*a) == g: return rank return None
def coset_decomposition(self, g): """ Decompose `g` as h_0*...*h_{len(u)} The Schreier-Sims coset representation u of `G` gives a univoque decomposition of an element `g` as h_0*...*h_{len(u)}, where h_i belongs to u[i] Output: [h_0, .., h_{len(u)}] if `g` belongs to `G` False otherwise Examples ======== >>> from sympy.combinatorics.permutations import Permutation >>> from sympy.combinatorics.perm_groups import PermutationGroup >>> a = Permutation([[0, 1, 3, 7, 6, 4], [2, 5]]) >>> b = Permutation([[0, 1, 3, 2], [4, 5, 7, 6]]) >>> G = PermutationGroup([a, b]) >>> c = Permutation([[0, 1, 2, 3, 4], [5, 6, 7]]) >>> G.coset_decomposition(c) False >>> c = Permutation([[0, 6], [1, 7], [2, 4], [3, 5]]) >>> G.coset_decomposition(c) [[6, 4, 2, 0, 7, 5, 3, 1], [0, 4, 1, 5, 2, 6, 3, 7], [0, 1, 2, 3, 4, 5, 6, 7]] >>> G.has_element(c) True """ u = self.coset_repr() if isinstance(g, Permutation): g = g.array_form g1 = g n = len(u) a = [] for i in range(n): x = g1[i] for h in u[i]: if h[i] == x: a.append(h) p2 = perm_af_invert(h) g1 = perm_af_mul(p2, g1) break else: return False if perm_af_muln(*a) == g: return a return False
def commutator(self): """ commutator subgroup The commutator subgroup is the subgroup generated by all commutators; it is equal to the normal closure of the set of commutators of the generators. see http://groupprops.subwiki.org/wiki/Derived_subgroup Examples ======== >>> from sympy.combinatorics.permutations import Permutation >>> from sympy.combinatorics.perm_groups import PermutationGroup >>> a = Permutation([1, 0, 2, 4, 3]) >>> b = Permutation([0, 1, 3, 2, 4]) >>> G = PermutationGroup([a, b]) >>> C = G.commutator() >>> list(C.generate(af=True)) [[0, 1, 2, 3, 4], [0, 1, 3, 4, 2], [0, 1, 4, 2, 3]] """ r = self._r gens = [p.array_form for p in self.generators] gens_inv = [perm_af_invert(p) for p in gens] set_commutators = set() for i in range(r): for j in range(r): p1 = gens[i] p1inv = gens_inv[i] p2 = gens[j] p2inv = gens_inv[j] c = [p1[p2[p1inv[k]]] for k in p2inv] ct = tuple(c) if not ct in set_commutators: set_commutators.add(ct) cms = [Permutation(p) for p in set_commutators] G2 = self.normal_closure(cms) return G2
def normal_closure(self, gens): """ normal closure in self of a list gens2 of permutations Examples ======== >>> from sympy.combinatorics.permutations import Permutation >>> from sympy.combinatorics.perm_groups import PermutationGroup >>> a = Permutation([1, 2, 0]) >>> b = Permutation([1, 0, 2]) >>> G = PermutationGroup([a, b]) >>> G.order() 6 >>> G1 = G.normal_closure([a]) >>> list(G1.generate(af=True)) [[0, 1, 2], [1, 2, 0], [2, 0, 1]] """ G2 = PermutationGroup(gens) if G2.is_normal(self): return G2 gens1 = [p.array_form for p in self.generators] b = 0 while not b: gens2 = [p.array_form for p in G2.generators] b = 1 for g1 in gens1: if not b: break for g2 in gens2: p = perm_af_muln(g1, g2, perm_af_invert(g1)) p = Permutation(p) if not G2.has_element(p): gens2 = G2.generators + [p] G2 = PermutationGroup(gens2) b = 0 break return G2
def schreier_sims(self): """ Schreier-Sims algorithm. It computes the generators of the stabilizers chain G > G_{b_1} > .. > G_{b1,..,b_r} > 1 in which G_{b_1,..,b_i} stabilizes b_1,..,b_i, and the corresponding `s` cosets. An element of the group can be written univoquely as the product h_1*..*h_s. We use Jerrum's filter in our implementation of the Schreier-Sims algorithm. It runs in polynomial time. This implementation is a translation of the C++ implementation in http://www.m8j.net Examples ======== >>> from sympy.combinatorics.permutations import Permutation >>> from sympy.combinatorics.perm_groups import PermutationGroup >>> a = Permutation([0, 2, 1]) >>> b = Permutation([1, 0, 2]) >>> G = PermutationGroup([a, b]) >>> G.schreier_sims() >>> G.stabilizers_gens() [[0, 2, 1]] >>> G.coset_repr() [[[0, 1, 2], [1, 0, 2], [2, 0, 1]], [[0, 1, 2], [0, 2, 1]]] """ if self._coset_repr: return JGr = _JGraph(self) alpha = 0 n = JGr.n self._order = 1 coset_repr = [] num_generators = [] generators = [] gen = range(n) base = {} JGr.gens += [None]*(n - len(JGr.gens)) while 1: self._coset_repr_n = 0 self._coset_repr = [None]*n JGr.schreier_tree(alpha, gen) cri = [] for p in self._coset_repr: if not p: cri.append(p) else: cri.append(perm_af_invert(p)) JGr.jerrum_filter(alpha, cri) if self._coset_repr_n > 1: base[alpha] = self._coset_repr_n self._order *= self._coset_repr_n coset_repr.append([p for p in self._coset_repr if p]) d = {} for p in self._coset_repr: if p: d[p[alpha]] = p num_generators.append(JGr.r) if JGr.r: generators.extend(JGr.gens[:JGr.r]) if JGr.r <= 0: break alpha += 1 self._coset_repr = coset_repr a = [] for p in generators: if p not in a: a.append(p) self._stabilizers_gens = a i = len(JGr.gens) - 1 while not JGr.gens[i]: i -= 1 JGr.gens = JGr.gens[:i+1] self._base = base.keys() self._coset_repr_n = base.values()
def schreier_sims(self): """ Schreier-Sims algorithm. It computes the generators of the stabilizers chain G > G_{b_1} > .. > G_{b1,..,b_r} > 1 in which G_{b_1,..,b_i} stabilizes b_1,..,b_i, and the corresponding `s` cosets. An element of the group can be written univoquely as the product h_1*..*h_s. We use Jerrum's filter in our implementation of the Schreier-Sims algorithm. It runs in polynomial time. This implementation is a translation of the C++ implementation in http://www.m8j.net Examples ======== >>> from sympy.combinatorics.permutations import Permutation >>> from sympy.combinatorics.perm_groups import PermutationGroup >>> a = Permutation([0, 2, 1]) >>> b = Permutation([1, 0, 2]) >>> G = PermutationGroup([a, b]) >>> G.schreier_sims() >>> G.stabilizers_gens() [[0, 2, 1]] >>> G.coset_repr() [[[0, 1, 2], [1, 0, 2], [2, 0, 1]], [[0, 1, 2], [0, 2, 1]]] """ if self._coset_repr: return JGr = _JGraph(self) alpha = 0 n = JGr.n self._order = 1 coset_repr = [] num_generators = [] generators = [] gen = range(n) base = {} JGr.gens += [None] * (n - len(JGr.gens)) while 1: self._coset_repr_n = 0 self._coset_repr = [None] * n JGr.schreier_tree(alpha, gen) cri = [] for p in self._coset_repr: if not p: cri.append(p) else: cri.append(perm_af_invert(p)) JGr.jerrum_filter(alpha, cri) if self._coset_repr_n > 1: base[alpha] = self._coset_repr_n self._order *= self._coset_repr_n coset_repr.append([p for p in self._coset_repr if p]) d = {} for p in self._coset_repr: if p: d[p[alpha]] = p num_generators.append(JGr.r) if JGr.r: generators.extend(JGr.gens[:JGr.r]) if JGr.r <= 0: break alpha += 1 self._coset_repr = coset_repr a = [] for p in generators: if p not in a: a.append(p) self._stabilizers_gens = a i = len(JGr.gens) - 1 while not JGr.gens[i]: i -= 1 JGr.gens = JGr.gens[:i + 1] self._base = base.keys() self._coset_repr_n = base.values()