pg_orrery/test/expected/orbital_elements.out
Ryan Malloy b33d63034b Add v0.9.0 apparent position features: equatorial type, refraction, proper motion, light-time
New equatorial type (24 bytes: RA/Dec/distance) captures apparent coordinates
of date — what the observation pipeline computes at precession step 3 but was
discarding before hour angle conversion. Matches telescope GoTo mount conventions.

24 new SQL functions (82 → 106 total):
- equatorial type I/O + 3 accessors (eq_ra, eq_dec, eq_distance)
- Satellite RA/Dec: eci_to_equatorial (topocentric), eci_to_equatorial_geo (geocentric)
- Solar system equatorial: planet/sun/moon/small_body_equatorial
- Atmospheric refraction: Bennett (1982) with domain clamp at -1 deg
- Refracted pass prediction: predict_passes_refracted (horizon at -0.569 deg)
- Stellar proper motion: star_observe_pm, star_equatorial_pm (Hipparcos/Gaia convention)
- Light-time correction: planet/sun/small_body_observe_apparent, *_equatorial_apparent
- DE equatorial variants: planet_equatorial_de, moon_equatorial_de

Also includes v0.8.0 orbital_elements type (MPC parser, small_body_observe),
GiST 0-based indexing fix, llms.txt updates, and doc improvements.

All 18 regression suites pass. Zero build warnings (GCC + Clang).
2026-02-21 15:31:46 -07:00

322 lines
15 KiB
Plaintext

-- orbital_elements regression tests
--
-- Tests orbital_elements type I/O, accessors, MPC parser,
-- small_body_heliocentric(), and small_body_observe().
CREATE EXTENSION IF NOT EXISTS pg_orrery;
NOTICE: extension "pg_orrery" already exists, skipping
\set boulder '''40.015N 105.270W 1655m'''::observer
-- ============================================================
-- Test 1: Type I/O round-trip
-- Construct orbital_elements from text literal, verify output matches.
-- Angles in degrees, stored as radians internally.
-- ============================================================
SELECT 'io_roundtrip' AS test,
'(2460600.000000,2.5478000000,0.0789126000,10.586640,73.429370,80.268600,2460319.000000,3.33,0.12)'::orbital_elements AS oe;
test | oe
--------------+---------------------------------------------------------------------------------------------------
io_roundtrip | (2460600.000000,2.5478000000,0.0789126000,10.586640,73.429370,80.268600,2460319.000000,3.33,0.12)
(1 row)
-- ============================================================
-- Test 2: Accessor functions (direct field access)
-- ============================================================
SELECT 'accessor_epoch' AS test,
round(oe_epoch('(2460600.0,2.5478,0.0789126,10.58664,73.42937,80.2686,2460319.0,3.33,0.12)'::orbital_elements)::numeric, 1) AS epoch_jd;
test | epoch_jd
----------------+-----------
accessor_epoch | 2460600.0
(1 row)
SELECT 'accessor_q' AS test,
round(oe_perihelion('(2460600.0,2.5478,0.0789126,10.58664,73.42937,80.2686,2460319.0,3.33,0.12)'::orbital_elements)::numeric, 4) AS q_au;
test | q_au
------------+--------
accessor_q | 2.5478
(1 row)
SELECT 'accessor_e' AS test,
round(oe_eccentricity('(2460600.0,2.5478,0.0789126,10.58664,73.42937,80.2686,2460319.0,3.33,0.12)'::orbital_elements)::numeric, 7) AS ecc;
test | ecc
------------+-----------
accessor_e | 0.0789126
(1 row)
SELECT 'accessor_inc' AS test,
round(oe_inclination('(2460600.0,2.5478,0.0789126,10.58664,73.42937,80.2686,2460319.0,3.33,0.12)'::orbital_elements)::numeric, 5) AS inc_deg;
test | inc_deg
--------------+----------
accessor_inc | 10.58664
(1 row)
SELECT 'accessor_omega' AS test,
round(oe_arg_perihelion('(2460600.0,2.5478,0.0789126,10.58664,73.42937,80.2686,2460319.0,3.33,0.12)'::orbital_elements)::numeric, 5) AS omega_deg;
test | omega_deg
----------------+-----------
accessor_omega | 73.42937
(1 row)
SELECT 'accessor_Omega' AS test,
round(oe_raan('(2460600.0,2.5478,0.0789126,10.58664,73.42937,80.2686,2460319.0,3.33,0.12)'::orbital_elements)::numeric, 4) AS Omega_deg;
test | omega_deg
----------------+-----------
accessor_Omega | 80.2686
(1 row)
SELECT 'accessor_tp' AS test,
round(oe_tp('(2460600.0,2.5478,0.0789126,10.58664,73.42937,80.2686,2460319.0,3.33,0.12)'::orbital_elements)::numeric, 1) AS tp_jd;
test | tp_jd
-------------+-----------
accessor_tp | 2460319.0
(1 row)
SELECT 'accessor_h' AS test,
round(oe_h_mag('(2460600.0,2.5478,0.0789126,10.58664,73.42937,80.2686,2460319.0,3.33,0.12)'::orbital_elements)::numeric, 2) AS h_mag;
test | h_mag
------------+-------
accessor_h | 3.33
(1 row)
SELECT 'accessor_g' AS test,
round(oe_g_slope('(2460600.0,2.5478,0.0789126,10.58664,73.42937,80.2686,2460319.0,3.33,0.12)'::orbital_elements)::numeric, 2) AS g_slope;
test | g_slope
------------+---------
accessor_g | 0.12
(1 row)
-- ============================================================
-- Test 3: Computed accessors
-- ============================================================
-- Semi-major axis: a = q / (1 - e) = 2.5478 / (1 - 0.0789126) ~ 2.766 AU
SELECT 'sma_elliptic' AS test,
round(oe_semi_major_axis('(2460600.0,2.5478,0.0789126,10.58664,73.42937,80.2686,2460319.0,3.33,0.12)'::orbital_elements)::numeric, 3) AS a_au;
test | a_au
--------------+-------
sma_elliptic | 2.766
(1 row)
-- Period: a^1.5 years. a ~ 2.766, period ~ 4.599 years
SELECT 'period_elliptic' AS test,
round(oe_period_years('(2460600.0,2.5478,0.0789126,10.58664,73.42937,80.2686,2460319.0,3.33,0.12)'::orbital_elements)::numeric, 2) AS period_yr;
test | period_yr
-----------------+-----------
period_elliptic | 4.60
(1 row)
-- Hyperbolic orbit (e=1.5): semi-major axis and period should be NULL
SELECT 'sma_hyperbolic' AS test,
oe_semi_major_axis('(2460600.0,1.0,1.5,10.0,73.0,80.0,2460319.0,NaN,NaN)'::orbital_elements) IS NULL AS is_null;
test | is_null
----------------+---------
sma_hyperbolic | t
(1 row)
SELECT 'period_hyperbolic' AS test,
oe_period_years('(2460600.0,1.0,1.5,10.0,73.0,80.0,2460319.0,NaN,NaN)'::orbital_elements) IS NULL AS is_null;
test | is_null
-------------------+---------
period_hyperbolic | t
(1 row)
-- ============================================================
-- Test 4: MPC parser -- (1) Ceres
--
-- MPCORB.DAT line for Ceres (epoch 2024 Oct 17.0 = K24AM):
-- Packed epoch K24AM -> K=2000, 24, A=Oct, M=22 -> 2024-10-22
-- (Actually: K=2000, year=24, month=A=10, day=M=22)
-- JD for 2024-10-22 = 2460605.5
--
-- a = 2.7660961 AU, e = 0.0789126
-- q = a*(1-e) = 2.7660961*(1-0.0789126) = 2.5478...
-- M = 60.07966 deg
-- ============================================================
SELECT 'mpc_ceres_sma' AS test,
round(oe_semi_major_axis(oe_from_mpc(
'00001 3.33 0.12 K24AM 60.07966 73.42937 80.26860 10.58664 0.0789126 0.21406048 2.7660961 0 MPO838504 8738 115 1801-2024 0.65 M-v 30k MPCLINUX 0000 (1) Ceres 20240825'
))::numeric, 4) AS a_au;
test | a_au
---------------+--------
mpc_ceres_sma | 2.7661
(1 row)
SELECT 'mpc_ceres_q' AS test,
round(oe_perihelion(oe_from_mpc(
'00001 3.33 0.12 K24AM 60.07966 73.42937 80.26860 10.58664 0.0789126 0.21406048 2.7660961 0 MPO838504 8738 115 1801-2024 0.65 M-v 30k MPCLINUX 0000 (1) Ceres 20240825'
))::numeric, 4) AS q_au;
test | q_au
-------------+--------
mpc_ceres_q | 2.5478
(1 row)
SELECT 'mpc_ceres_ecc' AS test,
round(oe_eccentricity(oe_from_mpc(
'00001 3.33 0.12 K24AM 60.07966 73.42937 80.26860 10.58664 0.0789126 0.21406048 2.7660961 0 MPO838504 8738 115 1801-2024 0.65 M-v 30k MPCLINUX 0000 (1) Ceres 20240825'
))::numeric, 7) AS ecc;
test | ecc
---------------+-----------
mpc_ceres_ecc | 0.0789126
(1 row)
SELECT 'mpc_ceres_inc' AS test,
round(oe_inclination(oe_from_mpc(
'00001 3.33 0.12 K24AM 60.07966 73.42937 80.26860 10.58664 0.0789126 0.21406048 2.7660961 0 MPO838504 8738 115 1801-2024 0.65 M-v 30k MPCLINUX 0000 (1) Ceres 20240825'
))::numeric, 5) AS inc_deg;
test | inc_deg
---------------+----------
mpc_ceres_inc | 10.58664
(1 row)
SELECT 'mpc_ceres_h' AS test,
round(oe_h_mag(oe_from_mpc(
'00001 3.33 0.12 K24AM 60.07966 73.42937 80.26860 10.58664 0.0789126 0.21406048 2.7660961 0 MPO838504 8738 115 1801-2024 0.65 M-v 30k MPCLINUX 0000 (1) Ceres 20240825'
))::numeric, 2) AS h_mag;
test | h_mag
-------------+-------
mpc_ceres_h | 3.33
(1 row)
-- ============================================================
-- Test 5: small_body_heliocentric -- compare to kepler_propagate
-- Both should produce the same heliocentric position for Ceres.
-- ============================================================
SELECT 'helio_vs_kepler' AS test,
round(helio_x(small_body_heliocentric(
oe_from_mpc('00001 3.33 0.12 K24AM 60.07966 73.42937 80.26860 10.58664 0.0789126 0.21406048 2.7660961 0 MPO838504 8738 115 1801-2024 0.65 M-v 30k MPCLINUX 0000 (1) Ceres 20240825'),
'2025-01-01 00:00:00+00'
))::numeric, 6) AS x_au,
round(helio_y(small_body_heliocentric(
oe_from_mpc('00001 3.33 0.12 K24AM 60.07966 73.42937 80.26860 10.58664 0.0789126 0.21406048 2.7660961 0 MPO838504 8738 115 1801-2024 0.65 M-v 30k MPCLINUX 0000 (1) Ceres 20240825'),
'2025-01-01 00:00:00+00'
))::numeric, 6) AS y_au,
round(helio_z(small_body_heliocentric(
oe_from_mpc('00001 3.33 0.12 K24AM 60.07966 73.42937 80.26860 10.58664 0.0789126 0.21406048 2.7660961 0 MPO838504 8738 115 1801-2024 0.65 M-v 30k MPCLINUX 0000 (1) Ceres 20240825'),
'2025-01-01 00:00:00+00'
))::numeric, 6) AS z_au;
test | x_au | y_au | z_au
-----------------+-----------+-----------+----------
helio_vs_kepler | -1.430911 | -2.313853 | 0.190494
(1 row)
-- Verify heliocentric distance is reasonable (Ceres orbits ~2.5-3.0 AU)
SELECT 'helio_distance' AS test,
round(helio_distance(small_body_heliocentric(
oe_from_mpc('00001 3.33 0.12 K24AM 60.07966 73.42937 80.26860 10.58664 0.0789126 0.21406048 2.7660961 0 MPO838504 8738 115 1801-2024 0.65 M-v 30k MPCLINUX 0000 (1) Ceres 20240825'),
'2025-01-01 00:00:00+00'
))::numeric, 2) AS dist_au,
helio_distance(small_body_heliocentric(
oe_from_mpc('00001 3.33 0.12 K24AM 60.07966 73.42937 80.26860 10.58664 0.0789126 0.21406048 2.7660961 0 MPO838504 8738 115 1801-2024 0.65 M-v 30k MPCLINUX 0000 (1) Ceres 20240825'),
'2025-01-01 00:00:00+00'
)) BETWEEN 2.5 AND 3.0 AS in_range;
test | dist_au | in_range
----------------+---------+----------
helio_distance | 2.73 | t
(1 row)
-- ============================================================
-- Test 6: small_body_observe -- topocentric observation
-- Verify we get reasonable az/el/range for Ceres from Boulder.
-- Range should be on the order of 1.5-4.5 AU in km.
-- ============================================================
SELECT 'observe_range' AS test,
topo_range(small_body_observe(
oe_from_mpc('00001 3.33 0.12 K24AM 60.07966 73.42937 80.26860 10.58664 0.0789126 0.21406048 2.7660961 0 MPO838504 8738 115 1801-2024 0.65 M-v 30k MPCLINUX 0000 (1) Ceres 20240825'),
:boulder, '2025-01-01 00:00:00+00'
)) BETWEEN 1.5 * 149597870.7 AND 4.5 * 149597870.7 AS range_reasonable;
test | range_reasonable
---------------+------------------
observe_range | t
(1 row)
-- Elevation should be a finite number
SELECT 'observe_elevation' AS test,
topo_elevation(small_body_observe(
oe_from_mpc('00001 3.33 0.12 K24AM 60.07966 73.42937 80.26860 10.58664 0.0789126 0.21406048 2.7660961 0 MPO838504 8738 115 1801-2024 0.65 M-v 30k MPCLINUX 0000 (1) Ceres 20240825'),
:boulder, '2025-01-01 00:00:00+00'
)) BETWEEN -90 AND 90 AS el_reasonable;
test | el_reasonable
-------------------+---------------
observe_elevation | t
(1 row)
-- ============================================================
-- Test 7: Parabolic/hyperbolic elements via text I/O
-- Verify type handles e >= 1 without error.
-- ============================================================
SELECT 'parabolic_io' AS test,
oe_eccentricity('(2460600.0,1.0,1.0,45.0,90.0,180.0,2460500.0,12.0,0.15)'::orbital_elements) AS ecc;
test | ecc
--------------+-----
parabolic_io | 1
(1 row)
SELECT 'hyperbolic_io' AS test,
oe_eccentricity('(2460600.0,0.5,2.5,30.0,60.0,120.0,2460400.0,8.0,0.04)'::orbital_elements) AS ecc;
test | ecc
---------------+-----
hyperbolic_io | 2.5
(1 row)
-- ============================================================
-- Test 8: Error paths
-- ============================================================
-- Invalid text input (wrong number of fields)
SELECT 'bad_text' AS test, '(1.0,2.0,3.0)'::orbital_elements;
ERROR: invalid input syntax for type orbital_elements: "(1.0,2.0,3.0)"
LINE 1: SELECT 'bad_text' AS test, '(1.0,2.0,3.0)'::orbital_elements...
^
HINT: Expected (epoch_jd,q_au,e,inc_deg,omega_deg,Omega_deg,tp_jd,H,G).
-- Negative perihelion distance
SELECT 'negative_q' AS test, '(2460600.0,-1.0,0.5,10.0,73.0,80.0,2460319.0,3.33,0.12)'::orbital_elements;
ERROR: perihelion distance must be positive: -1.000000
LINE 1: SELECT 'negative_q' AS test, '(2460600.0,-1.0,0.5,10.0,73.0,...
^
-- Negative eccentricity
SELECT 'negative_e' AS test, '(2460600.0,1.0,-0.1,10.0,73.0,80.0,2460319.0,3.33,0.12)'::orbital_elements;
ERROR: eccentricity must be non-negative: -0.100000
LINE 1: SELECT 'negative_e' AS test, '(2460600.0,1.0,-0.1,10.0,73.0,...
^
-- MPC line too short
SELECT 'mpc_short' AS test, oe_from_mpc('too short');
ERROR: MPC line too short: 9 characters (need at least 103)
-- ============================================================
-- Test 9: MPC packed date decoding
-- Verify K24AM -> 2024-10-22 -> JD 2460605.5
-- ============================================================
SELECT 'mpc_epoch' AS test,
round(oe_epoch(oe_from_mpc(
'00001 3.33 0.12 K24AM 60.07966 73.42937 80.26860 10.58664 0.0789126 0.21406048 2.7660961 0 MPO838504 8738 115 1801-2024 0.65 M-v 30k MPCLINUX 0000 (1) Ceres 20240825'
))::numeric, 1) AS epoch_jd;
test | epoch_jd
-----------+-----------
mpc_epoch | 2460605.5
(1 row)
-- ============================================================
-- Test 10: Consistency -- small_body_observe vs comet_observe
-- Both pipelines should produce the same topocentric result
-- when fed the same elements and Earth position.
-- ============================================================
WITH ceres AS (
SELECT oe_from_mpc(
'00001 3.33 0.12 K24AM 60.07966 73.42937 80.26860 10.58664 0.0789126 0.21406048 2.7660961 0 MPO838504 8738 115 1801-2024 0.65 M-v 30k MPCLINUX 0000 (1) Ceres 20240825'
) AS oe
), earth AS (
SELECT planet_heliocentric(3, '2025-06-15 12:00:00+00') AS pos
)
SELECT 'pipeline_match' AS test,
round(abs(
topo_elevation(small_body_observe(c.oe, :boulder, '2025-06-15 12:00:00+00'))
- topo_elevation(comet_observe(
oe_perihelion(c.oe), oe_eccentricity(c.oe),
oe_inclination(c.oe), oe_arg_perihelion(c.oe), oe_raan(c.oe),
oe_tp(c.oe),
helio_x(e.pos), helio_y(e.pos), helio_z(e.pos),
:boulder, '2025-06-15 12:00:00+00'
))
)::numeric, 6) AS el_diff_deg
FROM ceres c, earth e;
test | el_diff_deg
----------------+-------------
pipeline_match | 0.000000
(1 row)