pg_orrery/test/expected/de_ephemeris.out
Ryan Malloy 15fa553c0e Add optional JPL DE440/441 ephemeris support (v0.3.0)
Clean-room DE binary reader (~400 lines C) with Chebyshev/Clenshaw
evaluation — no GPL dependency on jpl_eph. Per-backend lazy
initialization preserves PARALLEL SAFE. Existing VSOP87/ELP82B
functions stay IMMUTABLE; new _de() variants are STABLE with
automatic fallback to compiled-in ephemerides on any DE failure.

Implementation:
- de_reader.c: header parse, record seek, Clenshaw recurrence
- eph_provider.c: GUC (pg_orbit.ephemeris_path), lazy init,
  ICRS-to-ecliptic frame rotation, on_proc_exit cleanup
- de_funcs.c: 11 new SQL functions (_de variants + diagnostics)
- Constant chain of custody rules 6-8 (frame rotation,
  same-provider, AU consistency)

Extract observe_from_geocentric() to astro_math.h for shared use
by planet_funcs.c, moon_funcs.c, and de_funcs.c.

57 → 68 functions, 11 → 12 regression test suites, all passing.
2026-02-16 19:54:48 -07:00

187 lines
9.3 KiB
Plaintext

-- de_ephemeris regression tests
--
-- Tests the _de() function variants and VSOP87 fallback behavior.
-- Since DE ephemeris files are not available in CI, these tests
-- verify that fallback behavior is correct and produces identical
-- results to the VSOP87 variants.
\set boulder '''40.015N 105.270W 1655m'''::observer
-- ============================================================
-- Test 1: pg_orbit_ephemeris_info() returns VSOP87 when no DE file
-- The provider should be 'VSOP87' since no ephemeris_path is set.
-- ============================================================
SELECT 'eph_info_vsop87' AS test,
(pg_orbit_ephemeris_info()).provider AS provider;
test | provider
-----------------+----------
eph_info_vsop87 | VSOP87
(1 row)
-- ============================================================
-- Test 2: planet_heliocentric_de falls back to VSOP87
-- Should produce identical results to planet_heliocentric()
-- when DE is unavailable.
-- ============================================================
SELECT 'helio_fallback' AS test,
round(helio_x(planet_heliocentric(3, '2024-06-21 12:00:00+00'))::numeric, 8) =
round(helio_x(planet_heliocentric_de(3, '2024-06-21 12:00:00+00'))::numeric, 8) AS x_match,
round(helio_y(planet_heliocentric(3, '2024-06-21 12:00:00+00'))::numeric, 8) =
round(helio_y(planet_heliocentric_de(3, '2024-06-21 12:00:00+00'))::numeric, 8) AS y_match,
round(helio_z(planet_heliocentric(3, '2024-06-21 12:00:00+00'))::numeric, 8) =
round(helio_z(planet_heliocentric_de(3, '2024-06-21 12:00:00+00'))::numeric, 8) AS z_match;
test | x_match | y_match | z_match
----------------+---------+---------+---------
helio_fallback | t | t | t
(1 row)
-- ============================================================
-- Test 3: planet_heliocentric_de Sun at origin
-- ============================================================
SELECT 'sun_origin_de' AS test,
round(helio_x(planet_heliocentric_de(0, '2024-06-21 12:00:00+00'))::numeric, 10) AS x,
round(helio_y(planet_heliocentric_de(0, '2024-06-21 12:00:00+00'))::numeric, 10) AS y,
round(helio_z(planet_heliocentric_de(0, '2024-06-21 12:00:00+00'))::numeric, 10) AS z;
test | x | y | z
---------------+--------------+--------------+--------------
sun_origin_de | 0.0000000000 | 0.0000000000 | 0.0000000000
(1 row)
-- ============================================================
-- Test 4: planet_observe_de falls back to VSOP87
-- Elevation and azimuth should match planet_observe().
-- ============================================================
SELECT 'observe_fallback' AS test,
round(topo_azimuth(planet_observe(5, :boulder, '2024-03-15 03:00:00+00'))::numeric, 4) =
round(topo_azimuth(planet_observe_de(5, :boulder, '2024-03-15 03:00:00+00'))::numeric, 4) AS az_match,
round(topo_elevation(planet_observe(5, :boulder, '2024-03-15 03:00:00+00'))::numeric, 4) =
round(topo_elevation(planet_observe_de(5, :boulder, '2024-03-15 03:00:00+00'))::numeric, 4) AS el_match;
test | az_match | el_match
------------------+----------+----------
observe_fallback | t | t
(1 row)
-- ============================================================
-- Test 5: sun_observe_de falls back to VSOP87
-- ============================================================
SELECT 'sun_fallback' AS test,
round(topo_azimuth(sun_observe(:boulder, '2024-06-21 18:00:00+00'))::numeric, 4) =
round(topo_azimuth(sun_observe_de(:boulder, '2024-06-21 18:00:00+00'))::numeric, 4) AS az_match,
round(topo_elevation(sun_observe(:boulder, '2024-06-21 18:00:00+00'))::numeric, 4) =
round(topo_elevation(sun_observe_de(:boulder, '2024-06-21 18:00:00+00'))::numeric, 4) AS el_match;
test | az_match | el_match
--------------+----------+----------
sun_fallback | t | t
(1 row)
-- ============================================================
-- Test 6: moon_observe_de falls back to ELP2000-82B
-- ============================================================
SELECT 'moon_fallback' AS test,
round(topo_azimuth(moon_observe(:boulder, '2024-06-21 18:00:00+00'))::numeric, 4) =
round(topo_azimuth(moon_observe_de(:boulder, '2024-06-21 18:00:00+00'))::numeric, 4) AS az_match,
round(topo_range(moon_observe(:boulder, '2024-06-21 18:00:00+00'))::numeric, 0) =
round(topo_range(moon_observe_de(:boulder, '2024-06-21 18:00:00+00'))::numeric, 0) AS range_match;
test | az_match | range_match
---------------+----------+-------------
moon_fallback | t | t
(1 row)
-- ============================================================
-- Test 7: lambert_c3_de falls back to VSOP87
-- Earth-Mars 2024 window should match lambert_c3().
-- ============================================================
SELECT 'lambert_fallback' AS test,
round(lambert_c3(3, 4, '2024-05-01 00:00:00+00', '2025-02-01 00:00:00+00')::numeric, 2) =
round(lambert_c3_de(3, 4, '2024-05-01 00:00:00+00', '2025-02-01 00:00:00+00')::numeric, 2) AS c3_match;
test | c3_match
------------------+----------
lambert_fallback | t
(1 row)
-- ============================================================
-- Test 8: lambert_transfer_de falls back to VSOP87
-- Should produce identical departure C3.
-- ============================================================
SELECT 'transfer_fallback' AS test,
round((lambert_transfer(3, 4, '2024-05-01 00:00:00+00', '2025-02-01 00:00:00+00')).c3_departure::numeric, 2) =
round((lambert_transfer_de(3, 4, '2024-05-01 00:00:00+00', '2025-02-01 00:00:00+00')).c3_departure::numeric, 2) AS c3_match;
test | c3_match
-------------------+----------
transfer_fallback | t
(1 row)
-- ============================================================
-- Test 9: galilean_observe_de falls back to VSOP87
-- ============================================================
SELECT 'galilean_fallback' AS test,
round(topo_elevation(galilean_observe(0, :boulder, '2024-03-15 03:00:00+00'))::numeric, 4) =
round(topo_elevation(galilean_observe_de(0, :boulder, '2024-03-15 03:00:00+00'))::numeric, 4) AS el_match;
test | el_match
-------------------+----------
galilean_fallback | t
(1 row)
-- ============================================================
-- Test 10: saturn_moon_observe_de falls back to VSOP87
-- ============================================================
SELECT 'saturn_moon_fallback' AS test,
round(topo_elevation(saturn_moon_observe(5, :boulder, '2024-06-15 04:00:00+00'))::numeric, 4) =
round(topo_elevation(saturn_moon_observe_de(5, :boulder, '2024-06-15 04:00:00+00'))::numeric, 4) AS el_match;
test | el_match
----------------------+----------
saturn_moon_fallback | t
(1 row)
-- ============================================================
-- Test 11: uranus_moon_observe_de falls back to VSOP87
-- ============================================================
SELECT 'uranus_moon_fallback' AS test,
round(topo_elevation(uranus_moon_observe(3, :boulder, '2024-06-15 04:00:00+00'))::numeric, 4) =
round(topo_elevation(uranus_moon_observe_de(3, :boulder, '2024-06-15 04:00:00+00'))::numeric, 4) AS el_match;
test | el_match
----------------------+----------
uranus_moon_fallback | t
(1 row)
-- ============================================================
-- Test 12: mars_moon_observe_de falls back to VSOP87
-- ============================================================
SELECT 'mars_moon_fallback' AS test,
round(topo_elevation(mars_moon_observe(0, :boulder, '2024-06-15 04:00:00+00'))::numeric, 4) =
round(topo_elevation(mars_moon_observe_de(0, :boulder, '2024-06-15 04:00:00+00'))::numeric, 4) AS el_match;
test | el_match
--------------------+----------
mars_moon_fallback | t
(1 row)
-- ============================================================
-- Test 13: All DE planet functions work (fallback mode)
-- Mercury through Neptune, all should return valid positions
-- matching their VSOP87 counterparts.
-- ============================================================
SELECT 'all_planets_de' AS test,
body_id,
round(helio_distance(planet_heliocentric_de(body_id, '2024-06-21 12:00:00+00'))::numeric, 2) AS dist_au
FROM generate_series(1, 8) AS body_id;
test | body_id | dist_au
----------------+---------+---------
all_planets_de | 1 | 0.33
all_planets_de | 2 | 0.72
all_planets_de | 3 | 1.02
all_planets_de | 4 | 1.40
all_planets_de | 5 | 5.02
all_planets_de | 6 | 9.69
all_planets_de | 7 | 19.59
all_planets_de | 8 | 29.90
(8 rows)
-- ============================================================
-- Test 14: Error handling - invalid body_id
-- ============================================================
SELECT 'invalid_body_de' AS test, planet_heliocentric_de(9, now());
ERROR: invalid body_id 9: must be 0 (Sun) or 1-8 (Mercury-Neptune)
-- ============================================================
-- Test 15: Error handling - cannot observe Earth from Earth
-- ============================================================
SELECT 'earth_error_de' AS test, planet_observe_de(3, :boulder, now());
ERROR: cannot observe Earth from Earth