nrl-tracker 1.1.3__tar.gz → 1.2.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.1.3/nrl_tracker.egg-info → nrl_tracker-1.2.0}/PKG-INFO +1 -1
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0/nrl_tracker.egg-info}/PKG-INFO +1 -1
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/nrl_tracker.egg-info/SOURCES.txt +3 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pyproject.toml +1 -1
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/__init__.py +1 -1
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/astronomical/reference_frames.py +127 -55
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/containers/__init__.py +24 -0
- nrl_tracker-1.2.0/pytcl/containers/base.py +219 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/containers/covertree.py +21 -26
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/containers/kd_tree.py +94 -29
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/containers/vptree.py +17 -26
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/core/__init__.py +18 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/core/validation.py +331 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/gravity/egm.py +13 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/gravity/spherical_harmonics.py +97 -36
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/mathematical_functions/special_functions/hypergeometric.py +79 -15
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/navigation/geodesy.py +245 -159
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/navigation/great_circle.py +98 -16
- nrl_tracker-1.2.0/tests/test_spatial_containers_parametrized.py +467 -0
- nrl_tracker-1.2.0/tests/test_validation.py +482 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/CONTRIBUTING.md +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/LICENSE +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/MANIFEST.in +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/README.md +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/nrl_tracker.egg-info/dependency_links.txt +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/nrl_tracker.egg-info/requires.txt +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/nrl_tracker.egg-info/top_level.txt +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/assignment_algorithms/__init__.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/assignment_algorithms/data_association.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/assignment_algorithms/gating.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/assignment_algorithms/jpda.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/assignment_algorithms/three_dimensional/__init__.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/assignment_algorithms/three_dimensional/assignment.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/assignment_algorithms/two_dimensional/__init__.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/assignment_algorithms/two_dimensional/assignment.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/assignment_algorithms/two_dimensional/kbest.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/astronomical/__init__.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/astronomical/ephemerides.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/astronomical/lambert.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/astronomical/orbital_mechanics.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/astronomical/relativity.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/astronomical/time_systems.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/atmosphere/__init__.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/atmosphere/models.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/clustering/__init__.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/clustering/dbscan.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/clustering/gaussian_mixture.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/clustering/hierarchical.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/clustering/kmeans.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/containers/cluster_set.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/containers/measurement_set.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/containers/rtree.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/containers/track_list.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/coordinate_systems/__init__.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/coordinate_systems/conversions/__init__.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/coordinate_systems/conversions/geodetic.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/coordinate_systems/conversions/spherical.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/coordinate_systems/jacobians/__init__.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/coordinate_systems/jacobians/jacobians.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/coordinate_systems/projections/__init__.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/coordinate_systems/projections/projections.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/coordinate_systems/rotations/__init__.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/coordinate_systems/rotations/rotations.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/core/array_utils.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/core/constants.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/dynamic_estimation/__init__.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/dynamic_estimation/batch_estimation/__init__.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/dynamic_estimation/imm.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/dynamic_estimation/information_filter.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/dynamic_estimation/kalman/__init__.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/dynamic_estimation/kalman/extended.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/dynamic_estimation/kalman/linear.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/dynamic_estimation/kalman/square_root.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/dynamic_estimation/kalman/unscented.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/dynamic_estimation/measurement_update/__init__.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/dynamic_estimation/particle_filters/__init__.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/dynamic_estimation/particle_filters/bootstrap.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/dynamic_estimation/smoothers.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/dynamic_models/__init__.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/dynamic_models/continuous_time/__init__.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/dynamic_models/continuous_time/dynamics.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/dynamic_models/discrete_time/__init__.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/dynamic_models/discrete_time/coordinated_turn.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/dynamic_models/discrete_time/polynomial.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/dynamic_models/discrete_time/singer.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/dynamic_models/process_noise/__init__.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/dynamic_models/process_noise/coordinated_turn.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/dynamic_models/process_noise/polynomial.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/dynamic_models/process_noise/singer.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/gravity/__init__.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/gravity/clenshaw.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/gravity/models.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/gravity/tides.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/logging_config.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/magnetism/__init__.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/magnetism/emm.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/magnetism/igrf.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/magnetism/wmm.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/mathematical_functions/__init__.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/mathematical_functions/basic_matrix/__init__.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/mathematical_functions/basic_matrix/decompositions.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/mathematical_functions/basic_matrix/special_matrices.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/mathematical_functions/combinatorics/__init__.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/mathematical_functions/combinatorics/combinatorics.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/mathematical_functions/continuous_optimization/__init__.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/mathematical_functions/geometry/__init__.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/mathematical_functions/geometry/geometry.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/mathematical_functions/interpolation/__init__.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/mathematical_functions/interpolation/interpolation.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/mathematical_functions/numerical_integration/__init__.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/mathematical_functions/numerical_integration/quadrature.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/mathematical_functions/polynomials/__init__.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/mathematical_functions/signal_processing/__init__.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/mathematical_functions/signal_processing/detection.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/mathematical_functions/signal_processing/filters.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/mathematical_functions/signal_processing/matched_filter.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/mathematical_functions/special_functions/__init__.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/mathematical_functions/special_functions/bessel.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/mathematical_functions/special_functions/debye.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/mathematical_functions/special_functions/elliptic.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/mathematical_functions/special_functions/error_functions.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/mathematical_functions/special_functions/gamma_functions.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/mathematical_functions/special_functions/lambert_w.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/mathematical_functions/special_functions/marcum_q.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/mathematical_functions/statistics/__init__.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/mathematical_functions/statistics/distributions.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/mathematical_functions/statistics/estimators.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/mathematical_functions/transforms/__init__.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/mathematical_functions/transforms/fourier.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/mathematical_functions/transforms/stft.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/mathematical_functions/transforms/wavelets.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/misc/__init__.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/navigation/__init__.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/navigation/ins.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/navigation/ins_gnss.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/navigation/rhumb.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/performance_evaluation/__init__.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/performance_evaluation/estimation_metrics.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/performance_evaluation/track_metrics.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/physical_values/__init__.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/plotting/__init__.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/plotting/coordinates.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/plotting/ellipses.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/plotting/metrics.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/plotting/tracks.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/scheduling/__init__.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/static_estimation/__init__.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/static_estimation/least_squares.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/static_estimation/maximum_likelihood.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/static_estimation/robust.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/terrain/__init__.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/terrain/dem.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/terrain/loaders.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/terrain/visibility.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/trackers/__init__.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/trackers/hypothesis.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/trackers/mht.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/trackers/multi_target.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/trackers/single_target.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/pytcl/transponders/__init__.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/setup.cfg +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/tests/__init__.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/tests/conftest.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/tests/test_additional_trees.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/tests/test_assignment_algorithms.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/tests/test_astronomical.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/tests/test_clustering.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/tests/test_coordinate_systems.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/tests/test_coverage_boost.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/tests/test_coverage_boost_2.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/tests/test_dynamic_models.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/tests/test_egm.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/tests/test_emm.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/tests/test_ephemerides.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/tests/test_gaussian_mixtures.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/tests/test_geophysical.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/tests/test_great_circle.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/tests/test_ins.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/tests/test_ins_gnss.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/tests/test_kalman_filters.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/tests/test_mathematical_functions.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/tests/test_maximum_likelihood.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/tests/test_mht.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/tests/test_performance_evaluation.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/tests/test_phase6_specialized.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/tests/test_plotting.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/tests/test_projections.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/tests/test_relativity.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/tests/test_rhumb.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/tests/test_signal_processing.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/tests/test_smoothers.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/tests/test_spatial_structures.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/tests/test_special_functions_phase12.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/tests/test_static_estimation.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/tests/test_terrain.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/tests/test_terrain_loaders.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/tests/test_tides.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/tests/test_trackers.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/tests/test_tracking_containers.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/tests/test_transforms.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/tests/test_v030_comprehensive.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/tests/test_v030_features.py +0 -0
- {nrl_tracker-1.1.3 → nrl_tracker-1.2.0}/tests/unit/test_core.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: nrl-tracker
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.2.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,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: nrl-tracker
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.2.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
|
|
@@ -34,6 +34,7 @@ pytcl/clustering/gaussian_mixture.py
|
|
|
34
34
|
pytcl/clustering/hierarchical.py
|
|
35
35
|
pytcl/clustering/kmeans.py
|
|
36
36
|
pytcl/containers/__init__.py
|
|
37
|
+
pytcl/containers/base.py
|
|
37
38
|
pytcl/containers/cluster_set.py
|
|
38
39
|
pytcl/containers/covertree.py
|
|
39
40
|
pytcl/containers/kd_tree.py
|
|
@@ -184,6 +185,7 @@ tests/test_relativity.py
|
|
|
184
185
|
tests/test_rhumb.py
|
|
185
186
|
tests/test_signal_processing.py
|
|
186
187
|
tests/test_smoothers.py
|
|
188
|
+
tests/test_spatial_containers_parametrized.py
|
|
187
189
|
tests/test_spatial_structures.py
|
|
188
190
|
tests/test_special_functions_phase12.py
|
|
189
191
|
tests/test_static_estimation.py
|
|
@@ -195,4 +197,5 @@ tests/test_tracking_containers.py
|
|
|
195
197
|
tests/test_transforms.py
|
|
196
198
|
tests/test_v030_comprehensive.py
|
|
197
199
|
tests/test_v030_features.py
|
|
200
|
+
tests/test_validation.py
|
|
198
201
|
tests/unit/test_core.py
|
|
@@ -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.2.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 = [
|
|
@@ -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
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
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,27 @@ Containers module.
|
|
|
3
3
|
|
|
4
4
|
This module provides spatial data structures for efficient
|
|
5
5
|
nearest neighbor queries, spatial indexing, and tracking containers.
|
|
6
|
+
|
|
7
|
+
Spatial Index Hierarchy
|
|
8
|
+
-----------------------
|
|
9
|
+
All spatial index structures inherit from BaseSpatialIndex which defines
|
|
10
|
+
a common interface for k-nearest neighbor and radius queries:
|
|
11
|
+
|
|
12
|
+
BaseSpatialIndex (abstract)
|
|
13
|
+
├── KDTree - K-dimensional tree (Euclidean space)
|
|
14
|
+
├── BallTree - Ball tree variant of KD-tree
|
|
15
|
+
├── RTree - Rectangle tree for bounding boxes
|
|
16
|
+
└── MetricSpatialIndex (abstract)
|
|
17
|
+
├── VPTree - Vantage point tree (any metric)
|
|
18
|
+
└── CoverTree - Cover tree (any metric)
|
|
6
19
|
"""
|
|
7
20
|
|
|
21
|
+
from pytcl.containers.base import (
|
|
22
|
+
BaseSpatialIndex,
|
|
23
|
+
MetricSpatialIndex,
|
|
24
|
+
SpatialQueryResult,
|
|
25
|
+
validate_query_input,
|
|
26
|
+
)
|
|
8
27
|
from pytcl.containers.cluster_set import (
|
|
9
28
|
ClusterSet,
|
|
10
29
|
ClusterStats,
|
|
@@ -33,6 +52,11 @@ from pytcl.containers.track_list import TrackList, TrackListStats, TrackQuery
|
|
|
33
52
|
from pytcl.containers.vptree import VPNode, VPTree, VPTreeResult
|
|
34
53
|
|
|
35
54
|
__all__ = [
|
|
55
|
+
# Base classes
|
|
56
|
+
"BaseSpatialIndex",
|
|
57
|
+
"MetricSpatialIndex",
|
|
58
|
+
"SpatialQueryResult",
|
|
59
|
+
"validate_query_input",
|
|
36
60
|
# K-D Tree
|
|
37
61
|
"KDNode",
|
|
38
62
|
"NearestNeighborResult",
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Base classes for spatial data structures.
|
|
3
|
+
|
|
4
|
+
This module provides abstract base classes that define the common interface
|
|
5
|
+
for spatial indexing data structures like KD-trees, VP-trees, R-trees, and
|
|
6
|
+
Cover trees.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import logging
|
|
10
|
+
from abc import ABC, abstractmethod
|
|
11
|
+
from typing import Callable, List, NamedTuple, Optional
|
|
12
|
+
|
|
13
|
+
import numpy as np
|
|
14
|
+
from numpy.typing import ArrayLike, NDArray
|
|
15
|
+
|
|
16
|
+
# Module logger
|
|
17
|
+
_logger = logging.getLogger("pytcl.containers")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class SpatialQueryResult(NamedTuple):
|
|
21
|
+
"""Result of a spatial query.
|
|
22
|
+
|
|
23
|
+
Attributes
|
|
24
|
+
----------
|
|
25
|
+
indices : ndarray
|
|
26
|
+
Indices of matching points in the original data.
|
|
27
|
+
distances : ndarray
|
|
28
|
+
Distances to matching points.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
indices: NDArray[np.intp]
|
|
32
|
+
distances: NDArray[np.floating]
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class BaseSpatialIndex(ABC):
|
|
36
|
+
"""
|
|
37
|
+
Abstract base class for spatial indexing data structures.
|
|
38
|
+
|
|
39
|
+
All spatial index implementations (KDTree, VPTree, RTree, CoverTree)
|
|
40
|
+
should inherit from this class and implement the required methods.
|
|
41
|
+
|
|
42
|
+
This provides a consistent interface for:
|
|
43
|
+
- Building the index from point data
|
|
44
|
+
- k-nearest neighbor queries
|
|
45
|
+
- Range/radius queries
|
|
46
|
+
- Dimension and size introspection
|
|
47
|
+
|
|
48
|
+
Parameters
|
|
49
|
+
----------
|
|
50
|
+
data : array_like
|
|
51
|
+
Data points of shape (n_samples, n_features).
|
|
52
|
+
|
|
53
|
+
Attributes
|
|
54
|
+
----------
|
|
55
|
+
data : ndarray
|
|
56
|
+
The indexed data points.
|
|
57
|
+
n_samples : int
|
|
58
|
+
Number of data points.
|
|
59
|
+
n_features : int
|
|
60
|
+
Dimensionality of data points.
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
def __init__(self, data: ArrayLike):
|
|
64
|
+
self.data = np.asarray(data, dtype=np.float64)
|
|
65
|
+
|
|
66
|
+
if self.data.ndim != 2:
|
|
67
|
+
raise ValueError(
|
|
68
|
+
f"Data must be 2-dimensional (n_samples, n_features), "
|
|
69
|
+
f"got shape {self.data.shape}"
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
self.n_samples, self.n_features = self.data.shape
|
|
73
|
+
_logger.debug(
|
|
74
|
+
"%s initialized with %d points in %d dimensions",
|
|
75
|
+
self.__class__.__name__,
|
|
76
|
+
self.n_samples,
|
|
77
|
+
self.n_features,
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
@abstractmethod
|
|
81
|
+
def query(
|
|
82
|
+
self,
|
|
83
|
+
X: ArrayLike,
|
|
84
|
+
k: int = 1,
|
|
85
|
+
) -> SpatialQueryResult:
|
|
86
|
+
"""
|
|
87
|
+
Query the index for k nearest neighbors.
|
|
88
|
+
|
|
89
|
+
Parameters
|
|
90
|
+
----------
|
|
91
|
+
X : array_like
|
|
92
|
+
Query points of shape (n_queries, n_features) or (n_features,).
|
|
93
|
+
k : int, optional
|
|
94
|
+
Number of nearest neighbors to return. Default is 1.
|
|
95
|
+
|
|
96
|
+
Returns
|
|
97
|
+
-------
|
|
98
|
+
result : SpatialQueryResult
|
|
99
|
+
Named tuple with indices and distances of k nearest neighbors
|
|
100
|
+
for each query point.
|
|
101
|
+
"""
|
|
102
|
+
pass
|
|
103
|
+
|
|
104
|
+
@abstractmethod
|
|
105
|
+
def query_radius(
|
|
106
|
+
self,
|
|
107
|
+
X: ArrayLike,
|
|
108
|
+
r: float,
|
|
109
|
+
) -> List[List[int]]:
|
|
110
|
+
"""
|
|
111
|
+
Query the index for all points within radius r.
|
|
112
|
+
|
|
113
|
+
Parameters
|
|
114
|
+
----------
|
|
115
|
+
X : array_like
|
|
116
|
+
Query points of shape (n_queries, n_features) or (n_features,).
|
|
117
|
+
r : float
|
|
118
|
+
Search radius.
|
|
119
|
+
|
|
120
|
+
Returns
|
|
121
|
+
-------
|
|
122
|
+
indices : list of list of int
|
|
123
|
+
For each query point, a list of indices of data points
|
|
124
|
+
within distance r.
|
|
125
|
+
"""
|
|
126
|
+
pass
|
|
127
|
+
|
|
128
|
+
def __len__(self) -> int:
|
|
129
|
+
"""Return number of indexed points."""
|
|
130
|
+
return self.n_samples
|
|
131
|
+
|
|
132
|
+
def __repr__(self) -> str:
|
|
133
|
+
return (
|
|
134
|
+
f"{self.__class__.__name__}("
|
|
135
|
+
f"n_samples={self.n_samples}, n_features={self.n_features})"
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
class MetricSpatialIndex(BaseSpatialIndex):
|
|
140
|
+
"""
|
|
141
|
+
Base class for metric space spatial indices.
|
|
142
|
+
|
|
143
|
+
Extends BaseSpatialIndex with support for custom distance metrics.
|
|
144
|
+
Used by VP-trees and Cover trees which can work with any metric.
|
|
145
|
+
|
|
146
|
+
Parameters
|
|
147
|
+
----------
|
|
148
|
+
data : array_like
|
|
149
|
+
Data points of shape (n_samples, n_features).
|
|
150
|
+
metric : callable, optional
|
|
151
|
+
Distance function with signature metric(x, y) -> float.
|
|
152
|
+
Default is Euclidean distance.
|
|
153
|
+
"""
|
|
154
|
+
|
|
155
|
+
def __init__(
|
|
156
|
+
self,
|
|
157
|
+
data: ArrayLike,
|
|
158
|
+
metric: Optional[Callable[[NDArray, NDArray], float]] = None,
|
|
159
|
+
):
|
|
160
|
+
super().__init__(data)
|
|
161
|
+
|
|
162
|
+
if metric is None:
|
|
163
|
+
self.metric = self._euclidean_distance
|
|
164
|
+
else:
|
|
165
|
+
self.metric = metric
|
|
166
|
+
|
|
167
|
+
@staticmethod
|
|
168
|
+
def _euclidean_distance(x: NDArray, y: NDArray) -> float:
|
|
169
|
+
"""Default Euclidean distance metric."""
|
|
170
|
+
return float(np.sqrt(np.sum((x - y) ** 2)))
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def validate_query_input(
|
|
174
|
+
X: ArrayLike,
|
|
175
|
+
n_features: int,
|
|
176
|
+
) -> NDArray[np.floating]:
|
|
177
|
+
"""
|
|
178
|
+
Validate and reshape query input.
|
|
179
|
+
|
|
180
|
+
Parameters
|
|
181
|
+
----------
|
|
182
|
+
X : array_like
|
|
183
|
+
Query points.
|
|
184
|
+
n_features : int
|
|
185
|
+
Expected number of features.
|
|
186
|
+
|
|
187
|
+
Returns
|
|
188
|
+
-------
|
|
189
|
+
X : ndarray
|
|
190
|
+
Validated query array of shape (n_queries, n_features).
|
|
191
|
+
|
|
192
|
+
Raises
|
|
193
|
+
------
|
|
194
|
+
ValueError
|
|
195
|
+
If query has wrong number of features.
|
|
196
|
+
"""
|
|
197
|
+
X = np.asarray(X, dtype=np.float64)
|
|
198
|
+
|
|
199
|
+
if X.ndim == 1:
|
|
200
|
+
X = X.reshape(1, -1)
|
|
201
|
+
|
|
202
|
+
if X.shape[1] != n_features:
|
|
203
|
+
_logger.warning(
|
|
204
|
+
"Query feature mismatch: got %d, expected %d", X.shape[1], n_features
|
|
205
|
+
)
|
|
206
|
+
raise ValueError(f"Query has {X.shape[1]} features, expected {n_features}")
|
|
207
|
+
|
|
208
|
+
_logger.debug(
|
|
209
|
+
"Validated query input: %d queries, %d features", X.shape[0], X.shape[1]
|
|
210
|
+
)
|
|
211
|
+
return X
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
__all__ = [
|
|
215
|
+
"SpatialQueryResult",
|
|
216
|
+
"BaseSpatialIndex",
|
|
217
|
+
"MetricSpatialIndex",
|
|
218
|
+
"validate_query_input",
|
|
219
|
+
]
|
|
@@ -11,11 +11,17 @@ References
|
|
|
11
11
|
neighbor," ICML 2006.
|
|
12
12
|
"""
|
|
13
13
|
|
|
14
|
+
import logging
|
|
14
15
|
from typing import Callable, List, NamedTuple, Optional, Set, Tuple
|
|
15
16
|
|
|
16
17
|
import numpy as np
|
|
17
18
|
from numpy.typing import ArrayLike, NDArray
|
|
18
19
|
|
|
20
|
+
from pytcl.containers.base import MetricSpatialIndex, validate_query_input
|
|
21
|
+
|
|
22
|
+
# Module logger
|
|
23
|
+
_logger = logging.getLogger("pytcl.containers.covertree")
|
|
24
|
+
|
|
19
25
|
|
|
20
26
|
class CoverTreeResult(NamedTuple):
|
|
21
27
|
"""Result of Cover tree query.
|
|
@@ -60,7 +66,7 @@ class CoverTreeNode:
|
|
|
60
66
|
self.children[level].append(child)
|
|
61
67
|
|
|
62
68
|
|
|
63
|
-
class CoverTree:
|
|
69
|
+
class CoverTree(MetricSpatialIndex):
|
|
64
70
|
"""
|
|
65
71
|
Cover Tree for metric space nearest neighbor search.
|
|
66
72
|
|
|
@@ -93,6 +99,11 @@ class CoverTree:
|
|
|
93
99
|
|
|
94
100
|
The implementation uses a simplified version of the original
|
|
95
101
|
algorithm for clarity.
|
|
102
|
+
|
|
103
|
+
See Also
|
|
104
|
+
--------
|
|
105
|
+
MetricSpatialIndex : Abstract base class for metric-based spatial indices.
|
|
106
|
+
VPTree : Alternative metric space index using vantage points.
|
|
96
107
|
"""
|
|
97
108
|
|
|
98
109
|
def __init__(
|
|
@@ -101,19 +112,9 @@ class CoverTree:
|
|
|
101
112
|
metric: Optional[Callable[[NDArray, NDArray], float]] = None,
|
|
102
113
|
base: float = 2.0,
|
|
103
114
|
):
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
if self.data.ndim != 2:
|
|
107
|
-
raise ValueError("Data must be 2-dimensional")
|
|
108
|
-
|
|
109
|
-
self.n_samples, self.n_features = self.data.shape
|
|
115
|
+
super().__init__(data, metric)
|
|
110
116
|
self.base = base
|
|
111
117
|
|
|
112
|
-
if metric is None:
|
|
113
|
-
self.metric = self._euclidean_distance
|
|
114
|
-
else:
|
|
115
|
-
self.metric = metric
|
|
116
|
-
|
|
117
118
|
# Compute distance cache for small datasets
|
|
118
119
|
self._distance_cache: dict[Tuple[int, int], float] = {}
|
|
119
120
|
|
|
@@ -124,10 +125,12 @@ class CoverTree:
|
|
|
124
125
|
|
|
125
126
|
if self.n_samples > 0:
|
|
126
127
|
self._build_tree()
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
128
|
+
_logger.debug(
|
|
129
|
+
"CoverTree built with base=%.1f, levels=%d to %d",
|
|
130
|
+
base,
|
|
131
|
+
self.min_level,
|
|
132
|
+
self.max_level,
|
|
133
|
+
)
|
|
131
134
|
|
|
132
135
|
def _distance(self, i: int, j: int) -> float:
|
|
133
136
|
"""Get distance between points i and j (with caching)."""
|
|
@@ -245,11 +248,7 @@ class CoverTree:
|
|
|
245
248
|
result : CoverTreeResult
|
|
246
249
|
Indices and distances of k nearest neighbors.
|
|
247
250
|
"""
|
|
248
|
-
X =
|
|
249
|
-
|
|
250
|
-
if X.ndim == 1:
|
|
251
|
-
X = X.reshape(1, -1)
|
|
252
|
-
|
|
251
|
+
X = validate_query_input(X, self.n_features)
|
|
253
252
|
n_queries = X.shape[0]
|
|
254
253
|
|
|
255
254
|
all_indices = np.zeros((n_queries, k), dtype=np.intp)
|
|
@@ -368,11 +367,7 @@ class CoverTree:
|
|
|
368
367
|
indices : list of lists
|
|
369
368
|
For each query, list of indices within radius.
|
|
370
369
|
"""
|
|
371
|
-
X =
|
|
372
|
-
|
|
373
|
-
if X.ndim == 1:
|
|
374
|
-
X = X.reshape(1, -1)
|
|
375
|
-
|
|
370
|
+
X = validate_query_input(X, self.n_features)
|
|
376
371
|
n_queries = X.shape[0]
|
|
377
372
|
results: List[List[int]] = []
|
|
378
373
|
|