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
Add support for np.clip
and ndarray.clip
.
#3468
Conversation
Note that I had to comment out a couple tests because they are currently broken! Something to do with
|
I'm also encountering some strange behaviour on this branch when it comes to using this overloaded
This raises the error:
The strange thing is I can get this to work if I replace the |
Codecov Report
@@ Coverage Diff @@
## master #3468 +/- ##
=========================================
Coverage ? 80.61%
=========================================
Files ? 392
Lines ? 79833
Branches ? 9076
=========================================
Hits ? 64359
Misses ? 14060
Partials ? 1414 |
numba/targets/arrayobj.py
Outdated
raise ValueError("array_clip: must set either max or min") | ||
if out is None: | ||
out = np.empty_like(a) | ||
for i in range(len(a)): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note to self: use for index, val in np.ndenumerate(a):
and add multi-dimensional tests. Currently blocked on #3469 for the scalar case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Assuming that fixing np.ndenumerate(scalar)
may well be complicated, could the implementation just branch to an function that specifically deals with scalars as a workaround? e.g.
@overload(np.clip)
def np_clip(a, a_min, a_max, out=None):
if isinstance(a, types.Number):
def np_clip_impl(a, a_min, a_max, out=None):
# scalar impl
elif isinstance(a, types.Array):
def np_clip_impl(a, a_min, a_max, out=None):
# array impl
return np_clip_impl
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After the most recent changes (see thread) I can't see a way of doing this cleanly, since this blows up the amount of case-work for handling a_min
and/or a_max
being None
. Suggestions welcome, or this could be deferred to #3469?
Could someone please provide me with a lead on why |
Speculating here, I think it is related to how Numba handles "optional" types, which are types where the value is either of a specific type (like @sklam: Can you offer some advice here? |
I think the "optional" return type is because type inference has to accommodate The issue present arises because from numba import njit
import numpy as np
def foo(A):
return np.cos(A)
njit("(float64[:],)")(foo)(np.arange(10.)) # fine
njit("(optional(float64[:]),)")(foo)(np.arange(10.)) # broken To force type inference to determine the type, specialisation is needed: def _alloc_out(a, X): # dummy function that takes the arg we need to resolved and information to do so
pass
@overload(_alloc_out)
def _alloc_out_impl(a, X): # overload the dummy function returning an implementation determined by whether `X` is a None-like type.
if X is None or isinstance(X, types.NoneType):
def impl(a, X):
return np.empty_like(a)
else:
def impl(a, X):
return X
return impl
@overload(np.clip)
def np_clip(a, a_min, a_max, out=None):
def np_clip_impl(a, a_min, a_max, out=None):
if a_min is None and a_max is None:
raise ValueError("array_clip: must set either max or min")
ret = _alloc_out(a, out) # Force typing to explicitly resolve the type of `ret`, the function call will be inlined
for i in range(len(a)):
if a_min is not None and a[i] < a_min:
ret[i] = a_min
elif a_max is not None and a[i] > a_max:
ret[i] = a_max
else:
ret[i] = a[i]
return ret
return np_clip_impl by delegating the compilation of |
Thanks @stuartarchibald. I pushed a commit with your change. That did the trick in getting I also had to do something similar to handle the possibilities of It also supports multi-dimensional arrays now. The scalar case is broken (as in #3469), which I could fix with more type-specific implementations, but this would double the amount of code to handle all the possibilities again -- unless I'm missing a cleaner way of doing this. |
After some more follow-up, it appears that: jit(nopython=True)(lambda a: np.expm1(a.clip(-5, 5)))(a) is still broken for the same reason (difference is between |
@arvoelke RE #3468 (comment) I took a look at this and was a bit puzzled, so dug around a bit and think it's a bug, reported it here #3489. For now, please could you try just dropping the Also, as it stands right now, the cut-off for the 0.41.0 Release Candidate is Monday 18th Nov if you were hoping for it to be in the next release? (we'll endeavour to get review turned around in time!) Thanks. |
I gave this a shot, but it appears as though the above snippet still fails when @overload_method(types.Array, 'clip')
def array_clip(a, a_min=None, a_max=None):
# TODO: out argument not supported (see issue #3489)
def impl(a, a_min=None, a_max=None):
return np.clip(a, a_min, a_max)
return impl Reminder that these optional arguments have defaults for If on the other hand I remove the defaults, as in: @overload_method(types.Array, 'clip')
def array_clip(a, a_min, a_max):
# TODO: out argument not supported (see issue #3489)
def impl(a, a_min, a_max):
return np.clip(a, a_min, a_max)
return impl then So I assume this is connected to #3489 in the same manner. Since these defaults are a pretty significant feature of |
In the interest of streamlining our conversation and the review process, I've pushed a commit that removes |
This test calls the function: def lower_clip_result(a):
return np.expm1(np.clip(a, -5, 5)) Maybe I should use a function other than |
you can use |
Thanks. Passed locally and pushed. Surprised that even passed before! |
All of the builds passed this time. Is this ready for review, or should we hold off to fix the |
I think we need to implement both to avoid confusion for users. We are planning for 0.42 to be a short dev cycle, with a release in the 3rd week of December, so if we miss 0.41, it will make it there. |
As title. Fixes numba#3465
Just checking in on the state of this PR. @arvoelke was wondering if you have time to address the above, if not, don't worry, one of the core developers can take over to do the fixes. Thanks. |
My bad for letting this slip. It would be good if someone else could take over. A big thing that was slowing me down here was the use-case "in which a_min and/or a_max can be array_like." See above for documentation / etc. If the concern here isn't about matching the entire spec for |
@arvoelke thanks for letting us know and your work on this so far. One of the core devs will pick it up. |
any chance this will ever get merged? |
Thanks for asking about this! Yes, there is definitely a chance that it will become merged. As soon as it becomes a priority of one of the core devs or when someone from the community picks it up and drives it to completion. |
If this is something you need immediately (i.e., before merging), you're okay with calling @extending.overload(np.clip)
def np_clip(a, a_min, a_max, out=None):
def np_clip_impl(a, a_min, a_max, out=None):
if out is None:
out = np.empty_like(a)
for i in range(len(a)):
if a[i] < a_min:
out[i] = a_min
elif a[i] > a_max:
out[i] = a_max
else:
out[i] = a[i]
return out
return np_clip_impl Extending this to handle all of the different ways in which |
How about implementing def np_clip(a, a_min, a_max, out=None):
if out is None:
out = np.empty_like(a)
out[:] = np_minimum(np_maximum(a, a_min), a_max)
return out This seems to cover various input cases ... |
PR #6808 started to continue this work. |
#3468 continued: Add support for `np.clip`
As title.
Fixes #3465