fastquadtree 0.4.2__cp38-abi3-win_amd64.whl → 0.5.1__cp38-abi3-win_amd64.whl

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/__init__.py CHANGED
@@ -1,13 +1,13 @@
1
1
  from __future__ import annotations
2
2
 
3
- from typing import Any, Iterable, List, Optional, Tuple, overload
4
- from typing import Literal
3
+ from typing import Any, Iterable, Literal, Tuple, overload
5
4
 
6
- # Compiled Rust module is provided by maturin (tool.maturin.module-name)
7
- from ._native import QuadTree as _RustQuadTree
8
5
  from ._bimap import BiMap # type: ignore[attr-defined]
9
6
  from ._item import Item
10
7
 
8
+ # Compiled Rust module is provided by maturin (tool.maturin.module-name)
9
+ from ._native import QuadTree as _RustQuadTree
10
+
11
11
  Bounds = Tuple[float, float, float, float]
12
12
  """Axis-aligned rectangle as (min_x, min_y, max_x, max_y)."""
13
13
 
@@ -45,14 +45,14 @@ class QuadTree:
45
45
  ValueError: If parameters are invalid or inserts are out of bounds.
46
46
  """
47
47
 
48
- __slots__ = ("_native", "_items", "_next_id", "_count", "_bounds")
48
+ __slots__ = ("_bounds", "_count", "_items", "_native", "_next_id")
49
49
 
50
50
  def __init__(
51
51
  self,
52
52
  bounds: Bounds,
53
53
  capacity: int,
54
54
  *,
55
- max_depth: Optional[int] = None,
55
+ max_depth: int | None = None,
56
56
  track_objects: bool = False,
57
57
  start_id: int = 1,
58
58
  ):
@@ -60,14 +60,14 @@ class QuadTree:
60
60
  self._native = _RustQuadTree(bounds, capacity)
61
61
  else:
62
62
  self._native = _RustQuadTree(bounds, capacity, max_depth=max_depth)
63
- self._items: Optional[BiMap] = BiMap() if track_objects else None
63
+ self._items: BiMap | None = BiMap() if track_objects else None
64
64
  self._next_id: int = int(start_id)
65
65
  self._count: int = 0
66
66
  self._bounds = bounds
67
67
 
68
68
  # ---------- inserts ----------
69
69
 
70
- def insert(self, xy: Point, *, id: Optional[int] = None, obj: Any = None) -> int:
70
+ def insert(self, xy: Point, *, id_: int | None = None, obj: Any = None) -> int:
71
71
  """
72
72
  Insert a single point.
73
73
 
@@ -83,15 +83,14 @@ class QuadTree:
83
83
  Raises:
84
84
  ValueError: If the point is outside tree bounds.
85
85
  """
86
- if id is None:
87
- id = self._next_id
86
+ if id_ is None:
87
+ id_ = self._next_id
88
88
  self._next_id += 1
89
- else:
90
- # ensure future auto-ids do not collide
91
- if id >= self._next_id:
92
- self._next_id = id + 1
89
+ # ensure future auto-ids do not collide
90
+ elif id_ >= self._next_id:
91
+ self._next_id = id_ + 1
93
92
 
94
- if not self._native.insert(id, xy):
93
+ if not self._native.insert(id_, xy):
95
94
  x, y = xy
96
95
  bx0, by0, bx1, by1 = self._bounds
97
96
  raise ValueError(
@@ -99,10 +98,10 @@ class QuadTree:
99
98
  )
100
99
 
101
100
  if self._items is not None:
102
- self._items.add(Item(id, xy[0], xy[1], obj))
101
+ self._items.add(Item(id_, xy[0], xy[1], obj))
103
102
 
104
103
  self._count += 1
105
- return id
104
+ return id_
106
105
 
107
106
  def insert_many_points(self, points: Iterable[Point]) -> int:
108
107
  """
@@ -136,39 +135,39 @@ class QuadTree:
136
135
  self._count += inserted
137
136
  return inserted
138
137
 
139
- def attach(self, id: int, obj: Any) -> None:
138
+ def attach(self, id_: int, obj: Any) -> None:
140
139
  """
141
140
  Attach or replace the Python object for an existing id.
142
141
  Tracking must be enabled.
143
142
 
144
143
  Args:
145
- id: Target id.
144
+ id_: Target id.
146
145
  obj: Object to associate with id.
147
146
  """
148
147
  if self._items is None:
149
148
  raise ValueError("Cannot attach objects when track_objects=False")
150
149
 
151
- item = self._items.by_id(id)
150
+ item = self._items.by_id(id_)
152
151
  if item is None:
153
- raise KeyError(f"Id {id} not found in quadtree")
154
- self._items.add(Item(id, item.x, item.y, obj))
152
+ raise KeyError(f"Id {id_} not found in quadtree")
153
+ self._items.add(Item(id_, item.x, item.y, obj))
155
154
 
156
- def delete(self, id: int, xy: Point) -> bool:
155
+ def delete(self, id_: int, xy: Point) -> bool:
157
156
  """
158
157
  Delete an item by id and exact coordinates.
159
158
 
160
159
  Args:
161
- id: Integer id to remove.
160
+ id_: Integer id to remove.
162
161
  xy: Coordinates (x, y) of the item.
163
162
 
164
163
  Returns:
165
164
  True if the item was found and deleted, else False.
166
165
  """
167
- deleted = self._native.delete(id, xy)
166
+ deleted = self._native.delete(id_, xy)
168
167
  if deleted:
169
168
  self._count -= 1
170
169
  if self._items is not None:
171
- self._items.pop_id(id) # ignore result
170
+ self._items.pop_id(id_) # ignore result
172
171
  return deleted
173
172
 
174
173
  def delete_by_object(self, obj: Any) -> bool:
@@ -196,21 +195,21 @@ class QuadTree:
196
195
  if item is None:
197
196
  return False
198
197
 
199
- return self.delete(item.id, (item.x, item.y))
198
+ return self.delete(item.id_, (item.x, item.y))
200
199
 
201
200
  # ---------- queries ----------
202
201
 
203
202
  @overload
204
203
  def query(
205
204
  self, rect: Bounds, *, as_items: Literal[False] = ...
206
- ) -> List[_IdCoord]: ...
205
+ ) -> list[_IdCoord]: ...
207
206
 
208
207
  @overload
209
- def query(self, rect: Bounds, *, as_items: Literal[True]) -> List[Item]: ...
208
+ def query(self, rect: Bounds, *, as_items: Literal[True]) -> list[Item]: ...
210
209
 
211
210
  def query(
212
211
  self, rect: Bounds, *, as_items: bool = False
213
- ) -> List[_IdCoord] | List[Item]:
212
+ ) -> list[_IdCoord] | list[Item]:
214
213
  """
215
214
  Return all points inside an axis-aligned rectangle.
216
215
 
@@ -228,8 +227,8 @@ class QuadTree:
228
227
 
229
228
  if self._items is None:
230
229
  raise ValueError("Cannot return results as items with track_objects=False")
231
- out: List[Item] = []
232
- for id_, x, y in raw:
230
+ out: list[Item] = []
231
+ for id_, _, _ in raw:
233
232
  item = self._items.by_id(id_)
234
233
  if item is None:
235
234
  raise RuntimeError(
@@ -242,12 +241,10 @@ class QuadTree:
242
241
  @overload
243
242
  def nearest_neighbor(
244
243
  self, xy: Point, *, as_item: Literal[False] = ...
245
- ) -> Optional[_IdCoord]: ...
244
+ ) -> _IdCoord | None: ...
246
245
 
247
246
  @overload
248
- def nearest_neighbor(
249
- self, xy: Point, *, as_item: Literal[True]
250
- ) -> Optional[Item]: ...
247
+ def nearest_neighbor(self, xy: Point, *, as_item: Literal[True]) -> Item | None: ...
251
248
 
252
249
  def nearest_neighbor(self, xy: Point, *, as_item: bool = False):
253
250
  """
@@ -278,12 +275,12 @@ class QuadTree:
278
275
  @overload
279
276
  def nearest_neighbors(
280
277
  self, xy: Point, k: int, *, as_items: Literal[False] = ...
281
- ) -> List[_IdCoord]: ...
278
+ ) -> list[_IdCoord]: ...
282
279
 
283
280
  @overload
284
281
  def nearest_neighbors(
285
282
  self, xy: Point, k: int, *, as_items: Literal[True]
286
- ) -> List[Item]: ...
283
+ ) -> list[Item]: ...
287
284
 
288
285
  def nearest_neighbors(self, xy: Point, k: int, *, as_items: bool = False):
289
286
  """
@@ -303,7 +300,7 @@ class QuadTree:
303
300
  if self._items is None:
304
301
  raise ValueError("Cannot return results as items with track_objects=False")
305
302
 
306
- out: List[Item] = []
303
+ out: list[Item] = []
307
304
  for id_, _, _ in raw:
308
305
  item = self._items.by_id(id_)
309
306
  if item is None:
@@ -316,7 +313,7 @@ class QuadTree:
316
313
 
317
314
  # ---------- misc ----------
318
315
 
319
- def get(self, id: int) -> Any | None:
316
+ def get(self, id_: int) -> Any | None:
320
317
  """
321
318
  Return the object associated with id.
322
319
 
@@ -325,12 +322,12 @@ class QuadTree:
325
322
  """
326
323
  if self._items is None:
327
324
  raise ValueError("Cannot get objects when track_objects=False")
328
- item = self._items.by_id(id)
325
+ item = self._items.by_id(id_)
329
326
  if item is None:
330
327
  return None
331
328
  return item.obj
332
329
 
333
- def get_all_rectangles(self) -> List[Bounds]:
330
+ def get_all_rectangles(self) -> list[Bounds]:
334
331
  """
335
332
  Return all node rectangles in the current quadtree.
336
333
 
@@ -339,7 +336,7 @@ class QuadTree:
339
336
  """
340
337
  return self._native.get_all_rectangles()
341
338
 
342
- def get_all_objects(self) -> List[Any]:
339
+ def get_all_objects(self) -> list[Any]:
343
340
  """
344
341
  Return all tracked objects.
345
342
 
@@ -350,7 +347,7 @@ class QuadTree:
350
347
  raise ValueError("Cannot get objects when track_objects=False")
351
348
  return [t.obj for t in self._items.items() if t.obj is not None]
352
349
 
353
- def get_all_items(self) -> List[Item]:
350
+ def get_all_items(self) -> list[Item]:
354
351
  """
355
352
  Return all tracked items.
356
353
 
@@ -385,4 +382,4 @@ class QuadTree:
385
382
  NativeQuadTree = _RustQuadTree
386
383
 
387
384
 
388
- __all__ = ["QuadTree", "Item", "Bounds", "Point"]
385
+ __all__ = ["Bounds", "Item", "Point", "QuadTree"]
fastquadtree/__init__.pyi CHANGED
@@ -1,67 +1,71 @@
1
- from typing import Any, Iterable, List, Optional, Tuple, Type, overload
2
- from typing import Literal as _Literal # avoid polluting public namespace
1
+ from typing import (
2
+ Any,
3
+ Iterable,
4
+ Literal as _Literal, # avoid polluting public namespace
5
+ overload,
6
+ )
3
7
 
4
- Bounds = Tuple[float, float, float, float]
5
- Point = Tuple[float, float]
8
+ Bounds = tuple[float, float, float, float]
9
+ Point = tuple[float, float]
6
10
 
7
11
  class Item:
8
- id: int
12
+ id_: int
9
13
  x: float
10
14
  y: float
11
15
  obj: Any | None
12
16
 
13
17
  class QuadTree:
14
18
  # Expose the raw native class for power users
15
- NativeQuadTree: Type
19
+ NativeQuadTree: type
16
20
 
17
21
  def __init__(
18
22
  self,
19
23
  bounds: Bounds,
20
24
  capacity: int,
21
25
  *,
22
- max_depth: Optional[int] = None,
26
+ max_depth: int | None = None,
23
27
  track_objects: bool = False,
24
28
  start_id: int = 1,
25
29
  ) -> None: ...
26
30
 
27
31
  # Inserts
28
- def insert(self, xy: Point, *, id: Optional[int] = ..., obj: Any = ...) -> int: ...
32
+ def insert(self, xy: Point, *, id_: int | None = ..., obj: Any = ...) -> int: ...
29
33
  def insert_many_points(self, points: Iterable[Point]) -> int: ...
30
- def insert_many(self, items: Iterable[Tuple[Point, Any]]) -> int: ...
31
- def attach(self, id: int, obj: Any) -> None: ...
34
+ def insert_many(self, items: Iterable[tuple[Point, Any]]) -> int: ...
35
+ def attach(self, id_: int, obj: Any) -> None: ...
32
36
 
33
37
  # Deletions
34
- def delete(self, id: int, xy: Point) -> bool: ...
38
+ def delete(self, id_: int, xy: Point) -> bool: ...
35
39
  def delete_by_object(self, obj: Any) -> bool: ...
36
40
 
37
41
  # Queries
38
42
  @overload
39
43
  def query(
40
44
  self, rect: Bounds, *, as_items: _Literal[False] = ...
41
- ) -> List[Tuple[int, float, float]]: ...
45
+ ) -> list[tuple[int, float, float]]: ...
42
46
  @overload
43
- def query(self, rect: Bounds, *, as_items: _Literal[True]) -> List[Item]: ...
47
+ def query(self, rect: Bounds, *, as_items: _Literal[True]) -> list[Item]: ...
44
48
  @overload
45
49
  def nearest_neighbor(
46
50
  self, xy: Point, *, as_item: _Literal[False] = ...
47
- ) -> Optional[Tuple[int, float, float]]: ...
51
+ ) -> tuple[int, float, float] | None: ...
48
52
  @overload
49
53
  def nearest_neighbor(
50
54
  self, xy: Point, *, as_item: _Literal[True]
51
- ) -> Optional[Item]: ...
55
+ ) -> Item | None: ...
52
56
  @overload
53
57
  def nearest_neighbors(
54
58
  self, xy: Point, k: int, *, as_items: _Literal[False] = ...
55
- ) -> List[Tuple[int, float, float]]: ...
59
+ ) -> list[tuple[int, float, float]]: ...
56
60
  @overload
57
61
  def nearest_neighbors(
58
62
  self, xy: Point, k: int, *, as_items: _Literal[True]
59
- ) -> List[Item]: ...
63
+ ) -> list[Item]: ...
60
64
 
61
65
  # Misc
62
- def get(self, id: int) -> Any | None: ...
63
- def get_all_rectangles(self) -> List[Bounds]: ...
64
- def get_all_objects(self) -> List[Any]: ...
65
- def get_all_items(self) -> List[Item]: ...
66
+ def get(self, id_: int) -> Any | None: ...
67
+ def get_all_rectangles(self) -> list[Bounds]: ...
68
+ def get_all_objects(self) -> list[Any]: ...
69
+ def get_all_items(self) -> list[Item]: ...
66
70
  def count_items(self) -> int: ...
67
71
  def __len__(self) -> int: ...
fastquadtree/_bimap.py CHANGED
@@ -1,6 +1,7 @@
1
1
  # _bimap.py
2
2
  from __future__ import annotations
3
- from typing import Any, Iterable, Iterator, Tuple, Optional
3
+
4
+ from typing import Any, Iterable, Iterator
4
5
 
5
6
  from ._item import Item
6
7
 
@@ -33,7 +34,7 @@ class BiMap:
33
34
  Insert or replace mapping for this Item.
34
35
  Handles conflicts so that both id and obj point to this exact Item.
35
36
  """
36
- id_ = item.id
37
+ id_ = item.id_
37
38
  obj = item.obj
38
39
 
39
40
  # Unlink any old item currently bound to this id
@@ -47,20 +48,20 @@ class BiMap:
47
48
  if obj is not None:
48
49
  prev = self._objid_to_item.get(id(obj))
49
50
  if prev is not None and prev is not item:
50
- self._id_to_item.pop(prev.id, None)
51
+ self._id_to_item.pop(prev.id_, None)
51
52
 
52
53
  # Link new
53
54
  self._id_to_item[id_] = item
54
55
  if obj is not None:
55
56
  self._objid_to_item[id(obj)] = item
56
57
 
57
- def by_id(self, id_: int) -> Optional[Item]:
58
+ def by_id(self, id_: int) -> Item | None:
58
59
  return self._id_to_item.get(id_)
59
60
 
60
- def by_obj(self, obj: Any) -> Optional[Item]:
61
+ def by_obj(self, obj: Any) -> Item | None:
61
62
  return self._objid_to_item.get(id(obj))
62
63
 
63
- def pop_id(self, id_: int) -> Optional[Item]:
64
+ def pop_id(self, id_: int) -> Item | None:
64
65
  it = self._id_to_item.pop(id_, None)
65
66
  if it is not None:
66
67
  obj = it.obj
@@ -68,19 +69,19 @@ class BiMap:
68
69
  self._objid_to_item.pop(id(obj), None)
69
70
  return it
70
71
 
71
- def pop_obj(self, obj: Any) -> Optional[Item]:
72
+ def pop_obj(self, obj: Any) -> Item | None:
72
73
  it = self._objid_to_item.pop(id(obj), None)
73
74
  if it is not None:
74
- self._id_to_item.pop(it.id, None)
75
+ self._id_to_item.pop(it.id_, None)
75
76
  return it
76
77
 
77
- def pop_item(self, item: Item) -> Optional[Item]:
78
+ def pop_item(self, item: Item) -> Item | None:
78
79
  """
79
80
  Remove this exact Item if present on either side.
80
81
  """
81
82
  removed = None
82
83
  # Remove by id first
83
- removed = self._id_to_item.pop(item.id)
84
+ removed = self._id_to_item.pop(item.id_)
84
85
 
85
86
  # Remove by obj side
86
87
  obj = item.obj
@@ -104,7 +105,7 @@ class BiMap:
104
105
  def contains_obj(self, obj: Any) -> bool:
105
106
  return id(obj) in self._objid_to_item
106
107
 
107
- def items_by_id(self) -> Iterator[Tuple[int, Item]]:
108
+ def items_by_id(self) -> Iterator[tuple[int, Item]]:
108
109
  return iter(self._id_to_item.items())
109
110
 
110
111
  def items(self) -> Iterator[Item]:
fastquadtree/_item.py CHANGED
@@ -1,5 +1,6 @@
1
1
  # item.py
2
2
  from __future__ import annotations
3
+
3
4
  from typing import Any
4
5
 
5
6
 
@@ -8,16 +9,16 @@ class Item:
8
9
  Lightweight view of an index entry.
9
10
 
10
11
  Attributes:
11
- id: Integer identifier.
12
+ id_: Integer identifier.
12
13
  x: X coordinate.
13
14
  y: Y coordinate.
14
15
  obj: The attached Python object if available, else None.
15
16
  """
16
17
 
17
- __slots__ = ("id", "x", "y", "obj")
18
+ __slots__ = ("id_", "obj", "x", "y")
18
19
 
19
- def __init__(self, id: int, x: float, y: float, obj: Any | None = None):
20
- self.id = id
20
+ def __init__(self, id_: int, x: float, y: float, obj: Any | None = None):
21
+ self.id_ = id_
21
22
  self.x = x
22
23
  self.y = y
23
24
  self.obj = obj
fastquadtree/_native.pyd CHANGED
Binary file
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fastquadtree
3
- Version: 0.4.2
3
+ Version: 0.5.1
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
@@ -36,8 +43,8 @@ Project-URL: Issues, https://github.com/Elan456/fastquadtree/issues
36
43
 
37
44
  [![Rust core via PyO3](https://img.shields.io/badge/Rust-core%20via%20PyO3-orange)](https://pyo3.rs/)
38
45
  [![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)
40
- [![Type checking: mypy](https://img.shields.io/badge/type%20checking-mypy-2a6db2)](http://mypy-lang.org/)
46
+ [![Lint and format: Ruff](https://img.shields.io/badge/Lint%20and%20format-Ruff-46a758?logo=ruff&logoColor=white)](https://docs.astral.sh/ruff/)
47
+
41
48
 
42
49
 
43
50
  ![Interactive_V2_Screenshot](https://raw.githubusercontent.com/Elan456/fastquadtree/main/assets/interactive_v2_screenshot.png)
@@ -49,6 +56,29 @@ Rust-optimized quadtree with a simple Python API.
49
56
  - Python ≥ 3.8
50
57
  - Import path: `from fastquadtree import QuadTree`
51
58
 
59
+ ## Benchmarks
60
+
61
+ fastquadtree **outperforms** all other quadtree Python packages, including the Rtree spatial index.
62
+
63
+ ### Library comparison
64
+
65
+ ![Total time](https://raw.githubusercontent.com/Elan456/fastquadtree/main/assets/quadtree_bench_time.png)
66
+ ![Throughput](https://raw.githubusercontent.com/Elan456/fastquadtree/main/assets/quadtree_bench_throughput.png)
67
+
68
+ ### Summary (largest dataset, PyQtree baseline)
69
+ - Points: **500,000**, Queries: **500**
70
+ --------------------
71
+ - Fastest total: **fastquadtree** at **1.591 s**
72
+
73
+ | Library | Build (s) | Query (s) | Total (s) | Speed vs PyQtree |
74
+ |---|---:|---:|---:|---:|
75
+ | fastquadtree | 0.165 | 1.427 | 1.591 | 5.09× |
76
+ | Rtree | 1.320 | 2.369 | 3.688 | 2.20× |
77
+ | PyQtree | 2.687 | 5.415 | 8.102 | 1.00× |
78
+ | nontree-QuadTree | 1.284 | 9.891 | 11.175 | 0.73× |
79
+ | quads | 2.346 | 10.129 | 12.475 | 0.65× |
80
+ | e-pyquadtree | 1.795 | 11.855 | 13.650 | 0.59× |
81
+
52
82
  ## Install
53
83
 
54
84
  ```bash
@@ -109,25 +139,7 @@ print(f"Deleted player: {deleted}") # True
109
139
 
110
140
  You can keep the tree pure and manage your own id → object map, or let the wrapper manage it.
111
141
 
112
- **Option A: Manage your own map**
113
-
114
- ```python
115
- from fastquadtree import QuadTree
116
-
117
- qt = QuadTree((0, 0, 1000, 1000), capacity=16)
118
- objects: dict[int, object] = {}
119
-
120
- def add(obj) -> int:
121
- obj_id = qt.insert(obj.position) # auto id
122
- objects[obj_id] = obj
123
- return obj_id
124
-
125
- # Later, resolve ids back to objects
126
- ids = [obj_id for (obj_id, x, y) in qt.query((100, 100, 300, 300))]
127
- selected = [objects[i] for i in ids]
128
- ```
129
-
130
- **Option B: Ask the wrapper to track objects**
142
+ **Wrapper Managed Objects**
131
143
 
132
144
  ```python
133
145
  from fastquadtree import QuadTree
@@ -137,7 +149,7 @@ qt = QuadTree((0, 0, 1000, 1000), capacity=16, track_objects=True)
137
149
  # Store the object alongside the point
138
150
  qt.insert((25, 40), obj={"name": "apple"})
139
151
 
140
- # Ask for Item objects so you can access .obj lazily
152
+ # Ask for Item objects within a bounding box
141
153
  items = qt.query((0, 0, 100, 100), as_items=True)
142
154
  for it in items:
143
155
  print(it.id, it.x, it.y, it.obj)
@@ -151,7 +163,7 @@ qt.attach(123, my_object) # binds object to id 123
151
163
 
152
164
  ## API
153
165
 
154
- ### `QuadTree(bounds, capacity, *, max_depth=None, track_objects=False, start_id=1)`
166
+ ### `QuadTree(bounds, capacity, max_depth=None, track_objects=False, start_id=1)`
155
167
 
156
168
  * `bounds` — tuple `(min_x, min_y, max_x, max_y)` defines the 2D area covered by the quadtree
157
169
  * `capacity` — max number of points kept in a leaf before splitting
@@ -204,30 +216,8 @@ Full docs are in the docstrings of the [Python Shim](pysrc/fastquadtree/__init__
204
216
  * For fastest local runs, use `maturin develop --release`.
205
217
  * The wrapper keeps Python overhead low: raw tuple results by default, `Item` wrappers only when requested.
206
218
 
207
- ## Benchmarks
208
-
209
- fastquadtree outperforms all other quadtree python packages (at least all the ones I could find and install via pip.)
210
-
211
- ### Library comparison
212
-
213
- ![Total time](https://raw.githubusercontent.com/Elan456/fastquadtree/main/assets/quadtree_bench_time.png)
214
- ![Throughput](https://raw.githubusercontent.com/Elan456/fastquadtree/main/assets/quadtree_bench_throughput.png)
215
-
216
- ### Summary (largest dataset, PyQtree baseline)
217
- - Points: **500,000**, Queries: **500**
218
- - Fastest total: **fastquadtree** at **2.207 s**
219
-
220
- | Library | Build (s) | Query (s) | Total (s) | Speed vs PyQtree |
221
- |---|---:|---:|---:|---:|
222
- | fastquadtree | 0.321 | 1.885 | 2.207 | 4.27× |
223
- | Rtree | 1.718 | 4.376 | 6.095 | 1.55× |
224
- | nontree-QuadTree | 1.617 | 7.643 | 9.260 | 1.02× |
225
- | PyQtree | 4.349 | 5.082 | 9.431 | 1.00× |
226
- | quads | 3.874 | 9.058 | 12.932 | 0.73× |
227
- | e-pyquadtree | 2.732 | 10.598 | 13.330 | 0.71× |
228
- | Brute force | 0.019 | 19.986 | 20.005 | 0.47× |
229
219
 
230
- ### Native vs Shim
220
+ ### Native vs Shim Benchmark
231
221
 
232
222
  **Setup**
233
223
  - Points: 500,000
@@ -277,7 +267,7 @@ Check the CLI arguments for the cross-library benchmark in `benchmarks/quadtree_
277
267
  Allowed. For k-nearest, duplicates are de-duplicated by id. For range queries you will see every inserted point.
278
268
 
279
269
  **Can I delete items from the quadtree?**
280
- Yes! Use `delete(id, xy)` to remove specific items. You must provide both the ID and exact location for precise deletion. This handles cases where multiple items exist at the same location. If you're using `track_objects=True`, you can also use `delete_by_object(obj, xy)` for convenient object-based deletion with O(1) lookup. The tree automatically merges nodes when item counts drop below capacity.
270
+ Yes! Use `delete(id, xy)` to remove specific items. You must provide both the ID and exact location for precise deletion. This handles cases where multiple items exist at the same location. If you're using `track_objects=True`, you can also use `delete_by_object(obj)` for convenient object-based deletion with O(1) lookup. The tree automatically merges nodes when item counts drop below capacity.
281
271
 
282
272
  **Can I store rectangles or circles?**
283
273
  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.
@@ -0,0 +1,10 @@
1
+ fastquadtree-0.5.1.dist-info/METADATA,sha256=3tYPbCeIMK_f0U0lR1_oeKPagw3M00sPO2GYq6us-xs,10443
2
+ fastquadtree-0.5.1.dist-info/WHEEL,sha256=7bfl5v0wbVhXZba613g0x-n2obNNfpQuN8I1cQ4oaU8,94
3
+ fastquadtree-0.5.1.dist-info/licenses/LICENSE,sha256=46IVFhoCIwMo-ocq4olyEB1eBvvtaKic5yGLeKXnDuc,1092
4
+ fastquadtree/__init__.py,sha256=NhKAMKyc9N6UV1BKpyF4ssGamLiZlwi-gqwx1znhDEw,12583
5
+ fastquadtree/__init__.pyi,sha256=hoX8-dibrH3Q9kv8M-h0Tac0BsJAoh1VYMd84vayhDE,2137
6
+ fastquadtree/_bimap.py,sha256=SPFFw9hWAVRKZVXyDGYFFNw4MLV8qUlOq8gDbLcTqkg,3402
7
+ fastquadtree/_item.py,sha256=p-ymnE9S-c8Lc00KhNKN9Pt4bd05nZ6g-DB8q8D9zYk,533
8
+ fastquadtree/_native.pyd,sha256=Wz9Z_lSFp6JCgOY5gCYeHl4RwzgCyoyB-ZAgvoWVVhA,247808
9
+ fastquadtree/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
+ fastquadtree-0.5.1.dist-info/RECORD,,
@@ -1,10 +0,0 @@
1
- fastquadtree-0.4.2.dist-info/METADATA,sha256=EMQa7CtuH5DoKCsGXFzxZJvyYjNVjOmz0zCnMnCtVSA,10735
2
- fastquadtree-0.4.2.dist-info/WHEEL,sha256=7bfl5v0wbVhXZba613g0x-n2obNNfpQuN8I1cQ4oaU8,94
3
- fastquadtree-0.4.2.dist-info/licenses/LICENSE,sha256=46IVFhoCIwMo-ocq4olyEB1eBvvtaKic5yGLeKXnDuc,1092
4
- fastquadtree/__init__.py,sha256=rqttCrFBaqMjD7BDZlcwkU9eeOQh2gzRi81xKHHLwLk,12652
5
- fastquadtree/__init__.pyi,sha256=4lKaqtnvylRzhj322ue_y_DO_xxrkzOzjpYnH5SN1Fg,2166
6
- fastquadtree/_bimap.py,sha256=zMp20NJkvVuO3jn2OeVApf7j4siBI4r2kU6SHyb7EZE,3428
7
- fastquadtree/_item.py,sha256=imyiwnbmY21mHsOvNqB4j-TsOPpAmsvb38LKxEL8q4o,526
8
- fastquadtree/_native.pyd,sha256=DwOyfT3BhgOCOV7_vq6EclT0XtYA-7-2_uf9oZPgMSs,309248
9
- fastquadtree/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
- fastquadtree-0.4.2.dist-info/RECORD,,