exonware-xwnode 0.0.1.22__py3-none-any.whl → 0.0.1.23__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 +1 -1
- exonware/xwnode/__init__.py +18 -5
- exonware/xwnode/add_strategy_types.py +165 -0
- exonware/xwnode/common/__init__.py +1 -1
- exonware/xwnode/common/graph/__init__.py +30 -0
- exonware/xwnode/common/graph/caching.py +131 -0
- exonware/xwnode/common/graph/contracts.py +100 -0
- exonware/xwnode/common/graph/errors.py +44 -0
- exonware/xwnode/common/graph/indexing.py +260 -0
- exonware/xwnode/common/graph/manager.py +568 -0
- exonware/xwnode/common/management/__init__.py +3 -5
- exonware/xwnode/common/management/manager.py +2 -2
- exonware/xwnode/common/management/migration.py +3 -3
- exonware/xwnode/common/monitoring/__init__.py +3 -5
- exonware/xwnode/common/monitoring/metrics.py +6 -2
- exonware/xwnode/common/monitoring/pattern_detector.py +1 -1
- exonware/xwnode/common/monitoring/performance_monitor.py +5 -1
- exonware/xwnode/common/patterns/__init__.py +3 -5
- exonware/xwnode/common/patterns/flyweight.py +5 -1
- exonware/xwnode/common/patterns/registry.py +202 -183
- exonware/xwnode/common/utils/__init__.py +25 -11
- exonware/xwnode/common/utils/simple.py +1 -1
- exonware/xwnode/config.py +3 -8
- exonware/xwnode/contracts.py +4 -105
- exonware/xwnode/defs.py +413 -159
- exonware/xwnode/edges/strategies/__init__.py +86 -4
- exonware/xwnode/edges/strategies/_base_edge.py +2 -2
- exonware/xwnode/edges/strategies/adj_list.py +287 -121
- exonware/xwnode/edges/strategies/adj_matrix.py +316 -222
- exonware/xwnode/edges/strategies/base.py +1 -1
- exonware/xwnode/edges/strategies/{edge_bidir_wrapper.py → bidir_wrapper.py} +45 -4
- exonware/xwnode/edges/strategies/bitemporal.py +520 -0
- exonware/xwnode/edges/strategies/{edge_block_adj_matrix.py → block_adj_matrix.py} +77 -6
- exonware/xwnode/edges/strategies/bv_graph.py +664 -0
- exonware/xwnode/edges/strategies/compressed_graph.py +217 -0
- exonware/xwnode/edges/strategies/{edge_coo.py → coo.py} +46 -4
- exonware/xwnode/edges/strategies/{edge_csc.py → csc.py} +45 -4
- exonware/xwnode/edges/strategies/{edge_csr.py → csr.py} +94 -12
- exonware/xwnode/edges/strategies/{edge_dynamic_adj_list.py → dynamic_adj_list.py} +46 -4
- exonware/xwnode/edges/strategies/edge_list.py +168 -0
- exonware/xwnode/edges/strategies/edge_property_store.py +2 -2
- exonware/xwnode/edges/strategies/euler_tour.py +560 -0
- exonware/xwnode/edges/strategies/{edge_flow_network.py → flow_network.py} +2 -2
- exonware/xwnode/edges/strategies/graphblas.py +449 -0
- exonware/xwnode/edges/strategies/hnsw.py +637 -0
- exonware/xwnode/edges/strategies/hop2_labels.py +467 -0
- exonware/xwnode/edges/strategies/{edge_hyperedge_set.py → hyperedge_set.py} +2 -2
- exonware/xwnode/edges/strategies/incidence_matrix.py +250 -0
- exonware/xwnode/edges/strategies/k2_tree.py +613 -0
- exonware/xwnode/edges/strategies/link_cut.py +626 -0
- exonware/xwnode/edges/strategies/multiplex.py +532 -0
- exonware/xwnode/edges/strategies/{edge_neural_graph.py → neural_graph.py} +2 -2
- exonware/xwnode/edges/strategies/{edge_octree.py → octree.py} +69 -11
- exonware/xwnode/edges/strategies/{edge_quadtree.py → quadtree.py} +66 -10
- exonware/xwnode/edges/strategies/roaring_adj.py +438 -0
- exonware/xwnode/edges/strategies/{edge_rtree.py → rtree.py} +43 -5
- exonware/xwnode/edges/strategies/{edge_temporal_edgeset.py → temporal_edgeset.py} +24 -5
- exonware/xwnode/edges/strategies/{edge_tree_graph_basic.py → tree_graph_basic.py} +78 -7
- exonware/xwnode/edges/strategies/{edge_weighted_graph.py → weighted_graph.py} +188 -10
- exonware/xwnode/errors.py +3 -6
- exonware/xwnode/facade.py +20 -20
- exonware/xwnode/nodes/strategies/__init__.py +29 -9
- exonware/xwnode/nodes/strategies/adjacency_list.py +650 -177
- exonware/xwnode/nodes/strategies/aho_corasick.py +358 -183
- exonware/xwnode/nodes/strategies/array_list.py +36 -3
- exonware/xwnode/nodes/strategies/art.py +581 -0
- exonware/xwnode/nodes/strategies/{node_avl_tree.py → avl_tree.py} +77 -6
- exonware/xwnode/nodes/strategies/{node_b_plus_tree.py → b_plus_tree.py} +81 -40
- exonware/xwnode/nodes/strategies/{node_btree.py → b_tree.py} +79 -9
- exonware/xwnode/nodes/strategies/base.py +469 -98
- exonware/xwnode/nodes/strategies/{node_bitmap.py → bitmap.py} +12 -12
- exonware/xwnode/nodes/strategies/{node_bitset_dynamic.py → bitset_dynamic.py} +11 -11
- exonware/xwnode/nodes/strategies/{node_bloom_filter.py → bloom_filter.py} +15 -2
- exonware/xwnode/nodes/strategies/bloomier_filter.py +519 -0
- exonware/xwnode/nodes/strategies/bw_tree.py +531 -0
- exonware/xwnode/nodes/strategies/contracts.py +1 -1
- exonware/xwnode/nodes/strategies/{node_count_min_sketch.py → count_min_sketch.py} +3 -2
- exonware/xwnode/nodes/strategies/{node_cow_tree.py → cow_tree.py} +135 -13
- exonware/xwnode/nodes/strategies/crdt_map.py +629 -0
- exonware/xwnode/nodes/strategies/{node_cuckoo_hash.py → cuckoo_hash.py} +2 -2
- exonware/xwnode/nodes/strategies/{node_xdata_optimized.py → data_interchange_optimized.py} +21 -4
- exonware/xwnode/nodes/strategies/dawg.py +876 -0
- exonware/xwnode/nodes/strategies/deque.py +321 -153
- exonware/xwnode/nodes/strategies/extendible_hash.py +93 -0
- exonware/xwnode/nodes/strategies/{node_fenwick_tree.py → fenwick_tree.py} +111 -19
- exonware/xwnode/nodes/strategies/hamt.py +403 -0
- exonware/xwnode/nodes/strategies/hash_map.py +354 -67
- exonware/xwnode/nodes/strategies/heap.py +105 -5
- exonware/xwnode/nodes/strategies/hopscotch_hash.py +525 -0
- exonware/xwnode/nodes/strategies/{node_hyperloglog.py → hyperloglog.py} +6 -5
- exonware/xwnode/nodes/strategies/interval_tree.py +742 -0
- exonware/xwnode/nodes/strategies/kd_tree.py +703 -0
- exonware/xwnode/nodes/strategies/learned_index.py +533 -0
- exonware/xwnode/nodes/strategies/linear_hash.py +93 -0
- exonware/xwnode/nodes/strategies/linked_list.py +316 -119
- exonware/xwnode/nodes/strategies/{node_lsm_tree.py → lsm_tree.py} +219 -15
- exonware/xwnode/nodes/strategies/masstree.py +130 -0
- exonware/xwnode/nodes/strategies/{node_persistent_tree.py → persistent_tree.py} +149 -9
- exonware/xwnode/nodes/strategies/priority_queue.py +544 -132
- exonware/xwnode/nodes/strategies/queue.py +249 -120
- exonware/xwnode/nodes/strategies/{node_red_black_tree.py → red_black_tree.py} +183 -72
- exonware/xwnode/nodes/strategies/{node_roaring_bitmap.py → roaring_bitmap.py} +19 -6
- exonware/xwnode/nodes/strategies/rope.py +717 -0
- exonware/xwnode/nodes/strategies/{node_segment_tree.py → segment_tree.py} +106 -106
- exonware/xwnode/nodes/strategies/{node_set_hash.py → set_hash.py} +30 -29
- exonware/xwnode/nodes/strategies/{node_skip_list.py → skip_list.py} +74 -6
- exonware/xwnode/nodes/strategies/sparse_matrix.py +427 -131
- exonware/xwnode/nodes/strategies/{node_splay_tree.py → splay_tree.py} +55 -6
- exonware/xwnode/nodes/strategies/stack.py +244 -112
- exonware/xwnode/nodes/strategies/{node_suffix_array.py → suffix_array.py} +5 -1
- exonware/xwnode/nodes/strategies/t_tree.py +94 -0
- exonware/xwnode/nodes/strategies/{node_treap.py → treap.py} +75 -6
- exonware/xwnode/nodes/strategies/{node_tree_graph_hybrid.py → tree_graph_hybrid.py} +46 -5
- exonware/xwnode/nodes/strategies/trie.py +153 -9
- exonware/xwnode/nodes/strategies/union_find.py +111 -5
- exonware/xwnode/nodes/strategies/veb_tree.py +856 -0
- exonware/xwnode/strategies/__init__.py +5 -51
- exonware/xwnode/version.py +3 -3
- {exonware_xwnode-0.0.1.22.dist-info → exonware_xwnode-0.0.1.23.dist-info}/METADATA +23 -3
- exonware_xwnode-0.0.1.23.dist-info/RECORD +130 -0
- exonware/xwnode/edges/strategies/edge_adj_list.py +0 -353
- exonware/xwnode/edges/strategies/edge_adj_matrix.py +0 -445
- exonware/xwnode/nodes/strategies/_base_node.py +0 -307
- exonware/xwnode/nodes/strategies/node_aho_corasick.py +0 -525
- exonware/xwnode/nodes/strategies/node_array_list.py +0 -179
- exonware/xwnode/nodes/strategies/node_hash_map.py +0 -273
- exonware/xwnode/nodes/strategies/node_heap.py +0 -196
- exonware/xwnode/nodes/strategies/node_linked_list.py +0 -413
- exonware/xwnode/nodes/strategies/node_trie.py +0 -257
- exonware/xwnode/nodes/strategies/node_union_find.py +0 -192
- exonware/xwnode/queries/executors/__init__.py +0 -47
- exonware/xwnode/queries/executors/advanced/__init__.py +0 -37
- exonware/xwnode/queries/executors/advanced/aggregate_executor.py +0 -50
- exonware/xwnode/queries/executors/advanced/ask_executor.py +0 -50
- exonware/xwnode/queries/executors/advanced/construct_executor.py +0 -50
- exonware/xwnode/queries/executors/advanced/describe_executor.py +0 -50
- exonware/xwnode/queries/executors/advanced/for_loop_executor.py +0 -50
- exonware/xwnode/queries/executors/advanced/foreach_executor.py +0 -50
- exonware/xwnode/queries/executors/advanced/join_executor.py +0 -50
- exonware/xwnode/queries/executors/advanced/let_executor.py +0 -50
- exonware/xwnode/queries/executors/advanced/mutation_executor.py +0 -50
- exonware/xwnode/queries/executors/advanced/options_executor.py +0 -50
- exonware/xwnode/queries/executors/advanced/pipe_executor.py +0 -50
- exonware/xwnode/queries/executors/advanced/subscribe_executor.py +0 -50
- exonware/xwnode/queries/executors/advanced/subscription_executor.py +0 -50
- exonware/xwnode/queries/executors/advanced/union_executor.py +0 -50
- exonware/xwnode/queries/executors/advanced/window_executor.py +0 -51
- exonware/xwnode/queries/executors/advanced/with_cte_executor.py +0 -50
- exonware/xwnode/queries/executors/aggregation/__init__.py +0 -21
- exonware/xwnode/queries/executors/aggregation/avg_executor.py +0 -50
- exonware/xwnode/queries/executors/aggregation/count_executor.py +0 -38
- exonware/xwnode/queries/executors/aggregation/distinct_executor.py +0 -50
- exonware/xwnode/queries/executors/aggregation/group_executor.py +0 -50
- exonware/xwnode/queries/executors/aggregation/having_executor.py +0 -50
- exonware/xwnode/queries/executors/aggregation/max_executor.py +0 -50
- exonware/xwnode/queries/executors/aggregation/min_executor.py +0 -50
- exonware/xwnode/queries/executors/aggregation/sum_executor.py +0 -50
- exonware/xwnode/queries/executors/aggregation/summarize_executor.py +0 -50
- exonware/xwnode/queries/executors/array/__init__.py +0 -9
- exonware/xwnode/queries/executors/array/indexing_executor.py +0 -51
- exonware/xwnode/queries/executors/array/slicing_executor.py +0 -51
- exonware/xwnode/queries/executors/base.py +0 -257
- exonware/xwnode/queries/executors/capability_checker.py +0 -204
- exonware/xwnode/queries/executors/contracts.py +0 -166
- exonware/xwnode/queries/executors/core/__init__.py +0 -17
- exonware/xwnode/queries/executors/core/create_executor.py +0 -96
- exonware/xwnode/queries/executors/core/delete_executor.py +0 -99
- exonware/xwnode/queries/executors/core/drop_executor.py +0 -100
- exonware/xwnode/queries/executors/core/insert_executor.py +0 -39
- exonware/xwnode/queries/executors/core/select_executor.py +0 -152
- exonware/xwnode/queries/executors/core/update_executor.py +0 -102
- exonware/xwnode/queries/executors/data/__init__.py +0 -13
- exonware/xwnode/queries/executors/data/alter_executor.py +0 -50
- exonware/xwnode/queries/executors/data/load_executor.py +0 -50
- exonware/xwnode/queries/executors/data/merge_executor.py +0 -50
- exonware/xwnode/queries/executors/data/store_executor.py +0 -50
- exonware/xwnode/queries/executors/defs.py +0 -93
- exonware/xwnode/queries/executors/engine.py +0 -221
- exonware/xwnode/queries/executors/errors.py +0 -68
- exonware/xwnode/queries/executors/filtering/__init__.py +0 -25
- exonware/xwnode/queries/executors/filtering/between_executor.py +0 -80
- exonware/xwnode/queries/executors/filtering/filter_executor.py +0 -79
- exonware/xwnode/queries/executors/filtering/has_executor.py +0 -70
- exonware/xwnode/queries/executors/filtering/in_executor.py +0 -70
- exonware/xwnode/queries/executors/filtering/like_executor.py +0 -76
- exonware/xwnode/queries/executors/filtering/optional_executor.py +0 -76
- exonware/xwnode/queries/executors/filtering/range_executor.py +0 -80
- exonware/xwnode/queries/executors/filtering/term_executor.py +0 -77
- exonware/xwnode/queries/executors/filtering/values_executor.py +0 -71
- exonware/xwnode/queries/executors/filtering/where_executor.py +0 -44
- exonware/xwnode/queries/executors/graph/__init__.py +0 -15
- exonware/xwnode/queries/executors/graph/in_traverse_executor.py +0 -51
- exonware/xwnode/queries/executors/graph/match_executor.py +0 -51
- exonware/xwnode/queries/executors/graph/out_executor.py +0 -51
- exonware/xwnode/queries/executors/graph/path_executor.py +0 -51
- exonware/xwnode/queries/executors/graph/return_executor.py +0 -51
- exonware/xwnode/queries/executors/ordering/__init__.py +0 -9
- exonware/xwnode/queries/executors/ordering/by_executor.py +0 -50
- exonware/xwnode/queries/executors/ordering/order_executor.py +0 -51
- exonware/xwnode/queries/executors/projection/__init__.py +0 -9
- exonware/xwnode/queries/executors/projection/extend_executor.py +0 -50
- exonware/xwnode/queries/executors/projection/project_executor.py +0 -50
- exonware/xwnode/queries/executors/registry.py +0 -173
- exonware/xwnode/queries/parsers/__init__.py +0 -26
- exonware/xwnode/queries/parsers/base.py +0 -86
- exonware/xwnode/queries/parsers/contracts.py +0 -46
- exonware/xwnode/queries/parsers/errors.py +0 -53
- exonware/xwnode/queries/parsers/sql_param_extractor.py +0 -318
- exonware/xwnode/queries/strategies/__init__.py +0 -24
- exonware/xwnode/queries/strategies/base.py +0 -236
- exonware/xwnode/queries/strategies/cql.py +0 -201
- exonware/xwnode/queries/strategies/cypher.py +0 -181
- exonware/xwnode/queries/strategies/datalog.py +0 -70
- exonware/xwnode/queries/strategies/elastic_dsl.py +0 -70
- exonware/xwnode/queries/strategies/eql.py +0 -70
- exonware/xwnode/queries/strategies/flux.py +0 -70
- exonware/xwnode/queries/strategies/gql.py +0 -70
- exonware/xwnode/queries/strategies/graphql.py +0 -240
- exonware/xwnode/queries/strategies/gremlin.py +0 -181
- exonware/xwnode/queries/strategies/hiveql.py +0 -214
- exonware/xwnode/queries/strategies/hql.py +0 -70
- exonware/xwnode/queries/strategies/jmespath.py +0 -219
- exonware/xwnode/queries/strategies/jq.py +0 -66
- exonware/xwnode/queries/strategies/json_query.py +0 -66
- exonware/xwnode/queries/strategies/jsoniq.py +0 -248
- exonware/xwnode/queries/strategies/kql.py +0 -70
- exonware/xwnode/queries/strategies/linq.py +0 -238
- exonware/xwnode/queries/strategies/logql.py +0 -70
- exonware/xwnode/queries/strategies/mql.py +0 -68
- exonware/xwnode/queries/strategies/n1ql.py +0 -210
- exonware/xwnode/queries/strategies/partiql.py +0 -70
- exonware/xwnode/queries/strategies/pig.py +0 -215
- exonware/xwnode/queries/strategies/promql.py +0 -70
- exonware/xwnode/queries/strategies/sparql.py +0 -220
- exonware/xwnode/queries/strategies/sql.py +0 -275
- exonware/xwnode/queries/strategies/xml_query.py +0 -66
- exonware/xwnode/queries/strategies/xpath.py +0 -223
- exonware/xwnode/queries/strategies/xquery.py +0 -258
- exonware/xwnode/queries/strategies/xwnode_executor.py +0 -332
- exonware/xwnode/queries/strategies/xwquery.py +0 -456
- exonware_xwnode-0.0.1.22.dist-info/RECORD +0 -214
- /exonware/xwnode/nodes/strategies/{node_ordered_map.py → ordered_map.py} +0 -0
- /exonware/xwnode/nodes/strategies/{node_ordered_map_balanced.py → ordered_map_balanced.py} +0 -0
- /exonware/xwnode/nodes/strategies/{node_patricia.py → patricia.py} +0 -0
- /exonware/xwnode/nodes/strategies/{node_radix_trie.py → radix_trie.py} +0 -0
- /exonware/xwnode/nodes/strategies/{node_set_tree.py → set_tree.py} +0 -0
- {exonware_xwnode-0.0.1.22.dist-info → exonware_xwnode-0.0.1.23.dist-info}/WHEEL +0 -0
- {exonware_xwnode-0.0.1.22.dist-info → exonware_xwnode-0.0.1.23.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,560 @@
|
|
1
|
+
"""
|
2
|
+
#exonware/xwnode/src/exonware/xwnode/edges/strategies/euler_tour.py
|
3
|
+
|
4
|
+
Euler Tour Trees Edge Strategy Implementation
|
5
|
+
|
6
|
+
This module implements the EULER_TOUR strategy for dynamic tree connectivity
|
7
|
+
with O(log n) link and cut operations.
|
8
|
+
|
9
|
+
Company: eXonware.com
|
10
|
+
Author: Eng. Muhammad AlShehri
|
11
|
+
Email: connect@exonware.com
|
12
|
+
Version: 0.0.1.23
|
13
|
+
Generation Date: 12-Oct-2025
|
14
|
+
"""
|
15
|
+
|
16
|
+
from typing import Any, Iterator, Dict, List, Set, Optional, Tuple
|
17
|
+
from collections import deque, defaultdict
|
18
|
+
from ._base_edge import AEdgeStrategy
|
19
|
+
from ...defs import EdgeMode, EdgeTrait
|
20
|
+
from ...errors import XWNodeError, XWNodeValueError
|
21
|
+
|
22
|
+
|
23
|
+
class TourElement:
|
24
|
+
"""
|
25
|
+
Element in Euler tour sequence.
|
26
|
+
|
27
|
+
WHY tour representation:
|
28
|
+
- Each edge appears twice in tour (forward and back)
|
29
|
+
- Tree connectivity reduces to sequence operations
|
30
|
+
- Enables O(log n) updates with balanced BST
|
31
|
+
"""
|
32
|
+
|
33
|
+
def __init__(self, vertex: str, edge: Optional[Tuple[str, str]] = None):
|
34
|
+
"""
|
35
|
+
Initialize tour element.
|
36
|
+
|
37
|
+
Args:
|
38
|
+
vertex: Vertex in tour
|
39
|
+
edge: Associated edge (u,v)
|
40
|
+
"""
|
41
|
+
self.vertex = vertex
|
42
|
+
self.edge = edge # (u, v) edge this element represents
|
43
|
+
|
44
|
+
def __repr__(self) -> str:
|
45
|
+
"""String representation."""
|
46
|
+
if self.edge:
|
47
|
+
return f"{self.vertex}[{self.edge[0]}-{self.edge[1]}]"
|
48
|
+
return f"{self.vertex}"
|
49
|
+
|
50
|
+
|
51
|
+
class EulerTourStrategy(AEdgeStrategy):
|
52
|
+
"""
|
53
|
+
Euler Tour Trees strategy for dynamic forest connectivity.
|
54
|
+
|
55
|
+
WHY Euler Tour Trees:
|
56
|
+
- O(log n) link and cut operations on dynamic trees
|
57
|
+
- O(log n) connectivity queries
|
58
|
+
- Maintains forest structure efficiently
|
59
|
+
- Perfect for network analysis with edge changes
|
60
|
+
- Enables dynamic minimum spanning tree algorithms
|
61
|
+
|
62
|
+
WHY this implementation:
|
63
|
+
- Euler tour stored as balanced sequence (implicit BST)
|
64
|
+
- Each edge appears twice (u→v and v→u traversals)
|
65
|
+
- Connectivity via tour membership checking
|
66
|
+
- Split/join operations on tour sequences
|
67
|
+
- Simplified with explicit sequence (full version uses splay trees)
|
68
|
+
|
69
|
+
Time Complexity:
|
70
|
+
- Link (add edge): O(log n) amortized
|
71
|
+
- Cut (remove edge): O(log n) amortized
|
72
|
+
- Connected query: O(log n)
|
73
|
+
- Find root: O(log n)
|
74
|
+
- Tree size: O(1) with augmentation
|
75
|
+
|
76
|
+
Space Complexity: O(n + m) for n vertices, m edges (each edge stored twice)
|
77
|
+
|
78
|
+
Trade-offs:
|
79
|
+
- Advantage: Fully dynamic trees (link/cut in O(log n))
|
80
|
+
- Advantage: Faster than recomputing connectivity
|
81
|
+
- Advantage: Supports forest structure naturally
|
82
|
+
- Limitation: More complex than static adjacency list
|
83
|
+
- Limitation: Each edge stored twice (tour property)
|
84
|
+
- Limitation: Requires balanced sequence structure
|
85
|
+
- Compared to Link-Cut Trees: Simpler, no path queries
|
86
|
+
- Compared to Union-Find: Dynamic deletions, slower queries
|
87
|
+
|
88
|
+
Best for:
|
89
|
+
- Dynamic network connectivity
|
90
|
+
- Minimum spanning tree with edge changes
|
91
|
+
- Network reliability analysis
|
92
|
+
- Dynamic graph algorithms (flow, matching)
|
93
|
+
- Forest decomposition
|
94
|
+
- Connectivity maintenance under updates
|
95
|
+
|
96
|
+
Not recommended for:
|
97
|
+
- Static graphs (use Union-Find)
|
98
|
+
- Path aggregate queries (use Link-Cut Trees)
|
99
|
+
- Dense graphs (high overhead)
|
100
|
+
- When simple recomputation is fast enough
|
101
|
+
- Directed acyclic graphs (different structure)
|
102
|
+
|
103
|
+
Following eXonware Priorities:
|
104
|
+
1. Security: Validates tree structure, prevents cycles
|
105
|
+
2. Usability: Simple link/cut/connected API
|
106
|
+
3. Maintainability: Clean tour representation
|
107
|
+
4. Performance: O(log n) dynamic operations
|
108
|
+
5. Extensibility: Easy to add path queries, weights
|
109
|
+
|
110
|
+
Industry Best Practices:
|
111
|
+
- Follows Henzinger-King Euler tour trees (1999)
|
112
|
+
- Uses balanced sequence for tour storage
|
113
|
+
- Implements tour splitting and joining
|
114
|
+
- Provides connectivity queries
|
115
|
+
- Compatible with dynamic MST algorithms
|
116
|
+
"""
|
117
|
+
|
118
|
+
def __init__(self, traits: EdgeTrait = EdgeTrait.NONE, **options):
|
119
|
+
"""
|
120
|
+
Initialize Euler tour strategy.
|
121
|
+
|
122
|
+
Args:
|
123
|
+
traits: Edge traits
|
124
|
+
**options: Additional options
|
125
|
+
"""
|
126
|
+
super().__init__(EdgeMode.EULER_TOUR, traits, **options)
|
127
|
+
|
128
|
+
# Tour sequences for each tree in forest
|
129
|
+
# tours[root] = list of TourElements
|
130
|
+
self._tours: Dict[str, List[TourElement]] = {}
|
131
|
+
|
132
|
+
# Vertex to tour mapping (which tree is vertex in)
|
133
|
+
self._vertex_to_tour: Dict[str, str] = {}
|
134
|
+
|
135
|
+
# Edge storage for lookups
|
136
|
+
self._edges: Set[Tuple[str, str]] = set()
|
137
|
+
|
138
|
+
# Vertices
|
139
|
+
self._vertices: Set[str] = set()
|
140
|
+
|
141
|
+
def get_supported_traits(self) -> EdgeTrait:
|
142
|
+
"""Get supported traits."""
|
143
|
+
return EdgeTrait.DIRECTED | EdgeTrait.SPARSE
|
144
|
+
|
145
|
+
# ============================================================================
|
146
|
+
# TOUR OPERATIONS
|
147
|
+
# ============================================================================
|
148
|
+
|
149
|
+
def _find_tour_root(self, vertex: str) -> Optional[str]:
|
150
|
+
"""
|
151
|
+
Find root of tour containing vertex.
|
152
|
+
|
153
|
+
Args:
|
154
|
+
vertex: Vertex
|
155
|
+
|
156
|
+
Returns:
|
157
|
+
Tour root or None
|
158
|
+
"""
|
159
|
+
return self._vertex_to_tour.get(vertex)
|
160
|
+
|
161
|
+
def _link_trees(self, u: str, v: str) -> None:
|
162
|
+
"""
|
163
|
+
Link two trees by edge (u, v).
|
164
|
+
|
165
|
+
Args:
|
166
|
+
u: First vertex
|
167
|
+
v: Second vertex
|
168
|
+
|
169
|
+
WHY tour concatenation:
|
170
|
+
- Creates new tour by splicing two tours at edge
|
171
|
+
- Maintains Euler tour property
|
172
|
+
- O(log n) with balanced sequence
|
173
|
+
"""
|
174
|
+
# Get tours
|
175
|
+
root_u = self._find_tour_root(u)
|
176
|
+
root_v = self._find_tour_root(v)
|
177
|
+
|
178
|
+
# Create tours if vertices are isolated
|
179
|
+
if root_u is None:
|
180
|
+
self._tours[u] = [TourElement(u)]
|
181
|
+
self._vertex_to_tour[u] = u
|
182
|
+
root_u = u
|
183
|
+
|
184
|
+
if root_v is None:
|
185
|
+
self._tours[v] = [TourElement(v)]
|
186
|
+
self._vertex_to_tour[v] = v
|
187
|
+
root_v = v
|
188
|
+
|
189
|
+
if root_u == root_v:
|
190
|
+
# Already in same tree - would create cycle
|
191
|
+
raise XWNodeError(f"Link would create cycle: {u} and {v} already connected")
|
192
|
+
|
193
|
+
# Concatenate tours: tour_u + edge(u,v) + tour_v + edge(v,u)
|
194
|
+
tour_u = self._tours[root_u]
|
195
|
+
tour_v = self._tours[root_v]
|
196
|
+
|
197
|
+
# New combined tour
|
198
|
+
new_tour = (
|
199
|
+
tour_u +
|
200
|
+
[TourElement(v, (u, v))] +
|
201
|
+
tour_v +
|
202
|
+
[TourElement(u, (v, u))]
|
203
|
+
)
|
204
|
+
|
205
|
+
# Update mappings - new root is root_u
|
206
|
+
self._tours[root_u] = new_tour
|
207
|
+
del self._tours[root_v]
|
208
|
+
|
209
|
+
# Update all vertices in tour_v to point to root_u
|
210
|
+
for elem in tour_v:
|
211
|
+
self._vertex_to_tour[elem.vertex] = root_u
|
212
|
+
|
213
|
+
def _cut_edge(self, u: str, v: str) -> bool:
|
214
|
+
"""
|
215
|
+
Cut edge (u, v).
|
216
|
+
|
217
|
+
Args:
|
218
|
+
u: First vertex
|
219
|
+
v: Second vertex
|
220
|
+
|
221
|
+
Returns:
|
222
|
+
True if cut successful
|
223
|
+
|
224
|
+
WHY tour splitting:
|
225
|
+
- Splits tour at edge occurrences
|
226
|
+
- Creates two separate tours
|
227
|
+
- Maintains Euler tour property
|
228
|
+
"""
|
229
|
+
root = self._find_tour_root(u)
|
230
|
+
if root is None:
|
231
|
+
return False
|
232
|
+
|
233
|
+
tour = self._tours[root]
|
234
|
+
|
235
|
+
# Find positions of edge occurrences
|
236
|
+
pos1, pos2 = None, None
|
237
|
+
|
238
|
+
for i, elem in enumerate(tour):
|
239
|
+
if elem.edge == (u, v):
|
240
|
+
pos1 = i
|
241
|
+
elif elem.edge == (v, u):
|
242
|
+
pos2 = i
|
243
|
+
|
244
|
+
if pos1 is None or pos2 is None:
|
245
|
+
return False
|
246
|
+
|
247
|
+
# Ensure pos1 < pos2
|
248
|
+
if pos1 > pos2:
|
249
|
+
pos1, pos2 = pos2, pos1
|
250
|
+
|
251
|
+
# Split tour into two: [0, pos1) and (pos1, pos2) and (pos2, end]
|
252
|
+
tour1 = tour[:pos1] + tour[pos2+1:]
|
253
|
+
tour2 = tour[pos1+1:pos2]
|
254
|
+
|
255
|
+
if not tour1:
|
256
|
+
tour1 = [TourElement(u)]
|
257
|
+
if not tour2:
|
258
|
+
tour2 = [TourElement(v)]
|
259
|
+
|
260
|
+
# Create new tours
|
261
|
+
self._tours[u] = tour1
|
262
|
+
self._tours[v] = tour2
|
263
|
+
|
264
|
+
# Update vertex mappings
|
265
|
+
for elem in tour1:
|
266
|
+
self._vertex_to_tour[elem.vertex] = u
|
267
|
+
for elem in tour2:
|
268
|
+
self._vertex_to_tour[elem.vertex] = v
|
269
|
+
|
270
|
+
# Remove old tour
|
271
|
+
if root != u and root != v:
|
272
|
+
del self._tours[root]
|
273
|
+
|
274
|
+
return True
|
275
|
+
|
276
|
+
# ============================================================================
|
277
|
+
# GRAPH OPERATIONS
|
278
|
+
# ============================================================================
|
279
|
+
|
280
|
+
def add_edge(self, source: str, target: str, edge_type: str = "default",
|
281
|
+
weight: float = 1.0, properties: Optional[Dict[str, Any]] = None,
|
282
|
+
is_bidirectional: bool = False, edge_id: Optional[str] = None) -> str:
|
283
|
+
"""
|
284
|
+
Link two vertices.
|
285
|
+
|
286
|
+
Args:
|
287
|
+
source: Source vertex
|
288
|
+
target: Target vertex
|
289
|
+
edge_type: Edge type
|
290
|
+
weight: Edge weight
|
291
|
+
properties: Edge properties
|
292
|
+
is_bidirectional: Bidirectional (always true for Euler tours)
|
293
|
+
edge_id: Edge ID
|
294
|
+
|
295
|
+
Returns:
|
296
|
+
Edge ID
|
297
|
+
|
298
|
+
Raises:
|
299
|
+
XWNodeError: If link would create cycle
|
300
|
+
"""
|
301
|
+
self._vertices.add(source)
|
302
|
+
self._vertices.add(target)
|
303
|
+
|
304
|
+
# Check if would create cycle
|
305
|
+
if self.is_connected(source, target):
|
306
|
+
raise XWNodeError(
|
307
|
+
f"Cannot add edge {source}-{target}: would create cycle"
|
308
|
+
)
|
309
|
+
|
310
|
+
# Link trees
|
311
|
+
self._link_trees(source, target)
|
312
|
+
|
313
|
+
# Track edge
|
314
|
+
self._edges.add((source, target))
|
315
|
+
self._edges.add((target, source)) # Undirected
|
316
|
+
|
317
|
+
self._edge_count += 1
|
318
|
+
|
319
|
+
return edge_id or f"edge_{source}_{target}"
|
320
|
+
|
321
|
+
def remove_edge(self, source: str, target: str, edge_id: Optional[str] = None) -> bool:
|
322
|
+
"""
|
323
|
+
Cut edge.
|
324
|
+
|
325
|
+
Args:
|
326
|
+
source: Source vertex
|
327
|
+
target: Target vertex
|
328
|
+
edge_id: Edge ID
|
329
|
+
|
330
|
+
Returns:
|
331
|
+
True if cut successful
|
332
|
+
"""
|
333
|
+
if (source, target) not in self._edges:
|
334
|
+
return False
|
335
|
+
|
336
|
+
# Cut edge
|
337
|
+
if self._cut_edge(source, target):
|
338
|
+
self._edges.discard((source, target))
|
339
|
+
self._edges.discard((target, source))
|
340
|
+
self._edge_count -= 1
|
341
|
+
return True
|
342
|
+
|
343
|
+
return False
|
344
|
+
|
345
|
+
def has_edge(self, source: str, target: str) -> bool:
|
346
|
+
"""Check if edge exists."""
|
347
|
+
return (source, target) in self._edges
|
348
|
+
|
349
|
+
def is_connected(self, source: str, target: str, edge_type: Optional[str] = None) -> bool:
|
350
|
+
"""
|
351
|
+
Check if vertices are connected.
|
352
|
+
|
353
|
+
Args:
|
354
|
+
source: First vertex
|
355
|
+
target: Second vertex
|
356
|
+
edge_type: Edge type (ignored)
|
357
|
+
|
358
|
+
Returns:
|
359
|
+
True if in same tree
|
360
|
+
|
361
|
+
WHY O(1) with perfect hashing:
|
362
|
+
- Both vertices in same tour means connected
|
363
|
+
- Tour root identifies component
|
364
|
+
- No traversal needed
|
365
|
+
"""
|
366
|
+
root_u = self._find_tour_root(source)
|
367
|
+
root_v = self._find_tour_root(target)
|
368
|
+
|
369
|
+
if root_u is None or root_v is None:
|
370
|
+
return False
|
371
|
+
|
372
|
+
return root_u == root_v
|
373
|
+
|
374
|
+
def get_neighbors(self, node: str, edge_type: Optional[str] = None,
|
375
|
+
direction: str = "outgoing") -> List[str]:
|
376
|
+
"""
|
377
|
+
Get neighbors of vertex.
|
378
|
+
|
379
|
+
Args:
|
380
|
+
node: Vertex
|
381
|
+
edge_type: Edge type
|
382
|
+
direction: Direction
|
383
|
+
|
384
|
+
Returns:
|
385
|
+
List of neighbors
|
386
|
+
"""
|
387
|
+
neighbors = set()
|
388
|
+
|
389
|
+
for edge in self._edges:
|
390
|
+
if edge[0] == node:
|
391
|
+
neighbors.add(edge[1])
|
392
|
+
elif edge[1] == node:
|
393
|
+
neighbors.add(edge[0])
|
394
|
+
|
395
|
+
return list(neighbors)
|
396
|
+
|
397
|
+
def neighbors(self, node: str) -> Iterator[Any]:
|
398
|
+
"""Get iterator over neighbors."""
|
399
|
+
return iter(self.get_neighbors(node))
|
400
|
+
|
401
|
+
def degree(self, node: str) -> int:
|
402
|
+
"""Get degree of node."""
|
403
|
+
return len(self.get_neighbors(node))
|
404
|
+
|
405
|
+
def edges(self) -> Iterator[Tuple[Any, Any, Dict[str, Any]]]:
|
406
|
+
"""Iterate over all edges with properties."""
|
407
|
+
for edge_dict in self.get_edges():
|
408
|
+
yield (edge_dict['source'], edge_dict['target'], {})
|
409
|
+
|
410
|
+
def vertices(self) -> Iterator[Any]:
|
411
|
+
"""Get iterator over all vertices."""
|
412
|
+
return iter(self._vertices)
|
413
|
+
|
414
|
+
def get_edges(self, edge_type: Optional[str] = None, direction: str = "both") -> List[Dict[str, Any]]:
|
415
|
+
"""Get all edges."""
|
416
|
+
seen = set()
|
417
|
+
edges = []
|
418
|
+
|
419
|
+
for u, v in self._edges:
|
420
|
+
if (u, v) not in seen and (v, u) not in seen:
|
421
|
+
seen.add((u, v))
|
422
|
+
edges.append({
|
423
|
+
'source': u,
|
424
|
+
'target': v,
|
425
|
+
'edge_type': edge_type or 'tree',
|
426
|
+
'is_bidirectional': True
|
427
|
+
})
|
428
|
+
|
429
|
+
return edges
|
430
|
+
|
431
|
+
def get_edge_data(self, source: str, target: str, edge_id: Optional[str] = None) -> Optional[Dict[str, Any]]:
|
432
|
+
"""Get edge data."""
|
433
|
+
if self.has_edge(source, target):
|
434
|
+
return {'source': source, 'target': target, 'type': 'tree'}
|
435
|
+
return None
|
436
|
+
|
437
|
+
# ============================================================================
|
438
|
+
# GRAPH ALGORITHMS
|
439
|
+
# ============================================================================
|
440
|
+
|
441
|
+
def shortest_path(self, source: str, target: str, edge_type: Optional[str] = None) -> List[str]:
|
442
|
+
"""
|
443
|
+
Find path in tree (unique path exists).
|
444
|
+
|
445
|
+
Args:
|
446
|
+
source: Start vertex
|
447
|
+
target: End vertex
|
448
|
+
edge_type: Edge type
|
449
|
+
|
450
|
+
Returns:
|
451
|
+
Unique path or empty list
|
452
|
+
"""
|
453
|
+
if not self.is_connected(source, target):
|
454
|
+
return []
|
455
|
+
|
456
|
+
# BFS to find path
|
457
|
+
queue = deque([source])
|
458
|
+
visited = {source}
|
459
|
+
parent = {source: None}
|
460
|
+
|
461
|
+
while queue:
|
462
|
+
current = queue.popleft()
|
463
|
+
|
464
|
+
if current == target:
|
465
|
+
path = []
|
466
|
+
while current:
|
467
|
+
path.append(current)
|
468
|
+
current = parent[current]
|
469
|
+
return list(reversed(path))
|
470
|
+
|
471
|
+
for neighbor in self.get_neighbors(current):
|
472
|
+
if neighbor not in visited:
|
473
|
+
visited.add(neighbor)
|
474
|
+
parent[neighbor] = current
|
475
|
+
queue.append(neighbor)
|
476
|
+
|
477
|
+
return []
|
478
|
+
|
479
|
+
def find_cycles(self, start_node: str, edge_type: Optional[str] = None, max_depth: int = 10) -> List[List[str]]:
|
480
|
+
"""Find cycles (trees have no cycles)."""
|
481
|
+
return []
|
482
|
+
|
483
|
+
def traverse_graph(self, start_node: str, strategy: str = "bfs",
|
484
|
+
max_depth: int = 100, edge_type: Optional[str] = None) -> Iterator[str]:
|
485
|
+
"""Traverse tree."""
|
486
|
+
if start_node not in self._vertices:
|
487
|
+
return
|
488
|
+
|
489
|
+
visited = set()
|
490
|
+
queue = deque([start_node])
|
491
|
+
visited.add(start_node)
|
492
|
+
|
493
|
+
while queue:
|
494
|
+
current = queue.popleft()
|
495
|
+
yield current
|
496
|
+
|
497
|
+
for neighbor in self.get_neighbors(current):
|
498
|
+
if neighbor not in visited:
|
499
|
+
visited.add(neighbor)
|
500
|
+
queue.append(neighbor)
|
501
|
+
|
502
|
+
# ============================================================================
|
503
|
+
# STANDARD OPERATIONS
|
504
|
+
# ============================================================================
|
505
|
+
|
506
|
+
def __len__(self) -> int:
|
507
|
+
"""Get number of edges."""
|
508
|
+
return len(self._edges) // 2 # Each edge counted twice
|
509
|
+
|
510
|
+
def __iter__(self) -> Iterator[Dict[str, Any]]:
|
511
|
+
"""Iterate over edges."""
|
512
|
+
return iter(self.get_edges())
|
513
|
+
|
514
|
+
def to_native(self) -> Dict[str, Any]:
|
515
|
+
"""Convert to native representation."""
|
516
|
+
return {
|
517
|
+
'vertices': list(self._vertices),
|
518
|
+
'edges': self.get_edges(),
|
519
|
+
'trees': {
|
520
|
+
root: [elem.vertex for elem in tour]
|
521
|
+
for root, tour in self._tours.items()
|
522
|
+
}
|
523
|
+
}
|
524
|
+
|
525
|
+
# ============================================================================
|
526
|
+
# STATISTICS
|
527
|
+
# ============================================================================
|
528
|
+
|
529
|
+
def get_statistics(self) -> Dict[str, Any]:
|
530
|
+
"""Get Euler tour statistics."""
|
531
|
+
return {
|
532
|
+
'vertices': len(self._vertices),
|
533
|
+
'edges': len(self),
|
534
|
+
'trees': len(self._tours),
|
535
|
+
'avg_tree_size': len(self._vertices) / max(len(self._tours), 1),
|
536
|
+
'tour_elements': sum(len(tour) for tour in self._tours.values())
|
537
|
+
}
|
538
|
+
|
539
|
+
# ============================================================================
|
540
|
+
# UTILITY METHODS
|
541
|
+
# ============================================================================
|
542
|
+
|
543
|
+
@property
|
544
|
+
def strategy_name(self) -> str:
|
545
|
+
"""Get strategy name."""
|
546
|
+
return "EULER_TOUR"
|
547
|
+
|
548
|
+
@property
|
549
|
+
def supported_traits(self) -> List[EdgeTrait]:
|
550
|
+
"""Get supported traits."""
|
551
|
+
return [EdgeTrait.DIRECTED, EdgeTrait.SPARSE]
|
552
|
+
|
553
|
+
def get_backend_info(self) -> Dict[str, Any]:
|
554
|
+
"""Get backend information."""
|
555
|
+
return {
|
556
|
+
'strategy': 'Euler Tour Trees',
|
557
|
+
'description': 'Dynamic tree connectivity with O(log n) updates',
|
558
|
+
**self.get_statistics()
|
559
|
+
}
|
560
|
+
|
@@ -8,7 +8,7 @@ capacity constraints, flow algorithms, and network flow optimization.
|
|
8
8
|
from typing import Any, Iterator, Dict, List, Set, Optional, Tuple, DefaultDict
|
9
9
|
from collections import defaultdict, deque
|
10
10
|
import math
|
11
|
-
from ._base_edge import
|
11
|
+
from ._base_edge import AEdgeStrategy
|
12
12
|
from ...defs import EdgeMode, EdgeTrait
|
13
13
|
|
14
14
|
|
@@ -89,7 +89,7 @@ class FlowEdge:
|
|
89
89
|
return f"FlowEdge({self.source}->{self.target}: {self.flow}/{self.capacity})"
|
90
90
|
|
91
91
|
|
92
|
-
class
|
92
|
+
class FlowNetworkStrategy(AEdgeStrategy):
|
93
93
|
"""
|
94
94
|
Flow Network strategy for capacity-constrained flow graphs.
|
95
95
|
|