Skip to content

API: Return scalars for scalar inputs to np.real/imag #8388

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 26, 2017
Merged

API: Return scalars for scalar inputs to np.real/imag #8388

merged 1 commit into from
Mar 26, 2017

Conversation

gfyoung
Copy link
Contributor

@gfyoung gfyoung commented Dec 17, 2016

Returns scalars for scalar inputs for the functions np.real and np.imag, as consistent with functions like np.angle and np.conj.

Closes #8386.

@seberg
Copy link
Member

seberg commented Dec 18, 2016

Its not obvious that this is correct. doing this implies that putting in a 0-D array will not give you a view like the attribute does.

@gfyoung
Copy link
Contributor Author

gfyoung commented Dec 18, 2016

@seberg : True, but it is consistent with what these other numpy functions do. It would be preferable to choose one (return view) or the other (return scalar).

@seberg
Copy link
Member

seberg commented Dec 18, 2016

The other functions all return copies though.

@gfyoung
Copy link
Contributor Author

gfyoung commented Dec 18, 2016

Then in that case we should return scalars across the board, unless there is an advantageous reason why we should return a view for the 0-D array case that would override consistency.

@seberg
Copy link
Member

seberg commented Dec 18, 2016

It is a view for all other dimensions, I am just saying that it is an API change, and that I think the comparison to angle or conj is only half valid.

@gfyoung
Copy link
Contributor Author

gfyoung commented Dec 18, 2016

Fair enough, though I did acknowledge that this is an API change if you read the title of this PR.

Also, correct me if I'm wrong, but how many functions in numpy return array when a scalar is passed in? I think the majority behavior is to return a scalar.

@charris
Copy link
Member

charris commented Dec 29, 2016

Having a view can be useful as the view can be used as the value of an out parameter.

EDIT: Not clear to me that this should treat 0-D arrays as scalars.

@gfyoung
Copy link
Contributor Author

gfyoung commented Dec 29, 2016

@charris: I still return 0-D arrays when one is passed in, but when a scalar is passed in, I return scalar. That seems consistent with what we do with other functions.

@seberg
Copy link
Member

seberg commented Dec 29, 2016

@gfyoung you do? In that case, can you make it clear with a test pointing that out? 0-D arrays are rare, but still....

@gfyoung
Copy link
Contributor Author

gfyoung commented Dec 29, 2016

@seberg , @charris : Added test cases to confirm that 0-D array input gets 0-D array output. Ready to merge if there are no other concerns.

@seberg
Copy link
Member

seberg commented Dec 29, 2016 via email

@gfyoung
Copy link
Contributor Author

gfyoung commented Dec 29, 2016

Nope, sorry, but me asking for a test was a trick question really.

What type of comment is this? Not really sure why you're trying to be "clever".

Also, you should look at the PR again before making comments like this. I wrote the test case out, and the behavior does return 0-D array upon 0-D input as requested.

@seberg
Copy link
Member

seberg commented Dec 29, 2016

Was a bit evil, but anyway... You did not test for array return, because scalars are equal to arrays in those tests.

@gfyoung
Copy link
Contributor Author

gfyoung commented Dec 29, 2016

You did not test for array return, because scalars are equal to arrays in those tests.

Now that's a more useful comment: straightforward and to the point. I've updated the tests to check.

Please refrain from your "trickery" in subsequent comments. Thanks!

@gfyoung
Copy link
Contributor Author

gfyoung commented Dec 29, 2016

@charris , @seberg : Updated test to confirm that 0-D array is returned when we pass in 0-D array, and everything is still passing.


"""
return asanyarray(val).real
arr = asanyarray(val)
return arr.real.item() if isscalar(val) else arr.real
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now it never returns scalars.

Copy link
Contributor Author

@gfyoung gfyoung Dec 30, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I STRONGLY beg to differ on this point. I just confirmed it myself. I would encourage you to check it out yourself to confirm.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oops, misread.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, okay. No worries. 😄

@gfyoung
Copy link
Contributor Author

gfyoung commented Jan 11, 2017

@charris , @seberg : Any updates on this?

from numpy.core.numeric import asarray, asanyarray, array, isnan, \
obj2sctype, zeros
from numpy.core.numeric import (asarray, asanyarray, array, isnan,
obj2sctype, zeros, isscalar)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given that we imported _nx on the line above, and much of this file uses it already, is there any real point importing isscalar here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair enough. Done.

assert_(isinstance(out, np.ndarray))

y = 1
assert_equal(y, np.real(y))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't actually check that the scalar is returned - all of these tests would have passed before this patch, I think.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, that is true. Done.

@gfyoung
Copy link
Contributor Author

gfyoung commented Feb 20, 2017

@eric-wieser : Made the requested changes. Please have a look.

Copy link
Member

@eric-wieser eric-wieser left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generally looks good, just some minor nitpicks on doc comments - not sure if this needs a manual release note

Output array. If `val` is real, the type of `val` is used for the
output. If `val` has complex elements, the returned type is float.
out : ndarray or scalar
The real component of a complex argument. If `val` is real, the type
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit-pick: "component of the ..."

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

Output array. If `val` is real, the type of `val` is used for the
output. If `val` has complex elements, the returned type is float.
out : ndarray or scalar
The imaginary component of a complex argument. If `val` is real,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(same here)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

out : ndarray or scalar
The imaginary component of a complex argument. If `val` is real,
the type of `val` is used for the output. If `val` has complex
elements, the returned type is float.
Copy link
Member

@eric-wieser eric-wieser Feb 20, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not really float: complex128 -> float64, complex32 -> float32. But that was here before, so out of scope for this PR, I think

Copy link
Contributor Author

@gfyoung gfyoung Feb 20, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, the exact float type seems platform-dependent. On my machine, it actually returns float128 when I pass in complex128.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That sounds like a bug if its true?

Copy link
Contributor Author

@gfyoung gfyoung Feb 20, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't tell at this point, though @eric-wieser I am a little surprised that complex128 would return float64 if the architecture is capable of supporting complex128.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am a little surprised that complex128 would return float64

Why? 64 bits of real + 64 bits of imaginary = 128 bits of complex

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay...but then why would I get float128 returned on my machine then?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since the result is a view (unless it happens to be scalar), it frankly seems pretty improbable to me at least for the array case. Can you double check? And if, what system exactly?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, okay. I should have clarified. For the scalar case, I get float128, but for the array case, I do indeed get float64.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By "the scalar case", you presumably mean complex not np.complex128

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@eric-wieser : That is correct.

@gfyoung
Copy link
Contributor Author

gfyoung commented Feb 20, 2017

not sure if this needs a manual release note

IMO probably not This change is relatively minor compared to those that would find themselves in the release notes.

@gfyoung
Copy link
Contributor Author

gfyoung commented Feb 21, 2017

@eric-wieser @seberg : Changes made as requested, and all is green.


"""
return asanyarray(val).real
arr = asanyarray(val)
return arr.real.item() if _nx.isscalar(val) else arr.real
Copy link
Member

@eric-wieser eric-wieser Feb 21, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just thinking about duck typing here... How about:

try:
    return val.real
except AttributeError:
    return asanyarray(val).real

Which has the nice bonus that builtin complex returns builtin float not np.float64

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. Done.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Although now this is inconsistent with np.conj, which does not return a native complex...

What was the previous implementation?

@eric-wieser
Copy link
Member

eric-wieser commented Feb 21, 2017

Tests look good now, but I'm kinda tempted to duck-type this in the same way we duck-type np.min and np.ndim(...) in fromnumeric

@gfyoung
Copy link
Contributor Author

gfyoung commented Feb 21, 2017

@eric-wieser @seberg : Changes made as requested, and all is green.

@eric-wieser
Copy link
Member

Not green for me, but looks good. @seberg, do you agree with the duck-typing I suggested above that @gfyoung has now implemented?

@gfyoung
Copy link
Contributor Author

gfyoung commented Mar 8, 2017

@seberg : Any updates on this? I think @eric-wieser is waiting for your response before merging.

@gfyoung
Copy link
Contributor Author

gfyoung commented Mar 25, 2017

@eric-wieser : @seberg seems to not be responding (either because the message wasn't seen or this is tacit approval). Ideas of how to move forward? This change seems relatively trivial.

@seberg
Copy link
Member

seberg commented Mar 26, 2017

It looks good to me now, sorry if I am slow but don't have much brain cycles for numpy right now, and frankly the first implementations looked too tricky to me to just approve off. Even here I would not mind mentioning it in the release notes, but probably it is not necessary.

@eric-wieser
Copy link
Member

eric-wieser commented Mar 26, 2017

Ugh, I've just realized that this fails to meet the goal of "Returns scalars for scalar inputs as consistent with functions like np.angle and np.conj".

type(np.angle(1+1j)) == np.float64, but type(np.real(1+1j)) == float

@seberg
Copy link
Member

seberg commented Mar 26, 2017

Well, this is subclassing hell.... np.angle I don't understand why that is a goal, there is no angle method after all. np.conjugate could indeed be set up to go via the method in principle, since it is pretty much a well defined method within python. This "function that is basically supposed to be an operator" thing is really annoying.

Overall, I think the approach of try/except is consistent with what we do on most methods at least, so I don't mind the change, and doubt right now it creates much trouble. @mhvk you are a subclass guy, maybe you can give your opinion/final decision on whether this is an improvement or not?

@eric-wieser
Copy link
Member

eric-wieser commented Mar 26, 2017

What we had in an earlier iteration actually fails in exactly the same way:

arr = asanyarray(val)
return arr.real.item() if isscalar(val) else arr.real
#               ^ item returns float, not float64

A possible fix would be to avoid .item():

arr = asanyarray(val)
return arr[()].real if isscalar(val) else arr.real

Or possibly

def asanyarray_or_scalar(val):
    arr = asanyarray(val)
    if isscalar(val):
        return arr[()]
    else:
        return arr

def real(val):
    return asanyarray_or_scalar(val).real

@seberg
Copy link
Member

seberg commented Mar 26, 2017

Not sure I like that, because it does not return views. Though, I admit, if it creates problems in code, it should at least not be silent problems, since the scalars are immutable.

@eric-wieser
Copy link
Member

@seberg: That comment got edited way too much, you should probably check it still says what you were replying to

@seberg
Copy link
Member

seberg commented Mar 26, 2017

Yeah, well... we just have to be careful we don't simply replace one problem with another.

@eric-wieser
Copy link
Member

I agree. I think the above code now does return a view, if passed a viewable object?

@mhvk
Copy link
Contributor

mhvk commented Mar 26, 2017

I had a look and I think this is the right implementation. I don't think it makes all that much sense to compare with conj and angle, as both actually do an operation, while real and imag just select a particular part of an operand. (Indeed, angle could use some work - why, o why, this need to asarray all input...).

So, I think this should just go in.

@seberg
Copy link
Member

seberg commented Mar 26, 2017

Isn't it a great convenience ;). Ok, then lets just put it in and hopefully we are not missing anything that might get broken here.

@seberg seberg merged commit 82e0860 into numpy:master Mar 26, 2017
@charris
Copy link
Member

charris commented Mar 26, 2017

Needs a release note...

@gfyoung gfyoung deleted the real-imag-scalar branch March 26, 2017 20:01
@gfyoung
Copy link
Contributor Author

gfyoung commented Mar 26, 2017

@seberg , @eric-wieser , @mhvk : Thanks for all of the input!

@charris : Acknowledged. Will put up PR as follow-up.

@WarrenWeckesser
Copy link
Member

WarrenWeckesser commented Mar 27, 2017

Take a look at scipy/scipy#7227

Note, in particular, the quirk of numpy.testing.assert_equal() that I commented on there (using numpy 1.12.1):

In [10]: from numpy.testing import assert_equal

In [11]: assert_equal(np.array(-0.0), 0)

In [12]: assert_equal(-0.0, 0)
---------------------------------------------------------------------------
AssertionError                            Traceback (most recent call last)
<ipython-input-12-8315da2bce84> in <module>()
----> 1 assert_equal(-0.0, 0)

/Users/warren/miniconda3scipy/lib/python3.5/site-packages/numpy/testing/utils.py in assert_equal(actual, desired, err_msg, verbose)
    410         elif desired == 0 and actual == 0:
    411             if not signbit(desired) == signbit(actual):
--> 412                 raise AssertionError(msg)
    413     # If TypeError or ValueError raised while using isnan and co, just handle
    414     # as before

AssertionError: 
Items are not equal:
 ACTUAL: -0.0
 DESIRED: 0

I haven't tracked down the ultimate source of the failure in scipy, but I suspect a use of assert_equal that previously passed is now failing because something that was an array containing -0.0 is now the scalar -0.0.

@WarrenWeckesser
Copy link
Member

I've narrowed down the problem to the issue reported in #8848, so further discussion should be over there.

@gfyoung
Copy link
Contributor Author

gfyoung commented Mar 27, 2017

@WarrenWeckesser : Still, yikes...what you indicated above is a bug it seems.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants