ENH: Add sort_compare dtype slot to register new-style sorts#30415
ENH: Add sort_compare dtype slot to register new-style sorts#30415MaanasArora wants to merge 6 commits into
sort_compare dtype slot to register new-style sorts#30415Conversation
There was a problem hiding this comment.
maybe add a mention for why you'd want to do this - e.g. if comparisons have some big cost that can be amortized over the whole array
There was a problem hiding this comment.
I've added a note - a bit on the detailed side, hopefully that works, thanks!
|
Thanks for looking @ngoldbaum!
Agreed, the full loop implementation would probably be overkill in many cases. I think this would be necessary to expose the sorts on the user-dtypes too since they don't define full sorts now, so a good way to test the new infrastructure! |
e4de2bd to
0d8c026
Compare
|
Added release note and support for GIL flag. @seberg Friendly ping if you want to look at this when you get the chance! |
mhvk
left a comment
There was a problem hiding this comment.
Apologies for the delay in looking at this - it really slipped too far....
This looks mostly great, some small comments in-line.
A bigger question is about the name: should it just be compare? I.e., can it not be used also for dtype comparison more generally, replacing NPY_DT_PyArray_ArrFuncs_compare? I cannot really think what the difference would be... (but if we go that route, we can adjust comparison in a follow-up PR). Let me ping @seberg on this bit...
Relatedly, I think not if we want to get rid of ArrFuncs eventually, but let me ask just in case: would it make sense to by default use NPY_DT_PyArray_ArrFuncs_compare if it is available?
| .nout = 1, | ||
| .dtypes = sort_dtypes, | ||
| .slots = sort_slots, | ||
| .flags = NPY_METH_NO_FLOATINGPOINT_ERRORS, |
There was a problem hiding this comment.
Given it being set here, is it still necessary to also do it in npy_default_get_(arg)sort_loop?
There was a problem hiding this comment.
Ah thanks, good catch! It's not - I'll remove it from there.
There was a problem hiding this comment.
It's correct in the get_loop, here it is rather irrelevant (it's a default, but for the default get_loop)
There was a problem hiding this comment.
Not sure I understand sorry - don't they have the same effect? But I can remove it from here if it's a design / reuse thing.
There was a problem hiding this comment.
You can remove it here, it doesn't matter really. The value here is what get_loop uses if you don't provide a custom get_loop. But you do, so it is effectively not used.
| .flags = NPY_METH_NO_FLOATINGPOINT_ERRORS, | ||
| }; | ||
| if (dtype->flags & NPY_NEEDS_PYAPI) { | ||
| sort_spec.flags |= NPY_METH_REQUIRES_PYAPI; |
There was a problem hiding this comment.
Same question here.
Also, in the other places, it uses PyDataType_REFCHK - any reason for the difference?
There was a problem hiding this comment.
Same here, it's not!
I think PyDataType_REFCHK is used for descriptors and not the dtypemeta (at least type wise).
There was a problem hiding this comment.
Yeah, NPY_NEEDS_PYAPI flag makes more sense. REFCHK doesn't matter, so should be changed in the first one.
Maybe it's best to keep it this way? The I guess this is partly saying that defining the comparisons ufuncs is just easy enough, while sorting is a bit tedious to implement. |
seberg
left a comment
There was a problem hiding this comment.
This looks good, sorry should have pushed this earlier. I am OK with seeing this is a bit distinct to comparisons.
Mainly, because the alternative to me would seem to be having operands for all normal comparisons + variations for NaN order for less and greater.
But... that adds a complexity that I am not sure is all that helpful (because as mentioned adding the ufunc family is pretty straight forward).
Would it be easy to use this for the SFloat (without losing the other test coverage completely since I think string ufuncs use that also?)
| PyArrayMethod_SortParameters *parameters = (PyArrayMethod_SortParameters *)context->parameters; | ||
| *flags |= NPY_METH_NO_FLOATINGPOINT_ERRORS; | ||
|
|
||
| if (PyDataType_REFCHK(context->descriptors[0])) { |
There was a problem hiding this comment.
As mentioned below, should use the NPY_REQUIRES_API flag here. Otherwise should be OK, if someone wants to relax this, they'll just have to implement the whole thing...
There was a problem hiding this comment.
This (and removing from registration) is done, thanks! (I'll push sfloat changes later)
| .nout = 1, | ||
| .dtypes = sort_dtypes, | ||
| .slots = sort_slots, | ||
| .flags = NPY_METH_NO_FLOATINGPOINT_ERRORS, |
There was a problem hiding this comment.
You can remove it here, it doesn't matter really. The value here is what get_loop uses if you don't provide a custom get_loop. But you do, so it is effectively not used.
| } PyArrayMethod_SortParameters; | ||
|
|
||
| typedef int (PyArrayDTypeMeta_SortCompare)(const void *a, const void *b, | ||
| PyArrayMethod_Context *context); |
There was a problem hiding this comment.
Might even be better this way, but I am slightly partial towards putting the context first, just because it is the way in ufunc loops.
There was a problem hiding this comment.
Unfortunately I cast directly to CompareFunc, so seems hard to do?
There was a problem hiding this comment.
What if we make a wrapper for the old-style CompareFunc? For the numpy dtypes we could (in a follow-up PR perhaps...) then write explicit loops, in order to keep the same performance.
|
No worries at all, thanks for reviewing @mhvk @seberg!
Yes, as Sebastian said, if we use this for comparison, I think we lose some functionality - also, keeping this tied to sorting makes it easier to separate concerns if a dtype has some sort-specific quirk (like some optimization that can be made for sort but not compare due to the superset/subset distinction). If we do add a general compare, it can probably be used to fill this by default, but that's for the future! One note though, this should also probably be used for
The |
|
Sorry, missed the top-level comment - thanks for reviewing.
Yes I think it's quite easy, in fact IIRC I used it for testing - going to push that! |
|
Now saw your reply to my bigger comment. I guess I can see that comparison might be different for Overall, it seems fine for comparison to remain as simple as possible and be used wherever (as it is for the old-style function, I think...). |
|
Just trying to keep playing devil's advocate a bit: the NaN case is indeed one where regular comparisons may different from what is needed for sorting, but wouldn't that be covered by having a context passed in? |
Well, they are slightly different if you look ahead:
I am not opposed at all to trying to merge. The big thing is that we definitely need to change the current sorting implementations (which seems fine) and either keep the old one, or add a mini-wrapper (and live with the slight performance hit). Changing+wrapping/duplicating the generic sort loop is fine. I think the main thing is that |

Follows #29737 and #30328 (and supersedes #29987) to implement a
sort_compareslot that can be used to register sorting methods using a comparison function. This functionality also existed in the ArrFuncs slots, but now we implement the new-style sorting methods, and the array method context is passed to the comparison function rather than the array itself.I've tested this with
mpfdtypeon the user dtypes repo (though having a bit of an issue with installation... the slot wasn't visible there, so had to redefine just the macro). Unfortunately there don't seem to be dtypes we can write tests for within numpy itself, though I did a small experiment with the scaled float dtype. ping @seberg @mhvk @ngoldbaum if you're interested - thanks!