nrl-tracker 1.7.0__py3-none-any.whl → 1.7.1__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 (75) hide show
  1. {nrl_tracker-1.7.0.dist-info → nrl_tracker-1.7.1.dist-info}/METADATA +3 -2
  2. {nrl_tracker-1.7.0.dist-info → nrl_tracker-1.7.1.dist-info}/RECORD +75 -75
  3. pytcl/__init__.py +2 -2
  4. pytcl/assignment_algorithms/__init__.py +15 -15
  5. pytcl/assignment_algorithms/gating.py +10 -10
  6. pytcl/assignment_algorithms/jpda.py +40 -40
  7. pytcl/assignment_algorithms/nd_assignment.py +5 -4
  8. pytcl/assignment_algorithms/network_flow.py +18 -8
  9. pytcl/assignment_algorithms/three_dimensional/assignment.py +3 -3
  10. pytcl/astronomical/__init__.py +6 -6
  11. pytcl/astronomical/ephemerides.py +14 -11
  12. pytcl/astronomical/reference_frames.py +8 -4
  13. pytcl/astronomical/relativity.py +6 -5
  14. pytcl/astronomical/special_orbits.py +9 -13
  15. pytcl/atmosphere/__init__.py +6 -6
  16. pytcl/atmosphere/nrlmsise00.py +153 -152
  17. pytcl/clustering/dbscan.py +2 -2
  18. pytcl/clustering/gaussian_mixture.py +3 -3
  19. pytcl/clustering/hierarchical.py +15 -15
  20. pytcl/clustering/kmeans.py +4 -4
  21. pytcl/containers/base.py +3 -3
  22. pytcl/containers/cluster_set.py +12 -2
  23. pytcl/containers/covertree.py +5 -3
  24. pytcl/containers/rtree.py +1 -1
  25. pytcl/containers/vptree.py +4 -2
  26. pytcl/coordinate_systems/conversions/geodetic.py +31 -7
  27. pytcl/coordinate_systems/jacobians/jacobians.py +2 -2
  28. pytcl/coordinate_systems/projections/projections.py +2 -2
  29. pytcl/coordinate_systems/rotations/rotations.py +10 -6
  30. pytcl/core/validation.py +3 -3
  31. pytcl/dynamic_estimation/__init__.py +16 -16
  32. pytcl/dynamic_estimation/gaussian_sum_filter.py +20 -38
  33. pytcl/dynamic_estimation/imm.py +14 -14
  34. pytcl/dynamic_estimation/kalman/__init__.py +1 -1
  35. pytcl/dynamic_estimation/kalman/constrained.py +35 -23
  36. pytcl/dynamic_estimation/kalman/extended.py +8 -8
  37. pytcl/dynamic_estimation/kalman/h_infinity.py +2 -2
  38. pytcl/dynamic_estimation/kalman/square_root.py +8 -2
  39. pytcl/dynamic_estimation/kalman/sr_ukf.py +3 -3
  40. pytcl/dynamic_estimation/kalman/ud_filter.py +11 -5
  41. pytcl/dynamic_estimation/kalman/unscented.py +8 -6
  42. pytcl/dynamic_estimation/particle_filters/bootstrap.py +15 -15
  43. pytcl/dynamic_estimation/rbpf.py +36 -40
  44. pytcl/gravity/spherical_harmonics.py +3 -3
  45. pytcl/gravity/tides.py +6 -6
  46. pytcl/logging_config.py +3 -3
  47. pytcl/magnetism/emm.py +10 -3
  48. pytcl/magnetism/wmm.py +4 -4
  49. pytcl/mathematical_functions/combinatorics/combinatorics.py +5 -5
  50. pytcl/mathematical_functions/geometry/geometry.py +5 -5
  51. pytcl/mathematical_functions/numerical_integration/quadrature.py +6 -6
  52. pytcl/mathematical_functions/signal_processing/detection.py +24 -24
  53. pytcl/mathematical_functions/signal_processing/filters.py +14 -14
  54. pytcl/mathematical_functions/signal_processing/matched_filter.py +12 -12
  55. pytcl/mathematical_functions/special_functions/bessel.py +15 -3
  56. pytcl/mathematical_functions/special_functions/debye.py +5 -1
  57. pytcl/mathematical_functions/special_functions/error_functions.py +3 -1
  58. pytcl/mathematical_functions/special_functions/gamma_functions.py +4 -4
  59. pytcl/mathematical_functions/special_functions/hypergeometric.py +6 -4
  60. pytcl/mathematical_functions/transforms/fourier.py +8 -8
  61. pytcl/mathematical_functions/transforms/stft.py +12 -12
  62. pytcl/mathematical_functions/transforms/wavelets.py +9 -9
  63. pytcl/navigation/geodesy.py +3 -3
  64. pytcl/navigation/great_circle.py +5 -5
  65. pytcl/plotting/coordinates.py +7 -7
  66. pytcl/plotting/tracks.py +2 -2
  67. pytcl/static_estimation/maximum_likelihood.py +16 -14
  68. pytcl/static_estimation/robust.py +5 -5
  69. pytcl/terrain/loaders.py +5 -5
  70. pytcl/trackers/hypothesis.py +1 -1
  71. pytcl/trackers/mht.py +9 -9
  72. pytcl/trackers/multi_target.py +1 -1
  73. {nrl_tracker-1.7.0.dist-info → nrl_tracker-1.7.1.dist-info}/LICENSE +0 -0
  74. {nrl_tracker-1.7.0.dist-info → nrl_tracker-1.7.1.dist-info}/WHEEL +0 -0
  75. {nrl_tracker-1.7.0.dist-info → nrl_tracker-1.7.1.dist-info}/top_level.txt +0 -0
@@ -20,7 +20,7 @@ References
20
20
  """
21
21
 
22
22
  from enum import Enum
23
- from typing import NamedTuple, Tuple
23
+ from typing import Any, NamedTuple, Tuple
24
24
 
25
25
  import numpy as np
26
26
  from numpy.typing import NDArray
@@ -79,7 +79,7 @@ class FlowEdge(NamedTuple):
79
79
 
80
80
  def assignment_to_flow_network(
81
81
  cost_matrix: NDArray[np.float64],
82
- ) -> Tuple[list, NDArray, NDArray]:
82
+ ) -> Tuple[list[FlowEdge], NDArray[np.floating], NDArray[Any]]:
83
83
  """
84
84
  Convert 2D assignment problem to min-cost flow network.
85
85
 
@@ -140,7 +140,9 @@ def assignment_to_flow_network(
140
140
  # Tasks to sink: capacity 1, cost 0
141
141
  for j in range(1, n + 1):
142
142
  task_node = m + j
143
- edges.append(FlowEdge(from_node=task_node, to_node=sink, capacity=1.0, cost=0.0))
143
+ edges.append(
144
+ FlowEdge(from_node=task_node, to_node=sink, capacity=1.0, cost=0.0)
145
+ )
144
146
 
145
147
  # Supply/demand: source supplies m units, sink demands m units
146
148
  supplies = np.zeros(n_nodes)
@@ -158,7 +160,7 @@ def assignment_to_flow_network(
158
160
 
159
161
 
160
162
  def min_cost_flow_successive_shortest_paths(
161
- edges: list,
163
+ edges: list[FlowEdge],
162
164
  supplies: NDArray[np.float64],
163
165
  max_iterations: int = 1000,
164
166
  ) -> MinCostFlowResult:
@@ -260,7 +262,9 @@ def min_cost_flow_successive_shortest_paths(
260
262
 
261
263
  # Find minimum capacity along path
262
264
  min_flow = min(residual_capacity[e] for e in path_edges)
263
- min_flow = min(min_flow, current_supplies[excess_node], -current_supplies[deficit_node])
265
+ min_flow = min(
266
+ min_flow, current_supplies[excess_node], -current_supplies[deficit_node]
267
+ )
264
268
 
265
269
  # Push flow along path
266
270
  total_cost = 0.0
@@ -295,7 +299,7 @@ def min_cost_flow_successive_shortest_paths(
295
299
 
296
300
  def assignment_from_flow_solution(
297
301
  flow: NDArray[np.float64],
298
- edges: list,
302
+ edges: list[FlowEdge],
299
303
  cost_matrix_shape: Tuple[int, int],
300
304
  ) -> Tuple[NDArray[np.intp], float]:
301
305
  """
@@ -331,7 +335,11 @@ def assignment_from_flow_solution(
331
335
  assignment = np.array(assignment, dtype=np.intp)
332
336
  cost = 0.0
333
337
  if len(assignment) > 0:
334
- cost = float(np.sum(flow[edge_idx] * edges[edge_idx].cost for edge_idx in range(len(edges))))
338
+ cost = float(
339
+ np.sum(
340
+ flow[edge_idx] * edges[edge_idx].cost for edge_idx in range(len(edges))
341
+ )
342
+ )
335
343
 
336
344
  return assignment, cost
337
345
 
@@ -356,6 +364,8 @@ def min_cost_assignment_via_flow(
356
364
  """
357
365
  edges, supplies, _ = assignment_to_flow_network(cost_matrix)
358
366
  result = min_cost_flow_successive_shortest_paths(edges, supplies)
359
- assignment, cost = assignment_from_flow_solution(result.flow, edges, cost_matrix.shape)
367
+ assignment, cost = assignment_from_flow_solution(
368
+ result.flow, edges, cost_matrix.shape
369
+ )
360
370
 
361
371
  return assignment, cost
@@ -11,7 +11,7 @@ cost subject to the constraint that each index appears in at most one
11
11
  selected tuple.
12
12
  """
13
13
 
14
- from typing import List, NamedTuple, Optional, Tuple
14
+ from typing import Any, List, NamedTuple, Optional, Tuple
15
15
 
16
16
  import numpy as np
17
17
  from numpy.typing import ArrayLike, NDArray
@@ -511,7 +511,7 @@ def assign3d_auction(
511
511
  assign_i: List[Optional[Tuple[int, int]]] = [None] * n1
512
512
 
513
513
  # Reverse: which i is assigned to (j, k)
514
- reverse: dict = {}
514
+ reverse: dict[tuple[int, int], int] = {}
515
515
 
516
516
  converged = False
517
517
 
@@ -585,7 +585,7 @@ def assign3d(
585
585
  cost_tensor: ArrayLike,
586
586
  method: str = "lagrangian",
587
587
  maximize: bool = False,
588
- **kwargs,
588
+ **kwargs: Any,
589
589
  ) -> Assignment3DResult:
590
590
  """
591
591
  Solve 3D assignment problem.
@@ -130,6 +130,12 @@ from pytcl.astronomical.relativity import (
130
130
  schwarzschild_radius,
131
131
  shapiro_delay,
132
132
  )
133
+ from pytcl.astronomical.sgp4 import (
134
+ SGP4Satellite,
135
+ SGP4State,
136
+ sgp4_propagate,
137
+ sgp4_propagate_batch,
138
+ )
133
139
  from pytcl.astronomical.special_orbits import (
134
140
  OrbitType,
135
141
  classify_orbit,
@@ -148,12 +154,6 @@ from pytcl.astronomical.special_orbits import (
148
154
  true_anomaly_to_parabolic_anomaly,
149
155
  velocity_parabolic,
150
156
  )
151
- from pytcl.astronomical.sgp4 import (
152
- SGP4Satellite,
153
- SGP4State,
154
- sgp4_propagate,
155
- sgp4_propagate_batch,
156
- )
157
157
  from pytcl.astronomical.time_systems import (
158
158
  JD_GPS_EPOCH, # Julian dates; Time scales; Unix time; GPS week; Sidereal time; Leap seconds; Constants
159
159
  )
@@ -49,9 +49,10 @@ References
49
49
 
50
50
  """
51
51
 
52
- from typing import Literal, Optional, Tuple
52
+ from typing import Any, Literal, Optional, Tuple
53
53
 
54
54
  import numpy as np
55
+ from numpy.typing import NDArray
55
56
 
56
57
  # Constants for unit conversion
57
58
  AU_PER_KM = 1.0 / 149597870.7 # 1 AU in km
@@ -152,10 +153,10 @@ class DEEphemeris:
152
153
  self.version = version
153
154
  self._jplephem = jplephem
154
155
  self._kernel: Optional[object] = None
155
- self._cache: dict = {}
156
+ self._cache: dict[str, Any] = {}
156
157
 
157
158
  @property
158
- def kernel(self):
159
+ def kernel(self) -> Optional[object]:
159
160
  """Lazy-load ephemeris kernel on first access.
160
161
 
161
162
  Note: This requires jplephem to be installed and the kernel file
@@ -204,7 +205,7 @@ class DEEphemeris:
204
205
 
205
206
  def sun_position(
206
207
  self, jd: float, frame: Literal["icrf", "ecliptic"] = "icrf"
207
- ) -> Tuple[np.ndarray, np.ndarray]:
208
+ ) -> Tuple[NDArray[np.floating], NDArray[np.floating]]:
208
209
  """Compute Sun position and velocity.
209
210
 
210
211
  Parameters
@@ -253,7 +254,7 @@ class DEEphemeris:
253
254
 
254
255
  def moon_position(
255
256
  self, jd: float, frame: Literal["icrf", "ecliptic", "earth_centered"] = "icrf"
256
- ) -> Tuple[np.ndarray, np.ndarray]:
257
+ ) -> Tuple[NDArray[np.floating], NDArray[np.floating]]:
257
258
  """Compute Moon position and velocity.
258
259
 
259
260
  Parameters
@@ -323,7 +324,7 @@ class DEEphemeris:
323
324
  ],
324
325
  jd: float,
325
326
  frame: Literal["icrf", "ecliptic"] = "icrf",
326
- ) -> Tuple[np.ndarray, np.ndarray]:
327
+ ) -> Tuple[np.ndarray[Any, Any], np.ndarray[Any, Any]]:
327
328
  """Compute planet position and velocity.
328
329
 
329
330
  Parameters
@@ -379,7 +380,7 @@ class DEEphemeris:
379
380
 
380
381
  def barycenter_position(
381
382
  self, body: str, jd: float
382
- ) -> Tuple[np.ndarray, np.ndarray]:
383
+ ) -> Tuple[NDArray[np.floating], NDArray[np.floating]]:
383
384
  """Compute position of any body relative to Solar System Barycenter.
384
385
 
385
386
  Parameters
@@ -424,7 +425,7 @@ def _get_default_ephemeris() -> DEEphemeris:
424
425
 
425
426
  def sun_position(
426
427
  jd: float, frame: Literal["icrf", "ecliptic"] = "icrf"
427
- ) -> Tuple[np.ndarray, np.ndarray]:
428
+ ) -> Tuple[NDArray[np.floating], NDArray[np.floating]]:
428
429
  """Convenience function: Compute Sun position and velocity.
429
430
 
430
431
  Parameters
@@ -451,7 +452,7 @@ def sun_position(
451
452
 
452
453
  def moon_position(
453
454
  jd: float, frame: Literal["icrf", "ecliptic", "earth_centered"] = "icrf"
454
- ) -> Tuple[np.ndarray, np.ndarray]:
455
+ ) -> Tuple[NDArray[np.floating], NDArray[np.floating]]:
455
456
  """Convenience function: Compute Moon position and velocity.
456
457
 
457
458
  Parameters
@@ -482,7 +483,7 @@ def planet_position(
482
483
  ],
483
484
  jd: float,
484
485
  frame: Literal["icrf", "ecliptic"] = "icrf",
485
- ) -> Tuple[np.ndarray, np.ndarray]:
486
+ ) -> Tuple[np.ndarray[Any, Any], np.ndarray[Any, Any]]:
486
487
  """Convenience function: Compute planet position and velocity.
487
488
 
488
489
  Parameters
@@ -509,7 +510,9 @@ def planet_position(
509
510
  return _get_default_ephemeris().planet_position(planet, jd, frame=frame)
510
511
 
511
512
 
512
- def barycenter_position(body: str, jd: float) -> Tuple[np.ndarray, np.ndarray]:
513
+ def barycenter_position(
514
+ body: str, jd: float
515
+ ) -> Tuple[NDArray[np.floating], NDArray[np.floating]]:
513
516
  """Convenience function: Position relative to Solar System Barycenter.
514
517
 
515
518
  Parameters
@@ -23,7 +23,7 @@ References
23
23
 
24
24
  import logging
25
25
  from functools import lru_cache
26
- from typing import Optional, Tuple
26
+ from typing import Any, Optional, Tuple
27
27
 
28
28
  import numpy as np
29
29
  from numpy.typing import NDArray
@@ -97,7 +97,9 @@ def precession_angles_iau76(T: float) -> Tuple[float, float, float]:
97
97
 
98
98
 
99
99
  @lru_cache(maxsize=_CACHE_MAXSIZE)
100
- def _precession_matrix_cached(jd_quantized: float) -> tuple:
100
+ def _precession_matrix_cached(
101
+ jd_quantized: float,
102
+ ) -> tuple[tuple[np.ndarray[Any, Any], ...], ...]:
101
103
  """Cached precession matrix computation (internal).
102
104
 
103
105
  Returns tuple of tuples for hashability.
@@ -234,7 +236,9 @@ def mean_obliquity_iau80(jd: float) -> float:
234
236
 
235
237
 
236
238
  @lru_cache(maxsize=_CACHE_MAXSIZE)
237
- def _nutation_matrix_cached(jd_quantized: float) -> tuple:
239
+ def _nutation_matrix_cached(
240
+ jd_quantized: float,
241
+ ) -> tuple[tuple[np.ndarray[Any, Any], ...], ...]:
238
242
  """Cached nutation matrix computation (internal).
239
243
 
240
244
  Returns tuple of tuples for hashability.
@@ -1414,7 +1418,7 @@ def clear_transformation_cache() -> None:
1414
1418
  _logger.debug("Transformation matrix cache cleared")
1415
1419
 
1416
1420
 
1417
- def get_cache_info() -> dict:
1421
+ def get_cache_info() -> dict[str, Any]:
1418
1422
  """Get cache statistics for transformation matrices.
1419
1423
 
1420
1424
  Returns
@@ -20,6 +20,7 @@ References:
20
20
  """
21
21
 
22
22
  import numpy as np
23
+ from numpy.typing import NDArray
23
24
 
24
25
  # Physical constants (CODATA 2018 values)
25
26
  C_LIGHT = 299792458.0 # Speed of light (m/s)
@@ -154,9 +155,9 @@ def proper_time_rate(v: float, r: float, gm: float = GM_EARTH) -> float:
154
155
 
155
156
 
156
157
  def shapiro_delay(
157
- observer_pos: np.ndarray,
158
- light_source_pos: np.ndarray,
159
- gravitating_body_pos: np.ndarray,
158
+ observer_pos: NDArray[np.floating],
159
+ light_source_pos: NDArray[np.floating],
160
+ gravitating_body_pos: NDArray[np.floating],
160
161
  gm: float = GM_SUN,
161
162
  ) -> float:
162
163
  """Compute Shapiro time delay for light propagation through gravitational field.
@@ -263,8 +264,8 @@ def schwarzschild_precession_per_orbit(a: float, e: float, gm: float = GM_SUN) -
263
264
 
264
265
 
265
266
  def post_newtonian_acceleration(
266
- r_vec: np.ndarray, v_vec: np.ndarray, gm: float = GM_EARTH
267
- ) -> np.ndarray:
267
+ r_vec: NDArray[np.floating], v_vec: NDArray[np.floating], gm: float = GM_EARTH
268
+ ) -> NDArray[np.floating]:
268
269
  """Compute post-Newtonian acceleration corrections (1PN order).
269
270
 
270
271
  Extends Newtonian gravity with first-order post-Newtonian corrections.
@@ -26,10 +26,10 @@ from numpy.typing import NDArray
26
26
  class OrbitType(Enum):
27
27
  """Classification of orbit types based on eccentricity."""
28
28
 
29
- CIRCULAR = 0 # e = 0
30
- ELLIPTICAL = 1 # 0 < e < 1
31
- PARABOLIC = 2 # e = 1 (boundary case)
32
- HYPERBOLIC = 3 # e > 1
29
+ CIRCULAR = 0 # e = 0
30
+ ELLIPTICAL = 1 # 0 < e < 1
31
+ PARABOLIC = 2 # e = 1 (boundary case)
32
+ HYPERBOLIC = 3 # e > 1
33
33
 
34
34
 
35
35
  class ParabolicElements(NamedTuple):
@@ -303,9 +303,7 @@ def hyperbolic_anomaly_to_true_anomaly(H: float, e: float) -> float:
303
303
  if e <= 1:
304
304
  raise ValueError(f"Eccentricity must be > 1 for hyperbolic orbits, got {e}")
305
305
 
306
- nu = 2.0 * np.arctan(
307
- np.sqrt((e + 1.0) / (e - 1.0)) * np.tanh(H / 2.0)
308
- )
306
+ nu = 2.0 * np.arctan(np.sqrt((e + 1.0) / (e - 1.0)) * np.tanh(H / 2.0))
309
307
 
310
308
  return nu
311
309
 
@@ -334,9 +332,7 @@ def true_anomaly_to_hyperbolic_anomaly(nu: float, e: float) -> float:
334
332
  if e <= 1:
335
333
  raise ValueError(f"Eccentricity must be > 1 for hyperbolic orbits, got {e}")
336
334
 
337
- H = 2.0 * np.arctanh(
338
- np.sqrt((e - 1.0) / (e + 1.0)) * np.tan(nu / 2.0)
339
- )
335
+ H = 2.0 * np.arctanh(np.sqrt((e - 1.0) / (e + 1.0)) * np.tan(nu / 2.0))
340
336
 
341
337
  return H
342
338
 
@@ -501,10 +497,10 @@ def semi_major_axis_from_energy(mu: float, specific_energy: float) -> float:
501
497
 
502
498
 
503
499
  def eccentricity_vector(
504
- r: NDArray,
505
- v: NDArray,
500
+ r: NDArray[np.floating],
501
+ v: NDArray[np.floating],
506
502
  mu: float,
507
- ) -> NDArray:
503
+ ) -> NDArray[np.floating]:
508
504
  """
509
505
  Compute eccentricity vector from position and velocity.
510
506
 
@@ -24,12 +24,6 @@ from pytcl.atmosphere.ionosphere import (
24
24
  simple_iri,
25
25
  )
26
26
  from pytcl.atmosphere.models import G0 # Constants
27
- from pytcl.atmosphere.nrlmsise00 import (
28
- F107Index,
29
- NRLMSISE00,
30
- NRLMSISE00Output,
31
- nrlmsise00,
32
- )
33
27
  from pytcl.atmosphere.models import (
34
28
  GAMMA,
35
29
  P0,
@@ -43,6 +37,12 @@ from pytcl.atmosphere.models import (
43
37
  true_airspeed_from_mach,
44
38
  us_standard_atmosphere_1976,
45
39
  )
40
+ from pytcl.atmosphere.nrlmsise00 import (
41
+ NRLMSISE00,
42
+ F107Index,
43
+ NRLMSISE00Output,
44
+ nrlmsise00,
45
+ )
46
46
 
47
47
  __all__ = [
48
48
  # Atmosphere state and models