nrl-tracker 0.21.4__py3-none-any.whl → 1.7.5__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. {nrl_tracker-0.21.4.dist-info → nrl_tracker-1.7.5.dist-info}/METADATA +57 -10
  2. nrl_tracker-1.7.5.dist-info/RECORD +165 -0
  3. pytcl/__init__.py +4 -3
  4. pytcl/assignment_algorithms/__init__.py +28 -0
  5. pytcl/assignment_algorithms/data_association.py +2 -7
  6. pytcl/assignment_algorithms/gating.py +10 -10
  7. pytcl/assignment_algorithms/jpda.py +40 -40
  8. pytcl/assignment_algorithms/nd_assignment.py +379 -0
  9. pytcl/assignment_algorithms/network_flow.py +371 -0
  10. pytcl/assignment_algorithms/three_dimensional/assignment.py +3 -3
  11. pytcl/astronomical/__init__.py +162 -8
  12. pytcl/astronomical/ephemerides.py +533 -0
  13. pytcl/astronomical/reference_frames.py +865 -56
  14. pytcl/astronomical/relativity.py +473 -0
  15. pytcl/astronomical/sgp4.py +710 -0
  16. pytcl/astronomical/special_orbits.py +532 -0
  17. pytcl/astronomical/tle.py +558 -0
  18. pytcl/atmosphere/__init__.py +45 -3
  19. pytcl/atmosphere/ionosphere.py +512 -0
  20. pytcl/atmosphere/nrlmsise00.py +809 -0
  21. pytcl/clustering/dbscan.py +2 -2
  22. pytcl/clustering/gaussian_mixture.py +3 -3
  23. pytcl/clustering/hierarchical.py +15 -15
  24. pytcl/clustering/kmeans.py +4 -4
  25. pytcl/containers/__init__.py +28 -21
  26. pytcl/containers/base.py +219 -0
  27. pytcl/containers/cluster_set.py +2 -1
  28. pytcl/containers/covertree.py +26 -29
  29. pytcl/containers/kd_tree.py +94 -29
  30. pytcl/containers/measurement_set.py +1 -9
  31. pytcl/containers/rtree.py +200 -1
  32. pytcl/containers/vptree.py +21 -28
  33. pytcl/coordinate_systems/conversions/geodetic.py +272 -5
  34. pytcl/coordinate_systems/jacobians/jacobians.py +2 -2
  35. pytcl/coordinate_systems/projections/__init__.py +4 -2
  36. pytcl/coordinate_systems/projections/projections.py +2 -2
  37. pytcl/coordinate_systems/rotations/rotations.py +10 -6
  38. pytcl/core/__init__.py +18 -0
  39. pytcl/core/validation.py +333 -2
  40. pytcl/dynamic_estimation/__init__.py +26 -0
  41. pytcl/dynamic_estimation/gaussian_sum_filter.py +434 -0
  42. pytcl/dynamic_estimation/imm.py +15 -18
  43. pytcl/dynamic_estimation/kalman/__init__.py +30 -0
  44. pytcl/dynamic_estimation/kalman/constrained.py +382 -0
  45. pytcl/dynamic_estimation/kalman/extended.py +9 -12
  46. pytcl/dynamic_estimation/kalman/h_infinity.py +613 -0
  47. pytcl/dynamic_estimation/kalman/square_root.py +60 -573
  48. pytcl/dynamic_estimation/kalman/sr_ukf.py +302 -0
  49. pytcl/dynamic_estimation/kalman/ud_filter.py +410 -0
  50. pytcl/dynamic_estimation/kalman/unscented.py +9 -10
  51. pytcl/dynamic_estimation/particle_filters/bootstrap.py +15 -15
  52. pytcl/dynamic_estimation/rbpf.py +589 -0
  53. pytcl/dynamic_estimation/smoothers.py +1 -5
  54. pytcl/dynamic_models/discrete_time/__init__.py +1 -5
  55. pytcl/dynamic_models/process_noise/__init__.py +1 -5
  56. pytcl/gravity/egm.py +13 -0
  57. pytcl/gravity/spherical_harmonics.py +98 -37
  58. pytcl/gravity/tides.py +6 -6
  59. pytcl/logging_config.py +328 -0
  60. pytcl/magnetism/__init__.py +10 -14
  61. pytcl/magnetism/emm.py +10 -3
  62. pytcl/magnetism/wmm.py +260 -23
  63. pytcl/mathematical_functions/combinatorics/combinatorics.py +5 -5
  64. pytcl/mathematical_functions/geometry/geometry.py +5 -5
  65. pytcl/mathematical_functions/interpolation/__init__.py +2 -2
  66. pytcl/mathematical_functions/numerical_integration/quadrature.py +6 -6
  67. pytcl/mathematical_functions/signal_processing/detection.py +24 -24
  68. pytcl/mathematical_functions/signal_processing/filters.py +14 -14
  69. pytcl/mathematical_functions/signal_processing/matched_filter.py +12 -12
  70. pytcl/mathematical_functions/special_functions/__init__.py +2 -2
  71. pytcl/mathematical_functions/special_functions/bessel.py +15 -3
  72. pytcl/mathematical_functions/special_functions/debye.py +136 -26
  73. pytcl/mathematical_functions/special_functions/error_functions.py +3 -1
  74. pytcl/mathematical_functions/special_functions/gamma_functions.py +4 -4
  75. pytcl/mathematical_functions/special_functions/hypergeometric.py +81 -15
  76. pytcl/mathematical_functions/transforms/fourier.py +8 -8
  77. pytcl/mathematical_functions/transforms/stft.py +12 -12
  78. pytcl/mathematical_functions/transforms/wavelets.py +9 -9
  79. pytcl/navigation/__init__.py +14 -10
  80. pytcl/navigation/geodesy.py +246 -160
  81. pytcl/navigation/great_circle.py +101 -19
  82. pytcl/navigation/ins.py +1 -5
  83. pytcl/plotting/coordinates.py +7 -7
  84. pytcl/plotting/tracks.py +2 -2
  85. pytcl/static_estimation/maximum_likelihood.py +16 -14
  86. pytcl/static_estimation/robust.py +5 -5
  87. pytcl/terrain/loaders.py +5 -5
  88. pytcl/trackers/__init__.py +3 -14
  89. pytcl/trackers/hypothesis.py +1 -1
  90. pytcl/trackers/mht.py +9 -9
  91. pytcl/trackers/multi_target.py +2 -5
  92. nrl_tracker-0.21.4.dist-info/RECORD +0 -148
  93. {nrl_tracker-0.21.4.dist-info → nrl_tracker-1.7.5.dist-info}/LICENSE +0 -0
  94. {nrl_tracker-0.21.4.dist-info → nrl_tracker-1.7.5.dist-info}/WHEEL +0 -0
  95. {nrl_tracker-0.21.4.dist-info → nrl_tracker-1.7.5.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,473 @@
1
+ """Relativistic corrections for precision astronomy and satellite positioning.
2
+
3
+ This module provides utilities for computing relativistic effects in orbital mechanics,
4
+ including gravitational time dilation, Shapiro delay, and coordinate transformations
5
+ in the Schwarzschild metric. These effects are critical for high-precision applications
6
+ such as GPS, pulsar timing, and celestial mechanics.
7
+
8
+ Key Physical Constants:
9
+ - Schwarzschild radius: r_s = 2GM/c^2
10
+ - Gravitational parameter for Earth: μ = GM = 3.986004418e14 m^3/s^2
11
+ - Speed of light: c = 299792458 m/s
12
+ - Gravitational constant: G = 6.67430e-11 m^3/(kg·s^2)
13
+
14
+ References:
15
+ - Soffel et al. (2003): The IAU 2000 Resolutions for Astrometry, Celestial Mechanics,
16
+ and Reference Frames
17
+ - Will, C. M. (2014): The Confrontation between General Relativity and Experiment
18
+ - Ries et al. (1992): Preliminary Analysis of LAGEOS II Observations for the
19
+ Determination of Relativistic Effects
20
+ """
21
+
22
+ import numpy as np
23
+ from numpy.typing import NDArray
24
+
25
+ # Physical constants (CODATA 2018 values)
26
+ C_LIGHT = 299792458.0 # Speed of light (m/s)
27
+ G_GRAV = 6.67430e-11 # Gravitational constant (m^3/(kg·s^2))
28
+ GM_EARTH = 3.986004418e14 # Gravitational parameter for Earth (m^3/s^2)
29
+ GM_SUN = 1.32712440018e20 # Gravitational parameter for Sun (m^3/s^2)
30
+ AU = 1.495978707e11 # Astronomical unit (m)
31
+
32
+
33
+ def schwarzschild_radius(mass: float) -> float:
34
+ """Compute Schwarzschild radius for a given mass.
35
+
36
+ The Schwarzschild radius is the radius at which an object becomes a black hole.
37
+ It is given by r_s = 2GM/c^2.
38
+
39
+ Parameters
40
+ ----------
41
+ mass : float
42
+ Mass of the object (kg)
43
+
44
+ Returns
45
+ -------
46
+ float
47
+ Schwarzschild radius (m)
48
+
49
+ Examples
50
+ --------
51
+ >>> r_s_earth = schwarzschild_radius(5.972e24) # Earth's mass
52
+ >>> print(f"Earth's Schwarzschild radius: {r_s_earth:.3e} m")
53
+ Earth's Schwarzschild radius: 8.870e-03 m
54
+
55
+ >>> r_s_sun = schwarzschild_radius(1.989e30) # Sun's mass
56
+ >>> print(f"Sun's Schwarzschild radius: {r_s_sun:.3e} m")
57
+ Sun's Schwarzschild radius: 2.952e+03 m
58
+ """
59
+ return 2.0 * G_GRAV * mass / (C_LIGHT**2)
60
+
61
+
62
+ def gravitational_time_dilation(r: float, gm: float = GM_EARTH) -> float:
63
+ """Compute gravitational time dilation factor sqrt(1 - 2GM/(rc^2)).
64
+
65
+ In general relativity, time passes slower in stronger gravitational fields.
66
+ This function computes the metric coefficient g_00 for the Schwarzschild metric,
67
+ which determines proper time relative to coordinate time.
68
+
69
+ Parameters
70
+ ----------
71
+ r : float
72
+ Distance from the gravitational body (m)
73
+ gm : float, optional
74
+ Gravitational parameter GM of the body (m^3/s^2).
75
+ Default is GM_EARTH.
76
+
77
+ Returns
78
+ -------
79
+ float
80
+ Time dilation factor in [0, 1]. A value less than 1 indicates time
81
+ passes slower at radius r compared to infinity.
82
+
83
+ Raises
84
+ ------
85
+ ValueError
86
+ If r is less than or equal to Schwarzschild radius
87
+
88
+ Examples
89
+ --------
90
+ Compute time dilation at Earth's surface (6371 km):
91
+
92
+ >>> r_earth = 6.371e6 # meters
93
+ >>> dilation = gravitational_time_dilation(r_earth)
94
+ >>> print(f"Time dilation at surface: {dilation:.15f}")
95
+ Time dilation at surface: 0.999999999300693
96
+
97
+ At GPS orbital altitude (~20,200 km):
98
+
99
+ >>> r_gps = 26.56e6 # meters
100
+ >>> dilation_gps = gravitational_time_dilation(r_gps)
101
+ >>> time_shift = (1 - dilation_gps) * 86400 * 1e9 # nanoseconds per day
102
+ >>> print(f"Time shift: {time_shift:.1f} ns/day")
103
+ """
104
+ r_s = schwarzschild_radius(gm / G_GRAV)
105
+ if r <= r_s:
106
+ raise ValueError(f"Radius {r} m is at or within Schwarzschild radius {r_s} m")
107
+
108
+ dilation_squared = 1.0 - 2.0 * gm / (C_LIGHT**2 * r)
109
+ return np.sqrt(dilation_squared)
110
+
111
+
112
+ def proper_time_rate(v: float, r: float, gm: float = GM_EARTH) -> float:
113
+ """Compute proper time rate accounting for both velocity and gravity.
114
+
115
+ The proper time rate combines special relativistic time dilation from velocity
116
+ and general relativistic time dilation from the gravitational potential.
117
+
118
+ d(tau)/d(t) = sqrt(1 - v^2/c^2) * sqrt(1 - 2GM/(rc^2))
119
+
120
+ For small velocities and weak fields: 1 - v^2/(2c^2) - GM/(rc^2)
121
+
122
+ Parameters
123
+ ----------
124
+ v : float
125
+ Velocity magnitude (m/s)
126
+ r : float
127
+ Distance from gravitational body (m)
128
+ gm : float, optional
129
+ Gravitational parameter GM (m^3/s^2). Default is GM_EARTH.
130
+
131
+ Returns
132
+ -------
133
+ float
134
+ Proper time rate. A value less than 1 indicates proper time passes
135
+ slower than coordinate time.
136
+
137
+ Examples
138
+ --------
139
+ Proper time rate for a GPS satellite at ~3.87 km/s and 26.56 Mm altitude:
140
+
141
+ >>> v_gps = 3870.0 # m/s
142
+ >>> r_gps = 26.56e6 # m
143
+ >>> rate = proper_time_rate(v_gps, r_gps)
144
+ >>> print(f"Proper time rate: {rate:.15f}")
145
+ >>> time_shift = (1 - rate) * 86400 # seconds per day
146
+ >>> print(f"Daily time shift: {time_shift:.3f} s/day")
147
+ """
148
+ # Special relativistic effect
149
+ special_rel = 1.0 - (v**2) / (2.0 * C_LIGHT**2)
150
+
151
+ # General relativistic effect
152
+ general_rel = -gm / (C_LIGHT**2 * r)
153
+
154
+ return special_rel + general_rel
155
+
156
+
157
+ def shapiro_delay(
158
+ observer_pos: NDArray[np.floating],
159
+ light_source_pos: NDArray[np.floating],
160
+ gravitating_body_pos: NDArray[np.floating],
161
+ gm: float = GM_SUN,
162
+ ) -> float:
163
+ """Compute Shapiro time delay for light propagation through gravitational field.
164
+
165
+ The Shapiro delay is the additional propagation time experienced by light
166
+ traveling through a gravitational field, compared to flat spacetime.
167
+
168
+ delay = (2GM/c^3) * ln((r_o + r_s + r_os) / (r_o + r_s - r_os))
169
+
170
+ where r_o is distance from body to observer, r_s is distance from body to
171
+ source, and r_os is distance from observer to source.
172
+
173
+ Parameters
174
+ ----------
175
+ observer_pos : np.ndarray
176
+ Position of observer (m), shape (3,)
177
+ light_source_pos : np.ndarray
178
+ Position of light source (m), shape (3,)
179
+ gravitating_body_pos : np.ndarray
180
+ Position of gravitating body (m), shape (3,)
181
+ gm : float, optional
182
+ Gravitational parameter GM (m^3/s^2). Default is GM_SUN.
183
+
184
+ Returns
185
+ -------
186
+ float
187
+ Shapiro delay (seconds)
188
+
189
+ Examples
190
+ --------
191
+ Earth-Sun-Spacecraft signal at superior conjunction (worst case):
192
+
193
+ >>> # Simplified geometry: Sun at origin, Earth at 1 AU, spacecraft beyond at distance
194
+ >>> sun_pos = np.array([0.0, 0.0, 0.0])
195
+ >>> earth_pos = np.array([1.496e11, 0.0, 0.0]) # 1 AU
196
+ >>> spacecraft_pos = np.array([1.496e11, 1.0e11, 0.0]) # Far from sun
197
+ >>> delay = shapiro_delay(earth_pos, spacecraft_pos, sun_pos, GM_SUN)
198
+ >>> print(f"Shapiro delay: {delay:.3e} seconds")
199
+ >>> print(f"Shapiro delay: {delay*1e6:.1f} microseconds")
200
+ """
201
+ # Compute distances
202
+ r_observer = np.linalg.norm(observer_pos - gravitating_body_pos)
203
+ r_source = np.linalg.norm(light_source_pos - gravitating_body_pos)
204
+ r_os = np.linalg.norm(observer_pos - light_source_pos)
205
+
206
+ # Shapiro delay formula (second-order PN)
207
+ # Check for valid geometry (gravitating body should affect path)
208
+ # The formula is valid when the impact parameter is close to the body
209
+ numerator = r_observer + r_source + r_os
210
+ denominator = r_observer + r_source - r_os
211
+
212
+ # If denominator <= 0, it means the path doesn't pass near the gravitating body
213
+ if denominator <= 0.0:
214
+ # Return zero delay if geometry is invalid (light path doesn't bend)
215
+ return 0.0
216
+
217
+ delay = (2.0 * gm / (C_LIGHT**3)) * np.log(numerator / denominator)
218
+ return delay
219
+
220
+
221
+ def schwarzschild_precession_per_orbit(a: float, e: float, gm: float = GM_SUN) -> float:
222
+ """Compute perihelion precession per orbit due to general relativity.
223
+
224
+ The advance of perihelion for an orbit around a central mass M is:
225
+
226
+ Δφ = (6π * GM) / (c^2 * a * (1 - e^2))
227
+
228
+ This effect is a key test of general relativity. For Mercury,
229
+ the predicted precession is ~43 arcseconds per century.
230
+
231
+ Parameters
232
+ ----------
233
+ a : float
234
+ Semi-major axis (m)
235
+ e : float
236
+ Eccentricity (dimensionless), must be in [0, 1)
237
+ gm : float, optional
238
+ Gravitational parameter GM (m^3/s^2). Default is GM_SUN.
239
+
240
+ Returns
241
+ -------
242
+ float
243
+ Perihelion precession per orbit (radians)
244
+
245
+ Examples
246
+ --------
247
+ Mercury's perihelion precession (GR contribution):
248
+
249
+ >>> a_mercury = 0.38709927 * AU # Semi-major axis in meters
250
+ >>> e_mercury = 0.20563593 # Eccentricity
251
+ >>> precession_rad = schwarzschild_precession_per_orbit(a_mercury, e_mercury, GM_SUN)
252
+ >>> precession_arcsec = precession_rad * 206265 # Convert to arcseconds
253
+ >>> orbital_period = 87.969 # days
254
+ >>> centuries = 36525 / orbital_period # Orbits per century
255
+ >>> precession_per_century = precession_arcsec * centuries
256
+ >>> print(f"GR perihelion precession: {precession_per_century:.1f} arcsec/century")
257
+ GR perihelion precession: 42.98 arcsec/century
258
+ """
259
+ if e < 0 or e >= 1:
260
+ raise ValueError(f"Eccentricity {e} must be in [0, 1)")
261
+
262
+ precession = (6.0 * np.pi * gm) / (C_LIGHT**2 * a * (1.0 - e**2))
263
+ return precession
264
+
265
+
266
+ def post_newtonian_acceleration(
267
+ r_vec: NDArray[np.floating], v_vec: NDArray[np.floating], gm: float = GM_EARTH
268
+ ) -> NDArray[np.floating]:
269
+ """Compute post-Newtonian acceleration corrections (1PN order).
270
+
271
+ Extends Newtonian gravity with first-order post-Newtonian corrections.
272
+
273
+ a_PN = -GM/r^2 * u_r + a_1PN
274
+
275
+ where a_1PN includes velocity-dependent and metric perturbation terms.
276
+
277
+ Parameters
278
+ ----------
279
+ r_vec : np.ndarray
280
+ Position vector (m), shape (3,)
281
+ v_vec : np.ndarray
282
+ Velocity vector (m/s), shape (3,)
283
+ gm : float, optional
284
+ Gravitational parameter GM (m^3/s^2). Default is GM_EARTH.
285
+
286
+ Returns
287
+ -------
288
+ np.ndarray
289
+ Total acceleration including 1PN corrections (m/s^2), shape (3,)
290
+
291
+ Examples
292
+ --------
293
+ Compare Newtonian and PN acceleration for LEO satellite:
294
+
295
+ >>> r = np.array([6.678e6, 0.0, 0.0]) # ~300 km altitude
296
+ >>> v = np.array([0.0, 7.7e3, 0.0]) # Circular orbit velocity
297
+ >>> a_total = post_newtonian_acceleration(r, v)
298
+ >>> a_newt = -GM_EARTH / np.linalg.norm(r)**3 * r
299
+ >>> correction_ratio = np.linalg.norm(a_total - a_newt) / np.linalg.norm(a_newt)
300
+ >>> print(f"PN correction: {correction_ratio*1e6:.1f} ppm")
301
+ """
302
+ r = np.linalg.norm(r_vec)
303
+ v_squared = np.sum(v_vec**2)
304
+
305
+ # Unit vector
306
+ u_r = r_vec / r
307
+
308
+ # Newtonian acceleration
309
+ a_newt = -gm / (r**2) * u_r
310
+
311
+ # 1PN corrections (in m/s^2)
312
+ c2 = C_LIGHT**2
313
+
314
+ # Term 1: Velocity squared effect on metric
315
+ term1 = (gm / c2) * (2.0 * v_squared / r - 4.0 * gm / r) * u_r / r
316
+
317
+ # Term 2: Radial velocity coupling
318
+ v_dot_r = np.dot(v_vec, u_r)
319
+ term2 = (4.0 * gm / c2) * v_dot_r * v_vec / r
320
+
321
+ # Combine corrections (these are small corrections to Newtonian acceleration)
322
+ a_1pn = term1 + term2
323
+
324
+ return a_newt + a_1pn
325
+
326
+
327
+ def geodetic_precession(
328
+ a: float, e: float, inclination: float, gm: float = GM_EARTH
329
+ ) -> float:
330
+ """Compute geodetic (de Sitter) precession rate of orbital plane.
331
+
332
+ The orbital plane of a satellite precesses due to frame-dragging effects
333
+ and spacetime curvature. The geodetic precession rate is:
334
+
335
+ Ω_geodetic = -GM/(c^2 * a^3 * (1 - e^2)^2) * cos(i)
336
+
337
+ Parameters
338
+ ----------
339
+ a : float
340
+ Semi-major axis (m)
341
+ e : float
342
+ Eccentricity (dimensionless)
343
+ inclination : float
344
+ Orbital inclination (radians)
345
+ gm : float, optional
346
+ Gravitational parameter (m^3/s^2). Default is GM_EARTH.
347
+
348
+ Returns
349
+ -------
350
+ float
351
+ Geodetic precession rate (radians per orbit)
352
+
353
+ Examples
354
+ --------
355
+ Geodetic precession for a typical Earth satellite:
356
+
357
+ >>> a = 6.678e6 # ~300 km altitude
358
+ >>> e = 0.0 # Circular
359
+ >>> i = np.radians(51.6) # ISS-like inclination
360
+ >>> rate = geodetic_precession(a, e, i)
361
+ >>> print(f"Precession per orbit: {rate*206265:.3f} arcsec")
362
+ """
363
+ p = a * (1.0 - e**2)
364
+ precession = -(gm / (C_LIGHT**2 * p**2)) * np.cos(inclination)
365
+ return precession
366
+
367
+
368
+ def lense_thirring_precession(
369
+ a: float,
370
+ e: float,
371
+ inclination: float,
372
+ angular_momentum: float,
373
+ gm: float = GM_EARTH,
374
+ ) -> float:
375
+ """Compute Lense-Thirring (frame-dragging) precession of orbital node.
376
+
377
+ A rotating central body drags the orbital plane of nearby objects.
378
+ The nodal precession rate due to this effect is:
379
+
380
+ Ω_LT = (2GM * J_2 * a * ω) / (c^2 * p^2) * f(e, i)
381
+
382
+ where J_2 is the quadrupole moment, ω is angular velocity, and f depends
383
+ on eccentricity and inclination.
384
+
385
+ Parameters
386
+ ----------
387
+ a : float
388
+ Semi-major axis (m)
389
+ e : float
390
+ Eccentricity (dimensionless)
391
+ inclination : float
392
+ Orbital inclination (radians)
393
+ angular_momentum : float
394
+ Angular momentum of central body (kg·m^2/s)
395
+ gm : float, optional
396
+ Gravitational parameter (m^3/s^2). Default is GM_EARTH.
397
+
398
+ Returns
399
+ -------
400
+ float
401
+ Lense-Thirring precession rate (radians per orbit)
402
+
403
+ Notes
404
+ -----
405
+ This is a simplified version. For Earth, J_2 effects typically dominate
406
+ classical nodal precession, while Lense-Thirring is a small correction
407
+ (~1 arcsec per year for typical satellites).
408
+
409
+ Examples
410
+ --------
411
+ Lense-Thirring effect for LAGEOS satellite:
412
+
413
+ >>> # LAGEOS parameters
414
+ >>> a = 12.27e6 # Semi-major axis
415
+ >>> e = 0.0045
416
+ >>> i = np.radians(109.9)
417
+ >>> L_earth = 7.05e33 # Earth's angular momentum
418
+ >>> rate = lense_thirring_precession(a, e, i, L_earth)
419
+ >>> print(f"LT precession per orbit: {rate*206265*1e3:.1f} milliarcsec")
420
+ """
421
+ p = a * (1.0 - e**2)
422
+
423
+ # Simplified Lense-Thirring term (second-order PN effect)
424
+ # For a sphere: Lense-Thirring parameter = 2GM*L/(c^2*M*r^3)
425
+ precession = (2.0 * angular_momentum * gm) / (C_LIGHT**2 * p**3)
426
+
427
+ return precession
428
+
429
+
430
+ def relativistic_range_correction(
431
+ distance: float, relative_velocity: float, gm: float = GM_EARTH
432
+ ) -> float:
433
+ """Compute relativistic range correction for ranging measurements.
434
+
435
+ When measuring distance to a satellite or spacecraft using ranging
436
+ (e.g., laser ranging), relativistic effects introduce corrections to
437
+ the measured range.
438
+
439
+ The main contributions are:
440
+ - Gravitational time dilation
441
+ - Relativistic Doppler effect
442
+
443
+ Parameters
444
+ ----------
445
+ distance : float
446
+ Distance to object (m)
447
+ relative_velocity : float
448
+ Radial velocity component (m/s, positive = receding)
449
+ gm : float, optional
450
+ Gravitational parameter (m^3/s^2). Default is GM_EARTH.
451
+
452
+ Returns
453
+ -------
454
+ float
455
+ Range correction (m)
456
+
457
+ Examples
458
+ --------
459
+ Range correction for lunar laser ranging:
460
+
461
+ >>> distance_to_moon = 3.84e8 # meters
462
+ >>> radial_velocity = 0.0 # Average over orbit
463
+ >>> correction = relativistic_range_correction(distance_to_moon, radial_velocity, GM_EARTH)
464
+ >>> print(f"Range correction: {correction:.1f} m")
465
+ """
466
+ # Gravitational correction (positive because the signal is delayed)
467
+ # Uses weak-field approximation
468
+ grav_correction = gm / (C_LIGHT**2)
469
+
470
+ # Doppler correction (second order effect, small)
471
+ doppler_correction = (relative_velocity**2) / (3.0 * C_LIGHT**2)
472
+
473
+ return grav_correction + doppler_correction