Unexpected variable reassignment

I wrote this function called cfd() as a central finite-difference algorithm.
The input is a 1-D array ‘x’.

The for-loop starts with creating x_fwd and x_bwd as copies of the original input x. My problem is right after that when I reassign the i-th element of x_fwd to a new value (x_fwd[i] = x_fwd[i] + (h/2)). After this line of code, the print statements reveal that the i-th element of both x and x_bwd have also been given a new value, even though the only code executed was for x_fwd. Why??

See code below.

I need x to stay the same always and I need to change elements of x_bwd and x_fwd independently of each other.

# central finite difference function
def cfd(x):
    print("entering cfd()")
    jacobian = np.zeros_like(x)
    print("initial jac: ", jacobian)
    h = 0.2   
    print("h>> ", h)

    for i in range(0, 10):
        print("\niteration: ", i)
        print("x>> ", x)
       
        x_fwd = x   # x-forward as a copy of the input x
        x_bwd = x   # x-backward as a copy of the input x
        print("x_fwd>> ", x_fwd)
        print("x_bwd>> ", x_bwd)
        
        print("Add h/2 to x_fwd[i]:")
        x_fwd[i] = x_fwd[i] + (h/2)   # <--- add h/2 to the i-th element of x_fwd
        print("x_fwd>> ", x_fwd)     # <--- this and the next two print lines reveal that the 
        print("x_bwd>> ", x_bwd)     #      i-th element of x_bwd and x are also changed. Why??
        print("x>> ", x)

        print("Subtract h/2 from x_bwd[i]:")
        x_bwd[i] = x_bwd[i] - (h/2)
        print("x_fwd>> ", x_fwd)
        print("x_bwd>> ", x_bwd)
        print("x>> ", x)

        print("f at x_fwd and x_bwd:")
        print("f(x_fwd)>> ", f(x_fwd))
        print("f(x_bwd)>> ", f(x_bwd))
        # the i-th element of the jacobian calcualted by central finite-difference
        jacobian[i] = (f(x_fwd) - f(x_bwd)) / h
        print("new jacobian: ", jacobian)
    return jacobian

x0 = [0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2]
cfd(x0)

You believe that is what it is doing, but it is not doing that :slight_smile:

x_fwd and x_bwd are references to the same object that x points to; if you are familiar with programming languages which offer pointers, imagine that x is a pointer and you’ve just copied the pointer (twice), but not the thing it points to.

If you want to copy x, you’ll need to use something like this.

1 Like

Your code goes:

 x_fwd = x   # x-forward as a copy of the input x
 x_bwd = x   # x-backward as a copy of the input x

These are not copies. They make the names x_fwd and xbwd refer to
the same array that x does. Thus you see the changes via any of
these names, because there’s only one array in use.

You probably want:

 x_fwd = x.copy()   # x-forward as a copy of the input x
 x_bwd = x.copy()   # x-backward as a copy of the input x

See: numpy.ndarray.copy — NumPy v1.24 Manual

Cheers,
Cameron Simpson cs@cskk.id.au

Thank you Kevin and Cameron, I understand now! A small thing but in the background not a small thing. I appreciate the responses

Just to note, x there is actually a Python list, not a NumPy array, but it also has a .copy() method so your solution still works the same.

Note, though, that if the list contained other non-immutable objects, (e.g. nested lists, dictionaries, arrays, custom class instances, etc), those objects would not be copied recursively and would point to the same object in both copies, unless you used copy.deepcopy(). Not an issue in your case if x is required to contain only numeric types, but something to keep in mind in case you run into this again in another context.

I took the OP’s x to be a Numpy 1-d array.

Right, that would make the most logical sense (as jacobian is an array, and that would allow solving the problem much more efficiently and concisely than a for loop via vectorization), but cfd is in fact passed a list for x, and is never cast to an array:

Converting x to an array should hopefully allow solving the problem more cleanly and efficiently using vectorization, and also both inherently make a (shallow) copy of the original list, and avoid mutating it in place to begin with. However, it is not clear what function the cryptically-named f is nor what it should do, so I cannot be sure, nor produce an example to benchmark.