Skip to content

ENH: ArrayMethod sorting for built-in dtypes - #31278

Closed
MaanasArora wants to merge 5 commits into
numpy:mainfrom
MaanasArora:builtin-sort-array-methods
Closed

MaanasArora wants to merge 5 commits into
numpy:mainfrom
MaanasArora:builtin-sort-array-methods

Conversation

@MaanasArora

Copy link
Copy Markdown
Contributor

Adds new-style sorting and argsorting array-methods (#29737) to built-in NumPy dtypes (bools, integers, floats, dates). This allows the new path in PyArray_Sort to be functional for these, which will enable writing descending sorts soon after this. Supersedes #31273 to be much leaner. ping @seberg @mhvk - thanks!

Still have to check the resolve descriptors setup and see if string/unicode can be added, but otherwise this is basically complete (modulo below refcount comment).

AI Disclosure

I used Claude to write the initial template *_(arg)sort_loop functions but I cleaned it to the point there is little left of it.

return -1;
}
NPY_DT_SLOTS(dtypemeta)->sort_meth = sort_method_@name@->method;
Py_INCREF(sort_method_@name@->method);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For some reason, decrefing sort_method_@name@ causes a segfault, but this is leaking otherwise...

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather cryptic, I agree. The problem is that:

    if (PyType_Ready(&PyArrayMethod_Type) < 0) {
        return -1;
    }
    if (PyType_Ready(&PyBoundArrayMethod_Type) < 0) {
        return -1;
    }

needs to be called first. Remind me: object and void already use this due to being the default fallback path, I guess?
(But I agree that they are different enough, either way.)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No I don't think so actually! object and void have a comparison function that is set here:

    (PyArray_CompareFunc*)@from@_compare,

So, they are using the old path. That said it shouldn't at all be hard to use npy_default_sort_loop to take in a new comparison function for these.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, if those use @from@_compare, can we then not do the same in #30415 (we discussed there somewhat at #30415 (review))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, we could add that slot, but in the end, I suppose either way we'll be using a hard-coded path for those. (We'll probably just want to change the signature of the internal sort function to re-align, but it is hard-coded either way.)
One could add a "uses compare slot" auto-fill, but I am not sure it is worth it.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, probably will hard-code those, so unclear whether filling the new slot with legacy sort is worth the tradeoff.

So for the DECREF... I guess this entire registration needed to be moved, so I added a function and called that after that block instead of within set_typeinfo, thanks!

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That should work yeah. If it is nicer, you can also just move the finalization of ArrayMethod_Type and BoundArrayMethod_Type to happen first, they don't have any dependencies and can happen much earlier.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah okay, thanks! That does look nicer (less verbose), pushed!

@seberg seberg left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, small comments, mostly how to fix the decref issue.

Comment thread numpy/_core/src/multiarray/arraytypes.c.src
}

return method->casting;
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmmm, I guess we these them for timedelta64/datetime64 unfortunately, because it is parametric? (Just too bad, since this is all very simple in principle...)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunate indeed, yes, it's because they're parametric. Given how often we're running into this, probably good to fix the issue somehow...

Comment thread numpy/_core/src/multiarray/arraytypes.c.src Outdated
return -1;
}
NPY_DT_SLOTS(dtypemeta)->sort_meth = sort_method_@name@->method;
Py_INCREF(sort_method_@name@->method);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather cryptic, I agree. The problem is that:

    if (PyType_Ready(&PyArrayMethod_Type) < 0) {
        return -1;
    }
    if (PyType_Ready(&PyBoundArrayMethod_Type) < 0) {
        return -1;
    }

needs to be called first. Remind me: object and void already use this due to being the default fallback path, I guess?
(But I agree that they are different enough, either way.)

@mhvk mhvk left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks nice! Main question I have is whether we can know these routines are actually used! You probably found out implicitly from errors... Can one test using the RuntimeError? Though it seems difficult to trigger in practice. So perhaps it can wait until we support descending...

@MaanasArora

Copy link
Copy Markdown
Contributor Author

Thanks @mhvk! Yes I found out via errors indeed, and confirmed with printf statements (I know...) Do you mean a runtime error in PyArray_Sort? I think that could make sense, something that checks if one of these dtypes has made it past the array method path, it should be fast so we probably don't care about penalizing other dtypes. Going to try this if it makes sense!

return timsort_@suff@(data[0], dimensions[0], NULL);
#endif
default:
PyErr_Format(PyExc_RuntimeError,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was wondering if it was possible to hit this RuntimeError -- but ideally from python. But that does seem tricky; having checked with print statements really is good enough!

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, not sure we can reach it easily. Although, once we follow up with reverse sorting, I think we'll have our proof at least?

@MaanasArora MaanasArora Apr 21, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can reach it by setting descending=True right? That should hit this?

Edit: sorry, I didn't recall we don't have it in Python yet for some reason! Yeah, that could be easier to test... no kwarg seems to hit this path right now in Python.

@seberg

seberg commented Apr 29, 2026

Copy link
Copy Markdown
Member

@MaanasArora happy to put this in if it helps with the other PR, just let me know. I think we'll want to move much of this to C++ to avoid that c.src -> C++ template transition (which is just really verbose).
(So in the end, maybe we can just go with the other PR directly and skip this also.)

@MaanasArora

Copy link
Copy Markdown
Contributor Author

@seberg Yeah I rebased the other PR and moved sort loop creation to C++, so we can just go with that one I think! I'll close this. Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants