.. _`Explanation`: Explanation =========== This section is to explain pulsar timing, how PINT works, and why it is built the way it is. PINT is used for pulsar timing and related activities. Some of it may make a lot more sense if you know something about the science it is used for. You can find an excellent introduction in the Handbook of Pulsar Astronomy, by Lorimer and Kramer. This document is aimed at using PINT specifically, and may also be more understandable if you have used other pulsar timing software, TEMPO_ or TEMPO2_, though we hope that you will find PINT sufficient for all your needs! .. _TEMPO: http://tempo.sourceforge.net/ .. _TEMPO2: https://www.atnf.csiro.au/research/pulsar/tempo2/ Time ---- With modern instrumentation, we are able to measure time - both time intervals and an absolute time scale - to stupendous accuracy. Pulsar timing is a powerful tool in large part because it takes advantage of that accuracy. Getting time measurements and calculations right to this level of accuracy does require a certain amount of care, in general and while using (and writing) PINT. Precision ''''''''' The first challenge that arises is numerical precision. Computers necessarily represent real numbers to finite precision. Python, in particular, uses floating-point numbers that occupy 64 bits, 11 of which encode the exponent and 53 of which encode the mantissa. This means that numbers are represented with a little less than 16 decimal digits of precision:: >>> import numpy as np >>> np.finfo(float).eps 2.220446049250313e-16 >>> 1 + np.finfo(float).eps 1.0000000000000002 >>> 1 + np.finfo(float).eps/2 1.0 >>> 1 + np.finfo(float).eps/2 == 1 True Unfortunately, we have observations spanning decades and we would often like to work with time measurements at the nanosecond level. It turns out that python's floating-point numbers simply don't have the precision we need for this:: >>> import astropy.units as u >>> (10*u.year*np.finfo(float).eps).to(u.ns) That is, if I want to represent a ten-year span, the smallest increment python's floating-point can cope with is about 70 nanoseconds - not enough for accurate pulsar timing work! There are a number of ways to approach this problem, all somewhat awkward in python. One approach of interest is that ``numpy`` provides floating-point types, for example, ``numpy.longdouble``, with more precision:: >>> np.finfo(np.longdouble).eps 1.084202172485504434e-19 >>> (10*u.year*np.finfo(np.longdouble).eps).to(u.ns) These numbers are represented with 80 bits, and most desktop and server machines have hardware for computing with these numbers, so they are not much slower than ordinary ("double-precision", 64-bit) floating-point numbers. Let me warn you about one point of possible confusion: modern computers have very complicated cache setups that prefer data to be aligned just so in memory, so ``numpy`` generally pads these numbers out with zeroes and stores them in larger memory spaces. Thus you will often see ``np.float96`` and ``np.float128`` types; these contain only numbers with 80-bit precision. Actual 128-bit precision is not currently available in ``numpy``, in part because on almost all current machines all calculations must be carried out in software, which takes 20-50 times as long. An alternative approach to dealing with more precision than your machine's floating-point numbers natively support is to represent numbers as a pair of double-precision values, with the second providing additional digits of precision to the first. These are generically called double-double numbers, and can be faster than "proper" 128-bit floating-point numbers. Sadly these are not implemented in ``numpy`` either. But because it is primarily *time* that requires such precision, ``astropy`` provides a type ``astropy.time.Time`` (and ``astropy.time.TimeDelta``) that uses a similar representation internally: two floating-point numbers, one of which is the integer number of days (in the Julian-Day_ system) and one of which is the fractional day. This allows very satisfactory precision:: >>> (1*u.day*np.finfo(float).eps).to(u.ns) >>> t = astropy.time.Time("2019-08-19", format="iso") >>> t