You can use sage for a lot of group theory tests. The following should give you an idea of how things work in Sage.
Sage has a lot of built-in groups that you can call. The following are a few examples.
S4 = SymmetricGroup(4)
D4 = DihedralGroup(4)
These are a few examples of groups. There is a whole lot of other 'named groups' that Sage has; you can look up the manual for it.
Even if you find a new group, and you want to understand it more, you can ask for it to print the multiplication table:
D4.cayley_table()
Or just list the elements:
D4.list()
Or even show the Cayley graph of it:
show(D4.cayley_graph())
Whenever you are unsure of what a specific command does, you can always ask for help
D4.cayley_graph?
You can use the disjoint cycle notation to refer to a specific element of the group.
rho = S4([(1,2),(3,4)])
pi = S4([(1,4),(2,3)])
tau = rho * pi * rho.inverse()
tau
You can also make membership queries (and a LOT more)
tau in D4
You can also specify permutations as a single array (where the i-th element represents the image of i under this permutation).
sigma = S4([4,3,2,1])
sigma
Let us consider the rotations of the cube:
S8 = SymmetricGroup(8)
rot_R = S8([(1,8,6,7),(4,5,2,3)])
rot_U = S8([(5,8,1,4),(2,6,7,3)])
rot_F = S8([(4,1,7,3),(5,8,6,2)])
G = PermutationGroup([rot_R, rot_U, rot_F])
G.order()
Hmm... wonder what this group is...
G.is_isomorphic(S4)
Sage has a built-in named group for the 3x3x3 Rubik's cube.
R3 = CubeGroup()
R3
R3.display2d("")
# What the cube looks like after a move sequence
R3.display2d("R D R-1")
len(R3.orbit(2))
R3.stabilizer(1)
len(R3.orbit(1)) * R3.stabilizer(1).order() == R3.order()
However, if you are interested more in the visualisation of moves rather than the group directly, there is a related class called RubiksCube
.
C = RubiksCube("R D R-1")
C.plot3d()
As a concrete example, we can try to build the general nxnxn Rubik's cube group. However, in order to build that, we need a way to label the stickers to make it easier to talk about precisely how the various moves work.
A natural way is to just think of each "piece" of the cube specified by the slice numbers in each direction.
Hence, for example, the (0,0,0) piece refers to the LDF corner. However, this alone isn't enough as we also need the face it belongs to. Well, it is enough to just specify the direction of the face as the face is determined by the slice number in that direction. We will therefore specify stickers using the notation:
(face_direction, x-slice-index, y-slice-index, z-slice-index)
Thus, in general for an $n \times n \times n$ cube, there are $3n^2 \cdot 2 = 6n^2$ stickers overall (once you fix a direction, the slice index in that direction can only be 0 or (n-1)).
num_layers = 3
stickers = [
(face_direction, x_idx, y_idx, z_idx)
for face_direction in range(3) # 0 => L/R, 1 => D/U, 2 => F/B
for x_idx in range(num_layers)
for y_idx in range(num_layers)
for z_idx in range(num_layers)
if [x_idx, y_idx, z_idx][face_direction] in [0, num_layers - 1]
# eg. If we are staring at L/R, then x-cord must be 0 or n-1
]
SymStickers = SymmetricGroup(len(stickers))
SymStickers
Now we can specify what "moves" are. Once again, we can specify the standard moves by mentioning the direction and the slice index.
def rotate_cube(direction, slice_idx, stickers, num_layers):
'''
Returns an array of permuted sticker indices (an array that is 1,...,len(stickers) permuted)
'''
output = []
for sticker in stickers:
face_dir, x_idx, y_idx, z_idx = sticker
indices = [x_idx, y_idx, z_idx]
if slice_idx != indices[direction]:
# Then this piece doesn't move at all
new_sticker = sticker
else:
if face_dir == direction:
new_direction = direction
else:
# The new direction is the 'other' direction
new_direction = [d for d in [0,1,2] if d != face_dir and d!=direction][0]
new_indices = [0,0,0]
new_indices[direction] = indices[direction]
new_indices[(direction + 1) % 3] = indices[(direction + 2) % 3]
new_indices[(direction + 2) % 3] = num_layers - 1 - indices[(direction + 1) % 3]
new_sticker = (new_direction, new_indices[0], new_indices[1], new_indices[2])
new_sticker_index = stickers.index(new_sticker) + 1
output.append(new_sticker_index)
return output
generators = [
SymStickers(rotate_cube(direction, slice_idx, stickers, num_layers))
for direction in [0,1,2]
for slice_idx in range(num_layers)
]
R = PermutationGroup(generators)
R.order()
CubeGroup().order()
R.order() / CubeGroup().order()
Can you see where the factor of 24 is from?
CubeGroup().display2d('')
Ah! We happen to be moving our centres as well! Let's fix that
generators = [
SymStickers(rotate_cube(direction, slice_idx, stickers, num_layers))
for direction in [0,1,2]
for slice_idx in range(num_layers) if slice_idx != 1
]
R = PermutationGroup(generators)
R.order() == CubeGroup().order()
You have a pretty good tutorial here. Play around with sage to get a better hang of things.