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.
Files changed (54) hide show
  1. fastquadtree-0.7.0/.github/workflows/docs.yml +20 -0
  2. {fastquadtree-0.6.1 → fastquadtree-0.7.0}/Cargo.lock +1 -1
  3. {fastquadtree-0.6.1 → fastquadtree-0.7.0}/Cargo.toml +1 -1
  4. {fastquadtree-0.6.1 → fastquadtree-0.7.0}/PKG-INFO +31 -8
  5. {fastquadtree-0.6.1 → fastquadtree-0.7.0}/README.md +23 -7
  6. fastquadtree-0.7.0/assets/ballpit.png +0 -0
  7. {fastquadtree-0.6.1 → fastquadtree-0.7.0}/benchmarks/quadtree_bench/runner.py +8 -0
  8. fastquadtree-0.7.0/benchmarks/quadtree_bench/system_info_collector.py +140 -0
  9. {fastquadtree-0.6.1 → fastquadtree-0.7.0}/benchmarks/requirements.txt +7 -1
  10. fastquadtree-0.7.0/docs/api/item.md +2 -0
  11. fastquadtree-0.7.0/docs/api/quadtree.md +2 -0
  12. fastquadtree-0.7.0/docs/index.md +4 -0
  13. fastquadtree-0.7.0/docs/quickstart.md +145 -0
  14. fastquadtree-0.7.0/interactive/ballpit.py +283 -0
  15. fastquadtree-0.7.0/mkdocs.yml +82 -0
  16. {fastquadtree-0.6.1 → fastquadtree-0.7.0}/pyproject.toml +7 -0
  17. {fastquadtree-0.6.1 → fastquadtree-0.7.0}/pysrc/fastquadtree/__init__.py +40 -4
  18. {fastquadtree-0.6.1 → fastquadtree-0.7.0}/pysrc/fastquadtree/__init__.pyi +1 -0
  19. fastquadtree-0.7.0/tests/test_clear.py +130 -0
  20. {fastquadtree-0.6.1 → fastquadtree-0.7.0}/.github/workflows/release.yml +0 -0
  21. {fastquadtree-0.6.1 → fastquadtree-0.7.0}/.gitignore +0 -0
  22. {fastquadtree-0.6.1 → fastquadtree-0.7.0}/.pre-commit-config.yaml +0 -0
  23. {fastquadtree-0.6.1 → fastquadtree-0.7.0}/LICENSE +0 -0
  24. {fastquadtree-0.6.1 → fastquadtree-0.7.0}/assets/interactive_v2_screenshot.png +0 -0
  25. {fastquadtree-0.6.1 → fastquadtree-0.7.0}/assets/quadtree_bench_throughput.png +0 -0
  26. {fastquadtree-0.6.1 → fastquadtree-0.7.0}/assets/quadtree_bench_time.png +0 -0
  27. {fastquadtree-0.6.1 → fastquadtree-0.7.0}/benchmarks/benchmark_native_vs_shim.py +0 -0
  28. {fastquadtree-0.6.1 → fastquadtree-0.7.0}/benchmarks/cross_library_bench.py +0 -0
  29. {fastquadtree-0.6.1 → fastquadtree-0.7.0}/benchmarks/quadtree_bench/__init__.py +0 -0
  30. {fastquadtree-0.6.1 → fastquadtree-0.7.0}/benchmarks/quadtree_bench/engines.py +0 -0
  31. {fastquadtree-0.6.1 → fastquadtree-0.7.0}/benchmarks/quadtree_bench/main.py +0 -0
  32. {fastquadtree-0.6.1 → fastquadtree-0.7.0}/benchmarks/quadtree_bench/plotting.py +0 -0
  33. {fastquadtree-0.6.1 → fastquadtree-0.7.0}/benchmarks/runner.py +0 -0
  34. {fastquadtree-0.6.1 → fastquadtree-0.7.0}/interactive/interactive.py +0 -0
  35. {fastquadtree-0.6.1 → fastquadtree-0.7.0}/interactive/interactive_v2.py +1 -1
  36. {fastquadtree-0.6.1 → fastquadtree-0.7.0}/interactive/requirements.txt +0 -0
  37. {fastquadtree-0.6.1 → fastquadtree-0.7.0}/pysrc/fastquadtree/_bimap.py +0 -0
  38. {fastquadtree-0.6.1 → fastquadtree-0.7.0}/pysrc/fastquadtree/_item.py +0 -0
  39. {fastquadtree-0.6.1 → fastquadtree-0.7.0}/pysrc/fastquadtree/py.typed +0 -0
  40. {fastquadtree-0.6.1 → fastquadtree-0.7.0}/src/geom.rs +0 -0
  41. {fastquadtree-0.6.1 → fastquadtree-0.7.0}/src/lib.rs +0 -0
  42. {fastquadtree-0.6.1 → fastquadtree-0.7.0}/src/quadtree.rs +0 -0
  43. {fastquadtree-0.6.1 → fastquadtree-0.7.0}/tests/insertions.rs +0 -0
  44. {fastquadtree-0.6.1 → fastquadtree-0.7.0}/tests/nearest_neighbor.rs +0 -0
  45. {fastquadtree-0.6.1 → fastquadtree-0.7.0}/tests/query.rs +0 -0
  46. {fastquadtree-0.6.1 → fastquadtree-0.7.0}/tests/rectangle_traversal.rs +0 -0
  47. {fastquadtree-0.6.1 → fastquadtree-0.7.0}/tests/test_bimap.py +0 -0
  48. {fastquadtree-0.6.1 → fastquadtree-0.7.0}/tests/test_delete.rs +0 -0
  49. {fastquadtree-0.6.1 → fastquadtree-0.7.0}/tests/test_delete_by_object.py +0 -0
  50. {fastquadtree-0.6.1 → fastquadtree-0.7.0}/tests/test_delete_python.py +0 -0
  51. {fastquadtree-0.6.1 → fastquadtree-0.7.0}/tests/test_python.py +0 -0
  52. {fastquadtree-0.6.1 → fastquadtree-0.7.0}/tests/test_unconventional_bounds.py +0 -0
  53. {fastquadtree-0.6.1 → fastquadtree-0.7.0}/tests/test_wrapper_edges.py +0 -0
  54. {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
@@ -22,7 +22,7 @@ checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9"
22
22
 
23
23
  [[package]]
24
24
  name = "fastquadtree"
25
- version = "0.6.1"
25
+ version = "0.7.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.7.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.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
+ [![Docs](https://img.shields.io/badge/docs-online-brightgreen)](https://elan456.github.io/fastquadtree/)
33
41
  [![PyPI version](https://img.shields.io/pypi/v/fastquadtree.svg)](https://pypi.org/project/fastquadtree/)
34
42
  [![Python versions](https://img.shields.io/pypi/pyversions/fastquadtree.svg)](https://pypi.org/project/fastquadtree/)
35
43
  [![Wheels](https://img.shields.io/pypi/wheel/fastquadtree.svg)](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 keeps Python overhead low: raw tuple results by default, `Item` wrappers only when requested.
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
- Check the CLI arguments for the cross-library benchmark in `benchmarks/quadtree_bench/main.py`.
286
+ ### Pygame Ball Pit Demo
287
+
288
+ ![Ballpit_Demo_Screenshot](https://raw.githubusercontent.com/Elan456/fastquadtree/main/assets/ballpit.png)
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
+ [![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)
@@ -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 keeps Python overhead low: raw tuple results by default, `Item` wrappers only when requested.
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
- Check the CLI arguments for the cross-library benchmark in `benchmarks/quadtree_bench/main.py`.
249
+ ### Pygame Ball Pit Demo
250
+
251
+ ![Ballpit_Demo_Screenshot](https://raw.githubusercontent.com/Elan456/fastquadtree/main/assets/ballpit.png)
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,2 @@
1
+ # fastquadtree.Item
2
+ ::: fastquadtree.Item
@@ -0,0 +1,2 @@
1
+ # fastquadtree.QuadTree
2
+ ::: fastquadtree.QuadTree
@@ -0,0 +1,4 @@
1
+ # fastquadtree
2
+
3
+ Rust powered quadtree for Python.
4
+ See the sidebar for Quickstart and API.
@@ -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