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,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
|
+
}
|