-
-
Notifications
You must be signed in to change notification settings - Fork 5.4k
ENH: Begin overhaul of ufunc machinery #20260
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
Conversation
Could you show how to add a docstring, too? |
@ev-br Done! It's just the second last argument in |
There are two separate items regarding Cython (the first one I am not 100% informed):
I think item 2 is here to stay regardless what we choose and the item 1 is the part handled here. And for that I think it would be great if we cut the fat out and use NumPy C API. But maintainability is a real concern since item 1 is already a very complicated machinery. So if we go down this path we should be exceptionally verbose in commenting and documenting for posterity. It is already very difficult for me to read even this much of code even though I feel what is happening. But I don't know why we are doing what we are doing in the individual steps. |
Thanks for the thoughts, @ilayn! I agree we should try and make this as clear and well-documented as possible, and we can work towards that. And yes, Effectively what we would be doing is using NumPy's machinery to expose the special functions as ufuncs and, elsewhere, using Cython's machinery to expose the special functions as Cython-compatible cimports for |
@izaid, have you looked at the ufunc definitions generated by |
@steppi That's very interesting, thanks for pointing it out. Indeed, it's basically the same. In that case, why use Cython at all in this intermediate step if we can now easily do exactly the same just with a C / C++ compiler and no intermediate step? 10 years ago, that probably wasn't true. Now I think having Cython do what C / C++ can do is just overcomplicating things. I'd argue we shouldn't use a secondary code generation step at all unless we really need it. |
I think we're all on the same page that Cython is no longer necessary here. From the comments in I think it's nice to be able to specify only the info needed for each function like we have in |
So what I showed was intended to be a reasonable mix between something simple and using template metaprogramming. If the goal is really to have one line per function, I could try and come up with an even more concise solution. I'd certainly prefer that then code generation from Python. My thoughts on writing it out by hand, which may not be shared, are that it's just copy-and-paste of a few lines per ufunc anyway. And doing that, but keeping it in C / C++, allows to clearly see what's going on underneath. |
Would we basically just be writing out the C++ version of |
@steppi I now believe it is possible to have a really simple solution provided we can use C++17. I've just updated the module (not totally done as is, but you get the idea). Almost everything in there would go in a single header. We could then just need to do, per ufunc:
The type signatures are deduced. |
Nice! That's really promising. We can use C++17 now too, #19811, which wasn't the case when we got started. |
c++17 should be fine, we have that as EDIT: snap |
@izaid, does the test suite pass locally for you? If not, but you just want to push so we can see the changes, you can add |
It does pass locally, and should now as well. |
Cool. What was the issue? It looked like every job that runs the test suite failed in CI earlier. |
Are you building with |
I've had a few bugs, may still, but what I have now should work portably. I am building with |
Seems to be working now. Lines 73 to 76 in 9a51186
|
Reasonably happy with this prototype now. To reiterate, everything before
That's it. |
|
We have both now. For the most part, the float32 functions that we've currently put in are just casting to float64, then back-casting to float32. This is the same behaviour as currently in SciPy. But long term, we are now in a position to enable float32 implementations easily. |
So template <typename T>
T ber(T x) {
std::complex<T> Be;
T ber, bei, ger, gei, der, dei, her, hei;
[snip] is the double implementation? Because I only saw this part template <>
inline float ber(float xf) {
double x = xf;
return ber(x);
}
C++ is really weird if it is so.
I think that is more ambitious than my fortran translation and probably starting to fall into YAGNI cluster. But don't let me stop you :) |
This is an implementation for a generic type
And this is indeed the
Haha, understood. I think it's relatively little work to enable generic floating-point types, but we'll do that one step at a time in future PRs. |
I think it will be a lot of work, but doable. For functions that use minimax polynomial or rational approximations, and where ones generated for 32 bit precision aren't available, we'd want to generate new rational functions and polynomials, to different degrees. For this, Boost has an implementation of the Remez exchange algorithm we could use. I've also been in contact with Stephen Moshier, the author of cephes, who explained to me relatively straightforward updates that could be made to the implementation he used for cephes that would make it a strong contender for us to use. There will also be other little details, like wanting to take asymptotic expansions to a different order. It's not all as simple as changing a tolerance from |
Yes but shouldn't it be actively rejecting if it does not support it? Hence my confusion, how would it downcast a complex? I think that would be a bit too liberal to allow for it. |
Indeed it doesn't do that. If it doesn't support it, it wouldn't compile. Also we only add the functions into the ufunc that we want. |
OK let me use an example, currently you have Is it going to reject it at runtime because it can't find the specialization? |
Yes, exactly that. The ufunc will reject at runtime. Why? Because we didn't put the complex implementation into the ufunc, just float and double:
|
Ah OK if something catches this error in the machinery and converts to a proper error then we are safe. |
Co-authored-by: Irwin Zaid <[email protected]>
Co-authored-by: Irwin Zaid <[email protected]> Co-authored-by: Lucas Colley <[email protected]>
Thanks @izaid, amazing work! I split this into three commits so that Lucas and I don't end up all over the blame after each only authoring a few minor changes. Separating out the linting commits would have required some advanced git surgery, because there was at least one that also touched some C++ code, so I just squashed those in with others. It's not really an issue at all. I verified that the diff is exactly the same as before the force push, so felt secure merging before all of the tests finished. |
Many thanks @steppi! I'm very glad to see this get in, it was the missing piece in our quest to sort out |
Hi all! This is a work in progress PR, and it is really meant as an example of one way we can go in the special overhaul. What I'm hoping to achieve is a discussion on the ultimate plan for special's build process. It's quite complex as is, and I don't think we need that complexity. Here we go.
I think most agree that the ultimate goal of the SciPy special module should be to provide a bunch of ufuncs, and we want the simplest possible method that turns the C++ scalar functions into ufuncs. So what is it? I see only two real options: using the NumPy C API directly or continuing to use Cython. Here I've created an example of using the NumPy C API.
What I have done is taken one example from specfun, specifically
expi
, and shown how to directly turn it into a ufunc using only NumPy's C API. To demonstrate overloading, I've overloaded theexpi
ufunc to also accept its complex versioncexpi
, which is also in specfun. The example module isspecfun2.cpp
in special. Most of that code is some functionality which will go in a common header. Everything works fine. No extra wrapper functions are needed, we can go right from the C++ function itself to the NumPy ufunc.Is this the way people want to go? I believe @ilayn has been thinking along these lines at #19964, so this PR is a natural continuation of that issue. If we can achieve consensus, I will do this for all of specfun that was just converted.
Closes #19964
cc @ilayn @steppi