Repository navigation
Wrap erfa functions with ufuncs and gufuncs - #7502
Conversation
|
Hi there @mhvk 👋 - thanks for the pull request! I'm just a friendly 🤖 that checks for issues related to the changelog and making sure that this pull request is milestoned and labeled correctly. This is mainly intended for the maintainers, so if you are not a maintainer you can ignore this, and a maintainer will let you know if any action is required on your part 😃. Everything looks good from my point of view! 👍 If there are any issues with this message, please report them here. |
|
@astrofrog - I posted a gist with only a small number of erfa functions wrapped - but ones that show the things that are needed to deal with different cases: |
|
Sorry, that should be https://gist.github.com/mhvk/a3c17f0db77b91151d50a0d278621d97, as I should at least include one function that needs the fix for when there are only output arguments with a core dimension of 3 ( |
|
More general comment on gists: note how for most functions, the python wrapper does essentially nothing but combine status codes. The only ones where more is done is for signatures where the output has some core dimension (e.g., is a vector) and the input is not, since in that case the gufunc needs an output argument to know what the dimension should be. It is actually very annoying that one cannot give a fixed size, but this is being fixed in numpy/numpy#11175 |
c9a55c0 to
df37662
Compare
Using ufuncs instead of C routines and a custom iterator has the advantage that casting to arrays with the right dtype and iteration is taken care of automatically, in numpy C code. For small arrays, this means substantially faster execution. Another advantage is that classes can now override the ufuncs using __array_ufunc__ - this is important both for ensuring Quantity input is disallowed (or at least treated correctly rather than just cast to array), and that a new Distribution class can work with the erfa functions.
Before, contents of astrom and ldbody were kept as well.
Also simplify the viewing back.
Rest should be kept as structured arrays.
df37662 to
370a036
Compare
|
I realize this is a difficult PR to review, but it would be nice to get this in - substantial performance increase, plus more to come with things that are enabled by this. @astrofrog - do you think you will have time to review? Or do you perhaps have a suggestion for someone else? |
astrofrog
left a comment
There was a problem hiding this comment.
I've just reviewed this - I don't understand 100% of the C code, but overall this looks great to me. Just a few minor comments below.
I know that @lpsinger has experience with writing ufuncs - @lpsinger - would you mind taking a look at this? (note that you can view the generated code if you need, see previous comment by @mhvk). In particular, I would be curious to know if there is anything that is being done here that would preclude using OpenMP in future to speed things up?
| except: | ||
| raise ValueError("{0} must be of trailing dimensions {1}".format(name, shape)) | ||
| class D3Fix(numpy.ndarray): | ||
| # We do this to help ensure Quantity happily lets this pass... |
There was a problem hiding this comment.
Could you provide more details in the comment here? I don't quite understand why this is needed.
There was a problem hiding this comment.
Yes, how could one... I added a very detailed comment.
|
|
||
|
|
||
| def arrayify_inputs_and_create_d3_fix(inputs, core_dims, out_core_shape, out_dtype): | ||
| # Need to ensure we can get a shape |
There was a problem hiding this comment.
This comment isn't very clear - could you write a comment to describe more verbosely what this function does?
There was a problem hiding this comment.
Now a proper docstring.
| # <--------------------------Actual ERFA-wrapping code------------------------> | ||
|
|
||
| {% for constant in constants %} | ||
| {{ constant.name }} = {{ constant.value }} |
There was a problem hiding this comment.
The generated code here has brackets that aren't needed:
DR2AS = (206264.8062470963551564734)
This appears to be the case only for floating point values (not integers)
There was a problem hiding this comment.
The parentheses are in the erfam.h file. I don't think it hurts to just let them be.
|
|
||
| /* Extra/PVMergeExtract */ | ||
| void eraPav2pv(double p[3], double v[3], double pv[2][3]); | ||
| void eraPv2pav(double pv[2][3], double p[3], double v[3]); |
There was a problem hiding this comment.
Could you add a comment to this file about why it is needed?
| or (subsection == "AngleOps") | ||
| or (subsection == "SphericalCartesian") | ||
| or (subsection == "MatrixVectorProducts") | ||
| or (subsection == 'VectorOps')): |
There was a problem hiding this comment.
The inner parentheses are not needed here
There was a problem hiding this comment.
I left them as I found them.
But talking about those: shall just take out the whole clause? We're compiling nearly everything anyway, so there is little point in terms of trying to keep it small or so.
I'll do that as a second commit, so it would be easy to revert.
| # pvmatarr = np.array([pvmat]*3, order='F') | ||
| # astrom4 = erfa.apcs13(jd1, jd2, pvmatarr) | ||
| # assert astrom4.shape == (3,) | ||
| # np.testing.assert_allclose(astrom4['em'], 1.010428384373318379) |
There was a problem hiding this comment.
Was this temporarily commented out?
There was a problem hiding this comment.
Some of those tests no longer make sense, since the pv vectors are not input as (..., 2, 3) arrays. I'll keep what does work still.
Also uncomment tests that were not passing in an earlier stage.
astrofrog
left a comment
There was a problem hiding this comment.
Looks good to me know. I'll leave it up to you as to whether to wait for another review.
|
Merging this into master appears to have broken >>> get_body_barycentric("earth", Time("2018-01-01T00:00:00")) |
|
@Cadair , best to open a new issue then! |
|
Whoa. My mind is blown by using jinja2 to generate ufunc wrappers. Your Numpy-fu exceeds my own. |
This is an alternative to #7484, with the main difference being that for matrices and vectors now the more logical "generalized ufuncs" are used, which take care of the trailing dimensions of
(3,)and(3,3), respectively. EDIT: improved performance is just like for #7484.In this version, I decided on purpose not to similarly treat
pv(trailing dimensions of(2, 3)) and integer decompositions of years, hours, and degrees (ints with trailing dimension of(4,)). I think those really are more logically treated as structured dtype, since their parts have different units - it is really bad to have to remember thatpv[..., 1, :]is the velocity part, with units that can beau/dayorm/sdepending on context - this is what we haveQuantityfor! But it will be very difficult to have different units for different parts of an array; in contrast, it should be quite trivial to have different units for different parts of a structured dtype. And seeingpv['v']it is much more obvious what one is looking at.In the end, I found that surprisingly few changes were required to the code to make this work, and I feel all add to the clarity. Hence, I prefer this PR over #7484.
p.s. The only thing I like a little less is the work-around I need to do for those functions which produce vectors or matrices, but do not take them. This is because currently
gufuncsignatures cannot indicate that some dimension should be a fixed number (being addressed... EDIT: numpy/numpy#11175). This means I have to generate an empty output with the right dimensions. But this is unhandy forQuantity, so right now I work around that by making the output of a special class. Not too elegant, but works.