nrl-tracker 0.21.5__py3-none-any.whl → 0.22.0__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.5.dist-info → nrl_tracker-0.22.0.dist-info}/METADATA +2 -2
- nrl_tracker-0.22.0.dist-info/RECORD +150 -0
- pytcl/__init__.py +9 -11
- pytcl/assignment_algorithms/__init__.py +32 -42
- pytcl/assignment_algorithms/data_association.py +9 -10
- pytcl/assignment_algorithms/gating.py +7 -5
- pytcl/assignment_algorithms/jpda.py +10 -14
- pytcl/assignment_algorithms/three_dimensional/__init__.py +6 -8
- pytcl/assignment_algorithms/three_dimensional/assignment.py +6 -2
- pytcl/assignment_algorithms/two_dimensional/__init__.py +9 -13
- pytcl/assignment_algorithms/two_dimensional/assignment.py +5 -2
- pytcl/assignment_algorithms/two_dimensional/kbest.py +9 -9
- pytcl/astronomical/__init__.py +130 -89
- pytcl/astronomical/ephemerides.py +524 -0
- pytcl/astronomical/lambert.py +6 -15
- pytcl/astronomical/orbital_mechanics.py +1 -3
- pytcl/astronomical/reference_frames.py +1 -3
- pytcl/astronomical/relativity.py +466 -0
- pytcl/astronomical/time_systems.py +2 -1
- pytcl/atmosphere/__init__.py +12 -14
- pytcl/atmosphere/models.py +5 -5
- pytcl/clustering/__init__.py +28 -36
- pytcl/clustering/dbscan.py +5 -2
- pytcl/clustering/gaussian_mixture.py +10 -10
- pytcl/clustering/hierarchical.py +7 -7
- pytcl/clustering/kmeans.py +7 -5
- pytcl/containers/__init__.py +29 -43
- pytcl/containers/cluster_set.py +13 -20
- pytcl/containers/covertree.py +8 -2
- pytcl/containers/kd_tree.py +6 -2
- pytcl/containers/measurement_set.py +11 -16
- pytcl/containers/rtree.py +8 -7
- pytcl/containers/track_list.py +13 -13
- pytcl/containers/vptree.py +7 -2
- pytcl/coordinate_systems/__init__.py +69 -74
- pytcl/coordinate_systems/conversions/__init__.py +20 -24
- pytcl/coordinate_systems/conversions/geodetic.py +7 -17
- pytcl/coordinate_systems/conversions/spherical.py +4 -2
- pytcl/coordinate_systems/jacobians/__init__.py +10 -12
- pytcl/coordinate_systems/jacobians/jacobians.py +2 -1
- pytcl/coordinate_systems/projections/__init__.py +27 -23
- pytcl/coordinate_systems/projections/projections.py +14 -39
- pytcl/coordinate_systems/rotations/__init__.py +20 -22
- pytcl/coordinate_systems/rotations/rotations.py +3 -4
- pytcl/core/__init__.py +16 -22
- pytcl/core/array_utils.py +7 -7
- pytcl/core/constants.py +1 -3
- pytcl/core/validation.py +13 -19
- pytcl/dynamic_estimation/__init__.py +77 -86
- pytcl/dynamic_estimation/imm.py +10 -15
- pytcl/dynamic_estimation/information_filter.py +8 -6
- pytcl/dynamic_estimation/kalman/__init__.py +40 -48
- pytcl/dynamic_estimation/kalman/extended.py +4 -5
- pytcl/dynamic_estimation/kalman/linear.py +7 -3
- pytcl/dynamic_estimation/kalman/square_root.py +7 -8
- pytcl/dynamic_estimation/kalman/unscented.py +8 -6
- pytcl/dynamic_estimation/particle_filters/__init__.py +12 -14
- pytcl/dynamic_estimation/particle_filters/bootstrap.py +8 -8
- pytcl/dynamic_estimation/smoothers.py +9 -10
- pytcl/dynamic_models/__init__.py +37 -41
- pytcl/dynamic_models/continuous_time/__init__.py +11 -11
- pytcl/dynamic_models/continuous_time/dynamics.py +4 -2
- pytcl/dynamic_models/discrete_time/__init__.py +11 -17
- pytcl/dynamic_models/process_noise/__init__.py +11 -17
- pytcl/dynamic_models/process_noise/polynomial.py +2 -6
- pytcl/gravity/__init__.py +55 -65
- pytcl/gravity/clenshaw.py +4 -7
- pytcl/gravity/egm.py +9 -6
- pytcl/gravity/models.py +1 -3
- pytcl/gravity/spherical_harmonics.py +6 -11
- pytcl/gravity/tides.py +9 -17
- pytcl/magnetism/__init__.py +26 -36
- pytcl/magnetism/emm.py +7 -13
- pytcl/magnetism/igrf.py +5 -6
- pytcl/magnetism/wmm.py +4 -10
- pytcl/mathematical_functions/__init__.py +69 -87
- pytcl/mathematical_functions/basic_matrix/__init__.py +25 -19
- pytcl/mathematical_functions/basic_matrix/decompositions.py +6 -5
- pytcl/mathematical_functions/basic_matrix/special_matrices.py +2 -1
- pytcl/mathematical_functions/combinatorics/__init__.py +18 -14
- pytcl/mathematical_functions/combinatorics/combinatorics.py +5 -4
- pytcl/mathematical_functions/geometry/__init__.py +15 -15
- pytcl/mathematical_functions/geometry/geometry.py +10 -15
- pytcl/mathematical_functions/interpolation/__init__.py +11 -13
- pytcl/mathematical_functions/interpolation/interpolation.py +8 -5
- pytcl/mathematical_functions/numerical_integration/__init__.py +16 -10
- pytcl/mathematical_functions/numerical_integration/quadrature.py +6 -2
- pytcl/mathematical_functions/signal_processing/__init__.py +42 -30
- pytcl/mathematical_functions/signal_processing/detection.py +9 -9
- pytcl/mathematical_functions/signal_processing/filters.py +7 -8
- pytcl/mathematical_functions/signal_processing/matched_filter.py +8 -7
- pytcl/mathematical_functions/special_functions/__init__.py +75 -77
- pytcl/mathematical_functions/special_functions/bessel.py +2 -1
- pytcl/mathematical_functions/special_functions/debye.py +4 -2
- pytcl/mathematical_functions/special_functions/elliptic.py +3 -4
- pytcl/mathematical_functions/special_functions/error_functions.py +2 -1
- pytcl/mathematical_functions/special_functions/gamma_functions.py +3 -4
- pytcl/mathematical_functions/special_functions/hypergeometric.py +2 -1
- pytcl/mathematical_functions/special_functions/lambert_w.py +3 -4
- pytcl/mathematical_functions/special_functions/marcum_q.py +2 -1
- pytcl/mathematical_functions/statistics/__init__.py +27 -31
- pytcl/mathematical_functions/statistics/distributions.py +21 -40
- pytcl/mathematical_functions/statistics/estimators.py +3 -4
- pytcl/mathematical_functions/transforms/__init__.py +45 -51
- pytcl/mathematical_functions/transforms/fourier.py +5 -2
- pytcl/mathematical_functions/transforms/stft.py +8 -11
- pytcl/mathematical_functions/transforms/wavelets.py +13 -20
- pytcl/navigation/__init__.py +96 -102
- pytcl/navigation/geodesy.py +13 -33
- pytcl/navigation/great_circle.py +7 -13
- pytcl/navigation/ins.py +12 -16
- pytcl/navigation/ins_gnss.py +24 -37
- pytcl/navigation/rhumb.py +7 -12
- pytcl/performance_evaluation/__init__.py +21 -25
- pytcl/performance_evaluation/estimation_metrics.py +3 -1
- pytcl/performance_evaluation/track_metrics.py +4 -4
- pytcl/plotting/__init__.py +30 -38
- pytcl/plotting/coordinates.py +8 -18
- pytcl/plotting/ellipses.py +5 -2
- pytcl/plotting/metrics.py +5 -10
- pytcl/plotting/tracks.py +7 -12
- pytcl/static_estimation/__init__.py +37 -41
- pytcl/static_estimation/least_squares.py +5 -4
- pytcl/static_estimation/maximum_likelihood.py +8 -5
- pytcl/static_estimation/robust.py +5 -2
- pytcl/terrain/__init__.py +28 -34
- pytcl/terrain/dem.py +6 -9
- pytcl/terrain/loaders.py +9 -14
- pytcl/terrain/visibility.py +4 -8
- pytcl/trackers/__init__.py +17 -25
- pytcl/trackers/hypothesis.py +8 -8
- pytcl/trackers/mht.py +18 -24
- pytcl/trackers/multi_target.py +8 -6
- pytcl/trackers/single_target.py +5 -2
- nrl_tracker-0.21.5.dist-info/RECORD +0 -148
- {nrl_tracker-0.21.5.dist-info → nrl_tracker-0.22.0.dist-info}/LICENSE +0 -0
- {nrl_tracker-0.21.5.dist-info → nrl_tracker-0.22.0.dist-info}/WHEEL +0 -0
- {nrl_tracker-0.21.5.dist-info → nrl_tracker-0.22.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,466 @@
|
|
|
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
|
+
|
|
24
|
+
# Physical constants (CODATA 2018 values)
|
|
25
|
+
C_LIGHT = 299792458.0 # Speed of light (m/s)
|
|
26
|
+
G_GRAV = 6.67430e-11 # Gravitational constant (m^3/(kg·s^2))
|
|
27
|
+
GM_EARTH = 3.986004418e14 # Gravitational parameter for Earth (m^3/s^2)
|
|
28
|
+
GM_SUN = 1.32712440018e20 # Gravitational parameter for Sun (m^3/s^2)
|
|
29
|
+
AU = 1.495978707e11 # Astronomical unit (m)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def schwarzschild_radius(mass: float) -> float:
|
|
33
|
+
"""Compute Schwarzschild radius for a given mass.
|
|
34
|
+
|
|
35
|
+
The Schwarzschild radius is the radius at which an object becomes a black hole.
|
|
36
|
+
It is given by r_s = 2GM/c^2.
|
|
37
|
+
|
|
38
|
+
Parameters
|
|
39
|
+
----------
|
|
40
|
+
mass : float
|
|
41
|
+
Mass of the object (kg)
|
|
42
|
+
|
|
43
|
+
Returns
|
|
44
|
+
-------
|
|
45
|
+
float
|
|
46
|
+
Schwarzschild radius (m)
|
|
47
|
+
|
|
48
|
+
Examples
|
|
49
|
+
--------
|
|
50
|
+
>>> r_s_earth = schwarzschild_radius(5.972e24) # Earth's mass
|
|
51
|
+
>>> print(f"Earth's Schwarzschild radius: {r_s_earth:.3e} m")
|
|
52
|
+
Earth's Schwarzschild radius: 8.870e-03 m
|
|
53
|
+
|
|
54
|
+
>>> r_s_sun = schwarzschild_radius(1.989e30) # Sun's mass
|
|
55
|
+
>>> print(f"Sun's Schwarzschild radius: {r_s_sun:.3e} m")
|
|
56
|
+
Sun's Schwarzschild radius: 2.952e+03 m
|
|
57
|
+
"""
|
|
58
|
+
return 2.0 * G_GRAV * mass / (C_LIGHT**2)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def gravitational_time_dilation(r: float, gm: float = GM_EARTH) -> float:
|
|
62
|
+
"""Compute gravitational time dilation factor sqrt(1 - 2GM/(rc^2)).
|
|
63
|
+
|
|
64
|
+
In general relativity, time passes slower in stronger gravitational fields.
|
|
65
|
+
This function computes the metric coefficient g_00 for the Schwarzschild metric,
|
|
66
|
+
which determines proper time relative to coordinate time.
|
|
67
|
+
|
|
68
|
+
Parameters
|
|
69
|
+
----------
|
|
70
|
+
r : float
|
|
71
|
+
Distance from the gravitational body (m)
|
|
72
|
+
gm : float, optional
|
|
73
|
+
Gravitational parameter GM of the body (m^3/s^2).
|
|
74
|
+
Default is GM_EARTH.
|
|
75
|
+
|
|
76
|
+
Returns
|
|
77
|
+
-------
|
|
78
|
+
float
|
|
79
|
+
Time dilation factor in [0, 1]. A value less than 1 indicates time
|
|
80
|
+
passes slower at radius r compared to infinity.
|
|
81
|
+
|
|
82
|
+
Raises
|
|
83
|
+
------
|
|
84
|
+
ValueError
|
|
85
|
+
If r is less than or equal to Schwarzschild radius
|
|
86
|
+
|
|
87
|
+
Examples
|
|
88
|
+
--------
|
|
89
|
+
Compute time dilation at Earth's surface (6371 km):
|
|
90
|
+
|
|
91
|
+
>>> r_earth = 6.371e6 # meters
|
|
92
|
+
>>> dilation = gravitational_time_dilation(r_earth)
|
|
93
|
+
>>> print(f"Time dilation at surface: {dilation:.15f}")
|
|
94
|
+
Time dilation at surface: 0.999999999300693
|
|
95
|
+
|
|
96
|
+
At GPS orbital altitude (~20,200 km):
|
|
97
|
+
|
|
98
|
+
>>> r_gps = 26.56e6 # meters
|
|
99
|
+
>>> dilation_gps = gravitational_time_dilation(r_gps)
|
|
100
|
+
>>> time_shift = (1 - dilation_gps) * 86400 * 1e9 # nanoseconds per day
|
|
101
|
+
>>> print(f"Time shift: {time_shift:.1f} ns/day")
|
|
102
|
+
"""
|
|
103
|
+
r_s = schwarzschild_radius(gm / G_GRAV)
|
|
104
|
+
if r <= r_s:
|
|
105
|
+
raise ValueError(f"Radius {r} m is at or within Schwarzschild radius {r_s} m")
|
|
106
|
+
|
|
107
|
+
dilation_squared = 1.0 - 2.0 * gm / (C_LIGHT**2 * r)
|
|
108
|
+
return np.sqrt(dilation_squared)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def proper_time_rate(v: float, r: float, gm: float = GM_EARTH) -> float:
|
|
112
|
+
"""Compute proper time rate accounting for both velocity and gravity.
|
|
113
|
+
|
|
114
|
+
The proper time rate combines special relativistic time dilation from velocity
|
|
115
|
+
and general relativistic time dilation from the gravitational potential.
|
|
116
|
+
|
|
117
|
+
d(tau)/d(t) = sqrt(1 - v^2/c^2) * sqrt(1 - 2GM/(rc^2))
|
|
118
|
+
|
|
119
|
+
For small velocities and weak fields: 1 - v^2/(2c^2) - GM/(rc^2)
|
|
120
|
+
|
|
121
|
+
Parameters
|
|
122
|
+
----------
|
|
123
|
+
v : float
|
|
124
|
+
Velocity magnitude (m/s)
|
|
125
|
+
r : float
|
|
126
|
+
Distance from gravitational body (m)
|
|
127
|
+
gm : float, optional
|
|
128
|
+
Gravitational parameter GM (m^3/s^2). Default is GM_EARTH.
|
|
129
|
+
|
|
130
|
+
Returns
|
|
131
|
+
-------
|
|
132
|
+
float
|
|
133
|
+
Proper time rate. A value less than 1 indicates proper time passes
|
|
134
|
+
slower than coordinate time.
|
|
135
|
+
|
|
136
|
+
Examples
|
|
137
|
+
--------
|
|
138
|
+
Proper time rate for a GPS satellite at ~3.87 km/s and 26.56 Mm altitude:
|
|
139
|
+
|
|
140
|
+
>>> v_gps = 3870.0 # m/s
|
|
141
|
+
>>> r_gps = 26.56e6 # m
|
|
142
|
+
>>> rate = proper_time_rate(v_gps, r_gps)
|
|
143
|
+
>>> print(f"Proper time rate: {rate:.15f}")
|
|
144
|
+
>>> time_shift = (1 - rate) * 86400 # seconds per day
|
|
145
|
+
>>> print(f"Daily time shift: {time_shift:.3f} s/day")
|
|
146
|
+
"""
|
|
147
|
+
# Special relativistic effect
|
|
148
|
+
special_rel = 1.0 - (v**2) / (2.0 * C_LIGHT**2)
|
|
149
|
+
|
|
150
|
+
# General relativistic effect
|
|
151
|
+
general_rel = -gm / (C_LIGHT**2 * r)
|
|
152
|
+
|
|
153
|
+
return special_rel + general_rel
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def shapiro_delay(
|
|
157
|
+
observer_pos: np.ndarray,
|
|
158
|
+
light_source_pos: np.ndarray,
|
|
159
|
+
gravitating_body_pos: np.ndarray,
|
|
160
|
+
gm: float = GM_SUN,
|
|
161
|
+
) -> float:
|
|
162
|
+
"""Compute Shapiro time delay for light propagation through gravitational field.
|
|
163
|
+
|
|
164
|
+
The Shapiro delay is the additional propagation time experienced by light
|
|
165
|
+
traveling through a gravitational field, compared to flat spacetime.
|
|
166
|
+
|
|
167
|
+
delay = (2GM/c^3) * ln((r_o + r_s + r_os) / (r_o + r_s - r_os))
|
|
168
|
+
|
|
169
|
+
where r_o is distance from body to observer, r_s is distance from body to
|
|
170
|
+
source, and r_os is distance from observer to source.
|
|
171
|
+
|
|
172
|
+
Parameters
|
|
173
|
+
----------
|
|
174
|
+
observer_pos : np.ndarray
|
|
175
|
+
Position of observer (m), shape (3,)
|
|
176
|
+
light_source_pos : np.ndarray
|
|
177
|
+
Position of light source (m), shape (3,)
|
|
178
|
+
gravitating_body_pos : np.ndarray
|
|
179
|
+
Position of gravitating body (m), shape (3,)
|
|
180
|
+
gm : float, optional
|
|
181
|
+
Gravitational parameter GM (m^3/s^2). Default is GM_SUN.
|
|
182
|
+
|
|
183
|
+
Returns
|
|
184
|
+
-------
|
|
185
|
+
float
|
|
186
|
+
Shapiro delay (seconds)
|
|
187
|
+
|
|
188
|
+
Examples
|
|
189
|
+
--------
|
|
190
|
+
Earth-Sun-Spacecraft signal at superior conjunction (worst case):
|
|
191
|
+
|
|
192
|
+
>>> # Simplified geometry: Sun at origin, Earth at 1 AU, spacecraft beyond at distance
|
|
193
|
+
>>> sun_pos = np.array([0.0, 0.0, 0.0])
|
|
194
|
+
>>> earth_pos = np.array([1.496e11, 0.0, 0.0]) # 1 AU
|
|
195
|
+
>>> spacecraft_pos = np.array([1.496e11, 1.0e11, 0.0]) # Far from sun
|
|
196
|
+
>>> delay = shapiro_delay(earth_pos, spacecraft_pos, sun_pos, GM_SUN)
|
|
197
|
+
>>> print(f"Shapiro delay: {delay:.3e} seconds")
|
|
198
|
+
>>> print(f"Shapiro delay: {delay*1e6:.1f} microseconds")
|
|
199
|
+
"""
|
|
200
|
+
# Compute distances
|
|
201
|
+
r_observer = np.linalg.norm(observer_pos - gravitating_body_pos)
|
|
202
|
+
r_source = np.linalg.norm(light_source_pos - gravitating_body_pos)
|
|
203
|
+
r_os = np.linalg.norm(observer_pos - light_source_pos)
|
|
204
|
+
|
|
205
|
+
# Shapiro delay formula (second-order PN)
|
|
206
|
+
# Check for valid geometry (gravitating body should affect path)
|
|
207
|
+
# The formula is valid when the impact parameter is close to the body
|
|
208
|
+
numerator = r_observer + r_source + r_os
|
|
209
|
+
denominator = r_observer + r_source - r_os
|
|
210
|
+
|
|
211
|
+
# If denominator <= 0, it means the path doesn't pass near the gravitating body
|
|
212
|
+
if denominator <= 0.0:
|
|
213
|
+
# Return zero delay if geometry is invalid (light path doesn't bend)
|
|
214
|
+
return 0.0
|
|
215
|
+
|
|
216
|
+
delay = (2.0 * gm / (C_LIGHT**3)) * np.log(numerator / denominator)
|
|
217
|
+
return delay
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def schwarzschild_precession_per_orbit(a: float, e: float, gm: float = GM_SUN) -> float:
|
|
221
|
+
"""Compute perihelion precession per orbit due to general relativity.
|
|
222
|
+
|
|
223
|
+
The advance of perihelion for an orbit around a central mass M is:
|
|
224
|
+
|
|
225
|
+
Δφ = (6π * GM) / (c^2 * a * (1 - e^2))
|
|
226
|
+
|
|
227
|
+
This effect is a key test of general relativity. For Mercury,
|
|
228
|
+
the predicted precession is ~43 arcseconds per century.
|
|
229
|
+
|
|
230
|
+
Parameters
|
|
231
|
+
----------
|
|
232
|
+
a : float
|
|
233
|
+
Semi-major axis (m)
|
|
234
|
+
e : float
|
|
235
|
+
Eccentricity (dimensionless), must be in [0, 1)
|
|
236
|
+
gm : float, optional
|
|
237
|
+
Gravitational parameter GM (m^3/s^2). Default is GM_SUN.
|
|
238
|
+
|
|
239
|
+
Returns
|
|
240
|
+
-------
|
|
241
|
+
float
|
|
242
|
+
Perihelion precession per orbit (radians)
|
|
243
|
+
|
|
244
|
+
Examples
|
|
245
|
+
--------
|
|
246
|
+
Mercury's perihelion precession (GR contribution):
|
|
247
|
+
|
|
248
|
+
>>> a_mercury = 0.38709927 * AU # Semi-major axis in meters
|
|
249
|
+
>>> e_mercury = 0.20563593 # Eccentricity
|
|
250
|
+
>>> precession_rad = schwarzschild_precession_per_orbit(a_mercury, e_mercury, GM_SUN)
|
|
251
|
+
>>> precession_arcsec = precession_rad * 206265 # Convert to arcseconds
|
|
252
|
+
>>> orbital_period = 87.969 # days
|
|
253
|
+
>>> centuries = 36525 / orbital_period # Orbits per century
|
|
254
|
+
>>> precession_per_century = precession_arcsec * centuries
|
|
255
|
+
>>> print(f"GR perihelion precession: {precession_per_century:.1f} arcsec/century")
|
|
256
|
+
GR perihelion precession: 42.98 arcsec/century
|
|
257
|
+
"""
|
|
258
|
+
if e < 0 or e >= 1:
|
|
259
|
+
raise ValueError(f"Eccentricity {e} must be in [0, 1)")
|
|
260
|
+
|
|
261
|
+
precession = (6.0 * np.pi * gm) / (C_LIGHT**2 * a * (1.0 - e**2))
|
|
262
|
+
return precession
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
def post_newtonian_acceleration(
|
|
266
|
+
r_vec: np.ndarray, v_vec: np.ndarray, gm: float = GM_EARTH
|
|
267
|
+
) -> np.ndarray:
|
|
268
|
+
"""Compute post-Newtonian acceleration corrections (1PN order).
|
|
269
|
+
|
|
270
|
+
Extends Newtonian gravity with first-order post-Newtonian corrections.
|
|
271
|
+
|
|
272
|
+
a_PN = -GM/r^2 * u_r + a_1PN
|
|
273
|
+
|
|
274
|
+
where a_1PN includes velocity-dependent and metric perturbation terms.
|
|
275
|
+
|
|
276
|
+
Parameters
|
|
277
|
+
----------
|
|
278
|
+
r_vec : np.ndarray
|
|
279
|
+
Position vector (m), shape (3,)
|
|
280
|
+
v_vec : np.ndarray
|
|
281
|
+
Velocity vector (m/s), shape (3,)
|
|
282
|
+
gm : float, optional
|
|
283
|
+
Gravitational parameter GM (m^3/s^2). Default is GM_EARTH.
|
|
284
|
+
|
|
285
|
+
Returns
|
|
286
|
+
-------
|
|
287
|
+
np.ndarray
|
|
288
|
+
Total acceleration including 1PN corrections (m/s^2), shape (3,)
|
|
289
|
+
|
|
290
|
+
Examples
|
|
291
|
+
--------
|
|
292
|
+
Compare Newtonian and PN acceleration for LEO satellite:
|
|
293
|
+
|
|
294
|
+
>>> r = np.array([6.678e6, 0.0, 0.0]) # ~300 km altitude
|
|
295
|
+
>>> v = np.array([0.0, 7.7e3, 0.0]) # Circular orbit velocity
|
|
296
|
+
>>> a_total = post_newtonian_acceleration(r, v)
|
|
297
|
+
>>> a_newt = -GM_EARTH / np.linalg.norm(r)**3 * r
|
|
298
|
+
>>> correction_ratio = np.linalg.norm(a_total - a_newt) / np.linalg.norm(a_newt)
|
|
299
|
+
>>> print(f"PN correction: {correction_ratio*1e6:.1f} ppm")
|
|
300
|
+
"""
|
|
301
|
+
r = np.linalg.norm(r_vec)
|
|
302
|
+
v_squared = np.sum(v_vec**2)
|
|
303
|
+
|
|
304
|
+
# Unit vector
|
|
305
|
+
u_r = r_vec / r
|
|
306
|
+
|
|
307
|
+
# Newtonian acceleration
|
|
308
|
+
a_newt = -gm / (r**2) * u_r
|
|
309
|
+
|
|
310
|
+
# 1PN corrections (in m/s^2)
|
|
311
|
+
c2 = C_LIGHT**2
|
|
312
|
+
|
|
313
|
+
# Term 1: Velocity squared effect on metric
|
|
314
|
+
term1 = (gm / c2) * (2.0 * v_squared / r - 4.0 * gm / r) * u_r / r
|
|
315
|
+
|
|
316
|
+
# Term 2: Radial velocity coupling
|
|
317
|
+
v_dot_r = np.dot(v_vec, u_r)
|
|
318
|
+
term2 = (4.0 * gm / c2) * v_dot_r * v_vec / r
|
|
319
|
+
|
|
320
|
+
# Combine corrections (these are small corrections to Newtonian acceleration)
|
|
321
|
+
a_1pn = term1 + term2
|
|
322
|
+
|
|
323
|
+
return a_newt + a_1pn
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
def geodetic_precession(a: float, e: float, inclination: float, gm: float = GM_EARTH) -> float:
|
|
327
|
+
"""Compute geodetic (de Sitter) precession rate of orbital plane.
|
|
328
|
+
|
|
329
|
+
The orbital plane of a satellite precesses due to frame-dragging effects
|
|
330
|
+
and spacetime curvature. The geodetic precession rate is:
|
|
331
|
+
|
|
332
|
+
Ω_geodetic = -GM/(c^2 * a^3 * (1 - e^2)^2) * cos(i)
|
|
333
|
+
|
|
334
|
+
Parameters
|
|
335
|
+
----------
|
|
336
|
+
a : float
|
|
337
|
+
Semi-major axis (m)
|
|
338
|
+
e : float
|
|
339
|
+
Eccentricity (dimensionless)
|
|
340
|
+
inclination : float
|
|
341
|
+
Orbital inclination (radians)
|
|
342
|
+
gm : float, optional
|
|
343
|
+
Gravitational parameter (m^3/s^2). Default is GM_EARTH.
|
|
344
|
+
|
|
345
|
+
Returns
|
|
346
|
+
-------
|
|
347
|
+
float
|
|
348
|
+
Geodetic precession rate (radians per orbit)
|
|
349
|
+
|
|
350
|
+
Examples
|
|
351
|
+
--------
|
|
352
|
+
Geodetic precession for a typical Earth satellite:
|
|
353
|
+
|
|
354
|
+
>>> a = 6.678e6 # ~300 km altitude
|
|
355
|
+
>>> e = 0.0 # Circular
|
|
356
|
+
>>> i = np.radians(51.6) # ISS-like inclination
|
|
357
|
+
>>> rate = geodetic_precession(a, e, i)
|
|
358
|
+
>>> print(f"Precession per orbit: {rate*206265:.3f} arcsec")
|
|
359
|
+
"""
|
|
360
|
+
p = a * (1.0 - e**2)
|
|
361
|
+
precession = -(gm / (C_LIGHT**2 * p**2)) * np.cos(inclination)
|
|
362
|
+
return precession
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
def lense_thirring_precession(
|
|
366
|
+
a: float, e: float, inclination: float, angular_momentum: float, gm: float = GM_EARTH
|
|
367
|
+
) -> float:
|
|
368
|
+
"""Compute Lense-Thirring (frame-dragging) precession of orbital node.
|
|
369
|
+
|
|
370
|
+
A rotating central body drags the orbital plane of nearby objects.
|
|
371
|
+
The nodal precession rate due to this effect is:
|
|
372
|
+
|
|
373
|
+
Ω_LT = (2GM * J_2 * a * ω) / (c^2 * p^2) * f(e, i)
|
|
374
|
+
|
|
375
|
+
where J_2 is the quadrupole moment, ω is angular velocity, and f depends
|
|
376
|
+
on eccentricity and inclination.
|
|
377
|
+
|
|
378
|
+
Parameters
|
|
379
|
+
----------
|
|
380
|
+
a : float
|
|
381
|
+
Semi-major axis (m)
|
|
382
|
+
e : float
|
|
383
|
+
Eccentricity (dimensionless)
|
|
384
|
+
inclination : float
|
|
385
|
+
Orbital inclination (radians)
|
|
386
|
+
angular_momentum : float
|
|
387
|
+
Angular momentum of central body (kg·m^2/s)
|
|
388
|
+
gm : float, optional
|
|
389
|
+
Gravitational parameter (m^3/s^2). Default is GM_EARTH.
|
|
390
|
+
|
|
391
|
+
Returns
|
|
392
|
+
-------
|
|
393
|
+
float
|
|
394
|
+
Lense-Thirring precession rate (radians per orbit)
|
|
395
|
+
|
|
396
|
+
Notes
|
|
397
|
+
-----
|
|
398
|
+
This is a simplified version. For Earth, J_2 effects typically dominate
|
|
399
|
+
classical nodal precession, while Lense-Thirring is a small correction
|
|
400
|
+
(~1 arcsec per year for typical satellites).
|
|
401
|
+
|
|
402
|
+
Examples
|
|
403
|
+
--------
|
|
404
|
+
Lense-Thirring effect for LAGEOS satellite:
|
|
405
|
+
|
|
406
|
+
>>> # LAGEOS parameters
|
|
407
|
+
>>> a = 12.27e6 # Semi-major axis
|
|
408
|
+
>>> e = 0.0045
|
|
409
|
+
>>> i = np.radians(109.9)
|
|
410
|
+
>>> L_earth = 7.05e33 # Earth's angular momentum
|
|
411
|
+
>>> rate = lense_thirring_precession(a, e, i, L_earth)
|
|
412
|
+
>>> print(f"LT precession per orbit: {rate*206265*1e3:.1f} milliarcsec")
|
|
413
|
+
"""
|
|
414
|
+
p = a * (1.0 - e**2)
|
|
415
|
+
|
|
416
|
+
# Simplified Lense-Thirring term (second-order PN effect)
|
|
417
|
+
# For a sphere: Lense-Thirring parameter = 2GM*L/(c^2*M*r^3)
|
|
418
|
+
precession = (2.0 * angular_momentum * gm) / (C_LIGHT**2 * p**3)
|
|
419
|
+
|
|
420
|
+
return precession
|
|
421
|
+
|
|
422
|
+
|
|
423
|
+
def relativistic_range_correction(
|
|
424
|
+
distance: float, relative_velocity: float, gm: float = GM_EARTH
|
|
425
|
+
) -> float:
|
|
426
|
+
"""Compute relativistic range correction for ranging measurements.
|
|
427
|
+
|
|
428
|
+
When measuring distance to a satellite or spacecraft using ranging
|
|
429
|
+
(e.g., laser ranging), relativistic effects introduce corrections to
|
|
430
|
+
the measured range.
|
|
431
|
+
|
|
432
|
+
The main contributions are:
|
|
433
|
+
- Gravitational time dilation
|
|
434
|
+
- Relativistic Doppler effect
|
|
435
|
+
|
|
436
|
+
Parameters
|
|
437
|
+
----------
|
|
438
|
+
distance : float
|
|
439
|
+
Distance to object (m)
|
|
440
|
+
relative_velocity : float
|
|
441
|
+
Radial velocity component (m/s, positive = receding)
|
|
442
|
+
gm : float, optional
|
|
443
|
+
Gravitational parameter (m^3/s^2). Default is GM_EARTH.
|
|
444
|
+
|
|
445
|
+
Returns
|
|
446
|
+
-------
|
|
447
|
+
float
|
|
448
|
+
Range correction (m)
|
|
449
|
+
|
|
450
|
+
Examples
|
|
451
|
+
--------
|
|
452
|
+
Range correction for lunar laser ranging:
|
|
453
|
+
|
|
454
|
+
>>> distance_to_moon = 3.84e8 # meters
|
|
455
|
+
>>> radial_velocity = 0.0 # Average over orbit
|
|
456
|
+
>>> correction = relativistic_range_correction(distance_to_moon, radial_velocity, GM_EARTH)
|
|
457
|
+
>>> print(f"Range correction: {correction:.1f} m")
|
|
458
|
+
"""
|
|
459
|
+
# Gravitational correction (positive because the signal is delayed)
|
|
460
|
+
# Uses weak-field approximation
|
|
461
|
+
grav_correction = gm / (C_LIGHT**2)
|
|
462
|
+
|
|
463
|
+
# Doppler correction (second order effect, small)
|
|
464
|
+
doppler_correction = (relative_velocity**2) / (3.0 * C_LIGHT**2)
|
|
465
|
+
|
|
466
|
+
return grav_correction + doppler_correction
|
pytcl/atmosphere/__init__.py
CHANGED
|
@@ -5,20 +5,18 @@ This module provides standard atmosphere models used for computing
|
|
|
5
5
|
temperature, pressure, density, and other properties at various altitudes.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
|
-
from pytcl.atmosphere.models import
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
us_standard_atmosphere_1976,
|
|
21
|
-
)
|
|
8
|
+
from pytcl.atmosphere.models import G0 # Constants
|
|
9
|
+
from pytcl.atmosphere.models import GAMMA
|
|
10
|
+
from pytcl.atmosphere.models import P0
|
|
11
|
+
from pytcl.atmosphere.models import RHO0
|
|
12
|
+
from pytcl.atmosphere.models import T0
|
|
13
|
+
from pytcl.atmosphere.models import AtmosphereState
|
|
14
|
+
from pytcl.atmosphere.models import R
|
|
15
|
+
from pytcl.atmosphere.models import altitude_from_pressure
|
|
16
|
+
from pytcl.atmosphere.models import isa_atmosphere
|
|
17
|
+
from pytcl.atmosphere.models import mach_number
|
|
18
|
+
from pytcl.atmosphere.models import true_airspeed_from_mach
|
|
19
|
+
from pytcl.atmosphere.models import us_standard_atmosphere_1976
|
|
22
20
|
|
|
23
21
|
__all__ = [
|
|
24
22
|
"AtmosphereState",
|
pytcl/atmosphere/models.py
CHANGED
|
@@ -5,10 +5,12 @@ This module provides standard atmosphere models used for computing
|
|
|
5
5
|
temperature, pressure, and density at various altitudes.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
|
-
from typing import NamedTuple
|
|
8
|
+
from typing import NamedTuple
|
|
9
|
+
from typing import Tuple
|
|
9
10
|
|
|
10
11
|
import numpy as np
|
|
11
|
-
from numpy.typing import ArrayLike
|
|
12
|
+
from numpy.typing import ArrayLike
|
|
13
|
+
from numpy.typing import NDArray
|
|
12
14
|
|
|
13
15
|
|
|
14
16
|
class AtmosphereState(NamedTuple):
|
|
@@ -218,9 +220,7 @@ def isa_atmosphere(
|
|
|
218
220
|
strat_mask = altitude > h_trop
|
|
219
221
|
temperature[strat_mask] = T_trop + temperature_offset
|
|
220
222
|
# Pressure at tropopause
|
|
221
|
-
P_trop = P0 * ((T0 + temperature_offset) / (T_trop + temperature_offset)) ** (
|
|
222
|
-
G0 / (R * L)
|
|
223
|
-
)
|
|
223
|
+
P_trop = P0 * ((T0 + temperature_offset) / (T_trop + temperature_offset)) ** (G0 / (R * L))
|
|
224
224
|
pressure[strat_mask] = P_trop * np.exp(
|
|
225
225
|
-G0 * (altitude[strat_mask] - h_trop) / (R * (T_trop + temperature_offset))
|
|
226
226
|
)
|
pytcl/clustering/__init__.py
CHANGED
|
@@ -6,42 +6,34 @@ commonly used in multi-target tracking for hypothesis management and
|
|
|
6
6
|
track clustering.
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
|
-
from pytcl.clustering.dbscan import
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
from pytcl.clustering.gaussian_mixture import
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
from pytcl.clustering.hierarchical import
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
from pytcl.clustering.kmeans import (
|
|
38
|
-
KMeansResult,
|
|
39
|
-
assign_clusters,
|
|
40
|
-
kmeans,
|
|
41
|
-
kmeans_elbow,
|
|
42
|
-
kmeans_plusplus_init,
|
|
43
|
-
update_centers,
|
|
44
|
-
)
|
|
9
|
+
from pytcl.clustering.dbscan import DBSCANResult
|
|
10
|
+
from pytcl.clustering.dbscan import compute_neighbors
|
|
11
|
+
from pytcl.clustering.dbscan import dbscan
|
|
12
|
+
from pytcl.clustering.dbscan import dbscan_predict
|
|
13
|
+
from pytcl.clustering.gaussian_mixture import GaussianComponent
|
|
14
|
+
from pytcl.clustering.gaussian_mixture import GaussianMixture
|
|
15
|
+
from pytcl.clustering.gaussian_mixture import MergeResult
|
|
16
|
+
from pytcl.clustering.gaussian_mixture import ReductionResult
|
|
17
|
+
from pytcl.clustering.gaussian_mixture import merge_gaussians
|
|
18
|
+
from pytcl.clustering.gaussian_mixture import moment_match
|
|
19
|
+
from pytcl.clustering.gaussian_mixture import prune_mixture
|
|
20
|
+
from pytcl.clustering.gaussian_mixture import reduce_mixture_runnalls
|
|
21
|
+
from pytcl.clustering.gaussian_mixture import reduce_mixture_west
|
|
22
|
+
from pytcl.clustering.gaussian_mixture import runnalls_merge_cost
|
|
23
|
+
from pytcl.clustering.gaussian_mixture import west_merge_cost
|
|
24
|
+
from pytcl.clustering.hierarchical import DendrogramNode
|
|
25
|
+
from pytcl.clustering.hierarchical import HierarchicalResult
|
|
26
|
+
from pytcl.clustering.hierarchical import LinkageType
|
|
27
|
+
from pytcl.clustering.hierarchical import agglomerative_clustering
|
|
28
|
+
from pytcl.clustering.hierarchical import compute_distance_matrix
|
|
29
|
+
from pytcl.clustering.hierarchical import cut_dendrogram
|
|
30
|
+
from pytcl.clustering.hierarchical import fcluster
|
|
31
|
+
from pytcl.clustering.kmeans import KMeansResult
|
|
32
|
+
from pytcl.clustering.kmeans import assign_clusters
|
|
33
|
+
from pytcl.clustering.kmeans import kmeans
|
|
34
|
+
from pytcl.clustering.kmeans import kmeans_elbow
|
|
35
|
+
from pytcl.clustering.kmeans import kmeans_plusplus_init
|
|
36
|
+
from pytcl.clustering.kmeans import update_centers
|
|
45
37
|
|
|
46
38
|
__all__ = [
|
|
47
39
|
# Gaussian mixture
|
pytcl/clustering/dbscan.py
CHANGED
|
@@ -12,11 +12,14 @@ References
|
|
|
12
12
|
with Noise," KDD 1996.
|
|
13
13
|
"""
|
|
14
14
|
|
|
15
|
-
from typing import List
|
|
15
|
+
from typing import List
|
|
16
|
+
from typing import NamedTuple
|
|
17
|
+
from typing import Set
|
|
16
18
|
|
|
17
19
|
import numpy as np
|
|
18
20
|
from numba import njit
|
|
19
|
-
from numpy.typing import ArrayLike
|
|
21
|
+
from numpy.typing import ArrayLike
|
|
22
|
+
from numpy.typing import NDArray
|
|
20
23
|
|
|
21
24
|
|
|
22
25
|
class DBSCANResult(NamedTuple):
|
|
@@ -13,10 +13,14 @@ References
|
|
|
13
13
|
Journal of the Royal Statistical Society, Series B, vol. 55, no. 2, 1993.
|
|
14
14
|
"""
|
|
15
15
|
|
|
16
|
-
from typing import List
|
|
16
|
+
from typing import List
|
|
17
|
+
from typing import NamedTuple
|
|
18
|
+
from typing import Optional
|
|
19
|
+
from typing import Tuple
|
|
17
20
|
|
|
18
21
|
import numpy as np
|
|
19
|
-
from numpy.typing import ArrayLike
|
|
22
|
+
from numpy.typing import ArrayLike
|
|
23
|
+
from numpy.typing import NDArray
|
|
20
24
|
|
|
21
25
|
|
|
22
26
|
class GaussianComponent(NamedTuple):
|
|
@@ -297,8 +301,7 @@ def prune_mixture(
|
|
|
297
301
|
total_weight = sum(c.weight for c in surviving)
|
|
298
302
|
if total_weight > 0:
|
|
299
303
|
surviving = [
|
|
300
|
-
GaussianComponent(c.weight / total_weight, c.mean, c.covariance)
|
|
301
|
-
for c in surviving
|
|
304
|
+
GaussianComponent(c.weight / total_weight, c.mean, c.covariance) for c in surviving
|
|
302
305
|
]
|
|
303
306
|
|
|
304
307
|
return surviving
|
|
@@ -392,8 +395,7 @@ def reduce_mixture_runnalls(
|
|
|
392
395
|
total_weight = sum(c.weight for c in working)
|
|
393
396
|
if total_weight > 0:
|
|
394
397
|
working = [
|
|
395
|
-
GaussianComponent(c.weight / total_weight, c.mean, c.covariance)
|
|
396
|
-
for c in working
|
|
398
|
+
GaussianComponent(c.weight / total_weight, c.mean, c.covariance) for c in working
|
|
397
399
|
]
|
|
398
400
|
|
|
399
401
|
return ReductionResult(working, n_original, len(working), total_cost)
|
|
@@ -533,8 +535,7 @@ def reduce_mixture_west(
|
|
|
533
535
|
total_weight = sum(c.weight for c in working)
|
|
534
536
|
if total_weight > 0:
|
|
535
537
|
working = [
|
|
536
|
-
GaussianComponent(c.weight / total_weight, c.mean, c.covariance)
|
|
537
|
-
for c in working
|
|
538
|
+
GaussianComponent(c.weight / total_weight, c.mean, c.covariance) for c in working
|
|
538
539
|
]
|
|
539
540
|
|
|
540
541
|
return ReductionResult(working, n_original, len(working), total_cost)
|
|
@@ -605,8 +606,7 @@ class GaussianMixture:
|
|
|
605
606
|
total = sum(c.weight for c in self.components)
|
|
606
607
|
if total > 0:
|
|
607
608
|
self.components = [
|
|
608
|
-
GaussianComponent(c.weight / total, c.mean, c.covariance)
|
|
609
|
-
for c in self.components
|
|
609
|
+
GaussianComponent(c.weight / total, c.mean, c.covariance) for c in self.components
|
|
610
610
|
]
|
|
611
611
|
|
|
612
612
|
@property
|