0

I was attempting implement the Runge-Kutta method detailed here. However, I wanted to return the values, so I modified the VDP1() equation as you will see below.

The odd behavior is that if I both calculate x and append that value to an array it will in each step seem to replace all entries in the list with that being appended. You can see the first code bit and output for the rub of the problem, the rest is just me showing what didn't work.

.extend() works as expected so this is perhaps just another opportunity to explain to stupid people like me why the two function so differently. I base my understanding of the two on the answer given here:

  • append adds its argument as a single element to the end of a list. The length of the list itself will increase by one.
  • extend iterates over its argument adding each element to the list, extending the list. The length of the list will increase by however many elements were in the iterable argument.

Which would not lead me to believe that each item would somehow be replaced by the item be appended. But anyway, here's the code with unexpected output.

def rKN(x, fx, n, hs):
    k1 = []
    k2 = []
    k3 = []
    k4 = []
    xk = []
    for i in range(n):
        k1.append(fx[i](x)*hs)
    for i in range(n):
        xk.append(x[i] + k1[i]*0.5)
    for i in range(n):
        k2.append(fx[i](xk)*hs)
    for i in range(n):
        xk[i] = x[i] + k2[i]*0.5
    for i in range(n):
        k3.append(fx[i](xk)*hs)
    for i in range(n):
        xk[i] = x[i] + k3[i]
    for i in range(n):
        k4.append(fx[i](xk)*hs)
    for i in range(n):
        x[i] = x[i] + (k1[i] + 2*(k2[i] + k3[i]) + k4[i])/6
    return x

def fa1(x):
    return 0.9*(1 - x[1]*x[1])*x[0] - x[1] + np.sin(x[2])

def fb1(x):
    return x[0]

def fc1(x):
    return 0.5

def VDP1():
    f = [fa1, fb1, fc1]
    x = [1, 1, 0]
    X_v = []
    hs = 0.05
    for i in range(3):
        x = rKN(x, f, 3, hs)
        # x = [1,i,3]
        print(x)
        X_v.append(x)
    print(X_v)
VDP1()

Output:

calc x value is [0.9472269022674134, 1.0487033185947015, 0.024999999999999998] 
calc x value is [0.8893603370715508, 1.0946376878598068, 0.049999999999999996] 
calc x value is [0.8271667883479003, 1.1375671528881417, 0.075] 
X_v array is 
[[0.8271667883479003, 1.1375671528881417, 0.075], [0.8271667883479003, 1.1375671528881417, 0.075], [0.8271667883479003, 1.1375671528881417, 0.075]]

As you can see the X_v array is just a three-peat of the final item.

If we switch from append to .extend(x) it does extend the list, however it returns it as a flat list when we would prefer a list of lists.

Output using .extend(x)

calc x value is [0.9472269022674134, 1.0487033185947015, 0.024999999999999998] 
calc x value is [0.8893603370715508, 1.0946376878598068, 0.049999999999999996] 
calc x value is [0.8271667883479003, 1.1375671528881417, 0.075] 
X_v array is 
[0.9472269022674134, 1.0487033185947015, 0.024999999999999998, 0.8893603370715508, 1.0946376878598068, 0.049999999999999996, 0.8271667883479003, 1.1375671528881417, 0.075]

If we try to use .extend([x]) to get that list of lists the results are the same as using append. If I try X_v = X_v + [x] I similarly get the same odd result.

If you want to get real confused run the below, where despite redefining x before appending, I get the old value of the append.

def VDP1():
    f = [fa1, fb1, fc1]
    x = [1, 1, 0]
    X_v = []
    hs = 0.05
    for i in range(3):
        x = rKN(x, f, 3, hs)
        # print(type(x))
        # print(len(x))
        x = [i-1,i,i+1]
        # print(type(x))
        # print(len(x))
        print('calc x value is {} '.format(x))
        X_v.append(x)
    print('X_v array is \n{}'.format(X_v))
VDP1()

Output:

calc x value is [-1, 0, 1] 
calc x value is [0, 1, 2] 
calc x value is [1, 2, 3] 
X_v array is 
[[-1.0013470352949683, -0.0500470769424533, 1.025], [-0.00479801080448152, 0.9998822513537077, 2.025], [1, 2, 3]]

If I instead add to a np.array using the i as an index location, I can get what I would have expected:

def VDP1():
    f = [fa1, fb1, fc1]
    x = [1, 1, 0]
    n = 4
    X_v = np.zeros([n,3])
    hs = 0.05
    for i in range(n):
        x = rKN(x, f, 3, hs)
        print('calc x value is {} '.format(x))
        X_v[i] = x
    print('X_v array is \n{}'.format(X_v))
VDP1()

Output:

X_v array is 
[[0.9472269  1.04870332 0.025     ]
 [0.88936034 1.09463769 0.05      ]
 [0.82716679 1.13756715 0.075     ]
 [0.7615015  1.17729645 0.1       ]]

So seems clear there is some kind of append/extend or list behavior goign on that I don't understand.

Thanks for any assistance!

born_naked
  • 748
  • 9
  • 19
  • 2
    The explanation is that you *keep appending the same list object*, You do `x = rKN(x, f, 3, hs)`, but `rkN` simply mutates the list you pass as `x`, and the *returns that same list*. You could do something like: `x = rKN(x.copy(), f, 3, hs)` and it should work. Or inside `rKN`, do something like `x = x.copy()` at the top. – juanpa.arrivillaga Nov 06 '21 at 20:18
  • Interesting...you are correct @juanpa.arrivillaga. Just curious, why does printing/extending x return the expected value, but appending does not? I ask cause I might have clued on to it if the print hadn't returned what I expected. – born_naked Nov 06 '21 at 20:21
  • 2
    Because `extend` doesn't add the original list object to the other list. `.append` does. As for the `print`, again, *you are working with the same list object that you append repeatedly*. It's not that the values in that object *don't change*, it's that it's *the same object*, so of course, if you have another list that references that same object several times, it will always be the same exact values, despite those values having changed at some point – juanpa.arrivillaga Nov 06 '21 at 20:26

1 Answers1

0
def rKN(x, fx, n, hs):
    k1 = []
    k2 = []
    k3 = []
    k4 = []
    xk = []
    result = []
    for i in range(n):
        k1.append(fx[i](x)*hs)
    for i in range(n):
        xk.append(x[i] + k1[i]*0.5)
    for i in range(n):
        k2.append(fx[i](xk)*hs)
    for i in range(n):
        xk[i] = x[i] + k2[i]*0.5
    for i in range(n):
        k3.append(fx[i](xk)*hs)
    for i in range(n):
        xk[i] = x[i] + k3[i]
    for i in range(n):
        k4.append(fx[i](xk)*hs)
    for i in range(n):
        #x[i] = x[i] + (k1[i] + 2*(k2[i] + k3[i]) + k4[i])/6# I changed  
        result.append(x[i] + (k1[i] + 2*(k2[i] + k3[i]) + k4[i])/6)
    return result #x[i]=value != result.append(value) at the memory

def fa1(x):
    return 0.9*(1 - x[1]*x[1])*x[0] - x[1] + np.sin(x[2])

def fb1(x):
    return x[0]

def fc1(x):
    return 0.5

def VDP1():
    f = [fa1, fb1, fc1]
    x = [1, 1, 0]
    X_v = []
    hs = 0.05
    for i in range(3):
        x = rKN(x, f, 3, hs)
        # x = [1,i,3]
        print(x)
        X_v.append(x)
    print(X_v)
VDP1()

print:
[0.9472269022674134, 1.0487033185947015, 0.024999999999999998]
[0.8893603370715508, 1.0946376878598068, 0.049999999999999996]
[0.8271667883479003, 1.1375671528881417, 0.075]
[[0.9472269022674134, 1.0487033185947015, 0.024999999999999998], [0.8893603370715508, 1.0946376878598068, 0.049999999999999996], [0.8271667883479003, 1.1375671528881417, 0.075]]


Yoni
  • 26
  • 3