exonware-xwnode 0.0.1.22__py3-none-any.whl → 0.0.1.23__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- exonware/__init__.py +1 -1
- exonware/xwnode/__init__.py +18 -5
- exonware/xwnode/add_strategy_types.py +165 -0
- exonware/xwnode/common/__init__.py +1 -1
- exonware/xwnode/common/graph/__init__.py +30 -0
- exonware/xwnode/common/graph/caching.py +131 -0
- exonware/xwnode/common/graph/contracts.py +100 -0
- exonware/xwnode/common/graph/errors.py +44 -0
- exonware/xwnode/common/graph/indexing.py +260 -0
- exonware/xwnode/common/graph/manager.py +568 -0
- exonware/xwnode/common/management/__init__.py +3 -5
- exonware/xwnode/common/management/manager.py +2 -2
- exonware/xwnode/common/management/migration.py +3 -3
- exonware/xwnode/common/monitoring/__init__.py +3 -5
- exonware/xwnode/common/monitoring/metrics.py +6 -2
- exonware/xwnode/common/monitoring/pattern_detector.py +1 -1
- exonware/xwnode/common/monitoring/performance_monitor.py +5 -1
- exonware/xwnode/common/patterns/__init__.py +3 -5
- exonware/xwnode/common/patterns/flyweight.py +5 -1
- exonware/xwnode/common/patterns/registry.py +202 -183
- exonware/xwnode/common/utils/__init__.py +25 -11
- exonware/xwnode/common/utils/simple.py +1 -1
- exonware/xwnode/config.py +3 -8
- exonware/xwnode/contracts.py +4 -105
- exonware/xwnode/defs.py +413 -159
- exonware/xwnode/edges/strategies/__init__.py +86 -4
- exonware/xwnode/edges/strategies/_base_edge.py +2 -2
- exonware/xwnode/edges/strategies/adj_list.py +287 -121
- exonware/xwnode/edges/strategies/adj_matrix.py +316 -222
- exonware/xwnode/edges/strategies/base.py +1 -1
- exonware/xwnode/edges/strategies/{edge_bidir_wrapper.py → bidir_wrapper.py} +45 -4
- exonware/xwnode/edges/strategies/bitemporal.py +520 -0
- exonware/xwnode/edges/strategies/{edge_block_adj_matrix.py → block_adj_matrix.py} +77 -6
- exonware/xwnode/edges/strategies/bv_graph.py +664 -0
- exonware/xwnode/edges/strategies/compressed_graph.py +217 -0
- exonware/xwnode/edges/strategies/{edge_coo.py → coo.py} +46 -4
- exonware/xwnode/edges/strategies/{edge_csc.py → csc.py} +45 -4
- exonware/xwnode/edges/strategies/{edge_csr.py → csr.py} +94 -12
- exonware/xwnode/edges/strategies/{edge_dynamic_adj_list.py → dynamic_adj_list.py} +46 -4
- exonware/xwnode/edges/strategies/edge_list.py +168 -0
- exonware/xwnode/edges/strategies/edge_property_store.py +2 -2
- exonware/xwnode/edges/strategies/euler_tour.py +560 -0
- exonware/xwnode/edges/strategies/{edge_flow_network.py → flow_network.py} +2 -2
- exonware/xwnode/edges/strategies/graphblas.py +449 -0
- exonware/xwnode/edges/strategies/hnsw.py +637 -0
- exonware/xwnode/edges/strategies/hop2_labels.py +467 -0
- exonware/xwnode/edges/strategies/{edge_hyperedge_set.py → hyperedge_set.py} +2 -2
- exonware/xwnode/edges/strategies/incidence_matrix.py +250 -0
- exonware/xwnode/edges/strategies/k2_tree.py +613 -0
- exonware/xwnode/edges/strategies/link_cut.py +626 -0
- exonware/xwnode/edges/strategies/multiplex.py +532 -0
- exonware/xwnode/edges/strategies/{edge_neural_graph.py → neural_graph.py} +2 -2
- exonware/xwnode/edges/strategies/{edge_octree.py → octree.py} +69 -11
- exonware/xwnode/edges/strategies/{edge_quadtree.py → quadtree.py} +66 -10
- exonware/xwnode/edges/strategies/roaring_adj.py +438 -0
- exonware/xwnode/edges/strategies/{edge_rtree.py → rtree.py} +43 -5
- exonware/xwnode/edges/strategies/{edge_temporal_edgeset.py → temporal_edgeset.py} +24 -5
- exonware/xwnode/edges/strategies/{edge_tree_graph_basic.py → tree_graph_basic.py} +78 -7
- exonware/xwnode/edges/strategies/{edge_weighted_graph.py → weighted_graph.py} +188 -10
- exonware/xwnode/errors.py +3 -6
- exonware/xwnode/facade.py +20 -20
- exonware/xwnode/nodes/strategies/__init__.py +29 -9
- exonware/xwnode/nodes/strategies/adjacency_list.py +650 -177
- exonware/xwnode/nodes/strategies/aho_corasick.py +358 -183
- exonware/xwnode/nodes/strategies/array_list.py +36 -3
- exonware/xwnode/nodes/strategies/art.py +581 -0
- exonware/xwnode/nodes/strategies/{node_avl_tree.py → avl_tree.py} +77 -6
- exonware/xwnode/nodes/strategies/{node_b_plus_tree.py → b_plus_tree.py} +81 -40
- exonware/xwnode/nodes/strategies/{node_btree.py → b_tree.py} +79 -9
- exonware/xwnode/nodes/strategies/base.py +469 -98
- exonware/xwnode/nodes/strategies/{node_bitmap.py → bitmap.py} +12 -12
- exonware/xwnode/nodes/strategies/{node_bitset_dynamic.py → bitset_dynamic.py} +11 -11
- exonware/xwnode/nodes/strategies/{node_bloom_filter.py → bloom_filter.py} +15 -2
- exonware/xwnode/nodes/strategies/bloomier_filter.py +519 -0
- exonware/xwnode/nodes/strategies/bw_tree.py +531 -0
- exonware/xwnode/nodes/strategies/contracts.py +1 -1
- exonware/xwnode/nodes/strategies/{node_count_min_sketch.py → count_min_sketch.py} +3 -2
- exonware/xwnode/nodes/strategies/{node_cow_tree.py → cow_tree.py} +135 -13
- exonware/xwnode/nodes/strategies/crdt_map.py +629 -0
- exonware/xwnode/nodes/strategies/{node_cuckoo_hash.py → cuckoo_hash.py} +2 -2
- exonware/xwnode/nodes/strategies/{node_xdata_optimized.py → data_interchange_optimized.py} +21 -4
- exonware/xwnode/nodes/strategies/dawg.py +876 -0
- exonware/xwnode/nodes/strategies/deque.py +321 -153
- exonware/xwnode/nodes/strategies/extendible_hash.py +93 -0
- exonware/xwnode/nodes/strategies/{node_fenwick_tree.py → fenwick_tree.py} +111 -19
- exonware/xwnode/nodes/strategies/hamt.py +403 -0
- exonware/xwnode/nodes/strategies/hash_map.py +354 -67
- exonware/xwnode/nodes/strategies/heap.py +105 -5
- exonware/xwnode/nodes/strategies/hopscotch_hash.py +525 -0
- exonware/xwnode/nodes/strategies/{node_hyperloglog.py → hyperloglog.py} +6 -5
- exonware/xwnode/nodes/strategies/interval_tree.py +742 -0
- exonware/xwnode/nodes/strategies/kd_tree.py +703 -0
- exonware/xwnode/nodes/strategies/learned_index.py +533 -0
- exonware/xwnode/nodes/strategies/linear_hash.py +93 -0
- exonware/xwnode/nodes/strategies/linked_list.py +316 -119
- exonware/xwnode/nodes/strategies/{node_lsm_tree.py → lsm_tree.py} +219 -15
- exonware/xwnode/nodes/strategies/masstree.py +130 -0
- exonware/xwnode/nodes/strategies/{node_persistent_tree.py → persistent_tree.py} +149 -9
- exonware/xwnode/nodes/strategies/priority_queue.py +544 -132
- exonware/xwnode/nodes/strategies/queue.py +249 -120
- exonware/xwnode/nodes/strategies/{node_red_black_tree.py → red_black_tree.py} +183 -72
- exonware/xwnode/nodes/strategies/{node_roaring_bitmap.py → roaring_bitmap.py} +19 -6
- exonware/xwnode/nodes/strategies/rope.py +717 -0
- exonware/xwnode/nodes/strategies/{node_segment_tree.py → segment_tree.py} +106 -106
- exonware/xwnode/nodes/strategies/{node_set_hash.py → set_hash.py} +30 -29
- exonware/xwnode/nodes/strategies/{node_skip_list.py → skip_list.py} +74 -6
- exonware/xwnode/nodes/strategies/sparse_matrix.py +427 -131
- exonware/xwnode/nodes/strategies/{node_splay_tree.py → splay_tree.py} +55 -6
- exonware/xwnode/nodes/strategies/stack.py +244 -112
- exonware/xwnode/nodes/strategies/{node_suffix_array.py → suffix_array.py} +5 -1
- exonware/xwnode/nodes/strategies/t_tree.py +94 -0
- exonware/xwnode/nodes/strategies/{node_treap.py → treap.py} +75 -6
- exonware/xwnode/nodes/strategies/{node_tree_graph_hybrid.py → tree_graph_hybrid.py} +46 -5
- exonware/xwnode/nodes/strategies/trie.py +153 -9
- exonware/xwnode/nodes/strategies/union_find.py +111 -5
- exonware/xwnode/nodes/strategies/veb_tree.py +856 -0
- exonware/xwnode/strategies/__init__.py +5 -51
- exonware/xwnode/version.py +3 -3
- {exonware_xwnode-0.0.1.22.dist-info → exonware_xwnode-0.0.1.23.dist-info}/METADATA +23 -3
- exonware_xwnode-0.0.1.23.dist-info/RECORD +130 -0
- exonware/xwnode/edges/strategies/edge_adj_list.py +0 -353
- exonware/xwnode/edges/strategies/edge_adj_matrix.py +0 -445
- exonware/xwnode/nodes/strategies/_base_node.py +0 -307
- exonware/xwnode/nodes/strategies/node_aho_corasick.py +0 -525
- exonware/xwnode/nodes/strategies/node_array_list.py +0 -179
- exonware/xwnode/nodes/strategies/node_hash_map.py +0 -273
- exonware/xwnode/nodes/strategies/node_heap.py +0 -196
- exonware/xwnode/nodes/strategies/node_linked_list.py +0 -413
- exonware/xwnode/nodes/strategies/node_trie.py +0 -257
- exonware/xwnode/nodes/strategies/node_union_find.py +0 -192
- exonware/xwnode/queries/executors/__init__.py +0 -47
- exonware/xwnode/queries/executors/advanced/__init__.py +0 -37
- exonware/xwnode/queries/executors/advanced/aggregate_executor.py +0 -50
- exonware/xwnode/queries/executors/advanced/ask_executor.py +0 -50
- exonware/xwnode/queries/executors/advanced/construct_executor.py +0 -50
- exonware/xwnode/queries/executors/advanced/describe_executor.py +0 -50
- exonware/xwnode/queries/executors/advanced/for_loop_executor.py +0 -50
- exonware/xwnode/queries/executors/advanced/foreach_executor.py +0 -50
- exonware/xwnode/queries/executors/advanced/join_executor.py +0 -50
- exonware/xwnode/queries/executors/advanced/let_executor.py +0 -50
- exonware/xwnode/queries/executors/advanced/mutation_executor.py +0 -50
- exonware/xwnode/queries/executors/advanced/options_executor.py +0 -50
- exonware/xwnode/queries/executors/advanced/pipe_executor.py +0 -50
- exonware/xwnode/queries/executors/advanced/subscribe_executor.py +0 -50
- exonware/xwnode/queries/executors/advanced/subscription_executor.py +0 -50
- exonware/xwnode/queries/executors/advanced/union_executor.py +0 -50
- exonware/xwnode/queries/executors/advanced/window_executor.py +0 -51
- exonware/xwnode/queries/executors/advanced/with_cte_executor.py +0 -50
- exonware/xwnode/queries/executors/aggregation/__init__.py +0 -21
- exonware/xwnode/queries/executors/aggregation/avg_executor.py +0 -50
- exonware/xwnode/queries/executors/aggregation/count_executor.py +0 -38
- exonware/xwnode/queries/executors/aggregation/distinct_executor.py +0 -50
- exonware/xwnode/queries/executors/aggregation/group_executor.py +0 -50
- exonware/xwnode/queries/executors/aggregation/having_executor.py +0 -50
- exonware/xwnode/queries/executors/aggregation/max_executor.py +0 -50
- exonware/xwnode/queries/executors/aggregation/min_executor.py +0 -50
- exonware/xwnode/queries/executors/aggregation/sum_executor.py +0 -50
- exonware/xwnode/queries/executors/aggregation/summarize_executor.py +0 -50
- exonware/xwnode/queries/executors/array/__init__.py +0 -9
- exonware/xwnode/queries/executors/array/indexing_executor.py +0 -51
- exonware/xwnode/queries/executors/array/slicing_executor.py +0 -51
- exonware/xwnode/queries/executors/base.py +0 -257
- exonware/xwnode/queries/executors/capability_checker.py +0 -204
- exonware/xwnode/queries/executors/contracts.py +0 -166
- exonware/xwnode/queries/executors/core/__init__.py +0 -17
- exonware/xwnode/queries/executors/core/create_executor.py +0 -96
- exonware/xwnode/queries/executors/core/delete_executor.py +0 -99
- exonware/xwnode/queries/executors/core/drop_executor.py +0 -100
- exonware/xwnode/queries/executors/core/insert_executor.py +0 -39
- exonware/xwnode/queries/executors/core/select_executor.py +0 -152
- exonware/xwnode/queries/executors/core/update_executor.py +0 -102
- exonware/xwnode/queries/executors/data/__init__.py +0 -13
- exonware/xwnode/queries/executors/data/alter_executor.py +0 -50
- exonware/xwnode/queries/executors/data/load_executor.py +0 -50
- exonware/xwnode/queries/executors/data/merge_executor.py +0 -50
- exonware/xwnode/queries/executors/data/store_executor.py +0 -50
- exonware/xwnode/queries/executors/defs.py +0 -93
- exonware/xwnode/queries/executors/engine.py +0 -221
- exonware/xwnode/queries/executors/errors.py +0 -68
- exonware/xwnode/queries/executors/filtering/__init__.py +0 -25
- exonware/xwnode/queries/executors/filtering/between_executor.py +0 -80
- exonware/xwnode/queries/executors/filtering/filter_executor.py +0 -79
- exonware/xwnode/queries/executors/filtering/has_executor.py +0 -70
- exonware/xwnode/queries/executors/filtering/in_executor.py +0 -70
- exonware/xwnode/queries/executors/filtering/like_executor.py +0 -76
- exonware/xwnode/queries/executors/filtering/optional_executor.py +0 -76
- exonware/xwnode/queries/executors/filtering/range_executor.py +0 -80
- exonware/xwnode/queries/executors/filtering/term_executor.py +0 -77
- exonware/xwnode/queries/executors/filtering/values_executor.py +0 -71
- exonware/xwnode/queries/executors/filtering/where_executor.py +0 -44
- exonware/xwnode/queries/executors/graph/__init__.py +0 -15
- exonware/xwnode/queries/executors/graph/in_traverse_executor.py +0 -51
- exonware/xwnode/queries/executors/graph/match_executor.py +0 -51
- exonware/xwnode/queries/executors/graph/out_executor.py +0 -51
- exonware/xwnode/queries/executors/graph/path_executor.py +0 -51
- exonware/xwnode/queries/executors/graph/return_executor.py +0 -51
- exonware/xwnode/queries/executors/ordering/__init__.py +0 -9
- exonware/xwnode/queries/executors/ordering/by_executor.py +0 -50
- exonware/xwnode/queries/executors/ordering/order_executor.py +0 -51
- exonware/xwnode/queries/executors/projection/__init__.py +0 -9
- exonware/xwnode/queries/executors/projection/extend_executor.py +0 -50
- exonware/xwnode/queries/executors/projection/project_executor.py +0 -50
- exonware/xwnode/queries/executors/registry.py +0 -173
- exonware/xwnode/queries/parsers/__init__.py +0 -26
- exonware/xwnode/queries/parsers/base.py +0 -86
- exonware/xwnode/queries/parsers/contracts.py +0 -46
- exonware/xwnode/queries/parsers/errors.py +0 -53
- exonware/xwnode/queries/parsers/sql_param_extractor.py +0 -318
- exonware/xwnode/queries/strategies/__init__.py +0 -24
- exonware/xwnode/queries/strategies/base.py +0 -236
- exonware/xwnode/queries/strategies/cql.py +0 -201
- exonware/xwnode/queries/strategies/cypher.py +0 -181
- exonware/xwnode/queries/strategies/datalog.py +0 -70
- exonware/xwnode/queries/strategies/elastic_dsl.py +0 -70
- exonware/xwnode/queries/strategies/eql.py +0 -70
- exonware/xwnode/queries/strategies/flux.py +0 -70
- exonware/xwnode/queries/strategies/gql.py +0 -70
- exonware/xwnode/queries/strategies/graphql.py +0 -240
- exonware/xwnode/queries/strategies/gremlin.py +0 -181
- exonware/xwnode/queries/strategies/hiveql.py +0 -214
- exonware/xwnode/queries/strategies/hql.py +0 -70
- exonware/xwnode/queries/strategies/jmespath.py +0 -219
- exonware/xwnode/queries/strategies/jq.py +0 -66
- exonware/xwnode/queries/strategies/json_query.py +0 -66
- exonware/xwnode/queries/strategies/jsoniq.py +0 -248
- exonware/xwnode/queries/strategies/kql.py +0 -70
- exonware/xwnode/queries/strategies/linq.py +0 -238
- exonware/xwnode/queries/strategies/logql.py +0 -70
- exonware/xwnode/queries/strategies/mql.py +0 -68
- exonware/xwnode/queries/strategies/n1ql.py +0 -210
- exonware/xwnode/queries/strategies/partiql.py +0 -70
- exonware/xwnode/queries/strategies/pig.py +0 -215
- exonware/xwnode/queries/strategies/promql.py +0 -70
- exonware/xwnode/queries/strategies/sparql.py +0 -220
- exonware/xwnode/queries/strategies/sql.py +0 -275
- exonware/xwnode/queries/strategies/xml_query.py +0 -66
- exonware/xwnode/queries/strategies/xpath.py +0 -223
- exonware/xwnode/queries/strategies/xquery.py +0 -258
- exonware/xwnode/queries/strategies/xwnode_executor.py +0 -332
- exonware/xwnode/queries/strategies/xwquery.py +0 -456
- exonware_xwnode-0.0.1.22.dist-info/RECORD +0 -214
- /exonware/xwnode/nodes/strategies/{node_ordered_map.py → ordered_map.py} +0 -0
- /exonware/xwnode/nodes/strategies/{node_ordered_map_balanced.py → ordered_map_balanced.py} +0 -0
- /exonware/xwnode/nodes/strategies/{node_patricia.py → patricia.py} +0 -0
- /exonware/xwnode/nodes/strategies/{node_radix_trie.py → radix_trie.py} +0 -0
- /exonware/xwnode/nodes/strategies/{node_set_tree.py → set_tree.py} +0 -0
- {exonware_xwnode-0.0.1.22.dist-info → exonware_xwnode-0.0.1.23.dist-info}/WHEEL +0 -0
- {exonware_xwnode-0.0.1.22.dist-info → exonware_xwnode-0.0.1.23.dist-info}/licenses/LICENSE +0 -0
@@ -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
|
-
}
|