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.
- exonware/__init__.py +14 -0
- exonware/xwnode/__init__.py +127 -0
- exonware/xwnode/base.py +676 -0
- exonware/xwnode/config.py +178 -0
- exonware/xwnode/contracts.py +730 -0
- exonware/xwnode/errors.py +503 -0
- exonware/xwnode/facade.py +460 -0
- exonware/xwnode/strategies/__init__.py +158 -0
- exonware/xwnode/strategies/advisor.py +463 -0
- exonware/xwnode/strategies/edges/__init__.py +32 -0
- exonware/xwnode/strategies/edges/adj_list.py +227 -0
- exonware/xwnode/strategies/edges/adj_matrix.py +391 -0
- exonware/xwnode/strategies/edges/base.py +169 -0
- exonware/xwnode/strategies/flyweight.py +328 -0
- exonware/xwnode/strategies/impls/__init__.py +13 -0
- exonware/xwnode/strategies/impls/_base_edge.py +403 -0
- exonware/xwnode/strategies/impls/_base_node.py +307 -0
- exonware/xwnode/strategies/impls/edge_adj_list.py +353 -0
- exonware/xwnode/strategies/impls/edge_adj_matrix.py +445 -0
- exonware/xwnode/strategies/impls/edge_bidir_wrapper.py +455 -0
- exonware/xwnode/strategies/impls/edge_block_adj_matrix.py +539 -0
- exonware/xwnode/strategies/impls/edge_coo.py +533 -0
- exonware/xwnode/strategies/impls/edge_csc.py +447 -0
- exonware/xwnode/strategies/impls/edge_csr.py +492 -0
- exonware/xwnode/strategies/impls/edge_dynamic_adj_list.py +503 -0
- exonware/xwnode/strategies/impls/edge_flow_network.py +555 -0
- exonware/xwnode/strategies/impls/edge_hyperedge_set.py +516 -0
- exonware/xwnode/strategies/impls/edge_neural_graph.py +650 -0
- exonware/xwnode/strategies/impls/edge_octree.py +574 -0
- exonware/xwnode/strategies/impls/edge_property_store.py +655 -0
- exonware/xwnode/strategies/impls/edge_quadtree.py +519 -0
- exonware/xwnode/strategies/impls/edge_rtree.py +820 -0
- exonware/xwnode/strategies/impls/edge_temporal_edgeset.py +558 -0
- exonware/xwnode/strategies/impls/edge_tree_graph_basic.py +271 -0
- exonware/xwnode/strategies/impls/edge_weighted_graph.py +411 -0
- exonware/xwnode/strategies/manager.py +775 -0
- exonware/xwnode/strategies/metrics.py +538 -0
- exonware/xwnode/strategies/migration.py +432 -0
- exonware/xwnode/strategies/nodes/__init__.py +50 -0
- exonware/xwnode/strategies/nodes/_base_node.py +307 -0
- exonware/xwnode/strategies/nodes/adjacency_list.py +267 -0
- exonware/xwnode/strategies/nodes/aho_corasick.py +345 -0
- exonware/xwnode/strategies/nodes/array_list.py +209 -0
- exonware/xwnode/strategies/nodes/base.py +247 -0
- exonware/xwnode/strategies/nodes/deque.py +200 -0
- exonware/xwnode/strategies/nodes/hash_map.py +135 -0
- exonware/xwnode/strategies/nodes/heap.py +307 -0
- exonware/xwnode/strategies/nodes/linked_list.py +232 -0
- exonware/xwnode/strategies/nodes/node_aho_corasick.py +520 -0
- exonware/xwnode/strategies/nodes/node_array_list.py +175 -0
- exonware/xwnode/strategies/nodes/node_avl_tree.py +371 -0
- exonware/xwnode/strategies/nodes/node_b_plus_tree.py +542 -0
- exonware/xwnode/strategies/nodes/node_bitmap.py +420 -0
- exonware/xwnode/strategies/nodes/node_bitset_dynamic.py +513 -0
- exonware/xwnode/strategies/nodes/node_bloom_filter.py +347 -0
- exonware/xwnode/strategies/nodes/node_btree.py +357 -0
- exonware/xwnode/strategies/nodes/node_count_min_sketch.py +470 -0
- exonware/xwnode/strategies/nodes/node_cow_tree.py +473 -0
- exonware/xwnode/strategies/nodes/node_cuckoo_hash.py +392 -0
- exonware/xwnode/strategies/nodes/node_fenwick_tree.py +301 -0
- exonware/xwnode/strategies/nodes/node_hash_map.py +269 -0
- exonware/xwnode/strategies/nodes/node_heap.py +191 -0
- exonware/xwnode/strategies/nodes/node_hyperloglog.py +407 -0
- exonware/xwnode/strategies/nodes/node_linked_list.py +409 -0
- exonware/xwnode/strategies/nodes/node_lsm_tree.py +400 -0
- exonware/xwnode/strategies/nodes/node_ordered_map.py +390 -0
- exonware/xwnode/strategies/nodes/node_ordered_map_balanced.py +565 -0
- exonware/xwnode/strategies/nodes/node_patricia.py +512 -0
- exonware/xwnode/strategies/nodes/node_persistent_tree.py +378 -0
- exonware/xwnode/strategies/nodes/node_radix_trie.py +452 -0
- exonware/xwnode/strategies/nodes/node_red_black_tree.py +497 -0
- exonware/xwnode/strategies/nodes/node_roaring_bitmap.py +570 -0
- exonware/xwnode/strategies/nodes/node_segment_tree.py +289 -0
- exonware/xwnode/strategies/nodes/node_set_hash.py +354 -0
- exonware/xwnode/strategies/nodes/node_set_tree.py +480 -0
- exonware/xwnode/strategies/nodes/node_skip_list.py +316 -0
- exonware/xwnode/strategies/nodes/node_splay_tree.py +393 -0
- exonware/xwnode/strategies/nodes/node_suffix_array.py +487 -0
- exonware/xwnode/strategies/nodes/node_treap.py +387 -0
- exonware/xwnode/strategies/nodes/node_tree_graph_hybrid.py +1434 -0
- exonware/xwnode/strategies/nodes/node_trie.py +252 -0
- exonware/xwnode/strategies/nodes/node_union_find.py +187 -0
- exonware/xwnode/strategies/nodes/node_xdata_optimized.py +369 -0
- exonware/xwnode/strategies/nodes/priority_queue.py +209 -0
- exonware/xwnode/strategies/nodes/queue.py +161 -0
- exonware/xwnode/strategies/nodes/sparse_matrix.py +206 -0
- exonware/xwnode/strategies/nodes/stack.py +152 -0
- exonware/xwnode/strategies/nodes/trie.py +274 -0
- exonware/xwnode/strategies/nodes/union_find.py +283 -0
- exonware/xwnode/strategies/pattern_detector.py +603 -0
- exonware/xwnode/strategies/performance_monitor.py +487 -0
- exonware/xwnode/strategies/queries/__init__.py +24 -0
- exonware/xwnode/strategies/queries/base.py +236 -0
- exonware/xwnode/strategies/queries/cql.py +201 -0
- exonware/xwnode/strategies/queries/cypher.py +181 -0
- exonware/xwnode/strategies/queries/datalog.py +70 -0
- exonware/xwnode/strategies/queries/elastic_dsl.py +70 -0
- exonware/xwnode/strategies/queries/eql.py +70 -0
- exonware/xwnode/strategies/queries/flux.py +70 -0
- exonware/xwnode/strategies/queries/gql.py +70 -0
- exonware/xwnode/strategies/queries/graphql.py +240 -0
- exonware/xwnode/strategies/queries/gremlin.py +181 -0
- exonware/xwnode/strategies/queries/hiveql.py +214 -0
- exonware/xwnode/strategies/queries/hql.py +70 -0
- exonware/xwnode/strategies/queries/jmespath.py +219 -0
- exonware/xwnode/strategies/queries/jq.py +66 -0
- exonware/xwnode/strategies/queries/json_query.py +66 -0
- exonware/xwnode/strategies/queries/jsoniq.py +248 -0
- exonware/xwnode/strategies/queries/kql.py +70 -0
- exonware/xwnode/strategies/queries/linq.py +238 -0
- exonware/xwnode/strategies/queries/logql.py +70 -0
- exonware/xwnode/strategies/queries/mql.py +68 -0
- exonware/xwnode/strategies/queries/n1ql.py +210 -0
- exonware/xwnode/strategies/queries/partiql.py +70 -0
- exonware/xwnode/strategies/queries/pig.py +215 -0
- exonware/xwnode/strategies/queries/promql.py +70 -0
- exonware/xwnode/strategies/queries/sparql.py +220 -0
- exonware/xwnode/strategies/queries/sql.py +275 -0
- exonware/xwnode/strategies/queries/xml_query.py +66 -0
- exonware/xwnode/strategies/queries/xpath.py +223 -0
- exonware/xwnode/strategies/queries/xquery.py +258 -0
- exonware/xwnode/strategies/queries/xwnode_executor.py +332 -0
- exonware/xwnode/strategies/queries/xwquery_strategy.py +424 -0
- exonware/xwnode/strategies/registry.py +604 -0
- exonware/xwnode/strategies/simple.py +273 -0
- exonware/xwnode/strategies/utils.py +532 -0
- exonware/xwnode/types.py +912 -0
- exonware/xwnode/version.py +78 -0
- exonware_xwnode-0.0.1.12.dist-info/METADATA +169 -0
- exonware_xwnode-0.0.1.12.dist-info/RECORD +132 -0
- exonware_xwnode-0.0.1.12.dist-info/WHEEL +4 -0
- 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
|
+
}
|