nrl-tracker 1.1.3__py3-none-any.whl → 1.3.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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: nrl-tracker
3
- Version: 1.1.3
3
+ Version: 1.3.0
4
4
  Summary: Python port of the U.S. Naval Research Laboratory's Tracker Component Library for target tracking algorithms
5
5
  Author: Original: David F. Crouse, Naval Research Laboratory
6
6
  Maintainer: Python Port Contributors
@@ -1,4 +1,4 @@
1
- pytcl/__init__.py,sha256=5eTDXKjyoXrm1LXhb6oXzUC_DivN4E1NyQ5YVmrTQ5w,1893
1
+ pytcl/__init__.py,sha256=PLcxb75b02Pbp9mvJ3PZYQR_ZEhjJybFZhYJ1CQTjL8,1893
2
2
  pytcl/logging_config.py,sha256=j7Zrkal5LwUIos-_Dm3cGKUR-jMkFdSZZikJTtzTeoE,8883
3
3
  pytcl/assignment_algorithms/__init__.py,sha256=f9V-TkEVmiKYYyth4PTpDfJvA7yYV_ys6Zix-QwWIYY,2136
4
4
  pytcl/assignment_algorithms/data_association.py,sha256=tsRxWJZk9aAPmE99BKXGouEpFfZrjPjb4HXvgxFUHhU,11405
@@ -13,24 +13,26 @@ pytcl/astronomical/__init__.py,sha256=Dtf6hqXyKyFL5VP-sqI7m2QGK6l-rqRGxVIhgDuYHO
13
13
  pytcl/astronomical/ephemerides.py,sha256=x2500S0rF1D2h0dMR_2BnZaChbBZTooHLdrevttxlAc,16471
14
14
  pytcl/astronomical/lambert.py,sha256=Lc8FT1JmpI9WSXsG2s5vIRkSoBSV7r5hd3o2bGh2Ojo,15607
15
15
  pytcl/astronomical/orbital_mechanics.py,sha256=8GssRanwTowCl6PJYqmB_SDnNznLUq5gkPa3j6iEo3U,19965
16
- pytcl/astronomical/reference_frames.py,sha256=GDak7af6BqOwGnCUxkvFoeqd_H2TMubdjG9lGPCoUB4,15799
16
+ pytcl/astronomical/reference_frames.py,sha256=gkbQrhHY_EN0xt8i7m0dIJp67GgpEcDArY7J3YH-Mb8,17981
17
17
  pytcl/astronomical/relativity.py,sha256=YPsXLD-VRh-nqs1laC-wKpRO00fflm4GkyLhojPydbo,15441
18
18
  pytcl/astronomical/time_systems.py,sha256=Jg0Zaq60hc4Ts1aQtb5bK4KSZhz-uQse8gYC89Y0-TA,15243
19
- pytcl/atmosphere/__init__.py,sha256=TTVz4hAM48Xd3jr6GKrR2GAABpx2z0aWvtzb9uIQiHk,737
19
+ pytcl/atmosphere/__init__.py,sha256=swugW8rY2Jof-z_kPQ2P9vInBYjr1M69C1Q8AgN3RVo,1457
20
+ pytcl/atmosphere/ionosphere.py,sha256=1qC3hY-27pD0XcLBjU735deKYmmi6qnj2fDG1zNbTqg,14681
20
21
  pytcl/atmosphere/models.py,sha256=pMLv8D7qoFqLZrlbTHLJJULOdDdhPskJ1m7KVKLV63E,9584
21
22
  pytcl/clustering/__init__.py,sha256=bYdhC_XJEt6KUUni9bIPxaddXNEGmIJQvGkA14rK4J8,1697
22
23
  pytcl/clustering/dbscan.py,sha256=PS6QlOwHFerbZNEb3zcNhN4oNQpgOOw5y0WskQzyKIo,7364
23
24
  pytcl/clustering/gaussian_mixture.py,sha256=U5U0Z46tZWdTLNdNNNJenoeviwZRAOvexVFYVLt4QMc,22865
24
25
  pytcl/clustering/hierarchical.py,sha256=Hw9BFCn5df_ATpJX63R3B31MHz27ztCw9ihMDIlI688,14202
25
26
  pytcl/clustering/kmeans.py,sha256=250FQyDol5S_Y4TznNn9cEuE96UDp7wvEkPZJ1DLul8,10697
26
- pytcl/containers/__init__.py,sha256=-hnqSMKlMugj2RRssx3p_48HWnfqLSrF6BCChsinCOg,1627
27
+ pytcl/containers/__init__.py,sha256=jZAZb0VUft5gjQghfg2S9PD-LsA5xgtXkc0mAS_Gnmk,2428
28
+ pytcl/containers/base.py,sha256=h3h5mJfSn8yTy1waFZRUxJQ9eHQ5npbimadFbFZuuV4,5520
27
29
  pytcl/containers/cluster_set.py,sha256=y36D5TNzvCN6xjg6taP2SD_MC-O5iLq9ncBlHsQ5IBs,22723
28
- pytcl/containers/covertree.py,sha256=1JWqXxoUFLxuMnjwj2qf0iz2uPzdujQYdwJW3l5qsOs,13282
29
- pytcl/containers/kd_tree.py,sha256=pxRC62RYkqz9zXPz6c1fubmtPPBDLYA5I9AXMAoGanw,16348
30
+ pytcl/containers/covertree.py,sha256=ePIqH1-0CxSFqCwmQ_G6MXPlXs4xH0gsmoZXF8QxhDk,13271
31
+ pytcl/containers/kd_tree.py,sha256=9CKHAzid0DZ879hut8M4dyW_976pIWNLX3uWzELPIu4,18563
30
32
  pytcl/containers/measurement_set.py,sha256=87AbdoZIUspn1yJsiMpyQ5LoEVcerUnXefXGGPtFTJg,12654
31
- pytcl/containers/rtree.py,sha256=gv2EztvPnaAXEa6OoFnOYBY1MfTwjNMYh_BCiIomHJk,15450
33
+ pytcl/containers/rtree.py,sha256=Ss1ks6xlLnNeRlKpHoWxMcgQTPhVwjT5agMeq5DaH5A,21844
32
34
  pytcl/containers/track_list.py,sha256=6q9Qgcwm-8H_JqtOCsMssF27av4XaSkhfDl-MWb1ABc,12520
33
- pytcl/containers/vptree.py,sha256=6fBNHrezkmj7L2nH0-2bONRN92f5cZAhS-5vaI1JZnA,8814
35
+ pytcl/containers/vptree.py,sha256=4tUq0ktafusU1PILZkQxi27CZryKlsHtFbym-vZYQWk,8747
34
36
  pytcl/coordinate_systems/__init__.py,sha256=jwYhu_-9AvOeP9WLG9PYtyDwfe0GjxNZ9-xCqiLymW4,3909
35
37
  pytcl/coordinate_systems/conversions/__init__.py,sha256=PkNevB78vBw0BkalydJBbQO91AyiMJxKRrgJNt4HsYc,1100
36
38
  pytcl/coordinate_systems/conversions/geodetic.py,sha256=qQSnJRt3jg5KiostvzyslPIbfn-1xBluo1r12oavWTQ,15737
@@ -41,10 +43,10 @@ pytcl/coordinate_systems/projections/__init__.py,sha256=eWNtezPO62IUWxv7jymenIXs
41
43
  pytcl/coordinate_systems/projections/projections.py,sha256=yODS7n1gA4jsCJcU8EaeclHrbUBsZI9O2M_XJs2HOXs,33169
42
44
  pytcl/coordinate_systems/rotations/__init__.py,sha256=nqAz4iJd2hEOX_r7Tz4cE524sShyxdbtcQ5m56RrDLg,1047
43
45
  pytcl/coordinate_systems/rotations/rotations.py,sha256=FAYHkShQcpOlWJjtvLfNvtCx-a56pr-cbpo0QjC5W9U,18227
44
- pytcl/core/__init__.py,sha256=H5JJPS-43DfF1UG7fSgV-VMTcZFBO8GuzDW1lM_1sm4,1152
46
+ pytcl/core/__init__.py,sha256=3GFQX_Q9f7fhmWlA6OQiS6OpM7HWhyT9iQhB8Mhi_kk,1580
45
47
  pytcl/core/array_utils.py,sha256=SsgEiAoRCWxAVKq1aa5-nPdOi-2AB6XNObu0IaGClUk,13983
46
48
  pytcl/core/constants.py,sha256=lZVDK5zsSR02_4b2Nqx9KDtZT9QaYhkZ9wuoODbifd4,8693
47
- pytcl/core/validation.py,sha256=WRlzMlUihtqc3XZoWOTFK0sBAZVDIwTMGCiWcX5OZVY,13093
49
+ pytcl/core/validation.py,sha256=nUmG8UmUk85dQ4CxJRipACb8zjsrAJPiyK8ADZN_KvU,23426
48
50
  pytcl/dynamic_estimation/__init__.py,sha256=jA5FF6kHYklY5LMOfZaKcCeiPTpVe8vHIMp3ECDOmsc,4582
49
51
  pytcl/dynamic_estimation/imm.py,sha256=IbKmouUiyzaYJbhWty63r3n_xV8thD-wd0qgZP1SxOI,22067
50
52
  pytcl/dynamic_estimation/information_filter.py,sha256=x7iQwO_iJT1dCSvDws5LqD3yAtjw9QVGUfMPcXn1IA4,17349
@@ -53,7 +55,9 @@ pytcl/dynamic_estimation/batch_estimation/__init__.py,sha256=JQ0s76Enov5a7plA4En
53
55
  pytcl/dynamic_estimation/kalman/__init__.py,sha256=yoFLj0n-NRkdZnRVL-BkHBlATk8pfZEVlsY3BhSYgKc,2387
54
56
  pytcl/dynamic_estimation/kalman/extended.py,sha256=51uhCqkZmErCx6MBfMq8eIQW8bD7n34zCe4v4dxNiMQ,10384
55
57
  pytcl/dynamic_estimation/kalman/linear.py,sha256=1Zgg9gZya0Vxs9im7sPUqLj0Luo463vS-RSa6GCReFI,12248
56
- pytcl/dynamic_estimation/kalman/square_root.py,sha256=Hw1F4_Zc7IA6Mt1WCkjx1UuLAUmNhM5vPLvueb7oRSA,26931
58
+ pytcl/dynamic_estimation/kalman/square_root.py,sha256=N7-lDml7Nw5HM5b5D11WOwG7rY1JlVoyis0ho-vk0H4,13345
59
+ pytcl/dynamic_estimation/kalman/sr_ukf.py,sha256=LeRGBSDpvSP9CyTZjEroz2Z2uueb6YpmzYricba0PDk,8640
60
+ pytcl/dynamic_estimation/kalman/ud_filter.py,sha256=fzSdcVO_P8-E2oXc32n79Rn56GI2VUmOoMDYBHw7keM,10077
57
61
  pytcl/dynamic_estimation/kalman/unscented.py,sha256=RDK6USkko9lj1K4-WYydh3_8GMZNng_PJVjfc-c_OwM,15427
58
62
  pytcl/dynamic_estimation/measurement_update/__init__.py,sha256=8rlyJwVpxf0fZj-AFo1hlewvryZRhUzcy3F8uMe6I8c,48
59
63
  pytcl/dynamic_estimation/particle_filters/__init__.py,sha256=-DRF5rVF2749suLlArmkTvVkqeMcV_mIx0eLeTj6wNU,906
@@ -71,14 +75,14 @@ pytcl/dynamic_models/process_noise/polynomial.py,sha256=natfpsdN3qM9VzPeXF_nBpsb
71
75
  pytcl/dynamic_models/process_noise/singer.py,sha256=lsJDT6xOvcS_qQKFtgHX0L7Ukpy4D7HgvPT8Q3I0ibU,3901
72
76
  pytcl/gravity/__init__.py,sha256=5xNdQSrrkt7-1-JPOYqR38CqvNJ7qKlPyMK36DGm6-I,3693
73
77
  pytcl/gravity/clenshaw.py,sha256=1BdxzU8IfGGd68H_U35soIJkiOHphY35e9mLElhPTOg,15364
74
- pytcl/gravity/egm.py,sha256=QTRuvCiMjuNQdZF163OGwjxuivpGu2dB6E0zQLbKPP8,18083
78
+ pytcl/gravity/egm.py,sha256=47I8nyXNhXUKPkufXahs4JGsBcqhM-9z2xGz0X4JPmU,18422
75
79
  pytcl/gravity/models.py,sha256=rdY3Do4M1eRFO74gu3xy-bBn7tox3zM49wYbfnsIQWw,11159
76
- pytcl/gravity/spherical_harmonics.py,sha256=uZasz-w2K16sWT6xrNIPyTEP6MSlMQSe_BCWpXhRkWY,14722
80
+ pytcl/gravity/spherical_harmonics.py,sha256=IpBh0LW4BQMzJck9Li6yveGlvYigCuXaoApRWDPsWtc,16498
77
81
  pytcl/gravity/tides.py,sha256=hef_BGewFGD7dJwg0t09Z6tfWLco_avATLuu66rnTpI,27733
78
- pytcl/magnetism/__init__.py,sha256=hE2BvberFSmimYuuwCYJ0g7ByxJAdj844vZJNkEotws,2502
82
+ pytcl/magnetism/__init__.py,sha256=pBASOzCPHNnYqUH_XDEblhGtjz50vY9uW2KS25A0zQQ,2701
79
83
  pytcl/magnetism/emm.py,sha256=5Jwl99wvdKYtx1-3LBB7x-w5KT-fqLiRg7uBW0Ai_Gw,22292
80
84
  pytcl/magnetism/igrf.py,sha256=3g0PsH8IdbwQQS28OR5XWD-g-QxvfUva7jOkKToxndQ,13384
81
- pytcl/magnetism/wmm.py,sha256=p0H7Eo02iB6nEMvGyvjsrAWOSKrIye6PGwQtNKfHaNw,15999
85
+ pytcl/magnetism/wmm.py,sha256=q7AJrpOrn1EBbWNjltPxhGEwg3P44ay1pc4dI5OIyUY,23444
82
86
  pytcl/mathematical_functions/__init__.py,sha256=zeJ1ffRRl83k2NHn3HTn-fgtFoWNPq6LCALc3xRo4Do,3767
83
87
  pytcl/mathematical_functions/basic_matrix/__init__.py,sha256=kZv3kMAEHBdVxhbyMxTyM0s-4XJP1tK6po82UsIE4tc,1318
84
88
  pytcl/mathematical_functions/basic_matrix/decompositions.py,sha256=PWJsFDiXM2T78RHdxBJZPFnl8kFbNZQpHrbpw0mhE00,12268
@@ -99,11 +103,11 @@ pytcl/mathematical_functions/signal_processing/filters.py,sha256=8Ojf4h4rfiucBXq
99
103
  pytcl/mathematical_functions/signal_processing/matched_filter.py,sha256=AahJZRZk2IIXzRL7www0n8bc0XoKabaLOe8yYNSjuDY,22893
100
104
  pytcl/mathematical_functions/special_functions/__init__.py,sha256=AJBCKj32daQxdahUQckW0bWowzOoapxni2eZnVXERdg,3859
101
105
  pytcl/mathematical_functions/special_functions/bessel.py,sha256=M0mwLQBaUXEHA8wyKReJ2D66I1v1XR7y-txAipd-WDs,14377
102
- pytcl/mathematical_functions/special_functions/debye.py,sha256=Nchjwkl1vzSL1L7nQpslb-lvT49LgTfdTIQMeSNn4vQ,6689
106
+ pytcl/mathematical_functions/special_functions/debye.py,sha256=5u-2KIQniwoVlqGSQguYhO7RcFQXtvY0aetiDiMYtQ0,9576
103
107
  pytcl/mathematical_functions/special_functions/elliptic.py,sha256=WyzBkrfZufIR5dUmCKGcxp6KNpVDrU89NGLDyRrZOqQ,7418
104
108
  pytcl/mathematical_functions/special_functions/error_functions.py,sha256=a3SS8FYAMRv1KdCmebOZL95yjvVt9gZRF2XOjHvQ9M8,6253
105
109
  pytcl/mathematical_functions/special_functions/gamma_functions.py,sha256=xXN_9SCokH10HjE8PpaPKHYVK_RZRHRAbZgR2mZYIAA,10191
106
- pytcl/mathematical_functions/special_functions/hypergeometric.py,sha256=gKn_tXboEst7pVDiW15IbKFAANM4XVqKtDc1dmWL-2A,9768
110
+ pytcl/mathematical_functions/special_functions/hypergeometric.py,sha256=5C4dXv3XxjyeGGVH-0i22NSLbAqkcPTR3kZ2J_GTles,11364
107
111
  pytcl/mathematical_functions/special_functions/lambert_w.py,sha256=ivRc4KH5Lwoxb_yijrJEwG0ITa0hhcYF7_gCfVBBNW4,6855
108
112
  pytcl/mathematical_functions/special_functions/marcum_q.py,sha256=OZ5QjIB1e_XvRG8A-3dbZ13YXHtdk2EYVEPaqtgVr14,9580
109
113
  pytcl/mathematical_functions/statistics/__init__.py,sha256=dfypStgmnFmOrnWcm-3CEvLinONHraFgx9O66_37bqw,1278
@@ -115,8 +119,8 @@ pytcl/mathematical_functions/transforms/stft.py,sha256=zQapXl-v69_RDPwMqci83jah1
115
119
  pytcl/mathematical_functions/transforms/wavelets.py,sha256=dm273Z_t13BlEVSlHTaGE7jR1ocugL7lEkcO499U7bY,21656
116
120
  pytcl/misc/__init__.py,sha256=SCHf_lQVfdl2gwUluHBiIloTF8HRH8EkgYfbNr7zOug,33
117
121
  pytcl/navigation/__init__.py,sha256=k1_x_FnnPrIzGeNu7zejPtPubIhweBgCfwqlZJEMw0I,6042
118
- pytcl/navigation/geodesy.py,sha256=M9XXfBTMCRdaWMV2-ViDSTEt94WZnMtxMeJQ1FAgQHY,17227
119
- pytcl/navigation/great_circle.py,sha256=TtlkWZbzr-HzSt4ultG_h137ZnX0pJZx_87kr3uvpjI,20923
122
+ pytcl/navigation/geodesy.py,sha256=KXRQqOrJGbxWQ4yrZI9jfMDvaoUx_4RL8mTWUHhBJX0,19694
123
+ pytcl/navigation/great_circle.py,sha256=y5YHBie21j1a1ac7OziEgg_jgUT1FpltgkTA5AGFGsk,23251
120
124
  pytcl/navigation/ins.py,sha256=OIi8_RjrgEYl0MFpJEFMjIlpgX8DYGTEhdLEvqG-ABU,31151
121
125
  pytcl/navigation/ins_gnss.py,sha256=euKF5JGgwmVBsw3jBf7_wa2z1BpZeVbSNmBuwzhGS6c,30157
122
126
  pytcl/navigation/rhumb.py,sha256=lr1c3iEXfoOSfIyyXSRWv6He5TlaxEHbJy-dhqM1gRw,18224
@@ -144,8 +148,8 @@ pytcl/trackers/mht.py,sha256=7mwhMmja3ri2wnx7W1wueDGn2r3ArwAxJDPUJ7IZAkQ,20617
144
148
  pytcl/trackers/multi_target.py,sha256=hvt89ERhMwpcHcIJeKHnkQSKdE3_LoRiX-gbaGoo300,10516
145
149
  pytcl/trackers/single_target.py,sha256=Yy3FwaNTArMWcaod-0HVeiioNV4xLWxNDn_7ZPVqQYs,6562
146
150
  pytcl/transponders/__init__.py,sha256=5fL4u3lKCYgPLo5uFeuZbtRZkJPABntuKYGUvVgMMEI,41
147
- nrl_tracker-1.1.3.dist-info/LICENSE,sha256=rB5G4WppIIUzMOYr2N6uyYlNJ00hRJqE5tie6BMvYuE,1612
148
- nrl_tracker-1.1.3.dist-info/METADATA,sha256=3SQ22wsdVY72uEXiA2yC-95Cd0nmLNeKDvm7hwdwClA,10145
149
- nrl_tracker-1.1.3.dist-info/WHEEL,sha256=pL8R0wFFS65tNSRnaOVrsw9EOkOqxLrlUPenUYnJKNo,91
150
- nrl_tracker-1.1.3.dist-info/top_level.txt,sha256=17megxcrTPBWwPZTh6jTkwTKxX7No-ZqRpyvElnnO-s,6
151
- nrl_tracker-1.1.3.dist-info/RECORD,,
151
+ nrl_tracker-1.3.0.dist-info/LICENSE,sha256=rB5G4WppIIUzMOYr2N6uyYlNJ00hRJqE5tie6BMvYuE,1612
152
+ nrl_tracker-1.3.0.dist-info/METADATA,sha256=9DRW_Wk7EPorvcFjSKjcpNIhklbfyAXEffZHD8BeS0E,10145
153
+ nrl_tracker-1.3.0.dist-info/WHEEL,sha256=pL8R0wFFS65tNSRnaOVrsw9EOkOqxLrlUPenUYnJKNo,91
154
+ nrl_tracker-1.3.0.dist-info/top_level.txt,sha256=17megxcrTPBWwPZTh6jTkwTKxX7No-ZqRpyvElnnO-s,6
155
+ nrl_tracker-1.3.0.dist-info/RECORD,,
pytcl/__init__.py CHANGED
@@ -20,7 +20,7 @@ References
20
20
  no. 5, pp. 18-27, May 2017.
21
21
  """
22
22
 
23
- __version__ = "1.1.3"
23
+ __version__ = "1.3.0"
24
24
  __author__ = "Python Port Contributors"
25
25
  __original_author__ = "David F. Crouse, Naval Research Laboratory"
26
26
 
@@ -21,6 +21,8 @@ References
21
21
  A&A, 2003.
22
22
  """
23
23
 
24
+ import logging
25
+ from functools import lru_cache
24
26
  from typing import Tuple
25
27
 
26
28
  import numpy as np
@@ -28,6 +30,22 @@ from numpy.typing import NDArray
28
30
 
29
31
  from pytcl.astronomical.time_systems import JD_J2000
30
32
 
33
+ # Module logger
34
+ _logger = logging.getLogger("pytcl.astronomical.reference_frames")
35
+
36
+ # Cache configuration
37
+ _CACHE_JD_DECIMALS = 6 # ~86ms precision for JD quantization
38
+ _CACHE_MAXSIZE = 128 # Max cached epochs
39
+
40
+
41
+ def _quantize_jd(jd: float) -> float:
42
+ """Quantize Julian date for cache key compatibility.
43
+
44
+ Rounds to _CACHE_JD_DECIMALS decimal places (~86ms precision).
45
+ This enables cache hits for nearly identical epochs.
46
+ """
47
+ return round(jd, _CACHE_JD_DECIMALS)
48
+
31
49
 
32
50
  def julian_centuries_j2000(jd: float) -> float:
33
51
  """
@@ -78,6 +96,37 @@ def precession_angles_iau76(T: float) -> Tuple[float, float, float]:
78
96
  )
79
97
 
80
98
 
99
+ @lru_cache(maxsize=_CACHE_MAXSIZE)
100
+ def _precession_matrix_cached(jd_quantized: float) -> tuple:
101
+ """Cached precession matrix computation (internal).
102
+
103
+ Returns tuple of tuples for hashability.
104
+ """
105
+ T = julian_centuries_j2000(jd_quantized)
106
+ zeta, theta, z = precession_angles_iau76(T)
107
+
108
+ cos_zeta = np.cos(zeta)
109
+ sin_zeta = np.sin(zeta)
110
+ cos_theta = np.cos(theta)
111
+ sin_theta = np.sin(theta)
112
+ cos_z = np.cos(z)
113
+ sin_z = np.sin(z)
114
+
115
+ return (
116
+ (
117
+ cos_zeta * cos_theta * cos_z - sin_zeta * sin_z,
118
+ -sin_zeta * cos_theta * cos_z - cos_zeta * sin_z,
119
+ -sin_theta * cos_z,
120
+ ),
121
+ (
122
+ cos_zeta * cos_theta * sin_z + sin_zeta * cos_z,
123
+ -sin_zeta * cos_theta * sin_z + cos_zeta * cos_z,
124
+ -sin_theta * sin_z,
125
+ ),
126
+ (cos_zeta * sin_theta, -sin_zeta * sin_theta, cos_theta),
127
+ )
128
+
129
+
81
130
  def precession_matrix_iau76(jd: float) -> NDArray[np.floating]:
82
131
  """
83
132
  Compute IAU 1976 precession matrix from J2000 to date.
@@ -92,34 +141,15 @@ def precession_matrix_iau76(jd: float) -> NDArray[np.floating]:
92
141
  P : ndarray
93
142
  Precession rotation matrix (3x3).
94
143
  Transforms from J2000 (GCRF) to mean of date.
95
- """
96
- T = julian_centuries_j2000(jd)
97
- zeta, theta, z = precession_angles_iau76(T)
98
144
 
99
- cos_zeta = np.cos(zeta)
100
- sin_zeta = np.sin(zeta)
101
- cos_theta = np.cos(theta)
102
- sin_theta = np.sin(theta)
103
- cos_z = np.cos(z)
104
- sin_z = np.sin(z)
105
-
106
- P = np.array(
107
- [
108
- [
109
- cos_zeta * cos_theta * cos_z - sin_zeta * sin_z,
110
- -sin_zeta * cos_theta * cos_z - cos_zeta * sin_z,
111
- -sin_theta * cos_z,
112
- ],
113
- [
114
- cos_zeta * cos_theta * sin_z + sin_zeta * cos_z,
115
- -sin_zeta * cos_theta * sin_z + cos_zeta * cos_z,
116
- -sin_theta * sin_z,
117
- ],
118
- [cos_zeta * sin_theta, -sin_zeta * sin_theta, cos_theta],
119
- ]
120
- )
121
-
122
- return P
145
+ Notes
146
+ -----
147
+ Results are cached for repeated queries at the same epoch.
148
+ Cache key is quantized to ~86ms precision.
149
+ """
150
+ jd_q = _quantize_jd(jd)
151
+ cached = _precession_matrix_cached(jd_q)
152
+ return np.array(cached)
123
153
 
124
154
 
125
155
  def nutation_angles_iau80(jd: float) -> Tuple[float, float]:
@@ -203,6 +233,38 @@ def mean_obliquity_iau80(jd: float) -> float:
203
233
  return eps0_arcsec * np.pi / (180 * 3600)
204
234
 
205
235
 
236
+ @lru_cache(maxsize=_CACHE_MAXSIZE)
237
+ def _nutation_matrix_cached(jd_quantized: float) -> tuple:
238
+ """Cached nutation matrix computation (internal).
239
+
240
+ Returns tuple of tuples for hashability.
241
+ """
242
+ dpsi, deps = nutation_angles_iau80(jd_quantized)
243
+ eps0 = mean_obliquity_iau80(jd_quantized)
244
+ eps = eps0 + deps
245
+
246
+ cos_eps0 = np.cos(eps0)
247
+ sin_eps0 = np.sin(eps0)
248
+ cos_eps = np.cos(eps)
249
+ sin_eps = np.sin(eps)
250
+ cos_dpsi = np.cos(dpsi)
251
+ sin_dpsi = np.sin(dpsi)
252
+
253
+ return (
254
+ (cos_dpsi, -sin_dpsi * cos_eps0, -sin_dpsi * sin_eps0),
255
+ (
256
+ sin_dpsi * cos_eps,
257
+ cos_dpsi * cos_eps0 * cos_eps + sin_eps0 * sin_eps,
258
+ cos_dpsi * sin_eps0 * cos_eps - cos_eps0 * sin_eps,
259
+ ),
260
+ (
261
+ sin_dpsi * sin_eps,
262
+ cos_dpsi * cos_eps0 * sin_eps - sin_eps0 * cos_eps,
263
+ cos_dpsi * sin_eps0 * sin_eps + cos_eps0 * cos_eps,
264
+ ),
265
+ )
266
+
267
+
206
268
  def nutation_matrix(jd: float) -> NDArray[np.floating]:
207
269
  """
208
270
  Compute nutation matrix.
@@ -217,35 +279,15 @@ def nutation_matrix(jd: float) -> NDArray[np.floating]:
217
279
  N : ndarray
218
280
  Nutation rotation matrix (3x3).
219
281
  Transforms from mean of date to true of date.
220
- """
221
- dpsi, deps = nutation_angles_iau80(jd)
222
- eps0 = mean_obliquity_iau80(jd)
223
- eps = eps0 + deps
224
282
 
225
- cos_eps0 = np.cos(eps0)
226
- sin_eps0 = np.sin(eps0)
227
- cos_eps = np.cos(eps)
228
- sin_eps = np.sin(eps)
229
- cos_dpsi = np.cos(dpsi)
230
- sin_dpsi = np.sin(dpsi)
231
-
232
- N = np.array(
233
- [
234
- [cos_dpsi, -sin_dpsi * cos_eps0, -sin_dpsi * sin_eps0],
235
- [
236
- sin_dpsi * cos_eps,
237
- cos_dpsi * cos_eps0 * cos_eps + sin_eps0 * sin_eps,
238
- cos_dpsi * sin_eps0 * cos_eps - cos_eps0 * sin_eps,
239
- ],
240
- [
241
- sin_dpsi * sin_eps,
242
- cos_dpsi * cos_eps0 * sin_eps - sin_eps0 * cos_eps,
243
- cos_dpsi * sin_eps0 * sin_eps + cos_eps0 * cos_eps,
244
- ],
245
- ]
246
- )
247
-
248
- return N
283
+ Notes
284
+ -----
285
+ Results are cached for repeated queries at the same epoch.
286
+ Cache key is quantized to ~86ms precision.
287
+ """
288
+ jd_q = _quantize_jd(jd)
289
+ cached = _nutation_matrix_cached(jd_q)
290
+ return np.array(cached)
249
291
 
250
292
 
251
293
  def earth_rotation_angle(jd_ut1: float) -> float:
@@ -647,6 +689,33 @@ def equatorial_to_ecliptic(
647
689
  return R @ r_eq
648
690
 
649
691
 
692
+ def clear_transformation_cache() -> None:
693
+ """Clear cached transformation matrices.
694
+
695
+ Call this function to clear all cached precession and nutation
696
+ matrices. Useful when memory is constrained or after processing
697
+ a batch of observations at different epochs.
698
+ """
699
+ _precession_matrix_cached.cache_clear()
700
+ _nutation_matrix_cached.cache_clear()
701
+ _logger.debug("Transformation matrix cache cleared")
702
+
703
+
704
+ def get_cache_info() -> dict:
705
+ """Get cache statistics for transformation matrices.
706
+
707
+ Returns
708
+ -------
709
+ dict
710
+ Dictionary with 'precession' and 'nutation' keys, each containing
711
+ CacheInfo namedtuple with hits, misses, maxsize, currsize.
712
+ """
713
+ return {
714
+ "precession": _precession_matrix_cached.cache_info(),
715
+ "nutation": _nutation_matrix_cached.cache_info(),
716
+ }
717
+
718
+
650
719
  __all__ = [
651
720
  # Time utilities
652
721
  "julian_centuries_j2000",
@@ -674,4 +743,7 @@ __all__ = [
674
743
  # Ecliptic/equatorial
675
744
  "ecliptic_to_equatorial",
676
745
  "equatorial_to_ecliptic",
746
+ # Cache management
747
+ "clear_transformation_cache",
748
+ "get_cache_info",
677
749
  ]
@@ -3,8 +3,26 @@ Atmospheric models for tracking applications.
3
3
 
4
4
  This module provides standard atmosphere models used for computing
5
5
  temperature, pressure, density, and other properties at various altitudes.
6
+
7
+ Submodules
8
+ ----------
9
+ models : Standard atmosphere models (US76, ISA)
10
+ ionosphere : Ionospheric models for GPS/GNSS corrections
6
11
  """
7
12
 
13
+ from pytcl.atmosphere.ionosphere import (
14
+ DEFAULT_KLOBUCHAR,
15
+ F_L1,
16
+ F_L2,
17
+ IonosphereState,
18
+ KlobucharCoefficients,
19
+ dual_frequency_tec,
20
+ ionospheric_delay_from_tec,
21
+ klobuchar_delay,
22
+ magnetic_latitude,
23
+ scintillation_index,
24
+ simple_iri,
25
+ )
8
26
  from pytcl.atmosphere.models import G0 # Constants
9
27
  from pytcl.atmosphere.models import (
10
28
  GAMMA,
@@ -21,17 +39,30 @@ from pytcl.atmosphere.models import (
21
39
  )
22
40
 
23
41
  __all__ = [
42
+ # Atmosphere state and models
24
43
  "AtmosphereState",
25
44
  "us_standard_atmosphere_1976",
26
45
  "isa_atmosphere",
27
46
  "altitude_from_pressure",
28
47
  "mach_number",
29
48
  "true_airspeed_from_mach",
30
- # Constants
49
+ # Atmosphere constants
31
50
  "T0",
32
51
  "P0",
33
52
  "RHO0",
34
53
  "G0",
35
54
  "R",
36
55
  "GAMMA",
56
+ # Ionosphere
57
+ "IonosphereState",
58
+ "KlobucharCoefficients",
59
+ "DEFAULT_KLOBUCHAR",
60
+ "klobuchar_delay",
61
+ "dual_frequency_tec",
62
+ "ionospheric_delay_from_tec",
63
+ "simple_iri",
64
+ "magnetic_latitude",
65
+ "scintillation_index",
66
+ "F_L1",
67
+ "F_L2",
37
68
  ]