How to calculate all 24 rotations of a 3D array?

I have a 3d array describing a polycube (imagine a piece of 3d tetris). How can I calculate all 24 rotations?

Numpy array manipulation procedures include the rot90 method, which gives 4 out of 24, but I don't know how to calculate the rest. My only idea is to convert a three-dimensional array into a two-dimensional coordinate matrix, multiply by a rotation matrix, and perform the inverse transformation. But I would rather work directly with a 3D array.

2x2x2 array example:

>>> from numpy import array >>> polycube array([[[1, 0], [1, 0]], [[1, 1], [0, 0]]]) 

An example of a 3x3x3 array:

 array([[[1, 1, 0], [1, 1, 0], [0, 0, 0]], [[0, 0, 0], [1, 0, 0], [1, 0, 0]], [[0, 0, 0], [0, 0, 0], [0, 0, 0]]]) 

Edit: I want only 24 orientation-preserving isometries, not all 48 rotations and reflections (although it would be interesting to know how to make them too). If this helps verify, I believe that the 3x3x3 example does not have rotational symmetry and is chiral (so 48 are different).

Motivation: I am writing a Soma cube- style puzzle solver.

+19
source share
9 answers

So far I have 12 of them, which make up numpy.transpose for rearranging the axes (xyz, yzx, zxy - all with the same hands) and rot90.

 def rotations12(polycube): for i in range(3): polycube = numpy.transpose(polycube, (1, 2, 0)) for angle in range(4): polycube = numpy.rot90(polycube) yield polycube 

Quick test 12 different: len(set(str(x) for x in rotations(polycube)))


Update: this is how I did all 24.

 def rotations24(polycube): # imagine shape is pointing in axis 0 (up) # 4 rotations about axis 0 yield from rotations4(polycube, 0) # rotate 180 about axis 1, now shape is pointing down in axis 0 # 4 rotations about axis 0 yield from rotations4(rot90(polycube, 2, axis=1), 0) # rotate 90 or 270 about axis 1, now shape is pointing in axis 2 # 8 rotations about axis 2 yield from rotations4(rot90(polycube, axis=1), 2) yield from rotations4(rot90(polycube, -1, axis=1), 2) # rotate about axis 2, now shape is pointing in axis 1 # 8 rotations about axis 1 yield from rotations4(rot90(polycube, axis=2), 1) yield from rotations4(rot90(polycube, -1, axis=2), 1) def rotations4(polycube, axis): """List the four rotations of the given cube about the given axis.""" for i in range(4): yield rot90(polycube, i, axis) 

Using this helper function that generalizes rot90 to rotate around any axis:

 def rot90(m, k=1, axis=2): """Rotate an array k*90 degrees in the counter-clockwise direction around the given axis""" m = numpy.swapaxes(m, 2, axis) m = numpy.rot90(m, k) m = numpy.swapaxes(m, 2, axis) return m 

I'm not sure this helper is perfect, but it seems to work.

+6
source

Take a look at the code for rot90 . I see 3 options for flip and swapaxes depending on the k axis parameter.

 fliplr(m).swapaxes(0, 1) fliplr(flipud(m)) fliplr(m.swapaxes(0, 1)) 

fliplr(m) is just m[:, ::-1] , and it is not surprising that flipud is m[::-1,...] .

You can flip the 3rd axis with m[:,:,::-1] or m[...,::-1] .

np.transpose is another tool for rearranging axes that may or may not be easier to use than swapaxes .

If rot90 gives you 4 of the turns, you can apply the same procedures to create others. You just have to understand the logic behind rot90 .

eg

 def flipbf(m): return m[:,:,::-1] flipbf(m).swapaxes(0, 2) flipbf(m).swapaxes(1, 2) etc 
+7
source

Change: since my solution basically comes down to the product of the parity of the axes multiplied by the parity of the permutation of the axes, the simplest method for generating all the regular rotations of an n-dimensional array is (scrolling some code Form @Divakar answer):

 import itertools as it def p_parity(a): a = np.asarray(a) l = a.size i, j = np.tril_indices(l, -1) return np.product(np.sign(a[i] - a[j])) def rotations_gen(m): n = m.ndim for i in it.product([-1, 1], repeat = n): for p in it.permutations(np.arange(n)): if np.product(i) * p_parity(p) == 1: s = [slice(None, None, j) for j in i] yield np.transpose(m[s], p) 

This works for any (even non-square) tensors of arbitrary dimension and is based directly on the definition of regular rotations under the tensor algebra below.

Background

The easiest way to explain this is in tensor terms, so let's turn all these rotations into rotation tensors. Rotation tensors are nxn matrices that rotate n-dimensional space. As such, they have several properties:

 np.linalg.det(R) == 1 # determinant = 1 np.inner(R, RT) == np.eye(R.shape[0]) # Transpose is inverse 

In addition, for rotations of 90 degrees, all members must be either 0, 1, or -1.

In three dimensions, there are three main families that make up together to make your 24 rotations.

First simple permutation:

 A = [[[1, 0, 0], [0, 1, 0], [0, 0, 1]], [[0, 1, 0], [0, 0, 1], [1, 0, 0]], [[0, 0, 1], [1, 0, 0], [0, 1, 0]]] 

The second includes the negation of some terms, so that the product of the diagonal is always 1:

 B = [[[ 1, 0, 0], [ 0, 1, 0], [ 0, 0, 1]], [[-1, 0, 0], [ 0,-1, 0], [ 0, 0, 1]], [[-1, 0, 0], [ 0, 1, 0], [ 0, 0,-1]], [[ 1, 0, 0], [ 0,-1, 0], [ 0, 0,-1]]] 

And the third determines whether the permutation is positive or negative, and denies the conditions if negative

 C = [[[ 1, 0, 0], [ 0, 1, 0], [ 0, 0, 1]], [[ 0, 0,-1], [ 0,-1, 0], [-1, 0, 0]], 

An important point in these families is that in each family, any product, degree, or transposition of two matrices gives another matrix in the family. Since we have three families, their products with each other form all possible rotations, in this case 3 * 4 * 2 = 24

Note: the other 24 "irregular" rotations are the same matrices multiplied by -np.eye(3) which give similar matrices with determinant = -1

request

This is all good, but how does this relate to manipulating arrays? We do not want to rotate using matrix multiplication, as this will lead to excessive memory and processing costs. Fortunately, every family is easily associated with array manipulation, which creates a view.

 def A_(m, i): # i in (0, 1, 2) idx = np.array([[0, 1, 2], [1, 2, 0], [2, 0, 1]]) return np.transpose(m, idx[i]) def B_(m, j): # j in (0, 1, 2, 3) idx = np.array([[ 1, 1, 1], [ 1,-1,-1], [-1, 1,-1], [-1,-1, 1]]) return m[::idx[j, 0], ::idx[j, 1], ::idx[j, 2]] def C_(m, k): # k in (1, -1) return np.transpose(m, np.arange(3)[::k])[::k, ::k, ::k] 

They all produce representations of m , and you can create a generator that creates representations that apply to all rotations:

 def cube_rot_gen(m): for i in [0, 1, 2]: for j in [0, 1, 2, 3]: for k in [1, -1]: yield C_(B_(A_(m, i), j), k) 
+3
source

Another option is to combine rotations around the axis of the cube that represents the matrix. Sort of:

 import numpy as np """ Basic rotations of a 3d matrix. ---------- Example: cube = array([[[0, 1], [2, 3]], [[4, 5], [6, 7]]]) axis 0: perpendicular to the face [[0,1],[2,3]] (front-rear) axis 1: perpendicular to the face [[1,5],[3,7]] (lateral right-left) axis 2: perpendicular to the face [[0,1],[5,4]] (top-bottom) ---------- Note: the command m[:, ::-1, :].swapaxes(0, 1)[::-1, :, :].swapaxes(0, 2) rotates the cube m around the diagonal axis 0-7. """ def basic_rot_ax(m, ax=0): """ :param m: 3d matrix :return: rotate the cube around axis ax, perpendicular to the face [[0,1],[2,3]] """ ax %= 3 if ax == 0: return np.rot90(m[:, ::-1, :].swapaxes(0, 1)[::-1, :, :].swapaxes(0, 2), 3) if ax == 1: return np.rot90(m, 1) if ax == 2: return m.swapaxes(0, 2)[::-1, :, :] def axial_rotations(m, rot=1, ax=2): """ :param m: 3d matrix :param rot: number of rotations :param ax: axis of rotation :return: m rotate rot times around axis ax, according to convention. """ if len(m.shape) is not 3: assert IOError rot %= 4 if rot == 0: return m for _ in range(rot): m = basic_rot_ax(m, ax=ax) return m 

If I am not mistaken, the 24 turns you are looking for are a combination of these 9 transformations.

+2
source

We start with the intention of getting all 48 combinations to get a general idea of ​​how to solve this for the n-dim array. Later we filter out the unnecessary 24 .

General idea to solve for all rotations

The idea of ​​a solution for the general case would be to basically do two things - scroll through each axis and rearrange the axes with all the combinations for a given number of axes.

Flip: for the flip, we will use the stepsize parameter for slicing, i.e. array [:: stepsize]. So, to flip, it will be: [::-1] and without flipping, just: [::1] . This stepsize can be assigned as a variable, ranging from 1 to -1 for two simple combinations. For ndarray, just extend this on all axes.

Axis permutation: to achieve this, we can use np.transpose and specify the required permutation order as an axis parameter with it. We will generate all possible orders using itertools.permutations .

That is all there is! Let implement c as an input a 3D array -

 import itertools def rotations48(a): # Get all combinations of axes that are permutable n = a.ndim axcomb = np.array(list(itertools.permutations(range(n), n))) # Initialize output array out = np.zeros((6,2,2,2,) + a.shape,dtype=a.dtype) # Run loop through all axes for flipping and permuting each axis for i,ax in enumerate(axcomb): for j,fx in enumerate([1,-1]): for k,fy in enumerate([1,-1]): for l,fz in enumerate([1,-1]): out[i,j,k,l] = np.transpose(a[::fx,::fy,::fz],ax) return out 

We could make it easier to flip nested loops in a single loop -

 def rotations48(a): n = a.ndim axcomb = list(itertools.permutations(range(n), n)) # all axes combinations pcomb = list(itertools.product([1,-1], repeat=n)) # all permuted orders out = np.zeros((6,8,) + a.shape,dtype=a.dtype) # Initialize output array for i,ax in enumerate(axcomb): #loop through all axes for permuting for j,(fx,fy,fz) in enumerate(pcomb): # all flipping combinations out[i,j] = np.transpose(a[::fx,::fy,::fz],ax) return out 

So that gives us all 48 combinations.

Expanding to a larger size: if we want to expand it to a 4D array, just edit the initialization part to expand by 2 and cut along another axis.


Solve in 24 turns

Now, as the OP claims to have a working solution , in order to get the desired 24 combinations, it is necessary to filter out from the solution we proposed. I could not find a common template for filtering, but got the indexes needed for indexing -

 idx = np.array([ 0, 3, 5, 6, 9, 10, 12, 15, 17, 18, 20, 23, 24, \ 27, 29, 30, 32, 35, 37, 38, 41, 42, 44, 47]) 

If you make sure that the output is the same as rotations24 , we would have:

 idx = np.array([ 0, 10, 3, 9, 5, 15, 6, 12, 41, 27, 42, 24, 44, \ 30, 47, 29, 18, 35, 17, 32, 20, 37, 23, 38]) 

Therefore, get the required 24 with indexing -

 final_out = out.reshape(48,-1)[idx] 

This works for 3D arrays with any total length.

Test run for verification

 # From /questions/1233932/how-to-calculate-all-24-rotations-of-3d-array/3967194#3967194 @Colonel Panic def rotations24_array(a): out0 = np.zeros((6,2,2,2,) + a.shape,dtype=a.dtype) p = [list(i) for i in rotations24(a)] out0 = np.zeros((6,4,m,m,m),dtype=a.dtype) for i in range(6): for j in range(4): out0[i,j] = p[i][j] return out0 

Check -

 In [486]: # Setup ...: np.random.seed(0) ...: m = 3 ...: a = np.random.randint(11,99,(m,m,m)) ...: ...: # Verify results ...: idx = np.array([ 0, 10, 3, 9, 5, 15, 6, 12, 41, 27, 42, 24, 44, \ ...: 30, 47, 29, 18, 35, 17, 32, 20, 37, 23, 38]) ...: out1 = rotations24_array(a).reshape(-1,m**3) ...: out2 = rotations48(a).reshape(48,-1)[idx] ...: print np.allclose(out1, out2) True 
+2
source

At the bottom of this page are 24 rotation matrices: http://www.euclideanspace.com/maths/algebra/matrix/transforms/examples/index.htm .

If a piece of tetris was represented by an array of coordinate triples, to apply rotation to the part, you would simply multiply the matrix by each triple in the array. You would do it 24 times to get 24 different pieces.

For example, if your array of boxes is (1,2,5), (1,2,4), (1,3,4)

And the rotation you are currently considering, for example,

  1 0 0 M = 0 0 -1 0 1 0 

Then the rotated figure has a view

 (1,2,5).M, (1,2,4).M, (1,3,4).M 

Where. represents matrix multiplication. Do this for every M in the list, and you will get all 24 spins. (You can either pre- or post- multiply by matrices, it doesn’t matter if you want the whole set of rotations to not have a specific order.)

So it is very simple.

Now, in order to get the data from your data structure and into the array structure that I used above, you need to do something like (sorry, I don't know Python)

 for i = 0 to 2 for j = 0 to 2 for k = 0 to 2 if polycube(i,j,k)==1 then push(i-1,j-1,k-1) onto end of newArray 

or something like that. Finally, in order to move from a newArray view to a polycube, you should do something like

  polycube = all zeros foreach point (i,j,k) in newArray polycube(i+1,j+1,k+1) = 1 

For a 2x2x2 polycube you would do

 for i = 0 to 1 for j = 0 to 1 for k = 0 to 1 if polycube(i,j,k)==1 then push(i-0.5,j-0.5,k-0.5) onto end of newArray 

and the corresponding inverse function.

+2
source

First, let's define 48 isometries as matrices that rearrange the basis vectors, possibly reversing some signs:

 def isometries(): basis = numpy.eye(3) for ix in range(3): for iy in range(3): if iy == ix: continue for iz in range(3): if iz == ix or iz == iy: continue for sx in range(-1, 2, 2): # -1, 1 for sy in range(-1, 2, 2): for sz in range(-1, 2, 2): yield numpy.array([sx * basis[ix], sy * basis[iy], sz * basis[iz]]) 

Then let me filter the rotations, which are isometries of determinant 1:

 def rotations(): return filter(lambda x: numpy.linalg.det(x) > 0, isometries()) 

Finally, we can apply this rotation to the polycube by shifting the indexes of the array so that the interior point is the origin, and multiplying the index vector by the rotation matrix:

 def rotate(rotation, shape): shift = numpy.array([1,1,1]) res = numpy.zeros(27).reshape(3,3,3) it = numpy.nditer(shape, flags=['multi_index']) while not it.finished: v = numpy.array(it.multi_index) - shift w = rotation.dot(v) dest = tuple(w + shift) res[dest] = it[0] it.iternext() return res 

For instance:

 >>> rotate(list(rotations())[5], polycube) array([[[ 0., 0., 0.], [ 0., 0., 0.], [ 0., 0., 0.]], [[ 0., 1., 1.], [ 0., 0., 0.], [ 0., 0., 0.]], [[ 1., 1., 0.], [ 1., 1., 0.], [ 0., 0., 0.]]]) 
0
source

I decided to post another answer. My first answer was very simple, but not completely in the spirit of the question. I think I now have a better answer, having thought a little.

TL DR The following code rotates the polycube throughout all 24 cube rotations. This happens without matrix multiplication; instead, the 3x3x3x3x3 index table is used, which gives several key index rotations in a polycube, and a Gray code of length 23 for the cube rotation group. It has only six real lines of code, almost all of which are for loops. It is very fast and quite compact (with the exception of the index table, which can be generated by the program (see below)).

 import numpy as np # polycube that we want to rotate A = np.array([[['a', 'b', 'c'], ['d', 'e', 'f'], ['g', 'h', 'i']], [['j', 'k', 'l'], ['m', '+', 'n'], ['o', 'p', 'q']], [['r', 's', 't'], ['u', 'v', 'w'], ['x', 'y', 'z']]]) # I just do np.copy here because I don't know how to declare an array T = np.copy(A) Idx=np.array([ [[[[2,0,0], [2,1,0], [2,2,0]], [[2,0,1], [2,1,1], [2,2,1]], [[2,0,2], [2,1,2], [2,2,2]]], [[[1,0,0], [1,1,0], [1,2,0]], [[1,0,1], [1,1,1], [1,2,1]], [[1,0,2], [1,1,2], [1,2,2]]], [[[0,0,0], [0,1,0], [0,2,0]], [[0,0,1], [0,1,1], [0,2,1]], [[0,0,2], [0,1,2], [0,2,2]]]], [[[[0,2,0], [1,2,0], [2,2,0]], [[0,1,0], [1,1,0], [2,1,0]], [[0,0,0], [1,0,0], [2,0,0]]], [[[0,2,1], [1,2,1], [2,2,1]], [[0,1,1], [1,1,1], [2,1,1]], [[0,0,1], [1,0,1], [2,0,1]]], [[[0,2,2], [1,2,2], [2,2,2]], [[0,1,2], [1,1,2], [2,1,2]], [[0,0,2], [1,0,2], [2,0,2]]]], [[[[2,2,2], [2,1,2], [2,0,2]], [[2,2,1], [2,1,1], [2,0,1]], [[2,2,0], [2,1,0], [2,0,0]]], [[[1,2,2], [1,1,2], [1,0,2]], [[1,2,1], [1,1,1], [1,0,1]], [[1,2,0], [1,1,0], [1,0,0]]], [[[0,2,2], [0,1,2], [0,0,2]], [[0,2,1], [0,1,1], [0,0,1]], [[0,2,0], [0,1,0], [0,0,0]]]] ]) # Gray code gives Hamiltonican circuit through cube rotation group gray = [3,2,1,2,1,2,3,2,3,2,1,2,1,2,3,2,3,2,1,2,1,2,3] # Oops, index starting at 0 gray[:] = [x-1 for x in gray] print(A) # I'm sure there must be a more efficient way to move T (temp) into A for g in gray: for i in [0,1,2]: for j in [0,1,2]: for k in [0,1,2]: T[i,j,k] = A[Idx[g,i,j,k,0],Idx[g,i,j,k,1],Idx[g,i,j,k,2]] A = np.copy(T) print("-----------------") print(A) 

My solution is to find the Gray code for the cube rotation group.

First, we need to find a good idea for the rotation group you are using. Consider a 2x2x2 cube with 1x1x1 cubes labeled as follows:

 1 2 3 4 4 3 2 1 

We start with 1 to fit the math literature. The top left square should be the front panel, and the bottom right square should be the back. Imagine 1 connected by edges with 2 and 3 on the front surface, with 4 on the back surface, etc.

Note that diagonally opposite cubes have the same number. Now any rotation of the cube corresponds to a rearrangement of four numbers on the front side. In addition, any rearrangement of numbers on the front side corresponds to a rotation. Four Z-axis turns coming out of a page,

 1 2 3 1 4 3 2 4 3 4 4 3 4 2 2 4 2 1 1 2 1 3 3 1 2 1 1 3 3 4 4 2 

We can mark the turns on the effect on the front side. These are 1234, 3142, 4321 and 2413.

Three additional rotations about the x axis (plus the single rotation we start from)

 1 2 3 4 2 1 4 3 3 4 4 3 2 1 1 2 4 3 3 4 1 2 2 1 2 1 4 3 1 2 3 4 

In our more compact designations, additional rotations are 3421, 2143 and 4312.

Three additional rotations around the Y axis

 1 2 2 3 3 4 4 1 3 4 4 3 4 1 1 4 1 2 2 1 2 3 3 2 2 1 3 2 4 3 1 4 

In compact recording, three additional turns - 2341, 3412 and 4123.

There are three turns, including identity, that fix 1 diagonal:

 1 2 1 4 1 3 3 4 4 3 2 3 3 2 4 2 2 4 2 1 4 1 3 1 

with the new labeled 1423 and 1342.

There are three turns, including identity, that capture 2 diagonals:

 1 2 4 2 3 2 3 4 4 3 1 3 3 1 4 1 1 4 2 1 2 4 2 3 

with new labeled 4213 and 3241.

There are three turns, including identity, that capture 3 diagonals:

 1 2 2 4 4 1 3 4 4 3 3 1 1 3 3 2 2 3 2 1 4 2 1 4 

with new labeled 2431 and 4132.

There are three turns, including identity, that capture 4 diagonals:

 1 2 2 3 3 1 3 4 4 3 1 4 4 1 2 4 4 2 2 1 3 2 1 3 

with new ones marked 2314 and 3124.

Finally, there are six marginal flips plus personality. 1-2 flips

 1 2 2 1 3 4 4 3 3 4 4 3 2 1 1 2 

with the new one, presented in 2134. Similarly, there are edges along the edges 1-3 (3214), 1-4 (4231), 2-3 (1324), 2-4 (1432) and 3-4 (1243),

Each of the 24 rotations corresponds to a separate permutation of 1234, and since there are 24 permutations and 24 rotations, each individual permutation corresponds to a rotation. We say that the cube rotation group and the permutation group S_4 of 1234 are isomorphic . (One way of thinking about this is that cube rotations act like a permutation of the cube's diagonals, and the correspondence is 1-1.)

We could figure out the effect of each of the 24 rotations on your arrays, but this is tedious and error prone. Instead, we will see that a group of rotations is generated by just three rotations, the edge flips over

 R1=transpose numbers in position2 1 and 2, and leave numbers in positions 3 and 4 alone R2=transpose numbers in positions 2 and 3, and leave numbers in positions 1 and 4 alone R3=transpose numbers in positions 3 and 4, and leave numbers in positions 1 and 2 alone 

and their compositions. We will add

 R0=identity operation 

for completeness. Using only these three generators, we can find the Hamiltonian chain through the Cayley graph of the rotation group:

 01 1234 apply R0 to starting position, ie, do nothing 02 1243 apply R3 to previous 03 1423 apply R2 to previous 04 4123 R1 05 4213 R2 06 2413 R1 07 2143 R2 08 2134 R3 09 2314 R2 10 2341 R3 11 2431 R2 12 4231 R1 13 4321 R2 14 3421 R1 15 3241 R2 16 3214 R3 17 3124 R2 18 3142 R3 19 3412 R2 20 4312 R1 21 4132 R2 22 1432 R1 23 1342 R2 24 1324 R3 

Note that each permutation (i.e., rotation of the cube) appears on this list exactly once, and that the transition from one element in the list to the next is performed by transposing the elements in positions 1-2, 2-3, or 3. -4 , which corresponds to the rotation of the edges R1, R2 and R3. This Gray code for S_4 (and much more, which, unfortunately, is quite difficult to understand if you do not know about reflection groups and Coxeter diagrams), is presented in the famous article “ Gray codes for reflection groups” by Conway, Sloan, and Wilkes .

Gray's code is summed up by this sequence of 23 numbers:

 gray = {3,2,1,2,1,2,3,2,3,2,1,2,1,2,3,2,3,2,1,2,1,2,3} 

Thus, we have significantly reduced the amount of work we have to do, starting from calculating the effect of 24 different rotations on a 2x2x2 polycube (and then on a 3x3x3 polycube), to measuring the effect of only 3 different rotations on our cubes. These three rotation functions can be stored in an array with index 0..3, and then applied as

 nextConfiguration = currentConfiguration.Rotation[gray[i]] 

(Please note that we can even get all 48 reflections / turns if we want, following this rotation sequence with one reflection P (any reflection will do, so choose the simplest one to implement), and then following this reflection with a gray sequence in the reverse order, which gives Gray code for a complete group of 48 reflections / rotations (building large sequences by attaching the reverse sequence to yourself is one of the “big ideas” when building Gray codes.))

We have come a long way without even considering the representation of the shapes you want to rotate, but now we need to implement R1, R2 and R3, so we need to consider the data structure that represents the shapes, a three-dimensional array. Consider the effect of R1, turning the rib 180 degrees on the axis through the midpoints of 1-2 ribs, on our 2x2x2 array:

 (0,0,0) (1,0,0) (1,0,0) (0,0,0) (0,1,0) (1,1,0) (0,0,1) (1,0,1) --> (1,0,1) (0,0,1) (1,1,0) (0,1,0) (0,1,1) (1,1,1) (1,1,1) (0,1,1) 

This tells us that our operation R1 can be implemented

 output(0,0,0) = input(1,0,0) output(1,0,0) = input(0,0,0) output(0,1,0) = input(1,0,1) output(1,1,0) = input(0,0,1) output(0,0,1) = input(1,1,0) output(1,0,1) = input(0,1,0) output(0,1,1) = input(1,1,1) output(1,1,1) = input(0,1,1) 

This defines the operation R1 in the case of 2x2x2. It should be clear how to determine R2 and R3 in the case of 2x2x2, as well as how to determine these operations in the case of 3x3x3.

Let me conclude by saying that defining the Ri operators is tedious and error prone, so we don’t want to do this too much if we can help. We can avoid the definition of only two operators, R1 (already defined above) and F, the rotation of the front face:

 (0,0,0) (1,0,0) (0,1,0) (0,0,0) (0,1,0) (1,1,0) (0,0,1) (1,0,1) --> (1,1,0) (1,0,0) (0,1,1) (0,0,1) (0,1,1) (1,1,1) (1,1,1) (1,0,1) 

Then we can represent R2 = FFFR1.F (where. It means "followed by"), and R3 = FFR1.FF (these are the "conjugates" of R1.)

For the 3x3x3 case, it's some work to find index tables manually, so I wrote a procedure for that, and then I found all 24 rotations of the alphabetical cube. See the code below. I'm sure a Python expert can make it much shorter. Python.

, 333, .

 import numpy as np # polycube that we want to rotate A = np.array([[['a', 'b', 'c'], ['d', 'e', 'f'], ['g', 'h', 'i']], [['j', 'k', 'l'], ['m', '+', 'n'], ['o', 'p', 'q']], [['r', 's', 't'], ['u', 'v', 'w'], ['x', 'y', 'z']]]) # I just do np.copy here because I don't know how to declare an array T = np.copy(A) # matrix representing top front edge rotation (12) R1 = np.array([[-1, 0, 0], [ 0, 0, 1], [ 0, 1, 0]]) # matrix representing right front edge rotation (23) R2 = np.array([[ 0, 0, 1], [ 0,-1, 0], [ 1, 0, 0]]) # matrix representing bottom front edge rotation (34) R3 = np.array([[-1, 0, 0], [ 0, 0,-1], [ 0,-1, 0]]) # Gray code gives Hamiltonican circuit through cube rotation group gray = [3,2,1,2,1,2,3,2,3,2,1,2,1,2,3,2,3,2,1,2,1,2,3] # trick for speed: we only need to apply each rotation matrix once # to this polycube of indices, then we use the result as a table of # pointers to new positions of polycube after rotation Idx0 = np.array([[[[0,0,0], [1,0,0], [2,0,0]], [[0,1,0], [1,1,0], [2,1,0]], [[0,2,0], [1,2,0], [2,2,0]]], [[[0,0,1], [1,0,1], [2,0,1]], [[0,1,1], [1,1,1], [2,1,1]], [[0,2,1], [1,2,1], [2,2,1]]], [[[0,0,2], [1,0,2], [2,0,2]], [[0,1,2], [1,1,2], [2,1,2]], [[0,2,2], [1,2,2], [2,2,2]]]]) # Again, copy array because I don't know how to declare Idx1 = np.copy(Idx0) Idx2 = np.copy(Idx0) Idx3 = np.copy(Idx0) # We have to subtract [1,1,1] then add it again to move the center of the # indexes to [0,0,0] for i in [0,1,2]: for j in [0,1,2]: for k in [0,1,2]: Idx1[i,j,k] = np.matmul(np.array([i,j,k])-np.array([1,1,1]),R1) + \ np.array([1,1,1]) for i in [0,1,2]: for j in [0,1,2]: for k in [0,1,2]: Idx2[i,j,k] = np.matmul(np.array([i,j,k])-np.array([1,1,1]),R2) + \ np.array([1,1,1]) for i in [0,1,2]: for j in [0,1,2]: for k in [0,1,2]: Idx3[i,j,k] = np.matmul(np.array([i,j,k])-np.array([1,1,1]),R3) + \ np.array([1,1,1]) # note that we have done only 81 vector*matrix multiplications. Now we don't # have to do any more; we just look up the result in the index tables Idx[123] print(A) # I'm sure there must be a more efficient way to move T (temp) into A for g in gray: if g == 1: for i in [0,1,2]: for j in [0,1,2]: for k in [0,1,2]: T[i,j,k] = A[Idx1[i,j,k,0],Idx1[i,j,k,1],Idx1[i,j,k,2]] A = np.copy(T) elif g == 2: for i in [0,1,2]: for j in [0,1,2]: for k in [0,1,2]: T[i,j,k] = A[Idx2[i,j,k,0],Idx2[i,j,k,1],Idx2[i,j,k,2]] A = np.copy(T) elif g == 3: for i in [0,1,2]: for j in [0,1,2]: for k in [0,1,2]: T[i,j,k] = A[Idx3[i,j,k,0],Idx3[i,j,k,1],Idx3[i,j,k,2]] A = np.copy(T) print("-----------------") print(A) 

 [[['a' 'b' 'c'] ['d' 'e' 'f'] ['g' 'h' 'i']] [['j' 'k' 'l'] ['m' '+' 'n'] ['o' 'p' 'q']] [['r' 's' 't'] ['u' 'v' 'w'] ['x' 'y' 'z']]] ----------------- [[['z' 'w' 't'] ['y' 'v' 's'] ['x' 'u' 'r']] [['q' 'n' 'l'] ['p' '+' 'k'] ['o' 'm' 'j']] [['i' 'f' 'c'] ['h' 'e' 'b'] ['g' 'd' 'a']]] ----------------- [[['x' 'o' 'g'] ['y' 'p' 'h'] ['z' 'q' 'i']] [['u' 'm' 'd'] ['v' '+' 'e'] ['w' 'n' 'f']] [['r' 'j' 'a'] ['s' 'k' 'b'] ['t' 'l' 'c']]] ----------------- [[['r' 's' 't'] ['j' 'k' 'l'] ['a' 'b' 'c']] [['u' 'v' 'w'] ['m' '+' 'n'] ['d' 'e' 'f']] [['x' 'y' 'z'] ['o' 'p' 'q'] ['g' 'h' 'i']]] ----------------- [[['a' 'd' 'g'] ['j' 'm' 'o'] ['r' 'u' 'x']] [['b' 'e' 'h'] ['k' '+' 'p'] ['s' 'v' 'y']] [['c' 'f' 'i'] ['l' 'n' 'q'] ['t' 'w' 'z']]] ----------------- [[['c' 'l' 't'] ['f' 'n' 'w'] ['i' 'q' 'z']] [['b' 'k' 's'] ['e' '+' 'v'] ['h' 'p' 'y']] [['a' 'j' 'r'] ['d' 'm' 'u'] ['g' 'o' 'x']]] ----------------- [[['i' 'h' 'g'] ['f' 'e' 'd'] ['c' 'b' 'a']] [['q' 'p' 'o'] ['n' '+' 'm'] ['l' 'k' 'j']] [['z' 'y' 'x'] ['w' 'v' 'u'] ['t' 's' 'r']]] ----------------- [[['r' 'u' 'x'] ['s' 'v' 'y'] ['t' 'w' 'z']] [['j' 'm' 'o'] ['k' '+' 'p'] ['l' 'n' 'q']] [['a' 'd' 'g'] ['b' 'e' 'h'] ['c' 'f' 'i']]] ----------------- [[['t' 'l' 'c'] ['s' 'k' 'b'] ['r' 'j' 'a']] [['w' 'n' 'f'] ['v' '+' 'e'] ['u' 'm' 'd']] [['z' 'q' 'i'] ['y' 'p' 'h'] ['x' 'o' 'g']]] ----------------- [[['g' 'h' 'i'] ['o' 'p' 'q'] ['x' 'y' 'z']] [['d' 'e' 'f'] ['m' '+' 'n'] ['u' 'v' 'w']] [['a' 'b' 'c'] ['j' 'k' 'l'] ['r' 's' 't']]] ----------------- [[['x' 'u' 'r'] ['o' 'm' 'j'] ['g' 'd' 'a']] [['y' 'v' 's'] ['p' '+' 'k'] ['h' 'e' 'b']] [['z' 'w' 't'] ['q' 'n' 'l'] ['i' 'f' 'c']]] ----------------- [[['z' 'q' 'i'] ['w' 'n' 'f'] ['t' 'l' 'c']] [['y' 'p' 'h'] ['v' '+' 'e'] ['s' 'k' 'b']] [['x' 'o' 'g'] ['u' 'm' 'd'] ['r' 'j' 'a']]] ----------------- [[['t' 's' 'r'] ['w' 'v' 'u'] ['z' 'y' 'x']] [['l' 'k' 'j'] ['n' '+' 'm'] ['q' 'p' 'o']] [['c' 'b' 'a'] ['f' 'e' 'd'] ['i' 'h' 'g']]] ----------------- [[['c' 'f' 'i'] ['b' 'e' 'h'] ['a' 'd' 'g']] [['l' 'n' 'q'] ['k' '+' 'p'] ['j' 'm' 'o']] [['t' 'w' 'z'] ['s' 'v' 'y'] ['r' 'u' 'x']]] ----------------- [[['a' 'j' 'r'] ['b' 'k' 's'] ['c' 'l' 't']] [['d' 'm' 'u'] ['e' '+' 'v'] ['f' 'n' 'w']] [['g' 'o' 'x'] ['h' 'p' 'y'] ['i' 'q' 'z']]] ----------------- [[['z' 'y' 'x'] ['q' 'p' 'o'] ['i' 'h' 'g']] [['w' 'v' 'u'] ['n' '+' 'm'] ['f' 'e' 'd']] [['t' 's' 'r'] ['l' 'k' 'j'] ['c' 'b' 'a']]] ----------------- [[['i' 'f' 'c'] ['q' 'n' 'l'] ['z' 'w' 't']] [['h' 'e' 'b'] ['p' '+' 'k'] ['y' 'v' 's']] [['g' 'd' 'a'] ['o' 'm' 'j'] ['x' 'u' 'r']]] ----------------- [[['r' 'j' 'a'] ['u' 'm' 'd'] ['x' 'o' 'g']] [['s' 'k' 'b'] ['v' '+' 'e'] ['y' 'p' 'h']] [['t' 'l' 'c'] ['w' 'n' 'f'] ['z' 'q' 'i']]] ----------------- [[['x' 'y' 'z'] ['u' 'v' 'w'] ['r' 's' 't']] [['o' 'p' 'q'] ['m' '+' 'n'] ['j' 'k' 'l']] [['g' 'h' 'i'] ['d' 'e' 'f'] ['a' 'b' 'c']]] ----------------- [[['g' 'd' 'a'] ['h' 'e' 'b'] ['i' 'f' 'c']] [['o' 'm' 'j'] ['p' '+' 'k'] ['q' 'n' 'l']] [['x' 'u' 'r'] ['y' 'v' 's'] ['z' 'w' 't']]] ----------------- [[['i' 'q' 'z'] ['h' 'p' 'y'] ['g' 'o' 'x']] [['f' 'n' 'w'] ['e' '+' 'v'] ['d' 'm' 'u']] [['c' 'l' 't'] ['b' 'k' 's'] ['a' 'j' 'r']]] ----------------- [[['c' 'b' 'a'] ['l' 'k' 'j'] ['t' 's' 'r']] [['f' 'e' 'd'] ['n' '+' 'm'] ['w' 'v' 'u']] [['i' 'h' 'g'] ['q' 'p' 'o'] ['z' 'y' 'x']]] ----------------- [[['t' 'w' 'z'] ['l' 'n' 'q'] ['c' 'f' 'i']] [['s' 'v' 'y'] ['k' '+' 'p'] ['b' 'e' 'h']] [['r' 'u' 'x'] ['j' 'm' 'o'] ['a' 'd' 'g']]] ----------------- [[['g' 'o' 'x'] ['d' 'm' 'u'] ['a' 'j' 'r']] [['h' 'p' 'y'] ['e' '+' 'v'] ['b' 'k' 's']] [['i' 'q' 'z'] ['f' 'n' 'w'] ['c' 'l' 't']]] 
0
source

. , 4- ( OHE).

 >>> m = np.reshape(np.arange(8),(2,2,2)) >>> m array([[[0, 1], [2, 3]], [[4, 5], [6, 7]]]) 

3 , . 24 000 , ( 1000 ):

 >>> rot_list = [] >>> for _ in range(24000): a = np.rot90(m,np.random.randint(0,4),axes=(0,np.random.randint(1,3))) b = np.rot90(a,np.random.randint(0,4),axes=(np.random.randint(1,3),0)) c = np.rot90(b,np.random.randint(0,4),axes=(1,2)) # or axes=(2,1) rot_list.append(c) >>> unique_rotation_matrices, counts = np.unique(np.asarray(rot_list),axis=0, return_counts=True) >>> len(unique_rotation_matrices) 24 

, , 24 . :

 >>> counts [1062 946 917 982 1096 978 1153 936 939 907 1183 932 958 932 1122 926 1115 954 933 932 1135 924 1138 900] 

, , .

0
source

Source: https://habr.com/ru/post/1233932/


All Articles