nrl-tracker 1.11.1__py3-none-any.whl → 1.12.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.
Files changed (30) hide show
  1. {nrl_tracker-1.11.1.dist-info → nrl_tracker-1.12.0.dist-info}/METADATA +4 -4
  2. {nrl_tracker-1.11.1.dist-info → nrl_tracker-1.12.0.dist-info}/RECORD +30 -30
  3. pytcl/__init__.py +2 -2
  4. pytcl/assignment_algorithms/network_flow.py +172 -60
  5. pytcl/astronomical/time_systems.py +21 -0
  6. pytcl/containers/cluster_set.py +36 -0
  7. pytcl/coordinate_systems/conversions/geodetic.py +58 -0
  8. pytcl/core/array_utils.py +52 -0
  9. pytcl/gpu/ekf.py +46 -0
  10. pytcl/gpu/kalman.py +16 -0
  11. pytcl/gpu/matrix_utils.py +44 -1
  12. pytcl/gpu/particle_filter.py +33 -0
  13. pytcl/gpu/ukf.py +31 -0
  14. pytcl/gpu/utils.py +15 -0
  15. pytcl/magnetism/igrf.py +72 -0
  16. pytcl/magnetism/wmm.py +52 -0
  17. pytcl/mathematical_functions/basic_matrix/decompositions.py +7 -0
  18. pytcl/mathematical_functions/basic_matrix/special_matrices.py +31 -0
  19. pytcl/mathematical_functions/geometry/geometry.py +33 -0
  20. pytcl/mathematical_functions/interpolation/interpolation.py +83 -0
  21. pytcl/mathematical_functions/signal_processing/detection.py +31 -0
  22. pytcl/mathematical_functions/signal_processing/filters.py +56 -0
  23. pytcl/mathematical_functions/signal_processing/matched_filter.py +32 -1
  24. pytcl/mathematical_functions/special_functions/hypergeometric.py +17 -0
  25. pytcl/mathematical_functions/statistics/estimators.py +71 -0
  26. pytcl/mathematical_functions/transforms/wavelets.py +25 -0
  27. pytcl/navigation/great_circle.py +33 -0
  28. {nrl_tracker-1.11.1.dist-info → nrl_tracker-1.12.0.dist-info}/LICENSE +0 -0
  29. {nrl_tracker-1.11.1.dist-info → nrl_tracker-1.12.0.dist-info}/WHEEL +0 -0
  30. {nrl_tracker-1.11.1.dist-info → nrl_tracker-1.12.0.dist-info}/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: nrl-tracker
3
- Version: 1.11.1
3
+ Version: 1.12.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
@@ -71,17 +71,17 @@ Requires-Dist: plotly>=5.15.0; extra == "visualization"
71
71
 
72
72
  # Tracker Component Library (Python)
73
73
 
74
- [![PyPI version](https://img.shields.io/badge/pypi-v1.11.0-blue.svg)](https://pypi.org/project/nrl-tracker/)
74
+ [![PyPI version](https://img.shields.io/badge/pypi-v1.11.1-blue.svg)](https://pypi.org/project/nrl-tracker/)
75
75
  [![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/)
76
76
  [![License: Public Domain](https://img.shields.io/badge/License-Public%20Domain-brightgreen.svg)](https://en.wikipedia.org/wiki/Public_domain)
77
77
  [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
78
- [![Tests](https://img.shields.io/badge/tests-2894%20passing-success.svg)](https://github.com/nedonatelli/TCL)
78
+ [![Tests](https://img.shields.io/badge/tests-3280%20passing-success.svg)](https://github.com/nedonatelli/TCL)
79
79
  [![MATLAB Parity](https://img.shields.io/badge/MATLAB%20Parity-100%25-brightgreen.svg)](docs/gap_analysis.rst)
80
80
  [![Type Checking](https://img.shields.io/badge/mypy--strict-passing-brightgreen.svg)](mypy.ini)
81
81
 
82
82
  A Python port of the [U.S. Naval Research Laboratory's Tracker Component Library](https://github.com/USNavalResearchLaboratory/TrackerComponentLibrary), a comprehensive collection of algorithms for target tracking, estimation, coordinate systems, and related mathematical functions.
83
83
 
84
- **1,070+ functions** | **153 modules** | **2,894 tests** | **100% MATLAB parity**
84
+ **1,070+ functions** | **153 modules** | **3,280 tests** | **100% MATLAB parity**
85
85
 
86
86
  ## Overview
87
87
 
@@ -1,4 +1,4 @@
1
- pytcl/__init__.py,sha256=HmAtN7X6Ng4OxkKX7kayquTrnQkn_ILi6kP_X0Vtu1M,2032
1
+ pytcl/__init__.py,sha256=d_xdJJ89h0N88qtQ41ZNqKLNj4tEoOjYoA7HQu55syk,2032
2
2
  pytcl/logging_config.py,sha256=UJaYufQgNuIjpsOMTPo3ewz1XCHPk8a08jTHyP7uoI4,8956
3
3
  pytcl/assignment_algorithms/__init__.py,sha256=kUWhmyLhZcs5GiUQA5_v7KA3qETGsvqV6wU8r7paO-k,2976
4
4
  pytcl/assignment_algorithms/data_association.py,sha256=tsRxWJZk9aAPmE99BKXGouEpFfZrjPjb4HXvgxFUHhU,11405
@@ -6,7 +6,7 @@ pytcl/assignment_algorithms/dijkstra_min_cost.py,sha256=z-Wk1HXRNKieBsRFqR8_UB8Q
6
6
  pytcl/assignment_algorithms/gating.py,sha256=JaRaFcFqjfdsTbbTP6k_GY2zemDSR02l5yInWHpb05Y,11439
7
7
  pytcl/assignment_algorithms/jpda.py,sha256=rOY_v1vesL6EJySwD0kRDTfe7wHoDFLITg_lJLM-bX4,21731
8
8
  pytcl/assignment_algorithms/nd_assignment.py,sha256=bcSNm3xSEjAg8gFb_TLQovpsLjNwvI5OOlh2y8XG4M0,24571
9
- pytcl/assignment_algorithms/network_flow.py,sha256=pPD63Z0-HOBv5XIqKUedt1KzTkcs0KG41DNojFZocDI,14459
9
+ pytcl/assignment_algorithms/network_flow.py,sha256=-9tQY-kBrjsTHqv23rBD0-iebVvtAsK_a605Qn0_9SM,18619
10
10
  pytcl/assignment_algorithms/network_simplex.py,sha256=Qi10PsIYcTc6MZ-9GPl6ivaLaGA9F5-B7ltBbmasRNM,5566
11
11
  pytcl/assignment_algorithms/three_dimensional/__init__.py,sha256=1Q40OUlUQoo7YKEucwdrSNo3D4A0Zibvkr8z4TpueBg,526
12
12
  pytcl/assignment_algorithms/three_dimensional/assignment.py,sha256=OGcjg3Yr1tYriWYBJ5k6jiRMpOHDISK8FJDY0nTQxxw,19244
@@ -21,7 +21,7 @@ pytcl/astronomical/reference_frames.py,sha256=MBqprzBpEvdq3ngRL-_pp-Vnj7AqbuXhjU
21
21
  pytcl/astronomical/relativity.py,sha256=vSay4am_ElkiGRwuu_4rwo5C10eN8hm86jy43xmaEt4,15933
22
22
  pytcl/astronomical/sgp4.py,sha256=iNZrqMRUzR-LFeZiluzlNmkwxeYbIyF2F1cygyeEZVE,21546
23
23
  pytcl/astronomical/special_orbits.py,sha256=N54c_wAD7XKk_diDOw2QjUSkmYECMyWQDq2P6EeEBEI,12745
24
- pytcl/astronomical/time_systems.py,sha256=Jg0Zaq60hc4Ts1aQtb5bK4KSZhz-uQse8gYC89Y0-TA,15243
24
+ pytcl/astronomical/time_systems.py,sha256=1r1DDRrMlX4eAe1d3quffIaTqIvaozmynza76aSfmrE,15615
25
25
  pytcl/astronomical/tle.py,sha256=t3e2-0f3Wiz77q-pC2jfpohkrDfoYOEHacpNgWMNLAk,14638
26
26
  pytcl/atmosphere/__init__.py,sha256=Joa6PBEfKun0Moii6BOzyVYG8AOFyvucKdVuY62ArQc,1685
27
27
  pytcl/atmosphere/ionosphere.py,sha256=tt_Qu1IvqZxah2P_QaWxpwroSgtDNU-6RrqtxMRZScY,16092
@@ -34,7 +34,7 @@ pytcl/clustering/hierarchical.py,sha256=MDBIcJtZHYruSsM_seAR9bNWKB63EDh1nIf6qAn4
34
34
  pytcl/clustering/kmeans.py,sha256=_r08KVvqHL1UnYE4rHBAJ1OKA97q-PWl2OOsTi6kKqk,10963
35
35
  pytcl/containers/__init__.py,sha256=i-o_KDjhbPWc9yAlgN1R5igmyWK3CVKIB-V7Qnr-fLg,2746
36
36
  pytcl/containers/base.py,sha256=oKD8vAH09qAEOG3uhtoTgJ9UU_hQhl6keSIlGEVucrs,7762
37
- pytcl/containers/cluster_set.py,sha256=uhfOIpXlYoI1U75TWcLMHjezVavnIZhVEGQHKCDmKo4,22774
37
+ pytcl/containers/cluster_set.py,sha256=_VYQa3uCf66LJWtF3LA2lCjyVsuJtfUFD3wKH9vHknk,23995
38
38
  pytcl/containers/covertree.py,sha256=oA09TAZYdxJbfkOUxAmXxqDn-l2XoQeUsnupjTF0Vo0,13245
39
39
  pytcl/containers/kd_tree.py,sha256=01CsFsSmiKb0PGjdQ3z1apmvqjT2T8t-1bLwXzyJu3c,18016
40
40
  pytcl/containers/measurement_set.py,sha256=87AbdoZIUspn1yJsiMpyQ5LoEVcerUnXefXGGPtFTJg,12654
@@ -43,7 +43,7 @@ pytcl/containers/track_list.py,sha256=6q9Qgcwm-8H_JqtOCsMssF27av4XaSkhfDl-MWb1AB
43
43
  pytcl/containers/vptree.py,sha256=-7znAilGCNpN7SN8TxVhNFIOyP-s9oJa9Vp4FJWehcg,8720
44
44
  pytcl/coordinate_systems/__init__.py,sha256=jwYhu_-9AvOeP9WLG9PYtyDwfe0GjxNZ9-xCqiLymW4,3909
45
45
  pytcl/coordinate_systems/conversions/__init__.py,sha256=PkNevB78vBw0BkalydJBbQO91AyiMJxKRrgJNt4HsYc,1100
46
- pytcl/coordinate_systems/conversions/geodetic.py,sha256=CarrTBW9rTC-CZ4E4YGxA8QjlpauuXJ2ZScnzc4QvK8,25001
46
+ pytcl/coordinate_systems/conversions/geodetic.py,sha256=v8ymPT5wUt8zGDPlttbbTkNHJMxKbBUfvgNserugwlU,27373
47
47
  pytcl/coordinate_systems/conversions/spherical.py,sha256=GwuS1k0aUQ3AG1zZJouioMjxSIuEPRZMk-arvUCTh2k,11563
48
48
  pytcl/coordinate_systems/jacobians/__init__.py,sha256=CRGB8GzvGT_sr4Ynm51S7gSX8grqt1pO1Pq1MWmHPTs,890
49
49
  pytcl/coordinate_systems/jacobians/jacobians.py,sha256=IkEwyseGM1LeI2-cQEqzGD-lCplK-PVCHup7Bh3QPl4,12947
@@ -52,7 +52,7 @@ pytcl/coordinate_systems/projections/projections.py,sha256=y_kwcu_zp0HHiKR-wp3v3
52
52
  pytcl/coordinate_systems/rotations/__init__.py,sha256=nqAz4iJd2hEOX_r7Tz4cE524sShyxdbtcQ5m56RrDLg,1047
53
53
  pytcl/coordinate_systems/rotations/rotations.py,sha256=-VheczqsqF-qHF46HVKYTe0oS76MTmqyXQV3EO2JAJs,22258
54
54
  pytcl/core/__init__.py,sha256=Rm02KyEP5jCgcaS6N6F-Bs2nLgW-EO1cjCD0NqAg664,3236
55
- pytcl/core/array_utils.py,sha256=SsgEiAoRCWxAVKq1aa5-nPdOi-2AB6XNObu0IaGClUk,13983
55
+ pytcl/core/array_utils.py,sha256=XkOzJD_65clohicbXV_uvDXtxo9_oupVMQfk_D8T4ds,15288
56
56
  pytcl/core/constants.py,sha256=cwkCjzCU7zG2ZsFcbqwslN632v7Lw50L85s-5q892mo,9988
57
57
  pytcl/core/exceptions.py,sha256=6ImMiwL86BdmTt-Rc8fXLXxKUGQ-PcQQyxIvKKzw-n0,24324
58
58
  pytcl/core/maturity.py,sha256=Sut19NfH1-6f3Qd2QSC6OAqvDcVHJDwf5-F_-oEAMJA,11596
@@ -91,12 +91,12 @@ pytcl/dynamic_models/process_noise/coordinated_turn.py,sha256=0PciDXtXHjgQdaYf7q
91
91
  pytcl/dynamic_models/process_noise/polynomial.py,sha256=w5ZW5Ouw6QpVtev_mnuCmZoj6_O6ovb2L_ENKDhHYIc,7742
92
92
  pytcl/dynamic_models/process_noise/singer.py,sha256=ozAdzH4s0wGlBaxajdyZvSnK8_CumgsUZDKeMW-TxDs,5735
93
93
  pytcl/gpu/__init__.py,sha256=aESvpn4Sa48xrQ4SIPb0j8uBt9bgiVHK_BgCXRLNY3o,4278
94
- pytcl/gpu/ekf.py,sha256=KPaojhYrti9F74C71_Pgc22HKDJeBSUkyrA7Iis9-L4,12575
95
- pytcl/gpu/kalman.py,sha256=8swMqLsnXjdl9-0vOg6wEqxtVHQRHcV4bXjHL8RwUmk,16417
96
- pytcl/gpu/matrix_utils.py,sha256=x2SBjN6f21YUeOOKThBtmIPyBnAXhTCvWteTxJZlSs0,12601
97
- pytcl/gpu/particle_filter.py,sha256=gqPt2ROFCkP-maFIlC8n7Td-ZNDZAN-42Ahen6TOfz8,17259
98
- pytcl/gpu/ukf.py,sha256=83tclGEAs4LWxocvUHSk7JIoUHozQnqusxM1qk_iedk,13273
99
- pytcl/gpu/utils.py,sha256=cedaW4evKeGCykFXI2QL_Ns8dU1yjL42MmYXf2gfGsw,14812
94
+ pytcl/gpu/ekf.py,sha256=Eveyy_pJ5AXMRPUIFL5p-kmJUGMPAmwuN9r4k5x_rlM,14212
95
+ pytcl/gpu/kalman.py,sha256=7DsEjSnMksymE4IT4m1H27-7gaB5zhHzAHdbfp_ZpCk,16939
96
+ pytcl/gpu/matrix_utils.py,sha256=HwUGj6TKgt7jFUI5vR95y4evW4ZgMdHCt8G9Dlxx0RY,13878
97
+ pytcl/gpu/particle_filter.py,sha256=frLpeDQY81gNKTvxTYqRH0w7VKEmw42IaazYQE5T-yk,18149
98
+ pytcl/gpu/ukf.py,sha256=SZ-JdA2bDzqSQwqhoYwQJKwGuaZbEWxa6sbSHPYkmJo,14268
99
+ pytcl/gpu/utils.py,sha256=jeOZW_L1hzPFgUlwtWSCYQnl0sUzMCJiH5ekyHV4P4A,15242
100
100
  pytcl/gravity/__init__.py,sha256=5xNdQSrrkt7-1-JPOYqR38CqvNJ7qKlPyMK36DGm6-I,3693
101
101
  pytcl/gravity/clenshaw.py,sha256=zhEtIxUY6Uj8EMv7ucO3JMBqauA5shFKbUW0HO2hUfI,17240
102
102
  pytcl/gravity/egm.py,sha256=LAeNbaQ7eZakk0ciwLec0_8q41MrBFouVLpDsETis6o,19683
@@ -105,46 +105,46 @@ pytcl/gravity/spherical_harmonics.py,sha256=SbCIlfNuJBwQ1nIJKo0DzgeEfW7RD_QnyKI0
105
105
  pytcl/gravity/tides.py,sha256=NjsiXSiI7f-0qGr7G7YJVpIOVGzDxagz2S2vf_aRq68,28681
106
106
  pytcl/magnetism/__init__.py,sha256=pBASOzCPHNnYqUH_XDEblhGtjz50vY9uW2KS25A0zQQ,2701
107
107
  pytcl/magnetism/emm.py,sha256=iIdxSL0uGGIf8nfA-c_SmHvg9_J7HwRA2-qbQIUW6IE,22380
108
- pytcl/magnetism/igrf.py,sha256=3g0PsH8IdbwQQS28OR5XWD-g-QxvfUva7jOkKToxndQ,13384
109
- pytcl/magnetism/wmm.py,sha256=7YsxnBmxeH5WnTTZ3VE91gr55Qq-OvVmBRAMbqlhGVg,23479
108
+ pytcl/magnetism/igrf.py,sha256=XsbhEwthNSb4i9keqqrSO9h_m62WD5AMAwg2eGpgaPc,15585
109
+ pytcl/magnetism/wmm.py,sha256=DYUUNrigWNOR6BETHO7FmD69amDpzP4s4aV9jg0fkiQ,25160
110
110
  pytcl/mathematical_functions/__init__.py,sha256=zeJ1ffRRl83k2NHn3HTn-fgtFoWNPq6LCALc3xRo4Do,3767
111
111
  pytcl/mathematical_functions/basic_matrix/__init__.py,sha256=kZv3kMAEHBdVxhbyMxTyM0s-4XJP1tK6po82UsIE4tc,1318
112
- pytcl/mathematical_functions/basic_matrix/decompositions.py,sha256=PWJsFDiXM2T78RHdxBJZPFnl8kFbNZQpHrbpw0mhE00,12268
113
- pytcl/mathematical_functions/basic_matrix/special_matrices.py,sha256=kOozwP2CHAj4qyO7Z9ct6GwDMkmHkk1bQa0e9G98FgA,13499
112
+ pytcl/mathematical_functions/basic_matrix/decompositions.py,sha256=yvmQt_1UAvG1O2fOg0lzXSDcJiYYFu2yDyloz8Gz8uM,12425
113
+ pytcl/mathematical_functions/basic_matrix/special_matrices.py,sha256=jD20KOJHF7P8xT5mtID1aniLUY84Dz701GwDbRrXKug,14545
114
114
  pytcl/mathematical_functions/combinatorics/__init__.py,sha256=byuHI0WkxOkQF8egrfjEr-awB2visWDXlGMnDux5IBg,1043
115
115
  pytcl/mathematical_functions/combinatorics/combinatorics.py,sha256=LzVxY3E5_pnpVl9XlcRdQjWhSSY8Fa1JUVYA6J25fro,12354
116
116
  pytcl/mathematical_functions/continuous_optimization/__init__.py,sha256=lck60eeCUOsRpEzPHBY3kiLKwNz_fhmYoUGP3lTmTwk,55
117
117
  pytcl/mathematical_functions/geometry/__init__.py,sha256=DhCmux9-6zxYRzlhQ9du18kvUL-leiiZwdd3Cmb5WX0,1092
118
- pytcl/mathematical_functions/geometry/geometry.py,sha256=iLKqTlLEGm8IScEDHEWOBQz5xfj-fflzIOzZaJ8fPtE,16522
118
+ pytcl/mathematical_functions/geometry/geometry.py,sha256=mjbGAIp3F-YsVoUdVhoPQOD4bTnZrdniwVZJ2-Kja8U,17710
119
119
  pytcl/mathematical_functions/interpolation/__init__.py,sha256=lK4Rs0Ds_fzf9q0n6id5epdN0U8V7yD87dS-w1hvN8I,741
120
- pytcl/mathematical_functions/interpolation/interpolation.py,sha256=2cXMDgWBjWDGHnK1K_lawFlJL8oPl5AQGf9MNgsESfo,12610
120
+ pytcl/mathematical_functions/interpolation/interpolation.py,sha256=E_CAexA4m-xOincCvslvPVqaw8FSRk55IAqd2CBbdak,15649
121
121
  pytcl/mathematical_functions/numerical_integration/__init__.py,sha256=iXiHzyV_KIhCv7tXErXlN1_fUEACN6yN3CYDHRA7esw,974
122
122
  pytcl/mathematical_functions/numerical_integration/quadrature.py,sha256=F5y8UQltTiAyIj_lGuuRYnSf465Rm_DNHMeq-E8bj-8,17732
123
123
  pytcl/mathematical_functions/polynomials/__init__.py,sha256=WJWZcoQhnvy5f59-kncMTgD9mCtgwfDgULvDYYHS5ys,43
124
124
  pytcl/mathematical_functions/signal_processing/__init__.py,sha256=_SzzBVtxmSvP8FKeogRdNmFo8FOVDDoexVOqd-lE7do,2325
125
- pytcl/mathematical_functions/signal_processing/detection.py,sha256=vLToMHdPkhom6ouo1oN0QqBYjEGv7SQENqTD0DOv1XY,30934
126
- pytcl/mathematical_functions/signal_processing/filters.py,sha256=xiB8VSFqTFkBCAom0yIWw7pK3Zjm6l-VZ_DAtwJMxFA,23676
127
- pytcl/mathematical_functions/signal_processing/matched_filter.py,sha256=El7XcUbunmXA7s-btXX_R4fgNx8d6QNa86GJETg4zAQ,23134
125
+ pytcl/mathematical_functions/signal_processing/detection.py,sha256=Gphym92pFx3NSradLcFiBZwGSEAedBh5bOncrq0huGA,32252
126
+ pytcl/mathematical_functions/signal_processing/filters.py,sha256=UhNnILWzpbJeiSait0xcnl48HEmNC5j47LWEogXMgr0,25233
127
+ pytcl/mathematical_functions/signal_processing/matched_filter.py,sha256=63htJUvSVFnVOwwFBtHtWEMp5puEXD7HWnsKlZrLQ4E,24511
128
128
  pytcl/mathematical_functions/special_functions/__init__.py,sha256=AJBCKj32daQxdahUQckW0bWowzOoapxni2eZnVXERdg,3859
129
129
  pytcl/mathematical_functions/special_functions/bessel.py,sha256=kRRPafQMXmooBglEteccjb6Hct1LLq3Oze4JfLQ-AmY,15459
130
130
  pytcl/mathematical_functions/special_functions/debye.py,sha256=eH7Y5qq5j-AMKKx7y8uMS_l_pb6z9_3SG6Igvnc1Fdg,9626
131
131
  pytcl/mathematical_functions/special_functions/elliptic.py,sha256=mF3hVrErlK376pw-QZDoq_R6y8gnJAZAK5Kmuq8_0n4,8131
132
132
  pytcl/mathematical_functions/special_functions/error_functions.py,sha256=6i5HqQfYdcz0UNhLnHpoMCnWzzZypjckSHTC5ISrcHA,6990
133
133
  pytcl/mathematical_functions/special_functions/gamma_functions.py,sha256=riTiy1cqYFtJrzscw0vgTkCaPBCPDGN8Ge6qWAD1zBg,11086
134
- pytcl/mathematical_functions/special_functions/hypergeometric.py,sha256=mCBf5NPl0mOkwvIwAUp-sbXshin5HyGsdqEeGbYt3wQ,11428
134
+ pytcl/mathematical_functions/special_functions/hypergeometric.py,sha256=_iyWm0yLVDTzNxgWogru5-LtVMUQ2FXJRZAcmj-o7eQ,12061
135
135
  pytcl/mathematical_functions/special_functions/lambert_w.py,sha256=flLSf_7IY5sRXzd2aGgyXfJTRCjekgtvguBV3MhB9GE,6943
136
136
  pytcl/mathematical_functions/special_functions/marcum_q.py,sha256=ASQ2vVfhmx4iEvtZTyiQ60-Hy8Qxl_4XgJzYufayJaQ,9910
137
137
  pytcl/mathematical_functions/statistics/__init__.py,sha256=dfypStgmnFmOrnWcm-3CEvLinONHraFgx9O66_37bqw,1278
138
138
  pytcl/mathematical_functions/statistics/distributions.py,sha256=icfFIIKCEFzkpFHuYGWL197nm8wvS7UPJlr9kd_uEgw,19373
139
- pytcl/mathematical_functions/statistics/estimators.py,sha256=TLnYXSwk5MzBakZrzDBupbOB3ONmJI7q1-oB2xuSVQM,10831
139
+ pytcl/mathematical_functions/statistics/estimators.py,sha256=egaHcbeI1812MuDZEimrh6efas-zXSuNjWM-pcpnyfE,12036
140
140
  pytcl/mathematical_functions/transforms/__init__.py,sha256=SPXSKHjqR6B_8pvgtbtOnEiCpU-u0JF2s7hAlhb0BbI,2343
141
141
  pytcl/mathematical_functions/transforms/fourier.py,sha256=yD1CcH7sdPlrOmBgL7JoMiPNgN8ee7bTwvblgRRf7l4,20823
142
142
  pytcl/mathematical_functions/transforms/stft.py,sha256=olDzNH02Nta5GoeEdsdX1tTVKODr6OxLEYt_h3ZtMgA,18878
143
- pytcl/mathematical_functions/transforms/wavelets.py,sha256=lR71JX5vX_OXl1g9H89OGiimM_oqfU-WfGYJF3uD2z8,21887
143
+ pytcl/mathematical_functions/transforms/wavelets.py,sha256=uHFh0wM10wSbP9Gs3P0WR3Vwz-Pue-g6Rr6H2OhJV68,22866
144
144
  pytcl/misc/__init__.py,sha256=SCHf_lQVfdl2gwUluHBiIloTF8HRH8EkgYfbNr7zOug,33
145
145
  pytcl/navigation/__init__.py,sha256=k1_x_FnnPrIzGeNu7zejPtPubIhweBgCfwqlZJEMw0I,6042
146
146
  pytcl/navigation/geodesy.py,sha256=NH4Txv_VtUdBk6LkQqnSnPfRnG4WnoGF7zGwB-mIbJA,23140
147
- pytcl/navigation/great_circle.py,sha256=K7CLS8k1P4uN9jpq4Nzc6jk_bSmo-ppb3vyUJbwYjVg,25503
147
+ pytcl/navigation/great_circle.py,sha256=9OJKvFP6KwiqaMrgGA6DVOTOFKTeZwiqWlCejyZpwvQ,26741
148
148
  pytcl/navigation/ins.py,sha256=OIi8_RjrgEYl0MFpJEFMjIlpgX8DYGTEhdLEvqG-ABU,31151
149
149
  pytcl/navigation/ins_gnss.py,sha256=euKF5JGgwmVBsw3jBf7_wa2z1BpZeVbSNmBuwzhGS6c,30157
150
150
  pytcl/navigation/rhumb.py,sha256=lEr3LJqowT-WsdSA4E-V7BFmaqxoI7OpJa05pl_-uGg,20562
@@ -172,8 +172,8 @@ pytcl/trackers/mht.py,sha256=osEOXMaCeTt1eVn_E08dLRhEvBroVmf8b81zO5Zp1lU,20720
172
172
  pytcl/trackers/multi_target.py,sha256=RDITa0xnbgtVYAMj5XXp4lljo5lZ2zAAc02KZlOjxbQ,10526
173
173
  pytcl/trackers/single_target.py,sha256=Yy3FwaNTArMWcaod-0HVeiioNV4xLWxNDn_7ZPVqQYs,6562
174
174
  pytcl/transponders/__init__.py,sha256=5fL4u3lKCYgPLo5uFeuZbtRZkJPABntuKYGUvVgMMEI,41
175
- nrl_tracker-1.11.1.dist-info/LICENSE,sha256=rB5G4WppIIUzMOYr2N6uyYlNJ00hRJqE5tie6BMvYuE,1612
176
- nrl_tracker-1.11.1.dist-info/METADATA,sha256=Rk3gTB07fj8sHOEj6eEnVo-3Dfvv7I29KO4WDmdJmdo,14038
177
- nrl_tracker-1.11.1.dist-info/WHEEL,sha256=pL8R0wFFS65tNSRnaOVrsw9EOkOqxLrlUPenUYnJKNo,91
178
- nrl_tracker-1.11.1.dist-info/top_level.txt,sha256=17megxcrTPBWwPZTh6jTkwTKxX7No-ZqRpyvElnnO-s,6
179
- nrl_tracker-1.11.1.dist-info/RECORD,,
175
+ nrl_tracker-1.12.0.dist-info/LICENSE,sha256=rB5G4WppIIUzMOYr2N6uyYlNJ00hRJqE5tie6BMvYuE,1612
176
+ nrl_tracker-1.12.0.dist-info/METADATA,sha256=R5r43JY6yLPCD0lrxbyffCMBJm8g_KEHSojmTbeKR7k,14038
177
+ nrl_tracker-1.12.0.dist-info/WHEEL,sha256=pL8R0wFFS65tNSRnaOVrsw9EOkOqxLrlUPenUYnJKNo,91
178
+ nrl_tracker-1.12.0.dist-info/top_level.txt,sha256=17megxcrTPBWwPZTh6jTkwTKxX7No-ZqRpyvElnnO-s,6
179
+ nrl_tracker-1.12.0.dist-info/RECORD,,
pytcl/__init__.py CHANGED
@@ -6,7 +6,7 @@ systems, dynamic models, estimation algorithms, and mathematical functions.
6
6
 
7
7
  This is a Python port of the U.S. Naval Research Laboratory's Tracker Component
8
8
  Library originally written in MATLAB.
9
- **Current Version:** 1.11.1 (January 5, 2026)
9
+ **Current Version:** 1.12.0 (January 5, 2026)
10
10
  **Status:** Production-ready, 2,894 tests passing, 76% line coverage
11
11
  Examples
12
12
  --------
@@ -21,7 +21,7 @@ References
21
21
  no. 5, pp. 18-27, May 2017.
22
22
  """
23
23
 
24
- __version__ = "1.11.1"
24
+ __version__ = "1.12.0"
25
25
  __author__ = "Python Port Contributors"
26
26
  __original_author__ = "David F. Crouse, Naval Research Laboratory"
27
27
 
@@ -179,13 +179,19 @@ def min_cost_flow_successive_shortest_paths(
179
179
  max_iterations: int = 1000,
180
180
  ) -> MinCostFlowResult:
181
181
  """
182
- Solve min-cost flow using successive shortest paths.
182
+ Solve min-cost flow using successive shortest paths with cost scaling.
183
183
 
184
184
  Algorithm:
185
- 1. While there is excess supply:
186
- - Find shortest path from a supply node to a demand node
187
- - Push maximum feasible flow along path
188
- - Update supplies and residual capacities
185
+ 1. Initialize potentials using Bellman-Ford
186
+ 2. While there is excess supply:
187
+ - Find shortest path using reduced costs (Dijkstra with potentials)
188
+ - Push unit flow along path
189
+ - Update node potentials
190
+ - Recompute shortest paths to maintain optimality
191
+
192
+ This is the standard min-cost flow algorithm that guarantees optimality
193
+ and convergence. It uses Dijkstra's algorithm with potentials, which
194
+ maintains the dual feasibility (reduced cost property).
189
195
 
190
196
  Parameters
191
197
  ----------
@@ -217,100 +223,163 @@ def min_cost_flow_successive_shortest_paths(
217
223
 
218
224
  Notes
219
225
  -----
220
- This is a simplified implementation using Bellman-Ford for shortest
221
- paths. Production code would use more efficient implementations.
226
+ This implementation uses successive shortest paths with potentials.
227
+ The algorithm is guaranteed to find the optimal solution for any
228
+ feasible min-cost flow problem.
229
+
230
+ For rectangular assignment problems (m < n or m > n), all m units
231
+ of flow must be satisfied. The algorithm ensures this by finding
232
+ augmenting paths until all supply is routed.
222
233
  """
223
234
  n_nodes = len(supplies)
224
235
  n_edges = len(edges)
225
236
 
226
- # Build adjacency lists for residual graph
227
- graph: list[list[tuple[int, int, float]]] = [[] for _ in range(n_nodes)]
237
+ # Initialize flow and residual capacity
228
238
  flow = np.zeros(n_edges)
229
239
  residual_capacity = np.array([e.capacity for e in edges])
230
240
 
241
+ # Initialize node potentials using Bellman-Ford from a dummy source
242
+ # This ensures all potentials are finite and maintains dual feasibility
243
+ potential = np.zeros(n_nodes)
244
+
245
+ # Build adjacency list representation
246
+ # Each entry: (to_node, edge_idx, is_reverse, cost)
247
+ graph: list[list[tuple[int, int, int, float]]] = [[] for _ in range(n_nodes)]
248
+
231
249
  for edge_idx, edge in enumerate(edges):
232
- graph[edge.from_node].append((edge.to_node, edge_idx, edge.cost))
233
- # Add reverse edge with negative cost
234
- graph[edge.to_node].append((edge.from_node, edge_idx, -edge.cost))
250
+ # Forward edge
251
+ graph[edge.from_node].append((edge.to_node, edge_idx, 0, edge.cost))
252
+ # Reverse edge (for flow cancellation)
253
+ graph[edge.to_node].append((edge.from_node, edge_idx, 1, -edge.cost))
235
254
 
236
255
  current_supplies = supplies.copy()
237
256
  iteration = 0
238
257
 
258
+ # Main algorithm loop
239
259
  while iteration < max_iterations:
240
- # Find a node with excess supply
241
- excess_node = None
260
+ # Find excess and deficit nodes
261
+ excess_node = -1
262
+ deficit_node = -1
263
+
242
264
  for node in range(n_nodes):
243
265
  if current_supplies[node] > 1e-10:
244
266
  excess_node = node
245
267
  break
246
268
 
247
- if excess_node is None:
248
- break
269
+ if excess_node < 0:
270
+ break # No more excess nodes
249
271
 
250
- # Find a node with deficit
251
- deficit_node = None
252
272
  for node in range(n_nodes):
253
273
  if current_supplies[node] < -1e-10:
254
274
  deficit_node = node
255
275
  break
256
276
 
257
- if deficit_node is None:
258
- break
277
+ if deficit_node < 0:
278
+ break # No deficit nodes
259
279
 
260
- # Find shortest path using Bellman-Ford relaxation
280
+ # Find shortest path using Dijkstra with potentials
281
+ # Reduced cost: c_reduced(u,v) = c(u,v) + π(u) - π(v)
261
282
  dist = np.full(n_nodes, np.inf)
262
283
  dist[excess_node] = 0.0
263
284
  parent = np.full(n_nodes, -1, dtype=int)
264
285
  parent_edge = np.full(n_nodes, -1, dtype=int)
286
+ parent_reverse = np.full(n_nodes, 0, dtype=int)
287
+ visited = np.zeros(n_nodes, dtype=bool)
288
+
289
+ # Dijkstra's algorithm
290
+ for _ in range(n_nodes):
291
+ # Find unvisited node with minimum distance
292
+ u = -1
293
+ min_dist = np.inf
294
+ for node in range(n_nodes):
295
+ if not visited[node] and dist[node] < min_dist:
296
+ u = node
297
+ min_dist = dist[node]
298
+
299
+ if u < 0 or dist[u] == np.inf:
300
+ break
301
+
302
+ visited[u] = True
265
303
 
266
- for _ in range(n_nodes - 1):
267
- for u in range(n_nodes):
268
- if dist[u] == np.inf:
269
- continue
270
- for v, edge_idx, cost in graph[u]:
271
- if residual_capacity[edge_idx] > 1e-10:
272
- new_dist = dist[u] + cost
273
- if new_dist < dist[v]:
274
- dist[v] = new_dist
275
- parent[v] = u
276
- parent_edge[v] = edge_idx
277
-
278
- if dist[deficit_node] == np.inf:
304
+ # Relax edges from u
305
+ for v, eidx, is_rev, cost in graph[u]:
306
+ if residual_capacity[eidx] > 1e-10:
307
+ # Compute reduced cost
308
+ reduced_cost = cost + potential[u] - potential[v]
309
+ new_dist = dist[u] + reduced_cost
310
+
311
+ if new_dist < dist[v] - 1e-10:
312
+ dist[v] = new_dist
313
+ parent[v] = u
314
+ parent_edge[v] = eidx
315
+ parent_reverse[v] = is_rev
316
+
317
+ if dist[deficit_node] >= np.inf:
279
318
  # No path found
280
319
  break
281
320
 
282
- # Extract path and find bottleneck capacity
321
+ # Update potentials to maintain dual feasibility
322
+ for node in range(n_nodes):
323
+ if dist[node] < np.inf:
324
+ potential[node] += dist[node]
325
+
326
+ # Extract path by backtracking
283
327
  path_edges = []
328
+ path_reverse_flags = []
284
329
  node = deficit_node
285
- while parent[node] != -1:
330
+ path_length = 0
331
+ visited_set = set()
332
+
333
+ while parent[node] >= 0:
334
+ if path_length >= n_nodes:
335
+ break # Safety check
336
+ if node in visited_set:
337
+ break # Cycle detected
338
+
339
+ visited_set.add(node)
286
340
  path_edges.append(parent_edge[node])
341
+ path_reverse_flags.append(parent_reverse[node])
287
342
  node = parent[node]
343
+ path_length += 1
344
+
345
+ if not path_edges:
346
+ iteration += 1
347
+ continue
288
348
 
289
349
  path_edges.reverse()
350
+ path_reverse_flags.reverse()
290
351
 
291
- # Find minimum capacity along path
352
+ # Find bottleneck capacity
292
353
  min_flow = min(residual_capacity[e] for e in path_edges)
293
354
  min_flow = min(
294
- min_flow, current_supplies[excess_node], -current_supplies[deficit_node]
355
+ min_flow,
356
+ current_supplies[excess_node],
357
+ -current_supplies[deficit_node],
295
358
  )
296
359
 
297
360
  # Push flow along path
298
- total_cost = 0.0
299
- for edge_idx in path_edges:
300
- flow[edge_idx] += min_flow
301
- residual_capacity[edge_idx] -= min_flow
302
- total_cost += min_flow * edges[edge_idx].cost
361
+ for edge_idx, is_reverse in zip(path_edges, path_reverse_flags):
362
+ if is_reverse == 0:
363
+ # Forward edge: increase flow
364
+ flow[edge_idx] += min_flow
365
+ residual_capacity[edge_idx] -= min_flow
366
+ else:
367
+ # Reverse edge: decrease flow (cancel)
368
+ flow[edge_idx] -= min_flow
369
+ residual_capacity[edge_idx] += min_flow
303
370
 
304
371
  current_supplies[excess_node] -= min_flow
305
372
  current_supplies[deficit_node] += min_flow
306
373
 
307
374
  iteration += 1
308
375
 
309
- # Compute total cost
310
- total_cost = float(np.sum(flow[i] * edges[i].cost for i in range(n_edges)))
376
+ # Compute total cost: include all flows (including negative which cancel)
377
+ total_cost = 0.0
378
+ for i, edge in enumerate(edges):
379
+ total_cost += flow[i] * edge.cost
311
380
 
312
381
  # Determine status
313
- if np.allclose(current_supplies, 0):
382
+ if np.allclose(current_supplies, 0, atol=1e-6):
314
383
  status = FlowStatus.OPTIMAL
315
384
  elif iteration >= max_iterations:
316
385
  status = FlowStatus.TIMEOUT
@@ -409,6 +478,14 @@ def assignment_from_flow_solution(
409
478
  """
410
479
  Extract assignment from flow network solution.
411
480
 
481
+ A valid flow solution for assignment should have:
482
+ - Exactly 1 unit of flow from each worker to some task
483
+ - Exactly 1 unit of flow to each task from some worker
484
+ - No negative flows on worker->task edges (those are cancellations)
485
+
486
+ This function extracts the actual assignment by identifying which
487
+ worker->task edges carry the net positive flow.
488
+
412
489
  Parameters
413
490
  ----------
414
491
  flow : ndarray
@@ -427,24 +504,59 @@ def assignment_from_flow_solution(
427
504
  """
428
505
  m, n = cost_matrix_shape
429
506
  assignment = []
507
+ cost = 0.0
508
+
509
+ # Build source node (node 0) and sink node (node m+n+1) indices
510
+ source = 0
511
+ sink = m + n + 1
512
+
513
+ # For a valid assignment solution:
514
+ # - Count flow out of source to each worker
515
+ # - Count flow into sink from each task
516
+ worker_outflow = np.zeros(m)
517
+ task_inflow = np.zeros(n)
518
+
519
+ # Collect all worker->task edges and their flows
520
+ worker_task_edges = []
430
521
 
431
522
  for edge_idx, edge in enumerate(edges):
432
- # Worker-to-task edges: from_node in [1, m], to_node in [m+1, m+n]
433
- if 1 <= edge.from_node <= m and m + 1 <= edge.to_node <= m + n:
434
- if flow[edge_idx] > 0.5: # Flow > 0 (allowing for numerical tolerance)
435
- worker_idx = edge.from_node - 1
436
- task_idx = edge.to_node - m - 1
437
- assignment.append([worker_idx, task_idx])
523
+ # Worker edges: from source (0) to worker nodes (1..m)
524
+ if edge.from_node == source and 1 <= edge.to_node <= m:
525
+ worker_id = edge.to_node - 1
526
+ worker_outflow[worker_id] += flow[edge_idx]
438
527
 
439
- assignment = np.array(assignment, dtype=np.intp)
440
- cost = 0.0
441
- if len(assignment) > 0:
442
- cost = float(
443
- np.sum(
444
- flow[edge_idx] * edges[edge_idx].cost for edge_idx in range(len(edges))
445
- )
446
- )
528
+ # Task edges: from task nodes (m+1..m+n) to sink
529
+ if m + 1 <= edge.from_node <= m + n and edge.to_node == sink:
530
+ task_id = edge.from_node - (m + 1)
531
+ task_inflow[task_id] += flow[edge_idx]
447
532
 
533
+ # Worker-to-task edges
534
+ if 1 <= edge.from_node <= m and m + 1 <= edge.to_node <= m + n:
535
+ worker_id = edge.from_node - 1
536
+ task_id = edge.to_node - (m + 1)
537
+ if flow[edge_idx] > 0.5: # Positive flow means this edge is used
538
+ worker_task_edges.append(
539
+ {
540
+ "worker": worker_id,
541
+ "task": task_id,
542
+ "flow": flow[edge_idx],
543
+ "cost": edge.cost,
544
+ "edge_idx": edge_idx,
545
+ }
546
+ )
547
+
548
+ # For assignment problems, each worker should have exactly 1 outgoing flow
549
+ # and each task should have exactly 1 incoming flow
550
+ # Extract the assignment from worker->task edges with positive flow
551
+ for edge_info in worker_task_edges:
552
+ assignment.append([edge_info["worker"], edge_info["task"]])
553
+ cost += edge_info["flow"] * edge_info["cost"]
554
+
555
+ assignment = (
556
+ np.array(assignment, dtype=np.intp)
557
+ if assignment
558
+ else np.empty((0, 2), dtype=np.intp)
559
+ )
448
560
  return assignment, cost
449
561
 
450
562
 
@@ -269,6 +269,13 @@ def mjd_to_jd(mjd: float) -> float:
269
269
  float
270
270
  Julian Date.
271
271
 
272
+ Examples
273
+ --------
274
+ >>> mjd = 44239.0 # 1980-01-01
275
+ >>> jd = mjd_to_jd(mjd)
276
+ >>> jd
277
+ 2444239.5
278
+
272
279
  Notes
273
280
  -----
274
281
  MJD = JD - 2400000.5
@@ -289,6 +296,13 @@ def jd_to_mjd(jd: float) -> float:
289
296
  -------
290
297
  float
291
298
  Modified Julian Date.
299
+
300
+ Examples
301
+ --------
302
+ >>> jd = 2444239.5 # 1980-01-01
303
+ >>> mjd = jd_to_mjd(jd)
304
+ >>> mjd
305
+ 44239.0
292
306
  """
293
307
  return jd - MJD_OFFSET
294
308
 
@@ -328,6 +342,13 @@ def jd_to_unix(jd: float) -> float:
328
342
  -------
329
343
  float
330
344
  Unix timestamp.
345
+
346
+ Examples
347
+ --------
348
+ >>> jd = 2440587.5 # 1970-01-01 00:00:00 UTC
349
+ >>> unix_to_jd = jd_to_unix(jd)
350
+ >>> unix_to_jd
351
+ 0.0
331
352
  """
332
353
  return (jd - JD_UNIX_EPOCH) * 86400.0
333
354
 
@@ -94,6 +94,23 @@ def compute_cluster_centroid(
94
94
  -------
95
95
  centroid : ndarray
96
96
  Centroid position [x, y].
97
+
98
+ Examples
99
+ --------
100
+ >>> from pytcl.trackers.multi_target import Track
101
+ >>> import numpy as np
102
+ >>> # Create sample tracks with [x, vx, y, vy] state vectors
103
+ >>> track1 = Track(state=np.array([0.0, 1.0, 0.0, 1.0]))
104
+ >>> track2 = Track(state=np.array([2.0, 1.0, 2.0, 1.0]))
105
+ >>> track3 = Track(state=np.array([4.0, 1.0, 4.0, 1.0]))
106
+ >>> tracks = [track1, track2, track3]
107
+ >>> centroid = compute_cluster_centroid(tracks)
108
+ >>> centroid
109
+ array([2., 2.])
110
+
111
+ See Also
112
+ --------
113
+ compute_cluster_covariance : Compute covariance of track positions.
97
114
  """
98
115
  track_list = list(tracks)
99
116
  if len(track_list) == 0:
@@ -122,6 +139,25 @@ def compute_cluster_covariance(
122
139
  -------
123
140
  covariance : ndarray
124
141
  Position covariance matrix (2x2).
142
+
143
+ Examples
144
+ --------
145
+ >>> from pytcl.trackers.multi_target import Track
146
+ >>> import numpy as np
147
+ >>> # Create collinear tracks (high variance along x-axis)
148
+ >>> track1 = Track(state=np.array([0.0, 1.0, 0.0, 0.0]))
149
+ >>> track2 = Track(state=np.array([1.0, 1.0, 0.0, 0.0]))
150
+ >>> track3 = Track(state=np.array([2.0, 1.0, 0.0, 0.0]))
151
+ >>> tracks = [track1, track2, track3]
152
+ >>> cov = compute_cluster_covariance(tracks)
153
+ >>> cov.shape
154
+ (2, 2)
155
+ >>> cov[0, 0] > cov[1, 1] # More spread in x than y
156
+ True
157
+
158
+ See Also
159
+ --------
160
+ compute_cluster_centroid : Compute centroid of track positions.
125
161
  """
126
162
  track_list = list(tracks)
127
163
  if len(track_list) < 2: