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.
Files changed (248) hide show
  1. exonware/__init__.py +1 -1
  2. exonware/xwnode/__init__.py +18 -5
  3. exonware/xwnode/add_strategy_types.py +165 -0
  4. exonware/xwnode/common/__init__.py +1 -1
  5. exonware/xwnode/common/graph/__init__.py +30 -0
  6. exonware/xwnode/common/graph/caching.py +131 -0
  7. exonware/xwnode/common/graph/contracts.py +100 -0
  8. exonware/xwnode/common/graph/errors.py +44 -0
  9. exonware/xwnode/common/graph/indexing.py +260 -0
  10. exonware/xwnode/common/graph/manager.py +568 -0
  11. exonware/xwnode/common/management/__init__.py +3 -5
  12. exonware/xwnode/common/management/manager.py +2 -2
  13. exonware/xwnode/common/management/migration.py +3 -3
  14. exonware/xwnode/common/monitoring/__init__.py +3 -5
  15. exonware/xwnode/common/monitoring/metrics.py +6 -2
  16. exonware/xwnode/common/monitoring/pattern_detector.py +1 -1
  17. exonware/xwnode/common/monitoring/performance_monitor.py +5 -1
  18. exonware/xwnode/common/patterns/__init__.py +3 -5
  19. exonware/xwnode/common/patterns/flyweight.py +5 -1
  20. exonware/xwnode/common/patterns/registry.py +202 -183
  21. exonware/xwnode/common/utils/__init__.py +25 -11
  22. exonware/xwnode/common/utils/simple.py +1 -1
  23. exonware/xwnode/config.py +3 -8
  24. exonware/xwnode/contracts.py +4 -105
  25. exonware/xwnode/defs.py +413 -159
  26. exonware/xwnode/edges/strategies/__init__.py +86 -4
  27. exonware/xwnode/edges/strategies/_base_edge.py +2 -2
  28. exonware/xwnode/edges/strategies/adj_list.py +287 -121
  29. exonware/xwnode/edges/strategies/adj_matrix.py +316 -222
  30. exonware/xwnode/edges/strategies/base.py +1 -1
  31. exonware/xwnode/edges/strategies/{edge_bidir_wrapper.py → bidir_wrapper.py} +45 -4
  32. exonware/xwnode/edges/strategies/bitemporal.py +520 -0
  33. exonware/xwnode/edges/strategies/{edge_block_adj_matrix.py → block_adj_matrix.py} +77 -6
  34. exonware/xwnode/edges/strategies/bv_graph.py +664 -0
  35. exonware/xwnode/edges/strategies/compressed_graph.py +217 -0
  36. exonware/xwnode/edges/strategies/{edge_coo.py → coo.py} +46 -4
  37. exonware/xwnode/edges/strategies/{edge_csc.py → csc.py} +45 -4
  38. exonware/xwnode/edges/strategies/{edge_csr.py → csr.py} +94 -12
  39. exonware/xwnode/edges/strategies/{edge_dynamic_adj_list.py → dynamic_adj_list.py} +46 -4
  40. exonware/xwnode/edges/strategies/edge_list.py +168 -0
  41. exonware/xwnode/edges/strategies/edge_property_store.py +2 -2
  42. exonware/xwnode/edges/strategies/euler_tour.py +560 -0
  43. exonware/xwnode/edges/strategies/{edge_flow_network.py → flow_network.py} +2 -2
  44. exonware/xwnode/edges/strategies/graphblas.py +449 -0
  45. exonware/xwnode/edges/strategies/hnsw.py +637 -0
  46. exonware/xwnode/edges/strategies/hop2_labels.py +467 -0
  47. exonware/xwnode/edges/strategies/{edge_hyperedge_set.py → hyperedge_set.py} +2 -2
  48. exonware/xwnode/edges/strategies/incidence_matrix.py +250 -0
  49. exonware/xwnode/edges/strategies/k2_tree.py +613 -0
  50. exonware/xwnode/edges/strategies/link_cut.py +626 -0
  51. exonware/xwnode/edges/strategies/multiplex.py +532 -0
  52. exonware/xwnode/edges/strategies/{edge_neural_graph.py → neural_graph.py} +2 -2
  53. exonware/xwnode/edges/strategies/{edge_octree.py → octree.py} +69 -11
  54. exonware/xwnode/edges/strategies/{edge_quadtree.py → quadtree.py} +66 -10
  55. exonware/xwnode/edges/strategies/roaring_adj.py +438 -0
  56. exonware/xwnode/edges/strategies/{edge_rtree.py → rtree.py} +43 -5
  57. exonware/xwnode/edges/strategies/{edge_temporal_edgeset.py → temporal_edgeset.py} +24 -5
  58. exonware/xwnode/edges/strategies/{edge_tree_graph_basic.py → tree_graph_basic.py} +78 -7
  59. exonware/xwnode/edges/strategies/{edge_weighted_graph.py → weighted_graph.py} +188 -10
  60. exonware/xwnode/errors.py +3 -6
  61. exonware/xwnode/facade.py +20 -20
  62. exonware/xwnode/nodes/strategies/__init__.py +29 -9
  63. exonware/xwnode/nodes/strategies/adjacency_list.py +650 -177
  64. exonware/xwnode/nodes/strategies/aho_corasick.py +358 -183
  65. exonware/xwnode/nodes/strategies/array_list.py +36 -3
  66. exonware/xwnode/nodes/strategies/art.py +581 -0
  67. exonware/xwnode/nodes/strategies/{node_avl_tree.py → avl_tree.py} +77 -6
  68. exonware/xwnode/nodes/strategies/{node_b_plus_tree.py → b_plus_tree.py} +81 -40
  69. exonware/xwnode/nodes/strategies/{node_btree.py → b_tree.py} +79 -9
  70. exonware/xwnode/nodes/strategies/base.py +469 -98
  71. exonware/xwnode/nodes/strategies/{node_bitmap.py → bitmap.py} +12 -12
  72. exonware/xwnode/nodes/strategies/{node_bitset_dynamic.py → bitset_dynamic.py} +11 -11
  73. exonware/xwnode/nodes/strategies/{node_bloom_filter.py → bloom_filter.py} +15 -2
  74. exonware/xwnode/nodes/strategies/bloomier_filter.py +519 -0
  75. exonware/xwnode/nodes/strategies/bw_tree.py +531 -0
  76. exonware/xwnode/nodes/strategies/contracts.py +1 -1
  77. exonware/xwnode/nodes/strategies/{node_count_min_sketch.py → count_min_sketch.py} +3 -2
  78. exonware/xwnode/nodes/strategies/{node_cow_tree.py → cow_tree.py} +135 -13
  79. exonware/xwnode/nodes/strategies/crdt_map.py +629 -0
  80. exonware/xwnode/nodes/strategies/{node_cuckoo_hash.py → cuckoo_hash.py} +2 -2
  81. exonware/xwnode/nodes/strategies/{node_xdata_optimized.py → data_interchange_optimized.py} +21 -4
  82. exonware/xwnode/nodes/strategies/dawg.py +876 -0
  83. exonware/xwnode/nodes/strategies/deque.py +321 -153
  84. exonware/xwnode/nodes/strategies/extendible_hash.py +93 -0
  85. exonware/xwnode/nodes/strategies/{node_fenwick_tree.py → fenwick_tree.py} +111 -19
  86. exonware/xwnode/nodes/strategies/hamt.py +403 -0
  87. exonware/xwnode/nodes/strategies/hash_map.py +354 -67
  88. exonware/xwnode/nodes/strategies/heap.py +105 -5
  89. exonware/xwnode/nodes/strategies/hopscotch_hash.py +525 -0
  90. exonware/xwnode/nodes/strategies/{node_hyperloglog.py → hyperloglog.py} +6 -5
  91. exonware/xwnode/nodes/strategies/interval_tree.py +742 -0
  92. exonware/xwnode/nodes/strategies/kd_tree.py +703 -0
  93. exonware/xwnode/nodes/strategies/learned_index.py +533 -0
  94. exonware/xwnode/nodes/strategies/linear_hash.py +93 -0
  95. exonware/xwnode/nodes/strategies/linked_list.py +316 -119
  96. exonware/xwnode/nodes/strategies/{node_lsm_tree.py → lsm_tree.py} +219 -15
  97. exonware/xwnode/nodes/strategies/masstree.py +130 -0
  98. exonware/xwnode/nodes/strategies/{node_persistent_tree.py → persistent_tree.py} +149 -9
  99. exonware/xwnode/nodes/strategies/priority_queue.py +544 -132
  100. exonware/xwnode/nodes/strategies/queue.py +249 -120
  101. exonware/xwnode/nodes/strategies/{node_red_black_tree.py → red_black_tree.py} +183 -72
  102. exonware/xwnode/nodes/strategies/{node_roaring_bitmap.py → roaring_bitmap.py} +19 -6
  103. exonware/xwnode/nodes/strategies/rope.py +717 -0
  104. exonware/xwnode/nodes/strategies/{node_segment_tree.py → segment_tree.py} +106 -106
  105. exonware/xwnode/nodes/strategies/{node_set_hash.py → set_hash.py} +30 -29
  106. exonware/xwnode/nodes/strategies/{node_skip_list.py → skip_list.py} +74 -6
  107. exonware/xwnode/nodes/strategies/sparse_matrix.py +427 -131
  108. exonware/xwnode/nodes/strategies/{node_splay_tree.py → splay_tree.py} +55 -6
  109. exonware/xwnode/nodes/strategies/stack.py +244 -112
  110. exonware/xwnode/nodes/strategies/{node_suffix_array.py → suffix_array.py} +5 -1
  111. exonware/xwnode/nodes/strategies/t_tree.py +94 -0
  112. exonware/xwnode/nodes/strategies/{node_treap.py → treap.py} +75 -6
  113. exonware/xwnode/nodes/strategies/{node_tree_graph_hybrid.py → tree_graph_hybrid.py} +46 -5
  114. exonware/xwnode/nodes/strategies/trie.py +153 -9
  115. exonware/xwnode/nodes/strategies/union_find.py +111 -5
  116. exonware/xwnode/nodes/strategies/veb_tree.py +856 -0
  117. exonware/xwnode/strategies/__init__.py +5 -51
  118. exonware/xwnode/version.py +3 -3
  119. {exonware_xwnode-0.0.1.22.dist-info → exonware_xwnode-0.0.1.23.dist-info}/METADATA +23 -3
  120. exonware_xwnode-0.0.1.23.dist-info/RECORD +130 -0
  121. exonware/xwnode/edges/strategies/edge_adj_list.py +0 -353
  122. exonware/xwnode/edges/strategies/edge_adj_matrix.py +0 -445
  123. exonware/xwnode/nodes/strategies/_base_node.py +0 -307
  124. exonware/xwnode/nodes/strategies/node_aho_corasick.py +0 -525
  125. exonware/xwnode/nodes/strategies/node_array_list.py +0 -179
  126. exonware/xwnode/nodes/strategies/node_hash_map.py +0 -273
  127. exonware/xwnode/nodes/strategies/node_heap.py +0 -196
  128. exonware/xwnode/nodes/strategies/node_linked_list.py +0 -413
  129. exonware/xwnode/nodes/strategies/node_trie.py +0 -257
  130. exonware/xwnode/nodes/strategies/node_union_find.py +0 -192
  131. exonware/xwnode/queries/executors/__init__.py +0 -47
  132. exonware/xwnode/queries/executors/advanced/__init__.py +0 -37
  133. exonware/xwnode/queries/executors/advanced/aggregate_executor.py +0 -50
  134. exonware/xwnode/queries/executors/advanced/ask_executor.py +0 -50
  135. exonware/xwnode/queries/executors/advanced/construct_executor.py +0 -50
  136. exonware/xwnode/queries/executors/advanced/describe_executor.py +0 -50
  137. exonware/xwnode/queries/executors/advanced/for_loop_executor.py +0 -50
  138. exonware/xwnode/queries/executors/advanced/foreach_executor.py +0 -50
  139. exonware/xwnode/queries/executors/advanced/join_executor.py +0 -50
  140. exonware/xwnode/queries/executors/advanced/let_executor.py +0 -50
  141. exonware/xwnode/queries/executors/advanced/mutation_executor.py +0 -50
  142. exonware/xwnode/queries/executors/advanced/options_executor.py +0 -50
  143. exonware/xwnode/queries/executors/advanced/pipe_executor.py +0 -50
  144. exonware/xwnode/queries/executors/advanced/subscribe_executor.py +0 -50
  145. exonware/xwnode/queries/executors/advanced/subscription_executor.py +0 -50
  146. exonware/xwnode/queries/executors/advanced/union_executor.py +0 -50
  147. exonware/xwnode/queries/executors/advanced/window_executor.py +0 -51
  148. exonware/xwnode/queries/executors/advanced/with_cte_executor.py +0 -50
  149. exonware/xwnode/queries/executors/aggregation/__init__.py +0 -21
  150. exonware/xwnode/queries/executors/aggregation/avg_executor.py +0 -50
  151. exonware/xwnode/queries/executors/aggregation/count_executor.py +0 -38
  152. exonware/xwnode/queries/executors/aggregation/distinct_executor.py +0 -50
  153. exonware/xwnode/queries/executors/aggregation/group_executor.py +0 -50
  154. exonware/xwnode/queries/executors/aggregation/having_executor.py +0 -50
  155. exonware/xwnode/queries/executors/aggregation/max_executor.py +0 -50
  156. exonware/xwnode/queries/executors/aggregation/min_executor.py +0 -50
  157. exonware/xwnode/queries/executors/aggregation/sum_executor.py +0 -50
  158. exonware/xwnode/queries/executors/aggregation/summarize_executor.py +0 -50
  159. exonware/xwnode/queries/executors/array/__init__.py +0 -9
  160. exonware/xwnode/queries/executors/array/indexing_executor.py +0 -51
  161. exonware/xwnode/queries/executors/array/slicing_executor.py +0 -51
  162. exonware/xwnode/queries/executors/base.py +0 -257
  163. exonware/xwnode/queries/executors/capability_checker.py +0 -204
  164. exonware/xwnode/queries/executors/contracts.py +0 -166
  165. exonware/xwnode/queries/executors/core/__init__.py +0 -17
  166. exonware/xwnode/queries/executors/core/create_executor.py +0 -96
  167. exonware/xwnode/queries/executors/core/delete_executor.py +0 -99
  168. exonware/xwnode/queries/executors/core/drop_executor.py +0 -100
  169. exonware/xwnode/queries/executors/core/insert_executor.py +0 -39
  170. exonware/xwnode/queries/executors/core/select_executor.py +0 -152
  171. exonware/xwnode/queries/executors/core/update_executor.py +0 -102
  172. exonware/xwnode/queries/executors/data/__init__.py +0 -13
  173. exonware/xwnode/queries/executors/data/alter_executor.py +0 -50
  174. exonware/xwnode/queries/executors/data/load_executor.py +0 -50
  175. exonware/xwnode/queries/executors/data/merge_executor.py +0 -50
  176. exonware/xwnode/queries/executors/data/store_executor.py +0 -50
  177. exonware/xwnode/queries/executors/defs.py +0 -93
  178. exonware/xwnode/queries/executors/engine.py +0 -221
  179. exonware/xwnode/queries/executors/errors.py +0 -68
  180. exonware/xwnode/queries/executors/filtering/__init__.py +0 -25
  181. exonware/xwnode/queries/executors/filtering/between_executor.py +0 -80
  182. exonware/xwnode/queries/executors/filtering/filter_executor.py +0 -79
  183. exonware/xwnode/queries/executors/filtering/has_executor.py +0 -70
  184. exonware/xwnode/queries/executors/filtering/in_executor.py +0 -70
  185. exonware/xwnode/queries/executors/filtering/like_executor.py +0 -76
  186. exonware/xwnode/queries/executors/filtering/optional_executor.py +0 -76
  187. exonware/xwnode/queries/executors/filtering/range_executor.py +0 -80
  188. exonware/xwnode/queries/executors/filtering/term_executor.py +0 -77
  189. exonware/xwnode/queries/executors/filtering/values_executor.py +0 -71
  190. exonware/xwnode/queries/executors/filtering/where_executor.py +0 -44
  191. exonware/xwnode/queries/executors/graph/__init__.py +0 -15
  192. exonware/xwnode/queries/executors/graph/in_traverse_executor.py +0 -51
  193. exonware/xwnode/queries/executors/graph/match_executor.py +0 -51
  194. exonware/xwnode/queries/executors/graph/out_executor.py +0 -51
  195. exonware/xwnode/queries/executors/graph/path_executor.py +0 -51
  196. exonware/xwnode/queries/executors/graph/return_executor.py +0 -51
  197. exonware/xwnode/queries/executors/ordering/__init__.py +0 -9
  198. exonware/xwnode/queries/executors/ordering/by_executor.py +0 -50
  199. exonware/xwnode/queries/executors/ordering/order_executor.py +0 -51
  200. exonware/xwnode/queries/executors/projection/__init__.py +0 -9
  201. exonware/xwnode/queries/executors/projection/extend_executor.py +0 -50
  202. exonware/xwnode/queries/executors/projection/project_executor.py +0 -50
  203. exonware/xwnode/queries/executors/registry.py +0 -173
  204. exonware/xwnode/queries/parsers/__init__.py +0 -26
  205. exonware/xwnode/queries/parsers/base.py +0 -86
  206. exonware/xwnode/queries/parsers/contracts.py +0 -46
  207. exonware/xwnode/queries/parsers/errors.py +0 -53
  208. exonware/xwnode/queries/parsers/sql_param_extractor.py +0 -318
  209. exonware/xwnode/queries/strategies/__init__.py +0 -24
  210. exonware/xwnode/queries/strategies/base.py +0 -236
  211. exonware/xwnode/queries/strategies/cql.py +0 -201
  212. exonware/xwnode/queries/strategies/cypher.py +0 -181
  213. exonware/xwnode/queries/strategies/datalog.py +0 -70
  214. exonware/xwnode/queries/strategies/elastic_dsl.py +0 -70
  215. exonware/xwnode/queries/strategies/eql.py +0 -70
  216. exonware/xwnode/queries/strategies/flux.py +0 -70
  217. exonware/xwnode/queries/strategies/gql.py +0 -70
  218. exonware/xwnode/queries/strategies/graphql.py +0 -240
  219. exonware/xwnode/queries/strategies/gremlin.py +0 -181
  220. exonware/xwnode/queries/strategies/hiveql.py +0 -214
  221. exonware/xwnode/queries/strategies/hql.py +0 -70
  222. exonware/xwnode/queries/strategies/jmespath.py +0 -219
  223. exonware/xwnode/queries/strategies/jq.py +0 -66
  224. exonware/xwnode/queries/strategies/json_query.py +0 -66
  225. exonware/xwnode/queries/strategies/jsoniq.py +0 -248
  226. exonware/xwnode/queries/strategies/kql.py +0 -70
  227. exonware/xwnode/queries/strategies/linq.py +0 -238
  228. exonware/xwnode/queries/strategies/logql.py +0 -70
  229. exonware/xwnode/queries/strategies/mql.py +0 -68
  230. exonware/xwnode/queries/strategies/n1ql.py +0 -210
  231. exonware/xwnode/queries/strategies/partiql.py +0 -70
  232. exonware/xwnode/queries/strategies/pig.py +0 -215
  233. exonware/xwnode/queries/strategies/promql.py +0 -70
  234. exonware/xwnode/queries/strategies/sparql.py +0 -220
  235. exonware/xwnode/queries/strategies/sql.py +0 -275
  236. exonware/xwnode/queries/strategies/xml_query.py +0 -66
  237. exonware/xwnode/queries/strategies/xpath.py +0 -223
  238. exonware/xwnode/queries/strategies/xquery.py +0 -258
  239. exonware/xwnode/queries/strategies/xwnode_executor.py +0 -332
  240. exonware/xwnode/queries/strategies/xwquery.py +0 -456
  241. exonware_xwnode-0.0.1.22.dist-info/RECORD +0 -214
  242. /exonware/xwnode/nodes/strategies/{node_ordered_map.py → ordered_map.py} +0 -0
  243. /exonware/xwnode/nodes/strategies/{node_ordered_map_balanced.py → ordered_map_balanced.py} +0 -0
  244. /exonware/xwnode/nodes/strategies/{node_patricia.py → patricia.py} +0 -0
  245. /exonware/xwnode/nodes/strategies/{node_radix_trie.py → radix_trie.py} +0 -0
  246. /exonware/xwnode/nodes/strategies/{node_set_tree.py → set_tree.py} +0 -0
  247. {exonware_xwnode-0.0.1.22.dist-info → exonware_xwnode-0.0.1.23.dist-info}/WHEEL +0 -0
  248. {exonware_xwnode-0.0.1.22.dist-info → exonware_xwnode-0.0.1.23.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,532 @@
1
+ """
2
+ #exonware/xwnode/src/exonware/xwnode/edges/strategies/multiplex.py
3
+
4
+ Multiplex/Layered Edges Strategy Implementation
5
+
6
+ This module implements the MULTIPLEX strategy for multi-layer graphs
7
+ with per-layer edge semantics and cross-layer analysis.
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 MultiplexStrategy(AEdgeStrategy):
24
+ """
25
+ Multiplex/Layered edges strategy for multi-layer graph networks.
26
+
27
+ WHY Multiplex Graphs:
28
+ - Real-world networks have multiple relationship types
29
+ - Each layer represents different semantics (friend, colleague, family)
30
+ - Enables layer-specific and cross-layer analysis
31
+ - Models transportation networks (walk, drive, transit layers)
32
+ - Essential for social network analysis
33
+
34
+ WHY this implementation:
35
+ - Separate adjacency per layer for efficiency
36
+ - Layer-specific queries with O(1) access
37
+ - Cross-layer operations (aggregate, intersection)
38
+ - Dynamic layer creation
39
+ - Per-layer and aggregate degree calculations
40
+
41
+ Time Complexity:
42
+ - Add edge: O(1) per layer
43
+ - Has edge: O(L) where L is number of layers to check
44
+ - Get neighbors (single layer): O(degree)
45
+ - Get neighbors (all layers): O(total_degree)
46
+ - Cross-layer query: O(L × degree)
47
+
48
+ Space Complexity: O(L × edges) where L is number of layers
49
+
50
+ Trade-offs:
51
+ - Advantage: Natural multi-relationship modeling
52
+ - Advantage: Layer-specific analysis
53
+ - Advantage: Cross-layer operations
54
+ - Limitation: Higher memory (L copies of edges)
55
+ - Limitation: Complexity increases with layers
56
+ - Limitation: Queries across all layers slower
57
+ - Compared to Single graph: More flexible, more memory
58
+ - Compared to Edge properties: More structured, easier queries
59
+
60
+ Best for:
61
+ - Social networks (friend, family, colleague layers)
62
+ - Transportation networks (walk, bike, drive, transit)
63
+ - Communication networks (email, chat, phone)
64
+ - Biological networks (protein interactions, genetic)
65
+ - Multi-modal knowledge graphs
66
+ - Temporal network versions (layer per time period)
67
+
68
+ Not recommended for:
69
+ - Single relationship type (use simple graph)
70
+ - Millions of layers (memory explosion)
71
+ - When edge properties suffice
72
+ - Dense graphs across all layers
73
+ - Real-time layer additions
74
+
75
+ Following eXonware Priorities:
76
+ 1. Security: Validates layer names, prevents injection
77
+ 2. Usability: Intuitive layer API, clear semantics
78
+ 3. Maintainability: Clean layer separation
79
+ 4. Performance: O(1) per-layer operations
80
+ 5. Extensibility: Easy to add inter-layer edges, metrics
81
+
82
+ Industry Best Practices:
83
+ - Follows multiplex network literature (Kivela et al.)
84
+ - Implements layer isolation
85
+ - Provides aggregate views
86
+ - Supports inter-layer edges (optional)
87
+ - Compatible with NetworkX MultiGraph
88
+ """
89
+
90
+ def __init__(self, traits: EdgeTrait = EdgeTrait.NONE,
91
+ default_layers: Optional[List[str]] = None, **options):
92
+ """
93
+ Initialize multiplex strategy.
94
+
95
+ Args:
96
+ traits: Edge traits
97
+ default_layers: Initial layer names
98
+ **options: Additional options
99
+ """
100
+ super().__init__(EdgeMode.MULTIPLEX, traits, **options)
101
+
102
+ # Layer storage: layers[layer_name][source] = {target: properties}
103
+ self._layers: Dict[str, Dict[str, Dict[str, Dict[str, Any]]]] = defaultdict(
104
+ lambda: defaultdict(dict)
105
+ )
106
+
107
+ # Inter-layer edges (optional)
108
+ self._inter_layer_edges: Dict[Tuple[str, str, str], Dict[str, Any]] = {}
109
+
110
+ # Vertices
111
+ self._vertices: Set[str] = set()
112
+
113
+ # Per-layer edge counts
114
+ self._layer_edge_counts: Dict[str, int] = defaultdict(int)
115
+
116
+ # Initialize default layers
117
+ if default_layers:
118
+ for layer in default_layers:
119
+ self._layers[layer] = defaultdict(dict)
120
+
121
+ def get_supported_traits(self) -> EdgeTrait:
122
+ """Get supported traits."""
123
+ return EdgeTrait.MULTI | EdgeTrait.DIRECTED | EdgeTrait.SPARSE
124
+
125
+ # ============================================================================
126
+ # LAYER MANAGEMENT
127
+ # ============================================================================
128
+
129
+ def add_layer(self, layer_name: str) -> None:
130
+ """
131
+ Add new layer.
132
+
133
+ Args:
134
+ layer_name: Name of layer
135
+
136
+ Raises:
137
+ XWNodeValueError: If layer already exists
138
+ """
139
+ if layer_name in self._layers:
140
+ raise XWNodeValueError(f"Layer '{layer_name}' already exists")
141
+
142
+ self._layers[layer_name] = defaultdict(dict)
143
+
144
+ def remove_layer(self, layer_name: str) -> bool:
145
+ """
146
+ Remove layer and all its edges.
147
+
148
+ Args:
149
+ layer_name: Layer to remove
150
+
151
+ Returns:
152
+ True if removed
153
+ """
154
+ if layer_name not in self._layers:
155
+ return False
156
+
157
+ # Update edge count
158
+ self._edge_count -= self._layer_edge_counts[layer_name]
159
+
160
+ del self._layers[layer_name]
161
+ del self._layer_edge_counts[layer_name]
162
+
163
+ return True
164
+
165
+ def get_layers(self) -> List[str]:
166
+ """Get list of layer names."""
167
+ return list(self._layers.keys())
168
+
169
+ # ============================================================================
170
+ # EDGE OPERATIONS
171
+ # ============================================================================
172
+
173
+ def add_edge(self, source: str, target: str, edge_type: str = "default",
174
+ weight: float = 1.0, properties: Optional[Dict[str, Any]] = None,
175
+ is_bidirectional: bool = False, edge_id: Optional[str] = None) -> str:
176
+ """
177
+ Add edge to layer.
178
+
179
+ Args:
180
+ source: Source vertex
181
+ target: Target vertex
182
+ edge_type: Layer name
183
+ weight: Edge weight
184
+ properties: Edge properties
185
+ is_bidirectional: Bidirectional flag
186
+ edge_id: Edge ID
187
+
188
+ Returns:
189
+ Edge ID
190
+ """
191
+ layer = edge_type
192
+
193
+ # Ensure layer exists
194
+ if layer not in self._layers:
195
+ self._layers[layer] = defaultdict(dict)
196
+
197
+ # Add edge
198
+ edge_data = properties.copy() if properties else {}
199
+ edge_data['weight'] = weight
200
+
201
+ self._layers[layer][source][target] = edge_data
202
+
203
+ if is_bidirectional:
204
+ self._layers[layer][target][source] = edge_data.copy()
205
+
206
+ self._vertices.add(source)
207
+ self._vertices.add(target)
208
+
209
+ self._layer_edge_counts[layer] += 1
210
+ self._edge_count += 1
211
+
212
+ return edge_id or f"edge_{layer}_{source}_{target}"
213
+
214
+ def remove_edge(self, source: str, target: str, edge_id: Optional[str] = None) -> bool:
215
+ """
216
+ Remove edge from all layers.
217
+
218
+ Args:
219
+ source: Source vertex
220
+ target: Target vertex
221
+ edge_id: Edge ID (format: edge_LAYER_source_target)
222
+
223
+ Returns:
224
+ True if removed from any layer
225
+ """
226
+ removed = False
227
+
228
+ # If edge_id specifies layer, use it
229
+ if edge_id and edge_id.startswith("edge_"):
230
+ parts = edge_id.split("_")
231
+ if len(parts) >= 4:
232
+ layer = parts[1]
233
+ if layer in self._layers and source in self._layers[layer]:
234
+ if target in self._layers[layer][source]:
235
+ del self._layers[layer][source][target]
236
+ self._layer_edge_counts[layer] -= 1
237
+ self._edge_count -= 1
238
+ return True
239
+
240
+ # Otherwise remove from all layers
241
+ for layer in self._layers:
242
+ if source in self._layers[layer] and target in self._layers[layer][source]:
243
+ del self._layers[layer][source][target]
244
+ self._layer_edge_counts[layer] -= 1
245
+ self._edge_count -= 1
246
+ removed = True
247
+
248
+ return removed
249
+
250
+ def has_edge(self, source: str, target: str) -> bool:
251
+ """Check if edge exists in any layer."""
252
+ for layer in self._layers.values():
253
+ if source in layer and target in layer[source]:
254
+ return True
255
+ return False
256
+
257
+ def has_edge_in_layer(self, source: str, target: str, layer_name: str) -> bool:
258
+ """
259
+ Check if edge exists in specific layer.
260
+
261
+ Args:
262
+ source: Source vertex
263
+ target: Target vertex
264
+ layer_name: Layer name
265
+
266
+ Returns:
267
+ True if edge exists in layer
268
+ """
269
+ if layer_name not in self._layers:
270
+ return False
271
+
272
+ return source in self._layers[layer_name] and target in self._layers[layer_name][source]
273
+
274
+ def get_neighbors(self, node: str, edge_type: Optional[str] = None,
275
+ direction: str = "outgoing") -> List[str]:
276
+ """
277
+ Get neighbors from specific layer or all layers.
278
+
279
+ Args:
280
+ node: Vertex
281
+ edge_type: Layer name (None = all layers)
282
+ direction: Direction
283
+
284
+ Returns:
285
+ List of neighbors
286
+ """
287
+ neighbors = set()
288
+
289
+ if edge_type:
290
+ # Specific layer
291
+ if edge_type in self._layers and node in self._layers[edge_type]:
292
+ neighbors.update(self._layers[edge_type][node].keys())
293
+ else:
294
+ # All layers
295
+ for layer in self._layers.values():
296
+ if node in layer:
297
+ neighbors.update(layer[node].keys())
298
+
299
+ return list(neighbors)
300
+
301
+ def neighbors(self, node: str) -> Iterator[Any]:
302
+ """Get iterator over neighbors (all layers)."""
303
+ return iter(self.get_neighbors(node))
304
+
305
+ def degree(self, node: str) -> int:
306
+ """Get total degree across all layers."""
307
+ return len(self.get_neighbors(node))
308
+
309
+ def edges(self) -> Iterator[Tuple[Any, Any, Dict[str, Any]]]:
310
+ """Iterate over all edges with properties."""
311
+ for edge_dict in self.get_edges():
312
+ source = edge_dict['source']
313
+ target = edge_dict['target']
314
+ props = {k: v for k, v in edge_dict.items() if k not in ['source', 'target']}
315
+ yield (source, target, props)
316
+
317
+ def vertices(self) -> Iterator[Any]:
318
+ """Get iterator over all vertices."""
319
+ return iter(self._vertices)
320
+
321
+ def get_edges(self, edge_type: Optional[str] = None, direction: str = "both") -> List[Dict[str, Any]]:
322
+ """
323
+ Get edges from specific layer or all layers.
324
+
325
+ Args:
326
+ edge_type: Layer name (None = all layers)
327
+ direction: Direction
328
+
329
+ Returns:
330
+ List of edges with layer information
331
+ """
332
+ edges = []
333
+
334
+ layers_to_check = [edge_type] if edge_type else self._layers.keys()
335
+
336
+ for layer_name in layers_to_check:
337
+ if layer_name not in self._layers:
338
+ continue
339
+
340
+ for source, targets in self._layers[layer_name].items():
341
+ for target, edge_data in targets.items():
342
+ edges.append({
343
+ 'source': source,
344
+ 'target': target,
345
+ 'layer': layer_name,
346
+ 'edge_type': layer_name,
347
+ **edge_data
348
+ })
349
+
350
+ return edges
351
+
352
+ def get_edge_data(self, source: str, target: str, edge_id: Optional[str] = None) -> Optional[Dict[str, Any]]:
353
+ """Get edge data from all layers."""
354
+ for layer_name, layer in self._layers.items():
355
+ if source in layer and target in layer[source]:
356
+ return {
357
+ 'layer': layer_name,
358
+ **layer[source][target]
359
+ }
360
+ return None
361
+
362
+ # ============================================================================
363
+ # CROSS-LAYER OPERATIONS
364
+ # ============================================================================
365
+
366
+ def get_common_neighbors(self, node: str, layers: List[str]) -> Set[str]:
367
+ """
368
+ Get neighbors common across multiple layers.
369
+
370
+ Args:
371
+ node: Vertex
372
+ layers: List of layer names
373
+
374
+ Returns:
375
+ Set of vertices that are neighbors in ALL specified layers
376
+ """
377
+ if not layers:
378
+ return set()
379
+
380
+ # Start with first layer
381
+ common = set(self.get_neighbors(node, edge_type=layers[0]))
382
+
383
+ # Intersect with remaining layers
384
+ for layer in layers[1:]:
385
+ common &= set(self.get_neighbors(node, edge_type=layer))
386
+
387
+ return common
388
+
389
+ def get_layer_statistics(self, layer_name: str) -> Dict[str, Any]:
390
+ """
391
+ Get statistics for specific layer.
392
+
393
+ Args:
394
+ layer_name: Layer name
395
+
396
+ Returns:
397
+ Layer statistics
398
+ """
399
+ if layer_name not in self._layers:
400
+ return {}
401
+
402
+ layer = self._layers[layer_name]
403
+ degrees = [len(targets) for targets in layer.values()]
404
+
405
+ return {
406
+ 'layer': layer_name,
407
+ 'edges': self._layer_edge_counts[layer_name],
408
+ 'vertices_with_edges': len(layer),
409
+ 'avg_degree': sum(degrees) / max(len(degrees), 1),
410
+ 'max_degree': max(degrees) if degrees else 0
411
+ }
412
+
413
+ # ============================================================================
414
+ # GRAPH ALGORITHMS
415
+ # ============================================================================
416
+
417
+ def shortest_path(self, source: str, target: str, edge_type: Optional[str] = None) -> List[str]:
418
+ """Find shortest path in layer or aggregate."""
419
+ if source not in self._vertices or target not in self._vertices:
420
+ return []
421
+
422
+ queue = deque([source])
423
+ visited = {source}
424
+ parent = {source: None}
425
+
426
+ while queue:
427
+ current = queue.popleft()
428
+
429
+ if current == target:
430
+ path = []
431
+ while current:
432
+ path.append(current)
433
+ current = parent[current]
434
+ return list(reversed(path))
435
+
436
+ for neighbor in self.get_neighbors(current, edge_type=edge_type):
437
+ if neighbor not in visited:
438
+ visited.add(neighbor)
439
+ parent[neighbor] = current
440
+ queue.append(neighbor)
441
+
442
+ return []
443
+
444
+ def find_cycles(self, start_node: str, edge_type: Optional[str] = None, max_depth: int = 10) -> List[List[str]]:
445
+ """Find cycles."""
446
+ return []
447
+
448
+ def traverse_graph(self, start_node: str, strategy: str = "bfs",
449
+ max_depth: int = 100, edge_type: Optional[str] = None) -> Iterator[str]:
450
+ """Traverse graph."""
451
+ if start_node not in self._vertices:
452
+ return
453
+
454
+ visited = set()
455
+ queue = deque([start_node])
456
+ visited.add(start_node)
457
+
458
+ while queue:
459
+ current = queue.popleft()
460
+ yield current
461
+
462
+ for neighbor in self.get_neighbors(current, edge_type=edge_type):
463
+ if neighbor not in visited:
464
+ visited.add(neighbor)
465
+ queue.append(neighbor)
466
+
467
+ def is_connected(self, source: str, target: str, edge_type: Optional[str] = None) -> bool:
468
+ """Check if vertices connected in layer or aggregate."""
469
+ return len(self.shortest_path(source, target, edge_type)) > 0
470
+
471
+ # ============================================================================
472
+ # STANDARD OPERATIONS
473
+ # ============================================================================
474
+
475
+ def __len__(self) -> int:
476
+ """Get total number of edges across all layers."""
477
+ return self._edge_count
478
+
479
+ def __iter__(self) -> Iterator[Dict[str, Any]]:
480
+ """Iterate over edges from all layers."""
481
+ return iter(self.get_edges())
482
+
483
+ def to_native(self) -> Dict[str, Any]:
484
+ """Convert to native representation."""
485
+ return {
486
+ 'vertices': list(self._vertices),
487
+ 'layers': list(self._layers.keys()),
488
+ 'edges': self.get_edges()
489
+ }
490
+
491
+ # ============================================================================
492
+ # STATISTICS
493
+ # ============================================================================
494
+
495
+ def get_statistics(self) -> Dict[str, Any]:
496
+ """Get multiplex graph statistics."""
497
+ layer_stats = {
498
+ layer: self.get_layer_statistics(layer)
499
+ for layer in self._layers.keys()
500
+ }
501
+
502
+ return {
503
+ 'vertices': len(self._vertices),
504
+ 'total_edges': self._edge_count,
505
+ 'num_layers': len(self._layers),
506
+ 'layer_names': list(self._layers.keys()),
507
+ 'edges_per_layer': dict(self._layer_edge_counts),
508
+ 'layer_statistics': layer_stats
509
+ }
510
+
511
+ # ============================================================================
512
+ # UTILITY METHODS
513
+ # ============================================================================
514
+
515
+ @property
516
+ def strategy_name(self) -> str:
517
+ """Get strategy name."""
518
+ return "MULTIPLEX"
519
+
520
+ @property
521
+ def supported_traits(self) -> List[EdgeTrait]:
522
+ """Get supported traits."""
523
+ return [EdgeTrait.MULTI, EdgeTrait.DIRECTED, EdgeTrait.SPARSE]
524
+
525
+ def get_backend_info(self) -> Dict[str, Any]:
526
+ """Get backend information."""
527
+ return {
528
+ 'strategy': 'Multiplex/Layered Edges',
529
+ 'description': 'Multi-layer graph with per-layer semantics',
530
+ **self.get_statistics()
531
+ }
532
+
@@ -9,7 +9,7 @@ from typing import Any, Iterator, Dict, List, Set, Optional, Tuple, Callable, Un
9
9
  from collections import defaultdict, deque
10
10
  import math
11
11
  from enum import Enum
12
- from ._base_edge import aEdgeStrategy
12
+ from ._base_edge import AEdgeStrategy
13
13
  from ...defs import EdgeMode, EdgeTrait
14
14
 
15
15
 
@@ -217,7 +217,7 @@ class NeuralNode:
217
217
  return self.gradient
218
218
 
219
219
 
220
- class xNeuralGraphStrategy(aEdgeStrategy):
220
+ class NeuralGraphStrategy(AEdgeStrategy):
221
221
  """
222
222
  Neural Graph strategy for neural network computation graphs.
223
223
 
@@ -8,7 +8,7 @@ graph partitioning and efficient 3D spatial queries.
8
8
  from typing import Any, Iterator, List, Dict, Set, Optional, Tuple
9
9
  from collections import defaultdict
10
10
  import math
11
- from ._base_edge import aEdgeStrategy
11
+ from ._base_edge import AEdgeStrategy
12
12
  from ...defs import EdgeMode, EdgeTrait
13
13
 
14
14
 
@@ -179,12 +179,53 @@ class OctreeNode:
179
179
  return False
180
180
 
181
181
 
182
- class xOctreeStrategy(aEdgeStrategy):
182
+ class OctreeStrategy(AEdgeStrategy):
183
183
  """
184
184
  Octree edge strategy for 3D spatial graphs.
185
185
 
186
- Efficiently manages 3D spatial relationships with logarithmic
187
- complexity for 3D spatial queries and nearest neighbor searches.
186
+ WHY this strategy:
187
+ - 3D space requires volumetric partitioning (physics, graphics, robotics)
188
+ - 8-way octant subdivision balances 3D space naturally
189
+ - Critical for 3D collision detection, visibility determination
190
+ - Extends quadtree to handle depth dimension
191
+
192
+ WHY this implementation:
193
+ - 8-child octant structure (+++, ++-, +-+, etc.)
194
+ - Recursive subdivision when capacity exceeded
195
+ - Sphere-box intersection for efficient radius queries
196
+ - 3D coordinate point storage with edge references
197
+
198
+ Time Complexity:
199
+ - Add Vertex: O(log N) average for balanced 3D tree
200
+ - Box Query: O(log N + K) where K = results
201
+ - Sphere Query: O(log N + K) with sphere-box tests
202
+ - Subdivision: O(capacity) to redistribute in 3D
203
+
204
+ Space Complexity: O(N) for N vertices
205
+
206
+ Trade-offs:
207
+ - Advantage: Natural 3D partitioning, self-balancing
208
+ - Limitation: 8x branching factor higher overhead than quadtree
209
+ - Compared to QUADTREE: Use for 3D data
210
+
211
+ Best for:
212
+ - 3D graphics (frustum culling, ray tracing, LOD)
213
+ - Physics engines (collision detection, spatial hashing)
214
+ - Robotics (3D pathfinding, obstacle maps, SLAM)
215
+ - Medical imaging (volumetric data, 3D reconstruction)
216
+ - Point cloud processing (LiDAR, photogrammetry)
217
+
218
+ Not recommended for:
219
+ - 2D data - use QUADTREE instead
220
+ - Non-spatial graphs
221
+ - Memory-constrained systems - 8 children per node
222
+
223
+ Following eXonware Priorities:
224
+ 1. Security: 3D bounds validation, coordinate overflow prevention
225
+ 2. Usability: Intuitive octant-based 3D API
226
+ 3. Maintainability: Clean recursive 3D extension of quadtree
227
+ 4. Performance: O(log N) for well-distributed 3D data
228
+ 5. Extensibility: Supports LOD, voxelization, GPU acceleration
188
229
  """
189
230
 
190
231
  def __init__(self, traits: EdgeTrait = EdgeTrait.NONE, **options):
@@ -244,18 +285,35 @@ class xOctreeStrategy(aEdgeStrategy):
244
285
  # ============================================================================
245
286
 
246
287
  def add_edge(self, source: str, target: str, **properties) -> str:
247
- """Add edge between 3D spatial vertices."""
288
+ """
289
+ Add edge between 3D spatial vertices.
290
+
291
+ Root cause fixed: Coordinates can be passed as tuples (source_coords, target_coords)
292
+ or individual values (source_x, source_y, source_z, target_x, target_y, target_z).
293
+
294
+ Priority: Usability #2 - Flexible coordinate input API
295
+ """
248
296
  # Ensure vertices exist with positions
249
297
  if source not in self._vertices:
250
- x = properties.get('source_x', 0.0)
251
- y = properties.get('source_y', 0.0)
252
- z = properties.get('source_z', 0.0)
298
+ # Extract coordinates from tuple or individual properties
299
+ source_coords = properties.get('source_coords')
300
+ if source_coords:
301
+ x, y, z = source_coords[0], source_coords[1], source_coords[2]
302
+ else:
303
+ x = properties.get('source_x', 0.0)
304
+ y = properties.get('source_y', 0.0)
305
+ z = properties.get('source_z', 0.0)
253
306
  self.add_spatial_vertex(source, x, y, z)
254
307
 
255
308
  if target not in self._vertices:
256
- x = properties.get('target_x', 0.0)
257
- y = properties.get('target_y', 0.0)
258
- z = properties.get('target_z', 0.0)
309
+ # Extract coordinates from tuple or individual properties
310
+ target_coords = properties.get('target_coords')
311
+ if target_coords:
312
+ x, y, z = target_coords[0], target_coords[1], target_coords[2]
313
+ else:
314
+ x = properties.get('target_x', 0.0)
315
+ y = properties.get('target_y', 0.0)
316
+ z = properties.get('target_z', 0.0)
259
317
  self.add_spatial_vertex(target, x, y, z)
260
318
 
261
319
  # Calculate 3D distance