pg_orrery/src/sidereal_time.c
Ryan Malloy 0544a78276 pg_orbit 0.2.0: Full solar system computation at the SQL layer
Phase 1 — Stars, comets, Keplerian propagation:
- star_observe() / star_observe_safe(): fixed star alt/az via IAU 1976
  precession, equatorial-to-horizontal transform
- kepler_propagate(): two-body Keplerian orbit propagation for
  elliptic, parabolic, and hyperbolic orbits
- comet_observe(): observe comets/asteroids from orbital elements
- heliocentric type: ecliptic J2000 position (x, y, z in AU)

Phase 2 — VSOP87 planets, ELP82B Moon, Sun:
- planet_heliocentric(): VSOP87 heliocentric ecliptic J2000 positions
  for Mercury through Neptune (Bretagnon & Francou, MIT)
- planet_observe(): full observation pipeline for any planet
- sun_observe(): Sun position from negated Earth VSOP87
- moon_observe(): ELP2000-82B lunar position (Chapront-Touzé, MIT)
- Clean-room precession (IAU 2006) and sidereal time (IERS 2010)
- elliptic_to_rectangular utility (Stellarium, MIT)

All Stellarium extractions are MIT-licensed, thread-safe (static
caching removed for PARALLEL SAFE), zero external data files.

All 9 regression tests pass (90ms total).
2026-02-16 01:36:27 -07:00

141 lines
3.8 KiB
C

/*
* sidereal_time.c -- Greenwich sidereal time (mean and apparent)
*
* Clean-room implementation from published standards:
*
* Mean sidereal time:
* Capitaine, Guinot & McCarthy (2000), A&A 355, 398-405.
* IERS Conventions (2010), IERS Technical Note 36, Ch. 5,
* Eq. 5.29 (GMST as polynomial in UT1).
*
* The polynomial form is:
* GMST(UT1) = theta_0 + theta_1*T + theta_2*T^2 + theta_3*T^3
* where T is Julian centuries of UT1 from J2000.0, and the
* result is in seconds of time. Coefficients from Capitaine
* et al. (2000), Eq. (42), which are the values adopted by
* the IERS for consistency with IAU 2000/2006 precession.
*
* Apparent sidereal time:
* GAST = GMST + equation_of_equinoxes
* EqEq = delta_psi * cos(epsilon_A)
* IERS Conventions (2010), Eq. 5.30.
*/
#include "sidereal_time.h"
#include "precession.h"
#include <math.h>
#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif
/* Arcseconds to radians */
#define ARCSEC_TO_RAD (M_PI / (180.0 * 3600.0))
/*
* get_mean_sidereal_time
*
* GMST in radians from the classical polynomial expression.
*
* The polynomial (Capitaine et al. 2000, Eq. 42):
* GMST = 67310.54841
* + (876600h + 8640184.812866) * T
* + 0.093104 * T^2
* - 6.2e-6 * T^3
*
* where T = (JD_UT1 - 2451545.0) / 36525.0
* and the result is in seconds of sidereal time.
*
* The constant 876600h = 876600 * 3600 = 3155760000 seconds
* accounts for complete rotations.
*
* To convert seconds of time to radians:
* 1 day = 86400 seconds of time = 2*pi radians
* 1 second of time = 2*pi/86400 = pi/43200 radians
*
* JDE is accepted for interface consistency but unused here;
* GMST is fundamentally a UT1 quantity.
*/
double
get_mean_sidereal_time(double JD, double JDE)
{
double T, T2, T3;
double gmst_sec;
double gmst_rad;
(void)JDE; /* GMST depends on UT1, not TDB */
T = (JD - 2451545.0) / 36525.0;
T2 = T * T;
T3 = T2 * T;
/*
* Polynomial in seconds of sidereal time.
* The large linear coefficient includes whole rotations
* accumulated since J2000.0.
*/
gmst_sec = 67310.54841
+ (876600.0 * 3600.0 + 8640184.812866) * T
+ 0.093104 * T2
- 6.2e-6 * T3;
/* Seconds of time -> radians, then normalize to [0, 2*pi) */
gmst_rad = fmod(gmst_sec * (M_PI / 43200.0), 2.0 * M_PI);
if (gmst_rad < 0.0)
gmst_rad += 2.0 * M_PI;
return gmst_rad;
}
/*
* get_apparent_sidereal_time
*
* GAST = GMST + equation of the equinoxes.
*
* The equation of the equinoxes (IERS 2010, Eq. 5.30):
* EqEq = delta_psi * cos(epsilon_A)
*
* where delta_psi is nutation in longitude and epsilon_A is
* the mean obliquity of the ecliptic, both in arcseconds.
*
* For the full IAU 2000A model, there is an additional
* "complementary terms" correction to EqEq (Eq. 5.31),
* but it is at the 0.1 mas level and negligible for our
* accuracy requirements.
*/
double
get_apparent_sidereal_time(double JD, double JDE)
{
double gmst;
double epsilon_A, chi_A, omega_A, psi_A;
double delta_psi, delta_epsilon;
double eq_eq;
double gast;
gmst = get_mean_sidereal_time(JD, JDE);
/* Get mean obliquity from precession (in arcseconds) */
get_precession_angles_vondrak(JDE, &epsilon_A, &chi_A, &omega_A, &psi_A);
/* Get nutation in longitude (in arcseconds) */
get_nutation_angles_iau2000b(JDE, &delta_psi, &delta_epsilon);
/*
* Equation of the equinoxes:
* delta_psi is in arcseconds, epsilon_A is in arcseconds.
* Convert delta_psi to radians, multiply by cos(epsilon_A in radians),
* result is in radians.
*/
eq_eq = (delta_psi * ARCSEC_TO_RAD) * cos(epsilon_A * ARCSEC_TO_RAD);
gast = gmst + eq_eq;
/* Normalize to [0, 2*pi) */
gast = fmod(gast, 2.0 * M_PI);
if (gast < 0.0)
gast += 2.0 * M_PI;
return gast;
}