Repository navigation
Conversation
The erfa.astrom struct needs a substantial amount of computing power to update Earth parameters. However, all but the Earth rotation angle are relatively slowly changing numbers. If some accuracy can be sacrificed, calculating the slowly changing astrom parameters only on certain time-steps, can lead to large speed-ups. For example, a time resolution of 10 minutes will still have an accuracy of <~50 mas if ICRS is transformed to AltAz.
|
Hi @bwinkel, First of all, thanks for submitting a PR. It's great to have people contributing, especially when they provide such detailed notes and justifications! That said, I'm afraid I'm not a fan of this PR for a couple of reasons. The first is a philosophical position, that I think it's important that a hypothetical user of astropy can use the co-ordinate package without worrying about accuracy. Obviously there's a trade-off to be made between accuracy and speed but I think we need to think carefully before intentionally degrading the accuracy of an existing code. For example, someone currently using astropy to predict lunar occultations might get bitten by this. The second is that the use of Since your own use case seems rather extreme to drive the default behaviour in astropy, perhaps the best way forward is to temporarily replace the transformations in astropy's frame transform graph in your own code - i.e if you want to use any particular function to transform CIRS->ICRS, just decorate that function with |
|
Thanks a lot for the feedback, Stuart. I suspected that Note, that caching the I must also say that the use case I described is all but extreme. Almost all radio telescope, I know of, nowadays sample data with a resolution of ~1 s; for some types of observations, even faster (1 ms to 100 ms). Typical observing sessions are a couple of hours. At the moment, they just don't use astropy for these transformations, but I could imagine that some would, if possible. |
|
Thanks for the response. I still don't quite follow why sampling data at fast rates means that you have to be able to convert positions at 10**5 times in less than one second? Nevertheless, it is definitely true that for many people the astropy coordinate package is not as fast as it needs to be; I didn't mean to sound so negative above. By describing your use case as extreme I simply mean that some users will demand high speed, and some will demand high accuracy and we need to balance the two (or find a way to satisfy both). I do think this PR could be a really valuable addition. However, given my concerns above, I think this PR should probably end up satisfying the following requirements:
with approximate_transforms:
fk5c.transform_to(GCRS(obstime=Time("J2000")))
(2) and (3) could be achieved by writing a utility function to calculate the |
|
I like the idea of introducing a context manager for low-precision transforms. Will wait a little bit for other comments and then try to find out, how one could implement it. Thanks a lot! |
|
take a look at how it's done here. One thing that would need some thought is what would happen if a user both specified that the JPL ephemerides be used, and approximate transformations performed. Apart from giving that particular user a talking to, of course. |
| x, y, s = get_cip(jd1, jd2) | ||
| earth_pv, earth_heliocentric = prepare_earth_position_vel(cirs_frame.obstime) | ||
| astrom = erfa.apci(jd1, jd2, earth_pv, earth_heliocentric, x, y, s) | ||
| # jd1, jd2 = get_jd12(cirs_frame.obstime, 'tdb') |
There was a problem hiding this comment.
eventually, you'll want to delete the commented out lines of course.
| ) | ||
|
|
||
| astrom_ui, _ = erfa.apci13(JD0, mjd_ui * MJD_RESOLUTION) | ||
| astrom = astrom_ui[mjd_uidx] |
There was a problem hiding this comment.
very nice. I'll have to remember this trick!
| # calculate Earth parameters for lower time resolution (they change slowly) | ||
| # only Earth rotation angle needs to be calculated for each time | ||
|
|
||
| jd1, jd2 = get_jd12(obstime, 'tdb') |
There was a problem hiding this comment.
The erfa docs suggest you can use tt here in place of tdb without loss of precision. Caching will mean it only makes a difference on first call, but it is significant (20ms vs 1s for 10**5 times)
|
@bwinkel actually, I rather like the helper function to provide an astrometry context. It certainly cleans up the code a lot where it is actually used. I think that it might be a good idea to present the user with discrete choices for the precision (i.e a set of pre-chosen time resolutions) and document their accuracy. Otherwise we're giving end users work to do, working out if their chosen time resolution is precise enough for their needs. with transform_precision.set('low'):
azels = cs.transform_to(tframes)I think at this point the general idea is firm enough for people like @eteq and @mhvk to look over and see if they approve in principle before putting in too much work with documentation/tests etc. |
|
One thing that I don't like about the helper function is, that it is vital to plug the appropriate frame into it (in each transform function, two frames are involved, but only one of them must be used for the helper). Since the function is not exposed to the user, this may be acceptable, but it's not really nice from a design perspective. I'm also thinking about how one could do some caching. If, for example, a conversion from AltAz to ICRS is performed, the MJD binning happens twice with the same obstimes. It would be cool, if the binned |
|
Good to have a detailed look at coordinates performance -- this is definitely a bottleneck, and the API is now sufficiently stable that we can and should improve it. That said, I think it is still at a state where we should optimize individual pieces, rather than try to work around slow code by doing interpolation. So, I'm much more keen on ideas like caching than on sampling at different time samples or making lower-precision approximations. @bwinkel - from what you write, it seems you did quite a bit of profiling already, and I was surprised that getting p.s. There is some related work in the pipeline, which could use input: @dmopalmer has been looking at the initialization of |
|
I agree with your reasoning, @mhvk, but in this particular case, the main bottleneck is indeed the calculation of the the Note, while getting I'm quite sure, that for >90% of the use cases, an precision of 1-10 mas would be more than sufficient. How many telescopes reach that level of pointing accuracy...? |
|
Hmm, you can even do this with 2D arrays (only if time is on axis 0). So here is another example, which also shows how the deviations are distributed. num_declinations = 35
num_times = 10 ** 4
with NumpyRNGContext(1):
_ra = np.repeat(0., num_declinations)[:, np.newaxis]
_dec = np.linspace(-85, 85, num_declinations)[:, np.newaxis]
_time_delta = np.linspace(0, 1, num_times)
ra = Longitude(_ra, unit=u.deg)
dec = Latitude(_dec, unit=u.deg)
cs = SkyCoord(ra, dec, frame='icrs')
times = Time(54000. + _time_delta, format='mjd')
loc = EarthLocation(lat=41.3 * u.deg, lon=-74 * u.deg, height=390 * u.m)
tframes = AltAz(obstime=times, location=loc)
with transform_precision.set(0):
azels0 = cs.transform_to(tframes) # 3.48 s
with transform_precision.set(200):
azels200 = cs.transform_to(tframes) # 348 ms
with transform_precision.set(600):
azels600 = cs.transform_to(tframes) # 253 ms
sep600 = np.degrees(coordinates.angle_utilities.angular_separation(
azels0.az.rad, azels0.alt.rad,
azels600.az.rad, azels600.alt.rad,
)) * 3600. * 1000
az = azels.az.to(u.deg).value
alt = azels.alt.to(u.deg).value
clf()
scatter(
az, alt, c=sep600,
edgecolor='none', cmap='viridis',
)
colorbar()
xlim((0, 360))
ylim((-90, 90))
xlabel('Az [d]')
ylabel('Alt [d]') |
|
On the actual use case you describe: I'd guess that the way to implement that would be to sample coarsely in the ICRS->CIRS and then more finely in CIRS->AltAz (oddly, if I try CIRS->AltAz, I get transforms of CIRS->ICRS and back, which seems wrong -- and doesn't help to speed things up). A quick profile of ICRS->CIRS suggests, as you wrote, the erfa routines Separately, it would be good to check that our vectorising of |
|
Oops, sorry, that last comment was put in before I saw your ones. So, the goal is really to avoid many calls to the erfa routines. As noted, we can easily get rid of one (saving 30%). Right now, I'm trying to understand why CIRS->AltAz goes via ICRS; without that, one could do the fine sampling there (CIRS hardly changes with time), and things might be fine at full precision. |
|
So, one way to optimize is indeed to undersample the transformation to CIRS and then interpolate. This gives a speedup by basically the undersampling factor while keeping very good precision: Timing both: |
|
On the above example: I wrote it without thinking too much about how best to handle things, in a way it was just to convince myself that even with the present machinery, given some thought, the problem can be solved. This is important mostly in the sense that if a solution is not much more difficult than a context manager, then I think we might as well document the solution rather than add more code. Even so, I think there is a question of whether we can do better for our users without something for them to set up, i.e., whether it is possible to get full precision much faster. E.g., one slow part is |
|
Interpolation is certainly a viable option, and I'm all for it, if it can be made work. But make no mistake, it would need very careful heuristics. It will be necessary, to handle cases where the coordinate pairs are not homogeneously sampled in time, or have relatively large gaps. So a clever analysis of the sampling would be needed, adding quite a bit of complexity to the interpolation function. For me either solution is fine, because I think that both methods potentially provide sufficient speed-up. It'd be good to hear some other opinions on this, before we proceed. I know, that @eteq and @adrn are working on implementing velocities. One should keep this in mind, as well. By-the-way, interpolation of spherical angles is difficult. You'd need to handle the case where the longitude crosses zero correctly and the quality of the solution will depend on latitude, I think. A better way might be to convert to Cartesian representation and do the interpolation on the (x, y, z) vectors. |
|
@bwinkel - sorry for being somewhat slow in getting up to speed in understanding where even the problem is. You're definitely right that interpolation should be done in the cartesian frame; and I guess the fact that I didn't immediately do that makes clear how useful it is for things to "just work", rather than letting people reinvent wheels. Nevertheless, I think the example is useful in helping to think whether it is possible to find solutions that benefit all of astropy rather than a specific use case. E.g., a general interpolation routine would, I think, fall in that category! It would also be useful for, e.g., moving objects. The application of such a routine to make telescope tracking better might perhaps be better in a Overall, I must admit I'm still rather weary of introducing complexity in the astropy core for this specific case, and would suggest to try to break the problem into pieces that are each useful (say, using just one call to p.s. A propos velocities: once we have this, you would get not just |
|
Hmm, using interpolation to speed up transformations would be relatively easy to put into another package, but how to deal with the heuristics in a proper and very general way is still unclear to me. The MJD-based binning of the |
just for curiosity, we removed the pure MJD-binning in get_astrom helper and implemented a linear interpolation of the Earth-PV, Heliocentric, and CIS (Cartesian) vectors based on a binned MJD support. Comparing to the MJD binning, the accuracy of the coordinate transforms is boosted by a factor of a thousand, or so, while the computing times are almost as fast as before.
|
Out of pure curiosity, I slightly modified the I re-created the above plots with the new method (errors are now in microarcsec!): The advantage of the new approach is also, that it should work with a differential approach to implement velocities, which would not have been the case for the pure MJD binning, submitted initially. I know that the PR is not a minor one. On the other hand, the default behavior is unchanged and there is an enormous speed gain for some use cases. So, besides the PR-size issue - is there a strong argument against such a change? |
|
I have to say I'm not at all opposed. I have my own use cases (telescope scheduling) where this kind of speedup is essential. The loss of precision is basically insignificant for the vast majority of users and the default behaviour is unchanged. I can't see a strong argument against, but I'm not the finally arbiter, so you'll have to convince people like @mhvk and @eteq. If you decide to take this further, as well as adding tests and documentation, I would try and make sure you keep an eye on issues with dimensionality - in general the obstime property of a frame (like most other frame attributes) can be an N-D object, so your interpolation code will have to deal with those cases. Another thing to check is that this doesn't have unintended consequences - e.g with an older state of this PR I noticed that |
|
I took a look at trying to whip this into shape for 3.1, but sadly I don't think it can be done. The performance improvements in 3.1 have ended up more in the "fix obvious flaws" category rather than "improve some of the use cases" as this PR is trying to do. That said, I agree with @mhvk that this is a promising direction to go (and @bwinkel has already put in a fair amount of work here!), so re-milestoned it to 3.2. Obviously, it's been over a year without much progress on this... But perhaps now that we've gotten some of the low-hanging fruit done for 3.1 it's a good idea to pursue this idea again? @bwinkel @maxnoe @StuartLittlefair @mhvk @adrn - Perhaps we should try to have a virtual coordinates meetup for this and related "more radical performance approaches", either as part of the coordination meeting this december, or just as an independent telecon? We could try to chart out a real path to implementing this idea and perhaps similar use cases as well. Hopefully then this good idea would no longer sit languishing for lack of sufficient review/clear direction. |
|
Thanks, @eteq! I'm certainly available for a meeting. Just drop me a note, when you know the date. |
|
Hi, I have no experience with the coordinates code base. I'm just in gamma-ray astronomy, where we have lots of coordinate trafos and 50 mas is absolute overkill by at least 3 orders of magnitude. So having some kind of way to get faster trafos at the expanse of precision would be great. I think I'd like a context manager as API |
That being said, I'd like to contribute anyway if I can |
|
Good idea @eteq. I won't be able to attend the coordination meeting, but a virtual meet up would work well for me |
One could have a very different point of view: Microarcsecond precision is only required for very few purposes. Every other purpose get's slowed down up to the point of non usability. At the moment, astropy's coordinate system is practically unusable for data analysis e.g. for @cta-observatory because of this. |
|
Also: the tests are passing, so this PR does not change any required features. If what you say is required, there should be a test, right? |
this is not so simple. The tests here don't pass as they were last run quite a while ago, and now there is conflict. And one also has to be a bit careful with any API changes as once it goes into a release prematurely we stick with it for a while even though better ways come up in a discussion. |
|
I agree with @bsipocz here, and this is also why I didn't pursue this further for a long time. If such a feature is wanted by the community (I honestly hope so), then one should first think about how it should be implemented in terms of design and API. My original solution went very deep into the core of the coordinates package. Perhaps, a better way of doing things can be identified in a meeting. |
|
How can we contribute here? This is a major issue for the Cherenkov Astronomy Community that regularly poses the question to move away from astropy.coordinates to something self-made for speed reasons. I don't want this to happen, but for that we need speedier alt/az ↔ ICRS transformation and we don't require much precision (gamma-ray angular resolution is around 3 arcminutes, so conversions with a precision up to arcseconds would be totally acceptable. |
|
I'm not sure what's the best to do with the milestone at this point. I'm bumping it to 4.2 now, but ideally the convo should be moved to an issue (e.g #8407) and the PR closed. |
|
Thanks @bsipocz. I still think that the proposed functionality would be useful (there have been several instances since I opened the PR, where I had to use other astrometry functions, because astropy was too slow). Should the sub-package maintainers decide to give it another try, I'm willing to help of course. |
|
|
||
| assert tcode in ['apio13', 'apci', 'apcs', 'apci13', 'apco13'] | ||
|
|
||
| if precision is None: |
There was a problem hiding this comment.
Is there a reason here not to use units?
There was a problem hiding this comment.
You're right, units should be used.
maxnoe
left a comment
There was a problem hiding this comment.
I want to look into this again as we have a dire need to speed up this use case.
What could be the steps forward here?
| astrom = erfa.apcs(jd1_tt, jd2_tt, pv, earth_pv, earth_heliocentric) | ||
|
|
||
| elif tcode == 'apci13': | ||
| pass |
There was a problem hiding this comment.
Shouldn't this raise errors instead of silently returning None
Note that this PR is years old and I'm not even sure, if the underlying code, on which this was built, is still in the coordinates sub-package. Not sure, how to proceed, as the original PR was controversial. |
|
@bwinkel I know, this is why I was asking. Have been following this from the start. I'll have a look into if it can be rebased onto the current master. |
|
Thanks a lot @maxnoe! Let me know, if you need help. |





The erfa.astrom struct needs a substantial amount of computing power to update Earth parameters. However, all but the Earth rotation angle are relatively slowly changing numbers. If some accuracy can be sacrificed, calculating the slowly changing astrom parameters on certain time-steps only, can lead to large speed-ups. For example, a time resolution of 10 minutes will still have an accuracy of <~50 mas if ICRS is transformed to AltAz.
The attached commit is merely a proof of concept. Since this is my first PR, I'd appreciate some guidance on how to proceed. Below, I add some additional information, ideas and questions that should be discussed before I'll work on a final solution.
The Problem
At our observatory, we often observe in AltAz frame for a session and then need to convert the coordinates to ICRS. A minimal example:
The last function can take very long to run, and in fact this is a show-stopper for me to use astropy coordinates for such astrometric calculations.
Accuracy
I made plots of the deviations. It seems, that with the proposed method (
_apci13_fastwith 10 min granularity) most points have less than 5 mas deviation from the original solution. No difference is apparent betweenerfa.apci13and the current implementation. The maximal deviation between_apci13_fastand current is of the order of 50 mas.Zooming-in on the azimuthal deviations shows a pattern, which is introduced by the 10-min grid (0.007 d):

Timing
There is a large performance bottleneck in the
cirs_to_icrsandicrs_to_cirstransform functions. Both calculate the astrom struct usingerfa.apci. The latter needs several parameters. One can already improve the speed somewhat, by usingerfa.apci13, which only needs time as parameter. A much larger speed-up is obtained, if the time granularity is changed to 10 minutes or so (see above).erfa.apci13_apci13_fastI'm not quite sure, why the (inital?) loop seems to be about 10x slower, when using
_apci13_fast. Profiling suggests, that a lot of time is spent inastropy._erfa._core._dtdb, which is related to the lineAccording to the erfa docs, 'utc' should be as good as 'tdb' here, and doesn't seem to have the described issue. [update]I just realized, that for much finer resolution (e.g., 0.1 s), the difference between utc and tdb in fact becomes important. So for the moment, I switched back to the use of tdb.[/update]
From proof-of-concept to a solution
While the quick-and-dirty solution already seems to provide a nice speed-up, there are other things to consider. The astrom struct is of course not only calculated in the
cirs_to_icrsandicrs_to_cirstransform functions (although here, the performance hit is most critical). It would be much nicer, if it could be cached in a low-level routine. Unfortunately, since the erfa core is auto-generated (as a C-extension), this seems very difficult to achieve (at least for me). This would have been easier with the older Cython-based erfa wrapper. Likewise, a higher-level cache (in SkyCoord?) would be feasible, which stores already computed astrom values, but I guess, that changes would need to be made at many different places. Not sure, which option would be best.Furthermore, the granularity (
MJD_RESOLUTION) should be modifiable by the user, especially if higher accuracy is needed than provided by the value of 10 min.