You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Add support for Heliocentric and Barycentric Julian Dates #2244
The tentative conclusion there was to support Heliocentric and Barycentric Julian Dates via utility functions that accept a Time and Coordinate object.
If we're willing to simply make this a utility function which accepts a Time and Coordinate and returns a corrected Time, then this should be relatively straightforward. I agree with @eteq that using some of the abstractions in coordinates should allow decoupling the Time and Coordinate. Basically BJD and HJD appear to be low-precision standards, so you should just be able to transform the coordinate to some reasonable frame (ICRS) and compute a correction.
Yep, Earth location is definitely necessary here. But that's also going to be necessary at least in part for implementing the IAU2000 transformation stack, so it might be possible to use the ERFA pieces to do that. Even if not then, we'll eventually want a more complete interface to solar system ephemerides anyway.
But I'm tagging this as "future" for now, and we can re-tag it something firm once we've got pre-requisites in place.
@taldcroft, @eteq - just saw this and the astropy-dev. I like the idea of providing utility functions since that doesn't commit us to an API for the classes yet (which needs some thought). I'm slightly wary of the "low precision" aspect, though, I think we should at least do (something like) the Stumpff (1980, A&AS 41, 1 [1]) algorithm, which also includes barycentric velocity. Ideally, of course, we do better and use the JPL ephemeris... It should be possible to lift at least the time aspect out of PINT [2] and/or jplephem [3](cc @scottransom, @demorest).
Overall, my sense would be for now to refer people to jplephem (or some lower precision equivalent) and for astropy aim to do it right from the get go, at full precision (as for Time and, soon, Coordinates). So, I'd suggest to first finish coordinates, then use those for a skeleton EarthLocation class (#1928), use that in Time, and then implement the proper JPL ephemeris.
@mhvk's plan sounds promising to me. The remaining question is if we want to change Time to use a utility function (with lat and lon keywords until we have EarthLocation) for TDB now so that people don't start depending on the current Time.lon and Time.lat approach...?
I don't know what the status of this issue is, or what the best way of contributing code to astropy is. However, I had to do this for my own work, and it was relatively easy following the work that has been done on erfa. I proceeded by sub-classing time.Time as shown in the code below to add helper functions that accept an EarthLocation object. In my tests of random points of the sky, it seems to agree with TEMPO2 answers to a few tens of microseconds.
I imagine it will need much work to include in the astropy project, but I thought I'd paste it here so you can use it if you wish
fromastropyimporttimefromastropyimportconstantsasconstfromastropyimportunitsasufromastropy.utils.iersimportIERSfromastropy.utils.iersimportIERS_A, IERS_A_URLfromastropy.utils.dataimportdownload_filefromastropy.ioimportasciifromastropyimportcoordinatesascoordfromastropyimporterfaimportnumpyasnpimportwarnings# mean sidereal rate (at J2000) in radians per (UT1) secondSR=7.292115855306589e-5''' time class: inherits astropy time object, and adds heliocentric, barycentric correction utilities.'''classTime(time.Time):
def__init__(self,*args,**kwargs):
super(Time, self).__init__(*args, **kwargs)
self.height=kwargs.get('height',0.0)
def_pvobs(self):
'''calculates position and velocity of the observatory returns position/velocity in AU and AU/d in GCRS reference frame '''# convert obs position from WGS84 (lat long) to ITRF geocentric coords in AUxyz=self.location.to(u.AU).value# now we need to convert this position to Celestial Coords# specifically, the GCRS coords.# conversion from celestial to terrestrial coords given by# [TRS] = RPOM * R_3(ERA) * RC2I * [CRS]# where:# [CRS] is vector in GCRS (geocentric celestial system)# [TRS] is vector in ITRS (International Terrestrial Ref System)# ERA is earth rotation angle# RPOM = polar motion matrixtt=self.ttmjd=self.utc.mjd# we need the IERS values to correct for the precession/nutation of the Earthiers_tab=IERS.open()
# Find UT1, which is needed to calculate ERA# uses IERS_B by default , for more recent times use IERS_A downloadtry:
ut1=self.ut1except:
try:
iers_a_file=download_file(IERS_A_URL, cache=True)
iers_a=IERS_A.open(iers_a_file)
self.delta_ut1_utc=self.get_delta_ut1_utc(iers_a)
ut1=self.ut1except:
# fall back to UTC with degraded accuracywarnings.warn('Cannot calculate UT1: using UTC with degraded accuracy')
ut1=self.utc# Gets x,y coords of Celestial Intermediate Pole (CIP) and CIO locator s# CIO = Celestial Intermediate Origin# Both in GCRSX,Y,S=erfa.xys00a(tt.jd1,tt.jd2)
# Get dX and dY from IERS BdX=np.interp(mjd, iers_tab['MJD'], iers_tab['dX_2000A']) *u.arcsecdY=np.interp(mjd, iers_tab['MJD'], iers_tab['dY_2000A']) *u.arcsec# Get GCRS to CIRS matrix# can be used to convert to Celestial Intermediate Ref Sys# from GCRS.rc2i=erfa.c2ixys(X+dX.to(u.rad).value, Y+dY.to(u.rad).value, S)
# Gets the Terrestrial Intermediate Origin (TIO) locator s'# Terrestrial Intermediate Ref Sys (TIRS) defined by TIO and CIP.# TIRS related to to CIRS by Earth Rotation Anglesp=erfa.sp00(tt.jd1,tt.jd2)
# Get X and Y from IERS B# X and Y arexp=np.interp(mjd, iers_tab['MJD'], iers_tab['PM_x']) *u.arcsecyp=np.interp(mjd, iers_tab['MJD'], iers_tab['PM_y']) *u.arcsec# Get the polar motion matrix. Relates ITRF to TIRS.rpm=erfa.pom00(xp.to(u.rad).value, yp.to(u.rad).value, sp)
# multiply ITRF position of obs by transpose of polar motion matrix# Gives Intermediate Ref Frame position of obsx,y,z=np.array([rpmMat.T.dot(xyz) forrpmMatinrpm]).T# Functions of Earth Rotation Angle, theta# Theta is angle bewtween TIO and CIO (along CIP)# USE UT1 here.theta=erfa.era00(ut1.jd1,ut1.jd2)
S,C=np.sin(theta),np.cos(theta)
# Position #GOT HEREpos=np.asarray([C*x-S*y, S*x+C*y, z]).T# multiply by inverse of GCRS to CIRS matrix# different methods for scalar times vs arraysifpos.ndim>1:
pos=np.array([np.dot(rc2i[j].T,pos[j]) forjinrange(len(pos))])
else:
pos=np.dot(rc2i.T,pos)
# Velocityvel=np.asarray([SR*(-S*x-C*y), SR*(C*x-S*y), np.zeros_like(x)]).T# multiply by inverse of GCRS to CIRS matrixifvel.ndim>1:
vel=np.array([np.dot(rc2i[j].T,vel[j]) forjinrange(len(pos))])
else:
vel=np.dot(rc2i.T,vel)
#return position and velocityreturnpos,veldef_obs_pos(self):
'''calculates heliocentric and barycentric position of the earth in AU and AU/d'''tdb=self.tdb# get heliocentric and barycentric position and velocity of Earth# BCRS reference frameh_pv,b_pv=erfa.epv00(tdb.jd1,tdb.jd2)
# h_pv etc can be shape (ntimes,2,3) or (2,3) if given a scalar timeifh_pv.ndim==2:
h_pv=h_pv[np.newaxis,:]
ifb_pv.ndim==2:
b_pv=b_pv[np.newaxis,:]
# unpack into position and velocity arraysh_pos=h_pv[:,0,:]
h_vel=h_pv[:,1,:]
# unpack into position and velocity arraysb_pos=b_pv[:,0,:]
b_vel=b_pv[:,1,:]
#now need position and velocity of observing stationpos_obs, vel_obs=self._pvobs()
#add this to heliocentric and barycentric position of center of Earthh_pos+=pos_obsb_pos+=pos_obsh_vel+=vel_obsb_vel+=vel_obsreturn (h_pos,h_vel,b_pos,b_vel)
def_vect(self,coord):
'''get unit vector pointing to star, and modulus of vector, in AU coordinate of star supplied as astropy.coordinate object assume zero proper motion, parallax and radial velocity'''pmra=pmdec=px=rv=0.0rar=coord.ra.radiandecr=coord.dec.radianwithwarnings.catch_warnings():
warnings.simplefilter("ignore")
# ignore warnings about 0 parallaxpos,vel=erfa.starpv(rar,decr,pmra,pmdec,px,rv)
modulus=np.sqrt(pos.dot(pos))
unit=pos/modulusmodulus/=const.au.valuereturnmodulus, unitdefhcor(self,coord):
mod, spos=self._vect(coord)
# get helio/bary-centric position and velocity of observatory, in AU, AU/dh_pos,h_vel,b_pos,b_vel=self._obs_pos()
# heliocentric light travel time, stcor_hel=const.au.value*np.array([np.dot(spos,hpos) forhposinh_pos]) /const.c.value#print 'Correction to add to get time at heliocentre = %.7f s' % tcor_heldt=time.TimeDelta(tcor_hel, format='sec', scale='tdb')
returnself.utc+dtdefbcor(self,coord):
mod, spos=self._vect(coord)
# get helio/bary-centric position and velocity of observatory, in AU, AU/dh_pos,h_vel,b_pos,b_vel=self._obs_pos()
# barycentric light travel time, stcor_bar=const.au.value*np.array([np.dot(spos,bpos) forbposinb_pos]) /const.c.value#print 'Correction to add to get time at barycentre = %.7f s' % tcor_bardt=time.TimeDelta(tcor_bar, format='sec', scale='tdb')
returnself.tdb+dt
@StuartLittlefair - that looks great! I'd very much like to include something along these lines in Time. I just checked epv00 and it is consistent with the JPL ephemeris within 11.2 km = 4micro-sec and 5 mm/s. I think this suffices for most applications, including RV planet work, and for those for which it is not good enough (e.g., pulsar timing), we can later include the option to work with the JPL ephemerides (without having those huge files be part of astropy!).
@eteq - how far along are you for the transformation to the various intermediate schemes? Presumably, your work includes quite a bit of this...
@StuartLittlefair - just a small comment -- height is already part of EarthLocation, so you would not need to override __init__ -- but perhaps this is just a leftover from earlier days?
@mhvk indeed, this is just a hangover. Thanks for pointing this out - the height property is unused anyway, so there's no need to override init. The code should probably have some assertions in there to raise errors when the time object does not have a location property though.
Has anyone yet formed a strong opinion about the need to develop a coherent framework for this? The problem is that a barycentric/heliocentric corrected time really has a position attached so it might be considered ideal implement this together with a framework that supports velocities, proper motions etc.
However, it's also an option to add a utility function to the time package that simply accepts a Time object and possibly an EarthLocation and returns a Time object in an appropriate scale. The location attributes for this new Time object would be nonsense of course.
If people are happy with the latter approach I can have a go at implementing this. It will be my first contribution to a big collaborative project though, so go easy on me...
@StuartLittlefair - I'm good with doing this as utility functions like you have outlined, probably putting this into a new module within the time subpackage and exporting the new public function(s). If it ever comes to pass that a grander framework is developed that includes this functionality then we can slowly deprecate the functional interface.
@StuartLittlefair - Help would be great! I think the first thing would be to split the problem up into pieces -- as your sample code above does already:
We need something to convert EarthLocation into a position relative to the solar system barycentre or the Sun. I think this may already be possible within coordinates, since the same would seem to be needed to support heliocentric ecliptic coordinates. @eteq? Otherwise, this might for now be a utility function that replaces your _obs_pos method.
Same for unit vector to object. My sense would be that coordinates supports this already too... @eteq?
The remainder is quite trivial, of course, and easiest may be to have a Time method that takes a SkyCoord instance as import and uses the functions in (1) and (2), returning a time or velocity offset (i.e., a Quantity), just like it is now possible to get a sidereal time. Similarly, or perhaps alternatively, one may want to have SkyCoord methods that takes a Time as an input.
With all the above in place, we might want to consider having Time support more general locations (not only EarthLocation). Since the corrections depend on the source one looked at, this doesn't seem trivial, and probably is of much lower priority.
The nice aspect of the separation that you already have, is that it should be relatively straightforward to substitute erfa approximations in (1) and (2) for functions that use the full JPL ephemerides.
Anyway, in the end I think @taldcroft is right in that the best way to start is with some utility functions, though arguably best within coordinates rather than time. Maybe @eteq can point us to what exists already -- by prodding around in the transformations, we may at the same time be able to help address #4268...
@mhvk - I agree that looks like a very sensible way forward.
With a few changes to the coordinates package I think the point 1 is quite straightforward. I'm not sure about 2, but as you say from that point on it is quite trivial, and just a question of API design.
Activity
taldcroft commentedon Mar 27, 2014
If we're willing to simply make this a utility function which accepts a Time and Coordinate and returns a corrected Time, then this should be relatively straightforward. I agree with @eteq that using some of the abstractions in coordinates should allow decoupling the Time and Coordinate. Basically BJD and HJD appear to be low-precision standards, so you should just be able to transform the coordinate to some reasonable frame (ICRS) and compute a correction.
I guess we need an Earth ephemeris though.
eteq commentedon Mar 27, 2014
Yep, Earth location is definitely necessary here. But that's also going to be necessary at least in part for implementing the IAU2000 transformation stack, so it might be possible to use the ERFA pieces to do that. Even if not then, we'll eventually want a more complete interface to solar system ephemerides anyway.
But I'm tagging this as "future" for now, and we can re-tag it something firm once we've got pre-requisites in place.
mhvk commentedon Mar 28, 2014
@taldcroft, @eteq - just saw this and the astropy-dev. I like the idea of providing utility functions since that doesn't commit us to an API for the classes yet (which needs some thought). I'm slightly wary of the "low precision" aspect, though, I think we should at least do (something like) the Stumpff (1980, A&AS 41, 1 [1]) algorithm, which also includes barycentric velocity. Ideally, of course, we do better and use the JPL ephemeris... It should be possible to lift at least the time aspect out of PINT [2] and/or jplephem [3](cc @scottransom, @demorest).
Overall, my sense would be for now to refer people to jplephem (or some lower precision equivalent) and for astropy aim to do it right from the get go, at full precision (as for
Timeand, soon,Coordinates). So, I'd suggest to first finish coordinates, then use those for a skeletonEarthLocationclass (#1928), use that inTime, and then implement the proper JPL ephemeris.[1] http://adsabs.harvard.edu/abs/1980A%26AS...41....1S
[2] https://github.com/nanograv/PINT
[3] https://github.com/brandon-rhodes/python-jplephem/
eteq commentedon Mar 28, 2014
@mhvk's plan sounds promising to me. The remaining question is if we want to change
Timeto use a utility function (withlatandlonkeywords until we haveEarthLocation) for TDB now so that people don't start depending on the currentTime.lonandTime.latapproach...?eteq commentedon Jul 11, 2014
I rated this "package-intermediate" if we go the utility function route, but it might be "package-expert" if it ends up being better done as a method
mhvk commentedon Jul 11, 2014
Probably, a function initially is best. I fear it is more than an intermediate amount of work, though, at least for the DE450 type of precision.
StuartLittlefair commentedon Nov 28, 2014
I don't know what the status of this issue is, or what the best way of contributing code to astropy is. However, I had to do this for my own work, and it was relatively easy following the work that has been done on erfa. I proceeded by sub-classing time.Time as shown in the code below to add helper functions that accept an EarthLocation object. In my tests of random points of the sky, it seems to agree with TEMPO2 answers to a few tens of microseconds.
I imagine it will need much work to include in the astropy project, but I thought I'd paste it here so you can use it if you wish
mhvk commentedon Nov 28, 2014
@StuartLittlefair - that looks great! I'd very much like to include something along these lines in
Time. I just checkedepv00and it is consistent with the JPL ephemeris within 11.2 km = 4micro-sec and 5 mm/s. I think this suffices for most applications, including RV planet work, and for those for which it is not good enough (e.g., pulsar timing), we can later include the option to work with the JPL ephemerides (without having those huge files be part of astropy!).@eteq - how far along are you for the transformation to the various intermediate schemes? Presumably, your work includes quite a bit of this...
Cadair commentedon Nov 28, 2014
ping @dpshelio
mhvk commentedon Nov 28, 2014
@StuartLittlefair - just a small comment --
heightis already part ofEarthLocation, so you would not need to override__init__-- but perhaps this is just a leftover from earlier days?StuartLittlefair commentedon Nov 28, 2014
@mhvk indeed, this is just a hangover. Thanks for pointing this out - the height property is unused anyway, so there's no need to override init. The code should probably have some assertions in there to raise errors when the time object does not have a location property though.
taldcroft commentedon Nov 2, 2015
Summary:
StuartLittlefair commentedon Nov 3, 2015
Has anyone yet formed a strong opinion about the need to develop a coherent framework for this? The problem is that a barycentric/heliocentric corrected time really has a position attached so it might be considered ideal implement this together with a framework that supports velocities, proper motions etc.
However, it's also an option to add a utility function to the time package that simply accepts a
Timeobject and possibly anEarthLocationand returns aTimeobject in an appropriate scale. The location attributes for this newTimeobject would be nonsense of course.If people are happy with the latter approach I can have a go at implementing this. It will be my first contribution to a big collaborative project though, so go easy on me...
taldcroft commentedon Nov 3, 2015
@StuartLittlefair - I'm good with doing this as utility functions like you have outlined, probably putting this into a new module within the
timesubpackage and exporting the new public function(s). If it ever comes to pass that a grander framework is developed that includes this functionality then we can slowly deprecate the functional interface.mhvk commentedon Nov 3, 2015
@StuartLittlefair - Help would be great! I think the first thing would be to split the problem up into pieces -- as your sample code above does already:
EarthLocationinto a position relative to the solar system barycentre or the Sun. I think this may already be possible within coordinates, since the same would seem to be needed to support heliocentric ecliptic coordinates. @eteq? Otherwise, this might for now be a utility function that replaces your_obs_posmethod.Timemethod that takes aSkyCoordinstance as import and uses the functions in (1) and (2), returning a time or velocity offset (i.e., aQuantity), just like it is now possible to get a sidereal time. Similarly, or perhaps alternatively, one may want to haveSkyCoordmethods that takes aTimeas an input.Timesupport more general locations (not onlyEarthLocation). Since the corrections depend on the source one looked at, this doesn't seem trivial, and probably is of much lower priority.The nice aspect of the separation that you already have, is that it should be relatively straightforward to substitute
erfaapproximations in (1) and (2) for functions that use the full JPL ephemerides.Anyway, in the end I think @taldcroft is right in that the best way to start is with some utility functions, though arguably best within
coordinatesrather thantime. Maybe @eteq can point us to what exists already -- by prodding around in the transformations, we may at the same time be able to help address #4268...StuartLittlefair commentedon Nov 3, 2015
@mhvk - I agree that looks like a very sensible way forward.
With a few changes to the coordinates package I think the point 1 is quite straightforward. I'm not sure about 2, but as you say from that point on it is quite trivial, and just a question of API design.