fastquadtree 0.6.1__tar.gz → 0.7.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.
- fastquadtree-0.7.0/.github/workflows/docs.yml +20 -0
- {fastquadtree-0.6.1 → fastquadtree-0.7.0}/Cargo.lock +1 -1
- {fastquadtree-0.6.1 → fastquadtree-0.7.0}/Cargo.toml +1 -1
- {fastquadtree-0.6.1 → fastquadtree-0.7.0}/PKG-INFO +31 -8
- {fastquadtree-0.6.1 → fastquadtree-0.7.0}/README.md +23 -7
- fastquadtree-0.7.0/assets/ballpit.png +0 -0
- {fastquadtree-0.6.1 → fastquadtree-0.7.0}/benchmarks/quadtree_bench/runner.py +8 -0
- fastquadtree-0.7.0/benchmarks/quadtree_bench/system_info_collector.py +140 -0
- {fastquadtree-0.6.1 → fastquadtree-0.7.0}/benchmarks/requirements.txt +7 -1
- fastquadtree-0.7.0/docs/api/item.md +2 -0
- fastquadtree-0.7.0/docs/api/quadtree.md +2 -0
- fastquadtree-0.7.0/docs/index.md +4 -0
- fastquadtree-0.7.0/docs/quickstart.md +145 -0
- fastquadtree-0.7.0/interactive/ballpit.py +283 -0
- fastquadtree-0.7.0/mkdocs.yml +82 -0
- {fastquadtree-0.6.1 → fastquadtree-0.7.0}/pyproject.toml +7 -0
- {fastquadtree-0.6.1 → fastquadtree-0.7.0}/pysrc/fastquadtree/__init__.py +40 -4
- {fastquadtree-0.6.1 → fastquadtree-0.7.0}/pysrc/fastquadtree/__init__.pyi +1 -0
- fastquadtree-0.7.0/tests/test_clear.py +130 -0
- {fastquadtree-0.6.1 → fastquadtree-0.7.0}/.github/workflows/release.yml +0 -0
- {fastquadtree-0.6.1 → fastquadtree-0.7.0}/.gitignore +0 -0
- {fastquadtree-0.6.1 → fastquadtree-0.7.0}/.pre-commit-config.yaml +0 -0
- {fastquadtree-0.6.1 → fastquadtree-0.7.0}/LICENSE +0 -0
- {fastquadtree-0.6.1 → fastquadtree-0.7.0}/assets/interactive_v2_screenshot.png +0 -0
- {fastquadtree-0.6.1 → fastquadtree-0.7.0}/assets/quadtree_bench_throughput.png +0 -0
- {fastquadtree-0.6.1 → fastquadtree-0.7.0}/assets/quadtree_bench_time.png +0 -0
- {fastquadtree-0.6.1 → fastquadtree-0.7.0}/benchmarks/benchmark_native_vs_shim.py +0 -0
- {fastquadtree-0.6.1 → fastquadtree-0.7.0}/benchmarks/cross_library_bench.py +0 -0
- {fastquadtree-0.6.1 → fastquadtree-0.7.0}/benchmarks/quadtree_bench/__init__.py +0 -0
- {fastquadtree-0.6.1 → fastquadtree-0.7.0}/benchmarks/quadtree_bench/engines.py +0 -0
- {fastquadtree-0.6.1 → fastquadtree-0.7.0}/benchmarks/quadtree_bench/main.py +0 -0
- {fastquadtree-0.6.1 → fastquadtree-0.7.0}/benchmarks/quadtree_bench/plotting.py +0 -0
- {fastquadtree-0.6.1 → fastquadtree-0.7.0}/benchmarks/runner.py +0 -0
- {fastquadtree-0.6.1 → fastquadtree-0.7.0}/interactive/interactive.py +0 -0
- {fastquadtree-0.6.1 → fastquadtree-0.7.0}/interactive/interactive_v2.py +1 -1
- {fastquadtree-0.6.1 → fastquadtree-0.7.0}/interactive/requirements.txt +0 -0
- {fastquadtree-0.6.1 → fastquadtree-0.7.0}/pysrc/fastquadtree/_bimap.py +0 -0
- {fastquadtree-0.6.1 → fastquadtree-0.7.0}/pysrc/fastquadtree/_item.py +0 -0
- {fastquadtree-0.6.1 → fastquadtree-0.7.0}/pysrc/fastquadtree/py.typed +0 -0
- {fastquadtree-0.6.1 → fastquadtree-0.7.0}/src/geom.rs +0 -0
- {fastquadtree-0.6.1 → fastquadtree-0.7.0}/src/lib.rs +0 -0
- {fastquadtree-0.6.1 → fastquadtree-0.7.0}/src/quadtree.rs +0 -0
- {fastquadtree-0.6.1 → fastquadtree-0.7.0}/tests/insertions.rs +0 -0
- {fastquadtree-0.6.1 → fastquadtree-0.7.0}/tests/nearest_neighbor.rs +0 -0
- {fastquadtree-0.6.1 → fastquadtree-0.7.0}/tests/query.rs +0 -0
- {fastquadtree-0.6.1 → fastquadtree-0.7.0}/tests/rectangle_traversal.rs +0 -0
- {fastquadtree-0.6.1 → fastquadtree-0.7.0}/tests/test_bimap.py +0 -0
- {fastquadtree-0.6.1 → fastquadtree-0.7.0}/tests/test_delete.rs +0 -0
- {fastquadtree-0.6.1 → fastquadtree-0.7.0}/tests/test_delete_by_object.py +0 -0
- {fastquadtree-0.6.1 → fastquadtree-0.7.0}/tests/test_delete_python.py +0 -0
- {fastquadtree-0.6.1 → fastquadtree-0.7.0}/tests/test_python.py +0 -0
- {fastquadtree-0.6.1 → fastquadtree-0.7.0}/tests/test_unconventional_bounds.py +0 -0
- {fastquadtree-0.6.1 → fastquadtree-0.7.0}/tests/test_wrapper_edges.py +0 -0
- {fastquadtree-0.6.1 → fastquadtree-0.7.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
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: fastquadtree
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.7.0
|
4
4
|
Classifier: Programming Language :: Python :: 3
|
5
5
|
Classifier: Programming Language :: Python :: 3 :: Only
|
6
6
|
Classifier: Programming Language :: Rust
|
@@ -17,6 +17,12 @@ Requires-Dist: pytest-cov>=4.1 ; 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'
|
20
26
|
Provides-Extra: dev
|
21
27
|
License-File: LICENSE
|
22
28
|
Summary: Rust-accelerated quadtree for Python with fast inserts, range queries, and k-NN search.
|
@@ -26,10 +32,12 @@ Requires-Python: >=3.8
|
|
26
32
|
Description-Content-Type: text/markdown
|
27
33
|
Project-URL: Homepage, https://github.com/Elan456/fastquadtree
|
28
34
|
Project-URL: Repository, https://github.com/Elan456/fastquadtree
|
35
|
+
Project-URL: Documentation, https://elan456.github.io/fastquadtree/
|
29
36
|
Project-URL: Issues, https://github.com/Elan456/fastquadtree/issues
|
30
37
|
|
31
38
|
# fastquadtree
|
32
39
|
|
40
|
+
[](https://elan456.github.io/fastquadtree/)
|
33
41
|
[](https://pypi.org/project/fastquadtree/)
|
34
42
|
[](https://pypi.org/project/fastquadtree/)
|
35
43
|
[](https://pypi.org/project/fastquadtree/#files)
|
@@ -51,6 +59,8 @@ Project-URL: Issues, https://github.com/Elan456/fastquadtree/issues
|
|
51
59
|
|
52
60
|
Rust-optimized quadtree with a simple Python API.
|
53
61
|
|
62
|
+
👉 **Docs:** https://elan456.github.io/fastquadtree/
|
63
|
+
|
54
64
|
- Python package: **`fastquadtree`**
|
55
65
|
- Python ≥ 3.8
|
56
66
|
- Import path: `from fastquadtree import QuadTree`
|
@@ -171,6 +181,8 @@ qt.attach(123, my_object) # binds object to id 123
|
|
171
181
|
|
172
182
|
## API
|
173
183
|
|
184
|
+
[Full api for QuadTree](https://elan456.github.io/fastquadtree/api/quadtree/)
|
185
|
+
|
174
186
|
### `QuadTree(bounds, capacity, max_depth=None, track_objects=False, start_id=1)`
|
175
187
|
|
176
188
|
* `bounds` — tuple `(min_x, min_y, max_x, max_y)` defines the 2D area covered by the quadtree
|
@@ -181,8 +193,6 @@ qt.attach(123, my_object) # binds object to id 123
|
|
181
193
|
|
182
194
|
### Core Methods
|
183
195
|
|
184
|
-
Full docs are in the docstrings of the [Python Shim](pysrc/fastquadtree/__init__.py)
|
185
|
-
|
186
196
|
- `insert(xy, *, id=None, obj=None) -> int`
|
187
197
|
|
188
198
|
- `insert_many_points(points) -> int`
|
@@ -197,6 +207,8 @@ Full docs are in the docstrings of the [Python Shim](pysrc/fastquadtree/__init__
|
|
197
207
|
|
198
208
|
- `delete_by_object(obj) -> bool (requires track_objects=True)`
|
199
209
|
|
210
|
+
- `clear(*, reset_ids=False) -> None`
|
211
|
+
|
200
212
|
- `attach(id, obj) -> None (requires track_objects=True)`
|
201
213
|
|
202
214
|
- `count_items() -> int`
|
@@ -205,6 +217,8 @@ Full docs are in the docstrings of the [Python Shim](pysrc/fastquadtree/__init__
|
|
205
217
|
|
206
218
|
- `get_all_rectangles() -> list[tuple] (for visualization)`
|
207
219
|
|
220
|
+
- `get_all_objects() -> list[object] (requires track_objects=True)`
|
221
|
+
|
208
222
|
### `Item` (returned when `as_items=True`)
|
209
223
|
|
210
224
|
* Attributes: `id`, `x`, `y`, and a lazy `obj` property
|
@@ -222,7 +236,7 @@ Full docs are in the docstrings of the [Python Shim](pysrc/fastquadtree/__init__
|
|
222
236
|
* Choose `capacity` so that leaves keep a small batch of points. Typical values are 8 to 64.
|
223
237
|
* If your data is very skewed, set a `max_depth` to prevent long chains.
|
224
238
|
* For fastest local runs, use `maturin develop --release`.
|
225
|
-
* The wrapper
|
239
|
+
* The wrapper only maintains an ID -> Obj map only if the quadtree was constructed with `track_objects=True`. If you don't need it, leave it off for best performance. Look at the [Native vs Shim Benchmark](#native-vs-shim-benchmark) below for details.
|
226
240
|
|
227
241
|
|
228
242
|
### Native vs Shim Benchmark
|
@@ -259,6 +273,8 @@ python benchmarks/cross_library_bench.py
|
|
259
273
|
python benchmarks/benchmark_native_vs_shim.py
|
260
274
|
```
|
261
275
|
|
276
|
+
Check the CLI arguments for the cross-library benchmark in `benchmarks/quadtree_bench/main.py`.
|
277
|
+
|
262
278
|
## Run Visualizer
|
263
279
|
A visualizer is included to help you understand how the quadtree subdivides space.
|
264
280
|
|
@@ -267,7 +283,17 @@ pip install -r interactive/requirements.txt
|
|
267
283
|
python interactive/interactive_v2.py
|
268
284
|
```
|
269
285
|
|
270
|
-
|
286
|
+
### Pygame Ball Pit Demo
|
287
|
+
|
288
|
+

|
289
|
+
|
290
|
+
A simple demo of moving objects with collision detection using **fastquadtree**.
|
291
|
+
You can toggle between quadtree mode and brute-force mode to see the performance difference.
|
292
|
+
|
293
|
+
```bash
|
294
|
+
pip install -r interactive/requirements.txt
|
295
|
+
python interactive/ball_pit.py
|
296
|
+
```
|
271
297
|
|
272
298
|
## FAQ
|
273
299
|
|
@@ -280,9 +306,6 @@ Yes! Use `delete(id, xy)` to remove specific items. You must provide both the ID
|
|
280
306
|
**Can I store rectangles or circles?**
|
281
307
|
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
308
|
|
283
|
-
**Threading**
|
284
|
-
Use one tree per thread if you need heavy parallel inserts from Python.
|
285
|
-
|
286
309
|
## License
|
287
310
|
|
288
311
|
MIT. See `LICENSE`.
|
@@ -1,5 +1,6 @@
|
|
1
1
|
# fastquadtree
|
2
2
|
|
3
|
+
[](https://elan456.github.io/fastquadtree/)
|
3
4
|
[](https://pypi.org/project/fastquadtree/)
|
4
5
|
[](https://pypi.org/project/fastquadtree/)
|
5
6
|
[](https://pypi.org/project/fastquadtree/#files)
|
@@ -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`
|
@@ -141,6 +144,8 @@ qt.attach(123, my_object) # binds object to id 123
|
|
141
144
|
|
142
145
|
## API
|
143
146
|
|
147
|
+
[Full api for QuadTree](https://elan456.github.io/fastquadtree/api/quadtree/)
|
148
|
+
|
144
149
|
### `QuadTree(bounds, capacity, max_depth=None, track_objects=False, start_id=1)`
|
145
150
|
|
146
151
|
* `bounds` — tuple `(min_x, min_y, max_x, max_y)` defines the 2D area covered by the quadtree
|
@@ -151,8 +156,6 @@ qt.attach(123, my_object) # binds object to id 123
|
|
151
156
|
|
152
157
|
### Core Methods
|
153
158
|
|
154
|
-
Full docs are in the docstrings of the [Python Shim](pysrc/fastquadtree/__init__.py)
|
155
|
-
|
156
159
|
- `insert(xy, *, id=None, obj=None) -> int`
|
157
160
|
|
158
161
|
- `insert_many_points(points) -> int`
|
@@ -167,6 +170,8 @@ Full docs are in the docstrings of the [Python Shim](pysrc/fastquadtree/__init__
|
|
167
170
|
|
168
171
|
- `delete_by_object(obj) -> bool (requires track_objects=True)`
|
169
172
|
|
173
|
+
- `clear(*, reset_ids=False) -> None`
|
174
|
+
|
170
175
|
- `attach(id, obj) -> None (requires track_objects=True)`
|
171
176
|
|
172
177
|
- `count_items() -> int`
|
@@ -175,6 +180,8 @@ Full docs are in the docstrings of the [Python Shim](pysrc/fastquadtree/__init__
|
|
175
180
|
|
176
181
|
- `get_all_rectangles() -> list[tuple] (for visualization)`
|
177
182
|
|
183
|
+
- `get_all_objects() -> list[object] (requires track_objects=True)`
|
184
|
+
|
178
185
|
### `Item` (returned when `as_items=True`)
|
179
186
|
|
180
187
|
* Attributes: `id`, `x`, `y`, and a lazy `obj` property
|
@@ -192,7 +199,7 @@ Full docs are in the docstrings of the [Python Shim](pysrc/fastquadtree/__init__
|
|
192
199
|
* Choose `capacity` so that leaves keep a small batch of points. Typical values are 8 to 64.
|
193
200
|
* If your data is very skewed, set a `max_depth` to prevent long chains.
|
194
201
|
* For fastest local runs, use `maturin develop --release`.
|
195
|
-
* The wrapper
|
202
|
+
* The wrapper only maintains an ID -> Obj map only if the quadtree was constructed with `track_objects=True`. If you don't need it, leave it off for best performance. Look at the [Native vs Shim Benchmark](#native-vs-shim-benchmark) below for details.
|
196
203
|
|
197
204
|
|
198
205
|
### Native vs Shim Benchmark
|
@@ -229,6 +236,8 @@ python benchmarks/cross_library_bench.py
|
|
229
236
|
python benchmarks/benchmark_native_vs_shim.py
|
230
237
|
```
|
231
238
|
|
239
|
+
Check the CLI arguments for the cross-library benchmark in `benchmarks/quadtree_bench/main.py`.
|
240
|
+
|
232
241
|
## Run Visualizer
|
233
242
|
A visualizer is included to help you understand how the quadtree subdivides space.
|
234
243
|
|
@@ -237,7 +246,17 @@ pip install -r interactive/requirements.txt
|
|
237
246
|
python interactive/interactive_v2.py
|
238
247
|
```
|
239
248
|
|
240
|
-
|
249
|
+
### Pygame Ball Pit Demo
|
250
|
+
|
251
|
+

|
252
|
+
|
253
|
+
A simple demo of moving objects with collision detection using **fastquadtree**.
|
254
|
+
You can toggle between quadtree mode and brute-force mode to see the performance difference.
|
255
|
+
|
256
|
+
```bash
|
257
|
+
pip install -r interactive/requirements.txt
|
258
|
+
python interactive/ball_pit.py
|
259
|
+
```
|
241
260
|
|
242
261
|
## FAQ
|
243
262
|
|
@@ -250,9 +269,6 @@ Yes! Use `delete(id, xy)` to remove specific items. You must provide both the ID
|
|
250
269
|
**Can I store rectangles or circles?**
|
251
270
|
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
271
|
|
253
|
-
**Threading**
|
254
|
-
Use one tree per thread if you need heavy parallel inserts from Python.
|
255
|
-
|
256
272
|
## License
|
257
273
|
|
258
274
|
MIT. See `LICENSE`.
|
Binary file
|
@@ -6,6 +6,7 @@ and result collection for performance analysis.
|
|
6
6
|
"""
|
7
7
|
|
8
8
|
import gc
|
9
|
+
import json
|
9
10
|
import math
|
10
11
|
import random
|
11
12
|
import statistics as stats
|
@@ -16,6 +17,7 @@ from typing import Any, Dict, List, Tuple
|
|
16
17
|
from tqdm import tqdm
|
17
18
|
|
18
19
|
from .engines import Engine
|
20
|
+
from .system_info_collector import collect_system_info
|
19
21
|
|
20
22
|
|
21
23
|
@dataclass
|
@@ -315,3 +317,9 @@ class BenchmarkRunner:
|
|
315
317
|
print(f"| Max points per node | {config.max_points} |")
|
316
318
|
print(f"| Max depth | {config.max_depth} |")
|
317
319
|
print(f"| Queries per experiment | {config.n_queries} |")
|
320
|
+
|
321
|
+
system_info = collect_system_info()
|
322
|
+
print("#### System Info")
|
323
|
+
print("```json")
|
324
|
+
print(json.dumps(system_info, indent=2))
|
325
|
+
print("```")
|
@@ -0,0 +1,140 @@
|
|
1
|
+
import contextlib
|
2
|
+
import json
|
3
|
+
import platform
|
4
|
+
from typing import Any, Dict
|
5
|
+
|
6
|
+
|
7
|
+
def safe_import(name: str):
|
8
|
+
with contextlib.suppress(ImportError):
|
9
|
+
return __import__(name)
|
10
|
+
|
11
|
+
|
12
|
+
def collect_system_info() -> Dict[str, Any]:
|
13
|
+
psutil = safe_import("psutil")
|
14
|
+
cpuinfo = safe_import("cpuinfo")
|
15
|
+
distro = safe_import("distro")
|
16
|
+
gputil = safe_import("GPUtil")
|
17
|
+
|
18
|
+
info = {
|
19
|
+
"os": {
|
20
|
+
"system": platform.system(),
|
21
|
+
"release": platform.release(),
|
22
|
+
"version": platform.version(),
|
23
|
+
"machine": platform.machine(),
|
24
|
+
},
|
25
|
+
"python": {
|
26
|
+
"implementation": platform.python_implementation(),
|
27
|
+
"version": platform.python_version(),
|
28
|
+
},
|
29
|
+
"cpu": {},
|
30
|
+
"memory_gb": None,
|
31
|
+
"gpus": [],
|
32
|
+
}
|
33
|
+
|
34
|
+
# CPU and RAM via psutil
|
35
|
+
if psutil:
|
36
|
+
with contextlib.suppress(Exception):
|
37
|
+
info["cpu"]["physical_cores"] = psutil.cpu_count(logical=False)
|
38
|
+
info["cpu"]["logical_cores"] = psutil.cpu_count(logical=True)
|
39
|
+
freq = psutil.cpu_freq()
|
40
|
+
if freq:
|
41
|
+
info["cpu"]["max_freq_mhz"] = getattr(freq, "max", None)
|
42
|
+
info["cpu"]["current_freq_mhz"] = getattr(freq, "current", None)
|
43
|
+
info["memory_gb"] = round(psutil.virtual_memory().total / (1024**3), 1)
|
44
|
+
|
45
|
+
# CPU model via py-cpuinfo
|
46
|
+
if cpuinfo:
|
47
|
+
with contextlib.suppress(Exception):
|
48
|
+
ci = cpuinfo.get_cpu_info()
|
49
|
+
info["cpu"]["model"] = ci.get("brand_raw") or ci.get("brand")
|
50
|
+
if not info["cpu"].get("max_freq_mhz"):
|
51
|
+
hz = ci.get("hz_advertised_friendly") or ci.get("hz_advertised")
|
52
|
+
info["cpu"]["advertised_freq"] = hz
|
53
|
+
|
54
|
+
# Linux distro pretty name
|
55
|
+
if distro and info["os"]["system"] == "Linux":
|
56
|
+
with contextlib.suppress(Exception):
|
57
|
+
info["os"]["distro"] = distro.name(pretty=True)
|
58
|
+
|
59
|
+
# GPUs via GPUtil
|
60
|
+
if gputil:
|
61
|
+
with contextlib.suppress(Exception):
|
62
|
+
for gpu in gputil.getGPUs():
|
63
|
+
info["gpus"].append(
|
64
|
+
{
|
65
|
+
"name": gpu.name,
|
66
|
+
"memory_total_mb": int(gpu.memoryTotal),
|
67
|
+
"driver": getattr(gpu, "driver", None),
|
68
|
+
"uuid": getattr(gpu, "uuid", None),
|
69
|
+
}
|
70
|
+
)
|
71
|
+
|
72
|
+
return info
|
73
|
+
|
74
|
+
|
75
|
+
def _fmt_num(x, nd=2):
|
76
|
+
try:
|
77
|
+
return f"{float(x):.{nd}f}"
|
78
|
+
except Exception: # noqa: BLE001
|
79
|
+
return str(x)
|
80
|
+
|
81
|
+
|
82
|
+
def format_system_info_markdown(info: dict) -> str:
|
83
|
+
osd = info.get("os", {})
|
84
|
+
pyd = info.get("python", {})
|
85
|
+
cpu = info.get("cpu", {})
|
86
|
+
gpus = info.get("gpus", []) or []
|
87
|
+
mem = info.get("memory_gb")
|
88
|
+
|
89
|
+
os_name = osd.get("distro") or osd.get("system")
|
90
|
+
kernel = osd.get("release")
|
91
|
+
machine = osd.get("machine")
|
92
|
+
py_impl = pyd.get("implementation")
|
93
|
+
py_ver = pyd.get("version")
|
94
|
+
|
95
|
+
lines = []
|
96
|
+
# Summary bullets
|
97
|
+
lines.append("#### System Info")
|
98
|
+
lines.append(f"- **OS**: {os_name} (Linux {kernel}) on {machine}")
|
99
|
+
lines.append(f"- **Python**: {py_impl} {py_ver}")
|
100
|
+
model = cpu.get("model") or "Unknown CPU"
|
101
|
+
pcores = cpu.get("physical_cores")
|
102
|
+
lcores = cpu.get("logical_cores")
|
103
|
+
maxmhz = cpu.get("max_freq_mhz")
|
104
|
+
curmhz = cpu.get("current_freq_mhz")
|
105
|
+
lines.append(f"- **CPU**: {model}")
|
106
|
+
parts = []
|
107
|
+
if pcores is not None:
|
108
|
+
parts.append(f"physical cores: {pcores}")
|
109
|
+
if lcores is not None:
|
110
|
+
parts.append(f"logical cores: {lcores}")
|
111
|
+
if maxmhz is not None:
|
112
|
+
parts.append(f"max freq: {_fmt_num(maxmhz)} MHz")
|
113
|
+
if curmhz is not None:
|
114
|
+
parts.append(f"current freq: {_fmt_num(curmhz)} MHz")
|
115
|
+
if parts:
|
116
|
+
lines.append(" • " + " \n • ".join(parts))
|
117
|
+
if mem is not None:
|
118
|
+
lines.append(f"- **Memory**: {_fmt_num(mem, 1)} GB")
|
119
|
+
|
120
|
+
# GPU section
|
121
|
+
if gpus:
|
122
|
+
lines.append("- **GPUs**:")
|
123
|
+
lines.append("")
|
124
|
+
lines.append("| # | Name | Memory (MB) | Driver | UUID |")
|
125
|
+
lines.append("|-:|------|------------:|:------|:-----|")
|
126
|
+
for i, g in enumerate(gpus):
|
127
|
+
lines.append(
|
128
|
+
f"| {i} | {g.get('name','')} | {g.get('memory_total_mb','')} | "
|
129
|
+
f"{g.get('driver','')} | {g.get('uuid','')} |"
|
130
|
+
)
|
131
|
+
else:
|
132
|
+
lines.append("- **GPUs**: none detected")
|
133
|
+
|
134
|
+
return "\n".join(lines)
|
135
|
+
|
136
|
+
|
137
|
+
# Example pretty print
|
138
|
+
if __name__ == "__main__":
|
139
|
+
print(json.dumps(collect_system_info(), indent=2))
|
140
|
+
print(format_system_info_markdown(collect_system_info()))
|
@@ -12,4 +12,10 @@ 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
|
21
|
+
GPUtil
|
@@ -0,0 +1,145 @@
|
|
1
|
+
# Quickstart
|
2
|
+
|
3
|
+
Meet **fastquadtree** — a Rust powered spatial index for Python
|
4
|
+
|
5
|
+
> TLDR: create a tree, insert points, query ranges or nearest neighbors.
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
```bash
|
10
|
+
pip install fastquadtree
|
11
|
+
```
|
12
|
+
|
13
|
+
---
|
14
|
+
|
15
|
+
## 30-second demo
|
16
|
+
|
17
|
+
```python
|
18
|
+
from fastquadtree import QuadTree
|
19
|
+
|
20
|
+
# 1) Make a tree that covers your world
|
21
|
+
qt = QuadTree(bounds=(0, 0, 1000, 1000), capacity=20)
|
22
|
+
|
23
|
+
# 2) Add some stuff
|
24
|
+
a = qt.insert((10, 10)) # auto id
|
25
|
+
b = qt.insert((200, 300)) # auto id
|
26
|
+
_ = qt.insert((999, 500), id_=42) # you can choose ids too
|
27
|
+
|
28
|
+
# 3) Ask spatial questions
|
29
|
+
print("Range hits:", qt.query((0, 0, 250, 350))) # -> [(id, x, y), ...]
|
30
|
+
|
31
|
+
print("Nearest to (210, 310):", qt.nearest_neighbor((210, 310)))
|
32
|
+
|
33
|
+
print("Top 3 near (210, 310):", qt.nearest_neighbors((210, 310), 3))
|
34
|
+
|
35
|
+
# 4) Delete by id and exact location
|
36
|
+
print("Deleted:", qt.delete(b, (200, 300)))
|
37
|
+
print("Count:", qt.count_items())
|
38
|
+
```
|
39
|
+
|
40
|
+
## Range queries that feel natural
|
41
|
+
|
42
|
+
```python
|
43
|
+
# Think of it like a camera frustum in 2D
|
44
|
+
viewport = (100, 200, 400, 600)
|
45
|
+
for id_, x, y in qt.query(viewport):
|
46
|
+
print(f"Visible: id={id_} at ({x:.1f}, {y:.1f})")
|
47
|
+
```
|
48
|
+
|
49
|
+
Use this for viewport culling, collision broad-phase, spatial filtering, and quick “what is inside this box” checks.
|
50
|
+
|
51
|
+
---
|
52
|
+
|
53
|
+
## Nearest neighbor for snapping and picking
|
54
|
+
|
55
|
+
```python
|
56
|
+
cursor = (212, 305)
|
57
|
+
hit = qt.nearest_neighbor(cursor)
|
58
|
+
if hit:
|
59
|
+
id_, x, y = hit
|
60
|
+
print(f"Closest to cursor is id={id_} at ({x:.1f}, {y:.1f})")
|
61
|
+
```
|
62
|
+
|
63
|
+
Need more than one neighbor
|
64
|
+
|
65
|
+
```python
|
66
|
+
for id_, x, y in qt.nearest_neighbors(cursor, k=5):
|
67
|
+
print(id_, x, y)
|
68
|
+
```
|
69
|
+
|
70
|
+
---
|
71
|
+
|
72
|
+
## Track Python objects when you need them
|
73
|
+
|
74
|
+
Turn on object tracking to bind your own objects to ids. Delete by object in O(1).
|
75
|
+
|
76
|
+
```python
|
77
|
+
qt = QuadTree((0, 0, 1000, 1000), capacity=16, track_objects=True)
|
78
|
+
|
79
|
+
player = {"name": "Alice", "hp": 100}
|
80
|
+
enemy = {"name": "Boblin", "hp": 60}
|
81
|
+
|
82
|
+
pid = qt.insert((50, 50), obj=player)
|
83
|
+
eid = qt.insert((80, 60), obj=enemy)
|
84
|
+
|
85
|
+
# Query as Item objects for convenience
|
86
|
+
items = qt.query((0, 0, 200, 200), as_items=True)
|
87
|
+
for it in items:
|
88
|
+
print(it.id, it.x, it.y, it.obj)
|
89
|
+
|
90
|
+
# Remove by object identity
|
91
|
+
qt.delete_by_object(player) # True
|
92
|
+
```
|
93
|
+
|
94
|
+
Tip: leave `track_objects=False` for max speed when you do not need object mapping.
|
95
|
+
|
96
|
+
---
|
97
|
+
|
98
|
+
## Reset between runs without breaking references
|
99
|
+
|
100
|
+
Keep the same `QuadTree` instance alive for UIs or game loops. Wipe contents and optionally reset ids.
|
101
|
+
|
102
|
+
```python
|
103
|
+
qt.clear(reset_ids=True) # tree is empty, auto ids start again at 1
|
104
|
+
```
|
105
|
+
|
106
|
+
---
|
107
|
+
|
108
|
+
## Tiny benchmark sketch
|
109
|
+
|
110
|
+
```python
|
111
|
+
import random, time
|
112
|
+
from fastquadtree import QuadTree
|
113
|
+
|
114
|
+
N = 200_000
|
115
|
+
pts = [(random.random()*1000, random.random()*1000) for _ in range(N)]
|
116
|
+
qt = QuadTree((0, 0, 1000, 1000), capacity=32)
|
117
|
+
|
118
|
+
t0 = time.perf_counter()
|
119
|
+
qt.insert_many_points(pts)
|
120
|
+
t1 = time.perf_counter()
|
121
|
+
|
122
|
+
hits = qt.query((250, 250, 750, 750))
|
123
|
+
t2 = time.perf_counter()
|
124
|
+
|
125
|
+
print(f"Build: {(t1-t0):.3f}s Query: {(t2-t1):.3f}s Hits: {len(hits)}")
|
126
|
+
```
|
127
|
+
|
128
|
+
---
|
129
|
+
|
130
|
+
## Common patterns
|
131
|
+
|
132
|
+
* **Toggle index vs brute force** while debugging performance
|
133
|
+
Keep an array of points for ground truth, wire a flag to switch `qt.query` vs a list comprehension.
|
134
|
+
* **Use `capacity` 8 to 64** for most workloads
|
135
|
+
If data is highly skewed, set a `max_depth` to avoid very deep trees.
|
136
|
+
* **Do not panic about memory**
|
137
|
+
Rebuilding with `clear()` drops the old native tree as soon as Python releases it.
|
138
|
+
|
139
|
+
---
|
140
|
+
|
141
|
+
## Next steps
|
142
|
+
|
143
|
+
* API reference lives in the sidebar
|
144
|
+
* Try the visualizer in `interactive/` to see splits live
|
145
|
+
* Bring your own objects, or stay minimal with ids and tuples
|