Data assignments between arrays of arrays

As an old Matlab user I am still sometimes struggling to understand how Julia assigns data in “arrays of arrays”:
For example, I want to copy a page of data from a 3D data array. The result is a Matrix, as expected:

A = zeros(3,3,2);
b = a[:,:,1]
julia> b
3×3 Matrix{Float64}:
** 0.0 0.0 0.0**
** 0.0 0.0 0.0**
** 0.0 0.0 0.0**

Similarly, I can replace a page of A:

A = zeros(3,3,2);
b = rand(3,3)
A[:,:,end] .= b
julia> A
3×3×2 Array{Float64, 3}:
[:, :, 1] =
** 0.0 0.0 0.0**
** 0.0 0.0 0.0**
** 0.0 0.0 0.0**

[:, :, 2] =
** 0.169528 0.718634 0.5193**
** 0.452352 0.807573 0.204051**
** 0.226577 0.223511 0.633584**

However, it doesn’t seem to work for “Arrays in Arrays” as I have naively thought. There, the extracted part b is still identical(!) to A

A = fill(rand(2,2,2),2,2)
b = A[:,:][:,:,1]

julia> A == b
true

So how can I copy a subset from A?
Analogously, it doesn’t seem to be possible to replace a subset of A by other data, like:

A = fill(rand(2,2,2),2,2)
julia> A[:,:][:,:,1] .= rand(2,2)
ERROR: MethodError: Cannot convert an object of type Float64 to an object of type Array{Float64, 3}

I know that this is a beginners question, but it really bothers me for some time now and all my web searches were unsucessful. I would appreciate any hints. Thanks!
Alex

In this case you query for the full array using A[:,:] and then ask for A[:,:,1], which works, as the index allows higher dimensions when using 1 as index.

julia> A[:,:] == A
true

julia> A[:,:,1] == A
true

julia> A[:,:,1,1] == A
true

What you want is the [:,:,1] for every index of A. This you could do using getindex, but this returns a Matrix per index of A.

julia> getindex.(A,:,:,1)
2×2 Matrix{Matrix{Float64}}:
 [0.310872 0.881689; 0.779912 0.138566]  [0.310872 0.881689; 0.779912 0.138566]
 [0.310872 0.881689; 0.779912 0.138566]  [0.310872 0.881689; 0.779912 0.138566]

A better way might be to use a 5 dimensional data structure.

julia> A = zeros(2,2,2,2,2);

julia> subA = rand(2,2,2)
2×2×2 Array{Float64, 3}:
[:, :, 1] =
 0.975893  0.584884
 0.463343  0.217259

[:, :, 2] =
 0.948323  0.414374
 0.381338  0.156916

julia> for i in 1:size(A,1), j in 1:size(A,2)
           A[i,j,:,:,:] .= subA
       end

julia> A[:,:,:,:,1]
2×2×2×2 Array{Float64, 4}:
[:, :, 1, 1] =
 0.975893  0.975893
 0.975893  0.975893

[:, :, 2, 1] =
 0.463343  0.463343
 0.463343  0.463343

[:, :, 1, 2] =
 0.584884  0.584884
 0.584884  0.584884

[:, :, 2, 2] =
 0.217259  0.217259
 0.217259  0.217259

To make your posts on discourse easier to read you can surround your code blocks by ``` .

4 Likes

There is also the package TensorCast.jl that might help with more complicated transforms.

Others to look at are Tullio.jl, TensorOperations.jl, OMEinsum.jl, Einsum.jl

As @feanor12 said, A[:, :] doesn’t do what you think, it just creates a copy of A. And since it is a copy of A, you would normally not see changes to A[:, :] in A, because you were changing the copy, not the original. Interestingly, in this case you do see the changes, because A and copy(A) are both pointing to the same array internally.

And that brings me to a different point. This:

A = fill(rand(2,2,2),2,2)

probably does not do what you expect. It creates a single array (rand(2,2,2)) and then creates a 2x2 array filled with references to the very same array. So this happens:

1.7.2> A = fill(rand(2,2,2),2,2);

1.7.2> A[1,1][1,1,1] = 100; # only make one change

1.7.2> A[1,1][:,:,1]  # expect one element to be 100.0
2×2 Matrix{Float64}:
 100.0       0.336786
   0.142365  0.36947

1.7.2> A[2,1][:,:,1]  # did not expect any element = 100.0, but there it is!
2×2 Matrix{Float64}:
 100.0       0.336786
   0.142365  0.36947

This is because, as explained, A is just a bunch of pointers all pointing to the same array.

Perhaps this is what you wanted:

A = [rand(2,2,2) for i in 1:2, j in 1:2]  # creates 4 different random arrays.

Now, if you want to set the first slice of each array inside A to the same values, you can try this:

1.7.2> setindex!.(A, Ref(fill(100.0, 2,2)), :, :, 1)
2×2 Matrix{Array{Float64, 3}}:
 [100.0 100.0; 100.0 100.0;;; 0.305401 0.883966; 0.541377 0.487306]  …  [100.0 100.0; 100.0 100.0;;; 0.218273 0.0704365; 0.527901 0.199329]
 [100.0 100.0; 100.0 100.0;;; 0.267275 0.0161309; 0.800307 0.14134]     [100.0 100.0; 100.0 100.0;;; 0.51063 0.903457; 0.907086 0.70358]

1.7.2> A[1,1]
2×2×2 Array{Float64, 3}:
[:, :, 1] =
 100.0  100.0
 100.0  100.0

[:, :, 2] =
 0.305401  0.883966
 0.541377  0.487306

1.7.2> A[2,1]
2×2×2 Array{Float64, 3}:
[:, :, 1] =
 100.0  100.0
 100.0  100.0

[:, :, 2] =
 0.267275  0.0161309
 0.800307  0.14134

Is that what you wanted?

6 Likes