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,516 @@
1
+ """
2
+ HyperEdge Set Strategy Implementation
3
+
4
+ This module implements the HYPEREDGE_SET strategy for hypergraphs where
5
+ edges can connect multiple vertices simultaneously.
6
+ """
7
+
8
+ from typing import Any, Iterator, Dict, List, Set, Optional, Tuple, FrozenSet
9
+ from collections import defaultdict
10
+ import uuid
11
+ import time
12
+ from ._base_edge import aEdgeStrategy
13
+ from ...types import EdgeMode, EdgeTrait
14
+
15
+
16
+ class HyperEdge:
17
+ """Represents a hyperedge connecting multiple vertices."""
18
+
19
+ def __init__(self, edge_id: str, vertices: Set[str], **properties):
20
+ self.edge_id = edge_id
21
+ self.vertices = frozenset(vertices) # Immutable set of vertices
22
+ self.properties = properties.copy()
23
+ self.created_at = time.time()
24
+ self.size = len(self.vertices)
25
+
26
+ # Validate hyperedge
27
+ if self.size < 2:
28
+ raise ValueError("HyperEdge must connect at least 2 vertices")
29
+
30
+ def contains_vertex(self, vertex: str) -> bool:
31
+ """Check if vertex is in this hyperedge."""
32
+ return vertex in self.vertices
33
+
34
+ def get_other_vertices(self, vertex: str) -> Set[str]:
35
+ """Get all other vertices in this hyperedge."""
36
+ if vertex not in self.vertices:
37
+ return set()
38
+ return set(self.vertices) - {vertex}
39
+
40
+ def intersects_with(self, other: 'HyperEdge') -> bool:
41
+ """Check if this hyperedge shares vertices with another."""
42
+ return bool(self.vertices & other.vertices)
43
+
44
+ def is_subset_of(self, other: 'HyperEdge') -> bool:
45
+ """Check if all vertices are contained in another hyperedge."""
46
+ return self.vertices.issubset(other.vertices)
47
+
48
+ def union_with(self, other: 'HyperEdge') -> Set[str]:
49
+ """Get union of vertices with another hyperedge."""
50
+ return set(self.vertices | other.vertices)
51
+
52
+ def to_dict(self) -> Dict[str, Any]:
53
+ """Convert to dictionary representation."""
54
+ return {
55
+ 'id': self.edge_id,
56
+ 'vertices': list(self.vertices),
57
+ 'size': self.size,
58
+ 'properties': self.properties,
59
+ 'created_at': self.created_at
60
+ }
61
+
62
+ def __repr__(self) -> str:
63
+ return f"HyperEdge({self.edge_id}, {set(self.vertices)})"
64
+
65
+ def __hash__(self) -> int:
66
+ return hash((self.edge_id, self.vertices))
67
+
68
+ def __eq__(self, other) -> bool:
69
+ if not isinstance(other, HyperEdge):
70
+ return False
71
+ return self.edge_id == other.edge_id and self.vertices == other.vertices
72
+
73
+
74
+ class xHyperEdgeSetStrategy(aEdgeStrategy):
75
+ """
76
+ HyperEdge Set strategy for hypergraph representation.
77
+
78
+ Efficiently manages hyperedges where each edge can connect multiple vertices,
79
+ supporting complex graph patterns like clustering, group relationships,
80
+ and multi-way connections.
81
+ """
82
+
83
+ def __init__(self, traits: EdgeTrait = EdgeTrait.NONE, **options):
84
+ """Initialize the HyperEdge Set strategy."""
85
+ super().__init__(EdgeMode.HYPEREDGE_SET, traits, **options)
86
+
87
+ self.max_edge_size = options.get('max_edge_size', 100) # Max vertices per hyperedge
88
+ self.enable_indexing = options.get('enable_indexing', True)
89
+ self.track_statistics = options.get('track_statistics', True)
90
+
91
+ # Core storage
92
+ self._hyperedges: Dict[str, HyperEdge] = {} # edge_id -> HyperEdge
93
+ self._vertex_to_edges: Dict[str, Set[str]] = defaultdict(set) # vertex -> set of edge_ids
94
+ self._vertices: Set[str] = set()
95
+
96
+ # Size-based indexing for efficient queries
97
+ self._edges_by_size: Dict[int, Set[str]] = defaultdict(set) if self.enable_indexing else None
98
+
99
+ # Statistics
100
+ self._edge_count = 0
101
+ self._total_connections = 0 # Sum of all edge sizes
102
+ self._max_degree = 0 # Maximum vertex degree
103
+
104
+ # Performance optimizations
105
+ self._edge_id_counter = 0
106
+
107
+ def get_supported_traits(self) -> EdgeTrait:
108
+ """Get the traits supported by the hyperedge set strategy."""
109
+ return (EdgeTrait.HYPER | EdgeTrait.WEIGHTED | EdgeTrait.SPARSE | EdgeTrait.MULTI)
110
+
111
+ def _generate_edge_id(self) -> str:
112
+ """Generate a unique edge ID."""
113
+ self._edge_id_counter += 1
114
+ return f"he_{self._edge_id_counter}"
115
+
116
+ def _update_indices(self, hyperedge: HyperEdge, operation: str) -> None:
117
+ """Update internal indices after edge operations."""
118
+ if operation == "add":
119
+ # Update vertex-to-edges mapping
120
+ for vertex in hyperedge.vertices:
121
+ self._vertex_to_edges[vertex].add(hyperedge.edge_id)
122
+ self._vertices.add(vertex)
123
+
124
+ # Update size index
125
+ if self._edges_by_size is not None:
126
+ self._edges_by_size[hyperedge.size].add(hyperedge.edge_id)
127
+
128
+ # Update statistics
129
+ self._edge_count += 1
130
+ self._total_connections += hyperedge.size
131
+
132
+ # Update max degree
133
+ for vertex in hyperedge.vertices:
134
+ degree = len(self._vertex_to_edges[vertex])
135
+ self._max_degree = max(self._max_degree, degree)
136
+
137
+ elif operation == "remove":
138
+ # Update vertex-to-edges mapping
139
+ for vertex in hyperedge.vertices:
140
+ self._vertex_to_edges[vertex].discard(hyperedge.edge_id)
141
+ if not self._vertex_to_edges[vertex]:
142
+ del self._vertex_to_edges[vertex]
143
+ self._vertices.discard(vertex)
144
+
145
+ # Update size index
146
+ if self._edges_by_size is not None:
147
+ self._edges_by_size[hyperedge.size].discard(hyperedge.edge_id)
148
+ if not self._edges_by_size[hyperedge.size]:
149
+ del self._edges_by_size[hyperedge.size]
150
+
151
+ # Update statistics
152
+ self._edge_count -= 1
153
+ self._total_connections -= hyperedge.size
154
+
155
+ # Recalculate max degree (expensive, could be optimized)
156
+ self._max_degree = max((len(edges) for edges in self._vertex_to_edges.values()), default=0)
157
+
158
+ # ============================================================================
159
+ # CORE EDGE OPERATIONS (Adapted for hyperedges)
160
+ # ============================================================================
161
+
162
+ def add_edge(self, source: str, target: str, **properties) -> str:
163
+ """Add a binary hyperedge (compatibility method)."""
164
+ return self.add_hyperedge([source, target], **properties)
165
+
166
+ def add_hyperedge(self, vertices: List[str], edge_id: Optional[str] = None, **properties) -> str:
167
+ """Add a hyperedge connecting multiple vertices."""
168
+ # Validate input
169
+ vertex_set = set(vertices)
170
+ if len(vertex_set) < 2:
171
+ raise ValueError("Hyperedge must connect at least 2 distinct vertices")
172
+
173
+ if len(vertex_set) > self.max_edge_size:
174
+ raise ValueError(f"Hyperedge size {len(vertex_set)} exceeds maximum {self.max_edge_size}")
175
+
176
+ # Generate edge ID if not provided
177
+ if edge_id is None:
178
+ edge_id = self._generate_edge_id()
179
+ elif edge_id in self._hyperedges:
180
+ raise ValueError(f"HyperEdge ID {edge_id} already exists")
181
+
182
+ # Create hyperedge
183
+ hyperedge = HyperEdge(edge_id, vertex_set, **properties)
184
+
185
+ # Store and index
186
+ self._hyperedges[edge_id] = hyperedge
187
+ self._update_indices(hyperedge, "add")
188
+
189
+ return edge_id
190
+
191
+ def remove_edge(self, source: str, target: str, edge_id: Optional[str] = None) -> bool:
192
+ """Remove a binary edge (compatibility method)."""
193
+ if edge_id:
194
+ return self.remove_hyperedge(edge_id)
195
+ else:
196
+ # Find and remove edge containing both vertices
197
+ for eid in self._vertex_to_edges.get(source, set()).copy():
198
+ hyperedge = self._hyperedges.get(eid)
199
+ if hyperedge and target in hyperedge.vertices and hyperedge.size == 2:
200
+ return self.remove_hyperedge(eid)
201
+ return False
202
+
203
+ def remove_hyperedge(self, edge_id: str) -> bool:
204
+ """Remove a hyperedge by ID."""
205
+ if edge_id not in self._hyperedges:
206
+ return False
207
+
208
+ hyperedge = self._hyperedges[edge_id]
209
+
210
+ # Remove from storage and indices
211
+ del self._hyperedges[edge_id]
212
+ self._update_indices(hyperedge, "remove")
213
+
214
+ return True
215
+
216
+ def has_edge(self, source: str, target: str) -> bool:
217
+ """Check if binary edge exists (compatibility method)."""
218
+ return self.has_connection(source, target)
219
+
220
+ def has_connection(self, vertex1: str, vertex2: str) -> bool:
221
+ """Check if two vertices are connected by any hyperedge."""
222
+ if vertex1 not in self._vertex_to_edges:
223
+ return False
224
+
225
+ for edge_id in self._vertex_to_edges[vertex1]:
226
+ hyperedge = self._hyperedges[edge_id]
227
+ if vertex2 in hyperedge.vertices:
228
+ return True
229
+
230
+ return False
231
+
232
+ def get_edge_data(self, source: str, target: str) -> Optional[Dict[str, Any]]:
233
+ """Get data for binary edge (compatibility method)."""
234
+ # Find first hyperedge containing both vertices
235
+ if source not in self._vertex_to_edges:
236
+ return None
237
+
238
+ for edge_id in self._vertex_to_edges[source]:
239
+ hyperedge = self._hyperedges[edge_id]
240
+ if target in hyperedge.vertices:
241
+ return hyperedge.to_dict()
242
+
243
+ return None
244
+
245
+ def get_hyperedge_data(self, edge_id: str) -> Optional[Dict[str, Any]]:
246
+ """Get data for a specific hyperedge."""
247
+ hyperedge = self._hyperedges.get(edge_id)
248
+ return hyperedge.to_dict() if hyperedge else None
249
+
250
+ def neighbors(self, vertex: str, direction: str = 'out') -> Iterator[str]:
251
+ """Get all vertices connected to given vertex."""
252
+ if vertex not in self._vertex_to_edges:
253
+ return iter([])
254
+
255
+ connected_vertices = set()
256
+
257
+ for edge_id in self._vertex_to_edges[vertex]:
258
+ hyperedge = self._hyperedges[edge_id]
259
+ connected_vertices.update(hyperedge.get_other_vertices(vertex))
260
+
261
+ return iter(connected_vertices)
262
+
263
+ def degree(self, vertex: str, direction: str = 'out') -> int:
264
+ """Get degree of vertex (number of connected vertices)."""
265
+ return len(set(self.neighbors(vertex)))
266
+
267
+ def hyperedge_degree(self, vertex: str) -> int:
268
+ """Get hyperedge degree (number of hyperedges containing vertex)."""
269
+ return len(self._vertex_to_edges.get(vertex, set()))
270
+
271
+ def edges(self, data: bool = False, include_hyperedges: bool = True) -> Iterator[tuple]:
272
+ """Get all edges/hyperedges."""
273
+ if include_hyperedges:
274
+ # Return hyperedges as (vertices, data)
275
+ for hyperedge in self._hyperedges.values():
276
+ if data:
277
+ yield (list(hyperedge.vertices), hyperedge.to_dict())
278
+ else:
279
+ yield (list(hyperedge.vertices),)
280
+ else:
281
+ # Return binary projections
282
+ seen_pairs = set()
283
+ for hyperedge in self._hyperedges.values():
284
+ vertices = list(hyperedge.vertices)
285
+ for i in range(len(vertices)):
286
+ for j in range(i + 1, len(vertices)):
287
+ pair = tuple(sorted([vertices[i], vertices[j]]))
288
+ if pair not in seen_pairs:
289
+ seen_pairs.add(pair)
290
+ if data:
291
+ yield (pair[0], pair[1], hyperedge.to_dict())
292
+ else:
293
+ yield (pair[0], pair[1])
294
+
295
+ def vertices(self) -> Iterator[str]:
296
+ """Get all vertices."""
297
+ return iter(self._vertices)
298
+
299
+ def __len__(self) -> int:
300
+ """Get number of hyperedges."""
301
+ return self._edge_count
302
+
303
+ def vertex_count(self) -> int:
304
+ """Get number of vertices."""
305
+ return len(self._vertices)
306
+
307
+ def clear(self) -> None:
308
+ """Clear all hyperedges and vertices."""
309
+ self._hyperedges.clear()
310
+ self._vertex_to_edges.clear()
311
+ self._vertices.clear()
312
+
313
+ if self._edges_by_size is not None:
314
+ self._edges_by_size.clear()
315
+
316
+ self._edge_count = 0
317
+ self._total_connections = 0
318
+ self._max_degree = 0
319
+ self._edge_id_counter = 0
320
+
321
+ def add_vertex(self, vertex: str) -> None:
322
+ """Add an isolated vertex."""
323
+ self._vertices.add(vertex)
324
+
325
+ def remove_vertex(self, vertex: str) -> bool:
326
+ """Remove vertex and all its hyperedges."""
327
+ if vertex not in self._vertices:
328
+ return False
329
+
330
+ # Remove all hyperedges containing this vertex
331
+ edge_ids_to_remove = list(self._vertex_to_edges.get(vertex, set()))
332
+ for edge_id in edge_ids_to_remove:
333
+ self.remove_hyperedge(edge_id)
334
+
335
+ return True
336
+
337
+ # ============================================================================
338
+ # HYPERGRAPH-SPECIFIC OPERATIONS
339
+ # ============================================================================
340
+
341
+ def get_hyperedges_containing(self, vertex: str) -> List[HyperEdge]:
342
+ """Get all hyperedges containing a specific vertex."""
343
+ result = []
344
+ for edge_id in self._vertex_to_edges.get(vertex, set()):
345
+ hyperedge = self._hyperedges.get(edge_id)
346
+ if hyperedge:
347
+ result.append(hyperedge)
348
+ return result
349
+
350
+ def get_hyperedges_by_size(self, size: int) -> List[HyperEdge]:
351
+ """Get all hyperedges of specific size."""
352
+ if self._edges_by_size is None or size not in self._edges_by_size:
353
+ return []
354
+
355
+ result = []
356
+ for edge_id in self._edges_by_size[size]:
357
+ hyperedge = self._hyperedges.get(edge_id)
358
+ if hyperedge:
359
+ result.append(hyperedge)
360
+
361
+ return result
362
+
363
+ def get_k_uniform_subgraph(self, k: int) -> 'xHyperEdgeSetStrategy':
364
+ """Extract k-uniform subgraph (all hyperedges of size k)."""
365
+ subgraph = xHyperEdgeSetStrategy(
366
+ traits=self._traits,
367
+ max_edge_size=self.max_edge_size,
368
+ enable_indexing=self.enable_indexing
369
+ )
370
+
371
+ for hyperedge in self.get_hyperedges_by_size(k):
372
+ subgraph.add_hyperedge(
373
+ list(hyperedge.vertices),
374
+ hyperedge.edge_id,
375
+ **hyperedge.properties
376
+ )
377
+
378
+ return subgraph
379
+
380
+ def find_maximal_cliques(self) -> List[Set[str]]:
381
+ """Find maximal cliques in the hypergraph."""
382
+ # Simple algorithm - can be optimized
383
+ cliques = []
384
+
385
+ for hyperedge in self._hyperedges.values():
386
+ # Check if this hyperedge forms a clique
387
+ vertices = list(hyperedge.vertices)
388
+ is_clique = True
389
+
390
+ # Check all pairs are connected
391
+ for i in range(len(vertices)):
392
+ for j in range(i + 1, len(vertices)):
393
+ if not self.has_connection(vertices[i], vertices[j]):
394
+ is_clique = False
395
+ break
396
+ if not is_clique:
397
+ break
398
+
399
+ if is_clique:
400
+ # Check if it's maximal (not contained in existing clique)
401
+ is_maximal = True
402
+ for existing_clique in cliques:
403
+ if hyperedge.vertices.issubset(existing_clique):
404
+ is_maximal = False
405
+ break
406
+
407
+ if is_maximal:
408
+ # Remove any cliques that are subsets of this one
409
+ cliques = [c for c in cliques if not c.issubset(hyperedge.vertices)]
410
+ cliques.append(set(hyperedge.vertices))
411
+
412
+ return cliques
413
+
414
+ def get_incidence_matrix(self) -> Tuple[List[str], List[str], List[List[int]]]:
415
+ """Get incidence matrix representation."""
416
+ vertices = sorted(self._vertices)
417
+ edge_ids = sorted(self._hyperedges.keys())
418
+
419
+ matrix = []
420
+ for edge_id in edge_ids:
421
+ hyperedge = self._hyperedges[edge_id]
422
+ row = [1 if vertex in hyperedge.vertices else 0 for vertex in vertices]
423
+ matrix.append(row)
424
+
425
+ return vertices, edge_ids, matrix
426
+
427
+ def get_vertex_neighborhoods(self, vertex: str, radius: int = 1) -> Set[str]:
428
+ """Get all vertices within given radius from vertex."""
429
+ if radius <= 0 or vertex not in self._vertices:
430
+ return set()
431
+
432
+ current_level = {vertex}
433
+ all_neighbors = set()
434
+
435
+ for _ in range(radius):
436
+ next_level = set()
437
+ for v in current_level:
438
+ neighbors = set(self.neighbors(v))
439
+ next_level.update(neighbors)
440
+ all_neighbors.update(neighbors)
441
+
442
+ current_level = next_level - all_neighbors - {vertex}
443
+ if not current_level:
444
+ break
445
+
446
+ return all_neighbors
447
+
448
+ def hypergraph_statistics(self) -> Dict[str, Any]:
449
+ """Get comprehensive hypergraph statistics."""
450
+ if not self._hyperedges:
451
+ return {
452
+ 'vertices': 0, 'hyperedges': 0, 'avg_degree': 0,
453
+ 'avg_hyperedge_size': 0, 'max_hyperedge_size': 0,
454
+ 'min_hyperedge_size': 0, 'uniformity': 0
455
+ }
456
+
457
+ hyperedge_sizes = [he.size for he in self._hyperedges.values()]
458
+ vertex_degrees = [self.hyperedge_degree(v) for v in self._vertices]
459
+
460
+ return {
461
+ 'vertices': len(self._vertices),
462
+ 'hyperedges': self._edge_count,
463
+ 'total_connections': self._total_connections,
464
+ 'avg_degree': sum(vertex_degrees) / len(vertex_degrees) if vertex_degrees else 0,
465
+ 'max_degree': max(vertex_degrees) if vertex_degrees else 0,
466
+ 'avg_hyperedge_size': sum(hyperedge_sizes) / len(hyperedge_sizes),
467
+ 'max_hyperedge_size': max(hyperedge_sizes),
468
+ 'min_hyperedge_size': min(hyperedge_sizes),
469
+ 'uniformity': len(set(hyperedge_sizes)) == 1, # All same size
470
+ 'density': self._total_connections / (len(self._vertices) * self._edge_count) if self._vertices and self._edge_count else 0
471
+ }
472
+
473
+ # ============================================================================
474
+ # PERFORMANCE CHARACTERISTICS
475
+ # ============================================================================
476
+
477
+ @property
478
+ def backend_info(self) -> Dict[str, Any]:
479
+ """Get backend implementation info."""
480
+ stats = self.hypergraph_statistics()
481
+
482
+ return {
483
+ 'strategy': 'HYPEREDGE_SET',
484
+ 'backend': 'Set-based hyperedge storage with vertex indexing',
485
+ 'max_edge_size': self.max_edge_size,
486
+ 'enable_indexing': self.enable_indexing,
487
+ 'track_statistics': self.track_statistics,
488
+ 'complexity': {
489
+ 'add_hyperedge': 'O(k)', # k = edge size
490
+ 'remove_hyperedge': 'O(k)',
491
+ 'has_connection': 'O(degree)',
492
+ 'neighbors': 'O(degree * avg_edge_size)',
493
+ 'space': 'O(V + E * avg_edge_size)'
494
+ }
495
+ }
496
+
497
+ @property
498
+ def metrics(self) -> Dict[str, Any]:
499
+ """Get performance metrics."""
500
+ stats = self.hypergraph_statistics()
501
+
502
+ # Estimate memory usage
503
+ vertex_memory = len(self._vertices) * 50 # Estimated bytes per vertex
504
+ edge_memory = sum(he.size * 30 + 100 for he in self._hyperedges.values()) # Estimated
505
+ index_memory = sum(len(edges) * 8 for edges in self._vertex_to_edges.values())
506
+
507
+ return {
508
+ 'vertices': stats['vertices'],
509
+ 'hyperedges': stats['hyperedges'],
510
+ 'total_connections': stats['total_connections'],
511
+ 'avg_hyperedge_size': f"{stats['avg_hyperedge_size']:.1f}",
512
+ 'max_degree': stats['max_degree'],
513
+ 'density': f"{stats['density']:.3f}",
514
+ 'memory_usage': f"{vertex_memory + edge_memory + index_memory} bytes (estimated)",
515
+ 'index_efficiency': f"{len(self._vertex_to_edges) / max(1, len(self._vertices)):.2f}"
516
+ }