fastquadtree 0.6.1__tar.gz → 0.8.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. fastquadtree-0.8.0/.github/workflows/docs.yml +20 -0
  2. {fastquadtree-0.6.1 → fastquadtree-0.8.0}/Cargo.lock +1 -1
  3. {fastquadtree-0.6.1 → fastquadtree-0.8.0}/Cargo.toml +1 -1
  4. {fastquadtree-0.6.1 → fastquadtree-0.8.0}/PKG-INFO +32 -141
  5. {fastquadtree-0.6.1 → fastquadtree-0.8.0}/README.md +21 -138
  6. fastquadtree-0.8.0/assets/ballpit.png +0 -0
  7. {fastquadtree-0.6.1 → fastquadtree-0.8.0}/benchmarks/benchmark_native_vs_shim.py +14 -8
  8. fastquadtree-0.8.0/benchmarks/cross_library_bench.py +6 -0
  9. {fastquadtree-0.6.1 → fastquadtree-0.8.0}/benchmarks/quadtree_bench/engines.py +1 -1
  10. {fastquadtree-0.6.1 → fastquadtree-0.8.0}/benchmarks/quadtree_bench/runner.py +2 -3
  11. {fastquadtree-0.6.1 → fastquadtree-0.8.0}/benchmarks/requirements.txt +6 -1
  12. fastquadtree-0.8.0/benchmarks/system_info_collector.py +259 -0
  13. fastquadtree-0.8.0/docs/api/point_item.md +4 -0
  14. fastquadtree-0.8.0/docs/api/quadtree.md +4 -0
  15. fastquadtree-0.8.0/docs/api/rect_item.md +4 -0
  16. fastquadtree-0.8.0/docs/api/rect_quadtree.md +4 -0
  17. fastquadtree-0.8.0/docs/benchmark.md +80 -0
  18. fastquadtree-0.8.0/docs/index.md +48 -0
  19. fastquadtree-0.8.0/docs/quickstart.md +135 -0
  20. fastquadtree-0.8.0/docs/runnables.md +33 -0
  21. fastquadtree-0.8.0/interactive/ballpit.py +283 -0
  22. {fastquadtree-0.6.1 → fastquadtree-0.8.0}/interactive/interactive.py +1 -1
  23. {fastquadtree-0.6.1 → fastquadtree-0.8.0}/interactive/interactive_v2.py +2 -2
  24. fastquadtree-0.8.0/interactive/interactive_v2_rect.py +432 -0
  25. {fastquadtree-0.6.1 → fastquadtree-0.8.0}/interactive/requirements.txt +1 -1
  26. fastquadtree-0.8.0/mkdocs.yml +86 -0
  27. {fastquadtree-0.6.1 → fastquadtree-0.8.0}/pyproject.toml +18 -4
  28. fastquadtree-0.8.0/pysrc/fastquadtree/__init__.py +5 -0
  29. fastquadtree-0.8.0/pysrc/fastquadtree/_base_quadtree.py +263 -0
  30. {fastquadtree-0.6.1 → fastquadtree-0.8.0}/pysrc/fastquadtree/_bimap.py +22 -22
  31. fastquadtree-0.8.0/pysrc/fastquadtree/_item.py +52 -0
  32. fastquadtree-0.8.0/pysrc/fastquadtree/point_quadtree.py +161 -0
  33. fastquadtree-0.8.0/pysrc/fastquadtree/rect_quadtree.py +98 -0
  34. fastquadtree-0.8.0/src/lib.rs +169 -0
  35. {fastquadtree-0.6.1 → fastquadtree-0.8.0}/src/quadtree.rs +1 -1
  36. fastquadtree-0.8.0/src/rect_quadtree.rs +295 -0
  37. fastquadtree-0.8.0/tests/rect_quadtree.rs +221 -0
  38. {fastquadtree-0.6.1 → fastquadtree-0.8.0}/tests/rectangle_traversal.rs +8 -8
  39. {fastquadtree-0.6.1 → fastquadtree-0.8.0}/tests/test_bimap.py +1 -1
  40. fastquadtree-0.8.0/tests/test_clear.py +130 -0
  41. {fastquadtree-0.6.1 → fastquadtree-0.8.0}/tests/test_delete.rs +12 -12
  42. {fastquadtree-0.6.1 → fastquadtree-0.8.0}/tests/test_delete_python.py +4 -4
  43. fastquadtree-0.8.0/tests/test_rect_quadtree.py +199 -0
  44. {fastquadtree-0.6.1 → fastquadtree-0.8.0}/tests/test_unconventional_bounds.py +2 -2
  45. {fastquadtree-0.6.1 → fastquadtree-0.8.0}/tests/test_wrapper_edges.py +6 -6
  46. fastquadtree-0.6.1/benchmarks/cross_library_bench.py +0 -3
  47. fastquadtree-0.6.1/pysrc/fastquadtree/__init__.py +0 -381
  48. fastquadtree-0.6.1/pysrc/fastquadtree/__init__.pyi +0 -70
  49. fastquadtree-0.6.1/pysrc/fastquadtree/_item.py +0 -24
  50. fastquadtree-0.6.1/src/lib.rs +0 -99
  51. {fastquadtree-0.6.1 → fastquadtree-0.8.0}/.github/workflows/release.yml +0 -0
  52. {fastquadtree-0.6.1 → fastquadtree-0.8.0}/.gitignore +0 -0
  53. {fastquadtree-0.6.1 → fastquadtree-0.8.0}/.pre-commit-config.yaml +0 -0
  54. {fastquadtree-0.6.1 → fastquadtree-0.8.0}/LICENSE +0 -0
  55. {fastquadtree-0.6.1 → fastquadtree-0.8.0}/assets/interactive_v2_screenshot.png +0 -0
  56. {fastquadtree-0.6.1 → fastquadtree-0.8.0}/assets/quadtree_bench_throughput.png +0 -0
  57. {fastquadtree-0.6.1 → fastquadtree-0.8.0}/assets/quadtree_bench_time.png +0 -0
  58. {fastquadtree-0.6.1 → fastquadtree-0.8.0}/benchmarks/quadtree_bench/__init__.py +0 -0
  59. {fastquadtree-0.6.1 → fastquadtree-0.8.0}/benchmarks/quadtree_bench/main.py +0 -0
  60. {fastquadtree-0.6.1 → fastquadtree-0.8.0}/benchmarks/quadtree_bench/plotting.py +0 -0
  61. {fastquadtree-0.6.1 → fastquadtree-0.8.0}/benchmarks/runner.py +0 -0
  62. {fastquadtree-0.6.1 → fastquadtree-0.8.0}/pysrc/fastquadtree/py.typed +0 -0
  63. {fastquadtree-0.6.1 → fastquadtree-0.8.0}/src/geom.rs +0 -0
  64. {fastquadtree-0.6.1 → fastquadtree-0.8.0}/tests/insertions.rs +0 -0
  65. {fastquadtree-0.6.1 → fastquadtree-0.8.0}/tests/nearest_neighbor.rs +0 -0
  66. {fastquadtree-0.6.1 → fastquadtree-0.8.0}/tests/query.rs +0 -0
  67. {fastquadtree-0.6.1 → fastquadtree-0.8.0}/tests/test_delete_by_object.py +0 -0
  68. {fastquadtree-0.6.1 → fastquadtree-0.8.0}/tests/test_python.py +0 -0
  69. {fastquadtree-0.6.1 → fastquadtree-0.8.0}/tests/unconventional_bounds.rs +0 -0
@@ -0,0 +1,20 @@
1
+ # .github/workflows/docs.yml
2
+ name: Docs
3
+ on:
4
+ push:
5
+ branches: [ main ]
6
+
7
+ permissions:
8
+ contents: write
9
+ pages: write
10
+ id-token: write
11
+
12
+ jobs:
13
+ build:
14
+ runs-on: ubuntu-latest
15
+ steps:
16
+ - uses: actions/checkout@v4
17
+ - uses: actions/setup-python@v5
18
+ with: { python-version: '3.11' }
19
+ - run: pip install -e '.[dev]'
20
+ - run: mkdocs gh-deploy --force
@@ -22,7 +22,7 @@ checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9"
22
22
 
23
23
  [[package]]
24
24
  name = "fastquadtree"
25
- version = "0.6.1"
25
+ version = "0.8.0"
26
26
  dependencies = [
27
27
  "pyo3",
28
28
  "smallvec",
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "fastquadtree"
3
- version = "0.6.1"
3
+ version = "0.8.0"
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: 0.6.1
3
+ Version: 0.8.0
4
4
  Classifier: Programming Language :: Python :: 3
5
5
  Classifier: Programming Language :: Python :: 3 :: Only
6
6
  Classifier: Programming Language :: Rust
@@ -12,11 +12,18 @@ Classifier: Topic :: Software Development :: Libraries
12
12
  Classifier: Typing :: Typed
13
13
  Classifier: License :: OSI Approved :: MIT License
14
14
  Requires-Dist: ruff>=0.6.0 ; extra == 'dev'
15
- Requires-Dist: pytest>=7.0 ; extra == 'dev'
16
- Requires-Dist: pytest-cov>=4.1 ; extra == 'dev'
15
+ Requires-Dist: pytest>=8.4.2 ; extra == 'dev'
16
+ Requires-Dist: pytest-cov>=7.0.0 ; extra == 'dev'
17
17
  Requires-Dist: coverage>=7.5 ; extra == 'dev'
18
18
  Requires-Dist: mypy>=1.10 ; extra == 'dev'
19
19
  Requires-Dist: build>=1.2.1 ; extra == 'dev'
20
+ Requires-Dist: mkdocs>=1.6 ; extra == 'dev'
21
+ Requires-Dist: mkdocs-material ; extra == 'dev'
22
+ Requires-Dist: mkdocstrings[python] ; extra == 'dev'
23
+ Requires-Dist: mkdocs-autorefs ; extra == 'dev'
24
+ Requires-Dist: mkdocs-git-revision-date-localized-plugin ; extra == 'dev'
25
+ Requires-Dist: mkdocs-minify-plugin ; extra == 'dev'
26
+ Requires-Dist: maturin>=1.5 ; extra == 'dev'
20
27
  Provides-Extra: dev
21
28
  License-File: LICENSE
22
29
  Summary: Rust-accelerated quadtree for Python with fast inserts, range queries, and k-NN search.
@@ -26,10 +33,12 @@ Requires-Python: >=3.8
26
33
  Description-Content-Type: text/markdown
27
34
  Project-URL: Homepage, https://github.com/Elan456/fastquadtree
28
35
  Project-URL: Repository, https://github.com/Elan456/fastquadtree
36
+ Project-URL: Documentation, https://elan456.github.io/fastquadtree/
29
37
  Project-URL: Issues, https://github.com/Elan456/fastquadtree/issues
30
38
 
31
39
  # fastquadtree
32
40
 
41
+ [![Docs](https://img.shields.io/badge/docs-online-brightgreen)](https://elan456.github.io/fastquadtree/)
33
42
  [![PyPI version](https://img.shields.io/pypi/v/fastquadtree.svg)](https://pypi.org/project/fastquadtree/)
34
43
  [![Python versions](https://img.shields.io/pypi/pyversions/fastquadtree.svg)](https://pypi.org/project/fastquadtree/)
35
44
  [![Wheels](https://img.shields.io/pypi/wheel/fastquadtree.svg)](https://pypi.org/project/fastquadtree/#files)
@@ -37,7 +46,7 @@ Project-URL: Issues, https://github.com/Elan456/fastquadtree/issues
37
46
 
38
47
  [![PyPI Downloads](https://static.pepy.tech/personalized-badge/fastquadtree?period=total&units=INTERNATIONAL_SYSTEM&left_color=GRAY&right_color=BLUE&left_text=Total+Downloads)](https://pepy.tech/projects/fastquadtree)
39
48
 
40
- [![Build](https://github.com/Elan456/fastquadtree/actions/workflows/release.yml/badge.svg)](https://github.com/Elan456/fastquadtree/actions/workflows/ci.yml)
49
+ [![Build](https://github.com/Elan456/fastquadtree/actions/workflows/release.yml/badge.svg)](https://github.com/Elan456/fastquadtree/actions/workflows/release.yml)
41
50
  [![Codecov](https://codecov.io/gh/Elan456/fastquadtree/branch/main/graph/badge.svg)](https://codecov.io/gh/Elan456/fastquadtree)
42
51
 
43
52
  [![Rust core via PyO3](https://img.shields.io/badge/Rust-core%20via%20PyO3-orange)](https://pyo3.rs/)
@@ -51,6 +60,8 @@ Project-URL: Issues, https://github.com/Elan456/fastquadtree/issues
51
60
 
52
61
  Rust-optimized quadtree with a simple Python API.
53
62
 
63
+ 👉 **Docs:** https://elan456.github.io/fastquadtree/
64
+
54
65
  - Python package: **`fastquadtree`**
55
66
  - Python ≥ 3.8
56
67
  - Import path: `from fastquadtree import QuadTree`
@@ -66,7 +77,6 @@ fastquadtree **outperforms** all other quadtree Python packages, including the R
66
77
 
67
78
  ### Summary (largest dataset, PyQtree baseline)
68
79
  - Points: **250,000**, Queries: **500**
69
- --------------------
70
80
  - Fastest total: **fastquadtree** at **0.120 s**
71
81
 
72
82
  | Library | Build (s) | Query (s) | Total (s) | Speed vs PyQtree |
@@ -79,7 +89,7 @@ fastquadtree **outperforms** all other quadtree Python packages, including the R
79
89
  | PyQtree | 1.492 | 0.263 | 1.755 | 1.00× |
80
90
  | quads | 1.407 | 0.484 | 1.890 | 0.93× |
81
91
 
82
- #### Benchmark Configuration
92
+ ### Benchmark Configuration
83
93
  | Parameter | Value |
84
94
  |---|---:|
85
95
  | Bounds | (0, 0, 1000, 1000) |
@@ -87,11 +97,13 @@ fastquadtree **outperforms** all other quadtree Python packages, including the R
87
97
  | Max depth | 16 |
88
98
  | Queries per experiment | 500 |
89
99
 
100
+ See the [benchmark section](https://elan456.github.io/fastquadtree/benchmark/) for details.
101
+
90
102
  ## Install
91
103
 
92
104
  ```bash
93
105
  pip install fastquadtree
94
- ````
106
+ ```
95
107
 
96
108
  If you are developing locally:
97
109
 
@@ -101,76 +113,12 @@ maturin develop --release
101
113
  ```
102
114
 
103
115
  ## Quickstart
104
-
105
- ```python
106
- from fastquadtree import QuadTree
107
-
108
- # Bounds are (min_x, min_y, max_x, max_y)
109
- qt = QuadTree(bounds=(0, 0, 1000, 1000), capacity=20) # max_depth is optional
110
-
111
- # Insert points with auto ids
112
- id1 = qt.insert((10, 10))
113
- id2 = qt.insert((200, 300))
114
- id3 = qt.insert((999, 500), id=42) # you can supply your own id
115
-
116
- # Axis-aligned rectangle query
117
- hits = qt.query((0, 0, 250, 350)) # returns [(id, x, y), ...] by default
118
- print(hits) # e.g. [(1, 10.0, 10.0), (2, 200.0, 300.0)]
119
-
120
- # Nearest neighbor
121
- best = qt.nearest_neighbor((210, 310)) # -> (id, x, y) or None
122
- print(best)
123
-
124
- # k-nearest neighbors
125
- top3 = qt.nearest_neighbors((210, 310), 3)
126
- print(top3) # list of up to 3 (id, x, y) tuples
127
-
128
- # Delete items by ID and location
129
- deleted = qt.delete(id2, (200, 300)) # True if found and deleted
130
- print(f"Deleted: {deleted}")
131
- print(f"Remaining items: {qt.count_items()}")
132
-
133
- # For object tracking with track_objects=True
134
- qt_tracked = QuadTree((0, 0, 1000, 1000), capacity=4, track_objects=True)
135
- player1 = {"name": "Alice", "score": 100}
136
- player2 = {"name": "Bob", "score": 200}
137
-
138
- id1 = qt_tracked.insert((50, 50), obj=player1)
139
- id2 = qt_tracked.insert((150, 150), obj=player2)
140
-
141
- # Delete by object reference (O(1) lookup!)
142
- deleted = qt_tracked.delete_by_object(player1)
143
- print(f"Deleted player: {deleted}") # True
144
- ```
145
-
146
- ### Working with Python objects
147
-
148
- You can keep the tree pure and manage your own id → object map, or let the wrapper manage it.
149
-
150
- **Wrapper Managed Objects**
151
-
152
- ```python
153
- from fastquadtree import QuadTree
154
-
155
- qt = QuadTree((0, 0, 1000, 1000), capacity=16, track_objects=True)
156
-
157
- # Store the object alongside the point
158
- qt.insert((25, 40), obj={"name": "apple"})
159
-
160
- # Ask for Item objects within a bounding box
161
- items = qt.query((0, 0, 100, 100), as_items=True)
162
- for it in items:
163
- print(it.id, it.x, it.y, it.obj)
164
- ```
165
-
166
- You can also attach or replace an object later:
167
-
168
- ```python
169
- qt.attach(123, my_object) # binds object to id 123
170
- ```
116
+ [See the quickstart guide](https://elan456.github.io/fastquadtree/quickstart/)
171
117
 
172
118
  ## API
173
119
 
120
+ [See the full API](https://elan456.github.io/fastquadtree/api/quadtree/)
121
+
174
122
  ### `QuadTree(bounds, capacity, max_depth=None, track_objects=False, start_id=1)`
175
123
 
176
124
  * `bounds` — tuple `(min_x, min_y, max_x, max_y)` defines the 2D area covered by the quadtree
@@ -179,36 +127,17 @@ qt.attach(123, my_object) # binds object to id 123
179
127
  * `track_objects` — if `True`, the wrapper maintains an id → object map for convenience.
180
128
  * `start_id` — starting value for auto-assigned ids
181
129
 
182
- ### Core Methods
183
-
184
- Full docs are in the docstrings of the [Python Shim](pysrc/fastquadtree/__init__.py)
130
+ ### Key Methods
185
131
 
186
132
  - `insert(xy, *, id=None, obj=None) -> int`
187
133
 
188
- - `insert_many_points(points) -> int`
189
-
190
134
  - `query(rect, *, as_items=False) -> list`
191
135
 
192
136
  - `nearest_neighbor(xy, *, as_item=False) -> (id, x, y) | Item | None`
193
137
 
194
- - `nearest_neighbors(xy, k, *, as_items=False) -> list`
195
-
196
138
  - `delete(id, xy) -> bool`
197
139
 
198
- - `delete_by_object(obj) -> bool (requires track_objects=True)`
199
-
200
- - `attach(id, obj) -> None (requires track_objects=True)`
201
-
202
- - `count_items() -> int`
203
-
204
- - `get(id) -> object | None`
205
-
206
- - `get_all_rectangles() -> list[tuple] (for visualization)`
207
-
208
- ### `Item` (returned when `as_items=True`)
209
-
210
- * Attributes: `id`, `x`, `y`, and a lazy `obj` property
211
- * Accessing `obj` performs a dictionary lookup only if tracking is enabled
140
+ There are more methods and object tracking versions in the [docs](https://elan456.github.io/fastquadtree/api/quadtree/).
212
141
 
213
142
  ### Geometric conventions
214
143
 
@@ -222,52 +151,17 @@ Full docs are in the docstrings of the [Python Shim](pysrc/fastquadtree/__init__
222
151
  * Choose `capacity` so that leaves keep a small batch of points. Typical values are 8 to 64.
223
152
  * If your data is very skewed, set a `max_depth` to prevent long chains.
224
153
  * For fastest local runs, use `maturin develop --release`.
225
- * The wrapper keeps Python overhead low: raw tuple results by default, `Item` wrappers only when requested.
226
-
227
-
228
- ### Native vs Shim Benchmark
229
-
230
- **Setup**
231
- - Points: 500,000
232
- - Queries: 500
233
- - Repeats: 5
234
-
235
- **Timing (seconds)**
154
+ * The wrapper maintains an object map only if the quadtree was constructed with `track_objects=True`. If you don't need it, leave it off for best performance.
155
+ * Refer to the [Native vs Shim Benchmark](https://elan456.github.io/fastquadtree/benchmark/#native-vs-shim-benchmark) for overhead details.
236
156
 
237
- | Variant | Build | Query | Total |
238
- |---|---:|---:|---:|
239
- | Native | 0.483 | 4.380 | 4.863 |
240
- | Shim (no map) | 0.668 | 4.167 | 4.835 |
241
- | Shim (track+objs) | 1.153 | 4.458 | 5.610 |
157
+ ### Pygame Ball Pit Demo
242
158
 
243
- **Overhead vs Native**
159
+ ![Ballpit_Demo_Screenshot](https://raw.githubusercontent.com/Elan456/fastquadtree/main/assets/ballpit.png)
244
160
 
245
- - No map: build 1.38x, query 0.95x, total 0.99x
246
- - Track + objs: build 2.39x, query 1.02x, total 1.15x
161
+ A simple demo of moving objects with collision detection using **fastquadtree**.
162
+ You can toggle between quadtree mode and brute-force mode to see the performance difference.
247
163
 
248
- ### Run benchmarks
249
- To run the benchmarks yourself, first install the dependencies:
250
-
251
- ```bash
252
- pip install -r benchmarks/requirements.txt
253
- ```
254
-
255
- Then run:
256
-
257
- ```bash
258
- python benchmarks/cross_library_bench.py
259
- python benchmarks/benchmark_native_vs_shim.py
260
- ```
261
-
262
- ## Run Visualizer
263
- A visualizer is included to help you understand how the quadtree subdivides space.
264
-
265
- ```bash
266
- pip install -r interactive/requirements.txt
267
- python interactive/interactive_v2.py
268
- ```
269
-
270
- Check the CLI arguments for the cross-library benchmark in `benchmarks/quadtree_bench/main.py`.
164
+ See the [runnables guide](https://elan456.github.io/fastquadtree/runnables/) for setup instructions.
271
165
 
272
166
  ## FAQ
273
167
 
@@ -278,10 +172,7 @@ Allowed. For k-nearest, duplicates are de-duplicated by id. For range queries yo
278
172
  Yes! Use `delete(id, xy)` to remove specific items. You must provide both the ID and exact location for precise deletion. This handles cases where multiple items exist at the same location. If you're using `track_objects=True`, you can also use `delete_by_object(obj)` for convenient object-based deletion with O(1) lookup. The tree automatically merges nodes when item counts drop below capacity.
279
173
 
280
174
  **Can I store rectangles or circles?**
281
- The core stores points. To index objects with extent, insert whatever representative point you choose. For rectangles you can insert centers or build an AABB tree separately.
282
-
283
- **Threading**
284
- Use one tree per thread if you need heavy parallel inserts from Python.
175
+ Yes, you can store rectangles using the `RectQuadTree` class. Circles can be approximated with bounding boxes. See the [RectQuadTree docs](https://elan456.github.io/fastquadtree/api/rect_quadtree/) for details.
285
176
 
286
177
  ## License
287
178
 
@@ -1,5 +1,6 @@
1
1
  # fastquadtree
2
2
 
3
+ [![Docs](https://img.shields.io/badge/docs-online-brightgreen)](https://elan456.github.io/fastquadtree/)
3
4
  [![PyPI version](https://img.shields.io/pypi/v/fastquadtree.svg)](https://pypi.org/project/fastquadtree/)
4
5
  [![Python versions](https://img.shields.io/pypi/pyversions/fastquadtree.svg)](https://pypi.org/project/fastquadtree/)
5
6
  [![Wheels](https://img.shields.io/pypi/wheel/fastquadtree.svg)](https://pypi.org/project/fastquadtree/#files)
@@ -7,7 +8,7 @@
7
8
 
8
9
  [![PyPI Downloads](https://static.pepy.tech/personalized-badge/fastquadtree?period=total&units=INTERNATIONAL_SYSTEM&left_color=GRAY&right_color=BLUE&left_text=Total+Downloads)](https://pepy.tech/projects/fastquadtree)
9
10
 
10
- [![Build](https://github.com/Elan456/fastquadtree/actions/workflows/release.yml/badge.svg)](https://github.com/Elan456/fastquadtree/actions/workflows/ci.yml)
11
+ [![Build](https://github.com/Elan456/fastquadtree/actions/workflows/release.yml/badge.svg)](https://github.com/Elan456/fastquadtree/actions/workflows/release.yml)
11
12
  [![Codecov](https://codecov.io/gh/Elan456/fastquadtree/branch/main/graph/badge.svg)](https://codecov.io/gh/Elan456/fastquadtree)
12
13
 
13
14
  [![Rust core via PyO3](https://img.shields.io/badge/Rust-core%20via%20PyO3-orange)](https://pyo3.rs/)
@@ -21,6 +22,8 @@
21
22
 
22
23
  Rust-optimized quadtree with a simple Python API.
23
24
 
25
+ 👉 **Docs:** https://elan456.github.io/fastquadtree/
26
+
24
27
  - Python package: **`fastquadtree`**
25
28
  - Python ≥ 3.8
26
29
  - Import path: `from fastquadtree import QuadTree`
@@ -36,7 +39,6 @@ fastquadtree **outperforms** all other quadtree Python packages, including the R
36
39
 
37
40
  ### Summary (largest dataset, PyQtree baseline)
38
41
  - Points: **250,000**, Queries: **500**
39
- --------------------
40
42
  - Fastest total: **fastquadtree** at **0.120 s**
41
43
 
42
44
  | Library | Build (s) | Query (s) | Total (s) | Speed vs PyQtree |
@@ -49,7 +51,7 @@ fastquadtree **outperforms** all other quadtree Python packages, including the R
49
51
  | PyQtree | 1.492 | 0.263 | 1.755 | 1.00× |
50
52
  | quads | 1.407 | 0.484 | 1.890 | 0.93× |
51
53
 
52
- #### Benchmark Configuration
54
+ ### Benchmark Configuration
53
55
  | Parameter | Value |
54
56
  |---|---:|
55
57
  | Bounds | (0, 0, 1000, 1000) |
@@ -57,11 +59,13 @@ fastquadtree **outperforms** all other quadtree Python packages, including the R
57
59
  | Max depth | 16 |
58
60
  | Queries per experiment | 500 |
59
61
 
62
+ See the [benchmark section](https://elan456.github.io/fastquadtree/benchmark/) for details.
63
+
60
64
  ## Install
61
65
 
62
66
  ```bash
63
67
  pip install fastquadtree
64
- ````
68
+ ```
65
69
 
66
70
  If you are developing locally:
67
71
 
@@ -71,76 +75,12 @@ maturin develop --release
71
75
  ```
72
76
 
73
77
  ## Quickstart
74
-
75
- ```python
76
- from fastquadtree import QuadTree
77
-
78
- # Bounds are (min_x, min_y, max_x, max_y)
79
- qt = QuadTree(bounds=(0, 0, 1000, 1000), capacity=20) # max_depth is optional
80
-
81
- # Insert points with auto ids
82
- id1 = qt.insert((10, 10))
83
- id2 = qt.insert((200, 300))
84
- id3 = qt.insert((999, 500), id=42) # you can supply your own id
85
-
86
- # Axis-aligned rectangle query
87
- hits = qt.query((0, 0, 250, 350)) # returns [(id, x, y), ...] by default
88
- print(hits) # e.g. [(1, 10.0, 10.0), (2, 200.0, 300.0)]
89
-
90
- # Nearest neighbor
91
- best = qt.nearest_neighbor((210, 310)) # -> (id, x, y) or None
92
- print(best)
93
-
94
- # k-nearest neighbors
95
- top3 = qt.nearest_neighbors((210, 310), 3)
96
- print(top3) # list of up to 3 (id, x, y) tuples
97
-
98
- # Delete items by ID and location
99
- deleted = qt.delete(id2, (200, 300)) # True if found and deleted
100
- print(f"Deleted: {deleted}")
101
- print(f"Remaining items: {qt.count_items()}")
102
-
103
- # For object tracking with track_objects=True
104
- qt_tracked = QuadTree((0, 0, 1000, 1000), capacity=4, track_objects=True)
105
- player1 = {"name": "Alice", "score": 100}
106
- player2 = {"name": "Bob", "score": 200}
107
-
108
- id1 = qt_tracked.insert((50, 50), obj=player1)
109
- id2 = qt_tracked.insert((150, 150), obj=player2)
110
-
111
- # Delete by object reference (O(1) lookup!)
112
- deleted = qt_tracked.delete_by_object(player1)
113
- print(f"Deleted player: {deleted}") # True
114
- ```
115
-
116
- ### Working with Python objects
117
-
118
- You can keep the tree pure and manage your own id → object map, or let the wrapper manage it.
119
-
120
- **Wrapper Managed Objects**
121
-
122
- ```python
123
- from fastquadtree import QuadTree
124
-
125
- qt = QuadTree((0, 0, 1000, 1000), capacity=16, track_objects=True)
126
-
127
- # Store the object alongside the point
128
- qt.insert((25, 40), obj={"name": "apple"})
129
-
130
- # Ask for Item objects within a bounding box
131
- items = qt.query((0, 0, 100, 100), as_items=True)
132
- for it in items:
133
- print(it.id, it.x, it.y, it.obj)
134
- ```
135
-
136
- You can also attach or replace an object later:
137
-
138
- ```python
139
- qt.attach(123, my_object) # binds object to id 123
140
- ```
78
+ [See the quickstart guide](https://elan456.github.io/fastquadtree/quickstart/)
141
79
 
142
80
  ## API
143
81
 
82
+ [See the full API](https://elan456.github.io/fastquadtree/api/quadtree/)
83
+
144
84
  ### `QuadTree(bounds, capacity, max_depth=None, track_objects=False, start_id=1)`
145
85
 
146
86
  * `bounds` — tuple `(min_x, min_y, max_x, max_y)` defines the 2D area covered by the quadtree
@@ -149,36 +89,17 @@ qt.attach(123, my_object) # binds object to id 123
149
89
  * `track_objects` — if `True`, the wrapper maintains an id → object map for convenience.
150
90
  * `start_id` — starting value for auto-assigned ids
151
91
 
152
- ### Core Methods
153
-
154
- Full docs are in the docstrings of the [Python Shim](pysrc/fastquadtree/__init__.py)
92
+ ### Key Methods
155
93
 
156
94
  - `insert(xy, *, id=None, obj=None) -> int`
157
95
 
158
- - `insert_many_points(points) -> int`
159
-
160
96
  - `query(rect, *, as_items=False) -> list`
161
97
 
162
98
  - `nearest_neighbor(xy, *, as_item=False) -> (id, x, y) | Item | None`
163
99
 
164
- - `nearest_neighbors(xy, k, *, as_items=False) -> list`
165
-
166
100
  - `delete(id, xy) -> bool`
167
101
 
168
- - `delete_by_object(obj) -> bool (requires track_objects=True)`
169
-
170
- - `attach(id, obj) -> None (requires track_objects=True)`
171
-
172
- - `count_items() -> int`
173
-
174
- - `get(id) -> object | None`
175
-
176
- - `get_all_rectangles() -> list[tuple] (for visualization)`
177
-
178
- ### `Item` (returned when `as_items=True`)
179
-
180
- * Attributes: `id`, `x`, `y`, and a lazy `obj` property
181
- * Accessing `obj` performs a dictionary lookup only if tracking is enabled
102
+ There are more methods and object tracking versions in the [docs](https://elan456.github.io/fastquadtree/api/quadtree/).
182
103
 
183
104
  ### Geometric conventions
184
105
 
@@ -192,52 +113,17 @@ Full docs are in the docstrings of the [Python Shim](pysrc/fastquadtree/__init__
192
113
  * Choose `capacity` so that leaves keep a small batch of points. Typical values are 8 to 64.
193
114
  * If your data is very skewed, set a `max_depth` to prevent long chains.
194
115
  * For fastest local runs, use `maturin develop --release`.
195
- * The wrapper keeps Python overhead low: raw tuple results by default, `Item` wrappers only when requested.
196
-
197
-
198
- ### Native vs Shim Benchmark
199
-
200
- **Setup**
201
- - Points: 500,000
202
- - Queries: 500
203
- - Repeats: 5
204
-
205
- **Timing (seconds)**
116
+ * The wrapper maintains an object map only if the quadtree was constructed with `track_objects=True`. If you don't need it, leave it off for best performance.
117
+ * Refer to the [Native vs Shim Benchmark](https://elan456.github.io/fastquadtree/benchmark/#native-vs-shim-benchmark) for overhead details.
206
118
 
207
- | Variant | Build | Query | Total |
208
- |---|---:|---:|---:|
209
- | Native | 0.483 | 4.380 | 4.863 |
210
- | Shim (no map) | 0.668 | 4.167 | 4.835 |
211
- | Shim (track+objs) | 1.153 | 4.458 | 5.610 |
119
+ ### Pygame Ball Pit Demo
212
120
 
213
- **Overhead vs Native**
121
+ ![Ballpit_Demo_Screenshot](https://raw.githubusercontent.com/Elan456/fastquadtree/main/assets/ballpit.png)
214
122
 
215
- - No map: build 1.38x, query 0.95x, total 0.99x
216
- - Track + objs: build 2.39x, query 1.02x, total 1.15x
123
+ A simple demo of moving objects with collision detection using **fastquadtree**.
124
+ You can toggle between quadtree mode and brute-force mode to see the performance difference.
217
125
 
218
- ### Run benchmarks
219
- To run the benchmarks yourself, first install the dependencies:
220
-
221
- ```bash
222
- pip install -r benchmarks/requirements.txt
223
- ```
224
-
225
- Then run:
226
-
227
- ```bash
228
- python benchmarks/cross_library_bench.py
229
- python benchmarks/benchmark_native_vs_shim.py
230
- ```
231
-
232
- ## Run Visualizer
233
- A visualizer is included to help you understand how the quadtree subdivides space.
234
-
235
- ```bash
236
- pip install -r interactive/requirements.txt
237
- python interactive/interactive_v2.py
238
- ```
239
-
240
- Check the CLI arguments for the cross-library benchmark in `benchmarks/quadtree_bench/main.py`.
126
+ See the [runnables guide](https://elan456.github.io/fastquadtree/runnables/) for setup instructions.
241
127
 
242
128
  ## FAQ
243
129
 
@@ -248,10 +134,7 @@ Allowed. For k-nearest, duplicates are de-duplicated by id. For range queries yo
248
134
  Yes! Use `delete(id, xy)` to remove specific items. You must provide both the ID and exact location for precise deletion. This handles cases where multiple items exist at the same location. If you're using `track_objects=True`, you can also use `delete_by_object(obj)` for convenient object-based deletion with O(1) lookup. The tree automatically merges nodes when item counts drop below capacity.
249
135
 
250
136
  **Can I store rectangles or circles?**
251
- The core stores points. To index objects with extent, insert whatever representative point you choose. For rectangles you can insert centers or build an AABB tree separately.
252
-
253
- **Threading**
254
- Use one tree per thread if you need heavy parallel inserts from Python.
137
+ Yes, you can store rectangles using the `RectQuadTree` class. Circles can be approximated with bounding boxes. See the [RectQuadTree docs](https://elan456.github.io/fastquadtree/api/rect_quadtree/) for details.
255
138
 
256
139
  ## License
257
140
 
Binary file
@@ -7,6 +7,7 @@ import random
7
7
  import statistics as stats
8
8
  from time import perf_counter as now
9
9
 
10
+ from system_info_collector import collect_system_info, format_system_info_markdown_lite
10
11
  from tqdm import tqdm
11
12
 
12
13
  from fastquadtree import QuadTree as ShimQuadTree
@@ -55,10 +56,10 @@ def bench_shim(points, queries, *, track_objects: bool, with_objs: bool):
55
56
  )
56
57
  if with_objs:
57
58
  for i, p in enumerate(points):
58
- qt.insert(p, id=i, obj=i) # store a tiny object
59
+ qt.insert(p, id_=i, obj=i) # store a tiny object
59
60
  else:
60
61
  for i, p in enumerate(points):
61
- qt.insert(p, id=i)
62
+ qt.insert(p, id_=i)
62
63
  t_build = now() - t0
63
64
 
64
65
  t0 = now()
@@ -133,14 +134,14 @@ def main():
133
134
  return f"{x:.3f}"
134
135
 
135
136
  md = f"""
136
- ### Native vs Shim
137
+ ## Native vs Shim
137
138
 
138
- **Setup**
139
+ ### Configuration
139
140
  - Points: {args.points:,}
140
141
  - Queries: {args.queries}
141
142
  - Repeats: {args.repeats}
142
143
 
143
- **Timing (seconds)**
144
+ ### Results
144
145
 
145
146
  | Variant | Build | Query | Total |
146
147
  |---|---:|---:|---:|
@@ -148,13 +149,18 @@ def main():
148
149
  | Shim (no map) | {fmt(s_build_no_map)} | {fmt(s_query_no_map)} | {fmt(s_build_no_map + s_query_no_map)} |
149
150
  | Shim (track+objs) | {fmt(s_build_map)} | {fmt(s_query_map)} | {fmt(s_build_map + s_query_map)} |
150
151
 
151
- **Overhead vs Native**
152
+ ### Summary
152
153
 
153
- - No map: build {s_build_no_map / n_build:.2f}x, query {s_query_no_map / n_query:.2f}x, total {(s_build_no_map + s_query_no_map) / (n_build + n_query):.2f}x
154
- - Track + objs: build {s_build_map / n_build:.2f}x, query {s_query_map / n_query:.2f}x, total {(s_build_map + s_query_map) / (n_build + n_query):.2f}x
154
+ Using the shim with object tracking increases build time by {fmt(s_build_map / n_build)}x and query time by {fmt(s_query_map / n_query)}x.
155
+ **Total slowdown = {fmt((s_build_map + s_query_map) / (n_build + n_query))}x.**
156
+
157
+ Adding the object map only impacts the build time, not the query time.
155
158
  """
156
159
  print(md.strip())
157
160
 
161
+ info = collect_system_info()
162
+ print(format_system_info_markdown_lite(info))
163
+
158
164
 
159
165
  if __name__ == "__main__":
160
166
  main()
@@ -0,0 +1,6 @@
1
+ import system_info_collector
2
+ from quadtree_bench.main import main
3
+
4
+ main()
5
+ system_info = system_info_collector.collect_system_info()
6
+ print(system_info_collector.format_system_info_markdown_lite(system_info))
@@ -104,7 +104,7 @@ def _create_fastquadtree_engine(
104
104
 
105
105
  def build(points):
106
106
  qt = RustQuadTree(bounds, max_points, max_depth=max_depth)
107
- qt.insert_many_points(points)
107
+ qt.insert_many(points)
108
108
  return qt
109
109
 
110
110
  def query(qt, queries):
@@ -255,8 +255,8 @@ class BenchmarkRunner:
255
255
  experiment_bar.close()
256
256
 
257
257
  # Add metadata to results
258
- results["engines"] = engines
259
- results["config"] = self.config
258
+ results["engines"] = engines # pyright: ignore[reportArgumentType]
259
+ results["config"] = self.config # pyright: ignore[reportArgumentType]
260
260
 
261
261
  return results
262
262
 
@@ -277,7 +277,6 @@ class BenchmarkRunner:
277
277
  print(
278
278
  f"- Points: **{config.experiments[i]:,}**, Queries: **{config.n_queries}**"
279
279
  )
280
- print("--------------------")
281
280
 
282
281
  # Find fastest and show key results
283
282
  ranked = sorted(
@@ -12,4 +12,9 @@ fastquadtree>=0.1.0
12
12
  quads>=1.1.0 # pure-Python quadtree
13
13
  nontree>=1.0.5 # pure-Python PR quadtree (TreeMap mode=4)
14
14
  rtree>=1.3.0 # R-tree comparator with wheels for Win/macOS/Linux
15
- shapely>=2.0.0 # STRtree comparator
15
+ shapely>=2.0.0 # STRtree comparator
16
+
17
+ # For getting system info
18
+ psutil
19
+ py-cpuinfo
20
+ distro