nrl-tracker 0.21.5__py3-none-any.whl → 0.22.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 (84) hide show
  1. {nrl_tracker-0.21.5.dist-info → nrl_tracker-0.22.1.dist-info}/METADATA +2 -2
  2. {nrl_tracker-0.21.5.dist-info → nrl_tracker-0.22.1.dist-info}/RECORD +84 -82
  3. pytcl/__init__.py +1 -1
  4. pytcl/assignment_algorithms/data_association.py +2 -7
  5. pytcl/assignment_algorithms/gating.py +3 -3
  6. pytcl/assignment_algorithms/jpda.py +4 -12
  7. pytcl/assignment_algorithms/two_dimensional/kbest.py +1 -3
  8. pytcl/astronomical/__init__.py +60 -7
  9. pytcl/astronomical/ephemerides.py +522 -0
  10. pytcl/astronomical/lambert.py +4 -14
  11. pytcl/astronomical/orbital_mechanics.py +1 -3
  12. pytcl/astronomical/reference_frames.py +1 -3
  13. pytcl/astronomical/relativity.py +466 -0
  14. pytcl/atmosphere/__init__.py +2 -2
  15. pytcl/atmosphere/models.py +1 -3
  16. pytcl/clustering/gaussian_mixture.py +4 -8
  17. pytcl/clustering/hierarchical.py +1 -5
  18. pytcl/clustering/kmeans.py +1 -3
  19. pytcl/containers/__init__.py +4 -21
  20. pytcl/containers/cluster_set.py +4 -19
  21. pytcl/containers/measurement_set.py +3 -15
  22. pytcl/containers/rtree.py +2 -5
  23. pytcl/coordinate_systems/conversions/geodetic.py +3 -15
  24. pytcl/coordinate_systems/projections/__init__.py +4 -2
  25. pytcl/coordinate_systems/projections/projections.py +11 -38
  26. pytcl/coordinate_systems/rotations/rotations.py +1 -3
  27. pytcl/core/array_utils.py +1 -4
  28. pytcl/core/constants.py +1 -3
  29. pytcl/core/validation.py +6 -17
  30. pytcl/dynamic_estimation/imm.py +4 -13
  31. pytcl/dynamic_estimation/kalman/extended.py +1 -4
  32. pytcl/dynamic_estimation/kalman/square_root.py +2 -6
  33. pytcl/dynamic_estimation/kalman/unscented.py +1 -4
  34. pytcl/dynamic_estimation/particle_filters/bootstrap.py +2 -6
  35. pytcl/dynamic_estimation/smoothers.py +2 -8
  36. pytcl/dynamic_models/discrete_time/__init__.py +1 -5
  37. pytcl/dynamic_models/process_noise/__init__.py +1 -5
  38. pytcl/dynamic_models/process_noise/polynomial.py +2 -6
  39. pytcl/gravity/clenshaw.py +2 -6
  40. pytcl/gravity/egm.py +1 -3
  41. pytcl/gravity/models.py +1 -3
  42. pytcl/gravity/spherical_harmonics.py +4 -10
  43. pytcl/gravity/tides.py +7 -16
  44. pytcl/magnetism/__init__.py +3 -14
  45. pytcl/magnetism/emm.py +3 -12
  46. pytcl/magnetism/wmm.py +2 -9
  47. pytcl/mathematical_functions/basic_matrix/decompositions.py +1 -3
  48. pytcl/mathematical_functions/combinatorics/combinatorics.py +1 -3
  49. pytcl/mathematical_functions/geometry/geometry.py +4 -12
  50. pytcl/mathematical_functions/interpolation/__init__.py +2 -2
  51. pytcl/mathematical_functions/interpolation/interpolation.py +1 -3
  52. pytcl/mathematical_functions/signal_processing/detection.py +3 -6
  53. pytcl/mathematical_functions/signal_processing/filters.py +2 -6
  54. pytcl/mathematical_functions/signal_processing/matched_filter.py +2 -4
  55. pytcl/mathematical_functions/special_functions/__init__.py +2 -2
  56. pytcl/mathematical_functions/special_functions/elliptic.py +1 -3
  57. pytcl/mathematical_functions/special_functions/gamma_functions.py +1 -3
  58. pytcl/mathematical_functions/special_functions/lambert_w.py +1 -3
  59. pytcl/mathematical_functions/statistics/distributions.py +12 -36
  60. pytcl/mathematical_functions/statistics/estimators.py +1 -3
  61. pytcl/mathematical_functions/transforms/stft.py +3 -9
  62. pytcl/mathematical_functions/transforms/wavelets.py +6 -18
  63. pytcl/navigation/__init__.py +14 -10
  64. pytcl/navigation/geodesy.py +9 -31
  65. pytcl/navigation/great_circle.py +4 -12
  66. pytcl/navigation/ins.py +3 -13
  67. pytcl/navigation/ins_gnss.py +7 -25
  68. pytcl/navigation/rhumb.py +3 -10
  69. pytcl/performance_evaluation/track_metrics.py +1 -3
  70. pytcl/plotting/coordinates.py +5 -17
  71. pytcl/plotting/metrics.py +3 -9
  72. pytcl/plotting/tracks.py +3 -11
  73. pytcl/static_estimation/least_squares.py +1 -2
  74. pytcl/static_estimation/maximum_likelihood.py +3 -3
  75. pytcl/terrain/dem.py +2 -8
  76. pytcl/terrain/loaders.py +5 -13
  77. pytcl/terrain/visibility.py +2 -7
  78. pytcl/trackers/__init__.py +3 -14
  79. pytcl/trackers/hypothesis.py +2 -7
  80. pytcl/trackers/mht.py +5 -15
  81. pytcl/trackers/multi_target.py +1 -4
  82. {nrl_tracker-0.21.5.dist-info → nrl_tracker-0.22.1.dist-info}/LICENSE +0 -0
  83. {nrl_tracker-0.21.5.dist-info → nrl_tracker-0.22.1.dist-info}/WHEEL +0 -0
  84. {nrl_tracker-0.21.5.dist-info → nrl_tracker-0.22.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,522 @@
1
+ """
2
+ JPL Ephemerides for High-Precision Celestial Mechanics
3
+
4
+ This module provides access to JPL Development Ephemeris (DE) files for computing
5
+ high-precision positions and velocities of celestial bodies (Sun, Moon, planets).
6
+
7
+ The module leverages the jplephem library, which provides optimized Fortran-based
8
+ interpolation of ephemeris kernels. Multiple DE versions are supported (DE405,
9
+ DE430, DE432s, DE440).
10
+
11
+ Constants
12
+ ---------
13
+ AU_PER_KM : float
14
+ Astronomical Unit in kilometers (1 AU = 149597870.7 km)
15
+ KM_PER_DAY_TO_AU_PER_DAY : float
16
+ Conversion factor for velocity from km/day to AU/day
17
+
18
+ Examples
19
+ --------
20
+ >>> from pytcl.astronomical.ephemerides import DEEphemeris
21
+ >>> from datetime import datetime
22
+ >>>
23
+ >>> # Load ephemeris (auto-downloads if needed)
24
+ >>> eph = DEEphemeris(version='DE440')
25
+ >>>
26
+ >>> # Query Sun position (AU)
27
+ >>> jd = 2451545.0 # J2000.0
28
+ >>> r_sun, v_sun = eph.sun_position(jd)
29
+ >>> print(f"Sun distance: {np.linalg.norm(r_sun):.6f} AU")
30
+ Sun distance: 0.983327 AU
31
+ >>>
32
+ >>> # Query Moon position
33
+ >>> r_moon, v_moon = eph.moon_position(jd)
34
+
35
+ Notes
36
+ -----
37
+ - Ephemeris files are auto-downloaded to ~/.jplephem/ on first use
38
+ - Time input is Julian Day (JD) in Terrestrial Time (TT) scale
39
+ - Positions returned in AU, velocities in AU/day in ICRF frame
40
+ - For highest precision, use DE440 (latest release) or DE432s (2013)
41
+
42
+ References
43
+ ----------
44
+ .. [1] Standish, E. M. (1995). "Report of the IAU WGAS Sub-group on
45
+ Numerical Standards". In Highlights of Astronomy (Vol. 10).
46
+ .. [2] Folkner, W. M., Williams, J. G., Boggs, D. H., Park, R. S., &
47
+ Kuchynka, P. (2014). "The Planetary and Lunar Ephemeris DE430 and DE431".
48
+ Interplanetary Network Progress Report, 42(196), 1-81.
49
+
50
+ """
51
+
52
+ from typing import Literal, Optional, Tuple
53
+
54
+ import numpy as np
55
+
56
+ # Constants for unit conversion
57
+ AU_PER_KM = 1.0 / 149597870.7 # 1 AU in km
58
+ KM_PER_DAY_TO_AU_PER_DAY = AU_PER_KM # velocity conversion factor
59
+ EPSILON_J2000 = 0.4090910179 # Mean obliquity of the ecliptic at J2000.0 (radians)
60
+
61
+ __all__ = [
62
+ "DEEphemeris",
63
+ "sun_position",
64
+ "moon_position",
65
+ "planet_position",
66
+ "barycenter_position",
67
+ ]
68
+
69
+
70
+ class DEEphemeris:
71
+ """High-precision JPL Development Ephemeris kernel wrapper.
72
+
73
+ This class manages access to JPL ephemeris files and provides methods
74
+ for querying positions and velocities of celestial bodies.
75
+
76
+ Parameters
77
+ ----------
78
+ version : {'DE405', 'DE430', 'DE432s', 'DE440'}, optional
79
+ Ephemeris version to load. Default is 'DE440' (latest).
80
+ - DE440: Latest JPL release (2020), covers 1550-2650
81
+ - DE432s: High-precision version (2013), covers 1350-3000
82
+ - DE430: Earlier release (2013), covers 1550-2650
83
+ - DE405: Older version (1998), compact, covers 1600-2200
84
+
85
+ Attributes
86
+ ----------
87
+ version : str
88
+ Ephemeris version identifier
89
+ kernel : jplephem.SpiceKernel
90
+ Loaded ephemeris kernel object
91
+ _cache : dict
92
+ Cache for frequently accessed positions
93
+
94
+ Raises
95
+ ------
96
+ ImportError
97
+ If jplephem is not installed
98
+ ValueError
99
+ If version is not recognized
100
+
101
+ Examples
102
+ --------
103
+ >>> eph = DEEphemeris(version='DE440')
104
+ >>> r_sun, v_sun = eph.sun_position(2451545.0)
105
+
106
+ """
107
+
108
+ # Valid ephemeris versions
109
+ _VALID_VERSIONS = {"DE405", "DE430", "DE432s", "DE440"}
110
+
111
+ # Supported bodies and their DE IDs
112
+ # See: https://naif.jpl.nasa.gov/pub/naif/toolkit_docs/C/req/naif_ids.html
113
+ _BODY_IDS = {
114
+ "mercury": 1,
115
+ "venus": 2,
116
+ "earth": 3,
117
+ "moon": 301,
118
+ "mars": 4,
119
+ "jupiter": 5,
120
+ "saturn": 6,
121
+ "uranus": 7,
122
+ "neptune": 8,
123
+ "pluto": 9,
124
+ "sun": 10,
125
+ "earth_moon_barycenter": 3,
126
+ "solar_system_barycenter": 0,
127
+ }
128
+
129
+ def __init__(self, version: str = "DE440") -> None:
130
+ """Initialize ephemeris kernel.
131
+
132
+ Parameters
133
+ ----------
134
+ version : str, optional
135
+ Ephemeris version (default: 'DE440')
136
+
137
+ """
138
+ if version not in self._VALID_VERSIONS:
139
+ raise ValueError(
140
+ f"Ephemeris version must be one of {self._VALID_VERSIONS}, " f"got '{version}'"
141
+ )
142
+
143
+ try:
144
+ import jplephem
145
+ except ImportError as e:
146
+ raise ImportError(
147
+ "jplephem is required for ephemeris access. " "Install with: pip install jplephem"
148
+ ) from e
149
+
150
+ self.version = version
151
+ self._jplephem = jplephem
152
+ self._kernel: Optional[object] = None
153
+ self._cache: dict = {}
154
+
155
+ @property
156
+ def kernel(self):
157
+ """Lazy-load ephemeris kernel on first access.
158
+
159
+ Note: This requires jplephem to be installed and the kernel file
160
+ to be available locally or downloadable from the JPL servers.
161
+ """
162
+ if self._kernel is None:
163
+ try:
164
+ # Try to load using jplephem SPK module
165
+ import os
166
+ import urllib.request
167
+
168
+ from jplephem.daf import DAF
169
+ from jplephem.spk import SPK
170
+
171
+ # Try to construct kernel filename
172
+ kernel_name = f"de{self.version[2:]}.bsp"
173
+ kernel_url = (
174
+ f"https://naif.jpl.nasa.gov/pub/naif/generic_kernels/spk/planets/{kernel_name}"
175
+ )
176
+
177
+ # Try to download if not exists
178
+ kernel_path = os.path.expanduser(f"~/.jplephem/{kernel_name}")
179
+ os_dir = os.path.dirname(kernel_path)
180
+ if not os.path.exists(os_dir):
181
+ os.makedirs(os_dir, exist_ok=True)
182
+
183
+ if not os.path.exists(kernel_path):
184
+ try:
185
+ urllib.request.urlretrieve(kernel_url, kernel_path)
186
+ except Exception as e:
187
+ raise RuntimeError(
188
+ f"Could not download ephemeris kernel from {kernel_url}. "
189
+ f"Please download manually and place at {kernel_path}"
190
+ ) from e
191
+
192
+ # Load the kernel using DAF and SPK
193
+ daf = DAF(open(kernel_path, "rb"))
194
+ self._kernel = SPK(daf)
195
+
196
+ except Exception as e:
197
+ raise RuntimeError(
198
+ f"Failed to load ephemeris kernel for version {self.version}. "
199
+ f"Ensure jplephem is installed and kernel files are accessible. "
200
+ f"Error: {str(e)}"
201
+ ) from e
202
+
203
+ return self._kernel
204
+
205
+ def sun_position(
206
+ self, jd: float, frame: Literal["icrf", "ecliptic"] = "icrf"
207
+ ) -> Tuple[np.ndarray, np.ndarray]:
208
+ """Compute Sun position and velocity.
209
+
210
+ Parameters
211
+ ----------
212
+ jd : float
213
+ Julian Day in Terrestrial Time (TT)
214
+ frame : {'icrf', 'ecliptic'}, optional
215
+ Coordinate frame (default: 'icrf').
216
+ - 'icrf': International Celestial Reference Frame
217
+ - 'ecliptic': Ecliptic coordinate system (J2000.0)
218
+
219
+ Returns
220
+ -------
221
+ position : ndarray, shape (3,)
222
+ Sun position in AU
223
+ velocity : ndarray, shape (3,)
224
+ Sun velocity in AU/day
225
+
226
+ Notes
227
+ -----
228
+ The Sun's position is computed relative to the Solar System Barycenter
229
+ (SSB) in the ICRF frame.
230
+
231
+ Examples
232
+ --------
233
+ >>> eph = DEEphemeris()
234
+ >>> r, v = eph.sun_position(2451545.0)
235
+ >>> print(f"Distance: {np.linalg.norm(r):.6f} AU")
236
+
237
+ """
238
+ # Sun position relative to SSB (in km)
239
+ segment = self.kernel[0, 10]
240
+ position, velocity = segment.compute_and_differentiate(jd)
241
+
242
+ # Convert from km to AU
243
+ position = np.array(position) * AU_PER_KM
244
+ velocity = np.array(velocity) * KM_PER_DAY_TO_AU_PER_DAY
245
+
246
+ if frame == "ecliptic":
247
+ from . import reference_frames
248
+
249
+ position = reference_frames.equatorial_to_ecliptic(position, EPSILON_J2000)
250
+ velocity = reference_frames.equatorial_to_ecliptic(velocity, EPSILON_J2000)
251
+
252
+ return position, velocity
253
+
254
+ def moon_position(
255
+ self, jd: float, frame: Literal["icrf", "ecliptic", "earth_centered"] = "icrf"
256
+ ) -> Tuple[np.ndarray, np.ndarray]:
257
+ """Compute Moon position and velocity.
258
+
259
+ Parameters
260
+ ----------
261
+ jd : float
262
+ Julian Day in Terrestrial Time (TT)
263
+ frame : {'icrf', 'ecliptic', 'earth_centered'}, optional
264
+ Coordinate frame (default: 'icrf').
265
+ - 'icrf': Moon position relative to Solar System Barycenter
266
+ - 'ecliptic': Ecliptic coordinates
267
+ - 'earth_centered': Position relative to Earth
268
+
269
+ Returns
270
+ -------
271
+ position : ndarray, shape (3,)
272
+ Moon position in AU (or relative to Earth for 'earth_centered')
273
+ velocity : ndarray, shape (3,)
274
+ Moon velocity in AU/day
275
+
276
+ Notes
277
+ -----
278
+ By default, returns Moon position relative to the Solar System Barycenter.
279
+ Use frame='earth_centered' for geocentric coordinates.
280
+
281
+ Examples
282
+ --------
283
+ >>> eph = DEEphemeris()
284
+ >>> r, v = eph.moon_position(2451545.0, frame='earth_centered')
285
+
286
+ """
287
+ if frame == "earth_centered":
288
+ # Moon relative to Earth
289
+ segment = self.kernel[3, 301]
290
+ position, velocity = segment.compute_and_differentiate(jd)
291
+ else:
292
+ # Moon relative to SSB: need to compute Earth->Moon, then add Earth->SSB
293
+ # Get Earth barycenter position
294
+ earth_segment = self.kernel[0, 3]
295
+ earth_pos, earth_vel = earth_segment.compute_and_differentiate(jd)
296
+
297
+ # Get Moon position relative to Earth
298
+ moon_segment = self.kernel[3, 301]
299
+ moon_rel_earth_pos, moon_rel_earth_vel = moon_segment.compute_and_differentiate(jd)
300
+
301
+ # Moon position relative to SSB
302
+ position = earth_pos + moon_rel_earth_pos
303
+ velocity = earth_vel + moon_rel_earth_vel
304
+
305
+ # Convert from km to AU
306
+ position = np.array(position) * AU_PER_KM
307
+ velocity = np.array(velocity) * KM_PER_DAY_TO_AU_PER_DAY
308
+
309
+ if frame == "ecliptic":
310
+ from . import reference_frames
311
+
312
+ position = reference_frames.equatorial_to_ecliptic(position, EPSILON_J2000)
313
+ velocity = reference_frames.equatorial_to_ecliptic(velocity, EPSILON_J2000)
314
+
315
+ return position, velocity
316
+
317
+ def planet_position(
318
+ self,
319
+ planet: Literal["mercury", "venus", "mars", "jupiter", "saturn", "uranus", "neptune"],
320
+ jd: float,
321
+ frame: Literal["icrf", "ecliptic"] = "icrf",
322
+ ) -> Tuple[np.ndarray, np.ndarray]:
323
+ """Compute planet position and velocity.
324
+
325
+ Parameters
326
+ ----------
327
+ planet : str
328
+ Planet name: 'mercury', 'venus', 'mars', 'jupiter', 'saturn',
329
+ 'uranus', 'neptune'
330
+ jd : float
331
+ Julian Day in Terrestrial Time (TT)
332
+ frame : {'icrf', 'ecliptic'}, optional
333
+ Coordinate frame (default: 'icrf')
334
+
335
+ Returns
336
+ -------
337
+ position : ndarray, shape (3,)
338
+ Planet position in AU
339
+ velocity : ndarray, shape (3,)
340
+ Planet velocity in AU/day
341
+
342
+ Raises
343
+ ------
344
+ ValueError
345
+ If planet name is not recognized
346
+
347
+ Examples
348
+ --------
349
+ >>> eph = DEEphemeris()
350
+ >>> r, v = eph.planet_position('mars', 2451545.0)
351
+
352
+ """
353
+ planet_lower = planet.lower()
354
+ if planet_lower not in self._BODY_IDS or planet_lower == "sun":
355
+ raise ValueError(
356
+ f"Planet must be one of {set(self._BODY_IDS.keys()) - {'sun', 'moon'}}, "
357
+ f"got '{planet}'"
358
+ )
359
+
360
+ planet_id = self._BODY_IDS[planet_lower]
361
+ segment = self.kernel[0, planet_id]
362
+ position, velocity = segment.compute_and_differentiate(jd)
363
+
364
+ # Convert from km to AU
365
+ position = np.array(position) * AU_PER_KM
366
+ velocity = np.array(velocity) * KM_PER_DAY_TO_AU_PER_DAY
367
+
368
+ if frame == "ecliptic":
369
+ from . import reference_frames
370
+
371
+ position = reference_frames.equatorial_to_ecliptic(position, EPSILON_J2000)
372
+ velocity = reference_frames.equatorial_to_ecliptic(velocity, EPSILON_J2000)
373
+
374
+ return position, velocity
375
+
376
+ def barycenter_position(self, body: str, jd: float) -> Tuple[np.ndarray, np.ndarray]:
377
+ """Compute position of any body relative to Solar System Barycenter.
378
+
379
+ Parameters
380
+ ----------
381
+ body : str
382
+ Body name ('sun', 'moon', 'mercury', ..., 'neptune')
383
+ jd : float
384
+ Julian Day in Terrestrial Time (TT)
385
+
386
+ Returns
387
+ -------
388
+ position : ndarray, shape (3,)
389
+ Position in AU
390
+ velocity : ndarray, shape (3,)
391
+ Velocity in AU/day
392
+
393
+ """
394
+ if body.lower() == "sun":
395
+ return self.sun_position(jd)
396
+ elif body.lower() == "moon":
397
+ return self.moon_position(jd, frame="icrf")
398
+ else:
399
+ return self.planet_position(body, jd)
400
+
401
+ def clear_cache(self) -> None:
402
+ """Clear internal position cache."""
403
+ self._cache.clear()
404
+
405
+
406
+ # Module-level convenience functions
407
+
408
+ _default_eph: Optional[DEEphemeris] = None
409
+
410
+
411
+ def _get_default_ephemeris() -> DEEphemeris:
412
+ """Get or create default ephemeris instance."""
413
+ global _default_eph
414
+ if _default_eph is None:
415
+ _default_eph = DEEphemeris(version="DE440")
416
+ return _default_eph
417
+
418
+
419
+ def sun_position(
420
+ jd: float, frame: Literal["icrf", "ecliptic"] = "icrf"
421
+ ) -> Tuple[np.ndarray, np.ndarray]:
422
+ """Convenience function: Compute Sun position and velocity.
423
+
424
+ Parameters
425
+ ----------
426
+ jd : float
427
+ Julian Day in Terrestrial Time (TT)
428
+ frame : {'icrf', 'ecliptic'}, optional
429
+ Coordinate frame (default: 'icrf')
430
+
431
+ Returns
432
+ -------
433
+ position : ndarray, shape (3,)
434
+ Sun position in AU
435
+ velocity : ndarray, shape (3,)
436
+ Sun velocity in AU/day
437
+
438
+ See Also
439
+ --------
440
+ DEEphemeris.sun_position : Full ephemeris class with caching
441
+
442
+ """
443
+ return _get_default_ephemeris().sun_position(jd, frame=frame)
444
+
445
+
446
+ def moon_position(
447
+ jd: float, frame: Literal["icrf", "ecliptic", "earth_centered"] = "icrf"
448
+ ) -> Tuple[np.ndarray, np.ndarray]:
449
+ """Convenience function: Compute Moon position and velocity.
450
+
451
+ Parameters
452
+ ----------
453
+ jd : float
454
+ Julian Day in Terrestrial Time (TT)
455
+ frame : {'icrf', 'ecliptic', 'earth_centered'}, optional
456
+ Coordinate frame (default: 'icrf')
457
+
458
+ Returns
459
+ -------
460
+ position : ndarray, shape (3,)
461
+ Moon position in AU
462
+ velocity : ndarray, shape (3,)
463
+ Moon velocity in AU/day
464
+
465
+ See Also
466
+ --------
467
+ DEEphemeris.moon_position : Full ephemeris class with caching
468
+
469
+ """
470
+ return _get_default_ephemeris().moon_position(jd, frame=frame)
471
+
472
+
473
+ def planet_position(
474
+ planet: Literal["mercury", "venus", "mars", "jupiter", "saturn", "uranus", "neptune"],
475
+ jd: float,
476
+ frame: Literal["icrf", "ecliptic"] = "icrf",
477
+ ) -> Tuple[np.ndarray, np.ndarray]:
478
+ """Convenience function: Compute planet position and velocity.
479
+
480
+ Parameters
481
+ ----------
482
+ planet : str
483
+ Planet name
484
+ jd : float
485
+ Julian Day in Terrestrial Time (TT)
486
+ frame : {'icrf', 'ecliptic'}, optional
487
+ Coordinate frame (default: 'icrf')
488
+
489
+ Returns
490
+ -------
491
+ position : ndarray, shape (3,)
492
+ Planet position in AU
493
+ velocity : ndarray, shape (3,)
494
+ Planet velocity in AU/day
495
+
496
+ See Also
497
+ --------
498
+ DEEphemeris.planet_position : Full ephemeris class with caching
499
+
500
+ """
501
+ return _get_default_ephemeris().planet_position(planet, jd, frame=frame)
502
+
503
+
504
+ def barycenter_position(body: str, jd: float) -> Tuple[np.ndarray, np.ndarray]:
505
+ """Convenience function: Position relative to Solar System Barycenter.
506
+
507
+ Parameters
508
+ ----------
509
+ body : str
510
+ Body name ('sun', 'moon', 'mercury', ..., 'neptune')
511
+ jd : float
512
+ Julian Day in Terrestrial Time (TT)
513
+
514
+ Returns
515
+ -------
516
+ position : ndarray, shape (3,)
517
+ Position in AU
518
+ velocity : ndarray, shape (3,)
519
+ Velocity in AU/day
520
+
521
+ """
522
+ return _get_default_ephemeris().barycenter_position(body, jd)
@@ -198,9 +198,7 @@ def lambert_universal(
198
198
  psi = (psi_low + psi_high) / 2
199
199
 
200
200
  else:
201
- raise ValueError(
202
- f"Lambert's problem did not converge after {max_iter} iterations"
203
- )
201
+ raise ValueError(f"Lambert's problem did not converge after {max_iter} iterations")
204
202
 
205
203
  # Compute f, g, f_dot, g_dot
206
204
  f = 1 - y / r1_mag
@@ -288,11 +286,7 @@ def lambert_izzo(
288
286
 
289
287
  # Cross product for angular momentum direction
290
288
  cross = np.cross(r1, r2)
291
- h_hat = (
292
- cross / np.linalg.norm(cross)
293
- if np.linalg.norm(cross) > 1e-10
294
- else np.array([0, 0, 1])
295
- )
289
+ h_hat = cross / np.linalg.norm(cross) if np.linalg.norm(cross) > 1e-10 else np.array([0, 0, 1])
296
290
 
297
291
  # Transfer angle
298
292
  cos_dnu = np.dot(r1_hat, r2_hat)
@@ -335,13 +329,9 @@ def lambert_izzo(
335
329
 
336
330
  # Time of flight equation
337
331
  if x < 1:
338
- psi = np.arccos(
339
- x * lambda_param + y * np.sqrt(1 - lambda_param * lambda_param)
340
- )
332
+ psi = np.arccos(x * lambda_param + y * np.sqrt(1 - lambda_param * lambda_param))
341
333
  else:
342
- psi = np.arccosh(
343
- x * lambda_param + y * np.sqrt(lambda_param * lambda_param - 1)
344
- )
334
+ psi = np.arccosh(x * lambda_param + y * np.sqrt(lambda_param * lambda_param - 1))
345
335
 
346
336
  T_x = (
347
337
  psi + multi_rev * np.pi - (x - lambda_param * y) * np.sqrt(abs(1 - x * x))
@@ -176,9 +176,7 @@ def mean_to_hyperbolic_anomaly(
176
176
  if abs(delta) < tol:
177
177
  return H
178
178
 
179
- raise ValueError(
180
- f"Hyperbolic Kepler's equation did not converge after {max_iter} iterations"
181
- )
179
+ raise ValueError(f"Hyperbolic Kepler's equation did not converge after {max_iter} iterations")
182
180
 
183
181
 
184
182
  def eccentric_to_true_anomaly(E: float, e: float) -> float:
@@ -292,9 +292,7 @@ def gmst_iau82(jd_ut1: float) -> float:
292
292
  T_u = (jd_0h - JD_J2000) / 36525.0
293
293
 
294
294
  # GMST at 0h UT1 (seconds)
295
- gmst_0h_sec = (
296
- 24110.54841 + 8640184.812866 * T_u + 0.093104 * T_u**2 - 6.2e-6 * T_u**3
297
- )
295
+ gmst_0h_sec = 24110.54841 + 8640184.812866 * T_u + 0.093104 * T_u**2 - 6.2e-6 * T_u**3
298
296
 
299
297
  # Add UT1 fraction
300
298
  ut1_fraction = (jd_ut1 - jd_0h) * 86400.0