pg_orrery/test/sql/refraction.sql
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

140 lines
6.8 KiB
SQL

-- refraction regression tests
--
-- Tests atmospheric refraction (Bennett 1982), pressure/temperature
-- correction, apparent elevation, and refracted pass prediction.
\set boulder '''40.015N 105.270W 1655m'''::observer
-- ISS TLE for pass prediction tests (inline in CTEs below)
-- ============================================================
-- Test 1: Refraction at horizon (0 deg) ~ 0.57 deg
-- Bennett: R = 1/tan(0 + 7.31/4.4) arcmin ~ 34.5 arcmin ~ 0.575 deg
-- ============================================================
SELECT 'refr_horizon' AS test,
round(atmospheric_refraction(0.0)::numeric, 2) AS refr_deg;
-- ============================================================
-- Test 2: Refraction at 30 deg ~ 0.03 deg
-- ============================================================
SELECT 'refr_30deg' AS test,
round(atmospheric_refraction(30.0)::numeric, 3) AS refr_deg;
-- ============================================================
-- Test 3: Refraction at zenith (90 deg) ~ 0 deg
-- ============================================================
SELECT 'refr_zenith' AS test,
round(atmospheric_refraction(90.0)::numeric, 4) AS refr_deg;
-- ============================================================
-- Test 4: Refraction at 10 deg ~ 0.09 deg
-- ============================================================
SELECT 'refr_10deg' AS test,
round(atmospheric_refraction(10.0)::numeric, 3) AS refr_deg;
-- ============================================================
-- Test 5: Domain guard - refraction at -5 deg should return 0
-- (below -1 deg validity range)
-- ============================================================
SELECT 'refr_below_range' AS test,
atmospheric_refraction(-5.0) AS refr_deg;
-- ============================================================
-- Test 6: Domain guard - refraction at -10 deg returns 0 (no NaN)
-- ============================================================
SELECT 'refr_deep_neg' AS test,
atmospheric_refraction(-10.0) AS refr_deg,
atmospheric_refraction(-10.0) = atmospheric_refraction(-10.0) AS is_finite;
-- ============================================================
-- Test 7: Refraction at exactly -1 deg (edge of domain)
-- Should return a small positive value
-- ============================================================
SELECT 'refr_minus1' AS test,
atmospheric_refraction(-1.0) > 0 AS positive;
-- ============================================================
-- Test 8: Extended refraction with P/T correction
-- Standard: P=1010, T=10 should match basic function
-- ============================================================
SELECT 'refr_ext_standard' AS test,
round(atmospheric_refraction_ext(0.0, 1010.0, 10.0)::numeric, 2) AS refr_deg,
round(atmospheric_refraction(0.0)::numeric, 2) AS refr_basic,
round(atmospheric_refraction_ext(0.0, 1010.0, 10.0)::numeric, 4) =
round(atmospheric_refraction(0.0)::numeric, 4) AS match;
-- ============================================================
-- Test 9: Extended refraction - cold high-altitude
-- At P=700 mbar, T=-20 C, refraction should be reduced
-- ============================================================
SELECT 'refr_ext_cold' AS test,
round(atmospheric_refraction_ext(0.0, 700.0, -20.0)::numeric, 2) AS refr_deg,
atmospheric_refraction_ext(0.0, 700.0, -20.0) <
atmospheric_refraction(0.0) AS less_than_standard;
-- ============================================================
-- Test 10: Extended refraction - hot sea level
-- At P=1013, T=35 C, refraction should be slightly different
-- ============================================================
SELECT 'refr_ext_hot' AS test,
round(atmospheric_refraction_ext(0.0, 1013.0, 35.0)::numeric, 2) AS refr_deg;
-- ============================================================
-- Test 11: Apparent elevation for a topocentric observation
-- Sun near horizon: geometric el small -> apparent el higher
-- ============================================================
SELECT 'apparent_el' AS test,
round(topo_elevation(sun_observe(:boulder, '2024-03-20 00:30:00+00'))::numeric, 1) AS geometric,
round(topo_elevation_apparent(sun_observe(:boulder, '2024-03-20 00:30:00+00'))::numeric, 1) AS apparent,
topo_elevation_apparent(sun_observe(:boulder, '2024-03-20 00:30:00+00')) >
topo_elevation(sun_observe(:boulder, '2024-03-20 00:30:00+00')) AS refraction_positive;
-- ============================================================
-- Test 12: Apparent elevation for star high up (refraction small)
-- Polaris from Boulder has el ~49 deg; refraction ~0.01 deg
-- ============================================================
SELECT 'apparent_el_high' AS test,
round(topo_elevation_apparent(
star_observe(2.530303, 89.2641, :boulder, '2024-06-15 04:00:00+00'))::numeric, 1) AS apparent_deg;
-- ============================================================
-- Test 13: Refracted pass prediction returns results
-- Using the ISS TLE, should find passes in a week window
-- ============================================================
WITH iss AS (
SELECT '1 25544U 98067A 24001.50000000 .00016717 00000-0 10270-3 0 9025
2 25544 51.6400 208.9163 0006703 30.1694 61.7520 15.50100486 00001'::tle AS t
)
SELECT 'refracted_passes' AS test,
count(*) > 0 AS has_passes
FROM iss, predict_passes_refracted(
t, :boulder,
'2024-01-02 00:00:00+00', '2024-01-09 00:00:00+00');
-- ============================================================
-- Test 14: Refracted passes find at least as many as standard
-- Because refracted horizon is -0.569 deg, satellites visible ~35s earlier
-- ============================================================
SELECT 'refracted_more_passes' AS test,
(SELECT count(*) FROM predict_passes_refracted(
'1 25544U 98067A 24001.50000000 .00016717 00000-0 10270-3 0 9025
2 25544 51.6400 208.9163 0006703 30.1694 61.7520 15.50100486 00001'::tle,
:boulder,
'2024-01-02 00:00:00+00', '2024-01-09 00:00:00+00'))
>=
(SELECT count(*) FROM predict_passes(
'1 25544U 98067A 24001.50000000 .00016717 00000-0 10270-3 0 9025
2 25544 51.6400 208.9163 0006703 30.1694 61.7520 15.50100486 00001'::tle,
:boulder,
'2024-01-02 00:00:00+00', '2024-01-09 00:00:00+00'))
AS refracted_ge_standard;
-- ============================================================
-- Test 15: Monotonicity - refraction decreases with elevation
-- ============================================================
SELECT 'refr_monotonic' AS test,
atmospheric_refraction(0.0) > atmospheric_refraction(10.0) AS r0_gt_r10,
atmospheric_refraction(10.0) > atmospheric_refraction(30.0) AS r10_gt_r30,
atmospheric_refraction(30.0) > atmospheric_refraction(60.0) AS r30_gt_r60,
atmospheric_refraction(60.0) > atmospheric_refraction(90.0) AS r60_gt_r90;