How to calculate all 24 rotations of 3d array?

后端 未结 9 737
清酒与你
清酒与你 2021-02-12 10:45

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

Numpy\'s array manipulation routines includes a rot90 me

相关标签:
9条回答
  • 2021-02-12 10:59

    Look at the code for rot90. I see 3 variations on flip and swapaxes, depending on k the axis parameter.

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

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

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

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

    If rot90 gives you 4 of the rotations, you should be able apply the same routines to produce the others. You just have to understand the logic underlying rot90.

    e.g.

    def flipbf(m):
        return m[:,:,::-1]
    
    flipbf(m).swapaxes(0, 2)
    flipbf(m).swapaxes(1, 2)
    etc
    
    0 讨论(0)
  • 2021-02-12 11:04

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

    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 wrong, the 24 rotations you are looking for are a combination of these 9 transformations.

    0 讨论(0)
  • 2021-02-12 11:04

    First let's determine the 48 isometries, as matrices that permute the basis vectors, possibly flipping 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's filter for the rotations, which are the isometries of determinant 1:

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

    Finally, we can apply such a rotation to a polycube by shifting the array indices such that the inner 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 example:

    >>> 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 讨论(0)
  • 2021-02-12 11:07

    Update: Numpy 1.12.0 added an axes argument to the rot90 function

    Here's how I made all 24 rotations:

    from numpy import rot90, array
    
    def rotations24(polycube):
        """List all 24 rotations of the given 3d array"""
        def rotations4(polycube, axes):
            """List the four rotations of the given 3d array in the plane spanned by the given axes."""
            for i in range(4):
                 yield rot90(polycube, i, axes)
    
        # imagine shape is pointing in axis 0 (up)
    
        # 4 rotations about axis 0
        yield from rotations4(polycube, (1,2))
    
        # rotate 180 about axis 1, now shape is pointing down in axis 0
        # 4 rotations about axis 0
        yield from rotations4(rot90(polycube, 2, axes=(0,2)), (1,2))
    
        # rotate 90 or 270 about axis 1, now shape is pointing in axis 2
        # 8 rotations about axis 2
        yield from rotations4(rot90(polycube, axes=(0,2)), (0,1))
        yield from rotations4(rot90(polycube, -1, axes=(0,2)), (0,1))
    
        # rotate about axis 2, now shape is pointing in axis 1
        # 8 rotations about axis 1
        yield from rotations4(rot90(polycube, axes=(0,1)), (0,2))
        yield from rotations4(rot90(polycube, -1, axes=(0,1)), (0,2))
    

    Test that all 24 rotations are indeed distinct:

    polycube = 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]]])
    
    assert len(set(str(x) for x in rotations24(polycube))) == 24
    
    0 讨论(0)
  • 2021-02-12 11:08

    I've decided to post another answer. My first answer was very straightforward, but not entirely in the spirit of the question. I think I have a better answer now, after thinking about it for a while.

    TL;DR The following code rotates a polycube through all 24 rotations of the cube. It does so with no matrix multiplications, instead using a 3x3x3x3x3 index table which gives a few key rotations of the indices in the polycube, and a length-23 Gray code for the rotation group of the cube. It has only six real lines of code, almost all of which are for loops. It is extremely fast, and fairly compact (except for the index table, which could 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 a Gray code for the rotation group of the cube.

    First, we must find a nice representation for the rotation group you are using. Consider a 2x2x2 cube with 1x1x1 cubies labeled in the following manner:

    1 2   
    3 4   4 3
          2 1
    

    We number starting at 1 to be consistent with the mathematics literature. The upper left square is meant to be the front face, and the lower right square the rear face. Imagine the 1 connected by edges to the 2 and 3 in the front face, and to the 4 in the rear face, etc.

    Note that diagonally opposite cubies have the same number. Now any rotation of the cube corresponds to a permutation of the four numbers on the front face. Furthermore, any permutation of the numbers on the front face corresponds to a rotation. The four rotations in the z-axis coming out of the page are

    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 label the rotations by the effect on the front face. They are 1234, 3142, 4321, and 2413.

    Three additional rotations about the x-axis (plus the identity rotation, with which we start) are

    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 notation, the additional rotations are 3421, 2143, and 4312.

    Three additional rotations about the y-axis are

    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 notation, the three additional rotations are 2341, 3412, and 4123.

    There are three rotations, including the identity, that fix the 1's 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 ones labeled 1423 and 1342.

    There are three rotations, including the identity, that fix the 2's diagonal:

    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 the new ones labeled 4213 and 3241.

    There are three rotations, including the identity, that fix the 3's diagonal:

    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 the new ones labeled 2431 and 4132.

    There are three rotations, including the identity, that fix the 4's diagonal:

    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 the new ones labeled 2314 and 3124.

    Finally, there are six edge flips plus the identity. The flips in the 1-2 edge are

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

    with the new one represented by 2134. Similarly there are edge flips in 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 distinct permutation of 1234, and since there are 24 permutations and 24 rotations, each distinct permutation corresponds to a rotation. We say that the rotation group of the cube, and the group S_4 of permutations of 1234, are isomorphic. (One way to think of this is that rotations of a cube have the action of permuting the diagonals of the cube, and the correspondence is 1-1.)

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

    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'll throw in

    R0=identity operation
    

    for completeness. Using just those three generators, we can find a Hamiltonian circuit through the Cayley graph of the rotation group:

    01 1234 apply R0 to starting position, i.e., 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 in this list exactly once, and that moving from one item in the list to the next is accomplished by a transposition of items in positions 1-2, 2-3, or 3-4, which corresponds to the edge flip rotations R1, R2, and R3. This Gray code for S_4 (and much else, which is unfortunately rather difficult to understand if you don't know about reflection groups and Coxeter diagrams) is presented in the famous paper Gray Codes for Reflection Groups by Conway, Sloane, and Wilks.

    The Gray code is summarized 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}
    

    So we have vastly reduced the amount of work we have to do, from figuring the effect of 24 different rotations on a 2x2x2 polycube (and then a 3x3x3 polycube), to figuring the effect of just 3 different rotations on our cubes. Those three rotation functions could be stored in an array indexed by 0..3, and then applied like

    nextConfiguration = currentConfiguration.Rotation[gray[i]]
    

    (Note that we can even get all 48 reflection/rotations if we want, by following that sequence of rotations by a single reflection P (any reflection will do, so choose the simplest to implement), and then following that reflection by the gray sequence in reverse, which gives a Gray code for the full group of 48 reflection/rotations. (Building larger sequences by tacking the reverse of a sequence onto itself is one of the "big ideas" in the construction of Gray codes.))

    We have come a long way without even considering the representation of the shapes that you want to rotate, but now we have to implement R1, R2, and R3 so we need to consider the data structure that represents the shapes, a 3 dimensional array. Let's consider the effect of R1, the 180 degree edge rotation on an axis through the midpoints of the 1-2 edges, 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 R1 operation can be implemented by

    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)
    

    That defines the operation R1 in the 2x2x2 case. It should be clear how to define R2 and R3 in the 2x2x2 case, and also how to define those operations in the 3x3x3 case.

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

    (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 = F.F.F.R1.F (where . means "followed by"), and R3 = F.F.R1.F.F. (These are "conjugates" of R1.)

    For the 3x3x3 case, it is some work to find the index tables by hand, so I have written a routine to do it, and then I found all 24 rotations of an alphabet cube. See code below. I'm sure it can be made much briefer by a Python expert. This is my first program in Python.

    The idea is to rotate not the contents of the 3x3x3 array, but its indices.

    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)
    

    Here is the output from the program

    [[['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 讨论(0)
  • 2021-02-12 11:12

    Edit: As my solution basically boils down to the product of the parities of the axes multiplied by the parity of the permutation of the axes, the simplest method for generating all of the regular rotations of an n-dimensional array is this (swiping some code form @Divakar's 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 tensor algebra below.

    Background

    Easiest way to explain this is in tensor terms, so lets turn all those rotations into rotation tensors. Rotation tensors are n x n matrices that rotate an n-dimensional space. As such they have a few properties:

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

    In addition, for 90 degree roatations all terms must be either 0, 1, or -1.

    In three dimensions, there are three basic families of these, which compose togther to make your 24 rotations.

    The first is 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 involves negating 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 negates the terms if negative

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

    The imprtant thing about these families is in each family any product, power or transpose of two matrices yields another matrix in the family. Since we have three families, their products with each other form all the 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 yeild similar matrices with determinant = -1

    Application

    That's all well and good, but how does that relate to array manipulation? We don't want to rotate by matrix multiplication, as that will cause undue overhead in memory and processing. Luckily, each family is easily related to a an array manipulation that produces 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]
    

    All of these produce views of m, and you can create a generator that produces views relating to all of the rotations by:

    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)
    
    0 讨论(0)
提交回复
热议问题