Improving Permutation Groups in Sage

Existing Code

Goals

Goal 1: Order, membership testing, and element enumeration

The algorithm for doing this was (almost) first described in section 4 of Charles Sims' 1970 paper, *Computational methods for permutation groups*. It is based on Schreier's Lemma and is thus called the Schreier-Sims method.

Definitions of base and strong generating set.

Knuth's article with specific recommendations about data structures and algorithmic specifics. Sage implementation of this article:

def check_perm(perm, n):
    if len(perm) != n+1 or sorted(perm) != range(n+1):
        raise RuntimeError("Incorrect format for perm.")

def invert_perm(perm):
    # checks
    check_perm(perm, len(perm)-1)

    # work
    L = [None]*len(perm)
    for i in range(len(perm)):
        L[perm[i]] = i
    return L

def multiply_perms(perm1, perm2):
    # checks
    assert len(perm1) == len(perm2)
    n = len(perm1)-1
    check_perm(perm1, n)
    check_perm(perm2, n)

    # work
    return [perm1[perm2[i]] for i in range(n+1)]

class StabChainKnuth:
    def __init__(self, n):
        # checks
        n = Integer(n)
        assert n >= 1

        # work
        self.degree = n
        self.sigmas = [None]
        self.T = [None]
        for i in range(1,n+1):
            self.sigmas.append([None]*i + [range(n+1)])
            self.T.append( [range(n+1)] )
        # Sigma(k) == [a for a in self.sigmas[k] if a is not None]
        
    def sigma(self, k, j):
        # checks
        k = Integer(k); j = Integer(j)
        assert self.degree >= k and k >= j and j >= 1
        
        # work
        return self.sigmas[k][j]
    
    def contained(self, perm, k):
        # checks
        k = Integer(k); assert self.degree >= k and k >= 1
        check_perm(perm, self.degree)
        
        # work
        for i in range(k+1,self.degree+1):
            if perm[i] != i:
                return False
        j = perm[k]
        sigma_k_j = self.sigma(k, j)
        if sigma_k_j is None:
            return False
        if k == 1:
            return True
        perm = multiply_perms(invert_perm(sigma_k_j),perm)
        return self.contained(perm, k-1)
    
    def A_k(self, perm, k):
        # checks
        k = Integer(k); assert self.degree >= k and k >= 1
        check_perm(perm, self.degree)
        for i in range(k+1,self.degree+1):
            assert perm[i] == i, 'must be in S_k'
        assert not self.contained(perm, k)
        
        # A1
        self.T[k].append(perm)
        # A2
        current_Sigma_k = []
        for sigma in self.sigmas[k]:
            if sigma is not None:
                current_Sigma_k.append(sigma)
        for sigma in current_Sigma_k:
            self.B_k(multiply_perms(perm, sigma), k)
    
    def B_k(self, perm, k):
        # checks
        k = Integer(k); assert self.degree >= k and k >= 1
        check_perm(perm, self.degree)
        for i in range(k+1,self.degree+1):
            assert perm[i] == i, 'must be in S_k'
        
        # B1
        j = perm[k]
        # B2
        sigma_k_j = self.sigma(k, j)
        if sigma_k_j is None:
            self.sigmas[k][j] = copy(perm)
            current_T_k = copy(self.T[k])
            for tau in current_T_k:
                self.B_k(multiply_perms(tau, perm), k)
            return
        # B3
        perm = multiply_perms(invert_perm(sigma_k_j),perm)
        if self.contained(perm, k-1):
            return
        # B4
        self.A_k(perm, k-1)
        
    def order(self, k):
        # checks
        k = Integer(k); assert self.degree >= k and k >= 1
        
        # work
        return prod(len([b for b in a if b is not None]) for a in self.sigmas[1:k+1])
    
    def members(self, k):
        # checks
        k = Integer(k); assert self.degree >= k and k >= 1
        
        # work
        if k == 1:
            yield range(self.degree+1)
        else:
            for perm in self.members(k-1):
                for sigma in self.sigmas[k]:
                    if sigma is not None:
                        yield multiply_perms(sigma, perm)
    
    def random_element(self, k):
        # checks
        k = Integer(k); assert self.degree >= k and k >= 1
        
        # work
        if k == 1:
            return range(self.degree+1)
        perm = self.random_element(k-1)
        sig_sigmas = [a for a in self.sigmas[k] if a is not None]
        sigma = randint(0,len(sig_sigmas)-1)
        sigma = sig_sigmas[sigma]
        return multiply_perms(sigma, perm)

example usage:

sage: S = StabChainKnuth(3)
sage: for a in S.sigmas:
...       print a
None
[None, [0, 1, 2, 3]]
[None, None, [0, 1, 2, 3]]
[None, None, None, [0, 1, 2, 3]]
sage: S.T[1:]
[[[0, 1, 2, 3]], [[0, 1, 2, 3]], [[0, 1, 2, 3]]]
sage: print S.order(1)
sage: print S.order(2)
sage: print S.order(3)
1
1
1
sage: print list(S.members(1))
sage: print list(S.members(2))
sage: print list(S.members(3))
[[0, 1, 2, 3]]
[[0, 1, 2, 3]]
[[0, 1, 2, 3]]
sage: S.A_k([0,2,1,3], 1)
Traceback (most recent call last):
...
AssertionError: must be in S_k
sage: S.A_k([0,2,1,3], 2)
sage: for a in S.sigmas:
...       print a
None
[None, [0, 1, 2, 3]]
[None, [0, 2, 1, 3], [0, 1, 2, 3]]
[None, None, None, [0, 1, 2, 3]]
sage: S.T[1:]
[[[0, 1, 2, 3]], [[0, 1, 2, 3], [0, 2, 1, 3]], [[0, 1, 2, 3]]]
sage: print S.order(1)
sage: print S.order(2)
sage: print S.order(3)
1
2
2
sage: print list(S.members(1))
sage: print list(S.members(2))
sage: print list(S.members(3))
[[0, 1, 2, 3]]
[[0, 2, 1, 3], [0, 1, 2, 3]]
[[0, 2, 1, 3], [0, 1, 2, 3]]
sage: S.A_k([0,2,3,1], 3)
sage: for a in S.sigmas:
...       print a
None
[None, [0, 1, 2, 3]]
[None, [0, 2, 1, 3], [0, 1, 2, 3]]
[None, [0, 2, 3, 1], [0, 3, 1, 2], [0, 1, 2, 3]]
sage: S.T[1:]
[[[0, 1, 2, 3]], [[0, 1, 2, 3], [0, 2, 1, 3]], [[0, 1, 2, 3], [0, 2, 3, 1]]]
sage: print S.order(1)
sage: print S.order(2)
sage: print S.order(3)
1
2
6
sage: print list(S.members(1))
sage: print list(S.members(2))
sage: print list(S.members(3))
[[0, 1, 2, 3]]
[[0, 2, 1, 3], [0, 1, 2, 3]]
[[0, 3, 2, 1], [0, 1, 3, 2], [0, 2, 1, 3], [0, 2, 3, 1], [0, 3, 1, 2], [0, 1, 2, 3]]
sage: for _ in range(10):
...       perm = S.random_element(2)
...       print perm, S.contained(perm, 2), S.contained(perm, 3)
[0, 1, 2, 3] True True
[0, 2, 1, 3] True True
[0, 1, 2, 3] True True
[0, 2, 1, 3] True True
[0, 2, 1, 3] True True
[0, 2, 1, 3] True True
[0, 1, 2, 3] True True
[0, 1, 2, 3] True True
[0, 1, 2, 3] True True
[0, 2, 1, 3] True True
sage: for _ in range(10):
...       perm = S.random_element(3)
...       print perm, S.contained(perm, 3), S.contained(perm, 2)
[0, 2, 3, 1] True False
[0, 3, 1, 2] True False
[0, 1, 2, 3] True True
[0, 2, 1, 3] True True
[0, 1, 2, 3] True True
[0, 2, 3, 1] True False
[0, 1, 2, 3] True True
[0, 1, 3, 2] True False
[0, 3, 2, 1] True False
[0, 3, 1, 2] True False

Tom Boothby's implementation of deterministic shallow Schreier trees:

Note, this works on permutations which support multiplication, inversion, and evaluation. Like the Sage ones.

def cube_orbit(cube,k,a,oorb):
   parents = {a:None}
   labels = {a:0}

   print cube

   walk = [(-j,cube[-j]) for j in range(k,0,-1)]
   walk+= [( j,cube[ j]) for j in range(1,k+1)]
   orb = [a]
   new = {}
   for j,g in walk:
       norb = []
       for b in orb:
           c = g(b)
           if parents.has_key(c):
               continue
           norb.append((b,c))
       for b,c in norb:
           parents[c] = b
           labels[c] = j
           orb.append(c)
           if not oorb.has_key(c):
               new[c] = True

   return parents,labels,new.keys()

def shallowtree(S,a,n):
   k = 1
   s = S[0]
   cube = {0:s*~s, 1:~s, -1:s}

   def search(new):
       for s in S:
           for n in new:
               if not parents.has_key(s(n)):
                   return n,s
       return None

   parents,labels,new = cube_orbit(cube,k,a,{a:True})
   found = search(new)
   while found is not None:
       gammai, ui = found
       g = cube[labels[gammai]]
       cur = parents[gammai]
       while cur is not None:
           g *= cube[labels[cur]]
           cur = parents[cur]
       gi = g*ui
       print gi
       k+=1
       cube[-k] = gi
       cube[k] = ~gi
       parents,labels,new = cube_orbit(cube,k,a,parents)
       found = search(new)

   return parents,labels,cube

Example:

Sn = SymmetricGroup(12)
S = [Sn((1,2,3,4,5,6,7,8,9,10,11,12)),Sn((1,2))]
par,lab,cub = shallowtree(S,1,12)

Tickets, discussions or links which are relevant:

  1. #5664

  2. conjugacy classes

Goal 2: Partition refinement

This consists of tying the partition refinement code already in Sage into the permutation group functionality. This will allow for partition refinement traversals in proper subgroups of S_n. Applications include

Goal 3: Remove as much GAP dependence as possible

The previous two goals will make this goal tenable.

Goal 4: Subgroup Normalizers

This is an NP complete problem, which is strictly harder than the problems in Goal 2.

PermutationGroups (last edited 2009-05-22 00:23:37 by rlm)