thehardyreader
thehardyreader

Reputation: 113

Function fill() for initialising array of arrays leads to unexpected results

I initialise two arrays of arrays using fill():

A = fill(Vector{Float32}(zeros(Float32, 4)), 3);
B = fill(Vector{Float32}(ones(Float32, 4)), 3);

When I then try to add two of the subarrays A[1], B[1] and store it in-place in the first array A[1] by A[1] .+= B[1] this results in all elements of A to be changed:

3-element Vector{MVector{4, Float32}}:
 [1.0, 1.0, 1.0, 1.0]
 [1.0, 1.0, 1.0, 1.0]
 [1.0, 1.0, 1.0, 1.0]

I found a workaround by initialising the arrays differently

A = Array{Vector{Float32},1}(undef, 3) 

for x in 1:3
    A[x] = zeros(Float32,4)
end

B = Array{Vector{Float32},1}(undef, 3) 

for x in 1:3
    B[x] = ones(Float32,4)
end

When I now do the same in-place addition, A is (as expected)

3-element Vector{Vector{Float32}}:
 [1.0, 1.0, 1.0, 1.0]
 [0.0, 0.0, 0.0, 0.0]
 [0.0, 0.0, 0.0, 0.0]

I suspect, that I do not fully understand what fill does as it seems like that the broadcasting .+= acts on the whole array of arrays and not the indexed subarray.

  1. Why is this the behaviour dependent on the initialisation?
  2. Is there a way to get the same behaviour as in the workaround but using fill or any other one-line initialisation.

I am quite new to Julia, any help is appreciated. I work with the Julia version 1.9.2.

Upvotes: 1

Views: 38

Answers (1)

L.Grozinger
L.Grozinger

Reputation: 2398

When you do A = fill(zeros(4), 3), you are taking the object zeros(4) and making an Array of that object with the dimensions specified. It does not make 3 different zeros(4) and make an Array of them.

In your loop version that is exactly what you do, you make a new zeros(4) each iteration and then slot it into the Array. To see the difference, imagine you had written this:

A = Vector{Vector{Float32}}(undef, 3)

x = zeros(Float32, 4)

for i in 1:3
    A[i] = x
end

Now you do your operation on the elements of A:

A[1] .+= ones(Float32, 4)

All the elements of A will change, because in fact there is really only one element, repeated 3 times:

julia> A
3-element Vector{Vector{Float32}}:
 [1.0, 1.0, 1.0, 1.0]
 [1.0, 1.0, 1.0, 1.0]
 [1.0, 1.0, 1.0, 1.0]

Further, since A = [x, x, x] you will also see your modification of A[1] reflected in x:

julia> x
4-element Vector{Float32}:
 1.0
 1.0
 1.0
 1.0

To do what you are trying to do in a concise way you could use list comprehensions:

A = [zeros(4) for _ in 1:3]
B = [ones(4) for _ in 1:3]
A[1] .+= B[1]

Usually it would be even better to use a Matrix instead of a Vector{Vector}. In this case you would have avoided this particular trap with the fill method E.g.:

A = fill(0f0, 3, 4)
B = fill(1f0, 3, 4)
A[1, :] .+= B[1, :]

Would give you:

julia> A
3×4 Matrix{Float32}:
 1.0  1.0  1.0  1.0
 0.0  0.0  0.0  0.0
 0.0  0.0  0.0  0.0

Upvotes: 2

Related Questions