-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
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
Modified astropy.units.Quantity.__array_ufunc__()
to return NotImplemented
if the inputs are incompatible
#13977
Conversation
6f95a6e
to
f0b073e
Compare
This is a behavior change, so I don't think we should backport. |
137b686
to
4cde504
Compare
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.
Really nice that this works! I have to think about it a little more, so just some initial comments for now.
Just keep in mind that there might be a bunch of conflicts you need to resolve when we apply |
32a6697
to
366bb26
Compare
@mhvk, I've been thinking about this more and I've been wondering if it would really be that bad of a performance hit if we just checked the types at the top of the function? It just seems that the way we have it now, duck types will have mildly poor performance since it has to evaluate most of the |
@byrdie - I think we should optimize for |
@mhvk, ok I just thought I'd ask since it would allow us to avoid this problem of having such a large FWIW I just timed it on my machine to check, and it seems pretty negligible to me. inputs = (np.empty((3, 4, 5)) * u.mm, np.empty((3, 4, 5)) * u.mm)
outputs = (np.empty((3, 4, 5)) * u.mm, )
%time not all(isinstance(io, (int, float, tuple, list, np.ndarray, u.Quantity, astropy.table.Column)) for io in inputs + outputs)
CPU times: total: 0 ns
Wall time: 0 ns Is this not a very accurate test? |
3ffabd9
to
b155bb9
Compare
Use I think we can put the |
p.s. Sorry to be so picky, but I try to be super careful to ensure use of |
I don't think you're being picky, I'm totally on-board with the need for speed and I agree that 1.69 microseconds is too slow. What do you mean by putting the |
(Without further checking) I meant astropy/astropy/units/quantity.py Lines 666 to 670 in a93dd3d
|
@mhvk, I think that is what I originally tried. If I do that, I get the error: function = <ufunc 'add'>, method = '__call__'
args = (<Quantity 1. m>, DuckQuantity1(data=<Quantity 1. mm>))
ufunc_helper = <function get_converters_and_unit at 0x00000231DAD5DC60>
units = [Unit("m"), None], converters = [None, False]
def converters_and_unit(function, method, *args):
"""Determine the required converters and the unit of the ufunc result.
Converters are functions required to convert to a ufunc's expected unit,
e.g., radian for np.sin; or to ensure units of two inputs are consistent,
e.g., for np.add. In these examples, the unit of the result would be
dimensionless_unscaled for np.sin, and the same consistent unit for np.add.
Parameters
----------
function : `~numpy.ufunc`
Numpy universal function
method : str
Method with which the function is evaluated, e.g.,
'__call__', 'reduce', etc.
*args : `~astropy.units.Quantity` or ndarray subclass
Input arguments to the function
Raises
------
TypeError : when the specified function cannot be used with Quantities
(e.g., np.logical_or), or when the routine does not know how to handle
the specified function (in which case an issue should be raised on
https://github.com/astropy/astropy).
UnitTypeError : when the conversion to the required (or consistent) units
is not possible.
"""
# Check whether we support this ufunc, by getting the helper function
# (defined in helpers) which returns a list of function(s) that convert the
# input(s) to the unit required for the ufunc, as well as the unit the
# result will have (a tuple of units if there are multiple outputs).
ufunc_helper = UFUNC_HELPERS[function]
if method == "__call__" or (method == "outer" and function.nin == 2):
# Find out the units of the arguments passed to the ufunc; usually,
# at least one is a quantity, but for two-argument ufuncs, the second
# could also be a Numpy array, etc. These are given unit=None.
units = [getattr(arg, "unit", None) for arg in args]
# Determine possible conversion functions, and the result unit.
converters, result_unit = ufunc_helper(function, *units)
if any(converter is False for converter in converters):
# for multi-argument ufuncs with a quantity and a non-quantity,
# the quantity normally needs to be dimensionless, *except*
# if the non-quantity can have arbitrary unit, i.e., when it
# is all zero, infinity or NaN. In that case, the non-quantity
# can just have the unit of the quantity
# (this allows, e.g., `q > 0.` independent of unit)
try:
# Don't fold this loop in the test above: this rare case
# should not make the common case slower.
for i, converter in enumerate(converters):
if converter is not False:
continue
if can_have_arbitrary_unit(args[i]):
converters[i] = None
else:
raise UnitConversionError(
f"Can only apply '{function.__name__}' function to "
"dimensionless quantities when other argument is not "
"a quantity (unless the latter is all zero/infinity/nan)."
)
except TypeError:
# _can_have_arbitrary_unit failed: arg could not be compared
# with zero or checked to be finite. Then, ufunc will fail too.
> raise TypeError(
"Unsupported operand type(s) for ufunc {}: '{}'".format(
function.__name__,
",".join([arg.__class__.__name__ for arg in args]),
)
)
E TypeError: Unsupported operand type(s) for ufunc add: 'Quantity,DuckQuantity1'
..\quantity_helper\converters.py:208: TypeError inside |
OK, I'll try to think a bit about this -- you are right that a super-big try/except is really not that nice (even if it does the job!). |
At this point, I'm tempted to just call it good the way it is, since it works and I've been testing it with my code for a while. The only other thing I can think of is having two |
2b61bb5
to
aeaa3d6
Compare
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.
Two small little clean-ups found upon another read. With that, ready to go in!
aeaa3d6
to
a23f5aa
Compare
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.
All OK now, thanks!
Hmm, the docs build failed, which I really do not understand since the PR that goes before this one (#14140) passed. Yet it seems related to that PR, not to what you have here. I'll need to investigate... |
My other PR is failing the docs build as well which has been confusing me. |
@byrdie - those failures are definitely a leftover of #14140 -- no idea how that passed with those omissions! Anyway, hopefully that's fixed in #14171. For here, thinking about it more, really we should return
What do you think? |
@mhvk, that looks good to me, I'll push an update shortly. |
51edff7
to
aa372e1
Compare
af62c3a
to
f3a74c0
Compare
58ed981
to
3408b5c
Compare
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.
@byrdie - sorry, but on yet another look, I found yet another really minor issue. With that out of the way, this really can go in...
3408b5c
to
2c5a4c0
Compare
…lemented` if the inputs are incompatible.
2c5a4c0
to
f3389c1
Compare
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.
All OK now, let's get it in. Thanks, @byrdie!
Thanks for all your help @mhvk! |
Description
This pull request is to address the current behavior of
astropy.units.Quantity.__array_ufunc__()
, which raises aValueError
instead of returningNotImplemented
if the inputs are incompatible.Fixes #13976
Checklist for package maintainer(s)
This checklist is meant to remind the package maintainer(s) who will review this pull request of some common things to look for. This list is not exhaustive.
Extra CI
label. Codestyle issues can be fixed by the bot.no-changelog-entry-needed
label. If this is a manual backport, use theskip-changelog-checks
label unless special changelog handling is necessary.astropy-bot
check might be missing; do not let the green checkmark fool you.backport-X.Y.x
label(s) before merge.