pg_orrery/test/sql/lambert_transfer.sql
Ryan Malloy 70420c3b4f Phase 4: Lambert transfer orbit solver for interplanetary trajectories
Add Universal Variable Lambert solver for computing transfer orbits
between any two planets. Enables pork chop plot generation as SQL:

  SELECT dep_date, arr_date, lambert_c3(3, 4, dep_date, arr_date)
  FROM generate_series(...) dep CROSS JOIN generate_series(...) arr;

New functions:
- lambert_transfer(dep_body, arr_body, dep_time, arr_time) → RECORD
  Returns C3 departure/arrival (km^2/s^2), v_infinity (km/s),
  time of flight (days), and transfer orbit SMA (AU).
- lambert_c3(dep_body, arr_body, dep_time, arr_time) → float8
  Convenience: departure C3 only, NULL on solver failure.

The solver uses Stumpff functions for unified elliptic/parabolic/hyperbolic
handling, with Newton-Raphson iteration and bisection fallback.
Each solve is sub-millisecond; PARALLEL SAFE for batch computation.

All 11 regression tests pass.
2026-02-16 02:00:09 -07:00

102 lines
4.6 KiB
SQL

-- lambert_transfer regression tests
--
-- Tests interplanetary Lambert transfer orbit solver.
-- Reference: Hohmann Earth-Mars transfer ~8.5 months, C3 ~8-16 km^2/s^2.
-- ============================================================
-- Test 1: Earth-Mars Hohmann-like transfer (2026 window)
-- Typical Earth-Mars C3 departure: 8-20 km^2/s^2
-- Transfer time: ~200-300 days
-- ============================================================
SELECT 'earth_mars_transfer' AS test,
round(c3_departure::numeric, 1) AS c3_dep,
round(c3_arrival::numeric, 1) AS c3_arr,
round(v_inf_departure::numeric, 1) AS vinf_dep,
round(v_inf_arrival::numeric, 1) AS vinf_arr,
round(tof_days::numeric, 0) AS tof,
round(transfer_sma::numeric, 2) AS sma_au
FROM lambert_transfer(3, 4,
'2026-05-01 00:00:00+00'::timestamptz,
'2027-01-15 00:00:00+00'::timestamptz);
-- ============================================================
-- Test 2: Earth-Venus transfer
-- Venus is closer, so C3 should be lower (~5-15 km^2/s^2).
-- Transfer time: ~100-200 days typical.
-- ============================================================
SELECT 'earth_venus_transfer' AS test,
round(c3_departure::numeric, 1) AS c3_dep,
round(tof_days::numeric, 0) AS tof,
round(transfer_sma::numeric, 2) AS sma_au
FROM lambert_transfer(3, 2,
'2026-06-01 00:00:00+00'::timestamptz,
'2026-10-15 00:00:00+00'::timestamptz);
-- ============================================================
-- Test 3: lambert_c3 convenience function
-- Should match c3_departure from lambert_transfer.
-- ============================================================
SELECT 'c3_convenience' AS test,
round(lambert_c3(3, 4,
'2026-05-01 00:00:00+00'::timestamptz,
'2027-01-15 00:00:00+00'::timestamptz)::numeric, 1) AS c3;
-- ============================================================
-- Test 4: Earth-Jupiter transfer (longer, higher energy)
-- C3 departure typically 70-100+ km^2/s^2 for direct transfers.
-- ============================================================
SELECT 'earth_jupiter_transfer' AS test,
round(c3_departure::numeric, 0) AS c3_dep,
round(tof_days::numeric, 0) AS tof
FROM lambert_transfer(3, 5,
'2026-01-01 00:00:00+00'::timestamptz,
'2028-06-01 00:00:00+00'::timestamptz);
-- ============================================================
-- Test 5: C3 is in reasonable physical range
-- Any Earth-Mars transfer should have C3 > 0 and < 200.
-- ============================================================
SELECT 'c3_range_check' AS test,
lambert_c3(3, 4,
'2026-05-01 00:00:00+00'::timestamptz,
'2027-01-15 00:00:00+00'::timestamptz) > 0 AS positive,
lambert_c3(3, 4,
'2026-05-01 00:00:00+00'::timestamptz,
'2027-01-15 00:00:00+00'::timestamptz) < 200 AS reasonable;
-- ============================================================
-- Test 6: SMA should be between Earth and Mars orbits
-- Hohmann transfer SMA ~ 1.26 AU for Earth-Mars.
-- Realistic transfers range ~1.1-2.5 AU.
-- ============================================================
SELECT 'sma_range_check' AS test,
transfer_sma > 0.8 AS above_venus,
transfer_sma < 5.0 AS below_jupiter
FROM lambert_transfer(3, 4,
'2026-05-01 00:00:00+00'::timestamptz,
'2027-01-15 00:00:00+00'::timestamptz);
-- ============================================================
-- Test 7: Error - same body departure and arrival
-- ============================================================
SELECT 'same_body_error' AS test, lambert_c3(3, 3, now(), now() + interval '100 days');
-- ============================================================
-- Test 8: Error - arrival before departure
-- ============================================================
SELECT 'time_error' AS test,
lambert_transfer(3, 4,
'2027-01-01 00:00:00+00'::timestamptz,
'2026-01-01 00:00:00+00'::timestamptz);
-- ============================================================
-- Test 9: Mini pork chop - 3x3 grid of departure/arrival dates
-- All should return non-NULL results.
-- ============================================================
SELECT 'pork_chop_mini' AS test,
dep_date::date AS dep,
arr_date::date AS arr,
round(lambert_c3(3, 4, dep_date, arr_date)::numeric, 1) AS c3
FROM generate_series('2026-04-01'::timestamptz, '2026-06-01'::timestamptz, interval '30 days') dep_date
CROSS JOIN generate_series('2027-01-01'::timestamptz, '2027-03-01'::timestamptz, interval '30 days') arr_date;