Scope, for loops and arrays

I’m using Julia 1.0 (yay!) and have set up a function where I’m having behavior I do not understand. Specifically, the following works:

    ϵ33FieldOld=[0.0 for theX3 in x3vals, theX1 in x1vals]

    for i in 1:10
       # compute a bunch of stuff here

        nz,nx=size(ϵ33Field)
        for i in 1:nz, j in 1:nx
            ϵ33FieldOld[i,j]=ϵ33Field[i,j]
        end
   end

In other words, the saved value of the array into ϵ33FieldOld works as I expect; I can refer to the previous value of ϵ33Field in the subsequent pass through the loop.

However, if I instead use:

    ϵ33FieldOld=[0.0 for theX3 in x3vals, theX1 in x1vals]

    for i in 1:10
       # compute a bunch of stuff here

       ϵ33FieldOld=ϵ33Field
   end

The ϵ33FieldOld is not updated. It always remains 0.

While the brute force copy works, it seems odd that I have to do this.

Any tips would be greatly appreciated.

It’s unrelated to scope or loops and isn’t specific to arrays.

What you are seeing is just the difference between changing binding and mutation. Simple assignment to local variables like ϵ33FieldOld = ϵ33Field only change the object the variable on the LHS is refering to. It never mutate the object that was in the variable and variable does not equal to object in julia (unlike C++). So after the assignment the variable points to a new value but anything that still refer to the old one is unchanged.

OTOH, setindex! is a mutation so it changes the object that is refered to by the variable (ϵ33FieldOld) and does not change which object the variable refer to. At the end of the loop, the variable still refer to the same object that has a different value.

You can achieve the effect of the loop without a explicit loop with ϵ33FieldOld .= ϵ33Field, which basically does the same thing as the loop. If you don’t want the copy of data, you can also just return the new ϵ33FieldOld from the function (or if this is actually in global scope, you can explicitly refer to that global variable by declaring global ϵ33FieldOld in the loop. You should also put the loop in a function instead).

Thanks for the comments. I should have noted that the small code extract is in fact from a function; there should be no global variables involved. I only need the results within the loop too.

However, I’m still working to grasp Julia terminology and I don’t fully follow your explanation. Specifically, if I simply use ϵ33FieldOld = ϵ33Field at the end of the loop, and then examine the values of ϵ33FieldOld immediately afterward, the values referenced by ϵ33FieldOld[1,2] (for example) are completely unchanged.
When you say:

To me that means the ϵ33FieldOld should refer to the same memory contents as ϵ33Field (in other words, in my simple mind it is like setting pointers equal in C).

But my understanding is definitely wrong, and I would appreciate any further explanations that might help. I can easily do ϵ33FieldOld.=ϵ33Field but I’d like to understand what I’m doing also! :grin:

Yes. If you have

for <...>
    a = b
end
a

that doesn’t work the way you want, you may have a scope issue. It’s also likely that since the two array are exactly the same your code might not be doing what you want it to do. Without full code it’s hard to tell.

It’s the broadcast version (.) of =, so it apply = to each index, or in another word,

Usually folks as for MWE, but since you indicate uncertainty because of the lack of ‘full code’, I’m going to paste in a longer version below. I will still try to omit some simple assignments etc. to keep it focussed. I understand how the .= works, but I don’t understand why the simple ϵ33FieldOld = ϵ33Field does not.

Here goes; note I did also try initializing (defining) ϵ33FieldOld inside the inner for loop.

function f(fracFile,gridFile,outStrainFileRoot,plotRangeScale=1.0)
   growthParams,maxCellSize,ξ1,nTSteps,Mtensor=readGrowingFractureProperties(fracFile)

    x1Grid,x3Grid,x2,Vp,Vs,ρ=readComputeDomain(gridFile)
    x1vals=collect(x1Grid[1]:x1Grid[3]:x1Grid[2])
    x3vals=collect(x3Grid[1]:x3Grid[3]:x3Grid[2])

    #------ omit a few simple lines of code here -------

    # initial strain state is 0:
    ϵ33FieldOld=[0.0 for theX3 in x3vals, theX1 in x1vals]

    for iFracTime in 1:10

        ϵ33Field = [integrateFractureOverYZ(ξ3min,ξ3max,nElemV,ξ2min,ξ2max,nElemH,ξ1,
                                            [x1,x2,x3],Mtensor,ν,μ)
                    for x3 in x3vals, x1 in x1vals]

        dϵ33Field = ϵ33Field-ϵ33FieldOld
        
        #---- full code has a few file saves and plot commands here -----

        # this does NOT update values accessed by ϵField33Old:
        ϵField33Old = ϵ33Field
        # the following version DOES work:
        # nz,nx=size(ϵ33Field)
        # for i in 1:nz, j in 1:nx 
        #     ϵ33FieldOld[i,j]=ϵ33Field[i,j]
        # end
    end

end

I am too, the point is that you need to provide code that’s actually runnable and can demonstrate the behavior. The W in MWE is equally (and more so I would say) important as M. In fact, the code you give still doesn’t W(ork). It doesn’t run and doesn’t show the issue…

1 Like