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
@@ -6,23 +6,61 @@ with O(1) edge operations and efficient matrix-based algorithms.
6
6
  """
7
7
 
8
8
  from typing import Any, Iterator, Dict, List, Set, Optional, Tuple, Union
9
- from .base import AGraphEdgeStrategy
9
+ from ._base_edge import AEdgeStrategy
10
10
  from ...defs import EdgeMode, EdgeTrait
11
11
 
12
12
 
13
- class AdjMatrixStrategy(AGraphEdgeStrategy):
13
+ class AdjMatrixStrategy(AEdgeStrategy):
14
14
  """
15
15
  Adjacency Matrix edge strategy for dense graph representation.
16
16
 
17
- Provides O(1) edge operations and efficient matrix-based graph algorithms,
18
- ideal for dense graphs where most vertices are connected.
17
+ WHY this strategy:
18
+ - O(1) edge lookup - fastest possible for connectivity checks
19
+ - Matrix multiplication enables powerful graph algorithms (transitive closure, shortest paths)
20
+ - Cache-friendly for dense graphs (sequential memory access)
21
+ - Simplest implementation for complete/near-complete graphs
22
+
23
+ WHY this implementation:
24
+ - 2D list-of-lists for dynamic resizing
25
+ - Auto-expanding capacity for growing graphs
26
+ - Direct indexing [i][j] provides O(1) access
27
+ - Stores full edge data (not just boolean) for weighted graphs
28
+
29
+ Time Complexity:
30
+ - Add Edge: O(1) - direct array assignment
31
+ - Has Edge: O(1) - direct array lookup
32
+ - Get Neighbors: O(V) - scan entire row
33
+ - Delete Edge: O(1) - direct array assignment
34
+
35
+ Space Complexity: O(V²) - stores all possible edges
36
+
37
+ Trade-offs:
38
+ - Advantage: Fastest edge lookups, simplest for dense graphs
39
+ - Limitation: Wastes memory on sparse graphs (stores empty cells)
40
+ - Compared to ADJ_LIST: Use when graph density > 50%
41
+
42
+ Best for:
43
+ - Small complete graphs (cliques, fully connected layers)
44
+ - Dense graphs (social groups, recommendation systems)
45
+ - Matrix-based algorithms (Floyd-Warshall, matrix powers)
46
+ - Graphs where |E| ≈ |V|² (not |E| ≈ |V|)
47
+
48
+ Not recommended for:
49
+ - Sparse graphs (<10% density) - use ADJ_LIST
50
+ - Very large graphs (>10K vertices) - O(V²) memory prohibitive
51
+ - Highly dynamic graphs - wasted space on deleted edges
52
+
53
+ Following eXonware Priorities:
54
+ 1. Security: Bounds checking on all array accesses
55
+ 2. Usability: Intuitive matrix[i][j] access pattern
56
+ 3. Maintainability: Simple 2D array, minimal logic
57
+ 4. Performance: O(1) operations, cache-friendly for dense
58
+ 5. Extensibility: Easy to add matrix operations, algorithms
19
59
  """
20
60
 
21
61
  def __init__(self, traits: EdgeTrait = EdgeTrait.NONE, **options):
22
62
  """Initialize the Adjacency Matrix strategy."""
23
- super().__init__(**options)
24
- self._mode = EdgeMode.ADJ_MATRIX
25
- self._traits = traits
63
+ super().__init__(EdgeMode.ADJ_MATRIX, traits, **options)
26
64
 
27
65
  self.is_directed = options.get('directed', True)
28
66
  self.initial_capacity = options.get('initial_capacity', 100)
@@ -52,107 +90,110 @@ class AdjMatrixStrategy(AGraphEdgeStrategy):
52
90
  # ============================================================================
53
91
 
54
92
  def _resize_matrix(self, new_capacity: int) -> None:
55
- """Resize the adjacency matrix to new capacity."""
93
+ """Resize the adjacency matrix to accommodate more vertices."""
56
94
  old_capacity = self._capacity
57
95
  self._capacity = new_capacity
58
96
 
59
- # Create new matrix
60
- new_matrix = [[None for _ in range(self._capacity)] for _ in range(self._capacity)]
97
+ # Expand existing rows
98
+ for row in self._matrix:
99
+ row.extend([None] * (new_capacity - old_capacity))
61
100
 
62
- # Copy existing data
63
- for i in range(min(old_capacity, self._capacity)):
64
- for j in range(min(old_capacity, self._capacity)):
65
- new_matrix[i][j] = self._matrix[i][j]
66
-
67
- self._matrix = new_matrix
101
+ # Add new rows
102
+ for _ in range(old_capacity, new_capacity):
103
+ self._matrix.append([None] * new_capacity)
68
104
 
69
105
  def _get_vertex_index(self, vertex: str) -> int:
70
106
  """Get or create index for vertex."""
71
- if vertex not in self._vertex_to_index:
72
- if self._vertex_count >= self._capacity:
73
- self._resize_matrix(self._capacity * 2)
74
-
75
- index = self._vertex_count
76
- self._vertex_to_index[vertex] = index
77
- self._index_to_vertex[index] = vertex
78
- self._vertex_count += 1
79
- return index
107
+ if vertex in self._vertex_to_index:
108
+ return self._vertex_to_index[vertex]
109
+
110
+ # Need to add new vertex
111
+ if self._vertex_count >= self._capacity:
112
+ self._resize_matrix(self._capacity * 2)
113
+
114
+ index = self._vertex_count
115
+ self._vertex_to_index[vertex] = index
116
+ self._index_to_vertex[index] = vertex
117
+ self._vertex_count += 1
80
118
 
81
- return self._vertex_to_index[vertex]
119
+ return index
82
120
 
83
121
  def _get_vertex_by_index(self, index: int) -> Optional[str]:
84
- """Get vertex by index."""
122
+ """Get vertex name by index."""
85
123
  return self._index_to_vertex.get(index)
86
124
 
87
125
  # ============================================================================
88
126
  # CORE EDGE OPERATIONS
89
127
  # ============================================================================
90
128
 
91
- def add_edge(self, from_node: Any, to_node: Any, **kwargs) -> None:
129
+ def add_edge(self, source: str, target: str, **properties) -> str:
92
130
  """Add an edge between source and target vertices."""
93
- source = str(from_node)
94
- target = str(to_node)
95
-
96
- # Validation
97
- if not self.allow_self_loops and source == target:
98
- raise ValueError("Self loops not allowed")
131
+ # Validate self-loops
132
+ if source == target and not self.allow_self_loops:
133
+ raise ValueError(f"Self-loops not allowed: {source} -> {target}")
99
134
 
100
135
  # Get vertex indices
101
136
  source_idx = self._get_vertex_index(source)
102
137
  target_idx = self._get_vertex_index(target)
103
138
 
139
+ # Generate edge ID
140
+ edge_id = f"edge_{self._edge_id_counter}"
141
+ self._edge_id_counter += 1
142
+
104
143
  # Create edge data
105
144
  edge_data = {
106
- 'weight': kwargs.get('weight', self.default_weight),
107
- 'id': f"edge_{self._edge_id_counter}",
108
- **kwargs
145
+ 'id': edge_id,
146
+ 'source': source,
147
+ 'target': target,
148
+ 'weight': properties.get('weight', self.default_weight),
149
+ 'properties': properties.copy()
109
150
  }
110
- self._edge_id_counter += 1
111
151
 
112
- # Add edge to matrix
113
- if self._matrix[source_idx][target_idx] is None:
152
+ # Check if edge already exists
153
+ if self._matrix[source_idx][target_idx] is not None:
154
+ # Update existing edge
155
+ self._matrix[source_idx][target_idx] = edge_data
156
+ else:
157
+ # Add new edge
158
+ self._matrix[source_idx][target_idx] = edge_data
114
159
  self._edge_count += 1
115
160
 
116
- self._matrix[source_idx][target_idx] = edge_data
117
-
118
- # Add reverse edge if undirected
161
+ # For undirected graphs, add symmetric edge
119
162
  if not self.is_directed and source != target:
120
163
  if self._matrix[target_idx][source_idx] is None:
121
- self._edge_count += 1
122
- self._matrix[target_idx][source_idx] = edge_data
164
+ self._matrix[target_idx][source_idx] = edge_data
165
+ # Don't increment edge count for undirected (it's the same edge)
123
166
 
124
- def remove_edge(self, from_node: Any, to_node: Any) -> bool:
125
- """Remove edge between vertices."""
126
- source = str(from_node)
127
- target = str(to_node)
128
-
167
+ return edge_id
168
+
169
+ def remove_edge(self, source: str, target: str, edge_id: Optional[str] = None) -> bool:
170
+ """Remove edge between source and target."""
129
171
  if source not in self._vertex_to_index or target not in self._vertex_to_index:
130
172
  return False
131
173
 
132
174
  source_idx = self._vertex_to_index[source]
133
175
  target_idx = self._vertex_to_index[target]
134
176
 
135
- removed = False
177
+ # Check if edge exists
178
+ if self._matrix[source_idx][target_idx] is None:
179
+ return False
180
+
181
+ # If edge_id specified, verify it matches
182
+ if edge_id and self._matrix[source_idx][target_idx]['id'] != edge_id:
183
+ return False
136
184
 
137
- # Remove edge from matrix
138
- if self._matrix[source_idx][target_idx] is not None:
139
- self._matrix[source_idx][target_idx] = None
140
- self._edge_count -= 1
141
- removed = True
185
+ # Remove edge
186
+ self._matrix[source_idx][target_idx] = None
187
+ self._edge_count -= 1
142
188
 
143
- # Remove reverse edge if undirected
189
+ # For undirected graphs, remove symmetric edge
144
190
  if not self.is_directed and source != target:
145
- if self._matrix[target_idx][source_idx] is not None:
146
- self._matrix[target_idx][source_idx] = None
147
- self._edge_count -= 1
191
+ self._matrix[target_idx][source_idx] = None
148
192
 
149
- return removed
193
+ return True
150
194
 
151
- def has_edge(self, from_node: Any, to_node: Any) -> bool:
152
- """Check if edge exists."""
153
- source = str(from_node)
154
- target = str(to_node)
155
-
195
+ def has_edge(self, source: str, target: str) -> bool:
196
+ """Check if edge exists between source and target."""
156
197
  if source not in self._vertex_to_index or target not in self._vertex_to_index:
157
198
  return False
158
199
 
@@ -161,204 +202,249 @@ class AdjMatrixStrategy(AGraphEdgeStrategy):
161
202
 
162
203
  return self._matrix[source_idx][target_idx] is not None
163
204
 
164
- def get_edge_count(self) -> int:
165
- """Get total number of edges."""
166
- return self._edge_count
167
-
168
- def get_vertex_count(self) -> int:
169
- """Get total number of vertices."""
170
- return self._vertex_count
171
-
172
- # ============================================================================
173
- # GRAPH EDGE STRATEGY METHODS
174
- # ============================================================================
175
-
176
- def get_neighbors(self, node: Any) -> List[Any]:
177
- """Get all neighboring nodes."""
178
- vertex = str(node)
179
- if vertex not in self._vertex_to_index:
180
- return []
181
-
182
- vertex_idx = self._vertex_to_index[vertex]
183
- neighbors = []
184
-
185
- for i in range(self._capacity):
186
- if self._matrix[vertex_idx][i] is not None:
187
- neighbor = self._index_to_vertex.get(i)
188
- if neighbor:
189
- neighbors.append(neighbor)
190
-
191
- return neighbors
192
-
193
- def get_edge_weight(self, from_node: Any, to_node: Any) -> float:
194
- """Get edge weight."""
195
- source = str(from_node)
196
- target = str(to_node)
197
-
205
+ def get_edge_data(self, source: str, target: str) -> Optional[Dict[str, Any]]:
206
+ """Get edge data between source and target."""
198
207
  if source not in self._vertex_to_index or target not in self._vertex_to_index:
199
- return 0.0
208
+ return None
200
209
 
201
210
  source_idx = self._vertex_to_index[source]
202
211
  target_idx = self._vertex_to_index[target]
203
212
 
204
- edge_data = self._matrix[source_idx][target_idx]
205
- return edge_data.get('weight', self.default_weight) if edge_data else 0.0
213
+ return self._matrix[source_idx][target_idx]
214
+
215
+ def get_edge_weight(self, source: str, target: str) -> Optional[float]:
216
+ """Get edge weight between source and target."""
217
+ edge_data = self.get_edge_data(source, target)
218
+ return edge_data['weight'] if edge_data else None
206
219
 
207
- def set_edge_weight(self, from_node: Any, to_node: Any, weight: float) -> None:
208
- """Set edge weight."""
209
- source = str(from_node)
210
- target = str(to_node)
220
+ def neighbors(self, vertex: str, direction: str = 'out') -> Iterator[str]:
221
+ """Get neighbors of a vertex."""
222
+ if vertex not in self._vertex_to_index:
223
+ return
211
224
 
212
- if source not in self._vertex_to_index or target not in self._vertex_to_index:
213
- raise ValueError(f"Edge {source} -> {target} not found")
225
+ vertex_idx = self._vertex_to_index[vertex]
214
226
 
215
- source_idx = self._vertex_to_index[source]
216
- target_idx = self._vertex_to_index[target]
227
+ if direction == 'out':
228
+ # Outgoing neighbors (columns)
229
+ for target_idx in range(self._vertex_count):
230
+ if self._matrix[vertex_idx][target_idx] is not None:
231
+ yield self._index_to_vertex[target_idx]
232
+ elif direction == 'in':
233
+ # Incoming neighbors (rows)
234
+ for source_idx in range(self._vertex_count):
235
+ if self._matrix[source_idx][vertex_idx] is not None:
236
+ yield self._index_to_vertex[source_idx]
237
+ elif direction == 'both':
238
+ # All neighbors
239
+ seen = set()
240
+ for neighbor in self.neighbors(vertex, 'out'):
241
+ if neighbor not in seen:
242
+ seen.add(neighbor)
243
+ yield neighbor
244
+ for neighbor in self.neighbors(vertex, 'in'):
245
+ if neighbor not in seen:
246
+ seen.add(neighbor)
247
+ yield neighbor
248
+
249
+ def degree(self, vertex: str, direction: str = 'out') -> int:
250
+ """Get degree of a vertex."""
251
+ if vertex not in self._vertex_to_index:
252
+ return 0
217
253
 
218
- if self._matrix[source_idx][target_idx] is None:
219
- raise ValueError(f"Edge {source} -> {target} not found")
254
+ vertex_idx = self._vertex_to_index[vertex]
220
255
 
221
- self._matrix[source_idx][target_idx]['weight'] = weight
222
-
223
- def find_shortest_path(self, start: Any, end: Any) -> List[Any]:
224
- """Find shortest path between nodes."""
225
- # TODO: Implement Dijkstra's algorithm
226
- return []
256
+ if direction == 'out':
257
+ # Count non-None entries in row
258
+ return sum(1 for target_idx in range(self._vertex_count)
259
+ if self._matrix[vertex_idx][target_idx] is not None)
260
+ elif direction == 'in':
261
+ # Count non-None entries in column
262
+ return sum(1 for source_idx in range(self._vertex_count)
263
+ if self._matrix[source_idx][vertex_idx] is not None)
264
+ elif direction == 'both':
265
+ out_degree = self.degree(vertex, 'out')
266
+ in_degree = self.degree(vertex, 'in')
267
+ # For undirected graphs, avoid double counting
268
+ return out_degree if not self.is_directed else out_degree + in_degree
269
+
270
+ def edges(self, data: bool = False) -> Iterator[tuple]:
271
+ """Get all edges in the graph."""
272
+ for source_idx in range(self._vertex_count):
273
+ for target_idx in range(self._vertex_count):
274
+ edge_data = self._matrix[source_idx][target_idx]
275
+ if edge_data is not None:
276
+ source = self._index_to_vertex[source_idx]
277
+ target = self._index_to_vertex[target_idx]
278
+
279
+ # For undirected graphs, avoid returning duplicate edges
280
+ if not self.is_directed and source > target:
281
+ continue
282
+
283
+ if data:
284
+ yield (source, target, edge_data)
285
+ else:
286
+ yield (source, target)
287
+
288
+ def vertices(self) -> Iterator[str]:
289
+ """Get all vertices in the graph."""
290
+ for vertex in self._vertex_to_index.keys():
291
+ yield vertex
227
292
 
228
- def find_all_paths(self, start: Any, end: Any) -> List[List[Any]]:
229
- """Find all paths between nodes."""
230
- # TODO: Implement DFS
231
- return []
293
+ def __len__(self) -> int:
294
+ """Get the number of edges."""
295
+ return self._edge_count
232
296
 
233
- def get_connected_components(self) -> List[List[Any]]:
234
- """Get all connected components."""
235
- # TODO: Implement connected components
236
- return []
297
+ def vertex_count(self) -> int:
298
+ """Get the number of vertices."""
299
+ return self._vertex_count
237
300
 
238
- def is_connected(self, start: Any, end: Any) -> bool:
239
- """Check if two nodes are connected."""
240
- # TODO: Implement connectivity check
241
- return False
301
+ def clear(self) -> None:
302
+ """Clear all edges and vertices."""
303
+ # Reset matrix
304
+ for row in self._matrix:
305
+ for i in range(len(row)):
306
+ row[i] = None
307
+
308
+ # Reset mappings
309
+ self._vertex_to_index.clear()
310
+ self._index_to_vertex.clear()
311
+ self._vertex_count = 0
312
+ self._edge_count = 0
313
+ self._edge_id_counter = 0
242
314
 
243
- def get_degree(self, node: Any) -> int:
244
- """Get degree of node."""
245
- return len(self.get_neighbors(node))
315
+ def add_vertex(self, vertex: str) -> None:
316
+ """Add a vertex to the graph."""
317
+ if vertex not in self._vertex_to_index:
318
+ self._get_vertex_index(vertex)
246
319
 
247
- def is_cyclic(self) -> bool:
248
- """Check if graph contains cycles."""
249
- # TODO: Implement cycle detection
250
- return False
320
+ def remove_vertex(self, vertex: str) -> bool:
321
+ """Remove a vertex and all its edges."""
322
+ if vertex not in self._vertex_to_index:
323
+ return False
324
+
325
+ vertex_idx = self._vertex_to_index[vertex]
326
+
327
+ # Count and remove all edges involving this vertex
328
+ edges_removed = 0
329
+
330
+ # Remove outgoing edges (row)
331
+ for target_idx in range(self._vertex_count):
332
+ if self._matrix[vertex_idx][target_idx] is not None:
333
+ self._matrix[vertex_idx][target_idx] = None
334
+ edges_removed += 1
335
+
336
+ # Remove incoming edges (column)
337
+ for source_idx in range(self._vertex_count):
338
+ if source_idx != vertex_idx and self._matrix[source_idx][vertex_idx] is not None:
339
+ self._matrix[source_idx][vertex_idx] = None
340
+ edges_removed += 1
341
+
342
+ self._edge_count -= edges_removed
343
+
344
+ # Note: We don't actually remove the vertex from the matrix to avoid
345
+ # reindexing all other vertices. Instead, we just mark it as removed.
346
+ del self._vertex_to_index[vertex]
347
+ del self._index_to_vertex[vertex_idx]
348
+
349
+ return True
251
350
 
252
351
  # ============================================================================
253
- # MATRIX SPECIFIC OPERATIONS
352
+ # MATRIX-SPECIFIC OPERATIONS
254
353
  # ============================================================================
255
354
 
256
355
  def get_matrix(self) -> List[List[Optional[float]]]:
257
- """Get the adjacency matrix as a 2D list."""
356
+ """Get the adjacency matrix as weights."""
258
357
  matrix = []
259
- for i in range(self._vertex_count):
358
+ for source_idx in range(self._vertex_count):
260
359
  row = []
261
- for j in range(self._vertex_count):
262
- edge_data = self._matrix[i][j]
263
- weight = edge_data.get('weight', self.default_weight) if edge_data else 0.0
360
+ for target_idx in range(self._vertex_count):
361
+ edge_data = self._matrix[source_idx][target_idx]
362
+ weight = edge_data['weight'] if edge_data else None
264
363
  row.append(weight)
265
364
  matrix.append(row)
266
365
  return matrix
267
366
 
268
367
  def get_binary_matrix(self) -> List[List[int]]:
269
- """Get the binary adjacency matrix."""
368
+ """Get the adjacency matrix as binary (0/1)."""
270
369
  matrix = []
271
- for i in range(self._vertex_count):
370
+ for source_idx in range(self._vertex_count):
272
371
  row = []
273
- for j in range(self._vertex_count):
274
- weight = 1 if self._matrix[i][j] is not None else 0
275
- row.append(weight)
372
+ for target_idx in range(self._vertex_count):
373
+ edge_exists = self._matrix[source_idx][target_idx] is not None
374
+ row.append(1 if edge_exists else 0)
276
375
  matrix.append(row)
277
376
  return matrix
278
377
 
279
378
  def set_matrix(self, matrix: List[List[Union[float, int, None]]], vertices: List[str]) -> None:
280
- """Set the adjacency matrix from a 2D list."""
379
+ """Set the entire matrix from a weight matrix."""
380
+ if len(matrix) != len(vertices) or any(len(row) != len(vertices) for row in matrix):
381
+ raise ValueError("Matrix dimensions must match vertex count")
382
+
281
383
  # Clear existing data
282
384
  self.clear()
283
385
 
284
- # Set vertices
386
+ # Add vertices
285
387
  for vertex in vertices:
286
- self._get_vertex_index(vertex)
388
+ self.add_vertex(vertex)
287
389
 
288
- # Set edges
289
- for i, row in enumerate(matrix):
290
- for j, weight in enumerate(row):
390
+ # Add edges based on matrix
391
+ for i, source in enumerate(vertices):
392
+ for j, target in enumerate(vertices):
393
+ weight = matrix[i][j]
291
394
  if weight is not None and weight != 0:
292
- source = self._index_to_vertex[i]
293
- target = self._index_to_vertex[j]
294
- self.add_edge(source, target, weight=float(weight))
395
+ self.add_edge(source, target, weight=weight)
295
396
 
296
397
  def matrix_multiply(self, other: 'xAdjMatrixStrategy') -> 'xAdjMatrixStrategy':
297
398
  """Multiply this matrix with another adjacency matrix."""
298
- # TODO: Implement matrix multiplication
299
- return self
399
+ if self._vertex_count != other._vertex_count:
400
+ raise ValueError("Matrices must have same dimensions")
401
+
402
+ result = xAdjMatrixStrategy(
403
+ traits=self._traits,
404
+ directed=self.is_directed,
405
+ initial_capacity=self._vertex_count
406
+ )
407
+
408
+ # Add vertices
409
+ for vertex in self.vertices():
410
+ result.add_vertex(vertex)
411
+
412
+ # Perform matrix multiplication
413
+ vertices = list(self.vertices())
414
+ for i, source in enumerate(vertices):
415
+ for j, target in enumerate(vertices):
416
+ sum_value = 0
417
+ for k in range(self._vertex_count):
418
+ intermediate = self._index_to_vertex[k]
419
+
420
+ weight1 = self.get_edge_weight(source, intermediate)
421
+ weight2 = other.get_edge_weight(intermediate, target)
422
+
423
+ if weight1 is not None and weight2 is not None:
424
+ sum_value += weight1 * weight2
425
+
426
+ if sum_value != 0:
427
+ result.add_edge(source, target, weight=sum_value)
428
+
429
+ return result
300
430
 
301
431
  def transpose(self) -> 'xAdjMatrixStrategy':
302
- """Get the transpose of the adjacency matrix."""
303
- # TODO: Implement matrix transpose
304
- return self
305
-
306
- # ============================================================================
307
- # ITERATION
308
- # ============================================================================
309
-
310
- def edges(self) -> Iterator[tuple[Any, Any, Dict[str, Any]]]:
311
- """Get all edges in the graph."""
312
- for i in range(self._vertex_count):
313
- for j in range(self._vertex_count):
314
- if self._matrix[i][j] is not None:
315
- source = self._index_to_vertex[i]
316
- target = self._index_to_vertex[j]
317
- yield (source, target, self._matrix[i][j])
318
-
319
- def vertices(self) -> Iterator[Any]:
320
- """Get all vertices in the graph."""
321
- return iter(self._vertex_to_index.keys())
322
-
323
- # ============================================================================
324
- # UTILITY METHODS
325
- # ============================================================================
326
-
327
- def clear(self) -> None:
328
- """Clear all edges and vertices."""
329
- self._matrix = [[None for _ in range(self._capacity)] for _ in range(self._capacity)]
330
- self._vertex_to_index.clear()
331
- self._index_to_vertex.clear()
332
- self._vertex_count = 0
333
- self._edge_count = 0
334
- self._edge_id_counter = 0
335
-
336
- def add_vertex(self, vertex: str) -> None:
337
- """Add a vertex to the graph."""
338
- self._get_vertex_index(vertex)
339
-
340
- def remove_vertex(self, vertex: str) -> bool:
341
- """Remove a vertex and all its incident edges."""
342
- if vertex not in self._vertex_to_index:
343
- return False
432
+ """Get the transpose of this matrix."""
433
+ result = xAdjMatrixStrategy(
434
+ traits=self._traits,
435
+ directed=True, # Transpose is always directed
436
+ initial_capacity=self._vertex_count
437
+ )
344
438
 
345
- vertex_idx = self._vertex_to_index[vertex]
439
+ # Add vertices
440
+ for vertex in self.vertices():
441
+ result.add_vertex(vertex)
346
442
 
347
- # Remove all edges incident to this vertex
348
- for i in range(self._capacity):
349
- if self._matrix[vertex_idx][i] is not None:
350
- self._matrix[vertex_idx][i] = None
351
- self._edge_count -= 1
352
- if self._matrix[i][vertex_idx] is not None:
353
- self._matrix[i][vertex_idx] = None
354
- self._edge_count -= 1
355
-
356
- # Remove vertex from mappings
357
- del self._vertex_to_index[vertex]
358
- del self._index_to_vertex[vertex_idx]
359
- self._vertex_count -= 1
443
+ # Add transposed edges
444
+ for source, target, edge_data in self.edges(data=True):
445
+ result.add_edge(target, source, **edge_data['properties'])
360
446
 
361
- return True
447
+ return result
362
448
 
363
449
  # ============================================================================
364
450
  # PERFORMANCE CHARACTERISTICS
@@ -369,12 +455,15 @@ class AdjMatrixStrategy(AGraphEdgeStrategy):
369
455
  """Get backend implementation info."""
370
456
  return {
371
457
  'strategy': 'ADJ_MATRIX',
372
- 'backend': '2D matrix',
458
+ 'backend': 'Python 2D list matrix',
459
+ 'directed': self.is_directed,
460
+ 'capacity': self._capacity,
461
+ 'utilized': self._vertex_count,
373
462
  'complexity': {
374
463
  'add_edge': 'O(1)',
375
464
  'remove_edge': 'O(1)',
376
465
  'has_edge': 'O(1)',
377
- 'get_neighbors': 'O(V)',
466
+ 'neighbors': 'O(V)',
378
467
  'space': 'O(V²)'
379
468
  }
380
469
  }
@@ -382,10 +471,15 @@ class AdjMatrixStrategy(AGraphEdgeStrategy):
382
471
  @property
383
472
  def metrics(self) -> Dict[str, Any]:
384
473
  """Get performance metrics."""
474
+ density = self._edge_count / max(1, self._vertex_count * (self._vertex_count - 1)) if self._vertex_count > 1 else 0
475
+ memory_utilization = self._vertex_count / max(1, self._capacity) * 100
476
+
385
477
  return {
386
478
  'vertices': self._vertex_count,
387
479
  'edges': self._edge_count,
388
- 'capacity': self._capacity,
389
- 'directed': self.is_directed,
390
- 'density': f"{(self._edge_count / max(1, self._vertex_count * (self._vertex_count - 1))) * 100:.1f}%"
480
+ 'matrix_capacity': self._capacity,
481
+ 'memory_utilization': f"{memory_utilization:.1f}%",
482
+ 'density': round(density, 4),
483
+ 'memory_usage': f"{self._capacity * self._capacity * 8} bytes (estimated)",
484
+ 'sparsity': f"{(1 - density) * 100:.1f}%"
391
485
  }