fastquadtree 0.5.0__tar.gz → 0.6.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 (47) hide show
  1. {fastquadtree-0.5.0 → fastquadtree-0.6.0}/.pre-commit-config.yaml +11 -9
  2. {fastquadtree-0.5.0 → fastquadtree-0.6.0}/Cargo.lock +2 -1
  3. {fastquadtree-0.5.0 → fastquadtree-0.6.0}/Cargo.toml +2 -1
  4. {fastquadtree-0.5.0 → fastquadtree-0.6.0}/PKG-INFO +31 -13
  5. {fastquadtree-0.5.0 → fastquadtree-0.6.0}/README.md +23 -13
  6. fastquadtree-0.6.0/assets/quadtree_bench_throughput.png +0 -0
  7. fastquadtree-0.6.0/assets/quadtree_bench_time.png +0 -0
  8. {fastquadtree-0.5.0 → fastquadtree-0.6.0}/benchmarks/benchmark_native_vs_shim.py +25 -6
  9. {fastquadtree-0.5.0 → fastquadtree-0.6.0}/benchmarks/quadtree_bench/__init__.py +2 -2
  10. {fastquadtree-0.5.0 → fastquadtree-0.6.0}/benchmarks/quadtree_bench/engines.py +60 -10
  11. {fastquadtree-0.5.0 → fastquadtree-0.6.0}/benchmarks/quadtree_bench/main.py +4 -4
  12. {fastquadtree-0.5.0 → fastquadtree-0.6.0}/benchmarks/quadtree_bench/plotting.py +24 -24
  13. {fastquadtree-0.5.0 → fastquadtree-0.6.0}/benchmarks/quadtree_bench/runner.py +97 -18
  14. {fastquadtree-0.5.0 → fastquadtree-0.6.0}/benchmarks/requirements.txt +2 -1
  15. {fastquadtree-0.5.0 → fastquadtree-0.6.0}/benchmarks/runner.py +0 -7
  16. {fastquadtree-0.5.0 → fastquadtree-0.6.0}/interactive/interactive.py +12 -6
  17. {fastquadtree-0.5.0 → fastquadtree-0.6.0}/interactive/interactive_v2.py +123 -95
  18. fastquadtree-0.6.0/pyproject.toml +131 -0
  19. {fastquadtree-0.5.0 → fastquadtree-0.6.0}/pysrc/fastquadtree/__init__.py +63 -70
  20. {fastquadtree-0.5.0 → fastquadtree-0.6.0}/pysrc/fastquadtree/__init__.pyi +24 -21
  21. {fastquadtree-0.5.0 → fastquadtree-0.6.0}/pysrc/fastquadtree/_bimap.py +12 -11
  22. {fastquadtree-0.5.0 → fastquadtree-0.6.0}/pysrc/fastquadtree/_item.py +5 -4
  23. {fastquadtree-0.5.0 → fastquadtree-0.6.0}/src/lib.rs +20 -6
  24. {fastquadtree-0.5.0 → fastquadtree-0.6.0}/src/quadtree.rs +72 -14
  25. {fastquadtree-0.5.0 → fastquadtree-0.6.0}/tests/query.rs +2 -2
  26. {fastquadtree-0.5.0 → fastquadtree-0.6.0}/tests/test_bimap.py +7 -6
  27. {fastquadtree-0.5.0 → fastquadtree-0.6.0}/tests/test_delete.rs +2 -2
  28. {fastquadtree-0.5.0 → fastquadtree-0.6.0}/tests/test_delete_by_object.py +1 -0
  29. {fastquadtree-0.5.0 → fastquadtree-0.6.0}/tests/test_delete_python.py +0 -1
  30. {fastquadtree-0.5.0 → fastquadtree-0.6.0}/tests/test_python.py +7 -4
  31. {fastquadtree-0.5.0 → fastquadtree-0.6.0}/tests/test_unconventional_bounds.py +4 -2
  32. {fastquadtree-0.5.0 → fastquadtree-0.6.0}/tests/test_wrapper_edges.py +14 -15
  33. {fastquadtree-0.5.0 → fastquadtree-0.6.0}/tests/unconventional_bounds.rs +2 -2
  34. fastquadtree-0.5.0/assets/quadtree_bench_throughput.png +0 -0
  35. fastquadtree-0.5.0/assets/quadtree_bench_time.png +0 -0
  36. fastquadtree-0.5.0/pyproject.toml +0 -56
  37. {fastquadtree-0.5.0 → fastquadtree-0.6.0}/.github/workflows/release.yml +0 -0
  38. {fastquadtree-0.5.0 → fastquadtree-0.6.0}/.gitignore +0 -0
  39. {fastquadtree-0.5.0 → fastquadtree-0.6.0}/LICENSE +0 -0
  40. {fastquadtree-0.5.0 → fastquadtree-0.6.0}/assets/interactive_v2_screenshot.png +0 -0
  41. {fastquadtree-0.5.0 → fastquadtree-0.6.0}/benchmarks/cross_library_bench.py +0 -0
  42. {fastquadtree-0.5.0 → fastquadtree-0.6.0}/interactive/requirements.txt +0 -0
  43. {fastquadtree-0.5.0 → fastquadtree-0.6.0}/pysrc/fastquadtree/py.typed +0 -0
  44. {fastquadtree-0.5.0 → fastquadtree-0.6.0}/src/geom.rs +0 -0
  45. {fastquadtree-0.5.0 → fastquadtree-0.6.0}/tests/insertions.rs +0 -0
  46. {fastquadtree-0.5.0 → fastquadtree-0.6.0}/tests/nearest_neighbor.rs +0 -0
  47. {fastquadtree-0.5.0 → fastquadtree-0.6.0}/tests/rectangle_traversal.rs +0 -0
@@ -1,12 +1,15 @@
1
1
  repos:
2
- # 0) Python formatter: black
3
- - repo: https://github.com/psf/black
4
- rev: 24.8.0
2
+ # 0) Python linter and formatter: Ruff
3
+ - repo: https://github.com/astral-sh/ruff-pre-commit
4
+ rev: v0.6.9
5
5
  hooks:
6
- - id: black
7
- name: black
8
- language_version: python3
9
- args: ["--line-length", "88"]
6
+ # Lint + autofix. If Ruff fixes something, the hook exits nonzero to force a re-run.
7
+ - id: ruff
8
+ name: ruff (lint + fix)
9
+ args: ["--fix", "--exit-non-zero-on-fix"]
10
+ # Code formatter (Black replacement)
11
+ - id: ruff-format
12
+ name: ruff (format)
10
13
 
11
14
  # Local hooks that run in sequence and do not receive file args
12
15
  - repo: local
@@ -28,10 +31,9 @@ repos:
28
31
  always_run: true
29
32
 
30
33
  # 3) Python tests under coverage
31
- # This both runs pytest and generates .coverage data
32
34
  - id: pytest
33
35
  name: pytest (under coverage)
34
36
  entry: pytest
35
37
  language: system
36
38
  pass_filenames: false
37
- always_run: true
39
+ always_run: true
@@ -22,9 +22,10 @@ checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9"
22
22
 
23
23
  [[package]]
24
24
  name = "fastquadtree"
25
- version = "0.5.0"
25
+ version = "0.6.0"
26
26
  dependencies = [
27
27
  "pyo3",
28
+ "smallvec",
28
29
  ]
29
30
 
30
31
  [[package]]
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "fastquadtree"
3
- version = "0.5.0"
3
+ version = "0.6.0"
4
4
  edition = "2021"
5
5
  readme = "README.md"
6
6
 
@@ -9,6 +9,7 @@ crate-type = ["rlib", "cdylib"]
9
9
 
10
10
  [dependencies]
11
11
  pyo3 = { version = "0.21", features = ["extension-module", "abi3-py38"] }
12
+ smallvec = "1.15.1"
12
13
 
13
14
  [profile.release]
14
15
  opt-level = 3
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fastquadtree
3
- Version: 0.5.0
3
+ Version: 0.6.0
4
4
  Classifier: Programming Language :: Python :: 3
5
5
  Classifier: Programming Language :: Python :: 3 :: Only
6
6
  Classifier: Programming Language :: Rust
@@ -11,6 +11,13 @@ Classifier: Topic :: Scientific/Engineering :: Information Analysis
11
11
  Classifier: Topic :: Software Development :: Libraries
12
12
  Classifier: Typing :: Typed
13
13
  Classifier: License :: OSI Approved :: MIT License
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'
17
+ Requires-Dist: coverage>=7.5 ; extra == 'dev'
18
+ Requires-Dist: mypy>=1.10 ; extra == 'dev'
19
+ Requires-Dist: build>=1.2.1 ; extra == 'dev'
20
+ Provides-Extra: dev
14
21
  License-File: LICENSE
15
22
  Summary: Rust-accelerated quadtree for Python with fast inserts, range queries, and k-NN search.
16
23
  Keywords: quadtree,spatial-index,geometry,rust,pyo3,nearest-neighbor,k-nn
@@ -28,15 +35,15 @@ Project-URL: Issues, https://github.com/Elan456/fastquadtree/issues
28
35
  [![Wheels](https://img.shields.io/pypi/wheel/fastquadtree.svg)](https://pypi.org/project/fastquadtree/#files)
29
36
  [![License: MIT](https://img.shields.io/pypi/l/fastquadtree.svg)](LICENSE)
30
37
 
31
- [![Downloads total](https://static.pepy.tech/badge/fastquadtree)](https://pepy.tech/projects/fastquadtree)
32
- [![Downloads month](https://static.pepy.tech/badge/fastquadtree/month)](https://pepy.tech/projects/fastquadtree)
38
+ [![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)
33
39
 
34
40
  [![Build](https://github.com/Elan456/fastquadtree/actions/workflows/release.yml/badge.svg)](https://github.com/Elan456/fastquadtree/actions/workflows/ci.yml)
35
41
  [![Codecov](https://codecov.io/gh/Elan456/fastquadtree/branch/main/graph/badge.svg)](https://codecov.io/gh/Elan456/fastquadtree)
36
42
 
37
43
  [![Rust core via PyO3](https://img.shields.io/badge/Rust-core%20via%20PyO3-orange)](https://pyo3.rs/)
38
44
  [![Built with maturin](https://img.shields.io/badge/Built%20with-maturin-1f6feb)](https://www.maturin.rs/)
39
- [![Code style: Black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
45
+ [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
46
+
40
47
 
41
48
 
42
49
  ![Interactive_V2_Screenshot](https://raw.githubusercontent.com/Elan456/fastquadtree/main/assets/interactive_v2_screenshot.png)
@@ -58,18 +65,27 @@ fastquadtree **outperforms** all other quadtree Python packages, including the R
58
65
  ![Throughput](https://raw.githubusercontent.com/Elan456/fastquadtree/main/assets/quadtree_bench_throughput.png)
59
66
 
60
67
  ### Summary (largest dataset, PyQtree baseline)
61
- - Points: **500,000**, Queries: **500**
68
+ - Points: **250,000**, Queries: **500**
62
69
  --------------------
63
- - Fastest total: **fastquadtree** at **1.591 s**
70
+ - Fastest total: **fastquadtree** at **0.120 s**
64
71
 
65
72
  | Library | Build (s) | Query (s) | Total (s) | Speed vs PyQtree |
66
73
  |---|---:|---:|---:|---:|
67
- | fastquadtree | 0.165 | 1.427 | 1.591 | 5.09× |
68
- | Rtree | 1.320 | 2.369 | 3.688 | 2.20× |
69
- | PyQtree | 2.687 | 5.415 | 8.102 | 1.00× |
70
- | nontree-QuadTree | 1.284 | 9.891 | 11.175 | 0.73× |
71
- | quads | 2.346 | 10.129 | 12.475 | 0.65× |
72
- | e-pyquadtree | 1.795 | 11.855 | 13.650 | 0.59× |
74
+ | fastquadtree | 0.031 | 0.089 | 0.120 | 14.64× |
75
+ | Shapely STRtree | 0.179 | 0.100 | 0.279 | 6.29× |
76
+ | nontree-QuadTree | 0.595 | 0.605 | 1.200 | 1.46× |
77
+ | Rtree | 0.961 | 0.300 | 1.261 | 1.39× |
78
+ | e-pyquadtree | 1.005 | 0.660 | 1.665 | 1.05× |
79
+ | PyQtree | 1.492 | 0.263 | 1.755 | 1.00× |
80
+ | quads | 1.407 | 0.484 | 1.890 | 0.93× |
81
+
82
+ #### Benchmark Configuration
83
+ | Parameter | Value |
84
+ |---|---:|
85
+ | Bounds | (0, 0, 1000, 1000) |
86
+ | Max points per node | 128 |
87
+ | Max depth | 16 |
88
+ | Queries per experiment | 500 |
73
89
 
74
90
  ## Install
75
91
 
@@ -273,7 +289,7 @@ MIT. See `LICENSE`.
273
289
 
274
290
  ## Acknowledgments
275
291
 
276
- * Python libraries compared: [PyQtree], [e-pyquadtree], [Rtree], [nontree], [quads]
292
+ * Python libraries compared: [PyQtree], [e-pyquadtree], [Rtree], [nontree], [quads], [Shapely]
277
293
  * Built with [PyO3] and [maturin]
278
294
 
279
295
  [PyQtree]: https://pypi.org/project/pyqtree/
@@ -283,3 +299,5 @@ MIT. See `LICENSE`.
283
299
  [Rtree]: https://pypi.org/project/Rtree/
284
300
  [nontree]: https://pypi.org/project/nontree/
285
301
  [quads]: https://pypi.org/project/quads/
302
+ [Shapely]: https://pypi.org/project/Shapely/
303
+
@@ -5,15 +5,15 @@
5
5
  [![Wheels](https://img.shields.io/pypi/wheel/fastquadtree.svg)](https://pypi.org/project/fastquadtree/#files)
6
6
  [![License: MIT](https://img.shields.io/pypi/l/fastquadtree.svg)](LICENSE)
7
7
 
8
- [![Downloads total](https://static.pepy.tech/badge/fastquadtree)](https://pepy.tech/projects/fastquadtree)
9
- [![Downloads month](https://static.pepy.tech/badge/fastquadtree/month)](https://pepy.tech/projects/fastquadtree)
8
+ [![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)
10
9
 
11
10
  [![Build](https://github.com/Elan456/fastquadtree/actions/workflows/release.yml/badge.svg)](https://github.com/Elan456/fastquadtree/actions/workflows/ci.yml)
12
11
  [![Codecov](https://codecov.io/gh/Elan456/fastquadtree/branch/main/graph/badge.svg)](https://codecov.io/gh/Elan456/fastquadtree)
13
12
 
14
13
  [![Rust core via PyO3](https://img.shields.io/badge/Rust-core%20via%20PyO3-orange)](https://pyo3.rs/)
15
14
  [![Built with maturin](https://img.shields.io/badge/Built%20with-maturin-1f6feb)](https://www.maturin.rs/)
16
- [![Code style: Black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
15
+ [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
16
+
17
17
 
18
18
 
19
19
  ![Interactive_V2_Screenshot](https://raw.githubusercontent.com/Elan456/fastquadtree/main/assets/interactive_v2_screenshot.png)
@@ -35,18 +35,27 @@ fastquadtree **outperforms** all other quadtree Python packages, including the R
35
35
  ![Throughput](https://raw.githubusercontent.com/Elan456/fastquadtree/main/assets/quadtree_bench_throughput.png)
36
36
 
37
37
  ### Summary (largest dataset, PyQtree baseline)
38
- - Points: **500,000**, Queries: **500**
38
+ - Points: **250,000**, Queries: **500**
39
39
  --------------------
40
- - Fastest total: **fastquadtree** at **1.591 s**
40
+ - Fastest total: **fastquadtree** at **0.120 s**
41
41
 
42
42
  | Library | Build (s) | Query (s) | Total (s) | Speed vs PyQtree |
43
43
  |---|---:|---:|---:|---:|
44
- | fastquadtree | 0.165 | 1.427 | 1.591 | 5.09× |
45
- | Rtree | 1.320 | 2.369 | 3.688 | 2.20× |
46
- | PyQtree | 2.687 | 5.415 | 8.102 | 1.00× |
47
- | nontree-QuadTree | 1.284 | 9.891 | 11.175 | 0.73× |
48
- | quads | 2.346 | 10.129 | 12.475 | 0.65× |
49
- | e-pyquadtree | 1.795 | 11.855 | 13.650 | 0.59× |
44
+ | fastquadtree | 0.031 | 0.089 | 0.120 | 14.64× |
45
+ | Shapely STRtree | 0.179 | 0.100 | 0.279 | 6.29× |
46
+ | nontree-QuadTree | 0.595 | 0.605 | 1.200 | 1.46× |
47
+ | Rtree | 0.961 | 0.300 | 1.261 | 1.39× |
48
+ | e-pyquadtree | 1.005 | 0.660 | 1.665 | 1.05× |
49
+ | PyQtree | 1.492 | 0.263 | 1.755 | 1.00× |
50
+ | quads | 1.407 | 0.484 | 1.890 | 0.93× |
51
+
52
+ #### Benchmark Configuration
53
+ | Parameter | Value |
54
+ |---|---:|
55
+ | Bounds | (0, 0, 1000, 1000) |
56
+ | Max points per node | 128 |
57
+ | Max depth | 16 |
58
+ | Queries per experiment | 500 |
50
59
 
51
60
  ## Install
52
61
 
@@ -250,7 +259,7 @@ MIT. See `LICENSE`.
250
259
 
251
260
  ## Acknowledgments
252
261
 
253
- * Python libraries compared: [PyQtree], [e-pyquadtree], [Rtree], [nontree], [quads]
262
+ * Python libraries compared: [PyQtree], [e-pyquadtree], [Rtree], [nontree], [quads], [Shapely]
254
263
  * Built with [PyO3] and [maturin]
255
264
 
256
265
  [PyQtree]: https://pypi.org/project/pyqtree/
@@ -259,4 +268,5 @@ MIT. See `LICENSE`.
259
268
  [maturin]: https://www.maturin.rs/
260
269
  [Rtree]: https://pypi.org/project/Rtree/
261
270
  [nontree]: https://pypi.org/project/nontree/
262
- [quads]: https://pypi.org/project/quads/
271
+ [quads]: https://pypi.org/project/quads/
272
+ [Shapely]: https://pypi.org/project/Shapely/
@@ -6,11 +6,11 @@ import gc
6
6
  import random
7
7
  import statistics as stats
8
8
  from time import perf_counter as now
9
+
9
10
  from tqdm import tqdm
10
11
 
11
- from fastquadtree._native import QuadTree as NativeQuadTree
12
12
  from fastquadtree import QuadTree as ShimQuadTree
13
-
13
+ from fastquadtree._native import QuadTree as NativeQuadTree
14
14
 
15
15
  BOUNDS = (0.0, 0.0, 1000.0, 1000.0)
16
16
  CAPACITY = 20
@@ -68,9 +68,10 @@ def bench_shim(points, queries, *, track_objects: bool, with_objs: bool):
68
68
  return t_build, t_query
69
69
 
70
70
 
71
- def median_times(fn, points, queries, repeats: int):
71
+ def median_times(fn, points, queries, repeats: int, desc: str = "Running"):
72
+ """Run benchmark multiple times and return median times."""
72
73
  builds, queries_t = [], []
73
- for _ in tqdm(range(repeats)):
74
+ for _ in tqdm(range(repeats), desc=desc, unit="run"):
74
75
  gc.disable()
75
76
  b, q = fn(points, queries)
76
77
  gc.enable()
@@ -86,29 +87,47 @@ def main():
86
87
  ap.add_argument("--repeats", type=int, default=5)
87
88
  args = ap.parse_args()
88
89
 
90
+ print("Native vs Shim Benchmark")
91
+ print("=" * 50)
92
+ print("Configuration:")
93
+ print(f" Points: {args.points:,}")
94
+ print(f" Queries: {args.queries}")
95
+ print(f" Repeats: {args.repeats}")
96
+ print()
97
+
89
98
  rng = random.Random(SEED)
90
99
  points = gen_points(args.points, rng)
91
100
  queries = gen_queries(args.queries, rng)
92
101
 
93
102
  # Warmup to load modules
103
+ print("Warming up...")
94
104
  _ = bench_native(points[:1000], queries[:50])
95
105
  _ = bench_shim(points[:1000], queries[:50], track_objects=False, with_objs=False)
106
+ print()
96
107
 
108
+ print("Running benchmarks...")
97
109
  n_build, n_query = median_times(
98
- lambda pts, qs: bench_native(pts, qs), points, queries, args.repeats
110
+ lambda pts, qs: bench_native(pts, qs),
111
+ points,
112
+ queries,
113
+ args.repeats,
114
+ desc="Native",
99
115
  )
100
116
  s_build_no_map, s_query_no_map = median_times(
101
117
  lambda pts, qs: bench_shim(pts, qs, track_objects=False, with_objs=False),
102
118
  points,
103
119
  queries,
104
120
  args.repeats,
121
+ desc="Shim (no map)",
105
122
  )
106
123
  s_build_map, s_query_map = median_times(
107
124
  lambda pts, qs: bench_shim(pts, qs, track_objects=True, with_objs=True),
108
125
  points,
109
126
  queries,
110
127
  args.repeats,
128
+ desc="Shim (track+objs)",
111
129
  )
130
+ print()
112
131
 
113
132
  def fmt(x):
114
133
  return f"{x:.3f}"
@@ -131,7 +150,7 @@ def main():
131
150
 
132
151
  **Overhead vs Native**
133
152
 
134
- - 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
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
135
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
136
155
  """
137
156
  print(md.strip())
@@ -6,8 +6,8 @@ implementations, including performance comparison, visualization, and analysis.
6
6
  """
7
7
 
8
8
  from .engines import Engine, get_engines
9
- from .runner import BenchmarkRunner, BenchmarkConfig
10
9
  from .plotting import PlotManager
10
+ from .runner import BenchmarkConfig, BenchmarkRunner
11
11
 
12
12
  __version__ = "1.0.0"
13
- __all__ = ["Engine", "get_engines", "BenchmarkRunner", "BenchmarkConfig", "PlotManager"]
13
+ __all__ = ["BenchmarkConfig", "BenchmarkRunner", "Engine", "PlotManager", "get_engines"]
@@ -7,9 +7,14 @@ allowing fair comparison of their performance characteristics.
7
7
 
8
8
  from typing import Any, Callable, Dict, List, Optional, Tuple
9
9
 
10
+ import numpy as np
11
+ from pyqtree import Index as PyQTree # Pyqtree
12
+
10
13
  # Built-in engines (always available in this repo)
11
14
  from pyquadtree.quadtree import QuadTree as EPyQuadTree # e-pyquadtree
12
- from pyqtree import Index as PyQTree # Pyqtree
15
+ from shapely import box as shp_box, points # Shapely 2.x
16
+ from shapely.strtree import STRtree
17
+
13
18
  from fastquadtree import QuadTree as RustQuadTree # fastquadtree
14
19
 
15
20
 
@@ -67,7 +72,10 @@ def _create_e_pyquadtree_engine(
67
72
  _ = qt.query(q)
68
73
 
69
74
  return Engine(
70
- "e-pyquadtree", "#1f77b4", build, query # display name # color (blue)
75
+ "e-pyquadtree",
76
+ "#1f77b4",
77
+ build,
78
+ query, # display name # color (blue)
71
79
  )
72
80
 
73
81
 
@@ -96,8 +104,7 @@ def _create_fastquadtree_engine(
96
104
 
97
105
  def build(points):
98
106
  qt = RustQuadTree(bounds, max_points, max_depth=max_depth)
99
- for p in points:
100
- qt.insert(p)
107
+ qt.insert_many_points(points)
101
108
  return qt
102
109
 
103
110
  def query(qt, queries):
@@ -105,7 +112,10 @@ def _create_fastquadtree_engine(
105
112
  _ = qt.query(q)
106
113
 
107
114
  return Engine(
108
- "fastquadtree", "#ff7f0e", build, query # display name # color (orange)
115
+ "fastquadtree",
116
+ "#ff7f0e",
117
+ build,
118
+ query, # display name # color (orange)
109
119
  )
110
120
 
111
121
 
@@ -165,7 +175,10 @@ def _create_nontree_engine(
165
175
  _ = tm.get_rect((xmin, ymin, xmax - xmin, ymax - ymin))
166
176
 
167
177
  return Engine(
168
- "nontree-QuadTree", "#17becf", build, query # display name # color (cyan)
178
+ "nontree-QuadTree",
179
+ "#17becf",
180
+ build,
181
+ query, # display name # color (cyan)
169
182
  )
170
183
 
171
184
 
@@ -178,7 +191,7 @@ def _create_brute_force_engine(
178
191
  # Append each item as if they were being added separately
179
192
  out = []
180
193
  for p in points:
181
- out.append(p)
194
+ out.append(p) # noqa: PERF402
182
195
  return out
183
196
 
184
197
  def query(points, queries):
@@ -187,7 +200,10 @@ def _create_brute_force_engine(
187
200
  _ = [p for p in points if q[0] <= p[0] <= q[2] and q[1] <= p[1] <= q[3]]
188
201
 
189
202
  return Engine(
190
- "Brute force", "#9467bd", build, query # display name # color (purple)
203
+ "Brute force",
204
+ "#9467bd",
205
+ build,
206
+ query, # display name # color (purple)
191
207
  )
192
208
 
193
209
 
@@ -213,8 +229,7 @@ def _create_rtree_engine(
213
229
  # Bulk stream loading is the fastest way to build
214
230
  # Keep the same 1x1 bbox convention used elsewhere for fairness
215
231
  stream = ((i, (x, y, x + 1, y + 1), None) for i, (x, y) in enumerate(points))
216
- idx = rindex.Index(stream, properties=p)
217
- return idx
232
+ return rindex.Index(stream, properties=p)
218
233
 
219
234
  def query(idx, queries):
220
235
  # Do not materialize results into a list, just consume the generator
@@ -226,6 +241,40 @@ def _create_rtree_engine(
226
241
  return Engine("Rtree", "#e377c2", build, query)
227
242
 
228
243
 
244
+ def _create_strtree_engine(
245
+ bounds: Tuple[int, int, int, int], max_points: int, max_depth: int
246
+ ) -> Optional[Engine]:
247
+ """Create engine adapter for Shapely STRtree (optional)."""
248
+
249
+ def build(points_list: List[Tuple[int, int]]):
250
+ # Build geometries efficiently
251
+
252
+ xs = np.fromiter(
253
+ (x for x, _ in points_list), dtype="float32", count=len(points_list)
254
+ )
255
+ ys = np.fromiter(
256
+ (y for _, y in points_list), dtype="float32", count=len(points_list)
257
+ )
258
+ geoms = points(xs, ys) # vectorized Point creation
259
+ assert type(geoms) is np.ndarray
260
+ tree = STRtree(geoms, node_capacity=max_points)
261
+ # Keep geoms alive next to the tree
262
+ return (tree, geoms)
263
+
264
+ def query(built, queries: List[Tuple[int, int, int, int]]):
265
+ tree, _geoms = built
266
+ for xmin, ymin, xmax, ymax in queries:
267
+ window = shp_box(xmin, ymin, xmax, ymax)
268
+ # Shapely 2.x returns ndarray of indices for a single geometry
269
+ res = tree.query(window)
270
+ # Consume results without materializing to keep parity with other engines
271
+ if hasattr(res, "__iter__"):
272
+ for _ in res:
273
+ pass
274
+
275
+ return Engine("Shapely STRtree", "#7f7f7f", build, query)
276
+
277
+
229
278
  def get_engines(
230
279
  bounds: Tuple[int, int, int, int] = (0, 0, 1000, 1000),
231
280
  max_points: int = 20,
@@ -255,6 +304,7 @@ def get_engines(
255
304
  _create_quads_engine,
256
305
  _create_nontree_engine,
257
306
  _create_rtree_engine,
307
+ _create_strtree_engine,
258
308
  ]
259
309
 
260
310
  for engine_creator in optional_engines:
@@ -10,8 +10,8 @@ import argparse
10
10
  from pathlib import Path
11
11
 
12
12
  from .engines import get_engines
13
- from .runner import BenchmarkRunner, BenchmarkConfig
14
13
  from .plotting import PlotManager
14
+ from .runner import BenchmarkConfig, BenchmarkRunner
15
15
 
16
16
 
17
17
  def main():
@@ -25,10 +25,10 @@ def main():
25
25
  parser.add_argument(
26
26
  "--max-points",
27
27
  type=int,
28
- default=20,
28
+ default=128,
29
29
  help="Maximum points per node before splitting",
30
30
  )
31
- parser.add_argument("--max-depth", type=int, default=10, help="Maximum tree depth")
31
+ parser.add_argument("--max-depth", type=int, default=16, help="Maximum tree depth")
32
32
  parser.add_argument(
33
33
  "--n-queries", type=int, default=500, help="Number of queries per experiment"
34
34
  )
@@ -41,7 +41,7 @@ def main():
41
41
  parser.add_argument(
42
42
  "--max-experiment-points",
43
43
  type=int,
44
- default=500_000,
44
+ default=250_000,
45
45
  help="Maximum number of points in largest experiment",
46
46
  )
47
47
  parser.add_argument(
@@ -5,7 +5,7 @@ This module handles creation of performance charts, graphs, and visualizations
5
5
  for benchmark results analysis.
6
6
  """
7
7
 
8
- from typing import Dict, Any, Tuple
8
+ from typing import Any, Dict, Tuple
9
9
 
10
10
  import plotly.graph_objects as go
11
11
  from plotly.subplots import make_subplots
@@ -43,7 +43,7 @@ class PlotManager:
43
43
  name=name,
44
44
  legendgroup=name,
45
45
  showlegend=show_legend,
46
- line=dict(color=color, width=3),
46
+ line={"color": color, "width": 3},
47
47
  ),
48
48
  row=1,
49
49
  col=col,
@@ -69,15 +69,15 @@ class PlotManager:
69
69
  f"{self.config.n_queries} queries)"
70
70
  ),
71
71
  template="plotly_dark",
72
- legend=dict(
73
- orientation="v",
74
- traceorder="normal",
75
- xanchor="left",
76
- x=0,
77
- yanchor="top",
78
- y=1,
79
- ),
80
- margin=dict(l=40, r=20, t=80, b=40),
72
+ legend={
73
+ "orientation": "v",
74
+ "traceorder": "normal",
75
+ "xanchor": "left",
76
+ "x": 0,
77
+ "yanchor": "top",
78
+ "y": 1,
79
+ },
80
+ margin={"l": 40, "r": 20, "t": 80, "b": 40},
81
81
  height=520,
82
82
  )
83
83
 
@@ -108,7 +108,7 @@ class PlotManager:
108
108
  name=name,
109
109
  legendgroup=name,
110
110
  showlegend=False,
111
- line=dict(color=color, width=3),
111
+ line={"color": color, "width": 3},
112
112
  ),
113
113
  row=1,
114
114
  col=1,
@@ -122,7 +122,7 @@ class PlotManager:
122
122
  name=name,
123
123
  legendgroup=name,
124
124
  showlegend=True,
125
- line=dict(color=color, width=3),
125
+ line={"color": color, "width": 3},
126
126
  ),
127
127
  row=1,
128
128
  col=2,
@@ -138,14 +138,14 @@ class PlotManager:
138
138
  fig.update_layout(
139
139
  title="Throughput",
140
140
  template="plotly_dark",
141
- legend=dict(
142
- orientation="h",
143
- x=0,
144
- xanchor="left",
145
- y=1.08, # above the subplots
146
- yanchor="bottom",
147
- ),
148
- margin=dict(l=60, r=40, t=120, b=40),
141
+ legend={
142
+ "orientation": "h",
143
+ "x": 0,
144
+ "xanchor": "left",
145
+ "y": 1.08, # above the subplots
146
+ "yanchor": "bottom",
147
+ },
148
+ margin={"l": 60, "r": 40, "t": 120, "b": 40},
149
149
  height=480,
150
150
  )
151
151
 
@@ -187,7 +187,7 @@ class PlotManager:
187
187
  height=480,
188
188
  )
189
189
  print(f"Saved PNG images to {output_dir}/ with prefix '{output_prefix}'")
190
- except Exception as e:
190
+ except Exception as e: # noqa: BLE001
191
191
  print(
192
192
  f"Failed to save PNG images. Install kaleido to enable PNG export: {e}"
193
193
  )
@@ -227,7 +227,7 @@ class PlotManager:
227
227
  x=config.experiments,
228
228
  y=values,
229
229
  name=f"{label} - {engine_name}",
230
- line=dict(color=color, width=3),
230
+ line={"color": color, "width": 3},
231
231
  mode="lines+markers",
232
232
  )
233
233
  )
@@ -237,7 +237,7 @@ class PlotManager:
237
237
  xaxis_title="Number of points",
238
238
  yaxis_title="Time (s)" if "rate" not in metric else "Ops/sec",
239
239
  template="plotly_dark",
240
- legend=dict(orientation="v"),
240
+ legend={"orientation": "v"},
241
241
  height=600,
242
242
  )
243
243