Skip to content

Fix the nutation in the true-of-date <-> J2000 transform used by the astrometric kernels - #989

Open
Cybis320 wants to merge 2 commits into
prereleasefrom
fix-nutation-frame
Open

Fix the nutation in the true-of-date <-> J2000 transform used by the astrometric kernels#989
Cybis320 wants to merge 2 commits into
prereleasefrom
fix-nutation-frame

Conversation

@Cybis320

@Cybis320 Cybis320 commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

Summary

equatorialCoordAndRotPrecession, which pointingCorrection uses for every XY ↔ RA/Dec projection, applied nutation incorrectly: the nutation in longitude was a rotation about the equatorial pole instead of the ecliptic pole, and both nutation rotations were applied after precessing to J2000 rather than removed in the frame of date. The 4-term nutation series itself is fine, and the mean precession (equatorialCoordPrecession) is accurate to 0.08″.

Against the IAU 2006/2000A model (erfa), the transform true-of-date → J2000 was off by:

Date error of the transform (max over the sky) with the fix
2021-03 30.8″ 0.05″
2024-09 3.4″ 0.17″
2026-01 12.2″ 0.02″
2027-07 24.4″ 0.20″
2029-06 32.3″ 0.17″

It follows the 18.6-year nutation cycle: near zero in 2024, growing towards ~34″ in 2029.

Change

  • Nutation is the standard rotation N = R1(−(ε+Δε))·R3(−Δψ)·R1(ε) from the existing series (nutationRotate, pure C), removed at the start epoch and applied at the final epoch; J2000 is treated as the mean catalog frame.
  • The position-angle change is computed from a north vector transported through the same rotation: a gnomonic projection defined in one frame is exactly the same projection in the other, rotated by the angle between the two frames' north directions at the centre.
  • cyTrueRaDec2ApparentAltAz / cyApparentAltAz2TrueRADec use the same true-of-date transform via two new helpers, trueOfDateFromJ2000 / j2000FromTrueOfDate. Before, they applied no nutation at all, so alt/az derived from kernel output disagreed with the kernel's own frame by up to ~30″. equatorialCoordPrecession (mean precession) is unchanged.
  • pointingCorrection is cpdef so it can be tested from Python. The cyjd2LST docstring now says what it returns (mean sidereal time; the equation of the equinoxes, ~1″, is not applied anywhere and Platepar.Ho is computed the same way, so the pair is consistent).

Verification

Tests/test_precession_nutation.py (erfa-dependent tests skip if pyerfa is absent). A distortion-free synthetic camera fixed in alt/az, referenced at the start of the night and followed for 8 hours on a 9×7 pixel grid over an 82×46° frame (two pointings, five dates 2021–2029): the kernel's XY → J2000 stays within 0.5″ of erfa at every grid point, with the rigid roll of the error field within 0.3″ and the non-rigid part within 0.1″. The centre alone cannot see a rotation or scale error; the corners can. The old transform drifts by up to 11″ in position and 11″ in roll over a night on this test. Also: transform within 0.5″ of erfa at five dates, J2000→date→J2000 round trips to 0.01″, the kernel transform and the converters agree to 0.001″, the position-angle change within 0.5″ of the exact geometry, and a fixed camera's kernel centre within 2″ of the true direction with no drift over a night.

Real data: USV001 2026-09-05 (217 FFs) recalibrated with the old and new kernels. Star-fit residuals identical (mean 0.1587 vs 0.1594 px). Only the fitted parameters move: the pointing by a few arcsec (the old transform's error happens to be small at that direction and date) and pos_angle_ref by +14″. Within-night pointing scatter is unchanged (0.163′ → 0.148′ in altitude).

What this changes for users

Nothing in measured astrometry: the error was a small rigid rotation of the sky, and the star fit absorbed it into RA_d, dec_d and pos_angle_ref. Those fitted values in existing platepars compensate for the old transform, so applied with the new kernel without refitting they are off by the same few arcsec to a few tens of arcsec until the next fit; the nightly recalibration does that automatically, and a SkyFit refit does it for a manually made platepar. The absolute meaning of RA_d/dec_d (and anything derived from it, such as the stored alt/az centre) becomes correct.

Found while checking the coordinate chain during the review of #986; independent of that PR and of #987.

🤖 Generated with Claude Code

https://claude.ai/code/session_01CzZHCZoftFjV712njkWB5v

…astrometric kernels

equatorialCoordAndRotPrecession (used by pointingCorrection, i.e. by every
XY <-> RA/Dec projection) applied nutation incorrectly: the nutation in
longitude was a rotation about the equatorial pole instead of the
ecliptic pole, and both nutation rotations were applied after the
precession to J2000 rather than removed in the frame of date. Against
the IAU 2006/2000A model (erfa) the transform was off by 3-32 arcsec
depending on date and direction, following the 18.6-year nutation cycle
(near zero in 2024, ~15" now, ~34" in 2029). The mean precession itself
(equatorialCoordPrecession) is accurate to 0.08".

The nutation is now the standard rotation N = R1(-(eps+deps)) R3(-dpsi)
R1(eps) built from the existing 4-term series (Meeus), removed at the
start epoch and applied at the final epoch, with J2000 treated as the
mean catalog frame. The position-angle change is computed from a north
vector transported through the same rotation, which is the exact
geometric statement that a gnomonic projection defined in one frame is
the same projection in the other. The J2000 <-> alt/az converters
(cyTrueRaDec2ApparentAltAz, cyApparentAltAz2TrueRADec) now use the same
true-of-date transform through two new helpers, trueOfDateFromJ2000 and
j2000FromTrueOfDate, so the code has one frame convention; before, they
applied no nutation at all and disagreed with the kernel by up to ~30".
pointingCorrection is made cpdef so it can be tested from Python. The
cyjd2LST docstring is corrected: it returns mean, not apparent, sidereal
time (the equation of the equinoxes, ~1", is not applied anywhere).

Verified against erfa: transform within 0.2" (series-limited), round
trips to 0.01", position-angle change within 0.5" of the exact geometry,
and a fixed camera's kernel centre within 2" of the true direction with
no drift over a night. Recalibrating a real night (USV001 2026-09-05,
217 FFs) with the old and new kernels gives identical star residuals
(0.159 px both); only the fitted RA_d/dec_d/pos_angle_ref move, by a few
arcsec in pointing and +14" in rotation, as expected.

Because the error was a small rigid rotation of the sky that the star fit
absorbed into RA_d, dec_d and pos_angle_ref, measured positions relative
to the stars were never affected. The fitted parameters of existing
platepars, however, compensate for the old transform: applied with the
new kernel without refitting they are off by the same few arcsec to a few
tens of arcsec until the next fit, which the nightly recalibration does
automatically.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CzZHCZoftFjV712njkWB5v
…pixels

A camera fixed in alt/az, referenced at the start of the night, is followed
for 8 hours on a 9 x 7 pixel grid over an 82 x 46 deg frame, at two
pointings and five dates from 2021 to 2029. The kernel's XY -> J2000 is
compared with erfa (GAST rotation, IAU 2006/2000A precession-nutation),
and the error field is split into the centre error, the rigid roll about
the line of sight and the non-rigid remainder. Everything stays within
0.5", the roll within 0.3" and the non-rigid part within 0.1". The centre
alone cannot see a rotation or scale error; the corners can. The old
transform drifts by up to 11" in position and 11" in roll over a night on
this test.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CzZHCZoftFjV712njkWB5v
Cybis320 added a commit that referenced this pull request Sep 6, 2026
…d geodetic kernels

Follows the cherry-picks of the nutation fix (#989), the aberration
correction (#990) and the refraction model (#991) onto this branch. The
RA/Dec kernels took the scale with #991; this branch's own kernels and
their iterative inverses (cyXYToAltAz, cyAltAzToXY, cyRaDecToXY_iter,
cyXYHttoENU_wgs84, cyXYToGeo_wgs84, cyENUToXY_iter, cyENHtToXY_iter,
cyGeoToXY_wgs84_iter) now take a refraction_scale argument as well, and
every wrapper and Platepar method passes refractionScale(elev), so the
alt/az and ENU paths refract consistently with the RA/Dec path. The
finite-target-height fraction is provided but not yet applied in the
height-aware kernels.

Tests adapted to the merged kernels: the refraction fit test builds its
star truth as the catalog direction (aberration removed) when the
kernels model the aberration; the pointing test converts the platepar's
epoch-of-date pointing to J2000 with the kernels' own transform, which
now includes the nutation; the ENU -> XY round-trip tolerance reflects
the solver's 0.01 px step criterion, which leaves up to ~0.1 px at low
altitude with refraction.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CzZHCZoftFjV712njkWB5v
Cybis320 added a commit that referenced this pull request Sep 6, 2026
…c kernels (includes PR #989 nutation fix)

# Conflicts:
#	RMS/Astrometry/ApplyAstrometry.py
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant