Relativistic Effects

This example demonstrates relativistic corrections for precision applications.

Overview

Relativistic effects are significant for:

  • GNSS timing: Satellite clock corrections

  • Deep space navigation: Signal propagation delays

  • Precision timing: Gravitational time dilation

  • Astrometry: Light deflection near Sun

Effects Covered

Gravitational Time Dilation
  • Clocks run slower in stronger gravity

  • GPS satellite clocks run ~45 μs/day fast

  • Essential for GNSS accuracy

Special Relativistic Time Dilation
  • Moving clocks run slower

  • GPS satellites: ~7 μs/day slow

  • Combined effect: ~38 μs/day fast

Gravitational Potential: Gravity decreases with altitude, affecting time dilation for satellites at different orbital heights.

Geodetic Precession
  • Spin axis precession in curved spacetime

  • De Sitter precession: ~1.9 arcsec/year

  • Lense-Thirring: frame dragging

Orbital Motion: Relativistic effects accumulate over orbital periods, requiring corrections for precision applications.

Shapiro Delay
  • Light delay in gravitational field

  • Solar conjunction corrections

  • Affects planetary radar

Planetary Positions: Shapiro delay corrections are essential for ranging to planets, especially during solar conjunction.

Code Highlights

The example demonstrates:

  • Time dilation computation with gravitational_time_dilation()

  • Shapiro delay with shapiro_delay()

  • Geodetic precession with geodetic_precession()

  • Combined corrections for satellite clocks

Source Code

  1"""Relativistic effects in orbital mechanics and space systems.
  2
  3This example demonstrates practical applications of general relativity
  4and special relativity in modern space systems, including:
  5
  61. Gravitational time dilation (GPS, atomic clocks)
  72. Perihelion precession (Mercury, binary pulsars)
  83. Shapiro delay (interplanetary communication)
  94. Post-Newtonian orbital corrections
 105. Proper time in gravitational fields
 116. Lense-Thirring frame-dragging effects
 12
 13These effects are essential for high-precision positioning, timing,
 14and fundamental physics tests.
 15"""
 16
 17import os
 18from pathlib import Path
 19
 20import numpy as np
 21import plotly.graph_objects as go
 22
 23from pytcl.astronomical.relativity import (
 24    AU,
 25    C_LIGHT,
 26    G_GRAV,
 27    GM_EARTH,
 28    GM_SUN,
 29    geodetic_precession,
 30    gravitational_time_dilation,
 31    lense_thirring_precession,
 32    post_newtonian_acceleration,
 33    proper_time_rate,
 34    relativistic_range_correction,
 35    schwarzschild_precession_per_orbit,
 36    schwarzschild_radius,
 37    shapiro_delay,
 38)
 39
 40SHOW_PLOTS = os.environ.get("PYTCL_SHOW_PLOTS", "1") != "0"
 41OUTPUT_DIR = Path(__file__).resolve().parent / "output"
 42
 43
 44def plot_precession_effects() -> None:
 45    """Visualize relativistic precession effects for different orbits."""
 46    # Schwarzschild precession for different orbital radii around the Sun
 47    # Using Mercury-like eccentricity (0.206)
 48    radii_au = np.linspace(0.4, 0.7, 50)  # Mercury to Venus range
 49    radii_m = radii_au * AU
 50    mercury_eccentricity = 0.20563593
 51
 52    # Compute precession for each radius (in arcsec per century)
 53    precessions = np.array(
 54        [
 55            schwarzschild_precession_per_orbit(r_m, mercury_eccentricity, GM_SUN)
 56            * 36525
 57            * 3600
 58            for r_m in radii_m
 59        ]
 60    )
 61
 62    fig = go.Figure()
 63
 64    fig.add_trace(
 65        go.Scatter(
 66            x=radii_au,
 67            y=precessions,
 68            mode="lines+markers",
 69            name="Schwarzschild Precession",
 70            line=dict(color="steelblue", width=2),
 71            marker=dict(size=6),
 72            hovertemplate="<b>Orbital Radius</b><br>%{x:.2f} AU<br>"
 73            "<b>Precession</b><br>%{y:.2f} arcsec/century<extra></extra>",
 74        )
 75    )
 76
 77    # Mercury actual position
 78    mercury_precession = (
 79        schwarzschild_precession_per_orbit(0.387 * AU, mercury_eccentricity, GM_SUN)
 80        * 36525
 81        * 3600
 82    )
 83    fig.add_trace(
 84        go.Scatter(
 85            x=[0.387],
 86            y=[mercury_precession],
 87            mode="markers+text",
 88            name="Mercury (Observed)",
 89            marker=dict(size=12, color="red", symbol="star"),
 90            text=['Mercury\n43"/century'],
 91            textposition="top center",
 92            hovertemplate="<b>Mercury</b><br>Radius: 0.387 AU<br>Precession: %{y:.2f} arcsec/century<extra></extra>",
 93        )
 94    )
 95
 96    fig.update_layout(
 97        title="Relativistic Perihelion Precession Around the Sun",
 98        xaxis_title="Orbital Radius (AU)",
 99        yaxis_title="Precession (arcsec/century)",
100        hovermode="x unified",
101        height=500,
102        plot_bgcolor="rgba(240,240,240,0.5)",
103        showlegend=True,
104    )
105
106    if SHOW_PLOTS:
107        fig.show()
108    else:
109        fig.write_html(
110            str(OUTPUT_DIR / "relativity_demo.html"),
111            include_plotlyjs="cdn",
112            div_id="relativity_demo",
113        )
114
115
116def plot_time_dilation_with_altitude() -> None:
117    """Visualize gravitational and special relativistic time dilation."""
118    altitudes_km = np.linspace(0, 35786, 100)  # From Earth surface to geostationary
119    altitudes_m = altitudes_km * 1e3 + 6.371e6  # Convert to meters from center
120
121    # Orbital velocities for circular orbits at each altitude
122    orbital_velocities = np.sqrt(GM_EARTH / altitudes_m)
123
124    # Time dilation factors
125    grav_dilation = np.array(
126        [gravitational_time_dilation(r, GM_EARTH) for r in altitudes_m]
127    )
128    sr_effect = (orbital_velocities**2) / (2.0 * C_LIGHT**2)
129    net_rate = grav_dilation - sr_effect
130
131    fig = go.Figure()
132
133    fig.add_trace(
134        go.Scatter(
135            x=altitudes_km,
136            y=(1 - grav_dilation) * 1e9,
137            mode="lines",
138            name="Gravitational Effect",
139            line=dict(color="blue", width=2),
140            hovertemplate="<b>Altitude:</b> %{x:.0f} km<br>"
141            "<b>Time gain rate:</b> %{y:.2f} ns/s<extra></extra>",
142        )
143    )
144
145    fig.add_trace(
146        go.Scatter(
147            x=altitudes_km,
148            y=sr_effect * 1e9,
149            mode="lines",
150            name="Special Relativistic Effect",
151            line=dict(color="red", width=2),
152            hovertemplate="<b>Altitude:</b> %{x:.0f} km<br>"
153            "<b>Time loss rate:</b> %{y:.2f} ns/s<extra></extra>",
154        )
155    )
156
157    fig.add_trace(
158        go.Scatter(
159            x=altitudes_km,
160            y=(1 - net_rate) * 1e9,
161            mode="lines",
162            name="Net Effect",
163            line=dict(color="green", width=2.5, dash="dash"),
164            hovertemplate="<b>Altitude:</b> %{x:.0f} km<br>"
165            "<b>Net time rate:</b> %{y:.2f} ns/s<extra></extra>",
166        )
167    )
168
169    # Mark GPS orbit
170    gps_alt = 20200
171    idx_gps = np.argmin(np.abs(altitudes_km - gps_alt))
172    fig.add_vline(
173        x=gps_alt,
174        line_dash="dash",
175        line_color="gray",
176        annotation_text="GPS Orbit",
177        annotation_position="top right",
178    )
179
180    fig.update_layout(
181        title="Relativistic Time Dilation vs Altitude",
182        xaxis_title="Altitude (km)",
183        yaxis_title="Time Rate Difference (ns/s)",
184        hovermode="x unified",
185        height=550,
186        plot_bgcolor="rgba(240,240,240,0.5)",
187        legend=dict(x=0.65, y=0.05),
188    )
189
190    if SHOW_PLOTS:
191        fig.show()
192    else:
193        fig.write_html(
194            str(OUTPUT_DIR / "relativity_demo.html"),
195            include_plotlyjs="cdn",
196            div_id="relativity_demo",
197        )
198
199
200def example_gps_time_dilation():
201    """Demonstrate time dilation effects in GPS satellites."""
202    print("=" * 70)
203    print("EXAMPLE 1: GPS Time Dilation Effects")
204    print("=" * 70)
205
206    # Show visualization
207    plot_time_dilation_with_altitude()
208
209    # GPS orbital parameters
210    r_gps = 26.56e6  # meters (~20,200 km altitude)
211    v_gps = 3870.0  # m/s (circular orbit velocity)
212
213    # Compute dilation factors
214    dilation = gravitational_time_dilation(r_gps, GM_EARTH)
215
216    # Special relativistic effect
217    sr_effect = (v_gps**2) / (2.0 * C_LIGHT**2)
218
219    # General relativistic effect
220    gr_effect = GM_EARTH / (C_LIGHT**2 * r_gps)
221
222    # Proper time rate
223    rate = proper_time_rate(v_gps, r_gps, GM_EARTH)
224
225    print(f"\nGPS Satellite Orbital Parameters:")
226    print(f"  Altitude: {(r_gps - 6.371e6) / 1e3:.1f} km")
227    print(f"  Orbital velocity: {v_gps:.1f} m/s")
228    print(f"  Orbital period: ~12 hours")
229
230    print(f"\nTime Dilation Effects:")
231    print(f"  Gravitational time dilation factor: {dilation:.15f}")
232    print(f"    (Time runs slower in gravity field)")
233
234    print(f"\nTime Rate Comparison (per day):")
235    print(f"  Special relativistic effect: -{sr_effect * 86400 * 1e9:.1f} ns/day")
236    print(f"    (Satellite moving fast, slows down time)")
237    print(f"  General relativistic effect: +{gr_effect * 86400 * 1e9:.1f} ns/day")
238    print(f"    (Weaker gravity field, speeds up time)")
239    print(f"  Net effect: {(1 - rate) * 86400 * 1e9:.1f} ns/day")
240    print(f"    (Net toward weaker field = time speeds up in orbit)")
241
242    print(f"\nPractical Impact:")
243    total_daily_shift = (1 - rate) * 86400
244    print(
245        f"  Without correction, GPS clock would drift: {total_daily_shift:.1f} seconds/day"
246    )
247    print(
248        f"  This would cause positioning error: {total_daily_shift * C_LIGHT / 2:.0f} meters/day"
249    )
250    print(
251        f"  GPS atomic clocks must be pre-offset by {-total_daily_shift * 1e6:.1f} microseconds/day"
252    )
253
254    # Time dilation vs altitude
255    print(f"\n" + "-" * 70)
256    print("Time dilation effect at different altitudes:")
257    print("-" * 70)
258
259    altitudes = [0, 300, 500, 1000, 5000, 20200, 35786]  # km
260
261    for alt_km in altitudes:
262        r = 6.371e6 + alt_km * 1000
263        # Approximate circular orbit velocity
264        v = np.sqrt(GM_EARTH / r)
265        time_rate = proper_time_rate(v, r, GM_EARTH)
266        daily_shift = (1 - time_rate) * 86400
267
268        if alt_km == 0:
269            alt_name = "Earth surface"
270        elif alt_km == 35786:
271            alt_name = "Geostationary"
272        elif alt_km == 20200:
273            alt_name = "GPS orbit"
274        else:
275            alt_name = ""
276
277        print(f"  {alt_km:>6} km {alt_name:<20} {daily_shift:>8.2f} seconds/day")
278
279
280def example_mercury_precession():
281    """Mercury's perihelion precession: a test of General Relativity."""
282    print("\n" + "=" * 70)
283    print("EXAMPLE 2: Mercury's Perihelion Precession")
284    print("=" * 70)
285
286    # Show visualization
287    plot_precession_effects()
288
289    # Mercury orbital elements
290    a_mercury = 0.38709927 * AU  # Semi-major axis
291    e_mercury = 0.20563593  # Eccentricity
292    orbital_period = 87.969  # days
293
294    # Compute GR precession per orbit
295    precession_rad = schwarzschild_precession_per_orbit(a_mercury, e_mercury, GM_SUN)
296    precession_arcsec = precession_rad * 206265  # Convert radians to arcseconds
297
298    # Compute precession per century
299    orbits_per_century = 36525 / orbital_period
300    precession_per_century = precession_arcsec * orbits_per_century
301
302    print(f"\nMercury Orbital Parameters:")
303    print(f"  Semi-major axis: {a_mercury / AU:.8f} AU = {a_mercury / 1e9:.3f} Gm")
304    print(f"  Eccentricity: {e_mercury:.8f}")
305    print(f"  Orbital period: {orbital_period:.3f} days")
306    print(f"  Perturbing body: Sun (GM = {GM_SUN:.3e} m³/s²)")
307
308    print(f"\nGeneral Relativistic Perihelion Precession:")
309    print(f"  Per orbit: {precession_arcsec:.4f} arcseconds")
310    print(f"  Per century: {precession_per_century:.2f} arcseconds")
311
312    print(f"\nHistorical Context:")
313    print(f"  Einstein's prediction (1916): ~43 arcsec/century")
314    print(f"  Observed (Le Verrier, 1859): 5600 ± 30 arcsec/century")
315    print(f"    (includes Newtonian planetary perturbations)")
316    print(f"  Newtonian precession: ~5557 arcsec/century")
317    print(f"  GR contribution: ~43 arcsec/century")
318    print(f"  Calculated: {precession_per_century:.1f} arcsec/century OK")
319    print(f"\nThis was Einstein's first experimental confirmation of GR!")
320
321
322def example_shapiro_delay():
323    """Shapiro delay in interplanetary communication."""
324    print("\n" + "=" * 70)
325    print("EXAMPLE 3: Shapiro Delay in Interplanetary Communication")
326    print("=" * 70)
327
328    print(f"\nScenario: Earth-Sun-Spacecraft geometry at superior conjunction")
329    print(f"(Spacecraft is on opposite side of Sun from Earth)")
330
331    # Geometry at superior conjunction
332    earth_pos = np.array([1.496e11, 0.0, 0.0])  # 1 AU
333    spacecraft_pos = np.array([-(1.496e11 + 0.8e11), 0.0, 0.0])  # ~1.8 AU away
334    sun_pos = np.array([0.0, 0.0, 0.0])
335
336    # Compute Shapiro delay
337    delay = shapiro_delay(earth_pos, spacecraft_pos, sun_pos, GM_SUN)
338
339    # Distance from Earth to spacecraft
340    distance = np.linalg.norm(earth_pos - spacecraft_pos)
341
342    # Nominal light travel time without Shapiro delay
343    light_travel = distance / C_LIGHT
344
345    print(f"\nParameters:")
346    print(f"  Earth distance from Sun: {np.linalg.norm(earth_pos) / AU:.3f} AU")
347    print(
348        f"  Spacecraft distance from Sun: {np.linalg.norm(spacecraft_pos) / AU:.3f} AU"
349    )
350    print(
351        f"  Earth-spacecraft distance: {distance / AU:.3f} AU = {distance / 1.496e11:.3f} AU"
352    )
353
354    print(f"\nRanging Measurement:")
355    print(f"  Signal travel time (geometric): {light_travel:.3f} seconds")
356    print(f"  Shapiro delay (GR correction): {delay * 1e6:.1f} microseconds")
357    print(f"  Total propagation time: {light_travel + delay:.3f} seconds")
358    print(f"  Error if uncorrected: {delay * C_LIGHT / 2:.0f} meters")
359
360    print(f"\nPractical Impact:")
361    print(f"  Mariner 10 Venus flybys: Shapiro delay ~50 microseconds")
362    print(f"  Cassini Saturn probe: Shapiro delay ~100+ microseconds")
363    print(f"  New Horizons: Shapiro delay ~200+ microseconds at aphelion")
364    print(
365        f"\nWithout Shapiro delay correction, spacecraft navigation would be off by kilometers!"
366    )
367
368    # Shapiro delay vs Sun distance
369    print(f"\n" + "-" * 70)
370    print("Shapiro delay at different distances from Sun:")
371    print("-" * 70)
372
373    # Earth at various distances
374    for au_dist in [0.5, 1.0, 2.0, 5.0]:
375        earth_var = np.array([au_dist * AU, 0.0, 0.0])
376        craft = np.array([-(au_dist * AU + 0.8 * AU), 0.0, 0.0])
377
378        delay_var = shapiro_delay(earth_var, craft, sun_pos, GM_SUN)
379        print(f"  Earth at {au_dist:.1f} AU: {delay_var * 1e6:.1f} microseconds")
380
381
382def example_post_newtonian_acceleration():
383    """Post-Newtonian orbital corrections."""
384    print("\n" + "=" * 70)
385    print("EXAMPLE 4: Post-Newtonian Orbital Corrections")
386    print("=" * 70)
387
388    # Low Earth Orbit satellite
389    r = 6.678e6  # ~300 km altitude
390
391    # Circular orbit velocity
392    v = np.sqrt(GM_EARTH / r)
393
394    # Set up position and velocity vectors
395    r_vec = np.array([r, 0.0, 0.0])
396    v_vec = np.array([0.0, v, 0.0])
397
398    # Compute accelerations
399    a_newt = -GM_EARTH / r**2 * np.array([1.0, 0.0, 0.0])
400    a_total = post_newtonian_acceleration(r_vec, v_vec, GM_EARTH)
401    a_pn = a_total - a_newt
402
403    # Compute relative correction
404    correction_magnitude = np.linalg.norm(a_pn)
405    relative_correction = correction_magnitude / np.linalg.norm(a_newt)
406
407    print(f"\nLEO Satellite Parameters:")
408    print(f"  Altitude: {(r - 6.371e6) / 1e3:.0f} km")
409    print(f"  Orbital velocity: {v:.1f} m/s")
410    print(f"  Orbital period: {2 * np.pi * r / v / 60:.1f} minutes")
411
412    print(f"\nAcceleration Comparison:")
413    print(f"  Newtonian acceleration: {np.linalg.norm(a_newt):.6f} m/s²")
414    print(f"  Post-Newtonian correction: {correction_magnitude:.3e} m/s²")
415    print(
416        f"  Relative correction: {relative_correction * 1e6:.1f} ppm (parts per million)"
417    )
418
419    print(f"\nOrbit Impact Over One Day:")
420    orbital_period = 2 * np.pi * r / v
421    daily_orbits = 86400 / orbital_period
422
423    # Accumulated error: dv = a*t
424    velocity_error = correction_magnitude * 86400
425
426    # Approximate range error (v*t / 2)
427    range_error = velocity_error * 86400 / 2
428
429    print(f"  Orbits per day: {daily_orbits:.1f}")
430    print(f"  Velocity error accumulation: {velocity_error:.3e} m/s")
431    print(f"  Position error: ~{range_error:.3f} meters")
432
433    print(f"\nPractical Impact:")
434    print(f"  For LEO satellites (e.g., ISS, TDRSS):")
435    print(f"    - PN effects are measurable but small (ppm level)")
436    print(f"    - Other perturbations (gravity harmonics, drag) dominate")
437    print(f"    - PN corrections important for ultra-precise orbit determination")
438    print(f"  For high-precision applications (LAGEOS, GPS):")
439    print(f"    - PN corrections must be included in force models")
440
441
442def example_geodetic_precession():
443    """Geodetic (de Sitter) precession of orbital plane."""
444    print("\n" + "=" * 70)
445    print("EXAMPLE 5: Geodetic Precession")
446    print("=" * 70)
447
448    # Different orbits
449    orbits = [
450        ("ISS", 6.678e6, 0.0, np.radians(51.6)),
451        ("LAGEOS", 12.27e6, 0.0045, np.radians(109.9)),
452        ("Polar", 6.678e6, 0.0, np.radians(90.0)),
453    ]
454
455    print(f"\nGeodetic Precession (causes orbital plane to rotate):")
456    print(f"  Formula: deltaomega_geodetic = 3pi GM / (c² a (1-e²)) per orbit")
457    print(f"  (Positive = prograde; magnitude independent of inclination)")
458    print("-" * 70)
459    print(f"{'Orbit':<12} {'Altitude':<12} {'Inclination':<16} {'Precession':<20}")
460    print("-" * 70)
461
462    for name, a, e, inc in orbits:
463        prec = geodetic_precession(a, e, inc, GM_EARTH)
464        alt_km = (a - 6.371e6) / 1e3
465        inc_deg = np.degrees(inc)
466
467        # Convert to degrees per year
468        orbital_period = 2 * np.pi * np.sqrt(a**3 / GM_EARTH)
469        orbits_per_year = 365.25 * 86400 / orbital_period
470        prec_per_year = prec * orbits_per_year * 206265  # arcsec/year
471
472        print(
473            f"{name:<12} {alt_km:>7.0f} km   {inc_deg:>6.1f}°         {prec_per_year:>8.2f} arcsec/year"
474        )
475
476    # De Sitter precession of the Earth-Moon gyroscope orbiting the Sun
477    prec_sun = geodetic_precession(AU, 0.0167, 0.0, GM_SUN)
478    per_century = prec_sun * 100 * 206265  # ~100 orbits per century
479    print(f"\nDe Sitter precession of the Earth-Moon system around the Sun:")
480    print(f"  {per_century:.2f} arcsec/century (confirmed by lunar laser ranging)")
481
482    print(f"\nPhysical Interpretation:")
483    print(f"  - Geodetic precession arises from parallel transport of velocity")
484    print(f"  - Also called de Sitter precession (discovered 1916)")
485    print(f"  - Related to Lense-Thirring effect (frame dragging)")
486    print(f"  - Magnitude depends only on orbit size and eccentricity, not inclination")
487
488
489def example_lense_thirring_precession():
490    """Lense-Thirring (frame-dragging) effect on orbital node."""
491    print("\n" + "=" * 70)
492    print("EXAMPLE 6: Lense-Thirring Effect (Frame-Dragging)")
493    print("=" * 70)
494
495    # LAGEOS satellite parameters
496    a = 12.27e6  # Semi-major axis
497    e = 0.0045
498    i = np.radians(109.9)
499    L_earth = 5.86e33  # Earth's spin angular momentum (kg·m²/s)
500
501    # Compute Lense-Thirring nodal precession rate (rad/s)
502    rate = lense_thirring_precession(a, e, i, L_earth, GM_EARTH)
503
504    # Convert to observable amounts
505    orbital_period = 2 * np.pi * np.sqrt(a**3 / GM_EARTH)
506    precession_per_orbit = rate * orbital_period  # radians per orbit
507    mas_per_year = rate * 86400 * 365.25 * 206265 * 1e3
508
509    print(f"\nLAGEOS Satellite (Test of General Relativity):")
510    print(
511        f"  Semi-major axis: {a / 1e6:.2f} Mm = {(a - 6.371e6) / 1e3:.0f} km altitude"
512    )
513    print(f"  Eccentricity: {e:.4f}")
514    print(f"  Inclination: {np.degrees(i):.2f}°")
515    print(
516        f"  Orbital period: {orbital_period / 60:.0f} minutes = {orbital_period / 3600:.2f} hours"
517    )
518
519    print(f"\nLense-Thirring Effect:")
520    print(f"  Nodal precession rate: {rate:.3e} rad/s (prograde)")
521    print(
522        f"  Precession per orbit: {precession_per_orbit * 206265 * 1e6:.1f} microarcseconds"
523    )
524    print(f"  Precession per year: {mas_per_year:.1f} milliarcseconds")
525    print(f"  Detection method: Laser ranging (~mm precision on altitude)")
526
527    print(f"\nHistorical Context:")
528    print(f"  - Predicted by Lense & Thirring (1918)")
529    print(f"  - Represents frame-dragging effect of rotating body")
530    print(f"  - LAGEOS confirmed at ~20% accuracy (1998)")
531    print(f"  - Gravity Probe B tested at higher precision (~0.5%)")
532
533    print(f"\nPhysical Interpretation:")
534    print(f"  - Earth's rotation 'drags' spacetime around it")
535    print(f"  - Causes orbits to precess even though not purely axisymmetric")
536    print(f"  - Similar to electromagnetic induction but for gravity")
537
538
539def example_relativistic_range_correction():
540    """Relativistic corrections to ranging measurements."""
541    print("\n" + "=" * 70)
542    print("EXAMPLE 7: Relativistic Range Corrections")
543    print("=" * 70)
544
545    print(f"\nRanging measurement technique:")
546    print(f"  - Send light signal to reflector (satellite or corner cube)")
547    print(f"  - Measure round-trip travel time: t = 2d/c")
548    print(f"  - Compute distance: d = ct/2")
549    print(f"\nSpacetime curvature (Shapiro delay) makes the measured range")
550    print(f"slightly longer than the geometric distance between the endpoints:")
551    print(f"  deltarho = (2GM/c²) ln((r1 + r2 + rho) / (r1 + r2 - rho))")
552
553    # Lunar laser ranging: ground station to a lunar retroreflector,
554    # through Earth's gravitational field
555    r_station = 6.371e6  # Ground station geocentric radius (m)
556    r_moon = 3.844e8  # Lunar retroreflector geocentric radius (m)
557    rho_moon = r_moon - r_station
558    r_corr_moon = relativistic_range_correction(r_station, r_moon, rho_moon, GM_EARTH)
559
560    print(f"\nLunar Laser Ranging (LLR):")
561    print(f"  Target: Apollo 11, 14, 15 retroreflectors on Moon")
562    print(f"  Range: {rho_moon / 1e3:,.0f} km")
563    print(f"  Relativistic correction: {r_corr_moon * 1e3:.1f} mm (one-way)")
564    print(f"  Precision of LLR: ~2-3 cm")
565    print(f"  The correction exceeds the measurement precision,")
566    print(f"  so it must be modeled in the ranging analysis.")
567
568    # GPS pseudoranging: satellite at zenith above the station
569    r_gps = 26.561e6  # GPS orbit radius (m)
570    rho_gps = r_gps - r_station
571    r_corr_gps = relativistic_range_correction(r_station, r_gps, rho_gps, GM_EARTH)
572
573    print(f"\nGPS Pseudoranging (satellite at zenith):")
574    print(f"  Range: {rho_gps / 1e3:,.0f} km")
575    print(f"  Relativistic correction: {r_corr_gps * 1e3:.2f} mm")
576
577    print(f"\n" + "-" * 70)
578    print("Shapiro range correction, ground station to overhead target:")
579    print("-" * 70)
580    print(f"{'Target':<22} {'Range (km)':<14} {'Correction (mm)':<18}")
581    print("-" * 70)
582
583    targets = {
584        "ISS (400 km)": 6.771e6,
585        "GPS (20,200 km)": 26.56e6,
586        "GEO (35,800 km)": 42.16e6,
587        "Moon (384,000 km)": 3.844e8,
588    }
589
590    for name, r_target in targets.items():
591        rho = r_target - r_station
592        corr = relativistic_range_correction(r_station, r_target, rho, GM_EARTH)
593        print(f"{name:<22} {rho / 1e3:>10,.0f}    {corr * 1e3:>14.2f}")
594
595    # Ranging through the Sun's field: Earth to the solar photosphere
596    r_sun_surface = 6.96e8  # Solar radius (m)
597    rho_sun = AU - r_sun_surface
598    corr_sun = relativistic_range_correction(AU, r_sun_surface, rho_sun, GM_SUN)
599    print("-" * 70)
600    print(f"\nRanging from Earth down to the solar surface (through the Sun's")
601    print(f"own field) picks up ~= {corr_sun / 1e3:.1f} km of Shapiro correction —")
602    print(f"this is why the delay dominates interplanetary ranging near")
603    print(f"superior conjunction.")
604
605
606if __name__ == "__main__":
607    """Run all relativity examples."""
608    print("\n")
609    print("+" + "=" * 68 + "+")
610    print("|" + " " * 68 + "|")
611    print("|" + "  Relativistic Effects in Space Systems".center(68) + "|")
612    print("|" + " " * 68 + "|")
613    print("+" + "=" * 68 + "+")
614
615    # Run examples
616    example_gps_time_dilation()
617    example_mercury_precession()
618    example_shapiro_delay()
619    example_post_newtonian_acceleration()
620    example_geodetic_precession()
621    example_lense_thirring_precession()
622    example_relativistic_range_correction()
623
624    OUTPUT_DIR = Path("docs/_static/images/examples")
625    OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
626
627    print("\n" + "=" * 70)
628    print("All relativity examples completed successfully!")
629    print("=" * 70 + "\n")

Running the Example

python examples/relativity_demo.py

See Also