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
@@ -1,445 +0,0 @@
1
- """
2
- Adjacency Matrix Edge Strategy Implementation
3
-
4
- This module implements the ADJ_MATRIX strategy for dense graph representation
5
- with O(1) edge operations and efficient matrix-based algorithms.
6
- """
7
-
8
- from typing import Any, Iterator, Dict, List, Set, Optional, Tuple, Union
9
- from ._base_edge import aEdgeStrategy
10
- from ...defs import EdgeMode, EdgeTrait
11
-
12
-
13
- class xAdjMatrixStrategy(aEdgeStrategy):
14
- """
15
- Adjacency Matrix edge strategy for dense graph representation.
16
-
17
- Provides O(1) edge operations and efficient matrix-based graph algorithms,
18
- ideal for dense graphs where most vertices are connected.
19
- """
20
-
21
- def __init__(self, traits: EdgeTrait = EdgeTrait.NONE, **options):
22
- """Initialize the Adjacency Matrix strategy."""
23
- super().__init__(EdgeMode.ADJ_MATRIX, traits, **options)
24
-
25
- self.is_directed = options.get('directed', True)
26
- self.initial_capacity = options.get('initial_capacity', 100)
27
- self.allow_self_loops = options.get('self_loops', True)
28
- self.default_weight = options.get('default_weight', 1.0)
29
-
30
- # Core storage: 2D matrix of edge weights/properties
31
- self._matrix: List[List[Optional[Dict[str, Any]]]] = []
32
- self._capacity = 0
33
-
34
- # Vertex management
35
- self._vertex_to_index: Dict[str, int] = {}
36
- self._index_to_vertex: Dict[int, str] = {}
37
- self._vertex_count = 0
38
- self._edge_count = 0
39
- self._edge_id_counter = 0
40
-
41
- # Initialize matrix
42
- self._resize_matrix(self.initial_capacity)
43
-
44
- def get_supported_traits(self) -> EdgeTrait:
45
- """Get the traits supported by the adjacency matrix strategy."""
46
- return (EdgeTrait.DENSE | EdgeTrait.DIRECTED | EdgeTrait.WEIGHTED | EdgeTrait.CACHE_FRIENDLY)
47
-
48
- # ============================================================================
49
- # MATRIX MANAGEMENT
50
- # ============================================================================
51
-
52
- def _resize_matrix(self, new_capacity: int) -> None:
53
- """Resize the adjacency matrix to accommodate more vertices."""
54
- old_capacity = self._capacity
55
- self._capacity = new_capacity
56
-
57
- # Expand existing rows
58
- for row in self._matrix:
59
- row.extend([None] * (new_capacity - old_capacity))
60
-
61
- # Add new rows
62
- for _ in range(old_capacity, new_capacity):
63
- self._matrix.append([None] * new_capacity)
64
-
65
- def _get_vertex_index(self, vertex: str) -> int:
66
- """Get or create index for vertex."""
67
- if vertex in self._vertex_to_index:
68
- return self._vertex_to_index[vertex]
69
-
70
- # Need to add new vertex
71
- if self._vertex_count >= self._capacity:
72
- self._resize_matrix(self._capacity * 2)
73
-
74
- index = self._vertex_count
75
- self._vertex_to_index[vertex] = index
76
- self._index_to_vertex[index] = vertex
77
- self._vertex_count += 1
78
-
79
- return index
80
-
81
- def _get_vertex_by_index(self, index: int) -> Optional[str]:
82
- """Get vertex name by index."""
83
- return self._index_to_vertex.get(index)
84
-
85
- # ============================================================================
86
- # CORE EDGE OPERATIONS
87
- # ============================================================================
88
-
89
- def add_edge(self, source: str, target: str, **properties) -> str:
90
- """Add an edge between source and target vertices."""
91
- # Validate self-loops
92
- if source == target and not self.allow_self_loops:
93
- raise ValueError(f"Self-loops not allowed: {source} -> {target}")
94
-
95
- # Get vertex indices
96
- source_idx = self._get_vertex_index(source)
97
- target_idx = self._get_vertex_index(target)
98
-
99
- # Generate edge ID
100
- edge_id = f"edge_{self._edge_id_counter}"
101
- self._edge_id_counter += 1
102
-
103
- # Create edge data
104
- edge_data = {
105
- 'id': edge_id,
106
- 'source': source,
107
- 'target': target,
108
- 'weight': properties.get('weight', self.default_weight),
109
- 'properties': properties.copy()
110
- }
111
-
112
- # Check if edge already exists
113
- if self._matrix[source_idx][target_idx] is not None:
114
- # Update existing edge
115
- self._matrix[source_idx][target_idx] = edge_data
116
- else:
117
- # Add new edge
118
- self._matrix[source_idx][target_idx] = edge_data
119
- self._edge_count += 1
120
-
121
- # For undirected graphs, add symmetric edge
122
- if not self.is_directed and source != target:
123
- if self._matrix[target_idx][source_idx] is None:
124
- self._matrix[target_idx][source_idx] = edge_data
125
- # Don't increment edge count for undirected (it's the same edge)
126
-
127
- return edge_id
128
-
129
- def remove_edge(self, source: str, target: str, edge_id: Optional[str] = None) -> bool:
130
- """Remove edge between source and target."""
131
- if source not in self._vertex_to_index or target not in self._vertex_to_index:
132
- return False
133
-
134
- source_idx = self._vertex_to_index[source]
135
- target_idx = self._vertex_to_index[target]
136
-
137
- # Check if edge exists
138
- if self._matrix[source_idx][target_idx] is None:
139
- return False
140
-
141
- # If edge_id specified, verify it matches
142
- if edge_id and self._matrix[source_idx][target_idx]['id'] != edge_id:
143
- return False
144
-
145
- # Remove edge
146
- self._matrix[source_idx][target_idx] = None
147
- self._edge_count -= 1
148
-
149
- # For undirected graphs, remove symmetric edge
150
- if not self.is_directed and source != target:
151
- self._matrix[target_idx][source_idx] = None
152
-
153
- return True
154
-
155
- def has_edge(self, source: str, target: str) -> bool:
156
- """Check if edge exists between source and target."""
157
- if source not in self._vertex_to_index or target not in self._vertex_to_index:
158
- return False
159
-
160
- source_idx = self._vertex_to_index[source]
161
- target_idx = self._vertex_to_index[target]
162
-
163
- return self._matrix[source_idx][target_idx] is not None
164
-
165
- def get_edge_data(self, source: str, target: str) -> Optional[Dict[str, Any]]:
166
- """Get edge data between source and target."""
167
- if source not in self._vertex_to_index or target not in self._vertex_to_index:
168
- return None
169
-
170
- source_idx = self._vertex_to_index[source]
171
- target_idx = self._vertex_to_index[target]
172
-
173
- return self._matrix[source_idx][target_idx]
174
-
175
- def get_edge_weight(self, source: str, target: str) -> Optional[float]:
176
- """Get edge weight between source and target."""
177
- edge_data = self.get_edge_data(source, target)
178
- return edge_data['weight'] if edge_data else None
179
-
180
- def neighbors(self, vertex: str, direction: str = 'out') -> Iterator[str]:
181
- """Get neighbors of a vertex."""
182
- if vertex not in self._vertex_to_index:
183
- return
184
-
185
- vertex_idx = self._vertex_to_index[vertex]
186
-
187
- if direction == 'out':
188
- # Outgoing neighbors (columns)
189
- for target_idx in range(self._vertex_count):
190
- if self._matrix[vertex_idx][target_idx] is not None:
191
- yield self._index_to_vertex[target_idx]
192
- elif direction == 'in':
193
- # Incoming neighbors (rows)
194
- for source_idx in range(self._vertex_count):
195
- if self._matrix[source_idx][vertex_idx] is not None:
196
- yield self._index_to_vertex[source_idx]
197
- elif direction == 'both':
198
- # All neighbors
199
- seen = set()
200
- for neighbor in self.neighbors(vertex, 'out'):
201
- if neighbor not in seen:
202
- seen.add(neighbor)
203
- yield neighbor
204
- for neighbor in self.neighbors(vertex, 'in'):
205
- if neighbor not in seen:
206
- seen.add(neighbor)
207
- yield neighbor
208
-
209
- def degree(self, vertex: str, direction: str = 'out') -> int:
210
- """Get degree of a vertex."""
211
- if vertex not in self._vertex_to_index:
212
- return 0
213
-
214
- vertex_idx = self._vertex_to_index[vertex]
215
-
216
- if direction == 'out':
217
- # Count non-None entries in row
218
- return sum(1 for target_idx in range(self._vertex_count)
219
- if self._matrix[vertex_idx][target_idx] is not None)
220
- elif direction == 'in':
221
- # Count non-None entries in column
222
- return sum(1 for source_idx in range(self._vertex_count)
223
- if self._matrix[source_idx][vertex_idx] is not None)
224
- elif direction == 'both':
225
- out_degree = self.degree(vertex, 'out')
226
- in_degree = self.degree(vertex, 'in')
227
- # For undirected graphs, avoid double counting
228
- return out_degree if not self.is_directed else out_degree + in_degree
229
-
230
- def edges(self, data: bool = False) -> Iterator[tuple]:
231
- """Get all edges in the graph."""
232
- for source_idx in range(self._vertex_count):
233
- for target_idx in range(self._vertex_count):
234
- edge_data = self._matrix[source_idx][target_idx]
235
- if edge_data is not None:
236
- source = self._index_to_vertex[source_idx]
237
- target = self._index_to_vertex[target_idx]
238
-
239
- # For undirected graphs, avoid returning duplicate edges
240
- if not self.is_directed and source > target:
241
- continue
242
-
243
- if data:
244
- yield (source, target, edge_data)
245
- else:
246
- yield (source, target)
247
-
248
- def vertices(self) -> Iterator[str]:
249
- """Get all vertices in the graph."""
250
- for vertex in self._vertex_to_index.keys():
251
- yield vertex
252
-
253
- def __len__(self) -> int:
254
- """Get the number of edges."""
255
- return self._edge_count
256
-
257
- def vertex_count(self) -> int:
258
- """Get the number of vertices."""
259
- return self._vertex_count
260
-
261
- def clear(self) -> None:
262
- """Clear all edges and vertices."""
263
- # Reset matrix
264
- for row in self._matrix:
265
- for i in range(len(row)):
266
- row[i] = None
267
-
268
- # Reset mappings
269
- self._vertex_to_index.clear()
270
- self._index_to_vertex.clear()
271
- self._vertex_count = 0
272
- self._edge_count = 0
273
- self._edge_id_counter = 0
274
-
275
- def add_vertex(self, vertex: str) -> None:
276
- """Add a vertex to the graph."""
277
- if vertex not in self._vertex_to_index:
278
- self._get_vertex_index(vertex)
279
-
280
- def remove_vertex(self, vertex: str) -> bool:
281
- """Remove a vertex and all its edges."""
282
- if vertex not in self._vertex_to_index:
283
- return False
284
-
285
- vertex_idx = self._vertex_to_index[vertex]
286
-
287
- # Count and remove all edges involving this vertex
288
- edges_removed = 0
289
-
290
- # Remove outgoing edges (row)
291
- for target_idx in range(self._vertex_count):
292
- if self._matrix[vertex_idx][target_idx] is not None:
293
- self._matrix[vertex_idx][target_idx] = None
294
- edges_removed += 1
295
-
296
- # Remove incoming edges (column)
297
- for source_idx in range(self._vertex_count):
298
- if source_idx != vertex_idx and self._matrix[source_idx][vertex_idx] is not None:
299
- self._matrix[source_idx][vertex_idx] = None
300
- edges_removed += 1
301
-
302
- self._edge_count -= edges_removed
303
-
304
- # Note: We don't actually remove the vertex from the matrix to avoid
305
- # reindexing all other vertices. Instead, we just mark it as removed.
306
- del self._vertex_to_index[vertex]
307
- del self._index_to_vertex[vertex_idx]
308
-
309
- return True
310
-
311
- # ============================================================================
312
- # MATRIX-SPECIFIC OPERATIONS
313
- # ============================================================================
314
-
315
- def get_matrix(self) -> List[List[Optional[float]]]:
316
- """Get the adjacency matrix as weights."""
317
- matrix = []
318
- for source_idx in range(self._vertex_count):
319
- row = []
320
- for target_idx in range(self._vertex_count):
321
- edge_data = self._matrix[source_idx][target_idx]
322
- weight = edge_data['weight'] if edge_data else None
323
- row.append(weight)
324
- matrix.append(row)
325
- return matrix
326
-
327
- def get_binary_matrix(self) -> List[List[int]]:
328
- """Get the adjacency matrix as binary (0/1)."""
329
- matrix = []
330
- for source_idx in range(self._vertex_count):
331
- row = []
332
- for target_idx in range(self._vertex_count):
333
- edge_exists = self._matrix[source_idx][target_idx] is not None
334
- row.append(1 if edge_exists else 0)
335
- matrix.append(row)
336
- return matrix
337
-
338
- def set_matrix(self, matrix: List[List[Union[float, int, None]]], vertices: List[str]) -> None:
339
- """Set the entire matrix from a weight matrix."""
340
- if len(matrix) != len(vertices) or any(len(row) != len(vertices) for row in matrix):
341
- raise ValueError("Matrix dimensions must match vertex count")
342
-
343
- # Clear existing data
344
- self.clear()
345
-
346
- # Add vertices
347
- for vertex in vertices:
348
- self.add_vertex(vertex)
349
-
350
- # Add edges based on matrix
351
- for i, source in enumerate(vertices):
352
- for j, target in enumerate(vertices):
353
- weight = matrix[i][j]
354
- if weight is not None and weight != 0:
355
- self.add_edge(source, target, weight=weight)
356
-
357
- def matrix_multiply(self, other: 'xAdjMatrixStrategy') -> 'xAdjMatrixStrategy':
358
- """Multiply this matrix with another adjacency matrix."""
359
- if self._vertex_count != other._vertex_count:
360
- raise ValueError("Matrices must have same dimensions")
361
-
362
- result = xAdjMatrixStrategy(
363
- traits=self._traits,
364
- directed=self.is_directed,
365
- initial_capacity=self._vertex_count
366
- )
367
-
368
- # Add vertices
369
- for vertex in self.vertices():
370
- result.add_vertex(vertex)
371
-
372
- # Perform matrix multiplication
373
- vertices = list(self.vertices())
374
- for i, source in enumerate(vertices):
375
- for j, target in enumerate(vertices):
376
- sum_value = 0
377
- for k in range(self._vertex_count):
378
- intermediate = self._index_to_vertex[k]
379
-
380
- weight1 = self.get_edge_weight(source, intermediate)
381
- weight2 = other.get_edge_weight(intermediate, target)
382
-
383
- if weight1 is not None and weight2 is not None:
384
- sum_value += weight1 * weight2
385
-
386
- if sum_value != 0:
387
- result.add_edge(source, target, weight=sum_value)
388
-
389
- return result
390
-
391
- def transpose(self) -> 'xAdjMatrixStrategy':
392
- """Get the transpose of this matrix."""
393
- result = xAdjMatrixStrategy(
394
- traits=self._traits,
395
- directed=True, # Transpose is always directed
396
- initial_capacity=self._vertex_count
397
- )
398
-
399
- # Add vertices
400
- for vertex in self.vertices():
401
- result.add_vertex(vertex)
402
-
403
- # Add transposed edges
404
- for source, target, edge_data in self.edges(data=True):
405
- result.add_edge(target, source, **edge_data['properties'])
406
-
407
- return result
408
-
409
- # ============================================================================
410
- # PERFORMANCE CHARACTERISTICS
411
- # ============================================================================
412
-
413
- @property
414
- def backend_info(self) -> Dict[str, Any]:
415
- """Get backend implementation info."""
416
- return {
417
- 'strategy': 'ADJ_MATRIX',
418
- 'backend': 'Python 2D list matrix',
419
- 'directed': self.is_directed,
420
- 'capacity': self._capacity,
421
- 'utilized': self._vertex_count,
422
- 'complexity': {
423
- 'add_edge': 'O(1)',
424
- 'remove_edge': 'O(1)',
425
- 'has_edge': 'O(1)',
426
- 'neighbors': 'O(V)',
427
- 'space': 'O(V²)'
428
- }
429
- }
430
-
431
- @property
432
- def metrics(self) -> Dict[str, Any]:
433
- """Get performance metrics."""
434
- density = self._edge_count / max(1, self._vertex_count * (self._vertex_count - 1)) if self._vertex_count > 1 else 0
435
- memory_utilization = self._vertex_count / max(1, self._capacity) * 100
436
-
437
- return {
438
- 'vertices': self._vertex_count,
439
- 'edges': self._edge_count,
440
- 'matrix_capacity': self._capacity,
441
- 'memory_utilization': f"{memory_utilization:.1f}%",
442
- 'density': round(density, 4),
443
- 'memory_usage': f"{self._capacity * self._capacity * 8} bytes (estimated)",
444
- 'sparsity': f"{(1 - density) * 100:.1f}%"
445
- }