Repository navigation
Correctly account for height in transformations between intermediate and AltAz frames - #5591
Conversation
|
I'm pretty sure this is ready to go, but would be good to get another pair of eyes... @adrn @StuartLittlefair @mhvk, or @astrofrog? |
astrofrog
left a comment
There was a problem hiding this comment.
This looks reasonable but I'm not the most familiar with this code
| # compute an "astrometric" ra/dec -i.e., the direction of the | ||
| # displacement vector from the observer to the target in CIRS | ||
| loccirs = altaz_frame.location.get_itrs(cirs_coo.obstime).transform_to(cirs_coo) | ||
| diffrepr = (cirs_coo.cartesian-loccirs.cartesian).represent_as(SphericalRepresentation) |
There was a problem hiding this comment.
Would UnitSphericalRepresentation be enough? (not sure if it makes a difference)
There was a problem hiding this comment.
Performance wise I think it's identical, but you're right conceptually it makes more sense (and uses slightly less memory). Will fix.
| altaz_frame.obswl.value) | ||
|
|
||
| az, zen, ha, obs_dec, obs_ra = erfa.atioq(cirs_ra, cirs_dec, astrom) | ||
| az, zen, _, _, _ = erfa.atioq(cirs_ra, cirs_dec, astrom) |
There was a problem hiding this comment.
In principle, could also just put [:2] at the end but up to you
There was a problem hiding this comment.
Obviously this is totally in the noise for essentially any real scenario, but...
>>> timeit a, b = f()[:2]
10000000 loops, best of 3: 177 ns per loop
>>> timeit a, b = f()[:2]
10000000 loops, best of 3: 177 ns per loop
>>> timeit a, b, _, _, _ = f()
10000000 loops, best of 3: 99.8 ns per loop
>>> timeit a, b, _, _, _ = f()
10000000 loops, best of 3: 105 ns per loop
So apparently the current case is actually faster (first time I checked this...)
| usrepr = cirs_coo.represent_as(UnitSphericalRepresentation) | ||
| cirs_ra = usrepr.lon.to(u.radian).value | ||
| cirs_dec = usrepr.lat.to(u.radian).value | ||
| if cirs_coo.distance.unit is u.one: |
There was a problem hiding this comment.
Is distance guaranteed to exist? I ask because below the way to test this seems to be something like dat.get_name() == 'unitspherical' or dat.to_cartesian().x.unit == u.one (I never quite understood these hoops; we probably should define a property that gives this information... but that's besides this PR).
There was a problem hiding this comment.
Good point. In nearly all practical cases it should exist because the default representation is Spherical, but the following produces an error:
c = CIRS(ra=..., dec=...)
c.representation = UnitSphericalRepresentation
c.transform_to(AltAz(...))
because forcing the representation to unit removes the distance. Will at least make sure it's internally consistent here, but you're right that we should make that all more consistent (either a method or a consistent one-liner idiom that's recommended in the docs)
| # compute an "astrometric" ra/dec -i.e., the direction of the | ||
| # displacement vector from the observer to the target in CIRS | ||
| loccirs = altaz_frame.location.get_itrs(cirs_coo.obstime).transform_to(cirs_coo) | ||
| diffrepr = (cirs_coo.cartesian-loccirs.cartesian).represent_as(SphericalRepresentation) |
There was a problem hiding this comment.
Might as well represent as UnitSphericalRepresentation (saves calculating the distance which is not needed anyway).
There was a problem hiding this comment.
@astrofrog noticed this too.. turns out it's the same calculations cost, but less memory intensive. (so will change)
There was a problem hiding this comment.
OK, makes sense I guess: squaring a few numbers and taking the square root is less work than arctan's.
| @@ -97,30 +106,22 @@ def altaz_to_cirs(altaz_coo, cirs_frame): | |||
| cirs_ra, cirs_dec = erfa.atoiq('A', az, zen, astrom)*u.radian | |||
|
|
|||
| if isinstance(altaz_coo.data, UnitSphericalRepresentation): | |||
There was a problem hiding this comment.
I guess a similar question here: is it possible here to have altaz data that have unit-less distances?
There was a problem hiding this comment.
same as above: unlikely but possible, so I will switch them all to the same idiom
|
|
||
| @pytest.mark.parametrize('testframe', totest_frames) | ||
| def test_cirs_altaz_nodist(testframe): | ||
| """ |
There was a problem hiding this comment.
Is this an object resembling the moon?
There was a problem hiding this comment.
oops, left over docstring from copy-and-paste - will fix
|
@eteq - this looks good modulo my queries about how to identify a frame as having no distance. p.s. I'm also wondering about light-travel-time effects for sources that move, but that is outside of this PR anyway... |
@mhvk - good point - light travel time is not accounted for here, but that requires deciding more clearly what |
|
Thanks for the review @astrofrog and @mhvk - assuming the tests pass I think the current version is set to merge. |
Correctly account for height in transformations between intermediate and AltAz frames
|
Tests passed, so merged. backported to v1.3.x in ff653f3 |
This addresses the problem pointed out in #5133, where the exact observer location (particularly the height above the other's surface) seemed to be getting ignored when converting to
AltAz.The root of the problem is that the ERFA transformations from CIRS to AltAz behave somewhat unexpectedly and do not properly account for these distance effects (in essence they assume the target is at infinite distance). Because everything goes through CIRS to get to AltAz, this showed up even in #5133 (where the starting point is ITRS). This PR fixes that by computing the "astrometric" ra/dec - i.e., the ra/dec of the angle from the observer to the target - and passes that into the ERFA transformations. (Then it reverses the transformation on the way back out into CIRS when going the other way.)
This PR also adds a set of regression tests to ensure this doesn't happen, inspired by #5133 (including @mhvk's #5133 (comment)).
This closes #5133. Tentatively milestoning it as 1.3.0, as it is a bugfix.