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.
- {fastquadtree-0.5.0 → fastquadtree-0.6.0}/.pre-commit-config.yaml +11 -9
- {fastquadtree-0.5.0 → fastquadtree-0.6.0}/Cargo.lock +2 -1
- {fastquadtree-0.5.0 → fastquadtree-0.6.0}/Cargo.toml +2 -1
- {fastquadtree-0.5.0 → fastquadtree-0.6.0}/PKG-INFO +31 -13
- {fastquadtree-0.5.0 → fastquadtree-0.6.0}/README.md +23 -13
- fastquadtree-0.6.0/assets/quadtree_bench_throughput.png +0 -0
- fastquadtree-0.6.0/assets/quadtree_bench_time.png +0 -0
- {fastquadtree-0.5.0 → fastquadtree-0.6.0}/benchmarks/benchmark_native_vs_shim.py +25 -6
- {fastquadtree-0.5.0 → fastquadtree-0.6.0}/benchmarks/quadtree_bench/__init__.py +2 -2
- {fastquadtree-0.5.0 → fastquadtree-0.6.0}/benchmarks/quadtree_bench/engines.py +60 -10
- {fastquadtree-0.5.0 → fastquadtree-0.6.0}/benchmarks/quadtree_bench/main.py +4 -4
- {fastquadtree-0.5.0 → fastquadtree-0.6.0}/benchmarks/quadtree_bench/plotting.py +24 -24
- {fastquadtree-0.5.0 → fastquadtree-0.6.0}/benchmarks/quadtree_bench/runner.py +97 -18
- {fastquadtree-0.5.0 → fastquadtree-0.6.0}/benchmarks/requirements.txt +2 -1
- {fastquadtree-0.5.0 → fastquadtree-0.6.0}/benchmarks/runner.py +0 -7
- {fastquadtree-0.5.0 → fastquadtree-0.6.0}/interactive/interactive.py +12 -6
- {fastquadtree-0.5.0 → fastquadtree-0.6.0}/interactive/interactive_v2.py +123 -95
- fastquadtree-0.6.0/pyproject.toml +131 -0
- {fastquadtree-0.5.0 → fastquadtree-0.6.0}/pysrc/fastquadtree/__init__.py +63 -70
- {fastquadtree-0.5.0 → fastquadtree-0.6.0}/pysrc/fastquadtree/__init__.pyi +24 -21
- {fastquadtree-0.5.0 → fastquadtree-0.6.0}/pysrc/fastquadtree/_bimap.py +12 -11
- {fastquadtree-0.5.0 → fastquadtree-0.6.0}/pysrc/fastquadtree/_item.py +5 -4
- {fastquadtree-0.5.0 → fastquadtree-0.6.0}/src/lib.rs +20 -6
- {fastquadtree-0.5.0 → fastquadtree-0.6.0}/src/quadtree.rs +72 -14
- {fastquadtree-0.5.0 → fastquadtree-0.6.0}/tests/query.rs +2 -2
- {fastquadtree-0.5.0 → fastquadtree-0.6.0}/tests/test_bimap.py +7 -6
- {fastquadtree-0.5.0 → fastquadtree-0.6.0}/tests/test_delete.rs +2 -2
- {fastquadtree-0.5.0 → fastquadtree-0.6.0}/tests/test_delete_by_object.py +1 -0
- {fastquadtree-0.5.0 → fastquadtree-0.6.0}/tests/test_delete_python.py +0 -1
- {fastquadtree-0.5.0 → fastquadtree-0.6.0}/tests/test_python.py +7 -4
- {fastquadtree-0.5.0 → fastquadtree-0.6.0}/tests/test_unconventional_bounds.py +4 -2
- {fastquadtree-0.5.0 → fastquadtree-0.6.0}/tests/test_wrapper_edges.py +14 -15
- {fastquadtree-0.5.0 → fastquadtree-0.6.0}/tests/unconventional_bounds.rs +2 -2
- fastquadtree-0.5.0/assets/quadtree_bench_throughput.png +0 -0
- fastquadtree-0.5.0/assets/quadtree_bench_time.png +0 -0
- fastquadtree-0.5.0/pyproject.toml +0 -56
- {fastquadtree-0.5.0 → fastquadtree-0.6.0}/.github/workflows/release.yml +0 -0
- {fastquadtree-0.5.0 → fastquadtree-0.6.0}/.gitignore +0 -0
- {fastquadtree-0.5.0 → fastquadtree-0.6.0}/LICENSE +0 -0
- {fastquadtree-0.5.0 → fastquadtree-0.6.0}/assets/interactive_v2_screenshot.png +0 -0
- {fastquadtree-0.5.0 → fastquadtree-0.6.0}/benchmarks/cross_library_bench.py +0 -0
- {fastquadtree-0.5.0 → fastquadtree-0.6.0}/interactive/requirements.txt +0 -0
- {fastquadtree-0.5.0 → fastquadtree-0.6.0}/pysrc/fastquadtree/py.typed +0 -0
- {fastquadtree-0.5.0 → fastquadtree-0.6.0}/src/geom.rs +0 -0
- {fastquadtree-0.5.0 → fastquadtree-0.6.0}/tests/insertions.rs +0 -0
- {fastquadtree-0.5.0 → fastquadtree-0.6.0}/tests/nearest_neighbor.rs +0 -0
- {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:
|
3
|
-
- repo: https://github.com/
|
4
|
-
rev:
|
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
|
-
|
7
|
-
|
8
|
-
|
9
|
-
args: ["--
|
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
|
@@ -1,6 +1,6 @@
|
|
1
1
|
[package]
|
2
2
|
name = "fastquadtree"
|
3
|
-
version = "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.
|
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
|
[](https://pypi.org/project/fastquadtree/#files)
|
29
36
|
[](LICENSE)
|
30
37
|
|
31
|
-
[](https://pepy.tech/projects/fastquadtree)
|
38
|
+
[](https://pepy.tech/projects/fastquadtree)
|
33
39
|
|
34
40
|
[](https://github.com/Elan456/fastquadtree/actions/workflows/ci.yml)
|
35
41
|
[](https://codecov.io/gh/Elan456/fastquadtree)
|
36
42
|
|
37
43
|
[](https://pyo3.rs/)
|
38
44
|
[](https://www.maturin.rs/)
|
39
|
-
[](https://github.com/astral-sh/ruff)
|
46
|
+
|
40
47
|
|
41
48
|
|
42
49
|

|
@@ -58,18 +65,27 @@ fastquadtree **outperforms** all other quadtree Python packages, including the R
|
|
58
65
|

|
59
66
|
|
60
67
|
### Summary (largest dataset, PyQtree baseline)
|
61
|
-
- Points: **
|
68
|
+
- Points: **250,000**, Queries: **500**
|
62
69
|
--------------------
|
63
|
-
- Fastest total: **fastquadtree** at **
|
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.
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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
|
[](https://pypi.org/project/fastquadtree/#files)
|
6
6
|
[](LICENSE)
|
7
7
|
|
8
|
-
[](https://pepy.tech/projects/fastquadtree)
|
8
|
+
[](https://pepy.tech/projects/fastquadtree)
|
10
9
|
|
11
10
|
[](https://github.com/Elan456/fastquadtree/actions/workflows/ci.yml)
|
12
11
|
[](https://codecov.io/gh/Elan456/fastquadtree)
|
13
12
|
|
14
13
|
[](https://pyo3.rs/)
|
15
14
|
[](https://www.maturin.rs/)
|
16
|
-
[](https://github.com/astral-sh/ruff)
|
16
|
+
|
17
17
|
|
18
18
|
|
19
19
|

|
@@ -35,18 +35,27 @@ fastquadtree **outperforms** all other quadtree Python packages, including the R
|
|
35
35
|

|
36
36
|
|
37
37
|
### Summary (largest dataset, PyQtree baseline)
|
38
|
-
- Points: **
|
38
|
+
- Points: **250,000**, Queries: **500**
|
39
39
|
--------------------
|
40
|
-
- Fastest total: **fastquadtree** at **
|
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.
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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/
|
Binary file
|
Binary file
|
@@ -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),
|
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__ = ["
|
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
|
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",
|
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
|
-
|
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",
|
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",
|
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",
|
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
|
-
|
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=
|
28
|
+
default=128,
|
29
29
|
help="Maximum points per node before splitting",
|
30
30
|
)
|
31
|
-
parser.add_argument("--max-depth", type=int, default=
|
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=
|
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
|
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=
|
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=
|
73
|
-
orientation
|
74
|
-
traceorder
|
75
|
-
xanchor
|
76
|
-
x
|
77
|
-
yanchor
|
78
|
-
y
|
79
|
-
|
80
|
-
margin=
|
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=
|
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=
|
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=
|
142
|
-
orientation
|
143
|
-
x
|
144
|
-
xanchor
|
145
|
-
y
|
146
|
-
yanchor
|
147
|
-
|
148
|
-
margin=
|
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=
|
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=
|
240
|
+
legend={"orientation": "v"},
|
241
241
|
height=600,
|
242
242
|
)
|
243
243
|
|