Skip to content

Correctly account for height in transformations between intermediate and AltAz frames - #5591

Merged
eteq merged 11 commits into
astropy:masterfrom
eteq:fix-5133
Dec 12, 2016
Merged

eteq merged 11 commits into
astropy:masterfrom
eteq:fix-5133

Conversation

@eteq

@eteq eteq commented Dec 12, 2016

Copy link
Copy Markdown
Member

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.

@eteq eteq added this to the v1.3.0 milestone Dec 12, 2016
@eteq

eteq commented Dec 12, 2016 •

Copy link
Copy Markdown
Member Author

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 astrofrog left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Would UnitSphericalRepresentation be enough? (not sure if it makes a difference)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

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)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

In principle, could also just put [:2] at the end but up to you

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

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:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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).

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

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)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Might as well represent as UnitSphericalRepresentation (saves calculating the distance which is not needed anyway).

@eteq eteq Dec 12, 2016 •

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

@astrofrog noticed this too.. turns out it's the same calculations cost, but less memory intensive. (so will change)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I guess a similar question here: is it possible here to have altaz data that have unit-less distances?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

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):
"""

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Is this an object resembling the moon?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

oops, left over docstring from copy-and-paste - will fix

@mhvk

mhvk commented Dec 12, 2016

Copy link
Copy Markdown
Contributor

@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...

@eteq

eteq commented Dec 12, 2016

Copy link
Copy Markdown
Member Author

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 distance means, whether there should be a way to turn it off, etc. So definitely a separate PR (possibly even with some breaking side-effects), but at least the foundation for making that will be good once this is in...

@eteq

eteq commented Dec 12, 2016

Copy link
Copy Markdown
Member Author

Thanks for the review @astrofrog and @mhvk - assuming the tests pass I think the current version is set to merge.

@eteq
eteq merged commit f7f70d3 into astropy:master Dec 12, 2016
@eteq
eteq deleted the fix-5133 branch December 12, 2016 23:06
eteq added a commit that referenced this pull request Dec 12, 2016
Correctly account for height in transformations between intermediate and AltAz frames
@eteq

eteq commented Dec 12, 2016 •

Copy link
Copy Markdown
Member Author

Tests passed, so merged. backported to v1.3.x in ff653f3

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.

Issue with height being ignored in AltAz and ITRS frames

3 participants