fastquadtree 2.0.0__tar.gz → 2.0.2__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 (110) hide show
  1. {fastquadtree-2.0.0 → fastquadtree-2.0.2}/Cargo.lock +1 -1
  2. {fastquadtree-2.0.0 → fastquadtree-2.0.2}/Cargo.toml +1 -1
  3. {fastquadtree-2.0.0 → fastquadtree-2.0.2}/PKG-INFO +28 -18
  4. {fastquadtree-2.0.0 → fastquadtree-2.0.2}/README.md +27 -17
  5. fastquadtree-2.0.2/assets/quadtree_bench_throughput.png +0 -0
  6. fastquadtree-2.0.2/assets/quadtree_bench_time.png +0 -0
  7. {fastquadtree-2.0.0 → fastquadtree-2.0.2}/benchmarks/quadtree_bench/engines.py +30 -5
  8. {fastquadtree-2.0.0 → fastquadtree-2.0.2}/benchmarks/quadtree_bench/plotting.py +29 -20
  9. {fastquadtree-2.0.0 → fastquadtree-2.0.2}/benchmarks/quadtree_bench/runner.py +1 -1
  10. {fastquadtree-2.0.0 → fastquadtree-2.0.2}/docs/benchmark.md +17 -8
  11. {fastquadtree-2.0.0 → fastquadtree-2.0.2}/docs/runnables.md +20 -3
  12. fastquadtree-2.0.2/examples/custom_id_example.py +31 -0
  13. fastquadtree-2.0.2/examples/object_tracking_example.py +30 -0
  14. {fastquadtree-2.0.0 → fastquadtree-2.0.2}/interactive/ballpit.py +31 -15
  15. {fastquadtree-2.0.0 → fastquadtree-2.0.2}/pysrc/fastquadtree/__init__.py +10 -0
  16. {fastquadtree-2.0.0 → fastquadtree-2.0.2}/pysrc/fastquadtree/_base_quadtree.py +12 -1
  17. {fastquadtree-2.0.0 → fastquadtree-2.0.2}/pysrc/fastquadtree/_item.py +4 -4
  18. {fastquadtree-2.0.0 → fastquadtree-2.0.2}/tests/test_python/integration/test_public_api_contracts.py +4 -0
  19. fastquadtree-2.0.0/assets/quadtree_bench_throughput.png +0 -0
  20. fastquadtree-2.0.0/assets/quadtree_bench_time.png +0 -0
  21. {fastquadtree-2.0.0 → fastquadtree-2.0.2}/.github/workflows/docs.yml +0 -0
  22. {fastquadtree-2.0.0 → fastquadtree-2.0.2}/.github/workflows/release.yml +0 -0
  23. {fastquadtree-2.0.0 → fastquadtree-2.0.2}/.github/workflows/test.yml +0 -0
  24. {fastquadtree-2.0.0 → fastquadtree-2.0.2}/.gitignore +0 -0
  25. {fastquadtree-2.0.0 → fastquadtree-2.0.2}/.pre-commit-config.yaml +0 -0
  26. {fastquadtree-2.0.0 → fastquadtree-2.0.2}/LICENSE +0 -0
  27. {fastquadtree-2.0.0 → fastquadtree-2.0.2}/assets/ballpit.png +0 -0
  28. {fastquadtree-2.0.0 → fastquadtree-2.0.2}/assets/interactive_v2_rect_screenshot.png +0 -0
  29. {fastquadtree-2.0.0 → fastquadtree-2.0.2}/assets/interactive_v2_screenshot.png +0 -0
  30. {fastquadtree-2.0.0 → fastquadtree-2.0.2}/benchmarks/benchmark_native_vs_shim.py +0 -0
  31. {fastquadtree-2.0.0 → fastquadtree-2.0.2}/benchmarks/benchmark_np_vs_list.py +0 -0
  32. {fastquadtree-2.0.0 → fastquadtree-2.0.2}/benchmarks/benchmark_serialization_vs_rebuild.py +0 -0
  33. {fastquadtree-2.0.0 → fastquadtree-2.0.2}/benchmarks/cross_library_bench.py +0 -0
  34. {fastquadtree-2.0.0 → fastquadtree-2.0.2}/benchmarks/quadtree_bench/__init__.py +0 -0
  35. {fastquadtree-2.0.0 → fastquadtree-2.0.2}/benchmarks/quadtree_bench/main.py +0 -0
  36. {fastquadtree-2.0.0 → fastquadtree-2.0.2}/benchmarks/quadtree_bench/optimizer.py +0 -0
  37. {fastquadtree-2.0.0 → fastquadtree-2.0.2}/benchmarks/requirements.txt +0 -0
  38. {fastquadtree-2.0.0 → fastquadtree-2.0.2}/benchmarks/runner.py +0 -0
  39. {fastquadtree-2.0.0 → fastquadtree-2.0.2}/benchmarks/system_info_collector.py +0 -0
  40. {fastquadtree-2.0.0 → fastquadtree-2.0.2}/docs/2.0_migration_guide.md +0 -0
  41. {fastquadtree-2.0.0 → fastquadtree-2.0.2}/docs/2.0_proposal.md +0 -0
  42. {fastquadtree-2.0.0 → fastquadtree-2.0.2}/docs/api/insert_result.md +0 -0
  43. {fastquadtree-2.0.0 → fastquadtree-2.0.2}/docs/api/item.md +0 -0
  44. {fastquadtree-2.0.0 → fastquadtree-2.0.2}/docs/api/point_item.md +0 -0
  45. {fastquadtree-2.0.0 → fastquadtree-2.0.2}/docs/api/pyqtree.md +0 -0
  46. {fastquadtree-2.0.0 → fastquadtree-2.0.2}/docs/api/quadtree.md +0 -0
  47. {fastquadtree-2.0.0 → fastquadtree-2.0.2}/docs/api/quadtree_objects.md +0 -0
  48. {fastquadtree-2.0.0 → fastquadtree-2.0.2}/docs/api/rect_item.md +0 -0
  49. {fastquadtree-2.0.0 → fastquadtree-2.0.2}/docs/api/rect_quadtree.md +0 -0
  50. {fastquadtree-2.0.0 → fastquadtree-2.0.2}/docs/api/rect_quadtree_objects.md +0 -0
  51. {fastquadtree-2.0.0 → fastquadtree-2.0.2}/docs/future_features.md +0 -0
  52. {fastquadtree-2.0.0 → fastquadtree-2.0.2}/docs/index.md +0 -0
  53. {fastquadtree-2.0.0 → fastquadtree-2.0.2}/docs/quickstart.md +0 -0
  54. {fastquadtree-2.0.0 → fastquadtree-2.0.2}/docs/rust_usage.md +0 -0
  55. {fastquadtree-2.0.0 → fastquadtree-2.0.2}/docs/styles/overrides.css +0 -0
  56. {fastquadtree-2.0.0 → fastquadtree-2.0.2}/interactive/interactive.py +0 -0
  57. {fastquadtree-2.0.0 → fastquadtree-2.0.2}/interactive/interactive_v2.py +0 -0
  58. {fastquadtree-2.0.0 → fastquadtree-2.0.2}/interactive/interactive_v2_rect.py +0 -0
  59. {fastquadtree-2.0.0 → fastquadtree-2.0.2}/interactive/requirements.txt +0 -0
  60. {fastquadtree-2.0.0 → fastquadtree-2.0.2}/mkdocs.yml +0 -0
  61. {fastquadtree-2.0.0 → fastquadtree-2.0.2}/pyproject.toml +0 -0
  62. {fastquadtree-2.0.0 → fastquadtree-2.0.2}/pyrightconfig.json +0 -0
  63. {fastquadtree-2.0.0 → fastquadtree-2.0.2}/pysrc/fastquadtree/_base_quadtree_objects.py +0 -0
  64. {fastquadtree-2.0.0 → fastquadtree-2.0.2}/pysrc/fastquadtree/_common.py +0 -0
  65. {fastquadtree-2.0.0 → fastquadtree-2.0.2}/pysrc/fastquadtree/_insert_result.py +0 -0
  66. {fastquadtree-2.0.0 → fastquadtree-2.0.2}/pysrc/fastquadtree/_obj_store.py +0 -0
  67. {fastquadtree-2.0.0 → fastquadtree-2.0.2}/pysrc/fastquadtree/point_quadtree.py +0 -0
  68. {fastquadtree-2.0.0 → fastquadtree-2.0.2}/pysrc/fastquadtree/point_quadtree_objects.py +0 -0
  69. {fastquadtree-2.0.0 → fastquadtree-2.0.2}/pysrc/fastquadtree/py.typed +0 -0
  70. {fastquadtree-2.0.0 → fastquadtree-2.0.2}/pysrc/fastquadtree/pyqtree.py +0 -0
  71. {fastquadtree-2.0.0 → fastquadtree-2.0.2}/pysrc/fastquadtree/rect_quadtree.py +0 -0
  72. {fastquadtree-2.0.0 → fastquadtree-2.0.2}/pysrc/fastquadtree/rect_quadtree_objects.py +0 -0
  73. {fastquadtree-2.0.0 → fastquadtree-2.0.2}/src/geom.rs +0 -0
  74. {fastquadtree-2.0.0 → fastquadtree-2.0.2}/src/lib.rs +0 -0
  75. {fastquadtree-2.0.0 → fastquadtree-2.0.2}/src/quadtree.rs +0 -0
  76. {fastquadtree-2.0.0 → fastquadtree-2.0.2}/src/rect_quadtree.rs +0 -0
  77. {fastquadtree-2.0.0 → fastquadtree-2.0.2}/tests/__init__.py +0 -0
  78. {fastquadtree-2.0.0 → fastquadtree-2.0.2}/tests/insertions.rs +0 -0
  79. {fastquadtree-2.0.0 → fastquadtree-2.0.2}/tests/nearest_neighbor.rs +0 -0
  80. {fastquadtree-2.0.0 → fastquadtree-2.0.2}/tests/query.rs +0 -0
  81. {fastquadtree-2.0.0 → fastquadtree-2.0.2}/tests/rect_quadtree.rs +0 -0
  82. {fastquadtree-2.0.0 → fastquadtree-2.0.2}/tests/rectangle_traversal.rs +0 -0
  83. {fastquadtree-2.0.0 → fastquadtree-2.0.2}/tests/serialization.rs +0 -0
  84. {fastquadtree-2.0.0 → fastquadtree-2.0.2}/tests/test_delete.rs +0 -0
  85. {fastquadtree-2.0.0 → fastquadtree-2.0.2}/tests/test_python/__init__.py +0 -0
  86. {fastquadtree-2.0.0 → fastquadtree-2.0.2}/tests/test_python/common/test_common_validation.py +0 -0
  87. {fastquadtree-2.0.0 → fastquadtree-2.0.2}/tests/test_python/common/test_insert_result_and_items.py +0 -0
  88. {fastquadtree-2.0.0 → fastquadtree-2.0.2}/tests/test_python/common/test_internal_edges.py +0 -0
  89. {fastquadtree-2.0.0 → fastquadtree-2.0.2}/tests/test_python/common/test_obj_store.py +0 -0
  90. {fastquadtree-2.0.0 → fastquadtree-2.0.2}/tests/test_python/common/test_serialization_container.py +0 -0
  91. {fastquadtree-2.0.0 → fastquadtree-2.0.2}/tests/test_python/conftest.py +0 -0
  92. {fastquadtree-2.0.0 → fastquadtree-2.0.2}/tests/test_python/integration/test_migration_breaks_and_safety.py +0 -0
  93. {fastquadtree-2.0.0 → fastquadtree-2.0.2}/tests/test_python/points/test_point_quadtree_core.py +0 -0
  94. {fastquadtree-2.0.0 → fastquadtree-2.0.2}/tests/test_python/points/test_point_quadtree_mutation.py +0 -0
  95. {fastquadtree-2.0.0 → fastquadtree-2.0.2}/tests/test_python/points/test_point_quadtree_numpy.py +0 -0
  96. {fastquadtree-2.0.0 → fastquadtree-2.0.2}/tests/test_python/points/test_point_quadtree_serialization.py +0 -0
  97. {fastquadtree-2.0.0 → fastquadtree-2.0.2}/tests/test_python/points_objects/test_point_quadtree_objects_core.py +0 -0
  98. {fastquadtree-2.0.0 → fastquadtree-2.0.2}/tests/test_python/points_objects/test_point_quadtree_objects_deletion_update.py +0 -0
  99. {fastquadtree-2.0.0 → fastquadtree-2.0.2}/tests/test_python/points_objects/test_point_quadtree_objects_numpy.py +0 -0
  100. {fastquadtree-2.0.0 → fastquadtree-2.0.2}/tests/test_python/points_objects/test_point_quadtree_objects_serialization.py +0 -0
  101. {fastquadtree-2.0.0 → fastquadtree-2.0.2}/tests/test_python/rects/test_rect_quadtree_core.py +0 -0
  102. {fastquadtree-2.0.0 → fastquadtree-2.0.2}/tests/test_python/rects/test_rect_quadtree_mutation.py +0 -0
  103. {fastquadtree-2.0.0 → fastquadtree-2.0.2}/tests/test_python/rects/test_rect_quadtree_numpy.py +0 -0
  104. {fastquadtree-2.0.0 → fastquadtree-2.0.2}/tests/test_python/rects/test_rect_quadtree_serialization.py +0 -0
  105. {fastquadtree-2.0.0 → fastquadtree-2.0.2}/tests/test_python/rects_objects/test_rect_quadtree_objects_core.py +0 -0
  106. {fastquadtree-2.0.0 → fastquadtree-2.0.2}/tests/test_python/rects_objects/test_rect_quadtree_objects_deletion_update.py +0 -0
  107. {fastquadtree-2.0.0 → fastquadtree-2.0.2}/tests/test_python/rects_objects/test_rect_quadtree_objects_numpy.py +0 -0
  108. {fastquadtree-2.0.0 → fastquadtree-2.0.2}/tests/test_python/rects_objects/test_rect_quadtree_objects_serialization.py +0 -0
  109. {fastquadtree-2.0.0 → fastquadtree-2.0.2}/tests/test_python/test_pyqtree_shim_compat.py +0 -0
  110. {fastquadtree-2.0.0 → fastquadtree-2.0.2}/tests/unconventional_bounds.rs +0 -0
@@ -30,7 +30,7 @@ dependencies = [
30
30
 
31
31
  [[package]]
32
32
  name = "fastquadtree"
33
- version = "2.0.0"
33
+ version = "2.0.2"
34
34
  dependencies = [
35
35
  "bincode",
36
36
  "num-traits",
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "fastquadtree"
3
- version = "2.0.0"
3
+ version = "2.0.2"
4
4
  edition = "2021"
5
5
  readme = "README.md"
6
6
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fastquadtree
3
- Version: 2.0.0
3
+ Version: 2.0.2
4
4
  Classifier: Programming Language :: Python :: 3
5
5
  Classifier: Programming Language :: Python :: 3 :: Only
6
6
  Classifier: Programming Language :: Rust
@@ -80,11 +80,6 @@ Rust-optimized quadtree with a clean Python API
80
80
 
81
81
  ----
82
82
 
83
-
84
- ## Examples
85
- See examples of how fastquadtree can be used in the [runnables](https://elan456.github.io/fastquadtree/runnables/) section.
86
-
87
-
88
83
  ## Install
89
84
  ```bash
90
85
  pip install fastquadtree
@@ -98,6 +93,17 @@ from fastquadtree import RectQuadTreeObjects # Bounding box handling with objec
98
93
  from fastquadtree.pyqtree import Index # Drop-in pyqtree shim (~6.5x faster while keeping the same API)
99
94
  ```
100
95
 
96
+
97
+ ## Quickstart
98
+
99
+ ```python
100
+ from fastquadtree import QuadTree
101
+
102
+ qt = QuadTree((0, 0, 1000, 1000), 16) # bounds and capacity
103
+ qt.insert((100, 200), id_=1) # insert point with ID 1
104
+ print(qt.query((0, 0, 500, 500))) # gets all points in that area: [(1, 100.0, 200.0)]
105
+ ```
106
+ [See the quickstart guide](https://elan456.github.io/fastquadtree/quickstart/) or the [interactive demos](https://elan456.github.io/fastquadtree/runnables/) for more details.
101
107
  ## Benchmarks
102
108
 
103
109
  fastquadtree **outperforms** all other quadtree Python packages, including the Rtree spatial index.
@@ -113,20 +119,23 @@ fastquadtree **outperforms** all other quadtree Python packages, including the R
113
119
 
114
120
  | Library | Build (s) | Query (s) | Total (s) | Speed vs PyQtree |
115
121
  |---|---:|---:|---:|---:|
116
- | fastquadtree | 0.063 | 0.026 | 0.089 | 48.99× |
117
- | Shapely STRtree | 0.314 | 0.178 | 0.492 | 8.90× |
118
- | fastquadtree (obj tracking) | 0.388 | 0.244 | 0.632 | 6.93× |
119
- | nontree-QuadTree | 1.180 | 1.287 | 2.467 | 1.77× |
120
- | Rtree | 1.905 | 0.622 | 2.527 | 1.73× |
121
- | e-pyquadtree | 2.083 | 1.479 | 3.562 | 1.23× |
122
- | quads | 3.058 | 1.140 | 4.198 | 1.04× |
123
- | PyQtree | 3.775 | 0.603 | 4.378 | 1.00× |
122
+ | fastquadtree (np)[^fqtnp] | 0.057 | 0.021 | 0.078 | 54.45× |
123
+ | fastquadtree[^fqt] | 0.060 | 0.189 | 0.249 | 17.04× |
124
+ | Shapely STRtree[^npreturn] | 0.321 | 0.196 | 0.517 | 8.21× |
125
+ | fastquadtree (obj tracking)[^fqto] | 0.437 | 0.239 | 0.675 | 6.28× |
126
+ | Rtree | 1.796 | 0.561 | 2.357 | 1.80× |
127
+ | nontree-QuadTree | 1.275 | 1.272 | 2.547 | 1.67× |
128
+ | e-pyquadtree | 2.144 | 1.507 | 3.650 | 1.16× |
129
+ | quads | 3.001 | 1.171 | 4.172 | 1.02× |
130
+ | PyQtree | 3.677 | 0.565 | 4.242 | 1.00× |
131
+
132
+ [^fqtnp]: Uses `query_np` for Numpy array return values rather than Python lists.
133
+ [^fqt]: Uses standard `query` method returning Python lists.
134
+ [^npreturn]: Uses Shapely STRtree with Numpy array points and returns.
135
+ [^fqto]: Uses QuadTreeObjects with object association.
124
136
 
125
137
  See the [benchmark section](https://elan456.github.io/fastquadtree/benchmark/) for details, including configurations, system info, and native vs shim benchmarks.
126
138
 
127
- ## Quickstart
128
- [See the quickstart guide](https://elan456.github.io/fastquadtree/quickstart/)
129
-
130
139
  ## API
131
140
 
132
141
  [See the full API](https://elan456.github.io/fastquadtree/api/quadtree/)
@@ -170,7 +179,8 @@ For object tracking, use `QuadTreeObjects` instead. See the [docs](https://elan4
170
179
  ![Ballpit_Demo_Screenshot](https://raw.githubusercontent.com/Elan456/fastquadtree/main/assets/ballpit.png)
171
180
 
172
181
  A simple demo of moving objects with collision detection using **fastquadtree**.
173
- You can toggle between quadtree mode and brute-force mode to see the performance difference.
182
+ You can toggle between fastquadtree, pyqtree, and brute-force mode to see the performance difference.
183
+ I typically see an FPS of ~70 with fastquadtree, ~25 with pyqtree, and <1 FPS with brute-force on my machine with 1500 balls.
174
184
 
175
185
  See the [runnables guide](https://elan456.github.io/fastquadtree/runnables/) for setup instructions.
176
186
 
@@ -39,11 +39,6 @@ Rust-optimized quadtree with a clean Python API
39
39
 
40
40
  ----
41
41
 
42
-
43
- ## Examples
44
- See examples of how fastquadtree can be used in the [runnables](https://elan456.github.io/fastquadtree/runnables/) section.
45
-
46
-
47
42
  ## Install
48
43
  ```bash
49
44
  pip install fastquadtree
@@ -57,6 +52,17 @@ from fastquadtree import RectQuadTreeObjects # Bounding box handling with objec
57
52
  from fastquadtree.pyqtree import Index # Drop-in pyqtree shim (~6.5x faster while keeping the same API)
58
53
  ```
59
54
 
55
+
56
+ ## Quickstart
57
+
58
+ ```python
59
+ from fastquadtree import QuadTree
60
+
61
+ qt = QuadTree((0, 0, 1000, 1000), 16) # bounds and capacity
62
+ qt.insert((100, 200), id_=1) # insert point with ID 1
63
+ print(qt.query((0, 0, 500, 500))) # gets all points in that area: [(1, 100.0, 200.0)]
64
+ ```
65
+ [See the quickstart guide](https://elan456.github.io/fastquadtree/quickstart/) or the [interactive demos](https://elan456.github.io/fastquadtree/runnables/) for more details.
60
66
  ## Benchmarks
61
67
 
62
68
  fastquadtree **outperforms** all other quadtree Python packages, including the Rtree spatial index.
@@ -72,20 +78,23 @@ fastquadtree **outperforms** all other quadtree Python packages, including the R
72
78
 
73
79
  | Library | Build (s) | Query (s) | Total (s) | Speed vs PyQtree |
74
80
  |---|---:|---:|---:|---:|
75
- | fastquadtree | 0.063 | 0.026 | 0.089 | 48.99× |
76
- | Shapely STRtree | 0.314 | 0.178 | 0.492 | 8.90× |
77
- | fastquadtree (obj tracking) | 0.388 | 0.244 | 0.632 | 6.93× |
78
- | nontree-QuadTree | 1.180 | 1.287 | 2.467 | 1.77× |
79
- | Rtree | 1.905 | 0.622 | 2.527 | 1.73× |
80
- | e-pyquadtree | 2.083 | 1.479 | 3.562 | 1.23× |
81
- | quads | 3.058 | 1.140 | 4.198 | 1.04× |
82
- | PyQtree | 3.775 | 0.603 | 4.378 | 1.00× |
81
+ | fastquadtree (np)[^fqtnp] | 0.057 | 0.021 | 0.078 | 54.45× |
82
+ | fastquadtree[^fqt] | 0.060 | 0.189 | 0.249 | 17.04× |
83
+ | Shapely STRtree[^npreturn] | 0.321 | 0.196 | 0.517 | 8.21× |
84
+ | fastquadtree (obj tracking)[^fqto] | 0.437 | 0.239 | 0.675 | 6.28× |
85
+ | Rtree | 1.796 | 0.561 | 2.357 | 1.80× |
86
+ | nontree-QuadTree | 1.275 | 1.272 | 2.547 | 1.67× |
87
+ | e-pyquadtree | 2.144 | 1.507 | 3.650 | 1.16× |
88
+ | quads | 3.001 | 1.171 | 4.172 | 1.02× |
89
+ | PyQtree | 3.677 | 0.565 | 4.242 | 1.00× |
90
+
91
+ [^fqtnp]: Uses `query_np` for Numpy array return values rather than Python lists.
92
+ [^fqt]: Uses standard `query` method returning Python lists.
93
+ [^npreturn]: Uses Shapely STRtree with Numpy array points and returns.
94
+ [^fqto]: Uses QuadTreeObjects with object association.
83
95
 
84
96
  See the [benchmark section](https://elan456.github.io/fastquadtree/benchmark/) for details, including configurations, system info, and native vs shim benchmarks.
85
97
 
86
- ## Quickstart
87
- [See the quickstart guide](https://elan456.github.io/fastquadtree/quickstart/)
88
-
89
98
  ## API
90
99
 
91
100
  [See the full API](https://elan456.github.io/fastquadtree/api/quadtree/)
@@ -129,7 +138,8 @@ For object tracking, use `QuadTreeObjects` instead. See the [docs](https://elan4
129
138
  ![Ballpit_Demo_Screenshot](https://raw.githubusercontent.com/Elan456/fastquadtree/main/assets/ballpit.png)
130
139
 
131
140
  A simple demo of moving objects with collision detection using **fastquadtree**.
132
- You can toggle between quadtree mode and brute-force mode to see the performance difference.
141
+ You can toggle between fastquadtree, pyqtree, and brute-force mode to see the performance difference.
142
+ I typically see an FPS of ~70 with fastquadtree, ~25 with pyqtree, and <1 FPS with brute-force on my machine with 1500 balls.
133
143
 
134
144
  See the [runnables guide](https://elan456.github.io/fastquadtree/runnables/) for setup instructions.
135
145
 
@@ -100,7 +100,7 @@ def _create_pyqtree_engine(
100
100
  def _create_fastquadtree_np_engine(
101
101
  bounds: Tuple[int, int, int, int], max_points: int, max_depth: int
102
102
  ) -> Engine:
103
- """Create engine adapter for fastquadtree."""
103
+ """Create engine adapter for fastquadtree but queries are returned as numpy arrays instead of Python lists."""
104
104
 
105
105
  def build(points):
106
106
  qt = FQTQuadTree(bounds, max_points, max_depth=max_depth)
@@ -111,9 +111,31 @@ def _create_fastquadtree_np_engine(
111
111
  for q in queries:
112
112
  _ = qt.query_np(q)
113
113
 
114
+ return Engine(
115
+ "fastquadtree (np)",
116
+ "#e55100",
117
+ build,
118
+ query, # display name # color (orange)
119
+ )
120
+
121
+
122
+ def _create_fastquadtree_engine(
123
+ bounds: Tuple[int, int, int, int], max_points: int, max_depth: int
124
+ ) -> Engine:
125
+ """Create engine adapter for fastquadtree."""
126
+
127
+ def build(points):
128
+ qt = FQTQuadTree(bounds, max_points, max_depth=max_depth)
129
+ qt.insert_many(points)
130
+ return qt
131
+
132
+ def query(qt, queries):
133
+ for q in queries:
134
+ _ = qt.query(q)
135
+
114
136
  return Engine(
115
137
  "fastquadtree",
116
- "#ff7f0e",
138
+ "#ff9500",
117
139
  build,
118
140
  query, # display name # color (orange)
119
141
  )
@@ -134,8 +156,8 @@ def _create_fastquadtree_items_engine(
134
156
  _ = qt.query(q)
135
157
 
136
158
  return Engine(
137
- "fastquadtree (obj tracking)",
138
- "#ffb347",
159
+ "fastquadtree (objs)",
160
+ "#ffc107",
139
161
  build,
140
162
  query, # display name # color (orange)
141
163
  )
@@ -314,7 +336,10 @@ def get_engines(
314
336
  """
315
337
  # Always available engines
316
338
  engines = {
317
- "fastquadtree": _create_fastquadtree_np_engine(bounds, max_points, max_depth),
339
+ "fastquadtree (np)": _create_fastquadtree_np_engine(
340
+ bounds, max_points, max_depth
341
+ ),
342
+ "fastquadtree": _create_fastquadtree_engine(bounds, max_points, max_depth),
318
343
  "fastquadtree (obj tracking)": _create_fastquadtree_items_engine(
319
344
  bounds, max_points, max_depth
320
345
  ),
@@ -61,23 +61,27 @@ class PlotManager:
61
61
 
62
62
  # Update layout
63
63
  fig.update_layout(
64
- title=(
65
- f"Tree build and query benchmarks "
66
- f"(Max Depth {self.config.max_depth}, "
67
- f"Capacity {self.config.max_points}, "
68
- f"{self.config.repeats}x median, "
69
- f"{self.config.n_queries} queries)"
70
- ),
64
+ title={
65
+ "text": (
66
+ f"Tree build and query benchmarks "
67
+ f"(Max Depth {self.config.max_depth}, "
68
+ f"Capacity {self.config.max_points}, "
69
+ f"{self.config.repeats}x median, "
70
+ f"{self.config.n_queries} queries)"
71
+ ),
72
+ "y": 0.98,
73
+ "x": 0.5,
74
+ "xanchor": "center",
75
+ },
71
76
  template="plotly_dark",
72
77
  legend={
73
- "orientation": "v",
74
- "traceorder": "normal",
75
- "xanchor": "left",
76
- "x": 0,
77
- "yanchor": "top",
78
- "y": 1,
78
+ "orientation": "h",
79
+ "x": 0.5,
80
+ "xanchor": "center",
81
+ "y": 1.12,
82
+ "yanchor": "bottom",
79
83
  },
80
- margin={"l": 40, "r": 20, "t": 80, "b": 40},
84
+ margin={"l": 40, "r": 20, "t": 140, "b": 40}, # Increase top margin
81
85
  height=520,
82
86
  )
83
87
 
@@ -93,7 +97,7 @@ class PlotManager:
93
97
  )
94
98
 
95
99
  # Set logarithmic scale for query rate
96
- fig.update_yaxes(type="log", row=1, col=2)
100
+ fig.update_yaxes(type="log", row=1, col=2, tickformat=".0s", dtick=0.25)
97
101
 
98
102
  for name in list(self.results["query_rate"].keys()):
99
103
  # Determine color
@@ -136,16 +140,21 @@ class PlotManager:
136
140
 
137
141
  # Update layout
138
142
  fig.update_layout(
139
- title="Throughput",
143
+ title={
144
+ "text": "Throughput",
145
+ "y": 0.98, # Push title to very top
146
+ "x": 0.5,
147
+ "xanchor": "center",
148
+ },
140
149
  template="plotly_dark",
141
150
  legend={
142
151
  "orientation": "h",
143
- "x": 0,
144
- "xanchor": "left",
145
- "y": 1.08, # above the subplots
152
+ "x": 0.5,
153
+ "xanchor": "center",
154
+ "y": 1.15, # Move legend higher
146
155
  "yanchor": "bottom",
147
156
  },
148
- margin={"l": 60, "r": 40, "t": 120, "b": 40},
157
+ margin={"l": 60, "r": 40, "t": 140, "b": 40}, # Increase top margin
149
158
  height=480,
150
159
  )
151
160
 
@@ -35,7 +35,7 @@ class BenchmarkConfig:
35
35
 
36
36
  def __post_init__(self):
37
37
  """Generate experiment point sizes."""
38
- self.experiments = [2, 4, 8, 16]
38
+ self.experiments = [128000]
39
39
  while self.experiments[-1] < self.max_experiment_points:
40
40
  self.experiments.append(int(self.experiments[-1] * 2))
41
41
  self.experiments[-1] = min(self.experiments[-1], self.max_experiment_points)
@@ -16,14 +16,23 @@ Quadtrees are the focus of the benchmark, but Rtrees are included for reference.
16
16
 
17
17
  | Library | Build (s) | Query (s) | Total (s) | Speed vs PyQtree |
18
18
  |---|---:|---:|---:|---:|
19
- | fastquadtree | 0.063 | 0.026 | 0.089 | 48.99× |
20
- | Shapely STRtree | 0.314 | 0.178 | 0.492 | 8.90× |
21
- | fastquadtree (obj tracking) | 0.388 | 0.244 | 0.632 | 6.93× |
22
- | nontree-QuadTree | 1.180 | 1.287 | 2.467 | 1.77× |
23
- | Rtree | 1.905 | 0.622 | 2.527 | 1.73× |
24
- | e-pyquadtree | 2.083 | 1.479 | 3.562 | 1.23× |
25
- | quads | 3.058 | 1.140 | 4.198 | 1.04× |
26
- | PyQtree | 3.775 | 0.603 | 4.378 | 1.00× |
19
+ | fastquadtree (np)[^fqtnp] | 0.057 | 0.021 | 0.078 | 54.45× |
20
+ | fastquadtree[^fqt] | 0.060 | 0.189 | 0.249 | 17.04× |
21
+ | Shapely STRtree[^npreturn] | 0.321 | 0.196 | 0.517 | 8.21× |
22
+ | fastquadtree (obj tracking)[^fqto] | 0.437 | 0.239 | 0.675 | 6.28× |
23
+ | Rtree | 1.796 | 0.561 | 2.357 | 1.80× |
24
+ | nontree-QuadTree | 1.275 | 1.272 | 2.547 | 1.67× |
25
+ | e-pyquadtree | 2.144 | 1.507 | 3.650 | 1.16× |
26
+ | quads | 3.001 | 1.171 | 4.172 | 1.02× |
27
+ | PyQtree | 3.677 | 0.565 | 4.242 | 1.00× |
28
+
29
+ [^fqtnp]: Uses `query_np` for Numpy array return values rather than Python lists.
30
+ [^fqt]: Uses standard `query` method returning Python lists.
31
+ [^npreturn]: Uses Shapely STRtree with Numpy array points and returns.
32
+ [^fqto]: Uses QuadTreeObjects with object association.
33
+
34
+
35
+
27
36
 
28
37
  #### Benchmark Configuration
29
38
  | Parameter | Value |
@@ -1,6 +1,26 @@
1
1
 
2
2
  # Runnable Examples
3
3
 
4
+ After cloning the repository, you can run the following examples to see how fastquadtree works in practice.
5
+
6
+ ## Environment Setup
7
+
8
+ ### Clone the repo
9
+ ```bash
10
+ git clone https://github.com/Elan456/fastquadtree.git
11
+ cd fastquadtree
12
+ ```
13
+
14
+ ### Create and activate a virtual environment, install dependencies
15
+ This step can be skipped if you already have a suitable environment.
16
+ ```bash
17
+ python -m venv .venv
18
+ source .venv/bin/activate # On Windows use `.venv\Scripts\activate`
19
+ pip install -e .
20
+ maturin develop --release
21
+ pip install -r interactive/requirements.txt
22
+ ```
23
+
4
24
  ## 1. Interactive demo
5
25
  - Add and delete boids with mouse clicks
6
26
  - Visualize KNN and range queries
@@ -10,7 +30,6 @@ You can see how the quadtree subdivides as you add points, and validate the accu
10
30
  By pressing 1, you can visualize the KNN query for each boid.
11
31
 
12
32
  ```bash
13
- pip install -r interactive/requirements.txt
14
33
  python interactive/interactive_v2.py
15
34
  ```
16
35
 
@@ -24,7 +43,6 @@ If you are creating a game or simulation environment where entities have boundin
24
43
  rectangular quadtree to quickly check which entities are intersecting with another.
25
44
 
26
45
  ```bash
27
- pip install -r interactive/requirements.txt
28
46
  python interactive/interactive_v2_rect.py
29
47
  ```
30
48
 
@@ -38,7 +56,6 @@ The ball pit demo shows how quadtrees offer massive performance improvements for
38
56
  Rectangular queries are used to find potential collisions, and then precise circle-circle collision checks are performed.
39
57
 
40
58
  ```bash
41
- pip install -r interactive/requirements.txt
42
59
  python interactive/ball_pit.py
43
60
  ```
44
61
 
@@ -0,0 +1,31 @@
1
+ from fastquadtree import QuadTree
2
+
3
+
4
+ class Person:
5
+ def __init__(self, x: float, y: float, name: str):
6
+ self.x = x
7
+ self.y = y
8
+ self.name = name
9
+
10
+
11
+ qt = QuadTree((0, 0, 1000, 1000), 16)
12
+
13
+ people = [
14
+ Person(100, 200, "Alice"),
15
+ Person(150, 250, "Bob"),
16
+ Person(300, 400, "Charlie"),
17
+ ]
18
+
19
+ for idx, person in enumerate(people):
20
+ qt.insert((person.x, person.y), id_=idx)
21
+
22
+ # Find people in a specific area
23
+ results = qt.query((90, 190, 200, 300))
24
+ for id_, x, y in results:
25
+ person = people[id_]
26
+ print(f"Found {person.name} at ({x}, {y})")
27
+
28
+
29
+ # Output:
30
+ # Found Alice at (100.0, 200.0)
31
+ # Found Bob at (150.0, 250.0)
@@ -0,0 +1,30 @@
1
+ from fastquadtree import QuadTreeObjects
2
+
3
+
4
+ class Person:
5
+ def __init__(self, x: float, y: float, name: str):
6
+ self.x = x
7
+ self.y = y
8
+ self.name = name
9
+
10
+
11
+ qt = QuadTreeObjects((0, 0, 1000, 1000), 16)
12
+
13
+ people = [
14
+ Person(100, 200, "Alice"),
15
+ Person(150, 250, "Bob"),
16
+ Person(300, 400, "Charlie"),
17
+ ]
18
+
19
+ for person in people:
20
+ qt.insert((person.x, person.y), obj=person)
21
+
22
+ # Find people in a specific area
23
+ results = qt.query((90, 190, 200, 300))
24
+ for item in results:
25
+ print(f"Found {item.obj.name} at ({item.x}, {item.y})")
26
+
27
+
28
+ # Output:
29
+ # Found Alice at (100, 200)
30
+ # Found Bob at (150, 250)
@@ -1,12 +1,13 @@
1
1
  import math
2
2
  import random
3
+ import time
3
4
  from typing import Iterable, List, Set, Tuple
4
5
 
5
6
  import pygame
6
7
  from pyqtree import Index as PyQIndex
7
8
 
8
9
  # Spatial backends -------------------------------------------------------------
9
- from fastquadtree import QuadTreeObjects
10
+ from fastquadtree import Quadtree
10
11
 
11
12
  # ---------------------------- Ball object ---------------------------- #
12
13
 
@@ -130,23 +131,23 @@ class FastQTIndex(SpatialBase):
130
131
  self.width = width
131
132
  self.height = height
132
133
  self.capacity = capacity
133
- self.qt = QuadTreeObjects((0, 0, width, height), capacity)
134
+ self.qt = Quadtree((0, 0, width, height), capacity)
134
135
 
135
136
  def rebuild(self, balls: List[Ball], width: int, height: int) -> None:
136
137
  if width != self.width or height != self.height:
137
138
  self.width, self.height = width, height
138
- self.qt = QuadTreeObjects((0, 0, width, height), self.capacity)
139
+ self.qt.clear()
139
140
  else:
140
141
  self.qt.clear()
141
- for b in balls:
142
- self.qt.insert((b.x, b.y), obj=b)
143
142
 
144
- def neighbors(self, b: Ball) -> Iterable[Ball]:
143
+ self.qt.insert_many([(b.x, b.y) for b in balls])
144
+
145
+ def neighbors(self, b: Ball, balls: List[Ball]) -> Iterable[Ball]:
145
146
  r2 = 2 * b.r
146
147
  min_x, min_y, max_x, max_y = b.x - r2, b.y - r2, b.x + r2, b.y + r2
147
148
  neighbors = self.qt.query((min_x, min_y, max_x, max_y))
148
149
  for it in neighbors:
149
- other = it.obj
150
+ other = balls[it[0]]
150
151
  if other is not None and other is not b:
151
152
  yield other
152
153
 
@@ -259,7 +260,7 @@ class BallPit:
259
260
  def _backend(self) -> SpatialBase:
260
261
  return self.backends[self.mode]
261
262
 
262
- def update(self, dt: float):
263
+ def update(self, dt: float) -> tuple[float, float]:
263
264
  # 1) Integrate motion
264
265
  ax, ay = 0.0, 0.0
265
266
  for b in self.balls:
@@ -268,13 +269,26 @@ class BallPit:
268
269
 
269
270
  # 2) Rebuild spatial index
270
271
  backend = self._backend()
272
+ start = time.perf_counter()
271
273
  backend.rebuild(self.balls, self.width, self.height)
274
+ end = time.perf_counter()
275
+
276
+ tree_build_time = end - start
277
+
278
+ total_neighbor_collection_time = 0.0
272
279
 
273
280
  # 3) Neighborhood checks with dedup on object id pairs
274
281
  self.pair_checks = 0
275
282
  processed: Set[Tuple[int, int]] = set()
276
283
  for a in self.balls:
277
- for other in backend.neighbors(a):
284
+ start = time.perf_counter()
285
+ if self.mode == "fastquadtree":
286
+ neighbors = backend.neighbors(a, self.balls) # type: ignore
287
+ else:
288
+ neighbors = backend.neighbors(a)
289
+ end = time.perf_counter()
290
+ total_neighbor_collection_time += end - start
291
+ for other in neighbors:
278
292
  a_id = id(a)
279
293
  o_id = id(other)
280
294
  key = (a_id, o_id) if a_id < o_id else (o_id, a_id)
@@ -283,11 +297,11 @@ class BallPit:
283
297
  processed.add(key)
284
298
  self.pair_checks += 1
285
299
  resolve_ball_ball(a, other)
300
+ return (total_neighbor_collection_time, tree_build_time)
286
301
 
287
- # 4) Some backends benefit from post-resolution rebuild for next frame
288
- backend.rebuild(self.balls, self.width, self.height)
289
-
290
- def draw(self, fps: float):
302
+ def draw(
303
+ self, fps: float, total_neighbor_collection_time: float, tree_build_time: float
304
+ ):
291
305
  for ball in self.balls:
292
306
  ball.draw(self.screen)
293
307
 
@@ -295,6 +309,8 @@ class BallPit:
295
309
  font = pygame.font.SysFont(None, 20)
296
310
  hud_lines = [
297
311
  f"FPS: {fps:.1f}",
312
+ f"Build time: {tree_build_time * 1000:.2f} ms",
313
+ f"Query time: {total_neighbor_collection_time * 1000:.2f} ms",
298
314
  f"Mode: {self.mode} (Tab to cycle, 1/2/3 to select)",
299
315
  f"Balls: {len(self.balls)}",
300
316
  f"Pair checks this frame: {self.pair_checks}",
@@ -373,7 +389,7 @@ def main():
373
389
  )
374
390
  ball_pit.add_ball(x, y, radius=r, color=color)
375
391
 
376
- ball_pit.update(dt)
392
+ stats = ball_pit.update(dt)
377
393
 
378
394
  screen.fill((255, 255, 255))
379
395
  fps = clock.get_fps()
@@ -387,7 +403,7 @@ def main():
387
403
  + fps
388
404
  ) / metrics[ball_pit.mode]["count"]
389
405
 
390
- ball_pit.draw(fps)
406
+ ball_pit.draw(fps, stats[0], stats[1])
391
407
  pygame.display.flip()
392
408
 
393
409
  pygame.quit()
@@ -19,13 +19,23 @@ from .point_quadtree_objects import QuadTreeObjects
19
19
  from .rect_quadtree import RectQuadTree
20
20
  from .rect_quadtree_objects import RectQuadTreeObjects
21
21
 
22
+ # Allow lowercase version of quadtree for convenience
23
+ Quadtree = QuadTree
24
+ QuadtreeObjects = QuadTreeObjects
25
+ Rectquadtree = RectQuadTree
26
+ RectquadtreeObjects = RectQuadTreeObjects
27
+
22
28
  __all__ = [
23
29
  "InsertResult",
24
30
  "Item",
25
31
  "PointItem",
26
32
  "QuadTree",
27
33
  "QuadTreeObjects",
34
+ "Quadtree",
35
+ "QuadtreeObjects",
28
36
  "RectItem",
29
37
  "RectQuadTree",
30
38
  "RectQuadTreeObjects",
39
+ "Rectquadtree",
40
+ "RectquadtreeObjects",
31
41
  ]
@@ -116,7 +116,8 @@ class _BaseQuadTree(Generic[G], ABC):
116
116
 
117
117
  def insert_many(self, geoms: list[G]) -> InsertResult:
118
118
  """
119
- Bulk insert geometries with auto-assigned contiguous IDs.
119
+ Bulk insert geometries with auto-assigned contiguous IDs. <br>
120
+ IDs start at 0 and increment by 1, so they will be aligned with the indexes of the input list if the tree started empty. <br>
120
121
 
121
122
  Custom IDs are not supported for bulk insertion. Use single insert()
122
123
  calls if you need custom IDs.
@@ -130,6 +131,16 @@ class _BaseQuadTree(Generic[G], ABC):
130
131
  Raises:
131
132
  TypeError: If geoms is a NumPy array (use insert_many_np instead).
132
133
  ValueError: If any geometry is outside bounds.
134
+
135
+ Example:
136
+ ```python
137
+ # Point Quadtree Example:
138
+
139
+ points = [(1.0, 2.0), (3.0, 4.0), (5.0, 6.0)]
140
+ qt = QuadTree(bounds=(0.0, 0.0, 10.0, 10.0), capacity=16)
141
+ result = qt.insert_many(points) # Each point's ID corresponds to its index in the points list
142
+ print(result) # InsertResult(count=3, start_id=0, end_id=2)
143
+ ```
133
144
  """
134
145
  if _is_np_array(geoms):
135
146
  raise TypeError(
@@ -24,10 +24,10 @@ class Item(Generic[G]):
24
24
 
25
25
  __slots__ = ("geom", "id_", "obj")
26
26
 
27
- def __init__(self, id_: int, geom: G, obj: Any | None = None):
27
+ def __init__(self, id_: int, geom: G, obj: Any = None):
28
28
  self.id_: int = id_
29
29
  self.geom: G = geom
30
- self.obj: Any | None = obj
30
+ self.obj: Any = obj
31
31
 
32
32
  def to_dict(self) -> dict[str, Any]:
33
33
  """
@@ -76,7 +76,7 @@ class PointItem(Item[Point]):
76
76
 
77
77
  __slots__ = ("x", "y")
78
78
 
79
- def __init__(self, id_: int, geom: Point, obj: Any | None = None):
79
+ def __init__(self, id_: int, geom: Point, obj: Any = None):
80
80
  super().__init__(id_, geom, obj)
81
81
  self.x, self.y = geom
82
82
 
@@ -100,6 +100,6 @@ class RectItem(Item[Bounds]):
100
100
 
101
101
  __slots__ = ("max_x", "max_y", "min_x", "min_y")
102
102
 
103
- def __init__(self, id_: int, geom: Bounds, obj: Any | None = None):
103
+ def __init__(self, id_: int, geom: Bounds, obj: Any = None):
104
104
  super().__init__(id_, geom, obj)
105
105
  self.min_x, self.min_y, self.max_x, self.max_y = geom
@@ -13,6 +13,10 @@ def test_public_api_all_exports():
13
13
  "RectItem",
14
14
  "RectQuadTree",
15
15
  "RectQuadTreeObjects",
16
+ "Quadtree", # Lowercase added in 2.0.2
17
+ "QuadtreeObjects",
18
+ "Rectquadtree",
19
+ "RectquadtreeObjects",
16
20
  ]
17
21
  assert sorted(fqt.__all__) == sorted(expected)
18
22
 
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes