def cirs_to_gcrs(time): # compute the polar motion p-matrix xp, yp = get_polar_motion(time) sp = erfa.sp00(*get_jd12(time, "tt")) pmmat = erfa.pom00(xp, yp, sp) # now determine the Earth Rotation Angle for the input obstime # era00 accepts UT1, so we convert if need be era = erfa.era00(*get_jd12(time, "ut1")) # c2tcio expects a GCRS->CIRS matrix, but we just set that to an I-matrix # because we're already in CIRS return erfa.c2tcio(np.eye(3), era, pmmat)
def apparent_longitude(t='now'): """ Returns the Sun's apparent longitude, referred to the true equinox of date. Corrections for nutation and aberration (for Earth motion) are included. Parameters ---------- t : {parse_time_types} A time (usually the start time) specified as a parse_time-compatible time string, number, or a datetime object. Notes ----- The nutation model is IAU 2000A nutation with adjustments to match IAU 2006 precession. """ time = parse_time(t) sun = SkyCoord(0*u.deg, 0*u.deg, 0*u.AU, frame='hcrs', obstime=time) coord = sun.transform_to(GeocentricMeanEcliptic(equinox=time)) # Astropy's GeocentricMeanEcliptic already includes aberration, so only add nutation jd1, jd2 = get_jd12(time, 'tt') nut_lon, _ = erfa.nut06a(jd1, jd2)*u.radian lon = coord.lon + nut_lon return Longitude(lon)
def delta_obs_ra_to_cirs_ra(mjd, longitude, latitude): """ difference between radio astronomers observed coords and CIRS coords Args ==== mjd: (float) modified Julian date longitude: (float) in degrees or as astropy Angle latitude: (float) in degrees or as astropy Angle Return ====== hour angle as astropy.coordinates.Angle """ from astropy import _erfa as erfa from astropy.coordinates.builtin_frames.utils import get_jd12 # get the Earth Rotation Angle era = erfa.era00(*get_jd12(APt.Time(mjd, format='mjd'), 'ut1')) logger.debug("delta_obs_ra_to_cirs_ra: ERA = %f rad", era) logger.debug("delta_obs_ra_to_cirs_ra: ERA = %s", format_angles(era * 12 / pi)) theta_earth = APc.Angle(era, unit='rad') # get the time of observation obs_time = APt.Time(mjd, format='mjd', location=(APc.Angle(longitude, unit="deg"), APc.Angle(latitude, unit="deg"))) # Greenwich apparent sidereal time of observation gast = obs_time.sidereal_time('apparent', longitude=0) # Greenwich ST logger.debug("delta_obs_ra_to_cirs_ra: GAST = %s", gast) logger.debug("delta_obs_ra_to_cirs_ra: delta = %s", gast - theta_earth) return (gast - theta_earth)
def velocity_of_Earth(full_BJD): """ Calculate 3D velocity of Earth for given epoch. If you need velocity projected on the plane of the sky, then use :py:func:`~MulensModel.coordinates.Coordinates.v_Earth_projected` Parameters : full_BJD: *float* Are we fitting for blending flux? If not then blending flux is fixed to 0. Default is the same as :py:func:`MulensModel.fit.Fit.fit_fluxes()`. Returns : velocity: *np.ndarray* (*float*, size of (3,)) 3D velocity in km/s. The frame follows *Astropy* conventions. """ # The 4 lines below, that calculate velocity for given epoch, # are based on astropy 1.3 code: # https://github.com/astropy/astropy/blob/master/astropy/ # coordinates/solar_system.py time = Time(full_BJD, format='jd', scale='tdb') (jd1, jd2) = get_jd12(time, 'tdb') (earth_pv_helio, earth_pv_bary) = erfa.epv00(jd1, jd2) factor = 1731.45683 # This scales AU/day to km/s. # The returned values are of np.ndarray type in astropy v1 and v2, # but np.void in v3. The np.asarray() works in both cases. velocity = np.asarray(earth_pv_bary[1]) * factor return velocity
def _cirs_to_tee_ra(cirs_ra, time): """ Convert from CIRS frame to the true equator & equinox frame. The frame radio astronomers call the apparent or current epoch is the "true equator & equinox" frame, notated E_upsilon in the USNO circular astropy doesn't have this frame but it's pretty easy to adapt the CIRS frame by modifying the ra to reflect the difference between GAST (Grenwich Apparent Sidereal Time) and the earth rotation angle (theta) Parameters ---------- cirs_ra : :class:`astropy.Angle` CIRS RA. time : :class:`astropy.Time` Time object for time to convert to the "true equator & equinox" frame. """ era = erfa.era00(*get_jd12(time, "ut1")) theta_earth = Angle(era, unit="rad") assert isinstance(time, Time) assert isinstance(cirs_ra, Angle) gast = time.sidereal_time("apparent", longitude=0) tee_ra = cirs_ra + (gast - theta_earth) return tee_ra
def test_tete_quick(self): # Following copied from intermediate_rotation_transforms.gcrs_to_tete rbpn = erfa.pnm06a(*get_jd12(self.obstime, 'tt')) loc_gcrs_frame = get_location_gcrs( self.loc, self.obstime, tete_to_itrs_mat(self.obstime, rbpn=rbpn), rbpn) self.check_obsgeo(loc_gcrs_frame.obsgeoloc, loc_gcrs_frame.obsgeovel)
def _obliquity_rotation_value(equinox): """ Function to calculate obliquity of the earth. This uses obl06 of erfa. """ jd1, jd2 = get_jd12(equinox, "tt") obl = erfa.obl06(jd1, jd2) * u.radian return obl.to(u.deg)
def _apparent_position_in_true_coordinates(skycoord): """ Convert Skycoord in GCRS frame into one in which RA and Dec are defined w.r.t to the true equinox and poles of the Earth """ jd1, jd2 = get_jd12(skycoord.obstime, 'tt') _, _, _, _, _, _, _, rbpn = erfa.pn00a(jd1, jd2) return SkyCoord( skycoord.frame.realize_frame(skycoord.cartesian.transform(rbpn)))
def test_atciqz_aticq(t, pos): """Check replacements against erfa versions for consistency.""" jd1, jd2 = get_jd12(t, 'tdb') astrom, _ = erfa.apci13(jd1, jd2) ra = pos.lon.to_value(u.rad) dec = pos.lat.to_value(u.rad) assert_allclose(erfa.atciqz(ra, dec, astrom), atciqz(pos, astrom)) assert_allclose(erfa.aticq(ra, dec, astrom), aticq(pos, astrom))
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 cirs_to_tee_ra(cirs_ra, time): from astropy import _erfa as erfa from astropy.coordinates.builtin_frames.utils import get_jd12 era = erfa.era00(*get_jd12(time, 'ut1')) theta_earth = Angle(era, unit='rad') assert (isinstance(time, Time)) assert (isinstance(cirs_ra, Angle)) gast = time.sidereal_time('apparent', longitude=0) tee_ra = cirs_ra + (gast - theta_earth) return tee_ra
def test_icrs_altaz_moonish(testframe): """ Check that something expressed in *ICRS* as being moon-like goes to the right AltAz distance """ # we use epv00 instead of get_sun because get_sun includes aberration earth_pv_helio, earth_pv_bary = epv00(*get_jd12(testframe.obstime, 'tdb')) earth_icrs_xyz = earth_pv_bary[0]*u.au moonoffset = [0, 0, MOONDIST.value]*MOONDIST.unit moonish_icrs = ICRS(CartesianRepresentation(earth_icrs_xyz + moonoffset)) moonaa = moonish_icrs.transform_to(testframe) # now check that the distance change is similar to earth radius assert 1000*u.km < np.abs(moonaa.distance - MOONDIST).to(u.au) < 7000*u.km
def mean_obliquity_of_ecliptic(t='now'): """ Returns the mean obliquity of the ecliptic, using the IAU 2006 definition. No correction for nutation is included. Parameters ---------- t : {parse_time_types} Time to use in a parse-time-compatible format """ time = parse_time(t) jd1, jd2 = get_jd12(time, 'tt') obl = erfa.obl06(jd1, jd2) * u.radian return Angle(obl, u.arcsec)
def test_icrs_altaz_moonish(testframe): """ Check that something expressed in *ICRS* as being moon-like goes to the right AltAz distance """ # we use epv00 instead of get_sun because get_sun includes aberration earth_pv_helio, earth_pv_bary = erfa.epv00(*get_jd12(testframe.obstime, 'tdb')) earth_icrs_xyz = earth_pv_bary[0]*u.au moonoffset = [0, 0, MOONDIST.value]*MOONDIST.unit moonish_icrs = ICRS(CartesianRepresentation(earth_icrs_xyz + moonoffset)) moonaa = moonish_icrs.transform_to(testframe) # now check that the distance change is similar to earth radius assert 1000*u.km < np.abs(moonaa.distance - MOONDIST).to(u.au) < 7000*u.km
def mean_obliquity_of_ecliptic(t='now'): """ Returns the mean obliquity of the ecliptic, using the IAU 2006 definition. No correction for nutation is included. Parameters ---------- t : {parse_time_types} A time (usually the start time) specified as a parse_time-compatible time string, number, or a datetime object. """ time = parse_time(t) jd1, jd2 = get_jd12(time, 'tt') obl = erfa.obl06(jd1, jd2)*u.radian return Angle(obl, u.arcsec)
def _gmst82_angle(obstime): """ Universal Time to Greenwich mean sidereal time (IAU 1982 model). Parameters ---------- obstime : Time time at which the polar motion should be calculated. Returns ------- float Greenwich mean sidereal time (radians) """ # Get GMST82 angle in rad gmst82 = erfa.gmst82(*get_jd12(obstime, 'ut1')) * u.rad return gmst82
def true_obliquity_of_ecliptic(t='now'): """ Returns the true obliquity of the ecliptic, using the IAU 2006 definition. Correction for nutation is included. Parameters ---------- t : {parse_time_types} Time to use in a parse-time-compatible format Notes ----- The nutation model is IAU 2000A nutation with adjustments to match IAU 2006 precession. """ time = parse_time(t) jd1, jd2 = get_jd12(time, 'tt') obl = erfa.obl06(jd1, jd2) * u.radian _, nut_obl = erfa.nut06a(jd1, jd2) * u.radian obl += nut_obl return Angle(obl, u.arcsec)
def _polar_mot_matrix(obstime): """ Form the matrix of polar motion for a given date, IAU 2000. The matrix operates in the sense V(TRS) = rpom * V(CIP), meaning that it is the final rotation when computing the pointing direction to a celestial source. Parameters ---------- obstime : Time time at which the polar motion should be calculated. Returns ------- 3x3 rotation matrix due to polar motion """ # compute the polar motion p-matrix xp, yp = get_polar_motion(obstime) sp = erfa.sp00(*get_jd12(obstime, 'tt')) polar_mot_mat = erfa.pom00(xp, yp, sp) return polar_mot_mat
def true_obliquity_of_ecliptic(t='now'): """ Returns the true obliquity of the ecliptic, using the IAU 2006 definition. Correction for nutation is included. Parameters ---------- t : {parse_time_types} A time (usually the start time) specified as a parse_time-compatible time string, number, or a datetime object. Notes ----- The nutation model is IAU 2000A nutation with adjustments to match IAU 2006 precession. """ time = parse_time(t) jd1, jd2 = get_jd12(time, 'tt') obl = erfa.obl06(jd1, jd2)*u.radian _, nut_obl = erfa.nut06a(jd1, jd2)*u.radian obl += nut_obl return Angle(obl, u.arcsec)
def _rotation_matrix_obliquity(time): """ Return the rotation matrix from Earth equatorial to ecliptic coordinates """ return rotation_matrix(erfa.obl06(*get_jd12(time, 'tt'))*u.radian, 'x')
def _ecliptic_rotation_matrix(): jd1, jd2 = get_jd12(J2000, J2000.scale) obl = _erfa.obl80(jd1, jd2) * u.radian assert obl.to(u.arcsec).value == 84381.448 return rotation_matrix(obl, "x")
def _ecliptic_rotation_matrix(): jd1, jd2 = get_jd12(J2000, J2000.scale) obl = erfa.obl80(jd1, jd2) * u.radian assert obl.to(u.arcsec).value == 84381.448 return rotation_matrix(obl, "x")
def test_iau_fullstack(fullstack_icrs, fullstack_fiducial_altaz, fullstack_times, fullstack_locations, fullstack_obsconditions): """ Test the full transform from ICRS <-> AltAz """ # create the altaz frame altazframe = AltAz(obstime=fullstack_times, location=fullstack_locations, pressure=fullstack_obsconditions[0], temperature=fullstack_obsconditions[1], relative_humidity=fullstack_obsconditions[2], obswl=fullstack_obsconditions[3]) aacoo = fullstack_icrs.transform_to(altazframe) # compare aacoo to the fiducial AltAz - should always be different assert np.all( np.abs(aacoo.alt - fullstack_fiducial_altaz.alt) > 50 * u.milliarcsecond) assert np.all( np.abs(aacoo.az - fullstack_fiducial_altaz.az) > 50 * u.milliarcsecond) # if the refraction correction is included, we *only* do the comparisons # where altitude >5 degrees. The SOFA guides imply that below 5 is where # where accuracy gets more problematic, and testing reveals that alt<~0 # gives garbage round-tripping, and <10 can give ~1 arcsec uncertainty if fullstack_obsconditions[0].value == 0: # but if there is no refraction correction, check everything msk = slice(None) tol = 5 * u.microarcsecond else: msk = aacoo.alt > 5 * u.deg # most of them aren't this bad, but some of those at low alt are offset # this much. For alt > 10, this is always better than 100 masec tol = 750 * u.milliarcsecond # now make sure the full stack round-tripping works icrs2 = aacoo.transform_to(ICRS) adras = np.abs(fullstack_icrs.ra - icrs2.ra)[msk] addecs = np.abs(fullstack_icrs.dec - icrs2.dec)[msk] assert np.all(adras < tol), 'largest RA change is {} mas, > {}'.format( np.max(adras.arcsec * 1000), tol) assert np.all(addecs < tol), 'largest Dec change is {} mas, > {}'.format( np.max(addecs.arcsec * 1000), tol) # check that we're consistent with the ERFA alt/az result iers_tab = iers.earth_orientation_table.get() xp, yp = u.Quantity(iers_tab.pm_xy(fullstack_times)).to_value(u.radian) lon = fullstack_locations.geodetic[0].to_value(u.radian) lat = fullstack_locations.geodetic[1].to_value(u.radian) height = fullstack_locations.geodetic[2].to_value(u.m) jd1, jd2 = get_jd12(fullstack_times, 'utc') pressure = fullstack_obsconditions[0].to_value(u.hPa) temperature = fullstack_obsconditions[1].to_value(u.deg_C) # Relative humidity can be a quantity or a number. relative_humidity = u.Quantity(fullstack_obsconditions[2], u.one).value obswl = fullstack_obsconditions[3].to_value(u.micron) astrom, eo = erfa.apco13(jd1, jd2, fullstack_times.delta_ut1_utc, lon, lat, height, xp, yp, pressure, temperature, relative_humidity, obswl) erfadct = _erfa_check(fullstack_icrs.ra.rad, fullstack_icrs.dec.rad, astrom) npt.assert_allclose(erfadct['alt'], aacoo.alt.radian, atol=1e-7) npt.assert_allclose(erfadct['az'], aacoo.az.radian, atol=1e-7)
def _get_body_barycentric_posvel(body, time, ephemeris=None, get_velocity=True): """Calculate the barycentric position (and velocity) of a solar system body. Parameters ---------- body : str or other The solar system body for which to calculate positions. Can also be a kernel specifier (list of 2-tuples) if the ``ephemeris`` is a JPL kernel. time : `~astropy.time.Time` Time of observation. ephemeris : str, optional Ephemeris to use. By default, use the one set with ``astropy.coordinates.solar_system_ephemeris.set`` get_velocity : bool, optional Whether or not to calculate the velocity as well as the position. Returns ------- position : `~astropy.coordinates.CartesianRepresentation` or tuple Barycentric (ICRS) position or tuple of position and velocity. Notes ----- No velocity can be calculated with the built-in ephemeris for the Moon. Whether or not velocities are calculated makes little difference for the built-in ephemerides, but for most JPL ephemeris files, the execution time roughly doubles. """ if ephemeris is None: ephemeris = solar_system_ephemeris.get() if ephemeris is None: raise ValueError(_EPHEMERIS_NOTE) kernel = solar_system_ephemeris.kernel else: kernel = _get_kernel(ephemeris) jd1, jd2 = get_jd12(time, 'tdb') if kernel is None: body = body.lower() earth_pv_helio, earth_pv_bary = erfa.epv00(jd1, jd2) if body == 'earth': body_pv_bary = earth_pv_bary elif body == 'moon': if get_velocity: raise KeyError("the Moon's velocity cannot be calculated with " "the '{0}' ephemeris.".format(ephemeris)) return calc_moon(time).cartesian else: sun_pv_bary = erfa.pvmpv(earth_pv_bary, earth_pv_helio) if body == 'sun': body_pv_bary = sun_pv_bary else: try: body_index = PLAN94_BODY_NAME_TO_PLANET_INDEX[body] except KeyError: raise KeyError( "{0}'s position and velocity cannot be " "calculated with the '{1}' ephemeris.".format( body, ephemeris)) body_pv_helio = erfa.plan94(jd1, jd2, body_index) body_pv_bary = erfa.pvppv(body_pv_helio, sun_pv_bary) body_pos_bary = CartesianRepresentation(body_pv_bary['p'], unit=u.au, xyz_axis=-1, copy=False) if get_velocity: body_vel_bary = CartesianRepresentation(body_pv_bary['v'], unit=u.au / u.day, xyz_axis=-1, copy=False) else: if isinstance(body, str): # Look up kernel chain for JPL ephemeris, based on name try: kernel_spec = BODY_NAME_TO_KERNEL_SPEC[body.lower()] except KeyError: raise KeyError("{0}'s position cannot be calculated with " "the {1} ephemeris.".format(body, ephemeris)) else: # otherwise, assume the user knows what their doing and intentionally # passed in a kernel chain kernel_spec = body # jplephem cannot handle multi-D arrays, so convert to 1D here. jd1_shape = getattr(jd1, 'shape', ()) if len(jd1_shape) > 1: jd1, jd2 = jd1.ravel(), jd2.ravel() # Note that we use the new jd1.shape here to create a 1D result array. # It is reshaped below. body_posvel_bary = np.zeros((2 if get_velocity else 1, 3) + getattr(jd1, 'shape', ())) for pair in kernel_spec: spk = kernel[pair] if spk.data_type == 3: # Type 3 kernels contain both position and velocity. posvel = spk.compute(jd1, jd2) if get_velocity: body_posvel_bary += posvel.reshape(body_posvel_bary.shape) else: body_posvel_bary[0] += posvel[:4] else: # spk.generate first yields the position and then the # derivative. If no velocities are desired, body_posvel_bary # has only one element and thus the loop ends after a single # iteration, avoiding the velocity calculation. for body_p_or_v, p_or_v in zip(body_posvel_bary, spk.generate(jd1, jd2)): body_p_or_v += p_or_v body_posvel_bary.shape = body_posvel_bary.shape[:2] + jd1_shape body_pos_bary = CartesianRepresentation(body_posvel_bary[0], unit=u.km, copy=False) if get_velocity: body_vel_bary = CartesianRepresentation(body_posvel_bary[1], unit=u.km / u.day, copy=False) return (body_pos_bary, body_vel_bary) if get_velocity else body_pos_bary
def test_iau_fullstack(fullstack_icrs, fullstack_fiducial_altaz, fullstack_times, fullstack_locations, fullstack_obsconditions): """ Test the full transform from ICRS <-> AltAz """ # create the altaz frame altazframe = AltAz(obstime=fullstack_times, location=fullstack_locations, pressure=fullstack_obsconditions[0], temperature=fullstack_obsconditions[1], relative_humidity=fullstack_obsconditions[2], obswl=fullstack_obsconditions[3]) aacoo = fullstack_icrs.transform_to(altazframe) # compare aacoo to the fiducial AltAz - should always be different assert np.all(np.abs(aacoo.alt - fullstack_fiducial_altaz.alt) > 50*u.milliarcsecond) assert np.all(np.abs(aacoo.az - fullstack_fiducial_altaz.az) > 50*u.milliarcsecond) # if the refraction correction is included, we *only* do the comparisons # where altitude >5 degrees. The SOFA guides imply that below 5 is where # where accuracy gets more problematic, and testing reveals that alt<~0 # gives garbage round-tripping, and <10 can give ~1 arcsec uncertainty if fullstack_obsconditions[0].value == 0: # but if there is no refraction correction, check everything msk = slice(None) tol = 5*u.microarcsecond else: msk = aacoo.alt > 5*u.deg # most of them aren't this bad, but some of those at low alt are offset # this much. For alt > 10, this is always better than 100 masec tol = 750*u.milliarcsecond # now make sure the full stack round-tripping works icrs2 = aacoo.transform_to(ICRS) adras = np.abs(fullstack_icrs.ra - icrs2.ra)[msk] addecs = np.abs(fullstack_icrs.dec - icrs2.dec)[msk] assert np.all(adras < tol), 'largest RA change is {0} mas, > {1}'.format(np.max(adras.arcsec*1000), tol) assert np.all(addecs < tol), 'largest Dec change is {0} mas, > {1}'.format(np.max(addecs.arcsec*1000), tol) # check that we're consistent with the ERFA alt/az result xp, yp = u.Quantity(iers.IERS_Auto.open().pm_xy(fullstack_times)).to_value(u.radian) lon = fullstack_locations.geodetic[0].to_value(u.radian) lat = fullstack_locations.geodetic[1].to_value(u.radian) height = fullstack_locations.geodetic[2].to_value(u.m) jd1, jd2 = get_jd12(fullstack_times, 'utc') pressure = fullstack_obsconditions[0].to_value(u.hPa) temperature = fullstack_obsconditions[1].to_value(u.deg_C) # Relative humidity can be a quantity or a number. relative_humidity = u.Quantity(fullstack_obsconditions[2], u.one).value obswl = fullstack_obsconditions[3].to_value(u.micron) astrom, eo = erfa.apco13(jd1, jd2, fullstack_times.delta_ut1_utc, lon, lat, height, xp, yp, pressure, temperature, relative_humidity, obswl) erfadct = _erfa_check(fullstack_icrs.ra.rad, fullstack_icrs.dec.rad, astrom) npt.assert_allclose(erfadct['alt'], aacoo.alt.radian, atol=1e-7) npt.assert_allclose(erfadct['az'], aacoo.az.radian, atol=1e-7)