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.
- {nrl_tracker-0.21.4.dist-info → nrl_tracker-1.7.5.dist-info}/METADATA +57 -10
- nrl_tracker-1.7.5.dist-info/RECORD +165 -0
- pytcl/__init__.py +4 -3
- pytcl/assignment_algorithms/__init__.py +28 -0
- pytcl/assignment_algorithms/data_association.py +2 -7
- pytcl/assignment_algorithms/gating.py +10 -10
- pytcl/assignment_algorithms/jpda.py +40 -40
- pytcl/assignment_algorithms/nd_assignment.py +379 -0
- pytcl/assignment_algorithms/network_flow.py +371 -0
- pytcl/assignment_algorithms/three_dimensional/assignment.py +3 -3
- pytcl/astronomical/__init__.py +162 -8
- pytcl/astronomical/ephemerides.py +533 -0
- pytcl/astronomical/reference_frames.py +865 -56
- pytcl/astronomical/relativity.py +473 -0
- pytcl/astronomical/sgp4.py +710 -0
- pytcl/astronomical/special_orbits.py +532 -0
- pytcl/astronomical/tle.py +558 -0
- pytcl/atmosphere/__init__.py +45 -3
- pytcl/atmosphere/ionosphere.py +512 -0
- pytcl/atmosphere/nrlmsise00.py +809 -0
- pytcl/clustering/dbscan.py +2 -2
- pytcl/clustering/gaussian_mixture.py +3 -3
- pytcl/clustering/hierarchical.py +15 -15
- pytcl/clustering/kmeans.py +4 -4
- pytcl/containers/__init__.py +28 -21
- pytcl/containers/base.py +219 -0
- pytcl/containers/cluster_set.py +2 -1
- pytcl/containers/covertree.py +26 -29
- pytcl/containers/kd_tree.py +94 -29
- pytcl/containers/measurement_set.py +1 -9
- pytcl/containers/rtree.py +200 -1
- pytcl/containers/vptree.py +21 -28
- pytcl/coordinate_systems/conversions/geodetic.py +272 -5
- pytcl/coordinate_systems/jacobians/jacobians.py +2 -2
- pytcl/coordinate_systems/projections/__init__.py +4 -2
- pytcl/coordinate_systems/projections/projections.py +2 -2
- pytcl/coordinate_systems/rotations/rotations.py +10 -6
- pytcl/core/__init__.py +18 -0
- pytcl/core/validation.py +333 -2
- pytcl/dynamic_estimation/__init__.py +26 -0
- pytcl/dynamic_estimation/gaussian_sum_filter.py +434 -0
- pytcl/dynamic_estimation/imm.py +15 -18
- pytcl/dynamic_estimation/kalman/__init__.py +30 -0
- pytcl/dynamic_estimation/kalman/constrained.py +382 -0
- pytcl/dynamic_estimation/kalman/extended.py +9 -12
- pytcl/dynamic_estimation/kalman/h_infinity.py +613 -0
- pytcl/dynamic_estimation/kalman/square_root.py +60 -573
- pytcl/dynamic_estimation/kalman/sr_ukf.py +302 -0
- pytcl/dynamic_estimation/kalman/ud_filter.py +410 -0
- pytcl/dynamic_estimation/kalman/unscented.py +9 -10
- pytcl/dynamic_estimation/particle_filters/bootstrap.py +15 -15
- pytcl/dynamic_estimation/rbpf.py +589 -0
- pytcl/dynamic_estimation/smoothers.py +1 -5
- pytcl/dynamic_models/discrete_time/__init__.py +1 -5
- pytcl/dynamic_models/process_noise/__init__.py +1 -5
- pytcl/gravity/egm.py +13 -0
- pytcl/gravity/spherical_harmonics.py +98 -37
- pytcl/gravity/tides.py +6 -6
- pytcl/logging_config.py +328 -0
- pytcl/magnetism/__init__.py +10 -14
- pytcl/magnetism/emm.py +10 -3
- pytcl/magnetism/wmm.py +260 -23
- pytcl/mathematical_functions/combinatorics/combinatorics.py +5 -5
- pytcl/mathematical_functions/geometry/geometry.py +5 -5
- pytcl/mathematical_functions/interpolation/__init__.py +2 -2
- pytcl/mathematical_functions/numerical_integration/quadrature.py +6 -6
- pytcl/mathematical_functions/signal_processing/detection.py +24 -24
- pytcl/mathematical_functions/signal_processing/filters.py +14 -14
- pytcl/mathematical_functions/signal_processing/matched_filter.py +12 -12
- pytcl/mathematical_functions/special_functions/__init__.py +2 -2
- pytcl/mathematical_functions/special_functions/bessel.py +15 -3
- pytcl/mathematical_functions/special_functions/debye.py +136 -26
- pytcl/mathematical_functions/special_functions/error_functions.py +3 -1
- pytcl/mathematical_functions/special_functions/gamma_functions.py +4 -4
- pytcl/mathematical_functions/special_functions/hypergeometric.py +81 -15
- pytcl/mathematical_functions/transforms/fourier.py +8 -8
- pytcl/mathematical_functions/transforms/stft.py +12 -12
- pytcl/mathematical_functions/transforms/wavelets.py +9 -9
- pytcl/navigation/__init__.py +14 -10
- pytcl/navigation/geodesy.py +246 -160
- pytcl/navigation/great_circle.py +101 -19
- pytcl/navigation/ins.py +1 -5
- pytcl/plotting/coordinates.py +7 -7
- pytcl/plotting/tracks.py +2 -2
- pytcl/static_estimation/maximum_likelihood.py +16 -14
- pytcl/static_estimation/robust.py +5 -5
- pytcl/terrain/loaders.py +5 -5
- pytcl/trackers/__init__.py +3 -14
- pytcl/trackers/hypothesis.py +1 -1
- pytcl/trackers/mht.py +9 -9
- pytcl/trackers/multi_target.py +2 -5
- nrl_tracker-0.21.4.dist-info/RECORD +0 -148
- {nrl_tracker-0.21.4.dist-info → nrl_tracker-1.7.5.dist-info}/LICENSE +0 -0
- {nrl_tracker-0.21.4.dist-info → nrl_tracker-1.7.5.dist-info}/WHEEL +0 -0
- {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
|