Repository navigation
ENH: implement np.arange dispatch for Quantity - #17059
Conversation
|
Thank you for your contribution to Astropy! 🌌 This checklist is meant to remind the package maintainers who will review this pull request of some common things to look for.
|
|
👋 Thank you for your draft pull request! Do you know that you can use |
4013170 to
ecd9c72
Compare
np.arange dispatch for Quantity
de78fcf to
288f351
Compare
3d5b004 to
f61b81e
Compare
|
I think I may be done with discovering and fixing edge cases 🤞🏻 |
c3aee78 to
a96e31f
Compare
|
One more edge case that I'd like to phrase as a question to reviewers: np.arange(start=1*u.s, stop=10*u.min)
np.arange(stop=10*u.min, start=1*u.s)In my current implementation they do, because keywords arguments follow the order they appear in the signature, which is invariant. I guess another question that emerges from this illustration is: should it be valid to specify start and stop with different units without specifying a step ? |
a96e31f to
b7f0882
Compare
|
|
||
| # now we can bind positional arguments to their meaningful names | ||
| assert len(args) <= 4 | ||
| match args: |
There was a problem hiding this comment.
I think this is the first time the requirements for an implementation are so complex that using match/case feels simpler than the equivalent forest of if/else , but I'm really glad we have it.
b7f0882 to
c59a4b9
Compare
mhvk
left a comment
There was a problem hiding this comment.
Wow! Some comments inline, but also a larger point: I wonder if we need to keep track of the unit of the argument of like (i.e., self in Quantity.__array_function__). For np.zeros and np.ones, it would certainly make most sense for the output to have the same unit as the argument of like. For np.arange, this is less clear since I think we want something like np.arange(0*u.m, 10*u.m, like=0*u.s) to succeed. But perhaps it should set the default unit?
I think we need some special logic in Quantity.__array_function__ that assigns the unit to self.unit if it is not already set, i.e., if the output unit is not a unit but some signal (cannot be None, which is already used for truly unit-less output, like from comparisons, but could perhaps be False or True or something else recognizable)
| if start is not None and stop is None: | ||
| start, stop = stop, start | ||
|
|
||
| has_out_unit = False |
There was a problem hiding this comment.
The loop looks a bit like _quantities2arrays, except it has to deal with None. One perhaps can reverse the operations here, as in
kwargs = {k: v for k, v in (("start", start), ("stop", stop), ("step", step)) if v is not None}
new_values = _quantities2arrays(kwargs.values())
kwargs = {k: v for k, v in zip(kwargs.keys(), new_values)}
kwargs["dtype"] = dtype
There was a problem hiding this comment.
this should be addressed on my next push (but I'm riding a train so I might not succeed before I get off)
I would agree that using the unit from Update: and now I read the read of the message I replied to 🤦🏻♂️ |
|
Ok I think I've addressed all but your larger scope suggestion about capturing |
|
@lpsinger thanks for the reminder (#17059 (review)), I somehow didn't see your message yesterday but I'll address it now ! |
|
Re: #17059 (comment) Conveniently I missed this suggestion yesterday, but I think it fits pretty well the more general problem I set out to deal with this morning (regarding somehow using the unit from the Since I think this also connects with my (currently unanswered) question in #17059 (comment), I think we need to discuss these (edge?) cases before we can move further. Let me summarize the questions to be resolved here:
Q = 1 * u.km
np.arange(start=1*u.s, stop=10*u.min, like=Q)
np.arange(stop=10*u.min, start=1*u.s, like=Q)
Q = 1 * u.km
np.arange(10, like=Q) |
|
1,2: since So, I think one can still use |
words to live by. I think it makes sense to require units be set on the stop argument for now. We can always add more flexibility in the future (but might regret doing so now). I'll give it another go now ! |
|
Alright, I've adjusted my test suite to our new requirements and applied your suggested refactor as best I could. |
mhvk
left a comment
There was a problem hiding this comment.
This looks great! Only nitpicks left.
|
|
||
| """ | ||
|
|
||
| from __future__ import annotations |
| if v is not None | ||
| } | ||
|
|
||
| new_values, out_unit = _quantities2arrays(*list(qty_kwargs.values()), unit_from_first=True) |
There was a problem hiding this comment.
No need for the list, i.e., just *qty_kwargs.values(), unit_from_first=True
| assert type(arr) is u.Quantity | ||
| assert arr.unit == u.radian | ||
| assert arr.dtype == expected.dtype | ||
| np.testing.assert_array_almost_equal(arr.to_value(u.arcsec), expected) |
There was a problem hiding this comment.
Might as well do an from numpy.testing import assert_array_almost_equal, assert_array_equal on top.
There was a problem hiding this comment.
ah, actually it's already there. Let me simplify this !
| @@ -0,0 +1 @@ | |||
| Added a ``np.arange`` dispatch for ``Quantity``. | |||
There was a problem hiding this comment.
Maybe expand
Added a ``np.arange`` dispatch for ``Quantity`` (requires one to use ``like=<some_quantity>``).
Just to avoid people getting too excited...
| <Quantity [0. , 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9] cm> | ||
|
|
||
| Also note that the unit of the output array is dictated by that of the ``stop`` | ||
| argument, and the data has a floating-point dtype. If an integer dtype is |
There was a problem hiding this comment.
I'd remove the actual example of getting integers out - I really do not want to encourage people to use it. Physical quantities are real! So just, and that, like for quantities generally, the data has floating-point dtype by default.
| <Quantity [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] mm> | ||
|
|
||
| Workarounds include moving the units outside of the call to | ||
| Alternatively, one may move the units outside of the call as |
There was a problem hiding this comment.
I think to was more appropriate here, though frankly fine to just remove, i.e., end with ... call::
|
Thanks Marten ! I think I've addressed everything ! |
mhvk
left a comment
There was a problem hiding this comment.
Great, all good now, thanks!
* ENH: implement np.arange dispatch for Quantity * also test output dtype * fix a broken case where dtype is passed positionally * simplify test suite * DOC: update docs * TST: adjust test suite to new requirements * RFC: refactor impl to match new requirements * TST: add a new test case * DOC: adjust docs * cleanup * address review comments
Description
Implement a helper function for
np.arangewithlike=Quantity(...).Since the implementation turned out to be complex with many overrides to take into account, I'm opening this PR at an early stage for visibility.
Ref #17001
TODO:
ARRAY_FUNCTIONSnumpy/numpy#27451)raisestatements (write actual error messages)like=Angle(...)