Bug in copy!(z,view(x,2,:)')?

Hi,
I am buffled by what copy!(z,view(x,1,:)') does. Seems off. Example:

x = [11 12;111 112]              #to read from
y = [ones(1,2)*1,ones(1,2)*2]    #2-vector of matrices, to be pushed to
z = zeros(1,2)                   #preallocate z

copy!(z,view(x,1,:)')       #copy first row of x to z
display(z)
pushfirst!(y,z)

copy!(z,view(x,2,:)')         #copy 2nd row  
display(z)
pushfirst!(y,z)

display(y)

gives

4-element Vector{Matrix{Float64}}:
 [111.0 112.0]
 [111.0 112.0]
 [1.0 1.0]
 [2.0 2.0]

Replacing by z = x[1,:]' etc does the right thing. Am I messing things up?

Looks like mutation semantics to me. You don’t reassign z in the given example, just mutate its preallocated instance. y gets the same instance assigned to z pushed to its beginning twice, in short y[1] === y[2]. If you want to see different values, you do need to allocate more.

3 Likes

Yes, the whole idea was to (keep) mutating an existing array, so as to avoid allocations in a long loop. Still, the display(z) (twice in the example) suggests that z is changed. It’s just at the stage of pushfirst!() that it somehow isn’t.

When you pushfirst!(y, z), you’re putting the matrix named z inside y. If you do it twice, y will contain [#= the matrix named z =#, #= the matrix named z =#, ...]. If you mutate the matrix named z, then that matrix changes. And since y has that matrix placed in two locations, you’ll see that change in both those locations. Because it’s just one matrix.

If you decide to do z = x[1,:]' in between, you’re deciding to repurpose the name z for a brand spanking new matrix — and that matrix will be completely independent from anything you might have called z in the past.

2 Likes

To possibly further clarify what @Benny and in the mean time also @mbauman are saying, the variable z just points to some location in memory. When you mutate z using copy! (or some .=-like operation), z still points to the same location, but the values in memory have now changed.

julia> z = ones(1, 2)
1Ă—2 Matrix{Float64}:
 1.0  1.0

julia> pointer(z)  # Memory location where z points to
Ptr{Float64} @0x000002245d8f0900

julia> z .*= 2
1Ă—2 Matrix{Float64}:
 2.0  2.0

julia> pointer(z)  # Same location (but different values in memory)
Ptr{Float64} @0x000002245d8f0900

julia> z = [2. 2.]  # New variable coincidentally also called z, with the same values as before
1Ă—2 Matrix{Float64}:
 2.0  2.0

julia> pointer(z)  # but new location
Ptr{Float64} @0x000002245da1c4a0

When you pushfirst! z into y the new first entry of y should be found at the memory location corresponding to z. Mutating z and using pushfirst! again, the first two entries both point to the same location in memory, so have the same values.

2 Likes

Thanks. I get it now. May I ask if there is a straightforward way around this? (The point is to avoid allocations.)

If you intend to have 2 different mutable matrices, you’ll need to allocate 2 mutable matrices because it’s the memory-safe way for references to share data, as you inadvertently ran into. If you only need the two, you can preallocate those. If you have a loop with indeterminate iterations, then you have a situation where you need to allocate frequently. Is it possible for the matrices to be immutable? That is far more feasible to store directly in y instead of a separate allocation, though you’ll probably want to preallocate elements for y because resizing needs allocation.

2 Likes

Thanks, I’ll follow your idea and probably set up multidimensional array y and change elements in that. (I was probably trying to be too clever with my vector of matrices…)