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
quantity-aware frompyfunc #11893
quantity-aware frompyfunc #11893
Conversation
👋 Thank you for your draft pull request! Do you know that you can use |
@nstarman - I like very much where this is going! But let me try to get myself up to speed by thinking through clearly what expected API would be. I think there are really two cases of interest:
|
See numpy/numpy#19356 for the |
The numpy fix to |
Thanks! sounds great. |
I would have to manually compile numpy to test this? Or wait for https://anaconda.org/scipy-wheels-nightly/numpy to update |
Indeed - and "nightly" is more like "weekly". But installation is not particularly difficult, at least if you already have everything set up for installing astropy, what should just work is
(in a virtual environment) |
@mhvk |
@nstarman - I don't think you can really fix the no-units-on-input, units-on-output case given the current On the implementation, currently, you assume that if no unit is given on the input, then it should just take it as being the standard unit. To match what is done for other ufuncs and in Basically, I think it is good practice not to mix Quantities too much with regular arrays, unless those are assumed to be dimensionless. And here sticking with stricter, at least initially, keeps the implementation simpler. |
1b95e70
to
23ee631
Compare
Hello @nstarman 👋! It looks like you've made some changes in your pull request, so I've checked the code again for style. There are no PEP8 style issues with this pull request - thanks! 🎉 Comment last updated at 2021-10-12 02:04:28 UTC |
7b2353c
to
f1b9e51
Compare
@mhvk |
Some todos:
|
39ad355
to
8aadf49
Compare
@nstarman - little time now but my first instinct is to ensure that the tests do not change global state (i.e., have a teardown function, or use |
I'll do that after this is merged. I made a card at https://github.com/astropy/astropy/projects/7#card-65426157 |
5e58feb
to
35d02cd
Compare
641999d
to
97ee653
Compare
97ee653
to
f192ed9
Compare
ping @mhvk. All tests are now passing. Finally figured out the problem in the parallel tests. |
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.
@nstarman - some initial comments.
I like the idea in principle, but I wonder what is best to expose. I first thought we should only expose a new u.frompyfunc
and not even refer to register_ufunc
, but thinking more about it, that actually seems backwards: register_ufunc
is missing much more! E.g., we do not cover all the scipy.special functions, and should really give users the option to just register those if they need them! If register_ufunc
is properly exposed, do we still need u.frompyfunc
? I feel it becomes very strange.
Another larger-picture question is now at frompyfunc
, but really belongs to register_ufunc
- I think ideally we allow just passing in a helper.
Another bigger point, I'm not sure about the introspection of units, since for quantity_input
, the presence of those unit annotations mean something completely different. I feel if you have access to the function and can define it with units, you should just write it for Quantity
in the first place - there would be no point to frompyfunc
in that case (or, rather, the u.frompyfunc
would assume it is a scalar function that takes Quantity
).
(Sorry for the abbreviated review - have to cook dinner here!)
|
||
def teardown_class(self): | ||
# restore states | ||
ADDED_NARG_UFUNCS = self.ORIGINAL_REGISTERED_NARG_UFUNCS - qh.REGISTERED_NARG_UFUNCS |
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.
This looks the wrong way around; I'd expect qh.REGISTERED_NARG_UFUNCS - self.ORIGINAL_REGISTERED_NARG_UFUNCS
This test is only run on the scalar inputs. | ||
""" | ||
got = func(*inp) | ||
# need to convert from an object array to float array, for comparison |
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.
Not sure I understand the need for the comment. Outdated? Or because of assert_allclose
rather than assert_allequal
?
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.
Ah. Yes. This applied to the commented out line below, which ended up being outdated when I switched to assert_allclose
. The previous implementation did not work with an object array.
def test_has_units_assumed_correct(self, func, inp, res): | ||
""" | ||
Test unitful input has unitful output and units are assumed to be | ||
correct if not given. This only applies.""" |
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.
applies to what?
Test unitful input has unitful output and units are assumed to be | ||
correct if not given. This only applies.""" | ||
# partial unit assignment to trigger unit assumptions. | ||
# single inputs can't trigger unit assumptions. |
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.
You could have an output with units to trigger the helper. But overkill, I think!
correct if not given. This only applies.""" | ||
# partial unit assignment to trigger unit assumptions. | ||
# single inputs can't trigger unit assumptions. | ||
inp = [inp[0] * u.km, *inp[1:]] if len(inp) > 1 else inp * u.km |
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.
It seems weird to create a non-list which you then expand below. Isn't the first part just correct for all cases?
@@ -0,0 +1 @@ | |||
Add quantity-aware version of ``numpy.frompyfunc``. |
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.
This does not tell how to access it! I think it should be u.frompyfunc
|
||
`~numpy.ufunc`s operate on only recognized `~numpy.dtype`s (e.g. float32), | ||
so units must be removed beforehand and replaced afterwards. Therefore units | ||
MUST BE KNOWN a priori, as they will not be propagated by the astropy |
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.
This not really true: for many ufuncs the input units can vary (say, np.add
or np.multiply
) and any ufunc has a helper that returns converters, but that helper has access to the actual input units.
Indeed, given this, my sense is that it should be possible to pass in an explicit helper function, but that for convenience it is also possible to just pass in the units here. Might it be an idea to have a helper=
argument that is either a callable, or a tuple with (lists of) input and output units?
inunits, ounits : unit-like or sequence thereof or None (optional) | ||
Sequence of the input and output units, respectively. | ||
|
||
.. warning:: |
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.
I don't think a warning
is appropriate here. Rather, the docstring should explain at the top that the function itself should not work on quantities. And here, maybe be clear that the inputs are converted to those units and then the value is taken, while for the output the units are attached to the output array (probably easiest if the two are dealt with separately)
Also, let's avoid abbreviations, so just outunits
or out_units
?
returned `~numpy.ufunc`. Outputs will be assigned these units. | ||
Make sure these are the correct units. | ||
|
||
identity : object (optional, keyword-only) |
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.
Since the identity cannot have units, I think it should stay before inunits, ounits
(and like frompyfunc
, there should be a *
after nout
to force keyword arguments)
_not_ equivalent to setting the identity to ``None``, which implies the | ||
operation is reorderable. | ||
|
||
assume_correct_units : bool (optional, keyword-only) |
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.
Not quite sure about this. I feel that if it is needed, people could just write their own helper. If we do keep it, maybe have it be a more generic default_units
? Could be True
for inunits
, a list of not equal, u.one
for dimensionless (default) and False
for not allowed.
Thanks @mhvk for the detailed review. I'll try to get to this in the next few days. Definitely some points to ponder in the interim. |
Signed-off-by: Nathaniel Starkman (@nstarman) <nstarkman@protonmail.com>
Signed-off-by: Nathaniel Starkman (@nstarman) <nstarkman@protonmail.com>
Signed-off-by: Nathaniel Starkman (@nstarman) <nstarkman@protonmail.com>
Addressing PR review. Signed-off-by: Nathaniel Starkman (@nstarman) <nstarkman@protonmail.com>
Signed-off-by: Nathaniel Starkman (@nstarman) <nstarkman@protonmail.com>
Signed-off-by: Nathaniel Starkman (@nstarman) <nstarkman@protonmail.com>
Signed-off-by: Nathaniel Starkman (@nstarman) <nstarkman@protonmail.com>
Signed-off-by: Nathaniel Starkman (@nstarman) <nstarkman@protonmail.com>
Signed-off-by: Nathaniel Starkman (@nstarman) <nstarkman@protonmail.com>
Signed-off-by: Nathaniel Starkman (@nstarman) <nstarkman@protonmail.com>
Signed-off-by: Nathaniel Starkman (@nstarman) <nstarkman@protonmail.com>
f192ed9
to
65f5458
Compare
While this might be generically useful, I don't currently have a need for it in Cosmology. If someone else wants to take over, that'd be great, otherwise I say close? |
I'd tend to agree - I think the first priority would be a general way to register a |
Getting
numpy.frompyfunc
to work with QuantitiesVery much WIP.
In particular, I'm trying to figure out:
helpers
seem to always require pre-specifying a return unit. For a generic functionlambda x: x
, that's not correct.q = u.Quantity(result.flat); q.shape = result.shape
, but this should be applied automatically. While this could be done with a wrapper around the ufunc, that partially defeats the purpose of a ufunc (noufunc.reduce
, etc.)Request Collaboration: @mhvk
numpy.frompyfunc
ExamplesBut it's always degrees...The
inunits
means it knows about input & output units.unless
x
doesn't have units...This is also not ideal
but can be corrected with
q = u.Quantity(result.flat); q.shape = result.shape
(u.Quantity(result)
doesn't work. I think this may be a bug.)One cool thing I did is enable a little signature introspection...
numpy.vectorize
is basically just a wrapper forfrompyfunc
, so getting the latter to work is definitely the first step.