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,271 @@
1
+ #exonware\xnode\strategies\impls\edge_tree_graph_basic.py
2
+ """
3
+ Tree-Graph Basic Edge Strategy Implementation
4
+
5
+ This module implements the TREE_GRAPH_BASIC strategy for basic edge storage
6
+ in tree+graph hybrid structures, providing minimal graph capabilities.
7
+ """
8
+
9
+ from typing import Any, Dict, List, Optional, Set, Tuple, Iterator
10
+ from ._base_edge import aEdgeStrategy
11
+ from ...types import EdgeMode, EdgeTrait
12
+
13
+
14
+ class xTreeGraphBasicStrategy(aEdgeStrategy):
15
+ """
16
+ Basic edge strategy for tree+graph hybrid structures.
17
+
18
+ Provides minimal graph capabilities optimized for tree navigation
19
+ with basic edge storage and traversal support.
20
+ """
21
+
22
+ def __init__(self, traits: EdgeTrait = EdgeTrait.NONE, **options):
23
+ """Initialize the tree-graph basic strategy."""
24
+ super().__init__(EdgeMode.TREE_GRAPH_BASIC, traits, **options)
25
+
26
+ # Basic edge storage - simple adjacency representation
27
+ self._edges: Dict[str, Set[str]] = {} # source -> {targets}
28
+ self._reverse_edges: Dict[str, Set[str]] = {} # target -> {sources}
29
+ self._edge_count = 0
30
+
31
+ # Statistics
32
+ self._total_additions = 0
33
+ self._total_removals = 0
34
+ self._max_degree = 0
35
+
36
+ def get_supported_traits(self) -> EdgeTrait:
37
+ """Get the traits supported by the tree-graph basic strategy."""
38
+ return (EdgeTrait.DIRECTED | EdgeTrait.WEIGHTED | EdgeTrait.MULTI)
39
+
40
+ def _update_degree_stats(self, node: str) -> None:
41
+ """Update degree statistics."""
42
+ degree = len(self._edges.get(node, set()))
43
+ self._max_degree = max(self._max_degree, degree)
44
+
45
+ def _add_edge_internal(self, source: str, target: str, weight: float = 1.0,
46
+ metadata: Optional[Dict[str, Any]] = None) -> bool:
47
+ """Internal method to add edge."""
48
+ if source not in self._edges:
49
+ self._edges[source] = set()
50
+ if target not in self._reverse_edges:
51
+ self._reverse_edges[target] = set()
52
+
53
+ # Check if edge already exists
54
+ if target in self._edges[source]:
55
+ return False # Edge already exists
56
+
57
+ # Add edge
58
+ self._edges[source].add(target)
59
+ self._reverse_edges[target].add(source)
60
+ self._edge_count += 1
61
+ self._total_additions += 1
62
+
63
+ # Update statistics
64
+ self._update_degree_stats(source)
65
+
66
+ return True
67
+
68
+ def _remove_edge_internal(self, source: str, target: str) -> bool:
69
+ """Internal method to remove edge."""
70
+ if source not in self._edges or target not in self._edges[source]:
71
+ return False
72
+
73
+ # Remove edge
74
+ self._edges[source].remove(target)
75
+ self._reverse_edges[target].remove(source)
76
+ self._edge_count -= 1
77
+ self._total_removals += 1
78
+
79
+ # Clean up empty sets
80
+ if not self._edges[source]:
81
+ del self._edges[source]
82
+ if not self._reverse_edges[target]:
83
+ del self._reverse_edges[target]
84
+
85
+ return True
86
+
87
+ # ============================================================================
88
+ # CORE OPERATIONS
89
+ # ============================================================================
90
+
91
+ def add_edge(self, source: str, target: str, weight: float = 1.0,
92
+ metadata: Optional[Dict[str, Any]] = None) -> bool:
93
+ """Add an edge between source and target nodes."""
94
+ if not isinstance(source, str) or not isinstance(target, str):
95
+ raise ValueError("Source and target must be strings")
96
+
97
+ return self._add_edge_internal(source, target, weight, metadata)
98
+
99
+ def remove_edge(self, source: str, target: str) -> bool:
100
+ """Remove an edge between source and target nodes."""
101
+ if not isinstance(source, str) or not isinstance(target, str):
102
+ raise ValueError("Source and target must be strings")
103
+
104
+ return self._remove_edge_internal(source, target)
105
+
106
+ def has_edge(self, source: str, target: str) -> bool:
107
+ """Check if an edge exists between source and target nodes."""
108
+ if not isinstance(source, str) or not isinstance(target, str):
109
+ return False
110
+
111
+ return source in self._edges and target in self._edges[source]
112
+
113
+ def get_neighbors(self, node: str) -> List[str]:
114
+ """Get all neighbors of a node."""
115
+ if not isinstance(node, str):
116
+ return []
117
+
118
+ return list(self._edges.get(node, set()))
119
+
120
+ def get_incoming(self, node: str) -> List[str]:
121
+ """Get all incoming neighbors of a node."""
122
+ if not isinstance(node, str):
123
+ return []
124
+
125
+ return list(self._reverse_edges.get(node, set()))
126
+
127
+ def get_outgoing(self, node: str) -> List[str]:
128
+ """Get all outgoing neighbors of a node."""
129
+ if not isinstance(node, str):
130
+ return []
131
+
132
+ return list(self._edges.get(node, set()))
133
+
134
+ def get_degree(self, node: str) -> int:
135
+ """Get the degree (number of neighbors) of a node."""
136
+ if not isinstance(node, str):
137
+ return 0
138
+
139
+ return len(self._edges.get(node, set()))
140
+
141
+ def get_in_degree(self, node: str) -> int:
142
+ """Get the in-degree of a node."""
143
+ if not isinstance(node, str):
144
+ return 0
145
+
146
+ return len(self._reverse_edges.get(node, set()))
147
+
148
+ def get_out_degree(self, node: str) -> int:
149
+ """Get the out-degree of a node."""
150
+ if not isinstance(node, str):
151
+ return 0
152
+
153
+ return len(self._edges.get(node, set()))
154
+
155
+ def clear(self) -> None:
156
+ """Clear all edges."""
157
+ self._edges.clear()
158
+ self._reverse_edges.clear()
159
+ self._edge_count = 0
160
+
161
+ def size(self) -> int:
162
+ """Get the number of edges."""
163
+ return self._edge_count
164
+
165
+ def is_empty(self) -> bool:
166
+ """Check if there are no edges."""
167
+ return self._edge_count == 0
168
+
169
+ def get_nodes(self) -> Set[str]:
170
+ """Get all nodes that have edges."""
171
+ nodes = set()
172
+ nodes.update(self._edges.keys())
173
+ nodes.update(self._reverse_edges.keys())
174
+ return nodes
175
+
176
+ def get_edge_count(self) -> int:
177
+ """Get the total number of edges."""
178
+ return self._edge_count
179
+
180
+ # ============================================================================
181
+ # ITERATION
182
+ # ============================================================================
183
+
184
+ def edges(self) -> Iterator[Tuple[str, str]]:
185
+ """Iterate over all edges as (source, target) pairs."""
186
+ for source, targets in self._edges.items():
187
+ for target in targets:
188
+ yield (source, target)
189
+
190
+ def nodes(self) -> Iterator[str]:
191
+ """Iterate over all nodes."""
192
+ yield from self.get_nodes()
193
+
194
+ def __iter__(self) -> Iterator[Tuple[str, str]]:
195
+ """Iterate over all edges."""
196
+ yield from self.edges()
197
+
198
+ # ============================================================================
199
+ # TREE-GRAPH BASIC SPECIFIC OPERATIONS
200
+ # ============================================================================
201
+
202
+ def get_children(self, node: str) -> List[str]:
203
+ """Get children of a node (for tree-like navigation)."""
204
+ return self.get_outgoing(node)
205
+
206
+ def get_parents(self, node: str) -> List[str]:
207
+ """Get parents of a node (for tree-like navigation)."""
208
+ return self.get_incoming(node)
209
+
210
+ def is_leaf(self, node: str) -> bool:
211
+ """Check if a node is a leaf (no outgoing edges)."""
212
+ return self.get_out_degree(node) == 0
213
+
214
+ def is_root(self, node: str) -> bool:
215
+ """Check if a node is a root (no incoming edges)."""
216
+ return self.get_in_degree(node) == 0
217
+
218
+ def get_roots(self) -> List[str]:
219
+ """Get all root nodes (nodes with no incoming edges)."""
220
+ all_nodes = self.get_nodes()
221
+ return [node for node in all_nodes if self.is_root(node)]
222
+
223
+ def get_leaves(self) -> List[str]:
224
+ """Get all leaf nodes (nodes with no outgoing edges)."""
225
+ all_nodes = self.get_nodes()
226
+ return [node for node in all_nodes if self.is_leaf(node)]
227
+
228
+ def get_path(self, source: str, target: str) -> Optional[List[str]]:
229
+ """Get a simple path from source to target using BFS."""
230
+ if source == target:
231
+ return [source]
232
+
233
+ if source not in self._edges:
234
+ return None
235
+
236
+ # Simple BFS for path finding
237
+ queue = [(source, [source])]
238
+ visited = {source}
239
+
240
+ while queue:
241
+ current, path = queue.pop(0)
242
+
243
+ for neighbor in self._edges.get(current, set()):
244
+ if neighbor == target:
245
+ return path + [neighbor]
246
+
247
+ if neighbor not in visited:
248
+ visited.add(neighbor)
249
+ queue.append((neighbor, path + [neighbor]))
250
+
251
+ return None
252
+
253
+ def is_connected(self, source: str, target: str) -> bool:
254
+ """Check if two nodes are connected."""
255
+ return self.get_path(source, target) is not None
256
+
257
+ def get_stats(self) -> Dict[str, Any]:
258
+ """Get performance statistics."""
259
+ nodes = self.get_nodes()
260
+ return {
261
+ 'edge_count': self._edge_count,
262
+ 'node_count': len(nodes),
263
+ 'max_degree': self._max_degree,
264
+ 'total_additions': self._total_additions,
265
+ 'total_removals': self._total_removals,
266
+ 'roots': len(self.get_roots()),
267
+ 'leaves': len(self.get_leaves()),
268
+ 'strategy': 'TREE_GRAPH_BASIC',
269
+ 'backend': 'Simple adjacency sets with reverse indexing',
270
+ 'traits': [trait.name for trait in EdgeTrait if self.has_trait(trait)]
271
+ }
@@ -0,0 +1,411 @@
1
+ #exonware\xwnode\strategies\impls\edge_weighted_graph.py
2
+ """
3
+ Weighted Graph Edge Strategy Implementation
4
+
5
+ This module implements the WEIGHTED_GRAPH strategy for graphs with numerical
6
+ edge weights, optimized for network algorithms and shortest path computations.
7
+ """
8
+
9
+ from typing import Any, Dict, List, Optional, Set, Tuple, Iterator
10
+ from ._base_edge import aEdgeStrategy
11
+ from ...types import EdgeMode, EdgeTrait
12
+ from ...errors import XWNodeUnsupportedCapabilityError
13
+ import threading
14
+
15
+
16
+ class WeightedEdge:
17
+ """Weighted edge with source, target, and weight."""
18
+
19
+ def __init__(self, source: str, target: str, weight: float = 1.0, data: Any = None):
20
+ self.source = source
21
+ self.target = target
22
+ self.weight = weight
23
+ self.data = data
24
+ self._hash = None
25
+
26
+ def __hash__(self) -> int:
27
+ """Cache hash for performance."""
28
+ if self._hash is None:
29
+ self._hash = hash((self.source, self.target, self.weight))
30
+ return self._hash
31
+
32
+ def __eq__(self, other) -> bool:
33
+ """Structural equality."""
34
+ if not isinstance(other, WeightedEdge):
35
+ return False
36
+ return (self.source == other.source and
37
+ self.target == other.target and
38
+ self.weight == other.weight)
39
+
40
+ def __repr__(self) -> str:
41
+ """String representation."""
42
+ return f"WeightedEdge({self.source} -> {self.target}, weight={self.weight})"
43
+
44
+
45
+ class xWeightedGraphStrategy(aEdgeStrategy):
46
+ """
47
+ Weighted graph edge strategy for graphs with numerical edge weights.
48
+
49
+ Provides efficient storage and retrieval of weighted edges with support
50
+ for network algorithms and shortest path computations.
51
+ """
52
+
53
+ def __init__(self, traits: EdgeTrait = EdgeTrait.NONE, **options):
54
+ """Initialize the weighted graph strategy."""
55
+ super().__init__(EdgeMode.WEIGHTED_GRAPH, traits, **options)
56
+
57
+ self.directed = options.get('directed', True)
58
+ self.default_weight = options.get('default_weight', 1.0)
59
+ self.weight_precision = options.get('weight_precision', 6)
60
+
61
+ # Core weighted graph storage
62
+ self._edges: Dict[Tuple[str, str], WeightedEdge] = {}
63
+ self._adjacency: Dict[str, Dict[str, float]] = {} # source -> {target: weight}
64
+ self._reverse_adjacency: Dict[str, Dict[str, float]] = {} # target -> {source: weight}
65
+ self._edge_count = 0
66
+
67
+ # Statistics
68
+ self._total_edges_added = 0
69
+ self._total_edges_removed = 0
70
+ self._total_weight_updates = 0
71
+ self._max_weight = 0.0
72
+ self._min_weight = float('inf')
73
+
74
+ # Thread safety
75
+ self._lock = threading.RLock()
76
+
77
+ def get_supported_traits(self) -> EdgeTrait:
78
+ """Get the traits supported by the weighted graph strategy."""
79
+ return (EdgeTrait.DIRECTED | EdgeTrait.WEIGHTED | EdgeTrait.SPARSE)
80
+
81
+ def _normalize_weight(self, weight: float) -> float:
82
+ """Normalize weight to specified precision."""
83
+ return round(weight, self.weight_precision)
84
+
85
+ def _update_weight_stats(self, weight: float) -> None:
86
+ """Update weight statistics."""
87
+ self._max_weight = max(self._max_weight, weight)
88
+ self._min_weight = min(self._min_weight, weight)
89
+
90
+ def _add_to_adjacency(self, source: str, target: str, weight: float) -> None:
91
+ """Add edge to adjacency structure."""
92
+ if source not in self._adjacency:
93
+ self._adjacency[source] = {}
94
+ self._adjacency[source][target] = weight
95
+
96
+ if target not in self._reverse_adjacency:
97
+ self._reverse_adjacency[target] = {}
98
+ self._reverse_adjacency[target][source] = weight
99
+
100
+ def _remove_from_adjacency(self, source: str, target: str) -> None:
101
+ """Remove edge from adjacency structure."""
102
+ if source in self._adjacency and target in self._adjacency[source]:
103
+ del self._adjacency[source][target]
104
+ if not self._adjacency[source]:
105
+ del self._adjacency[source]
106
+
107
+ if target in self._reverse_adjacency and source in self._reverse_adjacency[target]:
108
+ del self._reverse_adjacency[target][source]
109
+ if not self._reverse_adjacency[target]:
110
+ del self._reverse_adjacency[target]
111
+
112
+ # ============================================================================
113
+ # CORE OPERATIONS
114
+ # ============================================================================
115
+
116
+ def add_edge(self, source: str, target: str, weight: float = None, data: Any = None) -> bool:
117
+ """Add a weighted edge between source and target."""
118
+ if not isinstance(source, str) or not isinstance(target, str):
119
+ return False
120
+
121
+ if weight is None:
122
+ weight = self.default_weight
123
+
124
+ weight = self._normalize_weight(weight)
125
+
126
+ with self._lock:
127
+ edge_key = (source, target)
128
+
129
+ # Check if edge already exists
130
+ if edge_key in self._edges:
131
+ # Update existing edge
132
+ old_edge = self._edges[edge_key]
133
+ old_edge.weight = weight
134
+ old_edge.data = data
135
+ self._total_weight_updates += 1
136
+ self._update_weight_stats(weight)
137
+ return False
138
+
139
+ # Create new edge
140
+ edge = WeightedEdge(source, target, weight, data)
141
+ self._edges[edge_key] = edge
142
+ self._add_to_adjacency(source, target, weight)
143
+
144
+ # Add reverse edge if undirected
145
+ if not self.directed:
146
+ reverse_key = (target, source)
147
+ if reverse_key not in self._edges:
148
+ reverse_edge = WeightedEdge(target, source, weight, data)
149
+ self._edges[reverse_key] = reverse_edge
150
+ self._add_to_adjacency(target, source, weight)
151
+
152
+ self._edge_count += 1
153
+ self._total_edges_added += 1
154
+ self._update_weight_stats(weight)
155
+ return True
156
+
157
+ def get_edge(self, source: str, target: str) -> Optional[WeightedEdge]:
158
+ """Get edge between source and target."""
159
+ if not isinstance(source, str) or not isinstance(target, str):
160
+ return None
161
+
162
+ with self._lock:
163
+ edge_key = (source, target)
164
+ return self._edges.get(edge_key)
165
+
166
+ def get_edge_weight(self, source: str, target: str) -> Optional[float]:
167
+ """Get weight of edge between source and target."""
168
+ edge = self.get_edge(source, target)
169
+ return edge.weight if edge else None
170
+
171
+ def set_edge_weight(self, source: str, target: str, weight: float) -> bool:
172
+ """Set weight of edge between source and target."""
173
+ if not isinstance(source, str) or not isinstance(target, str):
174
+ return False
175
+
176
+ weight = self._normalize_weight(weight)
177
+
178
+ with self._lock:
179
+ edge_key = (source, target)
180
+ if edge_key in self._edges:
181
+ self._edges[edge_key].weight = weight
182
+ self._adjacency[source][target] = weight
183
+ self._reverse_adjacency[target][source] = weight
184
+
185
+ # Update reverse edge if undirected
186
+ if not self.directed:
187
+ reverse_key = (target, source)
188
+ if reverse_key in self._edges:
189
+ self._edges[reverse_key].weight = weight
190
+ self._adjacency[target][source] = weight
191
+ self._reverse_adjacency[source][target] = weight
192
+
193
+ self._total_weight_updates += 1
194
+ self._update_weight_stats(weight)
195
+ return True
196
+
197
+ return False
198
+
199
+ def delete_edge(self, source: str, target: str) -> bool:
200
+ """Remove edge between source and target."""
201
+ if not isinstance(source, str) or not isinstance(target, str):
202
+ return False
203
+
204
+ with self._lock:
205
+ edge_key = (source, target)
206
+
207
+ if edge_key in self._edges:
208
+ del self._edges[edge_key]
209
+ self._remove_from_adjacency(source, target)
210
+
211
+ # Remove reverse edge if undirected
212
+ if not self.directed:
213
+ reverse_key = (target, source)
214
+ if reverse_key in self._edges:
215
+ del self._edges[reverse_key]
216
+ self._remove_from_adjacency(target, source)
217
+
218
+ self._edge_count -= 1
219
+ self._total_edges_removed += 1
220
+ return True
221
+
222
+ return False
223
+
224
+ def has_edge(self, source: str, target: str) -> bool:
225
+ """Check if edge exists between source and target."""
226
+ if not isinstance(source, str) or not isinstance(target, str):
227
+ return False
228
+
229
+ with self._lock:
230
+ edge_key = (source, target)
231
+ return edge_key in self._edges
232
+
233
+ def get_edges_from(self, source: str) -> Iterator[WeightedEdge]:
234
+ """Get all edges from source node."""
235
+ if not isinstance(source, str):
236
+ return
237
+
238
+ with self._lock:
239
+ if source in self._adjacency:
240
+ for target, weight in self._adjacency[source].items():
241
+ edge_key = (source, target)
242
+ if edge_key in self._edges:
243
+ yield self._edges[edge_key]
244
+
245
+ def get_edges_to(self, target: str) -> Iterator[WeightedEdge]:
246
+ """Get all edges to target node."""
247
+ if not isinstance(target, str):
248
+ return
249
+
250
+ with self._lock:
251
+ if target in self._reverse_adjacency:
252
+ for source, weight in self._reverse_adjacency[target].items():
253
+ edge_key = (source, target)
254
+ if edge_key in self._edges:
255
+ yield self._edges[edge_key]
256
+
257
+ def get_neighbors(self, node: str) -> Iterator[str]:
258
+ """Get all neighbors of node."""
259
+ if not isinstance(node, str):
260
+ return
261
+
262
+ with self._lock:
263
+ if node in self._adjacency:
264
+ yield from self._adjacency[node].keys()
265
+
266
+ def get_incoming_neighbors(self, node: str) -> Iterator[str]:
267
+ """Get all incoming neighbors of node."""
268
+ if not isinstance(node, str):
269
+ return
270
+
271
+ with self._lock:
272
+ if node in self._reverse_adjacency:
273
+ yield from self._reverse_adjacency[node].keys()
274
+
275
+ def get_outgoing_neighbors(self, node: str) -> Iterator[str]:
276
+ """Get all outgoing neighbors of node."""
277
+ return self.get_neighbors(node)
278
+
279
+ def get_edge_count(self) -> int:
280
+ """Get total number of edges."""
281
+ return self._edge_count
282
+
283
+ def get_node_count(self) -> int:
284
+ """Get total number of nodes."""
285
+ with self._lock:
286
+ nodes = set()
287
+ for source, target in self._edges.keys():
288
+ nodes.add(source)
289
+ nodes.add(target)
290
+ return len(nodes)
291
+
292
+ def clear(self) -> None:
293
+ """Clear all edges."""
294
+ with self._lock:
295
+ self._edges.clear()
296
+ self._adjacency.clear()
297
+ self._reverse_adjacency.clear()
298
+ self._edge_count = 0
299
+
300
+ # ============================================================================
301
+ # WEIGHTED GRAPH SPECIFIC OPERATIONS
302
+ # ============================================================================
303
+
304
+ def get_min_weight_edge(self) -> Optional[WeightedEdge]:
305
+ """Get edge with minimum weight."""
306
+ if not self._edges:
307
+ return None
308
+
309
+ with self._lock:
310
+ min_edge = min(self._edges.values(), key=lambda e: e.weight)
311
+ return min_edge
312
+
313
+ def get_max_weight_edge(self) -> Optional[WeightedEdge]:
314
+ """Get edge with maximum weight."""
315
+ if not self._edges:
316
+ return None
317
+
318
+ with self._lock:
319
+ max_edge = max(self._edges.values(), key=lambda e: e.weight)
320
+ return max_edge
321
+
322
+ def get_edges_by_weight_range(self, min_weight: float, max_weight: float) -> Iterator[WeightedEdge]:
323
+ """Get all edges within weight range."""
324
+ with self._lock:
325
+ for edge in self._edges.values():
326
+ if min_weight <= edge.weight <= max_weight:
327
+ yield edge
328
+
329
+ def get_total_weight(self) -> float:
330
+ """Get total weight of all edges."""
331
+ with self._lock:
332
+ return sum(edge.weight for edge in self._edges.values())
333
+
334
+ def get_average_weight(self) -> float:
335
+ """Get average weight of all edges."""
336
+ if not self._edges:
337
+ return 0.0
338
+
339
+ return self.get_total_weight() / len(self._edges)
340
+
341
+ def get_weight_distribution(self) -> Dict[str, int]:
342
+ """Get distribution of edge weights."""
343
+ with self._lock:
344
+ distribution = {}
345
+ for edge in self._edges.values():
346
+ weight_str = str(edge.weight)
347
+ distribution[weight_str] = distribution.get(weight_str, 0) + 1
348
+ return distribution
349
+
350
+ def get_heavy_edges(self, threshold: float) -> Iterator[WeightedEdge]:
351
+ """Get all edges with weight above threshold."""
352
+ with self._lock:
353
+ for edge in self._edges.values():
354
+ if edge.weight > threshold:
355
+ yield edge
356
+
357
+ def get_light_edges(self, threshold: float) -> Iterator[WeightedEdge]:
358
+ """Get all edges with weight below threshold."""
359
+ with self._lock:
360
+ for edge in self._edges.values():
361
+ if edge.weight < threshold:
362
+ yield edge
363
+
364
+ def normalize_weights(self, target_min: float = 0.0, target_max: float = 1.0) -> None:
365
+ """Normalize all edge weights to target range."""
366
+ if not self._edges:
367
+ return
368
+
369
+ with self._lock:
370
+ # Find current min and max weights
371
+ current_min = min(edge.weight for edge in self._edges.values())
372
+ current_max = max(edge.weight for edge in self._edges.values())
373
+
374
+ if current_min == current_max:
375
+ # All weights are the same, set to target_min
376
+ for edge in self._edges.values():
377
+ edge.weight = target_min
378
+ self._adjacency[edge.source][edge.target] = target_min
379
+ self._reverse_adjacency[edge.target][edge.source] = target_min
380
+ else:
381
+ # Normalize weights
382
+ for edge in self._edges.values():
383
+ normalized_weight = target_min + (edge.weight - current_min) * (target_max - target_min) / (current_max - current_min)
384
+ edge.weight = self._normalize_weight(normalized_weight)
385
+ self._adjacency[edge.source][edge.target] = edge.weight
386
+ self._reverse_adjacency[edge.target][edge.source] = edge.weight
387
+
388
+ # Update statistics
389
+ self._min_weight = target_min
390
+ self._max_weight = target_max
391
+
392
+ def get_stats(self) -> Dict[str, Any]:
393
+ """Get performance statistics."""
394
+ with self._lock:
395
+ return {
396
+ 'edge_count': self._edge_count,
397
+ 'node_count': self.get_node_count(),
398
+ 'total_edges_added': self._total_edges_added,
399
+ 'total_edges_removed': self._total_edges_removed,
400
+ 'total_weight_updates': self._total_weight_updates,
401
+ 'min_weight': self._min_weight if self._min_weight != float('inf') else 0.0,
402
+ 'max_weight': self._max_weight,
403
+ 'average_weight': self.get_average_weight(),
404
+ 'total_weight': self.get_total_weight(),
405
+ 'directed': self.directed,
406
+ 'default_weight': self.default_weight,
407
+ 'weight_precision': self.weight_precision,
408
+ 'strategy': 'WEIGHTED_GRAPH',
409
+ 'backend': 'Weighted graph with numerical edge weights',
410
+ 'traits': [trait.name for trait in EdgeTrait if self.has_trait(trait)]
411
+ }