在Julia中切片和广播多维数组:meshgrid示例

时间:2015-03-14 15:39:13

标签: multidimensional-array slice julia

我最近开始通过编写自组织地图的简单实现来学习Julia。我希望用户指定地图的大小和尺寸,这意味着我无法真正使用循环来处理地图数组,因为我事先并不知道有多少层循环我需要。所以我绝对需要广播和切片功能,这些功能适用于任意维度的数组。

现在,我需要构建一个地图索引数组。假设我的地图是由大小为mapsize = (5, 10, 15)的数组定义的,我需要构建一个大小为indices的数组(3, 5, 10, 15),其中indices[:, a, b, c]应返回[a, b, c]

我来自Python / NumPy背景,其中解决方案已由特定的"函数",mgrid:

提供
indices = numpy.mgrid[:5, :10, :15]
print indices.shape # gives (3, 5, 10, 15)
print indices[:, 1, 2, 3] gives [1, 2, 3]

我没想到朱莉娅在一开始就有这样的功能,所以我转向广播。在NumPy中,广播基于一系列规则,我发现这些规则非常清晰和合乎逻辑。只要每个维度中的大小匹配或其中一个为1,就可以在同一个表达式中使用不同维度的数组:

(5, 10, 15)   broadcasts to  (5, 10, 15) 
   (10,  1)

(5,  1, 15)   also broadcasts to (5, 10, 15) 
(1, 10,  1)

为了解决这个问题,您还可以使用numpy.newaxis或None轻松地为数组添加新维度:

array = numpy.zeros((5, 15))
array[:,None,:] has shape (5, 1, 15)

这有助于轻松播放阵列:

A = numpy.arange(5)
B = numpy.arange(10)
C = numpy.arange(15)
bA, bB, bC = numpy.broadcast_arrays(A[:,None,None], B[None,:,None], C[None,None,:])
bA.shape == bB.shape == bC.shape = (5, 10, 15)

使用它,创建indices数组非常简单:

indices = numpy.array(numpy.broadcast_arrays(A[:,None,None], B[None,:,None], C[None,None,:]))
(indices == numpy.mgrid[:5,:10,:15]).all() returns True

一般情况当然有点复杂,但可以使用列表理解和切片来解决:

arrays = [ numpy.arange(i)[tuple([None if m!=n else slice(None) for m in range(len(mapsize))])] for n, i in enumerate(mapsize) ]
indices = numpy.array(numpy.broadcast_arrays(*arrays))

回到朱莉娅。我试图应用相同的理由,最终实现了上面代码的arrays列表的等价物。由于复合表达式语法,这最终比NumPy对应的更简单:

arrays = [ (idx = ones(Int, length(mapsize)); idx[n] = i;reshape([1:i], tuple(idx...))) for (n,i)=enumerate(mapsize) ]

现在我被困在这里,因为我真的不知道如何将广播应用到我的生成数组列表中...... broadcast[!]函数要求函数f应用,我没有。我尝试使用for循环来尝试强制广播:

indices = Array(Int, tuple(unshift!([i for i=mapsize], length(mapsize))...))
for i=1:length(mapsize)
    A[i] = arrays[i]
end

但这给了我一个错误:ERROR: convert has no method matching convert(::Type{Int64}, ::Array{Int64,3})

我这样做是对的吗?我忽略了重要的事情吗?任何帮助表示赞赏。

3 个答案:

答案 0 :(得分:5)

如果你正在运行julia 0.4,你可以这样做:

julia> function mgrid(mapsize)
           T = typeof(CartesianIndex(mapsize))
           indices = Array(T, mapsize)
           for I in eachindex(indices)
               indices[I] = I
           end
           indices
       end

如果可以说

,那就更好了
indices = [I for I in CartesianRange(CartesianIndex(mapsize))]

我会调查: - )。

答案 1 :(得分:5)

Julia中的广播几乎都是在NumPy广播中建模的,所以你应该发现它遵循或多或少相同的简单规则(不确定当不是所有输入具有相同数量的维度时填充尺寸的方式是相同的,因为Julia数组是列主要的。)

但是,newaxis索引和broadcast_arrays等许多有用的内容尚未实现。 (我希望他们愿意。)另请注意,与NumPy相比,Julia中的索引工作方式略有不同:当您在NumPy中留下尾随维度的索引时,其余索引默认为冒号。在Julia,他们可以说是默认为。

我不确定你是否真的需要一个meshgrid函数,大多数你想要使用它的东西可以通过使用arrays数组的原始条目和广播操作来完成。 meshgrid在matlab中有用的主要原因是因为它在广播中很糟糕。

但使用broadcast!函数完成您想要的操作非常简单:

# assume mapsize is a vector with the desired shape, e.g. mapsize = [2,3,4]

N = length(mapsize)
# Your line to create arrays below, with an extra initial dimension on each array
arrays = [ (idx = ones(Int, N+1); idx[n+1] = i;reshape([1:i], tuple(idx...))) for (n,i) in enumerate(mapsize) ]

# Create indices and fill it one coordinate at a time
indices = zeros(Int, tuple(N, mapsize...))
for (i,arr) in enumerate(arrays)
    dest = sub(indices, i, [Colon() for j=1:N]...)
    broadcast!(identity, dest, arr)
end

我必须在arrays的条目上添加一个初始单例维度,以与indices的轴对齐(newaxis在这里很有用......)。 然后我遍历每个坐标,在indices的相关部分创建一个子阵列(一个视图),然后填充它。 (索引将默认返回Julia 0.4中的子数组,但是现在我们必须明确使用sub

broadcast!的调用只是评估输入identity(x)=x上的标识函数arr=arrays[i],广播到输出的形状。使用identity函数没有效率损失; broadcast!根据给定的函数,参数的数量和结果的维数生成一个专门的函数。

答案 2 :(得分:3)

我想这与MATLAB meshgrid功能相同。我从来没有真正考虑到超过两个维度的概括,所以它更难以理解。

首先,这是我的完全通用版本,这有点疯狂,但我无法想到更好的方法来做到这一点,而不会生成常见维度的代码(例如2,3)

function numpy_mgridN(dims...)
    X = Any[zeros(Int,dims...) for d in 1:length(dims)]
    for d in 1:length(dims)
        base_idx = Any[1:nd for nd in dims]
        for i in 1:dims[d]
            cur_idx = copy(base_idx)
            cur_idx[d] = i
            X[d][cur_idx...] = i
        end
    end
    @show X
end
X = numpy_mgridN(3,4,5)
@show X[1][1,2,3]  # 1
@show X[2][1,2,3]  # 2
@show X[3][1,2,3]  # 3

现在,代码生成的意思是,对于2D情况,您可以简单地执行

function numpy_mgrid(dim1,dim2)
    X = [i for i in 1:dim1, j in 1:dim2]
    Y = [j for i in 1:dim1, j in 1:dim2]
    return X,Y
end

并且对于3D案例:

function numpy_mgrid(dim1,dim2,dim3)
    X = [i for i in 1:dim1, j in 1:dim2, k in 1:dim3]
    Y = [j for i in 1:dim1, j in 1:dim2, k in 1:dim3]
    Z = [k for i in 1:dim1, j in 1:dim2, k in 1:dim3]
    return X,Y,Z
end

用例如

X,Y,Z=numpy_mgrid(3,4,5)
@show X
@show Y
@show Z

我猜mgrid将它们全部推到一个张量中,所以你可以这样做

all = cat(4,X,Y,Z)

仍然略有不同:

julia> all[1,2,3,:]
1x1x1x3 Array{Int64,4}:
[:, :, 1, 1] =
 1

[:, :, 1, 2] =
 2

[:, :, 1, 3] =
 3

julia> vec(all[1,2,3,:])
3-element Array{Int64,1}:
 1
 2
 3