nrl-tracker 1.1.2__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.
Files changed (203) hide show
  1. {nrl_tracker-1.1.2/nrl_tracker.egg-info → nrl_tracker-1.2.0}/PKG-INFO +1 -1
  2. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0/nrl_tracker.egg-info}/PKG-INFO +1 -1
  3. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/nrl_tracker.egg-info/SOURCES.txt +3 -0
  4. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pyproject.toml +1 -1
  5. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/__init__.py +1 -1
  6. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/astronomical/reference_frames.py +127 -55
  7. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/containers/__init__.py +24 -0
  8. nrl_tracker-1.2.0/pytcl/containers/base.py +219 -0
  9. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/containers/covertree.py +21 -26
  10. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/containers/kd_tree.py +94 -29
  11. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/containers/vptree.py +17 -26
  12. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/core/__init__.py +18 -0
  13. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/core/validation.py +331 -0
  14. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/gravity/egm.py +13 -0
  15. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/gravity/spherical_harmonics.py +97 -36
  16. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/mathematical_functions/special_functions/hypergeometric.py +79 -15
  17. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/navigation/geodesy.py +245 -159
  18. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/navigation/great_circle.py +98 -16
  19. nrl_tracker-1.2.0/tests/test_spatial_containers_parametrized.py +467 -0
  20. nrl_tracker-1.2.0/tests/test_validation.py +482 -0
  21. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/CONTRIBUTING.md +0 -0
  22. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/LICENSE +0 -0
  23. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/MANIFEST.in +0 -0
  24. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/README.md +0 -0
  25. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/nrl_tracker.egg-info/dependency_links.txt +0 -0
  26. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/nrl_tracker.egg-info/requires.txt +0 -0
  27. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/nrl_tracker.egg-info/top_level.txt +0 -0
  28. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/assignment_algorithms/__init__.py +0 -0
  29. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/assignment_algorithms/data_association.py +0 -0
  30. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/assignment_algorithms/gating.py +0 -0
  31. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/assignment_algorithms/jpda.py +0 -0
  32. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/assignment_algorithms/three_dimensional/__init__.py +0 -0
  33. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/assignment_algorithms/three_dimensional/assignment.py +0 -0
  34. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/assignment_algorithms/two_dimensional/__init__.py +0 -0
  35. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/assignment_algorithms/two_dimensional/assignment.py +0 -0
  36. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/assignment_algorithms/two_dimensional/kbest.py +0 -0
  37. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/astronomical/__init__.py +0 -0
  38. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/astronomical/ephemerides.py +0 -0
  39. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/astronomical/lambert.py +0 -0
  40. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/astronomical/orbital_mechanics.py +0 -0
  41. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/astronomical/relativity.py +0 -0
  42. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/astronomical/time_systems.py +0 -0
  43. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/atmosphere/__init__.py +0 -0
  44. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/atmosphere/models.py +0 -0
  45. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/clustering/__init__.py +0 -0
  46. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/clustering/dbscan.py +0 -0
  47. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/clustering/gaussian_mixture.py +0 -0
  48. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/clustering/hierarchical.py +0 -0
  49. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/clustering/kmeans.py +0 -0
  50. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/containers/cluster_set.py +0 -0
  51. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/containers/measurement_set.py +0 -0
  52. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/containers/rtree.py +0 -0
  53. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/containers/track_list.py +0 -0
  54. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/coordinate_systems/__init__.py +0 -0
  55. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/coordinate_systems/conversions/__init__.py +0 -0
  56. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/coordinate_systems/conversions/geodetic.py +0 -0
  57. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/coordinate_systems/conversions/spherical.py +0 -0
  58. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/coordinate_systems/jacobians/__init__.py +0 -0
  59. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/coordinate_systems/jacobians/jacobians.py +0 -0
  60. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/coordinate_systems/projections/__init__.py +0 -0
  61. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/coordinate_systems/projections/projections.py +0 -0
  62. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/coordinate_systems/rotations/__init__.py +0 -0
  63. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/coordinate_systems/rotations/rotations.py +0 -0
  64. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/core/array_utils.py +0 -0
  65. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/core/constants.py +0 -0
  66. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/dynamic_estimation/__init__.py +0 -0
  67. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/dynamic_estimation/batch_estimation/__init__.py +0 -0
  68. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/dynamic_estimation/imm.py +0 -0
  69. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/dynamic_estimation/information_filter.py +0 -0
  70. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/dynamic_estimation/kalman/__init__.py +0 -0
  71. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/dynamic_estimation/kalman/extended.py +0 -0
  72. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/dynamic_estimation/kalman/linear.py +0 -0
  73. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/dynamic_estimation/kalman/square_root.py +0 -0
  74. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/dynamic_estimation/kalman/unscented.py +0 -0
  75. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/dynamic_estimation/measurement_update/__init__.py +0 -0
  76. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/dynamic_estimation/particle_filters/__init__.py +0 -0
  77. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/dynamic_estimation/particle_filters/bootstrap.py +0 -0
  78. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/dynamic_estimation/smoothers.py +0 -0
  79. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/dynamic_models/__init__.py +0 -0
  80. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/dynamic_models/continuous_time/__init__.py +0 -0
  81. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/dynamic_models/continuous_time/dynamics.py +0 -0
  82. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/dynamic_models/discrete_time/__init__.py +0 -0
  83. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/dynamic_models/discrete_time/coordinated_turn.py +0 -0
  84. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/dynamic_models/discrete_time/polynomial.py +0 -0
  85. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/dynamic_models/discrete_time/singer.py +0 -0
  86. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/dynamic_models/process_noise/__init__.py +0 -0
  87. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/dynamic_models/process_noise/coordinated_turn.py +0 -0
  88. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/dynamic_models/process_noise/polynomial.py +0 -0
  89. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/dynamic_models/process_noise/singer.py +0 -0
  90. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/gravity/__init__.py +0 -0
  91. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/gravity/clenshaw.py +0 -0
  92. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/gravity/models.py +0 -0
  93. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/gravity/tides.py +0 -0
  94. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/logging_config.py +0 -0
  95. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/magnetism/__init__.py +0 -0
  96. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/magnetism/emm.py +0 -0
  97. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/magnetism/igrf.py +0 -0
  98. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/magnetism/wmm.py +0 -0
  99. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/mathematical_functions/__init__.py +0 -0
  100. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/mathematical_functions/basic_matrix/__init__.py +0 -0
  101. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/mathematical_functions/basic_matrix/decompositions.py +0 -0
  102. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/mathematical_functions/basic_matrix/special_matrices.py +0 -0
  103. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/mathematical_functions/combinatorics/__init__.py +0 -0
  104. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/mathematical_functions/combinatorics/combinatorics.py +0 -0
  105. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/mathematical_functions/continuous_optimization/__init__.py +0 -0
  106. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/mathematical_functions/geometry/__init__.py +0 -0
  107. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/mathematical_functions/geometry/geometry.py +0 -0
  108. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/mathematical_functions/interpolation/__init__.py +0 -0
  109. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/mathematical_functions/interpolation/interpolation.py +0 -0
  110. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/mathematical_functions/numerical_integration/__init__.py +0 -0
  111. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/mathematical_functions/numerical_integration/quadrature.py +0 -0
  112. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/mathematical_functions/polynomials/__init__.py +0 -0
  113. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/mathematical_functions/signal_processing/__init__.py +0 -0
  114. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/mathematical_functions/signal_processing/detection.py +0 -0
  115. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/mathematical_functions/signal_processing/filters.py +0 -0
  116. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/mathematical_functions/signal_processing/matched_filter.py +0 -0
  117. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/mathematical_functions/special_functions/__init__.py +0 -0
  118. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/mathematical_functions/special_functions/bessel.py +0 -0
  119. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/mathematical_functions/special_functions/debye.py +0 -0
  120. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/mathematical_functions/special_functions/elliptic.py +0 -0
  121. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/mathematical_functions/special_functions/error_functions.py +0 -0
  122. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/mathematical_functions/special_functions/gamma_functions.py +0 -0
  123. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/mathematical_functions/special_functions/lambert_w.py +0 -0
  124. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/mathematical_functions/special_functions/marcum_q.py +0 -0
  125. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/mathematical_functions/statistics/__init__.py +0 -0
  126. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/mathematical_functions/statistics/distributions.py +0 -0
  127. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/mathematical_functions/statistics/estimators.py +0 -0
  128. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/mathematical_functions/transforms/__init__.py +0 -0
  129. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/mathematical_functions/transforms/fourier.py +0 -0
  130. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/mathematical_functions/transforms/stft.py +0 -0
  131. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/mathematical_functions/transforms/wavelets.py +0 -0
  132. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/misc/__init__.py +0 -0
  133. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/navigation/__init__.py +0 -0
  134. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/navigation/ins.py +0 -0
  135. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/navigation/ins_gnss.py +0 -0
  136. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/navigation/rhumb.py +0 -0
  137. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/performance_evaluation/__init__.py +0 -0
  138. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/performance_evaluation/estimation_metrics.py +0 -0
  139. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/performance_evaluation/track_metrics.py +0 -0
  140. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/physical_values/__init__.py +0 -0
  141. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/plotting/__init__.py +0 -0
  142. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/plotting/coordinates.py +0 -0
  143. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/plotting/ellipses.py +0 -0
  144. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/plotting/metrics.py +0 -0
  145. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/plotting/tracks.py +0 -0
  146. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/scheduling/__init__.py +0 -0
  147. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/static_estimation/__init__.py +0 -0
  148. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/static_estimation/least_squares.py +0 -0
  149. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/static_estimation/maximum_likelihood.py +0 -0
  150. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/static_estimation/robust.py +0 -0
  151. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/terrain/__init__.py +0 -0
  152. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/terrain/dem.py +0 -0
  153. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/terrain/loaders.py +0 -0
  154. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/terrain/visibility.py +0 -0
  155. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/trackers/__init__.py +0 -0
  156. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/trackers/hypothesis.py +0 -0
  157. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/trackers/mht.py +0 -0
  158. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/trackers/multi_target.py +0 -0
  159. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/trackers/single_target.py +0 -0
  160. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/pytcl/transponders/__init__.py +0 -0
  161. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/setup.cfg +0 -0
  162. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/tests/__init__.py +0 -0
  163. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/tests/conftest.py +0 -0
  164. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/tests/test_additional_trees.py +0 -0
  165. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/tests/test_assignment_algorithms.py +0 -0
  166. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/tests/test_astronomical.py +0 -0
  167. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/tests/test_clustering.py +0 -0
  168. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/tests/test_coordinate_systems.py +0 -0
  169. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/tests/test_coverage_boost.py +0 -0
  170. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/tests/test_coverage_boost_2.py +0 -0
  171. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/tests/test_dynamic_models.py +0 -0
  172. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/tests/test_egm.py +0 -0
  173. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/tests/test_emm.py +0 -0
  174. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/tests/test_ephemerides.py +0 -0
  175. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/tests/test_gaussian_mixtures.py +0 -0
  176. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/tests/test_geophysical.py +0 -0
  177. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/tests/test_great_circle.py +0 -0
  178. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/tests/test_ins.py +0 -0
  179. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/tests/test_ins_gnss.py +0 -0
  180. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/tests/test_kalman_filters.py +0 -0
  181. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/tests/test_mathematical_functions.py +0 -0
  182. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/tests/test_maximum_likelihood.py +0 -0
  183. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/tests/test_mht.py +0 -0
  184. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/tests/test_performance_evaluation.py +0 -0
  185. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/tests/test_phase6_specialized.py +0 -0
  186. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/tests/test_plotting.py +0 -0
  187. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/tests/test_projections.py +0 -0
  188. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/tests/test_relativity.py +0 -0
  189. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/tests/test_rhumb.py +0 -0
  190. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/tests/test_signal_processing.py +0 -0
  191. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/tests/test_smoothers.py +0 -0
  192. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/tests/test_spatial_structures.py +0 -0
  193. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/tests/test_special_functions_phase12.py +0 -0
  194. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/tests/test_static_estimation.py +0 -0
  195. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/tests/test_terrain.py +0 -0
  196. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/tests/test_terrain_loaders.py +0 -0
  197. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/tests/test_tides.py +0 -0
  198. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/tests/test_trackers.py +0 -0
  199. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/tests/test_tracking_containers.py +0 -0
  200. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/tests/test_transforms.py +0 -0
  201. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/tests/test_v030_comprehensive.py +0 -0
  202. {nrl_tracker-1.1.2 → nrl_tracker-1.2.0}/tests/test_v030_features.py +0 -0
  203. {nrl_tracker-1.1.2 → 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.1.2
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.1.2
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.1.2"
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 = [
@@ -20,7 +20,7 @@ References
20
20
  no. 5, pp. 18-27, May 2017.
21
21
  """
22
22
 
23
- __version__ = "1.1.2"
23
+ __version__ = "1.2.0"
24
24
  __author__ = "Python Port Contributors"
25
25
  __original_author__ = "David F. Crouse, Naval Research Laboratory"
26
26
 
@@ -21,6 +21,8 @@ References
21
21
  A&A, 2003.
22
22
  """
23
23
 
24
+ import logging
25
+ from functools import lru_cache
24
26
  from typing import Tuple
25
27
 
26
28
  import numpy as np
@@ -28,6 +30,22 @@ from numpy.typing import NDArray
28
30
 
29
31
  from pytcl.astronomical.time_systems import JD_J2000
30
32
 
33
+ # Module logger
34
+ _logger = logging.getLogger("pytcl.astronomical.reference_frames")
35
+
36
+ # Cache configuration
37
+ _CACHE_JD_DECIMALS = 6 # ~86ms precision for JD quantization
38
+ _CACHE_MAXSIZE = 128 # Max cached epochs
39
+
40
+
41
+ def _quantize_jd(jd: float) -> float:
42
+ """Quantize Julian date for cache key compatibility.
43
+
44
+ Rounds to _CACHE_JD_DECIMALS decimal places (~86ms precision).
45
+ This enables cache hits for nearly identical epochs.
46
+ """
47
+ return round(jd, _CACHE_JD_DECIMALS)
48
+
31
49
 
32
50
  def julian_centuries_j2000(jd: float) -> float:
33
51
  """
@@ -78,6 +96,37 @@ def precession_angles_iau76(T: float) -> Tuple[float, float, float]:
78
96
  )
79
97
 
80
98
 
99
+ @lru_cache(maxsize=_CACHE_MAXSIZE)
100
+ def _precession_matrix_cached(jd_quantized: float) -> tuple:
101
+ """Cached precession matrix computation (internal).
102
+
103
+ Returns tuple of tuples for hashability.
104
+ """
105
+ T = julian_centuries_j2000(jd_quantized)
106
+ zeta, theta, z = precession_angles_iau76(T)
107
+
108
+ cos_zeta = np.cos(zeta)
109
+ sin_zeta = np.sin(zeta)
110
+ cos_theta = np.cos(theta)
111
+ sin_theta = np.sin(theta)
112
+ cos_z = np.cos(z)
113
+ sin_z = np.sin(z)
114
+
115
+ return (
116
+ (
117
+ cos_zeta * cos_theta * cos_z - sin_zeta * sin_z,
118
+ -sin_zeta * cos_theta * cos_z - cos_zeta * sin_z,
119
+ -sin_theta * cos_z,
120
+ ),
121
+ (
122
+ cos_zeta * cos_theta * sin_z + sin_zeta * cos_z,
123
+ -sin_zeta * cos_theta * sin_z + cos_zeta * cos_z,
124
+ -sin_theta * sin_z,
125
+ ),
126
+ (cos_zeta * sin_theta, -sin_zeta * sin_theta, cos_theta),
127
+ )
128
+
129
+
81
130
  def precession_matrix_iau76(jd: float) -> NDArray[np.floating]:
82
131
  """
83
132
  Compute IAU 1976 precession matrix from J2000 to date.
@@ -92,34 +141,15 @@ def precession_matrix_iau76(jd: float) -> NDArray[np.floating]:
92
141
  P : ndarray
93
142
  Precession rotation matrix (3x3).
94
143
  Transforms from J2000 (GCRF) to mean of date.
95
- """
96
- T = julian_centuries_j2000(jd)
97
- zeta, theta, z = precession_angles_iau76(T)
98
144
 
99
- cos_zeta = np.cos(zeta)
100
- sin_zeta = np.sin(zeta)
101
- cos_theta = np.cos(theta)
102
- sin_theta = np.sin(theta)
103
- cos_z = np.cos(z)
104
- sin_z = np.sin(z)
105
-
106
- P = np.array(
107
- [
108
- [
109
- cos_zeta * cos_theta * cos_z - sin_zeta * sin_z,
110
- -sin_zeta * cos_theta * cos_z - cos_zeta * sin_z,
111
- -sin_theta * cos_z,
112
- ],
113
- [
114
- cos_zeta * cos_theta * sin_z + sin_zeta * cos_z,
115
- -sin_zeta * cos_theta * sin_z + cos_zeta * cos_z,
116
- -sin_theta * sin_z,
117
- ],
118
- [cos_zeta * sin_theta, -sin_zeta * sin_theta, cos_theta],
119
- ]
120
- )
121
-
122
- return P
145
+ Notes
146
+ -----
147
+ Results are cached for repeated queries at the same epoch.
148
+ Cache key is quantized to ~86ms precision.
149
+ """
150
+ jd_q = _quantize_jd(jd)
151
+ cached = _precession_matrix_cached(jd_q)
152
+ return np.array(cached)
123
153
 
124
154
 
125
155
  def nutation_angles_iau80(jd: float) -> Tuple[float, float]:
@@ -203,6 +233,38 @@ def mean_obliquity_iau80(jd: float) -> float:
203
233
  return eps0_arcsec * np.pi / (180 * 3600)
204
234
 
205
235
 
236
+ @lru_cache(maxsize=_CACHE_MAXSIZE)
237
+ def _nutation_matrix_cached(jd_quantized: float) -> tuple:
238
+ """Cached nutation matrix computation (internal).
239
+
240
+ Returns tuple of tuples for hashability.
241
+ """
242
+ dpsi, deps = nutation_angles_iau80(jd_quantized)
243
+ eps0 = mean_obliquity_iau80(jd_quantized)
244
+ eps = eps0 + deps
245
+
246
+ cos_eps0 = np.cos(eps0)
247
+ sin_eps0 = np.sin(eps0)
248
+ cos_eps = np.cos(eps)
249
+ sin_eps = np.sin(eps)
250
+ cos_dpsi = np.cos(dpsi)
251
+ sin_dpsi = np.sin(dpsi)
252
+
253
+ return (
254
+ (cos_dpsi, -sin_dpsi * cos_eps0, -sin_dpsi * sin_eps0),
255
+ (
256
+ sin_dpsi * cos_eps,
257
+ cos_dpsi * cos_eps0 * cos_eps + sin_eps0 * sin_eps,
258
+ cos_dpsi * sin_eps0 * cos_eps - cos_eps0 * sin_eps,
259
+ ),
260
+ (
261
+ sin_dpsi * sin_eps,
262
+ cos_dpsi * cos_eps0 * sin_eps - sin_eps0 * cos_eps,
263
+ cos_dpsi * sin_eps0 * sin_eps + cos_eps0 * cos_eps,
264
+ ),
265
+ )
266
+
267
+
206
268
  def nutation_matrix(jd: float) -> NDArray[np.floating]:
207
269
  """
208
270
  Compute nutation matrix.
@@ -217,35 +279,15 @@ def nutation_matrix(jd: float) -> NDArray[np.floating]:
217
279
  N : ndarray
218
280
  Nutation rotation matrix (3x3).
219
281
  Transforms from mean of date to true of date.
220
- """
221
- dpsi, deps = nutation_angles_iau80(jd)
222
- eps0 = mean_obliquity_iau80(jd)
223
- eps = eps0 + deps
224
282
 
225
- cos_eps0 = np.cos(eps0)
226
- sin_eps0 = np.sin(eps0)
227
- cos_eps = np.cos(eps)
228
- sin_eps = np.sin(eps)
229
- cos_dpsi = np.cos(dpsi)
230
- sin_dpsi = np.sin(dpsi)
231
-
232
- N = np.array(
233
- [
234
- [cos_dpsi, -sin_dpsi * cos_eps0, -sin_dpsi * sin_eps0],
235
- [
236
- sin_dpsi * cos_eps,
237
- cos_dpsi * cos_eps0 * cos_eps + sin_eps0 * sin_eps,
238
- cos_dpsi * sin_eps0 * cos_eps - cos_eps0 * sin_eps,
239
- ],
240
- [
241
- sin_dpsi * sin_eps,
242
- cos_dpsi * cos_eps0 * sin_eps - sin_eps0 * cos_eps,
243
- cos_dpsi * sin_eps0 * sin_eps + cos_eps0 * cos_eps,
244
- ],
245
- ]
246
- )
247
-
248
- return N
283
+ Notes
284
+ -----
285
+ Results are cached for repeated queries at the same epoch.
286
+ Cache key is quantized to ~86ms precision.
287
+ """
288
+ jd_q = _quantize_jd(jd)
289
+ cached = _nutation_matrix_cached(jd_q)
290
+ return np.array(cached)
249
291
 
250
292
 
251
293
  def earth_rotation_angle(jd_ut1: float) -> float:
@@ -647,6 +689,33 @@ def equatorial_to_ecliptic(
647
689
  return R @ r_eq
648
690
 
649
691
 
692
+ def clear_transformation_cache() -> None:
693
+ """Clear cached transformation matrices.
694
+
695
+ Call this function to clear all cached precession and nutation
696
+ matrices. Useful when memory is constrained or after processing
697
+ a batch of observations at different epochs.
698
+ """
699
+ _precession_matrix_cached.cache_clear()
700
+ _nutation_matrix_cached.cache_clear()
701
+ _logger.debug("Transformation matrix cache cleared")
702
+
703
+
704
+ def get_cache_info() -> dict:
705
+ """Get cache statistics for transformation matrices.
706
+
707
+ Returns
708
+ -------
709
+ dict
710
+ Dictionary with 'precession' and 'nutation' keys, each containing
711
+ CacheInfo namedtuple with hits, misses, maxsize, currsize.
712
+ """
713
+ return {
714
+ "precession": _precession_matrix_cached.cache_info(),
715
+ "nutation": _nutation_matrix_cached.cache_info(),
716
+ }
717
+
718
+
650
719
  __all__ = [
651
720
  # Time utilities
652
721
  "julian_centuries_j2000",
@@ -674,4 +743,7 @@ __all__ = [
674
743
  # Ecliptic/equatorial
675
744
  "ecliptic_to_equatorial",
676
745
  "equatorial_to_ecliptic",
746
+ # Cache management
747
+ "clear_transformation_cache",
748
+ "get_cache_info",
677
749
  ]
@@ -3,8 +3,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
- self.data = np.asarray(data, dtype=np.float64)
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
- def _euclidean_distance(self, x: NDArray, y: NDArray) -> float:
129
- """Default Euclidean distance metric."""
130
- return float(np.sqrt(np.sum((x - y) ** 2)))
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 = np.asarray(X, dtype=np.float64)
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 = np.asarray(X, dtype=np.float64)
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