nrl-tracker 1.9.1__tar.gz → 1.10.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/CONTRIBUTING.md +4 -2
- {nrl_tracker-1.9.1/nrl_tracker.egg-info → nrl_tracker-1.10.0}/PKG-INFO +49 -4
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/README.md +40 -3
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0/nrl_tracker.egg-info}/PKG-INFO +49 -4
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/nrl_tracker.egg-info/SOURCES.txt +10 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/nrl_tracker.egg-info/requires.txt +10 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pyproject.toml +14 -1
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/__init__.py +2 -2
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/assignment_algorithms/gating.py +18 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/assignment_algorithms/jpda.py +56 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/assignment_algorithms/nd_assignment.py +65 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/assignment_algorithms/network_flow.py +40 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/astronomical/ephemerides.py +18 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/astronomical/orbital_mechanics.py +131 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/atmosphere/ionosphere.py +44 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/atmosphere/models.py +29 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/clustering/dbscan.py +9 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/clustering/gaussian_mixture.py +20 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/clustering/hierarchical.py +29 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/clustering/kmeans.py +9 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/coordinate_systems/conversions/geodetic.py +46 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/coordinate_systems/conversions/spherical.py +35 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/coordinate_systems/rotations/rotations.py +147 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/core/__init__.py +16 -0
- nrl_tracker-1.10.0/pytcl/core/maturity.py +346 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/core/optional_deps.py +20 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/dynamic_estimation/gaussian_sum_filter.py +55 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/dynamic_estimation/imm.py +29 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/dynamic_estimation/information_filter.py +64 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/dynamic_estimation/kalman/extended.py +56 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/dynamic_estimation/kalman/linear.py +69 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/dynamic_estimation/kalman/unscented.py +81 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/dynamic_estimation/particle_filters/bootstrap.py +146 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/dynamic_estimation/rbpf.py +51 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/dynamic_estimation/smoothers.py +58 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/dynamic_models/continuous_time/dynamics.py +104 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/dynamic_models/discrete_time/coordinated_turn.py +6 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/dynamic_models/discrete_time/singer.py +12 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/dynamic_models/process_noise/coordinated_turn.py +46 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/dynamic_models/process_noise/polynomial.py +6 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/dynamic_models/process_noise/singer.py +52 -0
- nrl_tracker-1.10.0/pytcl/gpu/__init__.py +153 -0
- nrl_tracker-1.10.0/pytcl/gpu/ekf.py +425 -0
- nrl_tracker-1.10.0/pytcl/gpu/kalman.py +543 -0
- nrl_tracker-1.10.0/pytcl/gpu/matrix_utils.py +486 -0
- nrl_tracker-1.10.0/pytcl/gpu/particle_filter.py +568 -0
- nrl_tracker-1.10.0/pytcl/gpu/ukf.py +476 -0
- nrl_tracker-1.10.0/pytcl/gpu/utils.py +582 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/gravity/clenshaw.py +60 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/gravity/egm.py +47 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/gravity/models.py +34 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/gravity/spherical_harmonics.py +73 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/gravity/tides.py +34 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/mathematical_functions/numerical_integration/quadrature.py +85 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/mathematical_functions/special_functions/bessel.py +55 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/mathematical_functions/special_functions/elliptic.py +42 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/mathematical_functions/special_functions/error_functions.py +49 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/mathematical_functions/special_functions/gamma_functions.py +43 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/mathematical_functions/special_functions/lambert_w.py +5 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/mathematical_functions/special_functions/marcum_q.py +16 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/navigation/geodesy.py +101 -2
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/navigation/great_circle.py +71 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/navigation/rhumb.py +74 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/performance_evaluation/estimation_metrics.py +70 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/performance_evaluation/track_metrics.py +30 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/static_estimation/maximum_likelihood.py +54 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/static_estimation/robust.py +57 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/terrain/dem.py +69 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/terrain/visibility.py +65 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/trackers/hypothesis.py +65 -0
- nrl_tracker-1.10.0/tests/test_gpu.py +427 -0
- nrl_tracker-1.10.0/tests/test_gpu_utils.py +173 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/LICENSE +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/MANIFEST.in +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/nrl_tracker.egg-info/dependency_links.txt +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/nrl_tracker.egg-info/top_level.txt +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/assignment_algorithms/__init__.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/assignment_algorithms/data_association.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/assignment_algorithms/dijkstra_min_cost.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/assignment_algorithms/network_simplex.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/assignment_algorithms/three_dimensional/__init__.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/assignment_algorithms/three_dimensional/assignment.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/assignment_algorithms/two_dimensional/__init__.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/assignment_algorithms/two_dimensional/assignment.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/assignment_algorithms/two_dimensional/kbest.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/astronomical/__init__.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/astronomical/lambert.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/astronomical/reference_frames.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/astronomical/relativity.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/astronomical/sgp4.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/astronomical/special_orbits.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/astronomical/time_systems.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/astronomical/tle.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/atmosphere/__init__.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/atmosphere/nrlmsise00.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/clustering/__init__.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/containers/__init__.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/containers/base.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/containers/cluster_set.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/containers/covertree.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/containers/kd_tree.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/containers/measurement_set.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/containers/rtree.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/containers/track_list.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/containers/vptree.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/coordinate_systems/__init__.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/coordinate_systems/conversions/__init__.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/coordinate_systems/jacobians/__init__.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/coordinate_systems/jacobians/jacobians.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/coordinate_systems/projections/__init__.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/coordinate_systems/projections/projections.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/coordinate_systems/rotations/__init__.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/core/array_utils.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/core/constants.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/core/exceptions.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/core/validation.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/dynamic_estimation/__init__.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/dynamic_estimation/batch_estimation/__init__.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/dynamic_estimation/kalman/__init__.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/dynamic_estimation/kalman/constrained.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/dynamic_estimation/kalman/h_infinity.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/dynamic_estimation/kalman/matrix_utils.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/dynamic_estimation/kalman/square_root.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/dynamic_estimation/kalman/sr_ukf.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/dynamic_estimation/kalman/types.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/dynamic_estimation/kalman/ud_filter.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/dynamic_estimation/measurement_update/__init__.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/dynamic_estimation/particle_filters/__init__.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/dynamic_models/__init__.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/dynamic_models/continuous_time/__init__.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/dynamic_models/discrete_time/__init__.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/dynamic_models/discrete_time/polynomial.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/dynamic_models/process_noise/__init__.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/gravity/__init__.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/logging_config.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/magnetism/__init__.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/magnetism/emm.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/magnetism/igrf.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/magnetism/wmm.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/mathematical_functions/__init__.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/mathematical_functions/basic_matrix/__init__.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/mathematical_functions/basic_matrix/decompositions.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/mathematical_functions/basic_matrix/special_matrices.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/mathematical_functions/combinatorics/__init__.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/mathematical_functions/combinatorics/combinatorics.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/mathematical_functions/continuous_optimization/__init__.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/mathematical_functions/geometry/__init__.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/mathematical_functions/geometry/geometry.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/mathematical_functions/interpolation/__init__.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/mathematical_functions/interpolation/interpolation.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/mathematical_functions/numerical_integration/__init__.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/mathematical_functions/polynomials/__init__.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/mathematical_functions/signal_processing/__init__.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/mathematical_functions/signal_processing/detection.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/mathematical_functions/signal_processing/filters.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/mathematical_functions/signal_processing/matched_filter.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/mathematical_functions/special_functions/__init__.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/mathematical_functions/special_functions/debye.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/mathematical_functions/special_functions/hypergeometric.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/mathematical_functions/statistics/__init__.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/mathematical_functions/statistics/distributions.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/mathematical_functions/statistics/estimators.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/mathematical_functions/transforms/__init__.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/mathematical_functions/transforms/fourier.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/mathematical_functions/transforms/stft.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/mathematical_functions/transforms/wavelets.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/misc/__init__.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/navigation/__init__.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/navigation/ins.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/navigation/ins_gnss.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/performance_evaluation/__init__.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/physical_values/__init__.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/plotting/__init__.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/plotting/coordinates.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/plotting/ellipses.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/plotting/metrics.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/plotting/tracks.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/scheduling/__init__.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/static_estimation/__init__.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/static_estimation/least_squares.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/terrain/__init__.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/terrain/loaders.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/trackers/__init__.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/trackers/mht.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/trackers/multi_target.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/trackers/single_target.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/pytcl/transponders/__init__.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/setup.cfg +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/tests/__init__.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/tests/conftest.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/tests/test_additional_trees.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/tests/test_assignment_algorithms.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/tests/test_astronomical.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/tests/test_clustering.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/tests/test_constrained_ekf.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/tests/test_coordinate_systems.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/tests/test_coverage_boost.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/tests/test_coverage_boost_2.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/tests/test_dynamic_models.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/tests/test_egm.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/tests/test_emm.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/tests/test_ephemerides.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/tests/test_exceptions.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/tests/test_gaussian_mixtures.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/tests/test_gaussian_sum_filter.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/tests/test_geophysical.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/tests/test_great_circle.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/tests/test_h_infinity.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/tests/test_ins.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/tests/test_ins_gnss.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/tests/test_jpda.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/tests/test_kalman_filters.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/tests/test_mathematical_functions.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/tests/test_maximum_likelihood.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/tests/test_mht.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/tests/test_nd_assignment.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/tests/test_network_flow.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/tests/test_nrlmsise00.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/tests/test_optional_deps.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/tests/test_performance_evaluation.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/tests/test_phase6_specialized.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/tests/test_plotting.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/tests/test_projections.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/tests/test_rbpf.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/tests/test_relativity.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/tests/test_rhumb.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/tests/test_sgp4.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/tests/test_signal_processing.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/tests/test_smoothers.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/tests/test_spatial_containers_parametrized.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/tests/test_spatial_structures.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/tests/test_special_functions.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/tests/test_special_orbits.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/tests/test_static_estimation.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/tests/test_terrain.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/tests/test_terrain_loaders.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/tests/test_tides.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/tests/test_tod_mod.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/tests/test_trackers.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/tests/test_tracking_containers.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/tests/test_transforms.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/tests/test_validation.py +0 -0
- {nrl_tracker-1.9.1 → nrl_tracker-1.10.0}/tests/unit/test_core.py +0 -0
|
@@ -222,11 +222,12 @@ When porting a function from the original MATLAB library:
|
|
|
222
222
|
|
|
223
223
|
## Current Development Status
|
|
224
224
|
|
|
225
|
-
**Version:** v1.
|
|
225
|
+
**Version:** v1.10.0
|
|
226
226
|
**MATLAB Parity:** 100% ✅
|
|
227
227
|
**Test Suite:** 2,133 tests passing
|
|
228
228
|
**Code Coverage:** 76% (target 80%+ in v2.0.0)
|
|
229
229
|
**Quality:** 100% compliance (black, isort, flake8, mypy --strict)
|
|
230
|
+
**GPU Acceleration:** CuPy (NVIDIA) + MLX (Apple Silicon)
|
|
230
231
|
|
|
231
232
|
## v2.0.0 Roadmap - 8 Phases Over 18 Months
|
|
232
233
|
|
|
@@ -301,12 +302,13 @@ pytest --collect-only -q | tail -1
|
|
|
301
302
|
pytest --cov=pytcl --cov-report=term
|
|
302
303
|
```
|
|
303
304
|
|
|
304
|
-
Current metrics (v1.
|
|
305
|
+
Current metrics (v1.10.0):
|
|
305
306
|
- **Functions:** 1,070+
|
|
306
307
|
- **Modules:** 150+
|
|
307
308
|
- **Tests:** 2,133 (all passing)
|
|
308
309
|
- **Coverage:** 76%
|
|
309
310
|
- **MATLAB Parity:** 100%
|
|
311
|
+
- **GPU Backends:** 2 (CuPy + MLX)
|
|
310
312
|
|
|
311
313
|
### 3. Sync Examples
|
|
312
314
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: nrl-tracker
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.10.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
|
|
@@ -41,11 +41,17 @@ Provides-Extra: optimization
|
|
|
41
41
|
Requires-Dist: cvxpy>=1.3.0; extra == "optimization"
|
|
42
42
|
Provides-Extra: signal
|
|
43
43
|
Requires-Dist: pywavelets>=1.4.0; extra == "signal"
|
|
44
|
+
Provides-Extra: gpu
|
|
45
|
+
Requires-Dist: cupy-cuda12x>=12.0.0; extra == "gpu"
|
|
46
|
+
Provides-Extra: gpu-apple
|
|
47
|
+
Requires-Dist: mlx>=0.5.0; extra == "gpu-apple"
|
|
44
48
|
Provides-Extra: dev
|
|
45
49
|
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
46
50
|
Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
|
|
47
51
|
Requires-Dist: pytest-xdist>=3.0.0; extra == "dev"
|
|
48
52
|
Requires-Dist: pytest-benchmark>=4.0.0; extra == "dev"
|
|
53
|
+
Requires-Dist: pytest-timeout>=2.0.0; extra == "dev"
|
|
54
|
+
Requires-Dist: nbval>=0.10.0; extra == "dev"
|
|
49
55
|
Requires-Dist: hypothesis>=6.0.0; extra == "dev"
|
|
50
56
|
Requires-Dist: black>=23.0.0; extra == "dev"
|
|
51
57
|
Requires-Dist: isort>=5.12.0; extra == "dev"
|
|
@@ -56,6 +62,8 @@ Requires-Dist: sphinx>=6.0.0; extra == "dev"
|
|
|
56
62
|
Requires-Dist: sphinx-rtd-theme>=1.2.0; extra == "dev"
|
|
57
63
|
Requires-Dist: myst-parser>=1.0.0; extra == "dev"
|
|
58
64
|
Requires-Dist: nbsphinx>=0.9.0; extra == "dev"
|
|
65
|
+
Requires-Dist: jupyter>=1.0.0; extra == "dev"
|
|
66
|
+
Requires-Dist: ipykernel>=6.0.0; extra == "dev"
|
|
59
67
|
Provides-Extra: benchmark
|
|
60
68
|
Requires-Dist: pytest-benchmark>=4.0.0; extra == "benchmark"
|
|
61
69
|
Provides-Extra: all
|
|
@@ -63,17 +71,17 @@ Requires-Dist: nrl-tracker[astronomy,dev,geodesy,optimization,signal,visualizati
|
|
|
63
71
|
|
|
64
72
|
# Tracker Component Library (Python)
|
|
65
73
|
|
|
66
|
-
[](https://pypi.org/project/nrl-tracker/)
|
|
67
75
|
[](https://www.python.org/downloads/)
|
|
68
76
|
[](https://en.wikipedia.org/wiki/Public_domain)
|
|
69
77
|
[](https://github.com/psf/black)
|
|
70
|
-
[](https://github.com/nedonatelli/TCL)
|
|
71
79
|
[](docs/gap_analysis.rst)
|
|
72
80
|
[](mypy.ini)
|
|
73
81
|
|
|
74
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.
|
|
75
83
|
|
|
76
|
-
**1,070+ functions** | **153 modules** | **
|
|
84
|
+
**1,070+ functions** | **153 modules** | **2,133 tests** | **100% MATLAB parity**
|
|
77
85
|
|
|
78
86
|
## Overview
|
|
79
87
|
|
|
@@ -90,6 +98,7 @@ The Tracker Component Library provides building blocks for developing target tra
|
|
|
90
98
|
- **Navigation**: Geodetic calculations, INS mechanization, GNSS utilities, INS/GNSS integration
|
|
91
99
|
- **Geophysical Models**: Gravity (WGS84, EGM96/2008), magnetism (WMM, IGRF), atmosphere, tides, terrain
|
|
92
100
|
- **Signal Processing**: Digital filters, matched filtering, CFAR detection, transforms (FFT, STFT, wavelets)
|
|
101
|
+
- **GPU Acceleration**: CuPy (NVIDIA CUDA) and MLX (Apple Silicon) backends for batch Kalman filtering and particle filters
|
|
93
102
|
|
|
94
103
|
## Installation
|
|
95
104
|
|
|
@@ -111,6 +120,12 @@ pip install nrl-tracker[geodesy]
|
|
|
111
120
|
# For visualization
|
|
112
121
|
pip install nrl-tracker[visualization]
|
|
113
122
|
|
|
123
|
+
# For GPU acceleration (NVIDIA CUDA)
|
|
124
|
+
pip install nrl-tracker[gpu]
|
|
125
|
+
|
|
126
|
+
# For GPU acceleration (Apple Silicon M1/M2/M3)
|
|
127
|
+
pip install nrl-tracker[gpu-apple]
|
|
128
|
+
|
|
114
129
|
# For development
|
|
115
130
|
pip install nrl-tracker[dev]
|
|
116
131
|
|
|
@@ -183,6 +198,35 @@ assignment, total_cost = hungarian(cost_matrix)
|
|
|
183
198
|
print(f"Optimal assignment: {assignment}, Total cost: {total_cost}")
|
|
184
199
|
```
|
|
185
200
|
|
|
201
|
+
### GPU Acceleration
|
|
202
|
+
|
|
203
|
+
The library supports GPU acceleration for batch processing of multiple tracks:
|
|
204
|
+
|
|
205
|
+
```python
|
|
206
|
+
from pytcl.gpu import is_gpu_available, get_backend, to_gpu, to_cpu
|
|
207
|
+
|
|
208
|
+
# Check GPU availability (auto-detects CUDA or Apple Silicon)
|
|
209
|
+
if is_gpu_available():
|
|
210
|
+
print(f"GPU available, using {get_backend()} backend")
|
|
211
|
+
|
|
212
|
+
# Transfer data to GPU
|
|
213
|
+
x_gpu = to_gpu(states) # (n_tracks, state_dim)
|
|
214
|
+
P_gpu = to_gpu(covariances) # (n_tracks, state_dim, state_dim)
|
|
215
|
+
|
|
216
|
+
# Use batch Kalman filter operations
|
|
217
|
+
from pytcl.gpu import batch_kf_predict
|
|
218
|
+
x_pred, P_pred = batch_kf_predict(x_gpu, P_gpu, F, Q)
|
|
219
|
+
|
|
220
|
+
# Transfer results back to CPU
|
|
221
|
+
x_pred_cpu = to_cpu(x_pred)
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
**Supported backends:**
|
|
225
|
+
- **NVIDIA CUDA**: Via CuPy (`pip install nrl-tracker[gpu]`)
|
|
226
|
+
- **Apple Silicon**: Via MLX (`pip install nrl-tracker[gpu-apple]`)
|
|
227
|
+
|
|
228
|
+
The backend is automatically selected based on your platform.
|
|
229
|
+
|
|
186
230
|
## Module Structure
|
|
187
231
|
|
|
188
232
|
```
|
|
@@ -202,6 +246,7 @@ pytcl/
|
|
|
202
246
|
├── gravity/ # Gravity models
|
|
203
247
|
├── magnetism/ # Magnetic field models
|
|
204
248
|
├── terrain/ # Terrain elevation models
|
|
249
|
+
├── gpu/ # GPU acceleration (CuPy/MLX)
|
|
205
250
|
└── misc/ # Utilities, visualization
|
|
206
251
|
```
|
|
207
252
|
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
# Tracker Component Library (Python)
|
|
2
2
|
|
|
3
|
-
[](https://pypi.org/project/nrl-tracker/)
|
|
4
4
|
[](https://www.python.org/downloads/)
|
|
5
5
|
[](https://en.wikipedia.org/wiki/Public_domain)
|
|
6
6
|
[](https://github.com/psf/black)
|
|
7
|
-
[](https://github.com/nedonatelli/TCL)
|
|
8
8
|
[](docs/gap_analysis.rst)
|
|
9
9
|
[](mypy.ini)
|
|
10
10
|
|
|
11
11
|
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.
|
|
12
12
|
|
|
13
|
-
**1,070+ functions** | **153 modules** | **
|
|
13
|
+
**1,070+ functions** | **153 modules** | **2,133 tests** | **100% MATLAB parity**
|
|
14
14
|
|
|
15
15
|
## Overview
|
|
16
16
|
|
|
@@ -27,6 +27,7 @@ The Tracker Component Library provides building blocks for developing target tra
|
|
|
27
27
|
- **Navigation**: Geodetic calculations, INS mechanization, GNSS utilities, INS/GNSS integration
|
|
28
28
|
- **Geophysical Models**: Gravity (WGS84, EGM96/2008), magnetism (WMM, IGRF), atmosphere, tides, terrain
|
|
29
29
|
- **Signal Processing**: Digital filters, matched filtering, CFAR detection, transforms (FFT, STFT, wavelets)
|
|
30
|
+
- **GPU Acceleration**: CuPy (NVIDIA CUDA) and MLX (Apple Silicon) backends for batch Kalman filtering and particle filters
|
|
30
31
|
|
|
31
32
|
## Installation
|
|
32
33
|
|
|
@@ -48,6 +49,12 @@ pip install nrl-tracker[geodesy]
|
|
|
48
49
|
# For visualization
|
|
49
50
|
pip install nrl-tracker[visualization]
|
|
50
51
|
|
|
52
|
+
# For GPU acceleration (NVIDIA CUDA)
|
|
53
|
+
pip install nrl-tracker[gpu]
|
|
54
|
+
|
|
55
|
+
# For GPU acceleration (Apple Silicon M1/M2/M3)
|
|
56
|
+
pip install nrl-tracker[gpu-apple]
|
|
57
|
+
|
|
51
58
|
# For development
|
|
52
59
|
pip install nrl-tracker[dev]
|
|
53
60
|
|
|
@@ -120,6 +127,35 @@ assignment, total_cost = hungarian(cost_matrix)
|
|
|
120
127
|
print(f"Optimal assignment: {assignment}, Total cost: {total_cost}")
|
|
121
128
|
```
|
|
122
129
|
|
|
130
|
+
### GPU Acceleration
|
|
131
|
+
|
|
132
|
+
The library supports GPU acceleration for batch processing of multiple tracks:
|
|
133
|
+
|
|
134
|
+
```python
|
|
135
|
+
from pytcl.gpu import is_gpu_available, get_backend, to_gpu, to_cpu
|
|
136
|
+
|
|
137
|
+
# Check GPU availability (auto-detects CUDA or Apple Silicon)
|
|
138
|
+
if is_gpu_available():
|
|
139
|
+
print(f"GPU available, using {get_backend()} backend")
|
|
140
|
+
|
|
141
|
+
# Transfer data to GPU
|
|
142
|
+
x_gpu = to_gpu(states) # (n_tracks, state_dim)
|
|
143
|
+
P_gpu = to_gpu(covariances) # (n_tracks, state_dim, state_dim)
|
|
144
|
+
|
|
145
|
+
# Use batch Kalman filter operations
|
|
146
|
+
from pytcl.gpu import batch_kf_predict
|
|
147
|
+
x_pred, P_pred = batch_kf_predict(x_gpu, P_gpu, F, Q)
|
|
148
|
+
|
|
149
|
+
# Transfer results back to CPU
|
|
150
|
+
x_pred_cpu = to_cpu(x_pred)
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
**Supported backends:**
|
|
154
|
+
- **NVIDIA CUDA**: Via CuPy (`pip install nrl-tracker[gpu]`)
|
|
155
|
+
- **Apple Silicon**: Via MLX (`pip install nrl-tracker[gpu-apple]`)
|
|
156
|
+
|
|
157
|
+
The backend is automatically selected based on your platform.
|
|
158
|
+
|
|
123
159
|
## Module Structure
|
|
124
160
|
|
|
125
161
|
```
|
|
@@ -139,6 +175,7 @@ pytcl/
|
|
|
139
175
|
├── gravity/ # Gravity models
|
|
140
176
|
├── magnetism/ # Magnetic field models
|
|
141
177
|
├── terrain/ # Terrain elevation models
|
|
178
|
+
├── gpu/ # GPU acceleration (CuPy/MLX)
|
|
142
179
|
└── misc/ # Utilities, visualization
|
|
143
180
|
```
|
|
144
181
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: nrl-tracker
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.10.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
|
|
@@ -41,11 +41,17 @@ Provides-Extra: optimization
|
|
|
41
41
|
Requires-Dist: cvxpy>=1.3.0; extra == "optimization"
|
|
42
42
|
Provides-Extra: signal
|
|
43
43
|
Requires-Dist: pywavelets>=1.4.0; extra == "signal"
|
|
44
|
+
Provides-Extra: gpu
|
|
45
|
+
Requires-Dist: cupy-cuda12x>=12.0.0; extra == "gpu"
|
|
46
|
+
Provides-Extra: gpu-apple
|
|
47
|
+
Requires-Dist: mlx>=0.5.0; extra == "gpu-apple"
|
|
44
48
|
Provides-Extra: dev
|
|
45
49
|
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
46
50
|
Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
|
|
47
51
|
Requires-Dist: pytest-xdist>=3.0.0; extra == "dev"
|
|
48
52
|
Requires-Dist: pytest-benchmark>=4.0.0; extra == "dev"
|
|
53
|
+
Requires-Dist: pytest-timeout>=2.0.0; extra == "dev"
|
|
54
|
+
Requires-Dist: nbval>=0.10.0; extra == "dev"
|
|
49
55
|
Requires-Dist: hypothesis>=6.0.0; extra == "dev"
|
|
50
56
|
Requires-Dist: black>=23.0.0; extra == "dev"
|
|
51
57
|
Requires-Dist: isort>=5.12.0; extra == "dev"
|
|
@@ -56,6 +62,8 @@ Requires-Dist: sphinx>=6.0.0; extra == "dev"
|
|
|
56
62
|
Requires-Dist: sphinx-rtd-theme>=1.2.0; extra == "dev"
|
|
57
63
|
Requires-Dist: myst-parser>=1.0.0; extra == "dev"
|
|
58
64
|
Requires-Dist: nbsphinx>=0.9.0; extra == "dev"
|
|
65
|
+
Requires-Dist: jupyter>=1.0.0; extra == "dev"
|
|
66
|
+
Requires-Dist: ipykernel>=6.0.0; extra == "dev"
|
|
59
67
|
Provides-Extra: benchmark
|
|
60
68
|
Requires-Dist: pytest-benchmark>=4.0.0; extra == "benchmark"
|
|
61
69
|
Provides-Extra: all
|
|
@@ -63,17 +71,17 @@ Requires-Dist: nrl-tracker[astronomy,dev,geodesy,optimization,signal,visualizati
|
|
|
63
71
|
|
|
64
72
|
# Tracker Component Library (Python)
|
|
65
73
|
|
|
66
|
-
[](https://pypi.org/project/nrl-tracker/)
|
|
67
75
|
[](https://www.python.org/downloads/)
|
|
68
76
|
[](https://en.wikipedia.org/wiki/Public_domain)
|
|
69
77
|
[](https://github.com/psf/black)
|
|
70
|
-
[](https://github.com/nedonatelli/TCL)
|
|
71
79
|
[](docs/gap_analysis.rst)
|
|
72
80
|
[](mypy.ini)
|
|
73
81
|
|
|
74
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.
|
|
75
83
|
|
|
76
|
-
**1,070+ functions** | **153 modules** | **
|
|
84
|
+
**1,070+ functions** | **153 modules** | **2,133 tests** | **100% MATLAB parity**
|
|
77
85
|
|
|
78
86
|
## Overview
|
|
79
87
|
|
|
@@ -90,6 +98,7 @@ The Tracker Component Library provides building blocks for developing target tra
|
|
|
90
98
|
- **Navigation**: Geodetic calculations, INS mechanization, GNSS utilities, INS/GNSS integration
|
|
91
99
|
- **Geophysical Models**: Gravity (WGS84, EGM96/2008), magnetism (WMM, IGRF), atmosphere, tides, terrain
|
|
92
100
|
- **Signal Processing**: Digital filters, matched filtering, CFAR detection, transforms (FFT, STFT, wavelets)
|
|
101
|
+
- **GPU Acceleration**: CuPy (NVIDIA CUDA) and MLX (Apple Silicon) backends for batch Kalman filtering and particle filters
|
|
93
102
|
|
|
94
103
|
## Installation
|
|
95
104
|
|
|
@@ -111,6 +120,12 @@ pip install nrl-tracker[geodesy]
|
|
|
111
120
|
# For visualization
|
|
112
121
|
pip install nrl-tracker[visualization]
|
|
113
122
|
|
|
123
|
+
# For GPU acceleration (NVIDIA CUDA)
|
|
124
|
+
pip install nrl-tracker[gpu]
|
|
125
|
+
|
|
126
|
+
# For GPU acceleration (Apple Silicon M1/M2/M3)
|
|
127
|
+
pip install nrl-tracker[gpu-apple]
|
|
128
|
+
|
|
114
129
|
# For development
|
|
115
130
|
pip install nrl-tracker[dev]
|
|
116
131
|
|
|
@@ -183,6 +198,35 @@ assignment, total_cost = hungarian(cost_matrix)
|
|
|
183
198
|
print(f"Optimal assignment: {assignment}, Total cost: {total_cost}")
|
|
184
199
|
```
|
|
185
200
|
|
|
201
|
+
### GPU Acceleration
|
|
202
|
+
|
|
203
|
+
The library supports GPU acceleration for batch processing of multiple tracks:
|
|
204
|
+
|
|
205
|
+
```python
|
|
206
|
+
from pytcl.gpu import is_gpu_available, get_backend, to_gpu, to_cpu
|
|
207
|
+
|
|
208
|
+
# Check GPU availability (auto-detects CUDA or Apple Silicon)
|
|
209
|
+
if is_gpu_available():
|
|
210
|
+
print(f"GPU available, using {get_backend()} backend")
|
|
211
|
+
|
|
212
|
+
# Transfer data to GPU
|
|
213
|
+
x_gpu = to_gpu(states) # (n_tracks, state_dim)
|
|
214
|
+
P_gpu = to_gpu(covariances) # (n_tracks, state_dim, state_dim)
|
|
215
|
+
|
|
216
|
+
# Use batch Kalman filter operations
|
|
217
|
+
from pytcl.gpu import batch_kf_predict
|
|
218
|
+
x_pred, P_pred = batch_kf_predict(x_gpu, P_gpu, F, Q)
|
|
219
|
+
|
|
220
|
+
# Transfer results back to CPU
|
|
221
|
+
x_pred_cpu = to_cpu(x_pred)
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
**Supported backends:**
|
|
225
|
+
- **NVIDIA CUDA**: Via CuPy (`pip install nrl-tracker[gpu]`)
|
|
226
|
+
- **Apple Silicon**: Via MLX (`pip install nrl-tracker[gpu-apple]`)
|
|
227
|
+
|
|
228
|
+
The backend is automatically selected based on your platform.
|
|
229
|
+
|
|
186
230
|
## Module Structure
|
|
187
231
|
|
|
188
232
|
```
|
|
@@ -202,6 +246,7 @@ pytcl/
|
|
|
202
246
|
├── gravity/ # Gravity models
|
|
203
247
|
├── magnetism/ # Magnetic field models
|
|
204
248
|
├── terrain/ # Terrain elevation models
|
|
249
|
+
├── gpu/ # GPU acceleration (CuPy/MLX)
|
|
205
250
|
└── misc/ # Utilities, visualization
|
|
206
251
|
```
|
|
207
252
|
|
|
@@ -65,6 +65,7 @@ pytcl/core/__init__.py
|
|
|
65
65
|
pytcl/core/array_utils.py
|
|
66
66
|
pytcl/core/constants.py
|
|
67
67
|
pytcl/core/exceptions.py
|
|
68
|
+
pytcl/core/maturity.py
|
|
68
69
|
pytcl/core/optional_deps.py
|
|
69
70
|
pytcl/core/validation.py
|
|
70
71
|
pytcl/dynamic_estimation/__init__.py
|
|
@@ -99,6 +100,13 @@ pytcl/dynamic_models/process_noise/__init__.py
|
|
|
99
100
|
pytcl/dynamic_models/process_noise/coordinated_turn.py
|
|
100
101
|
pytcl/dynamic_models/process_noise/polynomial.py
|
|
101
102
|
pytcl/dynamic_models/process_noise/singer.py
|
|
103
|
+
pytcl/gpu/__init__.py
|
|
104
|
+
pytcl/gpu/ekf.py
|
|
105
|
+
pytcl/gpu/kalman.py
|
|
106
|
+
pytcl/gpu/matrix_utils.py
|
|
107
|
+
pytcl/gpu/particle_filter.py
|
|
108
|
+
pytcl/gpu/ukf.py
|
|
109
|
+
pytcl/gpu/utils.py
|
|
102
110
|
pytcl/gravity/__init__.py
|
|
103
111
|
pytcl/gravity/clenshaw.py
|
|
104
112
|
pytcl/gravity/egm.py
|
|
@@ -192,6 +200,8 @@ tests/test_exceptions.py
|
|
|
192
200
|
tests/test_gaussian_mixtures.py
|
|
193
201
|
tests/test_gaussian_sum_filter.py
|
|
194
202
|
tests/test_geophysical.py
|
|
203
|
+
tests/test_gpu.py
|
|
204
|
+
tests/test_gpu_utils.py
|
|
195
205
|
tests/test_great_circle.py
|
|
196
206
|
tests/test_h_infinity.py
|
|
197
207
|
tests/test_ins.py
|
|
@@ -17,6 +17,8 @@ pytest>=7.0.0
|
|
|
17
17
|
pytest-cov>=4.0.0
|
|
18
18
|
pytest-xdist>=3.0.0
|
|
19
19
|
pytest-benchmark>=4.0.0
|
|
20
|
+
pytest-timeout>=2.0.0
|
|
21
|
+
nbval>=0.10.0
|
|
20
22
|
hypothesis>=6.0.0
|
|
21
23
|
black>=23.0.0
|
|
22
24
|
isort>=5.12.0
|
|
@@ -27,11 +29,19 @@ sphinx>=6.0.0
|
|
|
27
29
|
sphinx-rtd-theme>=1.2.0
|
|
28
30
|
myst-parser>=1.0.0
|
|
29
31
|
nbsphinx>=0.9.0
|
|
32
|
+
jupyter>=1.0.0
|
|
33
|
+
ipykernel>=6.0.0
|
|
30
34
|
|
|
31
35
|
[geodesy]
|
|
32
36
|
pyproj>=3.4.0
|
|
33
37
|
geographiclib>=2.0
|
|
34
38
|
|
|
39
|
+
[gpu]
|
|
40
|
+
cupy-cuda12x>=12.0.0
|
|
41
|
+
|
|
42
|
+
[gpu-apple]
|
|
43
|
+
mlx>=0.5.0
|
|
44
|
+
|
|
35
45
|
[optimization]
|
|
36
46
|
cvxpy>=1.3.0
|
|
37
47
|
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "nrl-tracker"
|
|
7
|
-
version = "1.
|
|
7
|
+
version = "1.10.0"
|
|
8
8
|
description = "Python port of the U.S. Naval Research Laboratory's Tracker Component Library for target tracking algorithms"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
authors = [
|
|
@@ -63,11 +63,19 @@ optimization = [
|
|
|
63
63
|
signal = [
|
|
64
64
|
"pywavelets>=1.4.0",
|
|
65
65
|
]
|
|
66
|
+
gpu = [
|
|
67
|
+
"cupy-cuda12x>=12.0.0",
|
|
68
|
+
]
|
|
69
|
+
gpu-apple = [
|
|
70
|
+
"mlx>=0.5.0",
|
|
71
|
+
]
|
|
66
72
|
dev = [
|
|
67
73
|
"pytest>=7.0.0",
|
|
68
74
|
"pytest-cov>=4.0.0",
|
|
69
75
|
"pytest-xdist>=3.0.0",
|
|
70
76
|
"pytest-benchmark>=4.0.0",
|
|
77
|
+
"pytest-timeout>=2.0.0",
|
|
78
|
+
"nbval>=0.10.0",
|
|
71
79
|
"hypothesis>=6.0.0",
|
|
72
80
|
"black>=23.0.0",
|
|
73
81
|
"isort>=5.12.0",
|
|
@@ -78,6 +86,8 @@ dev = [
|
|
|
78
86
|
"sphinx-rtd-theme>=1.2.0",
|
|
79
87
|
"myst-parser>=1.0.0",
|
|
80
88
|
"nbsphinx>=0.9.0",
|
|
89
|
+
"jupyter>=1.0.0",
|
|
90
|
+
"ipykernel>=6.0.0",
|
|
81
91
|
]
|
|
82
92
|
benchmark = [
|
|
83
93
|
"pytest-benchmark>=4.0.0",
|
|
@@ -107,6 +117,7 @@ markers = [
|
|
|
107
117
|
"benchmark: marks tests as benchmarks",
|
|
108
118
|
"light: marks benchmark as part of light (PR) suite",
|
|
109
119
|
"full: marks benchmark as part of full (main) suite",
|
|
120
|
+
"notebook: marks notebook validation tests",
|
|
110
121
|
]
|
|
111
122
|
filterwarnings = [
|
|
112
123
|
"ignore::DeprecationWarning",
|
|
@@ -176,5 +187,7 @@ module = [
|
|
|
176
187
|
"plotly.*",
|
|
177
188
|
"cvxpy.*",
|
|
178
189
|
"pywt.*",
|
|
190
|
+
"cupy.*",
|
|
191
|
+
"mlx.*",
|
|
179
192
|
]
|
|
180
193
|
ignore_missing_imports = true
|
|
@@ -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.
|
|
9
|
+
**Current Version:** 1.10.0 (January 4, 2026)
|
|
10
10
|
**Status:** Production-ready, 2,133 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.
|
|
24
|
+
__version__ = "1.10.0"
|
|
25
25
|
__author__ = "Python Port Contributors"
|
|
26
26
|
__original_author__ = "David F. Crouse, Naval Research Laboratory"
|
|
27
27
|
|
|
@@ -322,6 +322,24 @@ def compute_gate_volume(
|
|
|
322
322
|
V = c_m * sqrt(det(S)) * gamma^(m/2)
|
|
323
323
|
|
|
324
324
|
where c_m is the volume of the unit hypersphere in m dimensions.
|
|
325
|
+
|
|
326
|
+
Examples
|
|
327
|
+
--------
|
|
328
|
+
Compute gate volume for a 2D measurement with 99% gate probability:
|
|
329
|
+
|
|
330
|
+
>>> import numpy as np
|
|
331
|
+
>>> from scipy.stats import chi2
|
|
332
|
+
>>> S = np.array([[4.0, 0.0], [0.0, 1.0]]) # innovation covariance
|
|
333
|
+
>>> gate_prob = 0.99
|
|
334
|
+
>>> threshold = chi2.ppf(gate_prob, df=2)
|
|
335
|
+
>>> volume = compute_gate_volume(S, threshold)
|
|
336
|
+
>>> volume > 0
|
|
337
|
+
True
|
|
338
|
+
|
|
339
|
+
See Also
|
|
340
|
+
--------
|
|
341
|
+
ellipsoidal_gate : Test if measurement passes gate.
|
|
342
|
+
mahalanobis_distance : Compute distance used in gating.
|
|
325
343
|
"""
|
|
326
344
|
S = np.asarray(innovation_covariance, dtype=np.float64)
|
|
327
345
|
m = S.shape[0]
|
|
@@ -136,6 +136,22 @@ def compute_likelihood_matrix(
|
|
|
136
136
|
Likelihood values, shape (n_tracks, n_meas).
|
|
137
137
|
gated : ndarray[Any]
|
|
138
138
|
Boolean gating matrix, shape (n_tracks, n_meas).
|
|
139
|
+
|
|
140
|
+
Examples
|
|
141
|
+
--------
|
|
142
|
+
>>> import numpy as np
|
|
143
|
+
>>> # Two tracks, two measurements
|
|
144
|
+
>>> states = [np.array([0.0, 1.0]), np.array([5.0, 0.0])]
|
|
145
|
+
>>> covs = [np.eye(2) * 0.5, np.eye(2) * 0.5]
|
|
146
|
+
>>> measurements = np.array([[0.1], [5.2]])
|
|
147
|
+
>>> H = np.array([[1, 0]])
|
|
148
|
+
>>> R = np.array([[0.1]])
|
|
149
|
+
>>> L, gated = compute_likelihood_matrix(states, covs, measurements, H, R)
|
|
150
|
+
>>> L.shape
|
|
151
|
+
(2, 2)
|
|
152
|
+
>>> # Track 0 should have high likelihood for measurement 0
|
|
153
|
+
>>> L[0, 0] > L[0, 1]
|
|
154
|
+
True
|
|
139
155
|
"""
|
|
140
156
|
n_tracks = len(track_states)
|
|
141
157
|
n_meas = len(measurements)
|
|
@@ -191,6 +207,23 @@ def jpda_probabilities(
|
|
|
191
207
|
Association probability matrix, shape (n_tracks, n_meas + 1).
|
|
192
208
|
beta[i, j] = P(measurement j is from track i) for j < n_meas.
|
|
193
209
|
beta[i, n_meas] = P(track i has no measurement).
|
|
210
|
+
|
|
211
|
+
Examples
|
|
212
|
+
--------
|
|
213
|
+
>>> import numpy as np
|
|
214
|
+
>>> # Likelihood matrix: 2 tracks, 2 measurements
|
|
215
|
+
>>> # Track 0 has high likelihood for meas 0
|
|
216
|
+
>>> # Track 1 has high likelihood for meas 1
|
|
217
|
+
>>> likelihood = np.array([[0.9, 0.1],
|
|
218
|
+
... [0.1, 0.8]])
|
|
219
|
+
>>> gated = np.array([[True, True],
|
|
220
|
+
... [True, True]])
|
|
221
|
+
>>> beta = jpda_probabilities(likelihood, gated, detection_prob=0.9)
|
|
222
|
+
>>> beta.shape # 2 tracks, 3 columns (2 meas + 1 miss)
|
|
223
|
+
(2, 3)
|
|
224
|
+
>>> # Track 0 most likely associated with measurement 0
|
|
225
|
+
>>> np.argmax(beta[0, :2])
|
|
226
|
+
0
|
|
194
227
|
"""
|
|
195
228
|
n_tracks, n_meas = likelihood_matrix.shape
|
|
196
229
|
|
|
@@ -591,6 +624,29 @@ def jpda(
|
|
|
591
624
|
-------
|
|
592
625
|
result : JPDAResult
|
|
593
626
|
Association probabilities and related information.
|
|
627
|
+
|
|
628
|
+
Examples
|
|
629
|
+
--------
|
|
630
|
+
Compute association probabilities for 2 tracks and 3 measurements:
|
|
631
|
+
|
|
632
|
+
>>> import numpy as np
|
|
633
|
+
>>> # Two tracks with [x, vx] state
|
|
634
|
+
>>> states = [np.array([0.0, 1.0]), np.array([10.0, -0.5])]
|
|
635
|
+
>>> covariances = [np.eye(2) * 0.5, np.eye(2) * 0.5]
|
|
636
|
+
>>> # Three position measurements
|
|
637
|
+
>>> measurements = np.array([[0.1], [9.8], [5.0]])
|
|
638
|
+
>>> H = np.array([[1, 0]]) # measure position only
|
|
639
|
+
>>> R = np.array([[0.1]])
|
|
640
|
+
>>> result = jpda(states, covariances, measurements, H, R)
|
|
641
|
+
>>> result.association_probs.shape # (2 tracks, 4 columns: 3 meas + miss)
|
|
642
|
+
(2, 4)
|
|
643
|
+
>>> # Track 0 should have high prob for measurement 0
|
|
644
|
+
>>> result.association_probs[0, 0] > 0.5
|
|
645
|
+
True
|
|
646
|
+
|
|
647
|
+
See Also
|
|
648
|
+
--------
|
|
649
|
+
jpda_update : JPDA with state update.
|
|
594
650
|
"""
|
|
595
651
|
# Convert inputs
|
|
596
652
|
track_states = [np.asarray(x, dtype=np.float64).flatten() for x in track_states]
|
|
@@ -67,6 +67,20 @@ def validate_cost_tensor(cost_tensor: NDArray[np.float64]) -> Tuple[int, ...]:
|
|
|
67
67
|
------
|
|
68
68
|
ValueError
|
|
69
69
|
If tensor has fewer than 2 dimensions.
|
|
70
|
+
|
|
71
|
+
Examples
|
|
72
|
+
--------
|
|
73
|
+
>>> import numpy as np
|
|
74
|
+
>>> cost = np.random.rand(3, 4, 5)
|
|
75
|
+
>>> dims = validate_cost_tensor(cost)
|
|
76
|
+
>>> dims
|
|
77
|
+
(3, 4, 5)
|
|
78
|
+
>>> # 1D tensor should raise error
|
|
79
|
+
>>> try:
|
|
80
|
+
... validate_cost_tensor(np.array([1, 2, 3]))
|
|
81
|
+
... except ValueError:
|
|
82
|
+
... print("Caught expected error")
|
|
83
|
+
Caught expected error
|
|
70
84
|
"""
|
|
71
85
|
if cost_tensor.ndim < 2:
|
|
72
86
|
raise ValueError(
|
|
@@ -98,6 +112,21 @@ def greedy_assignment_nd(
|
|
|
98
112
|
AssignmentNDResult
|
|
99
113
|
Assignments, total cost, and algorithm info.
|
|
100
114
|
|
|
115
|
+
Examples
|
|
116
|
+
--------
|
|
117
|
+
>>> import numpy as np
|
|
118
|
+
>>> # 3D cost tensor: 3 measurements x 2 tracks x 2 hypotheses
|
|
119
|
+
>>> cost = np.array([
|
|
120
|
+
... [[1.0, 5.0], [3.0, 2.0]], # meas 0
|
|
121
|
+
... [[4.0, 1.0], [2.0, 6.0]], # meas 1
|
|
122
|
+
... [[2.0, 3.0], [5.0, 1.0]], # meas 2
|
|
123
|
+
... ])
|
|
124
|
+
>>> result = greedy_assignment_nd(cost)
|
|
125
|
+
>>> result.cost # Total cost of greedy solution
|
|
126
|
+
4.0
|
|
127
|
+
>>> len(result.assignments) # Number of assignments made
|
|
128
|
+
2
|
|
129
|
+
|
|
101
130
|
Notes
|
|
102
131
|
-----
|
|
103
132
|
Greedy assignment is fast O(n log n) but not optimal. Used as
|
|
@@ -178,6 +207,18 @@ def relaxation_assignment_nd(
|
|
|
178
207
|
AssignmentNDResult
|
|
179
208
|
Assignments, total cost, convergence info, and optimality gap.
|
|
180
209
|
|
|
210
|
+
Examples
|
|
211
|
+
--------
|
|
212
|
+
>>> import numpy as np
|
|
213
|
+
>>> # 3x3x3 assignment problem
|
|
214
|
+
>>> np.random.seed(42)
|
|
215
|
+
>>> cost = np.random.rand(3, 3, 3)
|
|
216
|
+
>>> result = relaxation_assignment_nd(cost, max_iterations=50)
|
|
217
|
+
>>> result.converged
|
|
218
|
+
True
|
|
219
|
+
>>> result.assignments.shape[1] # 3D assignments
|
|
220
|
+
3
|
|
221
|
+
|
|
181
222
|
Notes
|
|
182
223
|
-----
|
|
183
224
|
The relaxation approach:
|
|
@@ -297,6 +338,18 @@ def auction_assignment_nd(
|
|
|
297
338
|
AssignmentNDResult
|
|
298
339
|
Assignments, total cost, convergence info, gap estimate.
|
|
299
340
|
|
|
341
|
+
Examples
|
|
342
|
+
--------
|
|
343
|
+
>>> import numpy as np
|
|
344
|
+
>>> # 4D assignment: sensors x measurements x tracks x hypotheses
|
|
345
|
+
>>> np.random.seed(123)
|
|
346
|
+
>>> cost = np.random.rand(2, 3, 3, 2) * 10
|
|
347
|
+
>>> result = auction_assignment_nd(cost, max_iterations=50, epsilon=0.1)
|
|
348
|
+
>>> len(result.assignments) > 0
|
|
349
|
+
True
|
|
350
|
+
>>> result.n_iterations <= 50
|
|
351
|
+
True
|
|
352
|
+
|
|
300
353
|
Notes
|
|
301
354
|
-----
|
|
302
355
|
The algorithm maintains a "price" for each index and allows bidding
|
|
@@ -368,6 +421,18 @@ def detect_dimension_conflicts(
|
|
|
368
421
|
-------
|
|
369
422
|
has_conflicts : bool
|
|
370
423
|
True if any index appears more than once in any dimension.
|
|
424
|
+
|
|
425
|
+
Examples
|
|
426
|
+
--------
|
|
427
|
+
>>> import numpy as np
|
|
428
|
+
>>> # Valid assignment: no index repeated in any dimension
|
|
429
|
+
>>> assignments = np.array([[0, 0], [1, 1]])
|
|
430
|
+
>>> detect_dimension_conflicts(assignments, (3, 3))
|
|
431
|
+
False
|
|
432
|
+
>>> # Invalid: index 0 used twice in first dimension
|
|
433
|
+
>>> assignments = np.array([[0, 0], [0, 1]])
|
|
434
|
+
>>> detect_dimension_conflicts(assignments, (3, 3))
|
|
435
|
+
True
|
|
371
436
|
"""
|
|
372
437
|
n_dims = len(dims)
|
|
373
438
|
|