Skip to content
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

Rewrite core in C using Numpy ufuncs #110

Merged
merged 6 commits into from
Nov 5, 2018
Merged

Conversation

lpsinger
Copy link
Contributor

@lpsinger lpsinger commented Oct 30, 2018

As a result, broadcasting across nside and ipix is supported automatically.

Fixes #72, closes #75.

@lpsinger lpsinger requested review from astrofrog and cdeil October 30, 2018 21:38
@lpsinger lpsinger force-pushed the ufuncs branch 2 times, most recently from a1e073e to f307f89 Compare October 30, 2018 22:16
@lpsinger lpsinger changed the title WIP: Rewrite core using Numpy ufuncs WIP: Rewrite core in C using Numpy ufuncs Oct 30, 2018
@lpsinger lpsinger force-pushed the ufuncs branch 4 times, most recently from 4e5a708 to 6ee0e69 Compare October 30, 2018 23:33
@lpsinger lpsinger changed the title WIP: Rewrite core in C using Numpy ufuncs Rewrite core in C using Numpy ufuncs Oct 30, 2018
@lpsinger
Copy link
Contributor Author

There are a few Python 2 issues with this patch. Is it OK to drop Python 2 support from this package?

@cdeil cdeil mentioned this pull request Oct 31, 2018
@cdeil
Copy link
Member

cdeil commented Oct 31, 2018

@lpsinger - Thanks!

Unless this is urgent for you, I'd suggest we decide about Python 2 support first in #111 and then continue here. (This discussion will probably take a few days, but hopefully within a week we should know.)

@lpsinger
Copy link
Contributor Author

Unless this is urgent for you, I'd suggest we decide about Python 2 support first in #111 and then continue here. (This discussion will probably take a few days, but hopefully within a week we should know.)

I have a header six.h that I could contribute so that we can decouple the two issues.

@cdeil
Copy link
Member

cdeil commented Oct 31, 2018

Sure, if you want this in quickly, and you can keep Python 2 support without much extra work, OK to add six.h.

As a result, broadcasting across nside and ipix is supported
automatically.

Fixes astropy#72, closes astropy#75.
@cdeil
Copy link
Member

cdeil commented Nov 1, 2018

@lpsinger - Can you have a look at the CI fails?

Apart from those, there's a few fails because Astropy isn't compatible with Matplotlib 3.0:

@astrofrog - What should be done about those? Probably tweak the versions in .travis.yml, e.g. pin MPL to 2.x for now? Can you do the appropriate edit in master?

@cdeil
Copy link
Member

cdeil commented Nov 1, 2018

Oops, I didn't refresh the page, so was looking at an older status.

The only CI fail that remains here is the unrelated one for MPL 3.0.

@astrofrog - Could you please do a code review on this PR?

Copy link
Member

@cdeil cdeil left a comment

Choose a reason for hiding this comment

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

@lpsinger - Two small inline comments from me.

Overall this looks good and I'm +1 to merge.

But I'm not really familiar with Python C interfacing much, so @astrofrog - if you could review this, or ping someone from the Astropy team that knows C / Numpy / ufunc well, that would be good. Although if no-one has time, I think we could also just merge this in for now, I don't really have a concern.

astropy_healpix/_core.c Outdated Show resolved Hide resolved
astropy_healpix/_core.c Show resolved Hide resolved
Copy link
Member

@astrofrog astrofrog left a comment

Choose a reason for hiding this comment

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

This looks good to me - the only thing perhaps missing (unless I didn't see it) would be a test to demonstrate the broadcasting functionality of the ufuncs. Otherwise, our tests are pretty extensive, so if they all pass then this is good to go.

Copy link
Member

@cdeil cdeil left a comment

Choose a reason for hiding this comment

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

I see that you have this everywhere in Python when calling into C:

    if _validate_order(order) == 'ring':
        func = _core.lonlat_to_healpix_ring
    else:  # _validate_order(order) == 'nested'
        func = _core.lonlat_to_healpix_nested

and then there is the order_funcs in the C code with the IMO quite complex way to select the func.

I really don't know, just as a question: is there a simpler way to do this, avoiding the whole *data and order_funcs stuff? E.g. by having a wrapper function that selects or helper function that applies the conversion? Related: I also see strcmp in one place - would int there to select ordering be better that str?

One small suggestion: you use -1 in many places as "NA" placeholder in the C code, right? Maybe make that a global constant instead of the hard-coded -1 for better readability?

@lpsinger - Do as you think best, and go ahead and merge this in.

Should we make a release to see if this works OK in package distribution?

for (i = 0; i < n; i ++)
{
int64_t pixel = *(int64_t *) &args[0][i * steps[0]];
int nside = *(int *) &args[1][i * steps[1]];
Copy link
Member

Choose a reason for hiding this comment

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

Why do we have mostly int64_t for integers, but then int nside? I guess it doesn't matter because nside will fit in an int, but wouldn't it be simpler to have int64_t everywhere so that we never have to think about what to put as type? - just a thought. Do as you think best and 👍 to merge this in.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Such a change would affect the astrometry.net code as well, so that's a bigger question.

@lpsinger
Copy link
Contributor Author

lpsinger commented Nov 5, 2018

I see that you have this everywhere in Python when calling into C:

    if _validate_order(order) == 'ring':
        func = _core.lonlat_to_healpix_ring
    else:  # _validate_order(order) == 'nested'
        func = _core.lonlat_to_healpix_nested

and then there is the order_funcs in the C code with the IMO quite complex way to select the func.

I really don't know, just as a question: is there a simpler way to do this, avoiding the whole *data and order_funcs stuff? E.g. by having a wrapper function that selects or helper function that applies the conversion?

In Healpy, the ordering is treated as a flag (nested=False or nested=True) and it is broadcastable like any other parameter. Switching between the two indexing modes is done inside the inner loop in C. That would certainly be simpler.

I judged that the user would never want to pass the ordering as a vector, that it would never need to be broadcastable, and I tried to avoid the potential performance hit of a conditional in the inner loop. However, the function pointer stuff might be equally costly, and is a bit less readable.

We could use preprocessor macros to make two versions of each loop function, one for each ordering. That would be more performant, but also more code and less readable code.

Another option is that the _core module could wrap healpixl_nested_to_xy, healpixl_xy_to_ring, healpixl_to_radec, and radec_to_healpixlf as ufuncs; then healpix_to_lonlat and lonlat_to_healpix could be composed from those functions in Python. That might be the simplest solution of all, but the memory access pattern would be sub-optimal.

Related: I also see strcmp in one place - would int there to select ordering be better that str?

We could use PyUnicode_RichCompare to do the comparison with Python and take advantage of string interning, but that would take several more lines of code and would come with its own reference counting overhead. But it's only compared once, so none of that would make a measurable difference in performance.

One small suggestion: you use -1 in many places as "NA" placeholder in the C code, right? Maybe make that a global constant instead of the hard-coded -1 for better readability?

Done.

@lpsinger
Copy link
Contributor Author

lpsinger commented Nov 5, 2018

Should we make a release to see if this works OK in package distribution?

I tried some of my own code that uses astropy-healpix through reproject, and the results looked good.

@lpsinger lpsinger merged commit da3defd into astropy:master Nov 5, 2018
@lpsinger lpsinger deleted the ufuncs branch November 5, 2018 16:05
@cdeil
Copy link
Member

cdeil commented Nov 5, 2018

In Healpy, the ordering is treated as a flag (nested=False or nested=True) and it is broadcastable like any other parameter. Switching between the two indexing modes is done inside the inner loop in C. That would certainly be simpler.

I judged that the user would never want to pass the ordering as a vector, that it would never need to be broadcastable, and I tried to avoid the potential performance hit of a conditional in the inner loop. However, the function pointer stuff might be equally costly, and is a bit less readable.

That does sound a bit simpler and easier to read to me.

Agreed that the broadcasting feature isn't needed here, and performance is probably very similar, so the only improvement here would be code simplicity, not needing to know about the not well-documented data option of ufuncs and the function pointer complexity.

@lpsinger @astrofrog - What do you think? Just keep as-is or change to this?

@cdeil cdeil added this to the 0.4 milestone Dec 18, 2018
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.

Implement Cython functions as Numpy ufuncs
3 participants