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,455 @@
1
+ """
2
+ Bidirectional Wrapper Edge Strategy Implementation
3
+
4
+ This module implements the BIDIR_WRAPPER strategy for efficient
5
+ undirected graph operations using dual directed edges.
6
+ """
7
+
8
+ from typing import Any, Iterator, List, Dict, Set, Optional, Tuple
9
+ from collections import defaultdict
10
+ from ._base_edge import aEdgeStrategy
11
+ from ...types import EdgeMode, EdgeTrait
12
+
13
+
14
+ class xBidirWrapperStrategy(aEdgeStrategy):
15
+ """
16
+ Bidirectional Wrapper edge strategy for undirected graphs.
17
+
18
+ Efficiently represents undirected edges using pairs of directed edges
19
+ with automatic synchronization and optimized undirected operations.
20
+ """
21
+
22
+ def __init__(self, traits: EdgeTrait = EdgeTrait.NONE, **options):
23
+ """Initialize the Bidirectional Wrapper strategy."""
24
+ super().__init__(EdgeMode.BIDIR_WRAPPER, traits, **options)
25
+
26
+ self.auto_sync = options.get('auto_sync', True)
27
+ self.weighted = options.get('weighted', True)
28
+ self.allow_self_loops = options.get('allow_self_loops', True)
29
+
30
+ # Core storage: directed adjacency lists for both directions
31
+ self._outgoing: Dict[str, Dict[str, float]] = defaultdict(dict) # source -> {target: weight}
32
+ self._incoming: Dict[str, Dict[str, float]] = defaultdict(dict) # target -> {source: weight}
33
+
34
+ # Undirected edge tracking
35
+ self._undirected_edges: Set[Tuple[str, str]] = set() # Canonical edge pairs (min, max)
36
+ self._vertices: Set[str] = set()
37
+
38
+ # Performance tracking
39
+ self._edge_count = 0
40
+ self._sync_operations = 0
41
+
42
+ def get_supported_traits(self) -> EdgeTrait:
43
+ """Get the traits supported by the bidirectional wrapper strategy."""
44
+ return (EdgeTrait.SPARSE | EdgeTrait.CACHE_FRIENDLY)
45
+
46
+ def _canonical_edge(self, source: str, target: str) -> Tuple[str, str]:
47
+ """Get canonical representation of undirected edge."""
48
+ return (min(source, target), max(source, target))
49
+
50
+ def _add_directed_edge(self, source: str, target: str, weight: float) -> None:
51
+ """Add directed edge to internal structures."""
52
+ self._outgoing[source][target] = weight
53
+ self._incoming[target][source] = weight
54
+ self._vertices.add(source)
55
+ self._vertices.add(target)
56
+
57
+ def _remove_directed_edge(self, source: str, target: str) -> bool:
58
+ """Remove directed edge from internal structures."""
59
+ if target in self._outgoing.get(source, {}):
60
+ del self._outgoing[source][target]
61
+ del self._incoming[target][source]
62
+ return True
63
+ return False
64
+
65
+ def _sync_undirected_edge(self, source: str, target: str, weight: float) -> None:
66
+ """Synchronize both directions of an undirected edge."""
67
+ if self.auto_sync:
68
+ self._add_directed_edge(source, target, weight)
69
+ if source != target: # Avoid double self-loops
70
+ self._add_directed_edge(target, source, weight)
71
+ self._sync_operations += 1
72
+
73
+ def _unsync_undirected_edge(self, source: str, target: str) -> bool:
74
+ """Remove both directions of an undirected edge."""
75
+ removed = False
76
+ if self._remove_directed_edge(source, target):
77
+ removed = True
78
+ if source != target and self._remove_directed_edge(target, source):
79
+ removed = True
80
+
81
+ if removed:
82
+ self._sync_operations += 1
83
+
84
+ return removed
85
+
86
+ # ============================================================================
87
+ # CORE EDGE OPERATIONS
88
+ # ============================================================================
89
+
90
+ def add_edge(self, source: str, target: str, **properties) -> str:
91
+ """Add undirected edge (creates two directed edges)."""
92
+ weight = properties.get('weight', 1.0) if self.weighted else 1.0
93
+
94
+ if not self.allow_self_loops and source == target:
95
+ raise ValueError("Self-loops not allowed")
96
+
97
+ canonical = self._canonical_edge(source, target)
98
+
99
+ # Check if undirected edge already exists
100
+ if canonical in self._undirected_edges:
101
+ # Update existing edge
102
+ self._sync_undirected_edge(source, target, weight)
103
+ else:
104
+ # Add new undirected edge
105
+ self._sync_undirected_edge(source, target, weight)
106
+ self._undirected_edges.add(canonical)
107
+ self._edge_count += 1
108
+
109
+ return f"{canonical[0]}<->{canonical[1]}"
110
+
111
+ def remove_edge(self, source: str, target: str, edge_id: Optional[str] = None) -> bool:
112
+ """Remove undirected edge (removes both directed edges)."""
113
+ canonical = self._canonical_edge(source, target)
114
+
115
+ if canonical in self._undirected_edges:
116
+ # Remove undirected edge
117
+ if self._unsync_undirected_edge(source, target):
118
+ self._undirected_edges.remove(canonical)
119
+ self._edge_count -= 1
120
+ return True
121
+
122
+ return False
123
+
124
+ def has_edge(self, source: str, target: str) -> bool:
125
+ """Check if undirected edge exists."""
126
+ canonical = self._canonical_edge(source, target)
127
+ return canonical in self._undirected_edges
128
+
129
+ def get_edge_data(self, source: str, target: str) -> Optional[Dict[str, Any]]:
130
+ """Get edge data."""
131
+ if not self.has_edge(source, target):
132
+ return None
133
+
134
+ # Get weight from one direction (they should be synchronized)
135
+ weight = self._outgoing.get(source, {}).get(target, 1.0)
136
+ canonical = self._canonical_edge(source, target)
137
+
138
+ return {
139
+ 'source': source,
140
+ 'target': target,
141
+ 'canonical': canonical,
142
+ 'weight': weight,
143
+ 'undirected': True,
144
+ 'self_loop': source == target
145
+ }
146
+
147
+ def neighbors(self, vertex: str, direction: str = 'both') -> Iterator[str]:
148
+ """Get neighbors of vertex."""
149
+ if direction in ['out', 'both']:
150
+ for neighbor in self._outgoing.get(vertex, {}):
151
+ yield neighbor
152
+
153
+ if direction in ['in', 'both'] and direction != 'out':
154
+ # For undirected graphs, incoming and outgoing are the same
155
+ # But avoid duplicates when direction is 'both'
156
+ for neighbor in self._incoming.get(vertex, {}):
157
+ if direction == 'in' or neighbor not in self._outgoing.get(vertex, {}):
158
+ yield neighbor
159
+
160
+ def degree(self, vertex: str, direction: str = 'both') -> int:
161
+ """Get degree of vertex."""
162
+ if direction == 'out':
163
+ return len(self._outgoing.get(vertex, {}))
164
+ elif direction == 'in':
165
+ return len(self._incoming.get(vertex, {}))
166
+ else: # both - for undirected graphs, this is just the degree
167
+ # Use set to avoid counting self-loops twice
168
+ neighbors = set()
169
+ neighbors.update(self._outgoing.get(vertex, {}))
170
+ neighbors.update(self._incoming.get(vertex, {}))
171
+ return len(neighbors)
172
+
173
+ def edges(self, data: bool = False) -> Iterator[tuple]:
174
+ """Get all undirected edges (returns each edge once)."""
175
+ for canonical in self._undirected_edges:
176
+ source, target = canonical
177
+
178
+ if data:
179
+ edge_data = self.get_edge_data(source, target)
180
+ yield (source, target, edge_data)
181
+ else:
182
+ yield (source, target)
183
+
184
+ def vertices(self) -> Iterator[str]:
185
+ """Get all vertices."""
186
+ return iter(self._vertices)
187
+
188
+ def __len__(self) -> int:
189
+ """Get number of undirected edges."""
190
+ return self._edge_count
191
+
192
+ def vertex_count(self) -> int:
193
+ """Get number of vertices."""
194
+ return len(self._vertices)
195
+
196
+ def clear(self) -> None:
197
+ """Clear all data."""
198
+ self._outgoing.clear()
199
+ self._incoming.clear()
200
+ self._undirected_edges.clear()
201
+ self._vertices.clear()
202
+ self._edge_count = 0
203
+ self._sync_operations = 0
204
+
205
+ def add_vertex(self, vertex: str) -> None:
206
+ """Add vertex to graph."""
207
+ self._vertices.add(vertex)
208
+
209
+ def remove_vertex(self, vertex: str) -> bool:
210
+ """Remove vertex and all its edges."""
211
+ if vertex not in self._vertices:
212
+ return False
213
+
214
+ # Remove all edges involving this vertex
215
+ edges_to_remove = []
216
+ for source, target in self.edges():
217
+ if source == vertex or target == vertex:
218
+ edges_to_remove.append((source, target))
219
+
220
+ for source, target in edges_to_remove:
221
+ self.remove_edge(source, target)
222
+
223
+ # Remove vertex
224
+ self._vertices.discard(vertex)
225
+ self._outgoing.pop(vertex, None)
226
+ self._incoming.pop(vertex, None)
227
+
228
+ return True
229
+
230
+ # ============================================================================
231
+ # UNDIRECTED GRAPH SPECIFIC OPERATIONS
232
+ # ============================================================================
233
+
234
+ def add_undirected_edge(self, vertex1: str, vertex2: str, weight: float = 1.0) -> str:
235
+ """Add undirected edge explicitly."""
236
+ return self.add_edge(vertex1, vertex2, weight=weight)
237
+
238
+ def get_undirected_degree(self, vertex: str) -> int:
239
+ """Get undirected degree (number of incident edges)."""
240
+ return self.degree(vertex, 'both')
241
+
242
+ def get_all_neighbors(self, vertex: str) -> Set[str]:
243
+ """Get all neighbors in undirected graph."""
244
+ neighbors = set()
245
+ neighbors.update(self._outgoing.get(vertex, {}))
246
+ neighbors.update(self._incoming.get(vertex, {}))
247
+ return neighbors
248
+
249
+ def is_connected_to(self, vertex1: str, vertex2: str) -> bool:
250
+ """Check if two vertices are connected."""
251
+ return self.has_edge(vertex1, vertex2)
252
+
253
+ def get_edge_weight(self, vertex1: str, vertex2: str) -> Optional[float]:
254
+ """Get weight of undirected edge."""
255
+ if not self.has_edge(vertex1, vertex2):
256
+ return None
257
+
258
+ # Return weight from either direction (should be same)
259
+ return self._outgoing.get(vertex1, {}).get(vertex2) or \
260
+ self._outgoing.get(vertex2, {}).get(vertex1)
261
+
262
+ def set_edge_weight(self, vertex1: str, vertex2: str, weight: float) -> bool:
263
+ """Set weight of undirected edge."""
264
+ if not self.has_edge(vertex1, vertex2):
265
+ return False
266
+
267
+ # Update both directions
268
+ self._sync_undirected_edge(vertex1, vertex2, weight)
269
+ return True
270
+
271
+ def get_connected_components(self) -> List[Set[str]]:
272
+ """Find connected components using DFS."""
273
+ visited = set()
274
+ components = []
275
+
276
+ for vertex in self._vertices:
277
+ if vertex not in visited:
278
+ component = set()
279
+ stack = [vertex]
280
+
281
+ while stack:
282
+ current = stack.pop()
283
+ if current not in visited:
284
+ visited.add(current)
285
+ component.add(current)
286
+
287
+ # Add all unvisited neighbors
288
+ for neighbor in self.get_all_neighbors(current):
289
+ if neighbor not in visited:
290
+ stack.append(neighbor)
291
+
292
+ if component:
293
+ components.append(component)
294
+
295
+ return components
296
+
297
+ def is_connected(self) -> bool:
298
+ """Check if graph is connected."""
299
+ components = self.get_connected_components()
300
+ return len(components) <= 1
301
+
302
+ def spanning_tree_edges(self) -> List[Tuple[str, str, float]]:
303
+ """Get edges of a minimum spanning tree using Kruskal's algorithm."""
304
+ # Get all edges with weights
305
+ edges = []
306
+ for source, target in self.edges():
307
+ weight = self.get_edge_weight(source, target)
308
+ edges.append((weight, source, target))
309
+
310
+ # Sort by weight
311
+ edges.sort()
312
+
313
+ # Union-Find for cycle detection
314
+ parent = {}
315
+ rank = {}
316
+
317
+ def find(x):
318
+ if x not in parent:
319
+ parent[x] = x
320
+ rank[x] = 0
321
+ if parent[x] != x:
322
+ parent[x] = find(parent[x])
323
+ return parent[x]
324
+
325
+ def union(x, y):
326
+ px, py = find(x), find(y)
327
+ if px == py:
328
+ return False
329
+ if rank[px] < rank[py]:
330
+ px, py = py, px
331
+ parent[py] = px
332
+ if rank[px] == rank[py]:
333
+ rank[px] += 1
334
+ return True
335
+
336
+ # Build MST
337
+ mst_edges = []
338
+ for weight, source, target in edges:
339
+ if union(source, target):
340
+ mst_edges.append((source, target, weight))
341
+
342
+ return mst_edges
343
+
344
+ def validate_synchronization(self) -> Dict[str, Any]:
345
+ """Validate that all undirected edges are properly synchronized."""
346
+ issues = []
347
+
348
+ for canonical in self._undirected_edges:
349
+ source, target = canonical
350
+
351
+ # Check forward direction
352
+ forward_weight = self._outgoing.get(source, {}).get(target)
353
+ if forward_weight is None:
354
+ issues.append(f"Missing forward edge: {source} -> {target}")
355
+ continue
356
+
357
+ # Check backward direction (skip for self-loops)
358
+ if source != target:
359
+ backward_weight = self._outgoing.get(target, {}).get(source)
360
+ if backward_weight is None:
361
+ issues.append(f"Missing backward edge: {target} -> {source}")
362
+ elif abs(forward_weight - backward_weight) > 1e-9:
363
+ issues.append(f"Weight mismatch: {source}<->{target} ({forward_weight} != {backward_weight})")
364
+
365
+ return {
366
+ 'synchronized': len(issues) == 0,
367
+ 'issues': issues,
368
+ 'sync_operations': self._sync_operations,
369
+ 'undirected_edges': len(self._undirected_edges),
370
+ 'directed_edges': sum(len(adj) for adj in self._outgoing.values())
371
+ }
372
+
373
+ def get_statistics(self) -> Dict[str, Any]:
374
+ """Get comprehensive bidirectional wrapper statistics."""
375
+ sync_status = self.validate_synchronization()
376
+ components = self.get_connected_components()
377
+
378
+ # Calculate clustering coefficient
379
+ total_clustering = 0
380
+ vertices_with_neighbors = 0
381
+
382
+ for vertex in self._vertices:
383
+ neighbors = self.get_all_neighbors(vertex)
384
+ degree = len(neighbors)
385
+
386
+ if degree >= 2:
387
+ # Count triangles
388
+ triangles = 0
389
+ for n1 in neighbors:
390
+ for n2 in neighbors:
391
+ if n1 < n2 and self.has_edge(n1, n2):
392
+ triangles += 1
393
+
394
+ # Clustering coefficient for this vertex
395
+ possible_edges = degree * (degree - 1) / 2
396
+ clustering = triangles / possible_edges if possible_edges > 0 else 0
397
+ total_clustering += clustering
398
+ vertices_with_neighbors += 1
399
+
400
+ avg_clustering = total_clustering / vertices_with_neighbors if vertices_with_neighbors > 0 else 0
401
+
402
+ return {
403
+ 'vertices': len(self._vertices),
404
+ 'undirected_edges': self._edge_count,
405
+ 'directed_edges_stored': sum(len(adj) for adj in self._outgoing.values()),
406
+ 'connected_components': len(components),
407
+ 'largest_component': max(len(comp) for comp in components) if components else 0,
408
+ 'is_connected': len(components) <= 1,
409
+ 'avg_degree': (2 * self._edge_count) / max(1, len(self._vertices)),
410
+ 'avg_clustering_coefficient': avg_clustering,
411
+ 'sync_status': sync_status,
412
+ 'weighted': self.weighted,
413
+ 'allow_self_loops': self.allow_self_loops,
414
+ 'auto_sync': self.auto_sync
415
+ }
416
+
417
+ # ============================================================================
418
+ # PERFORMANCE CHARACTERISTICS
419
+ # ============================================================================
420
+
421
+ @property
422
+ def backend_info(self) -> Dict[str, Any]:
423
+ """Get backend implementation info."""
424
+ return {
425
+ 'strategy': 'BIDIR_WRAPPER',
426
+ 'backend': 'Dual directed adjacency lists for undirected graphs',
427
+ 'auto_sync': self.auto_sync,
428
+ 'weighted': self.weighted,
429
+ 'allow_self_loops': self.allow_self_loops,
430
+ 'complexity': {
431
+ 'add_edge': 'O(1)',
432
+ 'remove_edge': 'O(1)',
433
+ 'has_edge': 'O(1)',
434
+ 'neighbors': 'O(degree)',
435
+ 'connected_components': 'O(V + E)',
436
+ 'space': 'O(2E + V)' # Double storage for undirected edges
437
+ }
438
+ }
439
+
440
+ @property
441
+ def metrics(self) -> Dict[str, Any]:
442
+ """Get performance metrics."""
443
+ stats = self.get_statistics()
444
+ sync_status = stats['sync_status']
445
+
446
+ return {
447
+ 'vertices': stats['vertices'],
448
+ 'undirected_edges': stats['undirected_edges'],
449
+ 'directed_edges_stored': stats['directed_edges_stored'],
450
+ 'connected_components': stats['connected_components'],
451
+ 'avg_degree': f"{stats['avg_degree']:.1f}",
452
+ 'clustering_coeff': f"{stats['avg_clustering_coefficient']:.3f}",
453
+ 'synchronized': sync_status['synchronized'],
454
+ 'memory_usage': f"{stats['directed_edges_stored'] * 16 + len(self._vertices) * 50} bytes (estimated)"
455
+ }