polytope-python 2.1.10__tar.gz → 2.1.12__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 (289) hide show
  1. {polytope_python-2.1.10/polytope_python.egg-info → polytope_python-2.1.12}/PKG-INFO +1 -1
  2. {polytope_python-2.1.10 → polytope_python-2.1.12}/docs/Service/Full_fields.md +2 -2
  3. {polytope_python-2.1.10 → polytope_python-2.1.12}/polytope_feature/_version.py +1 -1
  4. {polytope_python-2.1.10 → polytope_python-2.1.12/polytope_python.egg-info}/PKG-INFO +1 -1
  5. {polytope_python-2.1.10 → polytope_python-2.1.12}/polytope_python.egg-info/SOURCES.txt +1 -0
  6. {polytope_python-2.1.10 → polytope_python-2.1.12}/rust/src/distance.rs +5 -7
  7. polytope_python-2.1.12/tests/test_nn_quadtree_search.py +276 -0
  8. {polytope_python-2.1.10 → polytope_python-2.1.12}/.github/ci-config.yml +0 -0
  9. {polytope_python-2.1.10 → polytope_python-2.1.12}/.github/ci-hpc-config.yml +0 -0
  10. {polytope_python-2.1.10 → polytope_python-2.1.12}/.github/workflows/cd.yml +0 -0
  11. {polytope_python-2.1.10 → polytope_python-2.1.12}/.github/workflows/ci.yml +0 -0
  12. {polytope_python-2.1.10 → polytope_python-2.1.12}/.github/workflows/downstream-ci.yml +0 -0
  13. {polytope_python-2.1.10 → polytope_python-2.1.12}/.github/workflows/label-public-pr.yml +0 -0
  14. {polytope_python-2.1.10 → polytope_python-2.1.12}/.github/workflows/test-pypi.yml +0 -0
  15. {polytope_python-2.1.10 → polytope_python-2.1.12}/.gitignore +0 -0
  16. {polytope_python-2.1.10 → polytope_python-2.1.12}/.pre-commit-config.yaml +0 -0
  17. {polytope_python-2.1.10 → polytope_python-2.1.12}/.readthedocs.yaml +0 -0
  18. {polytope_python-2.1.10 → polytope_python-2.1.12}/ACKNOWLEDGEMENTS.rst +0 -0
  19. {polytope_python-2.1.10 → polytope_python-2.1.12}/CONTRIBUTING.rst +0 -0
  20. {polytope_python-2.1.10 → polytope_python-2.1.12}/LICENSE +0 -0
  21. {polytope_python-2.1.10 → polytope_python-2.1.12}/Makefile +0 -0
  22. {polytope_python-2.1.10 → polytope_python-2.1.12}/README.md +0 -0
  23. {polytope_python-2.1.10 → polytope_python-2.1.12}/codecov.yml +0 -0
  24. {polytope_python-2.1.10 → polytope_python-2.1.12}/docs/Algorithm/Developer_Guide/API.md +0 -0
  25. {polytope_python-2.1.10 → polytope_python-2.1.12}/docs/Algorithm/Developer_Guide/Axis_types.md +0 -0
  26. {polytope_python-2.1.10 → polytope_python-2.1.12}/docs/Algorithm/Developer_Guide/Datacube.md +0 -0
  27. {polytope_python-2.1.10 → polytope_python-2.1.12}/docs/Algorithm/Developer_Guide/Overview.md +0 -0
  28. {polytope_python-2.1.10 → polytope_python-2.1.12}/docs/Algorithm/Developer_Guide/Slicer.md +0 -0
  29. {polytope_python-2.1.10 → polytope_python-2.1.12}/docs/Algorithm/Developer_Guide/images/Polytope_APIs_3.png +0 -0
  30. {polytope_python-2.1.10 → polytope_python-2.1.12}/docs/Algorithm/Developer_Guide/images/polytope_components_5.png +0 -0
  31. {polytope_python-2.1.10 → polytope_python-2.1.12}/docs/Algorithm/Developer_Guide/images/slicing_process.png +0 -0
  32. {polytope_python-2.1.10 → polytope_python-2.1.12}/docs/Algorithm/Developer_Guide/shapes.md +0 -0
  33. {polytope_python-2.1.10 → polytope_python-2.1.12}/docs/Algorithm/Overview/Overview.md +0 -0
  34. {polytope_python-2.1.10 → polytope_python-2.1.12}/docs/Algorithm/Overview/Polytope_at_ECMWF.md +0 -0
  35. {polytope_python-2.1.10 → polytope_python-2.1.12}/docs/Algorithm/Overview/images_overview/ecmwf_datacube.png +0 -0
  36. {polytope_python-2.1.10 → polytope_python-2.1.12}/docs/Algorithm/Overview/images_overview/ecmwf_polytope.png +0 -0
  37. {polytope_python-2.1.10 → polytope_python-2.1.12}/docs/Algorithm/User_Guide/Building_Features.md +0 -0
  38. {polytope_python-2.1.10 → polytope_python-2.1.12}/docs/Algorithm/User_Guide/Example.md +0 -0
  39. {polytope_python-2.1.10 → polytope_python-2.1.12}/docs/Algorithm/User_Guide/Getting_started.md +0 -0
  40. {polytope_python-2.1.10 → polytope_python-2.1.12}/docs/Algorithm/User_Guide/Overview.md +0 -0
  41. {polytope_python-2.1.10 → polytope_python-2.1.12}/docs/Algorithm/User_Guide/images_users/shipping_route.png +0 -0
  42. {polytope_python-2.1.10 → polytope_python-2.1.12}/docs/Client/Overview.md +0 -0
  43. {polytope_python-2.1.10 → polytope_python-2.1.12}/docs/Server/Design.md +0 -0
  44. {polytope_python-2.1.10 → polytope_python-2.1.12}/docs/Server/Overview.md +0 -0
  45. {polytope_python-2.1.10 → polytope_python-2.1.12}/docs/Service/Data_Portfolio.md +0 -0
  46. {polytope_python-2.1.10 → polytope_python-2.1.12}/docs/Service/Design_doc.md +0 -0
  47. {polytope_python-2.1.10 → polytope_python-2.1.12}/docs/Service/Examples/OpenData/od_boundingbox_example.ipynb +0 -0
  48. {polytope_python-2.1.10 → polytope_python-2.1.12}/docs/Service/Examples/OpenData/od_circle_example.ipynb +0 -0
  49. {polytope_python-2.1.10 → polytope_python-2.1.12}/docs/Service/Examples/OpenData/od_country_example.ipynb +0 -0
  50. {polytope_python-2.1.10 → polytope_python-2.1.12}/docs/Service/Examples/OpenData/od_polygon_example.ipynb +0 -0
  51. {polytope_python-2.1.10 → polytope_python-2.1.12}/docs/Service/Examples/OpenData/od_position_example.ipynb +0 -0
  52. {polytope_python-2.1.10 → polytope_python-2.1.12}/docs/Service/Examples/OpenData/od_timeseries_example.ipynb +0 -0
  53. {polytope_python-2.1.10 → polytope_python-2.1.12}/docs/Service/Examples/OpenData/od_trajectory_example.ipynb +0 -0
  54. {polytope_python-2.1.10 → polytope_python-2.1.12}/docs/Service/Examples/OpenData/od_vertical_profile_example.ipynb +0 -0
  55. {polytope_python-2.1.10 → polytope_python-2.1.12}/docs/Service/Examples/boundingbox_example.ipynb +0 -0
  56. {polytope_python-2.1.10 → polytope_python-2.1.12}/docs/Service/Examples/circle_example.ipynb +0 -0
  57. {polytope_python-2.1.10 → polytope_python-2.1.12}/docs/Service/Examples/climate_dt_example.ipynb +0 -0
  58. {polytope_python-2.1.10 → polytope_python-2.1.12}/docs/Service/Examples/country_example.ipynb +0 -0
  59. {polytope_python-2.1.10 → polytope_python-2.1.12}/docs/Service/Examples/examples.md +0 -0
  60. {polytope_python-2.1.10 → polytope_python-2.1.12}/docs/Service/Examples/extremes_dt_example.ipynb +0 -0
  61. {polytope_python-2.1.10 → polytope_python-2.1.12}/docs/Service/Examples/on-demand_dt_example.ipynb +0 -0
  62. {polytope_python-2.1.10 → polytope_python-2.1.12}/docs/Service/Examples/opendata_example.ipynb +0 -0
  63. {polytope_python-2.1.10 → polytope_python-2.1.12}/docs/Service/Examples/operational_example.ipynb +0 -0
  64. {polytope_python-2.1.10 → polytope_python-2.1.12}/docs/Service/Examples/polygon_example.ipynb +0 -0
  65. {polytope_python-2.1.10 → polytope_python-2.1.12}/docs/Service/Examples/position_example.ipynb +0 -0
  66. {polytope_python-2.1.10 → polytope_python-2.1.12}/docs/Service/Examples/timeseries_example.ipynb +0 -0
  67. {polytope_python-2.1.10 → polytope_python-2.1.12}/docs/Service/Examples/trajectory_example.ipynb +0 -0
  68. {polytope_python-2.1.10 → polytope_python-2.1.12}/docs/Service/Examples/vertical_profile_example.ipynb +0 -0
  69. {polytope_python-2.1.10 → polytope_python-2.1.12}/docs/Service/Features/boundingbox.md +0 -0
  70. {polytope_python-2.1.10 → polytope_python-2.1.12}/docs/Service/Features/circle.md +0 -0
  71. {polytope_python-2.1.10 → polytope_python-2.1.12}/docs/Service/Features/feature.md +0 -0
  72. {polytope_python-2.1.10 → polytope_python-2.1.12}/docs/Service/Features/polygon.md +0 -0
  73. {polytope_python-2.1.10 → polytope_python-2.1.12}/docs/Service/Features/position.md +0 -0
  74. {polytope_python-2.1.10 → polytope_python-2.1.12}/docs/Service/Features/timeseries.md +0 -0
  75. {polytope_python-2.1.10 → polytope_python-2.1.12}/docs/Service/Features/trajectory.md +0 -0
  76. {polytope_python-2.1.10 → polytope_python-2.1.12}/docs/Service/Features/vertical_profile.md +0 -0
  77. {polytope_python-2.1.10 → polytope_python-2.1.12}/docs/Service/Installation.md +0 -0
  78. {polytope_python-2.1.10 → polytope_python-2.1.12}/docs/Service/Overview.md +0 -0
  79. {polytope_python-2.1.10 → polytope_python-2.1.12}/docs/Service/Quick_Start.md +0 -0
  80. {polytope_python-2.1.10 → polytope_python-2.1.12}/docs/extra.css +0 -0
  81. {polytope_python-2.1.10 → polytope_python-2.1.12}/docs/images/flight_path.png +0 -0
  82. {polytope_python-2.1.10 → polytope_python-2.1.12}/docs/images/greece.png +0 -0
  83. {polytope_python-2.1.10 → polytope_python-2.1.12}/docs/images/logo.gif +0 -0
  84. {polytope_python-2.1.10 → polytope_python-2.1.12}/docs/images/polytope_feature.png +0 -0
  85. {polytope_python-2.1.10 → polytope_python-2.1.12}/docs/images/polytope_logo_new_animated_AdobeExpress_3.gif +0 -0
  86. {polytope_python-2.1.10 → polytope_python-2.1.12}/docs/images/timeseries.png +0 -0
  87. {polytope_python-2.1.10 → polytope_python-2.1.12}/docs/images/timeseries_example.png +0 -0
  88. {polytope_python-2.1.10 → polytope_python-2.1.12}/docs/images/timeseries_qs.png +0 -0
  89. {polytope_python-2.1.10 → polytope_python-2.1.12}/docs/index.md +0 -0
  90. {polytope_python-2.1.10 → polytope_python-2.1.12}/docs/requirements.txt +0 -0
  91. {polytope_python-2.1.10 → polytope_python-2.1.12}/examples/3D_shipping_route.py +0 -0
  92. {polytope_python-2.1.10 → polytope_python-2.1.12}/examples/3D_shipping_route_wave_model.py +0 -0
  93. {polytope_python-2.1.10 → polytope_python-2.1.12}/examples/4D_flight_path.py +0 -0
  94. {polytope_python-2.1.10 → polytope_python-2.1.12}/examples/country_slicing.py +0 -0
  95. {polytope_python-2.1.10 → polytope_python-2.1.12}/examples/cyclic_route_around_earth.py +0 -0
  96. {polytope_python-2.1.10 → polytope_python-2.1.12}/examples/data/EMODnet_HA_WindFarms_pg_20220324.shp +0 -0
  97. {polytope_python-2.1.10 → polytope_python-2.1.12}/examples/data/EMODnet_HA_WindFarms_pg_20220324.shx +0 -0
  98. {polytope_python-2.1.10 → polytope_python-2.1.12}/examples/data/Shipping-Lanes-v1.shp +0 -0
  99. {polytope_python-2.1.10 → polytope_python-2.1.12}/examples/data/Shipping-Lanes-v1.shx +0 -0
  100. {polytope_python-2.1.10 → polytope_python-2.1.12}/examples/data/World_Countries__Generalized_.shp +0 -0
  101. {polytope_python-2.1.10 → polytope_python-2.1.12}/examples/data/World_Countries__Generalized_.shx +0 -0
  102. {polytope_python-2.1.10 → polytope_python-2.1.12}/examples/data/earth_image.jpg +0 -0
  103. {polytope_python-2.1.10 → polytope_python-2.1.12}/examples/data/map_earth_4k.jpg +0 -0
  104. {polytope_python-2.1.10 → polytope_python-2.1.12}/examples/data/mars_req_9km_wind.req +0 -0
  105. {polytope_python-2.1.10 → polytope_python-2.1.12}/examples/data/mars_req_levels.req +0 -0
  106. {polytope_python-2.1.10 → polytope_python-2.1.12}/examples/data/mars_req_timeseries.req +0 -0
  107. {polytope_python-2.1.10 → polytope_python-2.1.12}/examples/data/output4.grib +0 -0
  108. {polytope_python-2.1.10 → polytope_python-2.1.12}/examples/data/output4.req +0 -0
  109. {polytope_python-2.1.10 → polytope_python-2.1.12}/examples/data/output8.grib +0 -0
  110. {polytope_python-2.1.10 → polytope_python-2.1.12}/examples/data/output8.req +0 -0
  111. {polytope_python-2.1.10 → polytope_python-2.1.12}/examples/data/temp_model_levels.grib +0 -0
  112. {polytope_python-2.1.10 → polytope_python-2.1.12}/examples/data/timeseries_t2m.grib +0 -0
  113. {polytope_python-2.1.10 → polytope_python-2.1.12}/examples/data/winds.grib +0 -0
  114. {polytope_python-2.1.10 → polytope_python-2.1.12}/examples/healpix_grid_box_example.py +0 -0
  115. {polytope_python-2.1.10 → polytope_python-2.1.12}/examples/octahedral_grid_box_example.py +0 -0
  116. {polytope_python-2.1.10 → polytope_python-2.1.12}/examples/octahedral_grid_country_example.py +0 -0
  117. {polytope_python-2.1.10 → polytope_python-2.1.12}/examples/plotting_country_data.py +0 -0
  118. {polytope_python-2.1.10 → polytope_python-2.1.12}/examples/read_me_example.py +0 -0
  119. {polytope_python-2.1.10 → polytope_python-2.1.12}/examples/requirements_examples.txt +0 -0
  120. {polytope_python-2.1.10 → polytope_python-2.1.12}/examples/slicing_all_ecmwf_countries.py +0 -0
  121. {polytope_python-2.1.10 → polytope_python-2.1.12}/examples/timeseries_example.py +0 -0
  122. {polytope_python-2.1.10 → polytope_python-2.1.12}/examples/wind_farms.py +0 -0
  123. {polytope_python-2.1.10 → polytope_python-2.1.12}/mkdocs.yml +0 -0
  124. {polytope_python-2.1.10 → polytope_python-2.1.12}/performance/fdb_performance.py +0 -0
  125. {polytope_python-2.1.10 → polytope_python-2.1.12}/performance/fdb_performance_3D.py +0 -0
  126. {polytope_python-2.1.10 → polytope_python-2.1.12}/performance/fdb_scalability_plot.py +0 -0
  127. {polytope_python-2.1.10 → polytope_python-2.1.12}/performance/fdb_slice_many_numbers_timeseries.py +0 -0
  128. {polytope_python-2.1.10 → polytope_python-2.1.12}/performance/performance_many_num_steps.py +0 -0
  129. {polytope_python-2.1.10 → polytope_python-2.1.12}/performance/plotting_scalability.py +0 -0
  130. {polytope_python-2.1.10 → polytope_python-2.1.12}/performance/scalability_test.py +0 -0
  131. {polytope_python-2.1.10 → polytope_python-2.1.12}/performance/scalability_test_2.py +0 -0
  132. {polytope_python-2.1.10 → polytope_python-2.1.12}/performance_unstructured/octahedral_vs_unstructured_slicing.py +0 -0
  133. {polytope_python-2.1.10 → polytope_python-2.1.12}/performance_unstructured/plot_structured_vs_unstructured_slicing.py +0 -0
  134. {polytope_python-2.1.10 → polytope_python-2.1.12}/polytope_feature/__init__.py +0 -0
  135. {polytope_python-2.1.10 → polytope_python-2.1.12}/polytope_feature/datacube/__init__.py +0 -0
  136. {polytope_python-2.1.10 → polytope_python-2.1.12}/polytope_feature/datacube/backends/__init__.py +0 -0
  137. {polytope_python-2.1.10 → polytope_python-2.1.12}/polytope_feature/datacube/backends/catalogue_helper.py +0 -0
  138. {polytope_python-2.1.10 → polytope_python-2.1.12}/polytope_feature/datacube/backends/datacube.py +0 -0
  139. {polytope_python-2.1.10 → polytope_python-2.1.12}/polytope_feature/datacube/backends/fdb.py +0 -0
  140. {polytope_python-2.1.10 → polytope_python-2.1.12}/polytope_feature/datacube/backends/mock.py +0 -0
  141. {polytope_python-2.1.10 → polytope_python-2.1.12}/polytope_feature/datacube/backends/xarray.py +0 -0
  142. {polytope_python-2.1.10 → polytope_python-2.1.12}/polytope_feature/datacube/datacube_axis.py +0 -0
  143. {polytope_python-2.1.10 → polytope_python-2.1.12}/polytope_feature/datacube/index_tree.proto +0 -0
  144. {polytope_python-2.1.10 → polytope_python-2.1.12}/polytope_feature/datacube/index_tree_pb2.py +0 -0
  145. {polytope_python-2.1.10 → polytope_python-2.1.12}/polytope_feature/datacube/quadtree/quad_tree.py +0 -0
  146. {polytope_python-2.1.10 → polytope_python-2.1.12}/polytope_feature/datacube/quadtree/quadtree_additional_operations.py +0 -0
  147. {polytope_python-2.1.10 → polytope_python-2.1.12}/polytope_feature/datacube/switching_grid_helper.py +0 -0
  148. {polytope_python-2.1.10 → polytope_python-2.1.12}/polytope_feature/datacube/tensor_index_tree.py +0 -0
  149. {polytope_python-2.1.10 → polytope_python-2.1.12}/polytope_feature/datacube/transformations/__init__.py +0 -0
  150. {polytope_python-2.1.10 → polytope_python-2.1.12}/polytope_feature/datacube/transformations/datacube_cyclic/__init__.py +0 -0
  151. {polytope_python-2.1.10 → polytope_python-2.1.12}/polytope_feature/datacube/transformations/datacube_cyclic/datacube_cyclic.py +0 -0
  152. {polytope_python-2.1.10 → polytope_python-2.1.12}/polytope_feature/datacube/transformations/datacube_mappers/__init__.py +0 -0
  153. {polytope_python-2.1.10 → polytope_python-2.1.12}/polytope_feature/datacube/transformations/datacube_mappers/datacube_mappers.py +0 -0
  154. {polytope_python-2.1.10 → polytope_python-2.1.12}/polytope_feature/datacube/transformations/datacube_mappers/mapper_types/__init__.py +0 -0
  155. {polytope_python-2.1.10 → polytope_python-2.1.12}/polytope_feature/datacube/transformations/datacube_mappers/mapper_types/healpix.py +0 -0
  156. {polytope_python-2.1.10 → polytope_python-2.1.12}/polytope_feature/datacube/transformations/datacube_mappers/mapper_types/healpix_nested.py +0 -0
  157. {polytope_python-2.1.10 → polytope_python-2.1.12}/polytope_feature/datacube/transformations/datacube_mappers/mapper_types/irregular.py +0 -0
  158. {polytope_python-2.1.10 → polytope_python-2.1.12}/polytope_feature/datacube/transformations/datacube_mappers/mapper_types/irregular_mapper_types/__init__.py +0 -0
  159. {polytope_python-2.1.10 → polytope_python-2.1.12}/polytope_feature/datacube/transformations/datacube_mappers/mapper_types/irregular_mapper_types/icon.py +0 -0
  160. {polytope_python-2.1.10 → polytope_python-2.1.12}/polytope_feature/datacube/transformations/datacube_mappers/mapper_types/irregular_mapper_types/lambert_conformal.py +0 -0
  161. {polytope_python-2.1.10 → polytope_python-2.1.12}/polytope_feature/datacube/transformations/datacube_mappers/mapper_types/irregular_mapper_types/unstructured.py +0 -0
  162. {polytope_python-2.1.10 → polytope_python-2.1.12}/polytope_feature/datacube/transformations/datacube_mappers/mapper_types/local_regular.py +0 -0
  163. {polytope_python-2.1.10 → polytope_python-2.1.12}/polytope_feature/datacube/transformations/datacube_mappers/mapper_types/octahedral.py +0 -0
  164. {polytope_python-2.1.10 → polytope_python-2.1.12}/polytope_feature/datacube/transformations/datacube_mappers/mapper_types/reduced_gaussian.py +0 -0
  165. {polytope_python-2.1.10 → polytope_python-2.1.12}/polytope_feature/datacube/transformations/datacube_mappers/mapper_types/reduced_ll.py +0 -0
  166. {polytope_python-2.1.10 → polytope_python-2.1.12}/polytope_feature/datacube/transformations/datacube_mappers/mapper_types/regular.py +0 -0
  167. {polytope_python-2.1.10 → polytope_python-2.1.12}/polytope_feature/datacube/transformations/datacube_merger/__init__.py +0 -0
  168. {polytope_python-2.1.10 → polytope_python-2.1.12}/polytope_feature/datacube/transformations/datacube_merger/datacube_merger.py +0 -0
  169. {polytope_python-2.1.10 → polytope_python-2.1.12}/polytope_feature/datacube/transformations/datacube_reverse/__init__.py +0 -0
  170. {polytope_python-2.1.10 → polytope_python-2.1.12}/polytope_feature/datacube/transformations/datacube_reverse/datacube_reverse.py +0 -0
  171. {polytope_python-2.1.10 → polytope_python-2.1.12}/polytope_feature/datacube/transformations/datacube_transformations.py +0 -0
  172. {polytope_python-2.1.10 → polytope_python-2.1.12}/polytope_feature/datacube/transformations/datacube_type_change/__init__.py +0 -0
  173. {polytope_python-2.1.10 → polytope_python-2.1.12}/polytope_feature/datacube/transformations/datacube_type_change/datacube_type_change.py +0 -0
  174. {polytope_python-2.1.10 → polytope_python-2.1.12}/polytope_feature/datacube/tree_encoding.py +0 -0
  175. {polytope_python-2.1.10 → polytope_python-2.1.12}/polytope_feature/engine/__init__.py +0 -0
  176. {polytope_python-2.1.10 → polytope_python-2.1.12}/polytope_feature/engine/engine.py +0 -0
  177. {polytope_python-2.1.10 → polytope_python-2.1.12}/polytope_feature/engine/hullslicer.py +0 -0
  178. {polytope_python-2.1.10 → polytope_python-2.1.12}/polytope_feature/engine/optimised_point_in_polygon_slicer.py +0 -0
  179. {polytope_python-2.1.10 → polytope_python-2.1.12}/polytope_feature/engine/optimised_quadtree_slicer.py +0 -0
  180. {polytope_python-2.1.10 → polytope_python-2.1.12}/polytope_feature/engine/point_in_polygon_slicer.py +0 -0
  181. {polytope_python-2.1.10 → polytope_python-2.1.12}/polytope_feature/engine/quadtree_slicer.py +0 -0
  182. {polytope_python-2.1.10 → polytope_python-2.1.12}/polytope_feature/engine/slicing_tools.py +0 -0
  183. {polytope_python-2.1.10 → polytope_python-2.1.12}/polytope_feature/options.py +0 -0
  184. {polytope_python-2.1.10 → polytope_python-2.1.12}/polytope_feature/polytope.py +0 -0
  185. {polytope_python-2.1.10 → polytope_python-2.1.12}/polytope_feature/shapes.py +0 -0
  186. {polytope_python-2.1.10 → polytope_python-2.1.12}/polytope_feature/utility/__init__.py +0 -0
  187. {polytope_python-2.1.10 → polytope_python-2.1.12}/polytope_feature/utility/combinatorics.py +0 -0
  188. {polytope_python-2.1.10 → polytope_python-2.1.12}/polytope_feature/utility/engine_tools.py +0 -0
  189. {polytope_python-2.1.10 → polytope_python-2.1.12}/polytope_feature/utility/exceptions.py +0 -0
  190. {polytope_python-2.1.10 → polytope_python-2.1.12}/polytope_feature/utility/geometry.py +0 -0
  191. {polytope_python-2.1.10 → polytope_python-2.1.12}/polytope_feature/utility/list_tools.py +0 -0
  192. {polytope_python-2.1.10 → polytope_python-2.1.12}/polytope_feature/utility/profiling.py +0 -0
  193. {polytope_python-2.1.10 → polytope_python-2.1.12}/polytope_python.egg-info/dependency_links.txt +0 -0
  194. {polytope_python-2.1.10 → polytope_python-2.1.12}/polytope_python.egg-info/requires.txt +0 -0
  195. {polytope_python-2.1.10 → polytope_python-2.1.12}/polytope_python.egg-info/top_level.txt +0 -0
  196. {polytope_python-2.1.10 → polytope_python-2.1.12}/pyproject.toml +0 -0
  197. {polytope_python-2.1.10 → polytope_python-2.1.12}/rust/Cargo.toml +0 -0
  198. {polytope_python-2.1.10 → polytope_python-2.1.12}/rust/src/healpix_nested.rs +0 -0
  199. {polytope_python-2.1.10 → polytope_python-2.1.12}/rust/src/lambert_conformal.rs +0 -0
  200. {polytope_python-2.1.10 → polytope_python-2.1.12}/rust/src/lib.rs +0 -0
  201. {polytope_python-2.1.10 → polytope_python-2.1.12}/rust/src/list_tools.rs +0 -0
  202. {polytope_python-2.1.10 → polytope_python-2.1.12}/rust/src/octahedral.rs +0 -0
  203. {polytope_python-2.1.10 → polytope_python-2.1.12}/rust/src/point_in_polygon.rs +0 -0
  204. {polytope_python-2.1.10 → polytope_python-2.1.12}/rust/src/quadtree_mod.rs +0 -0
  205. {polytope_python-2.1.10 → polytope_python-2.1.12}/rust/src/slicing_tools.rs +0 -0
  206. {polytope_python-2.1.10 → polytope_python-2.1.12}/setup.cfg +0 -0
  207. {polytope_python-2.1.10 → polytope_python-2.1.12}/setup.py +0 -0
  208. {polytope_python-2.1.10 → polytope_python-2.1.12}/tests/conftest.py +0 -0
  209. {polytope_python-2.1.10 → polytope_python-2.1.12}/tests/fdb_data/schema +0 -0
  210. {polytope_python-2.1.10 → polytope_python-2.1.12}/tests/helper_functions.py +0 -0
  211. {polytope_python-2.1.10 → polytope_python-2.1.12}/tests/profiled_quadtree.profile +0 -0
  212. {polytope_python-2.1.10 → polytope_python-2.1.12}/tests/quadtree_slicer_profiler.py +0 -0
  213. {polytope_python-2.1.10 → polytope_python-2.1.12}/tests/test_axis_mappers.py +0 -0
  214. {polytope_python-2.1.10 → polytope_python-2.1.12}/tests/test_bad_request_error.py +0 -0
  215. {polytope_python-2.1.10 → polytope_python-2.1.12}/tests/test_combinatorics.py +0 -0
  216. {polytope_python-2.1.10 → polytope_python-2.1.12}/tests/test_cyclic_axis_over_negative_vals.py +0 -0
  217. {polytope_python-2.1.10 → polytope_python-2.1.12}/tests/test_cyclic_axis_slicer_not_0.py +0 -0
  218. {polytope_python-2.1.10 → polytope_python-2.1.12}/tests/test_cyclic_axis_slicing.py +0 -0
  219. {polytope_python-2.1.10 → polytope_python-2.1.12}/tests/test_cyclic_nearest.py +0 -0
  220. {polytope_python-2.1.10 → polytope_python-2.1.12}/tests/test_cyclic_simple.py +0 -0
  221. {polytope_python-2.1.10 → polytope_python-2.1.12}/tests/test_cyclic_snapping.py +0 -0
  222. {polytope_python-2.1.10 → polytope_python-2.1.12}/tests/test_datacube_axes_init.py +0 -0
  223. {polytope_python-2.1.10 → polytope_python-2.1.12}/tests/test_datacube_mock.py +0 -0
  224. {polytope_python-2.1.10 → polytope_python-2.1.12}/tests/test_datacube_xarray.py +0 -0
  225. {polytope_python-2.1.10 → polytope_python-2.1.12}/tests/test_date_time_unmerged.py +0 -0
  226. {polytope_python-2.1.10 → polytope_python-2.1.12}/tests/test_ecmwf_oper_data_fdb.py +0 -0
  227. {polytope_python-2.1.10 → polytope_python-2.1.12}/tests/test_engine_slicer.py +0 -0
  228. {polytope_python-2.1.10 → polytope_python-2.1.12}/tests/test_fdb_datacube.py +0 -0
  229. {polytope_python-2.1.10 → polytope_python-2.1.12}/tests/test_fdb_unmap_tree.py +0 -0
  230. {polytope_python-2.1.10 → polytope_python-2.1.12}/tests/test_float_type.py +0 -0
  231. {polytope_python-2.1.10 → polytope_python-2.1.12}/tests/test_healpix_mapper.py +0 -0
  232. {polytope_python-2.1.10 → polytope_python-2.1.12}/tests/test_healpix_nested_grid.py +0 -0
  233. {polytope_python-2.1.10 → polytope_python-2.1.12}/tests/test_hull_slicer.py +0 -0
  234. {polytope_python-2.1.10 → polytope_python-2.1.12}/tests/test_hullslicer_engine.py +0 -0
  235. {polytope_python-2.1.10 → polytope_python-2.1.12}/tests/test_icon_grid_unstructured.py +0 -0
  236. {polytope_python-2.1.10 → polytope_python-2.1.12}/tests/test_icon_grid_unstructured_fdb.py +0 -0
  237. {polytope_python-2.1.10 → polytope_python-2.1.12}/tests/test_incomplete_tree_fdb.py +0 -0
  238. {polytope_python-2.1.10 → polytope_python-2.1.12}/tests/test_lambert_lam_grid_unstructured_fdb.py +0 -0
  239. {polytope_python-2.1.10 → polytope_python-2.1.12}/tests/test_lambert_lam_grid_unstructured_fdb_optimised_quadtree.py +0 -0
  240. {polytope_python-2.1.10 → polytope_python-2.1.12}/tests/test_local_grid_cyclic.py +0 -0
  241. {polytope_python-2.1.10 → polytope_python-2.1.12}/tests/test_local_regular_grid.py +0 -0
  242. {polytope_python-2.1.10 → polytope_python-2.1.12}/tests/test_local_swiss_grid.py +0 -0
  243. {polytope_python-2.1.10 → polytope_python-2.1.12}/tests/test_mappers.py +0 -0
  244. {polytope_python-2.1.10 → polytope_python-2.1.12}/tests/test_merge_cyclic_octahedral.py +0 -0
  245. {polytope_python-2.1.10 → polytope_python-2.1.12}/tests/test_merge_octahedral_one_axis.py +0 -0
  246. {polytope_python-2.1.10 → polytope_python-2.1.12}/tests/test_merge_transformation.py +0 -0
  247. {polytope_python-2.1.10 → polytope_python-2.1.12}/tests/test_multiple_param_fdb.py +0 -0
  248. {polytope_python-2.1.10 → polytope_python-2.1.12}/tests/test_octahedral_grid.py +0 -0
  249. {polytope_python-2.1.10 → polytope_python-2.1.12}/tests/test_orca_irregular_grid.py +0 -0
  250. {polytope_python-2.1.10 → polytope_python-2.1.12}/tests/test_orca_irregular_grid_optimised_point_in_polygon.py +0 -0
  251. {polytope_python-2.1.10 → polytope_python-2.1.12}/tests/test_orca_irregular_grid_point_in_polygon.py +0 -0
  252. {polytope_python-2.1.10 → polytope_python-2.1.12}/tests/test_override_md5_hash_options.py +0 -0
  253. {polytope_python-2.1.10 → polytope_python-2.1.12}/tests/test_point_nearest.py +0 -0
  254. {polytope_python-2.1.10 → polytope_python-2.1.12}/tests/test_point_shape.py +0 -0
  255. {polytope_python-2.1.10 → polytope_python-2.1.12}/tests/test_point_union.py +0 -0
  256. {polytope_python-2.1.10 → polytope_python-2.1.12}/tests/test_polytope_extract.py +0 -0
  257. {polytope_python-2.1.10 → polytope_python-2.1.12}/tests/test_polytope_extract_fdb.py +0 -0
  258. {polytope_python-2.1.10 → polytope_python-2.1.12}/tests/test_profiling_requesttree.py +0 -0
  259. {polytope_python-2.1.10 → polytope_python-2.1.12}/tests/test_quad_tree.py +0 -0
  260. {polytope_python-2.1.10 → polytope_python-2.1.12}/tests/test_quadtree_edge_cases.py +0 -0
  261. {polytope_python-2.1.10 → polytope_python-2.1.12}/tests/test_quadtree_indices.py +0 -0
  262. {polytope_python-2.1.10 → polytope_python-2.1.12}/tests/test_quadtree_optimisation.py +0 -0
  263. {polytope_python-2.1.10 → polytope_python-2.1.12}/tests/test_reduced_ll_grid.py +0 -0
  264. {polytope_python-2.1.10 → polytope_python-2.1.12}/tests/test_regular_grid.py +0 -0
  265. {polytope_python-2.1.10 → polytope_python-2.1.12}/tests/test_regular_reduced_grid.py +0 -0
  266. {polytope_python-2.1.10 → polytope_python-2.1.12}/tests/test_request_tree.py +0 -0
  267. {polytope_python-2.1.10 → polytope_python-2.1.12}/tests/test_request_trees_after_slicing.py +0 -0
  268. {polytope_python-2.1.10 → polytope_python-2.1.12}/tests/test_reverse_transformation.py +0 -0
  269. {polytope_python-2.1.10 → polytope_python-2.1.12}/tests/test_shapes.py +0 -0
  270. {polytope_python-2.1.10 → polytope_python-2.1.12}/tests/test_shapes_volume.py +0 -0
  271. {polytope_python-2.1.10 → polytope_python-2.1.12}/tests/test_slice_date_range_fdb.py +0 -0
  272. {polytope_python-2.1.10 → polytope_python-2.1.12}/tests/test_slice_date_range_fdb_v2.py +0 -0
  273. {polytope_python-2.1.10 → polytope_python-2.1.12}/tests/test_slice_fdb_box.py +0 -0
  274. {polytope_python-2.1.10 → polytope_python-2.1.12}/tests/test_slicer_engine.py +0 -0
  275. {polytope_python-2.1.10 → polytope_python-2.1.12}/tests/test_slicer_era5.py +0 -0
  276. {polytope_python-2.1.10 → polytope_python-2.1.12}/tests/test_slicer_xarray.py +0 -0
  277. {polytope_python-2.1.10 → polytope_python-2.1.12}/tests/test_slicing_unsliceable_axis.py +0 -0
  278. {polytope_python-2.1.10 → polytope_python-2.1.12}/tests/test_slicing_xarray_3D.py +0 -0
  279. {polytope_python-2.1.10 → polytope_python-2.1.12}/tests/test_slicing_xarray_4D.py +0 -0
  280. {polytope_python-2.1.10 → polytope_python-2.1.12}/tests/test_snapping.py +0 -0
  281. {polytope_python-2.1.10 → polytope_python-2.1.12}/tests/test_snapping_real_data.py +0 -0
  282. {polytope_python-2.1.10 → polytope_python-2.1.12}/tests/test_tree_protobuf.py +0 -0
  283. {polytope_python-2.1.10 → polytope_python-2.1.12}/tests/test_tree_protobuf_encoding.py +0 -0
  284. {polytope_python-2.1.10 → polytope_python-2.1.12}/tests/test_tree_protobuf_encoding_fdb.py +0 -0
  285. {polytope_python-2.1.10 → polytope_python-2.1.12}/tests/test_type_change_transformation.py +0 -0
  286. {polytope_python-2.1.10 → polytope_python-2.1.12}/tests/test_union_gj.py +0 -0
  287. {polytope_python-2.1.10 → polytope_python-2.1.12}/tests/test_union_point_box.py +0 -0
  288. {polytope_python-2.1.10 → polytope_python-2.1.12}/tests/test_wave_spectra_data.py +0 -0
  289. {polytope_python-2.1.10 → polytope_python-2.1.12}/tox.ini +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: polytope-python
3
- Version: 2.1.10
3
+ Version: 2.1.12
4
4
  Summary: Polytope datacube feature extraction library
5
5
  Author-email: "European Centre for Medium-Range Weather Forecasts (ECMWF)" <software.support@ecmwf.int>
6
6
  Maintainer-email: James Hawkes <James.Hawkes@ecmwf.int>, Mathilde Leuridan <Mathilde.Leuridan@ecmwf.int>
@@ -11,8 +11,8 @@ Full field requests return GRIB data. They use the same keys as [MARS](https://c
11
11
  For full field extraction the Polytope service supports retrieval from the following datasets:
12
12
 
13
13
  * ECMWF Operational Data from the last two days [Further Details](https://apps.ecmwf.int/mars-catalogue/)
14
- * DestinE Climate Digital Twin [Further Details](https://confluence.ecmwf.int/display/DDCZArchive/Climate+DT+overview)
15
- * DestinE Extremes Digital Twin [Further Details](https://confluence.ecmwf.int/display/DDCZArchive/Extremes+DT+overview)
14
+ * DestinE Climate Digital Twin [Further Details](https://destine.ecmwf.int/climate-change-adaptation-digital-twin-climate-dt/)
15
+ * DestinE Extremes Digital Twin [Further Details](https://destine.ecmwf.int/weather-induced-extremes-digital-twin/)
16
16
  * DestinE On-Demand Extremes Digital Twin
17
17
  * ECMWF Opendata for AI datasets [Further Details](https://confluence.ecmwf.int/display/DAC/ECMWF+open+data%3A+real-time+forecasts+from+IFS+and+AIFS#ECMWFopendata:realtimeforecastsfromIFSandAIFS-IndexFilesIndexfiles)
18
18
 
@@ -1,2 +1,2 @@
1
1
  # Do not change! Do not track in version control!
2
- __version__ = "2.1.10"
2
+ __version__ = "2.1.12"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: polytope-python
3
- Version: 2.1.10
3
+ Version: 2.1.12
4
4
  Summary: Polytope datacube feature extraction library
5
5
  Author-email: "European Centre for Medium-Range Weather Forecasts (ECMWF)" <software.support@ecmwf.int>
6
6
  Maintainer-email: James Hawkes <James.Hawkes@ecmwf.int>, Mathilde Leuridan <Mathilde.Leuridan@ecmwf.int>
@@ -242,6 +242,7 @@ tests/test_merge_cyclic_octahedral.py
242
242
  tests/test_merge_octahedral_one_axis.py
243
243
  tests/test_merge_transformation.py
244
244
  tests/test_multiple_param_fdb.py
245
+ tests/test_nn_quadtree_search.py
245
246
  tests/test_octahedral_grid.py
246
247
  tests/test_orca_irregular_grid.py
247
248
  tests/test_orca_irregular_grid_optimised_point_in_polygon.py
@@ -5,13 +5,11 @@ pub fn dist2(a: (f64, f64), b: (f64, f64)) -> f64 {
5
5
  }
6
6
 
7
7
  pub fn box_dist2(center: (f64, f64), size: (f64, f64), point: (f64, f64)) -> f64 {
8
- let half_w = size.0 / 2.0;
9
- let half_h = size.1 / 2.0;
10
-
11
- let min_x = center.0 - half_w;
12
- let max_x = center.0 + half_w;
13
- let min_y = center.1 - half_h;
14
- let max_y = center.1 + half_h;
8
+ // `size` is already the half-extent of each axis
9
+ let min_x = center.0 - size.0;
10
+ let max_x = center.0 + size.0;
11
+ let min_y = center.1 - size.1;
12
+ let max_y = center.1 + size.1;
15
13
 
16
14
  let dx = if point.0 < min_x {
17
15
  min_x - point.0
@@ -0,0 +1,276 @@
1
+ """
2
+ Unit tests for QuadTree.nearest_neighbor() functionality.
3
+
4
+ Tests verify that:
5
+ 1. The nearest neighbor search correctly identifies the closest point
6
+ 2. Edge cases are handled (empty tree, single point, exact matches)
7
+ 3. Various query positions work correctly
8
+ 4. The algorithm works with large point sets
9
+ 5. Negative coordinates and scattered points work as expected
10
+ """
11
+
12
+ import pytest
13
+
14
+ try:
15
+ from polytope_feature.polytope_rs import QuadTree
16
+ except ImportError:
17
+ QuadTree = None
18
+
19
+ pytestmark = pytest.mark.skipif(QuadTree is None, reason="QuadTree not installed")
20
+
21
+
22
+ def squared_distance(p1, p2):
23
+ """Calculate squared Euclidean distance between two points."""
24
+ dx = p1[0] - p2[0]
25
+ dy = p1[1] - p2[1]
26
+ return dx * dx + dy * dy
27
+
28
+
29
+ def find_expected_nearest(query, points):
30
+ """
31
+ Brute force search to find expected nearest neighbor.
32
+
33
+ Args:
34
+ query: (x, y) query point
35
+ points: list of (x, y) points
36
+
37
+ Returns:
38
+ tuple: (index, distance_squared) or (None, None) if no points
39
+ """
40
+ if not points:
41
+ return None, None
42
+
43
+ min_distance = float("inf")
44
+ min_index = None
45
+
46
+ for idx, point in enumerate(points):
47
+ dist = squared_distance(query, point)
48
+ if dist < min_distance:
49
+ min_distance = dist
50
+ min_index = idx
51
+
52
+ return min_index, min_distance
53
+
54
+
55
+ class TestNearestNeighbor:
56
+ """Test suite for nearest neighbor search."""
57
+
58
+ def test_simple_grid(self):
59
+ """Test with simple grid of 5 points."""
60
+ print("\n=== Test: Simple Grid ===")
61
+ points = [
62
+ (10.0, 0.0), # index 1
63
+ (0.0, 0.0), # index 0
64
+ (0.0, 10.0), # index 2
65
+ (10.0, 10.0), # index 3
66
+ (5.0, 5.0), # index 4
67
+ ]
68
+
69
+ quadtree = QuadTree()
70
+ quadtree.build_point_tree(points)
71
+
72
+ # Test 1: Query at (1, 1) - should find point (0, 0)
73
+ query = (1.0, 1.0)
74
+ result = quadtree.nearest_neighbor(query, points)
75
+ expected = 1
76
+ assert result == expected, f"Query {query}: expected {expected}, got {result}"
77
+ print(f"✓ Query {query} → index {result} (point {points[result]})")
78
+
79
+ # Test 2: Query at (11, 11) - should find point (10, 10)
80
+ query = (11.0, 11.0)
81
+ result = quadtree.nearest_neighbor(query, points)
82
+ expected = 3
83
+ assert result == expected, f"Query {query}: expected {expected}, got {result}"
84
+ print(f"✓ Query {query} → index {result} (point {points[result]})")
85
+
86
+ # Test 3: Query at (5.1, 5.1) - should find point (5, 5)
87
+ query = (5.1, 5.1)
88
+ result = quadtree.nearest_neighbor(query, points)
89
+ expected = 4
90
+ assert result == expected, f"Query {query}: expected {expected}, got {result}"
91
+ print(f"✓ Query {query} → index {result} (point {points[result]})")
92
+
93
+ def test_exact_point_match(self):
94
+ """Test querying exactly on a point in the tree."""
95
+ print("\n=== Test: Exact Point Match ===")
96
+ points = [
97
+ (0.0, 0.0),
98
+ (10.0, 10.0),
99
+ (20.0, 20.0),
100
+ (15.0, 5.0),
101
+ ]
102
+
103
+ quadtree = QuadTree()
104
+ quadtree.build_point_tree(points)
105
+
106
+ # Query exactly at point (10, 10) - should find itself
107
+ query = (10.0, 10.0)
108
+ result = quadtree.nearest_neighbor(query, points)
109
+ expected = 1
110
+ assert result == expected, f"Query {query}: expected {expected}, got {result}"
111
+ print(f"✓ Query exactly at {query} → index {result} (itself)")
112
+
113
+ def test_random_points_correctness(self):
114
+ """Test correctness with random-ish points."""
115
+ print("\n=== Test: Random Points Correctness ===")
116
+ points = [
117
+ (1.0, 2.0),
118
+ (5.0, 3.0),
119
+ (9.0, 8.0),
120
+ (2.0, 9.0),
121
+ (7.0, 1.0),
122
+ (4.0, 6.0),
123
+ ]
124
+
125
+ quadtree = QuadTree()
126
+ quadtree.build_point_tree(points)
127
+
128
+ test_queries = [
129
+ (3.5, 2.5),
130
+ (6.0, 4.0),
131
+ (0.5, 0.5),
132
+ (8.5, 8.5),
133
+ ]
134
+
135
+ for query in test_queries:
136
+ result = quadtree.nearest_neighbor(query, points)
137
+ expected_idx, expected_dist = find_expected_nearest(query, points)
138
+
139
+ assert result == expected_idx, f"Query {query}: expected {expected_idx}, got {result}"
140
+
141
+ # Verify distances match
142
+ result_dist = squared_distance(query, points[result])
143
+ assert abs(result_dist - expected_dist) < 1e-10, f"Distance mismatch for query {query}"
144
+
145
+ print(f"✓ Query {query} → index {result} (point {points[result]}, dist²={result_dist:.2f})")
146
+
147
+ def test_single_point(self):
148
+ """Test edge case: only one point in tree."""
149
+ print("\n=== Test: Single Point ===")
150
+ points = [(5.0, 5.0)]
151
+
152
+ quadtree = QuadTree()
153
+ quadtree.build_point_tree(points)
154
+
155
+ query = (0.0, 0.0)
156
+ result = quadtree.nearest_neighbor(query, points)
157
+ expected = 0
158
+ assert result == expected, f"Query {query}: expected {expected}, got {result}"
159
+ print(f"✓ Query {query} with single point → index {result}")
160
+
161
+ def test_empty_tree(self):
162
+ """Test edge case: empty tree."""
163
+ print("\n=== Test: Empty Tree ===")
164
+ points = []
165
+
166
+ quadtree = QuadTree()
167
+ # Don't call build_point_tree on empty list
168
+
169
+ query = (5.0, 5.0)
170
+ result = quadtree.nearest_neighbor(query, points)
171
+ assert result is None, f"Query on empty tree should return None, got {result}"
172
+
173
+ def test_distant_query(self):
174
+ """Test with query point far from all tree points."""
175
+ print("\n=== Test: Distant Query ===")
176
+ points = [
177
+ (0.0, 0.0),
178
+ (1.0, 1.0),
179
+ (2.0, 2.0),
180
+ ]
181
+
182
+ quadtree = QuadTree()
183
+ quadtree.build_point_tree(points)
184
+
185
+ # Query far away - should still find correct nearest
186
+ query = (100.0, 100.0)
187
+ result = quadtree.nearest_neighbor(query, points)
188
+ expected = 2 # (2, 2) is closest
189
+ assert result == expected, f"Query {query}: expected {expected}, got {result}"
190
+ print(f"✓ Query {query} (distant) → index {result} (point {points[result]})")
191
+
192
+ def test_negative_coordinates(self):
193
+ """Test with negative coordinates."""
194
+ print("\n=== Test: Negative Coordinates ===")
195
+ points = [
196
+ (-10.0, -10.0),
197
+ (-5.0, 0.0),
198
+ (0.0, 5.0),
199
+ (5.0, -5.0),
200
+ ]
201
+
202
+ quadtree = QuadTree()
203
+ quadtree.build_point_tree(points)
204
+
205
+ query = (-6.0, -9.0)
206
+ result = quadtree.nearest_neighbor(query, points)
207
+ expected_idx, _ = find_expected_nearest(query, points)
208
+
209
+ assert result == expected_idx, f"Query {query}: expected {expected_idx}, got {result}"
210
+ print(f"✓ Query {query} with negatives → index {result} (point {points[result]})")
211
+
212
+ def test_large_tree(self):
213
+ """Test with many points to exercise quadtree splitting."""
214
+ print("\n=== Test: Large Tree (400 points) ===")
215
+ # Create 20x20 grid
216
+ points = []
217
+ for i in range(20):
218
+ for j in range(20):
219
+ points.append((i * 5.0, j * 5.0))
220
+
221
+ print(f"Building tree with {len(points)} points...")
222
+ quadtree = QuadTree()
223
+ quadtree.build_point_tree(points)
224
+
225
+ test_queries = [
226
+ (12.5, 12.5),
227
+ (75.0, 75.0),
228
+ (2.0, 98.0),
229
+ (97.0, 2.0),
230
+ ]
231
+
232
+ for query in test_queries:
233
+ result = quadtree.nearest_neighbor(query, points)
234
+ expected_idx, expected_dist = find_expected_nearest(query, points)
235
+
236
+ assert result == expected_idx, f"Query {query}: expected {expected_idx}, got {result}"
237
+
238
+ result_dist = squared_distance(query, points[result])
239
+ print(f"✓ Query {query} → index {result} (dist²={result_dist:.2f})")
240
+
241
+ def run_all(self):
242
+ """Run all tests."""
243
+ print("=" * 60)
244
+ print("QUADTREE NEAREST NEIGHBOR TESTS")
245
+ print("=" * 60)
246
+
247
+ tests = [
248
+ self.test_simple_grid,
249
+ self.test_exact_point_match,
250
+ self.test_random_points_correctness,
251
+ self.test_single_point,
252
+ self.test_empty_tree,
253
+ self.test_distant_query,
254
+ self.test_negative_coordinates,
255
+ self.test_large_tree,
256
+ ]
257
+
258
+ passed = 0
259
+ failed = 0
260
+
261
+ for test in tests:
262
+ try:
263
+ test()
264
+ passed += 1
265
+ except AssertionError as e:
266
+ print(f"✗ FAILED: {e}")
267
+ failed += 1
268
+ except Exception as e:
269
+ print(f"✗ ERROR: {e}")
270
+ failed += 1
271
+
272
+ print("\n" + "=" * 60)
273
+ print(f"RESULTS: {passed} passed, {failed} failed")
274
+ print("=" * 60)
275
+
276
+ return failed == 0