exonware-xwnode 0.0.1.22__py3-none-any.whl → 0.0.1.24__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.24.dist-info/METADATA +900 -0
- exonware_xwnode-0.0.1.24.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/METADATA +0 -168
- 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.24.dist-info}/WHEEL +0 -0
- {exonware_xwnode-0.0.1.22.dist-info → exonware_xwnode-0.0.1.24.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,250 @@
|
|
1
|
+
"""
|
2
|
+
#exonware/xwnode/src/exonware/xwnode/edges/strategies/edge_incidence_matrix.py
|
3
|
+
|
4
|
+
Incidence Matrix Edge Strategy Implementation
|
5
|
+
|
6
|
+
This module implements the INCIDENCE_MATRIX strategy for edge-centric
|
7
|
+
graph representation and queries.
|
8
|
+
|
9
|
+
Company: eXonware.com
|
10
|
+
Author: Eng. Muhammad AlShehri
|
11
|
+
Email: connect@exonware.com
|
12
|
+
Version: 0.0.1.24
|
13
|
+
Generation Date: 11-Oct-2025
|
14
|
+
"""
|
15
|
+
|
16
|
+
from typing import Any, Iterator, Dict, List, Set, Optional
|
17
|
+
from collections import defaultdict
|
18
|
+
from ._base_edge import AEdgeStrategy
|
19
|
+
from ...defs import EdgeMode, EdgeTrait
|
20
|
+
|
21
|
+
|
22
|
+
class IncidenceMatrixStrategy(AEdgeStrategy):
|
23
|
+
"""
|
24
|
+
Incidence Matrix - Edge-centric graph representation.
|
25
|
+
|
26
|
+
WHY this strategy:
|
27
|
+
- Edge-centric view essential for network flow, matching, edge coloring
|
28
|
+
- O(1) edge property access (better than adjacency for edge operations)
|
29
|
+
- Natural for multi-graphs (parallel edges get separate columns)
|
30
|
+
- Standard in graph theory textbooks and research
|
31
|
+
|
32
|
+
WHY this implementation:
|
33
|
+
- Dict[node][edge] = {1, -1, 0} for directed graphs
|
34
|
+
- Separate edge storage for O(1) property access
|
35
|
+
- 1/-1 encoding enables incidence-based algorithms
|
36
|
+
- Multi-edge support through unique edge IDs
|
37
|
+
|
38
|
+
Time Complexity:
|
39
|
+
- Add Edge: O(1) - insert into dict
|
40
|
+
- Remove Edge: O(1) - delete from dict
|
41
|
+
- Edge Properties: O(1) - direct edge dict access
|
42
|
+
- Node Neighbors: O(E) - scan all edges
|
43
|
+
- Edge Iteration: O(E) - iterate edge dict
|
44
|
+
|
45
|
+
Space Complexity: O(V * E) worst case (sparse dict in practice)
|
46
|
+
|
47
|
+
Trade-offs:
|
48
|
+
- Advantage: Fast edge operations, natural multi-graph support
|
49
|
+
- Limitation: Slow neighbor queries vs adjacency list
|
50
|
+
- Compared to ADJ_LIST: Use when edges are primary focus
|
51
|
+
|
52
|
+
Best for:
|
53
|
+
- Network flow algorithms (Ford-Fulkerson, max flow)
|
54
|
+
- Graph matching problems (bipartite matching)
|
55
|
+
- Edge coloring and scheduling
|
56
|
+
- Multi-graphs (parallel edges, duplicate connections)
|
57
|
+
- Educational/research graph theory
|
58
|
+
|
59
|
+
Not recommended for:
|
60
|
+
- Neighbor-heavy algorithms - use ADJ_LIST
|
61
|
+
- Large dense graphs - memory inefficient
|
62
|
+
- Simple connectivity queries
|
63
|
+
|
64
|
+
Following eXonware Priorities:
|
65
|
+
1. Security: Edge ID validation prevents injection
|
66
|
+
2. Usability: Clear edge-centric API for edge-focused problems
|
67
|
+
3. Maintainability: Standard incidence matrix, well-documented
|
68
|
+
4. Performance: O(1) edge access optimal for edge operations
|
69
|
+
5. Extensibility: Easy to add edge-based algorithms
|
70
|
+
"""
|
71
|
+
|
72
|
+
def __init__(self, traits: EdgeTrait = EdgeTrait.NONE, **options):
|
73
|
+
"""Initialize Incidence Matrix strategy."""
|
74
|
+
super().__init__(EdgeMode.INCIDENCE_MATRIX, traits, **options)
|
75
|
+
|
76
|
+
self.is_directed = options.get('directed', True)
|
77
|
+
|
78
|
+
# Incidence matrix: node -> edge_id -> value {1, -1, 0}
|
79
|
+
self._matrix: Dict[str, Dict[str, int]] = defaultdict(dict)
|
80
|
+
|
81
|
+
# Edge storage: edge_id -> {source, target, properties}
|
82
|
+
self._edges: Dict[str, Dict[str, Any]] = {}
|
83
|
+
|
84
|
+
# Node set
|
85
|
+
self._nodes: Set[str] = set()
|
86
|
+
|
87
|
+
# Edge counter
|
88
|
+
self._edge_id_counter = 0
|
89
|
+
|
90
|
+
def get_supported_traits(self) -> EdgeTrait:
|
91
|
+
"""Get supported traits."""
|
92
|
+
return EdgeTrait.SPARSE | EdgeTrait.DIRECTED | EdgeTrait.WEIGHTED | EdgeTrait.MULTI
|
93
|
+
|
94
|
+
# ============================================================================
|
95
|
+
# CORE EDGE OPERATIONS
|
96
|
+
# ============================================================================
|
97
|
+
|
98
|
+
def add_edge(self, source: str, target: str, **properties) -> str:
|
99
|
+
"""Add edge between source and target."""
|
100
|
+
# Generate edge ID
|
101
|
+
edge_id = f"edge_{self._edge_id_counter}"
|
102
|
+
self._edge_id_counter += 1
|
103
|
+
|
104
|
+
# Store edge data
|
105
|
+
self._edges[edge_id] = {
|
106
|
+
'id': edge_id,
|
107
|
+
'source': source,
|
108
|
+
'target': target,
|
109
|
+
'properties': properties.copy()
|
110
|
+
}
|
111
|
+
|
112
|
+
# Update incidence matrix
|
113
|
+
if self.is_directed:
|
114
|
+
self._matrix[source][edge_id] = 1 # Edge originates from source
|
115
|
+
self._matrix[target][edge_id] = -1 # Edge terminates at target
|
116
|
+
else:
|
117
|
+
self._matrix[source][edge_id] = 1
|
118
|
+
self._matrix[target][edge_id] = 1
|
119
|
+
|
120
|
+
# Add nodes to node set
|
121
|
+
self._nodes.add(source)
|
122
|
+
self._nodes.add(target)
|
123
|
+
|
124
|
+
self._edge_count += 1
|
125
|
+
return edge_id
|
126
|
+
|
127
|
+
def remove_edge(self, source: str, target: str, edge_id: Optional[str] = None) -> bool:
|
128
|
+
"""Remove edge between source and target."""
|
129
|
+
if edge_id:
|
130
|
+
# Remove specific edge by ID
|
131
|
+
if edge_id in self._edges:
|
132
|
+
edge_data = self._edges[edge_id]
|
133
|
+
src = edge_data['source']
|
134
|
+
tgt = edge_data['target']
|
135
|
+
|
136
|
+
# Remove from matrix
|
137
|
+
if src in self._matrix and edge_id in self._matrix[src]:
|
138
|
+
del self._matrix[src][edge_id]
|
139
|
+
if tgt in self._matrix and edge_id in self._matrix[tgt]:
|
140
|
+
del self._matrix[tgt][edge_id]
|
141
|
+
|
142
|
+
# Remove edge data
|
143
|
+
del self._edges[edge_id]
|
144
|
+
self._edge_count -= 1
|
145
|
+
return True
|
146
|
+
return False
|
147
|
+
else:
|
148
|
+
# Find and remove all edges between source and target
|
149
|
+
edges_to_remove = []
|
150
|
+
for eid, edge_data in self._edges.items():
|
151
|
+
if edge_data['source'] == source and edge_data['target'] == target:
|
152
|
+
edges_to_remove.append(eid)
|
153
|
+
|
154
|
+
for eid in edges_to_remove:
|
155
|
+
self.remove_edge(source, target, edge_id=eid)
|
156
|
+
|
157
|
+
return len(edges_to_remove) > 0
|
158
|
+
|
159
|
+
def has_edge(self, source: str, target: str) -> bool:
|
160
|
+
"""Check if edge exists between source and target."""
|
161
|
+
for edge_id, edge_data in self._edges.items():
|
162
|
+
if edge_data['source'] == source and edge_data['target'] == target:
|
163
|
+
return True
|
164
|
+
return False
|
165
|
+
|
166
|
+
def neighbors(self, node: str) -> Iterator[Any]:
|
167
|
+
"""Get neighbors of node (required by base class)."""
|
168
|
+
return iter(self.get_neighbors(node, "outgoing"))
|
169
|
+
|
170
|
+
def get_neighbors(self, node: str, direction: str = "outgoing") -> List[str]:
|
171
|
+
"""Get neighbors of node."""
|
172
|
+
neighbors = set()
|
173
|
+
|
174
|
+
if node not in self._matrix:
|
175
|
+
return []
|
176
|
+
|
177
|
+
# Check all edges incident to this node
|
178
|
+
for edge_id, value in self._matrix[node].items():
|
179
|
+
edge_data = self._edges[edge_id]
|
180
|
+
|
181
|
+
if direction == "outgoing" and value == 1:
|
182
|
+
neighbors.add(edge_data['target'])
|
183
|
+
elif direction == "incoming" and value == -1:
|
184
|
+
neighbors.add(edge_data['source'])
|
185
|
+
elif direction == "both":
|
186
|
+
if value == 1:
|
187
|
+
neighbors.add(edge_data['target'])
|
188
|
+
elif value == -1:
|
189
|
+
neighbors.add(edge_data['source'])
|
190
|
+
|
191
|
+
return list(neighbors)
|
192
|
+
|
193
|
+
def degree(self, node: str) -> int:
|
194
|
+
"""Get degree of node."""
|
195
|
+
if node not in self._matrix:
|
196
|
+
return 0
|
197
|
+
return len(self._matrix[node])
|
198
|
+
|
199
|
+
def edges(self) -> Iterator[tuple[Any, Any, Dict[str, Any]]]:
|
200
|
+
"""Iterator over all edges."""
|
201
|
+
for edge_data in self._edges.values():
|
202
|
+
yield (edge_data['source'], edge_data['target'], edge_data['properties'])
|
203
|
+
|
204
|
+
def vertices(self) -> Iterator[Any]:
|
205
|
+
"""Iterator over all vertices."""
|
206
|
+
return iter(self._nodes)
|
207
|
+
|
208
|
+
def __len__(self) -> int:
|
209
|
+
"""Get number of edges."""
|
210
|
+
return self._edge_count
|
211
|
+
|
212
|
+
# ============================================================================
|
213
|
+
# EDGE-CENTRIC OPERATIONS
|
214
|
+
# ============================================================================
|
215
|
+
|
216
|
+
def get_edge_by_id(self, edge_id: str) -> Optional[Dict[str, Any]]:
|
217
|
+
"""Get edge data by edge ID (O(1) operation)."""
|
218
|
+
return self._edges.get(edge_id)
|
219
|
+
|
220
|
+
def get_incident_edges(self, node: str) -> List[Dict[str, Any]]:
|
221
|
+
"""Get all edges incident to node."""
|
222
|
+
if node not in self._matrix:
|
223
|
+
return []
|
224
|
+
|
225
|
+
edge_ids = self._matrix[node].keys()
|
226
|
+
return [self._edges[eid] for eid in edge_ids]
|
227
|
+
|
228
|
+
def get_all_edge_ids(self) -> List[str]:
|
229
|
+
"""Get list of all edge IDs."""
|
230
|
+
return list(self._edges.keys())
|
231
|
+
|
232
|
+
def to_native(self) -> Dict[str, Any]:
|
233
|
+
"""Convert to native representation."""
|
234
|
+
return {
|
235
|
+
'edges': list(self._edges.values()),
|
236
|
+
'nodes': list(self._nodes),
|
237
|
+
'directed': self.is_directed
|
238
|
+
}
|
239
|
+
|
240
|
+
def get_backend_info(self) -> Dict[str, Any]:
|
241
|
+
"""Get backend information."""
|
242
|
+
return {
|
243
|
+
'strategy': 'Incidence Matrix',
|
244
|
+
'description': 'Edge-centric graph representation',
|
245
|
+
'total_edges': self._edge_count,
|
246
|
+
'total_nodes': len(self._nodes),
|
247
|
+
'directed': self.is_directed,
|
248
|
+
'matrix_size': sum(len(edges) for edges in self._matrix.values())
|
249
|
+
}
|
250
|
+
|