Lagrange Equilibrium Points
Lagrange points are the five positions in a two-body gravitational system where a third, much smaller body experiences zero net acceleration in the co-rotating frame. Three of them — the collinear points L1, L2, L3 — were identified by Euler in 1767. The remaining two equilateral points L4 and L5 were found by Lagrange in 1772. The physical reality matches the mathematics: SOHO stares at the Sun from Earth-Sun L1, JWST observes from the cold shadow of L2, and several thousand Trojan asteroids share Jupiter’s orbit clustered around L4 and L5.
pg_orrery v0.20.0 adds 37 functions for computing Lagrange point positions across every gravitational system the extension already models: Sun-planet (8 planets, each with 5 L-points), Earth-Moon (5 points), and 19 planetary moons spanning the Galilean, Saturn, Uranus, and Mars families. The solver uses the Circular Restricted Three-Body Problem (CR3BP): Newton-Raphson on the quintic equilibrium polynomial for the collinear points, the classical equilateral geometry for L4/L5, all projected from the co-rotating frame into heliocentric ecliptic J2000 coordinates via the instantaneous orbital geometry.
Every L-point can be queried as a heliocentric position, a topocentric observation, or an equatorial RA/Dec. Distances from asteroids to any L-point let you identify Trojans in bulk. Hill radii define gravitational spheres of influence. The total is 140 equilibrium positions — 40 Sun-planet, 5 Earth-Moon, 95 planetary moon — all accessible with a single function call.
How you do it today
Section titled “How you do it today”Computing Lagrange point positions requires solving the CR3BP for the specific mass ratio of the system, then projecting from the co-rotating frame into a physical coordinate system:
- JPL Horizons: Supports specific L-points as targets (e.g.,
@L2for Sun-Earth L2). Limited to Sun-planet systems. No planetary moon L-points. Web and email interface, not designed for batch queries. - Skyfield (Python): No built-in Lagrange point support. You can manually compute CR3BP positions, but it requires rolling your own quintic solver and coordinate frame rotation.
- GMAT: Full CR3BP module for mission design — computes libration point orbits, manifold transfers, station-keeping budgets. Essential for trajectory design, but overkill for “where is L2 on the sky tonight?”
- STK/Astrogator: Commercial. Full three-body dynamics with halo orbit families. Not designed for batch surveys across all planets and moon systems.
For all of these, the workflow is: pick a specific system (usually Sun-Earth), request one L-point at a time, get the result in one coordinate frame. Building a survey across all planets and moon systems requires scripting loops and managing coordinate transforms.
What changes with pg_orrery
Section titled “What changes with pg_orrery”Six function families cover the complete Lagrange point problem:
| Family | Functions | Systems | Use case |
|---|---|---|---|
| Sun-planet | lagrange_heliocentric, lagrange_observe, lagrange_equatorial | 8 planets x 5 L-points | Where are the Sun-planet equilibrium positions? |
| Earth-Moon | lunar_lagrange_observe, lunar_lagrange_equatorial | 5 L-points | Cislunar equilibrium for Artemis-era planning |
| Planetary moons | galilean_lagrange_*, saturn_moon_lagrange_*, uranus_moon_lagrange_*, mars_moon_lagrange_* | 19 moons x 5 L-points | Every moon system pg_orrery tracks |
| Distance | lagrange_distance, lagrange_distance_oe | Any Sun-planet L-point | Trojan asteroid identification |
| Hill sphere | hill_radius, hill_radius_lunar, lagrange_zone_radius | All systems | Gravitational influence boundaries |
| Convenience | lagrange_mass_ratio, lagrange_point_name | Diagnostic | CR3BP parameters, human-readable labels |
Body IDs follow the existing conventions: Sun-planet uses 1=Mercury through 8=Neptune, Galilean moons 0-3 (Io-Callisto), Saturn moons 0-7 (Mimas-Hyperion), Uranus moons 0-4 (Miranda-Oberon), Mars moons 0-1 (Phobos-Deimos). Point IDs are 1-5 for L1-L5.
All IMMUTABLE functions also have DE variants (_de suffix) that use JPL DE440/441 positions when configured. See the DE Ephemeris guide.
What pg_orrery does not replace
Section titled “What pg_orrery does not replace”- No station-keeping. Real spacecraft at L1/L2 require periodic maneuvers to maintain their halo or Lissajous orbits. pg_orrery computes the equilibrium point, not the orbit around it.
- No halo or Lissajous orbits. JWST doesn’t sit at L2 --- it orbits L2 in a halo orbit with a roughly 400,000 km radius. The extension returns the point itself.
- No manifold transfers. The stable/unstable manifolds of L1/L2 are the backbone of low-energy transfer design. For trajectory optimization, use GMAT or NASA’s MONTE.
- No four-body effects. The three-body approximation breaks down when multiple large bodies interact (e.g., Sun-Jupiter-Saturn near conjunction). The L-point positions are instantaneous geometric solutions.
- No libration orbit families. The extension computes the static equilibrium point, not the family of periodic orbits around it (Lyapunov, halo, vertical, butterfly).
For mission design beyond “where is the L-point?”, use GMAT with its CR3BP module or MONTE for multi-body dynamics.
Try it
Section titled “Try it”Where is JWST?
Section titled “Where is JWST?”Sun-Earth L2 sits about 1.5 million km anti-sunward of Earth. JWST has been there since January 2022. The L2 heliocentric distance should be slightly beyond Earth’s orbital radius:
-- Sun-Earth L1 and L2 heliocentric distancesSELECT lagrange_point_name(p) AS point, round(helio_distance(lagrange_heliocentric(3, p, '2000-01-01 12:00:00+00'))::numeric, 2) AS sun_dist_auFROM generate_series(1, 2) AS p;L1 is at roughly 0.97 AU (sunward of Earth) and L2 at roughly 0.99 AU (anti-sunward). Both are within about 0.01 AU --- around 1.5 million km --- of Earth’s position.
-- L2 sky position (always near the anti-solar point)SELECT round(eq_ra(lagrange_equatorial(3, 2, now()))::numeric, 4) AS ra_hours, round(eq_dec(lagrange_equatorial(3, 2, now()))::numeric, 4) AS dec_deg, constellation(lagrange_equatorial(3, 2, now())) AS constellation;Sun-Earth L2 is always approximately 12 hours of RA offset from the Sun. Its constellation changes throughout the year as the Earth orbits.
Complete L-point survey for one planet
Section titled “Complete L-point survey for one planet”Map all five Sun-Earth Lagrange points at once:
SELECT lagrange_point_name(p) AS point, round(helio_distance(lagrange_heliocentric(3, p, now()))::numeric, 4) AS sun_dist_au, round(eq_ra(lagrange_equatorial(3, p, now()))::numeric, 4) AS ra_hours, round(eq_dec(lagrange_equatorial(3, p, now()))::numeric, 4) AS dec_deg, constellation(lagrange_equatorial(3, p, now())) AS constellationFROM generate_series(1, 5) AS p;L4 leads Earth by roughly 60 degrees in its orbit; L5 trails by roughly 60 degrees. L3 is on the opposite side of the Sun. L1 and L2 are close to Earth, straddling it along the Sun-Earth line.
L1 distances across the solar system
Section titled “L1 distances across the solar system”The L1 point for each planet lies between the Sun and the planet. Its heliocentric distance scales with the planet’s orbital radius:
SELECT body_id, CASE body_id WHEN 1 THEN 'Mercury' WHEN 2 THEN 'Venus' WHEN 3 THEN 'Earth' WHEN 4 THEN 'Mars' WHEN 5 THEN 'Jupiter' WHEN 6 THEN 'Saturn' WHEN 7 THEN 'Uranus' WHEN 8 THEN 'Neptune' END AS planet, round(helio_distance(lagrange_heliocentric(body_id, 1, '2000-01-01 12:00:00+00'))::numeric, 2) AS l1_sun_dist_auFROM generate_series(1, 8) AS body_idORDER BY body_id;For a reference, verified values at J2000: Mercury 0.46, Venus 0.71, Earth 0.97, Mars 1.38, Jupiter 4.63, Saturn 8.77, Uranus 19.44, Neptune 29.35 AU.
Trojan asteroid proximity
Section titled “Trojan asteroid proximity”Jupiter’s L4 and L5 host the largest known populations of Trojan asteroids. With lagrange_distance_oe, you can measure how close an asteroid with known orbital elements is to a Lagrange point:
-- (588) Achilles — the first discovered Trojan, near Jupiter L4SELECT round(lagrange_distance_oe( 5, 4, oe_from_mpc('00588 14.39 0.15 K249V 41.50128 169.10254 334.19917 13.04512 0.0760428 0.22963720 5.1763803 0 MPO752723 4285 88 1992-2024 0.49 M-v 30h MPCW 0000 (588) Achilles 20240913'), '2024-06-21 12:00:00+00')::numeric, 2) AS dist_to_l4_au;For a bulk survey, load an MPC catalog into a table and query every asteroid’s distance to Jupiter L4 and L5:
-- Find objects within 1 AU of Jupiter L4 (Trojan candidates)SELECT name, round(lagrange_distance_oe(5, 4, oe, '2024-06-21 12:00:00+00')::numeric, 3) AS dist_auFROM mpc_asteroidsWHERE lagrange_distance_oe(5, 4, oe, '2024-06-21 12:00:00+00') < 1.0ORDER BY dist_auLIMIT 20;The lagrange_distance function works with raw heliocentric positions if you already have them, while lagrange_distance_oe accepts orbital_elements directly and handles the Keplerian propagation internally.
Earth-Moon L1 for cislunar operations
Section titled “Earth-Moon L1 for cislunar operations”Earth-Moon L1 sits between the Earth and Moon at roughly 326,000 km from Earth. Artemis Gateway is planned for a near-rectilinear halo orbit around the Moon, but Earth-Moon L1 and L2 are natural waypoints for cislunar logistics:
-- Earth-Moon L1 distance and sky positionSELECT round(eq_distance(lunar_lagrange_equatorial(1, now()))::numeric, 0) AS dist_km, round(eq_ra(lunar_lagrange_equatorial(1, now()))::numeric, 4) AS ra_hours, round(eq_dec(lunar_lagrange_equatorial(1, now()))::numeric, 4) AS dec_deg;The distance should fall between 300,000 and 360,000 km, varying with the Moon’s orbital eccentricity. The sky position tracks the Moon’s motion, offset slightly toward Earth.
-- All 5 Earth-Moon L-points from BoulderSELECT lagrange_point_name(p) AS point, round(topo_elevation(lunar_lagrange_observe(p, '40.0N 105.3W 1655m'::observer, now()))::numeric, 2) AS el_deg, round(topo_azimuth(lunar_lagrange_observe(p, '40.0N 105.3W 1655m'::observer, now()))::numeric, 2) AS az_degFROM generate_series(1, 5) AS p;Planetary moon Lagrange points
Section titled “Planetary moon Lagrange points”Every moon system pg_orrery tracks has Lagrange points. The Galilean moons of Jupiter are the most accessible:
-- Jupiter-Io L4 and L5 (leading and trailing Io by ~60 degrees)SELECT lagrange_point_name(p) AS point, round(eq_ra(galilean_lagrange_equatorial(0, p, now()))::numeric, 4) AS ra_hours, round(eq_dec(galilean_lagrange_equatorial(0, p, now()))::numeric, 4) AS dec_degFROM generate_series(4, 5) AS p;
-- Saturn-Titan L1 from GreenwichSELECT round(topo_elevation(saturn_moon_lagrange_observe(5, 1, '51.4769N 0.0005W 0m'::observer, now()))::numeric, 2) AS el_deg;Titan is the most massive Saturn moon (GM ratio 4226.5, compared to millions for the icy moons), so its Lagrange points are the most physically significant in the Saturn system. For context, Saturn’s Tethys actually has co-orbital companions near its L4 and L5 --- Telesto and Calypso.
-- All four Galilean moon families: one L4 eachSELECT 'Io' AS moon, round(eq_ra(galilean_lagrange_equatorial(0, 4, now()))::numeric, 4) AS l4_raUNION ALLSELECT 'Europa', round(eq_ra(galilean_lagrange_equatorial(1, 4, now()))::numeric, 4)UNION ALLSELECT 'Ganymede', round(eq_ra(galilean_lagrange_equatorial(2, 4, now()))::numeric, 4)UNION ALLSELECT 'Callisto', round(eq_ra(galilean_lagrange_equatorial(3, 4, now()))::numeric, 4);Hill sphere survey
Section titled “Hill sphere survey”The Hill radius defines the gravitational sphere of influence for each planet. Inside this radius, the planet’s gravity dominates over the Sun’s:
SELECT body_id, CASE body_id WHEN 1 THEN 'Mercury' WHEN 2 THEN 'Venus' WHEN 3 THEN 'Earth' WHEN 4 THEN 'Mars' WHEN 5 THEN 'Jupiter' WHEN 6 THEN 'Saturn' WHEN 7 THEN 'Uranus' WHEN 8 THEN 'Neptune' END AS planet, round(hill_radius(body_id, now())::numeric, 4) AS hill_au, round((hill_radius(body_id, now()) * 149597870.7)::numeric, 0) AS hill_kmFROM generate_series(1, 8) AS body_id;Jupiter has the largest Hill sphere at roughly 0.35 AU (about 53 million km). Earth’s is roughly 0.01 AU (about 1.5 million km) --- L1 and L2 sit right at the Hill sphere boundary, which is not a coincidence: the Hill radius and the L1 distance are both derived from the same cubic approximation of the CR3BP.
-- Earth-Moon Hill radius (Moon's gravitational influence)SELECT round(hill_radius_lunar(now())::numeric, 6) AS lunar_hill_au, round((hill_radius_lunar(now()) * 149597870.7)::numeric, 0) AS lunar_hill_km;The Moon’s Hill radius is much smaller --- roughly 60,000 km. Objects within this radius are gravitationally bound to the Moon rather than the Earth.
Libration zone radius
Section titled “Libration zone radius”The lagrange_zone_radius function estimates the approximate extent of stable libration around each L-point. The physics differs by point type: L1/L2 zones scale with the Hill radius, L4/L5 zones scale with the square root of the mass ratio (horseshoe/tadpole orbit widths from Dermott 1981), and L3’s zone is extremely narrow:
SELECT lagrange_point_name(p) AS point, round(lagrange_zone_radius(5, p, now())::numeric, 4) AS zone_auFROM generate_series(1, 5) AS p;Jupiter’s L4/L5 zones are the widest, which explains why they collect so many Trojans. The L3 zone is vanishingly small for all planets.
Sanity checks
Section titled “Sanity checks”Verify the solver produces physically consistent results:
-- L-point distance to itself should be exactly zeroSELECT round(lagrange_distance( 5, 4, lagrange_heliocentric(5, 4, '2000-01-01 12:00:00+00'), '2000-01-01 12:00:00+00')::numeric, 10) AS self_distance;
-- L4 and L5 should be equidistant from the Sun (equilateral triangle)SELECT abs( helio_distance(lagrange_heliocentric(5, 4, '2000-01-01 12:00:00+00')) - helio_distance(lagrange_heliocentric(5, 5, '2000-01-01 12:00:00+00'))) < 0.001 AS l4_l5_equidistant;
-- L1 is always closer to the Sun than L2SELECT helio_distance(lagrange_heliocentric(3, 1, now())) < helio_distance(lagrange_heliocentric(3, 2, now())) AS l1_closer_than_l2;