Loading [MathJax]/jax/output/HTML-CSS/fonts/TeX/fontdata.js

Schreier-Sims algorithm

Revision en2, by adamant, 2023-01-12 04:07:33

Hi everyone!

I have this blog drafter for quite a while, but the $300 contest motivated me to actually finish and publish it. I don't care about the prize that much as I think many other blogs published recently are talking about something even cooler. But I like to make people suffer, so I will apply anyway to make peltorator read even more stuff >:)

Besides, the contest makes it harder to stay on contribution top, so I got to do something to stay afloat. That being said...

Today I'd like to write about an algorithm that solves the following problem:

You're given a set of permutations Σ={σ1,,σm}. Find the size |σ1,,σm| of a group G generated by Σ.

At first, I wanted to get into a lot of group-theoretic details with proofs and all, but then I felt like it would make the article harder to comprehend, so I will try to keep group-theoretic stuff to minimum, while telling more about the high-level idea.

Prerequisites

It is strongly recommended to be familiar with some simpler basis constructing algorithms, e.g. Gauss elimination or XOR basis.

Familiarity with basic group theory (e.g. definitions) will also be beneficial.

What does the statement mean?

In this article, we will treat permutations as a bijective functions from {1,,n} to itself.

For example, the permutation σ=(123231) is perceived as a function σ, such that σ(1)=2, σ(2)=3 and σ(3)=1.

Then, we may as well define the composition σ1σ2 of permutations σ1 and σ2 as (σ1σ2)(k)=σ1(σ2(k)).

By the group G generated by Σ we mean the set of permutation that we will get by repeatedly composing permutations with each other in all possible ways, until no new permutations may be produced. Further we will rely on the following properties of permutations:

  • There is an identity permutation e(k)=k, such that eσ=σe=σ for any σ;
  • For every permutation σ, there is unique σ1 such that σ(σ1(k))=σ1(σ(k))=k, that is σσ1=σ1σ=e.

Together, the properties above just say that permutations form a group.

High-level idea

General idea of the algorithm is to find k sets G1,,GkG such that every element gG may be uniquely represented as g=g1g2gk, where g1G1,,gkGk. Then it will also hold that |G|=|G1||G2||Gk|.

It would be even nicer to make G1,,Gk such that finding g1g2gk for any given g is a reasonably simple task.

Which G1,,Gk to use?

This idea above is quite similar to representing any vector r of a linear space as a linear combination r=a1r1++akrk of basis vectors. The basis r1,,rk is usually constructed in a row-echelon form, meaning that each vector has a specific coordinate associated with it, so that all basis vectors with higher indices have zero coefficient in this coordinate. In this way, the coefficient ai may be found just by looking on the corresponding coordinate of ra1r1ai1ri1.

Generally, in linear spaces we may allow zero vectors in the basis and choose r1,,rn in such way that ri has zero value in all coordinates j<i. Then, the vector airi is uniquely determined by the i-th coefficient of ra1r1ai1ri1.

Getting back to permutations, Gi may be interpreted as a set of all possible values of airi. So instead of representing the vector r as a linear combination of airi, we represent the permutation g as a composition of gi. In linear spaces, we wanted airi to have the value 0 in coordinates j<i. And also for airi to be uniquely determined by the i-th coordinate of ra1r1ai1ri1.

What would it mean for us in permutation context?


What is the equivalent of ra1r1ai1ri1 in permutations?

We ultimately want g=g1gn. Cancelling first i1 permutations on both sides, we get to g=g1i1g11g.

What is the equivalent of coordinates in permutations?

It's the values σ(1),σ(2),,σ(n).

What is the equivalent of j-th coordinate being 0 for j<i?

We needed it, so that the composition of g1i and g always preserved g(j)=j for j<i.

By definition it means that g1i(g(j))=g(j)=j, which only happens when g1i(j)=gi(j)=j.

What is the equivalent of "airi is uniquely determined by the i-th coordinate of ra1r1ai1ri1"?

In the very end we ultimately want gnng11g=e, where e is the identity permutation e(k)=k.

To achieve this, we must pick gi in such way that g1i will turn g(i) into i.

By definition, it means that g1i(g(i))=i, or equivalently gi(i)=g(i).

In other words, we must have a(i)b(i) for any a,bGi.


That being said, Schreier-Sims algorithm constructs n sets G1,,Gn such that

  • every element giGi has gi(j)=j for j<i;
  • all elements giGi have distinct gi(i);
  • The sets G and G1G2Gn generate the same permutation group.

I will use Python in my code examples below. Let schreier_sims(S) be the function that takes a list of permutations Σ and returns G1,,Gn. Each Gi is also represented as a list of permutations. Then checking that an element belongs to G may be done as follows:

# compute the composition p[i] = p1[p2[i]]
def apply(p1, p2):
    return tuple(p1[p2[i]] for i in range(len(p2)))

# compute the inverse permutation ans[p[i]] = i
def inverse(p):
    ans = [0] * len(p)
    for i in range(len(p)):
        ans[p[i]] = i
    return tuple(ans)

# compute the sets G1, ..., Gn
def schreier_sims(S):
    ...

# G is the list of Schreier-Sims basis sets,
# p is the permutation that we need to check
def belongs(G, p):
    for i in range(len(G)):
        for g in G[i]:
            if p[i] == g[i]:
                p = apply(inverse(g), p)
    return all(p[j] == j for j in range(len(p)))

belongs(schreier_sims([[1, 2, 0]]), [2, 0, 1]) # True
belongs(schreier_sims([[1, 2, 0]]), [2, 1, 0]) # False

Is this enough?

No! There is an additional implicit assumption that we have made here. Specifically, that every permutation gG such that g(j)=j for j<i can be generated just by permutations from the sets Gi,Gi+1,,Gn. While this is a somewhat natural assumption if you're familiar with Gauss algorithm, it isn't necessarily true.

It could be that combining permutations from Gi, you will get a permutation gi such that gi(i)=i and then you will have to apply it to make g an identity permutation in the end. Therefore, we need a stronger set of constraints:

  • every element giGi has gi(j)=j for j<i;
  • all elements giGi have distinct gi(i);
  • For any permutation gG such that g(j)=j for j<i, it is also true that gGi,Gi+1,,Gn.

In group theory terms, the last condition means that the set of permutations G1Gn is a strong generating set of G.

Note that this is very similar to how we construct the basis for vectors over remainders modulo possibly composite number m (see e.g. the article by errorgorn). Key difference with vectors over fields here is the possible existence of torsions, when it is possible to annihilate the i-th coordinate of a vector by multiplying the vector with a non-zero constant (thus, possibly not annihilating the remaining coordinates).

Similarly with permutations, it may be possible to annihilate the i-th term of a permutation from Gi by applying other permutations from Gi to it, without making the permutation as a whole the identity permutation, hence it might be still needed in the decomposition unless G1,,Gn constitutes a strong generating set.

How to construct G1,,Gn?

Schreier-Sims algorithms construct a strong generating set iteratively. Let G(i) be the set of permutation gG, such that g(j)=j for j<i. In particular, G(1)=G. Note that G(i) is closed under composition, that is g1g2G(i) for any g1,g2G(i), because

g1(g2(j))=g1(j)=j

for any j<k. In group-theoretic terms, G(i) is called a stabilizer subgroup of 1,2,,i1.

From the definition of a strong generating set above it follows that

G(i)=GiGi+1Gn.

The iteration of the Schreier-Sims algorithm assumes that we have a generating set of permutation S such that G(i)=S, and from that it finds a set Gi and a new generating set S such that G(i)=GiS, and G(i+1)=S. In this way, it is possible to apply similar steps iteratively to construct Gi+1,Gi+2,,Gn.

Finding Gi

To find Gi, we should find a representative for every possible value of g(i) for gG(i). In other words, for every obtainable ji, we should find a permutation gjG(i) such that gj(i)=j. Once we apply g1j, to the permutation g having g(i)=j, we would make its i-th term being equal to i and therefore it will move to G(i+1), which is dealt with later on.

That being said, Gi will consist exactly of gj for all obtainable j. The set of all possible g(i) is called the orbit of the number i. We can find and organize the orbit by building a so-called Schreier tree. The tree consists of all possible values of g(i), and edges of the tree correspond to permutations that should be applied to change one element of the orbit to another.

Now, let gjG(i) be such that gj(i)=j. Then for any gS it holds that (ggj)(i)=g(j). This fact allows to construct the Schreier tree with a simple depth-first search. We will store actual permutations gj in an array orbit:

# orbit[j] = permutation g in <S> s.t. g[i] = j
def build_schreier_tree(i, S, orbit):
    for g in S:
        if g[i] not in orbit:
            orbit[g[i]] = apply(g, orbit[i])
            # g[i] = j
            build_schreier_tree(g[i], S, orbit)

The running time of the construction above is O(n|S|+n2).

Finding S

Assume that we have S s.t. S=G(i) and Gi, represented by the elements of the orbit of i. Then the generating set S such that S=G(i+1) can be found from what is called the Schreier's lemma:

S={(gsu)1sgu|uorbiti,sS}

Here, orbiti are the possible values of g(i) for gG(i) and gjGi is a representative having gj(i)=j.

Indeed, consider an element s=s1s2skG(i+1). Let ri=sisi+1sk, then we may rewrite it as

s=s1(gr2(i)g1r2(i))s2(gr3(i)g1r3(i))(grkg1rk)sk,

which, using that s(i)=i and rk(i)=sk(rk+1(i))=sk(u) for rk+1(i)=u, in turn further rewrites as

s=(g1is1gr2(i))(g1r2(i)s2gr3(i))(g1rk(i)skgi)=s1s2sk.

The process of generating such set is straight-forward:

# Given <S> = G^(i) and
# the orbit representatives of i,
# find S' s.t. <S'> = G^(i+1)
def make_gen(S, orbit):
    n = len(next(iter(S)))
    newS = set()
    for s in S:
        for u in orbit:
            newS.add(reduce(apply, [inverse(orbit[s[u]]), s, orbit[u]]))
    return newS

Note that the size of S generated in this way may be as high as n|S|, which would allow an exponential growth if not halted.

Making sure S has a reasonable size

For any set S, it is possible to construct another set S such that \langle S' \rangle = \langle S' ' \rangle, but |S' '| \leq n^2. There are several ways to construct such sets, for example Jerrum's filter even guarantees that |S' '| \leq n, but at the cost of higher construction complexity. In Schreier-Sims algorithm, however, since the construction and sifting is repeated several times iteratively, the preferred way is Sims filter. It guarantees that for any pair (i, j) there is at most one permutation g \in S' ' such that g(k)=k for k < i and g(i)=j.

The construction process for it is fairly straightforward and essentially repeats the Gauss method:

# Sifts S and returns S'
# such that <S> = <S'> and |S'| <= n^2
def normalize(S):
    n = len(next(iter(S)))
    newS = set()
    base = [{} for i in range(n)]
    for s in S:
        for x in range(0, n):
            if s[x] != x:
                if s[x] in base[x]:
                    s = apply(inverse(s), base[x][s[x]])
                else:
                    base[x][s[x]] = s
                    newS.add(s)
                    break
    return newS

Getting it all together

Gluing all the pieces mentioned above together, we get the following algorithm:

def schreier_sims(S):
    n = len(next(iter(S)))
    ans = []
    for i in range(n):
        orbit = {}
        # initiate with identity permutation
        orbit[i] = tuple(range(n))
        # find G_i = orbit
        build_schreier_tree(i, S, orbit)
        # add G_i to the answer
        ans += [[orbit[j] for j in orbit]]
        # generate S' and make sure it is at most n^2 in size
        S = normalize(make_gen(S, orbit))
    return ans

Complexity analysis here is not very pleasant and yields something around O(n^6). It is possible to further reduce it by roughly a factor of n by terminating the sifting process earlier, if sufficient amount of uniformly generated elements of G are sifted through.

Example problem

There was a problem in Petrozavodsk Summer-2011. Moscow SU Unpredictable Contest that asked specifically to find the size of a permutation group for n \leq 50. If you don't have an access to opentrains, I have uploaded this specific problem to the mashup which you can access by the invitation link, and here is my submission to it using the Schreier-Sims algorithm: [submission:188935679].

Tags permutations, group theory, xor basis, gauss, tutorial

History

 
 
 
 
Revisions
 
 
  Rev. Lang. By When Δ Comment
en11 English adamant 2023-01-14 03:22:22 8
en10 English adamant 2023-01-14 03:21:59 48
en9 English adamant 2023-01-13 03:08:01 3
en8 English adamant 2023-01-13 00:46:49 1 bump :pensive:
en7 English adamant 2023-01-12 04:20:58 39 Tiny change: '$\sigma = (122331)(123231)\begin{pma' -> '$\sigma = \begin{pma'
en6 English adamant 2023-01-12 04:17:43 10 Tiny change: '$\sigma = (122331)(123231)\begin{pma' -> '$\sigma = \begin{pma'
en5 English adamant 2023-01-12 04:14:39 10
en4 English adamant 2023-01-12 04:14:11 216 Tiny change: '$\sigma = (122331)(123231)\begin{pma' -> '$\sigma = \begin{pma'
en3 English adamant 2023-01-12 04:07:52 3
en2 English adamant 2023-01-12 04:07:33 18
en1 English adamant 2023-01-12 04:03:54 15709 Initial revision (published)