exonware-xwnode 0.0.1.12__py3-none-any.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.
Files changed (132) hide show
  1. exonware/__init__.py +14 -0
  2. exonware/xwnode/__init__.py +127 -0
  3. exonware/xwnode/base.py +676 -0
  4. exonware/xwnode/config.py +178 -0
  5. exonware/xwnode/contracts.py +730 -0
  6. exonware/xwnode/errors.py +503 -0
  7. exonware/xwnode/facade.py +460 -0
  8. exonware/xwnode/strategies/__init__.py +158 -0
  9. exonware/xwnode/strategies/advisor.py +463 -0
  10. exonware/xwnode/strategies/edges/__init__.py +32 -0
  11. exonware/xwnode/strategies/edges/adj_list.py +227 -0
  12. exonware/xwnode/strategies/edges/adj_matrix.py +391 -0
  13. exonware/xwnode/strategies/edges/base.py +169 -0
  14. exonware/xwnode/strategies/flyweight.py +328 -0
  15. exonware/xwnode/strategies/impls/__init__.py +13 -0
  16. exonware/xwnode/strategies/impls/_base_edge.py +403 -0
  17. exonware/xwnode/strategies/impls/_base_node.py +307 -0
  18. exonware/xwnode/strategies/impls/edge_adj_list.py +353 -0
  19. exonware/xwnode/strategies/impls/edge_adj_matrix.py +445 -0
  20. exonware/xwnode/strategies/impls/edge_bidir_wrapper.py +455 -0
  21. exonware/xwnode/strategies/impls/edge_block_adj_matrix.py +539 -0
  22. exonware/xwnode/strategies/impls/edge_coo.py +533 -0
  23. exonware/xwnode/strategies/impls/edge_csc.py +447 -0
  24. exonware/xwnode/strategies/impls/edge_csr.py +492 -0
  25. exonware/xwnode/strategies/impls/edge_dynamic_adj_list.py +503 -0
  26. exonware/xwnode/strategies/impls/edge_flow_network.py +555 -0
  27. exonware/xwnode/strategies/impls/edge_hyperedge_set.py +516 -0
  28. exonware/xwnode/strategies/impls/edge_neural_graph.py +650 -0
  29. exonware/xwnode/strategies/impls/edge_octree.py +574 -0
  30. exonware/xwnode/strategies/impls/edge_property_store.py +655 -0
  31. exonware/xwnode/strategies/impls/edge_quadtree.py +519 -0
  32. exonware/xwnode/strategies/impls/edge_rtree.py +820 -0
  33. exonware/xwnode/strategies/impls/edge_temporal_edgeset.py +558 -0
  34. exonware/xwnode/strategies/impls/edge_tree_graph_basic.py +271 -0
  35. exonware/xwnode/strategies/impls/edge_weighted_graph.py +411 -0
  36. exonware/xwnode/strategies/manager.py +775 -0
  37. exonware/xwnode/strategies/metrics.py +538 -0
  38. exonware/xwnode/strategies/migration.py +432 -0
  39. exonware/xwnode/strategies/nodes/__init__.py +50 -0
  40. exonware/xwnode/strategies/nodes/_base_node.py +307 -0
  41. exonware/xwnode/strategies/nodes/adjacency_list.py +267 -0
  42. exonware/xwnode/strategies/nodes/aho_corasick.py +345 -0
  43. exonware/xwnode/strategies/nodes/array_list.py +209 -0
  44. exonware/xwnode/strategies/nodes/base.py +247 -0
  45. exonware/xwnode/strategies/nodes/deque.py +200 -0
  46. exonware/xwnode/strategies/nodes/hash_map.py +135 -0
  47. exonware/xwnode/strategies/nodes/heap.py +307 -0
  48. exonware/xwnode/strategies/nodes/linked_list.py +232 -0
  49. exonware/xwnode/strategies/nodes/node_aho_corasick.py +520 -0
  50. exonware/xwnode/strategies/nodes/node_array_list.py +175 -0
  51. exonware/xwnode/strategies/nodes/node_avl_tree.py +371 -0
  52. exonware/xwnode/strategies/nodes/node_b_plus_tree.py +542 -0
  53. exonware/xwnode/strategies/nodes/node_bitmap.py +420 -0
  54. exonware/xwnode/strategies/nodes/node_bitset_dynamic.py +513 -0
  55. exonware/xwnode/strategies/nodes/node_bloom_filter.py +347 -0
  56. exonware/xwnode/strategies/nodes/node_btree.py +357 -0
  57. exonware/xwnode/strategies/nodes/node_count_min_sketch.py +470 -0
  58. exonware/xwnode/strategies/nodes/node_cow_tree.py +473 -0
  59. exonware/xwnode/strategies/nodes/node_cuckoo_hash.py +392 -0
  60. exonware/xwnode/strategies/nodes/node_fenwick_tree.py +301 -0
  61. exonware/xwnode/strategies/nodes/node_hash_map.py +269 -0
  62. exonware/xwnode/strategies/nodes/node_heap.py +191 -0
  63. exonware/xwnode/strategies/nodes/node_hyperloglog.py +407 -0
  64. exonware/xwnode/strategies/nodes/node_linked_list.py +409 -0
  65. exonware/xwnode/strategies/nodes/node_lsm_tree.py +400 -0
  66. exonware/xwnode/strategies/nodes/node_ordered_map.py +390 -0
  67. exonware/xwnode/strategies/nodes/node_ordered_map_balanced.py +565 -0
  68. exonware/xwnode/strategies/nodes/node_patricia.py +512 -0
  69. exonware/xwnode/strategies/nodes/node_persistent_tree.py +378 -0
  70. exonware/xwnode/strategies/nodes/node_radix_trie.py +452 -0
  71. exonware/xwnode/strategies/nodes/node_red_black_tree.py +497 -0
  72. exonware/xwnode/strategies/nodes/node_roaring_bitmap.py +570 -0
  73. exonware/xwnode/strategies/nodes/node_segment_tree.py +289 -0
  74. exonware/xwnode/strategies/nodes/node_set_hash.py +354 -0
  75. exonware/xwnode/strategies/nodes/node_set_tree.py +480 -0
  76. exonware/xwnode/strategies/nodes/node_skip_list.py +316 -0
  77. exonware/xwnode/strategies/nodes/node_splay_tree.py +393 -0
  78. exonware/xwnode/strategies/nodes/node_suffix_array.py +487 -0
  79. exonware/xwnode/strategies/nodes/node_treap.py +387 -0
  80. exonware/xwnode/strategies/nodes/node_tree_graph_hybrid.py +1434 -0
  81. exonware/xwnode/strategies/nodes/node_trie.py +252 -0
  82. exonware/xwnode/strategies/nodes/node_union_find.py +187 -0
  83. exonware/xwnode/strategies/nodes/node_xdata_optimized.py +369 -0
  84. exonware/xwnode/strategies/nodes/priority_queue.py +209 -0
  85. exonware/xwnode/strategies/nodes/queue.py +161 -0
  86. exonware/xwnode/strategies/nodes/sparse_matrix.py +206 -0
  87. exonware/xwnode/strategies/nodes/stack.py +152 -0
  88. exonware/xwnode/strategies/nodes/trie.py +274 -0
  89. exonware/xwnode/strategies/nodes/union_find.py +283 -0
  90. exonware/xwnode/strategies/pattern_detector.py +603 -0
  91. exonware/xwnode/strategies/performance_monitor.py +487 -0
  92. exonware/xwnode/strategies/queries/__init__.py +24 -0
  93. exonware/xwnode/strategies/queries/base.py +236 -0
  94. exonware/xwnode/strategies/queries/cql.py +201 -0
  95. exonware/xwnode/strategies/queries/cypher.py +181 -0
  96. exonware/xwnode/strategies/queries/datalog.py +70 -0
  97. exonware/xwnode/strategies/queries/elastic_dsl.py +70 -0
  98. exonware/xwnode/strategies/queries/eql.py +70 -0
  99. exonware/xwnode/strategies/queries/flux.py +70 -0
  100. exonware/xwnode/strategies/queries/gql.py +70 -0
  101. exonware/xwnode/strategies/queries/graphql.py +240 -0
  102. exonware/xwnode/strategies/queries/gremlin.py +181 -0
  103. exonware/xwnode/strategies/queries/hiveql.py +214 -0
  104. exonware/xwnode/strategies/queries/hql.py +70 -0
  105. exonware/xwnode/strategies/queries/jmespath.py +219 -0
  106. exonware/xwnode/strategies/queries/jq.py +66 -0
  107. exonware/xwnode/strategies/queries/json_query.py +66 -0
  108. exonware/xwnode/strategies/queries/jsoniq.py +248 -0
  109. exonware/xwnode/strategies/queries/kql.py +70 -0
  110. exonware/xwnode/strategies/queries/linq.py +238 -0
  111. exonware/xwnode/strategies/queries/logql.py +70 -0
  112. exonware/xwnode/strategies/queries/mql.py +68 -0
  113. exonware/xwnode/strategies/queries/n1ql.py +210 -0
  114. exonware/xwnode/strategies/queries/partiql.py +70 -0
  115. exonware/xwnode/strategies/queries/pig.py +215 -0
  116. exonware/xwnode/strategies/queries/promql.py +70 -0
  117. exonware/xwnode/strategies/queries/sparql.py +220 -0
  118. exonware/xwnode/strategies/queries/sql.py +275 -0
  119. exonware/xwnode/strategies/queries/xml_query.py +66 -0
  120. exonware/xwnode/strategies/queries/xpath.py +223 -0
  121. exonware/xwnode/strategies/queries/xquery.py +258 -0
  122. exonware/xwnode/strategies/queries/xwnode_executor.py +332 -0
  123. exonware/xwnode/strategies/queries/xwquery_strategy.py +424 -0
  124. exonware/xwnode/strategies/registry.py +604 -0
  125. exonware/xwnode/strategies/simple.py +273 -0
  126. exonware/xwnode/strategies/utils.py +532 -0
  127. exonware/xwnode/types.py +912 -0
  128. exonware/xwnode/version.py +78 -0
  129. exonware_xwnode-0.0.1.12.dist-info/METADATA +169 -0
  130. exonware_xwnode-0.0.1.12.dist-info/RECORD +132 -0
  131. exonware_xwnode-0.0.1.12.dist-info/WHEEL +4 -0
  132. exonware_xwnode-0.0.1.12.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,519 @@
1
+ """
2
+ Quadtree Edge Strategy Implementation
3
+
4
+ This module implements the QUADTREE strategy for 2D spatial
5
+ graph partitioning and efficient spatial queries.
6
+ """
7
+
8
+ from typing import Any, Iterator, List, Dict, Set, Optional, Tuple
9
+ from collections import defaultdict
10
+ import math
11
+ from ._base_edge import aEdgeStrategy
12
+ from ...types import EdgeMode, EdgeTrait
13
+
14
+
15
+ class QuadTreeNode:
16
+ """Node in the quadtree."""
17
+
18
+ def __init__(self, x: float, y: float, width: float, height: float, capacity: int = 4):
19
+ self.x = x # Bottom-left corner
20
+ self.y = y
21
+ self.width = width
22
+ self.height = height
23
+ self.capacity = capacity
24
+
25
+ # Points stored in this node
26
+ self.points: List[Tuple[float, float, str]] = [] # (x, y, vertex_id)
27
+
28
+ # Child nodes (NW, NE, SW, SE)
29
+ self.children: List[Optional['QuadTreeNode']] = [None, None, None, None]
30
+ self.is_leaf = True
31
+
32
+ def contains_point(self, x: float, y: float) -> bool:
33
+ """Check if point is within this node's bounds."""
34
+ return (self.x <= x < self.x + self.width and
35
+ self.y <= y < self.y + self.height)
36
+
37
+ def intersects_rect(self, rect_x: float, rect_y: float, rect_w: float, rect_h: float) -> bool:
38
+ """Check if this node intersects with given rectangle."""
39
+ return not (rect_x >= self.x + self.width or
40
+ rect_x + rect_w <= self.x or
41
+ rect_y >= self.y + self.height or
42
+ rect_y + rect_h <= self.y)
43
+
44
+ def subdivide(self) -> None:
45
+ """Subdivide this node into four children."""
46
+ if not self.is_leaf:
47
+ return
48
+
49
+ half_w = self.width / 2
50
+ half_h = self.height / 2
51
+
52
+ # Create four children (NW, NE, SW, SE)
53
+ self.children[0] = QuadTreeNode(self.x, self.y + half_h, half_w, half_h, self.capacity) # NW
54
+ self.children[1] = QuadTreeNode(self.x + half_w, self.y + half_h, half_w, half_h, self.capacity) # NE
55
+ self.children[2] = QuadTreeNode(self.x, self.y, half_w, half_h, self.capacity) # SW
56
+ self.children[3] = QuadTreeNode(self.x + half_w, self.y, half_w, half_h, self.capacity) # SE
57
+
58
+ self.is_leaf = False
59
+
60
+ # Redistribute points to children
61
+ for point in self.points:
62
+ x, y, vertex_id = point
63
+ for child in self.children:
64
+ if child and child.contains_point(x, y):
65
+ child.insert(x, y, vertex_id)
66
+ break
67
+
68
+ self.points.clear()
69
+
70
+ def insert(self, x: float, y: float, vertex_id: str) -> bool:
71
+ """Insert point into quadtree."""
72
+ if not self.contains_point(x, y):
73
+ return False
74
+
75
+ if self.is_leaf:
76
+ self.points.append((x, y, vertex_id))
77
+
78
+ # Subdivide if capacity exceeded
79
+ if len(self.points) > self.capacity:
80
+ self.subdivide()
81
+
82
+ return True
83
+ else:
84
+ # Insert into appropriate child
85
+ for child in self.children:
86
+ if child and child.insert(x, y, vertex_id):
87
+ return True
88
+ return False
89
+
90
+ def query_range(self, rect_x: float, rect_y: float, rect_w: float, rect_h: float) -> List[Tuple[float, float, str]]:
91
+ """Query points within given rectangle."""
92
+ result = []
93
+
94
+ if not self.intersects_rect(rect_x, rect_y, rect_w, rect_h):
95
+ return result
96
+
97
+ if self.is_leaf:
98
+ for x, y, vertex_id in self.points:
99
+ if rect_x <= x < rect_x + rect_w and rect_y <= y < rect_y + rect_h:
100
+ result.append((x, y, vertex_id))
101
+ else:
102
+ for child in self.children:
103
+ if child:
104
+ result.extend(child.query_range(rect_x, rect_y, rect_w, rect_h))
105
+
106
+ return result
107
+
108
+ def query_radius(self, center_x: float, center_y: float, radius: float) -> List[Tuple[float, float, str]]:
109
+ """Query points within given radius."""
110
+ # Convert circle to bounding rectangle for initial filtering
111
+ rect_x = center_x - radius
112
+ rect_y = center_y - radius
113
+ rect_w = rect_h = 2 * radius
114
+
115
+ candidates = self.query_range(rect_x, rect_y, rect_w, rect_h)
116
+
117
+ # Filter by actual distance
118
+ result = []
119
+ radius_sq = radius * radius
120
+
121
+ for x, y, vertex_id in candidates:
122
+ dist_sq = (x - center_x) ** 2 + (y - center_y) ** 2
123
+ if dist_sq <= radius_sq:
124
+ result.append((x, y, vertex_id))
125
+
126
+ return result
127
+
128
+ def remove(self, x: float, y: float, vertex_id: str) -> bool:
129
+ """Remove point from quadtree."""
130
+ if not self.contains_point(x, y):
131
+ return False
132
+
133
+ if self.is_leaf:
134
+ for i, point in enumerate(self.points):
135
+ if point[0] == x and point[1] == y and point[2] == vertex_id:
136
+ del self.points[i]
137
+ return True
138
+ return False
139
+ else:
140
+ for child in self.children:
141
+ if child and child.remove(x, y, vertex_id):
142
+ return True
143
+ return False
144
+
145
+
146
+ class xQuadtreeStrategy(aEdgeStrategy):
147
+ """
148
+ Quadtree edge strategy for 2D spatial graphs.
149
+
150
+ Efficiently manages spatial relationships with logarithmic
151
+ complexity for spatial queries and nearest neighbor searches.
152
+ """
153
+
154
+ def __init__(self, traits: EdgeTrait = EdgeTrait.NONE, **options):
155
+ """Initialize the Quadtree strategy."""
156
+ super().__init__(EdgeMode.QUADTREE, traits, **options)
157
+
158
+ # Spatial bounds
159
+ self.bounds_x = options.get('bounds_x', 0.0)
160
+ self.bounds_y = options.get('bounds_y', 0.0)
161
+ self.bounds_width = options.get('bounds_width', 1000.0)
162
+ self.bounds_height = options.get('bounds_height', 1000.0)
163
+ self.capacity = options.get('capacity', 4)
164
+
165
+ # Core quadtree
166
+ self._root = QuadTreeNode(self.bounds_x, self.bounds_y,
167
+ self.bounds_width, self.bounds_height, self.capacity)
168
+
169
+ # Vertex management
170
+ self._vertices: Dict[str, Tuple[float, float]] = {} # vertex_id -> (x, y)
171
+ self._edges: Dict[Tuple[str, str], Dict[str, Any]] = {} # (source, target) -> properties
172
+ self._spatial_edges: Set[Tuple[str, str]] = set() # Edges based on spatial proximity
173
+
174
+ # Performance tracking
175
+ self._edge_count = 0
176
+ self._spatial_threshold = options.get('spatial_threshold', 50.0) # Auto-connect distance
177
+
178
+ def get_supported_traits(self) -> EdgeTrait:
179
+ """Get the traits supported by the quadtree strategy."""
180
+ return (EdgeTrait.SPATIAL | EdgeTrait.SPARSE | EdgeTrait.CACHE_FRIENDLY)
181
+
182
+ def _auto_connect_spatial(self, vertex: str, x: float, y: float) -> None:
183
+ """Automatically connect vertex to nearby vertices."""
184
+ if self._spatial_threshold <= 0:
185
+ return
186
+
187
+ # Find nearby vertices
188
+ nearby = self._root.query_radius(x, y, self._spatial_threshold)
189
+
190
+ for nx, ny, neighbor_id in nearby:
191
+ if neighbor_id != vertex:
192
+ # Calculate distance
193
+ distance = math.sqrt((x - nx) ** 2 + (y - ny) ** 2)
194
+
195
+ # Add spatial edge
196
+ edge_key = (min(vertex, neighbor_id), max(vertex, neighbor_id))
197
+ if edge_key not in self._edges:
198
+ self._edges[edge_key] = {
199
+ 'distance': distance,
200
+ 'spatial': True,
201
+ 'weight': 1.0 / (1.0 + distance) # Inverse distance weight
202
+ }
203
+ self._spatial_edges.add(edge_key)
204
+ self._edge_count += 1
205
+
206
+ # ============================================================================
207
+ # CORE EDGE OPERATIONS
208
+ # ============================================================================
209
+
210
+ def add_edge(self, source: str, target: str, **properties) -> str:
211
+ """Add edge between spatial vertices."""
212
+ # Ensure vertices exist with positions
213
+ if source not in self._vertices:
214
+ # Assign random position if not specified
215
+ x = properties.get('source_x', self.bounds_x + self.bounds_width * 0.5)
216
+ y = properties.get('source_y', self.bounds_y + self.bounds_height * 0.5)
217
+ self.add_spatial_vertex(source, x, y)
218
+
219
+ if target not in self._vertices:
220
+ x = properties.get('target_x', self.bounds_x + self.bounds_width * 0.5)
221
+ y = properties.get('target_y', self.bounds_y + self.bounds_height * 0.5)
222
+ self.add_spatial_vertex(target, x, y)
223
+
224
+ # Calculate distance
225
+ sx, sy = self._vertices[source]
226
+ tx, ty = self._vertices[target]
227
+ distance = math.sqrt((sx - tx) ** 2 + (sy - ty) ** 2)
228
+
229
+ # Add edge
230
+ edge_key = (min(source, target), max(source, target))
231
+ self._edges[edge_key] = {
232
+ 'distance': distance,
233
+ 'spatial': properties.get('spatial', False),
234
+ 'weight': properties.get('weight', 1.0),
235
+ **properties
236
+ }
237
+
238
+ if edge_key not in self._spatial_edges:
239
+ self._edge_count += 1
240
+
241
+ return f"{source}<->{target}"
242
+
243
+ def remove_edge(self, source: str, target: str, edge_id: Optional[str] = None) -> bool:
244
+ """Remove edge between vertices."""
245
+ edge_key = (min(source, target), max(source, target))
246
+
247
+ if edge_key in self._edges:
248
+ del self._edges[edge_key]
249
+ self._spatial_edges.discard(edge_key)
250
+ self._edge_count -= 1
251
+ return True
252
+
253
+ return False
254
+
255
+ def has_edge(self, source: str, target: str) -> bool:
256
+ """Check if edge exists."""
257
+ edge_key = (min(source, target), max(source, target))
258
+ return edge_key in self._edges
259
+
260
+ def get_edge_data(self, source: str, target: str) -> Optional[Dict[str, Any]]:
261
+ """Get edge data."""
262
+ edge_key = (min(source, target), max(source, target))
263
+ return self._edges.get(edge_key)
264
+
265
+ def neighbors(self, vertex: str, direction: str = 'both') -> Iterator[str]:
266
+ """Get neighbors of vertex."""
267
+ for (v1, v2) in self._edges:
268
+ if v1 == vertex:
269
+ yield v2
270
+ elif v2 == vertex:
271
+ yield v1
272
+
273
+ def degree(self, vertex: str, direction: str = 'both') -> int:
274
+ """Get degree of vertex."""
275
+ return len(list(self.neighbors(vertex, direction)))
276
+
277
+ def edges(self, data: bool = False) -> Iterator[tuple]:
278
+ """Get all edges."""
279
+ for (source, target), edge_data in self._edges.items():
280
+ if data:
281
+ yield (source, target, edge_data)
282
+ else:
283
+ yield (source, target)
284
+
285
+ def vertices(self) -> Iterator[str]:
286
+ """Get all vertices."""
287
+ return iter(self._vertices.keys())
288
+
289
+ def __len__(self) -> int:
290
+ """Get number of edges."""
291
+ return self._edge_count
292
+
293
+ def vertex_count(self) -> int:
294
+ """Get number of vertices."""
295
+ return len(self._vertices)
296
+
297
+ def clear(self) -> None:
298
+ """Clear all data."""
299
+ self._root = QuadTreeNode(self.bounds_x, self.bounds_y,
300
+ self.bounds_width, self.bounds_height, self.capacity)
301
+ self._vertices.clear()
302
+ self._edges.clear()
303
+ self._spatial_edges.clear()
304
+ self._edge_count = 0
305
+
306
+ def add_vertex(self, vertex: str) -> None:
307
+ """Add vertex at random position."""
308
+ if vertex not in self._vertices:
309
+ # Random position within bounds
310
+ import random
311
+ x = self.bounds_x + random.random() * self.bounds_width
312
+ y = self.bounds_y + random.random() * self.bounds_height
313
+ self.add_spatial_vertex(vertex, x, y)
314
+
315
+ def remove_vertex(self, vertex: str) -> bool:
316
+ """Remove vertex and all its edges."""
317
+ if vertex not in self._vertices:
318
+ return False
319
+
320
+ # Remove from quadtree
321
+ x, y = self._vertices[vertex]
322
+ self._root.remove(x, y, vertex)
323
+
324
+ # Remove all edges
325
+ edges_to_remove = []
326
+ for (v1, v2) in self._edges:
327
+ if v1 == vertex or v2 == vertex:
328
+ edges_to_remove.append((v1, v2))
329
+
330
+ for edge_key in edges_to_remove:
331
+ del self._edges[edge_key]
332
+ self._spatial_edges.discard(edge_key)
333
+ self._edge_count -= 1
334
+
335
+ # Remove vertex
336
+ del self._vertices[vertex]
337
+ return True
338
+
339
+ # ============================================================================
340
+ # SPATIAL OPERATIONS
341
+ # ============================================================================
342
+
343
+ def add_spatial_vertex(self, vertex: str, x: float, y: float) -> None:
344
+ """Add vertex at specific spatial position."""
345
+ # Remove old position if exists
346
+ if vertex in self._vertices:
347
+ old_x, old_y = self._vertices[vertex]
348
+ self._root.remove(old_x, old_y, vertex)
349
+
350
+ # Add to quadtree
351
+ self._vertices[vertex] = (x, y)
352
+ self._root.insert(x, y, vertex)
353
+
354
+ # Auto-connect to nearby vertices
355
+ self._auto_connect_spatial(vertex, x, y)
356
+
357
+ def get_vertex_position(self, vertex: str) -> Optional[Tuple[float, float]]:
358
+ """Get vertex position."""
359
+ return self._vertices.get(vertex)
360
+
361
+ def set_vertex_position(self, vertex: str, x: float, y: float) -> None:
362
+ """Update vertex position."""
363
+ self.add_spatial_vertex(vertex, x, y)
364
+
365
+ def query_range(self, x: float, y: float, width: float, height: float) -> List[str]:
366
+ """Query vertices within rectangular range."""
367
+ points = self._root.query_range(x, y, width, height)
368
+ return [vertex_id for _, _, vertex_id in points]
369
+
370
+ def query_radius(self, center_x: float, center_y: float, radius: float) -> List[str]:
371
+ """Query vertices within circular range."""
372
+ points = self._root.query_radius(center_x, center_y, radius)
373
+ return [vertex_id for _, _, vertex_id in points]
374
+
375
+ def nearest_neighbors(self, vertex: str, k: int = 1) -> List[Tuple[str, float]]:
376
+ """Find k nearest neighbors to vertex."""
377
+ if vertex not in self._vertices:
378
+ return []
379
+
380
+ x, y = self._vertices[vertex]
381
+
382
+ # Query expanding radius until we have enough candidates
383
+ radius = 10.0
384
+ candidates = []
385
+
386
+ while len(candidates) < k * 2 and radius <= self.bounds_width:
387
+ candidates = self.query_radius(x, y, radius)
388
+ candidates = [v for v in candidates if v != vertex]
389
+ radius *= 2
390
+
391
+ # Calculate distances and sort
392
+ distances = []
393
+ for neighbor in candidates:
394
+ nx, ny = self._vertices[neighbor]
395
+ dist = math.sqrt((x - nx) ** 2 + (y - ny) ** 2)
396
+ distances.append((neighbor, dist))
397
+
398
+ distances.sort(key=lambda x: x[1])
399
+ return distances[:k]
400
+
401
+ def get_spatial_edges_in_range(self, x: float, y: float, width: float, height: float) -> List[Tuple[str, str]]:
402
+ """Get edges where both vertices are in given range."""
403
+ vertices_in_range = set(self.query_range(x, y, width, height))
404
+
405
+ spatial_edges = []
406
+ for (v1, v2) in self._edges:
407
+ if v1 in vertices_in_range and v2 in vertices_in_range:
408
+ spatial_edges.append((v1, v2))
409
+
410
+ return spatial_edges
411
+
412
+ def cluster_vertices(self, max_distance: float) -> List[List[str]]:
413
+ """Cluster vertices based on spatial proximity."""
414
+ visited = set()
415
+ clusters = []
416
+
417
+ for vertex in self._vertices:
418
+ if vertex in visited:
419
+ continue
420
+
421
+ # Start new cluster
422
+ cluster = []
423
+ stack = [vertex]
424
+
425
+ while stack:
426
+ current = stack.pop()
427
+ if current in visited:
428
+ continue
429
+
430
+ visited.add(current)
431
+ cluster.append(current)
432
+
433
+ # Find nearby vertices
434
+ x, y = self._vertices[current]
435
+ nearby = self.query_radius(x, y, max_distance)
436
+
437
+ for neighbor in nearby:
438
+ if neighbor not in visited:
439
+ stack.append(neighbor)
440
+
441
+ if cluster:
442
+ clusters.append(cluster)
443
+
444
+ return clusters
445
+
446
+ def get_spatial_statistics(self) -> Dict[str, Any]:
447
+ """Get comprehensive spatial statistics."""
448
+ if not self._vertices:
449
+ return {'vertices': 0, 'edges': 0, 'spatial_density': 0}
450
+
451
+ # Calculate spatial extent
452
+ positions = list(self._vertices.values())
453
+ min_x = min(pos[0] for pos in positions)
454
+ max_x = max(pos[0] for pos in positions)
455
+ min_y = min(pos[1] for pos in positions)
456
+ max_y = max(pos[1] for pos in positions)
457
+
458
+ spatial_area = (max_x - min_x) * (max_y - min_y) if len(positions) > 1 else 0
459
+ vertex_density = len(self._vertices) / max(1, spatial_area)
460
+
461
+ # Edge length statistics
462
+ edge_lengths = []
463
+ for (v1, v2), edge_data in self._edges.items():
464
+ edge_lengths.append(edge_data.get('distance', 0))
465
+
466
+ avg_edge_length = sum(edge_lengths) / len(edge_lengths) if edge_lengths else 0
467
+ max_edge_length = max(edge_lengths) if edge_lengths else 0
468
+ min_edge_length = min(edge_lengths) if edge_lengths else 0
469
+
470
+ return {
471
+ 'vertices': len(self._vertices),
472
+ 'edges': self._edge_count,
473
+ 'spatial_edges': len(self._spatial_edges),
474
+ 'spatial_extent': (max_x - min_x, max_y - min_y),
475
+ 'spatial_area': spatial_area,
476
+ 'vertex_density': vertex_density,
477
+ 'avg_edge_length': avg_edge_length,
478
+ 'min_edge_length': min_edge_length,
479
+ 'max_edge_length': max_edge_length,
480
+ 'spatial_threshold': self._spatial_threshold,
481
+ 'quadtree_capacity': self.capacity
482
+ }
483
+
484
+ # ============================================================================
485
+ # PERFORMANCE CHARACTERISTICS
486
+ # ============================================================================
487
+
488
+ @property
489
+ def backend_info(self) -> Dict[str, Any]:
490
+ """Get backend implementation info."""
491
+ return {
492
+ 'strategy': 'QUADTREE',
493
+ 'backend': '2D spatial partitioning with quadtree',
494
+ 'bounds': f"({self.bounds_x}, {self.bounds_y}, {self.bounds_width}, {self.bounds_height})",
495
+ 'capacity': self.capacity,
496
+ 'spatial_threshold': self._spatial_threshold,
497
+ 'complexity': {
498
+ 'insert': 'O(log n)',
499
+ 'remove': 'O(log n)',
500
+ 'range_query': 'O(log n + k)', # k = results
501
+ 'nearest_neighbor': 'O(log n + k)',
502
+ 'space': 'O(n)'
503
+ }
504
+ }
505
+
506
+ @property
507
+ def metrics(self) -> Dict[str, Any]:
508
+ """Get performance metrics."""
509
+ stats = self.get_spatial_statistics()
510
+
511
+ return {
512
+ 'vertices': stats['vertices'],
513
+ 'edges': stats['edges'],
514
+ 'spatial_edges': stats['spatial_edges'],
515
+ 'vertex_density': f"{stats['vertex_density']:.2f}",
516
+ 'avg_edge_length': f"{stats['avg_edge_length']:.1f}",
517
+ 'spatial_area': f"{stats['spatial_area']:.1f}",
518
+ 'memory_usage': f"{self._edge_count * 80 + len(self._vertices) * 100} bytes (estimated)"
519
+ }