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,555 @@
|
|
1
|
+
"""
|
2
|
+
Flow Network Edge Strategy Implementation
|
3
|
+
|
4
|
+
This module implements the FLOW_NETWORK strategy for flow graphs with
|
5
|
+
capacity constraints, flow algorithms, and network flow optimization.
|
6
|
+
"""
|
7
|
+
|
8
|
+
from typing import Any, Iterator, Dict, List, Set, Optional, Tuple, DefaultDict
|
9
|
+
from collections import defaultdict, deque
|
10
|
+
import math
|
11
|
+
from ._base_edge import aEdgeStrategy
|
12
|
+
from ...types import EdgeMode, EdgeTrait
|
13
|
+
|
14
|
+
|
15
|
+
class FlowEdge:
|
16
|
+
"""Represents an edge in a flow network with capacity and flow."""
|
17
|
+
|
18
|
+
def __init__(self, edge_id: str, source: str, target: str,
|
19
|
+
capacity: float, flow: float = 0.0, **properties):
|
20
|
+
self.edge_id = edge_id
|
21
|
+
self.source = source
|
22
|
+
self.target = target
|
23
|
+
self.capacity = max(0.0, float(capacity))
|
24
|
+
self.flow = max(0.0, min(float(flow), self.capacity))
|
25
|
+
self.properties = properties.copy()
|
26
|
+
|
27
|
+
# Flow network specific properties
|
28
|
+
self.cost_per_unit = properties.get('cost', 0.0) # For min-cost flow
|
29
|
+
self.is_residual = properties.get('is_residual', False)
|
30
|
+
|
31
|
+
@property
|
32
|
+
def residual_capacity(self) -> float:
|
33
|
+
"""Get remaining capacity for flow."""
|
34
|
+
return self.capacity - self.flow
|
35
|
+
|
36
|
+
@property
|
37
|
+
def utilization(self) -> float:
|
38
|
+
"""Get capacity utilization as percentage."""
|
39
|
+
return (self.flow / self.capacity * 100) if self.capacity > 0 else 0.0
|
40
|
+
|
41
|
+
def add_flow(self, amount: float) -> float:
|
42
|
+
"""Add flow to edge, returns actual amount added."""
|
43
|
+
max_addable = min(amount, self.residual_capacity)
|
44
|
+
self.flow += max_addable
|
45
|
+
return max_addable
|
46
|
+
|
47
|
+
def remove_flow(self, amount: float) -> float:
|
48
|
+
"""Remove flow from edge, returns actual amount removed."""
|
49
|
+
max_removable = min(amount, self.flow)
|
50
|
+
self.flow -= max_removable
|
51
|
+
return max_removable
|
52
|
+
|
53
|
+
def reset_flow(self) -> None:
|
54
|
+
"""Reset flow to zero."""
|
55
|
+
self.flow = 0.0
|
56
|
+
|
57
|
+
def is_saturated(self) -> bool:
|
58
|
+
"""Check if edge is at full capacity."""
|
59
|
+
return self.flow >= self.capacity
|
60
|
+
|
61
|
+
def reverse_edge(self) -> 'FlowEdge':
|
62
|
+
"""Create reverse edge for residual graph."""
|
63
|
+
return FlowEdge(
|
64
|
+
f"{self.edge_id}_reverse",
|
65
|
+
self.target,
|
66
|
+
self.source,
|
67
|
+
capacity=self.flow, # Reverse capacity is current flow
|
68
|
+
flow=0.0,
|
69
|
+
cost=-self.cost_per_unit, # Negative cost for reverse
|
70
|
+
is_residual=True
|
71
|
+
)
|
72
|
+
|
73
|
+
def to_dict(self) -> Dict[str, Any]:
|
74
|
+
"""Convert to dictionary representation."""
|
75
|
+
return {
|
76
|
+
'id': self.edge_id,
|
77
|
+
'source': self.source,
|
78
|
+
'target': self.target,
|
79
|
+
'capacity': self.capacity,
|
80
|
+
'flow': self.flow,
|
81
|
+
'residual_capacity': self.residual_capacity,
|
82
|
+
'utilization': self.utilization,
|
83
|
+
'cost_per_unit': self.cost_per_unit,
|
84
|
+
'is_residual': self.is_residual,
|
85
|
+
'properties': self.properties
|
86
|
+
}
|
87
|
+
|
88
|
+
def __repr__(self) -> str:
|
89
|
+
return f"FlowEdge({self.source}->{self.target}: {self.flow}/{self.capacity})"
|
90
|
+
|
91
|
+
|
92
|
+
class xFlowNetworkStrategy(aEdgeStrategy):
|
93
|
+
"""
|
94
|
+
Flow Network strategy for capacity-constrained flow graphs.
|
95
|
+
|
96
|
+
Supports max flow, min-cost flow, and multi-commodity flow algorithms
|
97
|
+
with residual graph construction and flow optimization.
|
98
|
+
"""
|
99
|
+
|
100
|
+
def __init__(self, traits: EdgeTrait = EdgeTrait.NONE, **options):
|
101
|
+
"""Initialize the Flow Network strategy."""
|
102
|
+
super().__init__(EdgeMode.FLOW_NETWORK, traits, **options)
|
103
|
+
|
104
|
+
self.enable_residual_graph = options.get('enable_residual_graph', True)
|
105
|
+
self.auto_balance = options.get('auto_balance', True) # Auto-balance flow
|
106
|
+
self.precision = options.get('precision', 1e-9) # Floating point precision
|
107
|
+
|
108
|
+
# Core storage
|
109
|
+
self._edges: Dict[str, FlowEdge] = {} # edge_id -> FlowEdge
|
110
|
+
self._outgoing: DefaultDict[str, Dict[str, str]] = defaultdict(dict) # source -> {target: edge_id}
|
111
|
+
self._incoming: DefaultDict[str, Dict[str, str]] = defaultdict(dict) # target -> {source: edge_id}
|
112
|
+
self._vertices: Set[str] = set()
|
113
|
+
|
114
|
+
# Flow network state
|
115
|
+
self._source_vertices: Set[str] = set() # Sources (supply > 0)
|
116
|
+
self._sink_vertices: Set[str] = set() # Sinks (demand > 0)
|
117
|
+
self._vertex_supply: Dict[str, float] = defaultdict(float) # Net supply/demand
|
118
|
+
|
119
|
+
# Flow statistics
|
120
|
+
self._total_capacity = 0.0
|
121
|
+
self._total_flow = 0.0
|
122
|
+
self._edge_count = 0
|
123
|
+
self._edge_id_counter = 0
|
124
|
+
|
125
|
+
def get_supported_traits(self) -> EdgeTrait:
|
126
|
+
"""Get the traits supported by the flow network strategy."""
|
127
|
+
return (EdgeTrait.DIRECTED | EdgeTrait.WEIGHTED | EdgeTrait.SPARSE)
|
128
|
+
|
129
|
+
def _generate_edge_id(self) -> str:
|
130
|
+
"""Generate unique edge ID."""
|
131
|
+
self._edge_id_counter += 1
|
132
|
+
return f"flow_edge_{self._edge_id_counter}"
|
133
|
+
|
134
|
+
def _update_flow_statistics(self) -> None:
|
135
|
+
"""Update flow network statistics."""
|
136
|
+
self._total_capacity = sum(edge.capacity for edge in self._edges.values())
|
137
|
+
self._total_flow = sum(edge.flow for edge in self._edges.values())
|
138
|
+
|
139
|
+
# ============================================================================
|
140
|
+
# CORE EDGE OPERATIONS
|
141
|
+
# ============================================================================
|
142
|
+
|
143
|
+
def add_edge(self, source: str, target: str, **properties) -> str:
|
144
|
+
"""Add flow edge with capacity."""
|
145
|
+
capacity = properties.pop('capacity', 1.0)
|
146
|
+
flow = properties.pop('flow', 0.0)
|
147
|
+
edge_id = properties.pop('edge_id', self._generate_edge_id())
|
148
|
+
|
149
|
+
if edge_id in self._edges:
|
150
|
+
raise ValueError(f"Edge ID {edge_id} already exists")
|
151
|
+
|
152
|
+
# Create flow edge
|
153
|
+
flow_edge = FlowEdge(edge_id, source, target, capacity, flow, **properties)
|
154
|
+
|
155
|
+
# Store edge and update indices
|
156
|
+
self._edges[edge_id] = flow_edge
|
157
|
+
self._outgoing[source][target] = edge_id
|
158
|
+
self._incoming[target][source] = edge_id
|
159
|
+
|
160
|
+
# Add vertices
|
161
|
+
self._vertices.add(source)
|
162
|
+
self._vertices.add(target)
|
163
|
+
|
164
|
+
self._edge_count += 1
|
165
|
+
self._update_flow_statistics()
|
166
|
+
|
167
|
+
return edge_id
|
168
|
+
|
169
|
+
def remove_edge(self, source: str, target: str, edge_id: Optional[str] = None) -> bool:
|
170
|
+
"""Remove flow edge."""
|
171
|
+
if edge_id and edge_id in self._edges:
|
172
|
+
edge = self._edges[edge_id]
|
173
|
+
if edge.source == source and edge.target == target:
|
174
|
+
# Remove from indices
|
175
|
+
del self._edges[edge_id]
|
176
|
+
del self._outgoing[source][target]
|
177
|
+
del self._incoming[target][source]
|
178
|
+
|
179
|
+
self._edge_count -= 1
|
180
|
+
self._update_flow_statistics()
|
181
|
+
return True
|
182
|
+
else:
|
183
|
+
# Find edge by endpoints
|
184
|
+
if target in self._outgoing.get(source, {}):
|
185
|
+
edge_id = self._outgoing[source][target]
|
186
|
+
return self.remove_edge(source, target, edge_id)
|
187
|
+
|
188
|
+
return False
|
189
|
+
|
190
|
+
def has_edge(self, source: str, target: str) -> bool:
|
191
|
+
"""Check if edge exists."""
|
192
|
+
return target in self._outgoing.get(source, {})
|
193
|
+
|
194
|
+
def get_edge_data(self, source: str, target: str) -> Optional[Dict[str, Any]]:
|
195
|
+
"""Get edge data."""
|
196
|
+
if target in self._outgoing.get(source, {}):
|
197
|
+
edge_id = self._outgoing[source][target]
|
198
|
+
edge = self._edges[edge_id]
|
199
|
+
return edge.to_dict()
|
200
|
+
return None
|
201
|
+
|
202
|
+
def get_flow_edge(self, source: str, target: str) -> Optional[FlowEdge]:
|
203
|
+
"""Get flow edge object."""
|
204
|
+
if target in self._outgoing.get(source, {}):
|
205
|
+
edge_id = self._outgoing[source][target]
|
206
|
+
return self._edges[edge_id]
|
207
|
+
return None
|
208
|
+
|
209
|
+
def neighbors(self, vertex: str, direction: str = 'out') -> Iterator[str]:
|
210
|
+
"""Get neighbors of vertex."""
|
211
|
+
if direction in ['out', 'both']:
|
212
|
+
for target in self._outgoing.get(vertex, {}):
|
213
|
+
yield target
|
214
|
+
|
215
|
+
if direction in ['in', 'both']:
|
216
|
+
for source in self._incoming.get(vertex, {}):
|
217
|
+
yield source
|
218
|
+
|
219
|
+
def degree(self, vertex: str, direction: str = 'out') -> int:
|
220
|
+
"""Get degree of vertex."""
|
221
|
+
if direction == 'out':
|
222
|
+
return len(self._outgoing.get(vertex, {}))
|
223
|
+
elif direction == 'in':
|
224
|
+
return len(self._incoming.get(vertex, {}))
|
225
|
+
else: # both
|
226
|
+
return len(self._outgoing.get(vertex, {})) + len(self._incoming.get(vertex, {}))
|
227
|
+
|
228
|
+
def edges(self, data: bool = False, include_residual: bool = False) -> Iterator[tuple]:
|
229
|
+
"""Get all edges."""
|
230
|
+
for edge in self._edges.values():
|
231
|
+
if not include_residual and edge.is_residual:
|
232
|
+
continue
|
233
|
+
|
234
|
+
if data:
|
235
|
+
yield (edge.source, edge.target, edge.to_dict())
|
236
|
+
else:
|
237
|
+
yield (edge.source, edge.target)
|
238
|
+
|
239
|
+
def vertices(self) -> Iterator[str]:
|
240
|
+
"""Get all vertices."""
|
241
|
+
return iter(self._vertices)
|
242
|
+
|
243
|
+
def __len__(self) -> int:
|
244
|
+
"""Get number of edges."""
|
245
|
+
return self._edge_count
|
246
|
+
|
247
|
+
def vertex_count(self) -> int:
|
248
|
+
"""Get number of vertices."""
|
249
|
+
return len(self._vertices)
|
250
|
+
|
251
|
+
def clear(self) -> None:
|
252
|
+
"""Clear all data."""
|
253
|
+
self._edges.clear()
|
254
|
+
self._outgoing.clear()
|
255
|
+
self._incoming.clear()
|
256
|
+
self._vertices.clear()
|
257
|
+
self._source_vertices.clear()
|
258
|
+
self._sink_vertices.clear()
|
259
|
+
self._vertex_supply.clear()
|
260
|
+
|
261
|
+
self._total_capacity = 0.0
|
262
|
+
self._total_flow = 0.0
|
263
|
+
self._edge_count = 0
|
264
|
+
self._edge_id_counter = 0
|
265
|
+
|
266
|
+
def add_vertex(self, vertex: str, supply: float = 0.0) -> None:
|
267
|
+
"""Add vertex with supply/demand."""
|
268
|
+
self._vertices.add(vertex)
|
269
|
+
self.set_vertex_supply(vertex, supply)
|
270
|
+
|
271
|
+
def remove_vertex(self, vertex: str) -> bool:
|
272
|
+
"""Remove vertex and all its edges."""
|
273
|
+
if vertex not in self._vertices:
|
274
|
+
return False
|
275
|
+
|
276
|
+
# Remove all outgoing edges
|
277
|
+
outgoing_targets = list(self._outgoing.get(vertex, {}).keys())
|
278
|
+
for target in outgoing_targets:
|
279
|
+
self.remove_edge(vertex, target)
|
280
|
+
|
281
|
+
# Remove all incoming edges
|
282
|
+
incoming_sources = list(self._incoming.get(vertex, {}).keys())
|
283
|
+
for source in incoming_sources:
|
284
|
+
self.remove_edge(source, vertex)
|
285
|
+
|
286
|
+
# Remove vertex
|
287
|
+
self._vertices.discard(vertex)
|
288
|
+
self._source_vertices.discard(vertex)
|
289
|
+
self._sink_vertices.discard(vertex)
|
290
|
+
self._vertex_supply.pop(vertex, None)
|
291
|
+
|
292
|
+
return True
|
293
|
+
|
294
|
+
# ============================================================================
|
295
|
+
# FLOW NETWORK OPERATIONS
|
296
|
+
# ============================================================================
|
297
|
+
|
298
|
+
def set_vertex_supply(self, vertex: str, supply: float) -> None:
|
299
|
+
"""Set vertex supply (positive) or demand (negative)."""
|
300
|
+
self._vertex_supply[vertex] = supply
|
301
|
+
|
302
|
+
if supply > self.precision:
|
303
|
+
self._source_vertices.add(vertex)
|
304
|
+
self._sink_vertices.discard(vertex)
|
305
|
+
elif supply < -self.precision:
|
306
|
+
self._sink_vertices.add(vertex)
|
307
|
+
self._source_vertices.discard(vertex)
|
308
|
+
else:
|
309
|
+
self._source_vertices.discard(vertex)
|
310
|
+
self._sink_vertices.discard(vertex)
|
311
|
+
|
312
|
+
def get_vertex_supply(self, vertex: str) -> float:
|
313
|
+
"""Get vertex supply/demand."""
|
314
|
+
return self._vertex_supply.get(vertex, 0.0)
|
315
|
+
|
316
|
+
def add_flow(self, source: str, target: str, amount: float) -> float:
|
317
|
+
"""Add flow to edge, returns actual amount added."""
|
318
|
+
edge = self.get_flow_edge(source, target)
|
319
|
+
if edge:
|
320
|
+
added = edge.add_flow(amount)
|
321
|
+
self._update_flow_statistics()
|
322
|
+
return added
|
323
|
+
return 0.0
|
324
|
+
|
325
|
+
def remove_flow(self, source: str, target: str, amount: float) -> float:
|
326
|
+
"""Remove flow from edge, returns actual amount removed."""
|
327
|
+
edge = self.get_flow_edge(source, target)
|
328
|
+
if edge:
|
329
|
+
removed = edge.remove_flow(amount)
|
330
|
+
self._update_flow_statistics()
|
331
|
+
return removed
|
332
|
+
return 0.0
|
333
|
+
|
334
|
+
def reset_all_flows(self) -> None:
|
335
|
+
"""Reset all edge flows to zero."""
|
336
|
+
for edge in self._edges.values():
|
337
|
+
edge.reset_flow()
|
338
|
+
self._update_flow_statistics()
|
339
|
+
|
340
|
+
def get_residual_graph(self) -> 'xFlowNetworkStrategy':
|
341
|
+
"""Create residual graph for flow algorithms."""
|
342
|
+
residual = xFlowNetworkStrategy(
|
343
|
+
traits=self._traits,
|
344
|
+
enable_residual_graph=False, # Avoid recursive residual graphs
|
345
|
+
precision=self.precision
|
346
|
+
)
|
347
|
+
|
348
|
+
# Add all vertices with same supply/demand
|
349
|
+
for vertex in self._vertices:
|
350
|
+
residual.add_vertex(vertex, self.get_vertex_supply(vertex))
|
351
|
+
|
352
|
+
# Add forward and backward edges
|
353
|
+
for edge in self._edges.values():
|
354
|
+
if not edge.is_residual: # Only process original edges
|
355
|
+
# Forward edge (residual capacity)
|
356
|
+
if edge.residual_capacity > self.precision:
|
357
|
+
residual.add_edge(
|
358
|
+
edge.source, edge.target,
|
359
|
+
capacity=edge.residual_capacity,
|
360
|
+
cost=edge.cost_per_unit,
|
361
|
+
edge_id=f"{edge.edge_id}_forward",
|
362
|
+
is_residual=True
|
363
|
+
)
|
364
|
+
|
365
|
+
# Backward edge (current flow)
|
366
|
+
if edge.flow > self.precision:
|
367
|
+
residual.add_edge(
|
368
|
+
edge.target, edge.source,
|
369
|
+
capacity=edge.flow,
|
370
|
+
cost=-edge.cost_per_unit,
|
371
|
+
edge_id=f"{edge.edge_id}_backward",
|
372
|
+
is_residual=True
|
373
|
+
)
|
374
|
+
|
375
|
+
return residual
|
376
|
+
|
377
|
+
def find_augmenting_path(self, source: str, sink: str) -> Optional[List[str]]:
|
378
|
+
"""Find augmenting path using BFS (Ford-Fulkerson algorithm)."""
|
379
|
+
if source not in self._vertices or sink not in self._vertices:
|
380
|
+
return None
|
381
|
+
|
382
|
+
# BFS to find path with available capacity
|
383
|
+
queue = deque([source])
|
384
|
+
visited = {source}
|
385
|
+
parent = {}
|
386
|
+
|
387
|
+
while queue:
|
388
|
+
current = queue.popleft()
|
389
|
+
|
390
|
+
if current == sink:
|
391
|
+
# Reconstruct path
|
392
|
+
path = []
|
393
|
+
node = sink
|
394
|
+
while node != source:
|
395
|
+
path.append(node)
|
396
|
+
node = parent[node]
|
397
|
+
path.append(source)
|
398
|
+
return path[::-1]
|
399
|
+
|
400
|
+
# Explore neighbors with available capacity
|
401
|
+
for neighbor in self.neighbors(current, 'out'):
|
402
|
+
if neighbor not in visited:
|
403
|
+
edge = self.get_flow_edge(current, neighbor)
|
404
|
+
if edge and edge.residual_capacity > self.precision:
|
405
|
+
visited.add(neighbor)
|
406
|
+
parent[neighbor] = current
|
407
|
+
queue.append(neighbor)
|
408
|
+
|
409
|
+
return None
|
410
|
+
|
411
|
+
def max_flow(self, source: str, sink: str) -> float:
|
412
|
+
"""Compute maximum flow using Ford-Fulkerson algorithm."""
|
413
|
+
if source not in self._vertices or sink not in self._vertices:
|
414
|
+
return 0.0
|
415
|
+
|
416
|
+
total_flow = 0.0
|
417
|
+
max_iterations = 1000 # Prevent infinite loops
|
418
|
+
iteration = 0
|
419
|
+
|
420
|
+
while iteration < max_iterations:
|
421
|
+
# Find augmenting path
|
422
|
+
path = self.find_augmenting_path(source, sink)
|
423
|
+
if not path:
|
424
|
+
break
|
425
|
+
|
426
|
+
# Find bottleneck capacity
|
427
|
+
bottleneck = float('inf')
|
428
|
+
for i in range(len(path) - 1):
|
429
|
+
edge = self.get_flow_edge(path[i], path[i + 1])
|
430
|
+
if edge:
|
431
|
+
bottleneck = min(bottleneck, edge.residual_capacity)
|
432
|
+
|
433
|
+
# Augment flow along path
|
434
|
+
if bottleneck > self.precision:
|
435
|
+
for i in range(len(path) - 1):
|
436
|
+
self.add_flow(path[i], path[i + 1], bottleneck)
|
437
|
+
total_flow += bottleneck
|
438
|
+
|
439
|
+
iteration += 1
|
440
|
+
|
441
|
+
return total_flow
|
442
|
+
|
443
|
+
def min_cut(self, source: str, sink: str) -> Tuple[Set[str], Set[str], float]:
|
444
|
+
"""Find minimum cut using max-flow min-cut theorem."""
|
445
|
+
# First compute max flow
|
446
|
+
max_flow_value = self.max_flow(source, sink)
|
447
|
+
|
448
|
+
# Find reachable vertices from source in residual graph
|
449
|
+
residual = self.get_residual_graph()
|
450
|
+
reachable = set()
|
451
|
+
queue = deque([source])
|
452
|
+
reachable.add(source)
|
453
|
+
|
454
|
+
while queue:
|
455
|
+
current = queue.popleft()
|
456
|
+
for neighbor in residual.neighbors(current, 'out'):
|
457
|
+
if neighbor not in reachable:
|
458
|
+
edge = residual.get_flow_edge(current, neighbor)
|
459
|
+
if edge and edge.capacity > self.precision:
|
460
|
+
reachable.add(neighbor)
|
461
|
+
queue.append(neighbor)
|
462
|
+
|
463
|
+
# Cut is between reachable and non-reachable vertices
|
464
|
+
cut_s = reachable
|
465
|
+
cut_t = self._vertices - reachable
|
466
|
+
|
467
|
+
return cut_s, cut_t, max_flow_value
|
468
|
+
|
469
|
+
def is_flow_feasible(self) -> bool:
|
470
|
+
"""Check if current flow satisfies flow conservation."""
|
471
|
+
for vertex in self._vertices:
|
472
|
+
flow_in = sum(
|
473
|
+
self.get_flow_edge(source, vertex).flow
|
474
|
+
for source in self._incoming.get(vertex, {})
|
475
|
+
if self.get_flow_edge(source, vertex)
|
476
|
+
)
|
477
|
+
|
478
|
+
flow_out = sum(
|
479
|
+
self.get_flow_edge(vertex, target).flow
|
480
|
+
for target in self._outgoing.get(vertex, {})
|
481
|
+
if self.get_flow_edge(vertex, target)
|
482
|
+
)
|
483
|
+
|
484
|
+
net_flow = flow_in - flow_out
|
485
|
+
expected_supply = self.get_vertex_supply(vertex)
|
486
|
+
|
487
|
+
if abs(net_flow - expected_supply) > self.precision:
|
488
|
+
return False
|
489
|
+
|
490
|
+
return True
|
491
|
+
|
492
|
+
def get_flow_statistics(self) -> Dict[str, Any]:
|
493
|
+
"""Get comprehensive flow statistics."""
|
494
|
+
if not self._edges:
|
495
|
+
return {
|
496
|
+
'total_capacity': 0, 'total_flow': 0, 'utilization': 0,
|
497
|
+
'saturated_edges': 0, 'empty_edges': 0, 'sources': 0, 'sinks': 0
|
498
|
+
}
|
499
|
+
|
500
|
+
saturated = sum(1 for edge in self._edges.values() if edge.is_saturated())
|
501
|
+
empty = sum(1 for edge in self._edges.values() if edge.flow < self.precision)
|
502
|
+
utilizations = [edge.utilization for edge in self._edges.values()]
|
503
|
+
|
504
|
+
return {
|
505
|
+
'vertices': len(self._vertices),
|
506
|
+
'edges': self._edge_count,
|
507
|
+
'sources': len(self._source_vertices),
|
508
|
+
'sinks': len(self._sink_vertices),
|
509
|
+
'total_capacity': self._total_capacity,
|
510
|
+
'total_flow': self._total_flow,
|
511
|
+
'overall_utilization': (self._total_flow / self._total_capacity * 100) if self._total_capacity > 0 else 0,
|
512
|
+
'saturated_edges': saturated,
|
513
|
+
'empty_edges': empty,
|
514
|
+
'avg_edge_utilization': sum(utilizations) / len(utilizations) if utilizations else 0,
|
515
|
+
'max_edge_utilization': max(utilizations) if utilizations else 0,
|
516
|
+
'flow_feasible': self.is_flow_feasible()
|
517
|
+
}
|
518
|
+
|
519
|
+
# ============================================================================
|
520
|
+
# PERFORMANCE CHARACTERISTICS
|
521
|
+
# ============================================================================
|
522
|
+
|
523
|
+
@property
|
524
|
+
def backend_info(self) -> Dict[str, Any]:
|
525
|
+
"""Get backend implementation info."""
|
526
|
+
return {
|
527
|
+
'strategy': 'FLOW_NETWORK',
|
528
|
+
'backend': 'Capacity-constrained adjacency lists with flow tracking',
|
529
|
+
'enable_residual_graph': self.enable_residual_graph,
|
530
|
+
'auto_balance': self.auto_balance,
|
531
|
+
'precision': self.precision,
|
532
|
+
'complexity': {
|
533
|
+
'add_edge': 'O(1)',
|
534
|
+
'max_flow': 'O(V * E^2)', # Ford-Fulkerson
|
535
|
+
'min_cut': 'O(V * E^2)',
|
536
|
+
'feasibility_check': 'O(V + E)',
|
537
|
+
'space': 'O(V + E)'
|
538
|
+
}
|
539
|
+
}
|
540
|
+
|
541
|
+
@property
|
542
|
+
def metrics(self) -> Dict[str, Any]:
|
543
|
+
"""Get performance metrics."""
|
544
|
+
stats = self.get_flow_statistics()
|
545
|
+
|
546
|
+
return {
|
547
|
+
'vertices': stats['vertices'],
|
548
|
+
'edges': stats['edges'],
|
549
|
+
'total_capacity': f"{stats['total_capacity']:.2f}",
|
550
|
+
'total_flow': f"{stats['total_flow']:.2f}",
|
551
|
+
'utilization': f"{stats['overall_utilization']:.1f}%",
|
552
|
+
'saturated_edges': stats['saturated_edges'],
|
553
|
+
'flow_feasible': stats['flow_feasible'],
|
554
|
+
'memory_usage': f"{self._edge_count * 120 + len(self._vertices) * 50} bytes (estimated)"
|
555
|
+
}
|