2

I'm trying to create a population of adjacency matrices with different random weights for which I am using the code underneath. My problem however is that upon running this, all the weights are that of the last iteration of the list comprehension? On printing these adjacencylists during generation, it works fine, however, the output of the getPopulation function is 5 times the same parameter set.

It feels like this would be an easy fix, but something (I think possibly very basic) is missing. Maybe a problem where deep copy is needed or something?

Already tried using normal for-loops, print statements etc.

import networkx as nx
import numpy as np

G = nx.DiGraph()
G.add_nodes_from(["Sadness", "Avoidance", "Guilt"])
G.add_edges_from([("Sadness", "Avoidance")], weight=1)
G.add_edges_from([("Avoidance", "Sadness")], weight=1)
G.add_edges_from([("Avoidance", "Guilt"), ("Guilt", "Sadness")], weight=1)
parameters = nx.to_numpy_matrix(G)

def getRandParamValue(adj):
    for p in np.transpose(adj.nonzero()):
        adj[p[0], p[1]] = adj[p[0], p[1]] * np.random.uniform()
    print(adj)
    return adj

def getPopulation(size, initParam):
    return [getRandParamValue(initParam) for i in range(size)]

getPopulation(5, parameters)

Upon printing the output in the getRandParamValue function it works fine:

[[0.         0.40218464 0.        ]
 [0.07330473 0.         0.7196376 ]
 [0.53148413 0.         0.        ]]
[[0.         0.34256617 0.        ]
 [0.01773899 0.         0.12460768]
 [0.1401687  0.         0.        ]]
[[0.         0.11086942 0.        ]
 [0.01449088 0.         0.04592752]
 [0.07903259 0.         0.        ]]
[[0.         0.01970867 0.        ]
 [0.00589168 0.         0.00860802]
 [0.06942081 0.         0.        ]]
[[0.         0.01045412 0.        ]
 [0.00084878 0.         0.00713334]
 [0.0024654  0.         0.        ]]

However, the output of getPopulation isn't identical to the previous output, while this should be expected:

[matrix([[0.        , 0.01045412, 0.        ],
         [0.00084878, 0.        , 0.00713334],
         [0.0024654 , 0.        , 0.        ]]),
 matrix([[0.        , 0.01045412, 0.        ],
         [0.00084878, 0.        , 0.00713334],
         [0.0024654 , 0.        , 0.        ]]),
 matrix([[0.        , 0.01045412, 0.        ],
         [0.00084878, 0.        , 0.00713334],
         [0.0024654 , 0.        , 0.        ]]),
 matrix([[0.        , 0.01045412, 0.        ],
         [0.00084878, 0.        , 0.00713334],
         [0.0024654 , 0.        , 0.        ]]),
 matrix([[0.        , 0.01045412, 0.        ],
         [0.00084878, 0.        , 0.00713334],
         [0.0024654 , 0.        , 0.        ]])]

The parameters matrix is just the following:

[[0. 1. 0.]
 [1. 0. 1.]
 [1. 0. 0.]]
TrebledJ
  • 8,713
  • 7
  • 26
  • 48
Bas Chatel
  • 23
  • 4
  • if you want the same result in your list than the one printed, just add `.copy()` in the `return` of `getRandParamValue` such as `return adj.copy()` – Ben.T May 30 '19 at 16:53
  • Awesome! Thank you so much, Why is this needed though? – Bas Chatel May 30 '19 at 16:55
  • Possible duplicate of [How to clone or copy a list?](https://stackoverflow.com/questions/2612802/how-to-clone-or-copy-a-list) – Devesh Kumar Singh May 30 '19 at 16:57
  • @BasChatel it is because numpy arrays are mutables, not sure I can provide more explanation, maybe read [this](http://www.math.buffalo.edu/~badzioch/MTH337/PT/PT-mutable_vs_immutable/PT-mutable_vs_immutable.html), (not on stackoverflow) it could help you to further understand. – Ben.T May 30 '19 at 17:08
  • I want to add a comment on what you do, if you define `size` with a big number, all the value will tend towards 0, as each time you multiply the previous value with a value between 0 and 1, see by yourself with printing the last element of the list of 100 elements: `print (getPopulation(100, parameters)[-1])`. Just to be sure you now what will happen – Ben.T May 30 '19 at 17:21

1 Answers1

0

So the problem is as follows:

def myfunction(L):
    L[0] += 1
    return L

my_outer_list = [1,2,3]
newlist = myfunction(my_outer_list)

print(newlist)
> [2, 2, 3]
print(my_outer_list)
> [2, 2, 3]

newlist[2]=-1
print(newlist)
> [2, 2, -1]
print(my_outer_list)
> [2, 2, -1]

I've passed the object my_outer_list to the function. Then that object gets modified, and the object is returned. So now newlist and my_outer_list aren't just equal, they are two different names for the very same thing. Things I do to that object change the object, and you see those changes no matter which name you use.

This is what's happened to you. If I had instead had myfunction return L.copy(), it would have returned a copy of L rather than exactly L.

So you should return adj.copy().

Joel
  • 22,598
  • 6
  • 69
  • 93