AmirHossein
AmirHossein

Reputation: 1426

Numpy: Why doesn't 'a += a.T' work?

As stated in scipy lecture notes, this will not work as expected:

a = np.random.randint(0, 10, (1000, 1000))
a += a.T
assert np.allclose(a, a.T)

But why? How does being a view affect this behavior?

Upvotes: 12

Views: 730

Answers (4)

Yuri Baburov
Yuri Baburov

Reputation: 722

a += a.T

does in-place summing up (it's using view a.T while processing), so you end up with a non-symmetric matrix you can easily check this, i.e. I got:

In [3]: a
Out[3]: 
array([[ 6, 15,  7, ...,  8,  9,  2],
       [15,  6,  9, ..., 14,  9,  7],
       [ 7,  9,  0, ...,  9,  5,  8],
       ..., 
       [ 8, 23, 15, ...,  6,  4, 10],
       [18, 13,  8, ...,  4,  2,  6],
       [ 3,  9,  9, ..., 16,  8,  4]])

You can see it's not symmetric, right? (compare right-top and left-bottom items)

if you do a real copy:

a += np.array(a.T)

it works fine, i.e.:

In [6]: a
Out[6]: 
array([[ 2, 11,  8, ...,  9, 15,  5],
       [11,  4, 14, ..., 10,  3, 13],
       [ 8, 14, 14, ..., 10,  9,  3],
       ..., 
       [ 9, 10, 10, ..., 16,  7,  6],
       [15,  3,  9, ...,  7, 14,  1],
       [ 5, 13,  3, ...,  6,  1,  2]])

To better understand why it does so, you can imagine you wrote the loop yourself as following:

In [8]: for i in xrange(1000):
            for j in xrange(1000):
                a[j,i] += a[i,j]
   ....:         

In [9]: a
Out[9]: 
array([[ 4,  5, 14, ..., 12, 16, 13],
       [ 3,  2, 16, ..., 16,  8,  8],
       [ 9, 12, 10, ...,  7,  7, 23],
       ..., 
       [ 8, 10,  6, ..., 14, 13, 23],
       [10,  4,  6, ...,  9, 16, 21],
       [11,  8, 14, ..., 16, 12, 12]])

It adds a[999,0] to calculate a[0,999] but a[999,0] already has sum of a[999,0] + a[0,999] -- so below the main diagonal you add the values twice.

Upvotes: 5

bakkal
bakkal

Reputation: 55448

assert np.allclose(a, a.T)

I just understood now that you are generating a symmetric matrix by summing a with it's transponse a.T resulting in a symmetric matrix)

Which makes us rightfully expect np.allclose(a, a.T) to return true (resulting matrix being symmetric so it should be equal to its transpose)

a += a.T # How being a view affects this behavior?

I've just narrowed it down to this

TL;DR a = a + a.T is fine for larger matrices, while a += a.T gives strange results starting from 91x91 size

>>> a = np.random.randint(0, 10, (1000, 1000))
>>> a += a.T  # using the += operator
>>> np.allclose(a, a.T)
False
>>> a = np.random.randint(0, 10, (1000, 1000))
>>> a = a + a.T # using the + operator
>>> np.allclose(a, a.T)
True

I've got same cut-off at size 90x90 like @Hannes (I am on Python 2.7 and Numpy 1.11.0, so there are at least two environments that can produce this)

>>> a = np.random.randint(0, 10, (90, 90))
>>> a += a.T
>>> np.allclose(a, a.T)
True
>>> a = np.random.randint(0, 10, (91, 91))
>>> a += a.T
>>> np.allclose(a, a.T)
False

Upvotes: 1

acdr
acdr

Reputation: 4726

For large arrays, the in-place operator causes you to apply the addition to the operator currently being operated on. For example:

>>> a = np.random.randint(0, 10, (1000, 1000))
>>> a
array([[9, 4, 2, ..., 7, 0, 6],
       [8, 4, 1, ..., 3, 5, 9],
       [6, 4, 9, ..., 6, 9, 7],
       ..., 
       [6, 2, 5, ..., 0, 4, 6],
       [5, 7, 9, ..., 8, 0, 5],
       [2, 0, 1, ..., 4, 3, 5]])

Notice that the top-right and bottom-left elements are 6 and 2.

>>> a += a.T
>>> a
array([[18, 12,  8, ..., 13,  5,  8],
       [12,  8,  5, ...,  5, 12,  9],
       [ 8,  5, 18, ..., 11, 18,  8],
       ..., 
       [19,  7, 16, ...,  0, 12, 10],
       [10, 19, 27, ..., 12,  0,  8],
       [10,  9,  9, ..., 14, 11, 10]])

Now notice that the top-right element is correct (8 = 6 + 2). However, the bottom-left element is the result not of 6 + 2, but of 8 + 2. In other words, the addition that was applied to the bottom-left element is the top-right element of the array after the addition. You'll notice this is true for all the other elements below the first row as well.

I imagine this works this way because you do not need to make a copy of your array. (Though it then looks like it does make a copy if the array is small.)

Upvotes: 1

Hannes Ovrén
Hannes Ovrén

Reputation: 21851

This problem is due to internal designs of numpy. It basically boils down to that the inplace operator will change the values as it goes, and then those changed values will be used where you were actually intending for the original value to be used.

This is discussed in this bug report, and it does not seem to be fixable.

The reason why it works for smaller size arrays seems to be because of how the data is buffered while being worked on.

To exactly understand why the issue crops up, I am afraid you will have to dig into the internals of numpy.

Upvotes: 5

Related Questions