def test_gcrs_self_transform_closeby(): """ Tests GCRS self transform for objects which are nearby and thus have reasonable parallax. Moon positions were originally created using JPL DE432s ephemeris. The two lunar positions (one geocentric, one at a defined location) are created via a transformation from ICRS to two different GCRS frames. We test that the GCRS-GCRS self transform can correctly map one GCRS frame onto the other. """ t = Time("2014-12-25T07:00") moon_geocentric = SkyCoord(GCRS(318.10579159*u.deg, -11.65281165*u.deg, 365042.64880308*u.km, obstime=t)) # this is the location of the Moon as seen from La Palma obsgeoloc = [-5592982.59658935, -63054.1948592, 3059763.90102216]*u.m obsgeovel = [4.59798494, -407.84677071, 0.]*u.m/u.s moon_lapalma = SkyCoord(GCRS(318.7048445*u.deg, -11.98761996*u.deg, 369722.8231031*u.km, obstime=t, obsgeoloc=obsgeoloc, obsgeovel=obsgeovel)) transformed = moon_geocentric.transform_to(moon_lapalma.frame) delta = transformed.separation_3d(moon_lapalma) assert_allclose(delta, 0.0*u.m, atol=1*u.m)
def test_sep(): from astropy.coordinates.builtin_frames import ICRS i1 = ICRS(ra=0*u.deg, dec=1*u.deg) i2 = ICRS(ra=0*u.deg, dec=2*u.deg) sep = i1.separation(i2) assert sep.deg == 1 i3 = ICRS(ra=[1, 2]*u.deg, dec=[3, 4]*u.deg, distance=[5, 6]*u.kpc) i4 = ICRS(ra=[1, 2]*u.deg, dec=[3, 4]*u.deg, distance=[4, 5]*u.kpc) sep3d = i3.separation_3d(i4) assert_allclose(sep3d.to(u.kpc), np.array([1, 1])*u.kpc) # check that it works even with velocities i5 = ICRS(ra=[1, 2]*u.deg, dec=[3, 4]*u.deg, distance=[5, 6]*u.kpc, pm_ra_cosdec=[1, 2]*u.mas/u.yr, pm_dec=[3, 4]*u.mas/u.yr, radial_velocity=[5, 6]*u.km/u.s) i6 = ICRS(ra=[1, 2]*u.deg, dec=[3, 4]*u.deg, distance=[7, 8]*u.kpc, pm_ra_cosdec=[1, 2]*u.mas/u.yr, pm_dec=[3, 4]*u.mas/u.yr, radial_velocity=[5, 6]*u.km/u.s) sep3d = i5.separation_3d(i6) assert_allclose(sep3d.to(u.kpc), np.array([2, 2])*u.kpc)
def test_transform_decos(): """ Tests the decorator syntax for creating transforms """ c1 = TCoo1(ra=1*u.deg, dec=2*u.deg) @frame_transform_graph.transform(t.FunctionTransform, TCoo1, TCoo2) def trans(coo1, f): return TCoo2(ra=coo1.ra, dec=coo1.dec * 2) c2 = c1.transform_to(TCoo2) assert_allclose(c2.ra.degree, 1) assert_allclose(c2.dec.degree, 4) c3 = TCoo1(r.CartesianRepresentation(x=1*u.pc, y=1*u.pc, z=2*u.pc)) @frame_transform_graph.transform(t.StaticMatrixTransform, TCoo1, TCoo2) def matrix(): return [[2, 0, 0], [0, 1, 0], [0, 0, 1]] c4 = c3.transform_to(TCoo2) assert_allclose(c4.cartesian.x, 2*u.pc) assert_allclose(c4.cartesian.y, 1*u.pc) assert_allclose(c4.cartesian.z, 2*u.pc)
def test_skyoffset_velocity_rotation(rotation, expectedpmlonlat): sc = SkyCoord(ra=170.9*u.deg, dec=-78.4*u.deg, pm_ra_cosdec=1*u.mas/u.yr, pm_dec=2*u.mas/u.yr) c_skyoffset0 = sc.transform_to(sc.skyoffset_frame(rotation=rotation)) assert_allclose(c_skyoffset0.pm_lon_coslat, expectedpmlonlat[0]) assert_allclose(c_skyoffset0.pm_lat, expectedpmlonlat[1])
def test_rotation(rotation, expectedlatlon): origin = ICRS(45*u.deg, 45*u.deg) target = ICRS(45*u.deg, 46*u.deg) aframe = SkyOffsetFrame(origin=origin, rotation=rotation) trans = target.transform_to(aframe) assert_allclose([trans.lon.wrap_at(180*u.deg), trans.lat], expectedlatlon, atol=1e-10*u.deg)
def test_skyoffset_velocity(): c = ICRS(ra=170.9*u.deg, dec=-78.4*u.deg, pm_ra_cosdec=74.4134*u.mas/u.yr, pm_dec=-93.2342*u.mas/u.yr) skyoffset_frame = SkyOffsetFrame(origin=c) c_skyoffset = c.transform_to(skyoffset_frame) assert_allclose(c_skyoffset.pm_lon_coslat, c.pm_ra_cosdec) assert_allclose(c_skyoffset.pm_lat, c.pm_dec)
def test_from_icrs(self): # scalar positions transformed = self.sun_icrs_scalar.transform_to(HCRS(obstime=self.t1)) separation = transformed.separation_3d(self.sun_hcrs_t1) assert_allclose(separation, 0*u.km, atol=self.tolerance) # nonscalar positions transformed = self.sun_icrs_arr.transform_to(HCRS(obstime=self.tarr)) separation = transformed.separation_3d(self.sun_hcrs_tarr) assert_allclose(separation, 0*u.km, atol=self.tolerance)
def test_skycoord_skyoffset_frame_rotation(rotation, expectedlatlon): """Test if passing a rotation argument via SkyCoord works""" origin = SkyCoord(45*u.deg, 45*u.deg) target = SkyCoord(45*u.deg, 46*u.deg) aframe = origin.skyoffset_frame(rotation=rotation) trans = target.transform_to(aframe) assert_allclose([trans.lon.wrap_at(180*u.deg), trans.lat], expectedlatlon, atol=1e-10*u.deg)
def test_barycentric(self): gcrs = self.wht_itrs.transform_to(GCRS(obstime=self.obstime)) bary = gcrs.transform_to(ICRS()) previous = [-1.02758958e+11, 9.68331109e+10, 4.19720938e+10] * u.m assert_allclose(bary.cartesian.xyz, previous) # And that it agrees with SLALIB answer to within 14km bary_slalib = [-0.6869012079, 0.6472893646, 0.2805661191] * u.au assert np.sqrt(((bary.cartesian.xyz - bary_slalib)**2).sum()) < 14. * u.km
def test_from_hcrs(self): # test scalar transform transformed = self.sun_hcrs_t1.transform_to(ICRS()) separation = transformed.separation_3d(self.sun_icrs_scalar) assert_allclose(separation, 0*u.km, atol=self.tolerance) # test non-scalar positions and times transformed = self.sun_hcrs_tarr.transform_to(ICRS()) separation = transformed.separation_3d(self.sun_icrs_arr) assert_allclose(separation, 0*u.km, atol=self.tolerance)
def test_cirs_altaz_nodist(testframe): """ Check that a UnitSphericalRepresentation coordinate round-trips for the CIRS<->AltAz transformation. """ coo0 = CIRS(UnitSphericalRepresentation(10*u.deg, 20*u.deg), obstime=testframe.obstime) # check that it round-trips coo1 = coo0.transform_to(testframe).transform_to(coo0) assert_allclose(coo0.cartesian.xyz, coo1.cartesian.xyz)
def test_heliocentric(self): gcrs = self.wht_itrs.transform_to(GCRS(obstime=self.obstime)) helio = gcrs.transform_to(HCRS(obstime=self.obstime)) # Check it doesn't change from previous times. previous = [-1.02597256e+11, 9.71725820e+10, 4.21268419e+10] * u.m assert_allclose(helio.cartesian.xyz, previous) # And that it agrees with SLALIB to within 14km helio_slalib = [-0.685820296, 0.6495585893, 0.2816005464] * u.au assert np.sqrt(((helio.cartesian.xyz - helio_slalib)**2).sum()) < 14. * u.km
def test_3d_separations(): """ Test 3D separation functionality """ c1 = ICRS(ra=1*u.deg, dec=1*u.deg, distance=9*u.kpc) c2 = ICRS(ra=1*u.deg, dec=1*u.deg, distance=10*u.kpc) sep3d = c2.separation_3d(c1) assert isinstance(sep3d, Distance) assert_allclose(sep3d - 1*u.kpc, 0*u.kpc, atol=1e-12*u.kpc)
def test_atciqz_aticq(st): """Check replacements against erfa versions for consistency.""" t, pos = st jd1, jd2 = get_jd12(t, 'tdb') astrom, _ = erfa.apci13(jd1, jd2) ra, dec = pos ra = ra.value dec = dec.value assert_allclose(erfa.atciqz(ra, dec, astrom), atciqz(ra, dec, astrom)) assert_allclose(erfa.aticq(ra, dec, astrom), aticq(ra, dec, astrom))
def test_cirs_altaz_moonish(testframe): """ Sanity-check that an object resembling the moon goes to the right place with a CIRS<->AltAz transformation """ moon = CIRS(MOONDIST_CART, obstime=testframe.obstime) moonaa = moon.transform_to(testframe) assert 1000*u.km < np.abs(moonaa.distance - moon.distance).to(u.km) < 7000*u.km # now check that it round-trips moon2 = moonaa.transform_to(moon) assert_allclose(moon.cartesian.xyz, moon2.cartesian.xyz)
def test_transform_classes(): """ Tests the class-based/OO syntax for creating transforms """ tfun = lambda c, f: f.__class__(ra=c.ra, dec=c.dec) trans1 = t.FunctionTransform(tfun, TCoo1, TCoo2, register_graph=frame_transform_graph) c1 = TCoo1(ra=1*u.radian, dec=0.5*u.radian) c2 = c1.transform_to(TCoo2) assert_allclose(c2.ra.radian, 1) assert_allclose(c2.dec.radian, 0.5) def matfunc(coo, fr): return [[1, 0, 0], [0, coo.ra.degree, 0], [0, 0, 1]] trans2 = t.DynamicMatrixTransform(matfunc, TCoo1, TCoo2) trans2.register(frame_transform_graph) c3 = TCoo1(ra=1*u.deg, dec=2*u.deg) c4 = c3.transform_to(TCoo2) assert_allclose(c4.ra.degree, 1) assert_allclose(c4.ra.degree, 1) # be sure to unregister the second one - no need for trans1 because it # already got unregistered when trans2 was created. trans2.unregister(frame_transform_graph)
def test_gcrs_altaz(): """ Check GCRS<->AltAz transforms for round-tripping. Has multiple paths """ from astropy.coordinates import EarthLocation ra, dec, _ = randomly_sample_sphere(1) gcrs = GCRS(ra=ra[0], dec=dec[0], obstime='J2000') # check array times sure N-d arrays work times = Time(np.linspace(2456293.25, 2456657.25, 51) * u.day, format='jd', scale='utc') loc = EarthLocation(lon=10 * u.deg, lat=80. * u.deg) aaframe = AltAz(obstime=times, location=loc) aa1 = gcrs.transform_to(aaframe) aa2 = gcrs.transform_to(ICRS).transform_to(CIRS).transform_to(aaframe) aa3 = gcrs.transform_to(ITRS).transform_to(CIRS).transform_to(aaframe) # make sure they're all consistent assert_allclose(aa1.alt, aa2.alt) assert_allclose(aa1.az, aa2.az) assert_allclose(aa1.alt, aa3.alt) assert_allclose(aa1.az, aa3.az)
def test_skyoffset_functional_ra(): # we do the 12)[1:-1] business because sometimes machine precision issues # lead to results that are either ~0 or ~360, which mucks up the final # comparison and leads to spurious failures. So this just avoids that by # staying away from the edges input_ra = np.linspace(0, 360, 12)[1:-1] input_dec = np.linspace(-90, 90, 12)[1:-1] icrs_coord = ICRS(ra=input_ra*u.deg, dec=input_dec*u.deg, distance=1.*u.kpc) for ra in np.linspace(0, 360, 24): # expected rotation expected = ICRS(ra=np.linspace(0-ra, 360-ra, 12)[1:-1]*u.deg, dec=np.linspace(-90, 90, 12)[1:-1]*u.deg, distance=1.*u.kpc) expected_xyz = expected.cartesian.xyz # actual transformation to the frame skyoffset_frame = SkyOffsetFrame(origin=ICRS(ra*u.deg, 0*u.deg)) actual = icrs_coord.transform_to(skyoffset_frame) actual_xyz = actual.cartesian.xyz # back to ICRS roundtrip = actual.transform_to(ICRS) roundtrip_xyz = roundtrip.cartesian.xyz # Verify assert_allclose(actual_xyz, expected_xyz, atol=1E-5*u.kpc) assert_allclose(icrs_coord.ra, roundtrip.ra, atol=1E-5*u.deg) assert_allclose(icrs_coord.dec, roundtrip.dec, atol=1E-5*u.deg) assert_allclose(icrs_coord.distance, roundtrip.distance, atol=1E-5*u.kpc)
def test_galactocentric(): # when z_sun=0, transformation should be very similar to Galactic icrs_coord = ICRS(ra=np.linspace(0, 360, 10)*u.deg, dec=np.linspace(-90, 90, 10)*u.deg, distance=1.*u.kpc) g_xyz = icrs_coord.transform_to(Galactic).cartesian.xyz gc_xyz = icrs_coord.transform_to(Galactocentric(z_sun=0*u.kpc)).cartesian.xyz diff = np.abs(g_xyz - gc_xyz) assert allclose(diff[0], 8.3*u.kpc, atol=1E-5*u.kpc) assert allclose(diff[1:], 0*u.kpc, atol=1E-5*u.kpc) # generate some test coordinates g = Galactic(l=[0, 0, 45, 315]*u.deg, b=[-45, 45, 0, 0]*u.deg, distance=[np.sqrt(2)]*4*u.kpc) xyz = g.transform_to(Galactocentric(galcen_distance=1.*u.kpc, z_sun=0.*u.pc)).cartesian.xyz true_xyz = np.array([[0, 0, -1.], [0, 0, 1], [0, 1, 0], [0, -1, 0]]).T*u.kpc assert allclose(xyz.to(u.kpc), true_xyz.to(u.kpc), atol=1E-5*u.kpc) # check that ND arrays work # from Galactocentric to Galactic x = np.linspace(-10., 10., 100) * u.kpc y = np.linspace(-10., 10., 100) * u.kpc z = np.zeros_like(x) g1 = Galactocentric(x=x, y=y, z=z) g2 = Galactocentric(x=x.reshape(100, 1, 1), y=y.reshape(100, 1, 1), z=z.reshape(100, 1, 1)) g1t = g1.transform_to(Galactic) g2t = g2.transform_to(Galactic) assert_allclose(g1t.cartesian.xyz, g2t.cartesian.xyz[:, :, 0, 0]) # from Galactic to Galactocentric l = np.linspace(15, 30., 100) * u.deg b = np.linspace(-10., 10., 100) * u.deg d = np.ones_like(l.value) * u.kpc g1 = Galactic(l=l, b=b, distance=d) g2 = Galactic(l=l.reshape(100, 1, 1), b=b.reshape(100, 1, 1), distance=d.reshape(100, 1, 1)) g1t = g1.transform_to(Galactocentric) g2t = g2.transform_to(Galactocentric) np.testing.assert_almost_equal(g1t.cartesian.xyz.value, g2t.cartesian.xyz.value[:, :, 0, 0])
def test_gcrs_altaz_bothroutes(testframe): """ Repeat of both the moonish and sunish tests above to make sure the two routes through the coordinate graph are consistent with each other """ sun = get_sun(testframe.obstime) sunaa_viaicrs = sun.transform_to(ICRS).transform_to(testframe) sunaa_viaitrs = sun.transform_to(ITRS(obstime=testframe.obstime)).transform_to(testframe) moon = GCRS(MOONDIST_CART, obstime=testframe.obstime) moonaa_viaicrs = moon.transform_to(ICRS).transform_to(testframe) moonaa_viaitrs = moon.transform_to(ITRS(obstime=testframe.obstime)).transform_to(testframe) assert_allclose(sunaa_viaicrs.cartesian.xyz, sunaa_viaitrs.cartesian.xyz) assert_allclose(moonaa_viaicrs.cartesian.xyz, moonaa_viaitrs.cartesian.xyz)
def test_gcrs_altaz_moonish(testframe): """ Sanity-check that an object resembling the moon goes to the right place with a GCRS->AltAz transformation """ moon = GCRS(MOONDIST_CART, obstime=testframe.obstime) moonaa = moon.transform_to(testframe) # now check that the distance change is similar to earth radius assert 1000*u.km < np.abs(moonaa.distance - moon.distance).to(u.au) < 7000*u.km # now check that it round-trips moon2 = moonaa.transform_to(moon) assert_allclose(moon.cartesian.xyz, moon2.cartesian.xyz)
def test_cirs_itrs(): """ Check basic CIRS<->ITRS transforms for round-tripping. """ ra, dec, _ = randomly_sample_sphere(200) cirs = CIRS(ra=ra, dec=dec, obstime='J2000') cirs6 = CIRS(ra=ra, dec=dec, obstime='J2006') cirs2 = cirs.transform_to(ITRS).transform_to(cirs) cirs6_2 = cirs6.transform_to(ITRS).transform_to(cirs) # different obstime # just check round-tripping assert_allclose(cirs.ra, cirs2.ra) assert_allclose(cirs.dec, cirs2.dec) assert not allclose(cirs.ra, cirs6_2.ra) assert not allclose(cirs.dec, cirs6_2.dec)
def test_m31_coord_transforms(fromsys, tosys, fromcoo, tocoo): """ This tests a variety of coordinate conversions for the Chandra point-source catalog location of M31 from NED, via SkyOffsetFrames """ from_origin = fromsys(fromcoo[0]*u.deg, fromcoo[1]*u.deg, distance=m31_dist) from_pos = SkyOffsetFrame(1*u.deg, 1*u.deg, origin=from_origin) to_origin = tosys(tocoo[0]*u.deg, tocoo[1]*u.deg, distance=m31_dist) to_astroframe = SkyOffsetFrame(origin=to_origin) target_pos = from_pos.transform_to(to_astroframe) assert_allclose(to_origin.separation(target_pos), np.hypot(from_pos.lon, from_pos.lat), atol=convert_precision) roundtrip_pos = target_pos.transform_to(from_pos) assert_allclose([roundtrip_pos.lon.wrap_at(180*u.deg), roundtrip_pos.lat], [1.0*u.deg, 1.0*u.deg], atol=convert_precision)
def test_inplace_array(): from astropy.coordinates.builtin_frames import ICRS i = ICRS([[1, 2], [3, 4]]*u.deg, [[10, 20], [30, 40]]*u.deg) # Add an in frame units version of the rep to the cache. repr(i) # Check that repr() has added a rep to the cache assert len(i.cache['representation']) == 2 # Modify the data i.data.lon[:, 0] = [100, 200]*u.deg # Clear the cache i.cache.clear() # This will use a second (potentially cached rep) assert_allclose(i.ra, [[100, 2], [200, 4]]*u.deg) assert_allclose(i.dec, [[10, 20], [30, 40]]*u.deg)
def test_proj_separations(): """ Test angular separation functionality """ c1 = ICRS(ra=0*u.deg, dec=0*u.deg) c2 = ICRS(ra=0*u.deg, dec=1*u.deg) sep = c2.separation(c1) # returns an Angle object assert isinstance(sep, Angle) assert sep.degree == 1 assert_allclose(sep.arcminute, 60.) # these operations have ambiguous interpretations for points on a sphere with pytest.raises(TypeError): c1 + c2 with pytest.raises(TypeError): c1 - c2 ngp = Galactic(l=0*u.degree, b=90*u.degree) ncp = ICRS(ra=0*u.degree, dec=90*u.degree) # if there is a defined conversion between the relevant coordinate systems, # it will be automatically performed to get the right angular separation assert_allclose(ncp.separation(ngp.transform_to(ICRS)).degree, ncp.separation(ngp).degree) # distance from the north galactic pole to celestial pole assert_allclose(ncp.separation(ngp.transform_to(ICRS)).degree, 62.87174758503201)
def test_create_data_frames(): from astropy.coordinates.builtin_frames import ICRS # from repr i1 = ICRS(r.SphericalRepresentation(1*u.deg, 2*u.deg, 3*u.kpc)) i2 = ICRS(r.UnitSphericalRepresentation(lon=1*u.deg, lat=2*u.deg)) # from preferred name i3 = ICRS(ra=1*u.deg, dec=2*u.deg, distance=3*u.kpc) i4 = ICRS(ra=1*u.deg, dec=2*u.deg) assert i1.data.lat == i3.data.lat assert i1.data.lon == i3.data.lon assert i1.data.distance == i3.data.distance assert i2.data.lat == i4.data.lat assert i2.data.lon == i4.data.lon # now make sure the preferred names work as properties assert_allclose(i1.ra, i3.ra) assert_allclose(i2.ra, i4.ra) assert_allclose(i1.distance, i3.distance) with pytest.raises(AttributeError): i1.ra = [11.]*u.deg
def test_ephemerides(): """ We test that using different ephemerides gives very similar results for transformations """ t = Time("2014-12-25T07:00") moon = SkyCoord(GCRS(318.10579159*u.deg, -11.65281165*u.deg, 365042.64880308*u.km, obstime=t)) icrs_frame = ICRS() hcrs_frame = HCRS(obstime=t) ecl_frame = HeliocentricTrueEcliptic(equinox=t) cirs_frame = CIRS(obstime=t) moon_icrs_builtin = moon.transform_to(icrs_frame) moon_hcrs_builtin = moon.transform_to(hcrs_frame) moon_helioecl_builtin = moon.transform_to(ecl_frame) moon_cirs_builtin = moon.transform_to(cirs_frame) with solar_system_ephemeris.set('jpl'): moon_icrs_jpl = moon.transform_to(icrs_frame) moon_hcrs_jpl = moon.transform_to(hcrs_frame) moon_helioecl_jpl = moon.transform_to(ecl_frame) moon_cirs_jpl = moon.transform_to(cirs_frame) # most transformations should differ by an amount which is # non-zero but of order milliarcsecs sep_icrs = moon_icrs_builtin.separation(moon_icrs_jpl) sep_hcrs = moon_hcrs_builtin.separation(moon_hcrs_jpl) sep_helioecl = moon_helioecl_builtin.separation(moon_helioecl_jpl) sep_cirs = moon_cirs_builtin.separation(moon_cirs_jpl) assert_allclose([sep_icrs, sep_hcrs, sep_helioecl], 0.0*u.deg, atol=10*u.mas) assert all(sep > 10*u.microarcsecond for sep in (sep_icrs, sep_hcrs, sep_helioecl)) # CIRS should be the same assert_allclose(sep_cirs, 0.0*u.deg, atol=1*u.microarcsecond)
def test_altaz_attribute_transforms(): """Test transforms between AltAz frames with different attributes.""" el1 = EarthLocation(0*u.deg, 0*u.deg, 0*u.m) origin1 = AltAz(0 * u.deg, 0*u.deg, obstime=Time("2000-01-01T12:00:00"), location=el1) frame1 = SkyOffsetFrame(origin=origin1) coo1 = SkyCoord(1 * u.deg, 1 * u.deg, frame=frame1) el2 = EarthLocation(0*u.deg, 0*u.deg, 0*u.m) origin2 = AltAz(0 * u.deg, 0*u.deg, obstime=Time("2000-01-01T11:00:00"), location=el2) frame2 = SkyOffsetFrame(origin=origin2) coo2 = coo1.transform_to(frame2) coo2_expected = [1.22522446, 0.70624298] * u.deg assert_allclose([coo2.lon.wrap_at(180*u.deg), coo2.lat], coo2_expected, atol=convert_precision) el3 = EarthLocation(0*u.deg, 90*u.deg, 0*u.m) origin3 = AltAz(0 * u.deg, 90*u.deg, obstime=Time("2000-01-01T12:00:00"), location=el3) frame3 = SkyOffsetFrame(origin=origin3) coo3 = coo2.transform_to(frame3) assert_allclose([coo3.lon.wrap_at(180*u.deg), coo3.lat], [1*u.deg, 1*u.deg], atol=convert_precision)
def test_skyoffset_functional_ra_dec(): # we do the 12)[1:-1] business because sometimes machine precision issues # lead to results that are either ~0 or ~360, which mucks up the final # comparison and leads to spurious failures. So this just avoids that by # staying away from the edges input_ra = np.linspace(0, 360, 12)[1:-1] input_dec = np.linspace(-90, 90, 12)[1:-1] input_ra_rad = np.deg2rad(input_ra) input_dec_rad = np.deg2rad(input_dec) icrs_coord = ICRS(ra=input_ra*u.deg, dec=input_dec*u.deg, distance=1.*u.kpc) for ra in np.linspace(0, 360, 10): for dec in np.linspace(-90, 90, 5): # expected rotation dec_rad = -np.deg2rad(dec) ra_rad = np.deg2rad(ra) expected_x = (-np.sin(input_dec_rad) * np.sin(dec_rad) + np.cos(input_ra_rad) * np.cos(input_dec_rad) * np.cos(dec_rad) * np.cos(ra_rad) + np.sin(input_ra_rad) * np.cos(input_dec_rad) * np.cos(dec_rad) * np.sin(ra_rad)) expected_y = (np.sin(input_ra_rad) * np.cos(input_dec_rad) * np.cos(ra_rad) - np.cos(input_ra_rad) * np.cos(input_dec_rad) * np.sin(ra_rad)) expected_z = (np.sin(input_dec_rad) * np.cos(dec_rad) + np.sin(dec_rad) * np.cos(ra_rad) * np.cos(input_ra_rad) * np.cos(input_dec_rad) + np.sin(dec_rad) * np.sin(ra_rad) * np.sin(input_ra_rad) * np.cos(input_dec_rad)) expected = SkyCoord(x=expected_x, y=expected_y, z=expected_z, unit='kpc', representation_type='cartesian') expected_xyz = expected.cartesian.xyz # actual transformation to the frame skyoffset_frame = SkyOffsetFrame(origin=ICRS(ra*u.deg, dec*u.deg)) actual = icrs_coord.transform_to(skyoffset_frame) actual_xyz = actual.cartesian.xyz # back to ICRS roundtrip = actual.transform_to(ICRS) # Verify assert_allclose(actual_xyz, expected_xyz, atol=1E-5*u.kpc) assert_allclose(icrs_coord.ra, roundtrip.ra, atol=1E-4*u.deg) assert_allclose(icrs_coord.dec, roundtrip.dec, atol=1E-5*u.deg) assert_allclose(icrs_coord.distance, roundtrip.distance, atol=1E-5*u.kpc)
def test_skycoord_skyoffset_frame(): m31 = SkyCoord(10.6847083, 41.26875, frame='icrs', unit=u.deg) m33 = SkyCoord(23.4621, 30.6599417, frame='icrs', unit=u.deg) m31_astro = m31.skyoffset_frame() m31_in_m31 = m31.transform_to(m31_astro) m33_in_m31 = m33.transform_to(m31_astro) assert_allclose([m31_in_m31.lon, m31_in_m31.lat], [0, 0]*u.deg, atol=1e-10*u.deg) assert_allclose([m33_in_m31.lon, m33_in_m31.lat], [11.13135175, -9.79084759]*u.deg) assert_allclose(m33.separation(m31), np.hypot(m33_in_m31.lon, m33_in_m31.lat), atol=.1*u.deg)
def test_array_indexing(): ra = np.linspace(0, 360, 10) dec = np.linspace(-90, 90, 10) j1975 = Time(1975, format='jyear', scale='utc') c1 = FK5(ra * u.deg, dec * u.deg, equinox=j1975) c2 = c1[4] assert c2.ra.degree == 160 assert c2.dec.degree == -10 c3 = c1[2:5] assert_allclose(c3.ra, [80, 120, 160] * u.deg) assert_allclose(c3.dec, [-50, -30, -10] * u.deg) c4 = c1[np.array([2, 5, 8])] assert_allclose(c4.ra, [80, 200, 320] * u.deg) assert_allclose(c4.dec, [-50, 10, 70] * u.deg) # now make sure the equinox is preserved assert c2.equinox == c1.equinox assert c3.equinox == c1.equinox assert c4.equinox == c1.equinox
def test_gcrs_altaz(): """ Check GCRS<->AltAz transforms for round-tripping. Has multiple paths """ from astropy.coordinates import EarthLocation ra, dec, _ = randomly_sample_sphere(1) gcrs = GCRS(ra=ra[0], dec=dec[0], obstime='J2000') # check array times sure N-d arrays work times = Time(np.linspace(2456293.25, 2456657.25, 51) * u.day, format='jd') loc = EarthLocation(lon=10 * u.deg, lat=80. * u.deg) aaframe = AltAz(obstime=times, location=loc) aa1 = gcrs.transform_to(aaframe) aa2 = gcrs.transform_to(ICRS).transform_to(CIRS).transform_to(aaframe) aa3 = gcrs.transform_to(ITRS).transform_to(CIRS).transform_to(aaframe) # make sure they're all consistent assert_allclose(aa1.alt, aa2.alt) assert_allclose(aa1.az, aa2.az) assert_allclose(aa1.alt, aa3.alt) assert_allclose(aa1.az, aa3.az)
def test_matching_function_3d_and_sky(): from astropy.coordinates import ICRS from astropy.coordinates.matching import match_coordinates_3d, match_coordinates_sky cmatch = ICRS([4, 2.1]*u.degree, [0, 0]*u.degree, distance=[1, 5] * u.kpc) ccatalog = ICRS([1, 2, 3, 4]*u.degree, [0, 0, 0, 0]*u.degree, distance=[1, 1, 1, 5] * u.kpc) idx, d2d, d3d = match_coordinates_3d(cmatch, ccatalog) npt.assert_array_equal(idx, [2, 3]) assert_allclose(d2d, [1, 1.9] * u.deg) assert np.abs(d3d[0].to_value(u.kpc) - np.radians(1)) < 1e-6 assert np.abs(d3d[1].to_value(u.kpc) - 5*np.radians(1.9)) < 1e-5 idx, d2d, d3d = match_coordinates_sky(cmatch, ccatalog) npt.assert_array_equal(idx, [3, 1]) assert_allclose(d2d, [0, 0.1] * u.deg) assert_allclose(d3d, [4, 4.0000019] * u.kpc)
def test_sep(): from astropy.coordinates.builtin_frames import ICRS i1 = ICRS(ra=0 * u.deg, dec=1 * u.deg) i2 = ICRS(ra=0 * u.deg, dec=2 * u.deg) sep = i1.separation(i2) assert sep.deg == 1 i3 = ICRS(ra=[1, 2] * u.deg, dec=[3, 4] * u.deg, distance=[5, 6] * u.kpc) i4 = ICRS(ra=[1, 2] * u.deg, dec=[3, 4] * u.deg, distance=[4, 5] * u.kpc) sep3d = i3.separation_3d(i4) assert_allclose(sep3d.to(u.kpc), np.array([1, 1]) * u.kpc) # check that it works even with velocities i5 = ICRS(ra=[1, 2] * u.deg, dec=[3, 4] * u.deg, distance=[5, 6] * u.kpc, pm_ra_cosdec=[1, 2] * u.mas / u.yr, pm_dec=[3, 4] * u.mas / u.yr, radial_velocity=[5, 6] * u.km / u.s) i6 = ICRS(ra=[1, 2] * u.deg, dec=[3, 4] * u.deg, distance=[7, 8] * u.kpc, pm_ra_cosdec=[1, 2] * u.mas / u.yr, pm_dec=[3, 4] * u.mas / u.yr, radial_velocity=[5, 6] * u.km / u.s) sep3d = i5.separation_3d(i6) assert_allclose(sep3d.to(u.kpc), np.array([2, 2]) * u.kpc) # 3d separations of dimensionless distances should still work i7 = ICRS(ra=1 * u.deg, dec=2 * u.deg, distance=3 * u.one) i8 = ICRS(ra=1 * u.deg, dec=2 * u.deg, distance=4 * u.one) sep3d = i7.separation_3d(i8) assert_allclose(sep3d, 1 * u.one) # but should fail with non-dimensionless with pytest.raises(ValueError): i7.separation_3d(i3)
def test_aa_high_precision(): """These tests are provided by @mkbrewer - see issue #10356. The code that produces them agrees very well (<0.5 mas) with SkyField once Polar motion is turned off, but SkyField does not include polar motion, so a comparison to Skyfield or JPL Horizons will be ~1" off. The absence of polar motion within Skyfield and the disagreement between Skyfield and Horizons make high precision comparisons to those codes difficult. Updated 2020-11-29, after the comparison between codes became even better, down to 100 nas. NOTE: the agreement reflects consistency in approach between two codes, not necessarily absolute precision. If this test starts failing, the tolerance can and shouls be weakened *if* it is clear that the change is due to an improvement (e.g., a new IAU precession model). """ lat = -22.959748*u.deg lon = -67.787260*u.deg elev = 5186*u.m loc = EarthLocation.from_geodetic(lon, lat, elev) # Note: at this level of precision for the comparison, we have to include # the location in the time, as it influences the transformation to TDB. t = Time('2017-04-06T00:00:00.0', location=loc) with solar_system_ephemeris.set('de430'): moon = get_body('moon', t, loc) moon_aa = moon.transform_to(AltAz(obstime=t, location=loc)) # Numbers from # https://github.com/astropy/astropy/pull/11073#issuecomment-735486271 TARGET_AZ, TARGET_EL = 15.032673509886*u.deg, 50.303110133928*u.deg TARGET_DISTANCE = 376252883.247239*u.m assert_allclose(moon_aa.az, TARGET_AZ, atol=0.1*u.uas, rtol=0) assert_allclose(moon_aa.alt, TARGET_EL, atol=0.1*u.uas, rtol=0) assert_allclose(moon_aa.distance, TARGET_DISTANCE, atol=0.1*u.mm, rtol=0)
def test_icrs_cirs(): """ Check a few cases of ICRS<->CIRS for consistency. Also includes the CIRS<->CIRS transforms at different times, as those go through ICRS """ usph = golden_spiral_grid(200) dist = np.linspace(0., 1, len(usph)) * u.pc inod = ICRS(usph) iwd = ICRS(ra=usph.lon, dec=usph.lat, distance=dist) cframe1 = CIRS() cirsnod = inod.transform_to(cframe1) # uses the default time # first do a round-tripping test inod2 = cirsnod.transform_to(ICRS()) assert_allclose(inod.ra, inod2.ra) assert_allclose(inod.dec, inod2.dec) # now check that a different time yields different answers cframe2 = CIRS(obstime=Time('J2005')) cirsnod2 = inod.transform_to(cframe2) assert not allclose(cirsnod.ra, cirsnod2.ra, rtol=1e-8) assert not allclose(cirsnod.dec, cirsnod2.dec, rtol=1e-8) # parallax effects should be included, so with and w/o distance should be different cirswd = iwd.transform_to(cframe1) assert not allclose(cirswd.ra, cirsnod.ra, rtol=1e-8) assert not allclose(cirswd.dec, cirsnod.dec, rtol=1e-8) # and the distance should transform at least somehow assert not allclose(cirswd.distance, iwd.distance, rtol=1e-8) # now check that the cirs self-transform works as expected cirsnod3 = cirsnod.transform_to(cframe1) # should be a no-op assert_allclose(cirsnod.ra, cirsnod3.ra) assert_allclose(cirsnod.dec, cirsnod3.dec) cirsnod4 = cirsnod.transform_to(cframe2) # should be different assert not allclose(cirsnod4.ra, cirsnod.ra, rtol=1e-8) assert not allclose(cirsnod4.dec, cirsnod.dec, rtol=1e-8) cirsnod5 = cirsnod4.transform_to(cframe1) # should be back to the same assert_allclose(cirsnod.ra, cirsnod5.ra) assert_allclose(cirsnod.dec, cirsnod5.dec)
def test_precessed_geocentric(): assert PrecessedGeocentric().equinox.jd == Time('J2000').jd gcrs_coo = GCRS(180 * u.deg, 2 * u.deg, distance=10000 * u.km) pgeo_coo = gcrs_coo.transform_to(PrecessedGeocentric()) assert np.abs(gcrs_coo.ra - pgeo_coo.ra) > 10 * u.marcsec assert np.abs(gcrs_coo.dec - pgeo_coo.dec) > 10 * u.marcsec assert_allclose(gcrs_coo.distance, pgeo_coo.distance) gcrs_roundtrip = pgeo_coo.transform_to(GCRS()) assert_allclose(gcrs_coo.ra, gcrs_roundtrip.ra) assert_allclose(gcrs_coo.dec, gcrs_roundtrip.dec) assert_allclose(gcrs_coo.distance, gcrs_roundtrip.distance) pgeo_coo2 = gcrs_coo.transform_to(PrecessedGeocentric(equinox='B1850')) assert np.abs(gcrs_coo.ra - pgeo_coo2.ra) > 1.5 * u.deg assert np.abs(gcrs_coo.dec - pgeo_coo2.dec) > 0.5 * u.deg assert_allclose(gcrs_coo.distance, pgeo_coo2.distance) gcrs2_roundtrip = pgeo_coo2.transform_to(GCRS()) assert_allclose(gcrs_coo.ra, gcrs2_roundtrip.ra) assert_allclose(gcrs_coo.dec, gcrs2_roundtrip.dec) assert_allclose(gcrs_coo.distance, gcrs2_roundtrip.distance)
def test_gcrs_cirs(): """ Check GCRS<->CIRS transforms for round-tripping. More complicated than the above two because it's multi-hop """ usph = golden_spiral_grid(200) gcrs = GCRS(usph, obstime='J2000') gcrs6 = GCRS(usph, obstime='J2006') gcrs2 = gcrs.transform_to(CIRS()).transform_to(gcrs) gcrs6_2 = gcrs6.transform_to(CIRS()).transform_to(gcrs) assert_allclose(gcrs.ra, gcrs2.ra) assert_allclose(gcrs.dec, gcrs2.dec) # these should be different: assert not allclose(gcrs.ra, gcrs6_2.ra, rtol=1e-8) assert not allclose(gcrs.dec, gcrs6_2.dec, rtol=1e-8) # now try explicit intermediate pathways and ensure they're all consistent gcrs3 = gcrs.transform_to(ITRS()).transform_to(CIRS()).transform_to( ITRS()).transform_to(gcrs) assert_allclose(gcrs.ra, gcrs3.ra) assert_allclose(gcrs.dec, gcrs3.dec) gcrs4 = gcrs.transform_to(ICRS()).transform_to(CIRS()).transform_to( ICRS()).transform_to(gcrs) assert_allclose(gcrs.ra, gcrs4.ra) assert_allclose(gcrs.dec, gcrs4.dec)
def test_transform(): """ This test just makes sure the transform architecture works, but does *not* actually test all the builtin transforms themselves are accurate """ from astropy.coordinates.builtin_frames import ICRS, FK4, FK5, Galactic from astropy.time import Time i = ICRS(ra=[1, 2] * u.deg, dec=[3, 4] * u.deg) f = i.transform_to(FK5) i2 = f.transform_to(ICRS) assert i2.data.__class__ == r.UnitSphericalRepresentation assert_allclose(i.ra, i2.ra) assert_allclose(i.dec, i2.dec) i = ICRS(ra=[1, 2] * u.deg, dec=[3, 4] * u.deg, distance=[5, 6] * u.kpc) f = i.transform_to(FK5) i2 = f.transform_to(ICRS) assert i2.data.__class__ != r.UnitSphericalRepresentation f = FK5(ra=1 * u.deg, dec=2 * u.deg, equinox=Time('J2001')) f4 = f.transform_to(FK4) f4_2 = f.transform_to(FK4(equinox=f.equinox)) # make sure attributes are copied over correctly assert f4.equinox == FK4.get_frame_attr_names()['equinox'] assert f4_2.equinox == f.equinox # make sure self-transforms also work i = ICRS(ra=[1, 2] * u.deg, dec=[3, 4] * u.deg) i2 = i.transform_to(ICRS) assert_allclose(i.ra, i2.ra) assert_allclose(i.dec, i2.dec) f = FK5(ra=1 * u.deg, dec=2 * u.deg, equinox=Time('J2001')) f2 = f.transform_to(FK5) # default equinox, so should be *different* assert f2.equinox == FK5().equinox with pytest.raises(AssertionError): assert_allclose(f.ra, f2.ra) with pytest.raises(AssertionError): assert_allclose(f.dec, f2.dec) # finally, check Galactic round-tripping i1 = ICRS(ra=[1, 2] * u.deg, dec=[3, 4] * u.deg) i2 = i1.transform_to(Galactic).transform_to(ICRS) assert_allclose(i1.ra, i2.ra) assert_allclose(i1.dec, i2.dec)
def test_transform_api(): from astropy.coordinates.representation import UnitSphericalRepresentation from astropy.coordinates.builtin_frames import ICRS, FK5 from astropy.coordinates.baseframe import frame_transform_graph, BaseCoordinateFrame from astropy.coordinates.transformations import DynamicMatrixTransform # <------------------------Transformations-------------------------------------> # Transformation functionality is the key to the whole scheme: they transform # low-level classes from one frame to another. # (used below but defined above in the API) fk5 = FK5(ra=8 * u.hour, dec=5 * u.deg) # If no data (or `None`) is given, the class acts as a specifier of a frame, but # without any stored data. J2001 = time.Time('J2001') fk5_J2001_frame = FK5(equinox=J2001) # if they do not have data, the string instead is the frame specification assert repr(fk5_J2001_frame) == "<FK5 Frame (equinox=J2001.000)>" # Note that, although a frame object is immutable and can't have data added, it # can be used to create a new object that does have data by giving the # `realize_frame` method a representation: srep = UnitSphericalRepresentation(lon=8 * u.hour, lat=5 * u.deg) fk5_j2001_with_data = fk5_J2001_frame.realize_frame(srep) assert fk5_j2001_with_data.data is not None # Now `fk5_j2001_with_data` is in the same frame as `fk5_J2001_frame`, but it # is an actual low-level coordinate, rather than a frame without data. # These frames are primarily useful for specifying what a coordinate should be # transformed *into*, as they are used by the `transform_to` method # E.g., this snippet precesses the point to the new equinox newfk5 = fk5.transform_to(fk5_J2001_frame) assert newfk5.equinox == J2001 # classes can also be given to `transform_to`, which then uses the defaults for # the frame information: samefk5 = fk5.transform_to(FK5) # `fk5` was initialized using default `obstime` and `equinox`, so: assert_allclose(samefk5.ra, fk5.ra, atol=1e-10 * u.deg) assert_allclose(samefk5.dec, fk5.dec, atol=1e-10 * u.deg) # transforming to a new frame necessarily loses framespec information if that # information is not applicable to the new frame. This means transforms are not # always round-trippable: fk5_2 = FK5(ra=8 * u.hour, dec=5 * u.deg, equinox=J2001) ic_trans = fk5_2.transform_to(ICRS) # `ic_trans` does not have an `equinox`, so now when we transform back to FK5, # it's a *different* RA and Dec fk5_trans = ic_trans.transform_to(FK5) assert not allclose(fk5_2.ra, fk5_trans.ra, rtol=0, atol=1e-10 * u.deg) # But if you explicitly give the right equinox, all is fine fk5_trans_2 = fk5_2.transform_to(FK5(equinox=J2001)) assert_allclose(fk5_2.ra, fk5_trans_2.ra, rtol=0, atol=1e-10 * u.deg) # Trying to transforming a frame with no data is of course an error: with pytest.raises(ValueError): FK5(equinox=J2001).transform_to(ICRS) # To actually define a new transformation, the same scheme as in the # 0.2/0.3 coordinates framework can be re-used - a graph of transform functions # connecting various coordinate classes together. The main changes are: # 1) The transform functions now get the frame object they are transforming the # current data into. # 2) Frames with additional information need to have a way to transform between # objects of the same class, but with different framespecinfo values # An example transform function: class SomeNewSystem(BaseCoordinateFrame): pass @frame_transform_graph.transform(DynamicMatrixTransform, SomeNewSystem, FK5) def new_to_fk5(newobj, fk5frame): _ = newobj.obstime _ = fk5frame.equinox # ... build a *cartesian* transform matrix using `eq` that transforms from # the `newobj` frame as observed at `ot` to FK5 an equinox `eq` matrix = np.eye(3) return matrix
def test_representations_api(): from astropy.coordinates.representation import SphericalRepresentation, \ UnitSphericalRepresentation, PhysicsSphericalRepresentation, \ CartesianRepresentation from astropy.coordinates import Angle, Longitude, Latitude, Distance # <-----------------Classes for representation of coordinate data--------------> # These classes inherit from a common base class and internally contain Quantity # objects, which are arrays (although they may act as scalars, like numpy's # length-0 "arrays") # They can be initialized with a variety of ways that make intuitive sense. # Distance is optional. UnitSphericalRepresentation(lon=8 * u.hour, lat=5 * u.deg) UnitSphericalRepresentation(lon=8 * u.hourangle, lat=5 * u.deg) SphericalRepresentation(lon=8 * u.hourangle, lat=5 * u.deg, distance=10 * u.kpc) # In the initial implementation, the lat/lon/distance arguments to the # initializer must be in order. A *possible* future change will be to allow # smarter guessing of the order. E.g. `Latitude` and `Longitude` objects can be # given in any order. UnitSphericalRepresentation(Longitude(8, u.hour), Latitude(5, u.deg)) SphericalRepresentation(Longitude(8, u.hour), Latitude(5, u.deg), Distance(10, u.kpc)) # Arrays of any of the inputs are fine UnitSphericalRepresentation(lon=[8, 9] * u.hourangle, lat=[5, 6] * u.deg) # Default is to copy arrays, but optionally, it can be a reference UnitSphericalRepresentation(lon=[8, 9] * u.hourangle, lat=[5, 6] * u.deg, copy=False) # strings are parsed by `Latitude` and `Longitude` constructors, so no need to # implement parsing in the Representation classes UnitSphericalRepresentation(lon=Angle('2h6m3.3s'), lat=Angle('0.1rad')) # Or, you can give `Quantity`s with keywords, and they will be internally # converted to Angle/Distance c1 = SphericalRepresentation(lon=8 * u.hourangle, lat=5 * u.deg, distance=10 * u.kpc) # Can also give another representation object with the `reprobj` keyword. c2 = SphericalRepresentation.from_representation(c1) # distance, lat, and lon typically will just match in shape SphericalRepresentation(lon=[8, 9] * u.hourangle, lat=[5, 6] * u.deg, distance=[10, 11] * u.kpc) # if the inputs are not the same, if possible they will be broadcast following # numpy's standard broadcasting rules. c2 = SphericalRepresentation(lon=[8, 9] * u.hourangle, lat=[5, 6] * u.deg, distance=10 * u.kpc) assert len(c2.distance) == 2 # when they can't be broadcast, it is a ValueError (same as Numpy) with pytest.raises(ValueError): c2 = UnitSphericalRepresentation(lon=[8, 9, 10] * u.hourangle, lat=[5, 6] * u.deg) # It's also possible to pass in scalar quantity lists with mixed units. These # are converted to array quantities following the same rule as `Quantity`: all # elements are converted to match the first element's units. c2 = UnitSphericalRepresentation( lon=Angle([8 * u.hourangle, 135 * u.deg]), lat=Angle([5 * u.deg, (6 * np.pi / 180) * u.rad])) assert c2.lat.unit == u.deg and c2.lon.unit == u.hourangle npt.assert_almost_equal(c2.lon[1].value, 9) # The Quantity initializer itself can also be used to force the unit even if the # first element doesn't have the right unit lon = u.Quantity([120 * u.deg, 135 * u.deg], u.hourangle) lat = u.Quantity([(5 * np.pi / 180) * u.rad, 0.4 * u.hourangle], u.deg) c2 = UnitSphericalRepresentation(lon, lat) # regardless of how input, the `lat` and `lon` come out as angle/distance assert isinstance(c1.lat, Angle) assert isinstance( c1.lat, Latitude) # `Latitude` is an `~astropy.coordinates.Angle` subclass assert isinstance(c1.distance, Distance) # but they are read-only, as representations are immutable once created with pytest.raises(AttributeError): c1.lat = Latitude(5, u.deg) # Note that it is still possible to modify the array in-place, but this is not # sanctioned by the API, as this would prevent things like caching. c2.lat[:] = [0] * u.deg # possible, but NOT SUPPORTED # To address the fact that there are various other conventions for how spherical # coordinates are defined, other conventions can be included as new classes. # Later there may be other conventions that we implement - for now just the # physics convention, as it is one of the most common cases. _ = PhysicsSphericalRepresentation(phi=120 * u.deg, theta=85 * u.deg, r=3 * u.kpc) # first dimension must be length-3 if a lone `Quantity` is passed in. c1 = CartesianRepresentation(np.random.randn(3, 100) * u.kpc) assert c1.xyz.shape[0] == 3 assert c1.xyz.unit == u.kpc assert c1.x.shape[0] == 100 assert c1.y.shape[0] == 100 assert c1.z.shape[0] == 100 # can also give each as separate keywords CartesianRepresentation(x=np.random.randn(100) * u.kpc, y=np.random.randn(100) * u.kpc, z=np.random.randn(100) * u.kpc) # if the units don't match but are all distances, they will automatically be # converted to match `x` xarr, yarr, zarr = np.random.randn(3, 100) c1 = CartesianRepresentation(x=xarr * u.kpc, y=yarr * u.kpc, z=zarr * u.kpc) c2 = CartesianRepresentation(x=xarr * u.kpc, y=yarr * u.kpc, z=zarr * u.pc) assert c1.xyz.unit == c2.xyz.unit == u.kpc assert_allclose((c1.z / 1000) - c2.z, 0 * u.kpc, atol=1e-10 * u.kpc) # representations convert into other representations via `represent_as` srep = SphericalRepresentation(lon=90 * u.deg, lat=0 * u.deg, distance=1 * u.pc) crep = srep.represent_as(CartesianRepresentation) assert_allclose(crep.x, 0 * u.pc, atol=1e-10 * u.pc) assert_allclose(crep.y, 1 * u.pc, atol=1e-10 * u.pc) assert_allclose(crep.z, 0 * u.pc, atol=1e-10 * u.pc)
def test_frame_api(): from astropy.coordinates.representation import SphericalRepresentation, \ UnitSphericalRepresentation from astropy.coordinates.builtin_frames import ICRS, FK5 # <--------------------Reference Frame/"Low-level" classes---------------------> # The low-level classes have a dual role: they act as specifiers of coordinate # frames and they *may* also contain data as one of the representation objects, # in which case they are the actual coordinate objects themselves. # They can always accept a representation as a first argument icrs = ICRS(UnitSphericalRepresentation(lon=8 * u.hour, lat=5 * u.deg)) # which is stored as the `data` attribute assert icrs.data.lat == 5 * u.deg assert icrs.data.lon == 8 * u.hourangle # Frames that require additional information like equinoxs or obstimes get them # as keyword parameters to the frame constructor. Where sensible, defaults are # used. E.g., FK5 is almost always J2000 equinox fk5 = FK5(UnitSphericalRepresentation(lon=8 * u.hour, lat=5 * u.deg)) J2000 = time.Time('J2000') fk5_2000 = FK5(UnitSphericalRepresentation(lon=8 * u.hour, lat=5 * u.deg), equinox=J2000) assert fk5.equinox == fk5_2000.equinox # the information required to specify the frame is immutable J2001 = time.Time('J2001') with pytest.raises(AttributeError): fk5.equinox = J2001 # Similar for the representation data. with pytest.raises(AttributeError): fk5.data = UnitSphericalRepresentation(lon=8 * u.hour, lat=5 * u.deg) # There is also a class-level attribute that lists the attributes needed to # identify the frame. These include attributes like `equinox` shown above. assert all(nm in ('equinox', 'obstime') for nm in fk5.get_frame_attr_names()) # the result of `get_frame_attr_names` is called for particularly in the # high-level class (discussed below) to allow round-tripping between various # frames. It is also part of the public API for other similar developer / # advanced users' use. # The actual position information is accessed via the representation objects assert_allclose(icrs.represent_as(SphericalRepresentation).lat, 5 * u.deg) # shorthand for the above assert_allclose(icrs.spherical.lat, 5 * u.deg) assert icrs.cartesian.z.value > 0 # Many frames have a "default" representation, the one in which they are # conventionally described, often with a special name for some of the # coordinates. E.g., most equatorial coordinate systems are spherical with RA and # Dec. This works simply as a shorthand for the longer form above assert_allclose(icrs.dec, 5 * u.deg) assert_allclose(fk5.ra, 8 * u.hourangle) assert icrs.representation_type == SphericalRepresentation # low-level classes can also be initialized with names valid for that representation # and frame: icrs_2 = ICRS(ra=8 * u.hour, dec=5 * u.deg, distance=1 * u.kpc) assert_allclose(icrs.ra, icrs_2.ra) # and these are taken as the default if keywords are not given: # icrs_nokwarg = ICRS(8*u.hour, 5*u.deg, distance=1*u.kpc) # assert icrs_nokwarg.ra == icrs_2.ra and icrs_nokwarg.dec == icrs_2.dec # they also are capable of computing on-sky or 3d separations from each other, # which will be a direct port of the existing methods: coo1 = ICRS(ra=0 * u.hour, dec=0 * u.deg) coo2 = ICRS(ra=0 * u.hour, dec=1 * u.deg) # `separation` is the on-sky separation assert coo1.separation(coo2).degree == 1.0 # while `separation_3d` includes the 3D distance information coo3 = ICRS(ra=0 * u.hour, dec=0 * u.deg, distance=1 * u.kpc) coo4 = ICRS(ra=0 * u.hour, dec=0 * u.deg, distance=2 * u.kpc) assert coo3.separation_3d(coo4).kpc == 1.0 # The next example fails because `coo1` and `coo2` don't have distances with pytest.raises(ValueError): assert coo1.separation_3d(coo2).kpc == 1.0
def test_sphere_cart(): """ Tests the spherical <-> cartesian transform functions """ from astropy.utils import NumpyRNGContext from astropy.coordinates import spherical_to_cartesian, cartesian_to_spherical x, y, z = spherical_to_cartesian(1, 0, 0) assert_allclose(x, 1) assert_allclose(y, 0) assert_allclose(z, 0) x, y, z = spherical_to_cartesian(0, 1, 1) assert_allclose(x, 0) assert_allclose(y, 0) assert_allclose(z, 0) x, y, z = spherical_to_cartesian(5, 0, np.arcsin(4. / 5.)) assert_allclose(x, 3) assert_allclose(y, 4) assert_allclose(z, 0) r, lat, lon = cartesian_to_spherical(0, 1, 0) assert_allclose(r, 1) assert_allclose(lat, 0 * u.deg) assert_allclose(lon, np.pi / 2 * u.rad) # test round-tripping with NumpyRNGContext(13579): x, y, z = np.random.randn(3, 5) r, lat, lon = cartesian_to_spherical(x, y, z) x2, y2, z2 = spherical_to_cartesian(r, lat, lon) assert_allclose(x, x2) assert_allclose(y, y2) assert_allclose(z, z2)
def check_obsgeo(self, obsgeoloc, obsgeovel): assert_allclose(obsgeoloc.xyz, self.obsgeoloc.xyz, atol=.1*u.um, rtol=0.) assert_allclose(obsgeovel.xyz, self.obsgeovel.xyz, atol=.1*u.mm/u.s, rtol=0.)
def test_precessedgeocentric_loopback(): from_coo = PrecessedGeocentric(1 * u.deg, 2 * u.deg, 3 * u.AU, obstime='2001-01-01', equinox='2001-01-01') # Change just the obstime to_frame = PrecessedGeocentric(obstime='2001-06-30', equinox='2001-01-01') 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 allclose(explicit_coo.ra, from_coo.ra, rtol=1e-10) assert not allclose(explicit_coo.dec, from_coo.dec, rtol=1e-10) assert not allclose(explicit_coo.distance, from_coo.distance, rtol=1e-10) # Confirm that the loopback matches the explicit transformation assert_allclose(explicit_coo.ra, implicit_coo.ra, rtol=1e-10) assert_allclose(explicit_coo.dec, implicit_coo.dec, rtol=1e-10) assert_allclose(explicit_coo.distance, implicit_coo.distance, rtol=1e-10) # Change just the equinox to_frame = PrecessedGeocentric(obstime='2001-01-01', 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 direction but not the distance assert not allclose(explicit_coo.ra, from_coo.ra, rtol=1e-10) assert not allclose(explicit_coo.dec, from_coo.dec, rtol=1e-10) assert allclose(explicit_coo.distance, from_coo.distance, rtol=1e-10) # Confirm that the loopback matches the explicit transformation assert_allclose(explicit_coo.ra, implicit_coo.ra, rtol=1e-10) assert_allclose(explicit_coo.dec, implicit_coo.dec, rtol=1e-10) assert_allclose(explicit_coo.distance, implicit_coo.distance, rtol=1e-10)
def test_gcrs_cirs(): """ Check GCRS<->CIRS transforms for round-tripping. More complicated than the above two because it's multi-hop """ ra, dec, _ = randomly_sample_sphere(200) gcrs = GCRS(ra=ra, dec=dec, obstime='J2000') gcrs6 = GCRS(ra=ra, dec=dec, obstime='J2006') gcrs2 = gcrs.transform_to(CIRS).transform_to(gcrs) gcrs6_2 = gcrs6.transform_to(CIRS).transform_to(gcrs) assert_allclose(gcrs.ra, gcrs2.ra) assert_allclose(gcrs.dec, gcrs2.dec) assert not allclose(gcrs.ra, gcrs6_2.ra) assert not allclose(gcrs.dec, gcrs6_2.dec) # now try explicit intermediate pathways and ensure they're all consistent gcrs3 = gcrs.transform_to(ITRS).transform_to(CIRS).transform_to( ITRS).transform_to(gcrs) assert_allclose(gcrs.ra, gcrs3.ra) assert_allclose(gcrs.dec, gcrs3.dec) gcrs4 = gcrs.transform_to(ICRS).transform_to(CIRS).transform_to( ICRS).transform_to(gcrs) assert_allclose(gcrs.ra, gcrs4.ra) assert_allclose(gcrs.dec, gcrs4.dec)
def test_icrs_gcrs(icoo): """ Check ICRS<->GCRS for consistency """ gcrscoo = icoo.transform_to(gcrs_frames[0]) # uses the default time # first do a round-tripping test icoo2 = gcrscoo.transform_to(ICRS()) assert_allclose(icoo.distance, icoo2.distance) assert_allclose(icoo.ra, icoo2.ra) assert_allclose(icoo.dec, icoo2.dec) assert isinstance(icoo2.data, icoo.data.__class__) # now check that a different time yields different answers gcrscoo2 = icoo.transform_to(gcrs_frames[1]) assert not allclose(gcrscoo.ra, gcrscoo2.ra, rtol=1e-8, atol=1e-10 * u.deg) assert not allclose( gcrscoo.dec, gcrscoo2.dec, rtol=1e-8, atol=1e-10 * u.deg) # now check that the cirs self-transform works as expected gcrscoo3 = gcrscoo.transform_to(gcrs_frames[0]) # should be a no-op assert_allclose(gcrscoo.ra, gcrscoo3.ra) assert_allclose(gcrscoo.dec, gcrscoo3.dec) gcrscoo4 = gcrscoo.transform_to(gcrs_frames[1]) # should be different assert not allclose(gcrscoo4.ra, gcrscoo.ra, rtol=1e-8, atol=1e-10 * u.deg) assert not allclose( gcrscoo4.dec, gcrscoo.dec, rtol=1e-8, atol=1e-10 * u.deg) gcrscoo5 = gcrscoo4.transform_to( gcrs_frames[0]) # should be back to the same assert_allclose(gcrscoo.ra, gcrscoo5.ra, rtol=1e-8, atol=1e-10 * u.deg) assert_allclose(gcrscoo.dec, gcrscoo5.dec, rtol=1e-8, atol=1e-10 * u.deg) # also make sure that a GCRS with a different geoloc/geovel gets a different answer # roughly a moon-like frame gframe3 = GCRS(obsgeoloc=[385000., 0, 0] * u.km, obsgeovel=[1, 0, 0] * u.km / u.s) gcrscoo6 = icoo.transform_to(gframe3) # should be different assert not allclose(gcrscoo.ra, gcrscoo6.ra, rtol=1e-8, atol=1e-10 * u.deg) assert not allclose( gcrscoo.dec, gcrscoo6.dec, rtol=1e-8, atol=1e-10 * u.deg) icooviag3 = gcrscoo6.transform_to(ICRS()) # and now back to the original assert_allclose(icoo.ra, icooviag3.ra) assert_allclose(icoo.dec, icooviag3.dec)
def test_icrs_cirs(): """ Check a few cases of ICRS<->CIRS for consistency. Also includes the CIRS<->CIRS transforms at different times, as those go through ICRS """ ra, dec, dist = randomly_sample_sphere(200) inod = ICRS(ra=ra, dec=dec) iwd = ICRS(ra=ra, dec=dec, distance=dist * u.pc) cframe1 = CIRS() cirsnod = inod.transform_to(cframe1) # uses the default time # first do a round-tripping test inod2 = cirsnod.transform_to(ICRS) assert_allclose(inod.ra, inod2.ra) assert_allclose(inod.dec, inod2.dec) # now check that a different time yields different answers cframe2 = CIRS(obstime=Time('J2005', scale='utc')) cirsnod2 = inod.transform_to(cframe2) assert not allclose(cirsnod.ra, cirsnod2.ra, rtol=1e-8) assert not allclose(cirsnod.dec, cirsnod2.dec, rtol=1e-8) # parallax effects should be included, so with and w/o distance should be different cirswd = iwd.transform_to(cframe1) assert not allclose(cirswd.ra, cirsnod.ra, rtol=1e-8) assert not allclose(cirswd.dec, cirsnod.dec, rtol=1e-8) # and the distance should transform at least somehow assert not allclose(cirswd.distance, iwd.distance, rtol=1e-8) # now check that the cirs self-transform works as expected cirsnod3 = cirsnod.transform_to(cframe1) # should be a no-op assert_allclose(cirsnod.ra, cirsnod3.ra) assert_allclose(cirsnod.dec, cirsnod3.dec) cirsnod4 = cirsnod.transform_to(cframe2) # should be different assert not allclose(cirsnod4.ra, cirsnod.ra, rtol=1e-8) assert not allclose(cirsnod4.dec, cirsnod.dec, rtol=1e-8) cirsnod5 = cirsnod4.transform_to(cframe1) # should be back to the same assert_allclose(cirsnod.ra, cirsnod5.ra) assert_allclose(cirsnod.dec, cirsnod5.dec)
def test_highlevel_api(): J2001 = time.Time('J2001') # <--------------------------"High-level" class--------------------------------> # The "high-level" class is intended to wrap the lower-level classes in such a # way that they can be round-tripped, as well as providing a variety of # convenience functionality. This document is not intended to show *all* of the # possible high-level functionality, rather how the high-level classes are # initialized and interact with the low-level classes # this creates an object that contains an `ICRS` low-level class, initialized # identically to the first ICRS example further up. sc = coords.SkyCoord(coords.SphericalRepresentation(lon=8 * u.hour, lat=5 * u.deg, distance=1 * u.kpc), frame='icrs') # Other representations and `system` keywords delegate to the appropriate # low-level class. The already-existing registry for user-defined coordinates # will be used by `SkyCoordinate` to figure out what various the `system` # keyword actually means. sc = coords.SkyCoord(ra=8 * u.hour, dec=5 * u.deg, frame='icrs') sc = coords.SkyCoord(l=120 * u.deg, b=5 * u.deg, frame='galactic') # High-level classes can also be initialized directly from low-level objects sc = coords.SkyCoord(coords.ICRS(ra=8 * u.hour, dec=5 * u.deg)) # The next example raises an error because the high-level class must always # have position data. with pytest.raises(ValueError): sc = coords.SkyCoord(coords.FK5(equinox=J2001)) # raises ValueError # similarly, the low-level object can always be accessed # this is how it's supposed to look, but sometimes the numbers get rounded in # funny ways # assert repr(sc.frame) == '<ICRS Coordinate: ra=120.0 deg, dec=5.0 deg>' rscf = repr(sc.frame) assert rscf.startswith('<ICRS Coordinate: (ra, dec) in deg') # and the string representation will be inherited from the low-level class. # same deal, should loook like this, but different archituectures/ python # versions may round the numbers differently # assert repr(sc) == '<SkyCoord (ICRS): ra=120.0 deg, dec=5.0 deg>' rsc = repr(sc) assert rsc.startswith('<SkyCoord (ICRS): (ra, dec) in deg') # Supports a variety of possible complex string formats sc = coords.SkyCoord('8h00m00s +5d00m00.0s', frame='icrs') # In the next example, the unit is only needed b/c units are ambiguous. In # general, we *never* accept ambiguity sc = coords.SkyCoord('8:00:00 +5:00:00.0', unit=(u.hour, u.deg), frame='icrs') # The next one would yield length-2 array coordinates, because of the comma sc = coords.SkyCoord(['8h 5d', '2°2′3″ 0.3rad'], frame='icrs') # It should also interpret common designation styles as a coordinate # NOT YET # sc = coords.SkyCoord('SDSS J123456.89-012345.6', frame='icrs') # but it should also be possible to provide formats for outputting to strings, # similar to `Time`. This can be added right away or at a later date. # transformation is done the same as for low-level classes, which it delegates to sc_fk5_j2001 = sc.transform_to(coords.FK5(equinox=J2001)) assert sc_fk5_j2001.equinox == J2001 # The key difference is that the high-level class remembers frame information # necessary for round-tripping, unlike the low-level classes: sc1 = coords.SkyCoord(ra=8 * u.hour, dec=5 * u.deg, equinox=J2001, frame='fk5') sc2 = sc1.transform_to('icrs') # The next assertion succeeds, but it doesn't mean anything for ICRS, as ICRS # isn't defined in terms of an equinox assert sc2.equinox == J2001 # But it *is* necessary once we transform to FK5 sc3 = sc2.transform_to('fk5') assert sc3.equinox == J2001 assert_allclose(sc1.ra, sc3.ra) # `SkyCoord` will also include the attribute-style access that is in the # v0.2/0.3 coordinate objects. This will *not* be in the low-level classes sc = coords.SkyCoord(ra=8 * u.hour, dec=5 * u.deg, frame='icrs') scgal = sc.galactic assert str(scgal).startswith('<SkyCoord (Galactic): (l, b)') # the existing `from_name` and `match_to_catalog_*` methods will be moved to the # high-level class as convenience functionality. # in remote-data test below! # m31icrs = coords.SkyCoord.from_name('M31', frame='icrs') # assert str(m31icrs) == '<SkyCoord (ICRS) RA=10.68471 deg, Dec=41.26875 deg>' if HAS_SCIPY: cat1 = coords.SkyCoord(ra=[1, 2] * u.hr, dec=[3, 4.01] * u.deg, distance=[5, 6] * u.kpc, frame='icrs') cat2 = coords.SkyCoord(ra=[1, 2, 2.01] * u.hr, dec=[3, 4, 5] * u.deg, distance=[5, 200, 6] * u.kpc, frame='icrs') idx1, sep2d1, dist3d1 = cat1.match_to_catalog_sky(cat2) idx2, sep2d2, dist3d2 = cat1.match_to_catalog_3d(cat2) assert np.any(idx1 != idx2)
def test_search_around(): from astropy.coordinates import ICRS, SkyCoord from astropy.coordinates.matching import search_around_sky, search_around_3d coo1 = ICRS([4, 2.1] * u.degree, [0, 0] * u.degree, distance=[1, 5] * u.kpc) coo2 = ICRS([1, 2, 3, 4] * u.degree, [0, 0, 0, 0] * u.degree, distance=[1, 1, 1, 5] * u.kpc) idx1_1deg, idx2_1deg, d2d_1deg, d3d_1deg = search_around_sky( coo1, coo2, 1.01 * u.deg) idx1_0p05deg, idx2_0p05deg, d2d_0p05deg, d3d_0p05deg = search_around_sky( coo1, coo2, 0.05 * u.deg) assert list(zip(idx1_1deg, idx2_1deg)) == [(0, 2), (0, 3), (1, 1), (1, 2)] assert d2d_1deg[0] == 1.0 * u.deg assert_allclose(d2d_1deg, [1, 0, .1, .9] * u.deg) assert list(zip(idx1_0p05deg, idx2_0p05deg)) == [(0, 3)] idx1_1kpc, idx2_1kpc, d2d_1kpc, d3d_1kpc = search_around_3d( coo1, coo2, 1 * u.kpc) idx1_sm, idx2_sm, d2d_sm, d3d_sm = search_around_3d( coo1, coo2, 0.05 * u.kpc) assert list(zip(idx1_1kpc, idx2_1kpc)) == [(0, 0), (0, 1), (0, 2), (1, 3)] assert list(zip(idx1_sm, idx2_sm)) == [(0, 1), (0, 2)] assert_allclose(d2d_sm, [2, 1] * u.deg) # Test for the non-matches, #4877 coo1 = ICRS([4.1, 2.1] * u.degree, [0, 0] * u.degree, distance=[1, 5] * u.kpc) idx1, idx2, d2d, d3d = search_around_sky(coo1, coo2, 1 * u.arcsec) assert idx1.size == idx2.size == d2d.size == d3d.size == 0 assert idx1.dtype == idx2.dtype == np.int assert d2d.unit == u.deg assert d3d.unit == u.kpc idx1, idx2, d2d, d3d = search_around_3d(coo1, coo2, 1 * u.m) assert idx1.size == idx2.size == d2d.size == d3d.size == 0 assert idx1.dtype == idx2.dtype == np.int assert d2d.unit == u.deg assert d3d.unit == u.kpc # Test when one or both of the coordinate arrays is empty, #4875 empty = ICRS(ra=[] * u.degree, dec=[] * u.degree, distance=[] * u.kpc) idx1, idx2, d2d, d3d = search_around_sky(empty, coo2, 1 * u.arcsec) assert idx1.size == idx2.size == d2d.size == d3d.size == 0 assert idx1.dtype == idx2.dtype == np.int assert d2d.unit == u.deg assert d3d.unit == u.kpc idx1, idx2, d2d, d3d = search_around_sky(coo1, empty, 1 * u.arcsec) assert idx1.size == idx2.size == d2d.size == d3d.size == 0 assert idx1.dtype == idx2.dtype == np.int assert d2d.unit == u.deg assert d3d.unit == u.kpc empty = ICRS(ra=[] * u.degree, dec=[] * u.degree, distance=[] * u.kpc) idx1, idx2, d2d, d3d = search_around_sky(empty, empty[:], 1 * u.arcsec) assert idx1.size == idx2.size == d2d.size == d3d.size == 0 assert idx1.dtype == idx2.dtype == np.int assert d2d.unit == u.deg assert d3d.unit == u.kpc idx1, idx2, d2d, d3d = search_around_3d(empty, coo2, 1 * u.m) assert idx1.size == idx2.size == d2d.size == d3d.size == 0 assert idx1.dtype == idx2.dtype == np.int assert d2d.unit == u.deg assert d3d.unit == u.kpc idx1, idx2, d2d, d3d = search_around_3d(coo1, empty, 1 * u.m) assert idx1.size == idx2.size == d2d.size == d3d.size == 0 assert idx1.dtype == idx2.dtype == np.int assert d2d.unit == u.deg assert d3d.unit == u.kpc idx1, idx2, d2d, d3d = search_around_3d(empty, empty[:], 1 * u.m) assert idx1.size == idx2.size == d2d.size == d3d.size == 0 assert idx1.dtype == idx2.dtype == np.int assert d2d.unit == u.deg assert d3d.unit == u.kpc # Test that input without distance units results in a # 'dimensionless_unscaled' unit cempty = SkyCoord(ra=[], dec=[], unit=u.deg) idx1, idx2, d2d, d3d = search_around_3d(cempty, cempty[:], 1 * u.m) assert d2d.unit == u.deg assert d3d.unit == u.dimensionless_unscaled idx1, idx2, d2d, d3d = search_around_sky(cempty, cempty[:], 1 * u.m) assert d2d.unit == u.deg assert d3d.unit == u.dimensionless_unscaled