# Licensed under a 3-clause BSD style license - see LICENSE.rst """ Accuracy tests for Ecliptic coordinate systems. """ import numpy as np import pytest from astropy.units import allclose as quantity_allclose from astropy import units as u from astropy.coordinates import SkyCoord from astropy.coordinates.builtin_frames import (FK5, ICRS, GCRS, GeocentricMeanEcliptic, GeocentricTrueEcliptic, BarycentricMeanEcliptic, BarycentricTrueEcliptic, CustomBarycentricEcliptic, HeliocentricMeanEcliptic, HeliocentricTrueEcliptic, HeliocentricEclipticIAU76) from astropy.coordinates.solar_system import get_body_barycentric_posvel from astropy.constants import R_sun, R_earth from astropy.time import Time def test_against_pytpm_doc_example(): """ Check that Astropy's Ecliptic systems give answers consistent with pyTPM Currently this is only testing against the example given in the pytpm docs """ fk5_in = SkyCoord('12h22m54.899s', '15d49m20.57s', frame=FK5(equinox='J2000')) pytpm_out = BarycentricMeanEcliptic(lon=178.78256462*u.deg, lat=16.7597002513*u.deg, equinox='J2000') astropy_out = fk5_in.transform_to(pytpm_out) assert pytpm_out.separation(astropy_out) < (1*u.arcsec) def test_ecliptic_heliobary(): """ Check that the ecliptic transformations for heliocentric and barycentric at least more or less make sense """ icrs = ICRS(1*u.deg, 2*u.deg, distance=1.5*R_sun) bary = icrs.transform_to(BarycentricMeanEcliptic()) helio = icrs.transform_to(HeliocentricMeanEcliptic()) # make sure there's a sizable distance shift - in 3d hundreds of km, but # this is 1D so we allow it to be somewhat smaller assert np.abs(bary.distance - helio.distance) > 1*u.km # now make something that's got the location of helio but in bary's frame. # this is a convenience to allow `separation` to work as expected helio_in_bary_frame = bary.realize_frame(helio.cartesian) assert bary.separation(helio_in_bary_frame) > 1*u.arcmin @pytest.mark.parametrize(('trueframe', 'meanframe'), [(BarycentricTrueEcliptic, BarycentricMeanEcliptic), (HeliocentricTrueEcliptic, HeliocentricMeanEcliptic), (GeocentricTrueEcliptic, GeocentricMeanEcliptic), (HeliocentricEclipticIAU76, HeliocentricMeanEcliptic)]) def test_ecliptic_roundtrips(trueframe, meanframe): """ Check that the various ecliptic transformations at least roundtrip """ icrs = ICRS(1*u.deg, 2*u.deg, distance=1.5*R_sun) truecoo = icrs.transform_to(trueframe()) meancoo = truecoo.transform_to(meanframe()) truecoo2 = meancoo.transform_to(trueframe()) assert not quantity_allclose(truecoo.cartesian.xyz, meancoo.cartesian.xyz) assert quantity_allclose(truecoo.cartesian.xyz, truecoo2.cartesian.xyz) @pytest.mark.parametrize(('trueframe', 'meanframe'), [(BarycentricTrueEcliptic, BarycentricMeanEcliptic), (HeliocentricTrueEcliptic, HeliocentricMeanEcliptic), (GeocentricTrueEcliptic, GeocentricMeanEcliptic)]) def test_ecliptic_true_mean_preserve_latitude(trueframe, meanframe): """ Check that the ecliptic true/mean transformations preserve latitude """ truecoo = trueframe(90*u.deg, 0*u.deg, distance=1*u.AU) meancoo = truecoo.transform_to(meanframe()) assert not quantity_allclose(truecoo.lon, meancoo.lon) assert quantity_allclose(truecoo.lat, meancoo.lat, atol=1e-10*u.arcsec) @pytest.mark.parametrize('frame', [HeliocentricMeanEcliptic, HeliocentricTrueEcliptic, HeliocentricEclipticIAU76]) def test_helioecliptic_induced_velocity(frame): # Create a coordinate with zero speed in ICRS time = Time('2021-01-01') icrs = ICRS(ra=1*u.deg, dec=2*u.deg, distance=3*u.AU, pm_ra_cosdec=0*u.deg/u.s, pm_dec=0*u.deg/u.s, radial_velocity=0*u.m/u.s) # Transforming to a helioecliptic frame should give an induced speed equal to the Sun's speed transformed = icrs.transform_to(frame(obstime=time)) _, vel = get_body_barycentric_posvel('sun', time) assert quantity_allclose(transformed.velocity.norm(), vel.norm()) # Transforming back to ICRS should get back to zero speed back = transformed.transform_to(ICRS()) assert quantity_allclose(back.velocity.norm(), 0*u.m/u.s, atol=1e-10*u.m/u.s) def test_ecl_geo(): """ Check that the geocentric version at least gets well away from GCRS. For a true "accuracy" test we need a comparison dataset that is similar to the geocentric/GCRS comparison we want to do here. Contributions welcome! """ gcrs = GCRS(10*u.deg, 20*u.deg, distance=1.5*R_earth) gecl = gcrs.transform_to(GeocentricMeanEcliptic()) assert quantity_allclose(gecl.distance, gcrs.distance) def test_arraytransforms(): """ Test that transforms to/from ecliptic coordinates work on array coordinates (not testing for accuracy.) """ ra = np.ones((4, ), dtype=float) * u.deg dec = 2*np.ones((4, ), dtype=float) * u.deg distance = np.ones((4, ), dtype=float) * u.au test_icrs = ICRS(ra=ra, dec=dec, distance=distance) test_gcrs = GCRS(test_icrs.data) bary_arr = test_icrs.transform_to(BarycentricMeanEcliptic()) assert bary_arr.shape == ra.shape helio_arr = test_icrs.transform_to(HeliocentricMeanEcliptic()) assert helio_arr.shape == ra.shape geo_arr = test_gcrs.transform_to(GeocentricMeanEcliptic()) assert geo_arr.shape == ra.shape # now check that we also can go back the other way without shape problems bary_icrs = bary_arr.transform_to(ICRS()) assert bary_icrs.shape == test_icrs.shape helio_icrs = helio_arr.transform_to(ICRS()) assert helio_icrs.shape == test_icrs.shape geo_gcrs = geo_arr.transform_to(GCRS()) assert geo_gcrs.shape == test_gcrs.shape def test_roundtrip_scalar(): icrs = ICRS(ra=1*u.deg, dec=2*u.deg, distance=3*u.au) gcrs = GCRS(icrs.cartesian) bary = icrs.transform_to(BarycentricMeanEcliptic()) helio = icrs.transform_to(HeliocentricMeanEcliptic()) geo = gcrs.transform_to(GeocentricMeanEcliptic()) bary_icrs = bary.transform_to(ICRS()) helio_icrs = helio.transform_to(ICRS()) geo_gcrs = geo.transform_to(GCRS()) assert quantity_allclose(bary_icrs.cartesian.xyz, icrs.cartesian.xyz) assert quantity_allclose(helio_icrs.cartesian.xyz, icrs.cartesian.xyz) assert quantity_allclose(geo_gcrs.cartesian.xyz, gcrs.cartesian.xyz) @pytest.mark.parametrize('frame', [HeliocentricMeanEcliptic, HeliocentricTrueEcliptic, GeocentricMeanEcliptic, GeocentricTrueEcliptic, HeliocentricEclipticIAU76]) def test_loopback_obstime(frame): # Test that the loopback properly handles a change in obstime from_coo = frame(1*u.deg, 2*u.deg, 3*u.AU, obstime='2001-01-01') to_frame = frame(obstime='2001-06-30') explicit_coo = from_coo.transform_to(ICRS()).transform_to(to_frame) implicit_coo = from_coo.transform_to(to_frame) # Confirm that the explicit transformation changes the coordinate assert not quantity_allclose(explicit_coo.lon, from_coo.lon, rtol=1e-10) assert not quantity_allclose(explicit_coo.lat, from_coo.lat, rtol=1e-10) assert not quantity_allclose(explicit_coo.distance, from_coo.distance, rtol=1e-10) # Confirm that the loopback matches the explicit transformation assert quantity_allclose(explicit_coo.lon, implicit_coo.lon, rtol=1e-10) assert quantity_allclose(explicit_coo.lat, implicit_coo.lat, rtol=1e-10) assert quantity_allclose(explicit_coo.distance, implicit_coo.distance, rtol=1e-10) @pytest.mark.parametrize('frame', [BarycentricMeanEcliptic, BarycentricTrueEcliptic, HeliocentricMeanEcliptic, HeliocentricTrueEcliptic, GeocentricMeanEcliptic, GeocentricTrueEcliptic]) def test_loopback_equinox(frame): # Test that the loopback properly handles a change in equinox from_coo = frame(1*u.deg, 2*u.deg, 3*u.AU, equinox='2001-01-01') to_frame = frame(equinox='2001-06-30') explicit_coo = from_coo.transform_to(ICRS()).transform_to(to_frame) implicit_coo = from_coo.transform_to(to_frame) # Confirm that the explicit transformation changes the lon/lat but not the distance assert not quantity_allclose(explicit_coo.lon, from_coo.lon, rtol=1e-10) assert not quantity_allclose(explicit_coo.lat, from_coo.lat, rtol=1e-10) assert quantity_allclose(explicit_coo.distance, from_coo.distance, rtol=1e-10) # Confirm that the loopback matches the explicit transformation assert quantity_allclose(explicit_coo.lon, implicit_coo.lon, rtol=1e-10) assert quantity_allclose(explicit_coo.lat, implicit_coo.lat, rtol=1e-10) assert quantity_allclose(explicit_coo.distance, implicit_coo.distance, rtol=1e-10) def test_loopback_obliquity(): # Test that the loopback properly handles a change in obliquity from_coo = CustomBarycentricEcliptic(1*u.deg, 2*u.deg, 3*u.AU, obliquity=84000*u.arcsec) to_frame = CustomBarycentricEcliptic(obliquity=85000*u.arcsec) explicit_coo = from_coo.transform_to(ICRS()).transform_to(to_frame) implicit_coo = from_coo.transform_to(to_frame) # Confirm that the explicit transformation changes the lon/lat but not the distance assert not quantity_allclose(explicit_coo.lon, from_coo.lon, rtol=1e-10) assert not quantity_allclose(explicit_coo.lat, from_coo.lat, rtol=1e-10) assert quantity_allclose(explicit_coo.distance, from_coo.distance, rtol=1e-10) # Confirm that the loopback matches the explicit transformation assert quantity_allclose(explicit_coo.lon, implicit_coo.lon, rtol=1e-10) assert quantity_allclose(explicit_coo.lat, implicit_coo.lat, rtol=1e-10) assert quantity_allclose(explicit_coo.distance, implicit_coo.distance, rtol=1e-10)