0

I was testing a piece of code and encountered the following behaviour. Although I have found an explanation and a solution myself (this looks related although not same), I'd like to ask here if anybody of you has more to add.

a) I cannot in-place-add a complex number to a numpy array that was created as real, but I can do it with a scalar variable

b) warning messages are issued only in a limited number of cases, and not very consistently

Take a look at these commands:

>>> from numpy import *
>>> a5=arange(5)
>>> a5[1:5]+=1j
>>> a5
array([0, 1, 2, 3, 4])
>>> a=8
>>> a+=1j
>>> a
(8+1j)
>>> a5+=1j
__main__:1: ComplexWarning: Casting complex values to real discards the imaginary part
>>> a5[3]+=1j
>>> a5[3]
3
>>> 

You see? A ComplexWarning was issued only when trying to add a complex to the whole vector, but not when attempting the same in place addition to a single element or a slice.

Is there a reason for this? (I barely can stand the fact that it doesn't make the addition, but certainly not the fact that the warning is issued only in one case). Is this a bug or a feature? How can it be best overcome? At least, wouldn't it be better that the ComplexWarning appear in all cases when the imaginary part is discarded for incompatibility?

PS. Meanwhile I provide a solution: z5=arange(5)+0j and c5=arange(5,dtype="complex"), will both behave as intended. But the first is is only partial relief, as it shows that a numpy range of integers CAN be added and assigned to a variable, provided that it is a complex one, or a new one being created.

Community
  • 1
  • 1
lurix66
  • 502
  • 1
  • 5
  • 14
  • 1
    Are you sure the `a5[1:5] += 1j` test came first? I don't remember the specifics, but unlike exceptions, a given warning will only be issued once. – user2357112 Dec 16 '15 at 19:10
  • Which version of numpy are you using? – ecatmur Dec 16 '15 at 19:15
  • 1
    Can't reproduce here (NumPy 1.9): I get the expected `ComplexWarning` on the first in-place addition, and no warning on the second (which as @user2357112 points out is the default behaviour for warnings). Note that the scalar addition is a red-herring: unlike the array operations, it's not an in-place addition, but rather a rebinding. – Mark Dickinson Dec 16 '15 at 19:20
  • @user2357112: No I'm not sure, I rearranged my command history and might have exchanged things. Indeed I didn't know or expect this behaviour of warnings. – lurix66 Dec 17 '15 at 08:22
  • @Mark Dickinson: I did it once again and I confirm your thoughts. – lurix66 Dec 17 '15 at 08:28

2 Answers2

1

This is recognised as an issue, but it has taken time to change as fixing it breaks compatibility.

Since numpy 1.10, the default casting rule for in-place operations and for ufuncs has been changed to "same_kind", so your code will issue a TypeError:

TypeError: Cannot cast ufunc add output from dtype('complex128') to dtype('int64') with casting rule 'same_kind'

From the release notes:

Default casting for inplace operations will change to ‘same_kind’ in Numpy 1.10.0. This will certainly break some code that is currently ignoring the warning.

ecatmur
  • 152,476
  • 27
  • 293
  • 366
  • (my numpy's version is `1.8.2`) Thank you for pointing out that this "is recognized as an issue", and appreciate your answer, although I can't follow its content (I have to to document myself on the casting rules and don't really get the meaning of changing it to `some_kind`). I bet you share that from the point of view of a user, knowing all those details should not be needed... – lurix66 Dec 17 '15 at 09:03
1

The warning is issued every time by NumPy, but Python's general warnings machinery by default only prints the warning the first time it's issued. You can change this behaviour using warnings.filterwarnings or warnings.simplefilter. See the warnings module documentation for more information.

Here's an example (with Python 2.7 and NumPy 1.9), showing that the warning is being issued for each one of the in-place operations.

Python 2.7.10 |Master 2.1.0.dev1829 (64-bit)| (default, Oct 21 2015, 09:09:19) 
[GCC 4.2.1 (Based on Apple Inc. build 5658) (LLVM build 2335.6)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> from numpy import *
>>> import warnings
>>> warnings.simplefilter('always', ComplexWarning)
>>> a5 = arange(5)
>>> a5[1:5] += 1j
__main__:1: ComplexWarning: Casting complex values to real discards the imaginary part
>>> a5
array([0, 1, 2, 3, 4])
>>> a5 += 1j
__main__:1: ComplexWarning: Casting complex values to real discards the imaginary part
>>> a5[3] += 1j
__main__:1: ComplexWarning: Casting complex values to real discards the imaginary part

The scalar addition that you show is a different beast altogether.

>>> a = 8
>>> a += 1j

In this case, the += operation is not doing an in-place operation (it can't, since after the first line, a is a Python int, and unlike NumPy arrays, int objects are immutable). Instead, it's equivalent to doing a = a + 1j. That's constructing a new Python complex object, and rebinding a to refer to that new object.

You say you "barely can stand the fact that it doesn't make the addition". But note that there's no way to efficiently do so: the contents of the original array are stored using 8-bytes per item. The contents of the sum are (double-precision) complex numbers, so would have to be stored using 16 bytes per item. That makes a true in-place operation impossible here.

Note that in NumPy 1.10, these operations will produce a TypeError rather than a warning.

Mark Dickinson
  • 29,088
  • 9
  • 83
  • 120
  • Thank you Mark, this definitely teaches me two things, that warnings have their own behaviour, and that `a+=b` statements _might or might not be really in-place_. As a user, I am somehow uncomfortable with the end result: it needs me to know too much Python's internals. A better behaviour would be, from my point of view, that any lhs variable is automatically cast to complex, in a consistent way, i.e. being it a scalar or not. I understand that it is a delicate decision, as it could open compatibility issues (as @ecatmur pointed out, see below) – lurix66 Dec 17 '15 at 08:52
  • @lurix66: Yeah, the syntactic ambiguity of the `+=` notation in the Python language has bothered me for a long time. Put it down to "Practicality beats Purity". – Mark Dickinson Dec 17 '15 at 08:55