fastquadtree 1.2.1__tar.gz → 1.3.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.

Potentially problematic release.


This version of fastquadtree might be problematic. Click here for more details.

Files changed (82) hide show
  1. {fastquadtree-1.2.1 → fastquadtree-1.3.0}/.github/workflows/release.yml +1 -1
  2. {fastquadtree-1.2.1 → fastquadtree-1.3.0}/Cargo.lock +2 -1
  3. {fastquadtree-1.2.1 → fastquadtree-1.3.0}/Cargo.toml +2 -1
  4. {fastquadtree-1.2.1 → fastquadtree-1.3.0}/PKG-INFO +7 -4
  5. {fastquadtree-1.2.1 → fastquadtree-1.3.0}/README.md +4 -2
  6. fastquadtree-1.3.0/assets/quadtree_bench_throughput.png +0 -0
  7. fastquadtree-1.3.0/assets/quadtree_bench_time.png +0 -0
  8. {fastquadtree-1.2.1 → fastquadtree-1.3.0}/docs/benchmark.md +10 -11
  9. {fastquadtree-1.2.1 → fastquadtree-1.3.0}/docs/future_features.md +29 -12
  10. {fastquadtree-1.2.1 → fastquadtree-1.3.0}/docs/index.md +1 -0
  11. {fastquadtree-1.2.1 → fastquadtree-1.3.0}/interactive/ballpit.py +1 -1
  12. {fastquadtree-1.2.1 → fastquadtree-1.3.0}/mkdocs.yml +8 -5
  13. {fastquadtree-1.2.1 → fastquadtree-1.3.0}/pyproject.toml +2 -1
  14. {fastquadtree-1.2.1 → fastquadtree-1.3.0}/pysrc/fastquadtree/_base_quadtree.py +21 -3
  15. {fastquadtree-1.2.1 → fastquadtree-1.3.0}/pysrc/fastquadtree/point_quadtree.py +21 -18
  16. {fastquadtree-1.2.1 → fastquadtree-1.3.0}/pysrc/fastquadtree/rect_quadtree.py +26 -19
  17. fastquadtree-1.3.0/src/geom.rs +73 -0
  18. fastquadtree-1.3.0/src/lib.rs +345 -0
  19. {fastquadtree-1.2.1 → fastquadtree-1.3.0}/src/quadtree.rs +69 -49
  20. {fastquadtree-1.2.1 → fastquadtree-1.3.0}/src/rect_quadtree.rs +30 -28
  21. {fastquadtree-1.2.1 → fastquadtree-1.3.0}/tests/insertions.rs +2 -2
  22. {fastquadtree-1.2.1 → fastquadtree-1.3.0}/tests/nearest_neighbor.rs +4 -4
  23. {fastquadtree-1.2.1 → fastquadtree-1.3.0}/tests/query.rs +2 -2
  24. {fastquadtree-1.2.1 → fastquadtree-1.3.0}/tests/rect_quadtree.rs +3 -3
  25. fastquadtree-1.3.0/tests/test_point_quadtree_dtypes.py +66 -0
  26. {fastquadtree-1.2.1 → fastquadtree-1.3.0}/tests/test_rect_quadtree.py +65 -38
  27. {fastquadtree-1.2.1 → fastquadtree-1.3.0}/tests/unconventional_bounds.rs +3 -3
  28. fastquadtree-1.2.1/assets/quadtree_bench_throughput.png +0 -0
  29. fastquadtree-1.2.1/assets/quadtree_bench_time.png +0 -0
  30. fastquadtree-1.2.1/src/geom.rs +0 -52
  31. fastquadtree-1.2.1/src/lib.rs +0 -282
  32. {fastquadtree-1.2.1 → fastquadtree-1.3.0}/.github/workflows/docs.yml +0 -0
  33. {fastquadtree-1.2.1 → fastquadtree-1.3.0}/.gitignore +0 -0
  34. {fastquadtree-1.2.1 → fastquadtree-1.3.0}/.pre-commit-config.yaml +0 -0
  35. {fastquadtree-1.2.1 → fastquadtree-1.3.0}/LICENSE +0 -0
  36. {fastquadtree-1.2.1 → fastquadtree-1.3.0}/assets/ballpit.png +0 -0
  37. {fastquadtree-1.2.1 → fastquadtree-1.3.0}/assets/interactive_v2_rect_screenshot.png +0 -0
  38. {fastquadtree-1.2.1 → fastquadtree-1.3.0}/assets/interactive_v2_screenshot.png +0 -0
  39. {fastquadtree-1.2.1 → fastquadtree-1.3.0}/benchmarks/benchmark_native_vs_shim.py +0 -0
  40. {fastquadtree-1.2.1 → fastquadtree-1.3.0}/benchmarks/benchmark_np_vs_list.py +0 -0
  41. {fastquadtree-1.2.1 → fastquadtree-1.3.0}/benchmarks/benchmark_serialization_vs_rebuild.py +0 -0
  42. {fastquadtree-1.2.1 → fastquadtree-1.3.0}/benchmarks/cross_library_bench.py +0 -0
  43. {fastquadtree-1.2.1 → fastquadtree-1.3.0}/benchmarks/quadtree_bench/__init__.py +0 -0
  44. {fastquadtree-1.2.1 → fastquadtree-1.3.0}/benchmarks/quadtree_bench/engines.py +0 -0
  45. {fastquadtree-1.2.1 → fastquadtree-1.3.0}/benchmarks/quadtree_bench/main.py +0 -0
  46. {fastquadtree-1.2.1 → fastquadtree-1.3.0}/benchmarks/quadtree_bench/plotting.py +0 -0
  47. {fastquadtree-1.2.1 → fastquadtree-1.3.0}/benchmarks/quadtree_bench/runner.py +0 -0
  48. {fastquadtree-1.2.1 → fastquadtree-1.3.0}/benchmarks/requirements.txt +0 -0
  49. {fastquadtree-1.2.1 → fastquadtree-1.3.0}/benchmarks/runner.py +0 -0
  50. {fastquadtree-1.2.1 → fastquadtree-1.3.0}/benchmarks/system_info_collector.py +0 -0
  51. {fastquadtree-1.2.1 → fastquadtree-1.3.0}/docs/api/point_item.md +0 -0
  52. {fastquadtree-1.2.1 → fastquadtree-1.3.0}/docs/api/pyqtree.md +0 -0
  53. {fastquadtree-1.2.1 → fastquadtree-1.3.0}/docs/api/quadtree.md +0 -0
  54. {fastquadtree-1.2.1 → fastquadtree-1.3.0}/docs/api/rect_item.md +0 -0
  55. {fastquadtree-1.2.1 → fastquadtree-1.3.0}/docs/api/rect_quadtree.md +0 -0
  56. {fastquadtree-1.2.1 → fastquadtree-1.3.0}/docs/quickstart.md +0 -0
  57. {fastquadtree-1.2.1 → fastquadtree-1.3.0}/docs/runnables.md +0 -0
  58. {fastquadtree-1.2.1 → fastquadtree-1.3.0}/docs/styles/overrides.css +0 -0
  59. {fastquadtree-1.2.1 → fastquadtree-1.3.0}/interactive/interactive.py +0 -0
  60. {fastquadtree-1.2.1 → fastquadtree-1.3.0}/interactive/interactive_v2.py +0 -0
  61. {fastquadtree-1.2.1 → fastquadtree-1.3.0}/interactive/interactive_v2_rect.py +0 -0
  62. {fastquadtree-1.2.1 → fastquadtree-1.3.0}/interactive/requirements.txt +0 -0
  63. {fastquadtree-1.2.1 → fastquadtree-1.3.0}/pysrc/fastquadtree/__init__.py +0 -0
  64. {fastquadtree-1.2.1 → fastquadtree-1.3.0}/pysrc/fastquadtree/_item.py +0 -0
  65. {fastquadtree-1.2.1 → fastquadtree-1.3.0}/pysrc/fastquadtree/_obj_store.py +0 -0
  66. {fastquadtree-1.2.1 → fastquadtree-1.3.0}/pysrc/fastquadtree/py.typed +0 -0
  67. {fastquadtree-1.2.1 → fastquadtree-1.3.0}/pysrc/fastquadtree/pyqtree.py +0 -0
  68. {fastquadtree-1.2.1 → fastquadtree-1.3.0}/tests/rectangle_traversal.rs +0 -0
  69. {fastquadtree-1.2.1 → fastquadtree-1.3.0}/tests/serialization.rs +0 -0
  70. {fastquadtree-1.2.1 → fastquadtree-1.3.0}/tests/test_base_quadtree.py +0 -0
  71. {fastquadtree-1.2.1 → fastquadtree-1.3.0}/tests/test_clear.py +0 -0
  72. {fastquadtree-1.2.1 → fastquadtree-1.3.0}/tests/test_delete.rs +0 -0
  73. {fastquadtree-1.2.1 → fastquadtree-1.3.0}/tests/test_delete_by_object.py +0 -0
  74. {fastquadtree-1.2.1 → fastquadtree-1.3.0}/tests/test_delete_python.py +0 -0
  75. {fastquadtree-1.2.1 → fastquadtree-1.3.0}/tests/test_insert_many_numpy.py +0 -0
  76. {fastquadtree-1.2.1 → fastquadtree-1.3.0}/tests/test_obj_store.py +0 -0
  77. {fastquadtree-1.2.1 → fastquadtree-1.3.0}/tests/test_point_quadtree_nn_runtime.py +0 -0
  78. {fastquadtree-1.2.1 → fastquadtree-1.3.0}/tests/test_pyqtree_shim_compat.py +0 -0
  79. {fastquadtree-1.2.1 → fastquadtree-1.3.0}/tests/test_python.py +0 -0
  80. {fastquadtree-1.2.1 → fastquadtree-1.3.0}/tests/test_serialization.py +0 -0
  81. {fastquadtree-1.2.1 → fastquadtree-1.3.0}/tests/test_unconventional_bounds.py +0 -0
  82. {fastquadtree-1.2.1 → fastquadtree-1.3.0}/tests/test_wrapper_edges.py +0 -0
@@ -16,7 +16,7 @@ jobs:
16
16
  - name: Set up Python
17
17
  uses: actions/setup-python@v5
18
18
  with:
19
- python-version: "3.10"
19
+ python-version: "3.9"
20
20
 
21
21
  - name: Create venv for maturin develop
22
22
  run: python -m venv .venv
@@ -30,9 +30,10 @@ dependencies = [
30
30
 
31
31
  [[package]]
32
32
  name = "fastquadtree"
33
- version = "1.2.1"
33
+ version = "1.3.0"
34
34
  dependencies = [
35
35
  "bincode",
36
+ "num-traits",
36
37
  "numpy",
37
38
  "pyo3",
38
39
  "serde",
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "fastquadtree"
3
- version = "1.2.1"
3
+ version = "1.3.0"
4
4
  edition = "2021"
5
5
  readme = "README.md"
6
6
 
@@ -13,6 +13,7 @@ smallvec = "1.15.1"
13
13
  numpy = "0.26"
14
14
  serde = { version = "1.0", features = ["derive"] }
15
15
  bincode = {version = "2.0.1", features = ["serde"]}
16
+ num-traits = "0.2"
16
17
 
17
18
  [profile.release]
18
19
  opt-level = 3
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fastquadtree
3
- Version: 1.2.1
3
+ Version: 1.3.0
4
4
  Classifier: Programming Language :: Python :: 3
5
5
  Classifier: Programming Language :: Python :: 3 :: Only
6
6
  Classifier: Programming Language :: Rust
@@ -26,12 +26,13 @@ Requires-Dist: mkdocs-minify-plugin ; extra == 'dev'
26
26
  Requires-Dist: maturin>=1.5 ; extra == 'dev'
27
27
  Requires-Dist: pyqtree==1.0.0 ; extra == 'dev'
28
28
  Requires-Dist: numpy ; extra == 'dev'
29
+ Requires-Dist: pre-commit ; extra == 'dev'
29
30
  Provides-Extra: dev
30
31
  License-File: LICENSE
31
32
  Summary: Rust-accelerated quadtree for Python with fast inserts, range queries, and k-NN search.
32
33
  Keywords: quadtree,spatial-index,geometry,rust,pyo3,nearest-neighbor,k-nn
33
34
  Author: Ethan Anderson
34
- Requires-Python: >=3.8
35
+ Requires-Python: >=3.9
35
36
  Description-Content-Type: text/markdown
36
37
  Project-URL: Homepage, https://github.com/Elan456/fastquadtree
37
38
  Project-URL: Repository, https://github.com/Elan456/fastquadtree
@@ -45,6 +46,8 @@ Project-URL: Issues, https://github.com/Elan456/fastquadtree/issues
45
46
 
46
47
  Rust-optimized quadtree with a clean Python API
47
48
 
49
+ 👉 **Check out the Docs:** https://elan456.github.io/fastquadtree/
50
+
48
51
  [![PyPI](https://img.shields.io/pypi/v/fastquadtree.svg)](https://pypi.org/project/fastquadtree/)
49
52
  [![Python versions](https://img.shields.io/pypi/pyversions/fastquadtree.svg)](https://pypi.org/project/fastquadtree/)
50
53
  [![Downloads](https://static.pepy.tech/personalized-badge/fastquadtree?period=total&units=INTERNATIONAL_SYSTEM&left_color=GRAY&right_color=BLUE&left_text=Total%20Downloads)](https://pepy.tech/projects/fastquadtree)
@@ -64,19 +67,19 @@ Rust-optimized quadtree with a clean Python API
64
67
 
65
68
  ## Why use fastquadtree
66
69
 
67
- - Clean [Python API](https://elan456.github.io/fastquadtree/api/quadtree/) with no external dependencies and modern typing hints
70
+ - Clean [Python API](https://elan456.github.io/fastquadtree/api/quadtree/) with **no external dependencies** and modern typing hints
68
71
  - The fastest quadtree Python package ([>10x faster](https://elan456.github.io/fastquadtree/benchmark/) than pyqtree)
69
72
  - Prebuilt wheels for Windows, macOS, and Linux
70
73
  - Support for [inserting bounding boxes](https://elan456.github.io/fastquadtree/api/rect_quadtree/) or points
71
74
  - Fast KNN and range queries
72
75
  - Optional object tracking for id ↔ object mapping
73
76
  - Fast [serialization](https://elan456.github.io/fastquadtree/benchmark/#serialization-vs-rebuild) to/from bytes
77
+ - Support for multiple data types (f32, f64, i32, i64) for coordinates
74
78
  - [100% test coverage](https://codecov.io/gh/Elan456/fastquadtree) and CI on GitHub Actions
75
79
  - Offers a drop-in [pyqtree shim](https://elan456.github.io/fastquadtree/benchmark/#pyqtree-drop-in-shim-performance-gains) that is 6.567x faster while keeping the same API
76
80
 
77
81
  ----
78
82
 
79
- 👉 **Docs:** https://elan456.github.io/fastquadtree/
80
83
 
81
84
  ## Examples
82
85
  See examples of how fastquadtree can be used in the [runnables](https://elan456.github.io/fastquadtree/runnables/) section.
@@ -5,6 +5,8 @@
5
5
 
6
6
  Rust-optimized quadtree with a clean Python API
7
7
 
8
+ 👉 **Check out the Docs:** https://elan456.github.io/fastquadtree/
9
+
8
10
  [![PyPI](https://img.shields.io/pypi/v/fastquadtree.svg)](https://pypi.org/project/fastquadtree/)
9
11
  [![Python versions](https://img.shields.io/pypi/pyversions/fastquadtree.svg)](https://pypi.org/project/fastquadtree/)
10
12
  [![Downloads](https://static.pepy.tech/personalized-badge/fastquadtree?period=total&units=INTERNATIONAL_SYSTEM&left_color=GRAY&right_color=BLUE&left_text=Total%20Downloads)](https://pepy.tech/projects/fastquadtree)
@@ -24,19 +26,19 @@ Rust-optimized quadtree with a clean Python API
24
26
 
25
27
  ## Why use fastquadtree
26
28
 
27
- - Clean [Python API](https://elan456.github.io/fastquadtree/api/quadtree/) with no external dependencies and modern typing hints
29
+ - Clean [Python API](https://elan456.github.io/fastquadtree/api/quadtree/) with **no external dependencies** and modern typing hints
28
30
  - The fastest quadtree Python package ([>10x faster](https://elan456.github.io/fastquadtree/benchmark/) than pyqtree)
29
31
  - Prebuilt wheels for Windows, macOS, and Linux
30
32
  - Support for [inserting bounding boxes](https://elan456.github.io/fastquadtree/api/rect_quadtree/) or points
31
33
  - Fast KNN and range queries
32
34
  - Optional object tracking for id ↔ object mapping
33
35
  - Fast [serialization](https://elan456.github.io/fastquadtree/benchmark/#serialization-vs-rebuild) to/from bytes
36
+ - Support for multiple data types (f32, f64, i32, i64) for coordinates
34
37
  - [100% test coverage](https://codecov.io/gh/Elan456/fastquadtree) and CI on GitHub Actions
35
38
  - Offers a drop-in [pyqtree shim](https://elan456.github.io/fastquadtree/benchmark/#pyqtree-drop-in-shim-performance-gains) that is 6.567x faster while keeping the same API
36
39
 
37
40
  ----
38
41
 
39
- 👉 **Docs:** https://elan456.github.io/fastquadtree/
40
42
 
41
43
  ## Examples
42
44
  See examples of how fastquadtree can be used in the [runnables](https://elan456.github.io/fastquadtree/runnables/) section.
@@ -11,21 +11,20 @@ Quadtrees are the focus of the benchmark, but Rtrees are included for reference.
11
11
  ![Throughput](https://raw.githubusercontent.com/Elan456/fastquadtree/main/assets/quadtree_bench_throughput.png)
12
12
 
13
13
  ### Summary (largest dataset, PyQtree baseline)
14
-
15
14
  - Points: **250,000**, Queries: **500**
16
- - Fastest total: **fastquadtree** at **0.120 s**
15
+ - Fastest total: **fastquadtree** at **0.100 s**
17
16
 
18
17
  | Library | Build (s) | Query (s) | Total (s) | Speed vs PyQtree |
19
18
  |---|---:|---:|---:|---:|
20
- | **fastquadtree** | 0.031 | 0.089 | 0.120 | **14.64×** |
21
- | Shapely STRtree | 0.179 | 0.100 | 0.279 | 6.29× |
22
- | nontree-QuadTree | 0.595 | 0.605 | 1.200 | 1.46× |
23
- | Rtree | 0.961 | 0.300 | 1.261 | 1.39× |
24
- | e-pyquadtree | 1.005 | 0.660 | 1.665 | 1.05× |
25
- | PyQtree | 1.492 | 0.263 | 1.755 | 1.00× |
26
- | quads | 1.407 | 0.484 | 1.890 | 0.93× |
27
-
28
- ### Benchmark Configuration
19
+ | fastquadtree | 0.027 | 0.073 | 0.100 | 16.12× |
20
+ | Shapely STRtree | 0.148 | 0.083 | 0.231 | 7.00× |
21
+ | Rtree | 0.814 | 0.245 | 1.059 | 1.53× |
22
+ | nontree-QuadTree | 0.532 | 0.582 | 1.114 | 1.45× |
23
+ | e-pyquadtree | 0.913 | 0.641 | 1.554 | 1.04× |
24
+ | PyQtree | 1.352 | 0.264 | 1.616 | 1.00× |
25
+ | quads | 1.231 | 0.465 | 1.695 | 0.95× |
26
+
27
+ #### Benchmark Configuration
29
28
  | Parameter | Value |
30
29
  |---|---:|
31
30
  | Bounds | (0, 0, 1000, 1000) |
@@ -5,26 +5,43 @@ If you really want any of these features, please let us know by opening an issue
5
5
 
6
6
  If you have any suggestions or would like to contribute, please feel free to open an issue or a pull request.
7
7
 
8
- ## Planned Features
8
+ The features will likely be implemented in the order they are listed below, but this is not guaranteed.
9
9
 
10
- ### 1. [COMPLETE] Quadtree serialization
10
+ ## 🚧 Planned Features
11
11
 
12
- By serializing the quadtree, we can save its state to a file and load it later. This will allow us to persist the quadtree structure and data across sessions. For example, you could pre build a quadtree with all the walls in your video game level, serialize it to a file, and then load it when the game starts. This will heavily reduce the game load time since you won't have to rebuild the quadtree from scratch every time.
12
+ ### 1. KNN with criteria function
13
13
 
14
- ### 2. Circle support
14
+ Currently, KNN only supports finding the nearest neighbors based on euclidean distance.
15
+ By adding a criteria function, we could allow users to define custom criteria for finding neighbors by passing a function that
16
+ takes in a point and returns a score. The KNN algorithm would then use this score to determine the nearest neighbors.
17
+
18
+ ### 2. KNN in rectangle quadtree
19
+
20
+ Currently, KNN is only supported in the point quadtree. By adding KNN support to the rectangle quadtree, we could allow users to find the nearest rectangles to a given point. This would be to the nearest edge of the rectangle, adding complexity to the algorithm.
21
+ However, it will allow for really quick collision detection between a point and a set of rectangles as the point can just do
22
+ robust-collision handling with the nearest rectangles.
23
+
24
+ ### 3. Circle support
15
25
 
16
26
  Currently, we support points and rectangles in two separate quadtrees.
17
27
  For example, in the ball-pit demo, we use a point quadtree, but then query a larger area to account for the radius of the balls.
18
28
  With a circle quadtree, we could directly insert circles and perform circle-circle collision detection.
19
29
 
20
- ### 3. KNN with criteria function
30
+ A good alternative is to use the rectangle quadtree and insert the minimum bounding rectangles of the circles.
21
31
 
22
- Currently, KNN only supports finding the nearest neighbors based on euclidean distance.
23
- By adding a criteria function, we could allow users to define custom criteria for finding neighbors by passing a function that
24
- takes in a point and returns a score. The KNN algorithm would then use this score to determine the nearest neighbors.
32
+ ## Completed Planned Features
25
33
 
26
- ### 4. KNN in rectangle quadtree
34
+ Once a feature from above is completed, it will be moved to this section.
27
35
 
28
- Currently, KNN is only supported in the point quadtree. By adding KNN support to the rectangle quadtree, we could allow users to find the nearest rectangles to a given point. This would be to the nearest edge of the rectangle, adding complexity to the algorithm.
29
- However, it will allow for really quick collision detection between a point and a set of rectangles as the point can just do
30
- robust-collision handling with the nearest rectangles.
36
+
37
+ ### Configurable Quadtree Coordinate Type (1.3.0)
38
+
39
+ Currently, the point quadtree only uses f32 for point coordinates, limiting precision in favor of better performance.
40
+ To make the quadtree more flexible, we could allow users to specify the coordinate type (e.g., f64, i32, etc.) when creating a quadtree.
41
+ The f32 will remain the default, but users will be able to specify a different type if needed.
42
+
43
+ If the type cannot be made truly generic, then only the following types would be supported: f32, f64, i32, i64
44
+
45
+ ### Quadtree serialization (1.2.0)
46
+
47
+ By serializing the quadtree, we can save its state to a file and load it later. This will allow us to persist the quadtree structure and data across sessions. For example, you could pre build a quadtree with all the walls in your video game level, serialize it to a file, and then load it when the game starts. This will heavily reduce the game load time since you won't have to rebuild the quadtree from scratch every time.
@@ -42,6 +42,7 @@
42
42
  - Fast KNN and range queries
43
43
  - Optional object tracking for id ↔ object mapping
44
44
  - Fast [serialization](benchmark.md#serialization-vs-rebuild) to/from bytes
45
+ - Support for multiple data types (f32, f64, i32, i64) for coordinates
45
46
  - [100% test coverage](https://codecov.io/gh/Elan456/fastquadtree) and CI on GitHub Actions
46
47
 
47
48
  ## Examples
@@ -246,7 +246,7 @@ def main():
246
246
 
247
247
  running = True
248
248
  while running:
249
- dt_ms = clock.tick(60) # target 60 fps
249
+ dt_ms = clock.tick(200) # target 200 FPS
250
250
  dt = dt_ms / 1000.0
251
251
 
252
252
  for event in pygame.event.get():
@@ -12,10 +12,12 @@ theme:
12
12
  - navigation.tracking
13
13
  - navigation.indexes
14
14
  - content.code.copy
15
- - toc.integrate
15
+ - toc.follow
16
16
  - search.suggest
17
17
  - search.highlight
18
18
  - content.tooltips
19
+ - navigation.footer
20
+ - navigation.top
19
21
  palette:
20
22
  - scheme: slate
21
23
  primary: indigo
@@ -37,13 +39,14 @@ plugins:
37
39
  options:
38
40
  docstring_style: google
39
41
  merge_init_into_class: true
40
- show_source: true
42
+ show_source: false
41
43
  show_root_heading: false
42
44
  show_signature: true
43
45
  show_if_no_docstring: true
44
- separate_signature: true
45
- line_length: 88
46
- heading_level: 2
46
+ separate_signature: false
47
+ line_length: 100
48
+ heading_level: 3
49
+ members_order: source
47
50
  filters: # Exclude __slots__ and __len__ and anything with a single underscore prefix (don't filter insert_many even though it has a single underscore)
48
51
  - "!__slots__"
49
52
  - "!__len__"
@@ -8,7 +8,7 @@ name = "fastquadtree"
8
8
  dynamic = ["version"]
9
9
  description = "Rust-accelerated quadtree for Python with fast inserts, range queries, and k-NN search."
10
10
  readme = { file = "README.md", content-type = "text/markdown" }
11
- requires-python = ">=3.8"
11
+ requires-python = ">=3.9"
12
12
  dependencies = [] # No runtime dependencies
13
13
  license = { file = "LICENSE" }
14
14
  authors = [{ name = "Ethan Anderson" }]
@@ -80,6 +80,7 @@ dev = [
80
80
  "maturin>=1.5", # build Rust wheels
81
81
  "pyqtree==1.0.0", # for comparison in tests
82
82
  "numpy",
83
+ "pre-commit",
83
84
  ]
84
85
 
85
86
  [tool.ruff]
@@ -18,6 +18,8 @@ from ._item import Item # base class for PointItem and RectItem
18
18
  from ._obj_store import ObjStore
19
19
 
20
20
  if TYPE_CHECKING:
21
+ from typing import Self # Only in Python 3.11+
22
+
21
23
  from numpy.typing import NDArray
22
24
 
23
25
  Bounds = Tuple[float, float, float, float]
@@ -46,6 +48,7 @@ class _BaseQuadTree(Generic[G, HitT, ItemType], ABC):
46
48
  "_bounds",
47
49
  "_capacity",
48
50
  "_count",
51
+ "_dtype",
49
52
  "_max_depth",
50
53
  "_native",
51
54
  "_next_id",
@@ -59,6 +62,10 @@ class _BaseQuadTree(Generic[G, HitT, ItemType], ABC):
59
62
  def _new_native(self, bounds: Bounds, capacity: int, max_depth: int | None) -> Any:
60
63
  """Create the native engine instance."""
61
64
 
65
+ @classmethod
66
+ def _new_native_from_bytes(cls, data: bytes, dtype: str) -> Any:
67
+ """Create the native engine instance from serialized bytes."""
68
+
62
69
  @staticmethod
63
70
  @abstractmethod
64
71
  def _make_item(id_: int, geom: G, obj: Any | None) -> ItemType:
@@ -73,10 +80,12 @@ class _BaseQuadTree(Generic[G, HitT, ItemType], ABC):
73
80
  *,
74
81
  max_depth: int | None = None,
75
82
  track_objects: bool = False,
83
+ dtype: str = "f32",
76
84
  ):
77
85
  self._bounds = bounds
78
86
  self._max_depth = max_depth
79
87
  self._capacity = capacity
88
+ self._dtype = dtype
80
89
  self._native = self._new_native(bounds, capacity, max_depth)
81
90
 
82
91
  self._track_objects = bool(track_objects)
@@ -132,12 +141,13 @@ class _BaseQuadTree(Generic[G, HitT, ItemType], ABC):
132
141
  return pickle.dumps(self.to_dict())
133
142
 
134
143
  @classmethod
135
- def from_bytes(cls, data: bytes) -> _BaseQuadTree[G, HitT, ItemType]:
144
+ def from_bytes(cls, data: bytes, dtype: str = "f32") -> Self:
136
145
  """
137
- Deserialize a quadtree from bytes.
146
+ Deserialize a quadtree from bytes. Specifiy the dtype if the original tree that was serialized used a non-default dtype.
138
147
 
139
148
  Args:
140
149
  data: Bytes representing the serialized quadtree from `to_bytes()`.
150
+ dtype: The data type used in the native engine ('f32', 'f64', 'i32', 'i64') when saved to bytes.
141
151
 
142
152
  Returns:
143
153
  A new quadtree instance with the same state as when serialized.
@@ -154,7 +164,15 @@ class _BaseQuadTree(Generic[G, HitT, ItemType], ABC):
154
164
  store_dict = in_dict["store"]
155
165
 
156
166
  qt = cls.__new__(cls) # type: ignore[call-arg]
157
- qt._native = cls._new_native_from_bytes(core_bytes)
167
+ try:
168
+ qt._native = cls._new_native_from_bytes(core_bytes, dtype=dtype)
169
+ except ValueError as ve:
170
+ raise ValueError(
171
+ "Failed to deserialize quadtree native core. "
172
+ "This may be due to a dtype mismatch. "
173
+ "Ensure the dtype used in from_bytes() matches the original tree. "
174
+ "Error details: " + str(ve)
175
+ ) from ve
158
176
 
159
177
  if store_dict is not None:
160
178
  qt._store = ObjStore.from_dict(store_dict, qt._make_item)
@@ -5,10 +5,17 @@ from typing import Any, Literal, Tuple, overload
5
5
 
6
6
  from ._base_quadtree import Bounds, _BaseQuadTree
7
7
  from ._item import Point, PointItem
8
- from ._native import QuadTree as _RustQuadTree # native point tree
8
+ from ._native import QuadTree as QuadTreeF32, QuadTreeF64, QuadTreeI32, QuadTreeI64
9
9
 
10
10
  _IdCoord = Tuple[int, float, float]
11
11
 
12
+ DTYPE_MAP = {
13
+ "f32": QuadTreeF32,
14
+ "f64": QuadTreeF64,
15
+ "i32": QuadTreeI32,
16
+ "i64": QuadTreeI64,
17
+ }
18
+
12
19
 
13
20
  class QuadTree(_BaseQuadTree[Point, _IdCoord, PointItem]):
14
21
  """
@@ -29,6 +36,7 @@ class QuadTree(_BaseQuadTree[Point, _IdCoord, PointItem]):
29
36
  capacity: Max number of points per node before splitting.
30
37
  max_depth: Optional max tree depth. If omitted, engine decides.
31
38
  track_objects: Enable id <-> object mapping inside Python.
39
+ dtype: Data type for coordinates and ids in the native engine. Default is 'f32'. Options are 'f32', 'f64', 'i32', 'i64'.
32
40
 
33
41
  Raises:
34
42
  ValueError: If parameters are invalid or inserts are out of bounds.
@@ -41,26 +49,16 @@ class QuadTree(_BaseQuadTree[Point, _IdCoord, PointItem]):
41
49
  *,
42
50
  max_depth: int | None = None,
43
51
  track_objects: bool = False,
52
+ dtype: str = "f32",
44
53
  ):
45
54
  super().__init__(
46
55
  bounds,
47
56
  capacity,
48
57
  max_depth=max_depth,
49
58
  track_objects=track_objects,
59
+ dtype=dtype,
50
60
  )
51
61
 
52
- @classmethod
53
- def from_bytes(cls, data: bytes) -> QuadTree:
54
- """
55
- Create a QuadTree instance from serialized bytes.
56
-
57
- Args:
58
- data: Serialized byte data from `to_bytes()`.
59
- Returns:
60
- A QuadTree instance.
61
- """
62
- return super().from_bytes(data)
63
-
64
62
  @overload
65
63
  def query(
66
64
  self, rect: Bounds, *, as_items: Literal[False] = ...
@@ -160,14 +158,19 @@ class QuadTree(_BaseQuadTree[Point, _IdCoord, PointItem]):
160
158
  return out
161
159
 
162
160
  def _new_native(self, bounds: Bounds, capacity: int, max_depth: int | None) -> Any:
163
- if max_depth is None:
164
- return _RustQuadTree(bounds, capacity)
165
- return _RustQuadTree(bounds, capacity, max_depth=max_depth)
161
+ """Create the native engine instance."""
162
+ rust_cls = DTYPE_MAP.get(self._dtype)
163
+ if rust_cls is None:
164
+ raise ValueError(f"Unsupported dtype: {self._dtype}")
165
+ return rust_cls(bounds, capacity, max_depth)
166
166
 
167
167
  @classmethod
168
- def _new_native_from_bytes(cls, data: bytes) -> Any:
168
+ def _new_native_from_bytes(cls, data: bytes, dtype: str = "f32") -> Any:
169
169
  """Create a new native engine instance from serialized bytes."""
170
- return _RustQuadTree.from_bytes(data)
170
+ rust_cls = DTYPE_MAP.get(dtype)
171
+ if rust_cls is None:
172
+ raise ValueError(f"Unsupported dtype: {dtype}")
173
+ return rust_cls.from_bytes(data)
171
174
 
172
175
  @staticmethod
173
176
  def _make_item(id_: int, geom: Point, obj: Any | None) -> PointItem:
@@ -5,10 +5,21 @@ from typing import Any, Literal, Tuple, overload
5
5
 
6
6
  from ._base_quadtree import Bounds, _BaseQuadTree
7
7
  from ._item import RectItem
8
- from ._native import RectQuadTree as _RustRectQuadTree # native rect tree
8
+ from ._native import (
9
+ RectQuadTree as RectQuadTreeF32,
10
+ RectQuadTreeF64,
11
+ RectQuadTreeI32,
12
+ RectQuadTreeI64,
13
+ )
9
14
 
10
15
  _IdRect = Tuple[int, float, float, float, float]
11
- Point = Tuple[float, float] # only for type hints in docstrings
16
+
17
+ DTYPE_MAP = {
18
+ "f32": RectQuadTreeF32,
19
+ "f64": RectQuadTreeF64,
20
+ "i32": RectQuadTreeI32,
21
+ "i64": RectQuadTreeI64,
22
+ }
12
23
 
13
24
 
14
25
  class RectQuadTree(_BaseQuadTree[Bounds, _IdRect, RectItem]):
@@ -30,6 +41,7 @@ class RectQuadTree(_BaseQuadTree[Bounds, _IdRect, RectItem]):
30
41
  capacity: Max number of points per node before splitting.
31
42
  max_depth: Optional max tree depth. If omitted, engine decides.
32
43
  track_objects: Enable id <-> object mapping inside Python.
44
+ dtype: Data type for coordinates and ids in the native engine. Default is 'f32'. Options are 'f32', 'f64', 'i32', 'i64'.
33
45
 
34
46
  Raises:
35
47
  ValueError: If parameters are invalid or inserts are out of bounds.
@@ -42,26 +54,16 @@ class RectQuadTree(_BaseQuadTree[Bounds, _IdRect, RectItem]):
42
54
  *,
43
55
  max_depth: int | None = None,
44
56
  track_objects: bool = False,
57
+ dtype: str = "f32",
45
58
  ):
46
59
  super().__init__(
47
60
  bounds,
48
61
  capacity,
49
62
  max_depth=max_depth,
50
63
  track_objects=track_objects,
64
+ dtype=dtype,
51
65
  )
52
66
 
53
- @classmethod
54
- def from_bytes(cls, data: bytes) -> RectQuadTree:
55
- """
56
- Create a RectQuadTree instance from serialized bytes.
57
-
58
- Args:
59
- data: Serialized byte data from `to_bytes()`.
60
- Returns:
61
- A RectQuadTree instance.
62
- """
63
- return super().from_bytes(data)
64
-
65
67
  @overload
66
68
  def query(
67
69
  self, rect: Bounds, *, as_items: Literal[False] = ...
@@ -96,14 +98,19 @@ class RectQuadTree(_BaseQuadTree[Bounds, _IdRect, RectItem]):
96
98
  return self._store.get_many_by_ids(self._native.query_ids(rect))
97
99
 
98
100
  def _new_native(self, bounds: Bounds, capacity: int, max_depth: int | None) -> Any:
99
- if max_depth is None:
100
- return _RustRectQuadTree(bounds, capacity)
101
- return _RustRectQuadTree(bounds, capacity, max_depth=max_depth)
101
+ """Create the native engine instance."""
102
+ rust_cls = DTYPE_MAP.get(self._dtype)
103
+ if rust_cls is None:
104
+ raise ValueError(f"Unsupported dtype: {self._dtype}")
105
+ return rust_cls(bounds, capacity, max_depth)
102
106
 
103
107
  @classmethod
104
- def _new_native_from_bytes(cls, data: bytes) -> Any:
108
+ def _new_native_from_bytes(cls, data: bytes, dtype: str = "f32") -> Any:
105
109
  """Create a new native engine instance from serialized bytes."""
106
- return _RustRectQuadTree.from_bytes(data)
110
+ rust_cls = DTYPE_MAP.get(dtype)
111
+ if rust_cls is None:
112
+ raise ValueError(f"Unsupported dtype: {dtype}")
113
+ return rust_cls.from_bytes(data)
107
114
 
108
115
  @staticmethod
109
116
  def _make_item(id_: int, geom: Bounds, obj: Any | None) -> RectItem:
@@ -0,0 +1,73 @@
1
+ use core::ops::{Add, Sub, Mul, Div};
2
+ use num_traits::{Zero, One};
3
+ use serde::{de::DeserializeOwned, Deserialize, Serialize};
4
+
5
+ // Creating a set of traits for coordinate types
6
+ // Requires basic arithmetic operations and ordering
7
+ pub trait Coord:
8
+ Copy + PartialOrd + Add<Output = Self> + Sub<Output = Self> + Mul<Output = Self> + Div<Output = Self> + Zero + One + Serialize + DeserializeOwned
9
+ {}
10
+
11
+ impl<T> Coord for T where
12
+ T: Copy + PartialOrd + Add<Output = Self> + Sub<Output = Self> + Mul<Output = Self> + Div<Output = Self> + Zero + One + Serialize + DeserializeOwned
13
+ {}
14
+
15
+ // Generic mid function for all Coord types
16
+ #[inline(always)]
17
+ pub fn mid<T: Coord>(a: T, b: T) -> T {
18
+ // a + (b - a) / 2
19
+ a + (b - a) / (T::one() + T::one()) // 1 + 1 = 2 for Div
20
+ }
21
+
22
+ #[derive(Copy, Clone, Debug, PartialEq, Default, Serialize, Deserialize)]
23
+ #[serde(bound(serialize = "", deserialize = ""))]
24
+ pub struct Point<T: Coord> {
25
+ pub x: T,
26
+ pub y: T,
27
+ }
28
+
29
+ #[derive(Copy, Clone, Debug, PartialEq, Default, Serialize, Deserialize)]
30
+ #[serde(bound(serialize = "", deserialize = ""))]
31
+ pub struct Rect<T: Coord> {
32
+ pub min_x: T,
33
+ pub min_y: T,
34
+ pub max_x: T,
35
+ pub max_y: T,
36
+ }
37
+
38
+ impl<T: Coord> Rect<T> {
39
+ pub fn contains(&self, point: &Point<T>) -> bool {
40
+ return point.x >= self.min_x && point.x < self.max_x && point.y >= self.min_y && point.y < self.max_y;
41
+ }
42
+
43
+ // Check if two Rect overlap at all
44
+ pub fn intersects(&self, other: &Rect<T>) -> bool {
45
+ return self.min_x < other.max_x && self.max_x > other.min_x && self.min_y < other.max_y && self.max_y > other.min_y
46
+ }
47
+ }
48
+
49
+ pub fn dist_sq_point_to_rect<T: Coord>(p: &Point<T>, r: &Rect<T>) -> T {
50
+ let dx = if p.x < r.min_x {
51
+ r.min_x - p.x
52
+ } else if p.x > r.max_x {
53
+ p.x - r.max_x
54
+ } else {
55
+ T::zero()
56
+ };
57
+
58
+ let dy = if p.y < r.min_y {
59
+ r.min_y - p.y
60
+ } else if p.y > r.max_y {
61
+ p.y - r.max_y
62
+ } else {
63
+ T::zero()
64
+ };
65
+
66
+ dx * dx + dy * dy
67
+ }
68
+
69
+ pub fn dist_sq_points<T: Coord>(a: &Point<T>, b: &Point<T>) -> T {
70
+ let dx = a.x - b.x;
71
+ let dy = a.y - b.y;
72
+ dx * dx + dy * dy
73
+ }