polytope-python 2.1.9__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.9/polytope_python.egg-info → polytope_python-2.1.12}/PKG-INFO +1 -1
  2. {polytope_python-2.1.9 → polytope_python-2.1.12}/docs/Service/Full_fields.md +2 -2
  3. {polytope_python-2.1.9 → polytope_python-2.1.12}/polytope_feature/_version.py +1 -1
  4. {polytope_python-2.1.9 → polytope_python-2.1.12}/polytope_feature/options.py +9 -5
  5. {polytope_python-2.1.9 → polytope_python-2.1.12/polytope_python.egg-info}/PKG-INFO +1 -1
  6. {polytope_python-2.1.9 → polytope_python-2.1.12}/polytope_python.egg-info/SOURCES.txt +1 -0
  7. {polytope_python-2.1.9 → polytope_python-2.1.12}/rust/src/distance.rs +5 -7
  8. polytope_python-2.1.12/tests/test_nn_quadtree_search.py +276 -0
  9. {polytope_python-2.1.9 → polytope_python-2.1.12}/.github/ci-config.yml +0 -0
  10. {polytope_python-2.1.9 → polytope_python-2.1.12}/.github/ci-hpc-config.yml +0 -0
  11. {polytope_python-2.1.9 → polytope_python-2.1.12}/.github/workflows/cd.yml +0 -0
  12. {polytope_python-2.1.9 → polytope_python-2.1.12}/.github/workflows/ci.yml +0 -0
  13. {polytope_python-2.1.9 → polytope_python-2.1.12}/.github/workflows/downstream-ci.yml +0 -0
  14. {polytope_python-2.1.9 → polytope_python-2.1.12}/.github/workflows/label-public-pr.yml +0 -0
  15. {polytope_python-2.1.9 → polytope_python-2.1.12}/.github/workflows/test-pypi.yml +0 -0
  16. {polytope_python-2.1.9 → polytope_python-2.1.12}/.gitignore +0 -0
  17. {polytope_python-2.1.9 → polytope_python-2.1.12}/.pre-commit-config.yaml +0 -0
  18. {polytope_python-2.1.9 → polytope_python-2.1.12}/.readthedocs.yaml +0 -0
  19. {polytope_python-2.1.9 → polytope_python-2.1.12}/ACKNOWLEDGEMENTS.rst +0 -0
  20. {polytope_python-2.1.9 → polytope_python-2.1.12}/CONTRIBUTING.rst +0 -0
  21. {polytope_python-2.1.9 → polytope_python-2.1.12}/LICENSE +0 -0
  22. {polytope_python-2.1.9 → polytope_python-2.1.12}/Makefile +0 -0
  23. {polytope_python-2.1.9 → polytope_python-2.1.12}/README.md +0 -0
  24. {polytope_python-2.1.9 → polytope_python-2.1.12}/codecov.yml +0 -0
  25. {polytope_python-2.1.9 → polytope_python-2.1.12}/docs/Algorithm/Developer_Guide/API.md +0 -0
  26. {polytope_python-2.1.9 → polytope_python-2.1.12}/docs/Algorithm/Developer_Guide/Axis_types.md +0 -0
  27. {polytope_python-2.1.9 → polytope_python-2.1.12}/docs/Algorithm/Developer_Guide/Datacube.md +0 -0
  28. {polytope_python-2.1.9 → polytope_python-2.1.12}/docs/Algorithm/Developer_Guide/Overview.md +0 -0
  29. {polytope_python-2.1.9 → polytope_python-2.1.12}/docs/Algorithm/Developer_Guide/Slicer.md +0 -0
  30. {polytope_python-2.1.9 → polytope_python-2.1.12}/docs/Algorithm/Developer_Guide/images/Polytope_APIs_3.png +0 -0
  31. {polytope_python-2.1.9 → polytope_python-2.1.12}/docs/Algorithm/Developer_Guide/images/polytope_components_5.png +0 -0
  32. {polytope_python-2.1.9 → polytope_python-2.1.12}/docs/Algorithm/Developer_Guide/images/slicing_process.png +0 -0
  33. {polytope_python-2.1.9 → polytope_python-2.1.12}/docs/Algorithm/Developer_Guide/shapes.md +0 -0
  34. {polytope_python-2.1.9 → polytope_python-2.1.12}/docs/Algorithm/Overview/Overview.md +0 -0
  35. {polytope_python-2.1.9 → polytope_python-2.1.12}/docs/Algorithm/Overview/Polytope_at_ECMWF.md +0 -0
  36. {polytope_python-2.1.9 → polytope_python-2.1.12}/docs/Algorithm/Overview/images_overview/ecmwf_datacube.png +0 -0
  37. {polytope_python-2.1.9 → polytope_python-2.1.12}/docs/Algorithm/Overview/images_overview/ecmwf_polytope.png +0 -0
  38. {polytope_python-2.1.9 → polytope_python-2.1.12}/docs/Algorithm/User_Guide/Building_Features.md +0 -0
  39. {polytope_python-2.1.9 → polytope_python-2.1.12}/docs/Algorithm/User_Guide/Example.md +0 -0
  40. {polytope_python-2.1.9 → polytope_python-2.1.12}/docs/Algorithm/User_Guide/Getting_started.md +0 -0
  41. {polytope_python-2.1.9 → polytope_python-2.1.12}/docs/Algorithm/User_Guide/Overview.md +0 -0
  42. {polytope_python-2.1.9 → polytope_python-2.1.12}/docs/Algorithm/User_Guide/images_users/shipping_route.png +0 -0
  43. {polytope_python-2.1.9 → polytope_python-2.1.12}/docs/Client/Overview.md +0 -0
  44. {polytope_python-2.1.9 → polytope_python-2.1.12}/docs/Server/Design.md +0 -0
  45. {polytope_python-2.1.9 → polytope_python-2.1.12}/docs/Server/Overview.md +0 -0
  46. {polytope_python-2.1.9 → polytope_python-2.1.12}/docs/Service/Data_Portfolio.md +0 -0
  47. {polytope_python-2.1.9 → polytope_python-2.1.12}/docs/Service/Design_doc.md +0 -0
  48. {polytope_python-2.1.9 → polytope_python-2.1.12}/docs/Service/Examples/OpenData/od_boundingbox_example.ipynb +0 -0
  49. {polytope_python-2.1.9 → polytope_python-2.1.12}/docs/Service/Examples/OpenData/od_circle_example.ipynb +0 -0
  50. {polytope_python-2.1.9 → polytope_python-2.1.12}/docs/Service/Examples/OpenData/od_country_example.ipynb +0 -0
  51. {polytope_python-2.1.9 → polytope_python-2.1.12}/docs/Service/Examples/OpenData/od_polygon_example.ipynb +0 -0
  52. {polytope_python-2.1.9 → polytope_python-2.1.12}/docs/Service/Examples/OpenData/od_position_example.ipynb +0 -0
  53. {polytope_python-2.1.9 → polytope_python-2.1.12}/docs/Service/Examples/OpenData/od_timeseries_example.ipynb +0 -0
  54. {polytope_python-2.1.9 → polytope_python-2.1.12}/docs/Service/Examples/OpenData/od_trajectory_example.ipynb +0 -0
  55. {polytope_python-2.1.9 → polytope_python-2.1.12}/docs/Service/Examples/OpenData/od_vertical_profile_example.ipynb +0 -0
  56. {polytope_python-2.1.9 → polytope_python-2.1.12}/docs/Service/Examples/boundingbox_example.ipynb +0 -0
  57. {polytope_python-2.1.9 → polytope_python-2.1.12}/docs/Service/Examples/circle_example.ipynb +0 -0
  58. {polytope_python-2.1.9 → polytope_python-2.1.12}/docs/Service/Examples/climate_dt_example.ipynb +0 -0
  59. {polytope_python-2.1.9 → polytope_python-2.1.12}/docs/Service/Examples/country_example.ipynb +0 -0
  60. {polytope_python-2.1.9 → polytope_python-2.1.12}/docs/Service/Examples/examples.md +0 -0
  61. {polytope_python-2.1.9 → polytope_python-2.1.12}/docs/Service/Examples/extremes_dt_example.ipynb +0 -0
  62. {polytope_python-2.1.9 → polytope_python-2.1.12}/docs/Service/Examples/on-demand_dt_example.ipynb +0 -0
  63. {polytope_python-2.1.9 → polytope_python-2.1.12}/docs/Service/Examples/opendata_example.ipynb +0 -0
  64. {polytope_python-2.1.9 → polytope_python-2.1.12}/docs/Service/Examples/operational_example.ipynb +0 -0
  65. {polytope_python-2.1.9 → polytope_python-2.1.12}/docs/Service/Examples/polygon_example.ipynb +0 -0
  66. {polytope_python-2.1.9 → polytope_python-2.1.12}/docs/Service/Examples/position_example.ipynb +0 -0
  67. {polytope_python-2.1.9 → polytope_python-2.1.12}/docs/Service/Examples/timeseries_example.ipynb +0 -0
  68. {polytope_python-2.1.9 → polytope_python-2.1.12}/docs/Service/Examples/trajectory_example.ipynb +0 -0
  69. {polytope_python-2.1.9 → polytope_python-2.1.12}/docs/Service/Examples/vertical_profile_example.ipynb +0 -0
  70. {polytope_python-2.1.9 → polytope_python-2.1.12}/docs/Service/Features/boundingbox.md +0 -0
  71. {polytope_python-2.1.9 → polytope_python-2.1.12}/docs/Service/Features/circle.md +0 -0
  72. {polytope_python-2.1.9 → polytope_python-2.1.12}/docs/Service/Features/feature.md +0 -0
  73. {polytope_python-2.1.9 → polytope_python-2.1.12}/docs/Service/Features/polygon.md +0 -0
  74. {polytope_python-2.1.9 → polytope_python-2.1.12}/docs/Service/Features/position.md +0 -0
  75. {polytope_python-2.1.9 → polytope_python-2.1.12}/docs/Service/Features/timeseries.md +0 -0
  76. {polytope_python-2.1.9 → polytope_python-2.1.12}/docs/Service/Features/trajectory.md +0 -0
  77. {polytope_python-2.1.9 → polytope_python-2.1.12}/docs/Service/Features/vertical_profile.md +0 -0
  78. {polytope_python-2.1.9 → polytope_python-2.1.12}/docs/Service/Installation.md +0 -0
  79. {polytope_python-2.1.9 → polytope_python-2.1.12}/docs/Service/Overview.md +0 -0
  80. {polytope_python-2.1.9 → polytope_python-2.1.12}/docs/Service/Quick_Start.md +0 -0
  81. {polytope_python-2.1.9 → polytope_python-2.1.12}/docs/extra.css +0 -0
  82. {polytope_python-2.1.9 → polytope_python-2.1.12}/docs/images/flight_path.png +0 -0
  83. {polytope_python-2.1.9 → polytope_python-2.1.12}/docs/images/greece.png +0 -0
  84. {polytope_python-2.1.9 → polytope_python-2.1.12}/docs/images/logo.gif +0 -0
  85. {polytope_python-2.1.9 → polytope_python-2.1.12}/docs/images/polytope_feature.png +0 -0
  86. {polytope_python-2.1.9 → polytope_python-2.1.12}/docs/images/polytope_logo_new_animated_AdobeExpress_3.gif +0 -0
  87. {polytope_python-2.1.9 → polytope_python-2.1.12}/docs/images/timeseries.png +0 -0
  88. {polytope_python-2.1.9 → polytope_python-2.1.12}/docs/images/timeseries_example.png +0 -0
  89. {polytope_python-2.1.9 → polytope_python-2.1.12}/docs/images/timeseries_qs.png +0 -0
  90. {polytope_python-2.1.9 → polytope_python-2.1.12}/docs/index.md +0 -0
  91. {polytope_python-2.1.9 → polytope_python-2.1.12}/docs/requirements.txt +0 -0
  92. {polytope_python-2.1.9 → polytope_python-2.1.12}/examples/3D_shipping_route.py +0 -0
  93. {polytope_python-2.1.9 → polytope_python-2.1.12}/examples/3D_shipping_route_wave_model.py +0 -0
  94. {polytope_python-2.1.9 → polytope_python-2.1.12}/examples/4D_flight_path.py +0 -0
  95. {polytope_python-2.1.9 → polytope_python-2.1.12}/examples/country_slicing.py +0 -0
  96. {polytope_python-2.1.9 → polytope_python-2.1.12}/examples/cyclic_route_around_earth.py +0 -0
  97. {polytope_python-2.1.9 → polytope_python-2.1.12}/examples/data/EMODnet_HA_WindFarms_pg_20220324.shp +0 -0
  98. {polytope_python-2.1.9 → polytope_python-2.1.12}/examples/data/EMODnet_HA_WindFarms_pg_20220324.shx +0 -0
  99. {polytope_python-2.1.9 → polytope_python-2.1.12}/examples/data/Shipping-Lanes-v1.shp +0 -0
  100. {polytope_python-2.1.9 → polytope_python-2.1.12}/examples/data/Shipping-Lanes-v1.shx +0 -0
  101. {polytope_python-2.1.9 → polytope_python-2.1.12}/examples/data/World_Countries__Generalized_.shp +0 -0
  102. {polytope_python-2.1.9 → polytope_python-2.1.12}/examples/data/World_Countries__Generalized_.shx +0 -0
  103. {polytope_python-2.1.9 → polytope_python-2.1.12}/examples/data/earth_image.jpg +0 -0
  104. {polytope_python-2.1.9 → polytope_python-2.1.12}/examples/data/map_earth_4k.jpg +0 -0
  105. {polytope_python-2.1.9 → polytope_python-2.1.12}/examples/data/mars_req_9km_wind.req +0 -0
  106. {polytope_python-2.1.9 → polytope_python-2.1.12}/examples/data/mars_req_levels.req +0 -0
  107. {polytope_python-2.1.9 → polytope_python-2.1.12}/examples/data/mars_req_timeseries.req +0 -0
  108. {polytope_python-2.1.9 → polytope_python-2.1.12}/examples/data/output4.grib +0 -0
  109. {polytope_python-2.1.9 → polytope_python-2.1.12}/examples/data/output4.req +0 -0
  110. {polytope_python-2.1.9 → polytope_python-2.1.12}/examples/data/output8.grib +0 -0
  111. {polytope_python-2.1.9 → polytope_python-2.1.12}/examples/data/output8.req +0 -0
  112. {polytope_python-2.1.9 → polytope_python-2.1.12}/examples/data/temp_model_levels.grib +0 -0
  113. {polytope_python-2.1.9 → polytope_python-2.1.12}/examples/data/timeseries_t2m.grib +0 -0
  114. {polytope_python-2.1.9 → polytope_python-2.1.12}/examples/data/winds.grib +0 -0
  115. {polytope_python-2.1.9 → polytope_python-2.1.12}/examples/healpix_grid_box_example.py +0 -0
  116. {polytope_python-2.1.9 → polytope_python-2.1.12}/examples/octahedral_grid_box_example.py +0 -0
  117. {polytope_python-2.1.9 → polytope_python-2.1.12}/examples/octahedral_grid_country_example.py +0 -0
  118. {polytope_python-2.1.9 → polytope_python-2.1.12}/examples/plotting_country_data.py +0 -0
  119. {polytope_python-2.1.9 → polytope_python-2.1.12}/examples/read_me_example.py +0 -0
  120. {polytope_python-2.1.9 → polytope_python-2.1.12}/examples/requirements_examples.txt +0 -0
  121. {polytope_python-2.1.9 → polytope_python-2.1.12}/examples/slicing_all_ecmwf_countries.py +0 -0
  122. {polytope_python-2.1.9 → polytope_python-2.1.12}/examples/timeseries_example.py +0 -0
  123. {polytope_python-2.1.9 → polytope_python-2.1.12}/examples/wind_farms.py +0 -0
  124. {polytope_python-2.1.9 → polytope_python-2.1.12}/mkdocs.yml +0 -0
  125. {polytope_python-2.1.9 → polytope_python-2.1.12}/performance/fdb_performance.py +0 -0
  126. {polytope_python-2.1.9 → polytope_python-2.1.12}/performance/fdb_performance_3D.py +0 -0
  127. {polytope_python-2.1.9 → polytope_python-2.1.12}/performance/fdb_scalability_plot.py +0 -0
  128. {polytope_python-2.1.9 → polytope_python-2.1.12}/performance/fdb_slice_many_numbers_timeseries.py +0 -0
  129. {polytope_python-2.1.9 → polytope_python-2.1.12}/performance/performance_many_num_steps.py +0 -0
  130. {polytope_python-2.1.9 → polytope_python-2.1.12}/performance/plotting_scalability.py +0 -0
  131. {polytope_python-2.1.9 → polytope_python-2.1.12}/performance/scalability_test.py +0 -0
  132. {polytope_python-2.1.9 → polytope_python-2.1.12}/performance/scalability_test_2.py +0 -0
  133. {polytope_python-2.1.9 → polytope_python-2.1.12}/performance_unstructured/octahedral_vs_unstructured_slicing.py +0 -0
  134. {polytope_python-2.1.9 → polytope_python-2.1.12}/performance_unstructured/plot_structured_vs_unstructured_slicing.py +0 -0
  135. {polytope_python-2.1.9 → polytope_python-2.1.12}/polytope_feature/__init__.py +0 -0
  136. {polytope_python-2.1.9 → polytope_python-2.1.12}/polytope_feature/datacube/__init__.py +0 -0
  137. {polytope_python-2.1.9 → polytope_python-2.1.12}/polytope_feature/datacube/backends/__init__.py +0 -0
  138. {polytope_python-2.1.9 → polytope_python-2.1.12}/polytope_feature/datacube/backends/catalogue_helper.py +0 -0
  139. {polytope_python-2.1.9 → polytope_python-2.1.12}/polytope_feature/datacube/backends/datacube.py +0 -0
  140. {polytope_python-2.1.9 → polytope_python-2.1.12}/polytope_feature/datacube/backends/fdb.py +0 -0
  141. {polytope_python-2.1.9 → polytope_python-2.1.12}/polytope_feature/datacube/backends/mock.py +0 -0
  142. {polytope_python-2.1.9 → polytope_python-2.1.12}/polytope_feature/datacube/backends/xarray.py +0 -0
  143. {polytope_python-2.1.9 → polytope_python-2.1.12}/polytope_feature/datacube/datacube_axis.py +0 -0
  144. {polytope_python-2.1.9 → polytope_python-2.1.12}/polytope_feature/datacube/index_tree.proto +0 -0
  145. {polytope_python-2.1.9 → polytope_python-2.1.12}/polytope_feature/datacube/index_tree_pb2.py +0 -0
  146. {polytope_python-2.1.9 → polytope_python-2.1.12}/polytope_feature/datacube/quadtree/quad_tree.py +0 -0
  147. {polytope_python-2.1.9 → polytope_python-2.1.12}/polytope_feature/datacube/quadtree/quadtree_additional_operations.py +0 -0
  148. {polytope_python-2.1.9 → polytope_python-2.1.12}/polytope_feature/datacube/switching_grid_helper.py +0 -0
  149. {polytope_python-2.1.9 → polytope_python-2.1.12}/polytope_feature/datacube/tensor_index_tree.py +0 -0
  150. {polytope_python-2.1.9 → polytope_python-2.1.12}/polytope_feature/datacube/transformations/__init__.py +0 -0
  151. {polytope_python-2.1.9 → polytope_python-2.1.12}/polytope_feature/datacube/transformations/datacube_cyclic/__init__.py +0 -0
  152. {polytope_python-2.1.9 → polytope_python-2.1.12}/polytope_feature/datacube/transformations/datacube_cyclic/datacube_cyclic.py +0 -0
  153. {polytope_python-2.1.9 → polytope_python-2.1.12}/polytope_feature/datacube/transformations/datacube_mappers/__init__.py +0 -0
  154. {polytope_python-2.1.9 → polytope_python-2.1.12}/polytope_feature/datacube/transformations/datacube_mappers/datacube_mappers.py +0 -0
  155. {polytope_python-2.1.9 → polytope_python-2.1.12}/polytope_feature/datacube/transformations/datacube_mappers/mapper_types/__init__.py +0 -0
  156. {polytope_python-2.1.9 → polytope_python-2.1.12}/polytope_feature/datacube/transformations/datacube_mappers/mapper_types/healpix.py +0 -0
  157. {polytope_python-2.1.9 → polytope_python-2.1.12}/polytope_feature/datacube/transformations/datacube_mappers/mapper_types/healpix_nested.py +0 -0
  158. {polytope_python-2.1.9 → polytope_python-2.1.12}/polytope_feature/datacube/transformations/datacube_mappers/mapper_types/irregular.py +0 -0
  159. {polytope_python-2.1.9 → polytope_python-2.1.12}/polytope_feature/datacube/transformations/datacube_mappers/mapper_types/irregular_mapper_types/__init__.py +0 -0
  160. {polytope_python-2.1.9 → polytope_python-2.1.12}/polytope_feature/datacube/transformations/datacube_mappers/mapper_types/irregular_mapper_types/icon.py +0 -0
  161. {polytope_python-2.1.9 → polytope_python-2.1.12}/polytope_feature/datacube/transformations/datacube_mappers/mapper_types/irregular_mapper_types/lambert_conformal.py +0 -0
  162. {polytope_python-2.1.9 → polytope_python-2.1.12}/polytope_feature/datacube/transformations/datacube_mappers/mapper_types/irregular_mapper_types/unstructured.py +0 -0
  163. {polytope_python-2.1.9 → polytope_python-2.1.12}/polytope_feature/datacube/transformations/datacube_mappers/mapper_types/local_regular.py +0 -0
  164. {polytope_python-2.1.9 → polytope_python-2.1.12}/polytope_feature/datacube/transformations/datacube_mappers/mapper_types/octahedral.py +0 -0
  165. {polytope_python-2.1.9 → polytope_python-2.1.12}/polytope_feature/datacube/transformations/datacube_mappers/mapper_types/reduced_gaussian.py +0 -0
  166. {polytope_python-2.1.9 → polytope_python-2.1.12}/polytope_feature/datacube/transformations/datacube_mappers/mapper_types/reduced_ll.py +0 -0
  167. {polytope_python-2.1.9 → polytope_python-2.1.12}/polytope_feature/datacube/transformations/datacube_mappers/mapper_types/regular.py +0 -0
  168. {polytope_python-2.1.9 → polytope_python-2.1.12}/polytope_feature/datacube/transformations/datacube_merger/__init__.py +0 -0
  169. {polytope_python-2.1.9 → polytope_python-2.1.12}/polytope_feature/datacube/transformations/datacube_merger/datacube_merger.py +0 -0
  170. {polytope_python-2.1.9 → polytope_python-2.1.12}/polytope_feature/datacube/transformations/datacube_reverse/__init__.py +0 -0
  171. {polytope_python-2.1.9 → polytope_python-2.1.12}/polytope_feature/datacube/transformations/datacube_reverse/datacube_reverse.py +0 -0
  172. {polytope_python-2.1.9 → polytope_python-2.1.12}/polytope_feature/datacube/transformations/datacube_transformations.py +0 -0
  173. {polytope_python-2.1.9 → polytope_python-2.1.12}/polytope_feature/datacube/transformations/datacube_type_change/__init__.py +0 -0
  174. {polytope_python-2.1.9 → polytope_python-2.1.12}/polytope_feature/datacube/transformations/datacube_type_change/datacube_type_change.py +0 -0
  175. {polytope_python-2.1.9 → polytope_python-2.1.12}/polytope_feature/datacube/tree_encoding.py +0 -0
  176. {polytope_python-2.1.9 → polytope_python-2.1.12}/polytope_feature/engine/__init__.py +0 -0
  177. {polytope_python-2.1.9 → polytope_python-2.1.12}/polytope_feature/engine/engine.py +0 -0
  178. {polytope_python-2.1.9 → polytope_python-2.1.12}/polytope_feature/engine/hullslicer.py +0 -0
  179. {polytope_python-2.1.9 → polytope_python-2.1.12}/polytope_feature/engine/optimised_point_in_polygon_slicer.py +0 -0
  180. {polytope_python-2.1.9 → polytope_python-2.1.12}/polytope_feature/engine/optimised_quadtree_slicer.py +0 -0
  181. {polytope_python-2.1.9 → polytope_python-2.1.12}/polytope_feature/engine/point_in_polygon_slicer.py +0 -0
  182. {polytope_python-2.1.9 → polytope_python-2.1.12}/polytope_feature/engine/quadtree_slicer.py +0 -0
  183. {polytope_python-2.1.9 → polytope_python-2.1.12}/polytope_feature/engine/slicing_tools.py +0 -0
  184. {polytope_python-2.1.9 → polytope_python-2.1.12}/polytope_feature/polytope.py +0 -0
  185. {polytope_python-2.1.9 → polytope_python-2.1.12}/polytope_feature/shapes.py +0 -0
  186. {polytope_python-2.1.9 → polytope_python-2.1.12}/polytope_feature/utility/__init__.py +0 -0
  187. {polytope_python-2.1.9 → polytope_python-2.1.12}/polytope_feature/utility/combinatorics.py +0 -0
  188. {polytope_python-2.1.9 → polytope_python-2.1.12}/polytope_feature/utility/engine_tools.py +0 -0
  189. {polytope_python-2.1.9 → polytope_python-2.1.12}/polytope_feature/utility/exceptions.py +0 -0
  190. {polytope_python-2.1.9 → polytope_python-2.1.12}/polytope_feature/utility/geometry.py +0 -0
  191. {polytope_python-2.1.9 → polytope_python-2.1.12}/polytope_feature/utility/list_tools.py +0 -0
  192. {polytope_python-2.1.9 → polytope_python-2.1.12}/polytope_feature/utility/profiling.py +0 -0
  193. {polytope_python-2.1.9 → polytope_python-2.1.12}/polytope_python.egg-info/dependency_links.txt +0 -0
  194. {polytope_python-2.1.9 → polytope_python-2.1.12}/polytope_python.egg-info/requires.txt +0 -0
  195. {polytope_python-2.1.9 → polytope_python-2.1.12}/polytope_python.egg-info/top_level.txt +0 -0
  196. {polytope_python-2.1.9 → polytope_python-2.1.12}/pyproject.toml +0 -0
  197. {polytope_python-2.1.9 → polytope_python-2.1.12}/rust/Cargo.toml +0 -0
  198. {polytope_python-2.1.9 → polytope_python-2.1.12}/rust/src/healpix_nested.rs +0 -0
  199. {polytope_python-2.1.9 → polytope_python-2.1.12}/rust/src/lambert_conformal.rs +0 -0
  200. {polytope_python-2.1.9 → polytope_python-2.1.12}/rust/src/lib.rs +0 -0
  201. {polytope_python-2.1.9 → polytope_python-2.1.12}/rust/src/list_tools.rs +0 -0
  202. {polytope_python-2.1.9 → polytope_python-2.1.12}/rust/src/octahedral.rs +0 -0
  203. {polytope_python-2.1.9 → polytope_python-2.1.12}/rust/src/point_in_polygon.rs +0 -0
  204. {polytope_python-2.1.9 → polytope_python-2.1.12}/rust/src/quadtree_mod.rs +0 -0
  205. {polytope_python-2.1.9 → polytope_python-2.1.12}/rust/src/slicing_tools.rs +0 -0
  206. {polytope_python-2.1.9 → polytope_python-2.1.12}/setup.cfg +0 -0
  207. {polytope_python-2.1.9 → polytope_python-2.1.12}/setup.py +0 -0
  208. {polytope_python-2.1.9 → polytope_python-2.1.12}/tests/conftest.py +0 -0
  209. {polytope_python-2.1.9 → polytope_python-2.1.12}/tests/fdb_data/schema +0 -0
  210. {polytope_python-2.1.9 → polytope_python-2.1.12}/tests/helper_functions.py +0 -0
  211. {polytope_python-2.1.9 → polytope_python-2.1.12}/tests/profiled_quadtree.profile +0 -0
  212. {polytope_python-2.1.9 → polytope_python-2.1.12}/tests/quadtree_slicer_profiler.py +0 -0
  213. {polytope_python-2.1.9 → polytope_python-2.1.12}/tests/test_axis_mappers.py +0 -0
  214. {polytope_python-2.1.9 → polytope_python-2.1.12}/tests/test_bad_request_error.py +0 -0
  215. {polytope_python-2.1.9 → polytope_python-2.1.12}/tests/test_combinatorics.py +0 -0
  216. {polytope_python-2.1.9 → polytope_python-2.1.12}/tests/test_cyclic_axis_over_negative_vals.py +0 -0
  217. {polytope_python-2.1.9 → polytope_python-2.1.12}/tests/test_cyclic_axis_slicer_not_0.py +0 -0
  218. {polytope_python-2.1.9 → polytope_python-2.1.12}/tests/test_cyclic_axis_slicing.py +0 -0
  219. {polytope_python-2.1.9 → polytope_python-2.1.12}/tests/test_cyclic_nearest.py +0 -0
  220. {polytope_python-2.1.9 → polytope_python-2.1.12}/tests/test_cyclic_simple.py +0 -0
  221. {polytope_python-2.1.9 → polytope_python-2.1.12}/tests/test_cyclic_snapping.py +0 -0
  222. {polytope_python-2.1.9 → polytope_python-2.1.12}/tests/test_datacube_axes_init.py +0 -0
  223. {polytope_python-2.1.9 → polytope_python-2.1.12}/tests/test_datacube_mock.py +0 -0
  224. {polytope_python-2.1.9 → polytope_python-2.1.12}/tests/test_datacube_xarray.py +0 -0
  225. {polytope_python-2.1.9 → polytope_python-2.1.12}/tests/test_date_time_unmerged.py +0 -0
  226. {polytope_python-2.1.9 → polytope_python-2.1.12}/tests/test_ecmwf_oper_data_fdb.py +0 -0
  227. {polytope_python-2.1.9 → polytope_python-2.1.12}/tests/test_engine_slicer.py +0 -0
  228. {polytope_python-2.1.9 → polytope_python-2.1.12}/tests/test_fdb_datacube.py +0 -0
  229. {polytope_python-2.1.9 → polytope_python-2.1.12}/tests/test_fdb_unmap_tree.py +0 -0
  230. {polytope_python-2.1.9 → polytope_python-2.1.12}/tests/test_float_type.py +0 -0
  231. {polytope_python-2.1.9 → polytope_python-2.1.12}/tests/test_healpix_mapper.py +0 -0
  232. {polytope_python-2.1.9 → polytope_python-2.1.12}/tests/test_healpix_nested_grid.py +0 -0
  233. {polytope_python-2.1.9 → polytope_python-2.1.12}/tests/test_hull_slicer.py +0 -0
  234. {polytope_python-2.1.9 → polytope_python-2.1.12}/tests/test_hullslicer_engine.py +0 -0
  235. {polytope_python-2.1.9 → polytope_python-2.1.12}/tests/test_icon_grid_unstructured.py +0 -0
  236. {polytope_python-2.1.9 → polytope_python-2.1.12}/tests/test_icon_grid_unstructured_fdb.py +0 -0
  237. {polytope_python-2.1.9 → polytope_python-2.1.12}/tests/test_incomplete_tree_fdb.py +0 -0
  238. {polytope_python-2.1.9 → polytope_python-2.1.12}/tests/test_lambert_lam_grid_unstructured_fdb.py +0 -0
  239. {polytope_python-2.1.9 → polytope_python-2.1.12}/tests/test_lambert_lam_grid_unstructured_fdb_optimised_quadtree.py +0 -0
  240. {polytope_python-2.1.9 → polytope_python-2.1.12}/tests/test_local_grid_cyclic.py +0 -0
  241. {polytope_python-2.1.9 → polytope_python-2.1.12}/tests/test_local_regular_grid.py +0 -0
  242. {polytope_python-2.1.9 → polytope_python-2.1.12}/tests/test_local_swiss_grid.py +0 -0
  243. {polytope_python-2.1.9 → polytope_python-2.1.12}/tests/test_mappers.py +0 -0
  244. {polytope_python-2.1.9 → polytope_python-2.1.12}/tests/test_merge_cyclic_octahedral.py +0 -0
  245. {polytope_python-2.1.9 → polytope_python-2.1.12}/tests/test_merge_octahedral_one_axis.py +0 -0
  246. {polytope_python-2.1.9 → polytope_python-2.1.12}/tests/test_merge_transformation.py +0 -0
  247. {polytope_python-2.1.9 → polytope_python-2.1.12}/tests/test_multiple_param_fdb.py +0 -0
  248. {polytope_python-2.1.9 → polytope_python-2.1.12}/tests/test_octahedral_grid.py +0 -0
  249. {polytope_python-2.1.9 → polytope_python-2.1.12}/tests/test_orca_irregular_grid.py +0 -0
  250. {polytope_python-2.1.9 → polytope_python-2.1.12}/tests/test_orca_irregular_grid_optimised_point_in_polygon.py +0 -0
  251. {polytope_python-2.1.9 → polytope_python-2.1.12}/tests/test_orca_irregular_grid_point_in_polygon.py +0 -0
  252. {polytope_python-2.1.9 → polytope_python-2.1.12}/tests/test_override_md5_hash_options.py +0 -0
  253. {polytope_python-2.1.9 → polytope_python-2.1.12}/tests/test_point_nearest.py +0 -0
  254. {polytope_python-2.1.9 → polytope_python-2.1.12}/tests/test_point_shape.py +0 -0
  255. {polytope_python-2.1.9 → polytope_python-2.1.12}/tests/test_point_union.py +0 -0
  256. {polytope_python-2.1.9 → polytope_python-2.1.12}/tests/test_polytope_extract.py +0 -0
  257. {polytope_python-2.1.9 → polytope_python-2.1.12}/tests/test_polytope_extract_fdb.py +0 -0
  258. {polytope_python-2.1.9 → polytope_python-2.1.12}/tests/test_profiling_requesttree.py +0 -0
  259. {polytope_python-2.1.9 → polytope_python-2.1.12}/tests/test_quad_tree.py +0 -0
  260. {polytope_python-2.1.9 → polytope_python-2.1.12}/tests/test_quadtree_edge_cases.py +0 -0
  261. {polytope_python-2.1.9 → polytope_python-2.1.12}/tests/test_quadtree_indices.py +0 -0
  262. {polytope_python-2.1.9 → polytope_python-2.1.12}/tests/test_quadtree_optimisation.py +0 -0
  263. {polytope_python-2.1.9 → polytope_python-2.1.12}/tests/test_reduced_ll_grid.py +0 -0
  264. {polytope_python-2.1.9 → polytope_python-2.1.12}/tests/test_regular_grid.py +0 -0
  265. {polytope_python-2.1.9 → polytope_python-2.1.12}/tests/test_regular_reduced_grid.py +0 -0
  266. {polytope_python-2.1.9 → polytope_python-2.1.12}/tests/test_request_tree.py +0 -0
  267. {polytope_python-2.1.9 → polytope_python-2.1.12}/tests/test_request_trees_after_slicing.py +0 -0
  268. {polytope_python-2.1.9 → polytope_python-2.1.12}/tests/test_reverse_transformation.py +0 -0
  269. {polytope_python-2.1.9 → polytope_python-2.1.12}/tests/test_shapes.py +0 -0
  270. {polytope_python-2.1.9 → polytope_python-2.1.12}/tests/test_shapes_volume.py +0 -0
  271. {polytope_python-2.1.9 → polytope_python-2.1.12}/tests/test_slice_date_range_fdb.py +0 -0
  272. {polytope_python-2.1.9 → polytope_python-2.1.12}/tests/test_slice_date_range_fdb_v2.py +0 -0
  273. {polytope_python-2.1.9 → polytope_python-2.1.12}/tests/test_slice_fdb_box.py +0 -0
  274. {polytope_python-2.1.9 → polytope_python-2.1.12}/tests/test_slicer_engine.py +0 -0
  275. {polytope_python-2.1.9 → polytope_python-2.1.12}/tests/test_slicer_era5.py +0 -0
  276. {polytope_python-2.1.9 → polytope_python-2.1.12}/tests/test_slicer_xarray.py +0 -0
  277. {polytope_python-2.1.9 → polytope_python-2.1.12}/tests/test_slicing_unsliceable_axis.py +0 -0
  278. {polytope_python-2.1.9 → polytope_python-2.1.12}/tests/test_slicing_xarray_3D.py +0 -0
  279. {polytope_python-2.1.9 → polytope_python-2.1.12}/tests/test_slicing_xarray_4D.py +0 -0
  280. {polytope_python-2.1.9 → polytope_python-2.1.12}/tests/test_snapping.py +0 -0
  281. {polytope_python-2.1.9 → polytope_python-2.1.12}/tests/test_snapping_real_data.py +0 -0
  282. {polytope_python-2.1.9 → polytope_python-2.1.12}/tests/test_tree_protobuf.py +0 -0
  283. {polytope_python-2.1.9 → polytope_python-2.1.12}/tests/test_tree_protobuf_encoding.py +0 -0
  284. {polytope_python-2.1.9 → polytope_python-2.1.12}/tests/test_tree_protobuf_encoding_fdb.py +0 -0
  285. {polytope_python-2.1.9 → polytope_python-2.1.12}/tests/test_type_change_transformation.py +0 -0
  286. {polytope_python-2.1.9 → polytope_python-2.1.12}/tests/test_union_gj.py +0 -0
  287. {polytope_python-2.1.9 → polytope_python-2.1.12}/tests/test_union_point_box.py +0 -0
  288. {polytope_python-2.1.9 → polytope_python-2.1.12}/tests/test_wave_spectra_data.py +0 -0
  289. {polytope_python-2.1.9 → 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.9
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.9"
2
+ __version__ = "2.1.12"
@@ -1,12 +1,11 @@
1
1
  import argparse
2
+ import logging
2
3
  from abc import ABC
3
4
  from typing import Dict, List, Literal, Optional, Tuple, Union
4
5
 
5
6
  from conflator import ConfigModel, Conflator
6
7
  from pydantic import ConfigDict
7
8
 
8
- from polytope_feature.datacube.switching_grid_helper import lookup_grid_config
9
-
10
9
 
11
10
  class TransformationConfig(ConfigModel):
12
11
  model_config = ConfigDict(extra="forbid")
@@ -111,9 +110,12 @@ class PolytopeOptions(ABC):
111
110
  replaced = replace_grid_config_in_options(config_options, pre_path)
112
111
  if replaced:
113
112
  axis_config = config_options.axis_config
114
- except Exception:
115
- # Fail silently and continue with original config
116
- pass
113
+ except Exception as e:
114
+ logging.warning(
115
+ "Dynamic grid replacement failed for georef '%s': %s. Using static grid config.",
116
+ pre_path.get("georef", "unknown"),
117
+ e,
118
+ )
117
119
  return (
118
120
  axis_config,
119
121
  compressed_axes_config,
@@ -151,6 +153,8 @@ def gridspec_to_grid_config(gridspec, md5hash):
151
153
 
152
154
 
153
155
  def replace_grid_config_in_options(options, req):
156
+ from polytope_feature.datacube.switching_grid_helper import lookup_grid_config
157
+
154
158
  gridspec, md5hash = lookup_grid_config(req)
155
159
  grid_config = gridspec_to_grid_config(gridspec, md5hash)
156
160
  if grid_config is not None:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: polytope-python
3
- Version: 2.1.9
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