Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python - one variable equals another variable when it shouldn't

Here is my sample code. It is meant to be an iterative procedure for gauss seidel (matrix solver). Essentially when the error is small enough it breaks out of the while loop.

i=1
while (i>0):
    x_past = x_present

    j=0
    while(j<3):
        value=0
        k=0
        while(k<3):
            if(k!=j):
                if(i==1):
                    if(k>j):
                        value=value+0
                    else:
                        value=value+x_present[k]*eqn[j][k]    
                else:
                    value=value+x_present[k]*eqn[j][k]
            else:
                value=value+eqn[j][k]
            k=k+1
        x_present[j:j+1]=[value]
        j=j+1
    print "X_PAST"
    print x_past
    print "X_PRESENT"
    print x_present    
    if(error(x_past, x_present)<10**-2):
        break;
    i=i+1

I've reduced the code so its more manageable. if you don't understand what its doing its not really that important to solving this problem.

Here is the issue. Everytime

x_present[j:j+1]=[value]

is run, x_past is made equal to x_present. I don't know why this is the case as the only place i have set x_past equal to x_present is at the top of the loop. If I take away the

x_past=x_present

sentence, x_past is never made equal to x_present. This leads me to think it is some combination of the two statements that is causing the issue.

This is a big problem cause if x_past = x_present the error = 0 every time and the loop breaks after the first iteration. The code does work, for example if I tell the code to run for 8 iterations and the break it gives me the answer its supposed to.

I've been trying to figure this out for the last 4 hours and am completely stumped. I haven't been working with python long, so my trouble shooting skills syntax wise aren't that great. Any help would be appreciated!!

like image 681
darudude Avatar asked Nov 05 '08 07:11

darudude


4 Answers

Yes, I think the answers here show your problem. Just to try and clarify a little bit.

You're referencing a list, so when the list changes any reference to that list will reflect that change. To demonstrate:

>>> x_present = [4,5,6]
>>>
>>> x_past = x_present
>>>
>>> x_past
[4, 5, 6]
>>>
>>> x_present.append(7)
>>>
>>> x_past
[4, 5, 6, 7]
>>>

If you want a copy of the list you have to do do this, listcopy = mylist[:]. (or import copy;listcopy = copy.copy(mylist)

>>> x_past = x_present[:]
>>> x_past
[4, 5, 6, 7]
>>>
>>> x_present.append(8)
>>>
>>> x_past
[4, 5, 6, 7]
like image 71
monkut Avatar answered Nov 11 '22 07:11

monkut


What are x_past and x_present? I don't know much Python, but from a .NET/Java perspective, if they're references to some data structure (a map or whatever) then making them references to the same object (as you do at the start) will mean that any changes made through one variable will be visible through the other. It sounds like you need to take a copy of the data structure instead of just doing a reference assignment. Does the data structure you're working with have any kind of "clone" functionality available?

As I say though, I don't know much Python so this could be totally wrong...

like image 37
Jon Skeet Avatar answered Nov 11 '22 06:11

Jon Skeet


As others pointed out the answer is to replace: x_past = x_present by x_past = x_present[:]. In general you could use a copy module to copy an object in Python.

>>> import copy
>>> a = range(10)
>>> a
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> b = a
>>> a += 10, 11
>>> a
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
>>> b
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
>>> c = copy.copy(a) # shallow copy
>>> c
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
>>> del a[3:]
>>> a
[0, 1, 2]
>>> b
[0, 1, 2]
>>> c
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]

Your code is unpythonic to say the least.

It could be replaced by something like the following code:

import copy
# assert(len(x_present) >= len(eqn))

first = True
while True:
    x_past = copy.copy(x_present) # copy

    for j, eqj in enumerate(eqn):
        x_present[j] = sum(x_present[k] * eqj[k] 
                           for k in range(j if first else len(eqj)) 
                           if k != j)
        x_present[j] += eqj[j] 

    print "X_PAST\n%s\nX_PRESENT\n%s" % (x_past, x_present)
    if allequal(x_past, x_present, tolerance=10**-2):
        break
    first = False

Here's a definition of allequal() (using an absolute error. It might or might not be a good idea in your case (you could use a relative error instead)):

def allequal(x, y, tolerance):
    return (len(x) == len(y) and 
            all(-tolerance < (xx - yy) < tolerance
                for xx, yy in zip(x, y)))
like image 3
jfs Avatar answered Nov 11 '22 08:11

jfs


In Python, everything is an object. So the statement x_past = x_present point to the same reference.

like image 1
eddy147 Avatar answered Nov 11 '22 06:11

eddy147