← Astronomy 17 / 18

Precision

A badly computed chart does not look wrong. Move Mars three degrees and the wheel comes out just as pretty, the aspects just as plausible, and nobody looking at it has any way to notice. That is the whole reason this chapter exists: precision here is not a number in a README, it is a class that asks JPL Horizons the same question the engine just answered itself, live, and prints how far apart the two came out.

Asking Horizons directly

HorizonsCheck::run() is that class, and vendor/bin/astronomy check is the command around it:

use Astronomy\HorizonsCheck;

$report = HorizonsCheck::run(from: '1900-01-01', to: '2050-01-01');

// $report['rows'] is one entry per body, each carrying its own worst longitude and latitude
// difference and a 'status' of 'ok', 'out', 'absent' (no Horizons identifier) or 'silent'
// (Horizons did not answer). $report['worst'] and $report['failures'] summarise the run.

It is deliberately not part of the test suite, which never touches the network at all: the numbers this whole chapter reports were measured once, by hand, and then fixed into the tests so the suite stays reproducible without anybody's connection. Reproducing them yourself is one command:

vendor/bin/astronomy check

That asks Horizons for every body the engine knows, in Terrestrial Time so neither side's delta T gets blamed for the other's, and prints a row per body:

body                 dates   worst long    worst lat   verdict
mercury                 16     0.118"      0.007"   ok
moon                    16     0.122"      0.006"   ok
ceres                   16     0.135"      0.006"   ok
…
Worst difference: 0.135 arcseconds (tolerance 1.000").

Two things are worth reading in that method's own source rather than taking on faith. Asking in Terrestrial Time is not a detail: Horizons converts to Universal Time with its own delta T and this engine with its own, and outside the well-observed twentieth century that difference alone can dwarf the thing actually being measured, which has happened here before. And the bodies with no Horizons identifier at all, the lunar nodes, Lilith and the eight bodies of the Hamburg school, report absent rather than being silently skipped or, worse, resolved to some unrelated numbered asteroid that happens to share their name.

What the numbers say

worst difference
All bodies, 1600 to 2400 0.18″
All bodies, twentieth century 0.04″
Median, twentieth century 0.002″
Fixed stars against their catalogue positions 0.04″ at 2000, 0.3″ at 1900 and 2100
Solar and lunar eclipses, 2020 to 2030 identical in date and type, all 50
Rise and set of the Sun, Venus and Jupiter, 431 events 0.12 s

A chart is read in degrees and minutes. A minute is sixty arcseconds, so the whole engine sits three orders of magnitude below the unit anybody actually reads off a wheel, across eight hundred years. Each row has its own chapter going deeper: fixed stars for the catalogue comparison, eclipses and occultations for the fifty solar and lunar events and for the central path checked against a published catalogue kilometre by kilometre, and rise and set for how a pass over the horizon is tracked and solved.

Orbits and phenomena are checked the same way and are worth a second look here, because between them they are not yet in either chapter's own summary. Against the osculating elements Horizons itself publishes, fourteen bodies in 1700, 2000 and 2300: nodes to 0.06 arcseconds for the tabulated bodies and 5.8 for the outer planets read off the analytic series, inclination to 0.09 arcseconds, eccentricity to 6.6e-6, semi-major axis to 1.2e-4 astronomical units, the three anomalies and the perihelion passage to 0.018 degrees. And what Phenomena::of() returns, against Horizons over twelve bodies: elongation to 0.14 arcseconds, apparent diameter to 0.0071 per cent (0.048 on the Moon, where the disc is biggest and the rounding shows most), illuminated fraction to 0.0065 percentage points, phase angle to 26.6 arcseconds, and magnitude to 0.037, worst on Neptune, which is the planet with the least Horizons data to fit against in the first place.

The 0.13 arcsecond that is left, and why

What remains after all of that is not the ephemerides and not the tables. It is a floor of about 0.13 arcseconds that comes from the precession model, and it is measured in a way that does not need a second ephemeris system to compare against: take the apparent vector Horizons itself publishes for a body, rotate it with this engine's own precession and nutation, and compare the result against the longitude Horizons also publishes for the same instant. Whatever is left over belongs to the rotation, not to the position underneath it.

This engine precesses with the 1976 model (Lieske), because that is the one VSOP87 and the JPL's own correction tables are written in, and using anything else would mean rotating consistently defined data through two different conventions and manufacturing a discrepancy that exists in neither source on its own. It is a structural reason and not a preference, and the Sun is what proves it, because the Sun carries no correction table of its own to hide behind: measured straight against Horizons, it stays flat to about a tenth of an arcsecond for six centuries either side of 2000, +0.03 arcseconds in 1700 and +0.11 in 2300. A drifting model would show up there first, on the one body with nothing else left to blame.

The quadratic term of that same precession is worth its own line, because it was caught twice on the same day by two measurements that had nothing to do with each other. Without it the error grows with the square of the time from 2000: 1.1 arcseconds in 1900, 13.6 in both 1650 and 2350. Pluto and Chiron against Horizons were off by fourteen arcseconds, symmetrically, on both sides of the year 2000, and the fixed stars against their own catalogue positions were dragging a second and a half in 1900. The Sun did not show it, because VSOP87 already comes in the ecliptic of date rather than an inertial one, and that absence is what pointed at the rotation and not at any of the tables underneath it.

Frames: the same triangle from somewhere else

Ephemeris offers five origins for the same body, geocentric, heliocentric, barycentric, topocentric and planetocentric, and the last one is the cleanest check of all of them, because what changes between origins is entirely light time, aberration and where the deflection of light actually happens, with no tables to lean on:

Against JPL Horizons, fifteen combinations of body and observer across 1700, 2000 and 2300: 0.0097 arcseconds worst case in longitude, 0.0049 in latitude, 4.6e-7 astronomical units in distance. That is ten times tighter than the engine's own 0.13 arcsecond floor, and it is not that the planetocentric geometry is done better than the geocentric one. It is that comparing it correctly means rotating the result into the ecliptic of J2000 first, and that rotation carries away exactly the same precession-model difference that sets the floor everywhere else. What that figure actually measures is the geometry alone: light time, aberration, deflection, and where the observer stands, none of which depend on which precession convention anybody chose.

Houses: checked against their own definition, until that stopped being enough

Houses shows the strongest self-check this engine has: at the equator, six systems that reach a cusp by six completely unrelated constructions, dividing the equator, the prime vertical, a diurnal arc, all collapse onto the same number, because at latitude zero those circles all coincide. If one of the six drifts even slightly, that one alone is wrong, and nothing external has to be consulted to know it.

That check is real, and it is also not enough on its own, which is worth sitting with rather than glossing over. Koch once measured the ascensional difference of the ascendant where the definition calls for that of the midheaven, and the mistake passed every self-check this engine has: the four angles came out exactly right, because the bug lived entirely in the eight intermediate cusps, and at the equator the ascensional difference is zero for either point, so the equator check could not tell the two apart either. What it produced instead, at a real latitude, was a cusp eleven at +7.20 degrees where the definition gives −3.10, with the eight intermediate cusps up to 35 degrees out while the wheel still looked perfectly ordinary. It surfaced only once the cusps were checked against independently published values for the same chart, where eleven of the twelve systems agreed to a third of an arcsecond and this one came out 126,000 arcseconds away.

That is the first of three things worth carrying out of this chapter and not just this section.

Three lessons about measuring, not just three facts

A check against a system's own definition cannot catch a wrong definition. It can only catch a disagreement between the code and the rule the code itself thinks it is following, and Koch's bug was consistent with itself all the way down: every cusp came out exactly where that wrong rule said it should. The equator collapse is still worth having, because it is free and it does catch real slips, just not this particular class of one. What catches a wrong definition is comparing the output against a value that came from somewhere else entirely, published independently of this engine's own idea of what the formula says.

Measuring a fit on its own points measures it against itself, and the number always looks better than it is. The data tells the sharpest version of this: a Chebyshev block of forty-eight days sampled once a day fits its own twenty-five coefficients to forty-nine points almost exactly, 0.024 arcseconds of residual at those very points, while the real error, measured between them where nothing was fitted to, is 0.244, ten times worse and looking for all the world like the better result. Vsop87Series's own truncation threshold is the same trap from the other direction: run with the wrong default it silently rewrote a planet's series with a third of its terms and passed every test that existed, because none of those tests had been built to catch a truncation nobody had asked for. Both are the same lesson: a number measured where the fit was told to be good says nothing about where it was not told anything at all.

A count that moves is a signal, even when nothing is failing. Crossings and retrogrades tells the concrete case: Pluto grazes a sign boundary by 1.6 arcminutes, turns back twenty-four days later, and a sweep step chosen from how far the body travels has no way of knowing that graze happened at all, because almost nothing moved during it. Two such pairs sat missing from a three-hundred-year window of ingresses for a long time, not because anything raised an exception, but because a total that should have been sixty-six quietly read sixty-two and every individual crossing reported still looked correct on its own. Once something in the engine counts how many of anything it found, over a fixed span, against a value that is itself checked another way, a change in that count is worth looking at before it is explained away, because a lower number in a suite that is still green is exactly what a missed case looks like from the outside.

Where to go next

The data is where every series and table this chapter's numbers depend on comes from, and how each one checks what it wrote. Downloadable bodies covers what is fetched rather than shipped, with its own measured tolerance per body. And positions, houses, orbits, phenomena, eclipses and crossings are where each of the numbers in this chapter is worked out in full, with the code that produces them.