exonware-xwnode 0.0.1.21__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.
Files changed (250) hide show
  1. exonware/__init__.py +8 -1
  2. exonware/xwnode/__init__.py +18 -5
  3. exonware/xwnode/add_strategy_types.py +165 -0
  4. exonware/xwnode/base.py +7 -5
  5. exonware/xwnode/common/__init__.py +1 -1
  6. exonware/xwnode/common/graph/__init__.py +30 -0
  7. exonware/xwnode/common/graph/caching.py +131 -0
  8. exonware/xwnode/common/graph/contracts.py +100 -0
  9. exonware/xwnode/common/graph/errors.py +44 -0
  10. exonware/xwnode/common/graph/indexing.py +260 -0
  11. exonware/xwnode/common/graph/manager.py +568 -0
  12. exonware/xwnode/common/management/__init__.py +3 -5
  13. exonware/xwnode/common/management/manager.py +9 -9
  14. exonware/xwnode/common/management/migration.py +6 -6
  15. exonware/xwnode/common/monitoring/__init__.py +3 -5
  16. exonware/xwnode/common/monitoring/metrics.py +7 -3
  17. exonware/xwnode/common/monitoring/pattern_detector.py +2 -2
  18. exonware/xwnode/common/monitoring/performance_monitor.py +6 -2
  19. exonware/xwnode/common/patterns/__init__.py +3 -5
  20. exonware/xwnode/common/patterns/advisor.py +1 -1
  21. exonware/xwnode/common/patterns/flyweight.py +6 -2
  22. exonware/xwnode/common/patterns/registry.py +203 -184
  23. exonware/xwnode/common/utils/__init__.py +25 -11
  24. exonware/xwnode/common/utils/simple.py +1 -1
  25. exonware/xwnode/config.py +3 -8
  26. exonware/xwnode/contracts.py +4 -105
  27. exonware/xwnode/defs.py +413 -159
  28. exonware/xwnode/edges/strategies/__init__.py +86 -4
  29. exonware/xwnode/edges/strategies/_base_edge.py +2 -2
  30. exonware/xwnode/edges/strategies/adj_list.py +287 -121
  31. exonware/xwnode/edges/strategies/adj_matrix.py +316 -222
  32. exonware/xwnode/edges/strategies/base.py +1 -1
  33. exonware/xwnode/edges/strategies/{edge_bidir_wrapper.py → bidir_wrapper.py} +45 -4
  34. exonware/xwnode/edges/strategies/bitemporal.py +520 -0
  35. exonware/xwnode/edges/strategies/{edge_block_adj_matrix.py → block_adj_matrix.py} +77 -6
  36. exonware/xwnode/edges/strategies/bv_graph.py +664 -0
  37. exonware/xwnode/edges/strategies/compressed_graph.py +217 -0
  38. exonware/xwnode/edges/strategies/{edge_coo.py → coo.py} +46 -4
  39. exonware/xwnode/edges/strategies/{edge_csc.py → csc.py} +45 -4
  40. exonware/xwnode/edges/strategies/{edge_csr.py → csr.py} +94 -12
  41. exonware/xwnode/edges/strategies/{edge_dynamic_adj_list.py → dynamic_adj_list.py} +46 -4
  42. exonware/xwnode/edges/strategies/edge_list.py +168 -0
  43. exonware/xwnode/edges/strategies/edge_property_store.py +2 -2
  44. exonware/xwnode/edges/strategies/euler_tour.py +560 -0
  45. exonware/xwnode/edges/strategies/{edge_flow_network.py → flow_network.py} +2 -2
  46. exonware/xwnode/edges/strategies/graphblas.py +449 -0
  47. exonware/xwnode/edges/strategies/hnsw.py +637 -0
  48. exonware/xwnode/edges/strategies/hop2_labels.py +467 -0
  49. exonware/xwnode/edges/strategies/{edge_hyperedge_set.py → hyperedge_set.py} +2 -2
  50. exonware/xwnode/edges/strategies/incidence_matrix.py +250 -0
  51. exonware/xwnode/edges/strategies/k2_tree.py +613 -0
  52. exonware/xwnode/edges/strategies/link_cut.py +626 -0
  53. exonware/xwnode/edges/strategies/multiplex.py +532 -0
  54. exonware/xwnode/edges/strategies/{edge_neural_graph.py → neural_graph.py} +2 -2
  55. exonware/xwnode/edges/strategies/{edge_octree.py → octree.py} +69 -11
  56. exonware/xwnode/edges/strategies/{edge_quadtree.py → quadtree.py} +66 -10
  57. exonware/xwnode/edges/strategies/roaring_adj.py +438 -0
  58. exonware/xwnode/edges/strategies/{edge_rtree.py → rtree.py} +43 -5
  59. exonware/xwnode/edges/strategies/{edge_temporal_edgeset.py → temporal_edgeset.py} +24 -5
  60. exonware/xwnode/edges/strategies/{edge_tree_graph_basic.py → tree_graph_basic.py} +78 -7
  61. exonware/xwnode/edges/strategies/{edge_weighted_graph.py → weighted_graph.py} +188 -10
  62. exonware/xwnode/errors.py +3 -6
  63. exonware/xwnode/facade.py +20 -20
  64. exonware/xwnode/nodes/strategies/__init__.py +29 -9
  65. exonware/xwnode/nodes/strategies/adjacency_list.py +650 -177
  66. exonware/xwnode/nodes/strategies/aho_corasick.py +358 -183
  67. exonware/xwnode/nodes/strategies/array_list.py +36 -3
  68. exonware/xwnode/nodes/strategies/art.py +581 -0
  69. exonware/xwnode/nodes/strategies/{node_avl_tree.py → avl_tree.py} +77 -6
  70. exonware/xwnode/nodes/strategies/{node_b_plus_tree.py → b_plus_tree.py} +81 -40
  71. exonware/xwnode/nodes/strategies/{node_btree.py → b_tree.py} +79 -9
  72. exonware/xwnode/nodes/strategies/base.py +469 -98
  73. exonware/xwnode/nodes/strategies/{node_bitmap.py → bitmap.py} +12 -12
  74. exonware/xwnode/nodes/strategies/{node_bitset_dynamic.py → bitset_dynamic.py} +11 -11
  75. exonware/xwnode/nodes/strategies/{node_bloom_filter.py → bloom_filter.py} +15 -2
  76. exonware/xwnode/nodes/strategies/bloomier_filter.py +519 -0
  77. exonware/xwnode/nodes/strategies/bw_tree.py +531 -0
  78. exonware/xwnode/nodes/strategies/contracts.py +1 -1
  79. exonware/xwnode/nodes/strategies/{node_count_min_sketch.py → count_min_sketch.py} +3 -2
  80. exonware/xwnode/nodes/strategies/{node_cow_tree.py → cow_tree.py} +135 -13
  81. exonware/xwnode/nodes/strategies/crdt_map.py +629 -0
  82. exonware/xwnode/nodes/strategies/{node_cuckoo_hash.py → cuckoo_hash.py} +2 -2
  83. exonware/xwnode/nodes/strategies/{node_xdata_optimized.py → data_interchange_optimized.py} +21 -4
  84. exonware/xwnode/nodes/strategies/dawg.py +876 -0
  85. exonware/xwnode/nodes/strategies/deque.py +321 -153
  86. exonware/xwnode/nodes/strategies/extendible_hash.py +93 -0
  87. exonware/xwnode/nodes/strategies/{node_fenwick_tree.py → fenwick_tree.py} +111 -19
  88. exonware/xwnode/nodes/strategies/hamt.py +403 -0
  89. exonware/xwnode/nodes/strategies/hash_map.py +354 -67
  90. exonware/xwnode/nodes/strategies/heap.py +105 -5
  91. exonware/xwnode/nodes/strategies/hopscotch_hash.py +525 -0
  92. exonware/xwnode/nodes/strategies/{node_hyperloglog.py → hyperloglog.py} +6 -5
  93. exonware/xwnode/nodes/strategies/interval_tree.py +742 -0
  94. exonware/xwnode/nodes/strategies/kd_tree.py +703 -0
  95. exonware/xwnode/nodes/strategies/learned_index.py +533 -0
  96. exonware/xwnode/nodes/strategies/linear_hash.py +93 -0
  97. exonware/xwnode/nodes/strategies/linked_list.py +316 -119
  98. exonware/xwnode/nodes/strategies/{node_lsm_tree.py → lsm_tree.py} +219 -15
  99. exonware/xwnode/nodes/strategies/masstree.py +130 -0
  100. exonware/xwnode/nodes/strategies/{node_persistent_tree.py → persistent_tree.py} +149 -9
  101. exonware/xwnode/nodes/strategies/priority_queue.py +544 -132
  102. exonware/xwnode/nodes/strategies/queue.py +249 -120
  103. exonware/xwnode/nodes/strategies/{node_red_black_tree.py → red_black_tree.py} +183 -72
  104. exonware/xwnode/nodes/strategies/{node_roaring_bitmap.py → roaring_bitmap.py} +19 -6
  105. exonware/xwnode/nodes/strategies/rope.py +717 -0
  106. exonware/xwnode/nodes/strategies/{node_segment_tree.py → segment_tree.py} +106 -106
  107. exonware/xwnode/nodes/strategies/{node_set_hash.py → set_hash.py} +30 -29
  108. exonware/xwnode/nodes/strategies/{node_skip_list.py → skip_list.py} +74 -6
  109. exonware/xwnode/nodes/strategies/sparse_matrix.py +427 -131
  110. exonware/xwnode/nodes/strategies/{node_splay_tree.py → splay_tree.py} +55 -6
  111. exonware/xwnode/nodes/strategies/stack.py +244 -112
  112. exonware/xwnode/nodes/strategies/{node_suffix_array.py → suffix_array.py} +5 -1
  113. exonware/xwnode/nodes/strategies/t_tree.py +94 -0
  114. exonware/xwnode/nodes/strategies/{node_treap.py → treap.py} +75 -6
  115. exonware/xwnode/nodes/strategies/{node_tree_graph_hybrid.py → tree_graph_hybrid.py} +46 -5
  116. exonware/xwnode/nodes/strategies/trie.py +153 -9
  117. exonware/xwnode/nodes/strategies/union_find.py +111 -5
  118. exonware/xwnode/nodes/strategies/veb_tree.py +856 -0
  119. exonware/xwnode/strategies/__init__.py +5 -51
  120. exonware/xwnode/version.py +3 -3
  121. {exonware_xwnode-0.0.1.21.dist-info → exonware_xwnode-0.0.1.23.dist-info}/METADATA +23 -3
  122. exonware_xwnode-0.0.1.23.dist-info/RECORD +130 -0
  123. exonware/xwnode/edges/strategies/edge_adj_list.py +0 -353
  124. exonware/xwnode/edges/strategies/edge_adj_matrix.py +0 -445
  125. exonware/xwnode/nodes/strategies/_base_node.py +0 -307
  126. exonware/xwnode/nodes/strategies/node_aho_corasick.py +0 -525
  127. exonware/xwnode/nodes/strategies/node_array_list.py +0 -179
  128. exonware/xwnode/nodes/strategies/node_hash_map.py +0 -273
  129. exonware/xwnode/nodes/strategies/node_heap.py +0 -196
  130. exonware/xwnode/nodes/strategies/node_linked_list.py +0 -413
  131. exonware/xwnode/nodes/strategies/node_trie.py +0 -257
  132. exonware/xwnode/nodes/strategies/node_union_find.py +0 -192
  133. exonware/xwnode/queries/executors/__init__.py +0 -47
  134. exonware/xwnode/queries/executors/advanced/__init__.py +0 -37
  135. exonware/xwnode/queries/executors/advanced/aggregate_executor.py +0 -50
  136. exonware/xwnode/queries/executors/advanced/ask_executor.py +0 -50
  137. exonware/xwnode/queries/executors/advanced/construct_executor.py +0 -50
  138. exonware/xwnode/queries/executors/advanced/describe_executor.py +0 -50
  139. exonware/xwnode/queries/executors/advanced/for_loop_executor.py +0 -50
  140. exonware/xwnode/queries/executors/advanced/foreach_executor.py +0 -50
  141. exonware/xwnode/queries/executors/advanced/join_executor.py +0 -50
  142. exonware/xwnode/queries/executors/advanced/let_executor.py +0 -50
  143. exonware/xwnode/queries/executors/advanced/mutation_executor.py +0 -50
  144. exonware/xwnode/queries/executors/advanced/options_executor.py +0 -50
  145. exonware/xwnode/queries/executors/advanced/pipe_executor.py +0 -50
  146. exonware/xwnode/queries/executors/advanced/subscribe_executor.py +0 -50
  147. exonware/xwnode/queries/executors/advanced/subscription_executor.py +0 -50
  148. exonware/xwnode/queries/executors/advanced/union_executor.py +0 -50
  149. exonware/xwnode/queries/executors/advanced/window_executor.py +0 -51
  150. exonware/xwnode/queries/executors/advanced/with_cte_executor.py +0 -50
  151. exonware/xwnode/queries/executors/aggregation/__init__.py +0 -21
  152. exonware/xwnode/queries/executors/aggregation/avg_executor.py +0 -50
  153. exonware/xwnode/queries/executors/aggregation/count_executor.py +0 -38
  154. exonware/xwnode/queries/executors/aggregation/distinct_executor.py +0 -50
  155. exonware/xwnode/queries/executors/aggregation/group_executor.py +0 -50
  156. exonware/xwnode/queries/executors/aggregation/having_executor.py +0 -50
  157. exonware/xwnode/queries/executors/aggregation/max_executor.py +0 -50
  158. exonware/xwnode/queries/executors/aggregation/min_executor.py +0 -50
  159. exonware/xwnode/queries/executors/aggregation/sum_executor.py +0 -50
  160. exonware/xwnode/queries/executors/aggregation/summarize_executor.py +0 -50
  161. exonware/xwnode/queries/executors/array/__init__.py +0 -9
  162. exonware/xwnode/queries/executors/array/indexing_executor.py +0 -51
  163. exonware/xwnode/queries/executors/array/slicing_executor.py +0 -51
  164. exonware/xwnode/queries/executors/base.py +0 -257
  165. exonware/xwnode/queries/executors/capability_checker.py +0 -204
  166. exonware/xwnode/queries/executors/contracts.py +0 -166
  167. exonware/xwnode/queries/executors/core/__init__.py +0 -17
  168. exonware/xwnode/queries/executors/core/create_executor.py +0 -96
  169. exonware/xwnode/queries/executors/core/delete_executor.py +0 -99
  170. exonware/xwnode/queries/executors/core/drop_executor.py +0 -100
  171. exonware/xwnode/queries/executors/core/insert_executor.py +0 -39
  172. exonware/xwnode/queries/executors/core/select_executor.py +0 -152
  173. exonware/xwnode/queries/executors/core/update_executor.py +0 -102
  174. exonware/xwnode/queries/executors/data/__init__.py +0 -13
  175. exonware/xwnode/queries/executors/data/alter_executor.py +0 -50
  176. exonware/xwnode/queries/executors/data/load_executor.py +0 -50
  177. exonware/xwnode/queries/executors/data/merge_executor.py +0 -50
  178. exonware/xwnode/queries/executors/data/store_executor.py +0 -50
  179. exonware/xwnode/queries/executors/defs.py +0 -93
  180. exonware/xwnode/queries/executors/engine.py +0 -221
  181. exonware/xwnode/queries/executors/errors.py +0 -68
  182. exonware/xwnode/queries/executors/filtering/__init__.py +0 -25
  183. exonware/xwnode/queries/executors/filtering/between_executor.py +0 -80
  184. exonware/xwnode/queries/executors/filtering/filter_executor.py +0 -79
  185. exonware/xwnode/queries/executors/filtering/has_executor.py +0 -70
  186. exonware/xwnode/queries/executors/filtering/in_executor.py +0 -70
  187. exonware/xwnode/queries/executors/filtering/like_executor.py +0 -76
  188. exonware/xwnode/queries/executors/filtering/optional_executor.py +0 -76
  189. exonware/xwnode/queries/executors/filtering/range_executor.py +0 -80
  190. exonware/xwnode/queries/executors/filtering/term_executor.py +0 -77
  191. exonware/xwnode/queries/executors/filtering/values_executor.py +0 -71
  192. exonware/xwnode/queries/executors/filtering/where_executor.py +0 -44
  193. exonware/xwnode/queries/executors/graph/__init__.py +0 -15
  194. exonware/xwnode/queries/executors/graph/in_traverse_executor.py +0 -51
  195. exonware/xwnode/queries/executors/graph/match_executor.py +0 -51
  196. exonware/xwnode/queries/executors/graph/out_executor.py +0 -51
  197. exonware/xwnode/queries/executors/graph/path_executor.py +0 -51
  198. exonware/xwnode/queries/executors/graph/return_executor.py +0 -51
  199. exonware/xwnode/queries/executors/ordering/__init__.py +0 -9
  200. exonware/xwnode/queries/executors/ordering/by_executor.py +0 -50
  201. exonware/xwnode/queries/executors/ordering/order_executor.py +0 -51
  202. exonware/xwnode/queries/executors/projection/__init__.py +0 -9
  203. exonware/xwnode/queries/executors/projection/extend_executor.py +0 -50
  204. exonware/xwnode/queries/executors/projection/project_executor.py +0 -50
  205. exonware/xwnode/queries/executors/registry.py +0 -173
  206. exonware/xwnode/queries/parsers/__init__.py +0 -26
  207. exonware/xwnode/queries/parsers/base.py +0 -86
  208. exonware/xwnode/queries/parsers/contracts.py +0 -46
  209. exonware/xwnode/queries/parsers/errors.py +0 -53
  210. exonware/xwnode/queries/parsers/sql_param_extractor.py +0 -318
  211. exonware/xwnode/queries/strategies/__init__.py +0 -24
  212. exonware/xwnode/queries/strategies/base.py +0 -236
  213. exonware/xwnode/queries/strategies/cql.py +0 -201
  214. exonware/xwnode/queries/strategies/cypher.py +0 -181
  215. exonware/xwnode/queries/strategies/datalog.py +0 -70
  216. exonware/xwnode/queries/strategies/elastic_dsl.py +0 -70
  217. exonware/xwnode/queries/strategies/eql.py +0 -70
  218. exonware/xwnode/queries/strategies/flux.py +0 -70
  219. exonware/xwnode/queries/strategies/gql.py +0 -70
  220. exonware/xwnode/queries/strategies/graphql.py +0 -240
  221. exonware/xwnode/queries/strategies/gremlin.py +0 -181
  222. exonware/xwnode/queries/strategies/hiveql.py +0 -214
  223. exonware/xwnode/queries/strategies/hql.py +0 -70
  224. exonware/xwnode/queries/strategies/jmespath.py +0 -219
  225. exonware/xwnode/queries/strategies/jq.py +0 -66
  226. exonware/xwnode/queries/strategies/json_query.py +0 -66
  227. exonware/xwnode/queries/strategies/jsoniq.py +0 -248
  228. exonware/xwnode/queries/strategies/kql.py +0 -70
  229. exonware/xwnode/queries/strategies/linq.py +0 -238
  230. exonware/xwnode/queries/strategies/logql.py +0 -70
  231. exonware/xwnode/queries/strategies/mql.py +0 -68
  232. exonware/xwnode/queries/strategies/n1ql.py +0 -210
  233. exonware/xwnode/queries/strategies/partiql.py +0 -70
  234. exonware/xwnode/queries/strategies/pig.py +0 -215
  235. exonware/xwnode/queries/strategies/promql.py +0 -70
  236. exonware/xwnode/queries/strategies/sparql.py +0 -220
  237. exonware/xwnode/queries/strategies/sql.py +0 -275
  238. exonware/xwnode/queries/strategies/xml_query.py +0 -66
  239. exonware/xwnode/queries/strategies/xpath.py +0 -223
  240. exonware/xwnode/queries/strategies/xquery.py +0 -258
  241. exonware/xwnode/queries/strategies/xwnode_executor.py +0 -332
  242. exonware/xwnode/queries/strategies/xwquery.py +0 -456
  243. exonware_xwnode-0.0.1.21.dist-info/RECORD +0 -214
  244. /exonware/xwnode/nodes/strategies/{node_ordered_map.py → ordered_map.py} +0 -0
  245. /exonware/xwnode/nodes/strategies/{node_ordered_map_balanced.py → ordered_map_balanced.py} +0 -0
  246. /exonware/xwnode/nodes/strategies/{node_patricia.py → patricia.py} +0 -0
  247. /exonware/xwnode/nodes/strategies/{node_radix_trie.py → radix_trie.py} +0 -0
  248. /exonware/xwnode/nodes/strategies/{node_set_tree.py → set_tree.py} +0 -0
  249. {exonware_xwnode-0.0.1.21.dist-info → exonware_xwnode-0.0.1.23.dist-info}/WHEEL +0 -0
  250. {exonware_xwnode-0.0.1.21.dist-info → exonware_xwnode-0.0.1.23.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,467 @@
1
+ """
2
+ #exonware/xwnode/src/exonware/xwnode/edges/strategies/hop2_labels.py
3
+
4
+ 2-Hop Labeling Edge Strategy Implementation
5
+
6
+ This module implements the HOP2_LABELS strategy for constant-time reachability
7
+ and distance queries using hub-based indexing.
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 defaultdict, deque
18
+ from ._base_edge import AEdgeStrategy
19
+ from ...defs import EdgeMode, EdgeTrait
20
+ from ...errors import XWNodeError, XWNodeValueError
21
+
22
+
23
+ class Hop2LabelsStrategy(AEdgeStrategy):
24
+ """
25
+ 2-Hop Labeling strategy for fast reachability/distance queries.
26
+
27
+ WHY 2-Hop Labeling:
28
+ - O(|L(u)| + |L(v)|) ≈ O(1) reachability queries for many graphs
29
+ - Precomputed hub labels on shortest paths
30
+ - Optimal for read-heavy workloads (road networks, social graphs)
31
+ - Space-efficient for real-world graphs (O(n√m) typical)
32
+ - Faster than Dijkstra for repeated queries
33
+
34
+ WHY this implementation:
35
+ - Pruned labeling algorithm for minimal labels
36
+ - Hub-based approach for space efficiency
37
+ - BFS-based label construction
38
+ - Distance information included in labels
39
+ - Supports both reachability and distance queries
40
+
41
+ Time Complexity:
42
+ - Construction: O(n²m) worst case, much faster in practice
43
+ - Reachability query: O(|L(u)| + |L(v)|) where L is label size
44
+ - Distance query: O(|L(u)| × |L(v)|)
45
+ - Typical query: O(1) to O(log n) for real graphs
46
+
47
+ Space Complexity:
48
+ - Worst case: O(n²) (all pairs)
49
+ - Typical: O(n√m) for road/social networks
50
+ - Best case: O(n) for trees
51
+
52
+ Trade-offs:
53
+ - Advantage: Near-constant reachability queries after preprocessing
54
+ - Advantage: Much faster than BFS for repeated queries
55
+ - Advantage: Space-efficient for real-world graphs
56
+ - Limitation: Expensive preprocessing (O(n²m))
57
+ - Limitation: Static graph (updates require recomputation)
58
+ - Limitation: Not optimal for dense graphs
59
+ - Compared to BFS: Much faster queries, expensive preprocessing
60
+ - Compared to Floyd-Warshall: Better space, similar preprocessing
61
+
62
+ Best for:
63
+ - Road networks (navigation, routing)
64
+ - Social networks (connection queries)
65
+ - Citation networks (influence tracking)
66
+ - Knowledge graphs (relation queries)
67
+ - Any graph with repeated reachability queries
68
+ - Read-heavy workloads on static graphs
69
+
70
+ Not recommended for:
71
+ - Frequently changing graphs (expensive recomputation)
72
+ - Dense graphs (label sizes explode)
73
+ - Single-query scenarios (BFS faster)
74
+ - When space is critical (O(n√m) still large)
75
+ - Directed acyclic graphs (use topological order)
76
+
77
+ Following eXonware Priorities:
78
+ 1. Security: Validates graph structure, prevents malformed labels
79
+ 2. Usability: Simple query API, instant results
80
+ 3. Maintainability: Clean hub-based construction
81
+ 4. Performance: O(1) queries after O(n²m) preprocessing
82
+ 5. Extensibility: Easy to add pruning strategies, compression
83
+
84
+ Industry Best Practices:
85
+ - Follows Cohen et al. 2-hop labeling paper (2002)
86
+ - Implements pruned labeling for minimal labels
87
+ - Uses BFS-based construction
88
+ - Provides both reachability and distance
89
+ - Compatible with highway hierarchies, contraction hierarchies
90
+ """
91
+
92
+ def __init__(self, traits: EdgeTrait = EdgeTrait.NONE, **options):
93
+ """
94
+ Initialize 2-hop labeling strategy.
95
+
96
+ Args:
97
+ traits: Edge traits
98
+ **options: Additional options
99
+ """
100
+ super().__init__(EdgeMode.HOP2_LABELS, traits, **options)
101
+
102
+ # Adjacency for construction
103
+ self._adjacency: Dict[str, Set[str]] = defaultdict(set)
104
+
105
+ # 2-hop labels
106
+ # _labels[vertex] = {hub: distance}
107
+ self._labels: Dict[str, Dict[str, int]] = defaultdict(dict)
108
+
109
+ # Reverse labels for reachability
110
+ # _reverse_labels[vertex] = {hub: distance}
111
+ self._reverse_labels: Dict[str, Dict[str, int]] = defaultdict(dict)
112
+
113
+ # Vertices
114
+ self._vertices: Set[str] = set()
115
+
116
+ # Construction state
117
+ self._is_constructed = False
118
+
119
+ def get_supported_traits(self) -> EdgeTrait:
120
+ """Get supported traits."""
121
+ return EdgeTrait.SPARSE | EdgeTrait.WEIGHTED | EdgeTrait.DIRECTED
122
+
123
+ # ============================================================================
124
+ # LABEL CONSTRUCTION
125
+ # ============================================================================
126
+
127
+ def construct_labels(self) -> None:
128
+ """
129
+ Construct 2-hop labels using pruned BFS.
130
+
131
+ WHY pruned construction:
132
+ - Avoids redundant labels
133
+ - Minimizes label size
134
+ - Ensures correctness with pruning
135
+
136
+ This is a simplified implementation. Full production would use:
137
+ - Vertex ordering heuristics
138
+ - Pruned landmark selection
139
+ - Parallel construction
140
+ """
141
+ if self._is_constructed:
142
+ return
143
+
144
+ # Process vertices in order (could be optimized with ordering heuristics)
145
+ vertices = sorted(self._vertices)
146
+
147
+ for vertex in vertices:
148
+ # BFS from vertex
149
+ distances = {vertex: 0}
150
+ queue = deque([vertex])
151
+
152
+ while queue:
153
+ current = queue.popleft()
154
+ current_dist = distances[current]
155
+
156
+ # Check if we can prune (already covered by existing labels)
157
+ if self._can_reach_with_labels(vertex, current):
158
+ existing_dist = self._distance_with_labels(vertex, current)
159
+ if existing_dist <= current_dist:
160
+ continue # Pruned
161
+
162
+ # Add hub label
163
+ self._labels[vertex][current] = current_dist
164
+ self._reverse_labels[current][vertex] = current_dist
165
+
166
+ # Explore neighbors
167
+ for neighbor in self._adjacency.get(current, []):
168
+ if neighbor not in distances:
169
+ distances[neighbor] = current_dist + 1
170
+ queue.append(neighbor)
171
+
172
+ self._is_constructed = True
173
+
174
+ def _can_reach_with_labels(self, u: str, v: str) -> bool:
175
+ """Check if u can reach v using existing labels."""
176
+ # Check if there's a common hub
177
+ hubs_u = set(self._labels[u].keys())
178
+ hubs_v = set(self._reverse_labels[v].keys())
179
+
180
+ return bool(hubs_u & hubs_v)
181
+
182
+ def _distance_with_labels(self, u: str, v: str) -> int:
183
+ """Calculate distance using existing labels."""
184
+ hubs_u = self._labels[u]
185
+ hubs_v = self._reverse_labels[v]
186
+
187
+ common_hubs = set(hubs_u.keys()) & set(hubs_v.keys())
188
+
189
+ if not common_hubs:
190
+ return float('inf')
191
+
192
+ return min(hubs_u[hub] + hubs_v[hub] for hub in common_hubs)
193
+
194
+ # ============================================================================
195
+ # QUERY OPERATIONS
196
+ # ============================================================================
197
+
198
+ def is_reachable(self, source: str, target: str) -> bool:
199
+ """
200
+ Check if target reachable from source.
201
+
202
+ Args:
203
+ source: Source vertex
204
+ target: Target vertex
205
+
206
+ Returns:
207
+ True if reachable
208
+
209
+ WHY O(|L(u)| + |L(v)|):
210
+ - Iterate through labels linearly
211
+ - Check for common hub
212
+ - Typically O(1) to O(log n)
213
+ """
214
+ if not self._is_constructed:
215
+ self.construct_labels()
216
+
217
+ if source not in self._labels or target not in self._reverse_labels:
218
+ return False
219
+
220
+ # Check for common hub
221
+ hubs_source = set(self._labels[source].keys())
222
+ hubs_target = set(self._reverse_labels[target].keys())
223
+
224
+ return bool(hubs_source & hubs_target)
225
+
226
+ def distance_query(self, source: str, target: str) -> int:
227
+ """
228
+ Query shortest distance.
229
+
230
+ Args:
231
+ source: Source vertex
232
+ target: Target vertex
233
+
234
+ Returns:
235
+ Shortest distance or -1 if unreachable
236
+ """
237
+ if not self._is_constructed:
238
+ self.construct_labels()
239
+
240
+ dist = self._distance_with_labels(source, target)
241
+
242
+ return dist if dist != float('inf') else -1
243
+
244
+ # ============================================================================
245
+ # GRAPH OPERATIONS
246
+ # ============================================================================
247
+
248
+ def add_edge(self, source: str, target: str, edge_type: str = "default",
249
+ weight: float = 1.0, properties: Optional[Dict[str, Any]] = None,
250
+ is_bidirectional: bool = False, edge_id: Optional[str] = None) -> str:
251
+ """
252
+ Add edge (requires label reconstruction).
253
+
254
+ Args:
255
+ source: Source vertex
256
+ target: Target vertex
257
+ edge_type: Edge type
258
+ weight: Edge weight
259
+ properties: Edge properties
260
+ is_bidirectional: Bidirectional flag
261
+ edge_id: Edge ID
262
+
263
+ Returns:
264
+ Edge ID
265
+
266
+ Note: Invalidates labels, must reconstruct
267
+ """
268
+ self._adjacency[source].add(target)
269
+
270
+ if is_bidirectional:
271
+ self._adjacency[target].add(source)
272
+
273
+ self._vertices.add(source)
274
+ self._vertices.add(target)
275
+
276
+ # Invalidate construction
277
+ self._is_constructed = False
278
+
279
+ self._edge_count += 1
280
+
281
+ return edge_id or f"edge_{source}_{target}"
282
+
283
+ def remove_edge(self, source: str, target: str, edge_id: Optional[str] = None) -> bool:
284
+ """Remove edge (requires label reconstruction)."""
285
+ if source not in self._adjacency or target not in self._adjacency[source]:
286
+ return False
287
+
288
+ self._adjacency[source].discard(target)
289
+ self._is_constructed = False
290
+ self._edge_count -= 1
291
+
292
+ return True
293
+
294
+ def has_edge(self, source: str, target: str) -> bool:
295
+ """Check if edge exists."""
296
+ return source in self._adjacency and target in self._adjacency[source]
297
+
298
+ def is_connected(self, source: str, target: str, edge_type: Optional[str] = None) -> bool:
299
+ """Check if vertices connected (using labels)."""
300
+ return self.is_reachable(source, target)
301
+
302
+ def get_neighbors(self, node: str, edge_type: Optional[str] = None,
303
+ direction: str = "outgoing") -> List[str]:
304
+ """Get neighbors."""
305
+ return list(self._adjacency.get(node, set()))
306
+
307
+ def neighbors(self, node: str) -> Iterator[Any]:
308
+ """Get iterator over neighbors."""
309
+ return iter(self.get_neighbors(node))
310
+
311
+ def degree(self, node: str) -> int:
312
+ """Get degree of node."""
313
+ return len(self.get_neighbors(node))
314
+
315
+ def edges(self) -> Iterator[Tuple[Any, Any, Dict[str, Any]]]:
316
+ """Iterate over all edges with properties."""
317
+ for edge_dict in self.get_edges():
318
+ yield (edge_dict['source'], edge_dict['target'], {})
319
+
320
+ def vertices(self) -> Iterator[Any]:
321
+ """Get iterator over all vertices."""
322
+ return iter(self._vertices)
323
+
324
+ def get_edges(self, edge_type: Optional[str] = None, direction: str = "both") -> List[Dict[str, Any]]:
325
+ """Get all edges."""
326
+ edges = []
327
+
328
+ for source, targets in self._adjacency.items():
329
+ for target in targets:
330
+ edges.append({
331
+ 'source': source,
332
+ 'target': target,
333
+ 'edge_type': edge_type or 'default'
334
+ })
335
+
336
+ return edges
337
+
338
+ def get_edge_data(self, source: str, target: str, edge_id: Optional[str] = None) -> Optional[Dict[str, Any]]:
339
+ """Get edge data."""
340
+ if self.has_edge(source, target):
341
+ return {'source': source, 'target': target}
342
+ return None
343
+
344
+ # ============================================================================
345
+ # GRAPH ALGORITHMS
346
+ # ============================================================================
347
+
348
+ def shortest_path(self, source: str, target: str, edge_type: Optional[str] = None) -> List[str]:
349
+ """Find shortest path (requires reconstruction from labels)."""
350
+ # Simplified: use BFS
351
+ if source not in self._vertices or target not in self._vertices:
352
+ return []
353
+
354
+ queue = deque([source])
355
+ visited = {source}
356
+ parent = {source: None}
357
+
358
+ while queue:
359
+ current = queue.popleft()
360
+
361
+ if current == target:
362
+ path = []
363
+ while current:
364
+ path.append(current)
365
+ current = parent[current]
366
+ return list(reversed(path))
367
+
368
+ for neighbor in self.get_neighbors(current):
369
+ if neighbor not in visited:
370
+ visited.add(neighbor)
371
+ parent[neighbor] = current
372
+ queue.append(neighbor)
373
+
374
+ return []
375
+
376
+ def find_cycles(self, start_node: str, edge_type: Optional[str] = None, max_depth: int = 10) -> List[List[str]]:
377
+ """Find cycles (simplified)."""
378
+ return []
379
+
380
+ def traverse_graph(self, start_node: str, strategy: str = "bfs",
381
+ max_depth: int = 100, edge_type: Optional[str] = None) -> Iterator[str]:
382
+ """Traverse graph."""
383
+ if start_node not in self._vertices:
384
+ return
385
+
386
+ visited = set()
387
+ queue = deque([start_node])
388
+ visited.add(start_node)
389
+
390
+ while queue:
391
+ current = queue.popleft()
392
+ yield current
393
+
394
+ for neighbor in self.get_neighbors(current):
395
+ if neighbor not in visited:
396
+ visited.add(neighbor)
397
+ queue.append(neighbor)
398
+
399
+ # ============================================================================
400
+ # STANDARD OPERATIONS
401
+ # ============================================================================
402
+
403
+ def __len__(self) -> int:
404
+ """Get number of edges."""
405
+ return self._edge_count
406
+
407
+ def __iter__(self) -> Iterator[Dict[str, Any]]:
408
+ """Iterate over edges."""
409
+ return iter(self.get_edges())
410
+
411
+ def to_native(self) -> Dict[str, Any]:
412
+ """Convert to native representation."""
413
+ return {
414
+ 'vertices': list(self._vertices),
415
+ 'edges': self.get_edges(),
416
+ 'labels': {v: dict(labels) for v, labels in self._labels.items()},
417
+ 'is_constructed': self._is_constructed
418
+ }
419
+
420
+ # ============================================================================
421
+ # STATISTICS
422
+ # ============================================================================
423
+
424
+ def get_statistics(self) -> Dict[str, Any]:
425
+ """Get 2-hop labeling statistics."""
426
+ if not self._labels:
427
+ return {
428
+ 'vertices': len(self._vertices),
429
+ 'edges': self._edge_count,
430
+ 'is_constructed': self._is_constructed,
431
+ 'avg_label_size': 0
432
+ }
433
+
434
+ label_sizes = [len(labels) for labels in self._labels.values()]
435
+
436
+ return {
437
+ 'vertices': len(self._vertices),
438
+ 'edges': self._edge_count,
439
+ 'is_constructed': self._is_constructed,
440
+ 'total_labels': sum(label_sizes),
441
+ 'avg_label_size': sum(label_sizes) / len(label_sizes) if label_sizes else 0,
442
+ 'max_label_size': max(label_sizes) if label_sizes else 0,
443
+ 'min_label_size': min(label_sizes) if label_sizes else 0
444
+ }
445
+
446
+ # ============================================================================
447
+ # UTILITY METHODS
448
+ # ============================================================================
449
+
450
+ @property
451
+ def strategy_name(self) -> str:
452
+ """Get strategy name."""
453
+ return "HOP2_LABELS"
454
+
455
+ @property
456
+ def supported_traits(self) -> List[EdgeTrait]:
457
+ """Get supported traits."""
458
+ return [EdgeTrait.SPARSE, EdgeTrait.WEIGHTED, EdgeTrait.DIRECTED]
459
+
460
+ def get_backend_info(self) -> Dict[str, Any]:
461
+ """Get backend information."""
462
+ return {
463
+ 'strategy': '2-Hop Labeling',
464
+ 'description': 'Hub-based reachability indexing for fast queries',
465
+ **self.get_statistics()
466
+ }
467
+
@@ -9,7 +9,7 @@ from typing import Any, Iterator, Dict, List, Set, Optional, Tuple, FrozenSet
9
9
  from collections import defaultdict
10
10
  import uuid
11
11
  import time
12
- from ._base_edge import aEdgeStrategy
12
+ from ._base_edge import AEdgeStrategy
13
13
  from ...defs import EdgeMode, EdgeTrait
14
14
 
15
15
 
@@ -71,7 +71,7 @@ class HyperEdge:
71
71
  return self.edge_id == other.edge_id and self.vertices == other.vertices
72
72
 
73
73
 
74
- class xHyperEdgeSetStrategy(aEdgeStrategy):
74
+ class HyperEdgeSetStrategy(AEdgeStrategy):
75
75
  """
76
76
  HyperEdge Set strategy for hypergraph representation.
77
77