如何计算3d数组的所有24个旋转?

时间:2015-10-17 18:21:42

标签: python numpy

我有一个3d numpy数组描述polycube(想象一个3d俄罗斯方块)。如何计算所有24个旋转?

Numpy的array manipulation routines包含了一个rot90方法,它提供了24个方法中的4个,但我对如何计算其余内容毫无头绪。我唯一的想法是将3d数组转换为坐标的2d矩阵,乘以旋转矩阵,然后转换回来。但我宁愿直接使用3d数组。

示例2x2x2数组:

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

       [[1, 1],
        [0, 0]]])

示例3x3x3数组:

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]]])

编辑:我只想要24个方向保持等距,而不是所有48个旋转和反射(虽然知道如何制作它们会很有趣)。如果它有助于测试,我相信3x3x3示例没有旋转对称性并且是手性的(因此48是不同的)。

动机:我正在为Soma cube式拼图编写解算器。

9 个答案:

答案 0 :(得分:7)

查看code for rot90。我在flipswapaxes上看到了3种变体,具体取决于k轴参数。

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

fliplr(m)只是m[:, ::-1],毫不奇怪,flipudm[::-1, ...]

您可以使用m[:,:,::-1]m[...,::-1]翻转第3轴。

np.transpose是排列轴的另一种工具,可能会或可能不会比swapaxes更容易使用。

如果rot90给你4个旋转,你应该能够应用相同的例程来产生其他的旋转。您只需要了解rot90背后的逻辑。

e.g。

def flipbf(m):
    return m[:,:,::-1]

flipbf(m).swapaxes(0, 2)
flipbf(m).swapaxes(1, 2)
etc

答案 1 :(得分:6)

到目前为止,我有12个,组成numpy.transpose来置换轴(xyz,yzx,zxy-所有相同的手性)和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

快速测试12是截然不同的:len(set(str(x) for x in rotations(polycube)))

更新:这是我如何制作所有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)

使用这个辅助函数推广rot90绕任何轴旋转:

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

我不相信这个助手是完美的,但似乎有效。

答案 2 :(得分:3)

编辑:由于我的解决方案基本上可以归结为轴的奇偶乘以轴的parity of the permutation的乘积,因此,这是生成轴的所有规则旋转的最简单方法这是一个n维数组(在@Divakar的答案中刷一些代码):

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)    

这适用于任意维度的任何(甚至非正方形)张量,并且直接基于下面的张量代数下规则旋转的定义。

背景

最简单的解释方法是张量,因此让所有这些旋转变为旋转张量。旋转张量是旋转n维空间的n x n矩阵。因此,它们具有一些属性:

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

此外,对于90度旋转,所有项都必须为0、1或-1。

在三个维度中,这些元素共有三个基本族,它们总共构成了24个旋转。

第一个是简单排列:

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]]]

第二个涉及否定某些项,以便对角线的乘积始终为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]]]

第三个参数确定排列是正数还是负数,如果负数则否定项

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

 [[ 0, 0,-1],
  [ 0,-1, 0],
  [-1, 0, 0]],

关于这些家庭的紧要关头是,在每个家庭中,两个矩阵的任何乘积,幂或转置都会在该家庭中产生另一个矩阵。由于我们有3个族,所以它们的乘积彼此构成所有可能的旋转,在这种情况下为3 * 4 * 2 = 24

注意:其他24个“不规则”旋转是相同的矩阵乘以-np.eye(3),得出行列式= -1的相似矩阵

应用

这很好,但这与数组操作有什么关系?我们不想轮流使用矩阵乘法,因为那样会导致内存和处理的开销过大。幸运的是,每个族很容易与产生视图的数组操作相关。

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]

所有这些都产生m的视图,您可以通过以下方式创建一个生成器,该生成器产生与所有旋转有关的视图:

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 :(得分:2)

另一种选择是围绕表示矩阵的立方体轴旋转组合。类似的东西:

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

如果我没错,你正在寻找的24个旋转是这9个转换的组合。

答案 4 :(得分:2)

我们将从获取所有48组合的意图开始,以便获得有关将其求解为n维数组的一般思路。稍后,我们将过滤掉不需要的24

解决所有旋转问题的通用思路

解决一般情况的想法是基本上做两件事-沿每个轴进行翻转和对给定轴数进行所有组合的置换轴。

翻转:要翻转,我们将使用stepsize参数进行切片,即array [:: stepsize]。因此,要翻转,它将是:[::-1],而如果不翻转,则将是:[::1]。对于两个简单的组合,可以将stepsize分配为在1-1之间变化的变量。对于ndarray,只需将其扩展到所有轴即可。

置换轴:要实现此目的,我们可以使用np.transpose并指定所需的置换顺序作为其轴参数。我们将使用itertools.permutations生成所有可能的订单。

这就是全部!让我们以a作为输入3D数组来实现它-

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

我们可以用一个循环简化嵌套循环的翻转-

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

因此,这使我们获得所有48组合。

扩展到更多维度::如果要将其扩展到4D数组,只需编辑初始化部分以扩展2并沿另一个轴切片。


解决24旋转

现在,由于OP声称拥有working solution才能获得所需的24组合,因此我们需要从我们提出的解决方案中过滤out。我找不到用于过滤的通用模式,但获得了建立索引所需的索引-

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])

如果您关心订单要获得与rotations24相同的输出,我们将有-

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])

因此,请使用24获得必需的indexing个-

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

这适用于具有任何通用长度的3D数组。

运行示例进行验证

# From https://stackoverflow.com/a/33190472/ @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

验证-

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

答案 5 :(得分:2)

本页底部有24个旋转矩阵:http://www.euclideanspace.com/maths/algebra/matrix/transforms/examples/index.htm

如果俄罗斯方块片断由坐标三元组数组表示,则要对片断进行旋转,您只需将矩阵乘以数组中的每个三元组即可。您将执行24次以获取24个不同的片段。

例如,如果您的盒子数组是(1,2,5),(1,2,4),(1,3,4)

例如,您目前正在考虑的轮换

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

然后旋转的图形具有表示形式

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

其中。代表矩阵乘法。对列表中的每个M执行此操作,您将获得所有24个旋转。 (您可以对矩阵进行预乘或后乘,如果不希望整个旋转集没有特定顺序都没关系。)

这很简单。

现在,要将数据从您的数据结构中移到我上面使用的数组结构中,您必须做类似的事情(对不起,我不懂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

或类似的东西。最后,要从newArray表示形式转到多立方体,您将执行类似的操作

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

对于2x2x2的多维数据集,您可以这样做

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

以及相应的逆函数。

答案 6 :(得分:0)

首先让我们确定48个等距线,它们是置换基向量的矩阵,可能会翻转一些符号:

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]])

然后让我们过滤旋转,这是行列式1的等距图。

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

最后,我们可以通过移动数组索引以内点为原点,然后将索引向量乘以旋转矩阵,将这种旋转应用于多面体:

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

例如:

>>> 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.]]])

答案 7 :(得分:0)

我决定发布另一个答案。我的第一个答案很简单,但并非完全出于问题的精神。经过一段时间的思考,我认为我现在有一个更好的答案。

TL; DR以下代码在多维数据集的所有24次旋转中旋转一个多维数据集。它无需矩阵乘法即可执行此操作,而是使用3x3x3x3x3索引表,该表给出了多维多维数据集中索引的一些关键旋转,以及一个长度为23的格雷码,用于多维数据集的旋转组。它只有六行真正的代码,几乎所有代码都是for循环的。它非常快速且相当紧凑(除了索引表,该表可以由程序生成(见下文))。

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)

我的解决方案是为多维数据集的旋转组找到一个Gray code

首先,我们必须为您使用的旋转组找到一个很好的表示形式。考虑一个2x2x2多维数据集,其中1x1x1立方体以下列方式标记:

1 2   
3 4   4 3
      2 1

我们的数字从1开始,以便与数学文献一致。左上角的正方形是前表面,右下角的正方形是后表面。想象一下通过边缘连接到正面的2和3以及背面的4等的1。

请注意,对角相对的cub具有相同的编号。现在,立方体的任何旋转都对应于正面上四个数字的排列。此外,前面的数字的任何排列都对应于旋转。从页面出来的z轴上的四个旋转是

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

我们可以通过正面上的效果来标记旋转。它们是1234、3142、4321和2413。

围绕x轴的另外三个旋转(加上我们开始的标识旋转)

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

在我们更紧凑的表示法中,附加旋转为3421、2143和4312。

绕着y轴的另外三个旋转

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

以紧凑表示法,另外三个旋转分别是2341、3412和4123。

包括标识在内的三个旋转可固定1的对角线:

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

新标签为1423和1342。

固定2个对角线的三个旋转(包括身份):

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

新标签为4213和3241。

有三个旋转(包括身份)可以固定3的对角线:

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

新标签为2431和4132。

固定4个对角线的三个旋转(包括身份):

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

新标签为2314和3124。

最后,有六个边缘翻转以及身份。 1-2边的翻转是

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

,新的2134代表。类似地,边1-3(3214),1-4(4231),2-3(1324),2-4(1432)和3-4都有边翻转(1243)。

24个旋转中的每个对应于1234的不同排列,并且由于存在24个排列和24个旋转,因此每个不同的排列都对应于一个旋转。我们说立方体的旋转组和1234的置换组S_4是同构的。 (一种思考方式是,立方体的旋转具有置换立方体的对角线的作用,对应关系为1-1。)

我们可以找出阵列中24个旋转中的每个旋转所产生的影响,但这很乏味且容易出错。相反,我们将看到旋转组仅由三个旋转生成,边缘翻转

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

及其组成。我们将投入

R0=identity operation

出于完整性考虑。仅使用这三个生成器,我们就可以通过旋转组的Hamiltonian circuit找到一个Cayley graph

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

请注意,每个排列(即,多维数据集的旋转)仅在此列表中出现一次,并且从列表中的一项移至下一项是通过对位置1-2、2-3中的项进行转置来完成的,即3-4,对应于边缘翻转旋转R1,R2和R3。著名的论文Gray Codes for Reflection Groups by Conway, Sloane, and Wilks中介绍了S_4的格雷码(还有很多其他东西,如果您不了解反射群和Coxeter图,很遗憾很难理解)。

格雷码由以下23个数字组成:

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

因此,从计算2x2x2多维数据集(然后是3x3x3多维数据集)上24个不同旋转的效果,到计算多维数据集仅3个不同旋转的效果,我们已经大大减少了工作量。这三个旋转函数可以存储在以0..3索引的数组中,然后像

一样应用
nextConfiguration = currentConfiguration.Rotation[gray[i]]

(请注意,如果需要,我们甚至可以得到全部48个反射/旋转,通过将旋转序列跟随一个反射P(任何反射都可以,因此选择最简单的实现方法),然后通过反向的灰色序列,它为48个反射/旋转的整个组提供了格雷代码(通过将序列的反向钉在自身上来构建更大的序列,这是格雷代码构造中的“重要思想”之一。) )

我们走了很长一段路,甚至没有考虑要旋转的形状的表示形式,但是现在我们必须实现R1,R2和R3,因此我们需要考虑代表这些形状的数据结构,即3维数组。让我们考虑R1的影响,即在2x2x2数组上通过1-2条边的中点的轴上的180度边旋转:

(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)

这告诉我们可以通过以下方式实现我们的R1操作

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)

这定义了2x2x2情况下的操作R1。应该清楚如何在2x2x2情况下定义R2和R3,以及如何在3x3x3情况下定义那些操作。

最后,我要说的是Ri运算符的定义很繁琐且容易出错,因此如果可以帮助我们,我们不想做太多。我们可以只定义两个运算符,即R1(已在上面定义)和F,即正面旋转:

(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)

然后我们可以表示R2 = F.F.F.R1.F(其中。表示“跟随”),R3 =F.F.R1.F.F。 (这些是R1的“共轭”。)

对于3x3x3情况,手动查找索引表是一项工作,因此我编写了一个例程来执行此操作,然后找到了字母立方体的所有24个旋转。请参见下面的代码。我敢肯定,Python专家可以简化它。这是我的第一个Python程序。

这个想法不是旋转3x3x3数组的内容,而是旋转其索引。

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']]]

答案 8 :(得分:0)

在送入我的神经网络之前,尝试随机旋转3D一个热编码的numpy数组时,我遇到了类似的问题。 我将在此处演示3D数组,但这也适用于第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]

分布看上去相当均匀,但是多次运行会发现分布略有偏差。