
====================================
 Skyfield’s Accuracy and Efficiency
====================================

This document is a work in progress,
that will be expanded into a full guide.
Right now it covers only one topic.

-----------------------
Precession and Nutation
-----------------------

As explained in the section on
:ref:`Positions:Apparent right ascension and declination`,
Skyfield uses the IAU 2000A precession-nutation model
to determine the orientation of the Earth’s axes on any given date.
This is used in:

* Apparent right ascension and declination
  as returned by :meth:`~skyfield.positionlib.ICRF.radec()`.

* Geographic positions generated by an Earth ellipsoid
  like the :data:`~skyfield.toposlib.wgs84` model.

* The :class:`~skyfield.framelib.true_equator_and_equinox_of_date`
  reference frame that can be used to generate |xyz| coordinates
  as described in the :doc:`coordinates` chapter.

* The ITRS reference frame
  offered through the :data:`~skyfield.framelib.itrs` object,
  which combines the orientation of the poles and equinox
  with the daily rotation of the Earth.

In case you ever need to do low-level math of your own,
each time object offers a matrix ``t.M``
that rotates coordinates from the ICRS
into the Earth equatorial coordinate system of that date and time.
See the section on :ref:`Coordinates:Rotation Matrices`
for a guide to using a rotation matrix.

.. _polar-motion:

------------
Polar Motion
------------

It was discovered more than a century ago
that the Earth’s crust has a slight freedom
to wobble with respect to the Earth’s axis of rotation in space,
because the continents and ocean basis are bound to the planet’s mass
only through the fluid coupling of our planet’s viscous mantle.
In Skyfield you can see the size of the effect
by loading an official data file
from the International Earth Rotation Service (IERS)
and measuring the maximum excursions
of the polar motion parameters *x* and *y*:

.. testcode::

    from skyfield.api import load
    from skyfield.data import iers

    url = load.build_url('finals2000A.all')
    with load.open(url) as f:
        finals_data = iers.parse_x_y_dut1_from_finals_all(f)

    ts = load.timescale()
    iers.install_polar_motion_table(ts, finals_data)

    tt, x_arcseconds, y_arcseconds = ts.polar_motion_table
    print('x:', max(abs(x_arcseconds)), 'arcseconds')
    print('y:', max(abs(y_arcseconds)), 'arcseconds')

.. testoutput::

    x: 0.32548 arcseconds
    y: 0.596732 arcseconds

In what kinds of Skyfield calculations
does the exact position of the Earth’s crust come into play?

At the most basic level,
the two polar motion rotations
are applied directly to the ITRS reference frame
in :data:`skyfield.framelib.itrs`,
supplementing the changes in the Earth’s orientation
that Skyfield was already expecting
thanks to its IAU 2000A precession and nutation models.
This, in turn, slightly affects:

* The position of an observer on the Earth’s surface.

* The relative position of a target with
  respect to an observer on the Earth’s surface.

* All alt-azimuth coordinates,
  since the polar angles *x* and *y* tilt the zenith and local horizon
  against which altitude and azimuth are measured for a particular observer.

* The position of any observation target
  that’s located on the Earth’s surface —
  for example, if you are calculating the position of a ground station
  from the perspective of a space probe.

To have Skyfield apply polar motion when computing positions and coordinates,
simply install the IERS tables on your timescale object
as shown in the example code above.
Polar motion will be used everywhere that it applies.

.. _the-leap-second-table:

---------------------
The leap second table
---------------------

If you want to double-check that Skyfield’s leap second table
agrees with other tools or software,
you can easily print it out.
Each timescale object offers an array of Julian dates ``leap_dates``
and another array of the same length named ``leap_offsets``
that offers the difference between UTC and TAI in seconds:

.. testcode::

    ts = load.timescale()
    for jd, offset in zip(ts.leap_dates, ts.leap_offsets):
        ymd = ts.tt_jd(jd).tt_strftime('%Y-%m-%d')
        print(jd, ymd, '{:+}'.format(int(offset)))

.. testoutput::

    2441499.5 1972-07-01 +11
    2441683.5 1973-01-01 +12
    2442048.5 1974-01-01 +13
    ...
    2456109.5 2012-07-01 +35
    2457204.5 2015-07-01 +36
    2457754.5 2017-01-01 +37

Note that each leap second occurs just before
the Julian date given in the table.
Taking the second row as an example,
the offset between TAI and UTC increased to +12
at the first moment of the day 1973-01-01.
It did so because that day was immediately preceded by a leap second
that was attached to the *previous* day as its final second.
So the leap second followed the normal, non-leap second 1972-12-31 12:59:59
and had the special designation 1972-12-31 12:59:60.

----------------------------------
Bad performance and using 100% CPU
----------------------------------

On some systems,
users `have reported
<https://github.com/skyfielders/python-skyfield/issues/595>`_
that Skyfield consumes 100% of all of their CPUs
and makes it difficult to do other work.

This isn’t something that Skyfield has direct control over.
It’s the underlying NumPy library
that decides how to perform each of the math operations
that Skyfield requests.
And in this case,
the user’s installed version of NumPy
was deciding to run a vector operation in parallel across all the CPUs.
(Ironically, this made the operation slower!)

In case you find NumPy misbehaving in the same way on your system,
the user reported that they were able to force single-threaded behavior
by setting this environment variable::

    export OPENBLAS_NUM_THREADS=1
    export MKL_NUM_THREADS=1

If you want to apply these settings right in your Python code,
then you must modify these environment settings
*before* importing NumPy::

    # First set up the environment.

    import os
    os.environ['OPENBLAS_NUM_THREADS'] = '1'
    os.environ['MKL_NUM_THREADS'] = '1'

    # And only then import NumPy.

    import numpy

The same solution might work on your system.
