Skip to content

Add umath functions to wrap radian angles #2868

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

Closed
wants to merge 5 commits into from

Conversation

ahojnnes
Copy link
Contributor

No description provided.

@@ -913,6 +913,62 @@ def add_newdoc(place, name, doc):

""")

add_newdoc('numpy.core.umath', 'wrap_to_pi',
"""
Wrap angles to interval `[-PI, PI]`wrap_to_pi.
Copy link
Member

Choose a reason for hiding this comment

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

copy/paste error?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes. Fixed.

@njsmith
Copy link
Member

njsmith commented Dec 29, 2012

  • Can you say a bit about your motivation for adding these? They're just equivalent to a % (2*np.pi) and a % (2*np.pi) - np.pi, right?
  • Why implement both wrap_to_pi and wrap_to_2pi?
  • Assuming we decide the functions are useful overall:
    • Needs tests
    • Should have "see also" notes added to np.wrap and vice-versa.

@ahojnnes
Copy link
Contributor Author

The basic motivation behind these functions is the same as behind rad2deg, which could also be easily written as a / np.pi * 180 - just for convenience. But to be precise it's actually not the same as a % (2*np.pi):

>>> np.array([1, 2, -2*np.pi, 2*np.pi]) % (2*np.pi)
array([ 1.,  2., -0.,  0.])
>>> np.wrap_to_2pi([1, 2, -2*np.pi, 2*np.pi])
array([ 1.        ,  2.        ,  0.        ,  6.28318531])

>>> np.array([1, 2, 2*np.pi, -np.pi, np.pi, -3]) % np.pi
array([ 1.,  2.,  0., -0.,  0., 0.14159265])
>>> np.wrap_to_pi([1, 2, 2*np.pi, -np.pi, np.pi, -3])
array([ 1.        ,  2.        ,  0.        , -3.14159265,  3.14159265, -3])

But some more detailed description about the exact behavior has to be added for sure. As well as test cases, but please let me know what you think about it before I spend more time on this.

@njsmith
Copy link
Member

njsmith commented Jan 2, 2013

That's a good point about rad2deg.

I don't understand your point about wrap_to_2pi being different from a % (2*np.pi) -- your example just looks like some meaningless difference in floating point rounding? Or am I missing something? (And obviously wrap_to_pi is different than a % np.pi, that's why I said it was the same as a % (2*np.pi) - np.pi ;-).)

You didn't answer the question about why you think both of these functions are needed. Though I guess if we have one, then we might want both, just because if we only have a single function named np.wrap then people will never be able to remember which convention it uses, whereas the current names have a reminder right in the name.

I guess I'm +0 on this idea given the increase in readability and the precedent set by np.unwrap, np.rad2deg. Anyone else?

@ahojnnes
Copy link
Contributor Author

@njsmith This is especially useful if you deal with some kind of polar or spherical coordinates. Here you don't want 360deg wrap to 0deg.

@njsmith
Copy link
Member

njsmith commented Jan 13, 2013

Hmm, if what you're saying is that it is critical that you wrap to the closed interval [0, 2pi] rather than the half-open interval [0, 2pi), then this actually makes me more nervous about this code, not less. It seems very likely that there are users who will expect the half-open interval behaviour and find the closed-interval behaviour very confusing -- will we need to add another pair of functions later for them? And the actual behaviour you're worried about -- the handling of the floating point number which is exactly equal (in floating point) to 2pi -- is the sort of behaviour that you can't really handle correctly in floating point in the first place. It's entirely possible that where you expected 360 degrees your calculation instead produces 360.00000000000006 degrees (error of 1 unit-in-the-last-place) -- now what? Should that wrap around to 0? If the user was depending on the behaviour that you're talking about, then will their code break whenever this happens?

This is more questions than I like to have about code that's proposed for merging into numpy, given that numpy is supposed to be the core set of operations that everyone relies on.

@ahojnnes
Copy link
Contributor Author

Sorry for the late reply. I just had a look at what Matlab does and they seem to do the same as I implemented here.

Nevertheless feel free to decide whether this is useful at all or if you wish any modified version of this.

@charris
Copy link
Member

charris commented May 4, 2013

@ahojnnes This should probably be discussed on the list as it is a new function. I tend to agree with @njsmith about the interval, I'm not sure why Matlab does it that way except Matlab tends to use closed intervals.

@type@ npy_wrap_to_pi@c@(@type@ x)
{
if (x < -NPY_PI || x > NPY_PI)
return npy_wrap_to_2pi(x + NPY_PI) - NPY_PI;
Copy link
Member

Choose a reason for hiding this comment

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

return remainder(x, NPY_PI) is enough here, i think - although that needs C99

@eric-wieser
Copy link
Member

eric-wieser commented Dec 6, 2019

I think we should close this - both operations are easy to spell as x % (2*pi) and (x - pi) % (2*pi) + pi respectively. If we want to make the second easier to spell, then #9963 would allow it to be spelt np.remainder_ieee(x, 2*pi). Defining this as part of numpy directly means we have to make difficult choices about open and closed ranges, which are probably best left to the user.

Thanks for the suggestion anyway @ahojnnes!

@eric-wieser eric-wieser closed this Dec 6, 2019
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.

5 participants