Skip to content

Conversation

@eteq
Copy link
Member

@eteq eteq commented Dec 17, 2014

As has long been promised, this PR implements the full stack necessary to do ICRS <-> AltAz transformations. So it allows stuff like:

>>> m33 = SkyCoord.from_name('M31')
>>> newhaven = EarthLocation(lat=41.3*u.deg, lon=-73*u.deg,height=50*u.m)
>>> time = Time('2008-8-1 2:30:00')
>>> aaframe = AltAz(obstime=time,location=newhaven)
>>> 'alt={0.alt}, az={0.az}'.format(m33.transform_to(aaframe))
'alt=21.9927170464 deg, az=53.6342622223 deg'

As well as the following plot:
out
which I made with:

>>> t2 = Time(time.jd + np.arange(1000)/1000.+.6,format='jd')
>>> dthr = (t2.jd - 2454680.5)*24
>>> aaframe2 = AltAz(obstime=t2,location=newhaven)
>>> sunazs = SkyCoord.get_sun(t2).transform_to(aaframe2)
>>> m33azs = m33.transform_to(aaframe2)
>>> plt.plot(dthr, sunazs.alt,c='y', label='Sun')
>>> plt.scatter(dthr, m31azs.alt,c = m31azs.az, lw=0, s=5, label='M33')
>>> plt.colorbar().set_label('Az [deg]')
>>> plt.legend(loc=0)
>>> plt.xlabel('hrs from 2 Aug 2008 UTC midnight')
>>> plt.ylabel('Alt [deg]')
>>> plt.xlim(dthr[0], dthr[-1])
>>> plt.ylim(0, 90)

While it's in principal a lot of code, a fair amount is testing, and most of the "guts" of all of it is from ERFA, so it's already well tested. I few items of note that reviewers might want to focus on:

  1. I relaxed the requirement that TimeFrameAttributes have to be scalar. The logic here is that for celestial->altaz, you sometimes want the same celestial thing at different times. Doing it this way lets us use the vectorized ERFA machinery, which is much faster than a bunch of individual SkyCoord's. However, it may not work for all of the non-ERFA transforms. Right now I have it giving a warning that says basically "you might encounter problems with some transforms if you do this." We can fix that up in a future release, but it's a huge step forward to allow this for e.g. observation planning (see 2 below)
  2. One thing that comes for "free" is the location of the sun (it's needed for various other steps). So I added `SkyCoord.get_sun`` to do just that. Combined with the point above, this makes the plot I've shown above possible. Note that that would have taken ~20 min to run without the change from 1 above, but now it's only a second or two.
  3. I added a QuantityFrameAttribute andEarthLocationFrameAttribute. The former might be useful for other frames (@adrn's Implement galactocentric frame #2761, perhaps?), and the latter is basically a way to allow earth locations to either use the existing EarthLocation or a coordinate frame (as long as it transforms to ITRS). This is a neat trick because it's sort of "nested coordinates" but shows the power of the flexible transformation scheme.
  4. The ERFA transformations from ICRS to GCRS and from ITRS to AltAz throw away the distance. So to keep distances, this just uses regular cartesian distances. However, that makes it (strictly speaking) incorrect because it ignores relativistic effects. These are of course quite important for the direction of a celestial body (aberration and light deflection are crucial here), but less so for the distance. More importantly, we'd have to have a discussion about what we even mean by distance in a fully GR-consistent way. My feeling is that this is fine as-is, and we can always refine it in future versions if anyone ever cares to this level of precision.
  5. There are not explicit accuracy tests. There are some spot-checks and a bunch of consistency tests (including comparing with raw ERFA), but no "I know what the alt/az and ra/dec are for these objects". Part of the problem is that there are a lot of subtlties in the various steps in the transformations, so you don't expect other people to agree exactly. But this is what astropy/coordinates-benchmark is for, so I'm deferring to that.

cc @astrofrog @adrn @mhvk @taldcroft @Cadair

@Cadair
Copy link
Member

Cadair commented Dec 17, 2014

ping @dpshelio

@astrofrog
Copy link
Member

@cdeil - if you have a chance to work on the coordinates-benchmark, can you also test out this branch against the other codes?

Copy link
Member

Choose a reason for hiding this comment

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

@eteq - Can you extract this generation of test coordinates into a utility function?
Below you are using the same code.

In Gammapy I have one which randomly samples the sphere ... feel free to copy it if you think it's useful:
https://gammapy.readthedocs.org/en/latest/_modules/gammapy/utils/random.html#sample_sphere

Also I don't see the point why grids of tests points on the sphere should be random, even if it's probably generating the same coordinates on almost all machines.
Instead a function that generates a grid of values that includes critical points like pole and lon=0 line where numerical problems can occur would be better.
But the first quick step is to extract this into a function (generate_coordinates_test_grid or something that has a few options, including N_TO_TEST) to have it in a central place.

Copy link
Member Author

Choose a reason for hiding this comment

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

@cdeil - I've implemented abstracting out the coordinate-generation and making it uniform on the sphere instead of in ra/dec - will push up the commits when I finish other work on this PR.

But I don't think it makes sense to use a grid: I did that for one of the earlier coordinate tests and ran into situations where chance coincidences of numbers (basically, a form of aliasing) would occasionally cause tests to succeed where they shouldn't have. Random points, though, don't have those sorts of failure modes.

On repeatability, my interpretation is that NumpyRNGContext(12345) should always give the same answer, regardless of architecture. So it's still 100% repeatable on every platform. (If that's not true, presumably its a bug!)

@cdeil
Copy link
Member

cdeil commented Dec 17, 2014

@eteq This is huge, thanks!
I actually wanted vectorisation for obstime for gammapy ... thanks for putting this in!

@astrofrog - I'll try to find time this afternoon and tomorrow to develop precision tests for this in coordinates-benchmarks, but please don't wait with review / merging this!

My guess would be that it will take weeks and discussions with coordinate experts to set up precision tests for the large grid of transforms and cases and to understand all the comparisons against other packages ... if there's bugs in this implementation they can be fixed in later PRs.

CHANGES.rst Outdated
Copy link
Member

Choose a reason for hiding this comment

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

SkyCoord.get_sun seems a bit weird to me ... will you add SkyCoord.get_mars and SkyCoord.get_moon as well?
If this has been discussed, OK, no strong objections, but if it's still up for discussion I'd prefer to keep SkyCoord simple and put the function to get the solar position somewhere else.

Copy link
Member

Choose a reason for hiding this comment

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

I have a feeling that this will be very useful for us, however, it does seem a little odd to have it attached to SkyCoord why not a separate function?

ping @dpshelio

Copy link
Contributor

Choose a reason for hiding this comment

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

Agreed that this is great to have but that it might be best to have it as a separate function, at least for now. Eventually we'll have access to the JPL ephemerides and therewith also to planets, as well as even more accurate solar positions.

Copy link
Member

Choose a reason for hiding this comment

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

@mhvk What's the plan for JPL ephemerides? These would be unspeakably useful!

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes, and pulsar timing needs it too. I've been hoping for quite a while to port https://github.com/brandon-rhodes/python-jplephem over to astropy (as discussed with @brandon-rhodes quite a while back). My own sense right now is to first get things shaken out with erfa, including barycentric and heliocentric corrections (see #2244), and then add a high-precision option using the JPL ephemerides.

... actually let me open an issue so we don't forget... #3219..

Copy link
Member Author

Choose a reason for hiding this comment

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

My reasoning for putting it in SkyCoord was that the sun is a somewhat special case because it's location is tied in with the definitions of coordinate frames in a way that other ephemerides are not. Also, for optical observing it has special significance, of course. So my thinking was that in v1.1 we'd implement a more general ephemerides approach (shall we move discussion of that to #3219 ?).

But it seems pretty clear that everyone here would rather have it an external function, so I'll just move it to funcs.py for now.

Copy link
Contributor

Choose a reason for hiding this comment

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

Sounds good. It may be that we end up reconsidering (your reasoning is certainly logical), but earlier to add a method later than to remove it!

@cdeil
Copy link
Member

cdeil commented Dec 17, 2014

@eteq - The example calculation and plot you give in this PR is great! Can you copy it into the Sphinx docs?

@mhvk
Copy link
Contributor

mhvk commented Dec 17, 2014

Fantastic! 👍 on the vectorisation for Time. Since Time will soon be multidimensional (#3138), one could even calculate multiple sources for multiple times in one go, and get an instant "what is most observable" idea.

@astrofrog
Copy link
Member

@eteq - very nice work! I've had a chance to try this out while traveling and I've outlined a few issues below.

Bug in GCRS to AltAz

I have tested this with a few cases and ran into a problem due to degeneracies in the transform graph. Basically if one does:

c3 = SkyCoord.get_sun(times).transform_to(AltAz(...))

The original SkyCoord will be in GCRS and the final one will be in AltAz. Now there are two completely equivalent paths:

<function gcrs_to_icrs at 0x1084b79d8>
<function icrs_to_cirs at 0x1079896a8>
<function cirs_to_altaz at 0x1084b7ae8>

and::

<function gcrs_to_itrs at 0x10dadce18>
<function itrs_to_cirs at 0x10dae4048>
<function cirs_to_altaz at 0x10dadcae8>

These are the functions in the composite transform obtained with:

frame_transform_graph.get_transform(GCRS, AltAz)

Now if I run the following example:

import numpy as np
from astropy.coordinates import SkyCoord, EarthLocation, AltAz
from astropy import units as u
from astropy.time import Time

times = Time(np.linspace(2456293.25, 2456657.25, 8575) * u.day,
             format='jd', scale='utc')

loc = EarthLocation(lon=10 * u.deg, lat=80. * u.deg)
aaframe = AltAz(obstime=times, location=loc)

c3 = SkyCoord.get_sun(times).transform_to(aaframe)

I sometimes get a failure, sometimes I don't, and this is due to the degeneracy in the transform graph. The exception only happens when going through ITRS, and is:

Traceback (most recent call last):
  File "bug.py", line 14, in <module>
    c3.transform_to(aaframe)
  File "/Users/tom/Library/Python/3.4/lib/python/site-packages/astropy-1.0.dev10692-py3.4-macosx-10.8-x86_64.egg/astropy/coordinates/sky_coordinate.py", line 331, in transform_to
    new_coord = trans(self.frame, generic_frame)
  File "/Users/tom/Library/Python/3.4/lib/python/site-packages/astropy-1.0.dev10692-py3.4-macosx-10.8-x86_64.egg/astropy/coordinates/transformations.py", line 920, in __call__
    curr_coord = t(curr_coord, curr_toframe)
  File "/Users/tom/Library/Python/3.4/lib/python/site-packages/astropy-1.0.dev10692-py3.4-macosx-10.8-x86_64.egg/astropy/coordinates/transformations.py", line 708, in __call__
    res = self.func(fromcoord, toframe)
  File "/Users/tom/Library/Python/3.4/lib/python/site-packages/astropy-1.0.dev10692-py3.4-macosx-10.8-x86_64.egg/astropy/coordinates/builtin_frames/intermediate_rotation_transforms.py", line 79, in gcrs_to_itrs
    crepr = cartrepr_from_matmul(pmat, gcrs_coo2)
  File "/Users/tom/Library/Python/3.4/lib/python/site-packages/astropy-1.0.dev10692-py3.4-macosx-10.8-x86_64.egg/astropy/coordinates/builtin_frames/intermediate_rotation_transforms.py", line 59, in cartrepr_from_matmul
    newxyz = np.dot(pmat, xyz.reshape(3,-1)).reshape(xyz.shape)
ValueError: total size of new array must be unchanged

This is just an issue with the order of the dimensions in the dot product. It took me a while to figure out what was causing this because the error was stochastic, but once I realized that the transform graph is where the stochasticity comes from it helped. So if you can force the transformation to go this way, it should be easy to debug.

Degeneracies in transform graph

A secondary recommendation is that we adjust the weights in the transform graph so that the fastest route is always picked. In fact, maybe we could actually do some speed benchmarks of each branch in the transform graph and adjust the weights accordingly? This will incidentally also ensure that there are no degeneracies. Of course the speed will depend on the machine, but if we use a 'typical' machine it should be fine for most cases.

This is probably too much effort for now, but I would at least advocate that we adjust weights as needed to avoid degenerate paths.

Generalize get_sun

In terms of API, I wonder whether get_sun is too specific and we should have something where we could also pass the name of the planets in future? Not a big deal, but it'd be cool in future to be able to say something like get_object('jupiter').

Dates in the future

The following code crashes because the dates are in the future:

import numpy as np
from astropy.coordinates import SkyCoord, EarthLocation, AltAz
from astropy import units as u
from astropy.time import Time

times = Time(np.linspace(2456493.25, 2456857.25, 8575) * u.day,
             format='jd')

loc = EarthLocation(lon=10 * u.deg, lat=80. * u.deg)
aaframe = AltAz(obstime=times, location=loc)

c3 = SkyCoord.get_sun(times)
c3.transform_to(aaframe)

The exception is:

Traceback (most recent call last):
  File "bug4.py", line 14, in <module>
    c3.transform_to(aaframe)
  File "/Users/tom/Library/Python/3.4/lib/python/site-packages/astropy-1.0.dev10692-py3.4-macosx-10.8-x86_64.egg/astropy/coordinates/sky_coordinate.py", line 331, in transform_to
    new_coord = trans(self.frame, generic_frame)
  File "/Users/tom/Library/Python/3.4/lib/python/site-packages/astropy-1.0.dev10692-py3.4-macosx-10.8-x86_64.egg/astropy/coordinates/transformations.py", line 920, in __call__
    curr_coord = t(curr_coord, curr_toframe)
  File "/Users/tom/Library/Python/3.4/lib/python/site-packages/astropy-1.0.dev10692-py3.4-macosx-10.8-x86_64.egg/astropy/coordinates/transformations.py", line 708, in __call__
    res = self.func(fromcoord, toframe)
  File "/Users/tom/Library/Python/3.4/lib/python/site-packages/astropy-1.0.dev10692-py3.4-macosx-10.8-x86_64.egg/astropy/coordinates/builtin_frames/cirs_observed_transforms.py", line 35, in cirs_to_altaz
    xp, yp = get_polar_motion(cirs_coo.obstime)
  File "/Users/tom/Library/Python/3.4/lib/python/site-packages/astropy-1.0.dev10692-py3.4-macosx-10.8-x86_64.egg/astropy/coordinates/builtin_frames/utils.py", line 34, in get_polar_motion
    xp, yp = iers.IERS.open().pm_xy(time.jd1, time.jd2)
  File "/Users/tom/Library/Python/3.4/lib/python/site-packages/astropy-1.0.dev10692-py3.4-macosx-10.8-x86_64.egg/astropy/utils/iers/iers.py", line 301, in pm_xy
    raise IndexError('(some) times are outside of range covered '
IndexError: (some) times are outside of range covered by IERS table.

This is problematic if preparing observations! 😄

If there are issues with the accuracy of time, I would have a fallback and emit a warning, rather than crash the whole thing.

Copy link
Member

Choose a reason for hiding this comment

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

altitudes below 5 degrees -> do you mean latitudes, or is the unit wrong?

Copy link
Contributor

Choose a reason for hiding this comment

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

This surely is correct: how high the source is over the horizon (too low, and refraction is more complicated -- no equivalent of the green flash!).

Copy link
Member Author

Choose a reason for hiding this comment

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

altitude is right. But to avoid confusion, I could edit this to say something like "altitudes below 5/zenith angles above 85". This is why ERFA uses zenith angles, apparently... too bad astronomers are slaves to tradition 😉

Copy link
Member

Choose a reason for hiding this comment

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

Ahh, sorry, of course, I was thinking altitude as in height ;)

@mhvk
Copy link
Contributor

mhvk commented Dec 17, 2014

@astrofrog - just quickly on iers -- at that table level, this should remain an exception, though there probably should be a hint in the exception on how to get IERS_A so that one can use predicted polar wander. Also, at the higher level, maybe it is possible to have a full_accuracy=False option which simply assumes the pole does not wander at all.

Copy link
Contributor

Choose a reason for hiding this comment

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

This will never get called, but still probably better to return np.zeros_like(i).

Copy link
Contributor

Choose a reason for hiding this comment

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

Ah, this is neat, automatically taking the unit from the Column. Should do the same for ut1_utc, though obviously not urgent (#3223).. For a bit of speed-up, you may want to add copy=False in the Quantity initialisation.

@mhvk
Copy link
Contributor

mhvk commented Dec 17, 2014

@eteq - just looked over the iers changes, and made in-line comments for trivialities. While you're at it, could you also update the module docstring and the IERS class docstring to make clear we now use both UT1-UTC and polar motion? If there is a way to use IERS_A predictions in SkyCoord, maybe add that to the former docstring too (if not, no worry for now).

@mhvk
Copy link
Contributor

mhvk commented Dec 17, 2014

On iers, at some point we had a lot of discussion about how one could automate the downloading of up-to-date IERS files. For 1.0, it is likely too late, but for someone more able than I am at this type of filehandling stuff: the corresponding issue is #1697.

@eteq
Copy link
Member Author

eteq commented Dec 17, 2014

To start with: I confess that I am shocked at this, but the tests passed on the first try!

@mhvk thanks for the review! re: iers - more discussion on that in eteq#35 . We can hash something out there and I'll try to get it in here.

@astrofrog

  • re: GCRS degeneracies: that's easy enough to change by adjusting the weights of the transforms. The key question is: which is the "best" one to use? The one that goes through ICRS is more "authoritative" because ICRS is the non-rotating frame, so that's probably the best answer? It also has the advantage of not needing polar motions.
    Also, there is a test that takes both of these paths... but I think it only uses 1D coordinates, so that's probably why it didn't trigger. Will try to fix that!
  • re: degeneracies in general: I agree with the spirit of making one always favored, but it's not clear for any of them which is faster. More important might be numerical stability? Definitely should keep an eye on this, though, as you say.
  • dates in the future: see the discussion in WIP: Add AltAz to RaDec conversion tests against other packages eteq/astropy#35 . My current proposal is twofold: use the IERS with predictions, and also allow prediciting to the future with a warning if we're outside the IERS range. But not for time in general, just for coordinates.

adrn added a commit to adrn/astropy that referenced this pull request Dec 17, 2014
@eteq
Copy link
Member Author

eteq commented Dec 19, 2014

I've worked my way through @astrofrog's comments (except that I need to add the example), and pushed up the work so far in case @cdeil or @Cadair want to examine some of it. More coming soon, though.

@adrn
Copy link
Member

adrn commented Dec 19, 2014

I don't have much to add about the code, but I just want to note that this is extremely cool and I think we should definite write a tutorial on "preparing for an observing run" once this is merged. I'm happy to spearhead the tutorial, but drop any ideas about what would go in such a tutorial here:
astropy-learn/astropy-tutorials#73

@eteq
Copy link
Member Author

eteq commented Dec 20, 2014

I think all the comments have been addressed, so this is good to go if the tests pass.

@cdeil - I've xfailed some of the tests you have in there because they're incomplete as you said. We can give that a more thorough shakeout in beta (as at that point it would be bug-fixing)

@astrofrog
Copy link
Member

I had a final look at this and it is good to go. Once this is merged we need to spend some time verifying the accuracy (which is where the coordinates-benchmark works comes in).

astrofrog added a commit that referenced this pull request Dec 20, 2014
Implement ICRS<->AltAz transformation stack
@astrofrog astrofrog merged commit 666c5b5 into astropy:master Dec 20, 2014
@astrofrog
Copy link
Member

@cdeil - just for info, this is merged, so it should make the coordinates-benchmark work easier to do.

@astrofrog
Copy link
Member

Opened an issue to remind us that accuracy tests are critical before 1.0 is released: #3244

@eteq eteq deleted the coords-icrs2altaz branch December 20, 2014 17:11
@cdeil
Copy link
Member

cdeil commented Jan 21, 2015

It looks like there's no example using AltAz yet in the docs?
http://astropy.readthedocs.org/en/latest/search.html?q=altaz&check_keywords=yes&area=default

@eteq Do you have time to add one?
For what it's worth, I think the two examples you gave at the top of this PR are great and all that's needed for people to get started.

@eteq
Copy link
Member Author

eteq commented Jan 23, 2015

@cdeil - an expanded version of the examples is in http://astropy.readthedocs.org/en/latest/coordinates/observing-example.html

But its weird you don't see that with the search... I just pushed a commit up that makes this document slightly easier to find (it's now mentioned in the "getting started" section). Maybe that will make the search work...? we'll see when the docs finish building.

@cdeil
Copy link
Member

cdeil commented Jan 24, 2015

@eteq - Thanks, this is a very nice example!

The reason it doesn't show up in the Sphinx search is because apparently only normal text is included, not code or links. E.g. for FK5 it's the same ... only the reference to the class is found, not to any of the examples and docs showing how to use it:
http://astropy.readthedocs.org/en/latest/search.html?q=fk5

So It's still pretty hard to find and unless there's a way to configure the Sphinx search to include references, only mentioning keywords like "horizontal", "altitude", "attitude" in the main text would help.

One minor issue I noticed is that if you run the example you get this warning that goes on for pages ... can you maybe not print the Time object fully here?

WARNING: Time input obstime=<Time object: scale='utc' format='iso' value=['2012-07-13 02:00:00.000' '2012-07-13 02:05:27.273'
 '2012-07-13 02:10:54.545' '2012-07-13 02:16:21.818'
 '2012-07-13 02:21:49.091' '2012-07-13 02:27:16.364'
 '2012-07-13 02:32:43.636' '2012-07-13 02:38:10.909'
 '2012-07-13 02:43:38.182' '2012-07-13 02:49:05.455'
 '2012-07-13 02:54:32.727' '2012-07-13 03:00:00.000'
 '2012-07-13 03:05:27.273' '2012-07-13 03:10:54.545'
 '2012-07-13 03:16:21.818' '2012-07-13 03:21:49.091'
...

http://nbviewer.ipython.org/gist/cdeil/5a1630f48c76420f93ad

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants