1

Lets say I have a group G={a,b,e} where a,b are arbitrary elements and e denotes the neutral element. I came up with a certain Cayley table and want to verify that what I have done is right by checking the associativity.

That means I want to check for arbitrary x,y,z in G that x(yz)=(xy)z, because by hand I would have to check 3*3*3 = 27 cases, which is just nuts.

I didn't come far in my coding so far and I'd appreciate some hints or elegant ways how to approach this problem. I am a beginner at python but have a basic understanding of loops and functions.

My idea of the program:

I define a function lets say group which takes as an input a string. I add the string by the .extend(string) function to certain list which allows me to analyze the given input one by one.

Via an if statement lets say:

if checklist[0] == "a" and checklist[1] == "b":
    checklist.pop[0]
    checklist[0] = "e"

I could first drop the first entry of the list and then replace it by the desired new value. I did continue like that adding more and more if statements and at the end recursively called my function again and it would terminate if the length of my checklist is equal to 1.

However this is not a very elegant code which also runs into problems with special cases. Hence I believe my effort was in vain and I ought to believe that there is a much simpler and more elegant solution to this problem. I hope you can help me guide me in the right direction to find it.


Code example (not complete but conceptual):

checklist = []

def group(abc):
    if len(checklist) == 1:
        return checklist
    else:
        checklist.extend(abc)
        if (checklist[0] == "a" and checklist[1] == "a"):
            checklist.pop(0)
            checklist[0] = "b"
        if (checklist[0] == "a" and checklist[1] == "b"):
            checklist.pop(0)
            checklist[0] = "e"
        if (checklist[0] == "a" and checklist[1] == "e"):
            checklist.pop(0)
            checklist[0] = "a"
        if (checklist[0] == "b" and checklist[1] == "a"):
            checklist.pop(0)
            checklist[0] == "e"
        if (checklist[0] == "b" and checklist[1] == "b"):
            checklist.pop(0)
            checklist[0] = "a"
        if (checklist[0] == "b" and checklist[1] == "e"):
            checklist.pop(0)
            checklist[0] = "b"
        if (checklist[0] == "e" and checklist[1] == "a"):
            checklist.pop(0)
            checklist[0] = "a"
        if (checklist[0] == "e" and checklist[1] == "b"):
            checklist.pop(0)
            checklist[0] = "b"
        if (checklist[0] == "e" and checklist[1] == "e"):
            checklist.pop(0)
            checklist[0] = "e"
        group(checklist)
        return checklist
Paul
  • 26,170
  • 12
  • 85
  • 119
Spaced
  • 231
  • 1
  • 14
  • 1
    **Beware**: Your audience may be unaware of [mathematical group theory](http://en.wikipedia.org/wiki/Group_theory) unless they had to take it in college. – Paul Oct 12 '14 at 17:49
  • Probably unrelated, but you might not have to reinvent the wheel: http://www.sagemath.org/doc/thematic_tutorials/group_theory.html# – Paul Oct 12 '14 at 17:51
  • I did add the line Paul, I already tested the code locally on my machine and it works for most cases, however it does never terminate for the string "bbb". If it helps I will upload my entire code to here, it wont be pretty though. Also thanks a lot for your link to sage, I will look into it. – Spaced Oct 12 '14 at 18:00
  • Maybe I didn't see it on first reading. I see it now,. – Paul Oct 12 '14 at 18:01
  • What you want looks like this: `associative = sum( [m(m(a,b),c)!=m(a,m(b,c)) for a in G for b in G for c in G])==0`. This array-defining syntax should work if m is defined. It is called a python list comprehension. It requires defining the multiply function m() and a list of elements for G. – Paul Oct 12 '14 at 18:09

2 Answers2

2

I would just use itertools.product to generate each triple of elements x, y, z of your group G and check that the triple is associative.

You could define a function which checks to see whether your group operation on a particular triple is associative and then proceed to check each possible triple in the group in turn. If "associativity fails..." is printed, then G is not associative under your group operation.

import itertools

G = [0, 1, 2, 3, 4, 5] # or whatever your group might be

def is_associative(x, y, z):
    if (x*y)*z == x*(y*z):
        return True
    return False

xyz = itertools.product(G, repeat=3)

for three in xyz:
    if not is_associative(*three):
         print("associativity fails for %s, %s, %s") % three

Obviously, in the definition of is_associative, you'd want to replace * with whatever your group operation is.

Alex Riley
  • 169,130
  • 45
  • 262
  • 238
  • Unfortunately, `itertools.permutations` won't repeat elements. That is, it will never test ('a','b','a') or ('a','a','a') if G=['a','b','c'] – Paul Oct 12 '14 at 18:22
  • this is a good approach, but I'd like to work with string elements and be able to test associative by a given Cayley-table. Which also means that the 'multiplication' for instance can be entirely abstract, lets say a*b=b – Spaced Oct 12 '14 at 18:25
  • (Fixed the repeating elements issue by using `product` instead.) @Spaced - thanks, the basic idea will regardless of whatever the operation `*` is. It's possible to define `*` so that `a*b` is the result of a Cayley table lookup. – Alex Riley Oct 12 '14 at 18:38
1

What you want looks like this:

def is_associative(m, G):
    return sum( [m(m(a,b),c)!=m(a,m(b,c)) for a in G for b in G for c in G])==0

where you feed it a function m and a list G, where m:(G,G)-->G

Where this can go bad with arbitrary m and G is in the (not) equals test.

Let's see if we can make this work with the (i,j,k) vectors and cross products in R^3

import numpy

def is_associative(m, equals, G):
    # count the unassociative triplets, return true if count is zero
    return sum( [\
                     not equals(m(m(a,b),c), m(a,m(b,c)) ) \
                     for a in G for b in G for c in G\
                 ])==0


# classic non-associative example in R^3

# G is 0 vector and unit vectors
G = [ [0,0,0], [1,0,0], [0,1,0], [0,0,1] ]

# m is cross product 
def m(a,b):
    return numpy.cross(a,b)

def vector_equals(a,b):
    return (a[0]==b[0]) and (a[1]==b[1]) and (a[2]==b[2])

for a in G:
    print a

print "Associative: "+str(is_associative(m, vector_equals, G))

Output: [0, 0, 0] [1, 0, 0] [0, 1, 0] [0, 0, 1] Associative: False

You might want to know why a finite group is non associative, so we can change things a bit to accomplish that as follows:

First, we'll define a test_associative function that returns not only true/false but the triplets that cause it to be false. Then, we'll call that and unpack the results.

import numpy

def test_associative(m, equals, G):
    # find the unassociative triplets
    # return (true,[]) if count is zero
    # return (false, array_of_unassociative_triplets)
    unassociative_triplets = [ (a,b,c)\
                     for a in G for b in G for c in G\
                     if not equals(m(m(a,b),c), m(a,m(b,c)) ) \
                 ]
    return (len(unassociative_triplets)==0, unassociative_triplets)


# classic non-associative example in R^3

# G is 0 vector and unit vectors
G = [ [0,0,0], [1,0,0], [0,1,0], [0,0,1] ]

# m is cross product 
def m(a,b):
    return numpy.cross(a,b)

def vector_equals(a,b):
    return (a[0]==b[0]) and (a[1]==b[1]) and (a[2]==b[2])

print "Elements of G:"

for a in G:
    print a

print "m() is the vector cross product x"

(is_associative, whynot) = test_associative(m, vector_equals, G)

print "Associative: "+str(is_associative)

if not is_associative:
    print "Non-associative triplets:"
    for triplet in whynot:
        print str(triplet)+\
            " (a*b)*c : "+str(m(m(triplet[0],triplet[1]), triplet[2]))+\
            " a*(b*c) : "+str(m(triplet[0],m(triplet[1], triplet[2])))

Output:

Elements of G:
[0, 0, 0]
[1, 0, 0]
[0, 1, 0]
[0, 0, 1]
m() is the vector cross product x
Associative: False
Non-associative triplets:
([1, 0, 0], [1, 0, 0], [0, 1, 0]) (a*b)*c : [0 0 0] a*(b*c) : [ 0 -1  0]
([1, 0, 0], [1, 0, 0], [0, 0, 1]) (a*b)*c : [0 0 0] a*(b*c) : [ 0  0 -1]
([1, 0, 0], [0, 1, 0], [0, 1, 0]) (a*b)*c : [-1  0  0] a*(b*c) : [0 0 0]
([1, 0, 0], [0, 0, 1], [0, 0, 1]) (a*b)*c : [-1  0  0] a*(b*c) : [0 0 0]
([0, 1, 0], [1, 0, 0], [1, 0, 0]) (a*b)*c : [ 0 -1  0] a*(b*c) : [0 0 0]
([0, 1, 0], [0, 1, 0], [1, 0, 0]) (a*b)*c : [0 0 0] a*(b*c) : [-1  0  0]
([0, 1, 0], [0, 1, 0], [0, 0, 1]) (a*b)*c : [0 0 0] a*(b*c) : [ 0  0 -1]
([0, 1, 0], [0, 0, 1], [0, 0, 1]) (a*b)*c : [ 0 -1  0] a*(b*c) : [0 0 0]
([0, 0, 1], [1, 0, 0], [1, 0, 0]) (a*b)*c : [ 0  0 -1] a*(b*c) : [0 0 0]
([0, 0, 1], [0, 1, 0], [0, 1, 0]) (a*b)*c : [ 0  0 -1] a*(b*c) : [0 0 0]
([0, 0, 1], [0, 0, 1], [1, 0, 0]) (a*b)*c : [0 0 0] a*(b*c) : [-1  0  0]
([0, 0, 1], [0, 0, 1], [0, 1, 0]) (a*b)*c : [0 0 0] a*(b*c) : [ 0 -1  0]

Using a multiplication table instead of a function is simply a matter of expressing the table as a python array and having m look up the result in the array. Python indexes arrays with integers from 0 to length-1 and you can have arrays inside arrays. You will need an isomorphism between your finite group G and {0,1,..., ||G||-1}

Paul
  • 26,170
  • 12
  • 85
  • 119
  • Rather than the trick with `sum`, it would be simpler (and more direct) to use `all`: `all(equals(m(m(a,b),c), m(a,m(b, c))) for a in ...)` – Mark Dickinson Oct 12 '14 at 19:19
  • Sounds good. Better would be to pull out the non-associative triplets. – Paul Oct 12 '14 at 19:21