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,574 @@
1
+ """
2
+ Octree Edge Strategy Implementation
3
+
4
+ This module implements the OCTREE strategy for 3D spatial
5
+ graph partitioning and efficient 3D 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 OctreeNode:
16
+ """Node in the octree (3D spatial partitioning)."""
17
+
18
+ def __init__(self, x: float, y: float, z: float, size: float, capacity: int = 8):
19
+ self.x = x # Center coordinates
20
+ self.y = y
21
+ self.z = z
22
+ self.size = size # Half-size of the cube
23
+ self.capacity = capacity
24
+
25
+ # Points stored in this node
26
+ self.points: List[Tuple[float, float, float, str]] = [] # (x, y, z, vertex_id)
27
+
28
+ # Child nodes (8 octants)
29
+ self.children: List[Optional['OctreeNode']] = [None] * 8
30
+ self.is_leaf = True
31
+
32
+ def contains_point(self, x: float, y: float, z: float) -> bool:
33
+ """Check if point is within this node's bounds."""
34
+ return (self.x - self.size <= x < self.x + self.size and
35
+ self.y - self.size <= y < self.y + self.size and
36
+ self.z - self.size <= z < self.z + self.size)
37
+
38
+ def intersects_box(self, box_x: float, box_y: float, box_z: float,
39
+ box_w: float, box_h: float, box_d: float) -> bool:
40
+ """Check if this node intersects with given box."""
41
+ return not (box_x >= self.x + self.size or
42
+ box_x + box_w <= self.x - self.size or
43
+ box_y >= self.y + self.size or
44
+ box_y + box_h <= self.y - self.size or
45
+ box_z >= self.z + self.size or
46
+ box_z + box_d <= self.z - self.size)
47
+
48
+ def intersects_sphere(self, center_x: float, center_y: float, center_z: float, radius: float) -> bool:
49
+ """Check if this node intersects with given sphere."""
50
+ # Find closest point on cube to sphere center
51
+ closest_x = max(self.x - self.size, min(center_x, self.x + self.size))
52
+ closest_y = max(self.y - self.size, min(center_y, self.y + self.size))
53
+ closest_z = max(self.z - self.size, min(center_z, self.z + self.size))
54
+
55
+ # Calculate distance from sphere center to closest point
56
+ dx = center_x - closest_x
57
+ dy = center_y - closest_y
58
+ dz = center_z - closest_z
59
+ distance_sq = dx * dx + dy * dy + dz * dz
60
+
61
+ return distance_sq <= radius * radius
62
+
63
+ def subdivide(self) -> None:
64
+ """Subdivide this node into eight children."""
65
+ if not self.is_leaf:
66
+ return
67
+
68
+ half_size = self.size / 2
69
+ quarter_size = half_size / 2
70
+
71
+ # Create eight children (octants)
72
+ octants = [
73
+ (self.x - quarter_size, self.y - quarter_size, self.z - quarter_size), # 0: ---
74
+ (self.x + quarter_size, self.y - quarter_size, self.z - quarter_size), # 1: +--
75
+ (self.x - quarter_size, self.y + quarter_size, self.z - quarter_size), # 2: -+-
76
+ (self.x + quarter_size, self.y + quarter_size, self.z - quarter_size), # 3: ++-
77
+ (self.x - quarter_size, self.y - quarter_size, self.z + quarter_size), # 4: --+
78
+ (self.x + quarter_size, self.y - quarter_size, self.z + quarter_size), # 5: +-+
79
+ (self.x - quarter_size, self.y + quarter_size, self.z + quarter_size), # 6: -++
80
+ (self.x + quarter_size, self.y + quarter_size, self.z + quarter_size), # 7: +++
81
+ ]
82
+
83
+ for i, (cx, cy, cz) in enumerate(octants):
84
+ self.children[i] = OctreeNode(cx, cy, cz, quarter_size, self.capacity)
85
+
86
+ self.is_leaf = False
87
+
88
+ # Redistribute points to children
89
+ for point in self.points:
90
+ x, y, z, vertex_id = point
91
+ for child in self.children:
92
+ if child and child.contains_point(x, y, z):
93
+ child.insert(x, y, z, vertex_id)
94
+ break
95
+
96
+ self.points.clear()
97
+
98
+ def insert(self, x: float, y: float, z: float, vertex_id: str) -> bool:
99
+ """Insert point into octree."""
100
+ if not self.contains_point(x, y, z):
101
+ return False
102
+
103
+ if self.is_leaf:
104
+ self.points.append((x, y, z, vertex_id))
105
+
106
+ # Subdivide if capacity exceeded
107
+ if len(self.points) > self.capacity:
108
+ self.subdivide()
109
+
110
+ return True
111
+ else:
112
+ # Insert into appropriate child
113
+ for child in self.children:
114
+ if child and child.insert(x, y, z, vertex_id):
115
+ return True
116
+ return False
117
+
118
+ def query_box(self, box_x: float, box_y: float, box_z: float,
119
+ box_w: float, box_h: float, box_d: float) -> List[Tuple[float, float, float, str]]:
120
+ """Query points within given box."""
121
+ result = []
122
+
123
+ if not self.intersects_box(box_x, box_y, box_z, box_w, box_h, box_d):
124
+ return result
125
+
126
+ if self.is_leaf:
127
+ for x, y, z, vertex_id in self.points:
128
+ if (box_x <= x < box_x + box_w and
129
+ box_y <= y < box_y + box_h and
130
+ box_z <= z < box_z + box_d):
131
+ result.append((x, y, z, vertex_id))
132
+ else:
133
+ for child in self.children:
134
+ if child:
135
+ result.extend(child.query_box(box_x, box_y, box_z, box_w, box_h, box_d))
136
+
137
+ return result
138
+
139
+ def query_sphere(self, center_x: float, center_y: float, center_z: float, radius: float) -> List[Tuple[float, float, float, str]]:
140
+ """Query points within given sphere."""
141
+ if not self.intersects_sphere(center_x, center_y, center_z, radius):
142
+ return []
143
+
144
+ result = []
145
+ radius_sq = radius * radius
146
+
147
+ if self.is_leaf:
148
+ for x, y, z, vertex_id in self.points:
149
+ dx = x - center_x
150
+ dy = y - center_y
151
+ dz = z - center_z
152
+ dist_sq = dx * dx + dy * dy + dz * dz
153
+
154
+ if dist_sq <= radius_sq:
155
+ result.append((x, y, z, vertex_id))
156
+ else:
157
+ for child in self.children:
158
+ if child:
159
+ result.extend(child.query_sphere(center_x, center_y, center_z, radius))
160
+
161
+ return result
162
+
163
+ def remove(self, x: float, y: float, z: float, vertex_id: str) -> bool:
164
+ """Remove point from octree."""
165
+ if not self.contains_point(x, y, z):
166
+ return False
167
+
168
+ if self.is_leaf:
169
+ for i, point in enumerate(self.points):
170
+ if (point[0] == x and point[1] == y and
171
+ point[2] == z and point[3] == vertex_id):
172
+ del self.points[i]
173
+ return True
174
+ return False
175
+ else:
176
+ for child in self.children:
177
+ if child and child.remove(x, y, z, vertex_id):
178
+ return True
179
+ return False
180
+
181
+
182
+ class xOctreeStrategy(aEdgeStrategy):
183
+ """
184
+ Octree edge strategy for 3D spatial graphs.
185
+
186
+ Efficiently manages 3D spatial relationships with logarithmic
187
+ complexity for 3D spatial queries and nearest neighbor searches.
188
+ """
189
+
190
+ def __init__(self, traits: EdgeTrait = EdgeTrait.NONE, **options):
191
+ """Initialize the Octree strategy."""
192
+ super().__init__(EdgeMode.OCTREE, traits, **options)
193
+
194
+ # 3D spatial bounds
195
+ self.center_x = options.get('center_x', 0.0)
196
+ self.center_y = options.get('center_y', 0.0)
197
+ self.center_z = options.get('center_z', 0.0)
198
+ self.size = options.get('size', 500.0) # Half-size of the root cube
199
+ self.capacity = options.get('capacity', 8)
200
+
201
+ # Core octree
202
+ self._root = OctreeNode(self.center_x, self.center_y, self.center_z, self.size, self.capacity)
203
+
204
+ # Vertex management
205
+ self._vertices: Dict[str, Tuple[float, float, float]] = {} # vertex_id -> (x, y, z)
206
+ self._edges: Dict[Tuple[str, str], Dict[str, Any]] = {} # (source, target) -> properties
207
+ self._spatial_edges: Set[Tuple[str, str]] = set() # Edges based on spatial proximity
208
+
209
+ # Performance tracking
210
+ self._edge_count = 0
211
+ self._spatial_threshold = options.get('spatial_threshold', 50.0) # Auto-connect distance
212
+
213
+ def get_supported_traits(self) -> EdgeTrait:
214
+ """Get the traits supported by the octree strategy."""
215
+ return (EdgeTrait.SPATIAL | EdgeTrait.SPARSE | EdgeTrait.CACHE_FRIENDLY)
216
+
217
+ def _auto_connect_spatial(self, vertex: str, x: float, y: float, z: float) -> None:
218
+ """Automatically connect vertex to nearby vertices."""
219
+ if self._spatial_threshold <= 0:
220
+ return
221
+
222
+ # Find nearby vertices
223
+ nearby = self._root.query_sphere(x, y, z, self._spatial_threshold)
224
+
225
+ for nx, ny, nz, neighbor_id in nearby:
226
+ if neighbor_id != vertex:
227
+ # Calculate 3D distance
228
+ distance = math.sqrt((x - nx) ** 2 + (y - ny) ** 2 + (z - nz) ** 2)
229
+
230
+ # Add spatial edge
231
+ edge_key = (min(vertex, neighbor_id), max(vertex, neighbor_id))
232
+ if edge_key not in self._edges:
233
+ self._edges[edge_key] = {
234
+ 'distance': distance,
235
+ 'spatial': True,
236
+ 'weight': 1.0 / (1.0 + distance), # Inverse distance weight
237
+ 'dimension': '3D'
238
+ }
239
+ self._spatial_edges.add(edge_key)
240
+ self._edge_count += 1
241
+
242
+ # ============================================================================
243
+ # CORE EDGE OPERATIONS
244
+ # ============================================================================
245
+
246
+ def add_edge(self, source: str, target: str, **properties) -> str:
247
+ """Add edge between 3D spatial vertices."""
248
+ # Ensure vertices exist with positions
249
+ if source not in self._vertices:
250
+ x = properties.get('source_x', 0.0)
251
+ y = properties.get('source_y', 0.0)
252
+ z = properties.get('source_z', 0.0)
253
+ self.add_spatial_vertex(source, x, y, z)
254
+
255
+ if target not in self._vertices:
256
+ x = properties.get('target_x', 0.0)
257
+ y = properties.get('target_y', 0.0)
258
+ z = properties.get('target_z', 0.0)
259
+ self.add_spatial_vertex(target, x, y, z)
260
+
261
+ # Calculate 3D distance
262
+ sx, sy, sz = self._vertices[source]
263
+ tx, ty, tz = self._vertices[target]
264
+ distance = math.sqrt((sx - tx) ** 2 + (sy - ty) ** 2 + (sz - tz) ** 2)
265
+
266
+ # Add edge
267
+ edge_key = (min(source, target), max(source, target))
268
+ self._edges[edge_key] = {
269
+ 'distance': distance,
270
+ 'spatial': properties.get('spatial', False),
271
+ 'weight': properties.get('weight', 1.0),
272
+ 'dimension': '3D',
273
+ **properties
274
+ }
275
+
276
+ if edge_key not in self._spatial_edges:
277
+ self._edge_count += 1
278
+
279
+ return f"{source}<->{target}"
280
+
281
+ def remove_edge(self, source: str, target: str, edge_id: Optional[str] = None) -> bool:
282
+ """Remove edge between vertices."""
283
+ edge_key = (min(source, target), max(source, target))
284
+
285
+ if edge_key in self._edges:
286
+ del self._edges[edge_key]
287
+ self._spatial_edges.discard(edge_key)
288
+ self._edge_count -= 1
289
+ return True
290
+
291
+ return False
292
+
293
+ def has_edge(self, source: str, target: str) -> bool:
294
+ """Check if edge exists."""
295
+ edge_key = (min(source, target), max(source, target))
296
+ return edge_key in self._edges
297
+
298
+ def get_edge_data(self, source: str, target: str) -> Optional[Dict[str, Any]]:
299
+ """Get edge data."""
300
+ edge_key = (min(source, target), max(source, target))
301
+ return self._edges.get(edge_key)
302
+
303
+ def neighbors(self, vertex: str, direction: str = 'both') -> Iterator[str]:
304
+ """Get neighbors of vertex."""
305
+ for (v1, v2) in self._edges:
306
+ if v1 == vertex:
307
+ yield v2
308
+ elif v2 == vertex:
309
+ yield v1
310
+
311
+ def degree(self, vertex: str, direction: str = 'both') -> int:
312
+ """Get degree of vertex."""
313
+ return len(list(self.neighbors(vertex, direction)))
314
+
315
+ def edges(self, data: bool = False) -> Iterator[tuple]:
316
+ """Get all edges."""
317
+ for (source, target), edge_data in self._edges.items():
318
+ if data:
319
+ yield (source, target, edge_data)
320
+ else:
321
+ yield (source, target)
322
+
323
+ def vertices(self) -> Iterator[str]:
324
+ """Get all vertices."""
325
+ return iter(self._vertices.keys())
326
+
327
+ def __len__(self) -> int:
328
+ """Get number of edges."""
329
+ return self._edge_count
330
+
331
+ def vertex_count(self) -> int:
332
+ """Get number of vertices."""
333
+ return len(self._vertices)
334
+
335
+ def clear(self) -> None:
336
+ """Clear all data."""
337
+ self._root = OctreeNode(self.center_x, self.center_y, self.center_z, self.size, self.capacity)
338
+ self._vertices.clear()
339
+ self._edges.clear()
340
+ self._spatial_edges.clear()
341
+ self._edge_count = 0
342
+
343
+ def add_vertex(self, vertex: str) -> None:
344
+ """Add vertex at random 3D position."""
345
+ if vertex not in self._vertices:
346
+ # Random position within bounds
347
+ import random
348
+ x = self.center_x + (random.random() - 0.5) * 2 * self.size
349
+ y = self.center_y + (random.random() - 0.5) * 2 * self.size
350
+ z = self.center_z + (random.random() - 0.5) * 2 * self.size
351
+ self.add_spatial_vertex(vertex, x, y, z)
352
+
353
+ def remove_vertex(self, vertex: str) -> bool:
354
+ """Remove vertex and all its edges."""
355
+ if vertex not in self._vertices:
356
+ return False
357
+
358
+ # Remove from octree
359
+ x, y, z = self._vertices[vertex]
360
+ self._root.remove(x, y, z, vertex)
361
+
362
+ # Remove all edges
363
+ edges_to_remove = []
364
+ for (v1, v2) in self._edges:
365
+ if v1 == vertex or v2 == vertex:
366
+ edges_to_remove.append((v1, v2))
367
+
368
+ for edge_key in edges_to_remove:
369
+ del self._edges[edge_key]
370
+ self._spatial_edges.discard(edge_key)
371
+ self._edge_count -= 1
372
+
373
+ # Remove vertex
374
+ del self._vertices[vertex]
375
+ return True
376
+
377
+ # ============================================================================
378
+ # 3D SPATIAL OPERATIONS
379
+ # ============================================================================
380
+
381
+ def add_spatial_vertex(self, vertex: str, x: float, y: float, z: float) -> None:
382
+ """Add vertex at specific 3D spatial position."""
383
+ # Remove old position if exists
384
+ if vertex in self._vertices:
385
+ old_x, old_y, old_z = self._vertices[vertex]
386
+ self._root.remove(old_x, old_y, old_z, vertex)
387
+
388
+ # Add to octree
389
+ self._vertices[vertex] = (x, y, z)
390
+ self._root.insert(x, y, z, vertex)
391
+
392
+ # Auto-connect to nearby vertices
393
+ self._auto_connect_spatial(vertex, x, y, z)
394
+
395
+ def get_vertex_position(self, vertex: str) -> Optional[Tuple[float, float, float]]:
396
+ """Get vertex 3D position."""
397
+ return self._vertices.get(vertex)
398
+
399
+ def set_vertex_position(self, vertex: str, x: float, y: float, z: float) -> None:
400
+ """Update vertex 3D position."""
401
+ self.add_spatial_vertex(vertex, x, y, z)
402
+
403
+ def query_box(self, x: float, y: float, z: float,
404
+ width: float, height: float, depth: float) -> List[str]:
405
+ """Query vertices within 3D box."""
406
+ points = self._root.query_box(x, y, z, width, height, depth)
407
+ return [vertex_id for _, _, _, vertex_id in points]
408
+
409
+ def query_sphere(self, center_x: float, center_y: float, center_z: float, radius: float) -> List[str]:
410
+ """Query vertices within 3D sphere."""
411
+ points = self._root.query_sphere(center_x, center_y, center_z, radius)
412
+ return [vertex_id for _, _, _, vertex_id in points]
413
+
414
+ def nearest_neighbors_3d(self, vertex: str, k: int = 1) -> List[Tuple[str, float]]:
415
+ """Find k nearest neighbors to vertex in 3D space."""
416
+ if vertex not in self._vertices:
417
+ return []
418
+
419
+ x, y, z = self._vertices[vertex]
420
+
421
+ # Query expanding radius until we have enough candidates
422
+ radius = 10.0
423
+ candidates = []
424
+
425
+ while len(candidates) < k * 2 and radius <= self.size * 2:
426
+ candidates = self.query_sphere(x, y, z, radius)
427
+ candidates = [v for v in candidates if v != vertex]
428
+ radius *= 2
429
+
430
+ # Calculate 3D distances and sort
431
+ distances = []
432
+ for neighbor in candidates:
433
+ nx, ny, nz = self._vertices[neighbor]
434
+ dist = math.sqrt((x - nx) ** 2 + (y - ny) ** 2 + (z - nz) ** 2)
435
+ distances.append((neighbor, dist))
436
+
437
+ distances.sort(key=lambda x: x[1])
438
+ return distances[:k]
439
+
440
+ def get_spatial_edges_in_box(self, x: float, y: float, z: float,
441
+ width: float, height: float, depth: float) -> List[Tuple[str, str]]:
442
+ """Get edges where both vertices are in given 3D box."""
443
+ vertices_in_box = set(self.query_box(x, y, z, width, height, depth))
444
+
445
+ spatial_edges = []
446
+ for (v1, v2) in self._edges:
447
+ if v1 in vertices_in_box and v2 in vertices_in_box:
448
+ spatial_edges.append((v1, v2))
449
+
450
+ return spatial_edges
451
+
452
+ def cluster_vertices_3d(self, max_distance: float) -> List[List[str]]:
453
+ """Cluster vertices based on 3D spatial proximity."""
454
+ visited = set()
455
+ clusters = []
456
+
457
+ for vertex in self._vertices:
458
+ if vertex in visited:
459
+ continue
460
+
461
+ # Start new cluster
462
+ cluster = []
463
+ stack = [vertex]
464
+
465
+ while stack:
466
+ current = stack.pop()
467
+ if current in visited:
468
+ continue
469
+
470
+ visited.add(current)
471
+ cluster.append(current)
472
+
473
+ # Find nearby vertices in 3D
474
+ x, y, z = self._vertices[current]
475
+ nearby = self.query_sphere(x, y, z, max_distance)
476
+
477
+ for neighbor in nearby:
478
+ if neighbor not in visited:
479
+ stack.append(neighbor)
480
+
481
+ if cluster:
482
+ clusters.append(cluster)
483
+
484
+ return clusters
485
+
486
+ def get_bounding_box(self) -> Tuple[float, float, float, float, float, float]:
487
+ """Get 3D bounding box of all vertices."""
488
+ if not self._vertices:
489
+ return (0, 0, 0, 0, 0, 0)
490
+
491
+ positions = list(self._vertices.values())
492
+ min_x = min(pos[0] for pos in positions)
493
+ max_x = max(pos[0] for pos in positions)
494
+ min_y = min(pos[1] for pos in positions)
495
+ max_y = max(pos[1] for pos in positions)
496
+ min_z = min(pos[2] for pos in positions)
497
+ max_z = max(pos[2] for pos in positions)
498
+
499
+ return (min_x, min_y, min_z, max_x, max_y, max_z)
500
+
501
+ def get_3d_statistics(self) -> Dict[str, Any]:
502
+ """Get comprehensive 3D spatial statistics."""
503
+ if not self._vertices:
504
+ return {'vertices': 0, 'edges': 0, 'volume': 0}
505
+
506
+ # Calculate 3D extent
507
+ min_x, min_y, min_z, max_x, max_y, max_z = self.get_bounding_box()
508
+
509
+ volume = (max_x - min_x) * (max_y - min_y) * (max_z - min_z) if len(self._vertices) > 1 else 0
510
+ vertex_density = len(self._vertices) / max(1, volume)
511
+
512
+ # Edge length statistics (3D distances)
513
+ edge_lengths = []
514
+ for (v1, v2), edge_data in self._edges.items():
515
+ edge_lengths.append(edge_data.get('distance', 0))
516
+
517
+ avg_edge_length = sum(edge_lengths) / len(edge_lengths) if edge_lengths else 0
518
+ max_edge_length = max(edge_lengths) if edge_lengths else 0
519
+ min_edge_length = min(edge_lengths) if edge_lengths else 0
520
+
521
+ return {
522
+ 'vertices': len(self._vertices),
523
+ 'edges': self._edge_count,
524
+ 'spatial_edges': len(self._spatial_edges),
525
+ 'bounding_box': (min_x, min_y, min_z, max_x, max_y, max_z),
526
+ 'volume': volume,
527
+ 'vertex_density': vertex_density,
528
+ 'avg_edge_length': avg_edge_length,
529
+ 'min_edge_length': min_edge_length,
530
+ 'max_edge_length': max_edge_length,
531
+ 'spatial_threshold': self._spatial_threshold,
532
+ 'octree_capacity': self.capacity,
533
+ 'octree_size': self.size * 2 # Full cube size
534
+ }
535
+
536
+ # ============================================================================
537
+ # PERFORMANCE CHARACTERISTICS
538
+ # ============================================================================
539
+
540
+ @property
541
+ def backend_info(self) -> Dict[str, Any]:
542
+ """Get backend implementation info."""
543
+ return {
544
+ 'strategy': 'OCTREE',
545
+ 'backend': '3D spatial partitioning with octree',
546
+ 'center': f"({self.center_x}, {self.center_y}, {self.center_z})",
547
+ 'size': self.size * 2, # Full cube size
548
+ 'capacity': self.capacity,
549
+ 'spatial_threshold': self._spatial_threshold,
550
+ 'complexity': {
551
+ 'insert': 'O(log n)',
552
+ 'remove': 'O(log n)',
553
+ 'box_query': 'O(log n + k)', # k = results
554
+ 'sphere_query': 'O(log n + k)',
555
+ 'nearest_neighbor': 'O(log n + k)',
556
+ 'space': 'O(n)'
557
+ }
558
+ }
559
+
560
+ @property
561
+ def metrics(self) -> Dict[str, Any]:
562
+ """Get performance metrics."""
563
+ stats = self.get_3d_statistics()
564
+
565
+ return {
566
+ 'vertices': stats['vertices'],
567
+ 'edges': stats['edges'],
568
+ 'spatial_edges': stats['spatial_edges'],
569
+ 'vertex_density': f"{stats['vertex_density']:.3f}",
570
+ 'avg_edge_length': f"{stats['avg_edge_length']:.1f}",
571
+ 'volume': f"{stats['volume']:.1f}",
572
+ 'octree_size': f"{stats['octree_size']:.1f}",
573
+ 'memory_usage': f"{self._edge_count * 100 + len(self._vertices) * 120} bytes (estimated)"
574
+ }